diff --git a/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs b/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs index e4dc52745b78..b5f4cc8ad9a3 100755 --- a/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs +++ b/Engine/Binaries/ThirdParty/Mono/Linux/bin/mcs @@ -1,4 +1,4 @@ #!/bin/sh -export PATH=$PATH:$UE_MONO_DIR/bin -export PKG_CONFIG_PATH=$UE_MONO_DIR/lib/pkgconfig:$PKG_CONFIG_PATH -exec $UE_MONO_DIR/bin/mono $MONO_OPTIONS $UE_MONO_DIR/lib/mono/4.5/mcs.exe "$@" +export PATH="$PATH:$UE_MONO_DIR/bin" +export PKG_CONFIG_PATH="$UE_MONO_DIR/lib/pkgconfig:$PKG_CONFIG_PATH" +exec "$UE_MONO_DIR/bin/mono" $MONO_OPTIONS "$UE_MONO_DIR/lib/mono/4.5/mcs.exe" "$@" diff --git a/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh b/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh index 6ffb5887bcf9..c9c76d48ce62 100755 --- a/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh +++ b/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh @@ -20,7 +20,7 @@ if [ ! -d "$BASE_PATH/../../../Source" ]; then exit 1 fi -source "$BASE_PATH/SetupMono.sh" $BASE_PATH +source "$BASE_PATH/SetupMono.sh" "$BASE_PATH" # make sure the UBT project has references to auto-discovered platform extension source files "${BASE_PATH}/../FindPlatformExtensionSources.sh" diff --git a/Engine/Build/BatchFiles/Linux/GitDependencies.sh b/Engine/Build/BatchFiles/Linux/GitDependencies.sh index f7ef7053ef5e..2316f948a868 100755 --- a/Engine/Build/BatchFiles/Linux/GitDependencies.sh +++ b/Engine/Build/BatchFiles/Linux/GitDependencies.sh @@ -19,7 +19,7 @@ BASE_PATH="`dirname "$SCRIPT_PATH"`" cd ../../../.. RESULT=0 -source "$BASE_PATH/SetupMono.sh" $BASE_PATH +source "$BASE_PATH/SetupMono.sh" "$BASE_PATH" while : ; do mono Engine/Binaries/DotNET/GitDependencies.exe $ARGS diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml index ad251547f027..bf4736204242 100644 --- a/Engine/Build/Commit.gitdeps.xml +++ b/Engine/Build/Commit.gitdeps.xml @@ -7802,6 +7802,7 @@ + @@ -28974,6 +28975,7 @@ + @@ -30319,7 +30321,7 @@ - + @@ -68512,6 +68514,7 @@ + @@ -71592,6 +71595,7 @@ + diff --git a/Engine/Build/InstalledEngineBuild.xml b/Engine/Build/InstalledEngineBuild.xml index e9b95fefb63c..8dd9d769612a 100644 --- a/Engine/Build/InstalledEngineBuild.xml +++ b/Engine/Build/InstalledEngineBuild.xml @@ -189,7 +189,7 @@ - + diff --git a/Engine/Config/BaseEditorPerProjectUserSettings.ini b/Engine/Config/BaseEditorPerProjectUserSettings.ini index 590231f879c1..7109711df79d 100644 --- a/Engine/Config/BaseEditorPerProjectUserSettings.ini +++ b/Engine/Config/BaseEditorPerProjectUserSettings.ini @@ -613,46 +613,90 @@ BackupIntervalInMinutes=5 [/Script/UnrealEd.FbxImportUI] -bImportMaterials=True -bAutoCreateGroups=True -bInvertNormalMaps=False -bImportTextures=True bOverrideFullName=True +bCreatePhysicsAsset=True +bAutoComputeLodDistances=True +LodDistance0=0.0 +LodDistance1=0.0 +LodDistance2=0.0 +LodDistance3=0.0 +LodDistance4=0.0 +LodDistance5=0.0 +LodDistance6=0.0 +LodDistance7=0.0 +MinimumLodNumber=0 +LodNumber=0 +bImportAnimations=False +bImportMaterials=True +bImportTextures=True + +[/Script/UnrealEd.FbxAssetImportData] +ImportTranslation=(X=0.0,Y=0.0,Z=0.0) +ImportRotation=(Pitch=0.0,Yaw=0.0,Roll=0.0) +ImportUniformScale=1.0 bConvertScene=True bForceFrontXAxis=False bConvertSceneUnit=False -bRemoveNamespaces=True -bImportAnimations=False -bResampleAnimations=False -bImportRigidMesh=False -bCombineMeshes=True -bCreatePhysicsAsset=True -bImportMesh=True -MinimumLodNumber=0 -LodNumber=0 -bAutoComputeLodDistances=True - +[/Script/UnrealEd.FbxMeshImportData] +bTransformVertexToAbsolute=True +bBakePivotInVertex=False +bReorderMaterialToFbxOrder=True +bImportMeshLODs=False +NormalImportMethod=FBXNIM_ComputeNormals +NormalGenerationMethod=EFBXNormalGenerationMethod::MikkTSpace [/Script/UnrealEd.FbxStaticMeshImportData] -bOneConvexHullPerUCX=True -bImportMeshLODs=False -NormalImportMethod=FBXNIM_ImportNormals +StaticMeshLODGroup="" VertexColorImportOption=EVertexColorImportOption::Ignore VertexOverrideColor=(R=255,G=255,B=255,A=255) - +bRemoveDegenerates=True +bBuildAdjacencyBuffer=True +bBuildReversedIndexBuffer=True +bGenerateLightmapUVs=True +bOneConvexHullPerUCX=True +bAutoGenerateCollision=True +bCombineMeshes=False +; Override of the base class default value +NormalImportMethod=FBXNIM_ImportNormals [/Script/UnrealEd.FbxSkeletalMeshImportData] +VertexColorImportOption=EVertexColorImportOption::Replace +VertexOverrideColor=(R=0,G=0,B=0,A=0) +bUpdateSkeletonReferencePose=False +bUseT0AsRefPose=False bPreserveSmoothingGroups=True -bImportMeshLODs=False +bImportMeshesInBoneHierarchy=True bImportMorphTargets=False ThresholdPosition=0.00002 ThresholdTangentNormal=0.00002 ThresholdUV=0.0009765625 +[/Script/UnrealEd.FbxAnimSequenceImportData] +bImportMeshesInBoneHierarchy=True +AnimationLength=FBXALIT_ExportedTime +FrameImportRange=(Min=0, Max=0) +bUseDefaultSampleRate=False +CustomSampleRate=0 +bImportCustomAttribute=True +bDeleteExistingCustomAttributeCurves=False +bImportBoneTracks=True +bSetMaterialDriveParameterOnCustomAttribute=False +bRemoveRedundantKeys=True +bDeleteExistingMorphTargetCurves=False +bDoNotImportCurveWithZero=True +bPreserveLocalTransform=False [/Script/UnrealEd.FbxTextureImportData] - +bInvertNormalMaps=False +MaterialSearchLocation=EMaterialSearchLocation::Local +BaseMaterialName="" +BaseColorName="" +BaseDiffuseTextureName="" +BaseNormalTextureName="" +BaseEmissiveColorName="" +BaseEmmisiveTextureName="" +BaseSpecularTextureName="" [SoundSettings] ChirpSoundClasses=Dialog DialogMedium DialogLoud DialogDeafening diff --git a/Engine/Config/BaseEditorSettings.ini b/Engine/Config/BaseEditorSettings.ini index c6f2c99615dd..ff56d1d1a4be 100644 --- a/Engine/Config/BaseEditorSettings.ini +++ b/Engine/Config/BaseEditorSettings.ini @@ -53,7 +53,7 @@ IncludeCollectionNames=True +Categories=(Identifier="Animation",Title=NSLOCTEXT("TutorialCategories","AnimationTitle","Animation"),Description=NSLOCTEXT("TutorialCategories","AnimationDescription","Tutorials covering the animation system in Unreal Engine 4."),Icon=,Texture=/Engine/Tutorial/SubEditors/TutorialAssets/icon_ShowSkeletalMeshes_40x.icon_ShowSkeletalMeshes_40x,SortOrder=600) +Categories=(Identifier="Landscape",Title=NSLOCTEXT("TutorialCategories","LandscapeTitle","Landscape"),Description=NSLOCTEXT("TutorialCategories","LandscapeDescription","Tutorials covering the Unreal Editor 4 terrain editor: Landscape."),Icon="LevelEditor.LandscapeMode",Texture=/Engine/Tutorial/Landscape/TutorialAssets/Landscape.Landscape,SortOrder=700) +Categories=(Identifier="Foliage",Title=NSLOCTEXT("TutorialCategories","FoliageTitle","Foliage"),Description=NSLOCTEXT("TutorialCategories","FoliageDescription","Tutorials covering the Unreal Engine 4 Foliage tool."),Icon="LevelEditor.FoliageMode",Texture=/Engine/Tutorial/Foliage/TutorialAssets/Foliage.Foliage,SortOrder=800) -+Categories=(Identifier="Mobile",Title=NSLOCTEXT("TutorialCategories","MobileTitle","Mobile"),Description=NSLOCTEXT("TutorialCategories","MobileDescription","Mobile Tutorials."),Icon="MaterialEditor.ToggleMobileStats",Texture=None,SortOrder=900) ++Categories=(Identifier="Mobile",Title=NSLOCTEXT("TutorialCategories","MobileTitle","Mobile"),Description=NSLOCTEXT("TutorialCategories","MobileDescription","Mobile Tutorials."),Icon="MaterialEditor.TogglePlatformStats",Texture=None,SortOrder=900) +StartupTutorial=/Engine/Tutorial/Basics/LevelEditorAttract.LevelEditorAttract_C +TutorialContexts=(Context="StaticMeshEditor",BrowserFilter=,AttractTutorial=None,LaunchTutorial=/Engine/Tutorial/SubEditors/StaticMeshEditorTutorial.StaticMeshEditorTutorial_C) +TutorialContexts=(Context="LevelEditor",BrowserFilter=,AttractTutorial=None,LaunchTutorial=/Engine/Tutorial/Basics/LevelEditorOverview.LevelEditorOverview_C) diff --git a/Engine/Config/BaseEngine.ini b/Engine/Config/BaseEngine.ini index a4c7bf425c20..a76c68b7387c 100644 --- a/Engine/Config/BaseEngine.ini +++ b/Engine/Config/BaseEngine.ini @@ -656,6 +656,7 @@ bUseStreamingPause=false +FunctionRedirects=(OldName="EditorUtilityWidget.OnDefaultActionClicked",NewName="EditorUtilityWidget.Run") +FunctionRedirects=(OldName="SkeletalMeshComponent.GetSubInstanceByName",NewName="KismetSystemLibrary.GetSubInstanceByTag") +StructRedirects=(OldName="/Script/AnimGraphRuntime.AnimNode_Root",NewName="/Script/Engine.AnimNode_Root") ++FunctionRedirects=(OldName="Widget.SetRenderAngle", NewName="Widget.SetRenderTransformAngle") +ClassRedirects=(OldName="/Script/CoreUObject.MulticastDelegateProperty",NewName="/Script/CoreUObject.MulticastInlineDelegateProperty") +ClassRedirects=(OldName="EditorAutomationActor",NewName="EditorUtilityActor") diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h index 291ae12e55f7..b3a603e39887 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2D/Classes/PaperTileMapComponent.h @@ -22,13 +22,13 @@ struct FSpriteRenderSection; * This component is created when you drag a tile map asset from the content browser into a Blueprint, or * contained inside of the actor created when you drag one into the level. * - * NOTE: This is an early access preview class. While not considered production-ready, it is a step beyond + * NOTE: This is an beta preview class. While not considered production-ready, it is a step beyond * 'experimental' and is being provided as a preview of things to come: * - We will try to provide forward-compatibility for content you create. * - The classes may change significantly in the future. * - The code is in an early state and may not meet the desired polish / quality bar. * - There is probably no documentation or example content yet. - * - They will be promoted out of 'Early Access' when they are production ready. + * - They will be promoted out of 'beta' when they are production ready. * * @see UPrimitiveComponent, UPaperTileMap */ diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp index 186405099b27..6cf6c39c5c76 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/SPaperEditorViewport.cpp @@ -179,7 +179,7 @@ FReply SPaperEditorViewport::OnMouseButtonDown(const FGeometry& MyGeometry, cons ReplyState.CaptureMouse( SharedThis(this) ); ReplyState.UseHighPrecisionMouseMovement( SharedThis(this) ); - SoftwareCursorPosition = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) ); + SoftwareCursorPosition = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale ); // clear any interpolation when you manually pan //DeferredMovementTargetObject = nullptr; @@ -189,7 +189,7 @@ FReply SPaperEditorViewport::OnMouseButtonDown(const FGeometry& MyGeometry, cons else if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { // START MARQUEE SELECTION. - const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) ); + const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale ); Marquee.Start( GraphMousePos, FMarqueeOperation::OperationTypeFromMouseEvent(MouseEvent) ); // Trigger a selection update now so that single-clicks without a drag still select something @@ -218,7 +218,7 @@ FReply SPaperEditorViewport::OnMouseButtonUp(const FGeometry& MyGeometry, const if (HasMouseCapture()) { FSlateRect ThisPanelScreenSpaceRect = MyGeometry.GetLayoutBoundingRect(); - const FVector2D ScreenSpaceCursorPos = MyGeometry.LocalToAbsolute( GraphCoordToPanelCoord( SoftwareCursorPosition ) ); + const FVector2D ScreenSpaceCursorPos = MyGeometry.LocalToAbsolute( GraphCoordToPanelCoord( SoftwareCursorPosition ) / MyGeometry.Scale ); FIntPoint BestPositionInViewport( FMath::RoundToInt( FMath::Clamp( ScreenSpaceCursorPos.X, ThisPanelScreenSpaceRect.Left, ThisPanelScreenSpaceRect.Right ) ), @@ -335,7 +335,7 @@ FReply SPaperEditorViewport::OnMouseMove(const FGeometry& MyGeometry, const FPoi { // We are marquee selecting - const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) ); + const FVector2D GraphMousePos = PanelCoordToGraphCoord( MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale ); Marquee.Rect.UpdateEndPoint(GraphMousePos); return FReply::Handled(); @@ -349,7 +349,7 @@ FReply SPaperEditorViewport::OnMouseMove(const FGeometry& MyGeometry, const FPoi FReply SPaperEditorViewport::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { // We want to zoom into this point; i.e. keep it the same fraction offset into the panel - const FVector2D WidgetSpaceCursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ); + const FVector2D WidgetSpaceCursorPos = MyGeometry.AbsoluteToLocal( MouseEvent.GetScreenSpacePosition() ) * MyGeometry.Scale; FVector2D PointToMaintainGraphSpace = PanelCoordToGraphCoord( WidgetSpaceCursorPos ); @@ -515,7 +515,7 @@ void SPaperEditorViewport::PaintSoftwareCursor(const FGeometry& AllottedGeometry FSlateDrawElement::MakeBox( OutDrawElements, DrawLayerId, - AllottedGeometry.ToPaintGeometry( GraphCoordToPanelCoord(SoftwareCursorPosition) - ( Brush->ImageSize / 2 ), Brush->ImageSize ), + AllottedGeometry.ToPaintGeometry( GraphCoordToPanelCoord(SoftwareCursorPosition) / AllottedGeometry.Scale - ( Brush->ImageSize / 2 ), Brush->ImageSize ), Brush); } } diff --git a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp index 0d917634ec03..3e374b4a5ba7 100644 --- a/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp +++ b/Engine/Plugins/2D/Paper2D/Source/Paper2DEditor/Private/TileMapEditing/TileMapEditor.cpp @@ -233,7 +233,7 @@ TSharedRef FTileMapEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args SNew(STextBlock) .Visibility(EVisibility::HitTestInvisible) .TextStyle(FEditorStyle::Get(), "Graph.CornerText") - .Text(LOCTEXT("TileMapEditorViewportEarlyAccessPreviewWarning", "Early access preview")) + .Text(LOCTEXT("TileMapEditorViewportEarlyAccessPreviewWarning", "Beta preview")) ] ]; } diff --git a/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp b/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp index 7d403d0c408b..230481520682 100644 --- a/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp +++ b/Engine/Plugins/Compositing/Composure/Source/ComposureLayersEditor/Private/ComposureDetailCustomizations.cpp @@ -25,7 +25,6 @@ #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Misc/Optional.h" -#include "Widgets/Input/SVectorInputBox.h" #include "Widgets/SWidget.h" #include "Widgets/Colors/SColorPicker.h" #include "Widgets/Input/SComboButton.h" diff --git a/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp b/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp index 53f3cd996a7e..8825dcd97125 100644 --- a/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertFrontend/Source/ConcertFrontend/Private/Widgets/SConcertScrollBox.cpp @@ -16,7 +16,7 @@ void SConcertScrollBox::Construct(const FArguments& InArgs) bPreventLock = false; ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); ChildSlot [ diff --git a/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp b/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp index c4a8f5e58cdd..ee3ff981f1d0 100644 --- a/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp +++ b/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp @@ -1285,7 +1285,7 @@ bool FPerforceUpdateStatusWorker::Execute(FPerforceSourceControlCommand& InComma if(Operation->ShouldGetOpenedOnly()) { - const FString ContentFolder = FPaths::ConvertRelativePathToFull(FPaths::RootDir()); + const FString ContentFolder = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); const FString FileQuery = FString::Printf(TEXT("%s..."), *ContentFolder); TArray Parameters = InCommand.Files; Parameters.Add(FileQuery); diff --git a/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp b/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp index 88c75ca31fa6..b64b49394814 100644 --- a/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp +++ b/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/SubversionSourceControlOperations.cpp @@ -619,7 +619,7 @@ bool FSubversionUpdateStatusWorker::Execute(FSubversionSourceControlCommand& InC Parameters.Add(TEXT("--verbose")); TArray Files; - Files.Add(FPaths::RootDir()); + Files.Add(FPaths::ProjectDir()); InCommand.bCommandSuccessful &= SubversionSourceControlUtils::RunCommand(TEXT("status"), Files, Parameters, ResultsXml, InCommand.ErrorMessages, InCommand.UserName); SubversionSourceControlUtils::ParseStatusResults(ResultsXml, InCommand.ErrorMessages, InCommand.UserName, InCommand.WorkingCopyRoot, OutStates); diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp index 75c69ef4fba8..32a1bac5bf51 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.cpp @@ -22,6 +22,7 @@ void FAssetManagerEditorCommands::RegisterCommands() UI_COMMAND(ViewAssetAudit, "Audit Assets...", "Opens the Asset Audit UI and displays information about the selected assets", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::A)); UI_COMMAND(OpenSelectedInAssetEditor, "Edit...", "Opens the selected asset in the relevent editor.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(ZoomToFit, "Zoom to Fit", "Zoom in and center the view on the selected item", EUserInterfaceActionType::Button, FInputChord(EKeys::Home)); UI_COMMAND(ReCenterGraph, "Re-Center Graph", "Re-centers the graph on this node, showing all referencers and references for this asset instead", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(CopyReferencedObjects, "Copy Referenced Objects List", "Copies the list of objects that the selected asset references to the clipboard.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(CopyReferencingObjects, "Copy Referencing Objects List", "Copies the list of objects that reference the selected asset to the clipboard.", EUserInterfaceActionType::Button, FInputChord()); @@ -29,6 +30,7 @@ void FAssetManagerEditorCommands::RegisterCommands() UI_COMMAND(ShowReferencingObjects, "Show Referencing Objects List", "Shows a list of objects that reference the selected asset.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(ShowReferenceTree, "Show Reference Tree", "Shows a reference tree for the selected asset.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(AuditReferencedObjects, "Audit References", "Opens the Asset Audit UI and displays information about all assets referenced by selected asset", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(Find, "Find", "Find objects in the reference viewer.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::F)); UI_COMMAND(MakeLocalCollectionWithReferencers, "Local", "Local. This collection is only visible to you and is not in source control.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(MakePrivateCollectionWithReferencers, "Private", "Private. This collection is only visible to you.", EUserInterfaceActionType::Button, FInputChord()); diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h index 45b9311cee3d..1f7f7b1de929 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/AssetManagerEditorCommands.h @@ -49,6 +49,12 @@ public: // Adds all referenced objects to asset audit window TSharedPtr AuditReferencedObjects; + /** Zoom in to fit the selected objects in the window */ + TSharedPtr ZoomToFit; + + /** Start finding objects */ + TSharedPtr Find; + // Creates a new collection with the list of assets that this asset references, user selects which ECollectionShareType to use. TSharedPtr MakeLocalCollectionWithReferencers; TSharedPtr MakePrivateCollectionWithReferencers; diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp index f6e8af3b3453..f7b9d42b9121 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp @@ -62,6 +62,7 @@ void UReferenceViewerSchema::GetContextMenuActions(const UEdGraph* CurrentGraph, MenuBuilder->BeginSection(TEXT("Misc"), NSLOCTEXT("ReferenceViewerSchema", "MiscSectionLabel", "Misc")); { + MenuBuilder->AddMenuEntry(FAssetManagerEditorCommands::Get().ZoomToFit); MenuBuilder->AddMenuEntry(FAssetManagerEditorCommands::Get().ReCenterGraph); MenuBuilder->AddSubMenu( NSLOCTEXT("ReferenceViewerSchema", "MakeCollectionWithTitle", "Make Collection with"), diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp index f1298dd107a0..b645d892f248 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp @@ -15,6 +15,7 @@ #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SSearchBox.h" #include "Widgets/Input/SSpinBox.h" #include "EditorStyleSet.h" #include "Engine/Selection.h" @@ -32,6 +33,7 @@ #include "Widgets/Input/SComboBox.h" #include "HAL/PlatformApplicationMisc.h" #include "AssetManagerEditorModule.h" +#include "Framework/Application/SlateApplication.h" #include "ObjectTools.h" @@ -179,6 +181,18 @@ void SReferenceViewer::Construct(const FArguments& InArgs) .BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") ) [ SNew(SVerticalBox) + +SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Center) + .Padding(2.f) + .AutoHeight() + [ + SAssignNew(SearchBox, SSearchBox) + .HintText(LOCTEXT("Search", "Search...")) + .ToolTipText(LOCTEXT("SearchTooltip", "Type here to search (pressing Enter zooms to the results)")) + .OnTextChanged(this, &SReferenceViewer::HandleOnSearchTextChanged) + .OnTextCommitted(this, &SReferenceViewer::HandleOnSearchTextCommitted) + ] +SVerticalBox::Slot() .AutoHeight() @@ -898,6 +912,15 @@ void SReferenceViewer::RegisterActions() ReferenceViewerActions = MakeShareable(new FUICommandList); FAssetManagerEditorCommands::Register(); + ReferenceViewerActions->MapAction( + FAssetManagerEditorCommands::Get().ZoomToFit, + FExecuteAction::CreateSP(this, &SReferenceViewer::ZoomToFit), + FCanExecuteAction::CreateSP(this, &SReferenceViewer::CanZoomToFit)); + + ReferenceViewerActions->MapAction( + FAssetManagerEditorCommands::Get().Find, + FExecuteAction::CreateSP(this, &SReferenceViewer::OnFind)); + ReferenceViewerActions->MapAction( FGlobalEditorCommonCommands::Get().FindInContentBrowser, FExecuteAction::CreateSP(this, &SReferenceViewer::ShowSelectionInContentBrowser), @@ -1427,4 +1450,91 @@ void SReferenceViewer::OnInitialAssetRegistrySearchComplete() } } +void SReferenceViewer::ZoomToFit() +{ + if (GraphEditorPtr.IsValid()) + { + GraphEditorPtr->ZoomToFit(true); + } +} + +bool SReferenceViewer::CanZoomToFit() const +{ + if (GraphEditorPtr.IsValid()) + { + return true; + } + + return false; +} + +void SReferenceViewer::OnFind() +{ + FSlateApplication::Get().SetKeyboardFocus(SearchBox, EFocusCause::SetDirectly); +} + +void SReferenceViewer::HandleOnSearchTextChanged(const FText& SearchText) +{ + if (GraphObj == nullptr || !GraphEditorPtr.IsValid()) + { + return; + } + + GraphEditorPtr->ClearSelectionSet(); + + if (SearchText.IsEmpty()) + { + return; + } + + FString SearchString = SearchText.ToString(); + TArray SearchWords; + SearchString.ParseIntoArrayWS( SearchWords ); + + TArray AllNodes; + GraphObj->GetNodesOfClass( AllNodes ); + + TArray NodePackageNames; + for (UEdGraphNode_Reference* Node : AllNodes) + { + NodePackageNames.Empty(); + Node->GetAllPackageNames(NodePackageNames); + + for (const FName& PackageName : NodePackageNames) + { + // package name must match all words + bool bMatch = true; + for (const FString& Word : SearchWords) + { + if (!PackageName.ToString().Contains(Word)) + { + bMatch = false; + break; + } + } + + if (bMatch) + { + GraphEditorPtr->SetNodeSelection(Node, true); + break; + } + } + } +} + +void SReferenceViewer::HandleOnSearchTextCommitted(const FText& SearchText, ETextCommit::Type CommitType) +{ + if (!GraphEditorPtr.IsValid()) + { + return; + } + + if (CommitType == ETextCommit::OnCleared) + { + GraphEditorPtr->ClearSelectionSet(); + } + + GraphEditorPtr->ZoomToFit(true); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h index a909a1991f60..93e651dbb98e 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.h @@ -6,6 +6,7 @@ #include "Input/Reply.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" +#include "Widgets/Input/SSearchBox.h" #include "GraphEditor.h" #include "AssetData.h" #include "HistoryManager.h" @@ -130,6 +131,13 @@ private: void ShowReferenceTree(); void ViewSizeMap(); void ViewAssetAudit(); + void ZoomToFit(); + bool CanZoomToFit() const; + void OnFind(); + + /** Handlers for searching */ + void HandleOnSearchTextChanged(const FText& SearchText); + void HandleOnSearchTextCommitted(const FText& SearchText, ETextCommit::Type CommitType); void ReCenterGraphOnNodes(const TSet& Nodes); @@ -153,6 +161,7 @@ private: TSharedPtr GraphEditorPtr; TSharedPtr ReferenceViewerActions; + TSharedPtr SearchBox; UEdGraph_ReferenceViewer* GraphObj; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h index 6ceeb8023040..c5dd90e85914 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h @@ -335,7 +335,7 @@ protected: }; - class FSelectOrDeselectMeshElementsChange : public FChange + class FSelectOrDeselectMeshElementsChange : public FSwapChange { public: @@ -371,7 +371,7 @@ protected: }; - class FDeselectAllMeshElementsChange : public FChange + class FDeselectAllMeshElementsChange : public FSwapChange { public: @@ -414,7 +414,7 @@ protected: }; - class FSetElementSelectionModeChange : public FChange + class FSetElementSelectionModeChange : public FSwapChange { public: diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.cpp new file mode 100644 index 000000000000..ae57edefe002 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.cpp @@ -0,0 +1,224 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SStylusInputDebugWidget.h" + +#include "IStylusState.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SCheckBox.h" + +#define LOCTEXT_NAMESPACE "StylusInputDebugWidget" + +SStylusInputDebugWidget::SStylusInputDebugWidget() +{ +} + +SStylusInputDebugWidget::~SStylusInputDebugWidget() +{ + InputSubsystem->RemoveMessageHandler(*this); +} + +void SStylusInputDebugWidget::Construct(const FArguments& InArgs, UStylusInputSubsystem& InSubsystem) +{ + InputSubsystem = &InSubsystem; + InputSubsystem->AddMessageHandler(*this); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("MostRecentIndex", "Most Recent Index")) + ] + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetIndexText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Position", "Position")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetPositionText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("NormalPressure", "Normal Pressure")) + ] + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetPressureText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("TangentPressure", "Tangent Pressure")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetTangentPressureText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Z", "Z")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetZText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Twist", "Twist")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetTwistText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Tilt", "Tilt")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetTiltText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("Size", "Size")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(this, &SStylusInputDebugWidget::GetSizeText) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("IsTouching", "Is Touching?")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(SCheckBox) + .IsChecked(this, &SStylusInputDebugWidget::IsTouching) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(STextBlock) + .Text(LOCTEXT("IsInverted", "Is Inverted?")) + ] + +SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew(SCheckBox) + .IsChecked(this, &SStylusInputDebugWidget::IsInverted) + ] + ] + ]; +} + +FText SStylusInputDebugWidget::GetVector2Text(FVector2D Value) +{ + return FText::FromString(FString::Format(TEXT("{0}, {1}"), { Value.X, Value.Y })); +} + +FText SStylusInputDebugWidget::GetFloatText(float Value) +{ + return FText::FromString(FString::Format(TEXT("{0}"), { Value })); +} + +ECheckBoxState SStylusInputDebugWidget::IsTouching() const +{ + return State.IsStylusDown() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +ECheckBoxState SStylusInputDebugWidget::IsInverted() const +{ + return State.IsStylusInverted() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.h new file mode 100644 index 000000000000..5e7bf2728ee4 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/SStylusInputDebugWidget.h @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/SCompoundWidget.h" + +#include "IStylusInputModule.h" +#include "IStylusState.h" + +class SStylusInputDebugWidget : public SCompoundWidget, + public IStylusMessageHandler +{ +public: + SStylusInputDebugWidget(); + virtual ~SStylusInputDebugWidget(); + + SLATE_BEGIN_ARGS(SStylusInputDebugWidget) + {} + SLATE_END_ARGS(); + + void Construct(const FArguments& InArgs, UStylusInputSubsystem& InSubsystem); + void OnStylusStateChanged(const FStylusState& InState, int32 InIndex) + { + State = InState; + LastIndex = InIndex; + } + +private: + + UStylusInputSubsystem* InputSubsystem; + FStylusState State; + int32 LastIndex; + + ECheckBoxState IsTouching() const; + ECheckBoxState IsInverted() const; + + FText GetPositionText() const { return GetVector2Text(State.GetPosition()); } + FText GetTiltText() const { return GetVector2Text(State.GetTilt()); } + FText GetSizeText() const { return GetVector2Text(State.GetSize()); } + + FText GetIndexText() const { return FText::FromString(FString::FromInt(LastIndex)); } + + FText GetPressureText() const { return GetFloatText(State.GetPressure()); } + FText GetTangentPressureText() const { return GetFloatText(State.GetTangentPressure()); } + FText GetZText() const { return GetFloatText(State.GetZ()); } + FText GetTwistText() const { return GetFloatText(State.GetTwist()); } + + static FText GetVector2Text(FVector2D Value); + static FText GetFloatText(float Value); +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/StylusInputModule.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/StylusInputModule.cpp new file mode 100644 index 000000000000..fde9d3453a41 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/StylusInputModule.cpp @@ -0,0 +1,131 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "IStylusInputModule.h" +#include "CoreMinimal.h" + +#include "Framework/Docking/TabManager.h" +#include "Interfaces/IMainFrameModule.h" +#include "Logging/LogMacros.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/SWindow.h" +#include "WorkspaceMenuStructure.h" +#include "WorkspaceMenuStructureModule.h" +#include "SStylusInputDebugWidget.h" + +#define LOCTEXT_NAMESPACE "FStylusInputModule" + + +static const FName StylusInputDebugTabName = FName("StylusInputDebug"); + +class FStylusInputModule : public IModuleInterface +{ +}; + +IMPLEMENT_MODULE(FStylusInputModule, StylusInput) + +// This is the function that all platform-specific implementations are required to implement. +TSharedPtr CreateStylusInputInterface(); + +#if PLATFORM_WINDOWS +#include "WindowsStylusInputInterface.h" +#else +TSharedPtr CreateStylusInputInterface() { return TSharedPtr(); } +#endif + +// TODO: Other platforms + +void UStylusInputSubsystem::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + + UE_LOG(LogStylusInput, Log, TEXT("Initializing StylusInput subsystem.")); + + InputInterface = CreateStylusInputInterface(); + + if (!InputInterface.IsValid()) + { + UE_LOG(LogStylusInput, Log, TEXT("StylusInput not supported on this platform.")); + return; + } + + const TSharedRef& TabManager = FGlobalTabmanager::Get(); + const IWorkspaceMenuStructure& MenuStructure = WorkspaceMenu::GetMenuStructure(); + + TabManager->RegisterNomadTabSpawner(StylusInputDebugTabName, + FOnSpawnTab::CreateUObject(this, &UStylusInputSubsystem::OnSpawnPluginTab)) + .SetDisplayName(LOCTEXT("DebugTabTitle", "Stylus Input Debug")) + .SetTooltipText(LOCTEXT("DebugTabTooltip", "Debug panel to display current values of stylus inputs.")) + .SetGroup(MenuStructure.GetDeveloperToolsMiscCategory()); +} + +void UStylusInputSubsystem::Deinitialize() +{ + Super::Deinitialize(); + + FGlobalTabmanager::Get()->UnregisterTabSpawner(StylusInputDebugTabName); + + InputInterface.Reset(); + + UE_LOG(LogStylusInput, Log, TEXT("Shutting down StylusInput subsystem.")); +} + +int32 UStylusInputSubsystem::NumInputDevices() const +{ + if (InputInterface.IsValid()) + { + return InputInterface->NumInputDevices(); + } + return 0; +} + +const IStylusInputDevice* UStylusInputSubsystem::GetInputDevice(int32 Index) const +{ + if (InputInterface.IsValid()) + { + return InputInterface->GetInputDevice(Index); + } + return nullptr; +} + +void UStylusInputSubsystem::AddMessageHandler(IStylusMessageHandler& InHandler) +{ + MessageHandlers.AddUnique(&InHandler); +} + +void UStylusInputSubsystem::RemoveMessageHandler(IStylusMessageHandler& InHandler) +{ + MessageHandlers.Remove(&InHandler); +} + +void UStylusInputSubsystem::Tick(float DeltaTime) +{ + if (InputInterface.IsValid()) + { + for (int32 DeviceIdx = 0; DeviceIdx < NumInputDevices(); ++DeviceIdx) + { + IStylusInputDevice* InputDevice = InputInterface->GetInputDevice(DeviceIdx); + if (InputDevice->IsDirty()) + { + InputDevice->Tick(); + + for (IStylusMessageHandler* Handler : MessageHandlers) + { + Handler->OnStylusStateChanged(InputDevice->GetCurrentState(), DeviceIdx); + } + } + } + } +} + +TSharedRef UStylusInputSubsystem::OnSpawnPluginTab(const FSpawnTabArgs& Args) +{ + return SNew(SDockTab) + .TabRole(ETabRole::NomadTab) + [ + SNew(SStylusInputDebugWidget, *this) + ]; +} + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.cpp new file mode 100644 index 000000000000..b80520e0a5a5 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.cpp @@ -0,0 +1,440 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "WindowsRealTimeStylusPlugin.h" + +#if PLATFORM_WINDOWS + +HRESULT FWindowsRealTimeStylusPlugin::QueryInterface(const IID& InterfaceID, void** Pointer) +{ + if ((InterfaceID == __uuidof(IStylusSyncPlugin)) || (InterfaceID == IID_IUnknown)) + { + *Pointer = this; + AddRef(); + return S_OK; + } + else if ((InterfaceID == IID_IMarshal) && (FreeThreadedMarshaller != nullptr)) + { + return FreeThreadedMarshaller->QueryInterface(InterfaceID, Pointer); + } + + *Pointer = nullptr; + return E_NOINTERFACE; +} + +HRESULT FWindowsRealTimeStylusPlugin::StylusDown(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) +{ + FTabletContextInfo* TabletContext = FindTabletContext(StylusInfo->tcid); + if (TabletContext != nullptr) + { + TabletContext->WindowsState.IsTouching = true; + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::StylusUp(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) +{ + FTabletContextInfo* TabletContext = FindTabletContext(StylusInfo->tcid); + if (TabletContext != nullptr) + { + // we know this is not touching + TabletContext->WindowsState.IsTouching = false; + TabletContext->WindowsState.NormalPressure = 0; + } + return S_OK; +} + +static void SetupPacketDescriptions(IRealTimeStylus* RealTimeStylus, FTabletContextInfo& TabletContext) +{ + ULONG NumPacketProperties = 0; + PACKET_PROPERTY* PacketProperties = nullptr; + HRESULT hr = RealTimeStylus->GetPacketDescriptionData(TabletContext.ID, nullptr, nullptr, &NumPacketProperties, &PacketProperties); + if (SUCCEEDED(hr) && PacketProperties != nullptr) + { + for (ULONG PropIdx = 0; PropIdx < NumPacketProperties; ++PropIdx) + { + PACKET_PROPERTY CurrentProperty = PacketProperties[PropIdx]; + + EWindowsPacketType PacketType = EWindowsPacketType::None; + if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_X) + { + PacketType = EWindowsPacketType::X; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_Y) + { + PacketType = EWindowsPacketType::Y; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_Z) + { + PacketType = EWindowsPacketType::Z; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_PACKET_STATUS) + { + PacketType = EWindowsPacketType::Status; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE) + { + PacketType = EWindowsPacketType::NormalPressure; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_TANGENT_PRESSURE) + { + PacketType = EWindowsPacketType::TangentPressure; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_BUTTON_PRESSURE) + { + PacketType = EWindowsPacketType::ButtonPressure; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_ALTITUDE_ORIENTATION) + { + PacketType = EWindowsPacketType::Altitude; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_AZIMUTH_ORIENTATION) + { + PacketType = EWindowsPacketType::Azimuth; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_TWIST_ORIENTATION) + { + PacketType = EWindowsPacketType::Twist; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_X_TILT_ORIENTATION) + { + PacketType = EWindowsPacketType::XTilt; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_Y_TILT_ORIENTATION) + { + PacketType = EWindowsPacketType::YTilt; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_WIDTH) + { + PacketType = EWindowsPacketType::Width; + } + else if (CurrentProperty.guid == GUID_PACKETPROPERTY_GUID_HEIGHT) + { + PacketType = EWindowsPacketType::Height; + } + + int32 CreatedIdx = TabletContext.PacketDescriptions.Emplace(); + FPacketDescription& PacketDescription = TabletContext.PacketDescriptions[CreatedIdx]; + PacketDescription.Type = PacketType; + PacketDescription.Minimum = CurrentProperty.PropertyMetrics.nLogicalMin; + PacketDescription.Maximum = CurrentProperty.PropertyMetrics.nLogicalMax; + PacketDescription.Resolution = CurrentProperty.PropertyMetrics.fResolution; + } + + ::CoTaskMemFree(PacketProperties); + } +} + +static void SetupTabletSupportedPackets(TComPtr RealTimeStylus, FTabletContextInfo& TabletContext) +{ + IInkTablet* InkTablet; + RealTimeStylus->GetTabletFromTabletContextId(TabletContext.ID, &InkTablet); + + int16 Supported; + + BSTR GuidBSTR; + + GuidBSTR = SysAllocString(STR_GUID_X); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::X); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_Y); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Y); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_Z); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Z); + TabletContext.AddSupportedInput(EStylusInputType::Z); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_PAKETSTATUS); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Status); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_NORMALPRESSURE); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::NormalPressure); + TabletContext.AddSupportedInput(EStylusInputType::Pressure); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_TANGENTPRESSURE); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::TangentPressure); + TabletContext.AddSupportedInput(EStylusInputType::TangentPressure); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_BUTTONPRESSURE); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::ButtonPressure); + TabletContext.AddSupportedInput(EStylusInputType::ButtonPressure); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_AZIMUTHORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Azimuth); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_ALTITUDEORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Altitude); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_XTILTORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::XTilt); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_YTILTORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::YTilt); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_TWISTORIENTATION); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Twist); + TabletContext.AddSupportedInput(EStylusInputType::Twist); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_WIDTH); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Width); + } + + SysFreeString(GuidBSTR); + GuidBSTR = SysAllocString(STR_GUID_HEIGHT); + + InkTablet->IsPacketPropertySupported(GuidBSTR, &Supported); + if (Supported) + { + TabletContext.SupportedPackets.Add(EWindowsPacketType::Height); + } + + SysFreeString(GuidBSTR); + + if (TabletContext.SupportedPackets.Contains(EWindowsPacketType::X) && + TabletContext.SupportedPackets.Contains(EWindowsPacketType::Y)) + { + TabletContext.AddSupportedInput(EStylusInputType::Position); + } + + if (TabletContext.SupportedPackets.Contains(EWindowsPacketType::XTilt) && + TabletContext.SupportedPackets.Contains(EWindowsPacketType::YTilt)) + { + TabletContext.AddSupportedInput(EStylusInputType::Tilt); + } + + if (TabletContext.SupportedPackets.Contains(EWindowsPacketType::Width) && + TabletContext.SupportedPackets.Contains(EWindowsPacketType::Height)) + { + TabletContext.AddSupportedInput(EStylusInputType::Size); + } +} + +FTabletContextInfo* FWindowsRealTimeStylusPlugin::FindTabletContext(TABLET_CONTEXT_ID TabletID) +{ + for (FTabletContextInfo& TabletContext : TabletContexts) + { + if (TabletContext.ID == TabletID) + { + return &TabletContext; + } + } + return nullptr; +} + +void FWindowsRealTimeStylusPlugin::AddTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID) +{ + FTabletContextInfo* FoundContext = FindTabletContext(TabletID); + if (FoundContext == nullptr) + { + int32 Created = TabletContexts.Emplace(); + FoundContext = &TabletContexts[Created]; + FoundContext->ID = TabletID; + } + + SetupTabletSupportedPackets(RealTimeStylus, *FoundContext); + SetupPacketDescriptions(RealTimeStylus, *FoundContext); +} + +void FWindowsRealTimeStylusPlugin::RemoveTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID) +{ + for (int32 ExistingIdx = 0; ExistingIdx < TabletContexts.Num(); ++ExistingIdx) + { + if (TabletContexts[ExistingIdx].ID == TabletID) + { + TabletContexts.RemoveAt(ExistingIdx); + break; + } + } +} + +HRESULT FWindowsRealTimeStylusPlugin::RealTimeStylusEnabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) +{ + for (ULONG TabletIdx = 0; TabletIdx < Num; ++TabletIdx) + { + AddTabletContext(RealTimeStylus, InTabletContexts[TabletIdx]); + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::RealTimeStylusDisabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) +{ + for (ULONG TabletIdx = 0; TabletIdx < Num; ++TabletIdx) + { + RemoveTabletContext(RealTimeStylus, InTabletContexts[TabletIdx]); + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::TabletAdded(IRealTimeStylus* RealTimeStylus, IInkTablet* InkTablet) +{ + TABLET_CONTEXT_ID TabletID; + if (SUCCEEDED(RealTimeStylus->GetTabletContextIdFromTablet(InkTablet, &TabletID))) + { + AddTabletContext(RealTimeStylus, TabletID); + } + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::TabletRemoved(IRealTimeStylus* RealTimeStylus, LONG iTabletIndex) +{ + TabletContexts.RemoveAt(iTabletIndex); + return S_OK; +} + +static float Normalize(int Value, const FPacketDescription& Desc) +{ + return (float) (Value - Desc.Minimum) / (float) ((Desc.Maximum - Desc.Minimum) + 1); +} + +static float ToDegrees(int Value, const FPacketDescription& Desc) +{ + return Value / Desc.Resolution; +} + +void FWindowsRealTimeStylusPlugin::HandlePacket(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets) +{ + FTabletContextInfo* TabletContext = FindTabletContext(StylusInfo->tcid); + if (TabletContext == nullptr) + { + return; + } + + TabletContext->SetDirty(); + TabletContext->WindowsState.IsInverted = StylusInfo->bIsInvertedCursor; + + ULONG PropertyCount = PacketBufferLength / PacketCount; + + for (ULONG i = 0; i < PropertyCount; ++i) + { + const FPacketDescription& PacketDescription = TabletContext->PacketDescriptions[i]; + + float Normalized = Normalize(Packets[i], PacketDescription); + + switch (PacketDescription.Type) + { + case EWindowsPacketType::X: + TabletContext->WindowsState.Position.X = Packets[i]; + break; + case EWindowsPacketType::Y: + TabletContext->WindowsState.Position.Y = Packets[i]; + break; + case EWindowsPacketType::Status: + break; + case EWindowsPacketType::Z: + TabletContext->WindowsState.Z = Normalized; + break; + case EWindowsPacketType::NormalPressure: + TabletContext->WindowsState.NormalPressure = Normalized; + break; + case EWindowsPacketType::TangentPressure: + TabletContext->WindowsState.TangentPressure = Normalized; + break; + case EWindowsPacketType::Twist: + TabletContext->WindowsState.Twist = ToDegrees(Packets[i], PacketDescription); + break; + case EWindowsPacketType::XTilt: + TabletContext->WindowsState.Tilt.X = ToDegrees(Packets[i], PacketDescription); + break; + case EWindowsPacketType::YTilt: + TabletContext->WindowsState.Tilt.Y = ToDegrees(Packets[i], PacketDescription); + break; + case EWindowsPacketType::Width: + TabletContext->WindowsState.Size.X = Normalized; + break; + case EWindowsPacketType::Height: + TabletContext->WindowsState.Size.Y = Normalized; + break; + } + } +} + +HRESULT FWindowsRealTimeStylusPlugin::Packets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets, ULONG* InOutPackets, LONG** PtrInOutPackets) +{ + HandlePacket(RealTimeStylus, StylusInfo, PacketCount, PacketBufferLength, Packets); + return S_OK; +} + +HRESULT FWindowsRealTimeStylusPlugin::InAirPackets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets, ULONG* InOutPackets, LONG** PtrInOutPackets) +{ + HandlePacket(RealTimeStylus, StylusInfo, PacketCount, PacketBufferLength, Packets); + return S_OK; +} + +#endif // PLATFORM_WINDOWS \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.h new file mode 100644 index 000000000000..11a07e19a5ca --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsRealTimeStylusPlugin.h @@ -0,0 +1,176 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" + +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/PreWindowsApi.h" +#include "Windows/COMPointer.h" + #include + #include +#include "Windows/PostWindowsApi.h" +#include "Windows/HideWindowsPlatformTypes.h" + +#include "IStylusState.h" + +/** + * Packet types as derived from IRealTimeStylus::GetPacketDescriptionData. + */ +enum class EWindowsPacketType +{ + None, + X, + Y, + Z, + Status, + NormalPressure, + TangentPressure, + ButtonPressure, + Azimuth, + Altitude, + Twist, + XTilt, + YTilt, + Width, + Height, +}; + +/** + * Stylus state for a single frame. + */ +struct FWindowsStylusState +{ + FVector2D Position; + float Z; + FVector2D Tilt; + float Twist; + float NormalPressure; + float TangentPressure; + FVector2D Size; + bool IsTouching : 1; + bool IsInverted : 1; + + FWindowsStylusState() : + Position(0, 0), Z(0), Tilt(0, 0), Twist(0), NormalPressure(0), TangentPressure(0), + Size(0, 0), IsTouching(false), IsInverted(false) + { + } + + FStylusState ToPublicState() const + { + return FStylusState(Position, Z, Tilt, Twist, NormalPressure, TangentPressure, Size, IsTouching, IsInverted); + } +}; + +/** + * Description of a packet's information, as derived from IRealTimeStylus::GetPacketDescriptionData. + */ +struct FPacketDescription +{ + EWindowsPacketType Type { EWindowsPacketType::None }; + int32 Minimum { 0 }; + int32 Maximum { 0 }; + float Resolution { 0 }; +}; + +struct FTabletContextInfo : public IStylusInputDevice +{ + int32 Index; + + TABLET_CONTEXT_ID ID; + TArray PacketDescriptions; + TArray SupportedPackets; + + FWindowsStylusState WindowsState; + + void AddSupportedInput(EStylusInputType Type) { SupportedInputs.Add(Type); } + void SetDirty() { Dirty = true; } + + virtual void Tick() override + { + PreviousState = CurrentState; + CurrentState = WindowsState.ToPublicState(); + Dirty = false; + } +}; + +/** + * An implementation of an IStylusSyncPlugin for use with the RealTimeStylus API. + */ +class FWindowsRealTimeStylusPlugin : public IStylusSyncPlugin +{ +public: + FWindowsRealTimeStylusPlugin() = default; + virtual ~FWindowsRealTimeStylusPlugin() + { + if (FreeThreadedMarshaller != nullptr) + { + FreeThreadedMarshaller->Release(); + } + } + + virtual ULONG AddRef() override { return ++RefCount; } + virtual ULONG Release() override + { + int NewRefCount = --RefCount; + if (NewRefCount == 0) + delete this; + + return NewRefCount; + } + + virtual HRESULT QueryInterface(const IID& InterfaceID, void** Pointer) override; + + virtual HRESULT TabletAdded(IRealTimeStylus* RealTimeStylus, IInkTablet* InkTablet) override; + virtual HRESULT TabletRemoved(IRealTimeStylus* RealTimeStylus, LONG iTabletIndex) override; + + virtual HRESULT RealTimeStylusEnabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) override; + virtual HRESULT RealTimeStylusDisabled(IRealTimeStylus* RealTimeStylus, ULONG Num, const TABLET_CONTEXT_ID* InTabletContexts) override; + + virtual HRESULT StylusInRange(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletContext, STYLUS_ID StylusID) override { return S_OK; } + virtual HRESULT StylusOutOfRange(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletContext, STYLUS_ID StylusID) override { return S_OK; } + + virtual HRESULT StylusDown(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) override; + virtual HRESULT StylusUp(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketSize, LONG* Packet, LONG** InOutPackets) override; + + virtual HRESULT StylusButtonDown(IRealTimeStylus* RealTimeStylus, STYLUS_ID StylusID, const GUID* GUID, POINT* Position) override { return S_OK; } + virtual HRESULT StylusButtonUp(IRealTimeStylus* RealTimeStylus, STYLUS_ID StylusID, const GUID* GUID, POINT* Position) override { return S_OK; } + + virtual HRESULT InAirPackets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets, ULONG* NumOutPackets, LONG** PtrOutPackets) override; + virtual HRESULT Packets(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, + ULONG PacketCount, ULONG PacketBufferSize, LONG* Packets, ULONG* NumOutPackets, LONG** PtrOutPackets) override; + + virtual HRESULT CustomStylusDataAdded(IRealTimeStylus* RealTimeStylus, const GUID* GUID, ULONG Data, const BYTE* ByteData) override { return S_OK; } + + virtual HRESULT SystemEvent(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletContext, STYLUS_ID StylusID, SYSTEM_EVENT EventType, SYSTEM_EVENT_DATA EventData) override { return S_OK; } + virtual HRESULT Error(IRealTimeStylus* RealTimeStylus, IStylusPlugin* Plugin, RealTimeStylusDataInterest DataInterest, HRESULT ErrorCode, LONG_PTR* Key) override { return S_OK; } + + virtual HRESULT DataInterest(RealTimeStylusDataInterest* OutDataInterest) override + { + *OutDataInterest = RTSDI_AllData; + return S_OK; + } + + virtual HRESULT UpdateMapping(IRealTimeStylus* RealTimeStylus) override { return S_OK; } + + FTabletContextInfo* FindTabletContext(TABLET_CONTEXT_ID TabletID); + + IUnknown* FreeThreadedMarshaller; + TArray TabletContexts; + bool HasChanges { false }; + +private: + int RefCount { 1 }; + + void HandlePacket(IRealTimeStylus* RealTimeStylus, const StylusInfo* StylusInfo, ULONG PacketCount, ULONG PacketBufferLength, LONG* Packets); + + void AddTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID); + void RemoveTabletContext(IRealTimeStylus* RealTimeStylus, TABLET_CONTEXT_ID TabletID); +}; + +#endif // PLATFORM_WINDOWS \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.cpp b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.cpp new file mode 100644 index 000000000000..ad1fbc709871 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.cpp @@ -0,0 +1,164 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "WindowsStylusInputInterface.h" +#include "WindowsRealTimeStylusPlugin.h" +#include "Interfaces/IMainFrameModule.h" + +#if PLATFORM_WINDOWS + +class FWindowsStylusInputInterfaceImpl +{ +public: + ~FWindowsStylusInputInterfaceImpl(); + + TComPtr RealTimeStylus; + TSharedPtr StylusPlugin; + void* DLLHandle { nullptr }; +}; + +FWindowsStylusInputInterface::FWindowsStylusInputInterface(FWindowsStylusInputInterfaceImpl* InImpl) +{ + Impl = InImpl; +} + +FWindowsStylusInputInterface::~FWindowsStylusInputInterface() +{ + delete Impl; +} + +int32 FWindowsStylusInputInterface::NumInputDevices() const +{ + return Impl->StylusPlugin->TabletContexts.Num(); +} + +IStylusInputDevice* FWindowsStylusInputInterface::GetInputDevice(int32 Index) const +{ + if (Index < 0 || Index >= Impl->StylusPlugin->TabletContexts.Num()) + { + return nullptr; + } + + return &Impl->StylusPlugin->TabletContexts[Index]; +} + +FWindowsStylusInputInterfaceImpl::~FWindowsStylusInputInterfaceImpl() +{ + RealTimeStylus->RemoveAllStylusSyncPlugins(); + RealTimeStylus.Reset(); + + StylusPlugin.Reset(); + + if (DLLHandle != nullptr) + { + FPlatformProcess::FreeDllHandle(DLLHandle); + DLLHandle = nullptr; + } +} + +static void OnMainFrameCreated(FWindowsStylusInputInterfaceImpl& WindowsImpl, TSharedPtr Window) +{ + TSharedPtr NativeWindow = Window->GetNativeWindow(); + HWND Hwnd = reinterpret_cast(NativeWindow->GetOSWindowHandle()); + + WindowsImpl.RealTimeStylus->put_HWND(reinterpret_cast(Hwnd)); + + // We desire to receive everything, but what we actually will receive is determined in AddTabletContext + TArray DesiredPackets = { + GUID_PACKETPROPERTY_GUID_X, + GUID_PACKETPROPERTY_GUID_Y, + GUID_PACKETPROPERTY_GUID_Z, + GUID_PACKETPROPERTY_GUID_PACKET_STATUS, + GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE, + GUID_PACKETPROPERTY_GUID_TANGENT_PRESSURE, + GUID_PACKETPROPERTY_GUID_X_TILT_ORIENTATION, + GUID_PACKETPROPERTY_GUID_Y_TILT_ORIENTATION, + GUID_PACKETPROPERTY_GUID_TWIST_ORIENTATION, + GUID_PACKETPROPERTY_GUID_WIDTH, + GUID_PACKETPROPERTY_GUID_HEIGHT, + // Currently not needed. + //GUID_PACKETPROPERTY_GUID_BUTTON_PRESSURE, + //GUID_PACKETPROPERTY_GUID_AZIMUTH_ORIENTATION, + //GUID_PACKETPROPERTY_GUID_ALTITUDE_ORIENTATION, + }; + + WindowsImpl.RealTimeStylus->SetDesiredPacketDescription(DesiredPackets.Num(), DesiredPackets.GetData()); + + WindowsImpl.RealTimeStylus->put_Enabled(Windows::TRUE); +} + +TSharedPtr CreateStylusInputInterface() +{ + FWindowsStylusInputInterfaceImpl* WindowsImpl = new FWindowsStylusInputInterfaceImpl(); + TSharedPtr InterfaceImpl = MakeShareable(new FWindowsStylusInputInterface(WindowsImpl)); + + if (!FWindowsPlatformMisc::CoInitialize()) + { + UE_LOG(LogStylusInput, Error, TEXT("Could not initialize COM library!")); + return nullptr; + } + + // Load RealTimeStylus DLL + const FString InkDLLDirectory = TEXT("C:\\Program Files\\Common Files\\microsoft shared\\ink"); + const FString RTSComDLL = TEXT("RTSCom.dll"); + FPlatformProcess::PushDllDirectory(*InkDLLDirectory); + + WindowsImpl->DLLHandle = FPlatformProcess::GetDllHandle(*(InkDLLDirectory / RTSComDLL)); + if (WindowsImpl->DLLHandle == nullptr) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not load RTSCom.dll!")); + return nullptr; + } + + FPlatformProcess::PopDllDirectory(*InkDLLDirectory); + + // Create RealTimeStylus interface + void* OutInstance { nullptr }; + HRESULT hr = ::CoCreateInstance(__uuidof(RealTimeStylus), nullptr, CLSCTX_INPROC, __uuidof(IRealTimeStylus), &OutInstance); + if (FAILED(hr)) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not create RealTimeStylus!")); + return nullptr; + } + + WindowsImpl->RealTimeStylus = static_cast(OutInstance); + WindowsImpl->StylusPlugin = MakeShareable(new FWindowsRealTimeStylusPlugin()); + + // Create free-threaded marshaller for the plugin + hr = ::CoCreateFreeThreadedMarshaler(WindowsImpl->StylusPlugin.Get(), + &WindowsImpl->StylusPlugin->FreeThreadedMarshaller); + if (FAILED(hr)) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not create FreeThreadedMarshaller!")); + return nullptr; + } + + // Add stylus plugin to the interface + hr = WindowsImpl->RealTimeStylus->AddStylusSyncPlugin(0, WindowsImpl->StylusPlugin.Get()); + if (FAILED(hr)) + { + FWindowsPlatformMisc::CoUninitialize(); + UE_LOG(LogStylusInput, Error, TEXT("Could not add stylus plugin to API!")); + return nullptr; + } + + // Set hook to catch main window creation + IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked("MainFrame"); + if (!MainFrameModule.IsWindowInitialized()) + { + MainFrameModule.OnMainFrameCreationFinished().AddLambda([WindowsImpl](TSharedPtr Window, bool) + { + OnMainFrameCreated(*WindowsImpl, Window); + }); + } + else + { + OnMainFrameCreated(*WindowsImpl, MainFrameModule.GetParentWindow()); + } + + return InterfaceImpl; +} + +#endif // PLATFORM_WINDOWS \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.h new file mode 100644 index 000000000000..896e9af8ae60 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Private/WindowsStylusInputInterface.h @@ -0,0 +1,22 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IStylusInputModule.h" + +class FWindowsStylusInputInterfaceImpl; + +class FWindowsStylusInputInterface : public IStylusInputInterfaceInternal +{ +public: + FWindowsStylusInputInterface(FWindowsStylusInputInterfaceImpl* InImpl); + virtual ~FWindowsStylusInputInterface(); + + virtual int32 NumInputDevices() const override; + virtual IStylusInputDevice* GetInputDevice(int32 Index) const override; + +private: + // pImpl to avoid including Windows headers. + FWindowsStylusInputInterfaceImpl* Impl; + TArray MessageHandlers; +}; diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusInputModule.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusInputModule.h new file mode 100644 index 000000000000..8c73cfd14230 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusInputModule.h @@ -0,0 +1,84 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IStylusState.h" + +#include "EditorSubsystem.h" +#include "TickableEditorObject.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "Widgets/Docking/SDockTab.h" + +#include "IStylusInputModule.generated.h" + +DEFINE_LOG_CATEGORY_STATIC(LogStylusInput, Log, All); + +/** + * Module to handle Wacom-style tablet input using styluses. + */ +class STYLUSINPUT_API IStylusInputModule : public IModuleInterface +{ +public: + + /** + * Retrieve the module instance. + */ + static inline IStylusInputModule& Get() + { + return FModuleManager::LoadModuleChecked("StylusInput"); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("StylusInput"); + } +}; + +// This is the interface that all platform-specific implementations must implement. +class IStylusInputInterfaceInternal +{ +public: + virtual IStylusInputDevice* GetInputDevice(int32 Index) const = 0; + virtual int32 NumInputDevices() const = 0; +}; + + +UCLASS() +class STYLUSINPUT_API UStylusInputSubsystem : + public UEditorSubsystem, + public FTickableEditorObject +{ + GENERATED_BODY() +public: + // UEditorSubsystem implementation + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + + /** Retrieve the input device that is at the given index, or nullptr if not found. Corresponds to the StylusIndex in IStylusMessageHandler. */ + const IStylusInputDevice* GetInputDevice(int32 Index) const; + + /** Return the number of active input devices. */ + int32 NumInputDevices() const; + + /** Add a message handler to receive messages from the stylus. */ + void AddMessageHandler(IStylusMessageHandler& MessageHandler); + + /** Remove a previously registered message handler. */ + void RemoveMessageHandler(IStylusMessageHandler& MessageHandler); + + // FTickableEditorObject implementation + virtual void Tick(float DeltaTime) override; + virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UStylusInputSubsystem, STATGROUP_Tickables); } + +private: + TSharedPtr InputInterface; + TArray MessageHandlers; + + TSharedRef OnSpawnPluginTab(const FSpawnTabArgs& Args); +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusState.h b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusState.h new file mode 100644 index 000000000000..c81aa730e2c4 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/Public/IStylusState.h @@ -0,0 +1,167 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Vector2D.h" + +/** + * The types of stylus inputs that can be potentially supported by a stylus. + */ +enum class STYLUSINPUT_API EStylusInputType +{ + Position, + Z, + Pressure, + Tilt, + TangentPressure, + ButtonPressure, + Twist, + Size +}; + +/** + * The current state of a single stylus, as sent by IStylusMessageHandler. + */ +class STYLUSINPUT_API FStylusState +{ +public: + + FStylusState() : Position(0, 0), Z(0), Tilt(0, 0), Twist(0), Pressure(0), + TangentPressure(0), Size(0, 0), IsDown(false), IsInverted(false) + { + } + + FStylusState(FVector2D InPosition, float InZ, FVector2D InTilt, float InTwist, + float InPressure, float InTanPressure, FVector2D InSize, + bool InDown, bool InInverted) : + Position(InPosition), Z(InZ), Tilt(InTilt), Twist(InTwist), + Pressure(InPressure), TangentPressure(InTanPressure), Size(InSize), + IsDown(InDown), IsInverted(InInverted) + { + } + + FStylusState(const FStylusState& Other) : + Position(Other.Position), Z(Other.Z), Tilt(Other.Tilt), Twist(Other.Twist), + Pressure(Other.Pressure), TangentPressure(Other.TangentPressure), Size(Other.Size), + IsDown(Other.IsDown), IsInverted(Other.IsInverted) + { + } + + /** + * The current position of the stylus on (or above) the tablet. Always valid. + * This value is in logical coordinates, not pixels. + * The mouse position can be used to retrieve the screenspace value. + * A value of (0,0) is in the top-left of the tablet. + */ + FVector2D GetPosition() const { return Position; } + + /** + * The current height of the stylus above the tablet. + * Defaults to 0 if EStylusInputType::Z is not supported. + */ + float GetZ() const { return Z; } + + /** + * The current tilt along the X axis in degrees, normalized to the range of [-90, 90]. + * Defaults to (0,0) if EStylusInputType::Tilt is not supported. + * A value of (0,0) means that the stylus is perfectly vertical. + * A positive X value means that the stylus is tilted to the right. + * A positive Y value means that the stylus is tilted forwards, away from the user. + * A value of -90 or 90 means that the pen is lying on the tablet, though in practice this isn't widely supported. + */ + FVector2D GetTilt() const { return Tilt; } + + /** + * The current twist amount around the stylus' own axis in degrees, normalized to the range of [0, 360). + * Defaults to 0 if EStylusInputType::Twist is not supported. + * A value of 1 represents a full rotation clockwise. + */ + float GetTwist() const { return Twist; } + + /** + * Get the current pressure along the tablet's normal, usually straight down. + * Normalized to the range [0, 1]. + * Defaults to 0 if EStylusInputType::Pressure is not supported. + */ + float GetPressure() const { return Pressure; } + + /** + * Get the current pressure along the tablet's surface. + * Normalized to the range [0, 1]. + * Defaults to 0 if EStylusInputType::TangentPressure is not supported. + */ + float GetTangentPressure() const { return TangentPressure; } + + /** + * Get the size of the touch in logical coordinates. + * Defaults to (0,0) if EStylusInputType::Size is not supported. + */ + FVector2D GetSize() const { return Size; } + + /** + * Is the stylus inverted? Ie. the eraser part is pointing down. + * Defaults to false if EStylusInputType::Tilt is not supported. + */ + bool IsStylusInverted() const { return IsInverted; } + + /** Is the stylus currently touching the tablet? */ + bool IsStylusDown() const { return IsDown; } + +private: + + FVector2D Position; + float Z; + FVector2D Tilt; + float Twist; + float Pressure; + float TangentPressure; + FVector2D Size; + + bool IsDown : 1; + bool IsInverted : 1; +}; + +/** An input device representing a stylus and its current state. */ +class STYLUSINPUT_API IStylusInputDevice +{ +public: + + virtual ~IStylusInputDevice() {} + + /** + * Get the current stylus state. + */ + const FStylusState& GetCurrentState() const { return CurrentState; } + + /** + * Get the previous stylus state. + */ + const FStylusState& GetPreviousState() const { return PreviousState; } + + /** + * Get the supported inputs of this tablet. + */ + const TArray& GetSupportedInputs() const { return SupportedInputs; } + + /** Update the input device. Not intended to be called externally. */ + virtual void Tick() = 0; + + /** Does the input device need to be ticked? */ + bool IsDirty() const { return Dirty; } + +protected: + FStylusState CurrentState; + FStylusState PreviousState; + TArray SupportedInputs; + bool Dirty : 1; +}; + +/** + * Interface to implement for classes that want to receive messages when a stylus state change occurs. + * Will trigger once per frame. + */ +class IStylusMessageHandler +{ +public: + virtual void OnStylusStateChanged(const FStylusState& NewState, int32 StylusIndex) = 0; +}; diff --git a/Engine/Plugins/Editor/StylusInput/Source/StylusInput/StylusInput.Build.cs b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/StylusInput.Build.cs new file mode 100644 index 000000000000..3082635e34af --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/Source/StylusInput/StylusInput.Build.cs @@ -0,0 +1,52 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class StylusInput : ModuleRules + { + public StylusInput(ReadOnlyTargetRules Target) : base(Target) + { + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "EditorSubsystem", + "Engine", + "UnrealEd" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "MainFrame", + "SlateCore", + "Slate", + "WorkspaceMenuStructure" + // ... add private dependencies that you statically link with here ... + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} diff --git a/Engine/Plugins/Editor/StylusInput/StylusInput.uplugin b/Engine/Plugins/Editor/StylusInput/StylusInput.uplugin new file mode 100644 index 000000000000..9ef98989f510 --- /dev/null +++ b/Engine/Plugins/Editor/StylusInput/StylusInput.uplugin @@ -0,0 +1,25 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "Stylus & Tablet Plugin", + "Description" : "Support for advanced stylus and tablet inputs such as pressure, stylus & tablet buttons, and pen angles.", + "Category" : "Input Devices", + "CreatedBy" : "Epic Games, Inc.", + "CreatedByURL" : "http://epicgames.com", + "DocsURL" : "", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : false, + "CanContainContent" : false, + "IsBetaVersion" : true, + "Installed" : false, + "Modules" : + [ + { + "Name" : "StylusInput", + "Type" : "Editor", + "LoadingPhase" : "Default" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp index 11a11b410175..ddfb93450893 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcFile.cpp @@ -21,10 +21,12 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include "Materials/MaterialInstance.h" THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -498,4 +500,4 @@ UMaterialInterface** FAbcFile::GetMaterialByName(const FString& InMaterialName) return MaterialMap.Find(InMaterialName); } -#undef LOCTEXT_NAMESPACE // "AbcFile" \ No newline at end of file +#undef LOCTEXT_NAMESPACE // "AbcFile" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp index 56d25eec3273..bb26a3cca1a8 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.cpp @@ -14,6 +14,7 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include @@ -24,6 +25,7 @@ THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h index 5d589c6240ee..27d69b9e28f3 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImportUtilities.h @@ -9,11 +9,13 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp index c1c4624bfb6e..cfca9a9955aa 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcImporter.cpp @@ -6,6 +6,7 @@ #include "Windows/WindowsHWrapper.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include @@ -13,6 +14,7 @@ THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #include "Misc/Paths.h" #include "Misc/FeedbackContext.h" diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp index 45d3a513e7f3..6a2181400caa 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcPolyMesh.cpp @@ -7,10 +7,12 @@ #include "Modules/ModuleManager.h" #include "MeshUtilities.h" +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END static const ESampleReadFlags ReadAllFlags = ESampleReadFlags::Positions | ESampleReadFlags::Indices | ESampleReadFlags::UVs | ESampleReadFlags::Normals | ESampleReadFlags::Colors | ESampleReadFlags::MaterialIndices; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp index 23d925d45b61..a142beb72451 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/AbcTransform.cpp @@ -5,10 +5,12 @@ #include "AbcFile.h" +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END FAbcTransform::FAbcTransform(const Alembic::AbcGeom::IXform& InTransform, const FAbcFile* InFile, IAbcObject* InParent /*= nullptr*/) : IAbcObject(InTransform, InFile, InParent), Transform(InTransform), Schema(InTransform.getSchema()) { diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h index 6e731bcc0ea3..c54b83045667 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Private/EigenHelper.h @@ -13,10 +13,12 @@ #pragma warning(push) #pragma warning(disable:6294) // Ill-defined for-loop: initial condition does not satisfy test. Loop body not executed. #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if defined(_MSC_VER) && USING_CODE_ANALYSIS #pragma warning(pop) #endif diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h index 2b9f3f830c55..11f8b5ca351f 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcFile.h @@ -10,12 +10,13 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include #include - THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -142,4 +143,4 @@ protected: /** Cached Mesh utilities ptr for normal calculations */ IMeshUtilities* MeshUtilities; -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h index bc677ad0c906..9cb54102b408 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcImporter.h @@ -13,9 +13,11 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -217,4 +219,4 @@ private: /** ABC file representation for currently opened filed */ class FAbcFile* AbcFile; -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h index fbb16dae8f78..caa6818c2e78 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicLibrary/Public/AbcObject.h @@ -10,10 +10,12 @@ #include "Windows/AllowWindowsPlatformTypes.h" #endif +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include #include THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" @@ -71,4 +73,4 @@ protected: float FrameTimes[MaxNumberOfResidentSamples]; int32 ResidentSampleIndices[MaxNumberOfResidentSamples]; bool InUseSamples[MaxNumberOfResidentSamples]; -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h b/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h index 6eb974d41daf..2e70cfa5993c 100644 --- a/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h +++ b/Engine/Plugins/Experimental/AlembicImporter/Source/ThirdParty/Eigen/Eigen/src/Core/MathFunctions.h @@ -383,7 +383,7 @@ inline NewType cast(const OldType& x) * Implementation of round * ****************************************************************************/ -#if EIGEN_HAS_CXX11_MATH +#if EIGEN_HAS_CXX11_MATH && !(defined(__ANDROID__) || defined(ANDROID)) template struct round_impl { static inline Scalar run(const Scalar& x) @@ -480,7 +480,7 @@ struct log1p_impl { static inline Scalar run(const Scalar& x) { EIGEN_STATIC_ASSERT_NON_INTEGER(Scalar) - #if EIGEN_HAS_CXX11_MATH + #if EIGEN_HAS_CXX11_MATH && !(defined(__ANDROID__) || defined(ANDROID)) using std::log1p; #endif using std_fallback::log1p; diff --git a/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp b/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp index 5f81cd40e478..0d47f2de2726 100644 --- a/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp +++ b/Engine/Plugins/Experimental/CodeEditor/Source/CodeEditor/Private/SCodeEditor.cpp @@ -32,12 +32,12 @@ void SCodeEditor::Construct(const FArguments& InArgs, UCodeProjectItem* InCodePr HorizontalScrollbar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(10.0f, 10.0f)); + .Thickness(FVector2D(14.0f, 14.0f)); VerticalScrollbar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(10.0f, 10.0f)); + .Thickness(FVector2D(14.0f, 14.0f)); ChildSlot [ diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp index e8d4f9d349ba..e2679fb4cb3b 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Graph/SControlRigGraphNode.cpp @@ -695,7 +695,7 @@ FSlateColor SControlRigGraphNode::GetPinTextColor(TWeakPtr GraphPin) // If there is no schema there is no owning node (or basically this is a deleted node) if (GraphNode) { - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !GraphPin.Pin()->IsEditingEnabled()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !GraphPin.Pin()->IsEditingEnabled() || GraphNode->IsNodeUnrelated()) { return FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } diff --git a/Engine/Plugins/Experimental/GeometryProcessing/GTEngine.tps b/Engine/Plugins/Experimental/GeometryProcessing/GTEngine.tps new file mode 100644 index 000000000000..4117a04cc26c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/GTEngine.tps @@ -0,0 +1,13 @@ + + + GTEngine + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/GTEngine_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/GeometryProcessing.uplugin b/Engine/Plugins/Experimental/GeometryProcessing/GeometryProcessing.uplugin new file mode 100644 index 000000000000..3f4d9c1dd8c3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/GeometryProcessing.uplugin @@ -0,0 +1,43 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.1", + "FriendlyName": "GeometryProcessing", + "Description": "Classes and Algorithms for Processing 2D and 3D Geometry", + "Category": "Other", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "GeometricObjects", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "GeometryAlgorithms", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "DynamicMesh", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "MeshConversion", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "MeshSolverUtilities", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs new file mode 100644 index 000000000000..b97899bccd56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/DynamicMesh.Build.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class DynamicMesh : ModuleRules +{ + public DynamicMesh(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "GeometricObjects" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3.cpp new file mode 100644 index 000000000000..3a4aa22a4891 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3.cpp @@ -0,0 +1,1110 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" +#include "Generators/MeshShapeGenerator.h" + + +const int FDynamicMesh3::InvalidID = IndexConstants::InvalidID; // -1; +const int FDynamicMesh3::NonManifoldID = -2; +const int FDynamicMesh3::InvalidGroupID = IndexConstants::InvalidID; // -1; + + +FDynamicMesh3::~FDynamicMesh3() +{ + if (VertexNormals != nullptr) + { + delete VertexNormals; + } + if (VertexColors != nullptr) + { + delete VertexColors; + } + if (VertexUVs != nullptr) + { + delete VertexUVs; + } + if (TriangleGroups != nullptr) + { + delete TriangleGroups; + } + if (AttributeSet != nullptr) + { + delete AttributeSet; + } +} + + + + +FDynamicMesh3::FDynamicMesh3(bool bWantNormals, bool bWantColors, bool bWantUVs, bool bWantTriGroups) +{ + Vertices = TDynamicVector(); + VertexNormals = (bWantNormals) ? new TDynamicVector() : nullptr; + VertexColors = (bWantColors) ? new TDynamicVector() : nullptr; + VertexUVs = (bWantUVs) ? new TDynamicVector() : nullptr; + + VertexEdgeLists = FSmallListSet(); + + VertexRefCounts = FRefCountVector(); + + Triangles = TDynamicVector(); + TriangleEdges = TDynamicVector(); + TriangleRefCounts = FRefCountVector(); + TriangleGroups = (bWantTriGroups) ? new TDynamicVector() : nullptr; + GroupIDCounter = 0; + + Edges = TDynamicVector(); + EdgeRefCounts = FRefCountVector(); + + AttributeSet = nullptr; +} + + +FDynamicMesh3::FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact, EMeshComponents flags) + : FDynamicMesh3(CopyMesh, bCompact, ((int)flags & (int)EMeshComponents::VertexNormals) != 0, ((int)flags & (int)EMeshComponents::VertexColors) != 0, + ((int)flags & (int)EMeshComponents::VertexUVs) != 0) +{ +} + + + + +// normals/colors/uvs will only be copied if they exist +FDynamicMesh3::FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact, bool bWantNormals, bool bWantColors, bool bWantUVs, bool bWantAttributes) +{ + VertexNormals = nullptr; + VertexColors = nullptr; + VertexUVs = nullptr; + TriangleGroups = nullptr; + AttributeSet = nullptr; + + if (bCompact) + { + CompactCopy(CopyMesh, bWantNormals, bWantColors, bWantUVs, bWantAttributes); + } + else + { + Copy(CopyMesh, bWantNormals, bWantColors, bWantUVs, bWantAttributes); + } +} + + + +const FDynamicMesh3& FDynamicMesh3::operator=(const FDynamicMesh3& CopyMesh) +{ + Copy(CopyMesh); + return *this; +} + + +FDynamicMesh3::FDynamicMesh3(const FMeshShapeGenerator* Generator) +{ + Copy(Generator); +} +void FDynamicMesh3::Copy(const FMeshShapeGenerator* Generator) +{ + Clear(); + + Vertices = TDynamicVector(); + VertexEdgeLists = FSmallListSet(); + VertexRefCounts = FRefCountVector(); + Triangles = TDynamicVector(); + TriangleEdges = TDynamicVector(); + TriangleRefCounts = FRefCountVector(); + TriangleGroups = new TDynamicVector(); + GroupIDCounter = 0; + Edges = TDynamicVector(); + EdgeRefCounts = FRefCountVector(); + + EnableAttributes(); + FDynamicMeshUVOverlay* UVOverlay = Attributes()->PrimaryUV(); + FDynamicMeshNormalOverlay* NormalOverlay = Attributes()->PrimaryNormals(); + + int NumVerts = Generator->Vertices.Num(); + for (int i = 0; i < NumVerts; ++i) + { + AppendVertex(Generator->Vertices[i]); + } + int NumUVs = Generator->UVs.Num(); + for (int i = 0; i < NumUVs; ++i) + { + UVOverlay->AppendElement(Generator->UVs[i], Generator->UVParentVertex[i]); + } + int NumNormals = Generator->Normals.Num(); + for (int i = 0; i < NumNormals; ++i) + { + NormalOverlay->AppendElement(Generator->Normals[i], Generator->NormalParentVertex[i]); + } + + int NumTris = Generator->Triangles.Num(); + for (int i = 0; i < NumTris; ++i) + { + int tid = AppendTriangle(Generator->Triangles[i], Generator->TrianglePolygonIDs[i]); + check(tid == i); + UVOverlay->SetTriangle(tid, Generator->TriangleUVs[i]); + NormalOverlay->SetTriangle(tid, Generator->TriangleNormals[i]); + } +} + + + + + +void FDynamicMesh3::Copy(const FDynamicMesh3& copy, bool bNormals, bool bColors, bool bUVs, bool bAttributes) +{ + // currently cannot re-use existing attribute buffers + DiscardVertexNormals(); + DiscardVertexColors(); + DiscardVertexUVs(); + DiscardTriangleGroups(); + DiscardAttributes(); + + Vertices = TDynamicVector(copy.Vertices); + + VertexNormals = (bNormals && copy.HasVertexNormals()) ? new TDynamicVector(*copy.VertexNormals) : nullptr; + VertexColors = (bColors && copy.HasVertexColors()) ? new TDynamicVector(*copy.VertexColors) : nullptr; + VertexUVs = (bUVs && copy.HasVertexUVs()) ? new TDynamicVector(*copy.VertexUVs) : nullptr; + + VertexRefCounts = FRefCountVector(copy.VertexRefCounts); + + VertexEdgeLists = FSmallListSet(copy.VertexEdgeLists); + + Triangles = TDynamicVector(copy.Triangles); + TriangleEdges = TDynamicVector(copy.TriangleEdges); + TriangleRefCounts = FRefCountVector(copy.TriangleRefCounts); + TriangleGroups = (copy.HasTriangleGroups()) ? new TDynamicVector(*copy.TriangleGroups) : nullptr; + GroupIDCounter = copy.GroupIDCounter; + + Edges = TDynamicVector(copy.Edges); + EdgeRefCounts = FRefCountVector(copy.EdgeRefCounts); + + if (bAttributes && copy.HasAttributes()) + { + EnableAttributes(); + AttributeSet->Copy(*copy.Attributes()); + } + + Timestamp = FMath::Max(Timestamp + 1, copy.Timestamp); + ShapeTimestamp = TopologyTimestamp = Timestamp; +} + + + + + +void FDynamicMesh3::CompactCopy(const FDynamicMesh3& copy, bool bNormals, bool bColors, bool bUVs, bool bAttributes, FCompactMaps* CompactInfo) +{ + // @todo these are not compacted and hence code cannot work. fix! + check(copy.AttributeSet == nullptr); + check(bNormals == false && bColors == false && bUVs == false); + + // TODO can't do until CompactInfo works + //if ( copy.IsCompact() ) { + // Copy(copy, bNormals, bColors, bUVs); + // CompactInfo ci = CompactInfo() { MapV = IdentityIndexMap() }; + // return ci; + //} + + // currently cannot re-use existing attribute buffers + DiscardVertexNormals(); + DiscardVertexColors(); + DiscardVertexUVs(); + DiscardTriangleGroups(); + + Vertices = TDynamicVector(); + VertexEdgeLists = FSmallListSet(); + VertexRefCounts = FRefCountVector(); + Triangles = TDynamicVector(); + TriangleEdges = TDynamicVector(); + TriangleRefCounts = FRefCountVector(); + Edges = TDynamicVector(); + EdgeRefCounts = FRefCountVector(); + GroupIDCounter = 0; + + TriangleGroups = (copy.HasTriangleGroups()) ? new TDynamicVector(*copy.TriangleGroups) : nullptr; + + // [TODO] if we knew some of these were dense we could copy directly... + + FVertexInfo vinfo; + TArray mapV; mapV.SetNum(copy.MaxVertexID()); + for (int vid : copy.VertexIndicesItr()) + { + copy.GetVertex(vid, vinfo, bNormals, bColors, bUVs); + mapV[vid] = AppendVertex(vinfo); + } + + // [TODO] would be much faster to explicitly copy triangle & edge data structures!! + if (copy.HasTriangleGroups()) + { + EnableTriangleGroups(0); + } + for (int tid : copy.TriangleIndicesItr()) + { + FIndex3i t = copy.GetTriangle(tid); + t = FIndex3i(mapV[t.A], mapV[t.B], mapV[t.C]); + int g = (copy.HasTriangleGroups()) ? copy.GetTriangleGroup(tid) : InvalidID; + AppendTriangle(t, g); + GroupIDCounter = FMath::Max(GroupIDCounter, g + 1); + } + + // generate compact info...? + if (CompactInfo != nullptr) + { + for (int vid : copy.VertexIndicesItr()) + { + CompactInfo->MapV.Add(vid, mapV[vid]); + } + } + + Timestamp = FMath::Max(Timestamp + 1, copy.Timestamp); + ShapeTimestamp = TopologyTimestamp = Timestamp; +} + + + + +void FDynamicMesh3::Clear() +{ + Vertices.Clear(); + VertexEdgeLists = FSmallListSet(); + VertexRefCounts = FRefCountVector(); + Triangles.Clear(); + TriangleEdges.Clear(); + TriangleRefCounts = FRefCountVector(); + Edges.Clear(); + EdgeRefCounts = FRefCountVector(); + + DiscardTriangleGroups(); + DiscardVertexColors(); + DiscardVertexUVs(); + DiscardVertexNormals(); + DiscardAttributes(); + + Timestamp = ShapeTimestamp = TopologyTimestamp = 0; + GroupIDCounter = 0; + CachedBoundingBoxTimestamp = -1; + bIsClosedCached = false; + CachedIsClosedTimestamp = -1; +} + + + +int FDynamicMesh3::GetComponentsFlags() const +{ + int c = 0; + if (HasVertexNormals()) + { + c |= (int)EMeshComponents::VertexNormals; + } + if (HasVertexColors()) + { + c |= (int)EMeshComponents::VertexColors; + } + if (HasVertexUVs()) + { + c |= (int)EMeshComponents::VertexUVs; + } + if (HasTriangleGroups()) + { + c |= (int)EMeshComponents::FaceGroups; + } + return c; +} + + + + + + + + + + +void FDynamicMesh3::EnableVertexNormals(const FVector3f& InitialNormal) +{ + if (HasVertexNormals()) + { + return; + } + VertexNormals = new TDynamicVector(); + int NV = MaxVertexID(); + VertexNormals->Resize(3 * NV); + for (int i = 0; i < NV; ++i) + { + int vi = 3 * i; + (*VertexNormals)[vi] = InitialNormal.X; + (*VertexNormals)[vi + 1] = InitialNormal.Y; + (*VertexNormals)[vi + 2] = InitialNormal.Z; + } +} + +void FDynamicMesh3::DiscardVertexNormals() +{ + if (VertexNormals != nullptr) + { + delete VertexNormals; + VertexNormals = nullptr; + } +} + + + + + +void FDynamicMesh3::EnableVertexColors(const FVector3f& InitialColor) +{ + if (HasVertexColors()) + { + return; + } + VertexColors = new TDynamicVector(); + int NV = MaxVertexID(); + VertexColors->Resize(3 * NV); + for (int i = 0; i < NV; ++i) + { + int vi = 3 * i; + (*VertexColors)[vi] = InitialColor.X; + (*VertexColors)[vi + 1] = InitialColor.Y; + (*VertexColors)[vi + 2] = InitialColor.Z; + } +} + +void FDynamicMesh3::DiscardVertexColors() +{ + if (VertexColors != nullptr) + { + delete VertexColors; + VertexColors = nullptr; + } +} + + + + +void FDynamicMesh3::EnableVertexUVs(const FVector2f& InitialUV) +{ + if (HasVertexUVs()) + { + return; + } + VertexUVs = new TDynamicVector(); + int NV = MaxVertexID(); + VertexUVs->Resize(2 * NV); + for (int i = 0; i < NV; ++i) + { + int vi = 2 * i; + (*VertexUVs)[vi] = InitialUV.X; + (*VertexUVs)[vi + 1] = InitialUV.Y; + } +} + +void FDynamicMesh3::DiscardVertexUVs() +{ + if (VertexUVs != nullptr) + { + delete VertexUVs; + VertexUVs = nullptr; + } +} + + + +void FDynamicMesh3::EnableTriangleGroups(int InitialGroup) +{ + if (HasTriangleGroups()) + { + return; + } + TriangleGroups = new TDynamicVector(); + int NT = MaxTriangleID(); + TriangleGroups->Resize(NT); + for (int i = 0; i < NT; ++i) + { + (*TriangleGroups)[i] = InitialGroup; + } + GroupIDCounter = 0; +} + +void FDynamicMesh3::DiscardTriangleGroups() +{ + if (TriangleGroups != nullptr) + { + delete TriangleGroups; + TriangleGroups = nullptr; + } + GroupIDCounter = 0; +} + + + + +void FDynamicMesh3::EnableAttributes() +{ + if (HasAttributes()) + { + return; + } + AttributeSet = new FDynamicMeshAttributeSet(this); + AttributeSet->Initialize(MaxVertexID(), MaxTriangleID()); +} + + +void FDynamicMesh3::DiscardAttributes() +{ + if (AttributeSet != nullptr) + { + delete AttributeSet; + AttributeSet = nullptr; + } +} + + + + + + +bool FDynamicMesh3::GetVertex(int vID, FVertexInfo& vinfo, bool bWantNormals, bool bWantColors, bool bWantUVs) const +{ + if (VertexRefCounts.IsValid(vID) == false) + { + return false; + } + int vi = 3 * vID; + vinfo.Position = FVector3d(Vertices[vi], Vertices[vi + 1], Vertices[vi + 2]); + vinfo.bHaveN = vinfo.bHaveUV = vinfo.bHaveC = false; + if (HasVertexNormals() && bWantNormals) + { + vinfo.bHaveN = true; + vinfo.Normal = FVector3f((*VertexNormals)[vi], (*VertexNormals)[vi + 1], (*VertexNormals)[vi + 2]); + } + if (HasVertexColors() && bWantColors) + { + vinfo.bHaveC = true; + vinfo.Color = FVector3f((*VertexColors)[vi], (*VertexColors)[vi + 1], (*VertexColors)[vi + 2]); + } + if (HasVertexUVs() && bWantUVs) + { + vinfo.bHaveUV = true; + vinfo.UV = FVector2f((*VertexUVs)[2*vID], (*VertexUVs)[2*vID + 1]); + } + return true; +} + + +int FDynamicMesh3::GetMaxVtxEdgeCount() const +{ + int max = 0; + for (int vid : VertexIndicesItr()) + { + max = FMath::Max(max, VertexEdgeLists.GetCount(vid)); + } + return max; +} + + + +FVertexInfo FDynamicMesh3::GetVertexInfo(int i) const +{ + FVertexInfo vi = FVertexInfo(); + vi.Position = GetVertex(i); + vi.bHaveN = vi.bHaveC = vi.bHaveUV = false; + if (HasVertexNormals()) + { + vi.bHaveN = true; + vi.Normal = GetVertexNormal(i); + } + if (HasVertexColors()) + { + vi.bHaveC = true; + vi.Color = GetVertexColor(i); + } + if (HasVertexUVs()) + { + vi.bHaveUV = true; + vi.UV = GetVertexUV(i); + } + return vi; +} + + + +FIndex3i FDynamicMesh3::GetTriNeighbourTris(int tID) const +{ + if (TriangleRefCounts.IsValid(tID)) + { + int tei = 3 * tID; + FIndex3i nbr_t = FIndex3i::Zero(); + for (int j = 0; j < 3; ++j) + { + int ei = 4 * TriangleEdges[tei + j]; + nbr_t[j] = (Edges[ei + 2] == tID) ? Edges[ei + 3] : Edges[ei + 2]; + } + return nbr_t; + } + else + { + return InvalidTriangle(); + } +} + + + +/** Enumerate triangle one ring at a vertex */ +FDynamicMesh3::vtx_triangles_enumerable FDynamicMesh3::VtxTrianglesItr(int vID) const +{ + check(VertexRefCounts.IsValid(vID)); + + TFunction expand_f = [this,vID](int eid, int& k) + { + int vOther = GetOtherEdgeVertex(eid, vID); + int i = 4 * eid; + if (k == -1) + { + int et0 = Edges[i + 2]; + if (TriHasSequentialVertices(et0, vID, vOther)) + { + k = 0; + return et0; + } + } + if (k != 1) + { + int et1 = Edges[i + 3]; + if (et1 != InvalidID && TriHasSequentialVertices(et1, vID, vOther)) + { + k = 1; + return et1; + } + } + k = -1; + return -1; + }; + + return vtx_triangles_enumerable(VertexEdgeLists.Values(vID), expand_f); +} + + + + + + + + +FString FDynamicMesh3::MeshInfoString() +{ + FString VtxString = FString::Printf(TEXT("Vertices count %d max %d %s VtxEdges %s"), + VertexCount(), MaxVertexID(), *VertexRefCounts.UsageStats(), *(VertexEdgeLists.MemoryUsage())); + FString TriString = FString::Printf(TEXT("Triangles count %d max %d %s"), + TriangleCount(), MaxTriangleID(), *TriangleRefCounts.UsageStats()); + FString EdgeString = FString::Printf(TEXT("Edges count %d max %d %s"), + EdgeCount(), MaxEdgeID(), *EdgeRefCounts.UsageStats()); + FString AttribString = FString::Printf(TEXT("VtxNormals %d VtxColors %d VtxUVs %d TriGroups %d Attributes %d"), + HasVertexNormals(), HasVertexColors(), HasVertexUVs(), HasTriangleGroups(), HasAttributes()); + FString InfoString = FString::Printf(TEXT("Closed %d Compact %d Timestamp %d ShapeTimestamp %d TopologyTimestamp %d MaxGroupID %d"), + GetCachedIsClosed(), IsCompact(), GetTimestamp(), GetShapeTimestamp(), GetTopologyTimestamp(), MaxGroupID()); + + return VtxString + "\n" + TriString + "\n" + EdgeString + "\n" + AttribString + "\n" + InfoString; +} + + + + +bool FDynamicMesh3::IsSameMesh(const FDynamicMesh3& m2, bool bCheckConnectivity, bool bCheckEdgeIDs, + bool bCheckNormals, bool bCheckColors, bool bCheckUVs, bool bCheckGroups, + float Epsilon) +{ + if (VertexCount() != m2.VertexCount()) + { + return false; + } + if (TriangleCount() != m2.TriangleCount()) + { + return false; + } + for (int vid : VertexIndicesItr()) + { + if (m2.IsVertex(vid) == false || VectorUtil::EpsilonEqual(GetVertex(vid), m2.GetVertex(vid), (double)Epsilon) == false) + { + return false; + } + } + for (int tid : TriangleIndicesItr()) + { + if (m2.IsTriangle(tid) == false || (GetTriangle(tid) != m2.GetTriangle(tid))) + { + return false; + } + } + if (bCheckConnectivity) + { + for (int eid : EdgeIndicesItr()) + { + FIndex4i e = GetEdge(eid); + int other_eid = m2.FindEdge(e[0], e[1]); + if (other_eid == InvalidID) + { + return false; + } + FIndex4i oe = m2.GetEdge(other_eid); + if (FMath::Min(e[2], e[3]) != FMath::Min(oe[2], oe[3]) || FMath::Max(e[2], e[3]) != FMath::Max(oe[2], oe[3])) + { + return false; + } + } + } + if (bCheckEdgeIDs) + { + if (EdgeCount() != m2.EdgeCount()) + { + return false; + } + for (int eid : EdgeIndicesItr()) + { + if (m2.IsEdge(eid) == false || GetEdge(eid) != m2.GetEdge(eid)) + { + return false; + } + } + } + if (bCheckNormals) + { + if (HasVertexNormals() != m2.HasVertexNormals()) + { + return false; + } + if (HasVertexNormals()) + { + for (int vid : VertexIndicesItr()) + { + if (VectorUtil::EpsilonEqual(GetVertexNormal(vid), m2.GetVertexNormal(vid), Epsilon) == false) + { + return false; + } + } + } + } + if (bCheckColors) + { + if (HasVertexColors() != m2.HasVertexColors()) + { + return false; + } + if (HasVertexColors()) + { + for (int vid : VertexIndicesItr()) + { + if (VectorUtil::EpsilonEqual(GetVertexColor(vid), m2.GetVertexColor(vid), Epsilon) == false) + { + return false; + } + } + } + } + if (bCheckUVs) + { + if (HasVertexUVs() != m2.HasVertexUVs()) + { + return false; + } + if (HasVertexUVs()) + { + for (int vid : VertexIndicesItr()) + { + if (VectorUtil::EpsilonEqual(GetVertexUV(vid), m2.GetVertexUV(vid), Epsilon) == false) + { + return false; + } + } + } + } + if (bCheckGroups) + { + if (HasTriangleGroups() != m2.HasTriangleGroups()) + { + return false; + } + if (HasTriangleGroups()) + { + for (int tid : TriangleIndicesItr()) + { + if (GetTriangleGroup(tid) != m2.GetTriangleGroup(tid)) + { + return false; + } + } + } + } + return true; +} + + + + + + + +bool FDynamicMesh3::CheckValidity(bool bAllowNonManifoldVertices, EValidityCheckFailMode FailMode) const +{ + + TArray triToVtxRefs; + triToVtxRefs.SetNum(MaxVertexID()); + //int[] triToVtxRefs = new int[this.MaxVertexID]; + + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + for (int tID : TriangleIndicesItr()) + { + CheckOrFailF(IsTriangle(tID)); + CheckOrFailF(TriangleRefCounts.GetRefCount(tID) == 1); + + // vertices must exist + FIndex3i tv = GetTriangle(tID); + for (int j = 0; j < 3; ++j) + { + CheckOrFailF(IsVertex(tv[j])); + triToVtxRefs[tv[j]] += 1; + } + + // edges must exist and reference this tri + FIndex3i e; + for (int j = 0; j < 3; ++j) + { + int a = tv[j], b = tv[(j + 1) % 3]; + e[j] = FindEdge(a, b); + CheckOrFailF(e[j] != InvalidID); + CheckOrFailF(EdgeHasTriangle(e[j], tID)); + CheckOrFailF(e[j] == FindEdgeFromTri(a, b, tID)); + } + CheckOrFailF(e[0] != e[1] && e[0] != e[2] && e[1] != e[2]); + + // tri nbrs must exist and reference this tri, or same edge must be boundary edge + FIndex3i te = GetTriEdges(tID); + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + CheckOrFailF(IsEdge(eid)); + int tOther = GetOtherEdgeTriangle(eid, tID); + if (tOther == InvalidID) + { + CheckOrFailF(IsBoundaryTriangle(tID)); + continue; + } + + CheckOrFailF(TriHasNeighbourTri(tOther, tID) == true); + + // edge must have same two verts as tri for same index + int a = tv[j], b = tv[(j + 1) % 3]; + FIndex2i ev = GetEdgeV(te[j]); + CheckOrFailF(IndexUtil::SamePairUnordered(a, b, ev[0], ev[1])); + + // also check that nbr edge has opposite orientation + FIndex3i othertv = GetTriangle(tOther); + int found = IndexUtil::FindTriOrderedEdge(b, a, othertv); + CheckOrFailF(found != InvalidID); + } + } + + + // edge verts/tris must exist + for (int eID : EdgeIndicesItr()) + { + CheckOrFailF(IsEdge(eID)); + CheckOrFailF(EdgeRefCounts.GetRefCount(eID) == 1); + FIndex2i ev = GetEdgeV(eID); + FIndex2i et = GetEdgeT(eID); + CheckOrFailF(IsVertex(ev[0])); + CheckOrFailF(IsVertex(ev[1])); + CheckOrFailF(et[0] != InvalidID); + CheckOrFailF(ev[0] < ev[1]); + CheckOrFailF(IsTriangle(et[0])); + if (et[1] != InvalidID) + { + CheckOrFailF(IsTriangle(et[1])); + } + } + + // verify compact check + bool is_compact = VertexRefCounts.IsDense(); + if (is_compact) + { + for (int vid = 0; vid < (int)Vertices.GetLength() / 3; ++vid) + { + CheckOrFailF(VertexRefCounts.IsValid(vid)); + } + } + + // vertex edges must exist and reference this vert + for (int vID : VertexIndicesItr()) + { + CheckOrFailF(IsVertex(vID)); + + FVector3d v = GetVertex(vID); + CheckOrFailF(FMathd::IsNaN(v.SquaredLength()) == false); + CheckOrFailF(FMathd::IsFinite(v.SquaredLength())); + + for (int edgeid : VertexEdgeLists.Values(vID)) + { + CheckOrFailF(IsEdge(edgeid)); + CheckOrFailF(EdgeHasVertex(edgeid, vID)); + + int otherV = GetOtherEdgeVertex(edgeid, vID); + int e2 = FindEdge(vID, otherV); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + e2 = FindEdge(otherV, vID); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + } + + for (int nbr_vid : VtxVerticesItr(vID)) + { + CheckOrFailF(IsVertex(nbr_vid)); + int edge = FindEdge(vID, nbr_vid); + CheckOrFailF(IsEdge(edge)); + } + + TArray vTris, vTris2; + GetVtxTriangles(vID, vTris, false); + GetVtxTriangles(vID, vTris2, true); + CheckOrFailF(vTris.Num() == vTris2.Num()); + //System.Console.WriteLine(string.Format("{0} {1} {2}", vID, vTris.Count, GetVtxEdges(vID).Count)); + if (bAllowNonManifoldVertices) + { + CheckOrFailF(vTris.Num() <= GetVtxEdgeCount(vID)); + } + else + { + CheckOrFailF(vTris.Num() == GetVtxEdgeCount(vID) || vTris.Num() == GetVtxEdgeCount(vID) - 1); + } + CheckOrFailF(VertexRefCounts.GetRefCount(vID) == vTris.Num() + 1); + CheckOrFailF(triToVtxRefs[vID] == vTris.Num()); + for (int tID : vTris) + { + CheckOrFailF(TriangleHasVertex(tID, vID)); + } + + // check that edges around vert only references tris above, and reference all of them! + TArray vRemoveTris(vTris); + for (int edgeid : VertexEdgeLists.Values(vID)) + { + FIndex2i edget = GetEdgeT(edgeid); + CheckOrFailF(vTris.Contains(edget[0])); + if (edget[1] != InvalidID) + { + CheckOrFailF(vTris.Contains(edget[1])); + } + vRemoveTris.Remove(edget[0]); + if (edget[1] != InvalidID) + { + vRemoveTris.Remove(edget[1]); + } + } + CheckOrFailF(vRemoveTris.Num() == 0); + } + + if (HasAttributes()) + { + Attributes()->PrimaryUV()->CheckValidity(true, FailMode); + Attributes()->PrimaryNormals()->CheckValidity(true, FailMode); + } + + return is_ok; +} + + + + + + + + + +int FDynamicMesh3::AddEdgeInternal(int vA, int vB, int tA, int tB) +{ + if (vB < vA) { + int t = vB; vB = vA; vA = t; + } + int eid = EdgeRefCounts.Allocate(); + int i = 4 * eid; + Edges.InsertAt(vA, i); + Edges.InsertAt(vB, i + 1); + Edges.InsertAt(tA, i + 2); + Edges.InsertAt(tB, i + 3); + + VertexEdgeLists.Insert(vA, eid); + VertexEdgeLists.Insert(vB, eid); + return eid; +} + + +int FDynamicMesh3::AddTriangleInternal(int a, int b, int c, int e0, int e1, int e2) +{ + int tid = TriangleRefCounts.Allocate(); + int i = 3 * tid; + Triangles.InsertAt(c, i + 2); + Triangles.InsertAt(b, i + 1); + Triangles.InsertAt(a, i); + TriangleEdges.InsertAt(e2, i + 2); + TriangleEdges.InsertAt(e1, i + 1); + TriangleEdges.InsertAt(e0, i + 0); + return tid; +} + + +int FDynamicMesh3::ReplaceEdgeVertex(int eID, int vOld, int vNew) +{ + int i = 4 * eID; + int a = Edges[i], b = Edges[i + 1]; + if (a == vOld) + { + Edges[i] = FMath::Min(b, vNew); + Edges[i + 1] = FMath::Max(b, vNew); + return 0; + } + else if (b == vOld) + { + Edges[i] = FMath::Min(a, vNew); + Edges[i + 1] = FMath::Max(a, vNew); + return 1; + } + else + { + return -1; + } +} + + +int FDynamicMesh3::ReplaceEdgeTriangle(int eID, int tOld, int tNew) +{ + int i = 4 * eID; + int a = Edges[i + 2], b = Edges[i + 3]; + if (a == tOld) { + if (tNew == InvalidID) + { + Edges[i + 2] = b; + Edges[i + 3] = InvalidID; + } + else + { + Edges[i + 2] = tNew; + } + return 0; + } + else if (b == tOld) + { + Edges[i + 3] = tNew; + return 1; + } + else + { + return -1; + } +} + +int FDynamicMesh3::ReplaceTriangleEdge(int tID, int eOld, int eNew) +{ + int i = 3 * tID; + if (TriangleEdges[i] == eOld) + { + TriangleEdges[i] = eNew; + return 0; + } + else if (TriangleEdges[i + 1] == eOld) + { + TriangleEdges[i + 1] = eNew; + return 1; + } + else if (TriangleEdges[i + 2] == eOld) + { + TriangleEdges[i + 2] = eNew; + return 2; + } + else + { + return -1; + } +} + + + +//! returns edge ID +int FDynamicMesh3::FindTriangleEdge(int tID, int vA, int vB) const +{ + int i = 3 * tID; + int tv0 = Triangles[i], tv1 = Triangles[i + 1]; + if (IndexUtil::SamePairUnordered(tv0, tv1, vA, vB)) return TriangleEdges[3 * tID]; + int tv2 = Triangles[i + 2]; + if (IndexUtil::SamePairUnordered(tv1, tv2, vA, vB)) return TriangleEdges[3 * tID + 1]; + if (IndexUtil::SamePairUnordered(tv2, tv0, vA, vB)) return TriangleEdges[3 * tID + 2]; + return InvalidID; +} + + + + + +int FDynamicMesh3::FindEdge(int vA, int vB) const +{ + check(IsVertex(vA)); + check(IsVertex(vB)); + + // [RMS] edge vertices must be sorted (min,max), + // that means we only need one index-check in inner loop. + // commented out code is robust to incorrect ordering, but slower. + int vO = FMath::Max(vA, vB); + int vI = FMath::Min(vA, vB); + for (int eid : VertexEdgeLists.Values(vI)) + { + if (Edges[4 * eid + 1] == vO) + { + //if (DoesEdgeHaveVertex(eid, vO)) + return eid; + } + } + return InvalidID; + + // this is slower, likely because it creates func<> every time. can we do w/o that? + //return VertexEdgeLists.Find(vI, (eid) => { return Edges[4 * eid + 1] == vO; }, InvalidID); +} + +int FDynamicMesh3::FindEdgeFromTri(int vA, int vB, int tID) const +{ + int i = 3 * tID; + int t0 = Triangles[i], t1 = Triangles[i + 1]; + if (IndexUtil::SamePairUnordered(vA, vB, t0, t1)) + { + return TriangleEdges[i]; + } + int t2 = Triangles[i + 2]; + if (IndexUtil::SamePairUnordered(vA, vB, t1, t2)) + { + return TriangleEdges[i + 1]; + } + if (IndexUtil::SamePairUnordered(vA, vB, t2, t0)) + { + return TriangleEdges[i + 2]; + } + return InvalidID; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Edits.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Edits.cpp new file mode 100644 index 000000000000..630333f0f72f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Edits.cpp @@ -0,0 +1,1837 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + + + + +int FDynamicMesh3::AppendVertex(const FVertexInfo& VtxInfo) +{ + int vid = VertexRefCounts.Allocate(); + int i = 3 * vid; + Vertices.InsertAt(VtxInfo.Position[2], i + 2); + Vertices.InsertAt(VtxInfo.Position[1], i + 1); + Vertices.InsertAt(VtxInfo.Position[0], i); + + if (HasVertexNormals()) + { + FVector3f n = (VtxInfo.bHaveN) ? VtxInfo.Normal : FVector3f::UnitY(); + VertexNormals->InsertAt(n[2], i + 2); + VertexNormals->InsertAt(n[1], i + 1); + VertexNormals->InsertAt(n[0], i); + } + + if (HasVertexColors()) + { + FVector3f c = (VtxInfo.bHaveC) ? VtxInfo.Color : FVector3f::One(); + VertexColors->InsertAt(c[2], i + 2); + VertexColors->InsertAt(c[1], i + 1); + VertexColors->InsertAt(c[0], i); + } + + if (HasVertexUVs()) + { + FVector2f u = (VtxInfo.bHaveUV) ? VtxInfo.UV : FVector2f::Zero(); + int j = 2 * vid; + VertexUVs->InsertAt(u[1], j + 1); + VertexUVs->InsertAt(u[0], j); + } + + AllocateEdgesList(vid); + + UpdateTimeStamp(true, true); + return vid; +} + + + +int FDynamicMesh3::AppendVertex(const FDynamicMesh3& from, int fromVID) +{ + int bi = 3 * fromVID; + + int vid = VertexRefCounts.Allocate(); + int i = 3 * vid; + Vertices.InsertAt(from.Vertices[bi + 2], i + 2); + Vertices.InsertAt(from.Vertices[bi + 1], i + 1); + Vertices.InsertAt(from.Vertices[bi], i); + if (HasVertexNormals()) + { + if (from.HasVertexNormals()) + { + VertexNormals->InsertAt((*from.VertexNormals)[bi + 2], i + 2); + VertexNormals->InsertAt((*from.VertexNormals)[bi + 1], i + 1); + VertexNormals->InsertAt((*from.VertexNormals)[bi], i); + } + else + { + VertexNormals->InsertAt(0, i + 2); + VertexNormals->InsertAt(1, i + 1); // y-up + VertexNormals->InsertAt(0, i); + } + } + + if (HasVertexColors()) + { + if (from.HasVertexColors()) + { + VertexColors->InsertAt((*from.VertexColors)[bi + 2], i + 2); + VertexColors->InsertAt((*from.VertexColors)[bi + 1], i + 1); + VertexColors->InsertAt((*from.VertexColors)[bi], i); + } + else + { + VertexColors->InsertAt(1, i + 2); + VertexColors->InsertAt(1, i + 1); // white + VertexColors->InsertAt(1, i); + } + } + + if (HasVertexUVs()) + { + int j = 2 * vid; + if (from.HasVertexUVs()) + { + int bj = 2 * fromVID; + VertexUVs->InsertAt((*from.VertexUVs)[bj + 1], j + 1); + VertexUVs->InsertAt((*from.VertexUVs)[bj], j); + } + else + { + VertexUVs->InsertAt(0, j + 1); + VertexUVs->InsertAt(0, j); + } + } + + AllocateEdgesList(vid); + + UpdateTimeStamp(true, true); + return vid; +} + + + +EMeshResult FDynamicMesh3::InsertVertex(int vid, const FVertexInfo& info, bool bUnsafe) +{ + if (VertexRefCounts.IsValid(vid)) + { + return EMeshResult::Failed_VertexAlreadyExists; + } + + bool bOK = (bUnsafe) ? VertexRefCounts.AllocateAtUnsafe(vid) : + VertexRefCounts.AllocateAt(vid); + if (bOK == false) + { + return EMeshResult::Failed_CannotAllocateVertex; + } + + int i = 3 * vid; + Vertices.InsertAt(info.Position[2], i + 2); + Vertices.InsertAt(info.Position[1], i + 1); + Vertices.InsertAt(info.Position[0], i); + + if (HasVertexNormals()) + { + FVector3f n = (info.bHaveN) ? info.Normal : FVector3f::UnitY(); + VertexNormals->InsertAt(n[2], i + 2); + VertexNormals->InsertAt(n[1], i + 1); + VertexNormals->InsertAt(n[0], i); + } + + if (HasVertexColors()) + { + FVector3f c = (info.bHaveC) ? info.Color : FVector3f::One(); + VertexColors->InsertAt(c[2], i + 2); + VertexColors->InsertAt(c[1], i + 1); + VertexColors->InsertAt(c[0], i); + } + + if (HasVertexUVs()) + { + FVector2f u = (info.bHaveUV) ? info.UV : FVector2f::Zero(); + int j = 2 * vid; + VertexUVs->InsertAt(u[1], j + 1); + VertexUVs->InsertAt(u[0], j); + } + + AllocateEdgesList(vid); + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + +int FDynamicMesh3::AppendTriangle(const FIndex3i& tv, int gid) +{ + if (IsVertex(tv[0]) == false || IsVertex(tv[1]) == false || IsVertex(tv[2]) == false) + { + check(false); + return InvalidID; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) + { + check(false); + return InvalidID; + } + + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = FindEdge(tv[0], tv[1]); + int e1 = FindEdge(tv[1], tv[2]); + int e2 = FindEdge(tv[2], tv[0]); + if ((e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (e2 != InvalidID && IsBoundaryEdge(e2) == false)) + { + return NonManifoldID; + } + + bool bHasGroups = HasTriangleGroups(); // have to check before changing .triangles + + // now safe to insert triangle + int tid = TriangleRefCounts.Allocate(); + int i = 3 * tid; + Triangles.InsertAt(tv[2], i + 2); + Triangles.InsertAt(tv[1], i + 1); + Triangles.InsertAt(tv[0], i); + if (bHasGroups) + { + TriangleGroups->InsertAt(gid, tid); + GroupIDCounter = FMath::Max(GroupIDCounter, gid + 1); + } + + // increment ref counts and update/create edges + VertexRefCounts.Increment(tv[0]); + VertexRefCounts.Increment(tv[1]); + VertexRefCounts.Increment(tv[2]); + + AddTriangleEdge(tid, tv[0], tv[1], 0, e0); + AddTriangleEdge(tid, tv[1], tv[2], 1, e1); + AddTriangleEdge(tid, tv[2], tv[0], 2, e2); + + if (HasAttributes()) + { + Attributes()->OnNewTriangle(tid, false); + } + + UpdateTimeStamp(true, true); + return tid; +} + + + + +EMeshResult FDynamicMesh3::InsertTriangle(int tid, const FIndex3i& tv, int gid, bool bUnsafe) +{ + if (TriangleRefCounts.IsValid(tid)) + { + return EMeshResult::Failed_TriangleAlreadyExists; + } + + if (IsVertex(tv[0]) == false || IsVertex(tv[1]) == false || IsVertex(tv[2]) == false) + { + check(false); + return EMeshResult::Failed_NotAVertex; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) + { + check(false); + return EMeshResult::Failed_InvalidNeighbourhood; + } + + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = FindEdge(tv[0], tv[1]); + int e1 = FindEdge(tv[1], tv[2]); + int e2 = FindEdge(tv[2], tv[0]); + if ((e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (e2 != InvalidID && IsBoundaryEdge(e2) == false)) + { + return EMeshResult::Failed_WouldCreateNonmanifoldEdge; + } + + bool bOK = (bUnsafe) ? TriangleRefCounts.AllocateAtUnsafe(tid) : + TriangleRefCounts.AllocateAt(tid); + if (bOK == false) + { + return EMeshResult::Failed_CannotAllocateTriangle; + } + + // now safe to insert triangle + int i = 3 * tid; + Triangles.InsertAt(tv[2], i + 2); + Triangles.InsertAt(tv[1], i + 1); + Triangles.InsertAt(tv[0], i); + if (HasTriangleGroups()) + { + TriangleGroups->InsertAt(gid, tid); + GroupIDCounter = FMath::Max(GroupIDCounter, gid + 1); + } + + // increment ref counts and update/create edges + VertexRefCounts.Increment(tv[0]); + VertexRefCounts.Increment(tv[1]); + VertexRefCounts.Increment(tv[2]); + + AddTriangleEdge(tid, tv[0], tv[1], 0, e0); + AddTriangleEdge(tid, tv[1], tv[2], 1, e1); + AddTriangleEdge(tid, tv[2], tv[0], 2, e2); + + if (HasAttributes()) + { + Attributes()->OnNewTriangle(tid, true); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + + +void FDynamicMesh3::CompactInPlace(FCompactMaps* CompactInfo) +{ + // @todo support this + check(HasAttributes() == false); + + //IndexMap mapV = (bComputeCompactInfo) ? IndexMap(MaxVertexID, VertexCount) : nullptr; + + // find first free vertex, and last used vertex + int iLastV = MaxVertexID() - 1, iCurV = 0; + while (iLastV >= 0 && VertexRefCounts.IsValidUnsafe(iLastV) == false) + { + iLastV--; + } + while (iCurV < iLastV && VertexRefCounts.IsValidUnsafe(iCurV)) + { + iCurV++; + } + + TDynamicVector &vref = VertexRefCounts.GetRawRefCountsUnsafe(); + + while (iCurV < iLastV) + { + int kc = iCurV * 3, kl = iLastV * 3; + Vertices[kc] = Vertices[kl]; Vertices[kc + 1] = Vertices[kl + 1]; Vertices[kc + 2] = Vertices[kl + 2]; + if (HasVertexNormals()) + { + (*VertexNormals)[kc] = (*VertexNormals)[kl]; + (*VertexNormals)[kc + 1] = (*VertexNormals)[kl + 1]; + (*VertexNormals)[kc + 2] = (*VertexNormals)[kl + 2]; + } + if (HasVertexColors()) + { + (*VertexColors)[kc] = (*VertexColors)[kl]; + (*VertexColors)[kc + 1] = (*VertexColors)[kl + 1]; + (*VertexColors)[kc + 2] = (*VertexColors)[kl + 2]; + } + if (HasVertexUVs()) + { + int ukc = iCurV * 2, ukl = iLastV * 2; + (*VertexUVs)[ukc] = (*VertexUVs)[ukl]; + (*VertexUVs)[ukc + 1] = (*VertexUVs)[ukl + 1]; + } + + for (int eid : VertexEdgeLists.Values(iLastV)) + { + // replace vertex in edges + ReplaceEdgeVertex(eid, iLastV, iCurV); + + // replace vertex in triangles + int t0 = Edges[4 * eid + 2]; + ReplaceTriangleVertex(t0, iLastV, iCurV); + int t1 = Edges[4 * eid + 3]; + if (t1 != InvalidID) + { + ReplaceTriangleVertex(t1, iLastV, iCurV); + } + } + + // shift vertex refcount to position + vref[iCurV] = vref[iLastV]; + vref[iLastV] = FRefCountVector::INVALID_REF_COUNT; + + // move edge list + VertexEdgeLists.Move(iLastV, iCurV); + + if (CompactInfo != nullptr) + { + CompactInfo->MapV[iLastV] = iCurV; + } + + // move cur forward one, last back one, and then search for next valid + iLastV--; iCurV++; + while (iLastV >= 0 && VertexRefCounts.IsValidUnsafe(iLastV) == false) + { + iLastV--; + } + while (iCurV < iLastV && VertexRefCounts.IsValidUnsafe(iCurV)) + { + iCurV++; + } + } + + // trim vertices data structures + VertexRefCounts.Trim(VertexCount()); + Vertices.Resize(VertexCount() * 3); + if (HasVertexNormals()) + { + VertexNormals->Resize(VertexCount() * 3); + } + if (HasVertexColors()) + { + VertexColors->Resize(VertexCount() * 3); + } + if (HasVertexUVs()) + { + VertexUVs->Resize(VertexCount() * 2); + } + + // [TODO] VertexEdgeLists!!! + + /** shift triangles **/ + + // find first free triangle, and last valid triangle + int iLastT = MaxTriangleID() - 1, iCurT = 0; + while (iLastT >= 0 && TriangleRefCounts.IsValidUnsafe(iLastT) == false) + { + iLastT--; + } + while (iCurT < iLastT && TriangleRefCounts.IsValidUnsafe(iCurT)) + { + iCurT++; + } + + TDynamicVector &tref = TriangleRefCounts.GetRawRefCountsUnsafe(); + + while (iCurT < iLastT) + { + int kc = iCurT * 3, kl = iLastT * 3; + + // shift triangle + for (int j = 0; j < 3; ++j) + { + Triangles[kc + j] = Triangles[kl + j]; + TriangleEdges[kc + j] = TriangleEdges[kl + j]; + } + if (HasTriangleGroups()) + { + (*TriangleGroups)[iCurT] = (*TriangleGroups)[iLastT]; + } + + // update edges + for (int j = 0; j < 3; ++j) + { + int eid = TriangleEdges[kc + j]; + ReplaceEdgeTriangle(eid, iLastT, iCurT); + } + + // shift triangle refcount to position + tref[iCurT] = tref[iLastT]; + tref[iLastT] = FRefCountVector::INVALID_REF_COUNT; + + // move cur forward one, last back one, and then search for next valid + iLastT--; iCurT++; + while (iLastT >= 0 && TriangleRefCounts.IsValidUnsafe(iLastT) == false) + { + iLastT--; + } + while (iCurT < iLastT && TriangleRefCounts.IsValidUnsafe(iCurT)) + { + iCurT++; + } + } + + // trim triangles data structures + TriangleRefCounts.Trim(TriangleCount()); + Triangles.Resize(TriangleCount() * 3); + TriangleEdges.Resize(TriangleCount() * 3); + if (HasTriangleGroups()) + { + TriangleGroups->Resize(TriangleCount()); + } + + /** shift edges **/ + + // find first free edge, and last used edge + int iLastE = MaxEdgeID() - 1, iCurE = 0; + while (iLastE >= 0 && EdgeRefCounts.IsValidUnsafe(iLastE) == false) + { + iLastE--; + } + while (iCurE < iLastE && EdgeRefCounts.IsValidUnsafe(iCurE)) + { + iCurE++; + } + + TDynamicVector &eref = EdgeRefCounts.GetRawRefCountsUnsafe(); + + while (iCurE < iLastE) + { + int kc = iCurE * 4, kl = iLastE * 4; + + // shift edge + for (int j = 0; j < 4; ++j) + { + Edges[kc + j] = Edges[kl + j]; + } + + // replace edge in vertex edges lists + int v0 = Edges[kc], v1 = Edges[kc + 1]; + VertexEdgeLists.Replace(v0, [iLastE](int eid) { return eid == iLastE; }, iCurE); + VertexEdgeLists.Replace(v1, [iLastE](int eid) { return eid == iLastE; }, iCurE); + + // replace edge in triangles + ReplaceTriangleEdge(Edges[kc + 2], iLastE, iCurE); + if (Edges[kc + 3] != InvalidID) + { + ReplaceTriangleEdge(Edges[kc + 3], iLastE, iCurE); + } + + // shift triangle refcount to position + eref[iCurE] = eref[iLastE]; + eref[iLastE] = FRefCountVector::INVALID_REF_COUNT; + + // move cur forward one, last back one, and then search for next valid + iLastE--; iCurE++; + while (iLastE >= 0 && EdgeRefCounts.IsValidUnsafe(iLastE) == false) + { + iLastE--; + } + while (iCurE < iLastE && EdgeRefCounts.IsValidUnsafe(iCurE)) + { + iCurE++; + } + } + + // trim edge data structures + EdgeRefCounts.Trim(EdgeCount()); + Edges.Resize(EdgeCount() * 4); +} + + + + + + + +EMeshResult FDynamicMesh3::ReverseTriOrientation(int tID) +{ + if (!IsTriangle(tID)) + { + return EMeshResult::Failed_NotATriangle; + } + ReverseTriOrientationInternal(tID); + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + +void FDynamicMesh3::ReverseTriOrientationInternal(int tID) +{ + FIndex3i t = GetTriangle(tID); + SetTriangleInternal(tID, t[1], t[0], t[2]); + FIndex3i te = GetTriEdges(tID); + SetTriangleEdgesInternal(tID, te[0], te[2], te[1]); + if (HasAttributes()) + { + Attributes()->OnReverseTriOrientation(tID); + } +} + +void FDynamicMesh3::ReverseOrientation(bool bFlipNormals) +{ + for (int tid : TriangleIndicesItr()) + { + ReverseTriOrientationInternal(tid); + } + if (bFlipNormals && HasVertexNormals()) + { + for (int vid : VertexIndicesItr()) + { + int i = 3 * vid; + (*VertexNormals)[i] = -(*VertexNormals)[i]; + (*VertexNormals)[i + 1] = -(*VertexNormals)[i + 1]; + (*VertexNormals)[i + 2] = -(*VertexNormals)[i + 2]; + } + } + UpdateTimeStamp(true, true); +} + + + + + + +EMeshResult FDynamicMesh3::RemoveVertex(int vID, bool bRemoveAllTriangles, bool bPreserveManifold) +{ + if (VertexRefCounts.IsValid(vID) == false) + { + return EMeshResult::Failed_NotAVertex; + } + + if (bRemoveAllTriangles) + { + // if any one-ring vtx is a boundary vtx and one of its outer-ring edges is an + // interior edge then we will create a bowtie if we remove that triangle + if (bPreserveManifold) + { + for (int tid : VtxTrianglesItr(vID)) + { + FIndex3i tri = GetTriangle(tid); + int j = IndexUtil::FindTriIndex(vID, tri); + int oa = tri[(j + 1) % 3], ob = tri[(j + 2) % 3]; + int eid = FindEdge(oa, ob); + if (IsBoundaryEdge(eid)) + { + continue; + } + if (IsBoundaryVertex(oa) || IsBoundaryVertex(ob)) + { + return EMeshResult::Failed_WouldCreateBowtie; + } + } + } + + TArray tris; + GetVtxTriangles(vID, tris, true); + for (int tID : tris) + { + EMeshResult result = RemoveTriangle(tID, false, bPreserveManifold); + if (result != EMeshResult::Ok) + { + return result; + } + } + } + + if (VertexRefCounts.GetRefCount(vID) != 1) + { + return EMeshResult::Failed_VertexStillReferenced; + } + + VertexRefCounts.Decrement(vID); + check(VertexRefCounts.IsValid(vID) == false); + VertexEdgeLists.Clear(vID); + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + +EMeshResult FDynamicMesh3::RemoveTriangle(int tID, bool bRemoveIsolatedVertices, bool bPreserveManifold) +{ + if (!TriangleRefCounts.IsValid(tID)) + { + check(false); + return EMeshResult::Failed_NotATriangle; + } + + FIndex3i tv = GetTriangle(tID); + FIndex3i te = GetTriEdges(tID); + + // if any tri vtx is a boundary vtx connected to two interior edges, then + // we cannot remove this triangle because it would create a bowtie vertex! + // (that vtx already has 2 boundary edges, and we would add two more) + if (bPreserveManifold) + { + for (int j = 0; j < 3; ++j) + { + if (IsBoundaryVertex(tv[j])) + { + if (IsBoundaryEdge(te[j]) == false && IsBoundaryEdge(te[(j + 2) % 3]) == false) + { + return EMeshResult::Failed_WouldCreateBowtie; + } + } + } + } + + // Remove triangle from its edges. if edge has no triangles left, + // then it is removed. + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + ReplaceEdgeTriangle(eid, tID, InvalidID); + if (Edges[4 * eid + 2] == InvalidID) + { + int a = Edges[4 * eid]; + VertexEdgeLists.Remove(a, eid); + + int b = Edges[4 * eid + 1]; + VertexEdgeLists.Remove(b, eid); + + EdgeRefCounts.Decrement(eid); + } + } + + // free this triangle + TriangleRefCounts.Decrement(tID); + check(TriangleRefCounts.IsValid(tID) == false); + + // Decrement vertex refcounts. If any hit 1 and we got remove-isolated flag, + // we need to remove that vertex + for (int j = 0; j < 3; ++j) + { + int vid = tv[j]; + VertexRefCounts.Decrement(vid); + if (bRemoveIsolatedVertices && VertexRefCounts.GetRefCount(vid) == 1) + { + VertexRefCounts.Decrement(vid); + check(VertexRefCounts.IsValid(vid) == false); + VertexEdgeLists.Clear(vid); + } + } + + if (HasAttributes()) + { + Attributes()->OnRemoveTriangle(tID, bRemoveIsolatedVertices); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + +EMeshResult FDynamicMesh3::SetTriangle(int tID, const FIndex3i& newv, bool bRemoveIsolatedVertices) +{ + // @todo support this. + check(HasAttributes() == false); + + FIndex3i tv = GetTriangle(tID); + FIndex3i te = GetTriEdges(tID); + if (tv[0] == newv[0] && tv[1] == newv[1]) + { + te[0] = -1; + } + if (tv[1] == newv[1] && tv[2] == newv[2]) + { + te[1] = -1; + } + if (tv[2] == newv[2] && tv[0] == newv[0]) + { + te[2] = -1; + } + + if (!TriangleRefCounts.IsValid(tID)) + { + check(false); + return EMeshResult::Failed_NotATriangle; + } + if (IsVertex(newv[0]) == false || IsVertex(newv[1]) == false || IsVertex(newv[2]) == false) + { + check(false); + return EMeshResult::Failed_NotAVertex; + } + if (newv[0] == newv[1] || newv[0] == newv[2] || newv[1] == newv[2]) + { + check(false); + return EMeshResult::Failed_BrokenTopology; + } + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = FindEdge(newv[0], newv[1]); + int e1 = FindEdge(newv[1], newv[2]); + int e2 = FindEdge(newv[2], newv[0]); + if ((te[0] != -1 && e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (te[1] != -1 && e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (te[2] != -1 && e2 != InvalidID && IsBoundaryEdge(e2) == false)) + { + return EMeshResult::Failed_BrokenTopology; + } + + + // [TODO] check that we are not going to create invalid stuff... + + // Remove triangle from its edges. if edge has no triangles left, then it is removed. + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + if (eid == -1) // we don't need to modify this edge + { + continue; + } + ReplaceEdgeTriangle(eid, tID, InvalidID); + if (Edges[4 * eid + 2] == InvalidID) + { + int a = Edges[4 * eid]; + VertexEdgeLists.Remove(a, eid); + + int b = Edges[4 * eid + 1]; + VertexEdgeLists.Remove(b, eid); + + EdgeRefCounts.Decrement(eid); + } + } + + // Decrement vertex refcounts. If any hit 1 and we got remove-isolated flag, + // we need to remove that vertex + for (int j = 0; j < 3; ++j) + { + int vid = tv[j]; + if (vid == newv[j]) // we don't need to modify this vertex + { + continue; + } + VertexRefCounts.Decrement(vid); + if (bRemoveIsolatedVertices && VertexRefCounts.GetRefCount(vid) == 1) + { + VertexRefCounts.Decrement(vid); + check(VertexRefCounts.IsValid(vid) == false); + VertexEdgeLists.Clear(vid); + } + } + + + // ok now re-insert with vertices + int i = 3 * tID; + for (int j = 0; j < 3; ++j) + { + if (newv[j] != tv[j]) + { + Triangles[i + j] = newv[j]; + VertexRefCounts.Increment(newv[j]); + } + } + + if (te[0] != -1) + { + AddTriangleEdge(tID, newv[0], newv[1], 0, e0); + } + if (te[1] != -1) + { + AddTriangleEdge(tID, newv[1], newv[2], 1, e1); + } + if (te[2] != -1) + { + AddTriangleEdge(tID, newv[2], newv[0], 2, e2); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + +EMeshResult FDynamicMesh3::SplitEdge(int eab, FEdgeSplitInfo& SplitInfo, double split_t) +{ + SplitInfo = FEdgeSplitInfo(); + + if (!IsEdge(eab)) + { + return EMeshResult::Failed_NotAnEdge; + } + + // look up primary edge & triangle + int eab_i = 4 * eab; + int a = Edges[eab_i], b = Edges[eab_i + 1]; + int t0 = Edges[eab_i + 2]; + if (t0 == InvalidID) + { + return EMeshResult::Failed_BrokenTopology; + } + FIndex3i T0tv = GetTriangle(t0); + int c = IndexUtil::OrientTriEdgeAndFindOtherVtx(a, b, T0tv); + if (VertexRefCounts.GetRawRefCount(c) > 32764) + { + return EMeshResult::Failed_HitValenceLimit; + } + if (a != Edges[eab_i]) + { + split_t = 1.0 - split_t; // if we flipped a/b order we need to reverse t + } + + SplitInfo.OriginalEdge = eab; + SplitInfo.OriginalVertices = FIndex2i(a, b); // this is the oriented a,b + SplitInfo.OriginalTriangles = FIndex2i(t0, InvalidID); + SplitInfo.SplitT = split_t; + + // quite a bit of code is duplicated between boundary and non-boundary case, but it + // is too hard to follow later if we factor it out... + if (IsBoundaryEdge(eab)) + { + // create vertex + FVector3d vNew = FVector3d::Lerp(GetVertex(a), GetVertex(b), split_t); + int f = AppendVertex(vNew); + if (HasVertexNormals()) + { + SetVertexNormal(f, FVector3f::Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized()); + } + if (HasVertexColors()) + { + SetVertexColor(f, FVector3f::Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t)); + } + if (HasVertexUVs()) + { + SetVertexUV(f, FVector2f::Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t)); + } + + // look up edge bc, which needs to be modified + FIndex3i T0te = GetTriEdges(t0); + int ebc = T0te[IndexUtil::FindEdgeIndexInTri(b, c, T0tv)]; + + // rewrite existing triangle + ReplaceTriangleVertex(t0, b, f); + + // add second triangle + int t2 = AddTriangleInternal(f, b, c, InvalidID, InvalidID, InvalidID); + if (HasTriangleGroups()) + { + TriangleGroups->InsertAt((*TriangleGroups)[t0], t2); + } + + // rewrite edge bc, create edge af + ReplaceEdgeTriangle(ebc, t0, t2); + int eaf = eab; + ReplaceEdgeVertex(eaf, b, f); + VertexEdgeLists.Remove(b, eab); + VertexEdgeLists.Insert(f, eaf); + + // create edges fb and fc + int efb = AddEdgeInternal(f, b, t2); + int efc = AddEdgeInternal(f, c, t0, t2); + + // update triangle edge-nbrs + ReplaceTriangleEdge(t0, ebc, efc); + SetTriangleEdgesInternal(t2, efb, ebc, efc); + + // update vertex refcounts + VertexRefCounts.Increment(c); + VertexRefCounts.Increment(f, 2); + + SplitInfo.bIsBoundary = true; + SplitInfo.OtherVertices = FIndex2i(c, InvalidID); + SplitInfo.NewVertex = f; + SplitInfo.NewEdges = FIndex3i(efb, efc, InvalidID); + SplitInfo.NewTriangles = FIndex2i(t2, InvalidID); + + if (HasAttributes()) + { + Attributes()->OnSplitEdge(SplitInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; + + } + else // interior triangle branch + { + // look up other triangle + int t1 = Edges[eab_i + 3]; + SplitInfo.OriginalTriangles.B = t1; + FIndex3i T1tv = GetTriangle(t1); + int d = IndexUtil::FindTriOtherVtx(a, b, T1tv); + if (VertexRefCounts.GetRawRefCount(d) > 32764) + { + return EMeshResult::Failed_HitValenceLimit; + } + + // create vertex + FVector3d vNew = FVector3d::Lerp(GetVertex(a), GetVertex(b), split_t); + int f = AppendVertex(vNew); + if (HasVertexNormals()) + { + SetVertexNormal(f, FVector3f::Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized()); + } + if (HasVertexColors()) + { + SetVertexColor(f, FVector3f::Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t)); + } + if (HasVertexUVs()) + { + SetVertexUV(f, FVector2f::Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t)); + } + + // look up edges that we are going to need to update + // [TODO OPT] could use ordering to reduce # of compares here + FIndex3i T0te = GetTriEdges(t0); + int ebc = T0te[IndexUtil::FindEdgeIndexInTri(b, c, T0tv)]; + FIndex3i T1te = GetTriEdges(t1); + int edb = T1te[IndexUtil::FindEdgeIndexInTri(d, b, T1tv)]; + + // rewrite existing triangles + ReplaceTriangleVertex(t0, b, f); + ReplaceTriangleVertex(t1, b, f); + + // add two triangles to close holes we just created + int t2 = AddTriangleInternal(f, b, c, InvalidID, InvalidID, InvalidID); + int t3 = AddTriangleInternal(f, d, b, InvalidID, InvalidID, InvalidID); + if (HasTriangleGroups()) + { + TriangleGroups->InsertAt((*TriangleGroups)[t0], t2); + TriangleGroups->InsertAt((*TriangleGroups)[t1], t3); + } + + // update the edges we found above, to point to triangles + ReplaceEdgeTriangle(ebc, t0, t2); + ReplaceEdgeTriangle(edb, t1, t3); + + // edge eab became eaf + int eaf = eab; //Edge * eAF = eAB; + ReplaceEdgeVertex(eaf, b, f); + + // update a/b/f vertex-edges + VertexEdgeLists.Remove(b, eab); + VertexEdgeLists.Insert(f, eaf); + + // create edges connected to f (also updates vertex-edges) + int efb = AddEdgeInternal(f, b, t2, t3); + int efc = AddEdgeInternal(f, c, t0, t2); + int edf = AddEdgeInternal(d, f, t1, t3); + + // update triangle edge-nbrs + ReplaceTriangleEdge(t0, ebc, efc); + ReplaceTriangleEdge(t1, edb, edf); + SetTriangleEdgesInternal(t2, efb, ebc, efc); + SetTriangleEdgesInternal(t3, edf, edb, efb); + + // update vertex refcounts + VertexRefCounts.Increment(c); + VertexRefCounts.Increment(d); + VertexRefCounts.Increment(f, 4); + + SplitInfo.bIsBoundary = false; + SplitInfo.OtherVertices = FIndex2i(c, d); + SplitInfo.NewVertex = f; + SplitInfo.NewEdges = FIndex3i(efb, efc, edf); + SplitInfo.NewTriangles = FIndex2i(t2, t3); + + if (HasAttributes()) + { + Attributes()->OnSplitEdge(SplitInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; + } + +} + + +EMeshResult FDynamicMesh3::SplitEdge(int vA, int vB, FEdgeSplitInfo& SplitInfo) +{ + int eid = FindEdge(vA, vB); + if (eid == InvalidID) + { + SplitInfo = FEdgeSplitInfo(); + return EMeshResult::Failed_NotAnEdge; + } + return SplitEdge(eid, SplitInfo); +} + + + + + + + + +EMeshResult FDynamicMesh3::FlipEdge(int eab, FEdgeFlipInfo& FlipInfo) +{ + FlipInfo = FEdgeFlipInfo(); + + if (!IsEdge(eab)) + { + return EMeshResult::Failed_NotAnEdge; + } + if (IsBoundaryEdge(eab)) + { + return EMeshResult::Failed_IsBoundaryEdge; + } + + // find oriented edge [a,b], tris t0,t1, and other verts c in t0, d in t1 + int eab_i = 4 * eab; + int a = Edges[eab_i], b = Edges[eab_i + 1]; + int t0 = Edges[eab_i + 2], t1 = Edges[eab_i + 3]; + FIndex3i T0tv = GetTriangle(t0), T1tv = GetTriangle(t1); + int c = IndexUtil::OrientTriEdgeAndFindOtherVtx(a, b, T0tv); + int d = IndexUtil::FindTriOtherVtx(a, b, T1tv); + if (c == InvalidID || d == InvalidID) + { + return EMeshResult::Failed_BrokenTopology; + } + + int flipped = FindEdge(c, d); + if (flipped != InvalidID) + { + return EMeshResult::Failed_FlippedEdgeExists; + } + + // find edges bc, ca, ad, db + int ebc = FindTriangleEdge(t0, b, c); + int eca = FindTriangleEdge(t0, c, a); + int ead = FindTriangleEdge(t1, a, d); + int edb = FindTriangleEdge(t1, d, b); + + // update triangles + SetTriangleInternal(t0, c, d, b); + SetTriangleInternal(t1, d, c, a); + + // update edge AB, which becomes flipped edge CD + SetEdgeVerticesInternal(eab, c, d); + SetEdgeTrianglesInternal(eab, t0, t1); + int ecd = eab; + + // update the two other edges whose triangle nbrs have changed + if (ReplaceEdgeTriangle(eca, t0, t1) == -1) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: first ReplaceEdgeTriangle failed")); + return EMeshResult::Failed_UnrecoverableError; + } + if (ReplaceEdgeTriangle(edb, t1, t0) == -1) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: second ReplaceEdgeTriangle failed")); + return EMeshResult::Failed_UnrecoverableError; + } + + // update triangle nbr lists (these are edges) + SetTriangleEdgesInternal(t0, ecd, edb, ebc); + SetTriangleEdgesInternal(t1, ecd, eca, ead); + + // remove old eab from verts a and b, and Decrement ref counts + if (VertexEdgeLists.Remove(a, eab) == false) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: first edge list remove failed")); + return EMeshResult::Failed_UnrecoverableError; + } + if (VertexEdgeLists.Remove(b, eab) == false) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: second edge list remove failed")); + return EMeshResult::Failed_UnrecoverableError; + } + VertexRefCounts.Decrement(a); + VertexRefCounts.Decrement(b); + if (IsVertex(a) == false || IsVertex(b) == false) + { + checkf(false, TEXT("FDynamicMesh3.FlipEdge: either a or b is not a vertex?")); + return EMeshResult::Failed_UnrecoverableError; + } + + // add edge ecd to verts c and d, and increment ref counts + VertexEdgeLists.Insert(c, ecd); + VertexEdgeLists.Insert(d, ecd); + VertexRefCounts.Increment(c); + VertexRefCounts.Increment(d); + + // success! collect up results + FlipInfo.EdgeID = eab; + FlipInfo.OriginalVerts = FIndex2i(a, b); + FlipInfo.OpposingVerts = FIndex2i(c, d); + FlipInfo.Triangles = FIndex2i(t0, t1); + + if (HasAttributes()) + { + Attributes()->OnFlipEdge(FlipInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + +EMeshResult FDynamicMesh3::FlipEdge(int vA, int vB, FEdgeFlipInfo& FlipInfo) +{ + int eid = FindEdge(vA, vB); + if (eid == InvalidID) + { + FlipInfo = FEdgeFlipInfo(); + return EMeshResult::Failed_NotAnEdge; + } + return FlipEdge(eid, FlipInfo); +} + + + + + + + + + +EMeshResult FDynamicMesh3::CollapseEdge(int vKeep, int vRemove, double collapse_t, FEdgeCollapseInfo& CollapseInfo) +{ + CollapseInfo = FEdgeCollapseInfo(); + + if (IsVertex(vKeep) == false || IsVertex(vRemove) == false) + { + return EMeshResult::Failed_NotAnEdge; + } + + int b = vKeep; // renaming for sanity. We remove a and keep b + int a = vRemove; + + int eab = FindEdge(a, b); + if (eab == InvalidID) + { + return EMeshResult::Failed_NotAnEdge; + } + + int t0 = Edges[4 * eab + 2]; + if (t0 == InvalidID) + { + return EMeshResult::Failed_BrokenTopology; + } + FIndex3i T0tv = GetTriangle(t0); + int c = IndexUtil::FindTriOtherVtx(a, b, T0tv); + + // look up opposing triangle/vtx if we are not in boundary case + bool bIsBoundaryEdge = false; + int d = InvalidID; + int t1 = Edges[4 * eab + 3]; + if (t1 != InvalidID) + { + FIndex3i T1tv = GetTriangle(t1); + d = IndexUtil::FindTriOtherVtx(a, b, T1tv); + if (c == d) + { + return EMeshResult::Failed_FoundDuplicateTriangle; + } + } + else + { + bIsBoundaryEdge = true; + } + + CollapseInfo.OpposingVerts = FIndex2i(c, d); + + // We cannot collapse if edge lists of a and b share vertices other + // than c and d (because then we will make a triangle [x b b]. + // Unfortunately I cannot see a way to do this more efficiently than brute-force search + // [TODO] if we had tri iterator for a, couldn't we check each tri for b (skipping t0 and t1) ? + int edges_a_count = VertexEdgeLists.GetCount(a); + int eac = InvalidID, ead = InvalidID, ebc = InvalidID, ebd = InvalidID; + for (int eid_a : VertexEdgeLists.Values(a)) + { + int vax = GetOtherEdgeVertex(eid_a, a); + if (vax == c) + { + eac = eid_a; + continue; + } + if (vax == d) + { + ead = eid_a; + continue; + } + if (vax == b) + { + continue; + } + for (int eid_b : VertexEdgeLists.Values(b)) + { + if (GetOtherEdgeVertex(eid_b, b) == vax) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + } + } + + // [RMS] I am not sure this tetrahedron case will detect bowtie vertices. + // But the single-triangle case does + + // We cannot collapse if we have a tetrahedron. In this case a has 3 nbr edges, + // and edge cd exists. But that is not conclusive, we also have to check that + // cd is an internal edge, and that each of its tris contain a or b + if (edges_a_count == 3 && bIsBoundaryEdge == false) + { + int edc = FindEdge(d, c); + int edc_i = 4 * edc; + if (edc != InvalidID && Edges[edc_i + 3] != InvalidID) + { + int edc_t0 = Edges[edc_i + 2]; + int edc_t1 = Edges[edc_i + 3]; + + if ((TriangleHasVertex(edc_t0, a) && TriangleHasVertex(edc_t1, b)) + || (TriangleHasVertex(edc_t0, b) && TriangleHasVertex(edc_t1, a))) + { + return EMeshResult::Failed_CollapseTetrahedron; + } + } + + } + else if (bIsBoundaryEdge == true && IsBoundaryEdge(eac)) + { + // Cannot collapse edge if we are down to a single triangle + ebc = FindEdgeFromTri(b, c, t0); + if (IsBoundaryEdge(ebc)) + { + return EMeshResult::Failed_CollapseTriangle; + } + } + + // [RMS] this was added from C++ version...seems like maybe I never hit + // this case? Conceivably could be porting bug but looking at the + // C++ code I cannot see how we could possibly have caught this case... + // + // cannot collapse an edge where both vertices are boundary vertices + // because that would create a bowtie + // + // NOTE: potentially scanning all edges here...couldn't we + // pick up eac/bc/ad/bd as we go? somehow? + if (bIsBoundaryEdge == false && IsBoundaryVertex(a) && IsBoundaryVertex(b)) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + + // save vertex positions before we delete removed (can defer kept?) + FVector3d KeptPos = GetVertex(vKeep); + FVector3d RemovedPos = GetVertex(vRemove); + + // 1) remove edge ab from vtx b + // 2) find edges ad and ac, and tris tad, tac across those edges (will use later) + // 3) for other edges, replace a with b, and add that edge to b + // 4) replace a with b in all triangles connected to a + int tad = InvalidID, tac = InvalidID; + for (int eid : VertexEdgeLists.Values(a)) + { + int o = GetOtherEdgeVertex(eid, a); + if (o == b) + { + if (VertexEdgeLists.Remove(b, eid) != true) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case o == b")); + return EMeshResult::Failed_UnrecoverableError; + } + } + else if (o == c) + { + if (VertexEdgeLists.Remove(c, eid) != true) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case o == c")); + return EMeshResult::Failed_UnrecoverableError; + } + tac = GetOtherEdgeTriangle(eid, t0); + } + else if (o == d) + { + if (VertexEdgeLists.Remove(d, eid) != true) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case o == c, step 1")); + return EMeshResult::Failed_UnrecoverableError; + } + tad = GetOtherEdgeTriangle(eid, t1); + } + else + { + if (ReplaceEdgeVertex(eid, a, b) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove case else")); + return EMeshResult::Failed_UnrecoverableError; + } + VertexEdgeLists.Insert(b, eid); + } + + // [TODO] perhaps we can already have unique tri list because of the manifold-nbrhood check we need to do... + for (int j = 0; j < 2; ++j) + { + int t_j = Edges[4 * eid + 2 + j]; + if (t_j != InvalidID && t_j != t0 && t_j != t1) + { + if (TriangleHasVertex(t_j, a)) + { + if (ReplaceTriangleVertex(t_j, a, b) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at remove last check")); + return EMeshResult::Failed_UnrecoverableError; + } + VertexRefCounts.Increment(b); + VertexRefCounts.Decrement(a); + } + } + } + } + + if (bIsBoundaryEdge == false) + { + // remove all edges from vtx a, then remove vtx a + VertexEdgeLists.Clear(a); + check(VertexRefCounts.GetRefCount(a) == 3); // in t0,t1, and initial ref + VertexRefCounts.Decrement(a, 3); + check(VertexRefCounts.IsValid(a) == false); + + // remove triangles T0 and T1, and update b/c/d refcounts + TriangleRefCounts.Decrement(t0); + TriangleRefCounts.Decrement(t1); + VertexRefCounts.Decrement(c); + VertexRefCounts.Decrement(d); + VertexRefCounts.Decrement(b, 2); + check(TriangleRefCounts.IsValid(t0) == false); + check(TriangleRefCounts.IsValid(t1) == false); + + // remove edges ead, eab, eac + EdgeRefCounts.Decrement(ead); + EdgeRefCounts.Decrement(eab); + EdgeRefCounts.Decrement(eac); + check(EdgeRefCounts.IsValid(ead) == false); + check(EdgeRefCounts.IsValid(eab) == false); + check(EdgeRefCounts.IsValid(eac) == false); + + // replace t0 and t1 in edges ebd and ebc that we kept + ebd = FindEdgeFromTri(b, d, t1); + if (ebc == InvalidID) // we may have already looked this up + { + ebc = FindEdgeFromTri(b, c, t0); + } + + if (ReplaceEdgeTriangle(ebd, t1, tad) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + + if (ReplaceEdgeTriangle(ebc, t0, tac) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebc replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + + // update tri-edge-nbrs in tad and tac + if (tad != InvalidID) + { + if (ReplaceTriangleEdge(tad, ead, ebd) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + } + if (tac != InvalidID) + { + if (ReplaceTriangleEdge(tac, eac, ebc) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + } + + } + else + { + // boundary-edge path. this is basically same code as above, just not referencing t1/d + + // remove all edges from vtx a, then remove vtx a + VertexEdgeLists.Clear(a); + check(VertexRefCounts.GetRefCount(a) == 2); // in t0 and initial ref + VertexRefCounts.Decrement(a, 2); + check(VertexRefCounts.IsValid(a) == false); + + // remove triangle T0 and update b/c refcounts + TriangleRefCounts.Decrement(t0); + VertexRefCounts.Decrement(c); + VertexRefCounts.Decrement(b); + check(TriangleRefCounts.IsValid(t0) == false); + + // remove edges eab and eac + EdgeRefCounts.Decrement(eab); + EdgeRefCounts.Decrement(eac); + check(EdgeRefCounts.IsValid(eab) == false); + check(EdgeRefCounts.IsValid(eac) == false); + + // replace t0 in edge ebc that we kept + ebc = FindEdgeFromTri(b, c, t0); + if (ReplaceEdgeTriangle(ebc, t0, tac) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=false branch, ebc replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + + // update tri-edge-nbrs in tac + if (tac != InvalidID) + { + if (ReplaceTriangleEdge(tac, eac, ebc) == -1) + { + checkf(false, TEXT("FDynamicMesh3::CollapseEdge: failed at isboundary=true branch, ebd replace triangle")); + return EMeshResult::Failed_UnrecoverableError; + } + } + } + + // set kept vertex to interpolated collapse position + SetVertex(vKeep, FVector3d::Lerp(KeptPos, RemovedPos, collapse_t)); + + CollapseInfo.KeptVertex = vKeep; + CollapseInfo.RemovedVertex = vRemove; + CollapseInfo.bIsBoundary = bIsBoundaryEdge; + CollapseInfo.CollapsedEdge = eab; + CollapseInfo.RemovedTris = FIndex2i(t0, t1); + CollapseInfo.RemovedEdges = FIndex2i(eac, ead); + CollapseInfo.KeptEdges = FIndex2i(ebc, ebd); + CollapseInfo.CollapseT = collapse_t; + + if (HasAttributes()) + { + Attributes()->OnCollapseEdge(CollapseInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + + + + +EMeshResult FDynamicMesh3::MergeEdges(int eKeep, int eDiscard, FMergeEdgesInfo& MergeInfo) +{ + MergeInfo = FMergeEdgesInfo(); + + if (IsEdge(eKeep) == false || IsEdge(eDiscard) == false) + { + return EMeshResult::Failed_NotAnEdge; + } + + FIndex4i edgeinfo_keep = GetEdge(eKeep); + FIndex4i edgeinfo_discard = GetEdge(eDiscard); + if (edgeinfo_keep[3] != InvalidID || edgeinfo_discard[3] != InvalidID) + { + return EMeshResult::Failed_NotABoundaryEdge; + } + + int a = edgeinfo_keep[0], b = edgeinfo_keep[1]; + int tab = edgeinfo_keep[2]; + int eab = eKeep; + int c = edgeinfo_discard[0], d = edgeinfo_discard[1]; + int tcd = edgeinfo_discard[2]; + int ecd = eDiscard; + + // Need to correctly orient a,b and c,d and then check that + // we will not join triangles with incompatible winding order + // I can't see how to do this purely topologically. + // So relying on closest-pairs testing. + IndexUtil::OrientTriEdge(a, b, GetTriangle(tab)); + //int tcd_otherv = OrientTriEdgeAndFindOtherVtx(ref c, ref d, GetTriangle(tcd)); + IndexUtil::OrientTriEdge(c, d, GetTriangle(tcd)); + int x = c; c = d; d = x; // joinable bdry edges have opposing orientations, so flip to get ac and b/d correspondences + FVector3d Va = GetVertex(a), Vb = GetVertex(b), Vc = GetVertex(c), Vd = GetVertex(d); + if (((Va - Vc).SquaredLength() + (Vb - Vd).SquaredLength()) > + ((Va - Vd).SquaredLength() + (Vb - Vc).SquaredLength())) + { + return EMeshResult::Failed_SameOrientation; + } + + // alternative that detects normal flip of triangle tcd. This is a more + // robust geometric test, but fails if tri is degenerate...also more expensive + //FVector3d otherv = GetVertex(tcd_otherv); + //FVector3d Ncd = TMathUtil.FastNormalDirection(GetVertex(c), GetVertex(d), otherv); + //FVector3d Nab = TMathUtil.FastNormalDirection(GetVertex(a), GetVertex(b), otherv); + //if (Ncd.Dot(Nab) < 0) + //return EMeshResult::Failed_SameOrientation; + + MergeInfo.KeptEdge = eab; + MergeInfo.RemovedEdge = ecd; + + // if a/c or b/d are connected by an existing edge, we can't merge + if (a != c && FindEdge(a, c) != InvalidID) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + if (b != d && FindEdge(b, d) != InvalidID) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + + // if vertices at either end already share a common neighbour vertex, and we + // do the merge, that would create duplicate edges. This is something like the + // 'link condition' in edge collapses. + // Note that we have to catch cases where both edges to the shared vertex are + // boundary edges, in that case we will also merge this edge later on + if (a != c) + { + int ea = 0, ec = 0, other_v = (b == d) ? b : -1; + for (int cnbr : VtxVerticesItr(c)) + { + if (cnbr != other_v && (ea = FindEdge(a, cnbr)) != InvalidID) + { + ec = FindEdge(c, cnbr); + if (IsBoundaryEdge(ea) == false || IsBoundaryEdge(ec) == false) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + } + } + } + if (b != d) + { + int eb = 0, ed = 0, other_v = (a == c) ? a : -1; + for (int dnbr : VtxVerticesItr(d)) + { + if (dnbr != other_v && (eb = FindEdge(b, dnbr)) != InvalidID) + { + ed = FindEdge(d, dnbr); + if (IsBoundaryEdge(eb) == false || IsBoundaryEdge(ed) == false) + { + return EMeshResult::Failed_InvalidNeighbourhood; + } + } + } + } + + // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator? + if (a != c) + { + // replace c w/ a in edges and tris connected to c, and move edges to a + for (int eid : VertexEdgeLists.Values(c)) + { + if (eid == eDiscard) + { + continue; + } + ReplaceEdgeVertex(eid, c, a); + short rc = 0; + if (ReplaceTriangleVertex(Edges[4 * eid + 2], c, a) >= 0) + { + rc++; + } + if (Edges[4 * eid + 3] != InvalidID) + { + if (ReplaceTriangleVertex(Edges[4 * eid + 3], c, a) >= 0) + { + rc++; + } + } + VertexEdgeLists.Insert(a, eid); + if (rc > 0) + { + VertexRefCounts.Increment(a, rc); + VertexRefCounts.Decrement(c, rc); + } + } + VertexEdgeLists.Clear(c); + VertexRefCounts.Decrement(c); + MergeInfo.RemovedVerts[0] = c; + } + else + { + VertexEdgeLists.Remove(a, ecd); + MergeInfo.RemovedVerts[0] = InvalidID; + } + MergeInfo.KeptVerts[0] = a; + + if (d != b) + { + // replace d w/ b in edges and tris connected to d, and move edges to b + for (int eid : VertexEdgeLists.Values(d)) + { + if (eid == eDiscard) + { + continue; + } + ReplaceEdgeVertex(eid, d, b); + short rc = 0; + if (ReplaceTriangleVertex(Edges[4 * eid + 2], d, b) >= 0) + { + rc++; + } + if (Edges[4 * eid + 3] != InvalidID) + { + if (ReplaceTriangleVertex(Edges[4 * eid + 3], d, b) >= 0) + { + rc++; + } + } + VertexEdgeLists.Insert(b, eid); + if (rc > 0) + { + VertexRefCounts.Increment(b, rc); + VertexRefCounts.Decrement(d, rc); + } + + } + VertexEdgeLists.Clear(d); + VertexRefCounts.Decrement(d); + MergeInfo.RemovedVerts[1] = d; + } + else + { + VertexEdgeLists.Remove(b, ecd); + MergeInfo.RemovedVerts[1] = InvalidID; + } + MergeInfo.KeptVerts[1] = b; + + // replace edge cd with edge ab in triangle tcd + ReplaceTriangleEdge(tcd, ecd, eab); + EdgeRefCounts.Decrement(ecd); + + // update edge-tri adjacency + SetEdgeTrianglesInternal(eab, tab, tcd); + + // Once we merge ab to cd, there may be additional edges (now) connected + // to either a or b that are connected to the same vertex on their 'other' side. + // So we now have two boundary edges connecting the same two vertices - disaster! + // We need to find and merge these edges. + // Q: I don't think it is possible to have multiple such edge-pairs at a or b + // But I am not certain...is a bit tricky to handle because we modify edges_v... + MergeInfo.ExtraRemovedEdges = FIndex2i(InvalidID, InvalidID); + MergeInfo.ExtraKeptEdges = MergeInfo.ExtraRemovedEdges; + for (int vi = 0; vi < 2; ++vi) + { + int v1 = a, v2 = c; // vertices of merged edge + if (vi == 1) + { + v1 = b; v2 = d; + } + if (v1 == v2) + { + continue; + } + + TArray edges_v; + GetVertexEdgesList(v1, edges_v); + int Nedges = (int)edges_v.Num(); + bool found = false; + // in this loop, we compare 'other' vert_1 and vert_2 of edges around v1. + // problem case is when vert_1 == vert_2 (ie two edges w/ same other vtx). + //restart_merge_loop: + for (int i = 0; i < Nedges && found == false; ++i) + { + int edge_1 = edges_v[i]; + if (IsBoundaryEdge(edge_1) == false) + { + continue; + } + int vert_1 = GetOtherEdgeVertex(edge_1, v1); + for (int j = i + 1; j < Nedges; ++j) + { + int edge_2 = edges_v[j]; + int vert_2 = GetOtherEdgeVertex(edge_2, v1); + if (vert_1 == vert_2 && IsBoundaryEdge(edge_2)) // if ! boundary here, we are in deep trouble... + { + // replace edge_2 w/ edge_1 in tri, update edge and vtx-edge-nbr lists + int tri_1 = Edges[4 * edge_1 + 2]; + int tri_2 = Edges[4 * edge_2 + 2]; + ReplaceTriangleEdge(tri_2, edge_2, edge_1); + SetEdgeTrianglesInternal(edge_1, tri_1, tri_2); + VertexEdgeLists.Remove(v1, edge_2); + VertexEdgeLists.Remove(vert_1, edge_2); + EdgeRefCounts.Decrement(edge_2); + MergeInfo.ExtraRemovedEdges[vi] = edge_2; + MergeInfo.ExtraKeptEdges[vi] = edge_1; + + //edges_v = VertexEdgeLists_list(v1); // this code allows us to continue checking, ie in case we had + //Nedges = edges_v.Count; // multiple such edges. but I don't think it's possible. + //goto restart_merge_loop; + found = true; // exit outer i loop + break; // exit inner j loop + } + } + } + } + + if (HasAttributes()) + { + Attributes()->OnMergeEdges(MergeInfo); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + + + + + + +EMeshResult FDynamicMesh3::PokeTriangle(int TriangleID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeResult) +{ + PokeResult = FPokeTriangleInfo(); + + if (!IsTriangle(TriangleID)) + { + return EMeshResult::Failed_NotATriangle; + } + + FIndex3i tv = GetTriangle(TriangleID); + FIndex3i te = GetTriEdges(TriangleID); + + // create vertex with interpolated vertex attribs + FVertexInfo vinfo; + GetTriBaryPoint(TriangleID, BaryCoordinates[0], BaryCoordinates[1], BaryCoordinates[2], vinfo); + int center = AppendVertex(vinfo); + + // add in edges to center vtx, do not connect to triangles yet + int eaC = AddEdgeInternal(tv[0], center, -1, -1); + int ebC = AddEdgeInternal(tv[1], center, -1, -1); + int ecC = AddEdgeInternal(tv[2], center, -1, -1); + VertexRefCounts.Increment(tv[0]); + VertexRefCounts.Increment(tv[1]); + VertexRefCounts.Increment(tv[2]); + VertexRefCounts.Increment(center, 3); + + // old triangle becomes tri along first edge + SetTriangleInternal(TriangleID, tv[0], tv[1], center); + SetTriangleEdgesInternal(TriangleID, te[0], ebC, eaC); + + // add two triangles + int t1 = AddTriangleInternal(tv[1], tv[2], center, te[1], ecC, ebC); + int t2 = AddTriangleInternal(tv[2], tv[0], center, te[2], eaC, ecC); + + // second and third edges of original tri have neighbours + ReplaceEdgeTriangle(te[1], TriangleID, t1); + ReplaceEdgeTriangle(te[2], TriangleID, t2); + + // set the triangles for the edges we created above + SetEdgeTrianglesInternal(eaC, TriangleID, t2); + SetEdgeTrianglesInternal(ebC, TriangleID, t1); + SetEdgeTrianglesInternal(ecC, t1, t2); + + // transfer groups + if (HasTriangleGroups()) + { + int g = (*TriangleGroups)[TriangleID]; + TriangleGroups->InsertAt(g, t1); + TriangleGroups->InsertAt(g, t2); + } + + PokeResult.OriginalTriangle = TriangleID; + PokeResult.TriVertices = tv; + PokeResult.NewVertex = center; + PokeResult.NewTriangles = FIndex2i(t1,t2); + PokeResult.NewEdges = FIndex3i(eaC, ebC, ecC); + PokeResult.BaryCoords = BaryCoordinates; + + if (HasAttributes()) + { + Attributes()->OnPokeTriangle(PokeResult); + } + + UpdateTimeStamp(true, true); + return EMeshResult::Ok; +} + + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Queries.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Queries.cpp new file mode 100644 index 000000000000..77d1db3bc356 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMesh3_Queries.cpp @@ -0,0 +1,836 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMesh3.h" + + +FIndex2i FDynamicMesh3::GetEdgeOpposingV(int eID) const +{ + // [TODO] there was a comment here saying this does more work than necessary?? + // ** it is important that verts returned maintain [c,d] order!! + int i = 4 * eID; + int a = Edges[i], b = Edges[i + 1]; + int t0 = Edges[i + 2], t1 = Edges[i + 3]; + int c = IndexUtil::FindTriOtherVtx(a, b, Triangles, t0); + if (t1 != InvalidID) + { + int d = IndexUtil::FindTriOtherVtx(a, b, Triangles, t1); + return FIndex2i(c, d); + } + else + { + return FIndex2i(c, InvalidID); + } +} + + +int FDynamicMesh3::GetVtxBoundaryEdges(int vID, int& e0, int& e1) const +{ + if (VertexRefCounts.IsValid(vID)) + { + int count = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int ei = 4 * eid; + if (Edges[ei + 3] == InvalidID) + { + if (count == 0) + { + e0 = eid; + } + else if (count == 1) + { + e1 = eid; + } + count++; + } + } + return count; + } + check(false); + return -1; +} + + +int FDynamicMesh3::GetAllVtxBoundaryEdges(int vID, TArray& EdgeListOut) const +{ + if (VertexRefCounts.IsValid(vID)) + { + int count = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int ei = 4 * eid; + if (Edges[ei + 3] == InvalidID) + { + EdgeListOut.Add(eid); + count++; + } + } + return count; + } + check(false); + return -1; +} + + + +void FDynamicMesh3::GetVtxNbrhood(int eID, int vID, int& vOther, int& oppV1, int& oppV2, int& t1, int& t2) const +{ + int i = 4 * eID; + vOther = (Edges[i] == vID) ? Edges[i + 1] : Edges[i]; + t1 = Edges[i + 2]; + oppV1 = IndexUtil::FindTriOtherVtx(vID, vOther, Triangles, t1); + t2 = Edges[i + 3]; + if (t2 != InvalidID) + { + oppV2 = IndexUtil::FindTriOtherVtx(vID, vOther, Triangles, t2); + } + else + { + t2 = InvalidID; + } +} + + +int FDynamicMesh3::GetVtxTriangleCount(int vID, bool bBruteForce) const +{ + if (bBruteForce) + { + TArray vTriangles; + if (GetVtxTriangles(vID, vTriangles, false) != EMeshResult::Ok) + { + return -1; + } + return (int)vTriangles.Num(); + } + + if (!IsVertex(vID)) + { + return -1; + } + int N = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int vOther = GetOtherEdgeVertex(eid, vID); + int i = 4 * eid; + int et0 = Edges[i + 2]; + if (TriHasSequentialVertices(et0, vID, vOther)) + { + N++; + } + int et1 = Edges[i + 3]; + if (et1 != InvalidID && TriHasSequentialVertices(et1, vID, vOther)) + { + N++; + } + } + return N; +} + + + +EMeshResult FDynamicMesh3::GetVtxTriangles(int vID, TArray& TrianglesOut, bool bUseOrientation) const +{ + if (!IsVertex(vID)) + { + return EMeshResult::Failed_NotAVertex; + } + + if (bUseOrientation) + { + for (int eid : VertexEdgeLists.Values(vID)) + { + int vOther = GetOtherEdgeVertex(eid, vID); + int i = 4 * eid; + int et0 = Edges[i + 2]; + if (TriHasSequentialVertices(et0, vID, vOther)) + { + TrianglesOut.Add(et0); + } + int et1 = Edges[i + 3]; + if (et1 != InvalidID && TriHasSequentialVertices(et1, vID, vOther)) + { + TrianglesOut.Add(et1); + } + } + } + else + { + // brute-force method + for (int eid : VertexEdgeLists.Values(vID)) + { + int i = 4 * eid; + int t0 = Edges[i + 2]; + TrianglesOut.AddUnique(t0); + + int t1 = Edges[i + 3]; + if (t1 != InvalidID) + { + TrianglesOut.AddUnique(t1); + } + } + } + return EMeshResult::Ok; +} + + + + + +bool FDynamicMesh3::IsBoundaryVertex(int vID) const +{ + check(IsVertex(vID)); + for (int eid : VertexEdgeLists.Values(vID)) + { + if (Edges[4 * eid + 3] == InvalidID) + { + return true; + } + } + return false; +} + + + +bool FDynamicMesh3::IsBoundaryTriangle(int tID) const +{ + check(IsTriangle(tID)); + int i = 3 * tID; + return IsBoundaryEdge(TriangleEdges[i]) || IsBoundaryEdge(TriangleEdges[i + 1]) || IsBoundaryEdge(TriangleEdges[i + 2]); +} + + + + +FIndex2i FDynamicMesh3::GetOrientedBoundaryEdgeV(int eID) const +{ + if (EdgeRefCounts.IsValid(eID)) + { + int ei = 4 * eID; + if (Edges[ei + 3] == InvalidID) + { + int a = Edges[ei], b = Edges[ei + 1]; + int ti = 3 * Edges[ei + 2]; + FIndex3i tri(Triangles[ti], Triangles[ti + 1], Triangles[ti + 2]); + int ai = IndexUtil::FindEdgeIndexInTri(a, b, tri); + return FIndex2i(tri[ai], tri[(ai + 1) % 3]); + } + } + check(false); + return InvalidEdge(); +} + + +bool FDynamicMesh3::IsGroupBoundaryEdge(int eID) const +{ + check(IsEdge(eID)); + check(HasTriangleGroups()); + + int et1 = Edges[4 * eID + 3]; + if (et1 == InvalidID) + { + return false; + } + int g1 = (*TriangleGroups)[et1]; + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + return g1 != g0; +} + + + +bool FDynamicMesh3::IsGroupBoundaryVertex(int vID) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + int group_id = InvalidGroupID; + for (int eID : VertexEdgeLists.Values(vID)) + { + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + if (group_id != g0) + { + if (group_id == InvalidGroupID) + { + group_id = g0; + } + else + { + return true; // saw multiple group IDs + } + } + int et1 = Edges[4 * eID + 3]; + if (et1 != InvalidID) + { + int g1 = (*TriangleGroups)[et1]; + if (group_id != g1) + { + return true; // saw multiple group IDs + } + } + } + return false; +} + + + +bool FDynamicMesh3::IsGroupJunctionVertex(int vID) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + FIndex2i groups(InvalidGroupID, InvalidGroupID); + for (int eID : VertexEdgeLists.Values(vID)) + { + FIndex2i et(Edges[4 * eID + 2], Edges[4 * eID + 3]); + for (int k = 0; k < 2; ++k) + { + if (et[k] == InvalidID) + { + continue; + } + int g0 = (*TriangleGroups)[et[k]]; + if (g0 != groups[0] && g0 != groups[1]) + { + if (groups[0] != InvalidGroupID && groups[1] != InvalidGroupID) + { + return true; + } + if (groups[0] == InvalidGroupID) + { + groups[0] = g0; + } + else + { + groups[1] = g0; + } + } + } + } + return false; +} + + +bool FDynamicMesh3::GetVertexGroups(int vID, FIndex4i& groups) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + groups = FIndex4i(InvalidGroupID, InvalidGroupID, InvalidGroupID, InvalidGroupID); + int ng = 0; + + for (int eID : VertexEdgeLists.Values(vID)) + { + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + if (groups.Contains(g0) == false) + { + groups[ng++] = g0; + } + if (ng == 4) + { + return false; + } + int et1 = Edges[4 * eID + 3]; + if (et1 != InvalidID) + { + int g1 = (*TriangleGroups)[et1]; + if (groups.Contains(g1) == false) + { + groups[ng++] = g1; + } + if (ng == 4) + { + return false; + } + } + } + return true; +} + + + +bool FDynamicMesh3::GetAllVertexGroups(int vID, TArray& GroupsOut) const +{ + check(IsVertex(vID)); + check(HasTriangleGroups()); + + for (int eID : VertexEdgeLists.Values(vID)) + { + int et0 = Edges[4 * eID + 2]; + int g0 = (*TriangleGroups)[et0]; + GroupsOut.AddUnique(g0); + + int et1 = Edges[4 * eID + 3]; + if (et1 != InvalidID) + { + int g1 = (*TriangleGroups)[et1]; + GroupsOut.AddUnique(g1); + } + } + return true; +} + + + + +/** + * returns true if vID is a "bowtie" vertex, ie multiple disjoint triangle sets in one-ring + */ +bool FDynamicMesh3::IsBowtieVertex(int vID) const +{ + check(VertexRefCounts.IsValid(vID)); + + int nEdges = VertexEdgeLists.GetCount(vID); + if (nEdges == 0) + { + return false; + } + + // find a boundary edge to start at + int start_eid = -1; + bool start_at_boundary = false; + for (int eid : VertexEdgeLists.Values(vID)) + { + if (Edges[4 * eid + 3] == InvalidID) + { + start_at_boundary = true; + start_eid = eid; + break; + } + } + // if no boundary edge, start at arbitrary edge + if (start_eid == -1) + { + start_eid = VertexEdgeLists.First(vID); + } + // initial triangle + int start_tid = Edges[4 * start_eid + 2]; + + int prev_tid = start_tid; + int prev_eid = start_eid; + + // walk forward to next edge. if we hit start edge or boundary edge, + // we are done the walk. count number of edges as we go. + int count = 1; + while (true) + { + int i = 3 * prev_tid; + FIndex3i tv(Triangles[i], Triangles[i + 1], Triangles[i + 2]); + FIndex3i te(TriangleEdges[i], TriangleEdges[i + 1], TriangleEdges[i + 2]); + int vert_idx = IndexUtil::FindTriIndex(vID, tv); + int e1 = te[vert_idx], e2 = te[(vert_idx + 2) % 3]; + int next_eid = (e1 == prev_eid) ? e2 : e1; + if (next_eid == start_eid) + { + break; + } + FIndex2i next_eid_tris = GetEdgeT(next_eid); + int next_tid = (next_eid_tris[0] == prev_tid) ? next_eid_tris[1] : next_eid_tris[0]; + if (next_tid == InvalidID) + { + break; + } + prev_eid = next_eid; + prev_tid = next_tid; + count++; + } + + // if we did not see all edges at vertex, we have a bowtie + int target_count = (start_at_boundary) ? nEdges - 1 : nEdges; + bool is_bowtie = (target_count != count); + return is_bowtie; +} + + + + +int FDynamicMesh3::FindTriangle(int a, int b, int c) const +{ + int eid = FindEdge(a, b); + if (eid == InvalidID) + { + return InvalidID; + } + int ei = 4 * eid; + + // triangles attached to edge [a,b] must contain verts a and b... + int ti = 3 * Edges[ei + 2]; + if (Triangles[ti] == c || Triangles[ti + 1] == c || Triangles[ti + 2] == c) + { + return Edges[ei + 2]; + } + if (Edges[ei + 3] != InvalidID) + { + ti = 3 * Edges[ei + 3]; + if (Triangles[ti] == c || Triangles[ti + 1] == c || Triangles[ti + 2] == c) + { + return Edges[ei + 3]; + } + } + + return InvalidID; +} + + + +/** + * Computes bounding box of all vertices. + */ +FAxisAlignedBox3d FDynamicMesh3::GetBounds() const +{ + double x = 0, y = 0, z = 0; + for (int vi : VertexIndicesItr()) // find initial valid vertex + { + int k = 3 * vi; + x = Vertices[k]; y = Vertices[k + 1]; z = Vertices[k + 2]; + break; + } + double minx = x, maxx = x, miny = y, maxy = y, minz = z, maxz = z; + for (int vi : VertexIndicesItr()) + { + int k = 3 * vi; + x = Vertices[k]; y = Vertices[k + 1]; z = Vertices[k + 2]; + if (x < minx) minx = x; else if (x > maxx) maxx = x; + if (y < miny) miny = y; else if (y > maxy) maxy = y; + if (z < minz) minz = z; else if (z > maxz) maxz = z; + } + return FAxisAlignedBox3d(FVector3d(minx, miny, minz), FVector3d(maxx, maxy, maxz)); +} + + +FAxisAlignedBox3d FDynamicMesh3::GetCachedBounds() +{ + if (CachedBoundingBoxTimestamp != GetShapeTimestamp()) + { + CachedBoundingBox = GetBounds(); + CachedBoundingBoxTimestamp = GetShapeTimestamp(); + } + return CachedBoundingBox; +} + + + + +bool FDynamicMesh3::IsClosed() const +{ + if (TriangleCount() == 0) + { + return false; + } + + int N = MaxEdgeID(); + for (int i = 0; i < N; ++i) + { + if (EdgeRefCounts.IsValid(i) && IsBoundaryEdge(i)) + { + return false; + } + } + return true; +} + + +bool FDynamicMesh3::GetCachedIsClosed() +{ + if (CachedIsClosedTimestamp != GetTopologyTimestamp()) + { + bIsClosedCached = IsClosed(); + CachedIsClosedTimestamp = GetTopologyTimestamp(); + } + return bIsClosedCached; +} + + + + + +// average of 1 or 2 face normals +FVector3d FDynamicMesh3::GetEdgeNormal(int eID) const +{ + if (EdgeRefCounts.IsValid(eID)) + { + int ei = 4 * eID; + FVector3d n = GetTriNormal(Edges[ei + 2]); + if (Edges[ei + 3] != InvalidID) + { + n += GetTriNormal(Edges[ei + 3]); + n.Normalize(); + } + return n; + } + check(false); + return FVector3d::Zero(); +} + +FVector3d FDynamicMesh3::GetEdgePoint(int eID, double t) const +{ + t = VectorUtil::Clamp(t, 0.0, 1.0); + if (EdgeRefCounts.IsValid(eID)) + { + int ei = 4 * eID; + int iv0 = 3 * Edges[ei]; + int iv1 = 3 * Edges[ei + 1]; + double mt = 1.0 - t; + return FVector3d( + mt*Vertices[iv0] + t * Vertices[iv1], + mt*Vertices[iv0 + 1] + t * Vertices[iv1 + 1], + mt*Vertices[iv0 + 2] + t * Vertices[iv1 + 2]); + } + check(false); + return FVector3d::Zero(); +} + + + +void FDynamicMesh3::GetVtxOneRingCentroid(int vID, FVector3d& centroid) const +{ + centroid = FVector3d::Zero(); + if (VertexRefCounts.IsValid(vID)) + { + int n = 0; + for (int eid : VertexEdgeLists.Values(vID)) + { + int other_idx = 3 * GetOtherEdgeVertex(eid, vID); + centroid.X += Vertices[other_idx]; + centroid.Y += Vertices[other_idx + 1]; + centroid.Z += Vertices[other_idx + 2]; + n++; + } + if (n > 0) + { + centroid *= 1.0 / n; + } + } +} + + + +FFrame3d FDynamicMesh3::GetVertexFrame(int vID, bool bFrameNormalY) const +{ + check(HasVertexNormals()); + + int vi = 3 * vID; + FVector3d v(Vertices[vi], Vertices[vi + 1], Vertices[vi + 2]); + FVector3d normal((*VertexNormals)[vi], (*VertexNormals)[vi + 1], (*VertexNormals)[vi + 2]); + int eid = VertexEdgeLists.First(vID); + int ovi = 3 * GetOtherEdgeVertex(eid, vID); + FVector3d ov(Vertices[ovi], Vertices[ovi + 1], Vertices[ovi + 2]); + FVector3d edge = (ov - v); + edge.Normalize(); + + FVector3d other = normal.Cross(edge); + edge = other.Cross(normal); + if (bFrameNormalY) + { + return FFrame3d(v, edge, normal, -other); + } + else + { + return FFrame3d(v, edge, other, normal); + } +} + + + +FVector3d FDynamicMesh3::GetTriNormal(int tID) const +{ + FVector3d v0, v1, v2; + GetTriVertices(tID, v0, v1, v2); + return VectorUtil::Normal(v0, v1, v2); +} + +double FDynamicMesh3::GetTriArea(int tID) const +{ + FVector3d v0, v1, v2; + GetTriVertices(tID, v0, v1, v2); + return VectorUtil::Area(v0, v1, v2); +} + + + +void FDynamicMesh3::GetTriInfo(int tID, FVector3d& Normal, double& Area, FVector3d& Centroid) const +{ + FVector3d v0, v1, v2; + GetTriVertices(tID, v0, v1, v2); + Centroid = (v0 + v1 + v2) * (1.0 / 3.0); + Area = VectorUtil::Area(v0, v1, v2); + Normal = VectorUtil::Normal(v0, v1, v2); + //normal = FastNormalArea(ref v0, ref v1, ref v2, out fArea); +} + + +FVector3d FDynamicMesh3::GetTriBaryPoint(int tID, double bary0, double bary1, double bary2) const +{ + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + return FVector3d( + (bary0*Vertices[ai] + bary1 * Vertices[bi] + bary2 * Vertices[ci]), + (bary0*Vertices[ai + 1] + bary1 * Vertices[bi + 1] + bary2 * Vertices[ci + 1]), + (bary0*Vertices[ai + 2] + bary1 * Vertices[bi + 2] + bary2 * Vertices[ci + 2])); +} + + +FVector3d FDynamicMesh3::GetTriBaryNormal(int tID, double bary0, double bary1, double bary2) const +{ + check(HasVertexNormals()); + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + const TDynamicVector& normalsR = *(this->VertexNormals); + FVector3d n = FVector3d( + (bary0*normalsR[ai] + bary1 * normalsR[bi] + bary2 * normalsR[ci]), + (bary0*normalsR[ai + 1] + bary1 * normalsR[bi + 1] + bary2 * normalsR[ci + 1]), + (bary0*normalsR[ai + 2] + bary1 * normalsR[bi + 2] + bary2 * normalsR[ci + 2])); + n.Normalize(); + return n; +} + +FVector3d FDynamicMesh3::GetTriCentroid(int tID) const +{ + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + double f = (1.0 / 3.0); + return FVector3d( + (Vertices[ai] + Vertices[bi] + Vertices[ci]) * f, + (Vertices[ai + 1] + Vertices[bi + 1] + Vertices[ci + 1]) * f, + (Vertices[ai + 2] + Vertices[bi + 2] + Vertices[ci + 2]) * f); +} + + +void FDynamicMesh3::GetTriBaryPoint(int tID, double bary0, double bary1, double bary2, FVertexInfo& vinfo) const +{ + vinfo = FVertexInfo(); + int ai = 3 * Triangles[3 * tID], + bi = 3 * Triangles[3 * tID + 1], + ci = 3 * Triangles[3 * tID + 2]; + vinfo.Position = FVector3d( + (bary0 * Vertices[ai] + bary1 * Vertices[bi] + bary2 * Vertices[ci]), + (bary0 * Vertices[ai + 1] + bary1 * Vertices[bi + 1] + bary2 * Vertices[ci + 1]), + (bary0 * Vertices[ai + 2] + bary1 * Vertices[bi + 2] + bary2 * Vertices[ci + 2])); + vinfo.bHaveN = HasVertexNormals(); + if (vinfo.bHaveN) + { + TDynamicVector& normalsR = *(this->VertexNormals); + vinfo.Normal = FVector3f( + (float)(bary0 * normalsR[ai] + bary1 * normalsR[bi] + bary2 * normalsR[ci]), + (float)(bary0 * normalsR[ai + 1] + bary1 * normalsR[bi + 1] + bary2 * normalsR[ci + 1]), + (float)(bary0 * normalsR[ai + 2] + bary1 * normalsR[bi + 2] + bary2 * normalsR[ci + 2])); + vinfo.Normal.Normalize(); + } + vinfo.bHaveC = HasVertexColors(); + if (vinfo.bHaveC) + { + TDynamicVector& colorsR = *(this->VertexColors); + vinfo.Color = FVector3f( + (float)(bary0 * colorsR[ai] + bary1 * colorsR[bi] + bary2 * colorsR[ci]), + (float)(bary0 * colorsR[ai + 1] + bary1 * colorsR[bi + 1] + bary2 * colorsR[ci + 1]), + (float)(bary0 * colorsR[ai + 2] + bary1 * colorsR[bi + 2] + bary2 * colorsR[ci + 2])); + } + vinfo.bHaveUV = HasVertexUVs(); + if (vinfo.bHaveUV) + { + TDynamicVector& uvR = *(this->VertexUVs); + ai = 2 * Triangles[3 * tID]; + bi = 2 * Triangles[3 * tID + 1]; + ci = 2 * Triangles[3 * tID + 2]; + vinfo.UV = FVector2f( + (float)(bary0 * uvR[ai] + bary1 * uvR[bi] + bary2 * uvR[ci]), + (float)(bary0 * uvR[ai + 1] + bary1 * uvR[bi + 1] + bary2 * uvR[ci + 1])); + } +} + + +FAxisAlignedBox3d FDynamicMesh3::GetTriBounds(int tID) const +{ + int vi = 3 * Triangles[3 * tID]; + double x = Vertices[vi], y = Vertices[vi + 1], z = Vertices[vi + 2]; + double minx = x, maxx = x, miny = y, maxy = y, minz = z, maxz = z; + for (int i = 1; i < 3; ++i) + { + vi = 3 * Triangles[3 * tID + i]; + x = Vertices[vi]; y = Vertices[vi + 1]; z = Vertices[vi + 2]; + if (x < minx) minx = x; else if (x > maxx) maxx = x; + if (y < miny) miny = y; else if (y > maxy) maxy = y; + if (z < minz) minz = z; else if (z > maxz) maxz = z; + } + return FAxisAlignedBox3d(FVector3d(minx, miny, minz), FVector3d(maxx, maxy, maxz)); +} + + +FFrame3d FDynamicMesh3::GetTriFrame(int tID, int nEdge) const +{ + int ti = 3 * tID; + int a = 3 * Triangles[ti + (nEdge % 3)]; + int b = 3 * Triangles[ti + ((nEdge + 1) % 3)]; + int c = 3 * Triangles[ti + ((nEdge + 2) % 3)]; + FVector3d v1(Vertices[a], Vertices[a + 1], Vertices[a + 2]); + FVector3d v2(Vertices[b], Vertices[b + 1], Vertices[b + 2]); + FVector3d v3(Vertices[c], Vertices[c + 1], Vertices[c + 2]); + + FVector3d edge1 = v2 - v1; edge1.Normalize(); + FVector3d edge2 = v3 - v2; edge2.Normalize(); + FVector3d normal = edge2.Cross(edge1); normal.Normalize(); + + FVector3d other = normal.Cross(edge1); + + FVector3d center = (v1 + v2 + v3) / 3; + return FFrame3d(center, edge1, other, normal); +} + + + +double FDynamicMesh3::GetTriSolidAngle(int tID, const FVector3d& p) const +{ + // inlined version of GetTriVertices & VectorUtil::TriSolidAngle + int ti = 3 * tID; + int ta = 3 * Triangles[ti]; + FVector3d a(Vertices[ta] - p.X, Vertices[ta + 1] - p.Y, Vertices[ta + 2] - p.Z); + int tb = 3 * Triangles[ti + 1]; + FVector3d b(Vertices[tb] - p.X, Vertices[tb + 1] - p.Y, Vertices[tb + 2] - p.Z); + int tc = 3 * Triangles[ti + 2]; + FVector3d c(Vertices[tc] - p.X, Vertices[tc + 1] - p.Y, Vertices[tc + 2] - p.Z); + double la = a.Length(), lb = b.Length(), lc = c.Length(); + double top = (la * lb * lc) + a.Dot(b) * lc + b.Dot(c) * la + c.Dot(a) * lb; + double bottom = a.X * (b.Y * c.Z - c.Y * b.Z) - a.Y * (b.X * c.Z - c.X * b.Z) + a.Z * (b.X * c.Y - c.X * b.Y); + // -2 instead of 2 to account for UE winding + return -2.0 * atan2(bottom, top); +} + + + +double FDynamicMesh3::GetTriInternalAngleR(int tID, int i) +{ + int ti = 3 * tID; + int ta = 3 * Triangles[ti]; + FVector3d a(Vertices[ta], Vertices[ta + 1], Vertices[ta + 2]); + int tb = 3 * Triangles[ti + 1]; + FVector3d b(Vertices[tb], Vertices[tb + 1], Vertices[tb + 2]); + int tc = 3 * Triangles[ti + 2]; + FVector3d c(Vertices[tc], Vertices[tc + 1], Vertices[tc + 2]); + if (i == 0) + { + return (b - a).Normalized().AngleR((c - a).Normalized()); + } + else if (i == 1) + { + return (a - b).Normalized().AngleR((c - b).Normalized()); + } + else + { + return (a - c).Normalized().AngleR((b - c).Normalized()); + } +} + + +double FDynamicMesh3::CalculateWindingNumber(const FVector3d& QueryPoint) const +{ + double sum = 0; + for (int tid : TriangleIndicesItr()) + { + sum += GetTriSolidAngle(tid, QueryPoint); + } + return sum / FMathd::FourPi; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshAttributeSet.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshAttributeSet.cpp new file mode 100644 index 000000000000..1d3651a0b7b1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshAttributeSet.cpp @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshAttributeSet.h" + + + + +bool FDynamicMeshAttributeSet::IsSeamEdge(int eid) const +{ + return UV0.IsSeamEdge(eid) || Normals0.IsSeamEdge(eid); +} + + + +void FDynamicMeshAttributeSet::OnNewTriangle(int TriangleID, bool bInserted) +{ + UV0.InitializeNewTriangle(TriangleID); + Normals0.InitializeNewTriangle(TriangleID); +} + +void FDynamicMeshAttributeSet::OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices) +{ + UV0.OnRemoveTriangle(TriangleID, bRemoveIsolatedVertices); + Normals0.OnRemoveTriangle(TriangleID, bRemoveIsolatedVertices); +} + +void FDynamicMeshAttributeSet::OnReverseTriOrientation(int TriangleID) +{ + UV0.OnReverseTriOrientation(TriangleID); + Normals0.OnReverseTriOrientation(TriangleID); +} + +void FDynamicMeshAttributeSet::OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo & splitInfo) +{ + UV0.OnSplitEdge(splitInfo); + Normals0.OnSplitEdge(splitInfo); +} + +void FDynamicMeshAttributeSet::OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo & flipInfo) +{ + UV0.OnFlipEdge(flipInfo); + Normals0.OnFlipEdge(flipInfo); +} + + +void FDynamicMeshAttributeSet::OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo & collapseInfo) +{ + UV0.OnCollapseEdge(collapseInfo); + Normals0.OnCollapseEdge(collapseInfo); +} + +void FDynamicMeshAttributeSet::OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo & pokeInfo) +{ + UV0.OnPokeTriangle(pokeInfo); + Normals0.OnPokeTriangle(pokeInfo); +} + +void FDynamicMeshAttributeSet::OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo & mergeInfo) +{ + UV0.OnMergeEdges(mergeInfo); + Normals0.OnMergeEdges(mergeInfo); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshEditor.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshEditor.cpp new file mode 100644 index 000000000000..d9203485baad --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshEditor.cpp @@ -0,0 +1,614 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "DynamicMeshEditor.h" +#include "DynamicMeshAttributeSet.h" +#include "Util/BufferUtil.h" +#include "MeshRegionBoundaryLoops.h" + + + +void FMeshIndexMappings::Initialize(FDynamicMesh3* Mesh) +{ + if (Mesh->HasAttributes()) + { + FDynamicMeshAttributeSet* Attribs = Mesh->Attributes(); + UVMaps.SetNum(Attribs->NumUVLayers()); + NormalMaps.SetNum(Attribs->NumNormalLayers()); + } +} + + + + + +void FDynamicMeshEditResult::GetAllTriangles(TArray& TrianglesOut) const +{ + BufferUtil::AppendElements(TrianglesOut, NewTriangles); + + int NumQuads = NewQuads.Num(); + for (int k = 0; k < NumQuads; ++k) + { + TrianglesOut.Add(NewQuads[k].A); + TrianglesOut.Add(NewQuads[k].B); + } + int NumPolys = NewPolygons.Num(); + for (int k = 0; k < NumPolys; ++k) + { + BufferUtil::AppendElements(TrianglesOut, NewPolygons[k]); + } +} + + + + + + +bool FDynamicMeshEditor::StitchVertexLoopsMinimal(const TArray& Loop1, const TArray& Loop2, FDynamicMeshEditResult& ResultOut) +{ + int N = Loop1.Num(); + checkf(N == Loop2.Num(), TEXT("FDynamicMeshEditor::StitchLoop: loops are not the same length!")); + if (N != Loop2.Num()) + { + return false; + } + + ResultOut.NewQuads.Reserve(N); + ResultOut.NewGroups.Reserve(N); + + int i = 0; + for (; i < N; ++i) + { + int a = Loop1[i]; + int b = Loop1[(i + 1) % N]; + int c = Loop2[i]; + int d = Loop2[(i + 1) % N]; + + int NewGroupID = Mesh->AllocateTriangleGroup(); + ResultOut.NewGroups.Add(NewGroupID); + + FIndex3i t1(b, a, d); + int tid1 = Mesh->AppendTriangle(t1, NewGroupID); + + FIndex3i t2(a, c, d); + int tid2 = Mesh->AppendTriangle(t2, NewGroupID); + + ResultOut.NewQuads.Add(FIndex2i(tid1, tid2)); + + if (tid1 < 0 || tid2 < 0) + { + goto operation_failed; + } + } + + return true; + +operation_failed: + // remove what we added so far + if (i > 0) + { + ResultOut.NewTriangles.SetNum(2 * i + 1); + if (RemoveTriangles(ResultOut.NewTriangles, false) == false) + { + checkf(false, TEXT("FDynamicMeshEditor::StitchLoop: failed to add all triangles, and also failed to back out changes.")); + } + } + return false; +} + + + + +bool FDynamicMeshEditor::RemoveTriangles(const TArray& Triangles, bool bRemoveIsolatedVerts) +{ + bool bAllOK = true; + int NumTriangles = Triangles.Num(); + for (int i = 0; i < NumTriangles; ++i) + { + if (Mesh->IsTriangle(Triangles[i]) == false) + { + continue; + } + + EMeshResult result = Mesh->RemoveTriangle(Triangles[i], bRemoveIsolatedVerts, false); + if (result != EMeshResult::Ok) + { + bAllOK = false; + } + } + return bAllOK; +} + + + + + +/** + * Make a copy of provided triangles, with new vertices. You provide IndexMaps because + * you know if you are doing a small subset or a full-mesh-copy. + */ +void FDynamicMeshEditor::DuplicateTriangles(const TArray& Triangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + ResultOut.Reset(); + IndexMaps.Initialize(Mesh); + + for (int TriangleID : Triangles) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleID); + + int NewGroupID = FindOrCreateDuplicateGroup(TriangleID, IndexMaps, ResultOut); + + FIndex3i NewTri; + NewTri[0] = FindOrCreateDuplicateVertex(Tri[0], IndexMaps, ResultOut); + NewTri[1] = FindOrCreateDuplicateVertex(Tri[1], IndexMaps, ResultOut); + NewTri[2] = FindOrCreateDuplicateVertex(Tri[2], IndexMaps, ResultOut); + + int NewTriangleID = Mesh->AppendTriangle(NewTri, NewGroupID); + IndexMaps.SetTriangle(TriangleID, NewTriangleID); + ResultOut.NewTriangles.Add(NewTriangleID); + + CopyAttributes(TriangleID, NewTriangleID, IndexMaps, ResultOut); + + //Mesh->CheckValidity(true); + } + +} + + + + +bool FDynamicMeshEditor::DisconnectTriangles(const TArray& Triangles, TArray& LoopSetOut) +{ + check(Mesh->HasAttributes() == false); // not supported yet + + // find the region boundary loops + FMeshRegionBoundaryLoops RegionLoops(Mesh, Triangles, false); + bool bOK = RegionLoops.Compute(); + check(bOK); + if (!bOK) + { + return false; + } + TArray& Loops = RegionLoops.Loops; + + // need to test Contains() many times + TSet TriangleSet; + TriangleSet.Reserve(Triangles.Num() * 3); + for (int TriID : Triangles) + { + TriangleSet.Add(TriID); + } + + // process each loop island + int NumLoops = Loops.Num(); + LoopSetOut.SetNum(NumLoops); + for ( int li = 0; li < NumLoops; ++li) + { + FEdgeLoop& Loop = Loops[li]; + FLoopPairSet& LoopPair = LoopSetOut[li]; + LoopPair.LoopA = Loop; + + // duplicate the vertices + int NumVertices = Loop.Vertices.Num(); + TMap LoopVertexMap; LoopVertexMap.Reserve(NumVertices); + TArray NewVertexLoop; NewVertexLoop.SetNum(NumVertices); + for (int vi = 0; vi < NumVertices; ++vi) + { + int VertID = Loop.Vertices[vi]; + int NewVertID = Mesh->AppendVertex(*Mesh, VertID); + LoopVertexMap.Add(Loop.Vertices[vi], NewVertID); + NewVertexLoop[vi] = NewVertID; + } + + // for each border triangle, rewrite vertices + int NumEdges = Loop.Edges.Num(); + for (int ei = 0; ei < NumEdges; ++ei) + { + int EdgeID = Loop.Edges[ei]; + FIndex2i EdgeTris = Mesh->GetEdgeT(EdgeID); + int EditTID = TriangleSet.Contains(EdgeTris.A) ? EdgeTris.A : EdgeTris.B; + if (EditTID == FDynamicMesh3::InvalidID) + { + continue; // happens on final edge, and on input boundary edges + } + + FIndex3i OldTri = Mesh->GetTriangle(EditTID); + FIndex3i NewTri = OldTri; + int Modified = 0; + for (int j = 0; j < 3; ++j) + { + const int* NewVertID = LoopVertexMap.Find(OldTri[j]); + if (NewVertID != nullptr) + { + NewTri[j] = *NewVertID; + ++Modified; + } + } + if (Modified > 0) + { + Mesh->SetTriangle(EditTID, NewTri, false); + } + } + + LoopPair.LoopB.InitializeFromVertices(Mesh, NewVertexLoop, false); + } + + return true; +} + + + + +FVector3f FDynamicMeshEditor::ComputeAndSetQuadNormal(const FIndex2i& QuadTris, bool bIsPlanar) +{ + FVector3f Normal(0, 0, 1); + if (bIsPlanar) + { + Normal = (FVector3f)Mesh->GetTriNormal(QuadTris.A); + } + else + { + Normal = (FVector3f)Mesh->GetTriNormal(QuadTris.A); + Normal += (FVector3f)Mesh->GetTriNormal(QuadTris.B); + Normal.Normalize(); + } + SetQuadNormals(QuadTris, Normal); + return Normal; +} + + + + +void FDynamicMeshEditor::SetQuadNormals(const FIndex2i& QuadTris, const FVector3f& Normal) +{ + check(Mesh->HasAttributes()); + FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals(); + + FIndex3i Triangle1 = Mesh->GetTriangle(QuadTris.A); + + FIndex3i NormalTriangle1; + NormalTriangle1[0] = Normals->AppendElement(Normal, Triangle1[0]); + NormalTriangle1[1] = Normals->AppendElement(Normal, Triangle1[1]); + NormalTriangle1[2] = Normals->AppendElement(Normal, Triangle1[2]); + Normals->SetTriangle(QuadTris.A, NormalTriangle1); + + if (Mesh->IsTriangle(QuadTris.B)) + { + FIndex3i Triangle2 = Mesh->GetTriangle(QuadTris.B); + FIndex3i NormalTriangle2; + for (int j = 0; j < 3; ++j) + { + int i = Triangle1.IndexOf(Triangle2[j]); + if (i == -1) + { + NormalTriangle2[j] = Normals->AppendElement(Normal, Triangle2[j]); + } + else + { + NormalTriangle2[j] = NormalTriangle1[i]; + } + } + Normals->SetTriangle(QuadTris.B, NormalTriangle2); + } + +} + + +void FDynamicMeshEditor::SetTriangleNormals(const TArray& Triangles, const FVector3f& Normal) +{ + check(Mesh->HasAttributes()); + FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals(); + + TMap Vertices; + + for (int tid : Triangles) + { + FIndex3i BaseTri = Mesh->GetTriangle(tid); + FIndex3i ElemTri; + for (int j = 0; j < 3; ++j) + { + const int* FoundElementID = Vertices.Find(BaseTri[j]); + if (FoundElementID == nullptr) + { + ElemTri[j] = Normals->AppendElement(Normal, BaseTri[j]); + Vertices.Add(BaseTri[j], ElemTri[j]); + } + else + { + ElemTri[j] = *FoundElementID; + } + } + Normals->SetTriangle(tid, ElemTri); + } +} + + + + + +void FDynamicMeshEditor::SetQuadUVsFromProjection(const FIndex2i& QuadTris, const FFrame3f& ProjectionFrame, float UVScaleFactor, int UVLayerIndex) +{ + check(Mesh->HasAttributes() && Mesh->Attributes()->NumUVLayers() > UVLayerIndex ); + FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex); + + FIndex4i AllUVIndices(-1, -1, -1, -1); + FVector2f AllUVs[4]; + + // project first triangle + FIndex3i Triangle1 = Mesh->GetTriangle(QuadTris.A); + FIndex3i UVTriangle1; + for (int j = 0; j < 3; ++j) + { + FVector2f UV = ProjectionFrame.ToPlaneUV( (FVector3f)Mesh->GetVertex(Triangle1[j]), 2); + UVTriangle1[j] = UVs->AppendElement(UV, Triangle1[j]); + AllUVs[j] = UV; + AllUVIndices[j] = UVTriangle1[j]; + } + UVs->SetTriangle(QuadTris.A, UVTriangle1); + + // project second triangle + if (Mesh->IsTriangle(QuadTris.B)) + { + FIndex3i Triangle2 = Mesh->GetTriangle(QuadTris.B); + FIndex3i UVTriangle2; + for (int j = 0; j < 3; ++j) + { + int i = Triangle1.IndexOf(Triangle2[j]); + if (i == -1) + { + FVector2f UV = ProjectionFrame.ToPlaneUV( (FVector3f)Mesh->GetVertex(Triangle2[j]), 2); + UVTriangle2[j] = UVs->AppendElement(UV, Triangle2[j]); + AllUVs[3] = UV; + AllUVIndices[3] = UVTriangle2[j]; + } + else + { + UVTriangle2[j] = UVTriangle1[i]; + } + } + UVs->SetTriangle(QuadTris.B, UVTriangle2); + } + + // shift UVs so that their bbox min-corner is at origin and scaled by external scale factor + FAxisAlignedBox2f UVBounds(FAxisAlignedBox2f::Empty()); + UVBounds.Contain(AllUVs[0]); UVBounds.Contain(AllUVs[1]); UVBounds.Contain(AllUVs[2]); + if (AllUVIndices[3] != -1) + { + UVBounds.Contain(AllUVs[3]); + } + for (int j = 0; j < 4; ++j) + { + if (AllUVIndices[j] != -1) + { + FVector2f TransformedUV = (AllUVs[j] - UVBounds.Min) * UVScaleFactor; + UVs->SetElement(AllUVIndices[j], TransformedUV); + } + } +} + + + + + + +void FDynamicMeshEditor::ReverseTriangleOrientations(const TArray& Triangles, bool bInvertNormals) +{ + for (int tid : Triangles) + { + Mesh->ReverseTriOrientation(tid); + } + if (bInvertNormals) + { + InvertTriangleNormals(Triangles); + } +} + + +void FDynamicMeshEditor::InvertTriangleNormals(const TArray& Triangles) +{ + // @todo re-use the TBitA + + if (Mesh->HasVertexNormals()) + { + TBitArray DoneVertices(false, Mesh->MaxVertexID()); + for (int TriangleID : Triangles) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleID); + for (int j = 0; j < 3; ++j) + { + if (DoneVertices[Tri[j]] == false) + { + Mesh->SetVertexNormal(Tri[j], -Mesh->GetVertexNormal(Tri[j])); + DoneVertices[Tri[j]] = true; + } + } + } + } + + + if (Mesh->HasAttributes()) + { + for (FDynamicMeshNormalOverlay* Normals : Mesh->Attributes()->GetAllNormalLayers()) + { + TBitArray DoneNormals(false, Normals->MaxElementID()); + for (int TriangleID : Triangles) + { + FIndex3i ElemTri = Normals->GetTriangle(TriangleID); + for (int j = 0; j < 3; ++j) + { + if (DoneNormals[ElemTri[j]] == false) + { + Normals->SetElement(ElemTri[j], -Normals->GetElement(ElemTri[j])); + DoneNormals[ElemTri[j]] = true; + } + } + } + } + } +} + + + + + +void FDynamicMeshEditor::CopyAttributes(int FromTriangleID, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + if (Mesh->HasAttributes() == false) + { + return; + } + + int UVLayerIndex = 0; + for (FDynamicMeshUVOverlay* UVOverlay : Mesh->Attributes()->GetAllUVLayers()) + { + FIndex3i FromElemTri = UVOverlay->GetTriangle(FromTriangleID); + FIndex3i ToElemTri = UVOverlay->GetTriangle(ToTriangleID); + for (int j = 0; j < 3; ++j) + { + if (FromElemTri[j] != FDynamicMesh3::InvalidID ) + { + int NewElemID = FindOrCreateDuplicateUV(FromElemTri[j], UVLayerIndex, IndexMaps); + ToElemTri[j] = NewElemID; + } + } + UVOverlay->SetTriangle(ToTriangleID, ToElemTri); + UVLayerIndex++; + } + + + int NormalLayerIndex = 0; + for (FDynamicMeshNormalOverlay* NormalOverlay : Mesh->Attributes()->GetAllNormalLayers()) + { + FIndex3i FromElemTri = NormalOverlay->GetTriangle(FromTriangleID); + FIndex3i ToElemTri = NormalOverlay->GetTriangle(ToTriangleID); + for (int j = 0; j < 3; ++j) + { + if (FromElemTri[j] != FDynamicMesh3::InvalidID) + { + int NewElemID = FindOrCreateDuplicateNormal(FromElemTri[j], NormalLayerIndex, IndexMaps); + ToElemTri[j] = NewElemID; + } + } + NormalOverlay->SetTriangle(ToTriangleID, ToElemTri); + NormalLayerIndex++; + } +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateUV(int ElementID, int UVLayerIndex, FMeshIndexMappings& IndexMaps) +{ + int NewElementID = IndexMaps.GetNewUV(UVLayerIndex, ElementID); + if (NewElementID == IndexMaps.InvalidID()) + { + FDynamicMeshUVOverlay* UVOverlay = Mesh->Attributes()->GetUVLayer(UVLayerIndex); + + // need to determine new parent vertex. It should be in the map already! + int ParentVertexID = UVOverlay->GetParentVertex(ElementID); + int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); + check(NewParentVertexID != IndexMaps.InvalidID()); + + NewElementID = UVOverlay->AppendElement( + UVOverlay->GetElement(ElementID), NewParentVertexID); + + IndexMaps.SetUV(UVLayerIndex, ElementID, NewElementID); + } + return NewElementID; +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateNormal(int ElementID, int NormalLayerIndex, FMeshIndexMappings& IndexMaps) +{ + int NewElementID = IndexMaps.GetNewNormal(NormalLayerIndex, ElementID); + if (NewElementID == IndexMaps.InvalidID()) + { + FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex); + + // need to determine new parent vertex. It should be in the map already! + int ParentVertexID = NormalOverlay->GetParentVertex(ElementID); + int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); + check(NewParentVertexID != IndexMaps.InvalidID()); + + NewElementID = NormalOverlay->AppendElement( + NormalOverlay->GetElement(ElementID), NewParentVertexID); + + IndexMaps.SetNormal(NormalLayerIndex, ElementID, NewElementID); + } + return NewElementID; +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateVertex(int VertexID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + int NewVertexID = IndexMaps.GetNewVertex(VertexID); + if (NewVertexID == IndexMaps.InvalidID()) + { + NewVertexID = Mesh->AppendVertex(*Mesh, VertexID); + IndexMaps.SetVertex(VertexID, NewVertexID); + ResultOut.NewVertices.Add(NewVertexID); + } + return NewVertexID; +} + + + +int FDynamicMeshEditor::FindOrCreateDuplicateGroup(int TriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) +{ + int GroupID = Mesh->GetTriangleGroup(TriangleID); + int NewGroupID = IndexMaps.GetNewGroup(GroupID); + if (NewGroupID == IndexMaps.InvalidID()) + { + NewGroupID = Mesh->AllocateTriangleGroup(); + IndexMaps.SetGroup(GroupID, NewGroupID); + ResultOut.NewGroups.Add(NewGroupID); + } + return NewGroupID; +} + + + + +void FDynamicMeshEditor::AppendMesh(const FDynamicMesh3* AppendMesh, + FMeshIndexMappings& IndexMapsOut, FDynamicMeshEditResult& ResultOut, + TFunction PositionTransform, + TFunction NormalTransform) +{ + IndexMapsOut.Reset(); + + FIndexMapi& VertexMap = IndexMapsOut.GetVertexMap(); + VertexMap.Reserve(AppendMesh->VertexCount()); + for (int VertID : AppendMesh->VertexIndicesItr()) + { + FVector3d Position = AppendMesh->GetVertex(VertID); + if (PositionTransform != nullptr) + { + Position = PositionTransform(VertID, Position); + } + int NewVertID = Mesh->AppendVertex(Position); + VertexMap.Add(VertID, NewVertID); + + if (AppendMesh->HasVertexNormals() && Mesh->HasVertexNormals()) + { + FVector3f Normal = AppendMesh->GetVertexNormal(VertID); + if (NormalTransform != nullptr) + { + Normal = NormalTransform(VertID, Normal); + } + Mesh->SetVertexNormal(NewVertID, Normal); + } + + if (AppendMesh->HasVertexColors() && Mesh->HasVertexColors()) + { + FVector3f Color = AppendMesh->GetVertexColor(VertID); + Mesh->SetVertexColor(NewVertID, Color); + } + } + + for (int TriID : AppendMesh->TriangleIndicesItr()) + { + FIndex3i Tri = AppendMesh->GetTriangle(TriID); + int NewTriID = Mesh->AppendTriangle(VertexMap.GetTo(Tri.A), VertexMap.GetTo(Tri.B), VertexMap.GetTo(Tri.C)); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshModule.cpp new file mode 100644 index 000000000000..cf6ab46cf365 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshModule.h" + +#define LOCTEXT_NAMESPACE "FDynamicMeshModule" + +void FDynamicMeshModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FDynamicMeshModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FDynamicMeshModule, DynamicMesh) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshOverlay.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshOverlay.cpp new file mode 100644 index 000000000000..87825f38a27d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/DynamicMeshOverlay.cpp @@ -0,0 +1,668 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshOverlay.h" +#include "DynamicMesh3.h" + + + + +template +void TDynamicMeshOverlay::ClearElements() +{ + Elements.Clear(); + ElementsRefCounts = FRefCountVector(); + ParentVertices.Clear(); + InitializeTriangles(ParentMesh->MaxTriangleID()); +} + + + +template +int TDynamicMeshOverlay::AppendElement(RealType ConstantValue, int SourceVertex) +{ + int vid = ElementsRefCounts.Allocate(); + int i = ElementSize * vid; + for (int k = ElementSize - 1; k >= 0; --k) + { + Elements.InsertAt(ConstantValue, i + k); + } + ParentVertices.InsertAt(SourceVertex, vid); + + //updateTimeStamp(true); + return vid; +} + + +template +int TDynamicMeshOverlay::AppendElement(const RealType* Value, int SourceVertex) +{ + int vid = ElementsRefCounts.Allocate(); + int i = ElementSize * vid; + + // insert in reverse order so that Resize() is only called once + for (int k = ElementSize - 1; k >= 0; --k) + { + Elements.InsertAt(Value[k], i + k); + } + + ParentVertices.InsertAt(SourceVertex, vid); + + //updateTimeStamp(true); + return vid; +} + + + + +template +void TDynamicMeshOverlay::InitializeTriangles(int MaxTriangleID) +{ + ElementTriangles.Resize(0); + ElementTriangles.Resize(MaxTriangleID * 3, FDynamicMesh3::InvalidID); +} + + + + +template +EMeshResult TDynamicMeshOverlay::SetTriangle(int tid, const FIndex3i& tv) +{ + if (IsElement(tv[0]) == false || IsElement(tv[1]) == false || IsElement(tv[2]) == false) + { + check(false); + return EMeshResult::Failed_NotAVertex; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) + { + check(false); + return EMeshResult::Failed_InvalidNeighbourhood; + } + + InternalSetTriangle(tid, tv, true); + + //updateTimeStamp(true); + return EMeshResult::Ok; +} + + +template +void TDynamicMeshOverlay::InternalSetTriangle(int tid, const FIndex3i& tv, bool bIncrementRefCounts) +{ + int i = 3 * tid; + ElementTriangles.InsertAt(tv[2], i + 2); + ElementTriangles.InsertAt(tv[1], i + 1); + ElementTriangles.InsertAt(tv[0], i); + + if (bIncrementRefCounts) + { + ElementsRefCounts.Increment(tv[0]); + ElementsRefCounts.Increment(tv[1]); + ElementsRefCounts.Increment(tv[2]); + } +} + + + +template +void TDynamicMeshOverlay::InitializeNewTriangle(int tid) +{ + int i = 3 * tid; + ElementTriangles.InsertAt(FDynamicMesh3::InvalidID, i + 2); + ElementTriangles.InsertAt(FDynamicMesh3::InvalidID, i + 1); + ElementTriangles.InsertAt(FDynamicMesh3::InvalidID, i); + + //updateTimeStamp(true); +} + + + + +template +bool TDynamicMeshOverlay::IsSeamEdge(int eid) const +{ + FIndex2i et = ParentMesh->GetEdgeT(eid); + if (et.B == FDynamicMesh3::InvalidID) + { + return true; + } + + FIndex2i ev = ParentMesh->GetEdgeV(eid); + int base_a = ev.A, base_b = ev.B; + + FIndex3i Triangle0 = GetTriangle(et.A); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_base_a1 = BaseTriangle0.IndexOf(base_a); + int idx_base_b1 = BaseTriangle0.IndexOf(base_b); + + FIndex3i Triangle1 = GetTriangle(et.B); + FIndex3i BaseTriangle1(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + int idx_base_a2 = BaseTriangle1.IndexOf(base_a); + int idx_base_b2 = BaseTriangle1.IndexOf(base_b); + + return !IndexUtil::SamePairUnordered(Triangle0[idx_base_a1], Triangle0[idx_base_b1], Triangle1[idx_base_a2], Triangle1[idx_base_b2]); + + + // [RMS] this doesn't seem to work but it should, and would be more efficient. + // - add ParentMesh->FindTriEdgeIndex(tid,eid) + // - SamePairUnordered query could directly index into ElementTriangles[] + //FIndex3i TriangleA = GetTriangle(et.A); + //FIndex3i TriangleB = GetTriangle(et.B); + + //FIndex3i BaseTriEdgesA = ParentMesh->GetTriEdges(et.A); + //int WhichA = (BaseTriEdgesA.A == eid) ? 0 : + // ((BaseTriEdgesA.B == eid) ? 1 : 2); + + //FIndex3i BaseTriEdgesB = ParentMesh->GetTriEdges(et.B); + //int WhichB = (BaseTriEdgesB.A == eid) ? 0 : + // ((BaseTriEdgesB.B == eid) ? 1 : 2); + + //return SamePairUnordered( + // TriangleA[WhichA], TriangleA[(WhichA + 1) % 3], + // TriangleB[WhichB], TriangleB[(WhichB + 1) % 3]); +} + + + +template +bool TDynamicMeshOverlay::IsSeamVertex(int vid) const +{ + // @todo can we do this more efficiently? At minimum we are looking up each triangle twice... + for (int edgeid : ParentMesh->VtxEdgesItr(vid)) + { + if (IsSeamEdge(edgeid)) + { + return true; + } + } + return false; +} + + +template +void TDynamicMeshOverlay::GetVertexElements(int vid, TArray& OutElements) const +{ + OutElements.Reset(); + for (int tid : ParentMesh->VtxTrianglesItr(vid)) + { + FIndex3i Triangle = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ParentVertices[Triangle[j]] == vid) + { + OutElements.AddUnique(Triangle[j]); + } + } + } +} + + + +template +int TDynamicMeshOverlay::CountVertexElements(int vid, bool bBruteForce) const +{ + TArray VertexElements; + if (bBruteForce) + { + for (int tid : ParentMesh->TriangleIndicesItr()) + { + FIndex3i Triangle = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ParentVertices[Triangle[j]] == vid) + { + VertexElements.AddUnique(Triangle[j]); + } + } + } + } + else + { + for (int tid : ParentMesh->VtxTrianglesItr(vid)) + { + FIndex3i Triangle = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ParentVertices[Triangle[j]] == vid) + { + VertexElements.AddUnique(Triangle[j]); + } + } + } + } + + return VertexElements.Num(); +} + + + + +template +void TDynamicMeshOverlay::OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices) +{ + FIndex3i Triangle = GetTriangle(TriangleID); + InitializeNewTriangle(TriangleID); + + // decrement element refcounts, and free element if it is now unreferenced + for (int j = 0; j < 3; ++j) + { + int elemid = Triangle[j]; + ElementsRefCounts.Decrement(elemid); + if (bRemoveIsolatedVertices && ElementsRefCounts.GetRefCount(elemid) == 1) + { + ElementsRefCounts.Decrement(elemid); + ParentVertices[elemid] = FDynamicMesh3::InvalidID; + check(ElementsRefCounts.IsValid(elemid) == false); + } + } +} + + +template +void TDynamicMeshOverlay::OnReverseTriOrientation(int TriangleID) +{ + FIndex3i Triangle = GetTriangle(TriangleID); + int i = 3 * TriangleID; + ElementTriangles[i] = Triangle[1]; // mirrors order in FDynamicMesh3::ReverseTriOrientationInternal + ElementTriangles[i + 1] = Triangle[0]; + ElementTriangles[i + 2] = Triangle[2]; +} + + + +//#pragma optimize( "", off ) +template +void TDynamicMeshOverlay::OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo& splitInfo) +{ + int orig_t0 = splitInfo.OriginalTriangles.A; + int orig_t1 = splitInfo.OriginalTriangles.B; + int base_a = splitInfo.OriginalVertices.A; + int base_b = splitInfo.OriginalVertices.B; + + // look up current triangle 0, and infer base triangle 0 + // @todo handle case where these are InvalidID because no UVs exist for this triangle + FIndex3i Triangle0 = GetTriangle(orig_t0); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_base_a1 = BaseTriangle0.IndexOf(base_a); + int idx_base_b1 = BaseTriangle0.IndexOf(base_b); + int idx_base_c = IndexUtil::GetOtherTriIndex(idx_base_a1, idx_base_b1); + + // create new element at lerp position + int NewElemID = AppendElement((RealType)0, splitInfo.NewVertex); + SetElementFromLerp(NewElemID, Triangle0[idx_base_a1], Triangle0[idx_base_b1], (RealType)splitInfo.SplitT); + + // rewrite triangle 0 + ElementTriangles[3*orig_t0 + idx_base_b1] = NewElemID; + + // create new triangle 2 w/ correct winding order + FIndex3i NewTriangle2(NewElemID, Triangle0[idx_base_b1], Triangle0[idx_base_c]); // mirrors DMesh3::SplitEdge [f,b,c] + InternalSetTriangle(splitInfo.NewTriangles.A, NewTriangle2, false); + + // update ref counts + ElementsRefCounts.Increment(NewElemID, 2); + ElementsRefCounts.Increment(Triangle0[idx_base_c]); + + if (orig_t1 == FDynamicMesh3::InvalidID) + { + return; // we are done if this is a boundary triangle + } + + // look up current triangle1 and infer base triangle 1 + // @todo handle case where these are InvalidID because no UVs exist for this triangle + FIndex3i Triangle1 = GetTriangle(orig_t1); + FIndex3i BaseTriangle1(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + int idx_base_a2 = BaseTriangle1.IndexOf(base_a); + int idx_base_b2 = BaseTriangle1.IndexOf(base_b); + int idx_base_d = IndexUtil::GetOtherTriIndex(idx_base_a2, idx_base_b2); + + int OtherNewElemID = NewElemID; + + // if we don't have a shared edge, we need to create another new UV for the other side + bool bHasSharedUVEdge = IndexUtil::SamePairUnordered(Triangle0[idx_base_a1], Triangle0[idx_base_b1], Triangle1[idx_base_a2], Triangle1[idx_base_b2]); + if (bHasSharedUVEdge == false) + { + // create new element at lerp position + OtherNewElemID = AppendElement((RealType)0, splitInfo.NewVertex); + SetElementFromLerp(OtherNewElemID, Triangle1[idx_base_a2], Triangle1[idx_base_b2], (RealType)splitInfo.SplitT); + } + + // rewrite triangle 1 + ElementTriangles[3*orig_t1 + idx_base_b2] = OtherNewElemID; + + // create new triangle 3 w/ correct winding order + FIndex3i NewTriangle3(OtherNewElemID, Triangle1[idx_base_d], Triangle1[idx_base_b2]); // mirrors DMesh3::SplitEdge [f,d,b] + InternalSetTriangle(splitInfo.NewTriangles.B, NewTriangle3, false); + + // update ref counts + ElementsRefCounts.Increment(OtherNewElemID, 2); + ElementsRefCounts.Increment(Triangle1[idx_base_d]); +} + + + +//#pragma optimize("", off) +template +void TDynamicMeshOverlay::OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo& FlipInfo) +{ + int orig_t0 = FlipInfo.Triangles.A; + int orig_t1 = FlipInfo.Triangles.B; + int base_a = FlipInfo.OriginalVerts.A; + int base_b = FlipInfo.OriginalVerts.B; + int base_c = FlipInfo.OpposingVerts.A; + int base_d = FlipInfo.OpposingVerts.B; + + // look up triangle 0 + FIndex3i Triangle0 = GetTriangle(orig_t0); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_base_a1 = BaseTriangle0.IndexOf(base_a); + int idx_base_b1 = BaseTriangle0.IndexOf(base_b); + int idx_base_c = IndexUtil::GetOtherTriIndex(idx_base_a1, idx_base_b1); + + // look up triangle 1 (must exist because base mesh would never flip a boundary edge) + FIndex3i Triangle1 = GetTriangle(orig_t1); + FIndex3i BaseTriangle1(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + int idx_base_a2 = BaseTriangle1.IndexOf(base_a); + int idx_base_b2 = BaseTriangle1.IndexOf(base_b); + int idx_base_d = IndexUtil::GetOtherTriIndex(idx_base_a2, idx_base_b2); + + // sanity checks + check(idx_base_c == BaseTriangle0.IndexOf(base_c)); + check(idx_base_d == BaseTriangle1.IndexOf(base_d)); + + // we should not have been called on a non-shared edge!! + bool bHasSharedUVEdge = IndexUtil::SamePairUnordered(Triangle0[idx_base_a1], Triangle0[idx_base_b1], Triangle1[idx_base_a2], Triangle1[idx_base_b2]); + check(bHasSharedUVEdge); + + int A = Triangle0[idx_base_a1]; + int B = Triangle0[idx_base_b1]; + int C = Triangle0[idx_base_c]; + int D = Triangle1[idx_base_d]; + + // set triangles to same index order as in FDynamicMesh::FlipEdge + int i0 = 3 * orig_t0; + ElementTriangles[i0] = C; ElementTriangles[i0+1] = D; ElementTriangles[i0+2] = B; + int i1 = 3 * orig_t1; + ElementTriangles[i1] = D; ElementTriangles[i1+1] = C; ElementTriangles[i1+2] = A; + + // update reference counts + ElementsRefCounts.Decrement(A); + ElementsRefCounts.Decrement(B); + ElementsRefCounts.Increment(C); + ElementsRefCounts.Increment(D); +} + + + + + +//#pragma optimize("", off) +template +void TDynamicMeshOverlay::OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo& collapseInfo) +{ + int vid_base_kept = collapseInfo.KeptVertex; + int vid_base_removed = collapseInfo.RemovedVertex; + int tid_removed0 = collapseInfo.RemovedTris.A; + int tid_removed1 = collapseInfo.RemovedTris.B; + + // look up triangle 0 + FIndex3i Triangle0 = GetTriangle(tid_removed0); + FIndex3i BaseTriangle0(ParentVertices[Triangle0.A], ParentVertices[Triangle0.B], ParentVertices[Triangle0.C]); + int idx_removed0_a = BaseTriangle0.IndexOf(vid_base_kept); + int idx_removed0_b = BaseTriangle0.IndexOf(vid_base_removed); + + // look up triangle 1 if this is not a boundary edge + FIndex3i Triangle1, BaseTriangle1; + if (collapseInfo.bIsBoundary == false) + { + Triangle1 = GetTriangle(tid_removed1); + BaseTriangle1 = FIndex3i(ParentVertices[Triangle1.A], ParentVertices[Triangle1.B], ParentVertices[Triangle1.C]); + + int idx_removed1_a = BaseTriangle1.IndexOf(vid_base_kept); + int idx_removed1_b = BaseTriangle1.IndexOf(vid_base_removed); + + // if this is an internal edge it cannot be a seam or we cannot collapse + bool bHasSharedUVEdge = IndexUtil::SamePairUnordered(Triangle0[idx_removed0_a], Triangle0[idx_removed0_b], Triangle1[idx_removed1_a], Triangle1[idx_removed1_b]); + check(bHasSharedUVEdge); + } + + // need to find the elementid for the "kept" vertex. Since this isn't a seam, + // there is just one, and we can grab it from "old" removed triangle + int kept_elemid = FDynamicMesh3::InvalidID; + for (int j = 0; j < 3; ++j) + { + if (BaseTriangle0[j] == vid_base_kept) + { + kept_elemid = Triangle0[j]; + } + } + check(kept_elemid != FDynamicMesh3::InvalidID); + + // look for still-existing triangles that have elements linked to the removed vertex. + // in that case, replace with the element we found + TArray removed_elements; + for (int onering_tid : ParentMesh->VtxTrianglesItr(vid_base_kept)) + { + FIndex3i elem_tri = GetTriangle(onering_tid); + for (int j = 0; j < 3; ++j) + { + int elem_id = elem_tri[j]; + if (ParentVertices[elem_id] == vid_base_removed) + { + ElementsRefCounts.Decrement(elem_id); + removed_elements.AddUnique(elem_id); + ElementTriangles[3*onering_tid + j] = kept_elemid; + ElementsRefCounts.Increment(kept_elemid); + } + } + } + check(removed_elements.Num() == 1); // should always be true for non-seam edges... + + // update position of kept element + SetElementFromLerp(kept_elemid, kept_elemid, removed_elements[0], collapseInfo.CollapseT); + + // clear the two triangles we removed + for (int j = 0; j < 3; ++j) + { + ElementsRefCounts.Decrement(Triangle0[j]); + ElementTriangles[3*tid_removed0 + j] = FDynamicMesh3::InvalidID; + if (collapseInfo.bIsBoundary == false) + { + ElementsRefCounts.Decrement(Triangle1[j]); + ElementTriangles[3*tid_removed1 + j] = FDynamicMesh3::InvalidID; + } + } + + // remove the elements associated with the removed vertex + for (int k = 0; k < removed_elements.Num(); ++k) + { + check(ElementsRefCounts.GetRefCount(removed_elements[k]) == 1); + ElementsRefCounts.Decrement(removed_elements[k]); + ParentVertices[removed_elements[k]] = FDynamicMesh3::InvalidID; + } + +} + + + +template +void TDynamicMeshOverlay::OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo& PokeInfo) +{ + FIndex3i Triangle = GetTriangle(PokeInfo.OriginalTriangle); + + // create new element at barycentric position + int CenterElemID = AppendElement((RealType)0, PokeInfo.NewVertex); + FVector3 BaryCoords((RealType)PokeInfo.BaryCoords.X, (RealType)PokeInfo.BaryCoords.Y, (RealType)PokeInfo.BaryCoords.Z); + SetElementFromBary(CenterElemID, Triangle[0], Triangle[1], Triangle[2], BaryCoords); + + // update orig triangle and two new ones. Winding orders here mirror FDynamicMesh3::PokeTriangle + SetTriangle(PokeInfo.OriginalTriangle, FIndex3i(Triangle[0], Triangle[1], CenterElemID) ); + SetTriangle(PokeInfo.NewTriangles.A, FIndex3i(Triangle[1], Triangle[2], CenterElemID)); + SetTriangle(PokeInfo.NewTriangles.B, FIndex3i(Triangle[2], Triangle[0], CenterElemID)); + + ElementsRefCounts.Increment(Triangle[0]); + ElementsRefCounts.Increment(Triangle[1]); + ElementsRefCounts.Increment(Triangle[2]); + ElementsRefCounts.Increment(CenterElemID, 3); +} + + +template +void TDynamicMeshOverlay::OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo& MergeInfo) +{ + // MergeEdges just merges vertices. For now we will not also merge UVs. So all we need to + // do is rewrite the UV parent vertices + + FIndex3i ModifiedEdges(MergeInfo.KeptEdge, MergeInfo.ExtraKeptEdges.A, MergeInfo.ExtraKeptEdges.B); + for (int EdgeIdx = 0; EdgeIdx < 3; ++EdgeIdx) + { + if (ParentMesh->IsEdge(ModifiedEdges[EdgeIdx]) == false) + { + continue; + } + + FIndex2i EdgeTris = ParentMesh->GetEdgeT(ModifiedEdges[EdgeIdx]); + for (int j = 0; j < 2; ++j) + { + FIndex3i ElemTriangle = GetTriangle(EdgeTris[j]); + for (int k = 0; k < 3; ++k) + { + int ParentVID = ParentVertices[ElemTriangle[k]]; + if (ParentVID == MergeInfo.RemovedVerts.A) + { + ParentVertices[ElemTriangle[k]] = MergeInfo.KeptVerts.A; + } + else if (ParentVID == MergeInfo.RemovedVerts.B) + { + ParentVertices[ElemTriangle[k]] = MergeInfo.KeptVerts.B; + } + } + } + } +} + + + + +template +void TDynamicMeshOverlay::SetElementFromLerp(int SetElement, int ElementA, int ElementB, RealType Alpha) +{ + int IndexSet = ElementSize * SetElement; + int IndexA = ElementSize * ElementA; + int IndexB = ElementSize * ElementB; + RealType Beta = ((RealType)1 - Alpha); + for (int i = 0; i < ElementSize; ++i) + { + Elements[IndexSet+i] = Beta*Elements[IndexA+i] + Alpha*Elements[IndexB+i]; + } +} + +template +void TDynamicMeshOverlay::SetElementFromBary(int SetElement, int ElementA, int ElementB, int ElementC, const FVector3& BaryCoords) +{ + int IndexSet = ElementSize * SetElement; + int IndexA = ElementSize * ElementA; + int IndexB = ElementSize * ElementB; + int IndexC = ElementSize * ElementC; + for (int i = 0; i < ElementSize; ++i) + { + Elements[IndexSet + i] = + BaryCoords.X*Elements[IndexA+i] + BaryCoords.Y*Elements[IndexB+i] + BaryCoords.Z*Elements[IndexC+i]; + } +} + + +//#pragma optimize("", off) +template +bool TDynamicMeshOverlay::CheckValidity(bool bAllowNonManifoldVertices, EValidityCheckFailMode FailMode) const +{ + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("TDynamicMeshOverlay::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("TDynamicMeshOverlay::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + // @todo: check that all connected element-pairs are also edges in parentmesh + + // check that parent vtx of each element is actually a vertex + for (int elemid : ElementIndicesItr()) + { + int ParentVID = GetParentVertex(elemid); + CheckOrFailF(ParentMesh->IsVertex(ParentVID)); + } + + // check that parent vertices of each element triangle are the same as base triangle + for (int tid : ParentMesh->TriangleIndicesItr()) + { + FIndex3i ElemTri = GetTriangle(tid); + FIndex3i BaseTri = ParentMesh->GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (ElemTri[j] != FDynamicMesh3::InvalidID) + { + CheckOrFailF(GetParentVertex(ElemTri[j]) == BaseTri[j]); + } + } + } + + // count references to each element + TArray RealRefCounts; RealRefCounts.Init(0, MaxElementID()); + for (int tid : ParentMesh->TriangleIndicesItr()) + { + FIndex3i Tri = GetTriangle(tid); + for (int j = 0; j < 3; ++j) + { + if (Tri[j] != FDynamicMesh3::InvalidID) + { + RealRefCounts[Tri[j]] += 1; + } + } + } + // verify that refcount list counts are same as actual reference counts + for (int elemid : ElementsRefCounts.Indices()) + { + int CurRefCount = ElementsRefCounts.GetRefCount(elemid); + CheckOrFailF(CurRefCount == RealRefCounts[elemid] + 1); + } + + return is_ok; +} + + + + + +// These are explicit instantiations of the templates that are exported from the shared lib. +// Only these instantiations of the template can be used. +// This is necessary because we have placed most of the templated functions in this .cpp file, instead of the header. +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; +template class DYNAMICMESH_API TDynamicMeshOverlay; + +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; +template class DYNAMICMESH_API TDynamicMeshVectorOverlay; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeLoop.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeLoop.cpp new file mode 100644 index 000000000000..bf107effc261 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeLoop.cpp @@ -0,0 +1,251 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "EdgeLoop.h" + + +void FEdgeLoop::Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn) +{ + Mesh = mesh; + Vertices = vertices; + Edges = edges; + if (BowtieVerticesIn != nullptr) + { + BowtieVertices = *BowtieVerticesIn; + bBowtiesCalculated = true; + } +} + + +void FEdgeLoop::InitializeFromEdges(const TArray& EdgesIn) +{ + check(Mesh != nullptr); + Edges = EdgesIn; + + int NumEdges = Edges.Num(); + Vertices.SetNum(NumEdges); + + FIndex2i start_ev = Mesh->GetEdgeV(Edges[0]); + FIndex2i prev_ev = start_ev; + for (int i = 1; i < NumEdges; ++i) + { + FIndex2i next_ev = Mesh->GetEdgeV(Edges[i % NumEdges]); + Vertices[i] = IndexUtil::FindSharedEdgeVertex(prev_ev, next_ev); + prev_ev = next_ev; + } + Vertices[0] = IndexUtil::FindEdgeOtherVertex(start_ev, Vertices[1]); +} + + +bool FEdgeLoop::InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient) +{ + check(Mesh != nullptr); + Vertices = VerticesIn; + + int NumVertices = Vertices.Num(); + Edges.SetNum(NumVertices); + for (int i = 0; i < NumVertices; ++i) + { + int a = Vertices[i], b = Vertices[(i + 1) % NumVertices]; + Edges[i] = Mesh->FindEdge(a, b); + if (Edges[i] == FDynamicMesh3::InvalidID) + { + checkf(false, TEXT("EdgeLoop.FromVertices: invalid edge [%d,%d]"), a, b); + return false; + } + } + + if (bAutoOrient) + { + SetCorrectOrientation(); + } + + return true; +} + + +void FEdgeLoop::CalculateBowtieVertices() +{ + BowtieVertices.Reset(); + int NumVertices = Vertices.Num(); + for (int i = 0; i < NumVertices; ++i) + { + if (Mesh->IsBowtieVertex(Vertices[i])) + { + BowtieVertices.Add(Vertices[i]); + } + } + bBowtiesCalculated = true; +} + + +FAxisAlignedBox3d FEdgeLoop::GetBounds() const +{ + FAxisAlignedBox3d box = FAxisAlignedBox3d::Empty(); + for (int i = 0; i < Vertices.Num(); ++i) + { + box.Contain(Mesh->GetVertex(Vertices[i])); + } + return box; +} + + +bool FEdgeLoop::SetCorrectOrientation() +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + int eid = Edges[i]; + if (Mesh->IsBoundaryEdge(eid)) + { + int a = Vertices[i], b = Vertices[(i + 1) % NumEdges]; + FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eid); + if (ev.A == b && ev.B == a) + { + Reverse(); + return true; + } + else + { + return false; + } + } + } + return false; +} + + +bool FEdgeLoop::IsInternalLoop() const +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (Mesh->IsBoundaryEdge(Edges[i])) + { + return false; + } + } + return true; +} + + +bool FEdgeLoop::IsBoundaryLoop(const FDynamicMesh3* TestMesh) const +{ + const FDynamicMesh3* UseMesh = (TestMesh != nullptr) ? TestMesh : Mesh; + + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (UseMesh->IsBoundaryEdge(Edges[i]) == false) + { + return false; + } + } + return true; +} + + +int FEdgeLoop::FindVertexIndex(int VertexID) const +{ + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + if (Vertices[i] == VertexID) + { + return i; + } + } + return -1; +} + + +int FEdgeLoop::FindNearestVertexIndex(const FVector3d& QueryPoint) const +{ + int iNear = -1; + double fNearSqr = TNumericLimits::Max(); + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + FVector3d lv = Mesh->GetVertex(Vertices[i]); + double d2 = QueryPoint.DistanceSquared(lv); + if (d2 < fNearSqr) + { + fNearSqr = d2; + iNear = i; + } + } + return iNear; +} + + + + +bool FEdgeLoop::CheckValidity(EValidityCheckFailMode FailMode) const +{ + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + CheckOrFailF(Vertices.Num() == Edges.Num()); + for (int ei = 0; ei < Edges.Num(); ++ei) + { + FIndex2i ev = Mesh->GetEdgeV(Edges[ei]); + CheckOrFailF(Mesh->IsVertex(ev.A)); + CheckOrFailF(Mesh->IsVertex(ev.B)); + CheckOrFailF(Mesh->FindEdge(ev.A, ev.B) != FDynamicMesh3::InvalidID); + CheckOrFailF(Vertices[ei] == ev.A || Vertices[ei] == ev.B); + CheckOrFailF(Vertices[(ei + 1) % Edges.Num()] == ev.A || Vertices[(ei + 1) % Edges.Num()] == ev.B); + } + for (int vi = 0; vi < Vertices.Num(); ++vi) + { + int a = Vertices[vi], b = Vertices[(vi + 1) % Vertices.Num()]; + CheckOrFailF(Mesh->IsVertex(a)); + CheckOrFailF(Mesh->IsVertex(b)); + CheckOrFailF(Mesh->FindEdge(a, b) != FDynamicMesh3::InvalidID); + int n = 0, edge_before_b = Edges[vi], edge_after_b = Edges[(vi + 1) % Vertices.Num()]; + for (int nbr_e : Mesh->VtxEdgesItr(b)) + { + if (nbr_e == edge_before_b || nbr_e == edge_after_b) + { + n++; + } + } + CheckOrFailF(n == 2); + } + return is_ok; +} + + + +void FEdgeLoop::VertexLoopToEdgeLoop(const FDynamicMesh3* Mesh, const TArray& VertexLoop, TArray& OutEdgeLoop) +{ + // @todo this function should be in a utility class? + + int NV = VertexLoop.Num(); + OutEdgeLoop.SetNum(NV); + for (int i = 0; i < NV; ++i) + { + int v0 = VertexLoop[i]; + int v1 = VertexLoop[(i + 1) % NV]; + OutEdgeLoop[i] = Mesh->FindEdge(v0, v1); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeSpan.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeSpan.cpp new file mode 100644 index 000000000000..dfff9c575320 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/EdgeSpan.cpp @@ -0,0 +1,279 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EdgeSpan.h" + +void FEdgeSpan::Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn) +{ + Mesh = mesh; + Vertices = vertices; + Edges = edges; + if (BowtieVerticesIn != nullptr) + { + BowtieVertices = *BowtieVerticesIn; + bBowtiesCalculated = true; + } +} + + + +void FEdgeSpan::InitializeFromEdges(const TArray& EdgesIn) +{ + check(Mesh != nullptr); + Edges = EdgesIn; + + int NumEdges = Edges.Num(); + Vertices.SetNum(NumEdges + 1); + + FIndex2i start_ev = Mesh->GetEdgeV(Edges[0]); + FIndex2i prev_ev = start_ev; + if (NumEdges > 1) + { + for (int i = 1; i < Edges.Num(); ++i) + { + FIndex2i next_ev = Mesh->GetEdgeV(Edges[i]); + Vertices[i] = IndexUtil::FindSharedEdgeVertex(prev_ev, next_ev); + prev_ev = next_ev; + } + Vertices[0] = IndexUtil::FindEdgeOtherVertex(start_ev, Vertices[1]); + Vertices[Vertices.Num() - 1] = IndexUtil::FindEdgeOtherVertex(prev_ev, Vertices[Vertices.Num() - 2]); + } + else + { + Vertices[0] = start_ev[0]; + Vertices[1] = start_ev[1]; + } +} + + + + +bool FEdgeSpan::InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient) +{ + check(Mesh != nullptr); + Vertices = VerticesIn; + + int NumVertices = Vertices.Num(); + Edges.SetNum(NumVertices - 1); + for (int i = 0; i < NumVertices - 1; ++i) + { + int a = Vertices[i], b = Vertices[i + 1]; + Edges[i] = Mesh->FindEdge(a, b); + if (Edges[i] == FDynamicMesh3::InvalidID) + { + checkf(false, TEXT("EdgeSpan.FromVertices: invalid edge [%d,%d]"), a, b); + return false; + } + } + + if (bAutoOrient) + { + SetCorrectOrientation(); + } + + return true; +} + + +void FEdgeSpan::CalculateBowtieVertices() +{ + BowtieVertices.Reset(); + int NumVertices = Vertices.Num(); + for (int i = 0; i < NumVertices; ++i) + { + if (Mesh->IsBowtieVertex(Vertices[i])) + { + BowtieVertices.Add(Vertices[i]); + } + } + bBowtiesCalculated = true; +} + + + +FAxisAlignedBox3d FEdgeSpan::GetBounds() const +{ + FAxisAlignedBox3d box = FAxisAlignedBox3d::Empty(); + for (int i = 0; i < Vertices.Num(); ++i) + { + box.Contain(Mesh->GetVertex(Vertices[i])); + } + return box; +} + + + +void FEdgeSpan::GetPolyline(FPolyline3d& PolylineOut) const +{ + PolylineOut.Clear(); + for (int i = 0; i < Vertices.Num(); ++i) + { + PolylineOut.AppendVertex(Mesh->GetVertex(Vertices[i])); + } +} + + +bool FEdgeSpan::SetCorrectOrientation() +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + int eid = Edges[i]; + if (Mesh->IsBoundaryEdge(eid)) + { + int a = Vertices[i], b = Vertices[i + 1]; + FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eid); + if (ev.A == b && ev.B == a) + { + Reverse(); + return true; + } + else + { + return false; + } + } + } + return false; +} + + + +bool FEdgeSpan::IsInternalspan() const +{ + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (Mesh->IsBoundaryEdge(Edges[i])) + { + return false; + } + } + return true; +} + + +bool FEdgeSpan::IsBoundaryspan(const FDynamicMesh3* TestMesh) const +{ + const FDynamicMesh3* UseMesh = (TestMesh != nullptr) ? TestMesh : Mesh; + + int NumEdges = Edges.Num(); + for (int i = 0; i < NumEdges; ++i) + { + if (UseMesh->IsBoundaryEdge(Edges[i]) == false) + { + return false; + } + } + return true; +} + + + +int FEdgeSpan::FindVertexIndex(int VertexID) const +{ + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + if (Vertices[i] == VertexID) + { + return i; + } + } + return -1; +} + + + +int FEdgeSpan::FindNearestVertexIndex(const FVector3d& QueryPoint) const +{ + int iNear = -1; + double fNearSqr = TNumericLimits::Max(); + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + FVector3d lv = Mesh->GetVertex(Vertices[i]); + double d2 = QueryPoint.DistanceSquared(lv); + if (d2 < fNearSqr) + { + fNearSqr = d2; + iNear = i; + } + } + return iNear; +} + + + +bool FEdgeSpan::CheckValidity(EValidityCheckFailMode FailMode) const +{ + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeSpan::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeSpan::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + + CheckOrFailF(Vertices.Num() == (Edges.Num() + 1)); + for (int ei = 0; ei < Edges.Num(); ++ei) + { + FIndex2i ev = Mesh->GetEdgeV(Edges[ei]); + CheckOrFailF(Mesh->IsVertex(ev.A)); + CheckOrFailF(Mesh->IsVertex(ev.B)); + CheckOrFailF(Mesh->FindEdge(ev.A, ev.B) != FDynamicMesh3::InvalidID); + CheckOrFailF(Vertices[ei] == ev.A || Vertices[ei] == ev.B); + CheckOrFailF(Vertices[ei + 1] == ev.A || Vertices[ei + 1] == ev.B); + } + for (int vi = 0; vi < Vertices.Num() - 1; ++vi) + { + int a = Vertices[vi], b = Vertices[vi + 1]; + CheckOrFailF(Mesh->IsVertex(a)); + CheckOrFailF(Mesh->IsVertex(b)); + CheckOrFailF(Mesh->FindEdge(a, b) != FDynamicMesh3::InvalidID); + + // @todo rewrite this test for span, has to handle endpoint vertices that only have one nbr + if (vi < Vertices.Num() - 2) { + int n = 0, edge_before_b = Edges[vi], edge_after_b = Edges[(vi + 1) % Vertices.Num()]; + for (int nbr_e : Mesh->VtxEdgesItr(b)) + { + if (nbr_e == edge_before_b || nbr_e == edge_after_b) + { + n++; + } + } + CheckOrFailF(n == 2); + } + } + return is_ok; +} + + + +void FEdgeSpan::VertexSpanToEdgeSpan(const FDynamicMesh3* Mesh, const TArray& VertexSpan, TArray& OutEdgeSpan) +{ + // @todo this function should be in a utility class? + + int NV = VertexSpan.Num(); + OutEdgeSpan.SetNum(NV - 1); + for (int i = 0; i < NV - 1; ++i) + { + int v0 = VertexSpan[i]; + int v1 = VertexSpan[i + 1]; + OutEdgeSpan[i] = Mesh->FindEdge(v0, v1); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/PlanarPolygonMeshGenerator.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/PlanarPolygonMeshGenerator.cpp new file mode 100644 index 000000000000..7ecd28c5b35e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/PlanarPolygonMeshGenerator.cpp @@ -0,0 +1,73 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Generators/PlanarPolygonMeshGenerator.h" +#include "CompGeom/PolygonTriangulation.h" + +FPlanarPolygonMeshGenerator::FPlanarPolygonMeshGenerator() +{ + Normal = FVector3f::UnitZ(); + IndicesMap = FIndex2i(0, 1); +} + + + +void FPlanarPolygonMeshGenerator::SetPolygon(const TArray& PolygonVerts) +{ + Polygon = FPolygon2d(); + int NumVerts = PolygonVerts.Num(); + for (int i = 0; i < NumVerts; ++i) + { + Polygon.AppendVertex(PolygonVerts[i]); + } +} + + + + +FMeshShapeGenerator& FPlanarPolygonMeshGenerator::Generate() +{ + int NumVertices = Polygon.VertexCount(); + check(NumVertices >= 3); + if (NumVertices < 3) + { + return *this; + } + + TArray TriangleList; + PolygonTriangulation::TriangulateSimplePolygon(Polygon.GetVertices(), TriangleList); + + int NumTriangles = TriangleList.Num(); + SetBufferSizes(NumVertices, NumTriangles, NumVertices, NumVertices); + + FAxisAlignedBox2d BoundingBox = Polygon.Bounds(); + double Width = BoundingBox.Width(), Height = BoundingBox.Height(); + double UVScale = FMath::Max(Width, Height); + + for (int VertIdx = 0; VertIdx < NumVertices; ++VertIdx) + { + FVector2d Pos = Polygon[VertIdx]; + + UVs[VertIdx] = FVector2f( + (float)((Pos.X - BoundingBox.Min.X) / UVScale), + (float)((Pos.Y - BoundingBox.Min.Y) / UVScale)); + UVParentVertex[VertIdx] = VertIdx; + + Normals[VertIdx] = Normal; + NormalParentVertex[VertIdx] = VertIdx; + + Vertices[VertIdx] = MakeVertex(Pos.X, Pos.Y); + } + + + for (int TriIdx = 0; TriIdx < NumTriangles; TriIdx++) + { + FIndex3i Tri = TriangleList[TriIdx]; + SetTriangle(TriIdx, Tri); + SetTriangleUVs(TriIdx, Tri); + SetTriangleNormals(TriIdx, Tri); + SetTrianglePolygon(TriIdx, 0); + } + + + return *this; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/RectangleMeshGenerator.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/RectangleMeshGenerator.cpp new file mode 100644 index 000000000000..4ed4d4345227 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Generators/RectangleMeshGenerator.cpp @@ -0,0 +1,104 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Generators/RectangleMeshGenerator.h" + + +FRectangleMeshGenerator::FRectangleMeshGenerator() +{ + Origin = FVector3d::Zero(); + Width = 10.0f; + Height = 10.0f; + WidthVertexCount = HeightVertexCount = 8; + Normal = FVector3f::UnitZ(); + IndicesMap = FIndex2i(0, 1); + bScaleUVByAspectRatio = true; +} + + + +FMeshShapeGenerator& FRectangleMeshGenerator::Generate() +{ + check(IndicesMap.A >= 0 && IndicesMap.A <= 2); + check(IndicesMap.B >= 0 && IndicesMap.B <= 2); + + int WidthNV = (WidthVertexCount > 1) ? WidthVertexCount : 2; + int HeightNV = (HeightVertexCount > 1) ? HeightVertexCount : 2; + + int TotalNumVertices = WidthNV * HeightNV; + int TotalNumTriangles = 2 * (WidthNV - 1) * (HeightNV - 1); + SetBufferSizes(TotalNumVertices, TotalNumTriangles, TotalNumVertices, TotalNumVertices); + + // corner vertices + FVector3d v00 = MakeVertex(-Width / 2.0f, -Height / 2.0f); + FVector3d v01 = MakeVertex(Width / 2.0f, -Height / 2.0f); + FVector3d v11 = MakeVertex(Width / 2.0f, Height / 2.0f); + FVector3d v10 = MakeVertex(-Width / 2.0f, Height / 2.0f); + + // corner UVs + float uvleft = 0.0f, uvright = 1.0f, uvbottom = 0.0f, uvtop = 1.0f; + if (bScaleUVByAspectRatio && Width != Height) + { + if (Width > Height) + { + uvtop = Height / Width; + } + else + { + uvright = Width / Height; + } + } + + FVector2f uv00 = FVector2f(uvleft, uvbottom); + FVector2f uv01 = FVector2f(uvright, uvbottom); + FVector2f uv11 = FVector2f(uvright, uvtop); + FVector2f uv10 = FVector2f(uvleft, uvtop); + + + int vi = 0; + int ti = 0; + + // add vertex rows + int start_vi = vi; + for (int yi = 0; yi < HeightNV; ++yi) + { + double ty = (double)yi / (double)(HeightNV - 1); + for (int xi = 0; xi < WidthNV; ++xi) + { + double tx = (double)xi / (double)(WidthNV - 1); + Normals[vi] = Normal; + NormalParentVertex[vi] = vi; + UVs[vi] = BilinearInterp(uv00, uv01, uv11, uv10, (float)tx, (float)ty); + UVParentVertex[vi] = vi; + Vertices[vi++] = BilinearInterp(v00, v01, v11, v10, tx, ty); + } + } + + // add triangulated quads + int PolyIndex = 0; + for (int y0 = 0; y0 < HeightNV - 1; ++y0) + { + for (int x0 = 0; x0 < WidthNV - 1; ++x0) + { + int i00 = start_vi + y0 * WidthNV + x0; + int i10 = start_vi + (y0 + 1)*WidthNV + x0; + int i01 = i00 + 1, i11 = i10 + 1; + + SetTriangle(ti, i00, i11, i01); + SetTrianglePolygon(ti, PolyIndex); + SetTriangleUVs(ti, i00, i11, i01); + SetTriangleNormals(ti, i00, i11, i01); + + ti++; + + SetTriangle(ti, i00, i10, i11); + SetTrianglePolygon(ti, PolyIndex); + SetTriangleUVs(ti, i00, i10, i11); + SetTriangleNormals(ti, i00, i10, i11); + + ti++; + PolyIndex++; + } + } + + return *this; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp new file mode 100644 index 000000000000..ea64df81cb64 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/GroupTopology.cpp @@ -0,0 +1,483 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GroupTopology.h" +#include "MeshRegionBoundaryLoops.h" + + + + + +bool FGroupTopology::FGroupEdge::IsConnectedToVertices(const TSet& Vertices) const +{ + for (int VertexID : Span.Vertices) + { + if (Vertices.Contains(VertexID)) + { + return true; + } + } + return false; +} + + + +FGroupTopology::FGroupTopology(const FDynamicMesh3* MeshIn, bool bAutoBuild) +{ + this->Mesh = MeshIn; + if (bAutoBuild) + { + RebuildTopology(); + } +} + + +void FGroupTopology::RebuildTopology() +{ + Groups.Reset(); + Edges.Reset(); + Corners.Reset(); + + // initialize groups map first to avoid resizes + GroupIDToGroupIndexMap.Reset(); + GroupIDToGroupIndexMap.Init(-1, Mesh->MaxGroupID()); + TArray GroupFaceCounts; + GroupFaceCounts.Init(0, Mesh->MaxGroupID()); + for (int tid : Mesh->TriangleIndicesItr()) + { + int GroupID = GetGroupID(tid); + if (GroupIDToGroupIndexMap[GroupID] == -1) + { + FGroup NewGroup; + NewGroup.GroupID = GroupID; + GroupIDToGroupIndexMap[GroupID] = Groups.Add(NewGroup); + } + GroupFaceCounts[GroupID]++; + } + for (FGroup& Group : Groups) + { + Group.Faces.Reserve(GroupFaceCounts[Group.GroupID]); + } + + + // sort faces into groups + for (int tid : Mesh->TriangleIndicesItr()) + { + int GroupID = GetGroupID(tid); + Groups[GroupIDToGroupIndexMap[GroupID]].Faces.Add(tid); + } + + // precompute junction vertices set + CornerVerticesFlags.Init(false, Mesh->MaxVertexID()); + for (int vid : Mesh->VertexIndicesItr()) + { + if (IsCornerVertex(vid)) + { + CornerVerticesFlags[vid] = true; + FCorner Corner = { vid }; + Corners.Add(Corner); + } + } + for (FCorner& Corner : Corners) + { + Mesh->GetAllVertexGroups(Corner.VertexID, Corner.NeighbourGroupIDs); + } + + + // construct boundary loops + for (FGroup& Group : Groups) + { + // finds FGroupEdges and uses to populate Group.Boundaries + ExtractGroupEdges(Group); + + // collect up .NeighbourGroupIDs and set .bIsOnBoundary + for (FGroupBoundary& Boundary : Group.Boundaries) + { + Boundary.bIsOnBoundary = false; + for (int EdgeIndex : Boundary.GroupEdges) + { + FGroupEdge& Edge = Edges[EdgeIndex]; + + int OtherGroupID = (Edge.Groups.A == Group.GroupID) ? Edge.Groups.B : Edge.Groups.A; + if (OtherGroupID != FDynamicMesh3::InvalidID) + { + Boundary.NeighbourGroupIDs.AddUnique(OtherGroupID); + } + else + { + Boundary.bIsOnBoundary = true; + } + } + } + + // make all-neighbour-groups list at group level + for (FGroupBoundary& Boundary : Group.Boundaries) + { + for (int NbrGroupID : Boundary.NeighbourGroupIDs) + { + Group.NeighbourGroupIDs.AddUnique(NbrGroupID); + } + } + } + +} + + +bool FGroupTopology::IsCornerVertex(int VertexID) const +{ + FIndex3i UniqueGroups; + int UniqueCount = 0; + for (int tid : Mesh->VtxTrianglesItr(VertexID)) + { + int GroupID = GetGroupID(tid); + if (UniqueCount == 0) + { + UniqueGroups[0] = GroupID; + UniqueCount++; + } + else if (UniqueCount == 1 && GroupID != UniqueGroups[0]) + { + UniqueGroups[1] = GroupID; + UniqueCount++; + } + else if (UniqueCount == 2 && GroupID != UniqueGroups[0] && GroupID != UniqueGroups[1]) + { + return true; + } + } + return false; +} + + + + +int FGroupTopology::GetCornerVertexID(int CornerID) const +{ + check(CornerID >= 0 && CornerID < Corners.Num()); + return Corners[CornerID].VertexID; +} + + +const FGroupTopology::FGroup* FGroupTopology::FindGroupByID(int GroupID) const +{ + if (GroupID < 0 || GroupID >= GroupIDToGroupIndexMap.Num() || GroupIDToGroupIndexMap[GroupID] == -1) + { + return nullptr; + } + return &Groups[GroupIDToGroupIndexMap[GroupID]]; +} + + +const TArray& FGroupTopology::GetGroupFaces(int GroupID) const +{ + const FGroup* Found = FindGroupByID(GroupID); + ensure(Found != nullptr); + return (Found != nullptr) ? Found->Faces : EmptyArray; +} + +const TArray& FGroupTopology::GetGroupNbrGroups(int GroupID) const +{ + const FGroup* Found = FindGroupByID(GroupID); + ensure(Found != nullptr); + return (Found != nullptr) ? Found->NeighbourGroupIDs : EmptyArray; +} + + + +int FGroupTopology::FindGroupEdgeID(int MeshEdgeID) const +{ + int GroupID = GetGroupID(Mesh->GetEdgeT(MeshEdgeID).A); + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeID : Boundary.GroupEdges) + { + const FGroupEdge& Edge = Edges[EdgeID]; + if (Edge.Span.Edges.Contains(MeshEdgeID)) + { + return EdgeID; + } + } + } + } + return -1; +} + + +const TArray& FGroupTopology::GetGroupEdgeVertices(int GroupEdgeID) const +{ + check(GroupEdgeID >= 0 && GroupEdgeID < Edges.Num()); + return Edges[GroupEdgeID].Span.Vertices; +} + + + +void FGroupTopology::FindEdgeNbrGroups(int GroupEdgeID, TArray& GroupsOut) const +{ + check(GroupEdgeID >= 0 && GroupEdgeID < Edges.Num()); + const TArray & Vertices = GetGroupEdgeVertices(GroupEdgeID); + FindVertexNbrGroups(Vertices[0], GroupsOut); + FindVertexNbrGroups(Vertices[Vertices.Num() - 1], GroupsOut); +} + +void FGroupTopology::FindEdgeNbrGroups(const TArray& GroupEdgeIDs, TArray& GroupsOut) const +{ + for (int GroupEdgeID : GroupEdgeIDs) + { + FindEdgeNbrGroups(GroupEdgeID, GroupsOut); + } +} + + + +void FGroupTopology::FindCornerNbrGroups(int CornerID, TArray& GroupsOut) const +{ + check(CornerID >= 0 && CornerID < Corners.Num()); + for (int GroupID : Corners[CornerID].NeighbourGroupIDs) + { + GroupsOut.AddUnique(GroupID); + } +} + +void FGroupTopology::FindCornerNbrGroups(const TArray& CornerIDs, TArray& GroupsOut) const +{ + for (int cid : CornerIDs) + { + FindCornerNbrGroups(cid, GroupsOut); + } +} + + + + +void FGroupTopology::FindVertexNbrGroups(int VertexID, TArray& GroupsOut) const +{ + for (int tid : Mesh->VtxTrianglesItr(VertexID)) + { + int GroupID = GetGroupID(tid); + GroupsOut.AddUnique(GroupID); + } +} + +void FGroupTopology::FindVertexNbrGroups(const TArray& VertexIDs, TArray& GroupsOut) const +{ + for (int vid : VertexIDs) + { + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + int GroupID = GetGroupID(tid); + GroupsOut.AddUnique(GroupID); + } + } +} + + + +void FGroupTopology::CollectGroupVertices(int GroupID, TSet& Vertices) const +{ + const FGroup* Found = FindGroupByID(GroupID); + ensure(Found != nullptr); + if (Found != nullptr) + { + for (int TriID : Found->Faces) + { + FIndex3i TriVerts = Mesh->GetTriangle(TriID); + Vertices.Add(TriVerts.A); + Vertices.Add(TriVerts.B); + Vertices.Add(TriVerts.C); + } + } +} + + + +void FGroupTopology::CollectGroupBoundaryVertices(int GroupID, TSet& Vertices) const +{ + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + const FGroupEdge& Edge = Edges[EdgeIndex]; + for (int vid : Edge.Span.Vertices) + { + Vertices.Add(vid); + } + } + } + } +} + + + + + +void FGroupTopology::ForGroupEdges(int GroupID, + const TFunction& EdgeFunc) const +{ + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + EdgeFunc(Edges[EdgeIndex], EdgeIndex); + } + } + } +} + + + +void FGroupTopology::ForGroupSetEdges(const TArray& GroupIDs, + const TFunction& EdgeFunc) const +{ + TArray DoneEdges; + + for (int GroupID : GroupIDs) + { + const FGroup* Group = FindGroupByID(GroupID); + ensure(Group != nullptr); + if (Group != nullptr) + { + for (const FGroupBoundary& Boundary : Group->Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + if (DoneEdges.Contains(EdgeIndex) == false) + { + EdgeFunc(Edges[EdgeIndex], EdgeIndex); + DoneEdges.Add(EdgeIndex); + } + } + } + } + } +} + + + + +void FGroupTopology::ExtractGroupEdges(FGroup& Group) +{ + FMeshRegionBoundaryLoops BdryLoops(Mesh, Group.Faces, true); + int NumLoops = BdryLoops.Loops.Num(); + + Group.Boundaries.SetNum(NumLoops); + for ( int li = 0; li < NumLoops; ++li ) + { + FEdgeLoop& Loop = BdryLoops.Loops[li]; + FGroupBoundary& Boundary = Group.Boundaries[li]; + + // find indices of corners of group polygon + TArray CornerIndices; + int NumV = Loop.Vertices.Num(); + for (int i = 0; i < NumV; ++i) + { + if (CornerVerticesFlags[Loop.Vertices[i]]) + { + CornerIndices.Add(i); + } + } + + // if we had no indices then this is like the cap of a cylinder, just one single long edge + if ( CornerIndices.Num() == 0 ) + { + FIndex2i EdgeID = MakeEdgeID(Loop.Edges[0]); + int OtherGroupID = (EdgeID.A == Group.GroupID) ? EdgeID.B : EdgeID.A; + int EdgeIndex = FindExistingGroupEdge(Group.GroupID, OtherGroupID, Loop.Vertices[0]); + if (EdgeIndex == -1) + { + FGroupEdge Edge = { EdgeID }; + Edge.Span = FEdgeSpan(Mesh); + Edge.Span.InitializeFromEdges(Loop.Edges); + EdgeIndex = Edges.Add(Edge); + } + Boundary.GroupEdges.Add(EdgeIndex); + continue; + } + + // duplicate first corner vertex so that we can just loop back around to it w/ modulo count + int NumSpans = CornerIndices.Num(); + int FirstIdx = CornerIndices[0]; + CornerIndices.Add(FirstIdx); + + // add each span + for (int k = 0; k < NumSpans; ++k) + { + int i0 = CornerIndices[k]; + + FIndex2i EdgeID = MakeEdgeID(Loop.Edges[i0]); + int OtherGroupID = (EdgeID.A == Group.GroupID) ? EdgeID.B : EdgeID.A; + int EdgeIndex = FindExistingGroupEdge(Group.GroupID, OtherGroupID, Loop.Vertices[i0]); + if (EdgeIndex != -1) + { + FGroupEdge& Existing = Edges[EdgeIndex]; + Boundary.GroupEdges.Add(EdgeIndex); + continue; + } + + FGroupEdge Edge = { EdgeID }; + + int i1 = CornerIndices[k+1]; + check(i0 != i1); // this case will happen on a cylinder w/ a cut side, I think... + // (but that case won't exist because that's wouldn't be a group boundary edge!) + TArray SpanVertices; + while (i0 != i1) + { + SpanVertices.Add(Loop.Vertices[i0]); + i0 = (i0 + 1) % NumV; + } + SpanVertices.Add(Loop.Vertices[i0]); + + Edge.Span = FEdgeSpan(Mesh); + Edge.Span.InitializeFromVertices(SpanVertices); + EdgeIndex = Edges.Add(Edge); + Boundary.GroupEdges.Add(EdgeIndex); + } + } +} + + + + + + +int FGroupTopology::FindExistingGroupEdge(int GroupID, int OtherGroupID, int FirstVertexID) +{ + if (OtherGroupID < 0) + { + return -1; + } + + FGroup& OtherGroup = Groups[GroupIDToGroupIndexMap[OtherGroupID]]; + FIndex2i EdgeID = MakeEdgeID(GroupID, OtherGroupID); + + for (FGroupBoundary& Boundary : OtherGroup.Boundaries) + { + for (int EdgeIndex : Boundary.GroupEdges) + { + if (Edges[EdgeIndex].Groups == EdgeID) + { + // same EdgeID pair may occur multiple times in the same boundary loop! + // need to check that at least one endpoint is the same vertex + + TArray& Vertices = Edges[EdgeIndex].Span.Vertices; + if (Vertices[0] == FirstVertexID || Vertices[Vertices.Num() - 1] == FirstVertexID) + { + return EdgeIndex; + } + } + } + } + return -1; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshAdapterUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshAdapterUtil.cpp new file mode 100644 index 000000000000..0011fa2bc6dd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshAdapterUtil.cpp @@ -0,0 +1,75 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshAdapterUtil.h" + +FPointSetAdapterd MeshAdapterUtil::MakeVerticesAdapter(const FDynamicMesh3* Mesh) +{ + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxVertexID(); }; + Adapter.PointCount = [Mesh]() { return Mesh->VertexCount(); }; + Adapter.IsPoint = [Mesh](int Idx) { return Mesh->IsVertex(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetVertex(Idx); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [Mesh] { return Mesh->HasVertexNormals(); }; + Adapter.GetPointNormal = [Mesh](int Idx) {return Mesh->GetVertexNormal(Idx); }; + + return Adapter; +} + + +FPointSetAdapterd MeshAdapterUtil::MakeTriCentroidsAdapter(const FDynamicMesh3* Mesh) +{ + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxTriangleID(); }; + Adapter.PointCount = [Mesh]() { return Mesh->TriangleCount(); }; + Adapter.IsPoint = [Mesh](int Idx) { return Mesh->IsTriangle(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetTriCentroid(Idx); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [] { return true; }; + Adapter.GetPointNormal = [Mesh](int Idx) {return (FVector3f)Mesh->GetTriNormal(Idx); }; + + return Adapter; +} + + + + +FPointSetAdapterd MeshAdapterUtil::MakeEdgeMidpointsAdapter(const FDynamicMesh3* Mesh) +{ + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxEdgeID(); }; + Adapter.PointCount = [Mesh]() { return Mesh->EdgeCount(); }; + Adapter.IsPoint = [Mesh] (int Idx) { return Mesh->IsEdge(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetEdgePoint(Idx, 0.5); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [] { return false; }; + Adapter.GetPointNormal = [](int Idx) { return FVector3f::UnitY();}; + + return Adapter; +} + + +FPointSetAdapterd MeshAdapterUtil::MakeBoundaryEdgeMidpointsAdapter(const FDynamicMesh3* Mesh) +{ + // may be possible to do this more quickly by directly iterating over Mesh.EdgesBuffer[eid*4+3] (still need to check valid) + int NumBoundaryEdges = 0; + for (int eid : Mesh->BoundaryEdgeIndicesItr()) + { + NumBoundaryEdges++; + } + + FPointSetAdapterd Adapter; + Adapter.MaxPointID = [Mesh]() { return Mesh->MaxEdgeID(); }; + Adapter.PointCount = [NumBoundaryEdges]() { return NumBoundaryEdges; }; + Adapter.IsPoint = [Mesh](int Idx) { return Mesh->IsEdge(Idx) && Mesh->IsBoundaryEdge(Idx); }; + Adapter.GetPoint = [Mesh](int Idx) { return Mesh->GetEdgePoint(Idx, 0.5); }; + Adapter.Timestamp = [Mesh] { return Mesh->GetTimestamp(); }; + + Adapter.HasNormals = [] { return false; }; + Adapter.GetPointNormal = [](int Idx) { return FVector3f::UnitY(); }; + + return Adapter; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshBoundaryLoops.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshBoundaryLoops.cpp new file mode 100644 index 000000000000..61bb18c41d5f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshBoundaryLoops.cpp @@ -0,0 +1,655 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "MeshBoundaryLoops.h" + + + +int FMeshBoundaryLoops::GetMaxVerticesLoopIndex() const +{ + int j = 0; + for (int i = 1; i < Loops.Num(); ++i) + { + if (Loops[i].Vertices.Num() > Loops[j].Vertices.Num()) + { + j = i; + } + } + return j; +} + + +FIndex2i FMeshBoundaryLoops::FindVertexInLoop(int VertexID) const +{ + int N = Loops.Num(); + for (int li = 0; li < N; ++li) + { + int idx = Loops[li].FindVertexIndex(VertexID); + if (idx >= 0) + { + return FIndex2i(li, idx); + } + } + return FIndex2i::Invalid(); +} + + + +int FMeshBoundaryLoops::FindLoopContainingVertex(int VertexID) const +{ + int N = Loops.Num(); + for (int li = 0; li < N; ++li) + { + if (Loops[li].Vertices.Contains(VertexID)) + { + return li; + } + } + return -1; +} + + +int FMeshBoundaryLoops::FindLoopContainingEdge(int EdgeID) const +{ + int N = Loops.Num(); + for (int li = 0; li < N; ++li) + { + if (Loops[li].Edges.Contains(EdgeID)) + { + return li; + } + } + return -1; +} + + + + +bool FMeshBoundaryLoops::Compute() +{ + // This algorithm assumes that triangles are oriented consistently, + // so closed boundary-loop can be followed by walking edges in-order + + Loops.Reset(); Spans.Reset(); + bSawOpenSpans = bFellBackToSpansOnFailure = false; + + // early-out if we don't actually have boundaries + if (Mesh->IsClosed()) + { + return true; + } + + int NE = Mesh->MaxEdgeID(); + + // Temporary memory used to indicate when we have "used" an edge. + TArray used_edge; + used_edge.Init(false, Mesh->MaxEdgeID()); + + // current loop is stored here, cleared after each loop extracted + TArray loop_edges; + TArray loop_verts; + TArray bowties; + + // Temp buffer for reading back all boundary edges of a vertex. + // probably always small but : pathological cases it could be large... + TArray all_e; + all_e.Reserve(32); + + // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? + + // process all edges of mesh + for (int eid = 0; eid < NE; ++eid) + { + if (Mesh->IsEdge(eid) == false) + { + continue; + } + if (used_edge[eid] == true) + { + continue; + } + if (Mesh->IsBoundaryEdge(eid) == false) + { + continue; + } + + if (EdgeFilterFunc != nullptr && EdgeFilterFunc(eid) == false) + { + used_edge[eid] = true; + continue; + } + + // ok this is start of a boundary chain + int eStart = eid; + used_edge[eStart] = true; + loop_edges.Add(eStart); + + int eCur = eid; + + // follow the chain in order of oriented edges + bool bClosed = false; + bool bIsOpenSpan = false; + while (!bClosed) + { + FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eCur); + int cure_a = ev.A, cure_b = ev.B; + if (bIsOpenSpan) + { + cure_a = ev.B; cure_b = ev.A; + } + else + { + loop_verts.Add(cure_a); + } + + int e0 = -1, e1 = 1; + int bdry_nbrs = Mesh->GetVtxBoundaryEdges(cure_b, e0, e1); + + // have to filter this list, if we are filtering. this is ugly. + if (EdgeFilterFunc != nullptr) + { + if (bdry_nbrs > 2) + { + // we may repreat this below...irritating... + all_e.Reset(); + int num_be = Mesh->GetAllVtxBoundaryEdges(cure_b, all_e); + num_be = BufferUtil::CountValid(all_e, EdgeFilterFunc, num_be); + } + else + { + if (EdgeFilterFunc(e0) == false) bdry_nbrs--; + if (EdgeFilterFunc(e1) == false) bdry_nbrs--; + } + } + + + if (bdry_nbrs < 2) + { // hit an 'endpoint' vertex (should only happen when Filter is on...) + if (SpanBehavior == ESpanBehaviors::Abort) + { + bSawOpenSpans = true; + goto CATASTROPHIC_ABORT; + } + if (bIsOpenSpan) + { + bClosed = true; + continue; + } + else + { + bIsOpenSpan = true; // begin open span + eCur = loop_edges[0]; // restart at other end of loop + Algo::Reverse(loop_edges); // do this so we can push to front + continue; + } + } + + int eNext = -1; + + if (bdry_nbrs > 2) + { + // found "bowtie" vertex...things just got complicated! + + if (cure_b == loop_verts[0]) + { + // The "end" of the current edge is the same as the start vertex. + // This means we can close the loop here. Might as well! + eNext = -2; // sentinel value used below + + } + else + { + // try to find an unused outgoing edge that is oriented properly. + // This could create sub-loops, we will handle those later + all_e.Reset(); + int num_be = Mesh->GetAllVtxBoundaryEdges(cure_b, all_e); + check(num_be == bdry_nbrs); + + if (EdgeFilterFunc != nullptr) + { + num_be = BufferUtil::FilterInPlace(all_e, EdgeFilterFunc, num_be); + } + + // Try to pick the best "turn left" vertex. + eNext = FindLeftTurnEdge(eCur, cure_b, all_e, num_be, used_edge); + if (eNext == -1) + { + if (FailureBehavior == EFailureBehaviors::Abort || SpanBehavior == ESpanBehaviors::Abort) + { + goto CATASTROPHIC_ABORT; + } + + // ok, we are stuck. all we can do now is terminate this loop and keep it as a span + if (bIsOpenSpan) + { + bClosed = true; + } + else + { + bIsOpenSpan = true; + bClosed = true; + } + continue; + } + } + + if (bowties.Contains(cure_b) == false) + { + bowties.Add(cure_b); + } + + } + else + { + // walk forward to next available edge + check(e0 == eCur || e1 == eCur); + eNext = (e0 == eCur) ? e1 : e0; + } + + if (eNext == -2) + { + // found a bowtie vert that is the same as start-of-loop, so we + // are just closing it off explicitly + bClosed = true; + } + else if (eNext == eStart) + { + // found edge at start of loop, so loop is done. + bClosed = true; + } + else if (used_edge[eNext] != false) + { + // disaster case - the next edge is already used, but it is not the start of our loop + // All we can do is convert to open span and terminate + if (FailureBehavior == EFailureBehaviors::Abort || SpanBehavior == ESpanBehaviors::Abort) + { + goto CATASTROPHIC_ABORT; + } + bIsOpenSpan = true; + bClosed = true; + + } + else + { + // push onto accumulated list + check(used_edge[eNext] == false); + loop_edges.Add(eNext); + used_edge[eNext] = true; + eCur = eNext; + } + } + + if (bIsOpenSpan) + { + bSawOpenSpans = true; + if (SpanBehavior == ESpanBehaviors::Compute) + { + Algo::Reverse(loop_edges); // orient properly + FEdgeSpan& NewSpan = Spans[Spans.Emplace()]; + NewSpan.InitializeFromEdges(Mesh, loop_edges); + } + } + else if (bowties.Num() > 0) + { + // if we saw a bowtie vertex, we might need to break up this loop, + // so call ExtractSubloops + Subloops subloops; + bool bSubloopsOK = ExtractSubloops(loop_verts, loop_edges, bowties, subloops); + if (bSubloopsOK == false) + { + goto CATASTROPHIC_ABORT; + } + for (FEdgeLoop& loop : subloops.Loops) + { + Loops.Add(loop); + } + if (subloops.Spans.Num() > 0) + { + bFellBackToSpansOnFailure = true; + for (FEdgeSpan& span : subloops.Spans) + { + Spans.Add(span); + } + } + } + else + { + // clean simple loop, convert to EdgeLoop instance + FEdgeLoop& NewLoop = Loops[Loops.Emplace()]; + NewLoop.Initialize(Mesh, loop_verts, loop_edges); + } + + // reset these lists + loop_edges.Reset(); + loop_verts.Reset(); + bowties.Reset(); + } + + return true; + +CATASTROPHIC_ABORT: + bAborted = true; + return false; +} + + + +FVector3d FMeshBoundaryLoops::GetVertexNormal(int vid) +{ + FVector3d n = FVector3d::Zero(); + for (int ti : Mesh->VtxTrianglesItr(vid)) + { + n += Mesh->GetTriNormal(ti); + } + n.Normalize(); + return n; +} + + + + +// ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. +// We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. +// If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. +// So, we compute the tangent plane at bowtie_v, and then the signed angle for each +// viable edge in this plane. +// +// [TODO] handle degenerate edges. what do we do then? Currently will only chose +// degenerate edge if there are no other options (I think...) +int FMeshBoundaryLoops::FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges) +{ + // compute normal and edge [a,bowtie] + FVector3d n = GetVertexNormal(bowtie_v); + //int other_v = Mesh->edge_other_v(incoming_e, bowtie_v); + FIndex2i ev = Mesh->GetEdgeV(incoming_e); + int other_v = (ev.A == bowtie_v) ? ev.B : ev.A; + FVector3d ab = Mesh->GetVertex(bowtie_v) - Mesh->GetVertex(other_v); + + // our winner + int best_e = -1; + double best_angle = TNumericLimits::Max(); + + for (int i = 0; i < bdry_edges_count; ++i) + { + int bdry_eid = bdry_edges[i]; + if (used_edges[bdry_eid] == true) + { + continue; // this edge is already used + } + + FIndex2i bdry_ev = Mesh->GetOrientedBoundaryEdgeV(bdry_eid); + if (bdry_ev.A != bowtie_v) + { + continue; // have to be able to chain to end of current edge, orientation-wise + } + + // compute projected angle + FVector3d bc = Mesh->GetVertex(bdry_ev.B) - Mesh->GetVertex(bowtie_v); + double fAngleS = VectorUtil::PlaneAngleSignedD(ab, bc, n); + + // turn left! + if (best_angle == TNumericLimits::Max() || fAngleS < best_angle) + { + best_angle = fAngleS; + best_e = bdry_eid; + } + } + + // [RMS] w/ bowtie vertices and open spans, this does happen + //Debug.Assert(best_e != -1); + + return best_e; +} + + + + + +// This is called when loopV contains one or more "bowtie" vertices. +// These vertices *might* be duplicated in loopV (but not necessarily) +// If they are, we have to break loopV into subloops that don't contain duplicates. +// +// The list bowties contains all the possible duplicates +// (all v in bowties occur in loopV at least once) +// +// Currently loopE is not used, and the returned EdgeLoop objects do not have their Edges +// arrays initialized. Perhaps to improve in future. +// +// An unhandled case to think about is where we have a sequence [..A..B..A..B..] where +// A and B are bowties. In this case there are no A->A or B->B subloops. What should +// we do here?? +bool FMeshBoundaryLoops::ExtractSubloops(TArray& loopV, TArray& loopE, TArray& bowties, Subloops& SubloopsOut) +{ + Subloops& subs = SubloopsOut; + + // figure out which bowties we saw are actually duplicated in loopV + TArray dupes; + for (int bv : bowties) + { + if (CountInList(loopV, bv) > 1) + { + dupes.Add(bv); + } + } + + // we might not actually have any duplicates, if we got luck. Early out in that case + if (dupes.Num() == 0) + { + FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()]; + NewLoop.Initialize(Mesh, loopV, loopE, &bowties); + return true; + } + + // This loop extracts subloops until we have dealt with all the + // duplicate vertices in loopV + while (dupes.Num() > 0) + { + + // Find shortest "simple" loop, ie a loop from a bowtie to itself that + // does not contain any other bowties. This is an independent loop. + // We're doing a lot of extra work here if we only have one element in dupes... + int bi = 0, bv = 0; + int start_i = -1, end_i = -1; + int bv_shortest = -1; int shortest = TNumericLimits::Max(); + for (; bi < dupes.Num(); ++bi) + { + bv = dupes[bi]; + if (IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i)) + { + int len = CountSpan(loopV, start_i, end_i); + if (len < shortest) + { + bv_shortest = bv; + shortest = len; + } + } + } + + // failed to find a simple loop. Not sure what to do in this situation. + // If we don't want to throw, all we can do is convert the remaining + // loop to a span and return. + // (Or should we keep it as a loop and set flag??) + if (bv_shortest == -1) + { + if (FailureBehavior == EFailureBehaviors::Abort) + { + FailureBowties = dupes; + bAborted = true; + return false; + } + + VerticesTemp.Reset(); + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + VerticesTemp.Add(loopV[i]); + } + } + + FEdgeSpan& NewSpan = subs.Spans[subs.Spans.Emplace()]; + NewSpan.InitializeFromVertices(Mesh, VerticesTemp, false); + NewSpan.SetBowtieVertices(bowties); + } + + if (bv != bv_shortest) + { + bv = bv_shortest; + // running again just to get start_i and end_i... + IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i); + } + + check(loopV[start_i] == bv && loopV[end_i] == bv); + + FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()]; + + VerticesTemp.Reset(); + ExtractSpan(loopV, start_i, end_i, true, VerticesTemp); + NewLoop.InitializeFromVertices(Mesh, VerticesTemp, false); + NewLoop.SetBowtieVertices(bowties); + + // If there are no more duplicates of this bowtie, we can treat + // it like a regular vertex now + if (CountInList(loopV, bv) < 2) + { + dupes.Remove(bv); + } + } + + // Should have one loop left that contains duplicates. + // Extract this as a separate loop + int nLeft = 0; + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + nLeft++; + } + if (nLeft > 0) + { + FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()]; + + VerticesTemp.Reset(); + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + VerticesTemp.Add(loopV[i]); + } + } + NewLoop.InitializeFromVertices(Mesh, VerticesTemp, false); + NewLoop.SetBowtieVertices(bowties); + } + + return true; +} + + + +// Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts. +// Also returns start and end indices in loopV of "clean" loop +// Note that start may be < end, if the "clean" loop wraps around the end +bool FMeshBoundaryLoops::IsSimpleBowtieLoop(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int& start_i, int& end_i) +{ + // find two indices of bowtie vert + start_i = FindIndex(LoopVerts, 0, BowtieVertex); + end_i = FindIndex(LoopVerts, start_i + 1, BowtieVertex); + + if (IsSimplePath(LoopVerts, BowtieVerts, BowtieVertex, start_i, end_i)) + { + return true; + } + else if (IsSimplePath(LoopVerts, BowtieVerts, BowtieVertex, end_i, start_i)) + { + int tmp = start_i; start_i = end_i; end_i = tmp; + return true; + } + else + { + return false; // not a simple bowtie loop! + } +} + + +// check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV +bool FMeshBoundaryLoops::IsSimplePath(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int i1, int i2) +{ + int N = LoopVerts.Num(); + for (int i = i1; i != i2; i = (i + 1) % N) + { + int vi = LoopVerts[i]; + if (vi == -1) + { + continue; // skip removed vertices + } + if (vi != BowtieVertex && BowtieVerts.Contains(vi)) + { + return false; + } + } + return true; +} + + +// Read out the span from loop[i0] to loop [i1-1] into an array. +// If bMarkInvalid, then these values are set to -1 in loop +void FMeshBoundaryLoops::ExtractSpan(TArray& Loop, int i0, int i1, bool bMarkInvalid, TArray& OutSpan) +{ + int num = CountSpan(Loop, i0, i1); + OutSpan.SetNum(num); + int ai = 0; + int N = Loop.Num(); + for (int i = i0; i != i1; i = (i + 1) % N) + { + if (Loop[i] != -1) + { + OutSpan[ai++] = Loop[i]; + if (bMarkInvalid) + { + Loop[i] = -1; + } + } + } +} + + +// count number of valid vertices in l between loop[i0] and loop[i1-1] +int FMeshBoundaryLoops::CountSpan(const TArray& Loop, int i0, int i1) +{ + int c = 0; + int N = Loop.Num(); + for (int i = i0; i != i1; i = (i + 1) % N) + { + if (Loop[i] != -1) + { + c++; + } + } + return c; +} + +// find the index of item in loop, starting at start index +int FMeshBoundaryLoops::FindIndex(const TArray& Loop, int Start, int Item) +{ + for (int i = Start; i < Loop.Num(); ++i) + { + if (Loop[i] == Item) + { + return i; + } + } + return -1; +} + +// count number of times item appears in loop +int FMeshBoundaryLoops::CountInList(const TArray& Loop, int Item) +{ + int c = 0; + for (int i = 0; i < Loop.Num(); ++i) + { + if (Loop[i] == Item) + { + c++; + } + } + return c; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshConstraintsUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshConstraintsUtil.cpp new file mode 100644 index 000000000000..049a2c1ab0db --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshConstraintsUtil.cpp @@ -0,0 +1,27 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshConstraintsUtil.h" + + +void FMeshConstraintsUtil::ConstrainAllSeams(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, bool bAllowSplits, bool bAllowSmoothing) +{ + if (Mesh.HasAttributes() == false) + { + return; + } + const FDynamicMeshAttributeSet* Attributes = Mesh.Attributes(); + + FEdgeConstraint EdgeConstraint = (bAllowSplits) ? FEdgeConstraint::SplitsOnly() : FEdgeConstraint::FullyConstrained(); + FVertexConstraint VtxConstraint = (bAllowSmoothing) ? FVertexConstraint::PinnedMovable() : FVertexConstraint::Pinned(); + + for (int EdgeID : Mesh.EdgeIndicesItr()) + { + if (Attributes->IsSeamEdge(EdgeID)) + { + Constraints.SetOrUpdateEdgeConstraint(EdgeID, EdgeConstraint); + FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.A, VtxConstraint); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.B, VtxConstraint); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshIndexUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshIndexUtil.cpp new file mode 100644 index 000000000000..4aab1391329a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshIndexUtil.cpp @@ -0,0 +1,48 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "MeshIndexUtil.h" + + +void MeshIndexUtil::ConverTriangleToVertexIDs(const FDynamicMesh3* Mesh, const TArray& TriangleIDs, TArray& VertexIDsOut) +{ + // if we are getting close to full mesh it is probably more efficient to use a bitmap... + + int NumTris = TriangleIDs.Num(); + + // @todo profile this constant + if (NumTris < 25) + { + for (int k = 0; k < NumTris; ++k) + { + if (Mesh->IsTriangle(TriangleIDs[k])) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleIDs[k]); + VertexIDsOut.AddUnique(Tri[0]); + VertexIDsOut.AddUnique(Tri[1]); + VertexIDsOut.AddUnique(Tri[2]); + } + } + } + else + { + TSet VertexSet; + VertexSet.Reserve(TriangleIDs.Num()*3); + for (int k = 0; k < NumTris; ++k) + { + if (Mesh->IsTriangle(TriangleIDs[k])) + { + FIndex3i Tri = Mesh->GetTriangle(TriangleIDs[k]); + VertexSet.Add(Tri[0]); + VertexSet.Add(Tri[1]); + VertexSet.Add(Tri[2]); + } + } + + VertexIDsOut.Reserve(VertexSet.Num()); + for (int VertexID : VertexSet) + { + VertexIDsOut.Add(VertexID); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshNormals.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshNormals.cpp new file mode 100644 index 000000000000..2f08fe2205e3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshNormals.cpp @@ -0,0 +1,138 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MeshNormals + +#include "MeshNormals.h" + + +void FMeshNormals::SetCount(int Count, bool bClearToZero) +{ + if (Normals.Num() < Count) + { + Normals.SetNum(Count); + } + if (bClearToZero) + { + for (int i = 0; i < Count; ++i) + { + Normals[i] = FVector3d::Zero(); + } + } +} + + + +void FMeshNormals::CopyToVertexNormals(FDynamicMesh3* SetMesh, bool bInvert) const +{ + if (SetMesh->HasVertexNormals() == false) + { + SetMesh->EnableVertexNormals(FVector3f::UnitX()); + } + + float sign = (bInvert) ? -1.0f : 1.0f; + int N = FMath::Min(Normals.Num(), SetMesh->MaxVertexID()); + for (int vi = 0; vi < N; ++vi) + { + if (Mesh->IsVertex(vi) && SetMesh->IsVertex(vi)) + { + SetMesh->SetVertexNormal(vi, sign * (FVector3f)Normals[vi]); + } + } +} + + + +void FMeshNormals::CopyToOverlay(FDynamicMeshNormalOverlay* NormalOverlay, bool bInvert) const +{ + float sign = (bInvert) ? -1.0f : 1.0f; + for (int ElemIdx : NormalOverlay->ElementIndicesItr()) + { + NormalOverlay->SetElement(ElemIdx, sign * (FVector3f)Normals[ElemIdx]); + } +} + + +void FMeshNormals::Compute_FaceAvg_AreaWeighted() +{ + SetCount(Mesh->MaxVertexID(), true); + + for (int TriIdx : Mesh->TriangleIndicesItr()) + { + FVector3d TriNormal, TriCentroid; double TriArea; + Mesh->GetTriInfo(TriIdx, TriNormal, TriArea, TriCentroid); + TriNormal *= TriArea; + + FIndex3i Triangle = Mesh->GetTriangle(TriIdx); + Normals[Triangle.A] += TriNormal; + Normals[Triangle.B] += TriNormal; + Normals[Triangle.C] += TriNormal; + } + + for (int VertIdx : Mesh->VertexIndicesItr()) + { + Normals[VertIdx].Normalize(); + } +} + + + +void FMeshNormals::Compute_Triangle() +{ + SetCount(Mesh->MaxTriangleID(), false); + + for (int TriIdx : Mesh->TriangleIndicesItr()) + { + Normals[TriIdx] = Mesh->GetTriNormal(TriIdx); + } +} + + + + +void FMeshNormals::Compute_Overlay_FaceAvg_AreaWeighted(const FDynamicMeshNormalOverlay* NormalOverlay) +{ + SetCount(NormalOverlay->MaxElementID(), true); + + for (int TriIdx : Mesh->TriangleIndicesItr()) + { + FVector3d TriNormal, TriCentroid; double TriArea; + Mesh->GetTriInfo(TriIdx, TriNormal, TriArea, TriCentroid); + TriNormal *= TriArea; + + FIndex3i Tri = NormalOverlay->GetTriangle(TriIdx); + for (int j = 0; j < 3; ++j) + { + if (Tri[j] != FDynamicMesh3::InvalidID) + { + Normals[Tri[j]] += TriNormal; + } + } + } + + for (int ElemIdx : NormalOverlay->ElementIndicesItr()) + { + Normals[ElemIdx].Normalize(); + } +} + + + +void FMeshNormals::QuickComputeVertexNormals(FDynamicMesh3& Mesh, bool bInvert) +{ + FMeshNormals normals(&Mesh); + normals.ComputeVertexNormals(); + normals.CopyToVertexNormals(&Mesh, bInvert); +} + + +FVector3d FMeshNormals::ComputeVertexNormal(const FDynamicMesh3& Mesh, int VertIdx) +{ + FVector3d SumNormal = FVector3d::Zero(); + for (int TriIdx : Mesh.VtxTrianglesItr(VertIdx)) + { + FVector3d Normal, Centroid; double Area; + Mesh.GetTriInfo(TriIdx, Normal, Area, Centroid); + SumNormal += Area * Normal; + } + return SumNormal.Normalized(); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRefinerBase.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRefinerBase.cpp new file mode 100644 index 000000000000..5790432090b3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRefinerBase.cpp @@ -0,0 +1,298 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshRefinerBase.h" +#include "DynamicMeshAttributeSet.h" + + + + + + +/* +* Check if edge collapse will create a face-normal flip. +* Also checks if collapse would violate link condition, since we are iterating over one-ring anyway. +* This only checks one-ring of vid, so you have to call it twice, with vid and vother reversed, to check both one-rings +*/ +bool FMeshRefinerBase::CheckIfCollapseCreatesFlipOrInvalid(int vid, int vother, const FVector3d& newv, int tc, int td) const +{ + FVector3d va = FVector3d::Zero(), vb = FVector3d::Zero(), vc = FVector3d::Zero(); + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + if (tid == tc || tid == td) + { + continue; + } + FIndex3i curt = Mesh->GetTriangle(tid); + if (curt[0] == vother || curt[1] == vother || curt[2] == vother) + { + return true; // invalid nbrhood for collapse + } + Mesh->GetTriVertices(tid, va, vb, vc); + FVector3d ncur = (vb - va).Cross(vc - va); + double sign = 0; + if (curt[0] == vid) + { + FVector3d nnew = (vb - newv).Cross(vc - newv); + sign = ComputeEdgeFlipMetric(ncur, nnew); + } + else if (curt[1] == vid) + { + FVector3d nnew = (newv - va).Cross(vc - va); + sign = ComputeEdgeFlipMetric(ncur, nnew); + } + else if (curt[2] == vid) + { + FVector3d nnew = (vb - va).Cross(newv - va); + sign = ComputeEdgeFlipMetric(ncur, nnew); + } + else + { + check(false); // this should never happen! + } + if (sign <= EdgeFlipTolerance) + { + return true; + } + } + return false; +} + + + + +/** + * Check if edge flip might reverse normal direction. + * Not entirely clear on how to best implement this test. Currently checking if any normal-pairs are reversed. + */ +bool FMeshRefinerBase::CheckIfFlipInvertsNormals(int a, int b, int c, int d, int t0) const +{ + FVector3d vC = Mesh->GetVertex(c), vD = Mesh->GetVertex(d); + FIndex3i tri_v = Mesh->GetTriangle(t0); + int oa = a, ob = b; + IndexUtil::OrientTriEdge(oa, ob, tri_v); + FVector3d vOA = Mesh->GetVertex(oa), vOB = Mesh->GetVertex(ob); + FVector3d n0 = VectorUtil::FastNormalDirection(vOA, vOB, vC); + FVector3d n1 = VectorUtil::FastNormalDirection(vOB, vOA, vD); + FVector3d f0 = VectorUtil::FastNormalDirection(vC, vD, vOB); + if (ComputeEdgeFlipMetric(n0, f0) <= EdgeFlipTolerance || ComputeEdgeFlipMetric(n1, f0) <= EdgeFlipTolerance) + { + return true; + } + FVector3d f1 = VectorUtil::FastNormalDirection(vD, vC, vOA); + if (ComputeEdgeFlipMetric(n0, f1) <= EdgeFlipTolerance || ComputeEdgeFlipMetric(n1, f1) <= EdgeFlipTolerance) + { + return true; + } + + // this only checks if output faces are pointing towards eachother, which seems + // to still result in normal-flips in some cases + //if (f0.Dot(f1) < 0) + // return true; + + return false; +} + + + + + + +/** + * Figure out if we can collapse edge eid=[a,b] under current constraint set. + * First we resolve vertex constraints using CanCollapseVertex(). However this + * does not catch some topological cases at the edge-constraint level, which + * which we will only be able to detect once we know if we are losing a or b. + * See comments on CanCollapseVertex() for what collapse_to is for. + */ +bool FMeshRefinerBase::CanCollapseEdge(int eid, int a, int b, int c, int d, int tc, int td, int& collapse_to) const +{ + collapse_to = -1; + if (Constraints == nullptr) + { + return true; + } + bool bVtx = CanCollapseVertex(eid, a, b, collapse_to); + if (bVtx == false) + { + return false; + } + + // when we lose a vtx in a collapse, we also lose two edges [iCollapse,c] and [iCollapse,d]. + // If either of those edges is constrained, we would lose that constraint. + // This would be bad. + int iCollapse = (collapse_to == a) ? b : a; + if (c != IndexConstants::InvalidID) + { + int ec = Mesh->FindEdgeFromTri(iCollapse, c, tc); + if (Constraints->GetEdgeConstraint(ec).IsUnconstrained() == false) + { + return false; + } + } + if (d != IndexConstants::InvalidID) + { + int ed = Mesh->FindEdgeFromTri(iCollapse, d, td); + if (Constraints->GetEdgeConstraint(ed).IsUnconstrained() == false) + { + return false; + } + } + + return true; +} + + + + + + + +/** + * Resolve vertex constraints for collapsing edge eid=[a,b]. Generally we would + * collapse a to b, and set the new position as 0.5*(v_a+v_b). However if a *or* b + * are constrained, then we want to keep that vertex and collapse to its position. + * This vertex (a or b) will be returned in collapse_to, which is -1 otherwise. + * If a *and* b are constrained, then things are complicated (and documented below). + */ +bool FMeshRefinerBase::CanCollapseVertex(int eid, int a, int b, int& collapse_to) const +{ + collapse_to = -1; + if (Constraints == nullptr) + { + return true; + } + FVertexConstraint ca, cb; + Constraints->GetVertexConstraint(a, ca); + Constraints->GetVertexConstraint(b, cb); + + // no constraint at all + if (ca.Fixed == false && cb.Fixed == false && ca.Target == nullptr && cb.Target == nullptr) + { + return true; + } + + // handle a or b fixed + if (ca.Fixed == true && cb.Fixed == false) + { + // if b is fixed to a target, and it is different than a's target, we can't collapse + if (cb.Target != nullptr && cb.Target != ca.Target) + { + return false; + } + collapse_to = a; + return true; + } + if (cb.Fixed == true && ca.Fixed == false) + { + if (ca.Target != nullptr && ca.Target != cb.Target) + { + return false; + } + collapse_to = b; + return true; + } + // if both fixed, and options allow, treat this edge as unconstrained (eg collapse to midpoint) + // [RMS] tried picking a or b here, but something weird happens, where + // eg cylinder cap will entirely erode away. Somehow edge lengths stay below threshold?? + if (AllowCollapseFixedVertsWithSameSetID + && ca.FixedSetID >= 0 + && ca.FixedSetID == cb.FixedSetID) + { + return true; + } + + // handle a or b w/ target + if (ca.Target != nullptr && cb.Target == nullptr) + { + collapse_to = a; + return true; + } + if (cb.Target != nullptr && ca.Target == nullptr) + { + collapse_to = b; + return true; + } + // if both vertices are on the same target, and the edge is on that target, + // then we can collapse to either and use the midpoint (which will be projected + // to the target). *However*, if the edge is not on the same target, then we + // cannot collapse because we would be changing the constraint topology! + if (cb.Target != nullptr && ca.Target != nullptr && ca.Target == cb.Target) + { + if (Constraints->GetEdgeConstraint(eid).Target == ca.Target) + { + return true; + } + } + + return false; +} + + + + + + +void FMeshRefinerBase::RuntimeDebugCheck(int eid) +{ + if (DebugEdges.Contains(eid)) + ensure(false); +} + +void FMeshRefinerBase::DoDebugChecks(bool bEndOfPass) +{ + if (DEBUG_CHECK_LEVEL == 0) + return; + + DebugCheckVertexConstraints(); + + if ((DEBUG_CHECK_LEVEL > 2) || (bEndOfPass && DEBUG_CHECK_LEVEL > 1)) + { + Mesh->CheckValidity(true); + DebugCheckUVSeamConstraints(); + } +} + + +void FMeshRefinerBase::DebugCheckUVSeamConstraints() +{ + // verify UV constraints (temporary?) + if (Mesh->HasAttributes() && Mesh->Attributes()->PrimaryUV() != nullptr && Constraints != nullptr) + { + for (int eid : Mesh->EdgeIndicesItr()) + { + if (Mesh->Attributes()->PrimaryUV()->IsSeamEdge(eid)) + { + auto cons = Constraints->GetEdgeConstraint(eid); + check(cons.IsUnconstrained() == false); + } + } + for (int vid : Mesh->VertexIndicesItr()) + { + if (Mesh->Attributes()->PrimaryUV()->IsSeamVertex(vid)) + { + auto cons = Constraints->GetVertexConstraint(vid); + check(cons.Fixed == true); + } + } + } +} + + +void FMeshRefinerBase::DebugCheckVertexConstraints() +{ + if (Constraints == nullptr) + { + return; + } + auto AllVtxConstraints = Constraints->GetVertexConstraints(); + for (const TPair& vc : AllVtxConstraints) + { + int vid = vc.Key; + if (vc.Value.Target != nullptr) + { + FVector3d curpos = Mesh->GetVertex(vid); + FVector3d projected = vc.Value.Target->Project(curpos, vid); + check((curpos - projected).SquaredLength() < 0.0001f); + } + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRegionBoundaryLoops.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRegionBoundaryLoops.cpp new file mode 100644 index 000000000000..ebfecb1a70ca --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshRegionBoundaryLoops.cpp @@ -0,0 +1,476 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "MeshRegionBoundaryLoops.h" +#include "MeshBoundaryLoops.h" // has a set of internal static functions we re-use +#include "VectorUtil.h" + + + +FMeshRegionBoundaryLoops::FMeshRegionBoundaryLoops(const FDynamicMesh3* MeshIn, const TArray& RegionTris, bool bAutoCompute) +{ + this->Mesh = MeshIn; + + // make flag set for included triangles + triangles.Init(false, Mesh->MaxTriangleID()); + for (int i = 0; i < RegionTris.Num(); ++i) + { + triangles[RegionTris[i]] = true; + } + + // make flag set for included edges + // NOTE: this currently processes non-boundary-edges twice. Could + // avoid w/ another IndexFlagSet, but the check is inexpensive... + edges.Init(false, Mesh->MaxEdgeID()); + for (int i = 0; i < RegionTris.Num(); ++i) + { + int tid = RegionTris[i]; + FIndex3i te = Mesh->GetTriEdges(tid); + for (int j = 0; j < 3; ++j) + { + int eid = te[j]; + if (!ContainsElement(edges, eid)) + { + FIndex2i et = Mesh->GetEdgeT(eid); + if (et.B == IndexConstants::InvalidID || triangles[et.A] != triangles[et.B]) + { + edges_roi.Add(eid); + edges[eid] = true; + } + } + } + } + + if (bAutoCompute) + { + Compute(); + } +} + + + +int FMeshRegionBoundaryLoops::GetMaxVerticesLoopIndex() const +{ + int j = 0; + for (int i = 1; i < Loops.Num(); ++i) + { + if (Loops[i].Vertices.Num() > Loops[j].Vertices.Num()) + { + j = i; + } + } + return j; +} + + + +bool FMeshRegionBoundaryLoops::Compute() +{ + // This algorithm assumes that triangles are oriented consistently, + // so closed boundary-loop can be followed by walking edges in-order + Loops.SetNum(0); + + // Temporary memory used to indicate when we have "used" an edge. + TArray used_edge; + used_edge.Init(false, Mesh->MaxEdgeID()); + + // current loop is stored here, cleared after each loop extracted + TArray loop_edges; + TArray loop_verts; + TArray bowties; + + // Temp buffer for reading back all boundary edges of a vertex. + // probably always small but : pathological cases it could be large... + TArray all_e; + all_e.SetNum(16); + + // process all edges of mesh + for (int eid : edges_roi) + { + if (used_edge[eid] == true) + { + continue; + } + if (IsEdgeOnBoundary(eid) == false) + { + continue; + } + + // ok this is start of a boundary chain + int eStart = eid; + used_edge[eStart] = true; + loop_edges.Add(eStart); + + int eCur = eid; + + // follow the chain : order of oriented edges + bool bClosed = false; + while (!bClosed) + { + + // [TODO] can do this more efficiently? + int tid_in = IndexConstants::InvalidID, tid_out = IndexConstants::InvalidID; + IsEdgeOnBoundary(eCur, tid_in, tid_out); + + FIndex2i ev = GetOrientedEdgeVerts(eCur, tid_in, tid_out); + int cure_a = ev.A, cure_b = ev.B; + loop_verts.Add(cure_a); + + int e0 = -1, e1 = 1; + int bdry_nbrs = GetVertexBoundaryEdges(cure_b, e0, e1); + + check(bdry_nbrs >= 2); // if (bdry_nbrs < 2) throw new MeshBoundaryLoopsException("MeshRegionBoundaryLoops.Compute: found broken neighbourhood at vertex " + cure_b){ UnclosedLoop = true }; + + int eNext = -1; + if (bdry_nbrs > 2) + { + // found "bowtie" vertex...things just got complicated! + + if (cure_b == loop_verts[0]) + { + // The "end" of the current edge is the same as the start vertex. + // This means we can close the loop here. Might as well! + eNext = -2; // sentinel value used below + + } + else + { + // try to find an unused outgoing edge that is oriented properly. + // This could create sub-loops, we will handle those later + if (bdry_nbrs >= all_e.Num()) + all_e.SetNum(bdry_nbrs); + int num_be = GetAllVertexBoundaryEdges(cure_b, all_e); + + check(num_be == bdry_nbrs); + + // Try to pick the best "turn left" vertex. + eNext = FindLeftTurnEdge(eCur, cure_b, all_e, num_be, used_edge); + + check(eNext != -1); // throw new MeshBoundaryLoopsException("MeshRegionBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b){ BowtieFailure = true }; + } + + if (bowties.Contains(cure_b) == false) + { + bowties.Add(cure_b); + } + + } + else + { + check(e0 == eCur || e1 == eCur); + eNext = (e0 == eCur) ? e1 : e0; + } + + if (eNext == -2) + { + // found a bowtie vert that is the same as start-of-loop, so we + // are just closing it off explicitly + bClosed = true; + } + else if (eNext == eStart) + { + // found edge at start of loop, so loop is done. + bClosed = true; + } + else + { + // push onto accumulated list + check(used_edge[eNext] == false); + loop_edges.Add(eNext); + eCur = eNext; + used_edge[eCur] = true; + } + } + + // if we saw a bowtie vertex, we might need to break up this loop, + // so call ExtractSubloops + if (bowties.Num() > 0) + { + TArray subloops = ExtractSubloops(loop_verts, loop_edges, bowties); + for (int i = 0; i < subloops.Num(); ++i) + { + Loops.Add(subloops[i]); + } + } + else + { + // clean simple loop, convert to FEdgeLoop instance + FEdgeLoop loop(Mesh); + loop.Vertices = loop_verts; + loop.Edges = loop_edges; + Loops.Add(loop); + } + + // reset these lists + loop_edges.SetNum(0); + loop_verts.SetNum(0); + bowties.SetNum(0); + } + + return true; +} + + + + + + +// returns true for both internal and mesh boundary edges +// tid_in and tid_out are triangles 'in' and 'out' of set, respectively +bool FMeshRegionBoundaryLoops::IsEdgeOnBoundary(int eid, int& tid_in, int& tid_out) const +{ + if (ContainsElement(edges, eid) == false) + { + return false; + } + + tid_in = tid_out = IndexConstants::InvalidID; + FIndex2i et = Mesh->GetEdgeT(eid); + if (et.B == IndexConstants::InvalidID) // boundary edge! + { + tid_in = et.A; + tid_out = et.B; + return true; + } + + bool in0 = triangles[et.A]; + bool in1 = triangles[et.B]; + if (in0 != in1) + { + tid_in = (in0) ? et.A : et.B; + tid_out = (in0) ? et.B : et.A; + return true; + } + return false; +} + + + +// return same indices as GetEdgeV, but oriented based on attached triangle +FIndex2i FMeshRegionBoundaryLoops::GetOrientedEdgeVerts(int eID, int tid_in, int tid_out) +{ + FIndex2i edgev = Mesh->GetEdgeV(eID); + int a = edgev.A, b = edgev.B; + FIndex3i tri = Mesh->GetTriangle(tid_in); + int ai = IndexUtil::FindEdgeIndexInTri(a, b, tri); + return FIndex2i(tri[ai], tri[(ai + 1) % 3]); +} + + +int FMeshRegionBoundaryLoops::GetVertexBoundaryEdges(int vID, int& e0, int& e1) +{ + int count = 0; + for (int eid : Mesh->VtxEdgesItr(vID)) + { + if (IsEdgeOnBoundary(eid)) + { + if (count == 0) + { + e0 = eid; + } + else if (count == 1) + { + e1 = eid; + } + count++; + } + } + return count; +} + + +int FMeshRegionBoundaryLoops::GetAllVertexBoundaryEdges(int vID, TArray& e) +{ + int count = 0; + for (int eid : Mesh->VtxEdgesItr(vID)) + { + if (IsEdgeOnBoundary(eid)) + { + e[count++] = eid; + } + } + return count; +} + + +FVector3d FMeshRegionBoundaryLoops::GetVertexNormal(int vid) +{ + FVector3d n = FVector3d::Zero(); + for (int ti : Mesh->VtxTrianglesItr(vid)) + { + n += Mesh->GetTriNormal(ti); + } + n.Normalize(); + return n; +} + + + +// +// [TODO] for internal vertices, there is no ambiguity : which is the left-turn edge, +// we should be using 'closest' left-neighbour edge. +// +// ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. +// We want to pick the best one to continue the loop that came : to bowtie_v on incoming_e. +// If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. +// So, we compute the tangent plane at bowtie_v, and then the signed angle for each +// viable edge : this plane. +int FMeshRegionBoundaryLoops::FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges) +{ + // compute normal and edge [a,bowtie] + FVector3d n = GetVertexNormal(bowtie_v); + //int other_v = Mesh->edge_other_v(incoming_e, bowtie_v); + FIndex2i ev = Mesh->GetEdgeV(incoming_e); + int other_v = (ev.A == bowtie_v) ? ev.B : ev.A; + FVector3d ab = Mesh->GetVertex(bowtie_v) - Mesh->GetVertex(other_v); + + // our winner + int best_e = -1; + double best_angle = TNumericLimits::Max(); + + for (int i = 0; i < bdry_edges_count; ++i) + { + int bdry_eid = bdry_edges[i]; + if (used_edges[bdry_eid] == true) + continue; // this edge is already used + + // [TODO] can do this more efficiently? + int tid_in = IndexConstants::InvalidID, tid_out = IndexConstants::InvalidID; + IsEdgeOnBoundary(bdry_eid, tid_in, tid_out); + FIndex2i bdry_ev = GetOrientedEdgeVerts(bdry_eid, tid_in, tid_out); + //FIndex2i bdry_ev = Mesh.GetOrientedBoundaryEdgeV(bdry_eid); + + if (bdry_ev.A != bowtie_v) { + continue; // have to be able to chain to end of current edge, orientation-wise + } + + // compute projected angle + FVector3d bc = Mesh->GetVertex(bdry_ev.B) - Mesh->GetVertex(bowtie_v); + double fAngleS = VectorUtil::PlaneAngleSignedD(ab, bc, n); + + // turn left! + if (best_angle == TNumericLimits::Max() || fAngleS < best_angle) + { + best_angle = fAngleS; + best_e = bdry_eid; + } + } + check(best_e != -1); + + return best_e; +} + + + + +// This is called when loopV contains one or more "bowtie" vertices. +// These vertices *might* be duplicated : loopV (but not necessarily) +// If they are, we have to break loopV into subloops that don't contain duplicates. +// +// The list bowties contains all the possible duplicates +// (all v in bowties occur in loopV at least once) +// +// Currently loopE is not used, and the returned FEdgeLoop objects do not have their Edges +// arrays initialized. Perhaps to improve : future. +TArray FMeshRegionBoundaryLoops::ExtractSubloops(TArray& loopV, const TArray& loopE, const TArray& bowties) +{ + TArray subs; + + // figure out which bowties we saw are actually duplicated : loopV + TArray dupes; + for (int bv : bowties) + { + if (FMeshBoundaryLoops::CountInList(loopV, bv) > 1) + { + dupes.Add(bv); + } + } + + // we might not actually have any duplicates, if we got luck. Early out : that case + if (dupes.Num() == 0) + { + FEdgeLoop NewLoop(Mesh); + NewLoop.Vertices = loopV; + NewLoop.Edges = loopE; + NewLoop.BowtieVertices = bowties; + subs.Add(NewLoop); + return subs; + } + + // This loop extracts subloops until we have dealt with all the + // duplicate vertices : loopV + while (dupes.Num() > 0) + { + + // Find shortest "simple" loop, ie a loop from a bowtie to itself that + // does not contain any other bowties. This is an independent loop. + // We're doing a lot of extra work here if we only have one element : dupes... + int bi = 0, bv = 0; + int start_i = -1, end_i = -1; + int bv_shortest = -1; int shortest = TNumericLimits::Max(); + for (; bi < dupes.Num(); ++bi) + { + bv = dupes[bi]; + if (FMeshBoundaryLoops::IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i)) + { + int len = FMeshBoundaryLoops::CountSpan(loopV, start_i, end_i); + if (len < shortest) + { + bv_shortest = bv; + shortest = len; + } + } + } + check(bv_shortest != -1); // throw new MeshBoundaryLoopsException("MeshRegionBoundaryLoops.Compute: Cannot find a valid simple loop"); + if (bv != bv_shortest) + { + bv = bv_shortest; + // running again just to get start_i and end_i... + FMeshBoundaryLoops::IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i); + } + + check(loopV[start_i] == bv && loopV[end_i] == bv); + + FEdgeLoop loop(Mesh); + FMeshBoundaryLoops::ExtractSpan(loopV, start_i, end_i, true, loop.Vertices); + FEdgeLoop::VertexLoopToEdgeLoop(Mesh, loop.Vertices, loop.Edges); + loop.BowtieVertices = bowties; + subs.Add(loop); + + // If there are no more duplicates of this bowtie, we can treat + // it like a regular vertex now + if (FMeshBoundaryLoops::CountInList(loopV, bv) < 2) + { + dupes.Remove(bv); + } + } + + // Should have one loop left that contains duplicates. + // Extract this as a separate loop + int nLeft = 0; + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + nLeft++; + } + } + if (nLeft > 0) + { + FEdgeLoop loop(Mesh); + loop.Vertices.SetNum(nLeft); + int vi = 0; + for (int i = 0; i < loopV.Num(); ++i) + { + if (loopV[i] != -1) + { + loop.Vertices[vi++] = loopV[i]; + } + } + FEdgeLoop::VertexLoopToEdgeLoop(Mesh, loop.Vertices, loop.Edges); + loop.BowtieVertices = bowties; + subs.Add(loop); + } + + return subs; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshSimplification.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshSimplification.cpp new file mode 100644 index 000000000000..9322c92c8866 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshSimplification.cpp @@ -0,0 +1,842 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshSimplification.h" +#include "DynamicMeshAttributeSet.h" +#include "Util/IndexUtil.h" + + + +template +QuadricErrorType TMeshSimplification::ComputeFaceQuadric(const int tid, FVector3d& nface, FVector3d& c, double& Area) const +{ + // compute the new quadric for this tri. + Mesh->GetTriInfo(tid, nface, Area, c); + + return FQuadricErrorType(nface, c); +} + + +// Face Quadric Error computation specialized for FAttrBasedQuadricErrord +template<> +FAttrBasedQuadricErrord TMeshSimplification::ComputeFaceQuadric(const int tid, FVector3d& nface, FVector3d& c, double& Area) const +{ + // compute the new quadric for this tri. + Mesh->GetTriInfo(tid, nface, Area, c); + + FVector3f n0; FVector3f n1; FVector3f n2; + + if (NormalOverlay != nullptr) + { + NormalOverlay->GetTriElements(tid, n0, n1, n2); + } + else + { + FIndex3i vids = Mesh->GetTriangle(tid); + n0 = Mesh->GetVertexNormal(vids[0]); + n1 = Mesh->GetVertexNormal(vids[1]); + n2 = Mesh->GetVertexNormal(vids[2]); + } + + + FVector3d p0, p1, p2; + Mesh->GetTriVertices(tid, p0, p1, p2); + + FVector3d n0d(n0.X, n0.Y, n0.Z); + FVector3d n1d(n1.X, n1.Y, n1.Z); + FVector3d n2d(n2.X, n2.Y, n2.Z); + + double attrweight = 1.; + return FQuadricErrorType(p0, p1, p2, n0d, n1d, n2d, nface, c, attrweight); +} + +template +void TMeshSimplification::InitializeTriQuadrics() +{ + const int NT = Mesh->MaxTriangleID(); + triQuadrics.SetNum(NT); + triAreas.SetNum(NT); + + // tested with ParallelFor - no measurable benifit + //@todo parallel version + //gParallel.BlockStartEnd(0, Mesh->MaxTriangleID - 1, (start_tid, end_tid) = > { + FVector3d n, c; + for (int tid : Mesh->TriangleIndicesItr()) + { + triQuadrics[tid] = ComputeFaceQuadric(tid, n, c, triAreas[tid]); + } + +} + +template +void TMeshSimplification::InitializeVertexQuadrics() +{ + + int NV = Mesh->MaxVertexID(); + vertQuadrics.SetNum(NV); + // tested with ParallelFor - no measurable benifit + //gParallel.BlockStartEnd(0, Mesh->MaxVertexID - 1, (start_vid, end_vid) = > { + for (int vid : Mesh->VertexIndicesItr()) + { + vertQuadrics[vid] = FQuadricErrorType::Zero(); + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + vertQuadrics[vid].Add(triAreas[tid], triQuadrics[tid]); + } + //check(TMathUtil.EpsilonEqual(0, vertQuadrics[i].Evaluate(Mesh->GetVertex(i)), TMathUtil.Epsilon * 10)); + } + +} + + + +template +void TMeshSimplification::InitializeQueue() +{ + int NE = Mesh->EdgeCount(); + int MaxEID = Mesh->MaxEdgeID(); + + EdgeQuadrics.SetNum(MaxEID); + EdgeQueue.Initialize(MaxEID); + TArray EdgeErrors; + EdgeErrors.SetNum(MaxEID); + + // @todo vertex quadrics can be computed in parallel + //gParallel.BlockStartEnd(0, MaxEID - 1, (start_eid, end_eid) = > { + //for (int eid = start_eid; eid <= end_eid; eid++) { + for (int eid : Mesh->EdgeIndicesItr()) + { + FIndex2i ev = Mesh->GetEdgeV(eid); + FQuadricErrorType Q(vertQuadrics[ev.A], vertQuadrics[ev.B]); + FVector3d opt = OptimalPoint(eid, Q, ev.A, ev.B); + EdgeErrors[eid] = { (float)Q.Evaluate(opt), eid }; + EdgeQuadrics[eid] = QEdge(eid, Q, opt); + } + + // sorted pq insert is faster, so sort edge errors array and index map + EdgeErrors.Sort(); + + // now do inserts + int N = EdgeErrors.Num(); + for (int i = 0; i < N; ++i) + { + int eid = EdgeErrors[i].eid; + if (Mesh->IsEdge(eid)) + { + QEdge edge = EdgeQuadrics[eid]; + EdgeQueue.Insert(edge.eid, EdgeErrors[i].error); + } + } + + /* + // previous code that does unsorted insert. This is marginally slower, but + // might get even slower on larger meshes? have only tried up to about 350k. + // (still, this function is not the bottleneck...) + int cur_eid = StartEdges(); + bool done = false; + do { + if (Mesh->IsEdge(cur_eid)) { + QEdge edge = EdgeQuadrics[cur_eid]; + double err = errList[cur_eid]; + EdgeQueue.Enqueue(cur_eid, (float)err); + } + cur_eid = GetNextEdge(cur_eid, out done); + } while (done == false); + */ +} + + + + +template +FVector3d TMeshSimplification::OptimalPoint(int eid, const FQuadricErrorType& q, int ea, int eb) +{ + // if we would like to preserve boundary, we need to know that here + // so that we properly score these edges + if (bHaveBoundary && bPreserveBoundaryShape) + { + if (Mesh->IsBoundaryEdge(eid)) + { + return (Mesh->GetVertex(ea) + Mesh->GetVertex(eb)) * 0.5; + } + else + { + if (IsBoundaryVertex(ea)) + { + return Mesh->GetVertex(ea); + } + else if (IsBoundaryVertex(eb)) + { + return Mesh->GetVertex(eb); + } + } + } + + // [TODO] if we have constraints, we should apply them here, for same reason as bdry above... + + if (bMinimizeQuadricPositionError == false) + { + return GetProjectedPoint((Mesh->GetVertex(ea) + Mesh->GetVertex(eb)) * 0.5); + } + else + { + FVector3d result = FVector3d::Zero(); + if (q.OptimalPoint(result)) + { + return GetProjectedPoint(result); + } + + // degenerate matrix, evaluate quadric at edge end and midpoints + // (could do line search here...) + FVector3d va = Mesh->GetVertex(ea); + FVector3d vb = Mesh->GetVertex(eb); + FVector3d c = GetProjectedPoint((va + vb) * 0.5); + double fa = q.Evaluate(va); + double fb = q.Evaluate(vb); + double fc = q.Evaluate(c); + double m = FMath::Min3(fa, fb, fc); + if (m == fa) + { + return va; + } + else if (m == fb) + { + return vb; + } + return c; + } +} + + + + +// update queue weight for each edge in vertex one-ring +template <> +void DYNAMICMESH_API TMeshSimplification::UpdateNeighbours(int vid, FIndex2i removedTris, FIndex2i opposingVerts) +{ + for (int eid : Mesh->VtxEdgesItr(vid)) + { + FIndex2i nev = Mesh->GetEdgeV(eid); + FQuadricErrord Q(vertQuadrics[nev.A], vertQuadrics[nev.B]); + FVector3d opt = OptimalPoint(eid, Q, nev.A, nev.B); + double err = Q.Evaluate(opt); + EdgeQuadrics[eid] = QEdge(eid, Q, opt); + if (EdgeQueue.Contains(eid)) + { + EdgeQueue.Update(eid, (float)err); + } + else + { + EdgeQueue.Insert(eid, (float)err); + } + } +} + +// update queue weight for each edge in vertex one-ring. Memoryless +template +void TMeshSimplification::UpdateNeighbours(int vid, FIndex2i removedTris, FIndex2i opposingVerts) +{ + + + // This is the faster version that selectively updates the one-ring + { + + // compute the change in affected face quadrics, and then propagate + // that change to the face adjacent verts. + FVector3d n, c; + double NewtriArea; + + // Update the triangle areas and quadrics that will have changed + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + + const double OldtriArea = triAreas[tid]; + const FQuadricErrorType OldtriQuadric = triQuadrics[tid]; + + + // compute the new quadric for this tri. + FQuadricErrorType NewtriQuadric = ComputeFaceQuadric(tid, n, c, NewtriArea); + + // update the arrays that hold the current face area & quadrics + triAreas[tid] = NewtriArea; + triQuadrics[tid] = NewtriQuadric; + + FIndex3i tri_vids = Mesh->GetTriangle(tid); + + // update the vert quadrics that are adjacent to vid. + for (int32 i = 0; i < 3; ++i) + { + if (tri_vids[i] == vid) continue; + + // correct the adjacent vertQuadrics + vertQuadrics[tri_vids[i]].Add(-OldtriArea, OldtriQuadric); // subtract old quadric + vertQuadrics[tri_vids[i]].Add(NewtriArea, NewtriQuadric); // add new quadric + } + } + + // remove the influence of the dead tris from the two verts that were opposing the collapsed edge + { + for (int i = 0; i < 2; ++i) + { + if (removedTris[i] != FDynamicMesh3::InvalidID) + { + const double oldArea = triAreas[removedTris[i]]; + FQuadricErrorType oldQuadric = triQuadrics[removedTris[i]]; + + triAreas[removedTris[i]] = 0.; + + // subtract the quadric from the opposing vert + vertQuadrics[opposingVerts[i]].Add(-oldArea, oldQuadric); + } + } + } + // Rebuild the quadric for the vert that was retained during the collapse. + // NB: in the version with memory this quadric took the value of the edge quadric that collapsed. + { + FQuadricErrorType vertQuadric; + for (int tid : Mesh->VtxTrianglesItr(vid)) + { + vertQuadric.Add(triAreas[tid], triQuadrics[tid]); + } + vertQuadrics[vid] = vertQuadric; + } + } + + // Update all the edges + { + TArray> EdgesToUpdate; + for (int adjvid : Mesh->VtxVerticesItr(vid)) + { + for (int eid : Mesh->VtxEdgesItr(adjvid)) + { + EdgesToUpdate.AddUnique(eid); + } + } + + for (int eid : EdgesToUpdate) + { + // The volume conservation plane data held in the + // vertex quadrics will have duplicates for + // the two face adjacent to the edge. + + const FIndex4i edgeData = Mesh->GetEdge(eid); + FQuadricErrorType Q(vertQuadrics[edgeData[0]], vertQuadrics[edgeData[1]]); + + FVector3d opt = OptimalPoint(eid, Q, edgeData[0], edgeData[1]); + double err = Q.Evaluate(opt); + EdgeQuadrics[eid] = QEdge(eid, Q, opt); + if (EdgeQueue.Contains(eid)) + { + EdgeQueue.Update(eid, (float)err); + } + else + { + EdgeQueue.Insert(eid, (float)err); + } + } + } +} + + + + + +template +void TMeshSimplification::Precompute(bool bMeshIsClosed) +{ + bHaveBoundary = false; + IsBoundaryVtxCache.SetNum(Mesh->MaxVertexID()); + if (bMeshIsClosed == false) + { + for (int eid : Mesh->BoundaryEdgeIndicesItr()) + { + FIndex2i ev = Mesh->GetEdgeV(eid); + IsBoundaryVtxCache[ev.A] = true; + IsBoundaryVtxCache[ev.B] = true; + bHaveBoundary = true; + } + } +} + + + + +template +void TMeshSimplification::DoSimplify() +{ + if (Mesh->TriangleCount() == 0) // badness if we don't catch this... + { + return; + } + + ProfileBeginPass(); + + ProfileBeginSetup(); + Precompute(); + if (Cancelled()) + { + return; + } + InitializeTriQuadrics(); + if (Cancelled()) + { + return; + } + InitializeVertexQuadrics(); + if (Cancelled()) + { + return; + } + InitializeQueue(); + if (Cancelled()) + { + return; + } + ProfileEndSetup(); + + ProfileBeginOps(); + + ProfileBeginCollapse(); + while (EdgeQueue.GetCount() > 0) + { + // termination criteria + if (SimplifyMode == ETargetModes::VertexCount) + { + if (Mesh->VertexCount() <= TargetCount) + { + break; + } + } + else + { + if (Mesh->TriangleCount() <= TargetCount) + { + break; + } + } + + COUNT_ITERATIONS++; + int eid = EdgeQueue.Dequeue(); + if (Mesh->IsEdge(eid) == false) + { + continue; + } + if (Cancelled()) + { + return; + } + + // find triangles adjacent to the target edge + // and the verts opposite the edge. + FIndex2i targetTris = Mesh->GetEdgeT(eid); + FIndex2i targetVrts = Mesh->GetEdgeOpposingV(eid); + + + int vKeptID; + ESimplificationResult result = CollapseEdge(eid, EdgeQuadrics[eid].collapse_pt, vKeptID); + if (result == ESimplificationResult::Ok_Collapsed) + { + vertQuadrics[vKeptID] = EdgeQuadrics[eid].q; + UpdateNeighbours(vKeptID, targetTris, targetVrts); + } + } + ProfileEndCollapse(); + ProfileEndOps(); + + if (Cancelled()) + { + return; + } + + Reproject(); + + ProfileEndPass(); +} + + +template +void TMeshSimplification::SimplifyToTriangleCount(int nCount) +{ + SimplifyMode = ETargetModes::TriangleCount; + TargetCount = FMath::Max(1, nCount); + MinEdgeLength = FMathd::MaxReal; + DoSimplify(); +} + +template +void TMeshSimplification::SimplifyToVertexCount(int nCount) +{ + SimplifyMode = ETargetModes::VertexCount; + TargetCount = FMath::Max(3, nCount); + MinEdgeLength = FMathd::MaxReal; + DoSimplify(); +} + +template +void TMeshSimplification::SimplifyToEdgeLength(double minEdgeLen) +{ + SimplifyMode = ETargetModes::MinEdgeLength; + TargetCount = 1; + MinEdgeLength = minEdgeLen; + DoSimplify(); +} + + + +template +void TMeshSimplification::FastCollapsePass(double fMinEdgeLength, int nRounds, bool MeshIsClosedHint) +{ + if (Mesh->TriangleCount() == 0) // badness if we don't catch this... + { + return; + } + + MinEdgeLength = fMinEdgeLength; + double min_sqr = MinEdgeLength * MinEdgeLength; + + // we don't collapse on the boundary + bHaveBoundary = false; + + ProfileBeginPass(); + + ProfileBeginSetup(); + Precompute(MeshIsClosedHint); + if (Cancelled()) + { + return; + } + ProfileEndSetup(); + + ProfileBeginOps(); + + ProfileBeginCollapse(); + + int N = Mesh->MaxEdgeID(); + int num_last_pass = 0; + for (int ri = 0; ri < nRounds; ++ri) + { + num_last_pass = 0; + + FVector3d va = FVector3d::Zero(), vb = FVector3d::Zero(); + for (int eid = 0; eid < N; ++eid) + { + if ((!Mesh->IsEdge(eid)) || Mesh->IsBoundaryEdge(eid)) + { + continue; + } + if (Cancelled()) + { + return; + } + + Mesh->GetEdgeV(eid, va, vb); + if (va.DistanceSquared(vb) > min_sqr) + { + continue; + } + + COUNT_ITERATIONS++; + + FVector3d midpoint = (va + vb) * 0.5; + int vKeptID; + ESimplificationResult result = CollapseEdge(eid, midpoint, vKeptID); + if (result == ESimplificationResult::Ok_Collapsed) + { + ++num_last_pass; + } + } + + if (num_last_pass == 0) // converged + { + break; + } + } + ProfileEndCollapse(); + ProfileEndOps(); + + if (Cancelled()) + { + return; + } + + Reproject(); + + ProfileEndPass(); +} + + + + + + + + + + + + + + +template +ESimplificationResult TMeshSimplification::CollapseEdge(int edgeID, FVector3d vNewPos, int& collapseToV) +{ + collapseToV = FDynamicMesh3::InvalidID; + RuntimeDebugCheck(edgeID); + + FEdgeConstraint constraint = + (Constraints == nullptr) ? FEdgeConstraint::Unconstrained() : Constraints->GetEdgeConstraint(edgeID); + if (constraint.NoModifications()) + { + return ESimplificationResult::Ignored_EdgeIsFullyConstrained; + } + if (constraint.CanCollapse() == false) + { + return ESimplificationResult::Ignored_EdgeIsFullyConstrained; + } + + + // look up verts and tris for this edge + if (Mesh->IsEdge(edgeID) == false) + { + return ESimplificationResult::Failed_NotAnEdge; + } + FIndex4i edgeInfo = Mesh->GetEdge(edgeID); + int a = edgeInfo.A, b = edgeInfo.B, t0 = edgeInfo.C, t1 = edgeInfo.D; + bool bIsBoundaryEdge = (t1 == FDynamicMesh3::InvalidID); + + // look up 'other' verts c (from t0) and d (from t1, if it exists) + FIndex3i T0tv = Mesh->GetTriangle(t0); + int c = IndexUtil::FindTriOtherVtx(a, b, T0tv); + FIndex3i T1tv = (bIsBoundaryEdge) ? FDynamicMesh3::InvalidTriangle() : Mesh->GetTriangle(t1); + int d = (bIsBoundaryEdge) ? FDynamicMesh3::InvalidID : IndexUtil::FindTriOtherVtx(a, b, T1tv); + + FVector3d vA = Mesh->GetVertex(a); + FVector3d vB = Mesh->GetVertex(b); + double edge_len_sqr = (vA - vB).SquaredLength(); + if (edge_len_sqr > MinEdgeLength * MinEdgeLength) + { + return ESimplificationResult::Ignored_EdgeTooLong; + } + + ProfileBeginCollapse(); + + // check if we should collapse, and also find which vertex we should collapse to, + // in cases where we have constraints/etc + int collapse_to = -1; + bool bCanCollapse = CanCollapseEdge(edgeID, a, b, c, d, t0, t1, collapse_to); + if (bCanCollapse == false) + { + return ESimplificationResult::Ignored_Constrained; + } + + // if we have a boundary, we want to collapse to boundary + if (bPreserveBoundaryShape && bHaveBoundary) + { + if (collapse_to != -1) + { + if ((IsBoundaryVertex(b) && collapse_to != b) || + (IsBoundaryVertex(a) && collapse_to != a)) + { + return ESimplificationResult::Ignored_Constrained; + } + } + if (IsBoundaryVertex(b)) + { + collapse_to = b; + } + else if (IsBoundaryVertex(a)) + { + collapse_to = a; + } + } + + // optimization: if edge cd exists, we cannot collapse or flip. look that up here? + // funcs will do it internally... + // (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...) + ESimplificationResult retVal = ESimplificationResult::Failed_OpNotSuccessful; + + int iKeep = b, iCollapse = a; + + // if either vtx is fixed, collapse to that position + double collapse_t = 0; + if (collapse_to == b) + { + vNewPos = vB; + collapse_t = 0; + } + else if (collapse_to == a) + { + iKeep = a; iCollapse = b; + vNewPos = vA; + collapse_t = 0; + } + else + { + vNewPos = GetProjectedCollapsePosition(iKeep, vNewPos); + double div = vA.Distance(vB); + collapse_t = (div < FMathd::ZeroTolerance) ? 0.5 : (vNewPos.Distance(Mesh->GetVertex(iKeep))) / div; + collapse_t = VectorUtil::Clamp(collapse_t, 0.0, 1.0); + } + + // check if this collapse will create a normal flip. Also checks + // for invalid collapse nbrhood, since we are doing one-ring iter anyway. + // [TODO] could we skip this one-ring check in CollapseEdge? pass in hints? + if (CheckIfCollapseCreatesFlipOrInvalid(a, b, vNewPos, t0, t1) || CheckIfCollapseCreatesFlipOrInvalid(b, a, vNewPos, t0, t1)) + { + ProfileEndCollapse(); + return ESimplificationResult::Ignored_CreatesFlip; + } + + // lots of cases where we cannot collapse, but we should just let + // Mesh sort that out, right? + COUNT_COLLAPSES++; + FDynamicMesh3::FEdgeCollapseInfo collapseInfo; + EMeshResult result = Mesh->CollapseEdge(iKeep, iCollapse, collapse_t, collapseInfo); + if (result == EMeshResult::Ok) + { + collapseToV = iKeep; + Mesh->SetVertex(iKeep, vNewPos); + if (Constraints != nullptr) + { + Constraints->ClearEdgeConstraint(edgeID); + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.A); + if (collapseInfo.RemovedEdges.B != FDynamicMesh3::InvalidID) + { + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.B); + } + Constraints->ClearVertexConstraint(iCollapse); + } + OnEdgeCollapse(edgeID, iKeep, iCollapse, collapseInfo); + DoDebugChecks(); + + retVal = ESimplificationResult::Ok_Collapsed; + } + + ProfileEndCollapse(); + return retVal; +} + + + + + + + + +// Project vertices onto projection target. +// We can do projection in parallel if we have .net +template +void TMeshSimplification::FullProjectionPass() +{ + auto project = [&](int vID) + { + if (IsVertexConstrained(vID)) + { + return; + } + if (VertexControlF != nullptr && ((int)VertexControlF(vID) & (int)EVertexControl::NoProject) != 0) + { + return; + } + FVector3d curpos = Mesh->GetVertex(vID); + FVector3d projected = ProjTarget->Project(curpos, vID); + Mesh->SetVertex(vID, projected); + }; + + ApplyToProjectVertices(project); + + // [RMS] not sure how to do this... + //if (EnableParallelProjection) { + // gParallel.ForEach(project_vertices(), project); + //} else { + // foreach (int vid in project_vertices()) + // project(vid); + //} +} + +template +void TMeshSimplification::ApplyToProjectVertices(const TFunction& apply_f) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + apply_f(vid); + } +} + +template +void TMeshSimplification::ProjectVertex(int vID, IProjectionTarget* targetIn) +{ + FVector3d curpos = Mesh->GetVertex(vID); + FVector3d projected = targetIn->Project(curpos, vID); + Mesh->SetVertex(vID, projected); +} + +// used by collapse-edge to get projected position for new vertex +template +FVector3d TMeshSimplification::GetProjectedCollapsePosition(int vid, const FVector3d& vNewPos) +{ + if (Constraints != nullptr) + { + FVertexConstraint vc = Constraints->GetVertexConstraint(vid); + if (vc.Target != nullptr) + { + return vc.Target->Project(vNewPos, vid); + } + if (vc.Fixed) + { + return vNewPos; + } + } + // no constraint applied, so if we have a target surface, project to that + if (EnableInlineProjection() && ProjTarget != nullptr) + { + if (VertexControlF == nullptr || ((int)VertexControlF(vid) & (int)EVertexControl::NoProject) == 0) + { + return ProjTarget->Project(vNewPos, vid); + } + } + return vNewPos; +} + + +// Custom behavior for FAttrBasedQuadric simplifier. +template<> +void TMeshSimplification::OnEdgeCollapse(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeCollapseInfo& collapseInfo) +{ + + // Update the normal + FAttrBasedQuadricErrord& Quadric = EdgeQuadrics[edgeID].q; + FVector3d collapse_pt = EdgeQuadrics[edgeID].collapse_pt; + + FVector3d UpdatedNormald; + Quadric.ComputeAttributes(collapse_pt, UpdatedNormald); + + FVector3f UpdatedNormal(UpdatedNormald.X, UpdatedNormald.Y, UpdatedNormald.Z); + UpdatedNormal.Normalize(); + + if (NormalOverlay != nullptr) + { + // Get all the elements associated with this vertex (could be more than one to account for split vertex data) + TArray ElementIdArray; + NormalOverlay->GetVertexElements(va, ElementIdArray); + + // update everyone with the same normal. + for (int ElementId : ElementIdArray) + { + NormalOverlay->SetElement(ElementId, UpdatedNormal); + } + } + else + { + Mesh->SetVertexNormal(va, UpdatedNormal); + } + +} + + + +// These are explicit instantiations of the templates that are exported from the shared lib. +// Only these instantiations of the template can be used. +// This is necessary because we have placed most of the templated functions in this .cpp file, instead of the header. +template class DYNAMICMESH_API TMeshSimplification< FAttrBasedQuadricErrord >; +template class DYNAMICMESH_API TMeshSimplification< FVolPresQuadricErrord >; +template class DYNAMICMESH_API TMeshSimplification< FQuadricErrord >; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshTangents.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshTangents.cpp new file mode 100644 index 000000000000..c4948e374d12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshTangents.cpp @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshTangents.h" +#include "Async/ParallelFor.h" + + +template +void TMeshTangents::SetTangentCount(int Count, bool bClearToZero) +{ + if (Tangents.Num() < Count) + { + Tangents.SetNum(Count); + } + if (Bitangents.Num() < Count) + { + Bitangents.SetNum(Count); + } + if (bClearToZero) + { + for (int i = 0; i < Count; ++i) + { + Tangents[i] = FVector3::Zero(); + Bitangents[i] = FVector3::Zero(); + } + } +} + + + +template +void TMeshTangents::Internal_ComputePerTriangleTangents(const FDynamicMeshNormalOverlay* NormalOverlay, const FDynamicMeshUVOverlay* UVOverlay) +{ + int MaxTriangleID = Mesh->MaxTriangleID(); + InitializePerTriangleTangents(false); + + ParallelFor(MaxTriangleID, [this, NormalOverlay, UVOverlay](int TriangleID) + { + if (Mesh->IsTriangle(TriangleID) == false) + { + return; + } + + FVector3d TriVertices[3]; + Mesh->GetTriVertices(TriangleID, TriVertices[0], TriVertices[1], TriVertices[2]); + FVector2f TriUVs[3]; + UVOverlay->GetTriElements(TriangleID, TriUVs[0], TriUVs[1], TriUVs[2]); + + for (int j = 0; j < 3; ++j) + { + FVector3d DPosition1 = TriVertices[(j+1)%3] - TriVertices[j]; + FVector3d DPosition2 = TriVertices[(j+2)%3] - TriVertices[j]; + FVector2d DUV1 = (FVector2d)TriUVs[(j+1)%3] - (FVector2d)TriUVs[j]; + FVector2d DUV2 = (FVector2d)TriUVs[(j+2)%3] - (FVector2d)TriUVs[j]; + + //@todo handle degenerate edges + + double DetUV = DUV1.Cross(DUV2); + double InvDetUV = (DetUV == 0.0f) ? 0.0f : 1.0 / DetUV; + FVector3d Tangent = (DPosition1 * DUV2.Y - DPosition2 * DUV1.Y) * InvDetUV; + Tangent.Normalize(); + Tangents[3*TriangleID + j] = (FVector3)Tangent; + + FVector3d Bitangent = (DPosition2 * DUV1.X - DPosition1 * DUV2.X) * InvDetUV; + Bitangent.Normalize(); + Bitangents[3*TriangleID + j] = (FVector3)Bitangent; + } + }); +} + + +template class DYNAMICMESH_API TMeshTangents; +template class DYNAMICMESH_API TMeshTangents; + + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshWeights.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshWeights.cpp new file mode 100644 index 000000000000..d5b1cda8d757 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/MeshWeights.cpp @@ -0,0 +1,147 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshWeights.h" +#include "VectorUtil.h" + + +FVector3d FMeshWeights::UniformCentroid(const FDynamicMesh3 & mesh, int VertexIndex) +{ + FVector3d Centroid; + mesh.GetVtxOneRingCentroid(VertexIndex, Centroid); + return Centroid; +} + + +FVector3d FMeshWeights::MeanValueCentroid(const FDynamicMesh3 & mesh, int v_i) +{ + // based on equations in https://www.inf.usi.ch/hormann/papers/Floater.2006.AGC.pdf (formula 9) + // refer to that paper for variable names/etc + + FVector3d vSum = FVector3d::Zero(); + double wSum = 0; + FVector3d Vi = mesh.GetVertex(v_i); + + int v_j = FDynamicMesh3::InvalidID, opp_v1 = FDynamicMesh3::InvalidID, opp_v2 = FDynamicMesh3::InvalidID; + int t1 = FDynamicMesh3::InvalidID, t2 = FDynamicMesh3::InvalidID; + for (int eid : mesh.VtxEdgesItr(v_i) ) + { + opp_v2 = FDynamicMesh3::InvalidID; + mesh.GetVtxNbrhood(eid, v_i, v_j, opp_v1, opp_v2, t1, t2); + + FVector3d Vj = mesh.GetVertex(v_j); + FVector3d vVj = (Vj - Vi); + double len_vVj = vVj.Normalize(); + // [RMS] is this the right thing to do? if vertices are coincident, + // weight of this vertex should be very high! + if (len_vVj < FMathd::ZeroTolerance) + continue; + FVector3d vVdelta = (mesh.GetVertex(opp_v1) - Vi).Normalized(); + double w_ij = VectorUtil::VectorTanHalfAngle(vVj, vVdelta); + + if (opp_v2 != FDynamicMesh3::InvalidID) { + FVector3d vVgamma = (mesh.GetVertex(opp_v2) - Vi).Normalized(); + w_ij += VectorUtil::VectorTanHalfAngle(vVj, vVgamma); + } + + w_ij /= len_vVj; + + vSum += w_ij * Vj; + wSum += w_ij; + } + if (wSum < FMathd::ZeroTolerance) + return Vi; + return vSum / wSum; +} + + + + +FVector3d FMeshWeights::CotanCentroid(const FDynamicMesh3& mesh, int v_i) +{ + // based on equations in http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + + FVector3d vSum = FVector3d::Zero(); + double wSum = 0; + FVector3d Vi = mesh.GetVertex(v_i); + + int v_j = FDynamicMesh3::InvalidID, opp_v1 = FDynamicMesh3::InvalidID, opp_v2 = FDynamicMesh3::InvalidID; + int t1 = FDynamicMesh3::InvalidID, t2 = FDynamicMesh3::InvalidID; + bool bAborted = false; + for (int eid : mesh.VtxEdgesItr(v_i)) + { + opp_v2 = FDynamicMesh3::InvalidID; + mesh.GetVtxNbrhood(eid, v_i, v_j, opp_v1, opp_v2, t1, t2); + FVector3d Vj = mesh.GetVertex(v_j); + + FVector3d Vo1 = mesh.GetVertex(opp_v1); + double cot_alpha_ij = VectorUtil::VectorCot( + (Vi - Vo1).Normalized(), (Vj - Vo1).Normalized()); + if (cot_alpha_ij == 0) { + bAborted = true; + break; + } + double w_ij = cot_alpha_ij; + + if (opp_v2 != FDynamicMesh3::InvalidID) { + FVector3d Vo2 = mesh.GetVertex(opp_v2); + double cot_beta_ij = VectorUtil::VectorCot( + (Vi - Vo2).Normalized(), (Vj - Vo2).Normalized()); + if (cot_beta_ij == 0) { + bAborted = true; + break; + } + w_ij += cot_beta_ij; + } + + vSum += w_ij * Vj; + wSum += w_ij; + } + if (bAborted || fabs(wSum) < FMathd::ZeroTolerance) + return Vi; + return vSum / wSum; +} + + + +double FMeshWeights::VoronoiArea(const FDynamicMesh3& mesh, int v_i) +{ + // based on equations in http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + + double areaSum = 0; + FVector3d Vi = mesh.GetVertex(v_i); + + for (int tid : mesh.VtxTrianglesItr(v_i) ) + { + FIndex3i t = mesh.GetTriangle(tid); + int ti = (t[0] == v_i) ? 0 : ((t[1] == v_i) ? 1 : 2); + FVector3d Vj = mesh.GetVertex(t[(ti + 1) % 3]); + FVector3d Vk = mesh.GetVertex(t[(ti + 2) % 3]); + + if (VectorUtil::IsObtuse(Vi, Vj, Vk)) + { + // if triangle is obtuse voronoi area is undefind and we just return portion of triangle area + FVector3d Vij = Vj - Vi; + FVector3d Vik = Vk - Vi; + Vij.Normalize(); Vik.Normalize(); + double areaT = 0.5 * Vij.Cross(Vik).Length(); + areaSum += ( Vij.AngleR(Vik) > FMathd::HalfPi ) ? // obtuse at v_i ? + (areaT * 0.5) : (areaT * 0.25); + + } + else + { + // voronoi area + FVector3d Vji = Vi - Vj; + double dist_ji = Vji.Normalize(); + FVector3d Vki = Vi - Vk; + double dist_ki = Vki.Normalize(); + FVector3d Vkj = (Vj - Vk).Normalized(); + + double cot_alpha_ij = VectorUtil::VectorCot(Vki, Vkj); + double cot_alpha_ik = VectorUtil::VectorCot(Vji, -Vkj); + areaSum += dist_ji * dist_ji * cot_alpha_ij * 0.125; + areaSum += dist_ki * dist_ki * cot_alpha_ik * 0.125; + } + } + return areaSum; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/ExtrudeMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/ExtrudeMesh.cpp new file mode 100644 index 000000000000..ca9a2e0f7f4a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/ExtrudeMesh.cpp @@ -0,0 +1,131 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Operations/ExtrudeMesh.h" +#include "MeshNormals.h" +#include "DynamicMeshEditor.h" + + +FExtrudeMesh::FExtrudeMesh(FDynamicMesh3* mesh) : Mesh(mesh) +{ + ExtrudedPositionFunc = [this](const FVector3d& Position, const FVector3f& Normal, int VertexID) + { + return Position + this->DefaultExtrudeDistance * Normal; + }; +} + + +bool FExtrudeMesh::Apply() +{ + //@todo should apply per-connected-component? will then handle bowties properly + + FMeshNormals Normals; + bool bHaveVertexNormals = Mesh->HasVertexNormals(); + if (!bHaveVertexNormals) + { + Normals = FMeshNormals(Mesh); + Normals.ComputeVertexNormals(); + } + + InitialLoops.SetMesh(Mesh); + InitialLoops.Compute(); + int NumInitialLoops = InitialLoops.GetLoopCount(); + + BufferUtil::AppendElements(InitialTriangles, Mesh->TriangleIndicesItr()); + BufferUtil::AppendElements(InitialVertices, Mesh->VertexIndicesItr()); + + // duplicate triangles of mesh + + FDynamicMeshEditor Editor(Mesh); + + FMeshIndexMappings IndexMap; + FDynamicMeshEditResult DuplicateResult; + Editor.DuplicateTriangles(InitialTriangles, IndexMap, DuplicateResult); + OffsetTriangles = DuplicateResult.NewTriangles; + OffsetTriGroups = DuplicateResult.NewGroups; + InitialToOffsetMapV = IndexMap.GetVertexMap().GetForwardMap(); + + // set vertices to new positions + for (int vid : InitialVertices) + { + int newvid = InitialToOffsetMapV[vid]; + if ( ! Mesh->IsVertex(newvid) ) + { + continue; + } + + FVector3d v = Mesh->GetVertex(vid); + FVector3f n = (bHaveVertexNormals) ? Mesh->GetVertexNormal(vid) : (FVector3f)Normals[vid]; + FVector3d newv = ExtrudedPositionFunc(v, n, vid); + + Mesh->SetVertex(newvid, newv); + } + + // we need to reverse one side + if (IsPositiveOffset) + { + Editor.ReverseTriangleOrientations(InitialTriangles, true); + } + else + { + Editor.ReverseTriangleOrientations(OffsetTriangles, true); + } + + // stitch each loop + NewLoops.SetNum(NumInitialLoops); + StitchTriangles.SetNum(NumInitialLoops); + StitchPolygonIDs.SetNum(NumInitialLoops); + int LoopIndex = 0; + for (FEdgeLoop& BaseLoop : InitialLoops.Loops) + { + int LoopCount = BaseLoop.GetVertexCount(); + + TArray OffsetLoop; + OffsetLoop.SetNum(LoopCount); + for (int k = 0; k < LoopCount; ++k) + { + OffsetLoop[k] = InitialToOffsetMapV[BaseLoop.Vertices[k]]; + } + + FDynamicMeshEditResult StitchResult; + if (IsPositiveOffset) + { + Editor.StitchVertexLoopsMinimal(OffsetLoop, BaseLoop.Vertices, StitchResult); + } + else + { + Editor.StitchVertexLoopsMinimal(BaseLoop.Vertices, OffsetLoop, StitchResult); + } + StitchResult.GetAllTriangles(StitchTriangles[LoopIndex]); + StitchPolygonIDs[LoopIndex] = StitchResult.NewGroups; + + // for each polygon we created in stitch, set UVs and normals + if (Mesh->HasAttributes()) + { + int NumNewQuads = StitchResult.NewQuads.Num(); + for (int k = 0; k < NumNewQuads; k++) + { + FVector3f Normal = Editor.ComputeAndSetQuadNormal(StitchResult.NewQuads[k], true); + + // @todo is there a simpler way to construct rotation from 3 known axes (third id Normal.Cross(UnitY)) + // (converting from matrix might end up being more efficient due to trig in ConstrainedAlignAxis?) + FFrame3f ProjectFrame(FVector3f::Zero(), Normal); + if (FMathd::Abs(ProjectFrame.Y().Dot(FVector3f::UnitY())) < 0.01) + { + ProjectFrame.ConstrainedAlignAxis(0, FVector3f::UnitX(), ProjectFrame.Z()); + } + else + { + ProjectFrame.ConstrainedAlignAxis(1, FVector3f::UnitY(), ProjectFrame.Z()); + } + Editor.SetQuadUVsFromProjection(StitchResult.NewQuads[k], ProjectFrame, UVScaleFactor); + } + } + + NewLoops[LoopIndex].InitializeFromVertices(Mesh, OffsetLoop); + LoopIndex++; + } + + return true; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp new file mode 100644 index 000000000000..7c9fee07c7d0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/GroupTopologyDeformer.cpp @@ -0,0 +1,309 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Operations/GroupTopologyDeformer.h" +#include "DynamicMesh3.h" +#include "GroupTopology.h" +#include "SegmentTypes.h" +#include "Async/ParallelFor.h" +#include "Containers/BitArray.h" + + +void FGroupTopologyDeformer::Initialize(const FDynamicMesh3* MeshIn, const FGroupTopology* TopologyIn) +{ + Mesh = MeshIn; + Topology = TopologyIn; +} + + + +void FGroupTopologyDeformer::Reset() +{ + HandleVertices.Reset(); + HandleBoundaryVertices.Reset(); + FixedBoundaryVertices.Reset(); + ROIEdgeVertices.Reset(); + ROIFaces.Reset(); + InitialPositions.Reset(); + ROIEdges.Reset(); + + EdgeEncodings.Reset(); + FaceEncodings.Reset(); +} + + +void FGroupTopologyDeformer::SetActiveHandleFaces(const TArray& FaceGroupIDs) +{ + Reset(); + + check(FaceGroupIDs.Num() == 1); // multi-face not supported yet + int GroupID = FaceGroupIDs[0]; + + // find set of vertices in handle + Topology->CollectGroupVertices(GroupID, HandleVertices); + Topology->CollectGroupBoundaryVertices(GroupID, HandleBoundaryVertices); + ModifiedVertices = HandleVertices; + + // find neighbour group set + TArray HandleGroups = FaceGroupIDs; + const TArray& GroupNbrGroups = Topology->GetGroupNbrGroups(GroupID); + CalculateROI(HandleGroups, GroupNbrGroups); + + SaveInitialPositions(); + + ComputeEncoding(); +} + + + +void FGroupTopologyDeformer::SetActiveHandleEdges(const TArray& TopologyEdgeIDs) +{ + Reset(); + + for (int EdgeID : TopologyEdgeIDs) + { + const TArray& EdgeVerts = Topology->GetGroupEdgeVertices(EdgeID); + for (int VertID : EdgeVerts) + { + HandleVertices.Add(VertID); + } + } + HandleBoundaryVertices = HandleVertices; + ModifiedVertices = HandleVertices; + + TArray HandleGroups; + TArray NbrGroups; + Topology->FindEdgeNbrGroups(TopologyEdgeIDs, NbrGroups); + CalculateROI(HandleGroups, NbrGroups); + + SaveInitialPositions(); + + ComputeEncoding(); +} + + +void FGroupTopologyDeformer::SetActiveHandleCorners(const TArray& CornerIDs) +{ + Reset(); + + for (int CornerID : CornerIDs) + { + int VertID = Topology->GetCornerVertexID(CornerID); + if (VertID >= 0) + { + HandleVertices.Add(VertID); + } + } + HandleBoundaryVertices = HandleVertices; + ModifiedVertices = HandleVertices; + + TArray HandleGroups; + TArray NbrGroups; + Topology->FindCornerNbrGroups(CornerIDs, NbrGroups); + CalculateROI(HandleGroups, NbrGroups); + + SaveInitialPositions(); + + ComputeEncoding(); +} + + + + +void FGroupTopologyDeformer::SaveInitialPositions() +{ + for (int VertID : ModifiedVertices) + { + InitialPositions.AddVertex(Mesh, VertID); + } +} + + + +void FGroupTopologyDeformer::CalculateROI(const TArray& HandleGroups, const TArray& ROIGroups) +{ + // sort ROI edges into various sets + // HandleBoundaryVertices: all vertices on border of handle + // FixedBoundaryVertices: all vertices on fixed border (ie part of edges not connected to handle) + // ROIEdges: list of edge data structures for edges we will deform + ROIEdges.Reserve(ROIGroups.Num() * 5); // guesstimate + Topology->ForGroupSetEdges(ROIGroups, [this, &HandleGroups](const FGroupTopology::FGroupEdge& Edge, int EdgeIndex) + { + if (HandleGroups.Contains(Edge.Groups.A) || HandleGroups.Contains(Edge.Groups.B)) + { + return; // this is a Handle boundary edge + } + + bool bIsConnectedToHandle = Edge.IsConnectedToVertices(HandleBoundaryVertices); + if (bIsConnectedToHandle) + { + for (int VertID : Edge.Span.Vertices) + { + ROIEdgeVertices.Add(VertID); + } + FROIEdge& ROIEdge = ROIEdges[ROIEdges.Add(FROIEdge())]; + ROIEdge.EdgeIndex = EdgeIndex; + ROIEdge.Span = Edge.Span; + if (HandleBoundaryVertices.Contains(ROIEdge.Span.Vertices[0]) == false) + { + ROIEdge.Span.Reverse(); + } + } + else + { + for (int VertID : Edge.Span.Vertices) + { + FixedBoundaryVertices.Add(VertID); + } + } + }); + + + // create ROI faces + ROIFaces.SetNum(ROIGroups.Num()); + int FaceIdx = 0; + for (int NbrGroupID : ROIGroups) + { + FaceVertsTemp.Reset(); + FaceBoundaryVertsTemp.Reset(); + Topology->CollectGroupVertices(NbrGroupID, FaceVertsTemp); + Topology->CollectGroupBoundaryVertices(NbrGroupID, FaceBoundaryVertsTemp); + + FROIFace& Face = ROIFaces[FaceIdx++]; + for (int vid : FaceVertsTemp) + { + TArray& AddTo = (FaceBoundaryVertsTemp.Contains(vid)) ? Face.BoundaryVerts : Face.InteriorVerts; + AddTo.Add(vid); + ModifiedVertices.Add(vid); + } + } +} + + + + + + +void FGroupTopologyDeformer::ComputeEncoding() +{ + // encode ROI edges + int NumROIEdges = ROIEdges.Num(); + EdgeEncodings.SetNum(NumROIEdges); + for (int ei = 0; ei < NumROIEdges; ei++) + { + FEdgeSpan& Span = ROIEdges[ei].Span; + int NumVerts = Span.Vertices.Num(); + + FEdgeEncoding& Encoding = EdgeEncodings[ei]; + Encoding.Vertices.SetNum(NumVerts); + + FVector3d StartV = Mesh->GetVertex(Span.Vertices[0]); + FVector3d EndV = Mesh->GetVertex(Span.Vertices[NumVerts - 1]); + FSegment3d Seg(StartV, EndV); + + for (int k = 1; k < NumVerts - 1; ++k) + { + FVector3d Pos = Mesh->GetVertex(Span.Vertices[k]); + FEdgeVertexEncoding& Enc = Encoding.Vertices[k]; + Enc.T = Seg.ProjectUnitRange(Pos); + //Enc.T *= Enc.T; + //Enc.T = FMathd::Sqrt(Enc.T); + Enc.Delta = Pos - Seg.PointBetween(Enc.T); + } + } + + // encode ROI faces + int NumROIFaces = ROIFaces.Num(); + FaceEncodings.SetNum(NumROIFaces); + for (int fi = 0; fi < NumROIFaces; ++fi) + { + FROIFace& Face = ROIFaces[fi]; + int NumBoundaryV = Face.BoundaryVerts.Num(); + int NumInteriorV = Face.InteriorVerts.Num(); + FFaceEncoding& Encoding = FaceEncodings[fi]; + Encoding.Vertices.SetNum(NumInteriorV); + for (int vi = 0; vi < NumInteriorV; ++vi) + { + FVector3d Pos = Mesh->GetVertex(Face.InteriorVerts[vi]); + FFaceVertexEncoding& VtxEncoding = Encoding.Vertices[vi]; + VtxEncoding.Weights.SetNum(NumBoundaryV); + VtxEncoding.Deltas.SetNum(NumBoundaryV); + double WeightSum = 0; + for (int k = 0; k < NumBoundaryV; ++k) + { + FVector3d BorderPos = Mesh->GetVertex(Face.BoundaryVerts[k]); + FVector3d DeltaVec = Pos - BorderPos; + double Weight = 1.0 / DeltaVec.SquaredLength(); + VtxEncoding.Deltas[k] = DeltaVec; + VtxEncoding.Weights[k] = Weight; + WeightSum += Weight; + } + FVector3d Reconstruct(0, 0, 0); + for (int k = 0; k < NumBoundaryV; ++k) + { + VtxEncoding.Weights[k] /= WeightSum; + Reconstruct += VtxEncoding.Weights[k] * (Mesh->GetVertex(Face.BoundaryVerts[k]) + VtxEncoding.Deltas[k]); + } + check(Reconstruct.Distance(Pos) < 0.0001); + } + } +} + + + +void FGroupTopologyDeformer::ClearSolution(FDynamicMesh3* TargetMesh) +{ + InitialPositions.SetPositions(TargetMesh); +} + + +void FGroupTopologyDeformer::UpdateSolution(FDynamicMesh3* TargetMesh, const TFunction& HandleVertexDeformFunc) +{ + InitialPositions.SetPositions(TargetMesh); + + for (int VertIdx : HandleVertices) + { + FVector3d DeformPos = HandleVertexDeformFunc(TargetMesh, VertIdx); + TargetMesh->SetVertex(VertIdx, DeformPos); + } + + // reconstruct edges + int NumEdges = ROIEdges.Num(); + for (int ei = 0; ei < NumEdges; ++ei) + { + const FEdgeSpan& Span = ROIEdges[ei].Span; + const FEdgeEncoding& Encoding = EdgeEncodings[ei]; + int NumVerts = Span.Vertices.Num(); + FVector3d A = TargetMesh->GetVertex(Span.Vertices[0]); + FVector3d B = TargetMesh->GetVertex(Span.Vertices[NumVerts - 1]); + FSegment3d Seg(A, B); + for (int k = 1; k < NumVerts - 1; ++k) + { + FVector3d NewPos = Seg.PointBetween(Encoding.Vertices[k].T); + NewPos += Encoding.Vertices[k].Delta; + TargetMesh->SetVertex(Span.Vertices[k], NewPos); + } + } + + // reconstruct faces + int NumFaces = ROIFaces.Num(); + ParallelFor(NumFaces, [this, &TargetMesh](int FaceIdx) { + const FROIFace& Face = ROIFaces[FaceIdx]; + const FFaceEncoding& Encoding = FaceEncodings[FaceIdx]; + int NumBorder = Face.BoundaryVerts.Num(); + int NumVerts = Face.InteriorVerts.Num(); + for (int vi = 0; vi < NumVerts; ++vi) + { + const FFaceVertexEncoding& VtxEncoding = Encoding.Vertices[vi]; + FVector3d Sum(0, 0, 0); + for (int k = 0; k < NumBorder; ++k) + { + Sum += VtxEncoding.Weights[k] * + (TargetMesh->GetVertex(Face.BoundaryVerts[k]) + VtxEncoding.Deltas[k]); + } + TargetMesh->SetVertex(Face.InteriorVerts[vi], Sum); + } + }); +} + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MergeCoincidentMeshEdges.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MergeCoincidentMeshEdges.cpp new file mode 100644 index 000000000000..39fa4359b504 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Operations/MergeCoincidentMeshEdges.cpp @@ -0,0 +1,198 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "Operations/MergeCoincidentMeshEdges.h" +#include "DynamicMesh3.h" +#include "MeshAdapterUtil.h" +#include "Spatial/PointSetHashTable.h" +#include "Util/IndexPriorityQueue.h" +#include "Util/IndexUtil.h" + + +const double FMergeCoincidentMeshEdges::DEFAULT_TOLERANCE = FMathf::ZeroTolerance; + + +bool FMergeCoincidentMeshEdges::Apply() +{ + MergeVtxDistSqr = MergeVertexTolerance * MergeVertexTolerance; + double UseMergeSearchTol = (MergeSearchTolerance > 0) ? MergeSearchTolerance : 2*MergeVertexTolerance; + + // + // construct hash table for edge midpoints + // + + FPointSetAdapterd EdgeMidpoints = MeshAdapterUtil::MakeBoundaryEdgeMidpointsAdapter(Mesh); + FPointSetHashtable MidpointsHash(&EdgeMidpoints); + + // use denser grid as vertex count increases + int hashN = 64; + if (Mesh->TriangleCount() > 100000) hashN = 128; + if (Mesh->TriangleCount() > 1000000) hashN = 256; + FAxisAlignedBox3d bounds = Mesh->GetCachedBounds(); + MidpointsHash.Build(bounds.MaxDim() / hashN, bounds.Min); + + // temp values and buffers + FVector3d A, B, C, D; + TArray equivBuffer; + TArray SearchMatches; + SearchMatches.SetNum(1024); SearchMatches.Reset(); // allocate buffer + + // + // construct edge equivalence sets. First we find all other edges with same + // midpoint, and then we form equivalence set for edge from subset that also + // has same endpoints + // + + typedef TArray EdgesList; + TArray EquivalenceSets; + EquivalenceSets.Init(nullptr, Mesh->MaxEdgeID()); + TSet RemainingEdges; + + // @todo equivalence sets should be symmetric. this neither enforces that, + // nor takes advantage of it. + for (int eid : Mesh->BoundaryEdgeIndicesItr()) + { + FVector3d midpt = Mesh->GetEdgePoint(eid, 0.5); + + // find all other edges with same midpoint in query sphere + SearchMatches.Reset(); + MidpointsHash.FindPointsInBall(midpt, UseMergeSearchTol, SearchMatches); + + int N = SearchMatches.Num(); + if (N == 1 && SearchMatches[0] != eid) + { + check(false); // how could this happen?! + } + if (N <= 1) + { + continue; // edge has no matches + } + + Mesh->GetEdgeV(eid, A, B); + + // if same endpoints, add to equivalence set for this edge + equivBuffer.Reset(); + for (int i = 0; i < N; ++i) + { + if (SearchMatches[i] != eid) + { + Mesh->GetEdgeV(SearchMatches[i], C, D); + if ( IsSameEdge(A, B, C, D) ) + { + equivBuffer.Add(SearchMatches[i]); + } + } + } + if (equivBuffer.Num() > 0) + { + EquivalenceSets[eid] = new EdgesList(equivBuffer); + RemainingEdges.Add(eid); + } + } + + + // + // add potential duplicate edges to priority queue, sorted by number of possible matches. + // + + // [TODO] could replace remaining hashset w/ PQ, and use conservative count? + // [TODO] Does this need to be a PQ? Not updating PQ below anyway... + FIndexPriorityQueue DuplicatesQueue; + DuplicatesQueue.Initialize(Mesh->MaxEdgeID()); + for (int eid : RemainingEdges) + { + if (OnlyUniquePairs) + { + if (EquivalenceSets[eid]->Num() != 1) + { + continue; + } + + // check that reverse match is the same and unique + int other_eid = (*EquivalenceSets[eid])[0]; + if (EquivalenceSets[other_eid]->Num() != 1 || (*EquivalenceSets[other_eid])[0] != eid) + { + continue; + } + } + + DuplicatesQueue.Insert(eid, EquivalenceSets[eid]->Num()); + } + + // + // process all potential matches, merging edges as we go in a greedy fashion. + // + + while (DuplicatesQueue.GetCount() > 0) + { + int eid = DuplicatesQueue.Dequeue(); + + if (Mesh->IsEdge(eid) == false || EquivalenceSets[eid] == nullptr || RemainingEdges.Contains(eid) == false) + { + continue; // dealt with this edge already + } + if (Mesh->IsBoundaryEdge(eid) == false) + { + continue; // this edge got merged already + } + + EdgesList& Matches = *EquivalenceSets[eid]; + + // select best viable match (currently just "first"...) + // @todo could we make better decisions here? prefer planarity? + bool bMerged = false; + int FailedCount = 0; + for (int i = 0; i < Matches.Num() && bMerged == false; ++i) + { + int other_eid = Matches[i]; + if (Mesh->IsEdge(other_eid) == false || Mesh->IsBoundaryEdge(other_eid) == false) + { + continue; + } + + FDynamicMesh3::FMergeEdgesInfo mergeInfo; + EMeshResult result = Mesh->MergeEdges(eid, other_eid, mergeInfo); + if (result != EMeshResult::Ok) + { + // if the operation failed we remove this edge from the equivalence set + Matches.RemoveAt(i); + i--; + + EquivalenceSets[other_eid]->Remove(eid); + //DuplicatesQueue.UpdatePriority(...); // should we do this? + + FailedCount++; + } + else + { + // ok we merged, other edge is no longer free + bMerged = true; + delete EquivalenceSets[other_eid]; + EquivalenceSets[other_eid] = nullptr; + RemainingEdges.Remove(other_eid); + } + } + + // Removing branch with two identical cases to fix static analysis warning. + // However, these two branches are *not* the same...we're just not sure + // what the right thing to do is in the else case + //if (bMerged) + //{ + delete EquivalenceSets[eid]; + EquivalenceSets[eid] = nullptr; + RemainingEdges.Remove(eid); + //} + //else + //{ + // // should we do something else here? doesn't make sense to put + // // back into Q, as it should be at the top, right? + // delete EquivalenceSets[eid]; + // EquivalenceSets[eid] = nullptr; + // RemainingEdges.Remove(eid); + //} + + } + + return true; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Remesher.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Remesher.cpp new file mode 100644 index 000000000000..3612641efc9d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Private/Remesher.cpp @@ -0,0 +1,637 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Remesher.h" +#include "DynamicMeshAttributeSet.h" +#include "MeshWeights.h" + + + + +void FRemesher::SetTargetEdgeLength(double fLength) +{ + // from Botsch paper + //MinEdgeLength = fLength * (4.0/5.0); + //MaxEdgeLength = fLength * (4.0/3.0); + // much nicer!! makes sense as when we split, edges are both > min ! + MinEdgeLength = fLength * 0.66; + MaxEdgeLength = fLength * 1.33; +} + + + +void FRemesher::Precompute() +{ + // if we know Mesh is closed, we can skip is-boundary checks, which makes + // the flip-valence tests much faster! + bMeshIsClosed = true; + for (int eid : Mesh->EdgeIndicesItr()) + { + if (Mesh->IsBoundaryEdge(eid)) + { + bMeshIsClosed = false; + break; + } + } +} + + + + + +void FRemesher::BasicRemeshPass() +{ + if (Mesh->TriangleCount() == 0) // badness if we don't catch this... + { + return; + } + + ProfileBeginPass(); + + // Iterate over all edges in the mesh at start of pass. + // Some may be removed, so we skip those. + // However, some old eid's may also be re-used, so we will touch + // some new edges. Can't see how we could efficiently prevent this. + // + ProfileBeginOps(); + + int cur_eid = StartEdges(); + bool done = false; + ModifiedEdgesLastPass = 0; + do + { + if (Mesh->IsEdge(cur_eid)) + { + EProcessResult result = ProcessEdge(cur_eid); + if (result == EProcessResult::Ok_Collapsed || result == EProcessResult::Ok_Flipped || result == EProcessResult::Ok_Split) + { + ModifiedEdgesLastPass++; + } + } + if (Cancelled()) // expensive to check every iter? + { + return; + } + cur_eid = GetNextEdge(cur_eid, done); + } while (done == false); + ProfileEndOps(); + + if (Cancelled()) + { + return; + } + + ProfileBeginSmooth(); + if (bEnableSmoothing && SmoothSpeedT > 0) + { + if (bEnableSmoothInPlace) + { + //FullSmoothPass_InPlace(EnableParallelSmooth); + check(false); + } + else + { + FullSmoothPass_Buffer(bEnableParallelSmooth); + } + DoDebugChecks(); + } + ProfileEndSmooth(); + + if (Cancelled()) + { + return; + } + + ProfileBeginProject(); + if (ProjTarget != nullptr && ProjectionMode == ETargetProjectionMode::AfterRefinement) + { + FullProjectionPass(); + DoDebugChecks(); + } + ProfileEndProject(); + + DoDebugChecks(true); + + if (Cancelled()) + { + return; + } + + ProfileEndPass(); +} + + + + + +FRemesher::EProcessResult FRemesher::ProcessEdge(int edgeID) +{ + RuntimeDebugCheck(edgeID); + + FEdgeConstraint constraint = + (Constraints == nullptr) ? FEdgeConstraint::Unconstrained() : Constraints->GetEdgeConstraint(edgeID); + if (constraint.NoModifications()) + { + return EProcessResult::Ignored_EdgeIsFullyConstrained; + } + + // look up verts and tris for this edge + if (Mesh->IsEdge(edgeID) == false) + { + return EProcessResult::Failed_NotAnEdge; + } + FIndex4i edge_info = Mesh->GetEdge(edgeID); + int a = edge_info.A, b = edge_info.B, t0 = edge_info.C, t1 = edge_info.D; + bool bIsBoundaryEdge = (t1 == IndexConstants::InvalidID); + + // look up 'other' verts c (from t0) and d (from t1, if it exists) + FIndex2i ov = Mesh->GetEdgeOpposingV(edgeID); + int c = ov[0], d = ov[1]; + + FVector3d vA = Mesh->GetVertex(a); + FVector3d vB = Mesh->GetVertex(b); + double edge_len_sqr = (vA - vB).SquaredLength(); + + ProfileBeginCollapse(); + + // check if we should collapse, and also find which vertex we should collapse to, + // in cases where we have constraints/etc + int collapse_to = -1; + bool bCanCollapse = bEnableCollapses + && constraint.CanCollapse() + && edge_len_sqr < MinEdgeLength*MinEdgeLength + && CanCollapseEdge(edgeID, a, b, c, d, t0, t1, collapse_to); + + // optimization: if edge cd exists, we cannot collapse or flip. look that up here? + // funcs will do it internally... + // (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...) + + // if edge length is too short, we want to collapse it + bool bTriedCollapse = false; + if (bCanCollapse) + { + int iKeep = b, iCollapse = a; + double collapse_t = 0.5; // need to know t-value along edge to update lerpable attributes properly + FVector3d vNewPos = (vA + vB) * collapse_t; + + // if either vtx is fixed, collapse to that position + if (collapse_to == b) + { + collapse_t = 0; + vNewPos = vB; + } + else if (collapse_to == a) + { + iKeep = a; iCollapse = b; + collapse_t = 0; + vNewPos = vA; + } + else + { + vNewPos = GetProjectedCollapsePosition(iKeep, vNewPos); + double div = vA.Distance(vB); + collapse_t = (div < FMathd::ZeroTolerance) ? 0.5 : (vNewPos.Distance(Mesh->GetVertex(iKeep))) / div; + collapse_t = VectorUtil::Clamp(collapse_t, 0.0, 1.0); + } + + // if new position would flip normal of one of the existing triangles + // either one-ring, don't allow it + if (bPreventNormalFlips) + { + if (CheckIfCollapseCreatesFlipOrInvalid(a, b, vNewPos, t0, t1) || CheckIfCollapseCreatesFlipOrInvalid(b, a, vNewPos, t0, t1)) + { + goto abort_collapse; + } + } + + // lots of cases where we cannot collapse, but we should just let + // mesh sort that out, right? + COUNT_COLLAPSES++; + FDynamicMesh3::FEdgeCollapseInfo collapseInfo; + EMeshResult result = Mesh->CollapseEdge(iKeep, iCollapse, collapse_t, collapseInfo); + if (result == EMeshResult::Ok) + { + Mesh->SetVertex(iKeep, vNewPos); + if (Constraints != nullptr) + { + Constraints->ClearEdgeConstraint(edgeID); + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.A); + if (collapseInfo.RemovedEdges.B != IndexConstants::InvalidID) + { + Constraints->ClearEdgeConstraint(collapseInfo.RemovedEdges.B); + } + Constraints->ClearVertexConstraint(iCollapse); + } + OnEdgeCollapse(edgeID, iKeep, iCollapse, collapseInfo); + DoDebugChecks(); + + return EProcessResult::Ok_Collapsed; + } + else + { + bTriedCollapse = true; + } + } +abort_collapse: + + ProfileEndCollapse(); + ProfileBeginFlip(); + + // if this is not a boundary edge, maybe we want to flip + bool bTriedFlip = false; + if (bEnableFlips && constraint.CanFlip() && bIsBoundaryEdge == false) + { + // can we do this more efficiently somehow? + bool a_is_boundary_vtx = (bMeshIsClosed) ? false : (bIsBoundaryEdge || Mesh->IsBoundaryVertex(a)); + bool b_is_boundary_vtx = (bMeshIsClosed) ? false : (bIsBoundaryEdge || Mesh->IsBoundaryVertex(b)); + bool c_is_boundary_vtx = (bMeshIsClosed) ? false : Mesh->IsBoundaryVertex(c); + bool d_is_boundary_vtx = (bMeshIsClosed) ? false : Mesh->IsBoundaryVertex(d); + int valence_a = Mesh->GetVtxEdgeCount(a), valence_b = Mesh->GetVtxEdgeCount(b); + int valence_c = Mesh->GetVtxEdgeCount(c), valence_d = Mesh->GetVtxEdgeCount(d); + int valence_a_target = (a_is_boundary_vtx) ? valence_a : 6; + int valence_b_target = (b_is_boundary_vtx) ? valence_b : 6; + int valence_c_target = (c_is_boundary_vtx) ? valence_c : 6; + int valence_d_target = (d_is_boundary_vtx) ? valence_d : 6; + + // if total valence error improves by flip, we want to do it + int curr_err = abs(valence_a - valence_a_target) + abs(valence_b - valence_b_target) + + abs(valence_c - valence_c_target) + abs(valence_d - valence_d_target); + int flip_err = abs((valence_a - 1) - valence_a_target) + abs((valence_b - 1) - valence_b_target) + + abs((valence_c + 1) - valence_c_target) + abs((valence_d + 1) - valence_d_target); + + bool bTryFlip = flip_err < curr_err; + if (bTryFlip && bPreventNormalFlips && CheckIfFlipInvertsNormals(a, b, c, d, t0)) + { + bTryFlip = false; + } + + if (bTryFlip) + { + FDynamicMesh3::FEdgeFlipInfo flipInfo; + COUNT_FLIPS++; + EMeshResult result = Mesh->FlipEdge(edgeID, flipInfo); + if (result == EMeshResult::Ok) + { + DoDebugChecks(); + return EProcessResult::Ok_Flipped; + } + else + { + bTriedFlip = true; + } + + } + } + + ProfileEndFlip(); + ProfileBeginSplit(); + + // if edge length is too long, we want to split it + bool bTriedSplit = false; + if (bEnableSplits && constraint.CanSplit() && edge_len_sqr > MaxEdgeLength*MaxEdgeLength) + { + FDynamicMesh3::FEdgeSplitInfo SplitInfo; + COUNT_SPLITS++; + EMeshResult result = Mesh->SplitEdge(edgeID, SplitInfo); + if (result == EMeshResult::Ok) + { + UpdateAfterSplit(edgeID, a, b, SplitInfo); + OnEdgeSplit(edgeID, a, b, SplitInfo); + DoDebugChecks(); + return EProcessResult::Ok_Split; + } + else + { + bTriedSplit = true; + } + } + + ProfileEndSplit(); + + + if (bTriedFlip || bTriedSplit || bTriedCollapse) + { + return EProcessResult::Failed_OpNotSuccessful; + } + else + { + return EProcessResult::Ignored_EdgeIsFine; + } +} + + + + + + +void FRemesher::UpdateAfterSplit(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) +{ + bool bPositionFixed = false; + if (Constraints != nullptr && Constraints->HasEdgeConstraint(edgeID)) + { + // inherit edge constraint + Constraints->SetOrUpdateEdgeConstraint(SplitInfo.NewEdges.A, Constraints->GetEdgeConstraint(edgeID)); + + // [RMS] update vertex constraints. Note that there is some ambiguity here. + // Both verts being constrained doesn't inherently mean that the edge is on + // a constraint, that's why these checks are only applied if edge is constrained. + // But constrained edge doesn't necessarily mean we want to inherit vert constraints!! + // + // although, pretty safe to assume that we would at least disable flips + // if both vertices are constrained to same line/curve. So, maybe this makes sense... + // + // (perhaps edge constraint should be explicitly tagged to resolve this ambiguity??) + + // vert inherits Fixed if both orig edge verts Fixed, and both tagged with same SetID + FVertexConstraint ca = Constraints->GetVertexConstraint(va); + FVertexConstraint cb = Constraints->GetVertexConstraint(vb); + if (ca.Fixed && cb.Fixed) + { + int nSetID = (ca.FixedSetID > 0 && ca.FixedSetID == cb.FixedSetID) ? + ca.FixedSetID : FVertexConstraint::InvalidSetID; + Constraints->SetOrUpdateVertexConstraint(SplitInfo.NewVertex, + new FVertexConstraint(true, nSetID)); + bPositionFixed = true; + } + + // vert inherits Target if: + // 1) both source verts and edge have same Target, and is same as edge target + // 2) either vert has same target as edge, and other vert is fixed + if (ca.Target != nullptr || cb.Target != nullptr) + { + IProjectionTarget* edge_target = Constraints->GetEdgeConstraint(edgeID).Target; + IProjectionTarget* set_target = nullptr; + if (ca.Target == cb.Target && ca.Target == edge_target) + { + set_target = edge_target; + } + else if (ca.Target == edge_target && cb.Fixed) + { + set_target = edge_target; + } + else if (cb.Target == edge_target && ca.Fixed) + { + set_target = edge_target; + } + + if (set_target != nullptr) + { + Constraints->SetOrUpdateVertexConstraint(SplitInfo.NewVertex, + new FVertexConstraint(set_target)); + ProjectVertex(SplitInfo.NewVertex, set_target); + bPositionFixed = true; + } + } + } + + if (EnableInlineProjection() && bPositionFixed == false && ProjTarget != nullptr) + { + ProjectVertex(SplitInfo.NewVertex, ProjTarget); + } +} + + + +void FRemesher::ProjectVertex(int VertexID, IProjectionTarget* UseTarget) +{ + FVector3d curpos = Mesh->GetVertex(VertexID); + FVector3d projected = UseTarget->Project(curpos, VertexID); + Mesh->SetVertex(VertexID, projected); +} + +// used by collapse-edge to get projected position for new vertex +FVector3d FRemesher::GetProjectedCollapsePosition(int vid, const FVector3d& vNewPos) +{ + if (Constraints != nullptr) + { + FVertexConstraint vc = Constraints->GetVertexConstraint(vid); + if (vc.Target != nullptr) + { + return vc.Target->Project(vNewPos, vid); + } + if (vc.Fixed) + { + return vNewPos; + } + } + // no constraint applied, so if we have a target surface, project to that + if (EnableInlineProjection() && ProjTarget != nullptr) + { + if (VertexControlF == nullptr || ((int)VertexControlF(vid) & (int)EVertexControl::NoProject) == 0) + { + return ProjTarget->Project(vNewPos, vid); + } + } + return vNewPos; +} + + + + +static FVector3d UniformSmooth(const FDynamicMesh3& mesh, int vID, double t) +{ + FVector3d v = mesh.GetVertex(vID); + FVector3d c; + mesh.GetVtxOneRingCentroid(vID, c); + return (1.0 - t)*v + (t)*c; +} + + +static FVector3d MeanValueSmooth(const FDynamicMesh3& mesh, int vID, double t) +{ + FVector3d v = mesh.GetVertex(vID); + FVector3d c = FMeshWeights::MeanValueCentroid(mesh, vID); + return (1.0 - t)*v + (t)*c; +} + +static FVector3d CotanSmooth(const FDynamicMesh3& mesh, int vID, double t) +{ + FVector3d v = mesh.GetVertex(vID); + FVector3d c = FMeshWeights::CotanCentroid(mesh, vID); + return (1.0 - t)*v + (t)*c; +} + +void FRemesher::FullSmoothPass_Buffer(bool bParallel) +{ + InitializeVertexBufferForPass(); + + TFunction UseSmoothFunc = UniformSmooth; + //Func smoothFunc = MeshUtil.UniformSmooth; + if (CustomSmoothF != nullptr) + { + UseSmoothFunc = CustomSmoothF; + } + else + { + if (SmoothType == ESmoothTypes::MeanValue) + { + UseSmoothFunc = MeanValueSmooth; + } + else if (SmoothType == ESmoothTypes::Cotan) + { + UseSmoothFunc = CotanSmooth; + } + } + + auto SmoothAndUpdateFunc = [&](int vID) + { + bool bModified = false; + FVector3d vSmoothed = ComputeSmoothedVertexPos(vID, UseSmoothFunc, bModified); + if (bModified) + { + TempFlagBuffer[vID] = true; + TempPosBuffer[vID] = vSmoothed; + } + }; + + //if (bParallel) { + // gParallel.ForEach(smooth_vertices(), smooth); + //} else { + // foreach (int vID in smooth_vertices()) + // smooth(vID); + //} + ApplyToSmoothVertices(SmoothAndUpdateFunc); + + ApplyVertexBuffer(bParallel); +} + + + + + + +void FRemesher::InitializeVertexBufferForPass() +{ + if ((int)TempPosBuffer.GetLength() < Mesh->MaxVertexID()) + { + TempPosBuffer.Resize(Mesh->MaxVertexID() + Mesh->MaxVertexID() / 5); + } + if (TempFlagBuffer.Num() < Mesh->MaxVertexID()) + { + TempFlagBuffer.SetNum(2 * Mesh->MaxVertexID()); + } + + TempFlagBuffer.Init(false, TempFlagBuffer.Num()); + //TempFlagBuffer.assign(TempFlagBuffer.size(), false); +} + +void FRemesher::ApplyVertexBuffer(bool bParallel) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + if (TempFlagBuffer[vid]) + { + Mesh->SetVertex(vid, TempPosBuffer[vid]); + } + } + + // [TODO] can probably use block-parallel here... + //if (bParallel) { + // gParallel.BlockStartEnd(0, Mesh->MaxVertexID-1, (a,b) => { + // for (int vid = a; vid <= b; vid++) { + // if (TempFlagBuffer[vid]) + // Mesh->SetVertex(vid, TempPosBuffer[vid]); + // } + // }); + //} else { + // foreach (int vid in Mesh->VertexIndices()) { + // if (TempFlagBuffer[vid]) + // Mesh->SetVertex(vid, TempPosBuffer[vid]); + // } + //} +} + + + +FVector3d FRemesher::ComputeSmoothedVertexPos(int vID, + TFunction smoothFunc, bool& bModified) +{ + bModified = false; + FVertexConstraint vConstraint = FVertexConstraint::Unconstrained(); + GetVertexConstraint(vID, vConstraint); + if (vConstraint.Fixed && vConstraint.Movable == false) + { + return Mesh->GetVertex(vID); + } + EVertexControl vControl = (VertexControlF == nullptr) ? EVertexControl::AllowAll : VertexControlF(vID); + if (((int)vControl & (int)EVertexControl::NoSmooth) != 0) + { + return Mesh->GetVertex(vID); + } + + FVector3d vSmoothed = smoothFunc(*Mesh, vID, SmoothSpeedT); + check(VectorUtil::IsFinite(vSmoothed)); // this will really catch a lot of bugs... + + // project onto either vtx constraint target, or surface target + if (vConstraint.Target != nullptr) + { + vSmoothed = vConstraint.Target->Project(vSmoothed, vID); + } + else if (EnableInlineProjection() && ProjTarget != nullptr) + { + if (((int)vControl & (int)EVertexControl::NoProject) == 0) + { + vSmoothed = ProjTarget->Project(vSmoothed, vID); + } + } + + bModified = true; + return vSmoothed; +} + + +void FRemesher::ApplyToSmoothVertices(const TFunction& VertexSmoothFunc) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + VertexSmoothFunc(vid); + } +} + + + + +// Project vertices onto projection target. +// We can do projection in parallel if we have .net +void FRemesher::FullProjectionPass() +{ + auto UseProjectionFunc = [&](int vID) + { + if (IsVertexConstrained(vID)) + { + return; + } + if (VertexControlF != nullptr && ((int)VertexControlF(vID) & (int)EVertexControl::NoProject) != 0) + { + return; + } + FVector3d curpos = Mesh->GetVertex(vID); + FVector3d projected = ProjTarget->Project(curpos, vID); + Mesh->SetVertex(vID, projected); + }; + + ApplyToProjectVertices(UseProjectionFunc); + + // [RMS] not sure how to do this... + //if (EnableParallelProjection) { + // gParallel.ForEach(project_vertices(), project); + //} else { + // foreach (int vid in project_vertices()) + // project(vid); + //} +} + + + + +void FRemesher::ApplyToProjectVertices(const TFunction& VertexProjectFunc) +{ + for (int vid : Mesh->VertexIndicesItr()) + { + VertexProjectFunc(vid); + } +} + + + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMesh3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMesh3.h new file mode 100644 index 000000000000..7feb9a356b3a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMesh3.h @@ -0,0 +1,1414 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp DMesh3 + +#pragma once + +#include "BoxTypes.h" +#include "FrameTypes.h" +#include "GeometryTypes.h" +#include "MathUtil.h" +#include "Quaternion.h" +#include "Util/DynamicVector.h" +#include "Util/IndexUtil.h" +#include "Util/IteratorUtil.h" +#include "Util/RefCountVector.h" +#include "Util/SmallListSet.h" +#include "VectorTypes.h" +#include "VectorUtil.h" + + +class FDynamicMeshAttributeSet; +class FMeshShapeGenerator; + +enum class EMeshComponents +{ + None = 0, + VertexNormals = 1, + VertexColors = 2, + VertexUVs = 4, + FaceGroups = 8 +}; + +/** + * FVertexInfo stores information about vertex attributes - position, normal, color, UV + */ +struct FVertexInfo +{ + FVector3d Position; + FVector3f Normal; + FVector3f Color; + FVector2f UV; + bool bHaveN, bHaveUV, bHaveC; + + FVertexInfo() + { + Position = FVector3d::Zero(); + Normal = Color = FVector3f::Zero(); + UV = FVector2f::Zero(); + bHaveN = bHaveC = bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn) + { + Position = PositionIn; + Normal = Color = FVector3f::Zero(); + UV = FVector2f::Zero(); + bHaveN = bHaveC = bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn, const FVector3f& NormalIn) + { + Position = PositionIn; + Normal = NormalIn; + Color = FVector3f::Zero(); + UV = FVector2f::Zero(); + bHaveN = true; + bHaveC = bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn, const FVector3f& NormalIn, const FVector3f& ColorIn) + { + Position = PositionIn; + Normal = NormalIn; + Color = ColorIn; + UV = FVector2f::Zero(); + bHaveN = bHaveC = true; + bHaveUV = false; + } + FVertexInfo(const FVector3d& PositionIn, const FVector3f& NormalIn, const FVector3f& ColorIn, const FVector2f& UVIn) + { + Position = PositionIn; + Normal = NormalIn; + Color = ColorIn; + UV = UVIn; + bHaveN = bHaveC = bHaveUV = true; + } +}; + +/** +* FDynamicMesh3 is a dynamic triangle mesh class. The mesh has has connectivity, +* is an indexed mesh, and allows for gaps in the index space. +* +* internally, all data is stored in POD-type buffers, except for the vertex->edge +* links, which are stored as List's. The arrays of POD data are stored in +* TDynamicVector's, so they grow in chunks, which is relatively efficient. The actual +* blocks are arrays, so they can be efficiently mem-copied into larger buffers +* if necessary. +* +* Reference counts for verts/tris/edges are stored as separate FRefCountVector +* instances. +* +* Vertices are stored as doubles, although this should be easily changed +* if necessary, as the internal data structure is not exposed +* +* Per-vertex Vertex Normals, Colors, and UVs are optional and stored as floats. +* +* For each vertex, VertexEdgeLists[i] is the unordered list of connected edges. The +* elements of the list are indices into the edges list. +* This list is unsorted but can be traversed in-order (ie cw/ccw) at some additional cost. +* +* Triangles are stored as 3 ints, with optionally a per-triangle integer group id. +* +* The edges of a triangle are similarly stored as 3 ints, in triangle_edes. If the +* triangle is [v1,v2,v3], then the triangle edges [e1,e2,e3] are +* e1=edge(v1,v2), e2=edge(v2,v3), e3=edge(v3,v1), where the e# are indexes into edges. +* +* Edges are stored as tuples of 4 ints. If the edge is between v1 and v2, with neighbour +* tris t1 and t2, then the edge is [min(v1,v2), max(v1,v2), t1, t2]. For a boundary +* edge, t2 is InvalidID. t1 is never InvalidID. +* +* Most of the class assumes that the mesh is manifold. Many functions will +* work if the topology is non-manifold, but behavior of operators like Split/Flip/Collapse +* edge is untested. +* +* The function CheckValidity() does extensive sanity checking on the mesh data structure. +* Use this to test your code, both for mesh construction and editing!! +* +* +* TODO: +* - Many of the iterators depend on lambda functions, can we replace these with calls to +* internal/static functions that do the same thing? +* - efficient TriTrianglesItr() implementation? +* - additional Topology timestamp? +* - CompactInPlace() does not compact VertexEdgeLists +* - CompactCopy does not support per-vertex or extended attributes +* ? TDynamicVector w/ 'stride' option, so that we can guarantee that tuples are in single block. +* The can have custom accessor that looks up entire tuple +*/ +class DYNAMICMESH_API FDynamicMesh3 +{ +public: + /** InvalidID indicates that a vertex/edge/triangle ID is invalid */ + static const int InvalidID; // = IndexConstants::InvalidID = -1 + /** NonManifoldID is returned by AppendTriangle() to indicate that the added triangle would result in nonmanifold geometry and hence was ignored */ + static const int NonManifoldID; // = -2 + /** InvalidGroupID indicates that a group ID is invalid */ + static const int InvalidGroupID; // = IndexConstants::InvalidID = -1 + + static FVector3d InvalidVertex() + { + return FVector3d(TNumericLimits::Max(), 0, 0); + } + static FIndex3i InvalidTriangle() + { + return FIndex3i(InvalidID, InvalidID, InvalidID); + } + static FIndex2i InvalidEdge() + { + return FIndex2i(InvalidID, InvalidID); + } + +protected: + /** Reference counts of vertex indices. Iterate over this to find out which vertex indices are valid. */ + FRefCountVector VertexRefCounts; + /** List of vertex positions */ + TDynamicVector Vertices; + /** (optional) List of per-vertex normals */ + TDynamicVector* VertexNormals = nullptr; + /** (optional) List of per-vertex colors */ + TDynamicVector* VertexColors = nullptr; + /** (optional) List of per-vertex uvs */ + TDynamicVector* VertexUVs = nullptr; + + /** List of per-vertex edge one-rings */ + FSmallListSet VertexEdgeLists; + + /** Reference counts of triangle indices. Iterate over this to find out which triangle indices are valid. */ + FRefCountVector TriangleRefCounts; + /** List of triangle vertex-index triplets [Vert0 Vert1 Vert2]*/ + TDynamicVector Triangles; + /** List of triangle edge triplets [Edge0 Edge1 Edge2] */ + TDynamicVector TriangleEdges; + /** (optional) List of per-triangle group identifiers */ + TDynamicVector* TriangleGroups = nullptr; + + /** Reference counts of edge indices. Iterate over this to find out which edge indices are valid. */ + FRefCountVector EdgeRefCounts; + /** List of edge elements. An edge is four elements [VertA, VertB, Tri0, Tri1], where VertA < VertB, and Tri1 may be InvalidID (if the edge is a boundary edge) */ + TDynamicVector Edges; + + FDynamicMeshAttributeSet* AttributeSet = nullptr; + + /** The mesh timestamp is incremented any time a function that modifies the mesh is called */ + int Timestamp = 0; + /** The shape timestamp is incremented any time a function that modifies the mesh shape or topology is called */ + int ShapeTimestamp = 0; + /** The topology timestamp is incremented any time a function that modifies the mesh topology is called */ + int TopologyTimestamp = 0; + + /** Upper bound on the triangle group IDs used in the mesh (may be larger than the actual maximum if triangles have been deleted) */ + int GroupIDCounter = 0; + + /** Cached vertex bounding box (includes un-referenced vertices) */ + FAxisAlignedBox3d CachedBoundingBox; + /** timestamp for CachedBoundingBox, if less than current timestamp, cache is invalid */ + int CachedBoundingBoxTimestamp = -1; + /** Cached value of IsClosed() */ + bool bIsClosedCached = false; + /** timestamp for bIsClosedCached, if less than current timestamp, cache is invalid */ + int CachedIsClosedTimestamp = -1; + +public: + virtual ~FDynamicMesh3(); + + explicit FDynamicMesh3(bool bWantNormals = true, bool bWantColors = false, bool bWantUVs = false, bool bWantTriGroups = false); + + FDynamicMesh3(EMeshComponents flags) + : FDynamicMesh3(((int)flags & (int)EMeshComponents::VertexNormals) != 0, ((int)flags & (int)EMeshComponents::VertexColors) != 0, + ((int)flags & (int)EMeshComponents::VertexUVs) != 0, ((int)flags & (int)EMeshComponents::FaceGroups) != 0) + { + } + + /** + * @param CopyMesh mesh to copy + * @param bCompact if true, compact CopyMesh on the fly + * @param bWantNormals should we copy per-vertex normals, if they exist + * @param bWantColors should we copy per-vertex colors, if they exist + * @param bWantUVs should we copy per-vertex uvs, if they exist + */ + FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact = false, bool bWantNormals = true, bool bWantColors = true, bool bWantUVs = true, bool bWantAttributes = true); + + /** + * @param CopyMesh mesh to copy + * @param bCompact if true, compact CopyMesh on the fly + * @param Flags which components of CopyMesh to copy, if they exist + */ + FDynamicMesh3(const FDynamicMesh3& CopyMesh, bool bCompact, EMeshComponents Flags); + + /** Initialize mesh from the output of a MeshShapeGenerator (assumes Generate() was already called) */ + FDynamicMesh3(const FMeshShapeGenerator* Generator); + + /** copy assignment operator */ + const FDynamicMesh3& operator=(const FDynamicMesh3& CopyMesh); + + // + // Copy functions to construct a mesh from an input mesh + // +public: + /** Set internal data structures to be a copy of input mesh */ + void Copy(const FDynamicMesh3& CopyMesh, bool bNormals = true, bool bColors = true, bool bUVs = true, bool bAttributes = true); + + /** Initialize mesh from the output of a MeshShapeGenerator (assumes Generate() was already called) */ + void Copy(const FMeshShapeGenerator* Generator); + + // @todo make this work + struct FCompactMaps + { + TMap MapV; + }; + + /** Copy input mesh while compacting, ie removing unused vertices/triangles/edges */ + void CompactCopy(const FDynamicMesh3& CopyMesh, bool bNormals = true, bool bColors = true, bool bUVs = true, bool bAttributes = true, FCompactMaps* CompactInfo = nullptr); + + /** Discard all data */ + void Clear(); + + +public: + + /** @return number of vertices in the mesh */ + int VertexCount() const + { + return (int)VertexRefCounts.GetCount(); + } + /** @return number of triangles in the mesh */ + int TriangleCount() const + { + return (int)TriangleRefCounts.GetCount(); + } + /** @return number of edges in the mesh */ + int EdgeCount() const + { + return (int)EdgeRefCounts.GetCount(); + } + + /** @return upper bound on vertex IDs used in the mesh, ie all vertex IDs in use are < MaxVertexID */ + int MaxVertexID() const + { + return (int)VertexRefCounts.GetMaxIndex(); + } + /** @return upper bound on triangle IDs used in the mesh, ie all triangle IDs in use are < MaxTriangleID */ + int MaxTriangleID() const + { + return (int)TriangleRefCounts.GetMaxIndex(); + } + /** @return upper bound on edge IDs used in the mesh, ie all edge IDs in use are < MaxEdgeID */ + int MaxEdgeID() const + { + return (int)EdgeRefCounts.GetMaxIndex(); + } + /** @return upper bound on group IDs used in the mesh, ie all group IDs in use are < MaxGroupID */ + int MaxGroupID() const + { + return GroupIDCounter; + } + + + /** @return true if this mesh has per-vertex normals */ + bool HasVertexNormals() const + { + return VertexNormals != nullptr; + } + /** @return true if this mesh has per-vertex colors */ + bool HasVertexColors() const + { + return VertexColors != nullptr; + } + /** @return true if this mesh has per-vertex UVs */ + bool HasVertexUVs() const + { + return VertexUVs != nullptr; + } + /** @return true if this mesh has per-triangle groups */ + bool HasTriangleGroups() const + { + return TriangleGroups != nullptr; + } + + + /** @return true if this mesh has attribute layers */ + bool HasAttributes() const + { + return AttributeSet != nullptr; + } + + /** @return bitwise-or of EMeshComponents flags specifying which extra data this mesh has */ + int GetComponentsFlags() const; + + + /** @return true if VertexID is a valid vertex in this mesh */ + inline bool IsVertex(int VertexID) const + { + return VertexRefCounts.IsValid(VertexID); + } + /** @return true if TriangleID is a valid triangle in this mesh */ + inline bool IsTriangle(int TriangleID) const + { + return TriangleRefCounts.IsValid(TriangleID); + } + /** @return true if EdgeID is a valid edge in this mesh */ + inline bool IsEdge(int EdgeID) const + { + return EdgeRefCounts.IsValid(EdgeID); + } + + + // + // Mesh Element Iterators + // The functions VertexIndicesItr() / TriangleIndicesItr() / EdgeIndicesItr() allow you to do: + // for ( int eid : EdgeIndicesItr() ) { ... } + // and other related begin() / end() idioms +public: + // simplify names for iterations + typedef typename FRefCountVector::IndexEnumerable vertex_iterator; + typedef typename FRefCountVector::IndexEnumerable triangle_iterator; + typedef typename FRefCountVector::IndexEnumerable edge_iterator; + template + using value_iteration = FRefCountVector::MappedEnumerable; + using vtx_triangles_enumerable = ExpandEnumerable; + + /** @return enumerable object for valid vertex indices suitable for use with range-based for, ie for ( int i : VertexIndicesItr() ) */ + vertex_iterator VertexIndicesItr() const + { + return VertexRefCounts.Indices(); + } + + /** @return enumerable object for valid triangle indices suitable for use with range-based for, ie for ( int i : TriangleIndicesItr() ) */ + triangle_iterator TriangleIndicesItr() const + { + return TriangleRefCounts.Indices(); + } + + /** @return enumerable object for valid edge indices suitable for use with range-based for, ie for ( int i : EdgeIndicesItr() ) */ + edge_iterator EdgeIndicesItr() const + { + return EdgeRefCounts.Indices(); + } + + // TODO: write helper functions that allow us to do these iterations w/o lambdas + + /** @return enumerable object for boundary edge indices suitable for use with range-based for, ie for ( int i : BoundaryEdgeIndicesItr() ) */ + FRefCountVector::FilteredEnumerable BoundaryEdgeIndicesItr() const + { + return EdgeRefCounts.FilteredIndices([this](int EdgeID) {\ + return Edges[4*EdgeID + 3] == InvalidID; + }); + } + + /** Enumerate positions of all vertices in mesh */ + value_iteration VerticesItr() const + { + return VertexRefCounts.MappedIndices([this](int VertexID) { + int i = 3 * VertexID; + return FVector3d(Vertices[i], Vertices[i + 1], Vertices[i + 2]); + }); + } + + /** Enumerate all triangles in the mesh */ + value_iteration TrianglesItr() const + { + return TriangleRefCounts.MappedIndices([this](int TriangleID) { + int i = 3 * TriangleID; + return FIndex3i(Triangles[i], Triangles[i + 1], Triangles[i + 2]); + }); + } + + /** Enumerate edges. Each returned element is [v0,v1,t0,t1], where t1 will be InvalidID if this is a boundary edge */ + value_iteration EdgesItr() const + { + return EdgeRefCounts.MappedIndices([this](int EdgeID) { + int i = 4 * EdgeID; + return FIndex4i(Edges[i], Edges[i + 1], Edges[i + 2], Edges[i + 3]); + }); + } + + /** @return enumerable object for one-ring vertex neighbours of a vertex, suitable for use with range-based for, ie for ( int i : VtxVerticesItr(VertexID) ) */ + FSmallListSet::ValueEnumerable VtxVerticesItr(int VertexID) const + { + check(VertexRefCounts.IsValid(VertexID)); + return VertexEdgeLists.Values(VertexID, [VertexID, this](int eid) { return GetOtherEdgeVertex(eid, VertexID); }); + } + + /** @return enumerable object for one-ring edges of a vertex, suitable for use with range-based for, ie for ( int i : VtxEdgesItr(VertexID) ) */ + FSmallListSet::ValueEnumerable VtxEdgesItr(int VertexID) const + { + check(VertexRefCounts.IsValid(VertexID)); + return VertexEdgeLists.Values(VertexID); + } + + /** @return enumerable object for one-ring triangles of a vertex, suitable for use with range-based for, ie for ( int i : VtxTrianglesItr(VertexID) ) */ + vtx_triangles_enumerable VtxTrianglesItr(int VertexID) const; + + + + // + // Mesh Construction + // +public: + /** Append vertex at position and other fields, returns vid */ + int AppendVertex(const FVertexInfo& VertInfo); + + /** Append vertex at position, returns vid */ + int AppendVertex(const FVector3d& Position) + { + return AppendVertex(FVertexInfo(Position)); + } + + /** Copy vertex SourceVertexID from existing SourceMesh, returns new vertex id */ + int AppendVertex(const FDynamicMesh3& SourceMesh, int SourceVertexID); + + int AppendTriangle(const FIndex3i& TriVertices, int GroupID = -1); + + inline int AppendTriangle(int Vertex0, int Vertex1, int Vertex2, int GroupID = -1) + { + return AppendTriangle(FIndex3i(Vertex0, Vertex1, Vertex2), GroupID); + } + + // + // Support for inserting vertex and triangle at specific IDs. This is a bit tricky + // because we likely will need to update the free lists in the RefCountVectors, which + // can be expensive. If you are going to do many inserts (eg inside a loop), wrap in + // BeginUnsafe / EndUnsafe calls, and pass bUnsafe = true to the InsertX() calls, to + // the defer free list rebuild until you are done. + // + + /** Call this before a set of unsafe InsertVertex() calls */ + virtual void BeginUnsafeVerticesInsert() + { + // do nothing... + } + + /** Call after a set of unsafe InsertVertex() calls to rebuild free list */ + virtual void EndUnsafeVerticesInsert() + { + VertexRefCounts.RebuildFreeList(); + } + + /** + * Insert vertex at given index, assuming it is unused. + * If bUnsafe, we use fast id allocation that does not update free list. + * You should only be using this between BeginUnsafeVerticesInsert() / EndUnsafeVerticesInsert() calls + */ + EMeshResult InsertVertex(int VertexID, const FVertexInfo& VertInfo, bool bUnsafe = false); + + /** Call this before a set of unsafe InsertTriangle() calls */ + virtual void BeginUnsafeTrianglesInsert() + { + // do nothing... + } + + /** Call after a set of unsafe InsertTriangle() calls to rebuild free list */ + virtual void EndUnsafeTrianglesInsert() + { + TriangleRefCounts.RebuildFreeList(); + } + + /** + * Insert triangle at given index, assuming it is unused. + * If bUnsafe, we use fast id allocation that does not update free list. + * You should only be using this between BeginUnsafeTrianglesInsert() / EndUnsafeTrianglesInsert() calls + */ + EMeshResult InsertTriangle(int TriangleID, const FIndex3i& TriVertices, int GroupID = -1, bool bUnsafe = false); + + // + // Vertex/Tri/Edge accessors + // +public: + + /** @return the vertex position */ + inline FVector3d GetVertex(int VertexID) const + { + check(IsVertex(VertexID)); + int i = 3 * VertexID; + return FVector3d(Vertices[i], Vertices[i + 1], Vertices[i + 2]); + } + + /** Set vertex position */ + inline void SetVertex(int VertexID, const FVector3d& vNewPos) + { + check(VectorUtil::IsFinite(vNewPos)); + check(IsVertex(VertexID)); + int i = 3 * VertexID; + Vertices[i] = vNewPos.X; + Vertices[i + 1] = vNewPos.Y; + Vertices[i + 2] = vNewPos.Z; + UpdateTimeStamp(true, false); + } + + /** Get extended vertex information */ + bool GetVertex(int VertexID, FVertexInfo& VertInfo, bool bWantNormals, bool bWantColors, bool bWantUVs) const; + + /** Get all vertex information available */ + FVertexInfo GetVertexInfo(int VertexID) const; + + /** @return the valence of a vertex (the number of connected edges) */ + int GetVtxEdgeCount(int VertexID) const + { + return VertexRefCounts.IsValid(VertexID) ? VertexEdgeLists.GetCount(VertexID) : -1; + } + + /** @return the max valence of all vertices in the mesh */ + int GetMaxVtxEdgeCount() const; + + /** Get triangle vertices */ + inline FIndex3i GetTriangle(int TriangleID) const + { + check(IsTriangle(TriangleID)); + int i = 3 * TriangleID; + return FIndex3i(Triangles[i], Triangles[i + 1], Triangles[i + 2]); + } + + /** Get triangle edges */ + inline FIndex3i GetTriEdges(int TriangleID) const + { + check(IsTriangle(TriangleID)); + int i = 3 * TriangleID; + return FIndex3i(TriangleEdges[i], TriangleEdges[i + 1], TriangleEdges[i + 2]); + } + + /** Get one of the edges of a triangle */ + inline int GetTriEdge(int TriangleID, int j) const + { + check(IsTriangle(TriangleID)); + return TriangleEdges[3 * TriangleID + j]; + } + + /** Find the neighbour triangles of a triangle (any of them might be InvalidID) */ + FIndex3i GetTriNeighbourTris(int TriangleID) const; + + /** Get the three vertex positions of a triangle */ + inline void GetTriVertices(int TriangleID, FVector3d& v0, FVector3d& v1, FVector3d& v2) const + { + int i = 3 * TriangleID; + int ai = 3 * Triangles[i]; + v0.X = Vertices[ai]; v0.Y = Vertices[ai + 1]; v0.Z = Vertices[ai + 2]; + int bi = 3 * Triangles[i + 1]; + v1.X = Vertices[bi]; v1.Y = Vertices[bi + 1]; v1.Z = Vertices[bi + 2]; + int ci = 3 * Triangles[i + 2]; + v2.X = Vertices[ci]; v2.Y = Vertices[ci + 1]; v2.Z = Vertices[ci + 2]; + } + + /** Get the position of one of the vertices of a triangle */ + inline FVector3d GetTriVertex(int TriangleID, int j) const + { + int vi = 3 * Triangles[3 * TriangleID + j]; + return FVector3d(Vertices[vi], Vertices[vi + 1], Vertices[vi + 2]); + } + + /** Get the vertices and triangles of an edge, returned as [v0,v1,t0,t1], where t1 may be InvalidID */ + inline FIndex4i GetEdge(int EdgeID) const + { + check(IsEdge(EdgeID)); + int i = 4 * EdgeID; + return FIndex4i(Edges[i], Edges[i + 1], Edges[i + 2], Edges[i + 3]); + } + + /** Get the vertex pair for an edge */ + inline FIndex2i GetEdgeV(int EdgeID) const + { + check(IsEdge(EdgeID)); + int i = 4 * EdgeID; + return FIndex2i(Edges[i], Edges[i + 1]); + } + + /** Get the vertex positions of an edge */ + inline bool GetEdgeV(int EdgeID, FVector3d& a, FVector3d& b) const + { + check(IsEdge(EdgeID)); + int iv0 = 3 * Edges[4 * EdgeID]; + a.X = Vertices[iv0]; + a.Y = Vertices[iv0 + 1]; + a.Z = Vertices[iv0 + 2]; + int iv1 = 3 * Edges[4 * EdgeID + 1]; + b.X = Vertices[iv1]; + b.Y = Vertices[iv1 + 1]; + b.Z = Vertices[iv1 + 2]; + return true; + } + + /** Get the triangle pair for an edge. The second triangle may be InvalidID */ + inline FIndex2i GetEdgeT(int EdgeID) const + { + check(IsEdge(EdgeID)); + int i = 4 * EdgeID; + return FIndex2i(Edges[i + 2], Edges[i + 3]); + } + + /** Return edge vertex indices, but oriented based on attached triangle (rather than min-sorted) */ + FIndex2i GetOrientedBoundaryEdgeV(int EdgeID) const; + + // + // Vertex and Triangle attribute arrays + // +public: + void EnableVertexNormals(const FVector3f& InitialNormal); + void DiscardVertexNormals(); + + FVector3f GetVertexNormal(int vID) const + { + if (HasVertexNormals() == false) + { + return FVector3f::UnitY(); + } + check(IsVertex(vID)); + int i = 3 * vID; + return FVector3f((*VertexNormals)[i], (*VertexNormals)[i + 1], (*VertexNormals)[i + 2]); + } + + void SetVertexNormal(int vID, const FVector3f& vNewNormal) + { + if (HasVertexNormals()) + { + check(IsVertex(vID)); + int i = 3 * vID; + (*VertexNormals)[i] = vNewNormal.X; + (*VertexNormals)[i + 1] = vNewNormal.Y; + (*VertexNormals)[i + 2] = vNewNormal.Z; + UpdateTimeStamp(false, false); + } + } + + void EnableVertexColors(const FVector3f& InitialColor); + void DiscardVertexColors(); + + + FVector3f GetVertexColor(int vID) const + { + if (HasVertexColors() == false) + { + return FVector3f::One(); + } + check(IsVertex(vID)); + int i = 3 * vID; + return FVector3f((*VertexColors)[i], (*VertexColors)[i + 1], (*VertexColors)[i + 2]); + } + + void SetVertexColor(int vID, const FVector3f& vNewColor) + { + if (HasVertexColors()) + { + check(IsVertex(vID)); + int i = 3 * vID; + (*VertexColors)[i] = vNewColor.X; + (*VertexColors)[i + 1] = vNewColor.Y; + (*VertexColors)[i + 2] = vNewColor.Z; + UpdateTimeStamp(false, false); + } + } + + void EnableVertexUVs(const FVector2f& InitialUV); + void DiscardVertexUVs(); + + FVector2f GetVertexUV(int vID) const + { + if (HasVertexUVs() == false) + { + return FVector2f::Zero(); + } + check(IsVertex(vID)); + int i = 2 * vID; + return FVector2f((*VertexUVs)[i], (*VertexUVs)[i + 1]); + } + + void SetVertexUV(int vID, const FVector2f& vNewUV) + { + if (HasVertexUVs()) + { + check(IsVertex(vID)); + int i = 2 * vID; + (*VertexUVs)[i] = vNewUV.X; + (*VertexUVs)[i + 1] = vNewUV.Y; + UpdateTimeStamp(false, false); + } + } + + void EnableTriangleGroups(int InitialGroupID = 0); + void DiscardTriangleGroups(); + + int AllocateTriangleGroup() { return ++GroupIDCounter; } + + int GetTriangleGroup(int tID) const + { + return (HasTriangleGroups() == false) ? -1 + : (TriangleRefCounts.IsValid(tID) ? (*TriangleGroups)[tID] : 0); + } + + void SetTriangleGroup(int tid, int group_id) + { + if (HasTriangleGroups()) + { + check(IsTriangle(tid)); + (*TriangleGroups)[tid] = group_id; + GroupIDCounter = FMath::Max(GroupIDCounter, group_id + 1); + UpdateTimeStamp(false, false); + } + } + + FDynamicMeshAttributeSet* Attributes() + { + return AttributeSet; + } + const FDynamicMeshAttributeSet* Attributes() const + { + return AttributeSet; + } + + void EnableAttributes(); + + void DiscardAttributes(); + + + // + // topological queries + // +public: + /** Returns true if edge is on the mesh boundary, ie only connected to one triangle */ + inline bool IsBoundaryEdge(int EdgeID) const + { + check(IsEdge(EdgeID)); + return Edges[4 * EdgeID + 3] == InvalidID; + } + + /** Returns true if the vertex is part of any boundary edges */ + bool IsBoundaryVertex(int VertexID) const; + + /** Returns true if any edge of triangle is a boundary edge */ + bool IsBoundaryTriangle(int TriangleID) const; + + /** Find id of edge connecting A and B */ + int FindEdge(int VertexA, int VertexB) const; + + /** Find edgeid for edge [a,b] from triangle that contains the edge. Faster than FindEdge() because it is constant-time. */ + int FindEdgeFromTri(int VertexA, int VertexB, int TriangleID) const; + + /** Find triangle made up of any permutation of vertices [a,b,c] */ + int FindTriangle(int A, int B, int C) const; + + /** + * If edge has vertices [a,b], and is connected two triangles [a,b,c] and [a,b,d], + * this returns [c,d], or [c,InvalidID] for a boundary edge + */ + FIndex2i GetEdgeOpposingV(int EdgeID) const; + + /** + * Given an edge and vertex on that edge, returns other vertex of edge, the two opposing verts, and the two connected triangles (OppVert2Out and Tri2Out are be InvalidID for boundary edge) + */ + void GetVtxNbrhood(int EdgeID, int VertexID, int& OtherVertOut, int& OppVert1Out, int& OppVert2Out, int& Tri1Out, int& Tri2Out) const; + + /** + * Returns count of boundary edges at vertex, and the first two boundary + * edges if found. If return is > 2, call GetAllVtxBoundaryEdges + */ + int GetVtxBoundaryEdges(int VertexID, int& Edge0Out, int& Edge1Out) const; + + /** + * Find edge ids of boundary edges connected to vertex. + * @param vID Vertex ID + * @param EdgeListOut boundary edge IDs are appended to this list + * @return count of number of elements of e that were filled + */ + int GetAllVtxBoundaryEdges(int VertexID, TArray& EdgeListOut) const; + + /** + * return # of triangles attached to vID, or -1 if invalid vertex + * if bBruteForce = true, explicitly checks, which creates a list and is expensive + * default is false, uses orientation, no memory allocation + */ + int GetVtxTriangleCount(int VertexID, bool bBruteForce = false) const; + + /** + * Get triangle one-ring at vertex. + * bUseOrientation is more efficient but returns incorrect result if vertex is a bowtie + */ + EMeshResult GetVtxTriangles(int VertexID, TArray& TrianglesOut, bool bUseOrientation) const; + + /** Returns true if the two triangles connected to edge have different group IDs */ + bool IsGroupBoundaryEdge(int EdgeID) const; + + /** Returns true if vertex has more than one tri group in its tri nbrhood */ + bool IsGroupBoundaryVertex(int VertexID) const; + + /** Returns true if more than two group boundary edges meet at vertex (ie 3+ groups meet at this vertex) */ + bool IsGroupJunctionVertex(int VertexID) const; + + /** Returns up to 4 group IDs at vertex. Returns false if > 4 encountered */ + bool GetVertexGroups(int VertexID, FIndex4i& GroupsOut) const; + + /** Returns all group IDs at vertex */ + bool GetAllVertexGroups(int VertexID, TArray& GroupsOut) const; + + /** returns true if vID is a "bowtie" vertex, ie multiple disjoint triangle sets in one-ring */ + bool IsBowtieVertex(int VertexID) const; + + /** returns true if vertices, edges, and triangles are all dense (Count == MaxID) **/ + bool IsCompact() const + { + return VertexRefCounts.IsDense() && EdgeRefCounts.IsDense() && TriangleRefCounts.IsDense(); + } + + /** @return true if vertex count == max vertex id */ + bool IsCompactV() const + { + return VertexRefCounts.IsDense(); + } + + /** @return true if triangle count == max triangle id */ + bool IsCompactT() const + { + return TriangleRefCounts.IsDense(); + } + + /** returns measure of compactness in range [0,1], where 1 is fully compacted */ + double CompactMetric() const + { + return ((double)VertexCount() / (double)MaxVertexID() + (double)TriangleCount() / (double)MaxTriangleID()) * 0.5; + } + + /** @return true if mesh has no boundary edges */ + bool IsClosed() const; + + /** @return value of IsClosed() and cache result. cache is invalidated and recomputed if topology has changed since last call */ + bool GetCachedIsClosed(); + + + /** Timestamp is incremented any time any change is made to the mesh */ + inline int GetTimestamp() const + { + return Timestamp; + } + + /** ShapeTimestamp is incremented any time any vertex position is changed or the mesh topology is modified */ + inline int GetShapeTimestamp() const + { + return ShapeTimestamp; + } + + /** TopologyTimestamp is incremented any time any vertex position is changed or the mesh topology is modified */ + inline int GetTopologyTimestamp() const + { + return TopologyTimestamp; + } + + // + // Geometric queries + // +public: + /** Returns bounding box of all mesh vertices (including unreferenced vertices) */ + FAxisAlignedBox3d GetBounds() const; + + /** Returns GetBounds() and saves result, cache is invalidated and recomputed if topology has changed since last call */ + FAxisAlignedBox3d GetCachedBounds(); + + /** + * Compute a normal/tangent frame at vertex that is "stable" as long as + * the mesh topology doesn't change, meaning that one axis of the frame + * will be computed from projection of outgoing edge. Requires that vertex normals are available. + * By default, frame.Z is normal, and .X points along mesh edge. + * If bFrameNormalY, then frame.Y is normal (X still points along mesh edge) + */ + FFrame3d GetVertexFrame(int VertexID, bool bFrameNormalY = false) const; + + /** Calculate face normal of triangle */ + FVector3d GetTriNormal(int TriangleID) const; + + /** Calculate area triangle */ + double GetTriArea(int TriangleID) const; + + /** + * Compute triangle normal, area, and centroid all at once. Re-uses vertex + * lookups and computes normal & area simultaneously. *However* does not produce + * the same normal/area as separate calls, because of this. + */ + void GetTriInfo(int TriangleID, FVector3d& Normal, double& Area, FVector3d& Centroid) const; + + /** Compute centroid of triangle */ + FVector3d GetTriCentroid(int TriangleID) const; + + /** Interpolate vertex positions of triangle using barycentric coordinates */ + FVector3d GetTriBaryPoint(int TriangleID, double Bary0, double Bary1, double Bary2) const; + + /** Interpolate vertex normals of triangle using barycentric coordinates */ + FVector3d GetTriBaryNormal(int TriangleID, double Bary0, double Bary1, double Bary2) const; + + /** Compute interpolated vertex attributes at point of triangle */ + void GetTriBaryPoint(int TriangleID, double Bary0, double Bary1, double Bary2, FVertexInfo& VertInfo) const; + + /** Construct bounding box of triangle as efficiently as possible */ + FAxisAlignedBox3d GetTriBounds(int TriangleID) const; + + /** Construct stable frame at triangle centroid, where frame.Z is face normal, and frame.X is aligned with edge nEdge of triangle. */ + FFrame3d GetTriFrame(int TriangleID, int Edge = 0) const; + + /** Compute solid angle of oriented triangle tID relative to point p - see WindingNumber() */ + double GetTriSolidAngle(int TriangleID, const FVector3d& p) const; + + /** Compute internal angle at vertex i of triangle (where i is 0,1,2); */ + double GetTriInternalAngleR(int TriangleID, int i); + + /** Returns average normal of connected face normals */ + FVector3d GetEdgeNormal(int EdgeID) const; + + /** Get point along edge, t clamped to range [0,1] */ + FVector3d GetEdgePoint(int EdgeID, double ParameterT) const; + + /** + * Fastest possible one-ring centroid. This is used inside many other algorithms + * so it helps to have it be maximally efficient + */ + void GetVtxOneRingCentroid(int VertexID, FVector3d& CentroidOut) const; + + /** + * Compute mesh winding number, from Jacobson et al, Robust Inside-Outside Segmentation using Generalized Winding Numbers + * http://igl.ethz.ch/projects/winding-number/ + * returns ~0 for points outside a closed, consistently oriented mesh, and a positive or negative integer + * for points inside, with value > 1 depending on how many "times" the point inside the mesh (like in 2D polygon winding) + */ + double CalculateWindingNumber(const FVector3d& QueryPoint) const; + + // + // direct buffer access + // +public: + const TDynamicVector& GetVerticesBuffer() + { + return Vertices; + } + const FRefCountVector& GetVerticesRefCounts() + { + return VertexRefCounts; + } + const TDynamicVector* GetNormalsBuffer() + { + return VertexNormals; + } + const TDynamicVector* GetColorsBuffer() + { + return VertexColors; + } + const TDynamicVector* GetUVBuffer() + { + return VertexUVs; + } + + const TDynamicVector& GetTrianglesBuffer() + { + return Triangles; + } + const FRefCountVector& GetTrianglesRefCounts() + { + return TriangleRefCounts; + } + const TDynamicVector* GetTriangleGroupsBuffer() + { + return TriangleGroups; + } + + const TDynamicVector& GetEdgesBuffer() + { + return Edges; + } + const FRefCountVector& GetEdgesRefCounts() + { + return EdgeRefCounts; + } + const FSmallListSet& GetVertexEdges() + { + return VertexEdgeLists; + } + + // + // Mesh Edit operations + // +public: + /** + * Compact mesh in-place, by moving vertices around and rewriting indices. + * Should be faster if the amount of compacting is not too significant, and is useful in some places. + * + * @param bComputeCompactInfo if false, then returned CompactMaps is not initialized + * @todo VertexEdgeLists is not compacted. does not affect indices, but does keep memory. + * @todo returned CompactMaps is currently not valid + */ + void CompactInPlace(FCompactMaps* CompactInfo = nullptr); + + /** + * Reverse the ccw/cw orientation of all triangles in the mesh, and + * optionally flip the vertex normals if they exist + */ + void ReverseOrientation(bool bFlipNormals = true); + + /** + * Reverse the ccw/cw orientation of a triangle + */ + EMeshResult ReverseTriOrientation(int TriangleID); + + /** + * Remove vertex vID, and all connected triangles if bRemoveAllTriangles = true + * Returns Failed_VertexStillReferenced if vertex is still referenced by triangles. + * if bPreserveManifold, checks that we will not create a bowtie vertex first + */ + EMeshResult RemoveVertex(int VertexID, bool bRemoveAllTriangles = true, bool bPreserveManifold = false); + + /** + * Remove a triangle from the mesh. Also removes any unreferenced edges after tri is removed. + * If bRemoveIsolatedVertices is false, then if you remove all tris from a vert, that vert is also removed. + * If bPreserveManifold, we check that you will not create a bowtie vertex (and return false). + * If this check is not done, you have to make sure you don't create a bowtie, because other + * code assumes we don't have bowties, and will not handle it properly + */ + EMeshResult RemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices = true, bool bPreserveManifold = false); + + /** + * Rewrite the triangle to reference the new tuple of vertices. + * + * @todo this function currently does not guarantee that the returned mesh is well-formed. Only call if you know it's OK. + */ + virtual EMeshResult SetTriangle(int TriangleID, const FIndex3i& NewVertices, bool bRemoveIsolatedVertices = true); + + + + /** Information about the mesh elements created by a call to SplitEdge() */ + struct FEdgeSplitInfo + { + int OriginalEdge; // the edge that was split + FIndex2i OriginalVertices; // original edge vertices [a,b] + FIndex2i OtherVertices; // original opposing vertices [c,d] - d is InvalidID for boundary edges + FIndex2i OriginalTriangles; // original edge triangles [t0,t1] + bool bIsBoundary; // was the split edge a boundary edge? (redundant) + + int NewVertex; // new vertex f that was created + FIndex2i NewTriangles; // new triangles [t2,t3], oriented as explained below + FIndex3i NewEdges; // new edges are [f,b], [f,c] and [f,d] if this is not a boundary edge + + double SplitT; // parameter value for NewVertex along original edge + }; + + /** + * Split an edge of the mesh by inserting a vertex. This creates a new triangle on either side of the edge (ie a 2-4 split). + * If the original edge had vertices [a,b], with triangles t0=[a,b,c] and t1=[b,a,d], then the split inserts new vertex f. + * After the split t0=[a,f,c] and t1=[f,a,d], and we have t2=[f,b,c] and t3=[f,d,b] (it's best to draw it out on paper...) + * + * @param EdgeAB index of the edge to be split + * @param SplitInfo returned information about new and modified mesh elements + * @param SplitParameterT defines the position along the edge that we split at, must be between 0 and 1, and is assumed to be based on the order of vertices returned by GetEdgeV() + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult SplitEdge(int EdgeAB, FEdgeSplitInfo& SplitInfo, double SplitParameterT = 0.5); + + /** + * Splits the edge between two vertices at the midpoint, if this edge exists + * @param EdgeVertA index of first vertex + * @param EdgeVertB index of second vertex + * @param SplitInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + EMeshResult SplitEdge(int EdgeVertA, int EdgeVertB, FEdgeSplitInfo& SplitInfo); + + + + /** Information about the mesh elements modified by a call to FlipEdge() */ + struct FEdgeFlipInfo + { + int EdgeID; // the edge that was flipped + FIndex2i OriginalVerts; // original verts of the flipped edge, that are no longer connected + FIndex2i OpposingVerts; // the opposing verts of the flipped edge, that are now connected + FIndex2i Triangles; // the two triangle IDs. Original tris vert [Vert0,Vert1,OtherVert0] and [Vert1,Vert0,OtherVert1]. + // New triangles are [OtherVert0, OtherVert1, Vert1] and [OtherVert1, OtherVert0, Vert0] + }; + + /** + * Flip/Rotate an edge of the mesh. This does not change the number of edges, vertices, or triangles. + * Boundary edges of the mesh cannot be flipped. + * @param EdgeAB index of edge to be flipped + * @param FlipInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult FlipEdge(int EdgeAB, FEdgeFlipInfo& FlipInfo); + + /** calls FlipEdge() on the edge between two vertices, if it exists + * @param EdgeVertA index of first vertex + * @param EdgeVertB index of second vertex + * @param FlipInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult FlipEdge(int EdgeVertA, int EdgeVertB, FEdgeFlipInfo& FlipInfo); + + + + /** Information about mesh elements modified/removed by CollapseEdge() */ + struct FEdgeCollapseInfo + { + int KeptVertex; // the vertex that was kept (ie collapsed "to") + int RemovedVertex; // the vertex that was removed + FIndex2i OpposingVerts; // the opposing vertices [c,d]. If the edge was a boundary edge, d is InvalidID + bool bIsBoundary; // was the edge a boundary edge + + int CollapsedEdge; // the edge that was collapsed/removed + FIndex2i RemovedTris; // the triangles that were removed in the collapse (second is InvalidID for boundary edge) + FIndex2i RemovedEdges; // the edges that were removed (second is InvalidID for boundary edge) + FIndex2i KeptEdges; // the edges that were kept (second is InvalidID for boundary edge) + + double CollapseT; // interpolation parameter along edge for new vertex in range [0,1] + }; + + /** + * Collapse the edge between the two vertices, if topologically possible. + * @param KeepVertID index of the vertex that should be kept + * @param RemoveVertID index of the vertex that should be removed + * @param EdgeParameterT vKeep is moved to Lerp(KeepPos, RemovePos, collapse_t) + * @param CollapseInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult CollapseEdge(int KeepVertID, int RemoveVertID, double EdgeParameterT, FEdgeCollapseInfo& CollapseInfo); + virtual EMeshResult CollapseEdge(int KeepVertID, int RemoveVertID, FEdgeCollapseInfo& CollapseInfo) + { + return CollapseEdge(KeepVertID, RemoveVertID, 0, CollapseInfo); + } + + + + /** Information about mesh elements modified by MergeEdges() */ + struct FMergeEdgesInfo + { + int KeptEdge; // the edge that was kept + int RemovedEdge; // the edge that was removed + + FIndex2i KeptVerts; // The two vertices that were kept (redundant w/ KeptEdge?) + FIndex2i RemovedVerts; // The removed vertices of RemovedEdge. Either may be InvalidID if it was same as the paired KeptVert + + FIndex2i ExtraRemovedEdges; // extra removed edges, see description below. Either may be or InvalidID + FIndex2i ExtraKeptEdges; // extra kept edges, paired with ExtraRemovedEdges + }; + + /** + * Given two edges of the mesh, weld both their vertices, so that one edge is removed. + * This could result in one neighbour edge-pair attached to each vertex also collapsing, + * so those cases are detected and handled (eg middle edge-pair in abysmal ascii drawing below) + * + * ._._._. (dots are vertices) + * \._./ + * + * @param KeepEdgeID index of the edge that should be kept + * @param DiscardEdgeID index of the edge that should be removed + * @param MergeInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult MergeEdges(int KeepEdgeID, int DiscardEdgeID, FMergeEdgesInfo& MergeInfo); + + + + /** Information about mesh elements modified/created by PokeTriangle() */ + struct FPokeTriangleInfo + { + int OriginalTriangle; // the triangle that was poked + FIndex3i TriVertices; // vertices of the original triangle + + int NewVertex; // the new vertex that was inserted + FIndex2i NewTriangles; // the two new triangles that were added (OriginalTriangle is re-used, see code for vertex orders) + FIndex3i NewEdges; // the three new edges connected to NewVertex + + FVector3d BaryCoords; // barycentric coords that NewVertex was inserted at + }; + + /** + * Insert a new vertex inside a triangle, ie do a 1 to 3 triangle split + * @param TriangleID index of triangle to poke + * @param BaryCoordinates barycentric coordinates of poke position + * @param PokeInfo returned information about new and modified mesh elements + * @return Ok on success, or enum value indicates why operation cannot be applied. Mesh remains unmodified on error. + */ + virtual EMeshResult PokeTriangle(int TriangleID, const FVector3d& BaryCoordinates, FPokeTriangleInfo& PokeInfo); + + /** Call PokeTriangle at the centroid of the triangle */ + virtual EMeshResult PokeTriangle(int TriangleID, FPokeTriangleInfo& PokeInfo) + { + return PokeTriangle(TriangleID, FVector3d::One() / 3.0, PokeInfo); + } + + // + // Debug utility functions + // +public: + /** + * Returns a debug string that contains mesh statistics and other information + */ + virtual FString MeshInfoString(); + + /** + * Check if another mesh is the same as this mesh. By default only checks + * vertices and triangles, turn on other parameters w/ flags + */ + virtual bool IsSameMesh(const FDynamicMesh3& OtherMesh, bool bCheckConnectivity, bool bCheckEdgeIDs = false, + bool bCheckNormals = false, bool bCheckColors = false, bool bCheckUVs = false, + bool bCheckGroups = false, + float Epsilon = TMathUtil::Epsilon); + + + /** + * Checks that the mesh is well-formed, ie all internal data structures are consistent + */ + virtual bool CheckValidity(bool bAllowNonManifoldVertices = false, EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + // + // Internal functions + // +protected: + + inline void SetTriangleInternal(int TriangleID, int v0, int v1, int v2) + { + int i = 3 * TriangleID; + Triangles[i] = v0; + Triangles[i + 1] = v1; + Triangles[i + 2] = v2; + } + inline void SetTriangleEdgesInternal(int TriangleID, int e0, int e1, int e2) + { + int i = 3 * TriangleID; + TriangleEdges[i] = e0; + TriangleEdges[i + 1] = e1; + TriangleEdges[i + 2] = e2; + } + + int AddEdgeInternal(int vA, int vB, int tA, int tB = InvalidID); + int AddTriangleInternal(int a, int b, int c, int e0, int e1, int e2); + + inline int ReplaceTriangleVertex(int TriangleID, int vOld, int vNew) + { + int i = 3 * TriangleID; + if (Triangles[i] == vOld) + { + Triangles[i] = vNew; + return 0; + } + if (Triangles[i + 1] == vOld) + { + Triangles[i + 1] = vNew; + return 1; + } + if (Triangles[i + 2] == vOld) + { + Triangles[i + 2] = vNew; + return 2; + } + return -1; + } + + inline void AllocateEdgesList(int VertexID) + { + if (VertexID < (int)VertexEdgeLists.Size()) + { + VertexEdgeLists.Clear(VertexID); + } + VertexEdgeLists.AllocateAt(VertexID); + } + + void GetVertexEdgesList(int VertexID, TArray& EdgesOut) const + { + for (int eid : VertexEdgeLists.Values(VertexID)) + { + EdgesOut.Add(eid); + } + } + + inline void SetEdgeVerticesInternal(int EdgeID, int a, int b) + { + int i = 4 * EdgeID; + if (a < b) + { + Edges[i] = a; + Edges[i + 1] = b; + } + else + { + Edges[i] = b; + Edges[i + 1] = a; + } + } + + inline void SetEdgeTrianglesInternal(int EdgeID, int t0, int t1) + { + int i = 4 * EdgeID; + Edges[i + 2] = t0; + Edges[i + 3] = t1; + } + + int ReplaceEdgeVertex(int EdgeID, int vOld, int vNew); + int ReplaceEdgeTriangle(int EdgeID, int tOld, int tNew); + int ReplaceTriangleEdge(int EdgeID, int eOld, int eNew); + + inline bool TriangleHasVertex(int TriangleID, int VertexID) const + { + int i = 3 * TriangleID; + return Triangles[i] == VertexID || Triangles[i + 1] == VertexID || Triangles[i + 2] == VertexID; + } + + inline bool TriHasNeighbourTri(int CheckTriID, int NbrTriID) const + { + int i = 3 * CheckTriID; + return EdgeHasTriangle(TriangleEdges[i], NbrTriID) || EdgeHasTriangle(TriangleEdges[i + 1], NbrTriID) || EdgeHasTriangle(TriangleEdges[i + 2], NbrTriID); + } + + inline bool TriHasSequentialVertices(int TriangleID, int vA, int vB) const + { + int i = 3 * TriangleID; + int v0 = Triangles[i], v1 = Triangles[i + 1], v2 = Triangles[i + 2]; + return ((v0 == vA && v1 == vB) || (v1 == vA && v2 == vB) || (v2 == vA && v0 == vB)); + } + + int FindTriangleEdge(int TriangleID, int vA, int vB) const; + + inline bool EdgeHasVertex(int EdgeID, int VertexID) const + { + int i = 4 * EdgeID; + return (Edges[i] == VertexID) || (Edges[i + 1] == VertexID); + } + inline bool EdgeHasTriangle(int EdgeID, int TriangleID) const + { + int i = 4 * EdgeID; + return (Edges[i + 2] == TriangleID) || (Edges[i + 3] == TriangleID); + } + + inline int GetOtherEdgeVertex(int EdgeID, int VertexID) const + { + int i = 4 * EdgeID; + int ev0 = Edges[i], ev1 = Edges[i + 1]; + return (ev0 == VertexID) ? ev1 : ((ev1 == VertexID) ? ev0 : InvalidID); + } + inline int GetOtherEdgeTriangle(int EdgeID, int TriangleID) const + { + int i = 4 * EdgeID; + int et0 = Edges[i + 2], et1 = Edges[i + 3]; + return (et0 == TriangleID) ? et1 : ((et1 == TriangleID) ? et0 : InvalidID); + } + + inline void AddTriangleEdge(int TriangleID, int v0, int v1, int j, int EdgeID) + { + if (EdgeID != InvalidID) + { + Edges[4 * EdgeID + 3] = TriangleID; + TriangleEdges.InsertAt(EdgeID, 3 * TriangleID + j); + } + else + { + TriangleEdges.InsertAt(AddEdgeInternal(v0, v1, TriangleID), 3 * TriangleID + j); + } + } + + void ReverseTriOrientationInternal(int TriangleID); + + void UpdateTimeStamp(bool bShapeChange, bool bTopologyChange) + { + Timestamp++; + if (bShapeChange) + { + ShapeTimestamp++; + } + if (bTopologyChange) + { + check(bShapeChange); // we consider topology change to be a shape change! + TopologyTimestamp++; + } + } + + + +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAABBTree3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAABBTree3.h new file mode 100644 index 000000000000..2082ca9caa02 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAABBTree3.h @@ -0,0 +1,6 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Spatial/MeshAABBTree3.h" +#include "DynamicMesh3.h" + +typedef TMeshAABBTree3 FDynamicMeshAABBTree3; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAttributeSet.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAttributeSet.h new file mode 100644 index 000000000000..31ba43ec4ddd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshAttributeSet.h @@ -0,0 +1,171 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "DynamicMeshOverlay.h" + +/** Standard UV overlay type - 2-element float */ +typedef TDynamicMeshVectorOverlay FDynamicMeshUVOverlay; +/** Standard Normal overlay type - 3-element float */ +typedef TDynamicMeshVectorOverlay FDynamicMeshNormalOverlay; + + +/** + * FDynamicMeshAttributeSet manages a set of extended attributes for a FDynamicMesh3. + * This includes UV and Normal overlays, etc. + * + * Currently the default is to always have one UV layer and one Normal layer + * + * @todo current internal structure is a work-in-progress + */ +class DYNAMICMESH_API FDynamicMeshAttributeSet +{ +public: + + FDynamicMeshAttributeSet(FDynamicMesh3* Mesh) + : ParentMesh(Mesh), UV0(Mesh), Normals0(Mesh) + { + UVLayers.Add(&UV0); + NormalLayers.Add(&Normals0); + } + + virtual ~FDynamicMeshAttributeSet() + { + } + + void Copy(const FDynamicMeshAttributeSet& Copy) + { + UV0.Copy(Copy.UV0); + Normals0.Copy(Copy.Normals0); + // parent mesh is *not* copied! + } + + /** @return the parent mesh for this overlay */ + const FDynamicMesh3* GetParentMesh() const { return ParentMesh; } + /** @return the parent mesh for this overlay */ + FDynamicMesh3* GetParentMesh() { return ParentMesh; } + + + /** @return number of UV layers */ + virtual int NumUVLayers() const + { + return 1; + } + + /** @return number of Normals layers */ + virtual int NumNormalLayers() const + { + return 1; + } + + /** @return true if the given edge is a seam edge in any overlay */ + virtual bool IsSeamEdge(int EdgeID) const; + + + // + // UV Layers + // + + + /** @return the UV layer at the given Index */ + FDynamicMeshUVOverlay* GetUVLayer(int Index) + { + return (Index == 0) ? &UV0 : nullptr; + } + + /** @return the UV layer at the given Index */ + const FDynamicMeshUVOverlay* GetUVLayer(int Index) const + { + return (Index == 0) ? &UV0 : nullptr; + } + + /** @return list of all UV layers */ + const TArray& GetAllUVLayers() const + { + return UVLayers; + } + + /** @return the primary UV layer (layer 0) */ + FDynamicMeshUVOverlay* PrimaryUV() + { + return &UV0; + } + /** @return the primary UV layer (layer 0) */ + const FDynamicMeshUVOverlay* PrimaryUV() const + { + return &UV0; + } + + + // + // Normal Layers + // + + /** @return the Normal layer at the given Index */ + FDynamicMeshNormalOverlay* GetNormalLayer(int Index) + { + return (Index == 0) ? &Normals0 : nullptr; + } + + /** @return the Normal layer at the given Index */ + const FDynamicMeshNormalOverlay* GetNormalLayer(int Index) const + { + return (Index == 0) ? &Normals0 : nullptr; + } + + /** @return list of all Normal layers */ + const TArray& GetAllNormalLayers() const + { + return NormalLayers; + } + + /** @return the primary Normal layer (layer 0) */ + FDynamicMeshNormalOverlay* PrimaryNormals() + { + return &Normals0; + } + /** @return the primary Normal layer (layer 0) */ + const FDynamicMeshNormalOverlay* PrimaryNormals() const + { + return &Normals0; + } + + +protected: + /** Parent mesh of this attribute set */ + FDynamicMesh3* ParentMesh; + + /** Default UV layer */ + FDynamicMeshUVOverlay UV0; + /** Default Normals layer */ + FDynamicMeshNormalOverlay Normals0; + + TArray UVLayers; + TArray NormalLayers; + + +protected: + friend class FDynamicMesh3; + + /** + * Initialize the existing attribute layers with the given vertex and triangle sizes + */ + void Initialize(int MaxVertexID, int MaxTriangleID) + { + UV0.InitializeTriangles(MaxTriangleID); + Normals0.InitializeTriangles(MaxTriangleID); + } + + // These functions are called by the FDynamicMesh3 to update the various + // attributes when the parent mesh topology has been modified. + virtual void OnNewTriangle(int TriangleID, bool bInserted); + virtual void OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices); + virtual void OnReverseTriOrientation(int TriangleID); + virtual void OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo & splitInfo); + virtual void OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo & flipInfo); + virtual void OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo & collapseInfo); + virtual void OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo & pokeInfo); + virtual void OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo & mergeInfo); +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshEditor.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshEditor.h new file mode 100644 index 000000000000..317c950beb12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshEditor.h @@ -0,0 +1,304 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MeshEditor + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeLoop.h" + + + + +/** + * FMeshIndexMappings stores a set of integer IndexMaps for a mesh + * This is a convenient object to have, to avoid passing around large numbers of separate maps. + * The individual maps are not necessarily all filled by every operation. + */ +struct DYNAMICMESH_API FMeshIndexMappings +{ +protected: + FIndexMapi VertexMap; + FIndexMapi TriangleMap; + FIndexMapi GroupMap; + + TArray UVMaps; + TArray NormalMaps; + +public: + /** Size internal arrays-of-maps to be suitable for this Mesh */ + void Initialize(FDynamicMesh3* Mesh); + + /** @return the value used to indicate "invalid" in the mapping */ + int InvalidID() { return VertexMap.GetInvalidID(); } + + void Reset() + { + VertexMap.Reset(); + TriangleMap.Reset(); + GroupMap.Reset(); + for (FIndexMapi& UVMap : UVMaps) + { + UVMap.Reset(); + } + for (FIndexMapi& NormalMap : NormalMaps) + { + NormalMap.Reset(); + } + } + + FIndexMapi& GetVertexMap() { return VertexMap; } + inline void SetVertex(int FromID, int ToID) { VertexMap.Add(FromID, ToID); } + inline int GetNewVertex(int FromID) const { return VertexMap.GetTo(FromID); } + inline bool ContainsVertex(int FromID) const { return VertexMap.ContainsFrom(FromID); } + + FIndexMapi& GetTriangleMap() { return TriangleMap; } + void SetTriangle(int FromID, int ToID) { TriangleMap.Add(FromID, ToID); } + int GetNewTriangle(int FromID) const { return TriangleMap.GetTo(FromID); } + inline bool ContainsTriangle(int FromID) const { return TriangleMap.ContainsFrom(FromID); } + + FIndexMapi& GetGroupMap() { return GroupMap; } + void SetGroup(int FromID, int ToID) { GroupMap.Add(FromID, ToID); } + int GetNewGroup(int FromID) const { return GroupMap.GetTo(FromID); } + inline bool ContainsGroup(int FromID) const { return GroupMap.ContainsFrom(FromID); } + + FIndexMapi& GetUVMap(int UVLayer) { return UVMaps[UVLayer]; } + void SetUV(int UVLayer, int FromID, int ToID) { UVMaps[UVLayer].Add(FromID, ToID); } + int GetNewUV(int UVLayer, int FromID) const { return UVMaps[UVLayer].GetTo(FromID); } + inline bool ContainsUV(int UVLayer, int FromID) const { return UVMaps[UVLayer].ContainsFrom(FromID); } + + FIndexMapi& GetNormalMap(int NormalLayer) { return NormalMaps[NormalLayer]; } + void SetNormal(int NormalLayer, int FromID, int ToID) { NormalMaps[NormalLayer].Add(FromID, ToID); } + int GetNewNormal(int NormalLayer, int FromID) const { return NormalMaps[NormalLayer].GetTo(FromID); } + inline bool ContainsNormal(int NormalLayer, int FromID) const { return NormalMaps[NormalLayer].ContainsFrom(FromID); } + +}; + + +/** + * FDynamicMeshEditResult is used to return information about new mesh elements + * created by mesh changes, primarily in FDynamicMeshEditor + */ +struct DYNAMICMESH_API FDynamicMeshEditResult +{ + /** New vertices created by an edit */ + TArray NewVertices; + + /** New triangles created by an edit. Note that this list may be empty if the operation created quads or polygons */ + TArray NewTriangles; + /** New quads created by an edit, where each quad is a pair of triangle IDs */ + TArray NewQuads; + /** New polygons created by an edit, where each polygon is a list of triangle IDs */ + TArray> NewPolygons; + + /** New triangle groups created by an edit */ + TArray NewGroups; + + /** clear this data structure */ + void Reset() + { + NewVertices.Reset(); + NewTriangles.Reset(); + NewQuads.Reset(); + NewPolygons.Reset(); + NewGroups.Reset(); + } + + /** Flatten the triangle/quad/polygon lists into a single list of all triangles */ + void GetAllTriangles(TArray& TrianglesOut) const; +}; + + + + +/** + * FDynamicMeshEditor implements low-level mesh editing operations. These operations + * can be used to construct higher-level operations. For example an Extrude operation + * could be implemented via DuplicateTriangles() and StitchLoopMinimal(). + */ +class DYNAMICMESH_API FDynamicMeshEditor +{ +public: + /** The mesh we will be editing */ + FDynamicMesh3* Mesh; + + FDynamicMeshEditor(FDynamicMesh3* MeshIn) + { + Mesh = MeshIn; + } + + + ////////////////////////////////////////////////////////////////////////// + // Create and Remove Triangle Functions + ////////////////////////////////////////////////////////////////////////// + + /** + * Stitch together two loops of vertices with a quad-strip of triangles. + * Loops must be oriented (ordered) correctly for your use case. + * @param Loop1 first loop of sequential vertices + * @param Loop2 second loop of sequential vertices + * @param ResultOut lists of newly created triangles/vertices/etc + * @return true if operation suceeded. If a failure occurs, any added triangles are removed via RemoveTriangles + */ + bool StitchVertexLoopsMinimal(const TArray& VertexLoop1, const TArray& VertexLoop2, FDynamicMeshEditResult& ResultOut); + + + /** + * Duplicate triangles of a mesh. This duplicates the current groups and also any attributes existing on the triangles. + * @param Triangles the triangles to duplicate + * @param IndexMaps returned mappings from old to new triangles/vertices/etc (you may initialize to optimize memory usage, etc) + * @param ResultOut lists of newly created triangles/vertices/etc + */ + void DuplicateTriangles(const TArray& Triangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + + /** + * Pair of associated edge loops. + */ + struct FLoopPairSet + { + FEdgeLoop LoopA; + FEdgeLoop LoopB; + }; + + /** + * Finds boundary loops of connected components of a set of triangles, and duplicates the vertices + * along the boundary, such that the triangles become disconnected. + * @param Triangles set of triangles + * @param LoopSetOut set of boundary loops. LoopA is original loop which remains with "outer" triangles, and LoopB is new boundary loop of triangle set + * @return true on success + */ + bool DisconnectTriangles(const TArray& Triangles, TArray& LoopSetOut); + + + /** + * Remove a list of triangles from the mesh, and optionally any vertices that are now orphaned + * @param Triangles the triangles to remove + * @param bRemoveIsolatedVerts if true, remove vertices that end up with no triangles + * @return true if all removes succeeded + */ + bool RemoveTriangles(const TArray& Triangles, bool bRemoveIsolatedVerts); + + + ////////////////////////////////////////////////////////////////////////// + // Normal utility functions + ////////////////////////////////////////////////////////////////////////// + + /** + * Reverse the orientation of the given triangles, and optionally flip relevant normals + * @param Triangles the triangles to modify + * @param bInvertNormals if ture we call InvertTriangleNormals() + */ + void ReverseTriangleOrientations(const TArray& Triangles, bool bInvertNormals); + + /** + * Flip the normals of the given triangles. This includes their vertex normals, if they + * exist, as well as any per-triangle attribute normals. @todo currently creates full-mesh bit arrays, could be more efficient on subsets + * @param Triangles the triangles to modify + */ + void InvertTriangleNormals(const TArray& Triangles); + + + /** + * Calculate and set the per-triangle normals of the two input quads. + * Average of the two face normals is used unless the quad is planar + * @param QuadTris pair of triangle IDs. If second ID is invalid, it is ignored + * @param bIsPlanar if the quad is known to be planar, operation is more efficient + * @return the normal vector that was set + */ + FVector3f ComputeAndSetQuadNormal(const FIndex2i& QuadTris, bool bIsPlanar = false); + + + /** + * Create and set new shared per-triangle normals for a pair of triangles that share one edge (ie a quad) + * @param QuadTris pair of triangle IDs. If second ID is invalid, it is ignored + * @param Normal normal vector to set + */ + void SetQuadNormals(const FIndex2i& QuadTris, const FVector3f& Normal); + + /** + * Create and set new shared per-triangle normals for a list of triangles + * @param Triangles list of triangle IDs + * @param Normal normal vector to set + */ + void SetTriangleNormals(const TArray& Triangles, const FVector3f& Normal); + + + ////////////////////////////////////////////////////////////////////////// + // UV utility functions + ////////////////////////////////////////////////////////////////////////// + + + /** + * Project the two triangles of the quad onto a plane defined by the normal and use that to create/set new shared per-triangle UVs. + * UVs are translated so that their bbox min-corner is at origin, and scaled by given scale factor + * @param QuadTris pair of triangle IDs. If second ID is invalid, it is ignored + * @param ProjectFrame vertices are projected into XY axes of this frame + * @param UVScaleFactor UVs are scaled + * @param UVLayerIndex which UV layer to operate on (must exist) + */ + void SetQuadUVsFromProjection(const FIndex2i& QuadTris, const FFrame3f& ProjectFrame, float UVScaleFactor = 1.0f, int UVLayerIndex = 0); + + + + ////////////////////////////////////////////////////////////////////////// + // mesh element copying / duplication + ////////////////////////////////////////////////////////////////////////// + + + /** + * Find "new" vertex for input vertex under Index mapping, or create new if missing + * @param VertexID the source vertex we want a copy of + * @param IndexMaps source/destination mapping of already-duplicated vertices + * @param ResultOut newly-created vertices are stored here + * @return index of duplicate vertex + */ + int FindOrCreateDuplicateVertex(int VertexID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + /** + * Find "new" group for input group under Index mapping, or create new if missing + * @param TriangleID the source triangle whose group we want a copy of + * @param IndexMaps source/destination mapping of already-duplicated groups + * @param ResultOut newly-created groups are stored here + * @return index of duplicate group + */ + int FindOrCreateDuplicateGroup(int TriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + /** + * Find "new" UV for input UV element under Index mapping, or create new if missing + * @param ElementID the source UV we want a duplicate of + * @param UVLayerIndex which UV layer to consider + * @param IndexMaps source/destination mapping of already-duplicated UVs + * @return index of duplicate UV in given UV layer + */ + int FindOrCreateDuplicateUV(int ElementID, int UVLayerIndex, FMeshIndexMappings& IndexMaps); + + /** + * Find "new" normal for input normal element under Index mapping, or create new if missing + * @param ElementID the source normal we want a duplicate of + * @param NormalLayerIndex which normal layer to consider + * @param IndexMaps source/destination mapping of already-duplicated normals + * @return index of duplicate normal in given normal layer + */ + int FindOrCreateDuplicateNormal(int ElementID, int NormalLayerIndex, FMeshIndexMappings& IndexMaps); + + + /** + * Copy all attribute-layer values from one triangle to another, using the IndexMaps to track and re-use shared attribute values. + * @param FromTriangleID source triangle + * @param ToTriangleID destination triangle + * @param IndexMaps mappings passed to FindOrCreateDuplicateX functions to track already-created attributes + * @param ResultOut information about new attributes is stored here (@todo populate this, at time of writing there are no attribute fields) + */ + void CopyAttributes(int FromTriangleID, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut); + + + + + void AppendMesh(const FDynamicMesh3* AppendMesh, FMeshIndexMappings& IndexMapsOut, FDynamicMeshEditResult& ResultOut, + TFunction PositionTransform = nullptr, + TFunction NormalTransform = nullptr); + +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshModule.h new file mode 100644 index 000000000000..34ea25d2c6b1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FDynamicMeshModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshOverlay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshOverlay.h new file mode 100644 index 000000000000..b2d5ad9f65a8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/DynamicMeshOverlay.h @@ -0,0 +1,282 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "Util/DynamicVector.h" +#include "Util/RefCountVector.h" +#include "VectorTypes.h" +#include "GeometryTypes.h" +#include "DynamicMesh3.h" + + +/** + * TDynamicMeshOverlay is an add-on to a FDynamicMesh3 that allows for per-triangle storage + * of an "element" (eg like a per-triangle UV or normal). However the elements can be shared + * between triangles because they are stored in a separate indexable list. As a result, + * the overlay has it's own topology, IE there may be "seam" boundary edges in the + * overlay topology that are not mesh boundary edges in the associated/parent mesh. + * + * A "seam" edge is one where at least one of the elements of the triangles on either + * side of the edge is not shared between the two triangles. + * + * The FDynamicMesh3 mesh topology operations (eg split/flip/collapse edge, poke face, etc) + * can be mirrored to the overlay via OnSplitEdge(), etc. + * + * Note that although this is a template, many of the functions are defined in the .cpp file. + * As a result you need to explicitly instantiate and export the instance of the template that + * you wish to use in the block at the top of DynamicMeshOverlay.cpp + */ +template +class TDynamicMeshOverlay +{ + +protected: + /** The parent mesh this overlay belongs to */ + FDynamicMesh3* ParentMesh; + + /** Reference counts of element indices. Iterate over this to find out which elements are valid. */ + FRefCountVector ElementsRefCounts; + /** List of element values */ + TDynamicVector Elements; + /** List of parent vertex indices, one per element */ + TDynamicVector ParentVertices; + + /** List of triangle element-index triplets [Elem0 Elem1 Elem2]*/ + TDynamicVector ElementTriangles; + + friend class FDynamicMesh3; + friend class FDynamicMeshAttributeSet; + +public: + /** Create an empty overlay */ + TDynamicMeshOverlay() + { + ParentMesh = nullptr; + } + + /** Create an overlay for the given parent mesh */ + TDynamicMeshOverlay(FDynamicMesh3* ParentMeshIn) + { + ParentMesh = ParentMeshIn; + } + + /** @return the parent mesh for this overlay */ + const FDynamicMesh3* GetParentMesh() const { return ParentMesh; } + /** @return the parent mesh for this overlay */ + FDynamicMesh3* GetParentMesh() { return ParentMesh; } + + /** Set this overlay to contain the same arrays as the copy overlay */ + void Copy(const TDynamicMeshOverlay& Copy) + { + ElementsRefCounts = FRefCountVector(Copy.ElementsRefCounts); + Elements = Copy.Elements; + ParentVertices = Copy.ParentVertices; + ElementTriangles = Copy.ElementTriangles; + } + + /** Discard current set of elements, but keep triangles */ + void ClearElements(); + + /** @return the number of in-use Elements in the overlay */ + int ElementCount() const { return (int)ElementsRefCounts.GetCount(); } + /** @return the maximum element index in the overlay. This may be larger than the count if Elements have been deleted. */ + int MaxElementID() const { return (int)ElementsRefCounts.GetMaxIndex(); } + /** @return true if this element index is in use */ + inline bool IsElement(int vID) const { return ElementsRefCounts.IsValid(vID); } + + + typedef typename FRefCountVector::IndexEnumerable element_iterator; + + /** @return enumerator for valid element indices suitable for use with range-based for */ + element_iterator ElementIndicesItr() const { return ElementsRefCounts.Indices(); } + + + /** Allocate a new element with the given constant value and parent-mesh vertex */ + int AppendElement(RealType ConstantValue, int ParentVertex); + /** Allocate a new element with the given value and parent-mesh vertex */ + int AppendElement(const RealType* Value, int ParentVertex); + + /** Initialize the triangle list to the given size, and set all triangles to InvalidID */ + void InitializeTriangles(int MaxTriangleID); + /** Set the triangle to the given Element index tuple, and increment element reference counts */ + EMeshResult SetTriangle(int TriangleID, const FIndex3i& TriElements); + + /** Get the element at a given index */ + inline void GetElement(int ElementID, RealType* Data) const + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Data[i] = Elements[k + i]; + } + } + + /** Get the element at a given index */ + template + void GetElement(int ElementID, AsType& Data) const + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Data[i] = Elements[k + i]; + } + } + + + /** Get the parent vertex id for the element at a given index */ + inline int GetParentVertex(int ElementID) const + { + return ParentVertices[ElementID]; + } + + + /** Get the element index tuple for a triangle */ + inline FIndex3i GetTriangle(int TriangleID) const + { + int i = 3 * TriangleID; + return FIndex3i(ElementTriangles[i], ElementTriangles[i + 1], ElementTriangles[i + 2]); + } + + + /** Set the element at a given index */ + inline void SetElement(int ElementID, const RealType* Data) + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Elements[k + i] = Data[i]; + } + } + + /** Set the element at a given index */ + template + void SetElement(int ElementID, const AsType& Data) + { + int k = ElementID * ElementSize; + for (int i = 0; i < ElementSize; ++i) + { + Elements[k + i] = Data[i]; + } + } + + + + /** Returns true if the parent-mesh edge is a "Seam" in this overlay */ + bool IsSeamEdge(int EdgeID) const; + /** Returns true if the parent-mesh vertex is connected to any seam edges */ + bool IsSeamVertex(int VertexID) const; + + /** find the elements associated with a given parent-mesh vertex */ + void GetVertexElements(int VertexID, TArray& OutElements) const; + /** Count the number of unique elements for a given parent-mesh vertex */ + int CountVertexElements(int VertexID, bool bBruteForce = false) const; + + /** + * Checks that the overlay mesh is well-formed, ie all internal data structures are consistent + */ + bool CheckValidity(bool bAllowNonManifoldVertices = true, EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + +public: + /** Set a triangle's element indices to InvalidID */ + void InitializeNewTriangle(int TriangleID); + /** Remove a triangle from the overlay */ + void OnRemoveTriangle(int TriangleID, bool bRemoveIsolatedVertices); + /** Reverse the orientation of a triangle's elements */ + void OnReverseTriOrientation(int TriangleID); + /** Update the overlay to reflect an edge split in the parent mesh */ + void OnSplitEdge(const FDynamicMesh3::FEdgeSplitInfo& SplitInfo); + /** Update the overlay to reflect an edge flip in the parent mesh */ + void OnFlipEdge(const FDynamicMesh3::FEdgeFlipInfo& FlipInfo); + /** Update the overlay to reflect an edge collapse in the parent mesh */ + void OnCollapseEdge(const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo); + /** Update the overlay to reflect a face poke in the parent mesh */ + void OnPokeTriangle(const FDynamicMesh3::FPokeTriangleInfo& PokeInfo); + /** Update the overlay to reflect an edge merge in the parent mesh */ + void OnMergeEdges(const FDynamicMesh3::FMergeEdgesInfo& MergeInfo); + +protected: + /** Set the value at an Element to be a linear interpolation of two other Elements */ + void SetElementFromLerp(int SetElement, int ElementA, int ElementB, RealType Alpha); + /** Set the value at an Element to be a barycentric interpolation of three other Elements */ + void SetElementFromBary(int SetElement, int ElementA, int ElementB, int ElementC, const FVector3& BaryCoords); + + /** updates the triangles array and optionally the element reference counts */ + void InternalSetTriangle(int TriangleID, const FIndex3i& TriElements, bool bIncrementRefCounts); +}; + + + + + + +/** + * TDynamicMeshVectorOverlay is an convenient extension of TDynamicMeshOverlay that adds + * a specific N-element Vector type to the template, and adds accessor functions + * that convert between that N-element vector type and the N-element arrays used by TDynamicMeshOverlay. + */ +template +class TDynamicMeshVectorOverlay : public TDynamicMeshOverlay +{ +public: + using BaseType = TDynamicMeshOverlay; + + TDynamicMeshVectorOverlay() + : TDynamicMeshOverlay() + { + } + + TDynamicMeshVectorOverlay(FDynamicMesh3* parentMesh) + : TDynamicMeshOverlay(parentMesh) + { + } + + /** + * Append a new Element to the overlay + */ + inline int AppendElement(const VectorType& Value, int SourceVertex) + { + return BaseType::AppendElement((const RealType*)Value, SourceVertex); + } + + /** + * Get Element at a specific ID + */ + inline VectorType GetElement(int ElementID) const + { + VectorType V; + BaseType::GetElement(ElementID, V); + return V; + } + + /** + * Get Element at a specific ID + */ + inline void GetElement(int ElementID, VectorType& V) const + { + BaseType::GetElement(ElementID, V); + } + + /** + * Get the three Elements associated with a triangle + */ + inline void GetTriElements(int TriangleID, VectorType& A, VectorType& B, VectorType& C) const + { + int i = 3 * TriangleID; + GetElement(BaseType::ElementTriangles[i], A); + GetElement(BaseType::ElementTriangles[i+1], B); + GetElement(BaseType::ElementTriangles[i+2], C); + } + + + /** + * Set Element at a specific ID + */ + inline void SetElement(int ElementID, const VectorType& Value) + { + BaseType::SetElement(ElementID, Value); + } +}; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeLoop.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeLoop.h new file mode 100644 index 000000000000..ee9f1f3be222 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeLoop.h @@ -0,0 +1,204 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp EdgeLoop + +#pragma once + +#include "DynamicMesh3.h" +#include "Util/IndexUtil.h" + + +/** + * Sequential lists of vertices/edges in a mesh that form a closed loop + */ +class DYNAMICMESH_API FEdgeLoop +{ +public: + /** The Mesh that contains this EdgeLoop */ + const FDynamicMesh3* Mesh; + + /** Ordered list of vertices forming the EdgeLoop */ + TArray Vertices; + /** Ordered list of edges forming the EdgeLoop */ + TArray Edges; + + /** List of bowtie vertices. This list is only valid if bBowtiesCalculated = true. */ + TArray BowtieVertices; + + /** If true, then BowtieVertices list is valid */ + bool bBowtiesCalculated = false; + + FEdgeLoop() + { + Mesh = nullptr; + } + + FEdgeLoop(const FDynamicMesh3* mesh) + { + Mesh = mesh; + } + + /** + * Initialize EdgeLoop with the given vertices and edges + */ + FEdgeLoop(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges) + { + Mesh = mesh; + Vertices = vertices; + Edges = edges; + } + + + /** + * Initialize the loop data + */ + void Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn = nullptr); + + /** + * Construct an FEdgeLoop from a list of edges of the mesh + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const TArray& EdgesIn); + + /** + * Construct an FEdgeLoop from a list of edges of the mesh + * @param MeshIn the mesh the edges exist on + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const FDynamicMesh3* MeshIn, const TArray& EdgesIn) + { + Mesh = MeshIn; + InitializeFromEdges(EdgesIn); + } + + + + /** + * Construct EdgeLoop from list of vertices of mesh + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the loop to be consistent with boundary edges + * @return false if we found any parts of vertices that are not connected by an edge + */ + bool InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient = true); + + /** + * Construct EdgeLoop from list of vertices of mesh + * @param MeshIn Mesh that contains the loop + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the loop to be consistent with boundary edges + * @return false if we found any parts of vertices that are not connected by an edge + */ + bool InitializeFromVertices(const FDynamicMesh3* MeshIn, const TArray& VerticesIn, bool bAutoOrient = true) + { + Mesh = MeshIn; + return InitializeFromVertices(VerticesIn, bAutoOrient); + } + + + + + /** Set the bowtie vertices */ + void SetBowtieVertices(const TArray& Bowties) + { + BowtieVertices = Bowties; + bBowtiesCalculated = true; + } + + + /** + * Populate the BowtieVertices member + */ + void CalculateBowtieVertices(); + + /** + * @return number of vertices in the loop + */ + int GetVertexCount() const + { + return Vertices.Num(); + } + + /** + * @return number of edges in the loop + */ + int GetEdgeCount() const + { + return Edges.Num(); + } + + /** + * @return vertex position in loop at the given LoopIndex + */ + inline FVector3d GetVertex(int LoopIndex) const + { + return Mesh->GetVertex(Vertices[LoopIndex]); + } + + /** + * @return bounding box of the vertices of the EdgeLoop + */ + FAxisAlignedBox3d GetBounds() const; + + + /** + * If any edges if the loop are on a mesh boundary, we can check that the loop is + * oriented such that it corresponds with the boundary edges, and if not, reverse it. + * @return true if the loop was reversed + */ + bool SetCorrectOrientation(); + + + /** + * Reverse order of vertices and edges in loop + */ + void Reverse() + { + Algo::Reverse(Vertices); + Algo::Reverse(Edges); + } + + + /** + * @return true if all edges of this loop are internal edges, ie not on the mesh boundary + */ + bool IsInternalLoop() const; + + + /** + * @param TestMesh use this mesh instead of the internal Mesh pointer + * @return true if all edges of this loop are boundary edges + */ + bool IsBoundaryLoop(const FDynamicMesh3* TestMesh = nullptr) const; + + + /** + * @return index of VertexID in the Vertices list, or -1 if not found + */ + int FindVertexIndex(int VertexID) const; + + + /** + * @return index of vertex in the Vertices list that is closest to QueryPoint + */ + int FindNearestVertexIndex(const FVector3d& QueryPoint) const; + + + + /** + * Exhaustively check that verts and edges of this EdgeLoop are consistent. This is quite expensive. + * @return true if loop is consistent/valid + */ + bool CheckValidity(EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + + + + /** + * Utility function to convert a vertex loop to an edge loop + * @param Mesh mesh to operate on + * @param VertexLoop ordered list of vertices + * @param OutEdgeLoop computed list of sequential connected vertices + */ + static void VertexLoopToEdgeLoop(const FDynamicMesh3* Mesh, const TArray& VertexLoop, TArray& OutEdgeLoop); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeSpan.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeSpan.h new file mode 100644 index 000000000000..c4eafb723088 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/EdgeSpan.h @@ -0,0 +1,207 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp EdgeSpan + +#pragma once + +#include "DynamicMesh3.h" +#include "Util/IndexUtil.h" +#include "Polyline3.h" + + +/** + * Sequential lists of vertices/edges in a mesh that is *not* closed. + * If the list is closed it should be an FEdgeLoop + */ +class DYNAMICMESH_API FEdgeSpan +{ +public: + /** The Mesh that contains this EdgeSpan */ + const FDynamicMesh3* Mesh; + + /** Ordered list of vertices forming the EdgeSpan */ + TArray Vertices; + /** Ordered list of edges forming the EdgeSpan */ + TArray Edges; + + /** List of bowtie vertices. This list is only valid if bBowtiesCalculated = true. */ + TArray BowtieVertices; + + /** If true, then BowtieVertices list is valid */ + bool bBowtiesCalculated = false; + + FEdgeSpan() + { + Mesh = nullptr; + } + + FEdgeSpan(const FDynamicMesh3* mesh) + { + Mesh = mesh; + } + + /** + * Initialize EdgeSpan with the given vertices and edges + */ + FEdgeSpan(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges) + { + Mesh = mesh; + Vertices = vertices; + Edges = edges; + } + + + /** + * Initialize the loop data + */ + void Initialize(const FDynamicMesh3* mesh, const TArray& vertices, const TArray & edges, const TArray* BowtieVerticesIn = nullptr); + + + /** + * Construct an FEdgeSpan from a list of edges of the mesh + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const TArray& EdgesIn); + + + /** + * Construct an FEdgeSpan from a list of edges of the mesh + * @param MeshIn the mesh the edges exist on + * @param EdgesIn list of sequential connected edges + */ + void InitializeFromEdges(const FDynamicMesh3* MeshIn, const TArray& EdgesIn) + { + Mesh = MeshIn; + InitializeFromEdges(EdgesIn); + } + + + /** + * Construct EdgeSpan from list of vertices of mesh + * @param MeshIn Mesh that contains the span + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the span to be consistent with boundary edges + * @return false if we found any parts of VerticesIn that are not connected by an edge + */ + bool InitializeFromVertices(const FDynamicMesh3* MeshIn, const TArray& VerticesIn, bool bAutoOrient = true) + { + Mesh = MeshIn; + return InitializeFromVertices(VerticesIn, bAutoOrient); + } + + + /** + * Construct EdgeSpan from list of vertices of mesh + * @param VerticesIn list of vertices that are sequentially connected by edges + * @param bAutoOrient if true, and any of the edges are boundary edges, we will re-orient the span to be consistent with boundary edges + * @return false if we found any parts of VerticesIn that are not connected by an edge + */ + bool InitializeFromVertices(const TArray& VerticesIn, bool bAutoOrient = true); + + + /** Set the bowtie vertices */ + void SetBowtieVertices(const TArray& Bowties) + { + BowtieVertices = Bowties; + bBowtiesCalculated = true; + } + + + /** + * Populate the BowtieVertices member + */ + void CalculateBowtieVertices(); + + + /** + * @return number of vertices in the span + */ + int GetVertexCount() const + { + return Vertices.Num(); + } + + /** + * @return number of edges in the span + */ + int GetEdgeCount() const + { + return Edges.Num(); + } + + /** + * @return vertex position in span at the given SpanIndex + */ + FVector3d GetVertex(int SpanIndex) const + { + return Mesh->GetVertex(Vertices[SpanIndex]); + } + + /** + * @return bounding box of the vertices of the EdgeSpan + */ + FAxisAlignedBox3d GetBounds() const; + + + /** + * Extract Polyline from Mesh based on vertex list + */ + void GetPolyline(FPolyline3d& PolylineOut) const; + + /** + * If any edges if the span are on a mesh boundary, we can check that the span is + * oriented such that it corresponds with the boundary edges, and if not, reverse it. + * @return true if the span was reversed + */ + bool SetCorrectOrientation(); + + + /** + * Reverse order of vertices and edges in span + */ + void Reverse() + { + Algo::Reverse(Vertices); + Algo::Reverse(Edges); + } + + + /** + * @return true if all edges of this span are internal edges, ie not on the mesh boundary + */ + bool IsInternalspan() const; + + /** + * @param TestMesh use this mesh instead of the internal Mesh pointer + * @return true if all edges of this span are boundary edges + */ + bool IsBoundaryspan(const FDynamicMesh3* TestMesh = nullptr) const; + + /** + * @return index of VertexID in the Vertices list, or -1 if not found + */ + int FindVertexIndex(int VertexID) const; + + /** + * @return index of vertex in the Vertices list that is closest to QueryPoint + */ + int FindNearestVertexIndex(const FVector3d& QueryPoint) const; + + + /** + * Exhaustively check that verts and edges of this EdgeSpan are consistent. This is quite expensive. + * @return true if span is consistent/valid + */ + bool CheckValidity(EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const; + + + + /** + * Utility function to convert a vertex span to an edge span + * @param Mesh mesh to operate on + * @param Vertexspan ordered list of vertices + * @param OutEdgeSpan computed list of sequential connected vertices + */ + static void VertexSpanToEdgeSpan(const FDynamicMesh3* Mesh, const TArray& VertexSpan, TArray& OutEdgeSpan); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MeshShapeGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MeshShapeGenerator.h new file mode 100644 index 000000000000..c4fb685b074a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MeshShapeGenerator.h @@ -0,0 +1,253 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "IndexTypes.h" + +/** + * Base class for triangle mesh generators (eg like to generate sphere, cylinder, etc) + * Subclasses must implement ::Generate() + */ +class DYNAMICMESH_API FMeshShapeGenerator +{ +public: + /** Array of vertex positions */ + TArray Vertices; + + /** Array of UV positions. These are completely independent of vertex list (ie not per-vertex unless that's what generator produces) */ + TArray UVs; + /** Parent vertex index for each UV. Same length as UVs array. */ + TArray UVParentVertex; + + /** Array of Normals. These are completely independent of vertex list (ie not per-vertex unless that's what generator produces) */ + TArray Normals; + /** Parent vertex index for each Normal. Same length as Normals array. */ + TArray NormalParentVertex; + + /** Array of triangle corner positions, stored as tuples of indices into Vertices array */ + TArray Triangles; + /** Array of triangle corner UVs, stored as tuples of indices into UVs array. Same length as Triangles array. */ + TArray TriangleUVs; + /** Array of triangle corner Normals, stored as tuples of indices into Normals array. Same length as Triangles array. */ + TArray TriangleNormals; + /** ARray of per-triangle integer polygon IDs. Same length as Triangles array. */ + TArray TrianglePolygonIDs; + + +public: + /** If true, reverse orientation of created mesh */ + bool bReverseOrientation = false; + + virtual ~FMeshShapeGenerator() + { + } + + + /** + * Subclasses implement this to generate mesh + */ + virtual FMeshShapeGenerator& Generate() = 0; + + + /** clear arrays so that Generate() can be run again */ + void Reset() + { + Vertices.Reset(); + UVs.Reset(); + UVParentVertex.Reset(); + Normals.Reset(); + NormalParentVertex.Reset(); + Triangles.Reset(); + TriangleUVs.Reset(); + TriangleNormals.Reset(); + TrianglePolygonIDs.Reset(); + } + + + /** + * Set the various internal buffers to the correct sizes for the given element counts + */ + void SetBufferSizes(int NumVertices, int NumTriangles, int NumUVs, int NumNormals) + { + if (NumVertices > 0) + { + Vertices.SetNum(NumVertices); + } + if (NumTriangles > 0) + { + Triangles.SetNum(NumTriangles); + TriangleUVs.SetNum(NumTriangles); + TriangleNormals.SetNum(NumTriangles); + TrianglePolygonIDs.SetNum(NumTriangles); + } + if (NumUVs > 0) + { + UVs.SetNum(NumUVs); + UVParentVertex.SetNum(NumUVs); + } + if (NumNormals > 0) + { + Normals.SetNum(NumNormals); + NormalParentVertex.SetNum(NumNormals); + } + } + /** + * Extends the various internal buffers to the correct sizes for the given additional element counts + */ + void ExtendBufferSizes(int AddVertices, int AddTriangles, int AddUVs, int AddNormals) + { + if (AddVertices > 0) + { + int NumVertices = Vertices.Num() + AddVertices; + Vertices.SetNum(NumVertices); + } + if (AddTriangles > 0) + { + int NumTriangles = Triangles.Num() + AddTriangles; + Triangles.SetNum(NumTriangles); + TriangleUVs.SetNum(NumTriangles); + TriangleNormals.SetNum(NumTriangles); + TrianglePolygonIDs.SetNum(NumTriangles); + } + if (AddUVs > 0) + { + int NumUVs = UVs.Num() + AddUVs; + UVs.SetNum(NumUVs); + UVParentVertex.SetNum(NumUVs); + } + if (AddNormals > 0) + { + int NumNormals = Normals.Num() + AddNormals; + Normals.SetNum(NumNormals); + NormalParentVertex.SetNum(NumNormals); + } + } + + + /** Set vertex at Index to given Position */ + inline void SetVertex(int Index, const FVector3d& Position) + { + Vertices[Index] = Position; + } + /** Append a new vertex at the given Position */ + inline int AppendVertex(const FVector3d& Position) + { + Vertices.Add(Position); + return Vertices.Num()-1; + } + + /** Set UV at Index to given value with given ParentVertex */ + inline void SetUV(int Index, const FVector2f& UV, int ParentVertex) + { + UVs[Index] = UV; + UVParentVertex[Index] = ParentVertex; + } + inline int AppendUV(const FVector2f& UV, int ParentVertex) + { + UVs.Add(UV); + UVParentVertex.Add(ParentVertex); + return UVs.Num() - 1; + } + + /** Set Normal at Index to given value with given ParentVertex */ + inline void SetNormal(int Index, const FVector3f& Normal, int ParentVertex) + { + Normals[Index] = Normal; + NormalParentVertex[Index] = ParentVertex; + } + inline int AppendNormal(const FVector3f& Normal, int ParentVertex) + { + Normals.Add(Normal); + NormalParentVertex.Add(ParentVertex); + return Normals.Num() - 1; + } + + + inline void SetTriangle(int Index, const FIndex3i& Tri) + { + Triangles[Index] = bReverseOrientation ? FIndex3i(Tri.C, Tri.B, Tri.A) : Tri; + } + inline void SetTriangle(int Index, int A, int B, int C) + { + Triangles[Index] = bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + inline void SetTriangle(int Index, int A, int B, int C, bool bClockwiseOverride) + { + Triangles[Index] = (bReverseOrientation != bClockwiseOverride) ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + + + inline int AppendTriangle(int Index, int A, int B, int C) + { + Triangles.Add( bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C) ); + return Triangles.Num() - 1; + } + + + inline void SetTriangleUVs(int Index, const FIndex3i& Tri) + { + TriangleUVs[Index] = bReverseOrientation ? FIndex3i(Tri.C, Tri.B, Tri.A) : Tri; + } + inline void SetTriangleUVs(int Index, int A, int B, int C) + { + TriangleUVs[Index] = bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + inline void SetTriangleUVs(int Index, int A, int B, int C, bool bClockwiseOverride) + { + TriangleUVs[Index] = (bReverseOrientation != bClockwiseOverride) ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + + + inline void SetTriangleNormals(int Index, const FIndex3i& Tri) + { + TriangleNormals[Index] = bReverseOrientation ? FIndex3i(Tri.C, Tri.B, Tri.A) : Tri; + } + inline void SetTriangleNormals(int Index, int A, int B, int C) + { + TriangleNormals[Index] = bReverseOrientation ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + inline void SetTriangleNormals(int Index, int A, int B, int C, bool bClockwiseOverride) + { + TriangleNormals[Index] = (bReverseOrientation != bClockwiseOverride) ? FIndex3i(C, B, A) : FIndex3i(A, B, C); + } + + inline void SetTrianglePolygon(int Index, int PolygonID) + { + TrianglePolygonIDs[Index] = PolygonID; + } + + + + + static FVector3d BilinearInterp(const FVector3d &v00, const FVector3d &v10, const FVector3d &v11, const FVector3d &v01, double tx, double ty) + { + FVector3d a = FVector3d::Lerp(v00, v01, ty); + FVector3d b = FVector3d::Lerp(v10, v11, ty); + return FVector3d::Lerp(a, b, tx); + } + + static FVector2d BilinearInterp(const FVector2d &v00, const FVector2d &v10, const FVector2d &v11, const FVector2d &v01, double tx, double ty) + { + FVector2d a = FVector2d::Lerp(v00, v01, ty); + FVector2d b = FVector2d::Lerp(v10, v11, ty); + return FVector2d::Lerp(a, b, tx); + } + + static FVector2f BilinearInterp(const FVector2f &v00, const FVector2f &v10, const FVector2f &v11, const FVector2f &v01, float tx, float ty) + { + FVector2f a = FVector2f::Lerp(v00, v01, ty); + FVector2f b = FVector2f::Lerp(v10, v11, ty); + return FVector2f::Lerp(a, b, tx); + } + + + static FVector3i LinearInterp(const FVector3i &a, const FVector3i &b, double t) + { + FVector3d c = FVector3d::Lerp((FVector3d)a, (FVector3d)b, t); + return FVector3i((int)round(c.X), (int)round(c.Y), (int)round(c.Z)); + } + + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MinimalBoxMeshGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MinimalBoxMeshGenerator.h new file mode 100644 index 000000000000..713e398b59ff --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/MinimalBoxMeshGenerator.h @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" +#include "OrientedBoxTypes.h" +#include "Util/IndexUtil.h" + +/** + * Generate an oriented Box mesh with the smallest number of triangles possible (6 vertices, 12 triangles) + */ +class /*DYNAMICMESH_API*/ FMinimalBoxMeshGenerator : public FMeshShapeGenerator +{ +public: + /** 3D box */ + FOrientedBox3d Box; + + /** If true (default), UVs are scaled so that there is no stretching. If false, UVs are scaled to fill unit square */ + bool bScaleUVByAspectRatio = true; + +public: + + /** Generate the mesh */ + virtual FMeshShapeGenerator& Generate() override + { + // box has 8 corners, 6 quad-faces, each face has 2 triangles and 4 attribute-corners + SetBufferSizes(8, 12, 24, 24); + + + for (int i = 0; i < 8; ++i) + { + Vertices[i] = Box.GetCorner(i); + } + + double MaxDimension = 2.0*Box.Extents.MaxAbs(); + float UVScale = (bScaleUVByAspectRatio) ? (1.0f / (float)MaxDimension) : 1.0f; + + int TriIndex = 0; + int AttribIndex = 0; + for (int fi = 0; fi < 6; ++fi) + { + int NormalAxisIdx = IndexUtil::BoxFaceNormals[fi]; + FVector3f FaceNormal = (FVector3f)(FMath::Sign(NormalAxisIdx) * Box.GetAxis(FMath::Abs(NormalAxisIdx) - 1)); + + double FaceWidth = Vertices[IndexUtil::BoxFaces[fi][0]].Distance(Vertices[IndexUtil::BoxFaces[fi][1]]); + double FaceHeight = Vertices[IndexUtil::BoxFaces[fi][1]].Distance(Vertices[IndexUtil::BoxFaces[fi][2]]); + double WidthUVScale = FaceWidth * UVScale; + double HeightUVScale = FaceHeight * UVScale; + + int ElementIndices[4]; + + for (int j = 0; j < 4; ++j) + { + Normals[AttribIndex] = FaceNormal; + FVector2i CornerUV = IndexUtil::BoxFacesUV[j]; + UVs[AttribIndex] = FVector2f(WidthUVScale*(float)CornerUV.X, HeightUVScale*(float)CornerUV.Y); + ElementIndices[j] = AttribIndex; + AttribIndex++; + } + + + SetTriangle(TriIndex, + IndexUtil::BoxFaces[fi][0], IndexUtil::BoxFaces[fi][1], IndexUtil::BoxFaces[fi][2]); + SetTrianglePolygon(TriIndex, fi); + SetTriangleUVs(TriIndex, ElementIndices[0], ElementIndices[1], ElementIndices[2]); + SetTriangleNormals(TriIndex, ElementIndices[0], ElementIndices[1], ElementIndices[2]); + TriIndex++; + + SetTriangle(TriIndex, + IndexUtil::BoxFaces[fi][0], IndexUtil::BoxFaces[fi][2], IndexUtil::BoxFaces[fi][3]); + SetTrianglePolygon(TriIndex, fi); + SetTriangleUVs(TriIndex, ElementIndices[0], ElementIndices[2], ElementIndices[3]); + SetTriangleNormals(TriIndex, ElementIndices[0], ElementIndices[2], ElementIndices[3]); + TriIndex++; + + + } + + return *this; + } + + + + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/PlanarPolygonMeshGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/PlanarPolygonMeshGenerator.h new file mode 100644 index 000000000000..3d5f2b05aa12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/PlanarPolygonMeshGenerator.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" +#include "Polygon2.h" + +/** + * Generate planar triangulation of a Polygon. + */ +class DYNAMICMESH_API FPlanarPolygonMeshGenerator : public FMeshShapeGenerator +{ +public: + /** Polygon to triangulate. If Polygon has self-intersections or degenerate edges, result is undefined. */ + FPolygon2d Polygon; + + /** Normal vector of all vertices will be set to this value. Default is +Z axis. */ + FVector3f Normal; + + /** How to map 2D indices to 3D. Default is (0,1) = (x,y,0). */ + FIndex2i IndicesMap; + +public: + FPlanarPolygonMeshGenerator(); + + /** Initialize the polygon from an array of 2D vertices */ + void SetPolygon(const TArray& PolygonVerts); + + /** Generate the triangulation */ + virtual FMeshShapeGenerator& Generate() override; + + + /** Create vertex at position under IndicesMap, shifted to Origin*/ + inline FVector3d MakeVertex(double x, double y) + { + FVector3d v(0, 0, 0); + v[IndicesMap.A] = x; + v[IndicesMap.B] = y; + return v; + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/RectangleMeshGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/RectangleMeshGenerator.h new file mode 100644 index 000000000000..c981ffe70af5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/RectangleMeshGenerator.h @@ -0,0 +1,52 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshShapeGenerator.h" + +/** + * Generate planar rectangular mesh with variable number of subdivisions along width and height. + * By default, center of rectangle is centered at (0,0,0) origin + */ +class DYNAMICMESH_API FRectangleMeshGenerator : FMeshShapeGenerator +{ +public: + /** Rectangle will be translated so that center is at this point */ + FVector3d Origin; + /** Normal vector of all vertices will be set to this value. Default is +Z axis. */ + FVector3f Normal; + + /** Width of rectangle */ + double Width; + /** Height of rectangle */ + double Height; + + /** Number of vertices along Width axis */ + int WidthVertexCount; + /** Number of vertices along Height axis */ + int HeightVertexCount; + + /** If true (default), UVs are scaled so that there is no stretching. If false, UVs are scaled to fill unit square */ + bool bScaleUVByAspectRatio = true; + + /** Specifices how 2D indices are mapped to 3D points. Default is (0,1) = (x,y,0). */ + FIndex2i IndicesMap; + +public: + FRectangleMeshGenerator(); + + /** Generate the mesh */ + virtual FMeshShapeGenerator& Generate() override; + + /** Create vertex at position under IndicesMap, shifted to Origin*/ + virtual FVector3d MakeVertex(double x, double y) + { + FVector3d v(0, 0, 0); + v[IndicesMap.A] = x; + v[IndicesMap.B] = y; + return Origin + v; + } + + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/SweepGenerator.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/SweepGenerator.h new file mode 100644 index 000000000000..48057afe8d70 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Generators/SweepGenerator.h @@ -0,0 +1,286 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp CylinderGenerator + +#pragma once + +#include "Math/UnrealMathUtility.h" +#include "MeshShapeGenerator.h" +#include "Misc/AssertionMacros.h" + +#include "CompGeom/PolygonTriangulation.h" +#include "FrameTypes.h" +#include "MatrixTypes.h" +#include "Polygon2.h" + +/** + * ECapType indicates the type of cap to use on a sweep + */ +enum class /*DYNAMICMESH_API*/ ECapType +{ + None = 0, + FlatTriangulation = 1 + // TODO: Cone, other caps ... +}; + +class /*DYNAMICMESH_API*/ FSweepGeneratorBase : public FMeshShapeGenerator +{ +public: + virtual ~FSweepGeneratorBase() + { + } + +protected: + int32 CapVertStart[2], CapNormalStart[2], CapUVStart[2], CapTriangleStart[2], CapPolygonStart[2]; + + /** + * Shared logic for creating vertex buffers and triangulations across all sweep primitives + * Note: Does not set vertex positions or normals; a separate call must do that. + */ + void ConstructMeshTopology(const FPolygon2d& CrossSection, + const TArrayView& UVSections, + const TArrayView& NormalSections, + int32 NumCrossSections, + const ECapType Caps[2], + FVector2d UVScale, FVector2d UVOffset) + { + // per cross section + int32 XVerts = CrossSection.VertexCount(); + int32 XNormals = XVerts + NormalSections.Num(); + int32 XUVs = XVerts + UVSections.Num() + 1; + + int32 NumVerts = XVerts * NumCrossSections; + int32 NumNormals = NumCrossSections > 1 ? XNormals * NumCrossSections : 0; + int32 NumUVs = NumCrossSections > 1 ? XUVs * NumCrossSections : 0; + int32 NumPolygons = (NumCrossSections - 1) * XVerts; + int32 NumTriangles = NumPolygons * 2; + + TArray OutTriangles; + + for (int32 CapIdx = 0; CapIdx < 2; CapIdx++) + { + CapVertStart[CapIdx] = NumVerts; + CapNormalStart[CapIdx] = NumNormals; + CapUVStart[CapIdx] = NumUVs; + CapTriangleStart[CapIdx] = NumTriangles; + CapPolygonStart[CapIdx] = NumPolygons; + + if (Caps[CapIdx] == ECapType::FlatTriangulation) + { + NumTriangles += XVerts - 2; + NumPolygons++; + NumUVs += XVerts; + NumNormals += XVerts; + } + + // TODO: support more cap type; e.g.: + //else if (Caps[CapIdx] == ECapType::FlatMidpointFan) + //{ + // NumTriangles += XVerts; + // NumPolygons += XVerts; + // NumUVs += XVerts + 1; + // NumNormals += XVerts + 1; + // NumVerts += 1; + //} + //else if (Caps[CapIdx] == ECapType::Cone) + //{ + // NumTriangles += XVerts; + // NumPolygons += XVerts; + // NumUVs += XVerts + 1; + // NumNormals += XVerts * 2; + // NumVerts += 1; + //} + } + + SetBufferSizes(NumVerts, NumTriangles, NumUVs, NumNormals); + + for (int32 CapIdx = 0; CapIdx < 2; CapIdx++) + { + + if (Caps[CapIdx] == ECapType::FlatTriangulation) + { + int32 VertOffset = CapIdx * (XVerts * (NumCrossSections - 1)); + + PolygonTriangulation::TriangulateSimplePolygon(CrossSection.GetVertices(), OutTriangles); + int32 TriIdx = CapTriangleStart[CapIdx]; + int32 PolyIdx = CapPolygonStart[CapIdx]; + for (const FIndex3i& Triangle : OutTriangles) + { + bool Flipped = CapIdx == 0; + Flipped = !Flipped; + SetTriangle(TriIdx, + Triangle.A + VertOffset, Triangle.B + VertOffset, Triangle.C + VertOffset, + Flipped); + SetTriangleUVs(TriIdx, + Triangle.A + CapUVStart[CapIdx], + Triangle.B + CapUVStart[CapIdx], + Triangle.C + CapUVStart[CapIdx], + Flipped); + SetTriangleNormals(TriIdx, + Triangle.A + CapNormalStart[CapIdx], + Triangle.B + CapNormalStart[CapIdx], + Triangle.C + CapNormalStart[CapIdx], + Flipped); + SetTrianglePolygon(TriIdx, PolyIdx); + TriIdx++; + } + for (int32 Idx = 0; Idx < XVerts; Idx++) + { + FVector2d CenteredVert = CrossSection.GetVertices()[Idx] * UVScale + UVOffset; + SetUV(CapUVStart[CapIdx] + Idx, FVector2f(CenteredVert.X, CenteredVert.Y), VertOffset + Idx); + SetNormal(CapNormalStart[CapIdx] + Idx, FVector3f::Zero(), VertOffset + Idx); + } + } + } + + // fill in UVs and triangles along length + if (NumCrossSections > 1) + { + int32 UVSection = 0, UVSubIdx = 0; + + int32 NumSections = UVSections.Num(); + int32 NextDupVertIdx = UVSection < NumSections ? UVSections[UVSection] : -1; + for (int32 VertSubIdx = 0; VertSubIdx < XVerts; UVSubIdx++) + { + float UVX = VertSubIdx / float(XVerts); + for (int32 XIdx = 0; XIdx < NumCrossSections; XIdx++) + { + float UVY = XIdx / float(NumCrossSections - 1); + SetUV(XIdx * XUVs + UVSubIdx, FVector2f(UVX, UVY), XIdx * XVerts + VertSubIdx); + } + + if (VertSubIdx == NextDupVertIdx) + { + NextDupVertIdx = UVSection < NumSections ? UVSections[UVSection] : -1; + } + else + { + for (int32 XIdx = 0; XIdx + 1 < NumCrossSections; XIdx++) + { + SetTriangleUVs( + XVerts * 2 * XIdx + 2 * VertSubIdx, + XIdx * XUVs + UVSubIdx, + XIdx * XUVs + UVSubIdx + 1, + (XIdx + 1) * XUVs + UVSubIdx, true); + SetTriangleUVs( + XVerts * 2 * XIdx + 2 * VertSubIdx + 1, + (XIdx + 1) * XUVs + UVSubIdx + 1, + (XIdx + 1) * XUVs + UVSubIdx, + XIdx * XUVs + UVSubIdx + 1, true); + } + VertSubIdx++; + } + } + { + // final UV + float UVX = 1.0f; + int32 VertSubIdx = 0; + for (int32 XIdx = 0; XIdx < NumCrossSections; XIdx++) + { + float UVY = XIdx / float(NumCrossSections - 1); + SetUV(XIdx * XUVs + UVSubIdx, FVector2f(UVX, UVY), XIdx * XVerts + VertSubIdx); + } + } + NumSections = NormalSections.Num(); + int32 NormalSection = 0; + NextDupVertIdx = NormalSection < NumSections ? NormalSections[NormalSection] : -1; + check(NextDupVertIdx < XVerts); + for (int32 VertSubIdx = 0, NormalSubIdx = 0; VertSubIdx < XVerts; NormalSubIdx++) + { + for (int32 XIdx = 0; XIdx < NumCrossSections; XIdx++) + { + // just set the normal parent; don't compute normal yet + SetNormal(XIdx * XNormals + NormalSubIdx, FVector3f(0, 0, 0), XIdx * XVerts + VertSubIdx); + } + + if (VertSubIdx == NextDupVertIdx) + { + NextDupVertIdx = NormalSection < NumSections ? NormalSections[NormalSection] : -1; + check(NextDupVertIdx < XVerts); + } + else + { + int32 WrappedNextNormalSubIdx = (NormalSubIdx + 1) % XNormals; + int32 WrappedNextVertexSubIdx = (VertSubIdx + 1) % XVerts; + for (int32 XIdx = 0; XIdx + 1 < NumCrossSections; XIdx++) + { + int32 T0Idx = XVerts * 2 * XIdx + 2 * VertSubIdx; + int32 T1Idx = T0Idx + 1; + int32 PIdx = XVerts * XIdx + VertSubIdx; + SetTrianglePolygon(T0Idx, PIdx); + SetTrianglePolygon(T1Idx, PIdx); + SetTriangle(T0Idx, + XIdx * XVerts + VertSubIdx, + XIdx * XVerts + WrappedNextVertexSubIdx, + (XIdx + 1) * XVerts + VertSubIdx, true); + SetTriangle(T1Idx, + (XIdx + 1) * XVerts + WrappedNextVertexSubIdx, + (XIdx + 1) * XVerts + VertSubIdx, + XIdx * XVerts + WrappedNextVertexSubIdx, true); + SetTriangleNormals( + T0Idx, + XIdx * XNormals + NormalSubIdx, + XIdx * XNormals + WrappedNextNormalSubIdx, + (XIdx + 1) * XNormals + NormalSubIdx, true); + SetTriangleNormals( + T1Idx, + (XIdx + 1) * XNormals + WrappedNextNormalSubIdx, + (XIdx + 1) * XNormals + NormalSubIdx, + XIdx * XNormals + WrappedNextNormalSubIdx, true); + } + VertSubIdx++; + } + } + } + } +}; + +/** + * Generate a cylinder with optional end caps + */ +class /*DYNAMICMESH_API*/ FCylinderGenerator : public FSweepGeneratorBase +{ +public: + float Radius[2] = {1.0f, 1.0f}; + float Height = 1.0f; + int AngleSamples = 16; + bool bCapped = false; + +public: + /** Generate the mesh */ + virtual FMeshShapeGenerator& Generate() override + { + FPolygon2d X = FPolygon2d::MakeCircle(1.0, AngleSamples); + const TArray& XVerts = X.GetVertices(); + ECapType Caps[2] = {ECapType::None, ECapType::None}; + + if (bCapped) + { + Caps[0] = ECapType::FlatTriangulation; + Caps[1] = ECapType::FlatTriangulation; + } + ConstructMeshTopology(X, {}, {}, 2, Caps, FVector2d(1, 1), FVector2d(0, 0)); + + for (int SubIdx = 0; SubIdx < X.VertexCount(); SubIdx++) + { + for (int XIdx = 0; XIdx < 2; ++XIdx) + { + Vertices[SubIdx + XIdx * AngleSamples] = + FVector3d(XVerts[SubIdx].X * Radius[XIdx], XVerts[SubIdx].Y * Radius[XIdx], XIdx * Height); + Normals[SubIdx + XIdx * AngleSamples] = FVector3f(XVerts[SubIdx].X, XVerts[SubIdx].Y, 0); + + if (bCapped) + { + Normals[CapNormalStart[XIdx] + SubIdx] = FVector3f(0, 0, 2 * XIdx - 1); + } + } + } + + for (int k = 0; k < Normals.Num(); ++k) + { + Normals[k].Normalize(); + } + + return *this; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h new file mode 100644 index 000000000000..0ada8383e864 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/GroupTopology.h @@ -0,0 +1,191 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeSpan.h" +#include "Util/IndexUtil.h" +#include "Containers/BitArray.h" + + +/** + * Given a per-triangle integer ("group"), FGroupTopology extracts a + * group-level topological graph from an input Mesh. The graph consists + * of three elements: + * Corners: there is a corner at each vertex where 3 or more groups meet (ie 3 or more groups in one-ring) + * Edges: a group edge is a list of one or more connected edges that have the same pair of different groups on either side + * Group: a group is a set of connected faces with the same GroupID + * + * By default, the triangle group attribute of the input Mesh is used. + * You can override GetGroupID to provide your own grouping. + * + * Various query functions are provided to allow group topology to be interrogated. + * Note that these functions refer to "GroupID", "CornerID", and "GroupEdgeID", + * these are simply indices into the internal .Groups, .Corners, and .Edges arrays + */ +class DYNAMICMESH_API FGroupTopology +{ +public: + FGroupTopology() {} + FGroupTopology(const FDynamicMesh3* Mesh, bool bAutoBuild); + + virtual ~FGroupTopology() {} + + + /** + * Build the group topology graph. + */ + void RebuildTopology(); + + /** + * Adjacency of Per-Triangle integers are what define the triangle groups. + * Override this function to provide an alternate group definition. + * @return group id integer for given TriangleID + */ + virtual int GetGroupID(int TriangleID) const + { + return Mesh->GetTriangleGroup(TriangleID); + } + + + /** + * FCorner is a "corner" in the group topology, IE a vertex where 3+ groups meet. + */ + struct DYNAMICMESH_API FCorner + { + /** Mesh Vertex ID */ + int VertexID; + /** List of IDs of groups connected to this Corner */ + TArray NeighbourGroupIDs; + }; + + /** List of Corners in the topology graph (ie these are the nodes/vertices of the graph) */ + TArray Corners; + + /** A Group is bounded by closed loops of FGroupEdge elements. A FGroupBoundary is one such loop. */ + struct DYNAMICMESH_API FGroupBoundary + { + /** Ordered list of edges forming this boundary */ + TArray GroupEdges; + /** List of IDs of groups on the "other side" of this boundary (this GroupBoundary is owned by a particular FGroup) */ + TArray NeighbourGroupIDs; + /** true if one or more edges in GroupEdges is on the mesh boundary */ + bool bIsOnBoundary; + }; + + /** FGroup is a set of connected triangles with the same GroupID */ + struct DYNAMICMESH_API FGroup + { + /** GroupID for this group */ + int GroupID; + /** List of triangles forming this group */ + TArray Faces; + + /** List of boundaries of this group (may be empty, eg on a closed component with only one group) */ + TArray Boundaries; + /** List of groups that are adjacent to this group */ + TArray NeighbourGroupIDs; + }; + + /** List of Groups in the topology graph (ie faces) */ + TArray Groups; + + + /** + * FGroupEdge is a sequence of group-boundary-edges where the two groups on either side of each edge + * are the same. The sequence is stored an FEdgeSpan. + * + * FGroupEdge instances are *shared* between the two FGroups on either side, via FGroupBoundary. + */ + struct DYNAMICMESH_API FGroupEdge + { + /** Identifier for this edge, as a pair of groups, sorted by increasing value */ + FIndex2i Groups; + /** Edge span for this edge */ + FEdgeSpan Span; + + /** @return the member of .Groups that is not GroupID */ + int OtherGroupID(int GroupID) const + { + check(Groups.A == GroupID || Groups.B == GroupID); + return (Groups.A == GroupID) ? Groups.B : Groups.A; + } + + /** @return true if any vertex in the Span is in the Vertices set*/ + bool IsConnectedToVertices(const TSet& Vertices) const; + }; + + /** List of Edges in the topology graph (each edge connects two corners/nodes) */ + TArray Edges; + + + + /** @return the mesh vertex ID for the given Corner ID */ + int GetCornerVertexID(int CornerID) const; + + /** @return the FGroup for the given GroupID, or nullptr if not found */ + const FGroup* FindGroupByID(int GroupID) const; + /** @return the list of triangles in the given GroupID, or empty list if not found */ + const TArray& GetGroupFaces(int GroupID) const; + /** @return the list of neighbour GroupIDs for the given GroupID, or empty list if not found */ + const TArray& GetGroupNbrGroups(int GroupID) const; + + /** @return the ID of the FGroupEdge that contains the given Mesh Edge ID*/ + int FindGroupEdgeID(int MeshEdgeID) const; + /** @return the list of vertices of a FGroupEdge identified by the GroupEdgeID */ + const TArray& GetGroupEdgeVertices(int GroupEdgeID) const; + + /** Add the groups connected to the given GroupEdgeID to the GroupsOut list. This is not the either-side pair, but the set of groups on the one-ring of each connected corner. */ + void FindEdgeNbrGroups(int GroupEdgeID, TArray& GroupsOut) const; + /** Add the groups connected to all the GroupEdgeIDs to the GroupsOut list. This is not the either-side pair, but the set of groups on the one-ring of each connected corner. */ + void FindEdgeNbrGroups(const TArray& GroupEdgeIDs, TArray& GroupsOut) const; + + /** Add all the groups connected to the given Corner to the GroupsOut list */ + void FindCornerNbrGroups(int CornerID, TArray& GroupsOut) const; + /** Add all the groups connected to the given Corners to the GroupsOut list */ + void FindCornerNbrGroups(const TArray& CornerIDs, TArray& GroupsOut) const; + + /** Add all the groups connected to the given Mesh Vertex to the GroupsOut list */ + void FindVertexNbrGroups(int VertexID, TArray& GroupsOut ) const; + /** Add all the groups connected to the given Mesh Vertices to the GroupsOut list */ + void FindVertexNbrGroups(const TArray& VertexIDs, TArray& GroupsOut) const; + + /** Call EdgeFunc for each boundary edge of the given Group (no order defined) */ + void ForGroupEdges(int GroupID, + const TFunction& EdgeFunc) const; + + /** Call EdgeFunc for each boundary edge of each of the given Groups (no order defined) */ + void ForGroupSetEdges(const TArray& GroupIDs, + const TFunction& EdgeFunc) const; + + /** Add all the vertices of the given GroupID to the Vertices set */ + void CollectGroupVertices(int GroupID, TSet& Vertices) const; + /** Add all the group boundary vertices of the given GroupID to the Vertices set */ + void CollectGroupBoundaryVertices(int GroupID, TSet& Vertices) const; + + +protected: + const FDynamicMesh3* Mesh = nullptr; + + TArray GroupIDToGroupIndexMap; // allow fast lookup of index in .Groups, given GroupID + TBitArray<> CornerVerticesFlags; // bit array of corners for fast testing in ExtractGroupEdges + TArray EmptyArray; + + /** @return true if given mesh vertex is a Corner vertex */ + virtual bool IsCornerVertex(int VertexID) const; + + void ExtractGroupEdges(FGroup& Group); + + FIndex2i MakeEdgeID(int MeshEdgeID) + { + FIndex2i EdgeTris = Mesh->GetEdgeT(MeshEdgeID); + return MakeEdgeID(GetGroupID(EdgeTris.A), GetGroupID(EdgeTris.B)); + } + FIndex2i MakeEdgeID(int Group1, int Group2) + { + check(Group1 != Group2); + return (Group1 < Group2) ? FIndex2i(Group1, Group2) : FIndex2i(Group2, Group1); + } + + int FindExistingGroupEdge(int GroupID, int OtherGroupID, int FirstVertexID); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshAdapterUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshAdapterUtil.h new file mode 100644 index 000000000000..db53a05ecfb0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshAdapterUtil.h @@ -0,0 +1,35 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "DynamicMesh3.h" +#include "PointSetAdapter.h" + +/** + * Utility functions for constructing PointSetAdapter instances from various + * point sets on a mesh + */ +namespace MeshAdapterUtil +{ + /** + * @return Mesh vertices as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeVerticesAdapter(const FDynamicMesh3* Mesh); + + /** + * @return Mesh triangle centroids as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeTriCentroidsAdapter(const FDynamicMesh3* Mesh); + + /** + * @return mesh edge midpoints as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeEdgeMidpointsAdapter(const FDynamicMesh3* Mesh); + + /** + * @return Mesh boundary edge midpoints as a point set + */ + FPointSetAdapterd DYNAMICMESH_API MakeBoundaryEdgeMidpointsAdapter(const FDynamicMesh3* Mesh); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshBoundaryLoops.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshBoundaryLoops.h new file mode 100644 index 000000000000..164d4503fed1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshBoundaryLoops.h @@ -0,0 +1,180 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshBoundaryLoops + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeLoop.h" +#include "EdgeSpan.h" +#include "VectorUtil.h" +#include "Util/BufferUtil.h" + + +class DYNAMICMESH_API FMeshBoundaryLoops +{ +public: + /** Mesh we are finding loops on */ + const FDynamicMesh3* Mesh = nullptr; + + /** Resulting set of loops filled by Compute() */ + TArray Loops; + + /** Resulting set of spans filled by Compute(), if SpanBehavior == Compute */ + TArray Spans; + + /** If true, we aborted computation due to unrecoverable errors */ + bool bAborted = false; + /** If true, we found at least one open span during Compute(). This can occur in failure cases or if the search is restricted to a subset using EdgeFilterFunc */ + bool bSawOpenSpans = false; + /** If true, we had to call back to spans because of failures during Compute(). This happens if we cannot extract simple loops from a loop with bowties. */ + bool bFellBackToSpansOnFailure = false; + + enum class ESpanBehaviors + { + Ignore, Abort, Compute + }; + /** What Compute() will do if it encounter open spans */ + ESpanBehaviors SpanBehavior = ESpanBehaviors::Compute; + + + enum class EFailureBehaviors + { + Abort, // die, and you clean up + ConvertToOpenSpan // keep un-closed loop as a span + }; + /** What Compute() will do if it encounters unrecoverable errors while walking around a loop */ + EFailureBehaviors FailureBehavior = EFailureBehaviors::ConvertToOpenSpan; + + /** If non-null, then only edges that pass this filter are considered. This may result in open spans. */ + TFunction EdgeFilterFunc = nullptr; + + /** If we encountered unrecoverable errors, it is generally due to bowtie vertices. The problematic bowties will be returned here so that the client can try repairing these vertices. */ + TArray FailureBowties; + + +public: + FMeshBoundaryLoops() {} + + FMeshBoundaryLoops(const FDynamicMesh3* MeshIn, bool bAutoCompute = true) + { + Mesh = MeshIn; + if (bAutoCompute) + { + Compute(); + } + } + + void SetMesh(const FDynamicMesh3* MeshIn) { Mesh = MeshIn; } + + /** + * Find the set of boundary EdgeLoops and EdgeSpans. + * .SpanBehavior and .FailureBehavior control what happens if we run into problem cases + * @return false if errors occurred, in this case output set is incomplete + */ + bool Compute(); + + + + /** @return number of loops found by Compute() */ + int GetLoopCount() const + { + return Loops.Num(); + } + + /** @return number of spans found by Compute() */ + int GetSpanCount() const + { + return Spans.Num(); + } + + /** @return Loop at the given index */ + const FEdgeLoop& operator[](int Index) const + { + return Loops[Index]; + } + + + /** @return index of loop with maximum number of vertices */ + int GetMaxVerticesLoopIndex() const; + + /** + * @return pair (LoopIndex,VertexIndexInLoop) of VertexID in EdgeLoops, or FIndex2i::Invalid if not found + */ + FIndex2i FindVertexInLoop(int VertexID) const; + + /** + * @return index of loop that contains vertex, or -1 if not found + */ + int FindLoopContainingVertex(int VertexID) const; + + /** + * @return index of loop that contains edge, or -1 if not found + */ + int FindLoopContainingEdge(int EdgeID) const; + + + + + +protected: + + TArray VerticesTemp; + + // [TODO] cache this : a dictionary? we will not need very many, but we will + // need each multiple times! + FVector3d GetVertexNormal(int vid); + + // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. + // We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. + // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. + int FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges); + + struct Subloops + { + TArray Loops; + TArray Spans; + }; + + + // This is called when loopV contains one or more "bowtie" vertices. + // These vertices *might* be duplicated in loopV (but not necessarily) + // If they are, we have to break loopV into subloops that don't contain duplicates. + bool ExtractSubloops(TArray& loopV, TArray& loopE, TArray& bowties, Subloops& SubloopsOut); + + + +protected: + + friend class FMeshRegionBoundaryLoops; + + /* + * static Utility functions that can be re-used in MeshRegionBoundaryLoops + * In all the functions below, the list loopV is assumed to possibly + * contain "removed" vertices indicated by -1. These are ignored. + */ + + // Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts. + // Also returns start and end indices in loopV of "clean" loop + // Note that start may be < end, if the "clean" loop wraps around the end + static bool IsSimpleBowtieLoop(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int& start_i, int& end_i); + + // check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV + static bool IsSimplePath(const TArray& LoopVerts, const TArray& BowtieVerts, int BowtieVertex, int i1, int i2); + + + // Read out the span from loop[i0] to loop [i1-1] into an array. + // If bMarkInvalid, then these values are set to -1 in loop + static void ExtractSpan(TArray& Loop, int i0, int i1, bool bMarkInvalid, TArray& OutSpan); + + // count number of valid vertices in l between loop[i0] and loop[i1-1] + static int CountSpan(const TArray& Loop, int i0, int i1); + + // find the index of item in loop, starting at start index + static int FindIndex(const TArray& Loop, int Start, int Item); + + // count number of times item appears in loop + static int CountInList(const TArray& Loop, int Item); + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraints.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraints.h new file mode 100644 index 000000000000..89fb3523b0cb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraints.h @@ -0,0 +1,325 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshConstraints + +#pragma once + +#include "Spatial/SpatialInterfaces.h" // for projection target + +/** + * EEdgeRefineFlags indicate constraints on triangle mesh edges + */ +enum class EEdgeRefineFlags +{ + /** Edge is unconstrained */ + NoConstraint = 0, + /** Edge cannot be flipped */ + NoFlip = 1, + /** Edge cannot be split */ + NoSplit = 2, + /** Edge cannot be collapsed */ + NoCollapse = 4, + /** Edge cannot be flipped, split, or collapsed */ + FullyConstrained = NoFlip | NoSplit | NoCollapse, + /** Edge can only be split */ + SplitsOnly = NoFlip | NoCollapse +}; + + +/** + * FEdgeConstraint is a constraint on a triangle mesh edge + */ +struct DYNAMICMESH_API FEdgeConstraint +{ +public: + /** Constraint flags on this edge */ + EEdgeRefineFlags RefineFlags; + /** Edge is associated with this projection target. */ + IProjectionTarget * Target; + + /** This ID is not a constraint, but can be used to find descendents of a constrained input edge after splits */ + int TrackingSetID; + + FEdgeConstraint() + { + RefineFlags = EEdgeRefineFlags::NoConstraint; + Target = nullptr; + TrackingSetID = -1; + } + + FEdgeConstraint(EEdgeRefineFlags ConstraintFlags) + { + RefineFlags = ConstraintFlags; + Target = nullptr; + TrackingSetID = -1; + } + + FEdgeConstraint(EEdgeRefineFlags ConstraintFlags, IProjectionTarget* TargetIn) + { + RefineFlags = ConstraintFlags; + Target = TargetIn; + TrackingSetID = -1; + } + + /** @return true if edge can be flipped */ + bool CanFlip() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::NoFlip) == 0; + } + + /** @return true if edge can be split */ + bool CanSplit() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::NoSplit) == 0; + } + + /** @return true if edge can be collapsed */ + bool CanCollapse() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::NoCollapse) == 0; + } + + /** @return true if edge cannot be modified at all */ + bool NoModifications() const + { + return ((int)RefineFlags & (int)EEdgeRefineFlags::FullyConstrained) == (int)EEdgeRefineFlags::FullyConstrained; + } + + /** @return true if edge is unconstrained */ + bool IsUnconstrained() const + { + return RefineFlags == EEdgeRefineFlags::NoConstraint && Target == nullptr; + } + + /** @return an unconstrained edge constraint (ie not constrained at all) */ + static FEdgeConstraint Unconstrained() { return FEdgeConstraint(EEdgeRefineFlags::NoConstraint); } + + /** @return a no-flip edge constraint */ + static FEdgeConstraint NoFlips() { return FEdgeConstraint(EEdgeRefineFlags::NoFlip); } + + /** @return a splits-only edge constraint */ + static FEdgeConstraint SplitsOnly() { return FEdgeConstraint(EEdgeRefineFlags::SplitsOnly); } + + /** @return a fully constrained edge constraint */ + static FEdgeConstraint FullyConstrained() { return FEdgeConstraint(EEdgeRefineFlags::FullyConstrained); } +}; + + + +/** + * FVertexConstraint is a constraint on a triangle mesh vertex + */ +struct DYNAMICMESH_API FVertexConstraint +{ +public: + /** value for FixedSetID that is treated as not-a-fixed-set-ID by various functions (ie don't use this value yourself) */ + static constexpr int InvalidSetID = -1; + + /** Is this vertex topologically fixed, ie cannot be removed by topology-change operations */ + bool Fixed; + /** Can this vertex be moved */ + bool Movable; + /** Fixed vertices with the same FixedSetID can optionally be collapsed together (ie in Remesher) */ + int FixedSetID; + + /** Vertex is associated with this ProjectionTarget, and should be projected onto it (ie in Remesher) */ + IProjectionTarget* Target; + + FVertexConstraint() + { + Fixed = false; + Movable = false; + FixedSetID = InvalidSetID; + Target = nullptr; + } + + FVertexConstraint(bool bIsFixed, bool bIsMovable = false, int SetID = InvalidSetID) + { + Fixed = bIsFixed; + Movable = bIsMovable; + FixedSetID = SetID; + Target = nullptr; + } + + FVertexConstraint(IProjectionTarget* TargetIn) + { + Fixed = false; + Movable = false; + FixedSetID = InvalidSetID; + Target = TargetIn; + } + + /** @return an unconstrained vertex constraint (ie not constrained at all) */ + static FVertexConstraint Unconstrained() { return FVertexConstraint(false, true); } + + /** @return a Pinned vertex constraint */ + static FVertexConstraint Pinned() { return FVertexConstraint(true, false); } + + /** @return a Pinned-but-Movable vertex constraint */ + static FVertexConstraint PinnedMovable() { return FVertexConstraint(true, true); } +}; + + + + +/** + * FMeshConstraints is a set of Edge and Vertex constraints for a Triangle Mesh + */ +class DYNAMICMESH_API FMeshConstraints +{ +protected: + + /** Map of mesh edge IDs to active EdgeConstraints */ + TMap Edges; + + /** Map of mesh vertex IDs to active VertexConstraints */ + TMap Vertices; + + /** internal counter used to allocate new FixedSetIDs */ + int FixedSetIDCounter; + +public: + + FMeshConstraints() + { + FixedSetIDCounter = 0; + } + + /** @return an unused Fixed Set ID */ + int AllocateSetID() + { + return FixedSetIDCounter++; + } + + /** @return map of active edge constraints */ + const TMap& GetEdgeConstraints() const + { + return Edges; + } + + /** @return map of active vertex constraints */ + const TMap& GetVertexConstraints() const + { + return Vertices; + } + + + /** @return true if any edges or vertices are constrained */ + bool HasConstraints() const + { + return Edges.Num() > 0 || Vertices.Num() > 0; + } + + + + /** @return true if given EdgeID has active constraint */ + bool HasEdgeConstraint(int EdgeID) const + { + return Edges.Find(EdgeID) != nullptr; + } + + /** @return EdgeConstraint for give EdgeID, may be Unconstrained */ + FEdgeConstraint GetEdgeConstraint(int EdgeID) const + { + const FEdgeConstraint* Found = Edges.Find(EdgeID); + if (Found == nullptr) + { + return FEdgeConstraint::Unconstrained(); + } + else + { + return *Found; + } + } + + /** @return true if EdgeID is constrained and ConstraintOut is filled */ + bool GetEdgeConstraint(int EdgeID, FEdgeConstraint& ConstraintOut) const + { + const FEdgeConstraint* Found = Edges.Find(EdgeID); + if (Found == nullptr) + { + return false; + } + else + { + ConstraintOut = *Found; + return true; + } + } + + /** Set the constraint on the given EdgeID */ + void SetOrUpdateEdgeConstraint(int EdgeID, const FEdgeConstraint& ec) + { + Edges.Add(EdgeID, ec); + } + + /** Clear the constraint on the given EdgeID */ + void ClearEdgeConstraint(int EdgeID) + { + Edges.Remove(EdgeID); + } + + + /** Find all constraint edges for the given SetID, and return via FoundListOut */ + void FindConstrainedEdgesBySetID(int SetID, TArray& FoundListOut) const + { + for (const TPair& pair : Edges) + { + if (pair.Value.TrackingSetID == SetID) + { + FoundListOut.Add(pair.Key); + } + } + } + + + + /** @return true if given VertexID has active constraint */ + bool HasVertexConstraint(int VertexID) const + { + return Vertices.Find(VertexID) != nullptr; + } + + + /** @return VertexConstraint for give VertexID, may be Unconstrained */ + FVertexConstraint GetVertexConstraint(int VertexID) const + { + const FVertexConstraint* Found = Vertices.Find(VertexID); + if (Found == nullptr) + { + return FVertexConstraint::Unconstrained(); + } + else + { + return *Found; + } + } + + /** @return true if VetexID is constrained and ConstraintOut is filled */ + bool GetVertexConstraint(int VertexID, FVertexConstraint& ConstraintOut)const + { + const FVertexConstraint* Found = Vertices.Find(VertexID); + if (Found == nullptr) + { + return false; + } + else + { + ConstraintOut = *Found; + return true; + } + } + + /** Set the constraint on the given VertexID */ + void SetOrUpdateVertexConstraint(int VertexID, const FVertexConstraint& vc) + { + Vertices.Add(VertexID, vc); + } + + /** Clear the constraint on the given VertexID */ + void ClearVertexConstraint(int VertexID) + { + Vertices.Remove(VertexID); + } + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraintsUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraintsUtil.h new file mode 100644 index 000000000000..566ed6a1dc8e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshConstraintsUtil.h @@ -0,0 +1,81 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshConstraintsUtil + +#pragma once + +#include "DynamicMesh3.h" +#include "MeshConstraints.h" +#include "DynamicMeshAttributeSet.h" + + +/** + * Utility functions for configuring a FMeshConstraints instance + */ +class DYNAMICMESH_API FMeshConstraintsUtil +{ +public: + + + /** + * Constrain all attribute seams for all overlays of a mesh + * @param Constraints the set of constraints to add to + * @param Mesh the mesh to constrain + * @param bAllowSplits should we allow constrained edges to be split + * @param bAllowSmoothing should we allow constrained vertices to be smoothed + */ + static void ConstrainAllSeams(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, bool bAllowSplits, bool bAllowSmoothing); + + + /** + * Constrain attribute seams of the given overlay + * @param Constraints the set of constraints to add to + * @param Mesh the mesh to constrain + * @param Overlay the attribute overlay to find seams in + */ + template + static void ConstrainSeams(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, const TDynamicMeshOverlay& Overlay) + { + for (int EdgeID : Mesh.EdgeIndicesItr()) + { + if (Overlay.IsSeamEdge(EdgeID)) + { + Constraints.SetOrUpdateEdgeConstraint(EdgeID, FEdgeConstraint::FullyConstrained()); + FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.A, FVertexConstraint::Pinned()); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.B, FVertexConstraint::Pinned()); + } + } + } + + + + /** + * For all edges, disable flip/split/collapse. For all vertices, pin in current position. + * @param Constraints the set of constraints to add to + * @param Mesh the mesh to constrain + */ + template + static void FullyConstrainEdges(FMeshConstraints& Constraints, const FDynamicMesh3& Mesh, iter BeginEdges, iter EndEdges) + { + while (BeginEdges != EndEdges) + { + int EdgeID = *BeginEdges; + if (Mesh.IsEdge(EdgeID)) + { + Constraints.SetOrUpdateEdgeConstraint(EdgeID, FEdgeConstraint::FullyConstrained()); + FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.A, FVertexConstraint::Pinned()); + Constraints.SetOrUpdateVertexConstraint(EdgeVerts.B, FVertexConstraint::Pinned()); + } + BeginEdges++; + } + } + + + +private: + FMeshConstraintsUtil() = delete; // this class is not constructible + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshIndexUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshIndexUtil.h new file mode 100644 index 000000000000..1ed881aad411 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshIndexUtil.h @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" + +/** + * Utility functions for dealing with mesh indices + */ +namespace MeshIndexUtil +{ + /** + * @param Mesh input mesh + * @param TriangleIDs list of triangle IDs of Mesh + * @param VertexIDsOut list of vertices contained by triangles + */ + DYNAMICMESH_API void ConverTriangleToVertexIDs(const FDynamicMesh3* Mesh, const TArray& TriangleIDs, TArray& VertexIDsOut); + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshNormals.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshNormals.h new file mode 100644 index 000000000000..e36b9615377f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshNormals.h @@ -0,0 +1,114 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MeshNormals + +#pragma once + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + +/** + * FMeshNormals is a utility class that can calculate and store various types of + * normal vectors for a FDynamicMesh. + */ +class DYNAMICMESH_API FMeshNormals +{ +protected: + /** Target Mesh */ + const FDynamicMesh3* Mesh; + /** Set of computed normals */ + TArray Normals; + +public: + FMeshNormals() + { + Mesh = nullptr; + } + + FMeshNormals(const FDynamicMesh3* Mesh) + { + SetMesh(Mesh); + } + + + void SetMesh(const FDynamicMesh3* MeshIn) + { + this->Mesh = MeshIn; + } + + const TArray& GetNormals() const { return Normals; } + + FVector3d& operator[](int i) { return Normals[i]; } + const FVector3d& operator[](int i) const { return Normals[i]; } + + + /** + * Set the size of the Normals array to Count, and optionally clear all values to (0,0,0) + */ + void SetCount(int Count, bool bClearToZero); + + /** + * Compute standard per-vertex normals by averaging one-ring face normals + */ + void ComputeVertexNormals() + { + Compute_FaceAvg_AreaWeighted(); + } + + /** + * Compute per-triangle normals + */ + void ComputeTriangleNormals() + { + Compute_Triangle(); + } + + /** + * Recompute the per-element normals of the given overlay by averaging one-ring face normals + * @warning NormalOverlay must be attached to ParentMesh or an exact copy + */ + void RecomputeOverlayNormals(FDynamicMeshNormalOverlay* NormalOverlay) + { + Compute_Overlay_FaceAvg_AreaWeighted(NormalOverlay); + } + + + + /** + * Copy the current set of normals to the vertex normals of SetMesh + * @warning assumes that the computed normals are vertex normals + * @param bInvert if true, normals are flipped + */ + void CopyToVertexNormals(FDynamicMesh3* SetMesh, bool bInvert = false) const; + + /** + * Copy the current set of normals to the NormalOverlay attribute layer + * @warning assumes that the computed normals are attribute normals + * @param bInvert if true, normals are flipped + */ + void CopyToOverlay(FDynamicMeshNormalOverlay* NormalOverlay, bool bInvert = false) const; + + + + /** + * Compute per-vertex normals for the given Mesh + * @param bInvert if true, normals are flipped + */ + static void QuickComputeVertexNormals(FDynamicMesh3& Mesh, bool bInvert = false); + + + /** + * @return the vertex normal at vertex VertIDx of Mesh + */ + static FVector3d ComputeVertexNormal(const FDynamicMesh3& Mesh, int VertIdx); + + +protected: + /** Compute per-vertex normals using area-weighted averaging of one-ring triangles */ + void Compute_FaceAvg_AreaWeighted(); + /** Compute per-triangle normals */ + void Compute_Triangle(); + /** Recompute the element Normals of the given attribute overlay using area-weighted averaging of one-ring triangles */ + void Compute_Overlay_FaceAvg_AreaWeighted(const FDynamicMeshNormalOverlay* NormalOverlay); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRefinerBase.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRefinerBase.h new file mode 100644 index 000000000000..a3ea12d9ae04 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRefinerBase.h @@ -0,0 +1,264 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp FMeshRefinerBase + +#pragma once + +#include "DynamicMesh3.h" +#include "MeshConstraints.h" +#include "Util/ProgressCancel.h" + + +/** + * This is a base class that implements common functionality for various triangle mesh resampling strategies + * (ie FRemesher and FReducer). You probably should not use this class directly. + */ +class DYNAMICMESH_API FMeshRefinerBase +{ +protected: + /** Mesh that will be refined */ + FDynamicMesh3* Mesh = nullptr; + + /** Constraints are used to control how certain edges and vertices can be modified */ + FMeshConstraints* Constraints = nullptr; + + /** Vertices can be projected onto this surface when they are modified */ + IProjectionTarget* ProjTarget = nullptr; + + + FMeshRefinerBase(FDynamicMesh3* MeshIn) + { + this->Mesh = MeshIn; + } + + FMeshRefinerBase() + { + } + + +public: + + /** + * If true, then when two Fixed vertices have the same non-invalid SetID, + * we treat them as not fixed and allow collapse + */ + bool AllowCollapseFixedVertsWithSameSetID = true; + + + enum class EVertexControl + { + AllowAll = 0, + NoSmooth = 1, + NoProject = 2, + NoMovement = NoSmooth | NoProject + }; + /** + * This function allows client to specify fine-grained control over what happens to specific vertices. + * Somewhat redundant w/ FVertexConstraints, but simpler to code and has the option to be dynamic during remesh pass. + */ + TFunction VertexControlF = nullptr; + + + /** Options for projecting vertices onto target surface */ + enum class ETargetProjectionMode + { + NoProjection = 0, // disable projection + AfterRefinement = 1, // do all projection after the refine/smooth pass + Inline = 2 // project after each vertex update. Better results but more + // expensive because eg we might create a vertex with + // split, then project, then smooth, then project again. + }; + + /** Method to use to project vertices onto target surface. Default is no projection. */ + ETargetProjectionMode ProjectionMode = ETargetProjectionMode::NoProjection; + + + + /** Set this to be able to cancel running Remesher/Reducer*/ + FProgressCancel* Progress = nullptr; + + + + /** This is a debugging aid, will break to debugger if these edges are touched, in debug builds */ + TArray DebugEdges; + + /** Set to true to profile various passes @todo re-enable this! */ + bool ENABLE_PROFILING = false; + + /** 0 = no checking, 1 = check constraints and validity each pass, 2 = check validity after every mesh change (v slow but best for debugging) */ + int DEBUG_CHECK_LEVEL = 0; + + +public: + virtual ~FMeshRefinerBase() {} + + /** Get the current mesh we are operating on */ + FDynamicMesh3* GetMesh() { return Mesh; } + + /** Get the current mesh constraints */ + FMeshConstraints* GetConstraints() { return Constraints; } + + /** + * Set external constraints. + * Note that this object will be updated during computation. + */ + void SetExternalConstraints(FMeshConstraints* ConstraintsIn) { Constraints = ConstraintsIn; } + + + /** Get the current Projection Target */ + IProjectionTarget* ProjectionTarget() { return this->ProjTarget; } + + /** Set a Projection Target */ + void SetProjectionTarget(IProjectionTarget* TargetIn) { this->ProjTarget = TargetIn; } + + + + + + /** @return edge flip tolerance */ + double GetEdgeFlipTolerance() { return EdgeFlipTolerance; } + + /** Set edge flip tolerance. Value is clamped to range [-1,1] */ + void SetEdgeFlipTolerance(double NewTolerance) { EdgeFlipTolerance = VectorUtil::Clamp(NewTolerance, -1.0, 1.0); } + + + + + + /** If this returns true, abort computation. */ + virtual bool Cancelled() + { + return (Progress == nullptr) ? false : Progress->Cancelled(); + } + + + +protected: + + /** If normals dot product is less than this, we consider it a normal flip. default = 0 */ + double EdgeFlipTolerance = 0.0f; + + /** + * @return edge-flip dotproduct metric in range [-1,1] measured between two possibly-not-normalized normal directions. if EdgeFlipTolerance is 0, only the sign of the returned value is valid + */ + inline double ComputeEdgeFlipMetric(const FVector3d& Direction0, const FVector3d& Direction1) const + { + if (EdgeFlipTolerance == 0) + { + return Direction0.Dot(Direction1); + } + else + { + return Direction0.Normalized().Dot(Direction1.Normalized()); + } + } + + + /** + * Check if edge collapse will create a face-normal flip. + * Also checks if collapse would violate link condition, since we are iterating over one-ring anyway. + * This only checks one-ring of vid, so you have to call it twice, with vid and vother reversed, to check both one-rings + * @param vid first vertex of edge + * @param vother other vertex of edge + * @param newv new vertex position after collapse + * @param tc triangle on one side of edge + * @param td triangle on other side of edge + */ + bool CheckIfCollapseCreatesFlipOrInvalid(int vid, int vother, const FVector3d& newv, int tc, int td) const; + + /** + * Check if edge flip might reverse normal direction. + * Not entirely clear on how to best implement this test. Currently checking if any normal-pairs are reversed. + * @param a first vertex of edge + * @param b second vertex of edge + * @param c opposing vertex 1 + * @param d opposing vertex 2 + * @param t0 index of triangle containing [a,b,c] + */ + bool CheckIfFlipInvertsNormals(int a, int b, int c, int d, int t0) const; + + /** + * Figure out if we can collapse edge eid=[a,b] under current constraint set. + * First we resolve vertex constraints using CanCollapseVertex(). However this + * does not catch some topological cases at the edge-constraint level, which + * which we will only be able to detect once we know if we are losing a or b. + * See comments on CanCollapseVertex() for what collapse_to is for. + * @param a first vertex of edge + * @param b second vertex of edge + * @param c opposing vertex 1 + * @param d opposing vertex 2 + * @param tc index of triangle [a,b,c] + * @param td index of triangle [a,b,d] + * @param collapse_to either a or b if we should collapse to one of those, or -1 if either is acceptable + */ + bool CanCollapseEdge(int eid, int a, int b, int c, int d, int tc, int td, int& collapse_to) const; + + /** + * Resolve vertex constraints for collapsing edge eid=[a,b]. Generally we would + * collapse a to b, and set the new position as 0.5*(v_a+v_b). However if a *or* b + * are constrained, then we want to keep that vertex and collapse to its position. + * This vertex (a or b) will be returned in collapse_to, which is -1 otherwise. + * If a *and* b are constrained, then things are complicated (and documented below). + * @param eid edge ID + * @param a first vertex of edge + * @param b second vertex of edge* + * @param collapse_to either a or b if we should collapse to one of those, or -1 if either is acceptable + */ + bool CanCollapseVertex(int eid, int a, int b, int& collapse_to) const; + + /** + * @return true if given vertex is Fixed under current constraints + */ + inline bool IsVertexFixed(int VertexID) + { + return (Constraints != nullptr && Constraints->GetVertexConstraint(VertexID).Fixed); + } + + + /** + * @return true if given vertex is Fixed or has a projection target + */ + inline bool IsVertexConstrained(int VertexID) + { + if (Constraints != nullptr) + { + FVertexConstraint vc = Constraints->GetVertexConstraint(VertexID); + return (vc.Fixed || vc.Target != nullptr); + } + return false; + } + + /** + * @return constraint for given vertex + */ + inline FVertexConstraint GetVertexConstraint(int VertexID) + { + if (Constraints != nullptr) + { + return Constraints->GetVertexConstraint(VertexID); + } + return FVertexConstraint::Unconstrained(); + } + + /** + * @return true if vertex has constraint + */ + inline bool GetVertexConstraint(int VertexID, FVertexConstraint& OutConstraint) + { + return (Constraints == nullptr) ? false : + Constraints->GetVertexConstraint(VertexID, OutConstraint); + } + + + + + + /* + * testing/debug/profiling stuff + */ + void RuntimeDebugCheck(int EdgeID); + virtual void DoDebugChecks(bool bEndOfPass = false); + void DebugCheckUVSeamConstraints(); + void DebugCheckVertexConstraints(); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRegionBoundaryLoops.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRegionBoundaryLoops.h new file mode 100644 index 000000000000..5d1212e1f17d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshRegionBoundaryLoops.h @@ -0,0 +1,97 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshRegionBoundaryLoops + +#pragma once + +#include "DynamicMesh3.h" +#include "EdgeLoop.h" + + +/** + * Extract FEdgeLoops on the boundary of a set of triangles of a mesh. + * @todo this was an early port and possibly can be optimized + */ +class DYNAMICMESH_API FMeshRegionBoundaryLoops +{ +public: + /** Mesh we are finding loops on */ + const FDynamicMesh3* Mesh = nullptr; + /** Resulting set of loops filled by Compute() */ + TArray Loops; + +public: + FMeshRegionBoundaryLoops() {} + + FMeshRegionBoundaryLoops(const FDynamicMesh3* MeshIn, const TArray& RegionTris, bool bAutoCompute = true); + + /** + * Find set of FEdgeLoops on the border of the input triangle set + * @return false if errors occurred, in this case output set is incomplete + */ + bool Compute(); + + + /** @return number of loops found by Compute() */ + int GetLoopCount() const + { + return Loops.Num(); + } + + /** @return Loop at the given index */ + const FEdgeLoop& operator[](int Index) const + { + return Loops[Index]; + } + + /** @return index of loop with maximum number of vertices */ + int GetMaxVerticesLoopIndex() const; + + + +protected: + + + // TODO: C# code used IndexFlagSet here, which is sparse if region size << mesh size + // list of included triangles and edges + TArray triangles; + TArray edges; + TArray edges_roi; + + + static bool ContainsElement(const TArray& flagset, int index) { return flagset[index] == true; } + + bool IsEdgeOnBoundary(int eid) const { return ContainsElement(edges, eid); } + + // returns true for both internal and mesh boundary edges + // tid_in and tid_out are triangles 'in' and 'out' of set, respectively + bool IsEdgeOnBoundary(int eid, int& tid_in, int& tid_out) const; + + // return same indices as GetEdgeV, but oriented based on attached triangle + FIndex2i GetOrientedEdgeVerts(int eID, int tid_in, int tid_out); + + // returns first two boundary edges, and count of total boundary edges + int GetVertexBoundaryEdges(int vID, int& e0, int& e1); + + // e needs to be large enough (ie call GetVtxBoundaryEdges, or as large as max one-ring) + // returns count, ie number of elements of e that were filled + int GetAllVertexBoundaryEdges(int vID, TArray& e); + + + // [TODO] cache this : a dictionary? we will not need very many, but we will + // need each multiple times! + FVector3d GetVertexNormal(int vid); + + // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. + // We want to pick the best one to continue the loop that came : to bowtie_v on incoming_e. + // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. + int FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray& bdry_edges, int bdry_edges_count, TArray& used_edges); + + + // This is called when loopV contains one or more "bowtie" vertices. + // These vertices *might* be duplicated : loopV (but not necessarily) + // If they are, we have to break loopV into subloops that don't contain duplicates. + TArray ExtractSubloops(TArray& loopV, const TArray& loopE, const TArray& bowties); + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshSimplification.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshSimplification.h new file mode 100644 index 000000000000..aace8f740a82 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshSimplification.h @@ -0,0 +1,333 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp Reducer + +#pragma once + +#include "MeshRefinerBase.h" +#include "QuadricError.h" +#include "Util/IndexPriorityQueue.h" + +#include "DynamicMeshAttributeSet.h" + + +enum class ESimplificationResult +{ + Ok_Collapsed = 0, + Ignored_CannotCollapse = 1, + Ignored_EdgeIsFullyConstrained = 2, + Ignored_EdgeTooLong = 3, + Ignored_Constrained = 4, + Ignored_CreatesFlip = 5, + Failed_OpNotSuccessful = 6, + Failed_NotAnEdge = 7 +}; + +/** + * Implementation of Garland & Heckbert Quadric Error Metric (QEM) Triangle Mesh Simplification + */ +template +class TMeshSimplification : public FMeshRefinerBase +{ +public: + + typedef QuadricErrorType FQuadricErrorType; + + /** + * If true, we try to find position for collapsed vertices that minimizes quadric error. + * If false we just use midpoints, which is actually significantly slower, because it results + * in may more points that would cause a triangle flip, which are then rejected. + */ + bool bMinimizeQuadricPositionError = true; + + /** if true, we try to keep boundary vertices on boundary. You probably want this. */ + bool bPreserveBoundaryShape = true; + + + + TMeshSimplification(FDynamicMesh3* m) : FMeshRefinerBase(m) + { + NormalOverlay = nullptr; + if (m->Attributes()) + { + NormalOverlay = m->Attributes()->PrimaryNormals(); + } + } + + + /** + * Simplify mesh until we reach a specific triangle count. + * Note that because triangles are removed in pairs, the resulting count may be TriangleCount-1 + * @param TriangleCount the target triangle count + */ + virtual void SimplifyToTriangleCount(int TriangleCount); + + /** + * Simplify mesh until it has a specific vertex count + * @param VertexCount the target vertex count + */ + virtual void SimplifyToVertexCount(int VertexCount); + + /** + * Simplify mesh until no edges smaller than min length remain. This is not a great criteria. + * @param MinEdgeLength collapse any edge longer than this + */ + virtual void SimplifyToEdgeLength(double MinEdgeLength); + + /** + * Does N rounds of collapsing edges longer than fMinEdgeLength. Does not use Quadrics or priority queue. + * This is a quick way to get triangle count down on huge meshes (eg like marching cubes output). + * @param MinEdgeLength collapse any edge longer than this + * @param Rounds number of collapse rounds + * @param MeshIsClosedHint if you know the mesh is closed, this pass this true to avoid some precomputes + */ + virtual void FastCollapsePass(double MinEdgeLength, int Rounds = 1, bool bMeshIsClosedHint = false); + + + +protected: + + TMeshSimplification() // for subclasses that extend our behavior + { + } + + // this just lets us write more concise code + bool EnableInlineProjection() const { return ProjectionMode == ETargetProjectionMode::Inline; } + + + double MinEdgeLength = FMathd::MaxReal; + int TargetCount = INT_MAX; + enum class ETargetModes + { + TriangleCount = 0, + VertexCount = 1, + MinEdgeLength = 2 + }; + ETargetModes SimplifyMode = ETargetModes::TriangleCount; + + + + /** Top-level function that does the simplification */ + virtual void DoSimplify(); + + + + // StartEdges() and GetNextEdge() control the iteration over edges that will be refined. + // Default here is to iterate over entire mesh-> + // Subclasses can override these two functions to restrict the affected edges (eg EdgeLoopRemesher) + + // We are using a modulo-index loop to break symmetry/pathological conditions. + const int ModuloPrime = 31337; // any prime will do... + int MaxEdgeID = 0; + virtual int StartEdges() + { + MaxEdgeID = Mesh->MaxEdgeID(); + return 0; + } + + virtual int GetNextEdge(int CurEdgeID, bool& bDoneOut) + { + int new_eid = (CurEdgeID + ModuloPrime) % MaxEdgeID; + bDoneOut = (new_eid == 0); + return new_eid; + } + + + + + TArray vertQuadrics; + virtual void InitializeVertexQuadrics(); + + TArray triAreas; + TArray triQuadrics; + virtual void InitializeTriQuadrics(); + + FDynamicMeshNormalOverlay* NormalOverlay; + + FQuadricErrorType ComputeFaceQuadric(const int tid, FVector3d& nface, FVector3d& c, double& Area) const; + + // internal class for priority queue + struct QEdge + { + int eid; + FQuadricErrorType q; + FVector3d collapse_pt; + + QEdge() { eid = 0; } + + QEdge(int edge_id, const FQuadricErrorType& qin, const FVector3d& pt) + { + eid = edge_id; + q = qin; + collapse_pt = pt; + } + }; + + TArray EdgeQuadrics; + FIndexPriorityQueue EdgeQueue; + + struct FEdgeError + { + float error; + int eid; + bool operator<(const FEdgeError& e2) const + { + return error < e2.error; + } + }; + + virtual void InitializeQueue(); + + // return point that minimizes quadric error for edge [ea,eb] + FVector3d OptimalPoint(int eid, const FQuadricErrorType& q, int ea, int eb); + + FVector3d GetProjectedPoint(const FVector3d& pos) + { + if (EnableInlineProjection() && ProjTarget != nullptr) + { + return ProjTarget->Project(pos); + } + return pos; + } + + + // update queue weight for each edge in vertex one-ring + virtual void UpdateNeighbours(int vid, FIndex2i removedTris, FIndex2i opposingVerts); + + + virtual void Reproject() + { + ProfileBeginProject(); + if (ProjTarget != nullptr && ProjectionMode == ETargetProjectionMode::AfterRefinement) + { + FullProjectionPass(); + DoDebugChecks(); + } + ProfileEndProject(); + } + + + + + + bool bHaveBoundary; + TArray IsBoundaryVtxCache; + void Precompute(bool bMeshIsClosed = false); + + inline bool IsBoundaryVertex(int vid) const + { + return IsBoundaryVtxCache[vid]; + } + + + + + + ESimplificationResult CollapseEdge(int edgeID, FVector3d vNewPos, int& collapseToV); + + + + // subclasses can override these to implement custom behavior... + virtual void OnEdgeCollapse(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeCollapseInfo& collapseInfo) + { + // this is for subclasses... + } + + + + + + // Project vertices onto projection target. + virtual void FullProjectionPass(); + + virtual void ProjectVertex(int vID, IProjectionTarget* targetIn); + + // used by collapse-edge to get projected position for new vertex + virtual FVector3d GetProjectedCollapsePosition(int vid, const FVector3d& vNewPos); + + virtual void ApplyToProjectVertices(const TFunction& apply_f); + + + + /* + * testing/debug/profiling stuff + */ +protected: + + + // + // profiling functions, turn on ENABLE_PROFILING to see output in console + // + int COUNT_COLLAPSES; + int COUNT_ITERATIONS; + //Stopwatch AllOpsW, SetupW, ProjectW, CollapseW; + + virtual void ProfileBeginPass() + { + if (ENABLE_PROFILING) + { + COUNT_COLLAPSES = 0; + COUNT_ITERATIONS = 0; + //AllOpsW = new Stopwatch(); + //SetupW = new Stopwatch(); + //ProjectW = new Stopwatch(); + //CollapseW = new Stopwatch(); + } + } + + virtual void ProfileEndPass() + { + if (ENABLE_PROFILING) + { + //System.Console.WriteLine(string.Format( + // "ReducePass: T {0} V {1} collapses {2} iterations {3}", mesh->TriangleCount, mesh->VertexCount, COUNT_COLLAPSES, COUNT_ITERATIONS + //)); + //System.Console.WriteLine(string.Format( + // " Timing1: setup {0} ops {1} project {2}", Util.ToSecMilli(SetupW.Elapsed), Util.ToSecMilli(AllOpsW.Elapsed), Util.ToSecMilli(ProjectW.Elapsed) + //)); + } + } + + virtual void ProfileBeginOps() + { + //if (ENABLE_PROFILING) AllOpsW.Start(); + } + virtual void ProfileEndOps() + { + //if (ENABLE_PROFILING) AllOpsW.Stop(); + } + virtual void ProfileBeginSetup() + { + //if (ENABLE_PROFILING) SetupW.Start(); + } + virtual void ProfileEndSetup() + { + //if (ENABLE_PROFILING) SetupW.Stop(); + } + + virtual void ProfileBeginProject() + { + //if (ENABLE_PROFILING) ProjectW.Start(); + } + virtual void ProfileEndProject() + { + //if (ENABLE_PROFILING) ProjectW.Stop(); + } + + virtual void ProfileBeginCollapse() + { + //if (ENABLE_PROFILING) CollapseW.Start(); + } + virtual void ProfileEndCollapse() + { + //if (ENABLE_PROFILING) CollapseW.Stop(); + } + + +}; + + +// The simplifier +typedef TMeshSimplification< FAttrBasedQuadricErrord > FAttrMeshSimplification; +typedef TMeshSimplification < FVolPresQuadricErrord > FVolPresMeshSimplification; +typedef TMeshSimplification< FQuadricErrord > FQEMSimplification; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshTangents.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshTangents.h new file mode 100644 index 000000000000..f90a4d60fe07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshTangents.h @@ -0,0 +1,103 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + +/** + * TMeshTangents is a utility class that can calculate and store various types of + * tangent vectors for a FDynamicMesh. + */ +template +class DYNAMICMESH_API TMeshTangents +{ +protected: + /** Target Mesh */ + const FDynamicMesh3* Mesh; + /** Set of computed tangents */ + TArray> Tangents; + /** Set of computed bitangents */ + TArray> Bitangents; + +public: + TMeshTangents() + { + Mesh = nullptr; + } + + TMeshTangents(const FDynamicMesh3* Mesh) + { + SetMesh(Mesh); + } + + void SetMesh(const FDynamicMesh3* MeshIn) + { + this->Mesh = MeshIn; + } + + const TArray>& GetTangents() const { return Tangents; } + + const TArray>& GetBitangents() const { return Bitangents; } + + + /** + * Return tangent and bitangent at a vertex of triangle for per-triangle computed tangents + * @param TriangleID triangle index in mesh + * @param TriVertIdx vertex index in range 0,1,2 + */ + void GetPerTriangleTangent(int TriangleID, int TriVertIdx, FVector3& TangentOut, FVector3& BitangentOut) const + { + int k = TriangleID * 3 + TriVertIdx; + TangentOut = Tangents[k]; + BitangentOut = Bitangents[k]; + } + + + /** + * Set tangent and bitangent at a vertex of triangle for per-triangle computed tangents. + * @param TriangleID triangle index in mesh + * @param TriVertIdx vertex index in range 0,1,2 + */ + void SetPerTriangleTangent(int TriangleID, int TriVertIdx, const FVector3& Tangent, const FVector3& Bitangent) + { + int k = TriangleID * 3 + TriVertIdx; + Tangents[k] = Tangent; + Bitangents[k] = Bitangent; + } + + + /** + * Calculate per-triangle tangent spaces based on the given per-triangle normal and UV overlays. + * In this mode there is no averaging of tangents across triangles. So if we have N triangles + * in the mesh, then 3*N tangents are generated. These tangents are computed in parallel. + */ + void ComputePerTriangleTangents(const FDynamicMeshNormalOverlay* NormalOverlay, const FDynamicMeshUVOverlay* UVOverlay) + { + Internal_ComputePerTriangleTangents(NormalOverlay, UVOverlay); + } + + + /** + * Set internal buffer sizes suitable for calculating per-triangle tangents. + * This is intended to be used if you wish to calculate your own tangents and use SetPerTriangleTangent() + */ + void InitializePerTriangleTangents(bool bClearToZero) + { + SetTangentCount(Mesh->MaxTriangleID() * 3, bClearToZero); + } + + + +protected: + /** + * Set the size of the Tangents array to Count, and optionally clear all values to (0,0,0) + */ + void SetTangentCount(int Count, bool bClearToZero); + + // Calculate per-triangle tangents + void Internal_ComputePerTriangleTangents(const FDynamicMeshNormalOverlay* NormalOverlay, const FDynamicMeshUVOverlay* UVOverlay); +}; + +typedef TMeshTangents FMeshTangentsf; +typedef TMeshTangents FMeshTangentsd; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshWeights.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshWeights.h new file mode 100644 index 000000000000..47ffc3feef23 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/MeshWeights.h @@ -0,0 +1,44 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp MeshWeights + +#pragma once + +#include "DynamicMesh3.h" + +/** + * FMeshWeights implements various techniques for computing local weights of a mesh, + * for example one-ring weights like Cotangent or Mean-Value. + */ +class DYNAMICMESH_API FMeshWeights +{ +public: + + /** + * Compute uniform centroid of a vertex one-ring. + * These weights are strictly positive and all equal to 1 / valence + */ + static FVector3d UniformCentroid(const FDynamicMesh3 & mesh, int VertexIndex); + + + /** + * Compute mean-value centroid of a vertex one-ring. + * These weights are strictly positive. + */ + static FVector3d MeanValueCentroid(const FDynamicMesh3 & mesh, int VertexIndex); + + /** + * Compute cotan-weighted centroid of a vertex one-ring. + * These weights are numerically unstable if any of the triangles are degenerate. + * We catch these problems and return input vertex as centroid + */ + static FVector3d CotanCentroid(const FDynamicMesh3 & mesh, int VertexIndex); + + /** + * Compute the voronoi area associated with a vertex. + */ + static double VoronoiArea(const FDynamicMesh3& mesh, int VertexIndex); + +protected: + FMeshWeights() = delete; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/ExtrudeMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/ExtrudeMesh.h new file mode 100644 index 000000000000..10b741f6f1c5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/ExtrudeMesh.h @@ -0,0 +1,98 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MergeCoincidentEdges + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" +#include "GeometryTypes.h" +#include "MeshBoundaryLoops.h" + + +class FDynamicMesh3; + +/** + * FExtrudeMesh implements a full-mesh extrusion of a mesh. This happens in two stages: + * 1) all triangles of input mesh are duplicated and offset + * 2) base and offset border loops are stitched together with triangulated quads + * Step 2 does not occur if there are no boundary loops (ie for a closed input mesh) + * + * Each quad of the border loop is assigned it's own normal and UVs (ie each is a separate UV-island) + */ +class DYNAMICMESH_API FExtrudeMesh +{ +public: + + // + // Inputs + // + + /** The mesh that we are modifying */ + FDynamicMesh3* Mesh; + + /** This function is called to generate the offset vertex position. Default returns (Position + DefaultExtrudeDistance * Normal) */ + TFunction ExtrudedPositionFunc; + + /** If no Extrude function is set, we will displace by DefaultExtrudeDistance*Normal */ + double DefaultExtrudeDistance = 1.0; + + /** if Extrusion is "negative" (ie negative distance, inset, etc) then this value must be set to false or the output will have incorrect winding orientation */ + bool IsPositiveOffset = true; + + /** quads on the stitch loop are planar-projected and scaled by this amount */ + float UVScaleFactor = 1.0f; + + + // + // Outputs + // + + /** Initial boundary loops on the mesh (may be empty) */ + FMeshBoundaryLoops InitialLoops; + /** set of triangles that were extruded */ + TArray InitialTriangles; + /** set of vertices that were extruded */ + TArray InitialVertices; + /** Map from initial vertices to new offset vertices */ + TMap InitialToOffsetMapV; + /** list of triangles on offset surface, in correspondence with InitialTriangles (note: can get vertices via InitialToOffsetMapV(InitialVertices) */ + TArray OffsetTriangles; + /** list of new groups of triangles on offset surface */ + TArray OffsetTriGroups; + + /** New edge loops on borders of offset patches (1-1 correspondence w/ InitialLoops.Loops) */ + TArray NewLoops; + /** Lists of triangle-strip "tubes" that connect each loop-pair */ + TArray> StitchTriangles; + /** List of group ids / polygon ids on each triangle-strip "tube" */ + TArray> StitchPolygonIDs; + + + + +public: + FExtrudeMesh(FDynamicMesh3* mesh); + + virtual ~FExtrudeMesh() {} + + + /** + * @return EOperationValidationResult::Ok if we can apply operation, or error code if we cannot + */ + virtual EOperationValidationResult Validate() + { + // @todo calculate MeshBoundaryLoops and make sure it is valid + + // is there any reason we couldn't do this?? + + return EOperationValidationResult::Ok; + } + + + /** + * Apply the Extrude operation to the input mesh. + * @return true if the algorithm succeeds + */ + virtual bool Apply(); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupTopologyDeformer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupTopologyDeformer.h new file mode 100644 index 000000000000..5e9974c553e7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/GroupTopologyDeformer.h @@ -0,0 +1,199 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "EdgeSpan.h" + +class FDynamicMesh3; +class FGroupTopology; + + + +/** + * Basic cache of vertex positions. + */ +class DYNAMICMESH_API FVertexPositionCache +{ +public: + TArray Vertices; + TArray Positions; + + void Reset() + { + Vertices.Reset(); + Positions.Reset(); + } + + /** Save this vertex. Does not check that VertexID hasn't already been added. */ + void AddVertex(const FDynamicMesh3* Mesh, int VertexID) + { + Vertices.Add(VertexID); + Positions.Add(Mesh->GetVertex(VertexID)); + } + + /** Apply saved positions to Mesh */ + void SetPositions(FDynamicMesh3* Mesh) + { + int Num = Vertices.Num(); + for (int k = 0; k < Num; ++k) + { + Mesh->SetVertex(Vertices[k], Positions[k]); + } + } +}; + + + +/** + * FGroupTopologyDeformer supports deforming a Mesh based on an overlaid FGroupTopology. + * First the client defines a set of "Handle" elements (Faces/Corners/Edges) using SetActiveHandleX(). + * The client will provide new vertex positions for the vertices of these elements via the + * HandleVertexDeformFunc argument to UpdateSolution(). Once the Handle vertices have been + * updated, the deformer solves for updated vertex positions in the GroupTopology Faces + * that are adjacent to the handles. This region is referred to as the "ROI" (Region-of-Interest) + * + * The default deformation is to first solve for the updated edges, and then + * solve for updated faces. This is done via linear encoding of the edge and face vertices + * relative to their boundaries (edge boundary is endpoint corners, face boundary is edges). + * + * Various functions can be overrided to customize behavior. + * + */ +class DYNAMICMESH_API FGroupTopologyDeformer +{ +public: + virtual ~FGroupTopologyDeformer() {} + + /** Set the Mesh and Topology to use for the deformation */ + void Initialize(const FDynamicMesh3* Mesh, const FGroupTopology* Topology); + + const FDynamicMesh3* GetMesh() const { return Mesh; } + const FGroupTopology* GetTopology() const { return Topology; } + + // + // Handle setup/configuraiton + // + + /** + * Set the active handle to the given Faces + */ + virtual void SetActiveHandleFaces(const TArray& FaceGroupIDs); + + /** + * Set the active handle to the given Corners + */ + virtual void SetActiveHandleCorners(const TArray& TopologyCornerIDs); + + /** + * Set the active handle to the given Edges + */ + virtual void SetActiveHandleEdges(const TArray& TopologyEdgeIDs); + + // + // Solving + // + + /** + * Update TargetMesh by first calling HandleVertexDeformFunc() to get new + * handle vertex positions, then solving for new ROI vertex positions. + */ + virtual void UpdateSolution(FDynamicMesh3* TargetMesh, const TFunction& HandleVertexDeformFunc); + + /** + * Restore the Handle and ROI vertex positions to their initial state + */ + virtual void ClearSolution(FDynamicMesh3* TargetMesh); + + + + /** @return the set of handle vertices */ + const TSet& GetHandleVertices() const { return HandleVertices; } + /** @return the set of all vertices that will be modified by the deformation*/ + const TSet& GetModifiedVertices() const { return ModifiedVertices; } + + +protected: + // + // These are functions that subclasses may wish to override + // + + /** reset all internal data structures, eg when changing handle */ + virtual void Reset(); + + /** + * Populate the internal ROI data structures based on the given HandleGroups and ROIGroups. + * HandleGroups will be empty if the Handle is a set of Vertices or Edges. + */ + virtual void CalculateROI(const TArray& HandleGroups, const TArray& ROIGroups); + + /** + * Save the positions of all vertices that will be modified + */ + virtual void SaveInitialPositions(); + + /** + * Precompute the representation of the ROI vertices at the initial positions. + */ + virtual void ComputeEncoding(); + +protected: + const FDynamicMesh3* Mesh = nullptr; + const FGroupTopology* Topology = nullptr; + + FVertexPositionCache InitialPositions; + TSet ModifiedVertices; + TSet HandleVertices; + TSet HandleBoundaryVertices; + TSet FixedBoundaryVertices; + TSet ROIEdgeVertices; + TSet FaceVertsTemp; + TSet FaceBoundaryVertsTemp; + + struct FROIEdge + { + int EdgeIndex; + FEdgeSpan Span; + }; + TArray ROIEdges; + + struct FROIFace + { + TArray BoundaryVerts; + TArray InteriorVerts; + }; + TArray ROIFaces; + + + // + // Deformation strategy (should be in subclass?) + // + + struct FEdgeVertexEncoding + { + double T; + FVector3d Delta; + FEdgeVertexEncoding() { T = 0; Delta = FVector3d::Zero(); } + }; + struct FEdgeEncoding + { + TArray Vertices; + }; + TArray EdgeEncodings; + + + + struct FFaceVertexEncoding + { + TArray Weights; + TArray Deltas; + }; + struct FFaceEncoding + { + TArray Vertices; + }; + TArray FaceEncodings; + + + + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MergeCoincidentMeshEdges.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MergeCoincidentMeshEdges.h new file mode 100644 index 000000000000..c6961cc403a5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Operations/MergeCoincidentMeshEdges.h @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp MergeCoincidentEdges + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" + +class FDynamicMesh3; + +/** + * FMergeCoincidentMeshEdges finds pairs of boundary edges of the mesh that are identical + * (ie have endpoint vertices at the same locations) and merges the pair into a single edge. + * This is similar to welding vertices but safer because it prevents bowties from being formed. + * + * Currently if the two edges have the same "orientation" (ie from their respective triangles) + * they cannot be merged. + * + */ +class DYNAMICMESH_API FMergeCoincidentMeshEdges +{ +public: + /** default tolerance is float ZeroTolerance */ + static const double DEFAULT_TOLERANCE; // = FMathf::ZeroTolerance; + + /** The mesh that we are modifying */ + FDynamicMesh3* Mesh; + + /** Edges are coincident if both pairs of endpoint vertices are closer than this distance */ + double MergeVertexTolerance = DEFAULT_TOLERANCE; + + /** Only merge unambiguous pairs that have unique duplicate-edge matches */ + bool OnlyUniquePairs = false; + + /** + * Edges are considered as potentially the same if their midpoints are within this distance. + * Due to floating-point roundoff this should be larger than MergeVertexTolerance. + * If zero, we set to MergeVertexTolerance*2 + */ + double MergeSearchTolerance = 0; + +public: + FMergeCoincidentMeshEdges(FDynamicMesh3* mesh) : Mesh(mesh) + { + } + + /** + * Run the merge operation and modify .Mesh + * @return true if the algorithm succeeds + */ + virtual bool Apply(); + + +protected: + double MergeVtxDistSqr; // cached value + + // returns true if endpoint vertices are within tolerance. Note that we do not know the order of + // the vertices here so we try both combinations. + inline bool IsSameEdge(const FVector3d& a, const FVector3d& b, const FVector3d& c, const FVector3d& d) const + { + return (a.DistanceSquared(c) < MergeVtxDistSqr && b.DistanceSquared(d) < MergeVtxDistSqr) || + (a.DistanceSquared(d) < MergeVtxDistSqr && b.DistanceSquared(c) < MergeVtxDistSqr); + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ProjectionTargets.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ProjectionTargets.h new file mode 100644 index 000000000000..abcd3bf382f2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/ProjectionTargets.h @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// Port of geometry3cpp ProjectionTargets + +#include "DynamicMeshAABBTree3.h" +#include "Distance/DistPoint3Triangle3.h" + + +/** + * MeshProjectionTarget provides an IProjectionTarget interface to a FDynamicMesh + FDynamicMeshAABBTree3 + * Use to project points to mesh surface. + */ +class MeshProjectionTarget : public IOrientedProjectionTarget +{ +public: + /** The mesh to project onto */ + FDynamicMesh3* Mesh; + /** An AABBTree for Mesh */ + FDynamicMeshAABBTree3* Spatial; + + ~MeshProjectionTarget() {} + + MeshProjectionTarget(FDynamicMesh3* MeshIn, FDynamicMeshAABBTree3* SpatialIn) + { + Mesh = MeshIn; + Spatial = SpatialIn; + } + + + /** + * @return Projection of Point onto this target + */ + virtual FVector3d Project(const FVector3d& Point, int Identifier = -1) override + { + double fDistSqr; + int tNearestID = Spatial->FindNearestTriangle(Point, fDistSqr); + FTriangle3d Triangle; + Mesh->GetTriVertices(tNearestID, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + FDistPoint3Triangle3d DistanceQuery(Point, Triangle); + DistanceQuery.GetSquared(); + return DistanceQuery.ClosestTrianglePoint; + } + + /** + * @return Projection of Point onto this target, and set ProjectNormalOut to the triangle normal at the returned point (*not* interpolated vertex normal) + */ + virtual FVector3d Project(const FVector3d& Point, FVector3d& ProjectNormalOut, int Identifier = -1) override + { + double fDistSqr; + int tNearestID = Spatial->FindNearestTriangle(Point, fDistSqr); + FTriangle3d Triangle; + Mesh->GetTriVertices(tNearestID, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + ProjectNormalOut = Triangle.Normal(); + + FDistPoint3Triangle3d DistanceQuery(Point, Triangle); + DistanceQuery.GetSquared(); + return DistanceQuery.ClosestTrianglePoint; + } + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Remesher.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Remesher.h new file mode 100644 index 000000000000..3938dc61ad60 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/Remesher.h @@ -0,0 +1,343 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp Remesher + +#pragma once + +#include "MeshRefinerBase.h" + + +/** + * FRemesher implements edge flip/split/collapse/smooth Remeshing. + * The basic process is very similar to the one described in: + * A Remeshing Approach to Multiresolution Modeling. Botsch & Kobbelt, SGP 2004. + * https://graphics.uni-bielefeld.de/publications/sgp04.pdf + * + * In the iterative usage, each call to BasicRemeshPass() makes a pass over the mesh + * and attempts to update the topology and shape to satisfy the target criteria. + * This function iterates over the mesh edges and calls ProcessEdge(), which tries to + * Collapse, then Split, then Flip the edge. After the topology pass, we optionally + * do a full-mesh Smoothing pass, and a full-mesh Reprojection pass, to project + * onto a target surface. + * + * A highly flexible constraint system can be used to control what is allowed to + * happen to individual edges and vertices, as well as Project vertices onto arbitrary + * proejction targets. This is done via FMeshConstraints which is set in the MeshRefinerBase base class. + * + * Many aspects of the algorithm can be controlled by the public variables in the block below. + * In addition many of the core internal functions can be overriden to customize behavior. + * Various callbacks functions are called when topology changes occur which allows + * you to for instance track changed regions of the mesh. + * + * @todo parallel smoothing and projection + * @todo port fast queue-based convergent remeshing + * + */ +class DYNAMICMESH_API FRemesher : public FMeshRefinerBase +{ +public: + // + // Configuration variables and flags + // + + /** Controls whether edge-flips are allowed during remeshing pass */ + bool bEnableFlips = true; + /** Controls whether edge collapses are allowed during remeshing pass */ + bool bEnableCollapses = true; + /** Controls whether edge splits are allowed during remeshing pass */ + bool bEnableSplits = true; + /** Controls whether vertex smoothing is applied during remeshing pass */ + bool bEnableSmoothing = true; + + /** Controls whether we try to prevent normal flips inside operations and smoothing. + * This is moderately expensive and often is not needed. Disabled by default. + */ + bool bPreventNormalFlips = false; + + /** Minimum target edge length. Edges shorter than this will be collapsed if possible. */ + double MinEdgeLength = 1.0; + /** Maximum target edge length. Edges longer than this will be split if possible. */ + double MaxEdgeLength = 3.0; + + /** Smoothing speed, in range [0,1] */ + double SmoothSpeedT = 0.1; + + /** Built-in Smoothing types */ + enum class ESmoothTypes + { + Uniform = 0, /** Uniform weights, produces regular mesh and fastest convergence */ + Cotan = 1, /** Cotangent weights prevent tangential flow and hence preserve triangle shape / texture coordinates, but can become unstable... */ + MeanValue = 2 /** Mean Value weights also have reduced tangential flow but are never negative and hence more stable */ + }; + + /** Type of smoothing that will be applied unless overridden by CustomSmoothF */ + ESmoothTypes SmoothType = ESmoothTypes::Uniform; + + /** Override default smoothing function */ + TFunction CustomSmoothF; + + + + /** enable parallel projection. Only applied in AfterRefinement mode */ + bool bEnableParallelProjection = true; + + /** + * Enable parallel smoothing. This will produce slightly different results + * across runs because we smooth in-place and hence there will be order side-effects. + */ + bool bEnableParallelSmooth = true; + + /** + * If smoothing is done in-place, we don't need an extra buffer, but also there will some randomness introduced in results. Probably worse. + */ + bool bEnableSmoothInPlace = false; + + + +public: + // + // Public API + // + + FRemesher(FDynamicMesh3* MeshIn) : FMeshRefinerBase(MeshIn) + { + } + + + /** Set min/max edge-lengths to sane values for given target edge length */ + void SetTargetEdgeLength(double fLength); + + + /** + * We can speed things up if we precompute some invariants. + * You need to re-run this if you are changing the mesh externally + * between remesh passes, otherwise you will get weird results. + * But you will probably still come out ahead, computation-time-wise + */ + virtual void Precompute(); + + + /** + * Linear edge-refinement pass, followed by smoothing and projection + * - Edges are processed in prime-modulo-order to break symmetry + * - smoothing is done in parallel if EnableParallelSmooth = true + * - Projection pass if ProjectionMode == AfterRefinement + * - number of modified edges returned in ModifiedEdgesLastPass + */ + virtual void BasicRemeshPass(); + + + +public: + // + // Callbacks and in-progress information + // + + + /** Callback for subclasses to override to implement custom behavior */ + virtual void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) + { + // this is for subclasses... + } + + /** Callback for subclasses to override to implement custom behavior */ + virtual void OnEdgeCollapse(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo) + { + // this is for subclasses... + } + + /** + * Number of edges that were modified in previous Remesh pass. + * If this number gets small relative to edge count, you have probably converged (ish) + */ + int ModifiedEdgesLastPass = 0; + + +protected: + + FRemesher() // for subclasses that extend our behavior + { + } + + /** we can avoid some checks/etc if we know that the mesh has no boundary edges. Set by Precompute() */ + bool bMeshIsClosed = false; + + // this just lets us write more concise code + bool EnableInlineProjection() const { return ProjectionMode == ETargetProjectionMode::Inline; } + + + // StartEdges() and GetNextEdge() control the iteration over edges that will be refined. + // Default here is to iterate over entire mesh-> + // Subclasses can override these two functions to restrict the affected edges (eg EdgeLoopFRemesher) + + + // We are using a modulo-index loop to break symmetry/pathological conditions. + // For example in a highly tessellated minimal cylinder, if the top/bottom loops have + // sequential edge IDs, and all edges are < min edge length, then we can easily end + // up successively collapsing each tiny edge, and eroding away the entire mesh! + // By using modulo-index loop we jump around and hence this is unlikely to happen. + const int ModuloPrime = 31337; // any prime will do... + int MaxEdgeID = 0; + virtual int StartEdges() + { + MaxEdgeID = Mesh->MaxEdgeID(); + return 0; + } + + virtual int GetNextEdge(int CurEdgeID, bool& bDone) + { + int new_eid = (CurEdgeID + ModuloPrime) % MaxEdgeID; + bDone = (new_eid == 0); + return new_eid; + } + + + + + + enum class EProcessResult + { + Ok_Collapsed, + Ok_Flipped, + Ok_Split, + Ignored_EdgeIsFine, + Ignored_EdgeIsFullyConstrained, + Failed_OpNotSuccessful, + Failed_NotAnEdge + }; + + virtual EProcessResult ProcessEdge(int edgeID); + + + + // After we split an edge, we have created a new edge and a new vertex. + // The edge needs to inherit the constraint on the other pre-existing edge that we kept. + // In addition, if the edge vertices were both constrained, then we /might/ + // want to also constrain this new vertex, possibly project to constraint target. + virtual void UpdateAfterSplit(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeSplitInfo& splitInfo); + + + + TDynamicVector TempPosBuffer; // this is a temporary buffer used by smoothing and projection + TArray TempFlagBuffer; // list of which indices in TempPosBuffer were modified + + virtual void InitializeVertexBufferForPass(); + + virtual void ApplyVertexBuffer(bool bParallel); + + + + + virtual void FullSmoothPass_Buffer(bool bParallel); + + /** computes smoothed vertex position w/ proper constraints/etc. Does not modify mesh. */ + virtual FVector3d ComputeSmoothedVertexPos(int VertexID, + TFunction SmoothFunc, bool& bModified); + + virtual void ApplyToSmoothVertices(const TFunction& VertexSmoothFunc); + + + + + // Project vertices onto projection target. + virtual void FullProjectionPass(); + + virtual void ProjectVertex(int VertexID, IProjectionTarget* UseTarget); + + // used by collapse-edge to get projected position for new vertex + virtual FVector3d GetProjectedCollapsePosition(int VertexID, const FVector3d& vNewPos); + + virtual void ApplyToProjectVertices(const TFunction& VertexProjectFunc); + + + /* + * testing/debug/profiling stuff + */ +protected: + + // + // profiling functions, turn on ENABLE_PROFILING to see output in console + // + int COUNT_SPLITS, COUNT_COLLAPSES, COUNT_FLIPS; + //Stopwatch AllOpsW, SmoothW, ProjectW, FlipW, SplitW, CollapseW; + + virtual void ProfileBeginPass() + { + if (ENABLE_PROFILING) + { + COUNT_SPLITS = COUNT_COLLAPSES = COUNT_FLIPS = 0; + //AllOpsW = new Stopwatch(); + //SmoothW = new Stopwatch(); + //ProjectW = new Stopwatch(); + //FlipW = new Stopwatch(); + //SplitW = new Stopwatch(); + //CollapseW = new Stopwatch(); + } + } + + virtual void ProfileEndPass() + { + if (ENABLE_PROFILING) { + //System.Console.WriteLine(string.Format( + // "RemeshPass: T {0} V {1} splits {2} flips {3} collapses {4}", mesh->TriangleCount, mesh->VertexCount, COUNT_SPLITS, COUNT_FLIPS, COUNT_COLLAPSES + // )); + //System.Console.WriteLine(string.Format( + // " Timing1: ops {0} smooth {1} project {2}", Util.ToSecMilli(AllOpsW.Elapsed), Util.ToSecMilli(SmoothW.Elapsed), Util.ToSecMilli(ProjectW.Elapsed) + // )); + //System.Console.WriteLine(string.Format( + // " Timing2: collapse {0} flip {1} split {2}", Util.ToSecMilli(CollapseW.Elapsed), Util.ToSecMilli(FlipW.Elapsed), Util.ToSecMilli(SplitW.Elapsed) + // )); + } + } + + virtual void ProfileBeginOps() + { + //if ( ENABLE_PROFILING ) AllOpsW.Start(); + } + virtual void ProfileEndOps() + { + //if ( ENABLE_PROFILING ) AllOpsW.Stop(); + } + virtual void ProfileBeginSmooth() + { + //if ( ENABLE_PROFILING ) SmoothW.Start(); + } + virtual void ProfileEndSmooth() + { + //if ( ENABLE_PROFILING ) SmoothW.Stop(); + } + virtual void ProfileBeginProject() + { + //if ( ENABLE_PROFILING ) ProjectW.Start(); + } + virtual void ProfileEndProject() + { + //if ( ENABLE_PROFILING ) ProjectW.Stop(); + } + + virtual void ProfileBeginCollapse() + { + //if ( ENABLE_PROFILING ) CollapseW.Start(); + } + virtual void ProfileEndCollapse() + { + //if ( ENABLE_PROFILING ) CollapseW.Stop(); + } + virtual void ProfileBeginFlip() + { + //if ( ENABLE_PROFILING ) FlipW.Start(); + } + virtual void ProfileEndFlip() + { + //if ( ENABLE_PROFILING ) FlipW.Stop(); + } + virtual void ProfileBeginSplit() + { + //if ( ENABLE_PROFILING ) SplitW.Start(); + } + virtual void ProfileEndSplit() + { + //if ( ENABLE_PROFILING ) SplitW.Stop(); + } + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h new file mode 100644 index 000000000000..5b71e3f43983 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h @@ -0,0 +1,96 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Remesher.h" +#include "Util/BufferUtil.h" + +/** + * FSubRegionRemesher is an extension + */ +class FSubRegionRemesher : public FRemesher +{ +protected: + // temp buffer of edges connected to VertexROI + TSet EdgeROI; + // list of edges to consider during current remesh pass (not updated during pass) + TArray Edges; + // current edge we are at in StartEdges/GetNextEdge iteration + int CurEdge; + +public: + FSubRegionRemesher(FDynamicMesh3* Mesh) : FRemesher(Mesh) + { + VertexControlF = [this](int vid) { + return this->VertexFilter(vid); + }; + } + + /** + * Set of vertices in ROI. You add vertices here initially, then + * we will update the list during each Remesh pass + */ + TSet VertexROI; + + + /** + * Must call UpdateROI before each remesh pass. + */ + void UpdateROI() + { + EdgeROI.Reset(); + for (int VertIdx : VertexROI) + { + for (int eid : GetMesh()->VtxEdgesItr(VertIdx)) + { + EdgeROI.Add(eid); + } + } + Edges.Reset(); + BufferUtil::AppendElements(Edges, EdgeROI); + //Edges = std::vector(EdgeROI.begin(), EdgeROI.end()); + } + + + + // + // specialization of Remesher functionality + // +protected: + virtual int StartEdges() override + { + CurEdge = 0; + return (Edges.Num() > 0) ? Edges[CurEdge] : IndexConstants::InvalidID; + } + + virtual int GetNextEdge(int CurEdgeID, bool& bDone) override + { + CurEdge++; + if (CurEdge >= Edges.Num()) + { + bDone = true; + return -1; + } + else + { + bDone = false; + return Edges[CurEdge]; + } + } + + virtual void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) override + { + VertexROI.Add(SplitInfo.NewVertex); + } + + virtual void OnEdgeCollapse(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo) override + { + VertexROI.Remove(CollapseInfo.RemovedVertex); + } + + EVertexControl VertexFilter(int VertexID) + { + return (VertexROI.Contains(VertexID) == false) ? EVertexControl::NoMovement : EVertexControl::AllowAll; + } + +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/GeometricObjects.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/GeometricObjects.Build.cs new file mode 100644 index 000000000000..08c95e93e1df --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/GeometricObjects.Build.cs @@ -0,0 +1,17 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class GeometricObjects : ModuleRules +{ + public GeometricObjects(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/CompGeom/PolygonTriangulation.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/CompGeom/PolygonTriangulation.cpp new file mode 100644 index 000000000000..9ca402e691d5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/CompGeom/PolygonTriangulation.cpp @@ -0,0 +1,157 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CompGeom/PolygonTriangulation.h" +#include "TriangleTypes.h" + + +// explicit instantiations +namespace PolygonTriangulation +{ + template GEOMETRICOBJECTS_API void TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles); + template GEOMETRICOBJECTS_API void TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles); +} + + + + +// +// Triangulate using ear clipping +// This is based on the 3D triangulation code from MeshDescription.cpp, simplified for 2D polygons +// +template +void PolygonTriangulation::TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles) +{ + struct Local + { + static inline bool IsTriangleFlipped(T OrientationSign, const FVector2& VertexPositionA, const FVector2& VertexPositionB, const FVector2& VertexPositionC) + { + T TriSignedArea = TTriangle2::SignedArea(VertexPositionA, VertexPositionB, VertexPositionC); + return TriSignedArea * OrientationSign < 0; + } + }; + + // Polygon must have at least three vertices/edges + int32 PolygonVertexCount = VertexPositions.Num(); + check(PolygonVertexCount >= 3); + + + // compute signed area of polygon + double PolySignedArea = 0; + for (int i = 0; i < PolygonVertexCount; ++i) + { + const FVector2& v1 = VertexPositions[i]; + const FVector2& v2 = VertexPositions[(i + 1) % PolygonVertexCount]; + PolySignedArea += v1.X*v2.Y - v1.Y*v2.X; + } + PolySignedArea *= 0.5; + bool bIsClockwise = PolySignedArea < 0; + double OrientationSign = (bIsClockwise) ? -1.0 : 1.0; + + + OutTriangles.Reset(); + + + // If perimeter has 3 vertices, just copy content of perimeter out + if (PolygonVertexCount == 3) + { + OutTriangles.Add(FIndex3i(0, 1, 2)); + return; + } + + // Make a simple linked list array of the previous and next vertex numbers, for each vertex number + // in the polygon. This will just save us having to iterate later on. + static TArray PrevVertexNumbers, NextVertexNumbers; + + PrevVertexNumbers.SetNumUninitialized(PolygonVertexCount, false); + NextVertexNumbers.SetNumUninitialized(PolygonVertexCount, false); + + for (int32 VertexNumber = 0; VertexNumber < PolygonVertexCount; ++VertexNumber) + { + PrevVertexNumbers[VertexNumber] = VertexNumber - 1; + NextVertexNumbers[VertexNumber] = VertexNumber + 1; + } + PrevVertexNumbers[0] = PolygonVertexCount - 1; + NextVertexNumbers[PolygonVertexCount - 1] = 0; + + + int32 EarVertexNumber = 0; + int32 EarTestCount = 0; + for (int32 RemainingVertexCount = PolygonVertexCount; RemainingVertexCount >= 3; ) + { + bool bIsEar = true; + + // If we're down to only a triangle, just treat it as an ear. Also, if we've tried every possible candidate + // vertex looking for an ear, go ahead and just treat the current vertex as an ear. This can happen when + // vertices are collinear or other degenerate cases. + if (RemainingVertexCount > 3 && EarTestCount < RemainingVertexCount) + { + const FVector2& PrevVertexPosition = VertexPositions[PrevVertexNumbers[EarVertexNumber]]; + const FVector2& EarVertexPosition = VertexPositions[EarVertexNumber]; + const FVector2& NextVertexPosition = VertexPositions[NextVertexNumbers[EarVertexNumber]]; + + // Figure out whether the potential ear triangle is facing the same direction as the polygon + // itself. If it's facing the opposite direction, then we're dealing with a concave triangle + // and we'll skip it for now. + if (!Local::IsTriangleFlipped( + OrientationSign, PrevVertexPosition, EarVertexPosition, NextVertexPosition)) + { + int32 TestVertexNumber = NextVertexNumbers[NextVertexNumbers[EarVertexNumber]]; + + do + { + // Test every other remaining vertex to make sure that it doesn't lie inside our potential ear + // triangle. If we find a vertex that's inside the triangle, then it cannot actually be an ear. + const FVector2& TestVertexPosition = VertexPositions[TestVertexNumber]; + if (TTriangle2::IsInside(PrevVertexPosition, EarVertexPosition, NextVertexPosition, TestVertexPosition)) + { + bIsEar = false; + break; + } + + TestVertexNumber = NextVertexNumbers[TestVertexNumber]; + } while (TestVertexNumber != PrevVertexNumbers[EarVertexNumber]); + } + else + { + bIsEar = false; + } + } + + if (bIsEar) + { + // OK, we found an ear! Let's save this triangle in our output buffer. + { + OutTriangles.Emplace(); + FIndex3i& Triangle = OutTriangles.Last(); + Triangle.A = PrevVertexNumbers[EarVertexNumber]; + Triangle.B = EarVertexNumber; + Triangle.C = NextVertexNumbers[EarVertexNumber]; + } + + // Update our linked list. We're effectively cutting off the ear by pointing the ear vertex's neighbors to + // point at their next sequential neighbor, and reducing the remaining vertex count by one. + { + NextVertexNumbers[PrevVertexNumbers[EarVertexNumber]] = NextVertexNumbers[EarVertexNumber]; + PrevVertexNumbers[NextVertexNumbers[EarVertexNumber]] = PrevVertexNumbers[EarVertexNumber]; + --RemainingVertexCount; + } + + // Move on to the previous vertex in the list, now that this vertex was cut + EarVertexNumber = PrevVertexNumbers[EarVertexNumber]; + + EarTestCount = 0; + } + else + { + // The vertex is not the ear vertex, because it formed a triangle that either had a normal which pointed in the opposite direction + // of the polygon, or at least one of the other polygon vertices was found to be inside the triangle. Move on to the next vertex. + EarVertexNumber = NextVertexNumbers[EarVertexNumber]; + + // Keep track of how many ear vertices we've tested, so that if we exhaust all remaining vertices, we can + // fall back to clipping the triangle and adding it to our mesh anyway. This is important for degenerate cases. + ++EarTestCount; + } + } + + check(OutTriangles.Num() > 0); +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/GeometricObjectsModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/GeometricObjectsModule.cpp new file mode 100644 index 000000000000..ab24447d7439 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/GeometricObjectsModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GeometricObjectsModule.h" + +#define LOCTEXT_NAMESPACE "FGeometricObjectsModule" + +void FGeometricObjectsModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FGeometricObjectsModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FGeometricObjectsModule, GeometricObjects) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/IndexUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/IndexUtil.cpp new file mode 100644 index 000000000000..c795f44ad45e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/IndexUtil.cpp @@ -0,0 +1,57 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Util/IndexUtil.h" + + +// integer indices offsets in x/y/z directions and diagonals +const FVector3i IndexUtil::GridOffsets26[] = +{ + // face-nbrs + FVector3i(0, 0,-1), FVector3i(0, 0, 1), + FVector3i(-1, 0, 0), FVector3i(1, 0, 0), + FVector3i(0,-1, 0), FVector3i(0, 1, 0), + // edge-nbrs (+y, 0, -y) + FVector3i(1, 1, 0), FVector3i(-1, 1, 0), + FVector3i(0, 1, 1), FVector3i(0, 1,-1), + FVector3i(1, 0, 1), FVector3i(-1, 0, 1), + FVector3i(1, 0,-1), FVector3i(-1, 0,-1), + FVector3i(1, -1, 0), FVector3i(-1,-1, 0), + FVector3i(0, -1, 1), FVector3i(0,-1,-1), + // corner-nbrs (+y,-y) + FVector3i(1, 1, 1), FVector3i(-1, 1, 1), + FVector3i(1, 1,-1), FVector3i(-1, 1,-1), + FVector3i(1,-1, 1), FVector3i(-1,-1, 1), + FVector3i(1,-1,-1), FVector3i(-1,-1,-1) +}; + + + + +// corners [ (-x,-y), (x,-y), (x,y), (-x,y) ], -z, then +z +// +// 7---6 +z or 3---2 -z +// |\ |\ |\ |\ +// 4-\-5 \ 0-\-1 \ +// \ 3---2 \ 7---6 +// \| | \| | +// 0---1 -z 4---5 +z +// + +const int IndexUtil::BoxFaces[6][4] = +{ + { 0, 1, 2, 3 }, // back, -z + { 5, 4, 7, 6 }, // front, +z + { 4, 0, 3, 7 }, // left, -x + { 1, 5, 6, 2 }, // right, +x, + { 4, 5, 1, 0 }, // bottom, -y + { 3, 2, 6, 7 } // top, +y +}; + + +static const FVector2i UV00(0, 0); +static const FVector2i UV10(1, 0); +static const FVector2i UV11(1, 1); +static const FVector2i UV01(0, 1); +const FVector2i IndexUtil::BoxFacesUV[4] = { UV00, UV10, UV11, UV01 }; + +const int IndexUtil::BoxFaceNormals[6] = { -3, 3, -1, 1, -2, 2 }; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/MathUtil.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/MathUtil.cpp new file mode 100644 index 000000000000..6b25e564ef5b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/MathUtil.cpp @@ -0,0 +1,50 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MathUtil.h" + +#include +#include + +#include "Math/UnrealMath.h" + +template<> GEOMETRICOBJECTS_API const float TMathUtil::Epsilon = FLT_EPSILON; +template<> GEOMETRICOBJECTS_API const float TMathUtil::ZeroTolerance = 1e-06f; +template<> GEOMETRICOBJECTS_API const float TMathUtil::MaxReal = FLT_MAX; +template<> GEOMETRICOBJECTS_API const float TMathUtil::Pi = (float)(4.0*atan(1.0)); +template<> GEOMETRICOBJECTS_API const float TMathUtil::FourPi = 4.0f*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::TwoPi = 2.0f*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::HalfPi = 0.5f*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvPi = 1.0f / TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvTwoPi = 1.0f / TMathUtil::TwoPi; +template<> GEOMETRICOBJECTS_API const float TMathUtil::DegToRad = TMathUtil::Pi / 180.0f; +template<> GEOMETRICOBJECTS_API const float TMathUtil::RadToDeg = 180.0f / TMathUtil::Pi; +//template<> const float TMathUtil::LN_2 = TMathUtil::Log(2.0f); +//template<> const float TMathUtil::LN_10 = TMathUtil::Log(10.0f); +//template<> const float TMathUtil::INV_LN_2 = 1.0f / TMathUtil::LN_2; +//template<> const float TMathUtil::INV_LN_10 = 1.0f / TMathUtil::LN_10; +template<> GEOMETRICOBJECTS_API const float TMathUtil::Sqrt2 = (float)(sqrt(2.0)); +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvSqrt2 = 1.0f / TMathUtil::Sqrt2; +template<> GEOMETRICOBJECTS_API const float TMathUtil::Sqrt3 = (float)(sqrt(3.0)); +template<> GEOMETRICOBJECTS_API const float TMathUtil::InvSqrt3 = 1.0f / TMathUtil::Sqrt3; + +template<> GEOMETRICOBJECTS_API const double TMathUtil::Epsilon = DBL_EPSILON; +template<> GEOMETRICOBJECTS_API const double TMathUtil::ZeroTolerance = 1e-08; +template<> GEOMETRICOBJECTS_API const double TMathUtil::MaxReal = DBL_MAX; +template<> GEOMETRICOBJECTS_API const double TMathUtil::Pi = 4.0*atan(1.0); +template<> GEOMETRICOBJECTS_API const double TMathUtil::FourPi = 4.0*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::TwoPi = 2.0*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::HalfPi = 0.5*TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvPi = 1.0 / TMathUtil::Pi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvTwoPi = 1.0 / TMathUtil::TwoPi; +template<> GEOMETRICOBJECTS_API const double TMathUtil::DegToRad = TMathUtil::Pi / 180.0; +template<> GEOMETRICOBJECTS_API const double TMathUtil::RadToDeg = 180.0 / TMathUtil::Pi; +//template<> const double TMathUtil::LN_2 = TMathUtil::Log(2.0); +//template<> const double TMathUtil::LN_10 = TMathUtil::Log(10.0); +//template<> const double TMathUtil::INV_LN_2 = 1.0 / TMathUtil::LN_2; +//template<> const double TMathUtil::INV_LN_10 = 1.0 / TMathUtil::LN_10; +template<> GEOMETRICOBJECTS_API const double TMathUtil::Sqrt2 = sqrt(2.0); +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvSqrt2 = 1.0f / TMathUtil::Sqrt2; +template<> GEOMETRICOBJECTS_API const double TMathUtil::Sqrt3 = sqrt(3.0); +template<> GEOMETRICOBJECTS_API const double TMathUtil::InvSqrt3 = 1.0f / TMathUtil::Sqrt3; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp new file mode 100644 index 000000000000..0c462f39dcfd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/GeometrySet3.cpp @@ -0,0 +1,169 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Spatial/GeometrySet3.h" +#include "Distance/DistRay3Segment3.h" +#include "Async/ParallelFor.h" + + +void FGeometrySet3::Reset(bool bPoints, bool bCurves) +{ + if (bPoints) + { + Points.Reset(); + } + if (bCurves) + { + Curves.Reset(); + } +} + + + +void FGeometrySet3::AddPoint(int PointID, const FVector3d& Point) +{ + check(PointIDToIndex.Contains(PointID) == false); + FPoint NewPoint = { PointID, Point }; + int NewIndex = Points.Add(NewPoint); + PointIDToIndex.Add(PointID, NewIndex); +} + +void FGeometrySet3::AddCurve(int CurveID, const FPolyline3d& Polyline) +{ + check(CurveIDToIndex.Contains(CurveID) == false); + int NewIndex = Curves.Add(FCurve()); + FCurve& NewCurve = Curves[NewIndex]; + NewCurve.ID = CurveID; + NewCurve.Geometry = Polyline; + NewCurve.Bounds = Polyline.GetBounds(); + CurveIDToIndex.Add(CurveID, NewIndex); +} + + +void FGeometrySet3::UpdatePoint(int PointID, const FVector3d& Point) +{ + const int* Index = PointIDToIndex.Find(PointID); + check(Index != nullptr); + Points[*Index].Position = Point; +} + +void FGeometrySet3::UpdateCurve(int CurveID, const FPolyline3d& Polyline) +{ + const int* Index = CurveIDToIndex.Find(CurveID); + check(Index != nullptr); + Curves[*Index].Geometry.SetVertices(Polyline.GetVertices()); + Curves[*Index].Bounds = Polyline.GetBounds(); +} + + + +bool FGeometrySet3::FindNearestPointToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const +{ + double MinRayParamT = TNumericLimits::Max(); + int NearestID = -1; + int NearestIndex = -1; + + FCriticalSection Critical; + + int NumPoints = Points.Num(); + ParallelFor(NumPoints, [&](int pi) + { + const FPoint& Point = Points[pi]; + double RayT = Ray.Project(Point.Position); + FVector3d RayPt = Ray.NearestPoint(Point.Position); + if (PointWithinToleranceTest(RayPt, Point.Position)) + { + Critical.Lock(); + if (RayT < MinRayParamT) + { + MinRayParamT = RayT; + NearestIndex = pi; + NearestID = Points[pi].ID; + } + Critical.Unlock(); + } + }); + + if (NearestID != -1) + { + ResultOut.ID = NearestID; + ResultOut.bIsPoint = true; + ResultOut.NearestRayPoint = Ray.PointAt(MinRayParamT); + ResultOut.NearestGeoPoint = Points[NearestIndex].Position; + ResultOut.RayParam = MinRayParamT; + } + + return NearestID != -1; +} + + + + + +bool FGeometrySet3::FindNearestCurveToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const +{ + double MinRayParamT = TNumericLimits::Max(); + int NearestID = -1; + int NearestIndex = -1; + int NearestSegmentIdx = -1; + double NearestSegmentParam = 0; + + FCriticalSection Critical; + + int NumCurves = Curves.Num(); + ParallelFor(NumCurves, [&](int ci) + { + const FCurve& Curve = Curves[ci]; + const FPolyline3d& Polyline = Curve.Geometry; + int NumSegments = Polyline.SegmentCount(); + + double CurveMinRayParamT = TNumericLimits::Max(); + int CurveNearestSegmentIdx = -1; + double CurveNearestSegmentParam = 0; + for (int si = 0; si < NumSegments; ++si) + { + double SegRayParam; double SegSegParam; + double SegDistSqr = FDistRay3Segment3d::SquaredDistance(Ray, Polyline.GetSegment(si), SegRayParam, SegSegParam); + if (SegRayParam < CurveMinRayParamT) + { + FVector3d RayPosition = Ray.PointAt(SegRayParam); + FVector3d CurvePosition = Polyline.GetSegmentPoint(si, SegSegParam); + if (PointWithinToleranceTest(RayPosition, CurvePosition)) + { + CurveMinRayParamT = SegRayParam; + CurveNearestSegmentIdx = si; + CurveNearestSegmentParam = SegSegParam; + } + } + } + + // if we found a possible point, lock outer structures and merge in + if (CurveMinRayParamT < TNumericLimits::Max()) + { + Critical.Lock(); + if (CurveMinRayParamT < MinRayParamT) + { + MinRayParamT = CurveMinRayParamT; + NearestIndex = ci; + NearestID = Curve.ID; + NearestSegmentIdx = CurveNearestSegmentIdx; + NearestSegmentParam = CurveNearestSegmentParam; + } + Critical.Unlock(); + } + }); // end ParallelFor + + if (NearestID != -1) + { + ResultOut.ID = NearestID; + ResultOut.bIsPoint = false; + ResultOut.NearestRayPoint = Ray.PointAt(MinRayParamT); + ResultOut.NearestGeoPoint = Curves[NearestIndex].Geometry.GetSegmentPoint(NearestSegmentIdx, NearestSegmentParam); + ResultOut.RayParam = MinRayParamT; + ResultOut.PolySegmentIdx = NearestSegmentIdx; + ResultOut.PolySegmentParam = NearestSegmentParam; + } + + return NearestID != -1; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/PointSetHashTable.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/PointSetHashTable.cpp new file mode 100644 index 000000000000..a2bc5a263e89 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Spatial/PointSetHashTable.cpp @@ -0,0 +1,88 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Spatial/PointSetHashTable.h" +#include "Util/IndexUtil.h" + +void FPointSetHashtable::Build(double CellSizeIn, const FVector3d& OriginIn) +{ + Origin = OriginIn; + CellSize = CellSizeIn; + GridIndexer = FShiftGridIndexer3d(Origin, CellSize); + + Grid = TSparseGrid3(); + + // insert all points into cell lists + int MaxID = Points->MaxPointID(); + for (int i = 0; i < MaxID; ++i) + { + if (Points->IsPoint(i)) + { + FVector3d Pt = Points->GetPoint(i); + FVector3i Idx = GridIndexer.ToGrid(Pt); + PointList* CellList = Grid.Get(Idx); + CellList->Add(i); + } + } +} + + +bool FPointSetHashtable::FindPointsInBall(const FVector3d& QueryPt, double QueryRadius, TArray& ResultOut) +{ + double HalfCellSize = CellSize * 0.5; + FVector3i QueryCellIdx = GridIndexer.ToGrid(QueryPt); + FVector3d QueryCellCenter = GridIndexer.FromGrid(QueryCellIdx) + HalfCellSize * FVector3d::One(); + + // currently large radius is unsupported... + // @todo support large radius! + // @todo we could alternately clamp this value + check(QueryRadius <= CellSize); + double RadiusSqr = QueryRadius * QueryRadius; + + // check all in this cell + PointList* CenterCellList = Grid.Get(QueryCellIdx, false); + if (CenterCellList != nullptr) + { + for (int vid : *CenterCellList) + { + if (QueryPt.DistanceSquared(Points->GetPoint(vid)) < RadiusSqr) + { + ResultOut.Add(vid); + } + } + } + + // if we are close enough to cell border we need to check nbrs + // [TODO] could iterate over fewer cells here, if r is bounded by CellSize, + // then we should only ever need to look at 3, depending on which octant we are in. + if ((QueryPt - QueryCellCenter).MaxAbs() + QueryRadius > HalfCellSize) + { + for (int ci = 0; ci < 26; ++ci) + { + FVector3i NbrOffset = IndexUtil::GridOffsets26[ci]; + + // if we are within r from face, we need to look into it + FVector3d PtToFaceCenter( + QueryCellCenter.X + HalfCellSize*NbrOffset.X - QueryPt.X, + QueryCellCenter.Y + HalfCellSize*NbrOffset.Y - QueryPt.Y, + QueryCellCenter.Z + HalfCellSize*NbrOffset.Z - QueryPt.Z); + if (PtToFaceCenter.MinAbs() > QueryRadius) + { + continue; + } + + PointList* NbrCellList = Grid.Get(QueryCellIdx + NbrOffset, false); + if (NbrCellList != nullptr) + { + for (int vid : *NbrCellList) + { + if (QueryPt.DistanceSquared(Points->GetPoint(vid)) < RadiusSqr) + { + ResultOut.Add(vid); + } + } + } + } + } + + return true; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Tests/VectorTypes.spec.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Tests/VectorTypes.spec.cpp new file mode 100644 index 000000000000..9f8a9c1ef99e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Tests/VectorTypes.spec.cpp @@ -0,0 +1,192 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" +#include "VectorTypes.h" + +#if WITH_DEV_AUTOMATION_TESTS + +BEGIN_DEFINE_SPEC(FVectorTypesSpec, + "GeometryProcessing.Unit", + EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask) +END_DEFINE_SPEC(FVectorTypesSpec) + +void FVectorTypesSpec::Define() +{ + using Vec = FVector3; + Describe("FVector3", [this]() + { + Describe("Constructors", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + It("FVector3()", [this]() + { + Vec v{}; + }); + It("FVector3(float, float, float)", [this, initialValues]() + { + Vec v{initialValues[0], initialValues[1], initialValues[2]}; + TestEqual("v.X", v.X, initialValues[0]); + TestEqual("v.Y", v.Y, initialValues[1]); + TestEqual("v.Z", v.Z, initialValues[2]); + }); + It("FVector3(const float*)", [this, initialValues]() + { + Vec v{initialValues}; + TestEqual("v.X", v.X, initialValues[0]); + TestEqual("v.Y", v.Y, initialValues[1]); + TestEqual("v.Z", v.Z, initialValues[2]); + }); + It("FVector3(const FVector&)", [this, initialValues]() + { + FVector fVector{initialValues[0], initialValues[1], + initialValues[2]}; + Vec fVector3{fVector}; + TestEqual("v.X", fVector.X, fVector3.X); + TestEqual("v.Y", fVector.Y, fVector3.Y); + TestEqual("v.Z", fVector.Z, fVector3.Z); + }); + It("FVector3(const FLinearColor&)", [this, initialValues]() + { + FLinearColor fLinearColor{initialValues[0], initialValues[1], + initialValues[2]}; + Vec fVector3{FVector{fLinearColor}}; + TestEqual("v.X", fLinearColor.R, fVector3.X); + TestEqual("v.Y", fLinearColor.G, fVector3.Y); + TestEqual("v.Z", fLinearColor.B, fVector3.Z); + }); + }); + Describe("Conversion", [this]() + { + It("(const float*)", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + const float *converted = static_cast(testVec); + for (int index : {0, 1, 2}) { + TestEqual(FString::Printf(TEXT("Component %d"), index), + initialValues[index], converted[index]); + } + }); + It("(float*)", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + float *converted = static_cast(testVec); + for (int index : {0, 1, 2}) { + TestEqual(FString::Printf(TEXT("Component %d"), index), + initialValues[index], converted[index]); + } + for (int index : {0, 1, 2}) { + converted[index] *= 2.f; + } + TestEqual("v.X", testVec.X, 2.f * initialValues[0]); + TestEqual("v.Y", testVec.Y, 2.f * initialValues[1]); + TestEqual("v.Z", testVec.Z, 2.f * initialValues[2]); + }); + It("(FVector)", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + FVector converted = static_cast(testVec); + for (int index : {0, 1, 2}) { + TestEqual(FString::Printf(TEXT("Component %d"), index), + testVec[index], converted[index]); + } + }); + It("(FLinearColor)", [this]() + { + constexpr float initialValues[3]{0.5f, 0.5f, 0.5f}; + Vec testVec{initialValues}; + FLinearColor converted = {testVec}; + TestEqual("Color.R", testVec.X, converted.R); + TestEqual("Color.G", testVec.Y, converted.G); + TestEqual("Color.B", testVec.Z, converted.B); + TestEqual("Color.A", 1.f, converted.A); + }); + }); + Describe("Assignment Operator", [this]() + { + It("assigns every component", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + Vec testVecCopy{}; + testVecCopy = testVec; + TestEqual("v.X", testVec.X, testVecCopy.X); + TestEqual("v.Y", testVec.Y, testVecCopy.Y); + TestEqual("v.Z", testVec.Z, testVecCopy.Z); + }); + }); + Describe("Element Access", [this]() + { + It("can be const", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + for ( int i : { 0, 1, 2 } ) + { + const float& val = testVec[i]; + TestEqual(FString::Printf(TEXT("Component %d"), i), val, initialValues[i]); + } + }); + It("can be mutable", [this]() + { + constexpr float initialValues[3]{1.f, 2.f, 3.f}; + Vec testVec{initialValues}; + for ( int i : { 0, 1, 2 } ) + { + float& val = testVec[i]; + TestEqual(FString::Printf(TEXT("Component %d"), i), val, testVec[i]); + val *= 2.f; + TestEqual(FString::Printf(TEXT("Component %d"), i), testVec[i], initialValues[i] * 2.f); + } + }); + }); + Describe("Length", [this]() + { + It("of Zero vector is 0.f", [this]() + { + TestEqual("Zero.Length()", Vec::Zero().Length(), 0.f); + }); + It("of One vector is Sqrt(3.f)", [this]() + { + TestEqual("One.Length()", Vec::One().Length(), FMathf::Sqrt(3.f)); + }); + It("of Unit vectors is 1.f", [this]() + { + TestEqual("UnitX.Length()", Vec::UnitX().Length(), 1.f); + TestEqual("UnitY.Length()", Vec::UnitY().Length(), 1.f); + TestEqual("UnitZ.Length()", Vec::UnitZ().Length(), 1.f); + }); + }); + Describe("SquaredLength", [this]() + { + It("of Zero vector is 0.f", [this]() + { + TestEqual("Zero.SquaredLength()", Vec::Zero().SquaredLength(), 0.f); + }); + It("of One vector is 3.f", [this]() + { + TestEqual("One.SquaredLength()", Vec::One().SquaredLength(), 3.f); + }); + It("of Unit vectors is 1.f", [this]() + { + TestEqual("UnitX.SquaredLength()", Vec::UnitX().SquaredLength(), 1.f); + TestEqual("UnitY.SquaredLength()", Vec::UnitY().SquaredLength(), 1.f); + TestEqual("UnitZ.SquaredLength()", Vec::UnitZ().SquaredLength(), 1.f); + }); + }); + Describe("Distance", [this]() + { + It("from vector to itself is 0.f", [this]() + { + const Vec testVecs[] = {Vec::Zero(), Vec::One(), Vec::UnitX(), Vec::UnitY(), Vec::UnitZ(), Vec(1.0, 2.0, 3.0)}; + for (const auto &testVec : testVecs) + { + TestEqual("Self Distance", testVec.Distance(testVec), 0.f); + } + }); + }); + }); +} +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Util/SmallListSet.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Util/SmallListSet.cpp new file mode 100644 index 000000000000..11198a6f306e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Private/Util/SmallListSet.cpp @@ -0,0 +1,400 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Util/SmallListSet.h" + +const int32 FSmallListSet::NullValue = -1; + + +FSmallListSet::FSmallListSet() +{ + ListHeads = TDynamicVector(); + LinkedListElements = TDynamicVector(); + FreeHeadIndex = NullValue; + ListBlocks = TDynamicVector(); + FreeBlocks = TDynamicVector(); +} + + +FSmallListSet::FSmallListSet(const FSmallListSet& copy) +{ + LinkedListElements = TDynamicVector(copy.LinkedListElements); + FreeHeadIndex = copy.FreeHeadIndex; + ListHeads = TDynamicVector(copy.ListHeads); + ListBlocks = TDynamicVector(copy.ListBlocks); + FreeBlocks = TDynamicVector(copy.FreeBlocks); +} + + +void FSmallListSet::Resize(int32 NewSize) +{ + int32 CurSize = (int32)ListHeads.GetLength(); + if (NewSize > CurSize) + { + ListHeads.Resize(NewSize); + for (int32 k = CurSize; k < NewSize; ++k) + { + ListHeads[k] = NullValue; + } + } +} + + +void FSmallListSet::AllocateAt(int32 ListIndex) +{ + check(ListIndex >= 0); + if (ListIndex >= (int)ListHeads.GetLength()) + { + int32 j = (int32)ListHeads.GetLength(); + ListHeads.InsertAt(NullValue, ListIndex); + // need to set intermediate values to null! + while (j < ListIndex) + { + ListHeads[j] = NullValue; + j++; + } + } + else + { + checkf(ListHeads[ListIndex] == NullValue, TEXT("FSmallListSet: list at %d is not empty!"), ListIndex); + } +} + + + + +void FSmallListSet::Insert(int32 ListIndex, int32 Value) +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr == NullValue) + { + block_ptr = AllocateBlock(); + ListBlocks[block_ptr] = 0; + ListHeads[ListIndex] = block_ptr; + } + + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + ListBlocks[block_ptr + N + 1] = Value; + } + else + { + // spill to linked list + int32 cur_head = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + + if (FreeHeadIndex == NullValue) + { + // allocate linkedlist node + int32 new_ptr = (int32)LinkedListElements.GetLength(); + LinkedListElements.Add(Value); + LinkedListElements.Add(cur_head); + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = new_ptr; + } + else + { + // pull from free list + int32 free_ptr = FreeHeadIndex; + FreeHeadIndex = LinkedListElements[free_ptr + 1]; + LinkedListElements[free_ptr] = Value; + LinkedListElements[free_ptr + 1] = cur_head; + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = free_ptr; + } + } + + // count element + ListBlocks[block_ptr] += 1; +} + + + + +bool FSmallListSet::Remove(int32 ListIndex, int32 Value) +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + int32 N = ListBlocks[block_ptr]; + + + int32 iEnd = block_ptr + FMath::Min(N, BLOCKSIZE); + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + + if (ListBlocks[i] == Value) + { + // TODO since this is a set and order doesn't matter, shouldn't you just move the last thing + // to the empty spot rather than shifting the whole list left? + for (int32 j = i + 1; j <= iEnd; ++j) // shift left + { + ListBlocks[j - 1] = ListBlocks[j]; + } + //block_store[iEnd] = -2; // OPTIONAL + + if (N > BLOCKSIZE) + { + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = LinkedListElements[cur_ptr + 1]; // point32 to cur->next + ListBlocks[iEnd] = LinkedListElements[cur_ptr]; + AddFreeLink(cur_ptr); + } + + ListBlocks[block_ptr] -= 1; + return true; + } + + } + + // search list + if (N > BLOCKSIZE) + { + if (RemoveFromLinkedList(block_ptr, Value)) + { + ListBlocks[block_ptr] -= 1; + return true; + } + } + + return false; +} + + + + +void FSmallListSet::Move(int32 FromIndex, int32 ToIndex) +{ + check(FromIndex >= 0); + check(ToIndex >= 0); + check(ListHeads[ToIndex] == NullValue); + check(ListHeads[FromIndex] != NullValue); + ListHeads[ToIndex] = ListHeads[FromIndex]; + ListHeads[FromIndex] = NullValue; +} + + + + +void FSmallListSet::Clear(int32 ListIndex) +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + + // if we have spilled to linked-list, free nodes + if (N > BLOCKSIZE) + { + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + int32 free_ptr = cur_ptr; + cur_ptr = LinkedListElements[cur_ptr + 1]; + AddFreeLink(free_ptr); + } + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = NullValue; + } + + // free our block + ListBlocks[block_ptr] = 0; + FreeBlocks.Add(block_ptr); + ListHeads[ListIndex] = NullValue; + } +} + + + + +bool FSmallListSet::Contains(int32 ListIndex, int32 Value) const +{ + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + int32 iEnd = block_ptr + N; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + if (ListBlocks[i] == Value) + { + return true; + } + } + } + else + { + // we spilled to linked list, have to iterate through it as well + int32 iEnd = block_ptr + BLOCKSIZE; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + if (ListBlocks[i] == Value) + { + return true; + } + } + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + if (LinkedListElements[cur_ptr] == Value) + { + return true; + } + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + } + } + return false; +} + + + + + + + +int32 FSmallListSet::Find(int32 ListIndex, const TFunction& PredicateFunc, int32 InvalidValue) const +{ + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + int32 iEnd = block_ptr + N; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + return Value; + } + } + } + else + { + // we spilled to linked list, have to iterate through it as well + int32 iEnd = block_ptr + BLOCKSIZE; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + return Value; + } + } + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + int32 Value = LinkedListElements[cur_ptr]; + if (PredicateFunc(Value)) + { + return Value; + } + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + } + } + return InvalidValue; +} + + + + + + +bool FSmallListSet::Replace(int32 ListIndex, const TFunction& PredicateFunc, int32 NewValue) +{ + int32 block_ptr = ListHeads[ListIndex]; + if (block_ptr != NullValue) + { + int32 N = ListBlocks[block_ptr]; + if (N < BLOCKSIZE) + { + int32 iEnd = block_ptr + N; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + ListBlocks[i] = NewValue; + return true; + } + } + } + else + { + // we spilled to linked list, have to iterate through it as well + int32 iEnd = block_ptr + BLOCKSIZE; + for (int32 i = block_ptr + 1; i <= iEnd; ++i) + { + int32 Value = ListBlocks[i]; + if (PredicateFunc(Value)) + { + ListBlocks[i] = NewValue; + return true; + } + } + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + while (cur_ptr != NullValue) + { + int32 Value = LinkedListElements[cur_ptr]; + if (PredicateFunc(Value)) + { + LinkedListElements[cur_ptr] = NewValue; + return true; + } + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + } + } + return false; +} + + + + + +int32 FSmallListSet::AllocateBlock() +{ + int32 nfree = (int32)FreeBlocks.GetLength(); + if (nfree > 0) + { + int32 ptr = FreeBlocks[nfree - 1]; + FreeBlocks.PopBack(); + return ptr; + } + int32 nsize = (int32)ListBlocks.GetLength(); + ListBlocks.InsertAt(NullValue, nsize + BLOCK_LIST_OFFSET); + ListBlocks[nsize] = 0; + AllocatedCount++; + return nsize; +} + + + +bool FSmallListSet::RemoveFromLinkedList(int32 block_ptr, int32 val) +{ + int32 cur_ptr = ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + int32 prev_ptr = NullValue; + while (cur_ptr != NullValue) + { + if (LinkedListElements[cur_ptr] == val) + { + int32 next_ptr = LinkedListElements[cur_ptr + 1]; + if (prev_ptr == NullValue) + { + ListBlocks[block_ptr + BLOCK_LIST_OFFSET] = next_ptr; + } + else + { + LinkedListElements[prev_ptr + 1] = next_ptr; + } + AddFreeLink(cur_ptr); + return true; + } + prev_ptr = cur_ptr; + cur_ptr = LinkedListElements[cur_ptr + 1]; + } + return false; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h new file mode 100644 index 000000000000..b89114569d13 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/BoxTypes.h @@ -0,0 +1,544 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Box.h" +#include "Math/Box2D.h" +#include "VectorTypes.h" + +template +struct TInterval1 +{ + RealType Min; + RealType Max; + + TInterval1() + { + } + TInterval1(const RealType& Min, const RealType& Max) + { + this->Min = Min; + this->Max = Max; + } + + static TInterval1 Empty() + { + return TInterval1(TNumericLimits::Max(), -TNumericLimits::Max()); + } + + RealType Center() const + { + return (Min + Max) * (RealType)0.5; + } + + RealType Extent() const + { + return (Max - Min)*(RealType).5; + } + RealType Length() const + { + return Max - Min; + } + + void Contain(const RealType& V) + { + if (V < Min) + { + Min = V; + } + if (V > Max) + { + Max = V; + } + } + + bool Contains(RealType D) const + { + return D >= Min && D <= Max; + } + + bool Overlaps(const TInterval1& O) const + { + return !(O.Min > Max || O.Max < Min); + } + + RealType SquaredDist(const TInterval1& O) const + { + if (Max < O.Min) + { + return (O.Min - Max) * (O.Min - Max); + } + else if (Min > O.Max) + { + return (Min - O.Max) * (Min - O.Max); + } + else + { + return 0; + } + } + RealType Dist(const TInterval1& O) const + { + if (Max < O.Min) + { + return O.Min - Max; + } + else if (Min > O.Max) + { + return Min - O.Max; + } + else + { + return 0; + } + } + + TInterval1 IntersectionWith(const TInterval1& O) const + { + if (O.Min > Max || O.Max < Min) + { + return TInterval1::Empty(); + } + return TInterval1(TMathUtil::Max(Min, O.Min), TMathUtil::Min(Max, O.Max)); + } + + /** + * clamp Value f to interval [Min,Max] + */ + RealType Clamp(RealType f) const + { + return (f < Min) ? Min : (f > Max) ? Max : f; + } + + /** + * interpolate between Min and Max using Value T in range [0,1] + */ + RealType Interpolate(RealType T) const + { + return (1 - T) * Min + (T)*Max; + } + + /** + * Convert Value into (clamped) T Value in range [0,1] + */ + RealType GetT(RealType Value) const + { + if (Value <= Min) + { + return 0; + } + else if (Value >= Max) + { + return 1; + } + else if (Min == Max) + { + return 0.5; + } + else + { + return (Value - Min) / (Max - Min); + } + } + + void Set(TInterval1 O) + { + Min = O.Min; + Max = O.Max; + } + + void Set(RealType A, RealType B) + { + Min = A; + Max = B; + } + + TInterval1 operator-(TInterval1 V) const + { + return TInterval1(-V.Min, -V.Max); + } + + TInterval1 operator+(RealType f) const + { + return TInterval1(Min + f, Max + f); + } + + TInterval1 operator-(RealType f) const + { + return TInterval1(Min - f, Max - f); + } + + TInterval1 operator*(RealType f) const + { + return TInterval1(Min * f, Max * f); + } +}; + +typedef TInterval1 FInterval1f; +typedef TInterval1 FInterval1d; +typedef TInterval1 FInterval1i; + + + +template +struct TAxisAlignedBox3 +{ + FVector3 Min; + FVector3 Max; + + TAxisAlignedBox3() + { + } + TAxisAlignedBox3(const FVector3& Min, const FVector3& Max) + { + this->Min = Min; + this->Max = Max; + } + TAxisAlignedBox3(const TAxisAlignedBox3& Box, const TFunction(const FVector3&)> TransformF) + { + if (TransformF == nullptr) + { + Min = Box.Min; + Max = Box.Max; + return; + } + + FVector3 C0 = TransformF(Box.GetCorner(0)); + Min = C0; + Max = C0; + for (int i = 1; i < 8; ++i) + { + Contain(TransformF(Box.GetCorner(i))); + } + } + + operator FBox() const + { + FVector MinV((float)Min.X, (float)Min.Y, (float)Min.Z); + FVector MaxV((float)Max.X, (float)Max.Y, (float)Max.Z); + return FBox(MinV, MaxV); + } + TAxisAlignedBox3(const FBox& Box) + { + Min = Box.Min; + Max = Box.Max; + } + + /** + * @param Index corner index in range 0-7 + * @return Corner point on the box identified by the given index. See diagram in OrientedBoxTypes.h for index/corner mapping. + */ + FVector3 GetCorner(int Index) const + { + check(Index >= 0 && Index <= 7); + double X = (((Index & 1) != 0) ^ ((Index & 2) != 0)) ? (Max.X) : (Min.X); + double Y = ((Index / 2) % 2 == 0) ? (Min.Y) : (Max.Y); + double Z = (Index < 4) ? (Min.Z) : (Max.Z); + return FVector3(X, Y, Z); + } + + static TAxisAlignedBox3 Empty() + { + return TAxisAlignedBox3( + FVector3(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()), + FVector3(-TNumericLimits::Max(), -TNumericLimits::Max(), -TNumericLimits::Max())); + } + + FVector3 Center() const + { + return FVector3( + (Min.X + Max.X) * (RealType)0.5, + (Min.Y + Max.Y) * (RealType)0.5, + (Min.Z + Max.Z) * (RealType)0.5); + } + + FVector3 Extents() const + { + return (Max - Min) * (RealType).5; + } + + void Contain(const FVector3& V) + { + if (V.X < Min.X) + { + Min.X = V.X; + } + if (V.X > Max.X) + { + Max.X = V.X; + } + if (V.Y < Min.Y) + { + Min.Y = V.Y; + } + if (V.Y > Max.Y) + { + Max.Y = V.Y; + } + if (V.Z < Min.Z) + { + Min.Z = V.Z; + } + if (V.Z > Max.Z) + { + Max.Z = V.Z; + } + } + + void Contain(const TAxisAlignedBox3& Other) + { + // todo: can be optimized + Contain(Other.Min); + Contain(Other.Max); + } + + bool Contains(const FVector3& V) const + { + return (Min.X <= V.X) && (Min.Y <= V.Y) && (Min.Z <= V.Z) && (Max.X >= V.X) && (Max.Y >= V.Y) && (Max.Z >= V.Z); + } + + TAxisAlignedBox3 Intersect(const TAxisAlignedBox3& Box) const + { + TAxisAlignedBox3 Intersection( + FVector3(TMathUtil::Max(Min.X, Box.Min.X), TMathUtil::Max(Min.Y, Box.Min.Y), TMathUtil::Max(Min.Z, Box.Min.Z)), + FVector3(TMathUtil::Min(Max.X, Box.Max.X), TMathUtil::Min(Max.Y, Box.Max.Y), TMathUtil::Min(Max.Z, Box.Max.Z))); + if (Intersection.Height() <= 0 || Intersection.Width() <= 0 || Intersection.Depth() <= 0) + { + return TAxisAlignedBox3::Empty(); + } + else + { + return Intersection; + } + } + + bool Intersects(TAxisAlignedBox3 Box) const + { + return !((Box.Max.X <= Min.X) || (Box.Min.X >= Max.X) || (Box.Max.Y <= Min.Y) || (Box.Min.Y >= Max.Y) || (Box.Max.Z <= Min.Z) || (Box.Min.Z >= Max.Z)); + } + + RealType DistanceSquared(const FVector3& V) const + { + RealType dx = (V.X < Min.X) ? Min.X - V.X : (V.X > Max.X ? V.X - Max.X : 0); + RealType dy = (V.Y < Min.Y) ? Min.Y - V.Y : (V.Y > Max.Y ? V.Y - Max.Y : 0); + RealType dz = (V.Z < Min.Z) ? Min.Z - V.Z : (V.Z > Max.Z ? V.Z - Max.Z : 0); + return dx * dx + dy * dy + dz * dz; + } + + RealType DistanceSquared(const TAxisAlignedBox3& Box) + { + // compute lensqr( max(0, abs(center1-center2) - (extent1+extent2)) ) + RealType delta_x = TMathUtil::Abs((Box.Min.X + Box.Max.X) - (Min.X + Max.X)) + - ((Max.X - Min.X) + (Box.Max.X - Box.Min.X)); + if (delta_x < 0) + { + delta_x = 0; + } + RealType delta_y = TMathUtil::Abs((Box.Min.Y + Box.Max.Y) - (Min.Y + Max.Y)) + - ((Max.Y - Min.Y) + (Box.Max.Y - Box.Min.Y)); + if (delta_y < 0) + { + delta_y = 0; + } + RealType delta_z = TMathUtil::Abs((Box.Min.Z + Box.Max.Z) - (Min.Z + Max.Z)) + - ((Max.Z - Min.Z) + (Box.Max.Z - Box.Min.Z)); + if (delta_z < 0) + { + delta_z = 0; + } + return (RealType)0.25 * (delta_x * delta_x + delta_y * delta_y + delta_z * delta_z); + } + + RealType Width() const + { + return TMathUtil::Max(Max.X - Min.X, (RealType)0); + } + + RealType Height() const + { + return TMathUtil::Max(Max.Y - Min.Y, (RealType)0); + } + + RealType Depth() const + { + return TMathUtil::Max(Max.Z - Min.Z, (RealType)0); + } + + RealType Volume() const + { + return Width() * Height() * Depth(); + } + + RealType DiagonalLength() const + { + return TMathUtil::Sqrt((Max.X - Min.X) * (Max.X - Min.X) + (Max.Y - Min.Y) * (Max.Y - Min.Y) + (Max.Z - Min.Z) * (Max.Z - Min.Z)); + } + + RealType MaxDim() const + { + return TMathUtil::Max(Width(), TMathUtil::Max(Height(), Depth())); + } + + FVector3 Diagonal() const + { + return FVector3(Max.X - Min.X, Max.Y - Min.Y, Max.Z - Min.Z); + } +}; + +template +struct TAxisAlignedBox2 +{ + FVector2 Min; + FVector2 Max; + + TAxisAlignedBox2() + { + } + TAxisAlignedBox2(const FVector2& Min, const FVector2& Max) + : Min(Min), Max(Max) + { + } + TAxisAlignedBox2(RealType SquareSize) + : Min((RealType)0, (RealType)0), Max(SquareSize, SquareSize) + { + } + TAxisAlignedBox2(RealType Width, RealType Height) + : Min((RealType)0, (RealType)0), Max(Width, Height) + { + } + + operator FBox2D() const + { + FVector2D MinV((float)Min.X, (float)Min.Y); + FVector2D MaxV((float)Max.X, (float)Max.Y); + return FBox2D(MinV, MaxV); + } + TAxisAlignedBox2(const FBox2D& Box) + { + Min = Box.Min; + Max = Box.Max; + } + + static TAxisAlignedBox2 Empty() + { + return TAxisAlignedBox2( + FVector2(TNumericLimits::Max(), TNumericLimits::Max()), + FVector2(-TNumericLimits::Max(), -TNumericLimits::Max())); + } + + FVector2 Center() const + { + return FVector2( + (Min.X + Max.X) * (RealType)0.5, + (Min.Y + Max.Y) * (RealType)0.5); + } + + FVector2 Extents() const + { + return (Max - Min) * (RealType).5; + } + + inline void Contain(const FVector2& V) + { + if (V.X < Min.X) + { + Min.X = V.X; + } + if (V.X > Max.X) + { + Max.X = V.X; + } + if (V.Y < Min.Y) + { + Min.Y = V.Y; + } + if (V.Y > Max.Y) + { + Max.Y = V.Y; + } + } + + inline void Contain(const TAxisAlignedBox2& Other) + { + // todo: can be optimized + Contain(Other.Min); + Contain(Other.Max); + } + + void Contain(const TArray>& Pts) + { + for (const FVector2& Pt : Pts) + { + Contain(Pt); + } + } + + bool Contains(const FVector2& V) const + { + return (Min.X <= V.X) && (Min.Y <= V.Y) && (Max.X >= V.X) && (Max.Y >= V.Y); + } + + bool Intersects(const TAxisAlignedBox2& Box) const + { + return !((Box.Max.X < Min.X) || (Box.Min.X > Max.X) || (Box.Max.Y < Min.Y) || (Box.Min.Y > Max.Y)); + } + + TAxisAlignedBox2 Intersect(const TAxisAlignedBox2 &Box) const + { + TAxisAlignedBox2 Intersection( + FVector2(TMathUtil::Max(Min.X, Box.Min.X), TMathUtil::Max(Min.Y, Box.Min.Y)), + FVector2(TMathUtil::Min(Max.X, Box.Max.X), TMathUtil::Min(Max.Y, Box.Max.Y))); + if (Intersection.Height() <= 0 || Intersection.Width() <= 0) + { + return TAxisAlignedBox2::Empty(); + } + else + { + return Intersection; + } + } + + RealType DistanceSquared(const FVector2& V) const + { + RealType dx = (V.X < Min.X) ? Min.X - V.X : (V.X > Max.X ? V.X - Max.X : 0); + RealType dy = (V.Y < Min.Y) ? Min.Y - V.Y : (V.Y > Max.Y ? V.Y - Max.Y : 0); + return dx * dx + dy * dy; + } + + inline RealType Width() const + { + return TMathUtil::Max(Max.X - Min.X, (RealType)0); + } + + inline RealType Height() const + { + return TMathUtil::Max(Max.Y - Min.Y, (RealType)0); + } + + inline RealType Area() const + { + return Width() * Height(); + } + + RealType DiagonalLength() const + { + return (RealType)TMathUtil::Sqrt((Max.X - Min.X) * (Max.X - Min.X) + (Max.Y - Min.Y) * (Max.Y - Min.Y)); + } + + inline RealType MaxDim() const + { + return TMathUtil::Max(Width(), Height()); + } + + inline RealType MinDim() const + { + return TMathUtil::Min(Width(), Height()); + } +}; + +typedef TAxisAlignedBox2 FAxisAlignedBox2f; +typedef TAxisAlignedBox2 FAxisAlignedBox2d; +typedef TAxisAlignedBox2 FAxisAlignedBox2i; +typedef TAxisAlignedBox3 FAxisAlignedBox3f; +typedef TAxisAlignedBox3 FAxisAlignedBox3d; +typedef TAxisAlignedBox3 FAxisAlignedBox3i; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CompGeom/PolygonTriangulation.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CompGeom/PolygonTriangulation.h new file mode 100644 index 000000000000..f5fba9188a7a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/CompGeom/PolygonTriangulation.h @@ -0,0 +1,19 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "IndexTypes.h" + +namespace PolygonTriangulation +{ + + /** + * Compute triangulation of simple polygon using ear-clipping + * @param VertexPositions ordered vertices of polygon + * @param OutTriangles computed triangulation. Each triangle is a tuple of indices into VertexPositions. + */ + template + void GEOMETRICOBJECTS_API TriangulateSimplePolygon(const TArray>& VertexPositions, TArray& OutTriangles); + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph.h new file mode 100644 index 000000000000..9b624afadd3b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph.h @@ -0,0 +1,640 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreTypes.h" + +#include "VectorTypes.h" +#include "IndexTypes.h" +#include "BoxTypes.h" +#include "GeometryTypes.h" +#include "Util/DynamicVector.h" +#include "Util/IndexUtil.h" +#include "Util/IteratorUtil.h" +#include "Util/RefCountVector.h" +#include "Util/SmallListSet.h" +#include "VectorUtil.h" + +#pragma once + +class FDynamicGraph +{ +public: + static constexpr int InvalidID = IndexConstants::InvalidID; // -1; + static constexpr int DuplicateEdgeID = -2; + + struct FEdge + { + int A, B, Group; + }; + + static FIndex2i InvalidEdgeV() + { + return FIndex2i(InvalidID, InvalidID); + } + static FEdge InvalidEdge3() + { + return FEdge{InvalidID, InvalidID, InvalidID}; + } + +protected: + FRefCountVector vertices_refcount; + + FSmallListSet vertex_edges; + + FRefCountVector edges_refcount; + TDynamicVector edges; // each edge is a tuple (v0,v0,GroupID) + + int timestamp = 0; + int shape_timestamp = 0; + + int max_group_id = 0; + +public: + FDynamicGraph() + : max_group_id(0) + { + } + + virtual ~FDynamicGraph() + { + } + +protected: + void updateTimeStamp(bool bShapeChange) + { + timestamp++; + if (bShapeChange) + { + shape_timestamp++; + } + } + +public: + int Timestamp() const + { + return timestamp; + } + int ShapeTimestamp() const + { + return shape_timestamp; + } + + int VertexCount() const + { + return vertices_refcount.GetCount(); + } + int EdgeCount() const + { + return edges_refcount.GetCount(); + } + + // these values are (max_used+1), ie so an iteration should be < MaxVertexID, not <= + int MaxVertexID() const + { + return vertices_refcount.GetMaxIndex(); + } + int MaxEdgeID() const + { + return edges_refcount.GetMaxIndex(); + } + int MaxGroupID() const + { + return max_group_id; + } + + bool IsVertex(int VID) const + { + return vertices_refcount.IsValid(VID); + } + bool IsEdge(int EID) const + { + return edges_refcount.IsValid(EID); + } + + /** + * Enumerate "other" vertices of edges connected to vertex (i.e. vertex one-ring) + */ + FSmallListSet::ValueEnumerable VtxVerticesItr(int VID) const + { + check(vertices_refcount.IsValid(VID)); + return vertex_edges.Values(VID, [VID, this](int EID) { return edge_other_v(EID, VID); }); + } + + /** + * Enumerate edge ids connected to vertex (i.e. edge one-ring) + */ + FSmallListSet::ValueEnumerable VtxEdgesItr(int VID) const + { + check(vertices_refcount.IsValid(VID)); + return vertex_edges.Values(VID); + } + + int GetVtxEdgeCount(int VID) const + { + return vertices_refcount.IsValid(VID) ? vertex_edges.GetCount(VID) : -1; + } + + int GetMaxVtxEdgeCount() const + { + int max = 0; + for (int VID : vertices_refcount.Indices()) + { + max = FMath::Max(max, vertex_edges.GetCount(VID)); + } + return max; + } + + FIndex2i GetEdgeV(int EID) const + { + return edges_refcount.IsValid(EID) ? FIndex2i(edges[EID].A, edges[EID].B) : InvalidEdgeV(); + } + + int GetEdgeGroup(int EID) const + { + return edges_refcount.IsValid(EID) ? edges[EID].Group : -1; + } + + void SetEdgeGroup(int EID, int GroupID) + { + check(edges_refcount.IsValid(EID)); + if (edges_refcount.IsValid(EID)) + { + edges[EID].Group = GroupID; + max_group_id = FMath::Max(max_group_id, GroupID + 1); + updateTimeStamp(false); + } + } + + int AllocateEdgeGroup() + { + return max_group_id++; + } + + FEdge GetEdge(int EID) const + { + return edges_refcount.IsValid(EID) ? edges[EID] : InvalidEdge3(); + } + +protected: + int append_vertex_internal() + { + int VID = vertices_refcount.Allocate(); + allocate_edges_list(VID); + updateTimeStamp(true); + return VID; + } + +private: + void allocate_edges_list(int VID) + { + if (VID < (int)vertex_edges.Size()) + { + vertex_edges.Clear(VID); + } + vertex_edges.AllocateAt(VID); + } + +public: + int AppendEdge(const FEdge& E) + { + return AppendEdge(E.A, E.B, E.Group); + } + int AppendEdge(const FIndex2i& ev, int GID = -1) + { + return AppendEdge(ev[0], ev[1], GID); + } + int AppendEdge(int v0, int v1, int GID = -1) + { + if (IsVertex(v0) == false || IsVertex(v1) == false) + { + check(false); + return InvalidID; + } + if (v0 == v1) + { + check(false); + return InvalidID; + } + int e0 = FindEdge(v0, v1); + if (e0 != InvalidID) + return DuplicateEdgeID; + + // Increment ref counts and update/create edges + vertices_refcount.Increment(v0); + vertices_refcount.Increment(v1); + max_group_id = FMath::Max(max_group_id, GID + 1); + + // now safe to insert edge + int EID = add_edge(v0, v1, GID); + + updateTimeStamp(true); + return EID; + } + +protected: + int add_edge(int A, int B, int GID) + { + if (B < A) + { + int t = B; + B = A; + A = t; + } + int EID = edges_refcount.Allocate(); + edges.InsertAt(FEdge{A, B, GID}, EID); + + vertex_edges.Insert(A, EID); + vertex_edges.Insert(B, EID); + return EID; + } + +public: + // + // iterators + // The functions vertices() / triangles() / edges() are provided so you can do: + // for ( int EID : edges() ) { ... } + // and other related begin() / end() idioms + // + typedef typename FRefCountVector::IndexEnumerable vertex_iterator; + vertex_iterator VertexIndices() const + { + return vertices_refcount.Indices(); + } + + typedef typename FRefCountVector::IndexEnumerable edge_iterator; + edge_iterator EdgeIndices() const + { + return edges_refcount.Indices(); + } + + template + using value_iteration = FRefCountVector::MappedEnumerable; + + /** + * Enumerate edges. + */ + value_iteration Edges() const + { + return edges_refcount.MappedIndices([=](int EID) { + return edges[EID]; + }); + } + + int FindEdge(int VA, int VB) const + { + int vMax = FMath::Max(VA, VB); + for (int EID : vertex_edges.Values(FMath::Min(VA, VB))) + { + //if (edge_has_v(EID, vMax)) // (slower option; does not use the fact that edges always have larger vertex as B) + if (edges[EID].B == vMax) + { + return EID; + } + } + return InvalidID; + } + + EMeshResult RemoveEdge(int EID, bool bRemoveIsolatedVertices) + { + if (!edges_refcount.IsValid(EID)) + { + check(false); + return EMeshResult::Failed_NotAnEdge; + } + + FIndex2i ev(edges[EID].A, edges[EID].B); + vertex_edges.Remove(ev.A, EID); + vertex_edges.Remove(ev.B, EID); + + edges_refcount.Decrement(EID); + + // Decrement vertex refcounts. If any hit 1 and we got remove-isolated flag, + // we need to remove that vertex + for (int j = 0; j < 2; ++j) + { + int VID = ev[j]; + vertices_refcount.Decrement(VID); + if (bRemoveIsolatedVertices && vertices_refcount.GetRefCount(VID) == 1) + { + vertices_refcount.Decrement(VID); + check(vertices_refcount.IsValid(VID) == false); + vertex_edges.Clear(VID); + } + } + + updateTimeStamp(true); + return EMeshResult::Ok; + } + + EMeshResult RemoveVertex(int VID, bool bRemoveIsolatedVertices) + { + for (int EID : VtxEdgesItr(VID)) + { + EMeshResult result = RemoveEdge(EID, bRemoveIsolatedVertices); + if (result != EMeshResult::Ok) + return result; + } + return EMeshResult::Ok; + } + + struct FEdgeSplitInfo + { + int VNew; + int ENewBN; // new edge [VNew,VB] (original was AB) + }; + EMeshResult SplitEdge(int VA, int VB, FEdgeSplitInfo& Split) + { + int EID = FindEdge(VA, VB); + if (EID == InvalidID) + { + return EMeshResult::Failed_NotAnEdge; + } + return SplitEdge(EID, Split); + } + EMeshResult SplitEdge(int EAB, FEdgeSplitInfo& Split) + { + if (!IsEdge(EAB)) + { + return EMeshResult::Failed_NotAnEdge; + } + + // look up primary edge + int eab_i = 3 * EAB; + int A = edges[EAB].A, B = edges[EAB].B; + int GID = edges[EAB].Group; + + // create new vertex + int f = append_new_split_vertex(A, B); + + // rewrite edge bc, create edge af + int eaf = EAB; + replace_edge_vertex(eaf, B, f); + vertex_edges.Remove(B, EAB); + vertex_edges.Insert(f, eaf); + + // create new edge fb + int efb = add_edge(f, B, GID); + + // update vertex refcounts + vertices_refcount.Increment(f, 2); + + Split.VNew = f; + Split.ENewBN = efb; + + updateTimeStamp(true); + return EMeshResult::Ok; + } + +protected: + virtual int append_new_split_vertex(int A, int B) + { + // Not Implemented: DGraph2.append_new_split_vertex + unimplemented(); + return InvalidID; + } + +public: + struct FEdgeCollapseInfo + { + int VKept; + int VRemoved; + + int ECollapsed; // edge we collapsed + }; + EMeshResult CollapseEdge(int VKeep, int VRemove, FEdgeCollapseInfo& Collapse) + { + bool DiscardIsolatedVertices = true; + + if (IsVertex(VKeep) == false || IsVertex(VRemove) == false) + { + return EMeshResult::Failed_NotAnEdge; + } + + int B = VKeep; // renaming for sanity. We remove A and keep B + int A = VRemove; + + int EAB = FindEdge(A, B); + if (EAB == InvalidID) + { + return EMeshResult::Failed_NotAnEdge; + } + + // get rid of any edges that will be duplicates + bool done = false; + while (!done) + { + done = true; + for (int eax : vertex_edges.Values(A)) + { + int o = edge_other_v(eax, A); + if (o != B && FindEdge(B, o) != InvalidID) + { + RemoveEdge(eax, DiscardIsolatedVertices); + done = false; + break; + } + } + } + + vertex_edges.Remove(B, EAB); + for (int eax : vertex_edges.Values(A)) + { + int o = edge_other_v(eax, A); + if (o == B) + { + continue; // discard this edge + } + replace_edge_vertex(eax, A, B); + vertices_refcount.Decrement(A); + vertex_edges.Insert(B, eax); + vertices_refcount.Increment(B); + } + + edges_refcount.Decrement(EAB); + vertices_refcount.Decrement(B); + vertices_refcount.Decrement(A); + if (DiscardIsolatedVertices) + { + vertices_refcount.Decrement(A); // second Decrement discards isolated vtx + check(!IsVertex(A)); + } + + vertex_edges.Clear(A); + + Collapse.VKept = VKeep; + Collapse.VRemoved = VRemove; + Collapse.ECollapsed = EAB; + + updateTimeStamp(true); + return EMeshResult::Ok; + } + +protected: + bool edge_has_v(int EID, int VID) const + { + return (edges[EID].A == VID) || (edges[EID].B == VID); + } + int edge_other_v(int EID, int VID) const + { + int ev0 = edges[EID].A, ev1 = edges[EID].B; + return (ev0 == VID) ? ev1 : ((ev1 == VID) ? ev0 : InvalidID); + } + int replace_edge_vertex(int EID, int VOld, int VNew) + { + int A = edges[EID].A, B = edges[EID].B; + if (A == VOld) + { + edges[EID].A = FMath::Min(B, VNew); + edges[EID].B = FMath::Max(B, VNew); + return 0; + } + else if (B == VOld) + { + edges[EID].A = FMath::Min(A, VNew); + edges[EID].B = FMath::Max(A, VNew); + return 1; + } + else + return -1; + } + +public: + bool IsCompact() const + { + return vertices_refcount.IsDense() && edges_refcount.IsDense(); + } + bool IsCompactV() const + { + return vertices_refcount.IsDense(); + } + + bool IsBoundaryVertex(int VID) const + { + return vertices_refcount.IsValid(VID) && vertex_edges.GetCount(VID) == 1; + } + + bool IsJunctionVertex(int VID) const + { + return vertices_refcount.IsValid(VID) && vertex_edges.GetCount(VID) > 2; + } + + bool IsRegularVertex(int VID) const + { + return vertices_refcount.IsValid(VID) && vertex_edges.GetCount(VID) == 2; + } + + + /** + * This function checks that the graph is well-formed, ie all internal data + * structures are consistent + */ + virtual bool CheckValidity(EValidityCheckFailMode FailMode = EValidityCheckFailMode::Check) const + { + bool is_ok = true; + TFunction CheckOrFailF = [&](bool b) + { + is_ok = is_ok && b; + }; + if (FailMode == EValidityCheckFailMode::Check) + { + CheckOrFailF = [&](bool b) + { + checkf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + else if (FailMode == EValidityCheckFailMode::Ensure) + { + CheckOrFailF = [&](bool b) + { + ensureMsgf(b, TEXT("FEdgeLoop::CheckValidity failed!")); + is_ok = is_ok && b; + }; + } + + // edge verts/tris must exist + for (int EID : EdgeIndices()) + { + CheckOrFailF(IsEdge(EID)); + CheckOrFailF(edges_refcount.GetRefCount(EID) == 1); + FIndex2i ev = GetEdgeV(EID); + CheckOrFailF(IsVertex(ev[0])); + CheckOrFailF(IsVertex(ev[1])); + CheckOrFailF(ev[0] < ev[1]); + } + + // verify compact check + bool is_compact = vertices_refcount.IsDense(); + if (is_compact) + { + for (int VID = 0; VID < VertexCount(); ++VID) + { + CheckOrFailF(vertices_refcount.IsValid(VID)); + } + } + + // vertex edges must exist and reference this vert + for (int VID : VertexIndices()) + { + CheckOrFailF(IsVertex(VID)); + + //FVector3d V = GetVertex(VID); + //CheckOrFailF(double.IsNaN(V.SquaredLength()) == false); + //CheckOrFailF(double.IsInfinity(V.SquaredLength()) == false); + + for (int edgeid : vertex_edges.Values(VID)) + { + CheckOrFailF(IsEdge(edgeid)); + CheckOrFailF(edge_has_v(edgeid, VID)); + + int otherV = edge_other_v(edgeid, VID); + int e2 = FindEdge(VID, otherV); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + e2 = FindEdge(otherV, VID); + CheckOrFailF(e2 != InvalidID); + CheckOrFailF(e2 == edgeid); + } + + CheckOrFailF(vertices_refcount.GetRefCount(VID) == vertex_edges.GetCount(VID) + 1); + } + + subclass_validity_checks(CheckOrFailF); + + return is_ok; + } + +protected: + virtual void subclass_validity_checks(TFunction CheckOrFailF) const + { + } + + inline void debug_check_is_vertex(int V) const + { + check(IsVertex(V)); + } + + inline void debug_check_is_edge(int E) const + { + check(IsEdge(E)); + } +}; + +/** + * Implementation of DGraph that has no dimensionality, ie no data + * stored for vertices besides indices. + */ +class FDynamicGraphN : public FDynamicGraph +{ +public: + int AppendVertex() + { + return append_vertex_internal(); + } + +protected: + // internal used in SplitEdge + virtual int append_new_split_vertex(int A, int B) override + { + return AppendVertex(); + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph2.h new file mode 100644 index 000000000000..958dc0b793ca --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Curve/DynamicGraph2.h @@ -0,0 +1,239 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CoreTypes.h" + +#include "BoxTypes.h" +#include "DynamicGraph.h" +#include "SegmentTypes.h" +#include "Util/DynamicVector.h" +#include "Util/IndexUtil.h" +#include "Util/IteratorUtil.h" +#include "Util/RefCountVector.h" +#include "Util/SmallListSet.h" +#include "VectorTypes.h" +#include "VectorUtil.h" + +#pragma once + +template +class FDynamicGraph2 : public FDynamicGraph +{ + TAxisAlignedBox2 cached_bounds; + int cached_bounds_timestamp = -1; + + TDynamicVectorN vertices; + +public: + static FVector2 InvalidVertex() + { + return FVector2(TNumericLimits::Max(), 0); + } + + FVector2 GetVertex(int VID) const + { + return vertices_refcount.IsValid(VID) ? vertices.AsVector2(VID) : InvalidVertex(); + } + + void SetVertex(int VID, FVector2 VNewPos) + { + check(VectorUtil::IsFinite(VNewPos)); // this will really catch a lot of bugs... + if (vertices_refcount.IsValid(VID)) + { + vertices.SetVector2(VID, VNewPos); + updateTimeStamp(true); + } + } + + using FDynamicGraph::GetEdgeV; + bool GetEdgeV(int EID, FVector2& A, FVector2& B) const + { + if (edges_refcount.IsValid(EID)) + { + A = vertices.AsVector2(edges[EID].A); + B = vertices.AsVector2(edges[EID].B); + return true; + } + return false; + } + + TSegment2 GetEdgeSegment(int EID) const + { + checkf(edges_refcount.IsValid(EID), TEXT("FDynamicGraph2.GetEdgeSegment: invalid segment with id %d"), EID); + const FEdge& e = edges[EID]; + return TSegment2( + vertices.AsVector2(e.A), + vertices.AsVector2(e.B)); + } + + FVector2 GetEdgeCenter(int EID) const + { + checkf(edges_refcount.IsValid(EID), TEXT("FDynamicGraph2.GetEdgeCenter: invalid segment with id %d"), EID); + const FEdge& e = edges[EID]; + return 0.5 * (vertices.AsVector2(e.A) + vertices.AsVector2(e.B)); + } + + int AppendVertex(FVector2d V) + { + int vid = append_vertex_internal(); + vertices.InsertAt({{V.X, V.Y}}, vid); + return vid; + } + + //void AppendPolygon(Polygon2d poly, int gid = -1) { + // int first = -1; + // int prev = -1; + // int N = poly.VertexCount; + // for (int i = 0; i < N; ++i) { + // int cur = AppendVertex(poly[i]); + // if (prev == -1) + // first = cur; + // else + // AppendEdge(prev, cur, gid); + // prev = cur; + // } + // AppendEdge(prev, first, gid); + //} + //void AppendPolygon(GeneralPolygon2d poly, int gid = -1) + //{ + // AppendPolygon(poly.Outer, gid); + // foreach(var hole in poly.Holes) + // AppendPolygon(hole, gid); + //} + + //void AppendPolyline(PolyLine2d poly, int gid = -1) + //{ + // int prev = -1; + // int N = poly.VertexCount; + // for (int i = 0; i < N; ++i) { + // int cur = AppendVertex(poly[i]); + // if (i > 0) + // AppendEdge(prev, cur, gid); + // prev = cur; + // } + //} + + /* + void AppendGraph(FDynamicGraph2 graph, int gid = -1) + { + int[] mapV = new int[graph.MaxVertexID]; + foreach(int vid in graph.VertexIndices()) { + mapV[vid] = this.AppendVertex(graph.GetVertex(vid)); + } + foreach(int eid in graph.EdgeIndices()) { + FIndex2i ev = graph.GetEdgeV(eid); + int use_gid = (gid == -1) ? graph.GetEdgeGroup(eid) : gid; + this.AppendEdge(mapV[ev.A], mapV[ev.B], use_gid); + } + } + +*/ + + /** Enumerate positions of all vertices in graph */ + value_iteration> Vertices() const + { + return vertices_refcount.MappedIndices>( + [=](int vid) { + return vertices.template AsVector2(vid); + }); + } + + /** + * return edges around VID sorted by angle, in clockwise order + */ + bool SortedVtxEdges(int VID, TArray& Sorted) const + { + if (vertices_refcount.IsValid(VID) == false) + return false; + Sorted.SetNumUninitialized(vertex_edges.GetCount(VID)); + for (int EID : vertex_edges.Values(VID)) + { + Sorted.Add(EID); + } + FVector2 V = vertices.AsVector2(VID); + Algo::SortBy(Sorted, [&](int EID) { + int NbrVID = edge_other_v(EID, VID); + FVector2 D = vertices.AsVector2(NbrVID) - V; + return TMathUtil::Atan2Positive(D.Y, D.X); + }); + + return true; + } + + // compute vertex bounding box + FAxisAlignedBox2d GetBounds() const + { + TAxisAlignedBox2 AABB; + for (const FVector2& V : Vertices()) + { + AABB.Contain(V); + } + return AABB; + } + + //! cached bounding box, lazily re-computed on access if mesh has changed + TAxisAlignedBox2 CachedBounds() + { + if (cached_bounds_timestamp != Timestamp()) + { + cached_bounds = GetBounds(); + cached_bounds_timestamp = Timestamp(); + } + return cached_bounds; + } + + /** + * Compute opening angle at vertex VID. + * If not a vertex, or valence != 2, returns InvalidValue argument. + * If either edge is degenerate, returns InvalidValue argument. + */ + double OpeningAngle(int VID, double InvalidValue = TNumericLimits::Max()) const + { + if (vertices_refcount.IsValid(VID) == false) + { + return InvalidValue; + } + if (vertex_edges.GetCount(VID) != 2) + { + return InvalidValue; + } + FSmallListSet::ValueIterator ValueIterate = vertex_edges.Values(VID).begin(); + + int nbra = edge_other_v(*ValueIterate, VID); + int nbrb = edge_other_v(*++ValueIterate, VID); + + FVector2 V = vertices.AsVector2(VID); + FVector2 A = vertices.AsVector2(nbra); + FVector2 B = vertices.AsVector2(nbrb); + A -= V; + if (A.Normalize() == 0) + { + return InvalidValue; + } + B -= V; + if (B.Normalize() == 0) + { + return InvalidValue; + } + return A.AngleD(B); + } + +protected: + // internal used in SplitEdge + virtual int append_new_split_vertex(int A, int B) override + { + FVector2 vNew = 0.5 * (GetVertex(A) + GetVertex(B)); + int f = AppendVertex(vNew); + return f; + } + + virtual void subclass_validity_checks(TFunction CheckOrFailF) const override + { + for (int VID : VertexIndices()) + { + FVector2 V = GetVertex(VID); + CheckOrFailF(VectorUtil::IsFinite(V)); + } + } +}; + +typedef FDynamicGraph2 FDynamicGraph2d; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Ray3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Ray3.h new file mode 100644 index 000000000000..14a74395a84d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Ray3.h @@ -0,0 +1,107 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistLine3Ray3 + +#pragma once + +#include "VectorTypes.h" +#include "LineTypes.h" +#include "RayTypes.h" + +/** + * Compute distance between 3D line and 3D ray + */ +template +class TDistLine3Ray3 +{ +public: + // Input + TLine3 Line; + TRay3 Ray; + + // Results + Real DistanceSquared = -1.0; + + FVector3 LineClosestPoint; + Real LineParameter; + FVector3 RayClosestPoint; + Real RayParameter; + + + TDistLine3Ray3(const TLine3& LineIn, const TRay3& RayIn) + { + Line = LineIn; + Ray = RayIn; + } + + Real Get() + { + return (Real)sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + FVector3 kDiff = Line.Origin - Ray.Origin; + Real a01 = -Line.Direction.Dot(Ray.Direction); + Real b0 = kDiff.Dot(Line.Direction); + Real c = kDiff.SquaredLength(); + Real det = FMath::Abs((Real)1 - a01 * a01); + Real b1, s0, s1, sqrDist; + + if (det >= TMathUtil::ZeroTolerance) + { + b1 = -kDiff.Dot(Ray.Direction); + s1 = a01 * b0 - b1; + + if (s1 >= (Real)0) + { + // Two interior points are closest, one on Line and one on Ray. + Real invDet = ((Real)1) / det; + s0 = (a01 * b1 - b0) * invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + ((Real)2) * b0) + + s1 * (a01 * s0 + s1 + ((Real)2) * b1) + c; + } + else + { + // Origin of Ray and interior point of Line are closest. + s0 = -b0; + s1 = (Real)0; + sqrDist = b0 * s0 + c; + } + } + else + { + // Lines are parallel, closest pair with one point at Ray origin. + s0 = -b0; + s1 = (Real)0; + sqrDist = b0 * s0 + c; + } + + LineClosestPoint = Line.Origin + s0 * Line.Direction; + RayClosestPoint = Ray.Origin + s1 * Ray.Direction; + LineParameter = s0; + RayParameter = s1; + + // Account for numerical round-off errors. + if (sqrDist < (Real)0) + { + sqrDist = (Real)0; + } + DistanceSquared = sqrDist; + + return sqrDist; + } +}; + +typedef TDistLine3Ray3 FDistLine3Ray3f; +typedef TDistLine3Ray3 FDistLine3Ray3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Segment3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Segment3.h new file mode 100644 index 000000000000..f52973f36c07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Segment3.h @@ -0,0 +1,122 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistLine3Segment3 +// which was ported from WildMagic 5 + +#pragma once + +#include "VectorTypes.h" +#include "SegmentTypes.h" +#include "LineTypes.h" + + +/** +* Compute unsigned distance between 3D line and 3D segment +*/ +template +class TDistLine3Segment3 +{ +public: + // Input + TLine3 Line; + TSegment3 Segment; + + // Output + Real DistanceSquared = -1.0; + Real LineParameter, SegmentParameter; + FVector3 LineClosest, SegmentClosest; + + + TDistLine3Segment3(const TLine3& LineIn, const TSegment3& SegmentIn) : Line(LineIn), Segment(SegmentIn) + { + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + FVector3 diff = Line.Origin - Segment.Center; + Real a01 = -Line.Direction.Dot(Segment.Direction); + Real b0 = diff.Dot(Line.Direction); + Real c = diff.SquaredLength(); + Real det = TMathUtil::Abs(1 - a01 * a01); + Real b1, s0, s1, sqrDist, extDet; + + if (det >= TMathUtil::ZeroTolerance) + { + // The Line and Segment are not parallel. + b1 = -diff.Dot(Segment.Direction); + s1 = a01 * b0 - b1; + extDet = Segment.Extent * det; + + if (s1 >= -extDet) + { + if (s1 <= extDet) + { + // Two interior points are closest, one on the Line and one + // on the Segment. + Real invDet = (1) / det; + s0 = (a01 * b1 - b0) * invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } + else + { + // The endpoint e1 of the Segment and an interior point of + // the Line are closest. + s1 = Segment.Extent; + s0 = -(a01 * s1 + b0); + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + } + else + { + // The end point e0 of the Segment and an interior point of the + // Line are closest. + s1 = -Segment.Extent; + s0 = -(a01 * s1 + b0); + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + } + else + { + // The Line and Segment are parallel. Choose the closest pair so that + // one point is at Segment center. + s1 = 0; + s0 = -b0; + sqrDist = b0 * s0 + c; + } + + LineClosest = Line.Origin + s0 * Line.Direction; + SegmentClosest = Segment.Center + s1 * Segment.Direction; + LineParameter = s0; + SegmentParameter = s1; + + // Account for numerical round-off errors. + if (sqrDist < 0) + { + sqrDist = 0; + } + + DistanceSquared = sqrDist; + + return DistanceSquared; + } +}; + +typedef TDistLine3Segment3 FDistLine3Segment3f; +typedef TDistLine3Segment3 FDistLine3Segment3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Triangle3.h new file mode 100644 index 000000000000..a86f22c76f2f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistLine3Triangle3.h @@ -0,0 +1,127 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistLine3Triangle3 +// which was ported from WildMagic 5 + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" +#include "SegmentTypes.h" +#include "DistLine3Segment3.h" + + +/** +* Compute unsigned distance between 3D line and 3D triangle +*/ +template +class TDistLine3Triangle3 +{ +public: + // Input + TLine3 Line; + TTriangle3 Triangle; + + // Output + Real DistanceSquared = -1.0; + Real LineParam; + FVector3 LineClosest, TriangleClosest, TriangleBaryCoords; + + + TDistLine3Triangle3(const TLine3& LineIn, const TTriangle3& TriangleIn) : Line(LineIn), Triangle(TriangleIn) + { + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + // Test if Line intersects Triangle. If so, the squared distance is zero. + FVector3 edge0 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge1 = Triangle.V[2] - Triangle.V[0]; + FVector3 normal = edge0.Cross(edge1); + normal.Normalize(); + Real NdD = normal.Dot(Line.Direction); + if (TMathUtil::Abs(NdD) > TMathUtil::ZeroTolerance) + { + // The line and triangle are not parallel, so the line intersects + // the plane of the triangle. + FVector3 diff = Line.Origin - Triangle.V[0]; + FVector3 U, V; + VectorUtil::MakePerpVectors(Line.Direction, U, V); + Real UdE0 = U.Dot(edge0); + Real UdE1 = U.Dot(edge1); + Real UdDiff = U.Dot(diff); + Real VdE0 = V.Dot(edge0); + Real VdE1 = V.Dot(edge1); + Real VdDiff = V.Dot(diff); + Real invDet = (1) / (UdE0 * VdE1 - UdE1 * VdE0); + + // Barycentric coordinates for the point of intersection. + Real b1 = (VdE1 * UdDiff - UdE1 * VdDiff) * invDet; + Real b2 = (UdE0 * VdDiff - VdE0 * UdDiff) * invDet; + Real b0 = 1 - b1 - b2; + + if (b0 >= 0 && b1 >= 0 && b2 >= 0) + { + // Line parameter for the point of intersection. + Real DdE0 = Line.Direction.Dot(edge0); + Real DdE1 = Line.Direction.Dot(edge1); + Real DdDiff = Line.Direction.Dot(diff); + LineParam = b1 * DdE0 + b2 * DdE1 - DdDiff; + + // Barycentric coordinates for the point of intersection. + TriangleBaryCoords = FVector3(b0, b1, b2); + + // The intersection point is inside or on the Triangle. + LineClosest = Line.Origin + LineParam * Line.Direction; + TriangleClosest = Triangle.V[0] + b1 * edge0 + b2 * edge1; + DistanceSquared = 0; + return 0; + } + } + + // Either (1) the Line is not parallel to the Triangle and the point of + // intersection of the Line and the plane of the Triangle is outside the + // Triangle or (2) the Line and Triangle are parallel. Regardless, the + // closest point on the Triangle is on an edge of the Triangle. Compare + // the Line to all three edges of the Triangle. + Real sqrDist = TMathUtil::MaxReal; + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + TSegment3 segment(Triangle.V[i0], Triangle.V[i1]); + TDistLine3Segment3 queryLS(Line, segment); + Real sqrDistTmp = queryLS.GetSquared(); + if (sqrDistTmp < sqrDist) + { + LineClosest = queryLS.LineClosest; + TriangleClosest = queryLS.SegmentClosest; + sqrDist = sqrDistTmp; + LineParam = queryLS.LineParameter; + Real ratio = queryLS.SegmentParameter / segment.Extent; + TriangleBaryCoords[i0] = (0.5) * (1 - ratio); + TriangleBaryCoords[i1] = 1 - TriangleBaryCoords[i0]; + TriangleBaryCoords[3 - i0 - i1] = 0; + } + } + + DistanceSquared = sqrDist; + return DistanceSquared; + } +}; + +typedef TDistLine3Triangle3 FDistLine3Triangle3f; +typedef TDistLine3Triangle3 FDistLine3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistPoint3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistPoint3Triangle3.h new file mode 100644 index 000000000000..d39af3ca44ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistPoint3Triangle3.h @@ -0,0 +1,275 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistPoint3Triangle3 + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" + + +/** + * Compute unsigned distance between 3D point and 3D triangle + */ +template +class TDistPoint3Triangle3 +{ +public: + // Input + FVector3 Point; + TTriangle3 Triangle; + + // Results + FVector3 TriangleBaryCoords; + FVector3 ClosestTrianglePoint; // do we need this just use Triangle.BarycentricPoint + + + TDistPoint3Triangle3(const FVector3& PointIn, const TTriangle3& TriangleIn) + { + Point = PointIn; + Triangle = TriangleIn; + } + + Real Get() + { + return (Real)sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + FVector3 diff = Triangle.V[0] - Point; + FVector3 edge0 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge1 = Triangle.V[2] - Triangle.V[0]; + Real a00 = edge0.SquaredLength(); + Real a01 = edge0.Dot(edge1); + Real a11 = edge1.SquaredLength(); + Real b0 = diff.Dot(edge0); + Real b1 = diff.Dot(edge1); + Real c = diff.SquaredLength(); + Real det = FMath::Abs(a00*a11 - a01 * a01); + Real s = a01 * b1 - a11 * b0; + Real t = a01 * b0 - a00 * b1; + Real sqrDistance; + + if (s + t <= det) + { + if (s < (Real)0) + { + if (t < (Real)0) // region 4 + { + if (b0 < (Real)0) + { + t = (Real)0; + if (-b0 >= a00) + { + s = (Real)1; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = -b0 / a00; + sqrDistance = b0 * s + c; + } + } + else + { + s = (Real)0; + if (b1 >= (Real)0) + { + t = (Real)0; + sqrDistance = c; + } + else if (-b1 >= a11) + { + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + t = -b1 / a11; + sqrDistance = b1 * t + c; + } + } + } + else // region 3 + { + s = (Real)0; + if (b1 >= (Real)0) + { + t = (Real)0; + sqrDistance = c; + } + else if (-b1 >= a11) + { + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + t = -b1 / a11; + sqrDistance = b1 * t + c; + } + } + } + else if (t < (Real)0) // region 5 + { + t = (Real)0; + if (b0 >= (Real)0) + { + s = (Real)0; + sqrDistance = c; + } + else if (-b0 >= a00) + { + s = (Real)1; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = -b0 / a00; + sqrDistance = b0 * s + c; + } + } + else // region 0 + { + // minimum at interior point + Real invDet = ((Real)1) / det; + s *= invDet; + t *= invDet; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + else + { + Real tmp0, tmp1, numer, denom; + + if (s < (Real)0) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Real)2)*a01 + a11; + if (numer >= denom) + { + s = (Real)1; + t = (Real)0; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = numer / denom; + t = (Real)1 - s; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + else + { + s = (Real)0; + if (tmp1 <= (Real)0) + { + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else if (b1 >= (Real)0) + { + t = (Real)0; + sqrDistance = c; + } + else + { + t = -b1 / a11; + sqrDistance = b1 * t + c; + } + } + } + else if (t < (Real)0) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Real)2)*a01 + a11; + if (numer >= denom) + { + t = (Real)1; + s = (Real)0; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + t = numer / denom; + s = (Real)1 - t; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + else + { + t = (Real)0; + if (tmp1 <= (Real)0) + { + s = (Real)1; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else if (b0 >= (Real)0) + { + s = (Real)0; + sqrDistance = c; + } + else + { + s = -b0 / a00; + sqrDistance = b0 * s + c; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= (Real)0) + { + s = (Real)0; + t = (Real)1; + sqrDistance = a11 + ((Real)2)*b1 + c; + } + else + { + denom = a00 - ((Real)2)*a01 + a11; + if (numer >= denom) + { + s = (Real)1; + t = (Real)0; + sqrDistance = a00 + ((Real)2)*b0 + c; + } + else + { + s = numer / denom; + t = (Real)1 - s; + sqrDistance = s * (a00*s + a01 * t + ((Real)2)*b0) + t * (a01*s + a11 * t + ((Real)2)*b1) + c; + } + } + } + } + + // Account for numerical round-off error. + if (sqrDistance < (Real)0) + { + sqrDistance = (Real)0; + } + + ClosestTrianglePoint = Triangle.V[0] + s * edge0 + t * edge1; + TriangleBaryCoords = FVector3((Real)1 - s - t, s, t); + return sqrDistance; + } + +}; + +typedef TDistPoint3Triangle3 FDistPoint3Triangle3f; +typedef TDistPoint3Triangle3 FDistPoint3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistRay3Segment3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistRay3Segment3.h new file mode 100644 index 000000000000..3dee02df99dd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistRay3Segment3.h @@ -0,0 +1,389 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistRay3Segment3 + +#pragma once + +#include "VectorTypes.h" +#include "SegmentTypes.h" +#include "RayTypes.h" + +/** + * Compute distance between 3D ray and 3D segment + */ +template +class TDistRay3Segment3 +{ +public: + // Input + TRay3 Ray; + TSegment3 Segment; + + // Results + Real DistanceSquared = -1.0; + + FVector3 RayClosestPoint; + Real RayParameter; + FVector3 SegmentClosestPoint; + Real SegmentParameter; + + + TDistRay3Segment3(const TRay3& RayIn, const TSegment3& SegmentIn) + { + Ray = RayIn; + Segment = SegmentIn; + } + + Real Get() + { + return (Real)sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + FVector3d diff = Ray.Origin - Segment.Center; + double a01 = -Ray.Direction.Dot(Segment.Direction); + double b0 = diff.Dot(Ray.Direction); + double b1 = -diff.Dot(Segment.Direction); + double c = diff.SquaredLength(); + double det = TMathUtil::Abs(1 - a01 * a01); + double s0, s1, sqrDist, extDet; + + if (det >= TMathUtil::ZeroTolerance ) + { + // The Ray and Segment are not parallel. + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = Segment.Extent * det; + + if (s0 >= 0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of Ray and Segment. + double invDet = (1) / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } + else // region 1 + { + s1 = Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else // region 5 + { + s1 = -Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = -Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + else if (s1 <= extDet) // region 3 + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + else // region 2 + { + s0 = -(a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + } + else + { + // Ray and Segment are parallel. + if (a01 > 0) + { + // Opposite direction vectors. + s1 = -Segment.Extent; + } + else + { + // Same direction vectors. + s1 = Segment.Extent; + } + + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + + RayClosestPoint = Ray.Origin + s0 * Ray.Direction; + SegmentClosestPoint = Segment.Center + s1 * Segment.Direction; + RayParameter = s0; + SegmentParameter = s1; + + // Account for numerical round-off errors. + if (sqrDist < (Real)0) + { + sqrDist = (Real)0; + } + DistanceSquared = sqrDist; + + return sqrDist; + } + + + + + + static double SquaredDistance(const TRay3& Ray, const TSegment3& Segment, + Real& RayParam, Real& SegParam) + { + FVector3 diff = Ray.Origin - Segment.Center; + double a01 = -Ray.Direction.Dot(Segment.Direction); + double b0 = diff.Dot(Ray.Direction); + double b1 = -diff.Dot(Segment.Direction); + double c = diff.SquaredLength(); + double det = TMathUtil::Abs(1 - a01 * a01); + double s0, s1, sqrDist, extDet; + + if (det >= TMathUtil::ZeroTolerance) + { + // The Ray and Segment are not parallel. + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = Segment.Extent * det; + + if (s0 >= 0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of Ray and Segment. + double invDet = (1) / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } + else // region 1 + { + s1 = Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else // region 5 + { + s1 = -Segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = -Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + else if (s1 <= extDet) // region 3 + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + else // region 2 + { + s0 = -(a01 * Segment.Extent + b0); + if (s0 > 0) + { + s1 = Segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + s1 = -b1; + if (s1 < -Segment.Extent) + { + s1 = -Segment.Extent; + } + else if (s1 > Segment.Extent) + { + s1 = Segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + } + else { + // Ray and Segment are parallel. + if (a01 > 0) + { + // Opposite direction vectors. + s1 = -Segment.Extent; + } + else + { + // Same direction vectors. + s1 = Segment.Extent; + } + + s0 = -(a01 * s1 + b0); + if (s0 > 0) + { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } + else + { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + + RayParam = s0; + SegParam = s1; + + // Account for numerical round-off errors. + if (sqrDist < 0) + { + sqrDist = 0; + } + return sqrDist; + } + + + + + + + +}; + +typedef TDistRay3Segment3 FDistRay3Segment3f; +typedef TDistRay3Segment3 FDistRay3Segment3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistSegment3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistSegment3Triangle3.h new file mode 100644 index 000000000000..6ebd65e2eb02 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistSegment3Triangle3.h @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistSegment3Triangle3 +// which was ported from WildMagic 5 + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" +#include "SegmentTypes.h" +#include "DistLine3Triangle3.h" +#include "DistPoint3Triangle3.h" + +/** +* Compute unsigned distance between 3D segment and 3D triangle +*/ +template +class TDistSegment3Triangle3 +{ +public: + // Input + TSegment3 Segment; + TTriangle3 Triangle; + + // Output + Real DistanceSquared = -1.0; + Real SegmentParameter; + FVector3 TriangleClosest, TriangleBaryCoords, SegmentClosest; + + TDistSegment3Triangle3(const TSegment3& SegmentIn, const TTriangle3& TriangleIn) : Segment(SegmentIn), Triangle(TriangleIn) + { + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + TLine3 line(Segment.Center, Segment.Direction); + TDistLine3Triangle3 queryLT(line, Triangle); + double sqrDist = queryLT.GetSquared(); + SegmentParameter = queryLT.LineParam; + + if (SegmentParameter >= -Segment.Extent) { + if (SegmentParameter <= Segment.Extent) { + SegmentClosest = queryLT.LineClosest; + TriangleClosest = queryLT.TriangleClosest; + TriangleBaryCoords = queryLT.TriangleBaryCoords; + } + else { + SegmentClosest = Segment.EndPoint(); + TDistPoint3Triangle3 queryPT(SegmentClosest, Triangle); + sqrDist = queryPT.GetSquared(); + TriangleClosest = queryPT.ClosestTrianglePoint; + SegmentParameter = Segment.Extent; + TriangleBaryCoords = queryPT.TriangleBaryCoords; + } + } + else { + SegmentClosest = Segment.StartPoint(); + TDistPoint3Triangle3 queryPT(SegmentClosest, Triangle); + sqrDist = queryPT.GetSquared(); + TriangleClosest = queryPT.ClosestTrianglePoint; + SegmentParameter = -Segment.Extent; + TriangleBaryCoords = queryPT.TriangleBaryCoords; + } + + DistanceSquared = sqrDist; + return DistanceSquared; + } +}; + +typedef TDistSegment3Triangle3 FDistSegment3Triangle3f; +typedef TDistSegment3Triangle3 FDistSegment3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistTriangle3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistTriangle3Triangle3.h new file mode 100644 index 000000000000..9004be13458d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Distance/DistTriangle3Triangle3.h @@ -0,0 +1,122 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DistTriangle3Triangle3 +// which was ported from WildMagic 5 + + +#pragma once + +#include "VectorTypes.h" +#include "TriangleTypes.h" +#include "SegmentTypes.h" +#include "DistSegment3Triangle3.h" +#include "DistPoint3Triangle3.h" + +/** +* Compute unsigned distance between 3D segment and 3D triangle +*/ +template +class TDistTriangle3Triangle3 +{ +public: + // Input + TTriangle3 Triangle[2]; + + // Output + Real DistanceSquared = -1.0; + FVector3 TriangleClosest[2]; + FVector3 TriangleBaryCoords[2]; + + + TDistTriangle3Triangle3() + { + } + TDistTriangle3Triangle3(const TTriangle3& TriangleA, const TTriangle3& TriangleB) + { + Triangle[0] = TriangleA; + Triangle[1] = TriangleB; + } + + Real Get() + { + return TMathUtil::Sqrt(ComputeResult()); + } + Real GetSquared() + { + return ComputeResult(); + } + + Real ComputeResult() + { + if (DistanceSquared >= 0) + { + return DistanceSquared; + } + + + // Compare edges of Triangle[0] to the interior of Triangle[1]. + Real sqrDist = TMathUtil::MaxReal, sqrDistTmp; + Real ratio; + int i0, i1; + for (i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + TSegment3 edge(Triangle[0].V[i0], Triangle[0].V[i1]); + + TDistSegment3Triangle3 queryST(edge, Triangle[1]); + sqrDistTmp = queryST.GetSquared(); + if (sqrDistTmp < sqrDist) + { + TriangleClosest[0] = queryST.SegmentClosest; + TriangleClosest[1] = queryST.TriangleClosest; + sqrDist = sqrDistTmp; + + ratio = queryST.SegmentParameter / edge.Extent; + TriangleBaryCoords[0][i0] = (0.5) * (1 - ratio); + TriangleBaryCoords[0][i1] = 1 - TriangleBaryCoords[0][i0]; + TriangleBaryCoords[0][3 - i0 - i1] = 0; + TriangleBaryCoords[1] = queryST.TriangleBaryCoords; + + if (sqrDist <= TMathUtil::ZeroTolerance) + { + DistanceSquared = 0; + return 0; + } + } + } + + // Compare edges of Triangle[1] to the interior of Triangle[0]. + for (i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + TSegment3 edge(Triangle[1].V[i0], Triangle[1].V[i1]); + + TDistSegment3Triangle3 queryST(edge, Triangle[0]); + sqrDistTmp = queryST.GetSquared(); + if (sqrDistTmp < sqrDist) + { + TriangleClosest[0] = queryST.SegmentClosest; + TriangleClosest[1] = queryST.TriangleClosest; + sqrDist = sqrDistTmp; + + ratio = queryST.SegmentParameter / edge.Extent; + TriangleBaryCoords[1][i0] = (0.5) * (1 - ratio); + TriangleBaryCoords[1][i1] = 1 - TriangleBaryCoords[1][i0]; + TriangleBaryCoords[1][3 - i0 - i1] = 0; + TriangleBaryCoords[0] = queryST.TriangleBaryCoords; + + if (sqrDist <= TMathUtil::ZeroTolerance) + { + DistanceSquared = 0; + return 0; + } + } + } + + + DistanceSquared = sqrDist; + return DistanceSquared; + } +}; + +typedef TDistTriangle3Triangle3 FDistTriangle3Triangle3f; +typedef TDistTriangle3Triangle3 FDistTriangle3Triangle3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/FrameTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/FrameTypes.h new file mode 100644 index 000000000000..92696261c6a1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/FrameTypes.h @@ -0,0 +1,377 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "VectorTypes.h" +#include "VectorUtil.h" +#include "RayTypes.h" +#include "Quaternion.h" + +/** + * TFrame3 is an object that represents an oriented 3D coordinate frame, ie orthogonal X/Y/Z axes at a point in space. + * One can think of this Frame as a local coordinate space measured along these axes. + * Functions are provided to map geometric objects to/from the Frame coordinate space. + * + * Internally the representation is the same as an FTransform, except a Frame has no Scale. + */ +template +struct TFrame3 +{ + /** + * Origin of the frame + */ + FVector3 Origin; + + /** + * Rotation of the frame. Think of this as the rotation of the unit X/Y/Z axes to the 3D frame axes. + */ + TQuaternion Rotation; + + /** + * Construct a frame positioned at (0,0,0) aligned to the unit axes + */ + TFrame3() + { + Origin = FVector3::Zero(); + Rotation = TQuaternion::Identity(); + } + + /** + * Construct a frame at the given Origin aligned to the unit axes + */ + TFrame3(const FVector3& OriginIn) + { + Origin = OriginIn; + Rotation = TQuaternion::Identity(); + } + + /** + * Construct a Frame from the given Origin and Rotation + */ + TFrame3(const FVector3& OriginIn, const TQuaternion RotationIn) + { + Origin = OriginIn; + Rotation = RotationIn; + } + + /** + * Construct a frame with the Z axis aligned to a target axis + * @param OriginIn origin of frame + * @param SetZ target Z axis + */ + TFrame3(const FVector3& OriginIn, const FVector3& SetZ) + { + Origin = OriginIn; + Rotation.SetFromTo(FVector3::UnitZ(), SetZ); + } + + /** + * Construct Frame from X/Y/Z axis vectors. Vectors must be mutually orthogonal. + * @param OriginIn origin of frame + * @param X desired X axis of frame + * @param Y desired Y axis of frame + * @param Z desired Z axis of frame + */ + TFrame3(const FVector3& OriginIn, const FVector3& X, const FVector3& Y, const FVector3& Z) + { + Origin = OriginIn; + Rotation = TQuaternion( TMatrix3(X, Y, Z, false) ); + } + + /** Construct a Frame from an FTransform */ + TFrame3(const FTransform& Transform) + { + Origin = Transform.GetTranslation(); + Rotation = Transform.GetRotation(); + } + + + /** + * @param AxisIndex index of axis of frame, either 0, 1, or 2 + * @return axis vector + */ + FVector3 GetAxis(int AxisIndex) const + { + switch (AxisIndex) + { + case 0: + return Rotation.AxisX(); + case 1: + return Rotation.AxisY(); + case 2: + return Rotation.AxisZ(); + default: + checkNoEntry(); + return FVector3::Zero(); // compiler demands a return value + } + } + + /** @return X axis of frame (axis 0) */ + FVector3 X() const + { + return Rotation.AxisX(); + } + + /** @return Y axis of frame (axis 1) */ + FVector3 Y() const + { + return Rotation.AxisY(); + } + + /** @return Z axis of frame (axis 2) */ + FVector3 Z() const + { + return Rotation.AxisZ(); + } + + /** @return conversion of this Frame to FTransform */ + FTransform ToFTransform() const + { + return FTransform(Rotation, Origin); + } + + + /** @return point at distances along frame axes */ + FVector3 PointAt(RealType X, RealType Y, RealType Z) const + { + return Rotation * FVector3(X,Y,Z) + Origin; + } + + /** @return point at distances along frame axes */ + FVector3 PointAt(const FVector3& Point) const + { + return Rotation * FVector3(Point.X, Point.Y, Point.Z) + Origin; + } + + /** @return input Point transformed into local coordinate system of Frame */ + FVector3 ToFramePoint(const FVector3& Point) const + { + return Rotation.InverseMultiply((Point-Origin)); + } + /** @return input Point transformed from local coordinate system of Frame into "World" coordinate system */ + FVector3 FromFramePoint(const FVector3& Point) const + { + return Rotation * Point + Origin; + } + + + /** @return input Vector transformed into local coordinate system of Frame */ + FVector3 ToFrameVector(const FVector3& Vector) const + { + return Rotation.InverseMultiply(Vector); + } + /** @return input Vector transformed from local coordinate system of Frame into "World" coordinate system */ + FVector3 FromFrameVector(const FVector3& Vector) const + { + return Rotation * Vector; + } + + + /** @return input Quaternion transformed into local coordinate system of Frame */ + TQuaternion ToFrame(const TQuaternion& Quat) const + { + return Rotation.Inverse() * Quat; + } + /** @return input Quaternion transformed from local coordinate system of Frame into "World" coordinate system */ + TQuaternion FromFrame(const TQuaternion& Quat) const + { + return Rotation * Quat; + } + + + /** @return input Ray transformed into local coordinate system of Frame */ + TRay3 ToFrame(const TRay3& Ray) const + { + return TRay3(ToFramePoint(Ray.Origin), ToFrameVector(Ray.Direction)); + } + /** @return input Ray transformed from local coordinate system of Frame into "World" coordinate system */ + TRay3 FromFrame(const TRay3& Ray) const + { + return TRay3(ToFramePoint(Ray.Origin), ToFrameVector(Ray.Direction)); + } + + + /** @return input Frame transformed into local coordinate system of this Frame */ + TFrame3 ToFrame(const TFrame3& Frame) const + { + return TFrame3(ToFramePoint(Frame.Origin), ToFrame(Frame.Rotation)); + } + /** @return input Frame transformed from local coordinate system of this Frame into "World" coordinate system */ + TFrame3 FromFrame(const TFrame3& Frame) const + { + return TFrame3(ToFramePoint(Frame.Origin), FromFrame(Frame.Rotation)); + } + + + + + /** + * Project 3D point into plane and convert to UV coordinates in that plane + * @param Pos 3D position + * @param PlaneNormalAxis which plane to project onto, identified by perpendicular normal. Default is 2, ie normal is Z, plane is (X,Y) + * @return 2D coordinates in UV plane, relative to origin + */ + FVector2 ToPlaneUV(const FVector3& Pos, int PlaneNormalAxis) const + { + int Axis0 = 0, Axis1 = 1; + if (PlaneNormalAxis == 0) + { + Axis0 = 2; + } + else if (PlaneNormalAxis == 1) + { + Axis1 = 2; + } + FVector3 LocalPos = Pos - Origin; + RealType U = LocalPos.Dot(GetAxis(Axis0)); + RealType V = LocalPos.Dot(GetAxis(Axis1)); + return FVector2(U, V); + } + + + + /** + * Map a point from local UV plane coordinates to the corresponding 3D point in one of the planes of the frame + * @param PosUV local UV plane coordinates + * @param PlaneNormalAxis which plane to map to, identified by perpendicular normal. Default is 2, ie normal is Z, plane is (X,Y) + * @return 3D coordinates in frame's plane (including Origin translation) + */ + FVector3 FromPlaneUV(const FVector2& PosUV, int PlaneNormalAxis) const + { + FVector3 PlanePos(PosUV[0], PosUV[1], 0); + if (PlaneNormalAxis == 0) + { + PlanePos[0] = 0; PlanePos[2] = PosUV[0]; + } + else if (PlaneNormalAxis == 1) + { + PlanePos[1] = 0; PlanePos[2] = PosUV[1]; + } + return Rotation*PlanePos + Origin; + } + + + + /** + * Project a point onto one of the planes of the frame + * @param Pos 3D position + * @param PlaneNormalAxis which plane to project onto, identified by perpendicular normal. Default is 2, ie normal is Z, plane is (X,Y) + * @return 3D coordinate in the plane + */ + FVector3 ToPlane(const FVector3& Pos, int PlaneNormalAxis) const + { + FVector3 Normal = GetAxis(PlaneNormalAxis); + FVector3 LocalVec = Pos - Origin; + RealType SignedDist = LocalVec.Dot(Normal); + return Pos - SignedDist * Normal; + } + + + + + /** + * Rotate this frame by given quaternion + */ + void Rotate(const TQuaternion& Quat) + { + Rotation = Quat * Rotation; + } + + + /** + * transform this frame by the given transform + */ + void Transform(const FTransform& XForm) + { + Origin = (FVector3)XForm.TransformPosition((FVector3f)Origin); + Rotation = TQuaternion(XForm.GetRotation()) * Rotation; + } + + + /** + * Align an axis of this frame with a target direction + * @param AxisIndex which axis to align + * @param ToDirection target direction + */ + void AlignAxis(int AxisIndex, const FVector3& ToDirection) + { + TQuaternion RelRotation(GetAxis(AxisIndex), ToDirection); + Rotate(RelRotation); + } + + + /** + * Compute rotation around vector that best-aligns axis of frame with target direction + * @param AxisIndex which axis to try to align + * @param ToDirection target direction + * @param AroundVector rotation is constrained to be around this vector (ie this direction in frame stays constant) + */ + void ConstrainedAlignAxis(int AxisIndex, const FVector3& ToDirection, const FVector3& AroundVector) + { + //@todo PlaneAngleSigned does acos() and then SetAxisAngleD() does cos/sin...can we optimize this? + FVector3 AxisVec = GetAxis(AxisIndex); + RealType AngleDeg = VectorUtil::PlaneAngleSignedD(AxisVec, ToDirection, AroundVector); + TQuaternion RelRotation; + RelRotation.SetAxisAngleD(AroundVector, AngleDeg); + Rotate(RelRotation); + } + + + /** + * Compute rotation around NormalAxis that best-aligns one of the other two frame axes with either given UpAxis or FallbackAxis + * (FallbackAxis is required if Dot(NormalAxis,UpAxis) > UpDotTolerance, ie if the Normal and Up directions are too closely aligned. + * Basically this divides direction-sphere into three regions - polar caps with size defined by UpDotTolerance, and + * a wide equator band covering the rest. When crossing between these regions the alignment has a discontinuity. + * It is impossible to avoid this discontinuity because it is impossible to comb a sphere. + * @param PerpAxis1 Index of first axis orthogonal to NormalAxis + * @param PerpAxis2 Index of second axis orthogonal to NormalAxis + * @param NormalAxis Axis of frame to rotate around + * @param UpAxis Target axis in equator region, defaults to UnitZ + * @param FallbackAxis Target axis in polar region, defaults to UnitX + * @param UpDotTolerance defaults to cos(45), ie flip between regions happens roughly half way to poles + */ + void ConstrainedAlignPerpAxes(int PerpAxis1 = 0, int PerpAxis2 = 1, int NormalAxis = 2, + const FVector3& UpAxis = FVector3::UnitZ(), + const FVector3& FallbackAxis = FVector3::UnitX(), + RealType UpDotTolerance = (RealType)0.707) + { + check(PerpAxis1 != PerpAxis2 && PerpAxis1 != NormalAxis && PerpAxis2 != NormalAxis); + const FVector3 NormalVec = GetAxis(NormalAxis); + + // decide if we should use Fallback (polar-cap) axis or main (equator-region) axis + const FVector3& TargetAxis = + (TMathUtil::Abs(NormalVec.Dot(UpAxis)) > UpDotTolerance) ? + FallbackAxis : UpAxis; + + // figure out which PerpAxis is closer to target, and align that one to +/- TargetAxis (whichever is smaller rotation) + FVector2 Dots(GetAxis(PerpAxis1).Dot(TargetAxis), GetAxis(PerpAxis2).Dot(TargetAxis)); + int UseAxis = (TMathUtil::Abs(Dots.X) > TMathUtil::Abs(Dots.Y)) ? 0 : 1; + RealType UseSign = Dots[UseAxis] < 0 ? -1 : 1; + ConstrainedAlignAxis(UseAxis, UseSign*TargetAxis, NormalVec); + } + + + /** + * Compute intersection of ray with plane defined by frame origin and axis as normal + * @param RayOrigin origin of ray + * @param RayDirection direction of ray + * @param PlaneNormalAxis which axis of frame to use as plane normal + * @return intersection point, or FVector3::Max() if ray is parallel to plane + */ + FVector3 RayPlaneIntersection(const FVector3& RayOrigin, const FVector3& RayDirection, int PlaneNormalAxis) + { + FVector3 Normal = GetAxis(PlaneNormalAxis); + RealType PlaneD = -Origin.Dot(Normal); + RealType NormalDot = RayDirection.Dot(Normal); + if (VectorUtil::EpsilonEqual(NormalDot, (RealType)0, TMathUtil::ZeroTolerance)) + return FVector3::Max(); + RealType t = -( RayOrigin.Dot(Normal) + PlaneD) / NormalDot; + return RayOrigin + t * RayDirection; + } + +}; + +typedef TFrame3 FFrame3f; +typedef TFrame3 FFrame3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometricObjectsModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometricObjectsModule.h new file mode 100644 index 000000000000..1238f56b0bd5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometricObjectsModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FGeometricObjectsModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometryTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometryTypes.h new file mode 100644 index 000000000000..e651d8a226b6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/GeometryTypes.h @@ -0,0 +1,169 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** + * EMeshResult is returned by various mesh/graph operations to either indicate success, + * or communicate which type of error ocurred (some errors are recoverable, and some not). + */ +enum class EMeshResult +{ + Ok = 0, + Failed_NotAVertex = 1, + Failed_NotATriangle = 2, + Failed_NotAnEdge = 3, + + Failed_BrokenTopology = 10, + Failed_HitValenceLimit = 11, + + Failed_IsBoundaryEdge = 20, + Failed_FlippedEdgeExists = 21, + Failed_IsBowtieVertex = 22, + Failed_InvalidNeighbourhood = 23, // these are all failures for CollapseEdge + Failed_FoundDuplicateTriangle = 24, + Failed_CollapseTetrahedron = 25, + Failed_CollapseTriangle = 26, + Failed_NotABoundaryEdge = 27, + Failed_SameOrientation = 28, + + Failed_WouldCreateBowtie = 30, + Failed_VertexAlreadyExists = 31, + Failed_CannotAllocateVertex = 32, + Failed_VertexStillReferenced = 33, + + Failed_WouldCreateNonmanifoldEdge = 50, + Failed_TriangleAlreadyExists = 51, + Failed_CannotAllocateTriangle = 52, + + Failed_UnrecoverableError = 1000 +}; + + + +/** + * EOperationValidationResult is meant to be returned by Validate() functions of + * Operation classes (eg like ExtrudeMesh, etc) to indicate whether the operation + * can be successfully applied. + */ +enum class EOperationValidationResult +{ + Ok = 0, + Failed_UnknownReason = 1, + + Failed_InvalidTopology = 2 +}; + + +/** + * EValidityCheckFailMode is passed to CheckValidity() functions of various classes + * to specify how validity checks should fail. + */ +enum class EValidityCheckFailMode +{ + /** Function returns false if a failure is encountered */ + ReturnOnly = 0, + /** Function check()'s if a failure is encountered */ + Check = 1, + /** Function ensure()'s if a failure is encountered */ + Ensure = 2 +}; + + + + + +/** + * TIndexMap stores mappings between indices, which are assumed to be an integer type. + * Both forward and backward mapping are stored + * + * @todo make either mapping optional + * @todo optionally support using flat arrays instead of TMaps + * @todo constructors that pick between flat array and TMap modes depending on potential size/sparsity of mapped range + * @todo identity and shift modes that don't actually store anything + */ +template +struct TIndexMap +{ +protected: + TMap ForwardMap; + TMap ReverseMap; + bool bWantForward; + bool bWantReverse; + IntType InvalidID; + +public: + + TIndexMap() + { + bWantForward = bWantReverse = true; + InvalidID = (IntType)-9999999; + } + + void Reset() + { + ForwardMap.Reset(); + ReverseMap.Reset(); + } + + /** @return the value used to indicate "invalid" in the mapping */ + inline IntType GetInvalidID() const { return InvalidID; } + + TMap& GetForwardMap() { return ForwardMap; } + const TMap& GetForwardMap() const { return ForwardMap; } + + TMap& GetReverseMap() { return ReverseMap; } + const TMap& GetReverseMap() const { return ReverseMap; } + + /** add mapping from one index to another */ + inline void Add(IntType FromID, IntType ToID) + { + ForwardMap.Add(FromID, ToID); + ReverseMap.Add(ToID, FromID); + } + + /** @return true if we can map forward from this value */ + inline bool ContainsFrom(IntType FromID) const + { + check(bWantForward); + return ForwardMap.Contains(FromID); + } + + /** @return true if we can reverse-map from this value */ + inline bool ContainsTo(IntType ToID) const + { + check(bWantReverse); + return ReverseMap.Contains(ToID); + } + + + /** @return forward-map of input value */ + inline IntType GetTo(IntType FromID) const + { + check(bWantForward); + const IntType* FoundVal = ForwardMap.Find(FromID); + return (FoundVal == nullptr) ? InvalidID : *FoundVal; + } + + /** @return reverse-map of input value */ + inline IntType GetFrom(IntType ToID) const + { + check(bWantReverse); + const IntType* FoundVal = ReverseMap.Find(ToID); + return (FoundVal == nullptr) ? InvalidID : *FoundVal; + } + + void Reserve(int NumElements) + { + if (bWantForward) + { + ForwardMap.Reserve(NumElements); + } + if (bWantReverse) + { + ReverseMap.Reserve(NumElements); + } + } +}; +typedef TIndexMap FIndexMapi; + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/IndexTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/IndexTypes.h new file mode 100644 index 000000000000..a707a623d154 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/IndexTypes.h @@ -0,0 +1,229 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/IntVector.h" +#include + + +namespace IndexConstants +{ + constexpr int InvalidID = -1; +} + +/** + * 2-index tuple. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + */ +struct FIndex2i +{ + int A, B; + + FIndex2i() + { + } + FIndex2i(int ValA, int ValB) + { + this->A = ValA; + this->B = ValB; + } + + static FIndex2i Zero() + { + return FIndex2i(0, 0); + } + static FIndex2i Max() + { + return FIndex2i(TNumericLimits::Max(), TNumericLimits::Max()); + } + static FIndex2i Invalid() + { + return FIndex2i(IndexConstants::InvalidID, IndexConstants::InvalidID); + } + + int& operator[](int Idx) + { + return (&A)[Idx]; + } + const int& operator[](int Idx) const + { + return (&A)[Idx]; + } + + inline bool operator==(const FIndex2i& Other) const + { + return A == Other.A && B == Other.B; + } + + inline bool operator!=(const FIndex2i& Other) const + { + return A != Other.A || B != Other.B; + } + + inline void Swap() + { + int tmp = A; + A = B; + B = tmp; + } +}; + +FORCEINLINE uint32 GetTypeHash(const FIndex2i& Index) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Index, sizeof(FIndex2i)); +} + + + +/** + * 3-index tuple. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + * + * Implicit casts to/from FIntVector are defined. + */ +struct FIndex3i +{ + int A, B, C; + + FIndex3i() + { + } + FIndex3i(int ValA, int ValB, int ValC) + { + this->A = ValA; + this->B = ValB; + this->C = ValC; + } + + static FIndex3i Zero() + { + return FIndex3i(0, 0, 0); + } + static FIndex3i Max() + { + return FIndex3i(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); + } + static FIndex3i Invalid() + { + return FIndex3i(IndexConstants::InvalidID, IndexConstants::InvalidID, IndexConstants::InvalidID); + } + + int& operator[](int Idx) + { + return (&A)[Idx]; + } + const int& operator[](int Idx) const + { + return (&A)[Idx]; + } + + bool operator==(const FIndex3i& Other) const + { + return A == Other.A && B == Other.B && C == Other.C; + } + + bool operator!=(const FIndex3i& Other) const + { + return A != Other.A || B != Other.B || C != Other.C; + } + + int IndexOf(int Value) const + { + return (A == Value) ? 0 : ((B == Value) ? 1 : (C == Value ? 2 : -1)); + } + + int Contains(int Value) const + { + return (A == Value) || (B == Value) || (C == Value); + } + + operator FIntVector() const + { + return FIntVector(A, B, C); + } + FIndex3i(const FIntVector& Vec) + { + A = Vec.X; + B = Vec.Y; + C = Vec.Z; + } +}; + +FORCEINLINE uint32 GetTypeHash(const FIndex3i& Index) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Index, sizeof(FIndex3i)); +} + + + +/** + * 4-index tuple. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + */ + +struct FIndex4i +{ + int A, B, C, D; + + FIndex4i() + { + } + FIndex4i(int ValA, int ValB, int ValC, int ValD) + { + this->A = ValA; + this->B = ValB; + this->C = ValC; + this->D = ValD; + } + + static FIndex4i Zero() + { + return FIndex4i(0, 0, 0, 0); + } + static FIndex4i Max() + { + return FIndex4i(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); + } + static FIndex4i Invalid() + { + return FIndex4i(IndexConstants::InvalidID, IndexConstants::InvalidID, IndexConstants::InvalidID, IndexConstants::InvalidID); + } + + int& operator[](int Idx) + { + return (&A)[Idx]; + } + const int& operator[](int Idx) const + { + return (&A)[Idx]; + } + + bool operator==(const FIndex4i& Other) const + { + return A == Other.A && B == Other.B && C == Other.C && D == Other.D; + } + + bool operator!=(const FIndex4i& Other) const + { + return A != Other.A || B != Other.B || C != Other.C || D != Other.D; + } + + bool Contains(int Idx) const + { + return A == Idx || B == Idx || C == Idx || D == Idx; + } +}; + +FORCEINLINE uint32 GetTypeHash(const FIndex4i& Index) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Index, sizeof(FIndex4i)); +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionUtil.h new file mode 100644 index 000000000000..02459e8793a3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntersectionUtil.h @@ -0,0 +1,267 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "BoxTypes.h" + + + +/** + * FLinearIntersection contains intersection information returned by linear/primitive intersection functions + */ +struct FLinearIntersection +{ + bool intersects; + int numIntersections; // 0, 1, or 2 + FInterval1d parameter; // t-values along ray +}; + + +/** + * IntersectionUtil contains functions to compute intersections between geometric objects + */ +namespace IntersectionUtil +{ + + template + bool RayTriangleTest(const FVector3& RayOrigin, const FVector3& RayDirection, const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + // same code as IntrRay3Triangle3, but can be called w/o constructing additional data structures + + // Compute the offset origin, edges, and normal. + FVector3 diff = RayOrigin - V0; + FVector3 edge1 = V1 - V0; + FVector3 edge2 = V2 - V0; + FVector3 normal = edge1.Cross(edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + RealType DdN = RayDirection.Dot(normal); + RealType sign; + if (DdN > TMathUtil::ZeroTolerance) + { + sign = 1; + } + else if (DdN < -TMathUtil::ZeroTolerance) + { + sign = -1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + return false; + } + + RealType DdQxE2 = sign * RayDirection.Dot(diff.Cross(edge2)); + if (DdQxE2 >= 0) + { + RealType DdE1xQ = sign * RayDirection.Dot(edge1.Cross(diff)); + if (DdE1xQ >= 0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check if ray does. + RealType QdN = -sign * diff.Dot(normal); + if (QdN >= 0) + { + // Ray intersects triangle. + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + return false; + + } + + + + /** + * Test if line intersects sphere + * @return true if line intersects sphere + */ + template + bool LineSphereTest( + const FVector3& LineOrigin, + const FVector3& LineDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrLine3Sphere3.h + + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + + FVector3 diff = LineOrigin - SphereCenter; + RealType a0 = diff.SquaredLength() - SphereRadius * SphereRadius; + RealType a1 = LineDirection.Dot(diff); + + // Intersection occurs when Q(t) has real roots. + RealType discr = a1 * a1 - a0; + return (discr >= 0); + } + + + /** + * Intersect line with sphere and return intersection info (# hits, ray parameters) + * @return true if line intersects sphere + */ + template + bool LineSphereIntersection( + const FVector3& LineOrigin, + const FVector3& LineDirection, + const FVector3& SphereCenter, + RealType SphereRadius, + FLinearIntersection& ResultOut) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrLine3Sphere3.h + + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + FVector3 diff = LineOrigin - SphereCenter; + RealType a0 = diff.SquaredLength() - SphereRadius * SphereRadius; + RealType a1 = LineDirection.Dot(diff); + + // Intersection occurs when Q(t) has real roots. + RealType discr = a1 * a1 - a0; + if (discr > 0) + { + ResultOut.intersects = true; + ResultOut.numIntersections = 2; + RealType root = FMath::Sqrt(discr); + ResultOut.parameter.Min = -a1 - root; + ResultOut.parameter.Max = -a1 + root; + } + else if (discr < 0) + { + ResultOut.intersects = false; + ResultOut.numIntersections = 0; + } + else + { + ResultOut.intersects = true; + ResultOut.numIntersections = 1; + ResultOut.parameter.Min = -a1; + ResultOut.parameter.Max = -a1; + } + return ResultOut.intersects; + } + + template + FLinearIntersection LineSphereIntersection( + const FVector3& LineOrigin, + const FVector3& LineDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + FLinearIntersection result; + LineSphereIntersection(LineOrigin, LineDirection, SphereCenter, SphereRadius, result); + return result; + } + + + + /** + * @return true if ray intersects sphere + */ + template + bool RaySphereTest( + const FVector3& RayOrigin, + const FVector3& RayDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Sphere3.h + + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + + FVector3 diff = RayOrigin - SphereCenter; + RealType a0 = diff.SquaredLength() - SphereRadius * SphereRadius; + if (a0 <= 0) + { + return true; // P is inside the sphere. + } + // else: P is outside the sphere + RealType a1 = RayDirection.Dot(diff); + if (a1 >= 0) + { + return false; + } + + // Intersection occurs when Q(t) has RealType roots. + RealType discr = a1 * a1 - a0; + return (discr >= 0); + } + + + /** + * Intersect ray with sphere and return intersection info (# hits, ray parameters) + * @return true if ray intersects sphere + */ + template + bool RaySphereIntersection( + const FVector3& RayOrigin, + const FVector3& RayDirection, + const FVector3& SphereCenter, + RealType SphereRadius, + FLinearIntersection& Result) + { + // [RMS] adapted from GeometricTools GTEngine + // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Sphere3.h + + RaySphereIntersection(RayOrigin, RayDirection, SphereCenter, SphereRadius, Result); + if (Result.intersects) + { + // The line containing the ray intersects the sphere; the t-interval + // is [t0,t1]. The ray intersects the sphere as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + if (Result.parameter.Max < 0) + { + Result.intersects = false; + Result.numIntersections = 0; + } + else if (Result.parameter.Min < 0) + { + Result.numIntersections--; + Result.parameter.Min = Result.parameter.Max; + } + } + return Result.intersects; + } + + template + FLinearIntersection RaySphereIntersection( + const FVector3& RayOrigin, + const FVector3& RayDirection, + const FVector3& SphereCenter, + RealType SphereRadius) + { + FLinearIntersection result; + LineSphereIntersection(RayOrigin, RayDirection, SphereCenter, SphereRadius, result); + return result; + } + +}; + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/Intersector1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/Intersector1.h new file mode 100644 index 000000000000..2bcebe30d937 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/Intersector1.h @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Intersector1.h ported from WildMagic5 +=============================================================================*/ + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" +#include "BoxTypes.h" + +/** + * TIntersector1 computes the intersection of one-dimensional intervals [u0,u1] and [v0,v1]. + * The end points of the input intervals must be ordered u0 <= u1 and v0 <= v1. + * Infinite and degenerate intervals are allowed. + */ +template +class TIntersector1 +{ +private: + /** intersection point/interval, access via GetIntersection */ + TInterval1 Intersections; + +public: + /** First interval */ + TInterval1 U; + /** Second interval */ + TInterval1 V; + + /** + * Number of intersections found. + * 0: intervals do not overlap + * 1: intervals are touching at a single point + * 2: intervals intersect in an interval + */ + int NumIntersections = 0; + + TIntersector1(RealType u0, RealType u1, RealType v0, RealType v1) : Intersections(0,0), U(u0, u1), V(v0, v1) + { + check(u0 <= u1); + check(v0 <= v1); + } + + TIntersector1(const TInterval1& u, const TInterval1& v) : Intersections(0, 0), U(u), V(v) + { + } + + /** + * Fast check to see if intervals intersect, but does not calculate intersection interval + * @return true if intervals intersect + */ + bool Test() const + { + return U.Min <= V.Max && U.Max >= V.Min; + } + + /** + * @return first or second point of intersection interval + */ + RealType GetIntersection(int i) + { + return i == 0 ? Intersections.Min : Intersections.Max; + } + + /** + * Calculate the intersection interval + * @return true if the intervals intersect + */ + bool Find() + { + if (U.Max < V.Min || U.Min > V.Max) + { + NumIntersections = 0; + } + else if (U.Max > V.Min) + { + if (U.Min < V.Max) + { + NumIntersections = 2; + Intersections.Min = (U.Min < V.Min ? V.Min : U.Min); + Intersections.Max = (U.Max > V.Max ? V.Max : U.Max); + if (Intersections.Min == Intersections.Max) + { + NumIntersections = 1; + } + } + else + { + // U.Min == V.Max + NumIntersections = 1; + Intersections.Min = U.Min; + } + } + else + { + // U.Max == V.Min + NumIntersections = 1; + Intersections.Min = U.Max; + } + + return NumIntersections > 0; + } + +}; + +typedef TIntersector1 FIntersector1d; +typedef TIntersector1 FIntersector1f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrLine2Line2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrLine2Line2.h new file mode 100644 index 000000000000..ad2e4e94af3a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrLine2Line2.h @@ -0,0 +1,175 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp IntrLine2Line2 + +#pragma once + +#include "VectorTypes.h" +#include "LineTypes.h" +#include "VectorUtil.h" // for EIntersectionType + +/** + * Compute intersection between two 2D lines + */ +template +class TIntrLine2Line2 +{ +protected: + // input data + TLine2 Line1; + TLine2 Line2; + RealType dotThresh = TMathUtil::ZeroTolerance; + +public: + // result data + FVector2 Point; + RealType Segment1Parameter; + RealType Segment2Parameter; + int Quantity = 0; + EIntersectionResult Result = EIntersectionResult::NotComputed; + EIntersectionType Type = EIntersectionType::Empty; + + + + TIntrLine2Line2(const TLine2& Line1In, const TLine2& Line2In) + : Line1(Line1In), Line2(Line2In) + { + } + + + const TLine2& GetLine1() const + { + return Line1; + } + + void SetLine1(const TLine2& Value) + { + Line1 = Value; + Result = EIntersectionResult::NotComputed; + } + + const TLine2& GetLine2() const + { + return Line2; + } + + void SetLine2(TLine2& Value) + { + Line2 = Value; + Result = EIntersectionResult::NotComputed; + } + + + RealType GetDotThreshold() const + { + return dotThresh; + } + + void SetDotThreshold(RealType Value) + { + dotThresh = FMath::Max(Value, (RealType)0); + Result = EIntersectionResult::NotComputed; + } + + bool IsSimpleIntersection() const + { + return Result == EIntersectionResult::Intersects && Type == EIntersectionType::Point; + } + + + TIntrLine2Line2& Compute() + { + Find(); + return *this; + } + + + bool Find() + { + if (Result != EIntersectionResult::NotComputed) + { + return (Result == EIntersectionResult::Intersects); + } + + // [RMS] if either line direction is not a normalized vector, + // results are garbage, so fail query + if (Line1.Direction.IsNormalized() == false || Line2.Direction.IsNormalized() == false) + { + Type = EIntersectionType::Empty; + Result = EIntersectionResult::InvalidQuery; + return false; + } + + FVector2 s = FVector2::Zero(); + Type = Classify(Line1.Origin, Line1.Direction, + Line2.Origin, Line2.Direction, dotThresh, s); + + if (Type == EIntersectionType::Point) + { + Quantity = 1; + Point = Line1.Origin + s.X*Line1.Direction; + Segment1Parameter = s.X; + Segment2Parameter = s.Y; + } + else if (Type == EIntersectionType::Line) + { + Quantity = std::numeric_limits::max(); + } + else + { + Quantity = 0; + } + + Result = (Type != EIntersectionType::Empty) ? + EIntersectionResult::Intersects : EIntersectionResult::NoIntersection; + return (Result == EIntersectionResult::Intersects); + } + + + + static EIntersectionType Classify( + const FVector2& P0, const FVector2& D0, + const FVector2& P1, const FVector2& D1, + RealType dotThreshold, FVector2& s) + { + // Ensure dotThreshold is nonnegative. + dotThreshold = FMath::Max(dotThreshold, (RealType)0); + + // The intersection of two lines is a solution to P0+s0*D0 = P1+s1*D1. + // Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If D0.Dot(Perp(D1)) = 0, + // the lines are parallel. Additionally, if Q.Dot(Perp(D1)) = 0, the + // lines are the same. If D0.Dot(Perp(D1)) is not zero, then + // s0 = Q.Dot(Perp(D1))/D0.Dot(Perp(D1)) + // produces the point of intersection. Also, + // s1 = Q.Dot(Perp(D0))/D0.Dot(Perp(D1)) + + FVector2 diff = P1 - P0; + RealType D0DotPerpD1 = D0.DotPerp(D1); + if (FMath::Abs(D0DotPerpD1) > dotThreshold) + { + // Lines intersect in a single point. + RealType invD0DotPerpD1 = 1.0 / D0DotPerpD1; + RealType diffDotPerpD0 = diff.DotPerp(D0); + RealType diffDotPerpD1 = diff.DotPerp(D1); + s[0] = diffDotPerpD1 * invD0DotPerpD1; + s[1] = diffDotPerpD0 * invD0DotPerpD1; + return EIntersectionType::Point; + } + + // Lines are parallel. + diff.Normalize(); + RealType diffNDotPerpD1 = diff.DotPerp(D1); + if (FMath::Abs(diffNDotPerpD1) <= dotThreshold) + { + // Lines are collinear. + return EIntersectionType::Line; + } + + // Lines are parallel, but distinct. + return EIntersectionType::Empty; + } + +}; + +typedef TIntrLine2Line2 FIntrLine2TLine2d; +typedef TIntrLine2Line2 FIntrLine2Line2f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3AxisAlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3AxisAlignedBox3.h new file mode 100644 index 000000000000..0c9ded110a5f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3AxisAlignedBox3.h @@ -0,0 +1,220 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic IntrRay3Box3, simplified for Axis-Aligned Bounding Box + +#pragma once + +#include "RayTypes.h" +#include "BoxTypes.h" +#include "VectorUtil.h" + + +/** + * Compute intersection between 3D ray and 3D axis-aligned box + */ +template +class TIntrRay3AxisAlignedBox3 +{ +public: + + // @todo port object version + + + + /** + * Test if ray intersects box + * @param Ray query ray + * @param Box query box + * @param ExpandExtents box is expanded by this amount in each direction, useful for dealing with float imprecision + * @return true if ray intersects box + */ + static bool TestIntersection(const TRay3& Ray, const TAxisAlignedBox3& Box, RealType ExpandExtents = 0) + { + FVector3 WdU = FVector3::Zero(); + FVector3 AWdU = FVector3::Zero(); + FVector3 DdU = FVector3::Zero(); + FVector3 ADdU = FVector3::Zero(); + RealType RHS; + + FVector3 diff = Ray.Origin - Box.Center(); + FVector3 extent = Box.Extents() + ExpandExtents; + + WdU.X = Ray.Direction.X; + AWdU.X = FMath::Abs(WdU.X); + DdU.X = diff.X; + ADdU.X = FMath::Abs(DdU.X); + if (ADdU.X > extent.X && DdU.X * WdU.X >= (RealType)0) + { + return false; + } + + WdU.Y = Ray.Direction.Y; + AWdU.Y = FMath::Abs(WdU.Y); + DdU.Y = diff.Y; + ADdU.Y = FMath::Abs(DdU.Y); + if (ADdU.Y > extent.Y && DdU.Y * WdU.Y >= (RealType)0) + { + return false; + } + + WdU.Z = Ray.Direction.Z; + AWdU.Z = FMath::Abs(WdU.Z); + DdU.Z = diff.Z; + ADdU.Z = FMath::Abs(DdU.Z); + if (ADdU.Z > extent.Z && DdU.Z * WdU.Z >= (RealType)0) + { + return false; + } + + FVector3 WxD = Ray.Direction.Cross(diff); + FVector3 AWxDdU = FVector3::Zero(); + + AWxDdU.X = FMath::Abs(WxD.X); + RHS = extent.Y * AWdU.Z + extent.Z * AWdU.Y; + if (AWxDdU.X > RHS) + { + return false; + } + + AWxDdU.Y = FMath::Abs(WxD.Y); + RHS = extent.X * AWdU.Z + extent.Z * AWdU.X; + if (AWxDdU.Y > RHS) + { + return false; + } + + AWxDdU.Z = FMath::Abs(WxD.Z); + RHS = extent.X * AWdU.Y + extent.Y * AWdU.X; + if (AWxDdU.Z > RHS) + { + return false; + } + + return true; + } + + + + /** + * Find intersection of ray with AABB and returns ray T-value of intersection point (or TNumericLimits::Max() on miss) + * @param Ray query ray + * @param Box query box + * @param RayParamOut ray intersect T-value, or TNumericLimits::Max() + * @return true if ray intersects box + */ + static bool FindIntersection(const TRay3& Ray, const TAxisAlignedBox3& Box, RealType& RayParamOut) + { + RealType RayParam0 = 0.0; + RealType RayParam1 = TNumericLimits::Max(); + int Quantity = 0; + FVector3 Point0 = FVector3::Zero(); + FVector3 Point1 = FVector3::Zero(); + EIntersectionType Type = EIntersectionType::Empty; + DoClipping(RayParam0, RayParam1, Ray.Origin, Ray.Direction, Box, + true, Quantity, Point0, Point1, Type); + + if (Type != EIntersectionType::Empty) + { + RayParamOut = RayParam0; + return true; + } + else + { + RayParamOut = TNumericLimits::Max(); + return false; + } + } + + + + +protected: + + // internal functions + + static bool DoClipping(RealType t0, RealType t1, + const FVector3& RayOrigin, const FVector3& RayDirection, + const TAxisAlignedBox3& Box, bool solid, + int& quantity, FVector3& Point0, FVector3& Point1, EIntersectionType& intrType) + { + FVector3 BOrigin = RayOrigin - Box.Center(); + FVector3 extent = Box.Extents(); + + RealType saveT0 = t0, saveT1 = t1; + bool notAllClipped = + Clip(+RayDirection.X, -BOrigin.X - extent.X, t0, t1) && + Clip(-RayDirection.X, +BOrigin.X - extent.X, t0, t1) && + Clip(+RayDirection.Y, -BOrigin.Y - extent.Y, t0, t1) && + Clip(-RayDirection.Y, +BOrigin.Y - extent.Y, t0, t1) && + Clip(+RayDirection.Z, -BOrigin.Z - extent.Z, t0, t1) && + Clip(-RayDirection.Z, +BOrigin.Z - extent.Z, t0, t1); + + if (notAllClipped && (solid || t0 != saveT0 || t1 != saveT1)) + { + if (t1 > t0) + { + intrType = EIntersectionType::Segment; + quantity = 2; + Point0 = RayOrigin + t0 * RayDirection; + Point1 = RayOrigin + t1 * RayDirection; + } + else + { + intrType = EIntersectionType::Point; + quantity = 1; + Point0 = RayOrigin + t0 * RayDirection; + } + } + else + { + quantity = 0; + intrType = EIntersectionType::Empty; + } + + return intrType != EIntersectionType::Empty; + } + + + + + static bool Clip(RealType denom, RealType numer, RealType& t0, RealType& t1) + { + // Return value is 'true' if line segment intersects the current test + // plane. Otherwise 'false' is returned in which case the line segment + // is entirely clipped. + + if (denom > (RealType)0) + { + if (numer > denom*t1) + { + return false; + } + if (numer > denom*t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (RealType)0) + { + if (numer > denom*t0) + { + return false; + } + if (numer > denom*t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (RealType)0; + } + } + + +}; + +typedef TIntrRay3AxisAlignedBox3 FIntrRay3AxisAlignedBox3f; +typedef TIntrRay3AxisAlignedBox3 FIntrRay3AxisAlignedBox3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3Triangle3.h new file mode 100644 index 000000000000..1b2fdd695a60 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrRay3Triangle3.h @@ -0,0 +1,172 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic IntrRay3Triangle3 + +#pragma once + +#include "VectorTypes.h" +#include "RayTypes.h" +#include "TriangleTypes.h" +#include "VectorUtil.h" + +/** + * Compute intersection between 3D ray and 3D triangle + */ +template +class TIntrRay3Triangle3 +{ +public: + // Input + TRay3 Ray; + TTriangle3 Triangle; + + // Output + Real RayParameter; + FVector3d TriangleBaryCoords; + EIntersectionType IntersectionType; + + + TIntrRay3Triangle3(const TRay3& RayIn, const TTriangle3& TriangleIn) + { + Ray = RayIn; + Triangle = TriangleIn; + } + + + /** + * @return true if ray intersects triangle + */ + bool Test() + { + // Compute the offset origin, edges, and normal. + FVector3 diff = Ray.Origin - Triangle.V[0]; + FVector3 edge1 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge2 = Triangle.V[2] - Triangle.V[0]; + FVector3 normal = edge1.Cross(edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Ray.Direction.Dot(normal); + Real sign; + if (DdN > TMathUtil::ZeroTolerance) + { + sign = (Real)1; + } + else if (DdN < -TMathUtil::ZeroTolerance) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + IntersectionType = EIntersectionType::Empty; + return false; + } + + Real DdQxE2 = sign * Ray.Direction.Dot(diff.Cross(edge2)); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * Ray.Direction.Dot(edge1.Cross(diff)); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check if ray does. + Real QdN = -sign * diff.Dot(normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + IntersectionType = EIntersectionType::Point; + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + IntersectionType = EIntersectionType::Empty; + return false; + } + + + /** + * Find intersection point + * @return true if ray intersects triangle + */ + bool Find() + { + // Compute the offset origin, edges, and normal. + FVector3 diff = Ray.Origin - Triangle.V[0]; + FVector3 edge1 = Triangle.V[1] - Triangle.V[0]; + FVector3 edge2 = Triangle.V[2] - Triangle.V[0]; + FVector3 normal = edge1.Cross(edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Ray.Direction.Dot(normal); + Real sign; + if (DdN > TMathUtil::ZeroTolerance) + { + sign = (Real)1; + } + else if (DdN < -TMathUtil::ZeroTolerance) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + IntersectionType = EIntersectionType::Empty; + return false; + } + + Real DdQxE2 = sign * Ray.Direction.Dot(diff.Cross(edge2)); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * Ray.Direction.Dot(edge1.Cross(diff)); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check if ray does. + Real QdN = -sign * diff.Dot(normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + Real inv = ((Real)1) / DdN; + RayParameter = QdN * inv; + TriangleBaryCoords.Y = DdQxE2 * inv; + TriangleBaryCoords.Z = DdE1xQ * inv; + TriangleBaryCoords.X = (Real)1 - TriangleBaryCoords.Y - TriangleBaryCoords.Z; + IntersectionType = EIntersectionType::Point; + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + IntersectionType = EIntersectionType::Empty; + return false; + } + +}; + +typedef TIntrRay3Triangle3 FIntrRay3Triangle3f; +typedef TIntrRay3Triangle3 FIntrRay3Triangle3d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrSegment2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrSegment2Segment2.h new file mode 100644 index 000000000000..962e1e2c9674 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Intersection/IntrSegment2Segment2.h @@ -0,0 +1,246 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic IntrSegment2Segment2 + +#pragma once + +#include "SegmentTypes.h" +#include "Intersection/Intersector1.h" +#include "Intersection/IntersectionUtil.h" +#include "Intersection/IntrLine2Line2.h" +#include "MathUtil.h" +#include "VectorTypes.h" +#include "VectorUtil.h" // for EIntersectionType + +// ported from WildMagic5 +// +// +// RealType IntervalThreshold +// The intersection testing uses the center-extent form for line segments. +// If you start with endpoints (Vector2) and create Segment2 +// objects, the conversion to center-extent form can contain small +// numerical round-off errors. Testing for the intersection of two +// segments that share an endpoint might lead to a failure due to the +// round-off errors. To allow for this, you may specify a small positive +// threshold that slightly enlarges the intervals for the segments. The +// default value is zero. +// +// RealType DotThreshold +// The computation for determining whether the linear components are +// parallel might contain small floating-point round-off errors. The +// default threshold is TMathUtil::ZeroTolerance. If you set the value, +// pass in a nonnegative number. +// +// +// The intersection set: Let q = Quantity. The cases are +// +// q = 0: The segments do not intersect. Type is Empty +// +// q = 1: The segments intersect in a single point. Type is Point +// Intersection point is Point0. +// +// q = 2: The segments are collinear and intersect in a segment. +// Type is Segment. Points are Point0 and Point1 + +/** + * Calculate intersection between two 2D line segments + */ +template +class TIntrSegment2Segment2 +{ +protected: + // inputs + TSegment2 Segment1; + TSegment2 Segment2; + RealType IntervalThreshold = 0; + RealType DotThreshold = TMathUtil::ZeroTolerance; + +public: + // outputs + int Quantity = 0; + EIntersectionResult Result = EIntersectionResult::NotComputed; + EIntersectionType Type = EIntersectionType::Empty; + // these values are all on segment 1, unlike many other tests!! + FVector2 Point0; + FVector2 Point1; // only set if Quantity == 2, ie segment overlap + + RealType Parameter0; + RealType Parameter1; // only set if Quantity == 2, ie segment overlap + + + + TIntrSegment2Segment2(const TSegment2& Segment1In, const TSegment2& Segment2In) + : Segment1(Segment1In), Segment2(Segment2In) + { + } + + + const TSegment2& GetSegment1() const + { + return Segment1; + } + + void SetSegment1(const TSegment2& Value) + { + Segment1 = Value; + Result = EIntersectionResult::NotComputed; + } + + const TSegment2& GetSegment2() const + { + return Segment2; + } + + void SetSegment2(const TSegment2& Value) + { + Segment2 = Value; + Result = EIntersectionResult::NotComputed; + } + + RealType GetIntervalThreshold() const + { + return IntervalThreshold; + } + + void SetIntervalThreshold(RealType Value) + { + IntervalThreshold = FMath::Max(Value, (RealType)0); + Result = EIntersectionResult::NotComputed; + } + + RealType GetDotThreshold() const + { + return DotThreshold; + } + + void SetDotThreshold(RealType Value) + { + DotThreshold = FMath::Max(Value, (RealType)0); + Result = EIntersectionResult::NotComputed; + } + + bool IsSimpleIntersection() const + { + return Result == EIntersectionResult::Intersects && Type == EIntersectionType::Point; + } + + + TIntrSegment2Segment2& Compute() + { + Find(); + return *this; + } + + + bool Find() + { + if (Result != EIntersectionResult::NotComputed) + { + return (Result == EIntersectionResult::Intersects); + } + + // [RMS] if either segment direction is not a normalized vector, + // results are garbage, so fail query + if (Segment1.Direction.IsNormalized() == false || Segment2.Direction.IsNormalized() == false) + { + Type = EIntersectionType::Empty; + Result = EIntersectionResult::InvalidQuery; + return false; + } + + + FVector2 s = FVector2::Zero(); + Type = TIntrLine2Line2::Classify(Segment1.Center, Segment1.Direction, + Segment2.Center, Segment2.Direction, + DotThreshold, s); + + if (Type == EIntersectionType::Point) + { + // Test whether the line-line intersection is on the segments. + if (FMath::Abs(s[0]) <= Segment1.Extent + IntervalThreshold + && FMath::Abs(s[1]) <= Segment2.Extent + IntervalThreshold) + { + Quantity = 1; + Point0 = Segment1.Center + s[0] * Segment1.Direction; + Parameter0 = s[0]; + } + else + { + Quantity = 0; + Type = EIntersectionType::Empty; + } + } + else if (Type == EIntersectionType::Line) + { + // Compute the location of Segment1 endpoints relative to segment0. + FVector2 diff = Segment2.Center - Segment1.Center; + RealType t1 = Segment1.Direction.Dot(diff); + RealType tmin = t1 - Segment2.Extent; + RealType tmax = t1 + Segment2.Extent; + TIntersector1 calc(-Segment1.Extent, Segment1.Extent, tmin, tmax); + calc.Find(); + Quantity = calc.NumIntersections; + if (Quantity == 2) + { + Type = EIntersectionType::Segment; + Parameter0 = calc.GetIntersection(0); + Point0 = Segment1.Center + + Parameter0 * Segment1.Direction; + Parameter1 = calc.GetIntersection(1); + Point1 = Segment1.Center + + Parameter1 * Segment1.Direction; + } + else if (Quantity == 1) + { + Type = EIntersectionType::Point; + Parameter0 = calc.GetIntersection(0); + Point0 = Segment1.Center + + Parameter0 * Segment1.Direction; + } + else + { + Type = EIntersectionType::Empty; + } + } + else + { + Quantity = 0; + } + + Result = (Type != EIntersectionType::Empty) ? + EIntersectionResult::Intersects : EIntersectionResult::NoIntersection; + + // [RMS] for debugging... + //SanityCheck(); + + return (Result == EIntersectionResult::Intersects); + } + + +protected: + void SanityCheck() + { + if (Quantity == 0) + { + check(Type == EIntersectionType::Empty); + check(Result == EIntersectionResult::NoIntersection); + } + else if (Quantity == 1) + { + check(Type == EIntersectionType::Point); + check(Segment1.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + check(Segment2.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + } + else if (Quantity == 2) + { + check(Type == EIntersectionType::Segment); + check(Segment1.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + check(Segment1.DistanceSquared(Point1) < TMathUtil::ZeroTolerance); + check(Segment2.DistanceSquared(Point0) < TMathUtil::ZeroTolerance); + check(Segment2.DistanceSquared(Point1) < TMathUtil::ZeroTolerance); + } + } +}; + +typedef TIntrSegment2Segment2 FIntrSegment2Segment2d; +typedef TIntrSegment2Segment2 FIntrSegment2Segment2f; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/LineTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/LineTypes.h new file mode 100644 index 000000000000..dc16ab69ab61 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/LineTypes.h @@ -0,0 +1,215 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// line types ported from WildMagic and geometry3Sharp + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" + +/** + * TLine2 is a two-dimensional infinite line. + * The line is stored in (Center,Direction) form. + */ +template +struct TLine2 +{ + /** Origin / Center Point of Line */ + FVector2 Origin; + + /** Direction of Line, Normalized */ + FVector2 Direction; + + /** + * Construct default line along X axis + */ + TLine2() + { + Origin = FVector2::Zero(); + Direction = FVector2::UnitX(); + } + + /** + * Construct line with given Origin and Direction + */ + TLine2(const FVector2& OriginIn, const FVector2& DirectionIn) + : Origin(OriginIn), Direction(DirectionIn) + { + } + + + /** + * @return line between two points + */ + static TLine2 FromPoints(const FVector2& Point0, const FVector2& Point1) + { + return TLine2(Point0, (Point1 - Point0).Normalized()); + } + + + /** + * @return point on line at given line parameter value (distance along line from origin) + */ + inline FVector2 PointAt(T LineParameter) const + { + return Origin + LineParameter * Direction; + } + + + /** + * @return line parameter (ie distance from Origin) at nearest point on line to QueryPoint + */ + inline T Project(const FVector2& QueryPoint) const + { + return (QueryPoint - Origin).Dot(Direction); + } + + /** + * @return smallest squared distance from line to QueryPoint + */ + inline T DistanceSquared(const FVector2& QueryPoint) const + { + T ParameterT = (QueryPoint - Origin).Dot(Direction); + FVector2 proj = Origin + ParameterT * Direction; + return (proj - QueryPoint).SquaredLength(); + } + + /** + * @return nearest point on line to QueryPoint + */ + inline FVector2 NearestPoint(const FVector2& QueryPoint) const + { + T ParameterT = (QueryPoint - Origin).Dot(Direction); + return Origin + ParameterT * Direction; + } + + + /** + * @return +1 if QueryPoint is "right" of line, -1 if "left" or 0 if "on" line (up to given tolerance) + */ + inline int WhichSide(const FVector2& QueryPoint, T OnLineTolerance = 0) const + { + T x0 = QueryPoint.X - Origin.X; + T y0 = QueryPoint.Y - Origin.Y; + T x1 = Direction.X; + T y1 = Direction.Y; + T det = x0 * y1 - x1 * y0; + return (det > OnLineTolerance ? +1 : (det < -OnLineTolerance ? -1 : 0)); + } + + + /** + * Calculate intersection point between this line and another one + * @param OtherLine line to test against + * @param IntersectionPointOut intersection point is stored here, if found + * @param ParallelDotTolerance tolerance used to determine if lines are parallel (and hence cannot intersect) + * @return true if lines intersect and IntersectionPointOut was computed + */ + bool IntersectionPoint(const TLine2& OtherLine, FVector2& IntersectionPointOut, T ParallelDotTolerance = TMathUtil::ZeroTolerance) const + { + // see IntrTLine2TLine2 for more detailed explanation + FVector2 diff = OtherLine.Origin - Origin; + T D0DotPerpD1 = Direction.DotPerp(OtherLine.Direction); + if (TMathUtil::Abs(D0DotPerpD1) > ParallelDotTolerance) // TLines intersect in a single point. + { + T invD0DotPerpD1 = ((T)1) / D0DotPerpD1; + T diffDotPerpD1 = diff.DotPerp(OtherLine.Direction); + T s = diffDotPerpD1 * invD0DotPerpD1; + IntersectionPointOut = Origin + s * Direction; + return true; + } + // TLines are parallel. + return false; + } +}; + + +typedef TLine2 FLine2d; +typedef TLine2 FLine2f; + + + + + + +/** + * TLine3 is a three-dimensional infinite line. + * The line is stored in (Center,Direction) form. + */ +template +struct TLine3 +{ + /** Origin / Center Point of Line */ + FVector3 Origin; + + /** Direction of Line, Normalized */ + FVector3 Direction; + + /** + * Construct default line along X axis + */ + TLine3() + { + Origin = FVector3::Zero(); + Direction = FVector3::UnitX(); + } + + /** + * Construct line with given Origin and Direction + */ + TLine3(const FVector3& OriginIn, const FVector3& DirectionIn) + : Origin(OriginIn), Direction(DirectionIn) + { + } + + + /** + * @return line between two points + */ + static TLine3 FromPoints(const FVector3& Point0, const FVector3& Point1) + { + return TLine3(Point0, (Point1 - Point0).Normalized()); + } + + + /** + * @return point on line at given line parameter value (distance along line from origin) + */ + inline FVector3 PointAt(T LineParameter) const + { + return Origin + LineParameter * Direction; + } + + + /** + * @return line parameter (ie distance from Origin) at nearest point on line to QueryPoint + */ + inline T Project(const FVector3& QueryPoint) const + { + return (QueryPoint - Origin).Dot(Direction); + } + + /** + * @return smallest squared distance from line to QueryPoint + */ + inline T DistanceSquared(const FVector3& QueryPoint) const + { + T t = (QueryPoint - Origin).Dot(Direction); + FVector3 proj = Origin + t * Direction; + return (proj - QueryPoint).SquaredLength(); + } + + /** + * @return nearest point on line to QueryPoint + */ + inline FVector3 NearestPoint(const FVector3& QueryPoint) const + { + T ParameterT = (QueryPoint - Origin).Dot(Direction); + return Origin + ParameterT * Direction; + } + + +}; + +typedef TLine3 FLine3d; +typedef TLine3 FLine3f; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h new file mode 100644 index 000000000000..285ca4e196fd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MathUtil.h @@ -0,0 +1,179 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" // required for GEOMETRICOBJECTS_API macro +#include + +/** + * Math constants and utility functions, templated on float/double type + */ +template +class TMathUtil +{ +public: + /** Machine Epsilon - float approx 1e-7, double approx 2e-16 */ + GEOMETRICOBJECTS_API static const RealType Epsilon; + /** Zero tolerance for math operations (eg like parallel tests) - float 1e-6, double 1e-8 */ + GEOMETRICOBJECTS_API static const RealType ZeroTolerance; + + /** largest possible number for type */ + GEOMETRICOBJECTS_API static const RealType MaxReal; + + /** 3.14159... */ + GEOMETRICOBJECTS_API static const RealType Pi; + GEOMETRICOBJECTS_API static const RealType FourPi; + GEOMETRICOBJECTS_API static const RealType TwoPi; + GEOMETRICOBJECTS_API static const RealType HalfPi; + + /** 1.0 / Pi */ + GEOMETRICOBJECTS_API static const RealType InvPi; + /** 1.0 / (2*Pi) */ + GEOMETRICOBJECTS_API static const RealType InvTwoPi; + + /** pi / 180 */ + GEOMETRICOBJECTS_API static const RealType DegToRad; + /** 180 / pi */ + GEOMETRICOBJECTS_API static const RealType RadToDeg; + + //static const RealType LN_2; + //static const RealType LN_10; + //static const RealType INV_LN_2; + //static const RealType INV_LN_10; + + GEOMETRICOBJECTS_API static const RealType Sqrt2; + GEOMETRICOBJECTS_API static const RealType InvSqrt2; + GEOMETRICOBJECTS_API static const RealType Sqrt3; + GEOMETRICOBJECTS_API static const RealType InvSqrt3; + + static inline bool IsNaN(const RealType Value); + static inline bool IsFinite(const RealType Value); + static inline RealType Abs(const RealType Value); + static inline RealType Clamp(const RealType Value, const RealType ClampMin, const RealType ClampMax); + static inline RealType Sign(const RealType Value); + static inline RealType SignNonZero(const RealType Value); + static inline RealType Max(const RealType A, const RealType B); + static inline RealType Max3(const RealType A, const RealType B, const RealType C); + static inline RealType Min(const RealType A, const RealType B); + static inline RealType Min3(const RealType A, const RealType B, const RealType C); + static inline RealType Sqrt(const RealType Value); + static inline RealType Atan2(const RealType ValueY, const RealType ValueX); + static inline RealType Sin(const RealType Value); + static inline RealType Cos(const RealType Value); + + + + /** + * @return result of Atan2 shifted to [0,2pi] (normal ATan2 returns in range [-pi,pi]) + */ + static inline RealType Atan2Positive(const RealType Y, const RealType X); + +private: + TMathUtil() = delete; +}; +typedef TMathUtil FMathf; +typedef TMathUtil FMathd; + + +template +bool TMathUtil::IsNaN(const RealType Value) +{ + return std::isnan(Value); +} + + +template +bool TMathUtil::IsFinite(const RealType Value) +{ + return std::isfinite(Value); +} + + +template +RealType TMathUtil::Abs(const RealType Value) +{ + return (Value >= (RealType)0) ? Value : -Value; +} + + +template +RealType TMathUtil::Clamp(const RealType Value, const RealType ClampMin, const RealType ClampMax) +{ + return (Value < ClampMin) ? ClampMin : ((Value > ClampMax) ? ClampMax : Value); +} + + +template +RealType TMathUtil::Sign(const RealType Value) +{ + return (Value > (RealType)0) ? (RealType)1 : ((Value < (RealType)0) ? (RealType)-1 : (RealType)0); +} + +template +RealType TMathUtil::SignNonZero(const RealType Value) +{ + return (Value < (RealType)0) ? (RealType)-1 : (RealType)1; +} + +template +RealType TMathUtil::Max(const RealType A, const RealType B) +{ + return (A >= B) ? A : B; +} + +template +RealType TMathUtil::Max3(const RealType A, const RealType B, const RealType C) +{ + return Max(Max(A, B), C); +} + +template +RealType TMathUtil::Min(const RealType A, const RealType B) +{ + return (A <= B) ? A : B; +} + +template +RealType TMathUtil::Min3(const RealType A, const RealType B, const RealType C) +{ + return Min(Min(A, B), C); +} + +template +RealType TMathUtil::Sqrt(const RealType Value) +{ + return sqrt(Value); +} + +template +RealType TMathUtil::Atan2(const RealType ValueY, const RealType ValueX) +{ + return atan2(ValueY, ValueX); +} + +template +RealType TMathUtil::Sin(const RealType Value) +{ + return sin(Value); +} + +template +RealType TMathUtil::Cos(const RealType Value) +{ + return cos(Value); +} + + +template +RealType TMathUtil::Atan2Positive(const RealType Y, const RealType X) +{ + // @todo this is a float atan2 !! + RealType Theta = TMathUtil::Atan2(Y, X); + if (Theta < 0) + { + return ((RealType)2 * TMathUtil::Pi) + Theta; + } + return Theta; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MatrixTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MatrixTypes.h new file mode 100644 index 000000000000..278c9e42f606 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MatrixTypes.h @@ -0,0 +1,419 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "VectorUtil.h" + +template +struct TMatrix3 +{ + FVector3 Row0; + FVector3 Row1; + FVector3 Row2; + + TMatrix3() + { + } + + TMatrix3(RealType ConstantValue) + { + Row0 = FVector3(ConstantValue, ConstantValue, ConstantValue); + Row1 = Row0; + Row2 = Row0; + } + + TMatrix3(RealType Diag0, RealType Diag1, RealType Diag2) + { + Row0 = FVector3(Diag0, 0, 0); + Row1 = FVector3(0, Diag1, 0); + Row2 = FVector3(0, 0, Diag2); + } + + /** + * Construct outer-product of U*transpose(V) of U and V + * result is that Mij = u_i * v_j + */ + TMatrix3(const FVector3& U, const FVector3& V) + : Row0(U.X * V.X, U.X * V.Y, U.X * V.Z), + Row1(U.Y * V.X, U.Y * V.Y, U.Y * V.Z), + Row2(U.Z * V.X, U.Z * V.Y, U.Z * V.Z) + { + } + + TMatrix3(RealType M00, RealType M01, RealType M02, RealType M10, RealType M11, RealType M12, RealType M20, RealType M21, RealType M22) + : Row0(M00, M01, M02), + Row1(M10, M11, M12), + Row2(M20, M21, M22) + { + } + + TMatrix3(const FVector3& V1, const FVector3& V2, const FVector3& V3, bool bRows) + { + if (bRows) + { + Row0 = V1; + Row1 = V2; + Row2 = V3; + } + else + { + Row0 = FVector3(V1.X, V2.X, V3.X); + Row1 = FVector3(V1.Y, V2.Y, V3.Y); + Row2 = FVector3(V1.Z, V2.Z, V3.Z); + } + } + + static TMatrix3 Zero() + { + return TMatrix3(0); + } + static TMatrix3 Identity() + { + return TMatrix3(1, 1, 1); + } + + RealType operator()(int Row, int Col) const + { + check(Row >= 0 && Row < 3 && Col >= 0 && Col < 3); + if (Row == 0) + { + return Row0[Col]; + } + else if (Row == 1) + { + return Row1[Col]; + } + else + { + return Row2[Col]; + } + } + + TMatrix3 operator*(RealType Scale) const + { + return TMatrix3( + Row0.X * Scale, Row0.Y * Scale, Row0.Z * Scale, + Row1.X * Scale, Row1.Y * Scale, Row1.Z * Scale, + Row2.X * Scale, Row2.Y * Scale, Row2.Z * Scale); + } + + FVector3 operator*(const FVector3& V) const + { + return FVector3( + Row0.X * V.X + Row0.Y * V.Y + Row0.Z * V.Z, + Row1.X * V.X + Row1.Y * V.Y + Row1.Z * V.Z, + Row2.X * V.X + Row2.Y * V.Y + Row2.Z * V.Z); + } + + TMatrix3 operator*(const TMatrix3& Mat2) const + { + RealType M00 = Row0.X * Mat2.Row0.X + Row0.Y * Mat2.Row1.X + Row0.Z * Mat2.Row2.X; + RealType M01 = Row0.X * Mat2.Row0.Y + Row0.Y * Mat2.Row1.Y + Row0.Z * Mat2.Row2.Y; + RealType M02 = Row0.X * Mat2.Row0.Z + Row0.Y * Mat2.Row1.Z + Row0.Z * Mat2.Row2.Z; + + RealType M10 = Row1.X * Mat2.Row0.X + Row1.Y * Mat2.Row1.X + Row1.Z * Mat2.Row2.X; + RealType M11 = Row1.X * Mat2.Row0.Y + Row1.Y * Mat2.Row1.Y + Row1.Z * Mat2.Row2.Y; + RealType M12 = Row1.X * Mat2.Row0.Z + Row1.Y * Mat2.Row1.Z + Row1.Z * Mat2.Row2.Z; + + RealType M20 = Row2.X * Mat2.Row0.X + Row2.Y * Mat2.Row1.X + Row2.Z * Mat2.Row2.X; + RealType M21 = Row2.X * Mat2.Row0.Y + Row2.Y * Mat2.Row1.Y + Row2.Z * Mat2.Row2.Y; + RealType M22 = Row2.X * Mat2.Row0.Z + Row2.Y * Mat2.Row1.Z + Row2.Z * Mat2.Row2.Z; + + return TMatrix3(M00, M01, M02, M10, M11, M12, M20, M21, M22); + } + + TMatrix3 operator+(const TMatrix3& Mat2) + { + return TMatrix3(Row0 + Mat2.Row0, Row1 + Mat2.Row1, Row2 + Mat2.Row2, true); + } + + TMatrix3 operator-(const TMatrix3& Mat2) + { + return TMatrix3(Row0 - Mat2.Row0, Row1 - Mat2.Row1, Row2 - Mat2.Row2, true); + } + + inline TMatrix3& operator*=(const RealType& Scalar) + { + Row0 *= Scalar; + Row1 *= Scalar; + Row2 *= Scalar; + return *this; + } + + inline TMatrix3& operator+=(const TMatrix3& Mat2) + { + Row0 += Mat2.Row0; + Row1 += Mat2.Row1; + Row2 += Mat2.Row2; + return *this; + } + + RealType InnerProduct(const TMatrix3& Mat2) const + { + return Row0.Dot(Mat2.Row0) + Row1.Dot(Mat2.Row1) + Row2.Dot(Mat2.Row2); + } + + RealType Determinant() const + { + RealType a11 = Row0.X, a12 = Row0.Y, a13 = Row0.Z, a21 = Row1.X, a22 = Row1.Y, a23 = Row1.Z, a31 = Row2.X, a32 = Row2.Y, a33 = Row2.Z; + RealType i00 = a33 * a22 - a32 * a23; + RealType i01 = -(a33 * a12 - a32 * a13); + RealType i02 = a23 * a12 - a22 * a13; + return a11 * i00 + a21 * i01 + a31 * i02; + } + + TMatrix3 Inverse() const + { + RealType a11 = Row0.X, a12 = Row0.Y, a13 = Row0.Z, a21 = Row1.X, a22 = Row1.Y, a23 = Row1.Z, a31 = Row2.X, a32 = Row2.Y, a33 = Row2.Z; + RealType i00 = a33 * a22 - a32 * a23; + RealType i01 = -(a33 * a12 - a32 * a13); + RealType i02 = a23 * a12 - a22 * a13; + + RealType i10 = -(a33 * a21 - a31 * a23); + RealType i11 = a33 * a11 - a31 * a13; + RealType i12 = -(a23 * a11 - a21 * a13); + + RealType i20 = a32 * a21 - a31 * a22; + RealType i21 = -(a32 * a11 - a31 * a12); + RealType i22 = a22 * a11 - a21 * a12; + + RealType det = a11 * i00 + a21 * i01 + a31 * i02; + ensure(TMathUtil::Abs(det) >= TMathUtil::Epsilon); + det = 1.0 / det; + return TMatrix3(i00 * det, i01 * det, i02 * det, i10 * det, i11 * det, i12 * det, i20 * det, i21 * det, i22 * det); + } + + TMatrix3 Transpose() const + { + return TMatrix3( + Row0.X, Row1.X, Row2.X, + Row0.Y, Row1.Y, Row2.Y, + Row0.Z, Row1.Z, Row2.Z); + } + + bool EpsilonEqual(const TMatrix3& Mat2, RealType Epsilon) const + { + return VectorUtil::EpsilonEqual(Row0, Mat2.Row0, Epsilon) && + VectorUtil::EpsilonEqual(Row1, Mat2.Row1, Epsilon) && + VectorUtil::EpsilonEqual(Row2, Mat2.Row2, Epsilon); + } + + static TMatrix3 AxisAngleR(const FVector3& Axis, RealType AngleRad) + { + RealType cs = TMathUtil::Cos(AngleRad); + RealType sn = TMathUtil::Sin(AngleRad); + RealType oneMinusCos = 1.0 - cs; + RealType x2 = Axis[0] * Axis[0]; + RealType y2 = Axis[1] * Axis[1]; + RealType z2 = Axis[2] * Axis[2]; + RealType xym = Axis[0] * Axis[1] * oneMinusCos; + RealType xzm = Axis[0] * Axis[2] * oneMinusCos; + RealType yzm = Axis[1] * Axis[2] * oneMinusCos; + RealType xSin = Axis[0] * sn; + RealType ySin = Axis[1] * sn; + RealType zSin = Axis[2] * sn; + return TMatrix3( + x2 * oneMinusCos + cs, xym - zSin, xzm + ySin, + xym + zSin, y2 * oneMinusCos + cs, yzm - xSin, + xzm - ySin, yzm + xSin, z2 * oneMinusCos + cs); + } + + static TMatrix3 AxisAngleD(const FVector3& Axis, RealType AngleDeg) + { + return AxisAngleR(Axis, TMathUtil::DegreesToRadians(AngleDeg)); + } +}; + + +template +struct TMatrix2 +{ + FVector2 Row0; + FVector2 Row1; + + TMatrix2() + { + } + + TMatrix2(RealType ConstantValue) + { + Row0 = Row1 = FVector2(ConstantValue, ConstantValue); + } + + TMatrix2(RealType Diag0, RealType Diag1) + { + Row0 = FVector2(Diag0, 0); + Row1 = FVector2(0, Diag1); + } + + /** + * Construct outer-product of U*transpose(V) of U and V + * result is that Mij = u_i * v_j + */ + TMatrix2(const FVector2& U, const FVector2& V) + : Row0(U.X * V.X, U.X * V.Y), + Row1(U.Y * V.X, U.Y * V.Y) + { + } + + TMatrix2(RealType M00, RealType M01, RealType M10, RealType M11) + : Row0(M00, M01), + Row1(M10, M11) + { + } + + TMatrix2(const FVector2& V1, const FVector2& V2, bool bRows) + { + if (bRows) + { + Row0 = V1; + Row1 = V2; + } + else + { + Row0 = FVector2(V1.X, V2.X); + Row1 = FVector2(V1.Y, V2.Y); + } + } + + static TMatrix2 Zero() + { + return TMatrix2(0); + } + static TMatrix2 Identity() + { + return TMatrix2(1, 1); + } + + RealType operator()(int Row, int Col) const + { + check(Row >= 0 && Row < 2 && Col >= 0 && Col < 2); + if (Row == 0) + { + return Row0[Col]; + } + else + { + return Row1[Col]; + } + } + + TMatrix2 operator*(RealType Scale) const + { + return TMatrix2( + Row0.X * Scale, Row0.Y * Scale, + Row1.X * Scale, Row1.Y * Scale); + } + + FVector2 operator*(const FVector2& V) const + { + return FVector2( + Row0.X * V.X + Row0.Y * V.Y, + Row1.X * V.X + Row1.Y * V.Y); + } + + TMatrix2 operator*(const TMatrix2& Mat2) const + { + RealType M00 = Row0.X * Mat2.Row0.X + Row0.Y * Mat2.Row1.X; + RealType M01 = Row0.X * Mat2.Row0.Y + Row0.Y * Mat2.Row1.Y; + + RealType M10 = Row1.X * Mat2.Row0.X + Row1.Y * Mat2.Row1.X; + RealType M11 = Row1.X * Mat2.Row0.Y + Row1.Y * Mat2.Row1.Y; + + return TMatrix2(M00, M01, M10, M11); + } + + TMatrix2 operator+(const TMatrix2& Mat2) + { + return TMatrix2(Row0 + Mat2.Row0, Row1 + Mat2.Row1, true); + } + + TMatrix2 operator-(const TMatrix2& Mat2) + { + return TMatrix2(Row0 - Mat2.Row0, Row1 - Mat2.Row1, true); + } + + inline TMatrix2& operator*=(const RealType& Scalar) + { + Row0 *= Scalar; + Row1 *= Scalar; + return *this; + } + + inline TMatrix2& operator+=(const TMatrix2& Mat2) + { + Row0 += Mat2.Row0; + Row1 += Mat2.Row1; + return *this; + } + + RealType InnerProduct(const TMatrix2& Mat2) const + { + return Row0.Dot(Mat2.Row0) + Row1.Dot(Mat2.Row1); + } + + RealType Determinant() const + { + return Row0.X * Row1.Y - Row0.Y * Row1.X; + } + + TMatrix2 Inverse() const + { + RealType Det = Determinant(); + ensure(TMathUtil::Abs(Det) >= TMathUtil::Epsilon); + RealType DetInv = 1.0 / Det; + return TMatrix2(Row1.Y * DetInv, -Row0.Y * DetInv, -Row1.X * DetInv, Row0.X * DetInv); + } + + TMatrix2 Transpose() const + { + return TMatrix2( + Row0.X, Row1.X, + Row0.Y, Row1.Y); + } + + bool EpsilonEqual(const TMatrix2& Mat2, RealType Epsilon) const + { + return VectorUtil::EpsilonEqual(Row0, Mat2.Row0, Epsilon) && + VectorUtil::EpsilonEqual(Row1, Mat2.Row1, Epsilon); + } + + static TMatrix2 RotationRad(RealType AngleRad) + { + RealType cs = TMathUtil::Cos(AngleRad); + RealType sn = TMathUtil::Sin(AngleRad); + return TMatrix2(cs, -sn, sn, cs); + } + + /** + * Assumes we have a rotation matrix (uniform scale ok) + */ + RealType GetAngleRad() + { + return TMathUtil::Atan2(Row1.X, Row0.X); + } + +}; + +template +inline TMatrix3 operator*(RealType Scale, const TMatrix3& Mat) +{ + return TMatrix3( + Mat.Row0.X * Scale, Mat.Row0.Y * Scale, Mat.Row0.Z * Scale, + Mat.Row1.X * Scale, Mat.Row1.Y * Scale, Mat.Row1.Z * Scale, + Mat.Row2.X * Scale, Mat.Row2.Y * Scale, Mat.Row2.Z * Scale); +} + +template +inline TMatrix2 operator*(RealType Scale, const TMatrix2& Mat) +{ + return TMatrix2( + Mat.Row0.X * Scale, Mat.Row0.Y * Scale, + Mat.Row1.X * Scale, Mat.Row1.Y * Scale); +} + +typedef TMatrix3 FMatrix3f; +typedef TMatrix3 FMatrix3d; +typedef TMatrix2 FMatrix2f; +typedef TMatrix2 FMatrix2d; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshAdapter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshAdapter.h new file mode 100644 index 000000000000..ce97e98cf025 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshAdapter.h @@ -0,0 +1,48 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "TriangleTypes.h" +#include "VectorTypes.h" + +#include "Math/IntVector.h" +#include "Math/Vector.h" + +template +struct TTriangleMeshAdapter +{ + TFunction IsTriangle; + TFunction IsVertex; + TFunction MaxTriangleID; + TFunction MaxVertexID; + TFunction TriangleCount; + TFunction VertexCount; + TFunction GetShapeTimestamp; + TFunction GetTriangle; + TFunction(int32)> GetVertex; + + inline void GetTriVertices(int TID, FVector3& V0, FVector3& V1, FVector3& V2) const + { + FIndex3i TriIndices = GetTriangle(TID); + V0 = GetVertex(TriIndices.A); + V1 = GetVertex(TriIndices.B); + V2 = GetVertex(TriIndices.C); + } +}; + +typedef TTriangleMeshAdapter TTriangleMeshAdapterd; +typedef TTriangleMeshAdapter TTriangleMeshAdapterf; + +TTriangleMeshAdapterd GetArrayMesh(TArray& Vertices, TArray& Triangles) +{ + return { + [&](int) { return true; }, + [&](int) { return true; }, + [&]() { return Triangles.Num(); }, + [&]() { return Vertices.Num(); }, + [&]() { return Triangles.Num(); }, + [&]() { return Vertices.Num(); }, + [&]() { return 0; }, + [&](int Idx) { return FIndex3i(Triangles[Idx]); }, + [&](int Idx) { return FVector3d(Vertices[Idx]); }}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshQueries.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshQueries.h new file mode 100644 index 000000000000..4bfc5a842709 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/MeshQueries.h @@ -0,0 +1,154 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Distance/DistPoint3Triangle3.h" +#include "Intersection/IntrRay3Triangle3.h" +#include "BoxTypes.h" +#include "IndexTypes.h" + +template +class TMeshQueries +{ +public: + TMeshQueries() = delete; + + /** + * construct a DistPoint3Triangle3 object for a Mesh triangle + */ + static FDistPoint3Triangle3d TriangleDistance(const TriangleMeshType& Mesh, int TriIdx, FVector3d Point) + { + check(Mesh.IsTriangle(TriIdx)); + FTriangle3d tri; + Mesh.GetTriVertices(TriIdx, tri.V[0], tri.V[1], tri.V[2]); + FDistPoint3Triangle3d q(Point, tri); + q.GetSquared(); + return q; + } + + /** + * convenience function to construct a IntrRay3Triangle3 object for a Mesh triangle + */ + static FIntrRay3Triangle3d TriangleIntersection(const TriangleMeshType& Mesh, int TriIdx, const FRay3d& Ray) + { + check(Mesh.IsTriangle(TriIdx)); + FTriangle3d tri; + Mesh.GetTriVertices(TriIdx, tri.V[0], tri.V[1], tri.V[2]); + FIntrRay3Triangle3d q(Ray, tri); + q.Find(); + return q; + } + + /** + * Compute triangle centroid + * @param Mesh Mesh with triangle + * @param TriIdx Index of triangle + * @return Computed centroid + */ + static FVector3d GetTriCentroid(const TriangleMeshType& Mesh, int TriIdx) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + return Triangle.Centroid(); + } + + /** + * Compute the normal, area, and centroid of a triangle all together + * @param Mesh Mesh w/ triangle + * @param TriIdx Index of triangle + * @param Normal Computed normal (returned by reference) + * @param Area Computed area (returned by reference) + * @param Centroid Computed centroid (returned by reference) + */ + static void GetTriNormalAreaCentroid(const TriangleMeshType& Mesh, int TriIdx, FVector3d& Normal, double& Area, FVector3d& Centroid) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + Centroid = Triangle.Centroid(); + Normal = VectorUtil::FastNormalArea(Triangle.V[0], Triangle.V[1], Triangle.V[2], Area); + } + + static FAxisAlignedBox3d GetTriBounds(const TriangleMeshType& Mesh, int TID) + { + FIndex3i TriInds = Mesh.GetTriangle(TID); + FVector3d MinV, MaxV, V = Mesh.GetVertex(TriInds.A); + MinV = MaxV = V; + for (int i = 1; i < 3; ++i) + { + V = Mesh.GetVertex(TriInds[i]); + if (V.X < MinV.X) MinV.X = V.X; + else if (V.X > MaxV.X) MaxV.X = V.X; + if (V.Y < MinV.Y) MinV.Y = V.Y; + else if (V.Y > MaxV.Y) MaxV.Y = V.Y; + if (V.Z < MinV.Z) MinV.Z = V.Z; + else if (V.Z > MaxV.Z) MaxV.Z = V.Z; + } + return FAxisAlignedBox3d(MinV, MaxV); + } + + // brute force search for nearest triangle to Point + static int FindNearestTriangle_LinearSearch(const TriangleMeshType& Mesh, const FVector3d& P) + { + int tNearest = IndexConstants::InvalidID; + double fNearestSqr = TNumericLimits::Max(); + for (int TriIdx : Mesh.TriangleIndicesItr()) + { + double distSqr = TriDistanceSqr(Mesh, TriIdx, P); + if (distSqr < fNearestSqr) + { + fNearestSqr = distSqr; + tNearest = TriIdx; + } + } + return tNearest; + } + + /** + * Compute distance from Point to triangle in Mesh, with minimal extra objects/etc + */ + static double TriDistanceSqr(const TriangleMeshType& Mesh, int TriIdx, const FVector3d& Point) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + FDistPoint3Triangle3d Distance(Point, Triangle); + return Distance.GetSquared(); + } + + // brute force search for nearest triangle intersection + static int FindHitTriangle_LinearSearch(const TriangleMeshType& Mesh, const FRay3d& Ray) + { + int tNearestID = IndexConstants::InvalidID; + double fNearestT = TNumericLimits::Max(); + FTriangle3d Triangle; + + for (int TriIdx : Mesh.TriangleIndicesItr()) + { + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + FIntrRay3Triangle3d Query(Ray, Triangle); + if (Query.Find()) + { + if (Query.RayParameter < fNearestT) + { + fNearestT = Query.RayParameter; + tNearestID = TriIdx; + } + } + } + + return tNearestID; + } + + /** + * convenience function to construct a IntrRay3Triangle3 object for a Mesh triangle + */ + static FIntrRay3Triangle3d RayTriangleIntersection(const TriangleMeshType& Mesh, int TriIdx, const FRay3d& Ray) + { + FTriangle3d Triangle; + Mesh.GetTriVertices(TriIdx, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + + FIntrRay3Triangle3d Query(Ray, Triangle); + Query.Find(); + return Query; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/OrientedBoxTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/OrientedBoxTypes.h new file mode 100644 index 000000000000..064257d4ea56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/OrientedBoxTypes.h @@ -0,0 +1,222 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// ported from geometry3Sharp Box3 + +#pragma once + +#include "VectorTypes.h" +#include "BoxTypes.h" +#include "FrameTypes.h" + +/** + * TOrientedBox3 is a non-axis-aligned 3D box defined by a 3D frame and extents along the axes of that frame + * The frame is at the center of the box. + */ +template +struct TOrientedBox3 +{ + // available for porting: ContainPoint, MergeBoxes() + + + /** 3D position (center) and orientation (axes) of the box */ + TFrame3 Frame; + /** Half-dimensions of box measured along the three axes */ + FVector3 Extents; + + TOrientedBox3() : Extents(1,1,1) {} + + /** + * Create axis-aligned box with given Origin and Extents + */ + TOrientedBox3(const FVector3& Origin, const FVector3 & ExtentsIn) + : Frame(Origin), Extents(ExtentsIn) + { + } + + /** + * Create oriented box with given Frame and Extents + */ + TOrientedBox3(const TFrame3& FrameIn, const FVector3 & ExtentsIn) + : Frame(FrameIn), Extents(ExtentsIn) + { + } + + /** + * Create oriented box from axis-aligned box + */ + TOrientedBox3(const TAxisAlignedBox3& AxisBox) + : Frame(AxisBox.Center()), Extents((RealType)0.5 * AxisBox.Diagonal()) + { + } + + + /** @return box with unit dimensions centered at origin */ + static TOrientedBox3 UnitZeroCentered() { return TOrientedBox3(FVector3::Zero(), (RealType)0.5*FVector3::One()); } + + /** @return box with unit dimensions where minimum corner is at origin */ + static TOrientedBox3 UnitPositive() { return TOrientedBox3((RealType)0.5*FVector3::One(), (RealType)0.5*FVector3::One()); } + + + /** @return center of the box */ + FVector3 Center() const { return Frame.Origin; } + + /** @return X axis of the box */ + FVector3 AxisX() const { return Frame.X(); } + + /** @return Y axis of the box */ + FVector3 AxisY() const { return Frame.Y(); } + + /** @return Z axis of the box */ + FVector3 AxisZ() const { return Frame.Z(); } + + /** @return an axis of the box */ + FVector3 GetAxis(int AxisIndex) const { return Frame.GetAxis(AxisIndex); } + + /** @return maximum extent of box */ + inline RealType MaxExtent() const + { + return Extents.MaxAbs(); + } + + /** @return minimum extent of box */ + inline RealType MinExtent() const + { + return Extents.MinAbs(); + } + + /** @return vector from minimum-corner to maximum-corner of box */ + inline FVector3 Diagonal() const + { + return Frame.PointAt(Extents.X, Extents.Y, Extents.Z) - Frame.PointAt(-Extents.X, -Extents.Y, -Extents.Z); + } + + /** @return volume of box */ + inline RealType Volume() const + { + return (RealType)8 * (Extents.X) * (Extents.Y) * (Extents.Z); + } + + /** @return true if box contains point */ + inline bool Contains(const FVector3& Point) + { + FVector3 InFramePoint = Frame.ToFramePoint(Point); + return (TMathUtil::Abs(InFramePoint.X) <= Extents.X) && + (TMathUtil::Abs(InFramePoint.Y) <= Extents.Y) && + (TMathUtil::Abs(InFramePoint.Z) <= Extents.Z); + } + + + // corners [ (-x,-y), (x,-y), (x,y), (-x,y) ], -z, then +z + // + // 7---6 +z or 3---2 -z + // |\ |\ |\ |\ + // 4-\-5 \ 0-\-1 \ + // \ 3---2 \ 7---6 + // \| | \| | + // 0---1 -z 4---5 +z + // + // @todo does this ordering make sense for UE? we are in LHS instead of RHS here + // if this is modified, likely need to update IndexUtil::BoxFaces and BoxFaceNormals + + /** + * @param Index corner index in range 0-7 + * @return Corner point on the box identified by the given index. See diagram in OrientedBoxTypes.h for index/corner mapping. + */ + FVector3 GetCorner(int Index) const + { + check(Index >= 0 && Index <= 7); + RealType dx = (((Index & 1) != 0) ^ ((Index & 2) != 0)) ? (Extents.X) : (-Extents.X); + RealType dy = ((Index / 2) % 2 == 0) ? (-Extents.Y) : (Extents.Y); + RealType dz = (Index < 4) ? (-Extents.Z) : (Extents.Z); + return Frame.PointAt(dx, dy, dz); + } + + + + + + /** + * Find squared distance to box. + * @param Point input point + * @return squared distance from point to box, or 0 if point is inside box + */ + RealType DistanceSquared(FVector3 Point) + { + // Ported from WildMagic5 Wm5DistPoint3Box3.cpp + + // Work in the box's coordinate system. + Point -= Frame.Origin; + + // Compute squared distance and closest point on box. + RealType sqrDistance = 0; + RealType delta; + FVector3 closest(0, 0, 0); + for (int i = 0; i < 3; ++i) + { + closest[i] = Point.Dot(GetAxis(i)); + if (closest[i] < -Extents[i]) + { + delta = closest[i] + Extents[i]; + sqrDistance += delta * delta; + closest[i] = -Extents[i]; + } + else if (closest[i] > Extents[i]) + { + delta = closest[i] - Extents[i]; + sqrDistance += delta * delta; + closest[i] = Extents[i]; + } + } + + return sqrDistance; + } + + + + + /** + * Find closest point on box + * @param Point input point + * @return closest point on box. Input point is returned if it is inside box. + */ + FVector3 ClosestPoint(FVector3 Point) + { + // Work in the box's coordinate system. + Point -= Frame.Origin; + + // Compute squared distance and closest point on box. + RealType sqrDistance = 0; + RealType delta; + FVector3 closest; + FVector3 Axes[3]; + for (int i = 0; i < 3; ++i) + { + Axes[i] = GetAxis(i); + closest[i] = Point.Dot(Axes[i]); + RealType extent = Extents[i]; + if (closest[i] < -extent) + { + delta = closest[i] + extent; + sqrDistance += delta * delta; + closest[i] = -extent; + } + else if (closest[i] > extent) + { + delta = closest[i] - extent; + sqrDistance += delta * delta; + closest[i] = extent; + } + } + + return Frame.Origin + closest.X*Axes[0] + closest.Y*Axes[1] + closest.Z*Axes[2]; + } + + + +}; + + + + +typedef TOrientedBox3 FOrientedBox3f; +typedef TOrientedBox3 FOrientedBox3d; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/PointSetAdapter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/PointSetAdapter.h new file mode 100644 index 000000000000..b34bb44856e3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/PointSetAdapter.h @@ -0,0 +1,33 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" + +/** + * TPointSetAdapter provides a very generic interface to an indexable list of points. + * The list may be sparse, ie some indices may be invalid. + */ +template +struct TPointSetAdapter +{ + /** Maximum point index */ + TFunction MaxPointID; + /** Number of points. If PointCount == MaxPointID, then there are no gaps in the index list */ + TFunction PointCount; + /** Returns true if this index valid */ + TFunction IsPoint; + /** Get point at this index */ + TFunction(int32)> GetPoint; + /** Returns a timestamp. If point set changes, timestamp should also change. */ + TFunction Timestamp; + + /** Returns true if this point set has per-point normals */ + TFunction HasNormals; + /** Get the normal at a point index */ + TFunction GetPointNormal; +}; + +typedef TPointSetAdapter FPointSetAdapterd; +typedef TPointSetAdapter FPointSetAdapterf; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h new file mode 100644 index 000000000000..cb63c6fecdff --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polygon2.h @@ -0,0 +1,1056 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// port of geometry3Sharp Polygon + +#pragma once + +#include "Templates/UnrealTemplate.h" +#include "Math/UnrealMath.h" +#include "VectorTypes.h" +#include "BoxTypes.h" +#include "SegmentTypes.h" +#include "LineTypes.h" +#include "MathUtil.h" +#include "Intersection/IntrSegment2Segment2.h" +#include "Util/DynamicVector.h" + +/** + * TPolygon2 is a 2D polygon represented as a list of Vertices. + * + * @todo move operators + */ +template +class TPolygon2 +{ +protected: + /** The list of vertices/corners of the polygon */ + TArray> Vertices; + + /** A counter that is incremented every time the polygon vertices are modified */ + int Timestamp; + +public: + + TPolygon2() : Timestamp(0) + { + } + + /** + * Construct polygon that is a copy of another polygon + */ + TPolygon2(const TPolygon2& Copy) : Vertices(Copy.Vertices), Timestamp(Copy.Timestamp) + { + } + + /** + * Construct polygon with given list of vertices + */ + TPolygon2(const TArray>& VertexList) : Vertices(VertexList), Timestamp(0) + { + } + + /** @return the Timestamp for the polygon, which is updated every time the polygon is modified */ + int GetTimestamp() const + { + return Timestamp; + } + + /** + * Get the vertex at a given index + */ + const FVector2& operator[](int Index) const + { + return Vertices[Index]; + } + + /** + * Get the vertex at a given index + * @warning changing the vertex via this operator does not update Timestamp! + */ + FVector2& operator[](int Index) + { + return Vertices[Index]; + } + + + /** + * @return first vertex of Polygon + */ + const FVector2& Start() const + { + return Vertices[0]; + } + + /** + * @return list of Vertices of Polygon + */ + const TArray>& GetVertices() const + { + return Vertices; + } + + /** + * @return number of Vertices in Polygon + */ + int VertexCount() const + { + return Vertices.Num(); + } + + /** + * Add a vertex to the Polygon + */ + void AppendVertex(const FVector2& Position) + { + Vertices.Add(Position); + Timestamp++; + } + + /** + * Add a list of Vertices to the Polygon + */ + void AppendVertices(const TArray>& NewVertices) + { + Vertices.Append(NewVertices); + Timestamp++; + } + + /** + * Set vertex at given index to a new Position + */ + void Set(int VertexIndex, const FVector2& Position) + { + Vertices[VertexIndex] = Position; + Timestamp++; + } + + /** + * Remove a vertex of the Polygon (existing Vertices are shifted) + */ + void RemoveVertex(int VertexIndex) + { + Vertices.RemoveAt(VertexIndex); + Timestamp++; + } + + /** + * Replace the list of Vertices with a new list + */ + void SetVertices(const TArray>& NewVertices) + { + Vertices = NewVertices; + Timestamp++; + } + + + /** + * Reverse the order of the Vertices in the Polygon (ie switch between Clockwise and CounterClockwise) + */ + void Reverse() + { + int32 j = Vertices.Num()-1; + for (int32 VertexIndex = 0; VertexIndex < j; VertexIndex++, j--) + { + Swap(Vertices[VertexIndex], Vertices[j]); + } + Timestamp++; + } + + + /** + * Get the tangent vector at a vertex of the polygon, which is the normalized + * vector from the previous vertex to the next vertex + */ + FVector2 GetTangent(int VertexIndex) const + { + FVector2 next = Vertices[(VertexIndex + 1) % Vertices.Num()]; + FVector2 prev = Vertices[VertexIndex == 0 ? Vertices.Num() - 1 : VertexIndex - 1]; + return (next - prev).Normalized(); + } + + + /** + * Get the normal vector at a vertex of the polygon, which is perpendicular to GetTangent() + * Points "inward" for a Clockwise Polygon, and outward for CounterClockwise + */ + FVector2 GetNormal(int VertexIndex) const + { + return GetTangent(VertexIndex).Perp(); + } + + + /** + * Construct a normal at a vertex of the Polygon by averaging the adjacent face normals. + * This vector is independent of the lengths of the adjacent segments. + * Points "inward" for a Clockwise Polygon, and outward for CounterClockwise + */ + FVector2 GetNormal_FaceAvg(int VertexIndex) const + { + FVector2 next = Vertices[(VertexIndex + 1) % Vertices.Num()]; + FVector2 prev = Vertices[VertexIndex == 0 ? Vertices.Num() - 1 : VertexIndex - 1]; + next -= Vertices[VertexIndex]; next.Normalize(); + prev -= Vertices[VertexIndex]; prev.Normalize(); + + FVector2 n = (next.Perp() - prev.Perp()); + T len = n.Normalize(); + if (len == 0) + { + return (next + prev).Normalized(); // this gives right direction for degenerate angle + } + else + { + return n; + } + } + + + /** + * @return the bounding box of the Polygon Vertices + */ + TAxisAlignedBox2 Bounds() const + { + TAxisAlignedBox2 box = TAxisAlignedBox2::Empty(); + box.Contain(Vertices); + return box; + } + + + /** + * SegmentIterator is used to iterate over the TSegment2 segments of the polygon + */ + class SegmentIterator + { + public: + inline bool operator!() + { + return i < polygon->VertexCount(); + } + inline TSegment2 operator*() const + { + check(polygon != nullptr && i < polygon->VertexCount()); + return TSegment2(polygon->Vertices[i], polygon->Vertices[i+1 % polygon->VertexCount()]); + } + //inline TSegment2 & operator*(); + inline SegmentIterator & operator++() // prefix + { + i++; + return *this; + } + inline SegmentIterator operator++(int) // postfix + { + SegmentIterator copy(*this); + i++; + return copy; + } + inline bool operator==(const SegmentIterator & i2) { return i2.polygon == polygon && i2.i == i; } + inline bool operator!=(const SegmentIterator & i2) { return i2.polygon != polygon || i2.i != i; } + protected: + const TPolygon2 * polygon; + int i; + inline SegmentIterator(const TPolygon2 * p, int iCur) : polygon(p), i(iCur) {} + friend class TPolygon2; + }; + friend class SegmentIterator; + + SegmentIterator SegmentItr() const + { + return SegmentIterator(this, 0); + } + + /** + * Wrapper around SegmentIterator that has begin() and end() suitable for range-based for loop + */ + class SegmentEnumerable + { + public: + const TPolygon2* polygon; + SegmentEnumerable() : polygon(nullptr) {} + SegmentEnumerable(const TPolygon2 * p) : polygon(p) {} + SegmentIterator begin() { return polygon->SegmentItr(); } + SegmentIterator end() { return SegmentIterator(polygon, polygon->VertexCount()); } + }; + + /** + * @return an object that can be used in a range-based for loop to iterate over the Segments of the Polygon + */ + SegmentEnumerable Segments() const + { + return SegmentEnumerable(this); + } + + + /** + * @return true if the Polygon Vertices have Clockwise winding order / orientation (signed area is negative) + */ + bool IsClockwise() const + { + return SignedArea() < 0; + } + + /** + * @return the signed area of the Polygon + */ + T SignedArea() const + { + T fArea = 0; + int N = Vertices.Num(); + if (N == 0) + { + return 0; + } + for (int i = 0; i < N; ++i) + { + const FVector2& v1 = Vertices[i]; + const FVector2& v2 = Vertices[(i + 1) % N]; + fArea += v1.X * v2.Y - v1.Y * v2.X; + } + return fArea * 0.5; + } + + /** + * @return the unsigned area of the Polygon + */ + T Area() const + { + return TMathUtil::Abs(SignedArea()); + } + + /** + * @return the total perimeter length of the Polygon + */ + T Perimeter() const + { + T fPerim = 0; + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + fPerim += Vertices[i].Distance(Vertices[(i + 1) % N]); + } + return fPerim; + } + + + /** + * Get the previous and next vertex positions for a given vertex of the Polygon + */ + void NeighbourPoints(int iVertex, FVector2 &PrevNbrOut, FVector2 &NextNbrOut) const + { + int N = Vertices.Num(); + PrevNbrOut = Vertices[(iVertex == 0) ? N - 1 : iVertex - 1]; + NextNbrOut = Vertices[(iVertex + 1) % N]; + } + + + /** + * Get the vectors from a given vertex to the previous and next Vertices, optionally normalized + */ + void NeighbourVectors(int iVertex, FVector2 &ToPrevOut, FVector2 &ToNextOut, bool bNormalize = false) const + { + int N = Vertices.Num(); + ToPrevOut = Vertices[(iVertex == 0) ? N - 1 : iVertex - 1] - Vertices[iVertex]; + ToNextOut = Vertices[(iVertex + 1) % N] - Vertices[iVertex]; + if (bNormalize) + { + ToPrevOut.Normalize(); + ToNextOut.Normalize(); + } + } + + + /** + * @return the opening angle in degrees at a vertex of the Polygon + */ + T OpeningAngleDeg(int iVertex) const + { + FVector2 e0, e1; + NeighbourVectors(iVertex, e0, e1, true); + return e0.AngleD(e1); + } + + + /** + * @return analytic winding integral for this Polygon at an arbitrary point + */ + T WindingIntegral(const FVector2& QueryPoint) const + { + T sum = 0; + int N = Vertices.Num(); + FVector2 a = Vertices[0] - QueryPoint, b = FVector2::Zero(); + for (int i = 0; i < N; ++i) + { + b = Vertices[(i + 1) % N] - QueryPoint; + sum += TMathUtil::Atan2(a.X * b.Y - a.Y * b.X, a.X * b.X + a.Y * b.Y); + a = b; + } + return sum / FMathd::TwoPi; + } + + + /** + * @return true if the given query point is inside the Polygon, based on the winding integral + */ + bool Contains(const FVector2& QueryPoint) const + { + int nWindingNumber = 0; + + int N = Vertices.Num(); + FVector2 a = Vertices[0], b = FVector2::Zero(); + for (int i = 0; i < N; ++i) + { + b = Vertices[(i + 1) % N]; + + if (a.Y <= QueryPoint.Y) // y <= P.Y (below) + { + if (b.Y > QueryPoint.Y) // an upward crossing + { + if (FVector2::Orient(a, b, QueryPoint) > 0) // P left of edge + ++nWindingNumber; // have a valid up intersect + } + } + else // y > P.Y (above) + { + if (b.Y <= QueryPoint.Y) // a downward crossing + { + if (FVector2::Orient(a, b, QueryPoint) < 0) // P right of edge + { + --nWindingNumber; // have a valid down intersect + } + } + } + a = b; + } + return nWindingNumber != 0; + } + + + /** + * @return true if the Polygon fully contains the OtherPolygon + */ + bool Contains(const TPolygon2& OtherPoly) const + { + // @todo fast bbox check? + + int N = OtherPoly.VertexCount(); + for (int i = 0; i < N; ++i) + { + if (Contains(OtherPoly[i]) == false) + { + return false; + } + } + + if (Intersects(OtherPoly)) + { + return false; + } + + return true; + } + + + /** + * @return true if the Segment is fully contained inside the Polygon + */ + bool Contains(const TSegment2& Segment) const + { + // [TODO] Add bbox check + if (Contains(Segment.StartPoint()) == false || Contains(Segment.EndPoint()) == false) + { + return false; + } + + for (TSegment2 seg : Segments()) + { + if (seg.Intersects(Segment)) + { + return false; + } + } + return true; + } + + + /** + * @return true if at least one edge of the OtherPolygon intersects the Polygon + */ + bool Intersects(const TPolygon2& OtherPoly) const + { + if (!Bounds().Intersects(OtherPoly.Bounds())) + { + return false; + } + + for (TSegment2 seg : Segments()) + { + for (TSegment2 oseg : OtherPoly.Segments()) + { + if (seg.Intersects(oseg)) + { + return true; + } + } + } + return false; + } + + + /** + * @return true if the Segment intersects an edge of the Polygon + */ + bool Intersects(const TSegment2& Segment) const + { + // [TODO] Add bbox check + if (Contains(Segment.StartPoint()) == true || Contains(Segment.EndPoint()) == true) + { + return true; + } + + // [TODO] Add bbox check + for (TSegment2 seg : Segments()) + { + if (seg.Intersects(Segment)) + { + return true; + } + } + return false; + } + + + /** + * Find all the points where an edge of the Polygon intersects an edge of the OtherPolygon + * @param OtherPoly polygon to test against + * @param OutArray intersection points are stored here + * @return true if any intersections were found + */ + bool FindIntersections(const TPolygon2& OtherPoly, TArray>& OutArray) const + { + if (!Bounds().Intersects(OtherPoly.Bounds())) + { + return false; + } + + bool bFoundIntersections = false; + for (TSegment2 seg : Segments()) + { + for (TSegment2 oseg : OtherPoly.Segments()) + { + // this computes test twice for intersections, but seg.intersects doesn't + // create any new objects so it should be much faster for majority of segments (should profile!) + if (seg.Intersects(oseg)) + { + //@todo can we replace with something like seg.intersects? + TIntrSegment2Segment2 intr(seg, oseg); + if (intr.Find()) + { + bFoundIntersections = true; + OutArray.Add(intr.Point0); + if (intr.Quantity == 2) + { + OutArray.Add(intr.Point1); + } + } + } + } + } + + return bFoundIntersections; + } + + + /** + * @return edge of the polygon starting at vertex SegmentIndex + */ + TSegment2 Segment(int SegmentIndex) const + { + return TSegment2(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]); + } + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [-Extent,Extent] along segment + * @return point on the segment at the given parameter value + */ + FVector2 GetSegmentPoint(int SegmentIndex, T SegmentParam) const + { + TSegment2 seg(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]); + return seg.PointAt(SegmentParam); + } + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [0,1] along segment + * @return point on the segment at the given parameter value + */ + FVector2 GetSegmentPointUnitParam(int SegmentIndex, T SegmentParam) const + { + TSegment2 seg(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]); + return seg.PointBetween(SegmentParam); + } + + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [0,1] along segment + * @return interpolated normal to the segment at the given parameter value + */ + FVector2 GetNormal(int iSeg, T SegmentParam) const + { + TSegment2 seg(Vertices[iSeg], Vertices[(iSeg + 1) % Vertices.Num()]); + T t = ((SegmentParam / seg.Extent) + 1.0) / 2.0; + + FVector2 n0 = GetNormal(iSeg); + FVector2 n1 = GetNormal((iSeg + 1) % Vertices.Num()); + return ((T(1) - t) * n0 + t * n1).Normalized(); + } + + + + /** + * Calculate the squared distance from a point to the polygon + * @param QueryPoint the query point + * @param NearestSegIndexOut The index of the nearest segment + * @param NearestSegParamOut the parameter value of the nearest point on the segment + * @return squared distance to the polygon + */ + T DistanceSquared(const FVector2& QueryPoint, int& NearestSegIndexOut, T& NearestSegParamOut) const + { + NearestSegIndexOut = -1; + NearestSegParamOut = TNumericLimits::Max(); + T dist = TNumericLimits::Max(); + int N = Vertices.Num(); + for (int vi = 0; vi < N; ++vi) + { + // @todo can't we just use segment function here now? + TSegment2 seg = TSegment2(Vertices[vi], Vertices[(vi + 1) % N]); + T t = (QueryPoint - seg.Center).Dot(seg.Direction); + T d = TNumericLimits::Max(); + if (t >= seg.Extent) + { + d = seg.EndPoint().DistanceSquared(QueryPoint); + } + else if (t <= -seg.Extent) + { + d = seg.StartPoint().DistanceSquared(QueryPoint); + } + else + { + d = (seg.PointAt(t) - QueryPoint).SquaredLength(); + } + if (d < dist) + { + dist = d; + NearestSegIndexOut = vi; + NearestSegParamOut = TMathUtil::Clamp(t, -seg.Extent, seg.Extent); + } + } + return dist; + } + + + /** + * Calculate the squared distance from a point to the polygon + * @param QueryPoint the query point + * @return squared distance to the polygon + */ + T DistanceSquared(const FVector2& QueryPoint) const + { + int seg; T segt; + return DistanceSquared(QueryPoint, seg, segt); + } + + + /** + * @return average edge length of all the edges of the Polygon + */ + T AverageEdgeLength() const + { + T avg = 0; int N = Vertices.Num(); + for (int i = 1; i < N; ++i) { + avg += Vertices[i].Distance(Vertices[i - 1]); + } + avg += Vertices[N - 1].Distance(Vertices[0]); + return avg / N; + } + + + /** + * Translate the polygon + * @returns the Polygon, so that you can chain calls like Translate().Scale() + */ + TPolygon2& Translate(const FVector2& Translate) + { + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + Vertices[i] += Translate; + } + Timestamp++; + return *this; + } + + /** + * Scale the Polygon relative to a given point + * @returns the Polygon, so that you can chain calls like Translate().Scale() + */ + TPolygon2& Scale(const FVector2& Scale, const FVector2& Origin) + { + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + Vertices[i] = Scale * (Vertices[i] - Origin) + Origin; + } + Timestamp++; + return *this; + } + + + /** + * Apply an arbitrary transformation to the Polygon + * @returns the Polygon, so that you can chain calls like Translate().Scale() + */ + TPolygon2& Transform(const TFunction (const FVector2&)>& TransformFunc) + { + int N = Vertices.Num(); + for (int i = 0; i < N; ++i) + { + Vertices[i] = TransformFunc(Vertices[i]); + } + Timestamp++; + return *this; + } + + + + + /** + * Offset each point by the given Distance along vertex "normal" direction + * @param OffsetDistance the distance to offset + * @param bUseFaceAvg if true, we offset by the average-face normal instead of the perpendicular-tangent normal + */ + void VtxNormalOffset(T OffsetDistance, bool bUseFaceAvg = false) + { + TArray> NewVertices; + NewVertices.SetNumUninitialized(Vertices.Num()); + if (bUseFaceAvg) + { + for (int k = 0; k < Vertices.Num(); ++k) + { + NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal_FaceAvg(k); + } + } + else + { + for (int k = 0; k < Vertices.Num(); ++k) + { + NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal(k); + } + } + for (int k = 0; k < Vertices.Num(); ++k) + { + Vertices[k] = NewVertices[k]; + } + + Timestamp++; + } + + + /** + * Offset polygon by fixed distance, by offsetting and intersecting edges. + * CounterClockWise Polygon offsets "outwards", ClockWise "inwards". + */ + void PolyOffset(T OffsetDistance) + { + // [TODO] possibly can do with half as many normalizes if we do w/ sequential edges, + // rather than centering on each v? + TArray> NewVertices; + NewVertices.SetNumUninitialized(Vertices.Num()); + for (int k = 0; k < Vertices.Num(); ++k) + { + FVector2 v = Vertices[k]; + FVector2 next = Vertices[(k + 1) % Vertices.Num()]; + FVector2 prev = Vertices[k == 0 ? Vertices.Num() - 1 : k - 1]; + FVector2 dn = (next - v).Normalized(); + FVector2 dp = (prev - v).Normalized(); + TLine2 ln(v + OffsetDistance * dn.Perp(), dn); + TLine2 lp(v - OffsetDistance * dp.Perp(), dp); + + bool bIntersectsAtPoint = ln.IntersectionPoint(lp, NewVertices[k]); + if (!bIntersectsAtPoint) // lines were parallel + { + NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal_FaceAvg(k); + } + } + for (int k = 0; k < Vertices.Num(); ++k) + { + Vertices[k] = NewVertices[k]; + } + + Timestamp++; + } + + +private: + // Polygon simplification + // code adapted from: http://softsurfer.com/Archive/algorithm_0205/algorithm_0205.htm + // simplifyDP(): + // This is the Douglas-Peucker recursive simplification routine + // It just marks Vertices that are part of the simplified polyline + // for approximating the polyline subchain v[j] to v[k]. + // Input: tol = approximation tolerance + // v[] = polyline array of vertex points + // j,k = indices for the subchain v[j] to v[k] + // Output: mk[] = array of markers matching vertex array v[] + static void SimplifyDouglasPeucker(T Tolerance, const TArray>& Vertices, int j, int k, TArray& Marked) + { + Marked.SetNum(Vertices.Num()); + if (k <= j + 1) // there is nothing to simplify + return; + + // check for adequate approximation by segment S from v[j] to v[k] + int maxi = j; // index of vertex farthest from S + T maxd2 = 0; // distance squared of farthest vertex + T tol2 = Tolerance * Tolerance; // tolerance squared + TSegment2 S = TSegment2(Vertices[j], Vertices[k]); // segment from v[j] to v[k] + + // test each vertex v[i] for max distance from S + // Note: this works in any dimension (2D, 3D, ...) + for (int i = j + 1; i < k; i++) + { + T dv2 = S.DistanceSquared(Vertices[i]); + if (dv2 <= maxd2) + continue; + // v[i] is a max vertex + maxi = i; + maxd2 = dv2; + } + if (maxd2 > tol2) // error is worse than the tolerance + { + // split the polyline at the farthest vertex from S + Marked[maxi] = true; // mark v[maxi] for the simplified polyline + // recursively simplify the two subpolylines at v[maxi] + SimplifyDouglasPeucker(Tolerance, Vertices, j, maxi, Marked); // polyline v[j] to v[maxi] + SimplifyDouglasPeucker(Tolerance, Vertices, maxi, k, Marked); // polyline v[maxi] to v[k] + } + // else the approximation is OK, so ignore intermediate Vertices + return; + } + + +public: + + /** + * Simplify the Polygon to reduce the vertex count + * @param ClusterTolerance Vertices closer than this distance will be merged into a single vertex + * @param LineDeviationTolerance Vertices are allowed to deviate this much from the input polygon lines + */ + void Simplify(T ClusterTolerance = 0.0001, T LineDeviationTolerance = 0.01) + { + int n = Vertices.Num(); + if (n < 3) + { + return; + } + + int i, k, pv; // misc counters + TArray> NewVertices; + NewVertices.SetNumUninitialized(n + 1); // vertex buffer + TArray Marked; + Marked.SetNumUninitialized(n + 1); + for (i = 0; i < n + 1; ++i) // marker buffer + { + Marked[i] = false; + } + + // STAGE 1. Vertex Reduction within tolerance of prior vertex cluster + T clusterTol2 = ClusterTolerance * ClusterTolerance; + NewVertices[0] = Vertices[0]; // start at the beginning + for (i = 1, k = 1, pv = 0; i < n; i++) + { + if (Vertices[i].DistanceSquared(Vertices[pv]) < clusterTol2) + { + continue; + } + NewVertices[k++] = Vertices[i]; + pv = i; + } + bool skip_dp = false; + if (k == 1) + { + NewVertices[k++] = Vertices[1]; + NewVertices[k++] = Vertices[2]; + skip_dp = true; + } + else if (k == 2) + { + NewVertices[k++] = Vertices[0]; + skip_dp = true; + } + + // push on start vertex again, because simplifyDP is for polylines, not polygons + NewVertices[k++] = Vertices[0]; + + // STAGE 2. Douglas-Peucker polyline simplification + int nv = 0; + if (skip_dp == false && LineDeviationTolerance > 0) + { + Marked[0] = Marked[k - 1] = true; // mark the first and last Vertices + SimplifyDouglasPeucker(LineDeviationTolerance, NewVertices, 0, k - 1, Marked); + for (i = 0; i < k - 1; ++i) + { + if (Marked[i]) + { + nv++; + } + } + } + else + { + for (i = 0; i < k; ++i) + { + Marked[i] = true; + } + nv = k - 1; + } + + // polygon requires at least 3 Vertices + if (nv == 2) + { + for (i = 1; i < k - 1; ++i) + { + if (Marked[1] == false) + { + Marked[1] = true; + } + else if (Marked[k - 2] == false) + { + Marked[k - 2] = true; + } + } + nv++; + } + else if (nv == 1) + { + Marked[1] = true; + Marked[2] = true; + nv += 2; + } + + // copy marked Vertices back to this polygon + Vertices.Reset(); + for (i = 0; i < k - 1; ++i) // last vtx is copy of first, and definitely marked + { + if (Marked[i]) + { + Vertices.Add(NewVertices[i]); + } + } + + Timestamp++; + return; + } + + + + /** + * Chamfer each vertex corner of the Polygon + * @param ChamferDist offset distance from corner that we cut at + */ + void Chamfer(T ChamferDist, T MinConvexAngleDeg = 30, T MinConcaveAngleDeg = 30) + { + check(IsClockwise()); + + TArray> OldV = Vertices; + int N = OldV.Num(); + Vertices.Reset(); + + int iCur = 0; + do { + FVector2 center = OldV[iCur]; + + int iPrev = (iCur == 0) ? N - 1 : iCur - 1; + FVector2 prev = OldV[iPrev]; + int iNext = (iCur + 1) % N; + FVector2 next = OldV[iNext]; + + FVector2 cp = prev - center; + T cpdist = cp.Normalize(); + FVector2 cn = next - center; + T cndist = cn.Normalize(); + + // if degenerate, skip this vert + if (cpdist < TMathUtil::ZeroTolerance || cndist < TMathUtil::ZeroTolerance) + { + iCur = iNext; + continue; + } + + T angle = cp.AngleD(cn); + // TODO document what this means sign-wise + // TODO re-test post Unreal port that this DotPerp is doing the right thing + T sign = cn.DotPerp(cp); + bool bConcave = (sign > 0); + + T thresh = (bConcave) ? MinConcaveAngleDeg : MinConvexAngleDeg; + + // ok not too sharp + if (angle > thresh) + { + Vertices.Add(center); + iCur = iNext; + continue; + } + + + T prev_cut_dist = TMathUtil::Min(ChamferDist, cpdist*0.5); + FVector2 prev_cut = center + cp * prev_cut_dist; + T next_cut_dist = TMathUtil::Min(ChamferDist, cndist * 0.5); + FVector2 next_cut = center + cn * next_cut_dist; + + Vertices.Add(prev_cut); + Vertices.Add(next_cut); + iCur = iNext; + } while (iCur != 0); + + Timestamp++; + } + + + + + /** + * Construct a four-vertex rectangle Polygon + */ + static TPolygon2 MakeRectangle(const FVector2& Center, T Width, T Height) + { + TPolygon2 Rectangle; + Rectangle.Vertices.SetNumUninitialized(4); + Rectangle.Set(0, FVector2(Center.X - Width / 2, Center.Y - Height / 2)); + Rectangle.Set(1, FVector2(Center.X + Width / 2, Center.Y - Height / 2)); + Rectangle.Set(2, FVector2(Center.X + Width / 2, Center.Y + Height / 2)); + Rectangle.Set(3, FVector2(Center.X - Width / 2, Center.Y + Height / 2)); + return Rectangle; + } + + + /** + * Construct a circular Polygon + */ + static TPolygon2 MakeCircle(T Radius, int Steps, T AngleShiftRadians = 0) + { + TPolygon2 Circle; + Circle.Vertices.SetNumUninitialized(Steps); + + for (int i = 0; i < Steps; ++i) + { + T t = (T)i / (T)Steps; + T a = TMathUtil::TwoPi * t + AngleShiftRadians; + Circle.Set(i, FVector2(Radius * TMathUtil::Cos(a), Radius * TMathUtil::Sin(a))); + } + + return Circle; + } +}; + +typedef TPolygon2 FPolygon2d; +typedef TPolygon2 FPolygon2f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polyline3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polyline3.h new file mode 100644 index 000000000000..2da246fad5f6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Polyline3.h @@ -0,0 +1,443 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "SegmentTypes.h" +#include "LineTypes.h" +#include "RayTypes.h" +#include "BoxTypes.h" + +/** + * TPolyline3 represents a 3D polyline stored as a list of Vertices. + * + * @todo move operators + */ +template +class TPolyline3 +{ +protected: + /** The list of vertices of the polyline */ + TArray> Vertices; + + /** A counter that is incremented every time the polyline vertices are modified */ + int Timestamp; + +public: + + + TPolyline3() : Timestamp(0) + { + } + + /** + * Construct polyline that is a copy of another polyline + */ + TPolyline3(const TPolyline3& Copy) : Vertices(Copy.Vertices), Timestamp(Copy.Timestamp) + { + } + + /** + * Construct polyline with given list of vertices + */ + TPolyline3(const TArray>& VertexList) : Vertices(VertexList), Timestamp(0) + { + } + + /** @return the Timestamp for the polyline, which is updated every time the polyline is modified */ + int GetTimestamp() const + { + return Timestamp; + } + + /** Explicitly increment the Timestamp */ + void IncrementTimestamp() + { + Timestamp++; + } + + /** + * Get the vertex at a given index + */ + const FVector3& operator[](int Index) const + { + return Vertices[Index]; + } + + /** + * Get the vertex at a given index + * @warning changing the vertex via this operator does not update Timestamp! + */ + FVector3& operator[](int Index) + { + return Vertices[Index]; + } + + + /** + * @return first vertex of polyline + */ + const FVector3& Start() const + { + return Vertices[0]; + } + + /** + * @return last vertex of polyline + */ + const FVector3& End() const + { + return Vertices[Vertices.Num()-1]; + } + + + /** + * @return list of Vertices of polyline + */ + const TArray>& GetVertices() const + { + return Vertices; + } + + /** + * @return number of Vertices in polyline + */ + int VertexCount() const + { + return Vertices.Num(); + } + + /** + * @return number of segments in polyline + */ + int SegmentCount() const + { + return Vertices.Num()-1; + } + + + /** Discard all vertices of polyline */ + void Clear() + { + Vertices.Reset(); + Timestamp++; + } + + /** + * Add a vertex to the polyline + */ + void AppendVertex(const FVector3& Position) + { + Vertices.Add(Position); + Timestamp++; + } + + /** + * Add a list of Vertices to the polyline + */ + void AppendVertices(const TArray>& NewVertices) + { + Vertices.Append(NewVertices); + Timestamp++; + } + + /** + * Set vertex at given index to a new Position + */ + void Set(int VertexIndex, const FVector3& Position) + { + Vertices[VertexIndex] = Position; + Timestamp++; + } + + /** + * Remove a vertex of the polyline (existing Vertices are shifted) + */ + void RemoveVertex(int VertexIndex) + { + Vertices.RemoveAt(VertexIndex); + Timestamp++; + } + + /** + * Replace the list of Vertices with a new list + */ + void SetVertices(const TArray>& NewVertices) + { + int NumVerts = NewVertices.Num(); + Vertices.SetNum(NumVerts, false); + for (int k = 0; k < NumVerts; ++k) + { + Vertices[k] = NewVertices[k]; + } + Timestamp++; + } + + + /** + * Reverse the order of the Vertices in the polyline (ie switch between Clockwise and CounterClockwise) + */ + void Reverse() + { + int32 j = Vertices.Num() - 1; + for (int32 VertexIndex = 0; VertexIndex < j; VertexIndex++, j--) + { + Swap(Vertices[VertexIndex], Vertices[j]); + } + Timestamp++; + } + + + /** + * Get the tangent vector at a vertex of the polyline, which is the normalized + * vector from the previous vertex to the next vertex + */ + FVector3 GetTangent(int VertexIndex) const + { + if (VertexIndex == 0) + { + return (Vertices[1] - Vertices[0]).Normalized(); + } + int NumVerts = Vertices.Num(); + if (VertexIndex == NumVerts - 1) + { + return (Vertices[NumVerts-1] - Vertices[NumVerts-2]).Normalized(); + } + return (Vertices[VertexIndex+1] - Vertices[VertexIndex-1]).Normalized(); + } + + + + /** + * @return edge of the poyline starting at vertex SegmentIndex + */ + TSegment3 GetSegment(int SegmentIndex) const + { + return TSegment3(Vertices[SegmentIndex], Vertices[SegmentIndex+1]); + } + + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [-Extent,Extent] along segment + * @return point on the segment at the given parameter value + */ + FVector3 GetSegmentPoint(int SegmentIndex, T SegmentParam) const + { + TSegment3 seg(Vertices[SegmentIndex], Vertices[SegmentIndex + 1]); + return seg.PointAt(SegmentParam); + } + + + /** + * @param SegmentIndex index of first vertex of the edge + * @param SegmentParam parameter in range [0,1] along segment + * @return point on the segment at the given parameter value + */ + FVector3 GetSegmentPointUnitParam(int SegmentIndex, T SegmentParam) const + { + TSegment3 seg(Vertices[SegmentIndex], Vertices[SegmentIndex + 1]); + return seg.PointBetween(SegmentParam); + } + + + + /** + * @return the bounding box of the polyline Vertices + */ + TAxisAlignedBox3 GetBounds() const + { + TAxisAlignedBox3 box = TAxisAlignedBox3::Empty(); + int NumVertices = Vertices.Num(); + for (int k = 0; k < NumVertices; ++k) + { + box.Contain(Vertices[k]); + } + return box; + } + + + + /** + * @return the total perimeter length of the Polygon + */ + T Length() const + { + T length = 0; + int N = SegmentCount(); + for (int i = 0; i < N; ++i) + { + length += Vertices[i].Distance(Vertices[i+1]); + } + return length; + } + + + + /** + * SegmentIterator is used to iterate over the TSegment3 segments of the polyline + */ + class SegmentIterator + { + public: + inline bool operator!() + { + return i < Polyline->SegmentCount(); + } + inline TSegment3 operator*() const + { + check(Polyline != nullptr && i < Polyline->SegmentCount()); + return TSegment3(Polyline->Vertices[i], Polyline->Vertices[i+1]); + } + inline SegmentIterator & operator++() // prefix + { + i++; + return *this; + } + inline SegmentIterator operator++(int) // postfix + { + SegmentIterator copy(*this); + i++; + return copy; + } + inline bool operator==(const SegmentIterator & i3) { return i3.Polyline == Polyline && i3.i == i; } + inline bool operator!=(const SegmentIterator & i3) { return i3.Polyline != Polyline || i3.i != i; } + protected: + const TPolyline3 * Polyline; + int i; + inline SegmentIterator(const TPolyline3 * p, int iCur) : Polyline(p), i(iCur) {} + friend class TPolyline3; + }; + friend class SegmentIterator; + + SegmentIterator SegmentItr() const + { + return SegmentIterator(this, 0); + } + + /** + * Wrapper around SegmentIterator that has begin() and end() suitable for range-based for loop + */ + class SegmentEnumerable + { + public: + const TPolyline3* Polyline; + SegmentEnumerable() : Polyline(nullptr) {} + SegmentEnumerable(const TPolyline3 * p) : Polyline(p) {} + SegmentIterator begin() { return Polyline->SegmentItr(); } + SegmentIterator end() { return SegmentIterator(Polyline, Polyline->SegmentCount()); } + }; + + /** + * @return an object that can be used in a range-based for loop to iterate over the Segments of the Polyline + */ + SegmentEnumerable Segments() const + { + return SegmentEnumerable(this); + } + + + + + + + /** + * Calculate the squared distance from a point to the polyline + * @param QueryPoint the query point + * @param NearestSegIndexOut The index of the nearest segment + * @param NearestSegParamOut the parameter value of the nearest point on the segment + * @return squared distance to the polyline + */ + T DistanceSquared(const FVector3& QueryPoint, int& NearestSegIndexOut, T& NearestSegParamOut) const + { + NearestSegIndexOut = -1; + NearestSegParamOut = TNumericLimits::Max(); + T dist = TNumericLimits::Max(); + int N = SegmentCount(); + for (int vi = 0; vi < N; ++vi) + { + // @todo can't we just use segment function here now? + TSegment3 seg = TSegment3(Vertices[vi], Vertices[vi+1]); + T t = (QueryPoint - seg.Center).Dot(seg.Direction); + T d = TNumericLimits::Max(); + if (t >= seg.Extent) + { + d = seg.EndPoint().DistanceSquared(QueryPoint); + } + else if (t <= -seg.Extent) + { + d = seg.StartPoint().DistanceSquared(QueryPoint); + } + else + { + d = (seg.PointAt(t) - QueryPoint).SquaredLength(); + } + if (d < dist) + { + dist = d; + NearestSegIndexOut = vi; + NearestSegParamOut = TMathUtil::Clamp(t, -seg.Extent, seg.Extent); + } + } + return dist; + } + + + + /** + * Calculate the squared distance from a point to the polyline + * @param QueryPoint the query point + * @return squared distance to the polyline + */ + T DistanceSquared(const FVector3& QueryPoint) const + { + int seg; T segt; + return DistanceSquared(QueryPoint, seg, segt); + } + + + + + /** + * @return average edge length of all the edges of the Polygon + */ + T AverageEdgeLength() const + { + T avg = 0; int N = Vertices.Num(); + for (int i = 1; i < N; ++i) { + avg += Vertices[i].Distance(Vertices[i - 1]); + } + return avg / (T)(N-1); + } + + + + /** + * Produce a new polyline that is smoother than this one + */ + void SmoothSubdivide(TPolyline3& NewPolyline) const + { + const T Alpha = (T)1 / (T)3; + const T OneMinusAlpha = (T)2 / (T)3; + + int N = Vertices.Num(); + NewPolyline.Vertices.SetNum(2*(N-1)); + NewPolyline.Vertices[0] = Vertices[0]; + N = N - 1; + int k = 1; + for (int i = 1; i < N; ++i) + { + const FVector3& Prev = Vertices[i-1]; + const FVector3& Cur = Vertices[i]; + const FVector3& Next = Vertices[i+1]; + NewPolyline.Vertices[k++] = Alpha * Prev + OneMinusAlpha * Cur; + NewPolyline.Vertices[k++] = OneMinusAlpha * Cur + Alpha * Next; + } + NewPolyline.Vertices[k] = Vertices[N]; + } + + +}; + +typedef TPolyline3 FPolyline3d; +typedef TPolyline3 FPolyline3f; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/QuadricError.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/QuadricError.h new file mode 100644 index 000000000000..a6f52ff7b99e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/QuadricError.h @@ -0,0 +1,690 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" +#include "MatrixTypes.h" + + +/** + * QuadricError represents a quadratic function that evaluates distance to plane. + * Stores minimal 10-coefficient form, following http://mgarland.org/files/papers/qtheory.pdf + * (symmetric matrix A, vector b, constant c) + */ +template +struct TQuadricError +{ + RealType Axx, Axy, Axz, Ayy, Ayz, Azz; + RealType bx, by, bz; //d = -normal.dot(c); b = d*normal + RealType c; // d*d + + inline static TQuadricError Zero() { return TQuadricError(); }; + + TQuadricError() + { + Axx = Axy = Axz = Ayy = Ayz = Azz = bx = by = bz = c = 0; + } + + /** + * Construct TQuadricError a plane with the given normal and a point on plane + */ + TQuadricError(const FVector3& Normal, const FVector3& Point) + { + Axx = Normal.X * Normal.X; + Axy = Normal.X * Normal.Y; + Axz = Normal.X * Normal.Z; + Ayy = Normal.Y * Normal.Y; + Ayz = Normal.Y * Normal.Z; + Azz = Normal.Z * Normal.Z; + bx = by = bz = c = 0; + FVector3 v = MultiplyA(Point); + bx = -v.X; by = -v.Y; bz = -v.Z; // b = -Normal.Dot(Point) Normal + c = Point.Dot(v); // note this is the same as Normal.Dot(Point) ^2 + } + + /** + * Construct TQuadricError that is the sum of two other TQuadricErrors + */ + TQuadricError(const TQuadricError& a, const TQuadricError& b) + { + Axx = a.Axx + b.Axx; + Axy = a.Axy + b.Axy; + Axz = a.Axz + b.Axz; + Ayy = a.Ayy + b.Ayy; + Ayz = a.Ayz + b.Ayz; + Azz = a.Azz + b.Azz; + bx = a.bx + b.bx; + by = a.by + b.by; + bz = a.bz + b.bz; + c = a.c + b.c; + } + + + /** + * Add scalar multiple of a TQuadricError to this TQuadricError + */ + void Add(RealType w, const TQuadricError& b) + { + Axx += w * b.Axx; + Axy += w * b.Axy; + Axz += w * b.Axz; + Ayy += w * b.Ayy; + Ayz += w * b.Ayz; + Azz += w * b.Azz; + bx += w * b.bx; + by += w * b.by; + bz += w * b.bz; + c += w * b.c; + } + + void Add(const TQuadricError& b) + { + Axx += b.Axx; + Axy += b.Axy; + Axz += b.Axz; + Ayy += b.Ayy; + Ayz += b.Ayz; + Azz += b.Azz; + bx += b.bx; + by += b.by; + bz += b.bz; + c += b.c; + } + + TQuadricError& operator=(const TQuadricError& b) + { + Axx = b.Axx; + Axy = b.Axy; + Axz = b.Axz; + Ayy = b.Ayy; + Ayz = b.Ayz; + Azz = b.Azz; + bx = b.bx; + by = b.by; + bz = b.bz; + c = b.c; + + return *this; + } + + /** + * Evaluates p*A*p + 2*dot(p,b) + c + * @return + */ + RealType Evaluate(const FVector3& pt) const + { + RealType x = Axx * pt.X + Axy * pt.Y + Axz * pt.Z; + RealType y = Axy * pt.X + Ayy * pt.Y + Ayz * pt.Z; + RealType z = Axz * pt.X + Ayz * pt.Y + Azz * pt.Z; + return (pt.X * x + pt.Y * y + pt.Z * z) + + 2.0 * (pt.X * bx + pt.Y * by + pt.Z * bz) + c; + } + + /** + * + */ + FVector3 MultiplyA(const FVector3& pt) const + { + RealType x = Axx * pt.X + Axy * pt.Y + Axz * pt.Z; + RealType y = Axy * pt.X + Ayy * pt.Y + Ayz * pt.Z; + RealType z = Axz * pt.X + Ayz * pt.Y + Azz * pt.Z; + return FVector3(x, y, z); + } + + + bool SolveAxEqualsb(FVector3& OutResult, const RealType bvecx, const RealType bvecy, const RealType bvecz, const RealType minThresh = 1000.0*TMathUtil::Epsilon) const + { + RealType a11 = Azz * Ayy - Ayz * Ayz; + RealType a12 = Axz * Ayz - Azz * Axy; + RealType a13 = Axy * Ayz - Axz * Ayy; + RealType a22 = Azz * Axx - Axz * Axz; + RealType a23 = Axy * Axz - Axx * Ayz; + RealType a33 = Axx * Ayy - Axy * Axy; + RealType det = (Axx * a11) + (Axy * a12) + (Axz * a13); + + // [RMS] not sure what we should be using for this threshold...have seen + // det less than 10^-9 on "normal" meshes. + if (FMath::Abs(det) > minThresh) + { + det = 1.0 / det; + a11 *= det; a12 *= det; a13 *= det; + a22 *= det; a23 *= det; a33 *= det; + RealType x = a11 * bvecx + a12 * bvecy + a13 * bvecz; + RealType y = a12 * bvecx + a22 * bvecy + a23 * bvecz; + RealType z = a13 * bvecx + a23 * bvecy + a33 * bvecz; + OutResult = FVector3(x, y, z); + return true; + } + else + { + return false; + } + } + + static bool InvertSymmetricMatrix(const RealType SM[6], RealType InvSM[6], RealType minThresh = 1000.0*TMathUtil::Epsilon) + { + bool Result = false; + // Axx = SM[0]; Axy = SM[1]; Axz = SM[2] + // Ayy = SM[3]; Ayz = SM[4] + // Azz = SM[5] + + InvSM[0] = SM[5] * SM[3] - SM[4] * SM[4]; //s11 + InvSM[1] = SM[2] * SM[4] - SM[5] * SM[1]; //s12 + InvSM[2] = SM[1] * SM[4] - SM[2] * SM[3]; //s13 + InvSM[3] = SM[5] * SM[0] - SM[2] * SM[2]; //s22 + InvSM[4] = SM[1] * SM[2] - SM[0] * SM[4]; //s23 + InvSM[5] = SM[0] * SM[3] - SM[1] * SM[1]; //s33 + RealType Det = (SM[0] * InvSM[0]) + (SM[1] * InvSM[1]) + (SM[2] * InvSM[2]); + if (FMath::Abs(Det) > minThresh) + { + RealType InvDet = RealType(1) / Det; + InvSM[0] *= InvDet; InvSM[1] *= InvDet; InvSM[2] *= InvDet; + InvSM[3] *= InvDet; InvSM[4] *= InvDet; + InvSM[5] *= InvDet; + Result = true; + } + return Result; + } + + static FVector3 MultiplySymmetricMatrix(const RealType SM[6], const RealType vec[3]) + { + + RealType a = SM[0] * vec[0] + SM[1] * vec[1] + SM[2] * vec[2]; + RealType b = SM[1] * vec[0] + SM[3] * vec[1] + SM[4] * vec[2]; + RealType c = SM[2] * vec[0] + SM[4] * vec[1] + SM[5] * vec[2]; + + return FVector3(a, b, c); + } + static FVector3 MultiplySymmetricMatrix(const RealType SM[6], const FVector3& vec) + { + RealType vectmp[3] = { vec.X, vec.Y, vec.Z }; + return MultiplySymmetricMatrix(SM, vectmp); + } + + + bool OptimalPoint(FVector3& OutResult, RealType minThresh = 1000.0*TMathUtil::Epsilon ) const + { + return SolveAxEqualsb(OutResult, -bx, -by, -bz, minThresh); + } + +}; + + +typedef TQuadricError FQuadricErrorf; +typedef TQuadricError FQuadricErrord; + +/** +* Quadric Error type for use in memory-less simplification with volume preservation constraints. +* +* See: http://hhoppe.com/newqem.pdf or https://www.cc.gatech.edu/~turk/my_papers/memless_vis98.pdf +* for information about the volume preservation. +*/ +template +class TVolPresQuadricError : public TQuadricError +{ +public: + + typedef TQuadricError BaseStruct; + + struct FPlaneData + { + FPlaneData(const FVector3& Normal, const FVector3& Point) + { + N = Normal; + Dist = -Normal.Dot(Point); + } + + FPlaneData(RealType w, const FPlaneData& other) + { + N = w * other.N; + Dist = w * other.Dist; + } + + FPlaneData(const FPlaneData& other) + { + N = other.N; + Dist = other.Dist; + } + + FPlaneData() + { + N = FVector3::Zero(); + Dist = RealType(0); + } + + void Add(const FPlaneData& other) + { + N += other.N; + Dist += other.Dist; + } + + void Add(RealType w, const FPlaneData& other) + { + N += w * other.N; + Dist += w * other.Dist; + } + + FPlaneData& operator=(const FPlaneData& other) + { + N = other.N; + Dist = other.Dist; + + return *this; + } + + FVector3 N; + RealType Dist; + }; + + FPlaneData PlaneData; + +public: + + inline static TVolPresQuadricError Zero() { return TVolPresQuadricError(); }; + + TVolPresQuadricError() : BaseStruct() + { + } + + TVolPresQuadricError(const FVector3& Normal, const FVector3& Point) + : BaseStruct(Normal, Point) + , PlaneData(Normal, Point) + { } + + + TVolPresQuadricError(const TVolPresQuadricError& a, const TVolPresQuadricError& b) + : BaseStruct(a, b) + , PlaneData(a.PlaneData) + { + PlaneData.Add(b.PlaneData); + } + + TVolPresQuadricError(const TVolPresQuadricError& a, const TVolPresQuadricError& b, const FPlaneData& DuplicatePlaneData) + : BaseStruct(a, b) + , PlaneData(a.PlaneData) + { + PlaneData.Add(b.PlaneData); + + // Subtract a single copy of the duplicate plane data. + PlaneData.Add(-1., DuplicatePlaneData); + } + + void Add(RealType w, const TVolPresQuadricError& b) + { + BaseStruct::Add(w, b); + + PlaneData.Add(w, b.PlaneData); + } + + void Add(const TVolPresQuadricError& b) + { + BaseStruct::Add(b); + PlaneData.Add(b.PlaneData); + } + + TVolPresQuadricError& operator=(const TVolPresQuadricError& other) + { + BaseStruct::operator=(other); + PlaneData = other.PlaneData; + return *this; + } + + bool OptimalPoint(FVector3& OutResult, RealType minThresh = 1000.0*TMathUtil::Epsilon) const + { + // Compute the unconstrained optimal point + bool bValid = BaseStruct::OptimalPoint(OutResult, minThresh); + + // if it failed ( the A matrix wasn't invertible) we early out + if (!bValid) + { + return bValid; + } + + // Adjust with the volume constraint. + // See: http://hhoppe.com/newqem.pdf or https://www.cc.gatech.edu/~turk/my_papers/memless_vis98.pdf + + FPlaneData gvol(1. / 3., PlaneData); + + // Compute the effect of the volumetric constraint. + RealType Tol = minThresh; //(1.e-7); NB: using minThresh here to better compare with the attribute version.. + //constexpr RealType Tol = (1.e-7); //NB: using minThresh here to better compare with the attribute version.. + + FVector3 Ainv_g; + if (BaseStruct::SolveAxEqualsb(Ainv_g, gvol.N.X, gvol.N.Y, gvol.N.Z, Tol)) + { + + RealType gt_Ainv_g = gvol.N.Dot(Ainv_g); + + // move the point to the constrained optimal + RealType gt_unopt = gvol.N.Dot(OutResult); + RealType lambda = (gvol.Dist + gt_unopt) / gt_Ainv_g; + + // Check if the constraint failed. + + if (FMath::Abs(lambda) > 1.e5) + { + return bValid; + } + + OutResult -= lambda * Ainv_g; + } + + return bValid; + + + } +}; + + +typedef TVolPresQuadricError FVolPresQuadricErrorf; +typedef TVolPresQuadricError FVolPresQuadricErrord; + +/** +* Quadric Error type for use in volume memory-less simplification with volume preservation constraints. +* using the normal as three additional attributes to contribute to the quadric error. +* See: http://hhoppe.com/newqem.pdf +*/ +template +class TAttrBasedQuadricError : public TVolPresQuadricError +{ +public: + typedef TVolPresQuadricError BaseClass; + typedef TQuadricError BaseStruct; + + // Triangle Quadric constructor. Take vertex locations, vertex normals, face normal, and center of face. + TAttrBasedQuadricError(const FVector3& P0, const FVector3& P1, const FVector3& P2, + const FVector3& N0, const FVector3& N1, const FVector3& N2, + const FVector3& NFace, const FVector3& CenterPoint, RealType AttrWeight) + : BaseClass(NFace, CenterPoint), a(0) + { + /** + * Given scalar attribute 'a' defined at the vertices e.g. a0, a1, a2 + * + * solve 4x4 system: + * (p0^t 1) (g[0]) = (a0) + * (p1^t 1) (g[1]) (a1) + * (p2^t 1) (g[2]) (a2) + * (n^t 0) ( d ) (0 ) + * + * for the interpolating gradient 'g' -- note this is really p0.dot(g) + d = a0 and n.dot(g) = 0 + * + * in practice we actually use a Schur complement approach that only requires inverting a 3x3 matrix. + * + * For this quadric, each component of the normal is treated as separate attribute. + */ + TMatrix3 Matrix(P0, P1, P2, true); // row-based matrix constructor. + + bool bSolved = false; + double det = Matrix.Determinant(); + if (FMath::Abs(det) > 1.e-5) + { + FMatrix3d InvMatrix = Matrix.Inverse(); + FVector3d NFaceT_InvMatrix; + for (int i = 0; i < 3; ++i) + { + NFaceT_InvMatrix[i] = NFace[0] * InvMatrix.Row0[i] + NFace[1] * InvMatrix.Row1[i] + NFace[2] * InvMatrix.Row2[i]; + } + + double NFaceDotInvMOne = NFaceT_InvMatrix.Dot(FVector3d(1, 1, 1)); + + if (FMath::Abs(NFaceDotInvMOne) > 1.e-5) + { + bSolved = true; + for (int i = 0; i < 3; ++i) + { + FVector3d AttrVec(N0[i], N1[i], N2[i]); + AttrVec *= AttrWeight; + + d[i] = NFaceT_InvMatrix.Dot(AttrVec) / NFaceDotInvMOne; + grad[i] = InvMatrix * (AttrVec - FVector3d(d[i], d[i], d[i])); + + BaseStruct::Axx += grad[i].X * grad[i].X; + + BaseStruct::Axy += grad[i].X * grad[i].Y; + BaseStruct::Axz += grad[i].X * grad[i].Z; + + BaseStruct::Ayy += grad[i].Y * grad[i].Y; + BaseStruct::Ayz += grad[i].Y * grad[i].Z; + BaseStruct::Azz += grad[i].Z * grad[i].Z; + + BaseStruct::bx += d[i] * grad[i].X; + BaseStruct::by += d[i] * grad[i].Y; + BaseStruct::bz += d[i] * grad[i].Z; + + BaseStruct::c += d[i] * d[i]; + } + } + + } + + if (!bSolved) + { + for (int i = 0; i < 3; ++i) + { + grad[i] = FVector3::Zero(); + d[i] = (N0[i] + N1[i] + N2[i]) / 3.; // just average the attribute. + } + } + } + + + TAttrBasedQuadricError() + :BaseClass() + { + a = 0.; + const RealType zero[3] = { RealType(0), RealType(0), RealType(0) }; + grad[0] = FVector3(zero); + grad[1] = FVector3(zero); + grad[2] = FVector3(zero); + + d[0] = RealType(0); + d[1] = RealType(0); + d[2] = RealType(0); + } + + TAttrBasedQuadricError(const TAttrBasedQuadricError& Aother, const TAttrBasedQuadricError& Bother) + :BaseClass(Aother, Bother) + { + a = Aother.a + Bother.a; + + grad[0] = Aother.grad[0] + Bother.grad[0]; + grad[1] = Aother.grad[1] + Bother.grad[1]; + grad[2] = Aother.grad[2] + Bother.grad[2]; + + d[0] = Aother.d[0] + Bother.d[0]; + d[1] = Aother.d[1] + Bother.d[1]; + d[2] = Aother.d[2] + Bother.d[2]; + } + + static TAttrBasedQuadricError Zero() { return TAttrBasedQuadricError(); } + + void Add(RealType w, const TAttrBasedQuadricError& other) + { + BaseClass::Add(w, other); + + a += w; // accumulate weight + + grad[0] += w * other.grad[0]; + grad[1] += w * other.grad[1]; + grad[2] += w * other.grad[2]; + + d[0] += w * other.d[0]; + d[1] += w * other.d[1]; + d[2] += w * other.d[2]; + } + + void Add(const TAttrBasedQuadricError& other) + { + BaseClass::Add(other); + a += other.a; + + grad[0] += other.grad[0]; + grad[1] += other.grad[1]; + grad[2] += other.grad[2]; + + d[0] += other.d[0]; + d[1] += other.d[1]; + d[2] += other.d[2]; + } + + TAttrBasedQuadricError& operator=(const TAttrBasedQuadricError& other) + { + BaseClass::operator=(other); + + a = other.a; + + grad[0] = other.grad[0]; + grad[1] = other.grad[1]; + grad[2] = other.grad[2]; + + d[0] = other.d[0]; + d[1] = other.d[1]; + d[2] = other.d[2]; + + return *this; + } + + // The optimal point not counting volume conservation. + bool OptimalPoint(FVector3& OptPoint, RealType minThresh = 1000.0*TMathUtil::Epsilon) const + { + // Generate symmetric matrix + RealType SM[6] = { 0., 0., 0., 0., 0., 0. }; + + + for (int i = 0; i < 3; ++i) + { + SM[0] -= grad[i].X * grad[i].X; SM[1] -= grad[i].X * grad[i].Y; SM[2] -= grad[i].X * grad[i].Z; + SM[3] -= grad[i].Y * grad[i].Y; SM[4] -= grad[i].Y * grad[i].Z; + SM[5] -= grad[i].Z * grad[i].Z; + } + + RealType InvA = (FMath::Abs(a) > 1.e-7) ? RealType(1) / a : 0.; + + SM[0] *= InvA; SM[1] *= InvA; SM[2] *= InvA; + SM[3] *= InvA; SM[4] *= InvA; + SM[5] *= InvA; + + // A - (grad_attr0, grad_attr1.. grad_attrn) . (grad_attr0, grad_attr1.. grad_attrn)^T + + SM[0] += BaseStruct::Axx; SM[1] += BaseStruct::Axy; SM[2] += BaseStruct::Axz; + SM[3] += BaseStruct::Ayy; SM[4] += BaseStruct::Ayz; + SM[5] += BaseStruct::Azz; + + + RealType InvSM[6]; + bool bValid = BaseStruct::InvertSymmetricMatrix(SM, InvSM, minThresh); + + RealType b[3] = { -BaseStruct::bx, -BaseStruct::by, -BaseStruct::bz }; + b[0] += InvA * (grad[0].X * d[0] + grad[1].X * d[1] + grad[2].X * d[2]); + b[1] += InvA * (grad[0].Y * d[0] + grad[1].Y * d[1] + grad[2].Y * d[2]); + b[2] += InvA * (grad[0].Z * d[0] + grad[1].Z * d[1] + grad[2].Z * d[2]); + + // add volume constraint + typename BaseClass::FPlaneData gvol(1. / 3., BaseClass::PlaneData); + + if (bValid) + { + // optimal point prior to volume correction + + OptPoint = BaseStruct::MultiplySymmetricMatrix(InvSM, b); + //SolveAxEqualsb(OptPoint, b[0], b[1], b[2]); + + FVector3 InvSMgvol = BaseStruct::MultiplySymmetricMatrix(InvSM, gvol.N); + //SolveAxEqualsb(InvSMgvol, gvol.N.X, gvol.N.Y, gvol.N.Z); + + RealType gvolDotInvSMgvol = gvol.N.Dot(InvSMgvol); + + // move the point to the constrained optimal + RealType gvolDotuncopt = gvol.N.Dot(OptPoint); + RealType lambda = (gvol.Dist + gvolDotuncopt) / gvolDotInvSMgvol; + + // Check if the constraint failed. + + if (FMath::Abs(lambda) > 1.e5) + { + return bValid; + } + + OptPoint -= lambda * InvSMgvol; + } + + return bValid; + + } + + RealType Evaluate(const FVector3& point, const FVector3& attr) const + { + // 6x6 symmetric matrix ( A -grad[0], -grad[1], -grad[2] ) + // ( -grad[0]^T ) + // ( -grad[1]^T aI ) + // ( -grad[2]^T ) + // + // aI is Identity matrix scaled by the accumulated area 'a' + // + // extended state vector v = ( point ) + // ( attr ) + // + + + RealType ptAp = point.Dot(BaseStruct::MultiplyA(point)); + RealType attrDotattr = attr.Dot(attr); + FVector3 Gradattr(grad[0].X * attr[0] + grad[1].X * attr[1] + grad[2].X * attr[2], + grad[0].Y * attr[0] + grad[1].Y * attr[1] + grad[2].Y * attr[2], + grad[0].Z * attr[0] + grad[1].Z * attr[1] + grad[2].Z * attr[2]); + RealType ptGradattr = point.Dot(Gradattr); + + RealType vtSv = ptAp + a * attrDotattr - RealType(2) * ptGradattr; + + // extended 'b' vector + // ( b ) + // (-d ) + // compute 2 + RealType vtb = point.X * BaseStruct::bx + point.Y * BaseStruct::by + point.Z * BaseStruct::bz + - (d[0] * attr[0] + d[1] * attr[1] + d[2] * attr[2]); + + RealType QuadricError = vtSv + RealType(2) * vtb + BaseStruct::c; + + return QuadricError; + } + + void ComputeAttributes(const FVector3& point, FVector3& attr) const + { + if (FMath::Abs(a) > 1.e-5) + { + RealType aInv = RealType(1) / a; + + attr.X = grad[0].Dot(point) + d[0]; + attr.Y = grad[1].Dot(point) + d[1]; + attr.Z = grad[2].Dot(point) + d[2]; + + attr *= aInv; + } + else + { + attr.X = RealType(0); + attr.Y = RealType(0); + attr.Z = RealType(0); + } + } + + RealType Evaluate(const FVector3& point) const + { + FVector3 attr; + ComputeAttributes(point, attr); + + return Evaluate(point, attr); + } + +public: + + RealType a; + // Additional planes for the attributes. + FVector3 grad[3]; + RealType d[3]; + + +}; + +typedef TAttrBasedQuadricError FAttrBasedQuadricErrorf; +typedef TAttrBasedQuadricError FAttrBasedQuadricErrord; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Quaternion.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Quaternion.h new file mode 100644 index 000000000000..48313f2e59ee --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Quaternion.h @@ -0,0 +1,456 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "VectorTypes.h" +#include "MatrixTypes.h" +#include "IndexTypes.h" + +// ported from geometry3Sharp Quaternion + +template +struct TQuaternion +{ + // note: in Wm5 version, this is a 4-element arraY stored in order (w,x,y,z). + RealType X; + RealType Y; + RealType Z; + RealType W; + + TQuaternion(); + TQuaternion(RealType X, RealType Y, RealType Z, RealType W); + TQuaternion(const RealType* Values); + TQuaternion(const TQuaternion& Copy); + TQuaternion(const FVector3& Axis, RealType Angle, bool bAngleIsDegrees); + TQuaternion(const FVector3& From, const FVector3& To); + TQuaternion(const TQuaternion& From, const TQuaternion& To, RealType InterpT); + TQuaternion(const TMatrix3& RotationMatrix); + + void SetAxisAngleD(const FVector3& Axis, RealType AngleDeg); + void SetAxisAngleR(const FVector3& Axis, RealType AngleRad); + void SetFromTo(const FVector3& From, const FVector3& To); + void SetToSlerp(const TQuaternion& From, const TQuaternion& To, RealType InterpT); + void SetFromRotationMatrix(const TMatrix3& RotationMatrix); + + static TQuaternion Zero() { return TQuaternion((RealType)0, (RealType)0, (RealType)0, (RealType)0); } + static TQuaternion Identity() { return TQuaternion((RealType)0, (RealType)0, (RealType)0, (RealType)1); } + + RealType & operator[](int i) { return (&X)[i]; } + const RealType & operator[](int i) const { return (&X)[i]; } + + bool EpsilonEqual(const TQuaternion& Other, RealType Epsilon) const; + + RealType Length() const { return (RealType)sqrt(X*X + Y*Y + Z*Z + W*W); } + RealType SquaredLength() const { return X*X + Y*Y + Z*Z + W*W; } + + FVector3 AxisX() const; + FVector3 AxisY() const; + FVector3 AxisZ() const; + + RealType Normalize(const RealType epsilon = 0); + TQuaternion Normalized(const RealType epsilon = 0) const; + + RealType Dot(const TQuaternion& Other) const; + + TQuaternion Inverse() const; + FVector3 InverseMultiply(const FVector3& Other) const; + + TMatrix3 ToRotationMatrix() const; + + + // available for porting: + // SetFromRotationMatrix(FMatrix3f) + + + inline operator FQuat() const + { + FQuat Quat; + Quat.X = (float)X; + Quat.Y = (float)Y; + Quat.Z = (float)Z; + Quat.W = (float)W; + return Quat; + } + inline TQuaternion(const FQuat& Quat) + { + X = (RealType)Quat.X; + Y = (RealType)Quat.Y; + Z = (RealType)Quat.Z; + W = (RealType)Quat.W; + } + +}; + + + +typedef TQuaternion FQuaternionf; +typedef TQuaternion FQuaterniond; + + + + +template +TQuaternion::TQuaternion() +{ + X = Y = Z = 0; W = 1; +} + +template +TQuaternion::TQuaternion(RealType x, RealType y, RealType z, RealType w) +{ + X = x; + Y = y; + Z = z; + W = w; +} + +template +TQuaternion::TQuaternion(const RealType* Values) +{ + X = Values[0]; + Y = Values[1]; + Z = Values[2]; + W = Values[3]; +} + +template +TQuaternion::TQuaternion(const TQuaternion& Copy) +{ + X = Copy.X; + Y = Copy.Y; + Z = Copy.Z; + W = Copy.W; +} + +template +TQuaternion::TQuaternion(const FVector3& Axis, RealType Angle, bool bAngleIsDegrees) +{ + X = Y = Z = 0; W = 1; + SetAxisAngleR(Axis, Angle * (bAngleIsDegrees ? TMathUtil::DegToRad : (RealType)1)); +} + +template +TQuaternion::TQuaternion(const FVector3& From, const FVector3& To) +{ + X = Y = Z = 0; W = 1; + SetFromTo(From, To); +} + +template +TQuaternion::TQuaternion(const TQuaternion& From, const TQuaternion& To, RealType InterpT) +{ + X = Y = Z = 0; W = 1; + SetToSlerp(From, To, InterpT); +} + +template +TQuaternion::TQuaternion(const TMatrix3& RotationMatrix) +{ + X = Y = Z = 0; W = 1; + SetFromRotationMatrix(RotationMatrix); +} + + + + + +template +RealType TQuaternion::Normalize(const RealType Epsilon) +{ + RealType length = Length(); + if (length > Epsilon) + { + RealType invLength = ((RealType)1) / length; + X *= invLength; + Y *= invLength; + Z *= invLength; + W *= invLength; + return invLength; + } + X = Y = Z = W = (RealType)0; + return 0; +} + +template +TQuaternion TQuaternion::Normalized(const RealType Epsilon) const +{ + RealType length = Length(); + if (length > Epsilon) + { + RealType invLength = ((RealType)1) / length; + return TQuaternion(X*invLength, Y*invLength, Z*invLength, W*invLength); + } + return Zero(); +} + +template +RealType TQuaternion::Dot(const TQuaternion& Other) const +{ + return X * Other.X + Y * Other.Y + Z * Other.Z + W * Other.W; +} + + +template +TQuaternion operator*(const TQuaternion& A, const TQuaternion& B) +{ + RealType W = A.W * B.W - A.X * B.X - A.Y * B.Y - A.Z * B.Z; + RealType X = A.W * B.X + A.X * B.W + A.Y * B.Z - A.Z * B.Y; + RealType Y = A.W * B.Y + A.Y * B.W + A.Z * B.X - A.X * B.Z; + RealType Z = A.W * B.Z + A.Z * B.W + A.X * B.Y - A.Y * B.X; + return TQuaternion(X, Y, Z, W); +} + +template +TQuaternion operator -(const TQuaternion& A, const TQuaternion& B) +{ + return TQuaternion(A.X - B.X, A.Y - B.Y, A.Z - B.Z, A.W - B.W); +} + +template +FVector3 operator*(const TQuaternion& Q, const FVector3& V) +{ + //return q.ToRotationMatrix() * v; + // inline-expansion of above: + RealType twoX = (RealType)2*Q.X; RealType twoY = (RealType)2*Q.Y; RealType twoZ = (RealType)2*Q.Z; + RealType twoWX = twoX * Q.W; RealType twoWY = twoY * Q.W; RealType twoWZ = twoZ * Q.W; + RealType twoXX = twoX * Q.X; RealType twoXY = twoY * Q.X; RealType twoXZ = twoZ * Q.X; + RealType twoYY = twoY * Q.Y; RealType twoYZ = twoZ * Q.Y; RealType twoZZ = twoZ * Q.Z; + return FVector3( + V.X * ((RealType)1 - (twoYY + twoZZ)) + V.Y * (twoXY - twoWZ) + V.Z * (twoXZ + twoWY), + V.X * (twoXY + twoWZ) + V.Y * ((RealType)1 - (twoXX + twoZZ)) + V.Z * (twoYZ - twoWX), + V.X * (twoXZ - twoWY) + V.Y * (twoYZ + twoWX) + V.Z * ((RealType)1 - (twoXX + twoYY))); ; +} + + +template +FVector3 TQuaternion::InverseMultiply(const FVector3& V) const +{ + RealType norm = SquaredLength(); + if (norm > 0) + { + RealType invNorm = (RealType)1 / norm; + RealType qX = -X * invNorm, qY = -Y * invNorm, qZ = -Z * invNorm, qW = W * invNorm; + RealType twoX = (RealType)2 * qX; RealType twoY = (RealType)2 * qY; RealType twoZ = (RealType)2 * qZ; + RealType twoWX = twoX * qW; RealType twoWY = twoY * qW; RealType twoWZ = twoZ * qW; + RealType twoXX = twoX * qX; RealType twoXY = twoY * qX; RealType twoXZ = twoZ * qX; + RealType twoYY = twoY * qY; RealType twoYZ = twoZ * qY; RealType twoZZ = twoZ * qZ; + return FVector3( + V.X * ((RealType)1 - (twoYY + twoZZ)) + V.Y * (twoXY - twoWZ) + V.Z * (twoXZ + twoWY), + V.X * (twoXY + twoWZ) + V.Y * ((RealType)1 - (twoXX + twoZZ)) + V.Z * (twoYZ - twoWX), + V.X * (twoXZ - twoWY) + V.Y * (twoYZ + twoWX) + V.Z * ((RealType)1 - (twoXX + twoYY))); + } + return FVector3::Zero(); +} + + +template +FVector3 TQuaternion::AxisX() const +{ + RealType twoY = (RealType)2 * Y; RealType twoZ = (RealType)2 * Z; + RealType twoWY = twoY * W; RealType twoWZ = twoZ * W; + RealType twoXY = twoY * X; RealType twoXZ = twoZ * X; + RealType twoYY = twoY * Y; RealType twoZZ = twoZ * Z; + return FVector3((RealType)1 - (twoYY + twoZZ), twoXY + twoWZ, twoXZ - twoWY); +} + +template +FVector3 TQuaternion::AxisY() const +{ + RealType twoX = (RealType)2 * X; RealType twoY = (RealType)2 * Y; RealType twoZ = (RealType)2 * Z; + RealType twoWX = twoX * W; RealType twoWZ = twoZ * W; RealType twoXX = twoX * X; + RealType twoXY = twoY * X; RealType twoYZ = twoZ * Y; RealType twoZZ = twoZ * Z; + return FVector3(twoXY - twoWZ, (RealType)1 - (twoXX + twoZZ), twoYZ + twoWX); +} + +template +FVector3 TQuaternion::AxisZ() const +{ + RealType twoX = (RealType)2 * X; RealType twoY = (RealType)2 * Y; RealType twoZ = (RealType)2 * Z; + RealType twoWX = twoX * W; RealType twoWY = twoY * W; RealType twoXX = twoX * X; + RealType twoXZ = twoZ * X; RealType twoYY = twoY * Y; RealType twoYZ = twoZ * Y; + return FVector3(twoXZ + twoWY, twoYZ - twoWX, (RealType)1 - (twoXX + twoYY)); +} + +template +TQuaternion TQuaternion::Inverse() const +{ + RealType norm = SquaredLength(); + if (norm > (RealType)0) + { + RealType invNorm = (RealType)1 / norm; + return TQuaternion( + -X * invNorm, -Y * invNorm, -Z * invNorm, W * invNorm); + } + return TQuaternion::Zero(); +} + + +template +void TQuaternion::SetAxisAngleD(const FVector3& Axis, RealType AngleDeg) +{ + SetAxisAngleR(Axis, TMathUtil::DegToRad * AngleDeg); +} + +template +void TQuaternion::SetAxisAngleR(const FVector3& Axis, RealType AngleRad) +{ + RealType halfAngle = (RealType)0.5 * AngleRad; + RealType sn = (RealType)sin(halfAngle); + W = (RealType)cos(halfAngle); + X = (sn * Axis.X); + Y = (sn * Axis.Y); + Z = (sn * Axis.Z); +} + + + +// this function can take non-normalized vectors vFrom and vTo (normalizes internally) +template +void TQuaternion::SetFromTo(const FVector3& From, const FVector3& To) +{ + // [TODO] this page seems to have optimized version: + // http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors + + // [RMS] not ideal to explicitly normalize here, but if we don't, + // output TQuaternion is not normalized and this causes problems, + // eg like drift if we do repeated SetFromTo() + FVector3 from = From.Normalized(), to = To.Normalized(); + FVector3 bisector = (from + to).Normalized(); + W = from.Dot(bisector); + if (W != 0) + { + FVector3 cross = from.Cross(bisector); + X = cross.X; + Y = cross.Y; + Z = cross.Z; + } + else + { + RealType invLength; + if ( (RealType)fabs(from.X) >= (RealType)fabs(from.Y)) + { + // V1.X or V1.Z is the largest magnitude component. + invLength = ((RealType)1 / (RealType)sqrt(from.X * from.X + from.Z * from.Z)); + X = -from.Z * invLength; + Y = 0; + Z = +from.X * invLength; + } + else + { + // V1.Y or V1.Z is the largest magnitude component. + invLength = ((RealType)1 / (RealType)sqrt(from.Y * from.Y + from.Z * from.Z)); + X = 0; + Y = +from.Z * invLength; + Z = -from.Y * invLength; + } + } + Normalize(); // just to be safe... +} + + + +template +void TQuaternion::SetToSlerp( const TQuaternion& From, const TQuaternion& To, RealType InterpT) +{ + RealType cs = From.Dot(To); + RealType angle = (RealType)acos(cs); + if (TMathUtil::Abs(angle) >= TMathUtil::ZeroTolerance) + { + RealType sn = (RealType)sin(angle); + RealType invSn = 1 / sn; + RealType tAngle = InterpT * angle; + RealType coeff0 = (RealType)sin(angle - tAngle) * invSn; + RealType coeff1 = (RealType)sin(tAngle) * invSn; + X = coeff0 * From.X + coeff1 * To.X; + Y = coeff0 * From.Y + coeff1 * To.Y; + Z = coeff0 * From.Z + coeff1 * To.Z; + W = coeff0 * From.W + coeff1 * To.W; + } + else + { + X = From.X; + Y = From.Y; + Z = From.Z; + W = From.W; + } +} + + + +template +void TQuaternion::SetFromRotationMatrix(const TMatrix3& RotationMatrix) +{ + // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes + // article "TQuaternion Calculus and Fast Animation". + FIndex3i next(1, 2, 0); + + RealType trace = RotationMatrix(0,0) + RotationMatrix(1,1) + RotationMatrix(2, 2); + RealType root; + + if (trace > 0) + { + // |w| > 1/2, may as well choose w > 1/2 + root = (RealType)sqrt(trace + (RealType)1); // 2w + W = ((RealType)0.5) * root; + root = ((RealType)0.5) / root; // 1/(4w) + X = (RotationMatrix(2, 1) - RotationMatrix(1, 2)) * root; + Y = (RotationMatrix(0, 2) - RotationMatrix(2, 0)) * root; + Z = (RotationMatrix(1, 0) - RotationMatrix(0, 1)) * root; + } + else + { + // |w| <= 1/2 + int i = 0; + if (RotationMatrix(1, 1) > RotationMatrix(0, 0)) + { + i = 1; + } + if (RotationMatrix(2, 2) > RotationMatrix(i, i)) + { + i = 2; + } + int j = next[i]; + int k = next[j]; + + root = (RealType)sqrt(RotationMatrix(i, i) - RotationMatrix(j, j) - RotationMatrix(k, k) + (RealType)1); + + FVector3 quat(X, Y, Z); + quat[i] = ((RealType)0.5) * root; + root = ((RealType)0.5) / root; + W = (RotationMatrix(k, j) - RotationMatrix(j, k)) * root; + quat[j] = (RotationMatrix(j, i) + RotationMatrix(i, j)) * root; + quat[k] = (RotationMatrix(k, i) + RotationMatrix(i, k)) * root; + X = quat.X; Y = quat.Y; Z = quat.Z; + } + + Normalize(); // we prefer normalized TQuaternions... +} + + + + +template +TMatrix3 TQuaternion::ToRotationMatrix() const +{ + RealType twoX = 2 * X; RealType twoY = 2 * Y; RealType twoZ = 2 * Z; + RealType twoWX = twoX * W; RealType twoWY = twoY * W; RealType twoWZ = twoZ * W; + RealType twoXX = twoX * X; RealType twoXY = twoY * X; RealType twoXZ = twoZ * X; + RealType twoYY = twoY * Y; RealType twoYZ = twoZ * Y; RealType twoZZ = twoZ * Z; + TMatrix3 m = TMatrix3::Zero(); + m.Row0 = FVector3(1 - (twoYY + twoZZ), twoXY - twoWZ, twoXZ + twoWY); + m.Row1 = FVector3(twoXY + twoWZ, 1 - (twoXX + twoZZ), twoYZ - twoWX); + m.Row2 = FVector3(twoXZ - twoWY, twoYZ + twoWX, 1 - (twoXX + twoYY)); + return m; +} + + + +template +bool TQuaternion::EpsilonEqual(const TQuaternion& Other, RealType Epsilon) const +{ + return (RealType)fabs(X - Other.X) <= Epsilon && + (RealType)fabs(Y - Other.Y) <= Epsilon && + (RealType)fabs(Z - Other.Z) <= Epsilon && + (RealType)fabs(W - Other.W) <= Epsilon; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/RayTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/RayTypes.h new file mode 100644 index 000000000000..df2916bb92cf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/RayTypes.h @@ -0,0 +1,115 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// port of geometry3Sharp Ray3 + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" +#include "Math/Ray.h" + +/* + * 3D Ray stored as Origin point and normalized Direction vector + */ +template +class TRay3 +{ +public: + /** Origin point */ + FVector3 Origin; + /** Direction vector, always normalized */ + FVector3 Direction; + + /** Construct ray at origin pointed down Z axis */ + TRay3() + { + Origin = FVector3::Zero(); + Direction = FVector3::UnitZ(); + } + + /** + * Construct ray from origin and direction + * @param Origin origin point + * @param Direction direction vector. Must be normalized if bIsNormalized=true + * @param bIsNormalized if true, Direction will not be re-normalized + */ + TRay3(const FVector3& Origin, const FVector3& Direction, bool bIsNormalized = false) + { + this->Origin = Origin; + this->Direction = Direction; + if (bIsNormalized == false) + { + this->Direction.Normalize(); + } + } + + /** + * @return point on ray at given (signed) Distance from the ray Origin + */ + FVector3 PointAt(RealType Distance) const + { + return Origin + Distance * Direction; + } + + + /** + * @return ray parameter (ie positive distance from Origin) at nearest point on ray to QueryPoint + */ + inline RealType Project(const FVector3& QueryPoint) const + { + RealType LineParam = (QueryPoint - Origin).Dot(Direction); + return (LineParam < 0) ? 0 : LineParam; + } + + /** + * @return smallest squared distance from line to QueryPoint + */ + inline RealType DistanceSquared(const FVector3& QueryPoint) const + { + RealType LineParam = (QueryPoint - Origin).Dot(Direction); + if (LineParam < 0) + { + return Origin.DistanceSquared(QueryPoint); + } + else + { + FVector3 NearestPt = Origin + LineParam * Direction; + return NearestPt.DistanceSquared(QueryPoint); + } + } + + /** + * @return nearest point on line to QueryPoint + */ + inline FVector3 NearestPoint(const FVector3& QueryPoint) const + { + RealType LineParam = (QueryPoint - Origin).Dot(Direction); + if (LineParam < 0) + { + return Origin; + } + else + { + return Origin + LineParam * Direction; + } + } + + + + // conversion operators + + inline operator FRay() const + { + return FRay((FVector)Origin, (FVector)Direction); + } + inline TRay3(const FRay & RayIn) + { + Origin = (FVector3)RayIn.Origin; + Direction = (FVector3)RayIn.Direction; + } + + +}; +typedef TRay3 FRay3f; +typedef TRay3 FRay3d; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SegmentTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SegmentTypes.h new file mode 100644 index 000000000000..d4a73077e10a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/SegmentTypes.h @@ -0,0 +1,506 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// ported from geometry3Sharp Segment2 + +#pragma once + +#include "Math/UnrealMath.h" +#include "VectorTypes.h" + + +/* + * 2D Line Segmented stored as Center point, normalized Direction vector, and scalar Extent + */ +template +struct TSegment2 +{ +public: + /** Center point of segment */ + FVector2 Center; + /** normalized Direction vector of segment */ + FVector2 Direction; + /** Extent of segment, which is half the total length */ + T Extent; + + /** + * Construct a Segment from two Points + */ + TSegment2(const FVector2& Point0, const FVector2& Point1) + { + // set from endpoints + Center = T(.5) * (Point0 + Point1); + Direction = Point1 - Point0; + Extent = T(.5) * Direction.Normalize(); + } + + /** + * Construct a segment from a Center Point, normalized Direction, and scalar Extent + */ + TSegment2(const FVector2& CenterIn, const FVector2& DirectionIn, T ExtentIn) + { + Center = CenterIn; + Direction = DirectionIn; + Extent = ExtentIn; + } + + + + /** Update the Segment with a new start point */ + inline void SetStartPoint(const FVector2& Point) + { + update_from_endpoints(Point, EndPoint()); + } + + /** Update the Segment with a new end point */ + inline void SetEndPoint(const FVector2& Point) + { + update_from_endpoints(StartPoint(), Point); + } + + /** Reverse the segment */ + void Reverse() + { + update_from_endpoints(EndPoint(), StartPoint()); + } + + + + + /** @return start point of segment */ + inline FVector2 StartPoint() const + { + return Center - Extent * Direction; + } + + /** @return end point of segment */ + inline FVector2 EndPoint() const + { + return Center + Extent * Direction; + } + + + /** @return the Length of the segment */ + inline T Length() const + { + return (T)2 * Extent; + } + + /** @return first (i == 0) or second (i == 1) endpoint of the Segment */ + inline FVector2 GetPointFromIndex(int i) const + { + return (i == 0) ? (Center - Extent * Direction) : (Center + Extent * Direction); + } + + /** + * @return point on segment at given (signed) Distance from the segment Origin + */ + inline FVector2 PointAt(T DistanceParameter) const + { + return Center + DistanceParameter * Direction; + } + + /** + * @param UnitParameter value in range [0,1] + * @return point on segment at that linearly interpolates between start and end based on Unit Parameter + */ + inline FVector2 PointBetween(T UnitParameter) const + { + return Center + ((T)2 * UnitParameter - (T)1) * Extent * Direction; + } + + /** + * @return minimum squared distance from Point to segment + */ + inline T DistanceSquared(const FVector2& Point) const + { + T DistParameter; + return DistanceSquared(Point, DistParameter); + } + + + + /** + * @param Point query point + * @param DistParameterOut calculated distance parameter in range [-Extent,Extent] + * @return minimum squared distance from Point to Segment + */ + T DistanceSquared(const FVector2& Point, T& DistParameterOut) const + { + DistParameterOut = (Point - Center).Dot(Direction); + if (DistParameterOut >= Extent) + { + DistParameterOut = Extent; + return Point.DistanceSquared(EndPoint()); + } + else if (DistParameterOut <= -Extent) + { + DistParameterOut = -Extent; + return Point.DistanceSquared(StartPoint()); + } + FVector2 ProjectedPt = Center + DistParameterOut * Direction; + return ProjectedPt.DistanceSquared(Point); + } + + + /** + * @return nearest point on segment to QueryPoint + */ + inline FVector2 NearestPoint(const FVector2& QueryPoint) const + { + T t = (QueryPoint - Center).Dot(Direction); + if (t >= Extent) + { + return EndPoint(); + } + if (t <= -Extent) + { + return StartPoint(); + } + return Center + t * Direction; + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment (not clamped to Extents) + */ + inline T Project(const FVector2& QueryPoint) const + { + return (QueryPoint - Center).Dot(Direction); + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment, mapped to [0,1] range along segment + */ + inline T ProjectUnitRange(const FVector2& QueryPoint) const + { + T ProjT = (QueryPoint - Center).Dot(Direction); + T Alpha = ((ProjT / Extent) + (T)1) * (T)0.5; + return TMathUtil::Clamp(Alpha, (T)0, (T)1); + } + + + + /** + * Determine which side of the segment the query point lies on + * @param QueryPoint test point + * @param Tolerance tolerance band in which we return 0 + * @return +1 if point is to right of line, -1 if left, and 0 if on line or within tolerance band + */ + int WhichSide(const FVector2& QueryPoint, T Tolerance = 0) + { + // [TODO] subtract Center from test? + FVector2 EndPt = Center + Extent * Direction; + FVector2 StartPt = Center - Extent * Direction; + T det = -FVector2::Orient(EndPt, StartPt, QueryPoint); + return (det > Tolerance ? +1 : (det < -Tolerance ? -1 : 0)); + } + + + /** + * Test if this segment intersects with OtherSegment. Returns true for parallel-line overlaps. Returns same result as IntrSegment2Segment2 + * @param OtherSegment segment to test against + * @param DotThresh dot-product tolerance used to determine if segments are parallel + * @param IntervalThresh distance tolerance used to allow slighly-not-touching segments to be considered overlapping + * @return true if segments intersect + */ + bool Intersects(const TSegment2& OtherSegment, T DotThresh = TMathUtil::Epsilon, T IntervalThresh = 0) const + { + // see IntrLine2Line2 and IntrSegment2Segment2 for details on this code + + FVector2 diff = OtherSegment.Center - Center; + T D0DotPerpD1 = Direction.DotPerp(OtherSegment.Direction); + if (TMathUtil::Abs(D0DotPerpD1) > DotThresh) // Lines intersect in a single point. + { + T invD0DotPerpD1 = ((T)1) / D0DotPerpD1; + T diffDotPerpD0 = diff.DotPerp(Direction); + T diffDotPerpD1 = diff.DotPerp(OtherSegment.Direction); + T s = diffDotPerpD1 * invD0DotPerpD1; + T s2 = diffDotPerpD0 * invD0DotPerpD1; + return TMathUtil::Abs(s) <= (Extent + IntervalThresh) + && TMathUtil::Abs(s2) <= (OtherSegment.Extent + IntervalThresh); + } + + // Lines are parallel. + diff.Normalize(); + T diffNDotPerpD1 = diff.DotPerp(OtherSegment.Direction); + if (TMathUtil::Abs(diffNDotPerpD1) <= DotThresh) + { + // Compute the location of OtherSegment endpoints relative to our Segment + diff = OtherSegment.Center - Center; + T t1 = Direction.Dot(diff); + T tmin = t1 - OtherSegment.Extent; + T tmax = t1 + OtherSegment.Extent; + TInterval1 extents(-Extent, Extent); + if (extents.Overlaps(TInterval1(tmin, tmax))) + { + return true; + } + return false; + } + + // lines are parallel but not collinear + return false; + } + + + + + + // 2D segment utility functions + + + /** + * Calculate distance from QueryPoint to segment (StartPt,EndPt) + */ + static T FastDistanceSquared(const FVector2& StartPt, const FVector2& EndPt, const FVector2& QueryPt, T Tolerance = TMathUtil::Epsilon) + { + T vx = EndPt.X - StartPt.X, vy = EndPt.Y - StartPt.Y; + T len2 = vx * vx + vy * vy; + T dx = QueryPt.X - StartPt.X, dy = QueryPt.Y - StartPt.Y; + //if (len2 < 1e-13) + if (len2 < Tolerance) + { + return dx * dx + dy * dy; + } + T t = (dx*vx + dy * vy); + if (t <= 0) + { + return dx * dx + dy * dy; + } + else if (t >= len2) + { + dx = QueryPt.X - EndPt.X; + dy = QueryPt.Y - EndPt.Y; + return dx * dx + dy * dy; + } + dx = QueryPt.X - (StartPt.X + ((t * vx) / len2)); + dy = QueryPt.Y - (StartPt.Y + ((t * vy) / len2)); + return dx * dx + dy * dy; + } + + + /** + * Determine which side of the segment the query point lies on + * @param StartPt first point of Segment + * @param EndPt second point of Segment + * @param QueryPoint test point + * @param Tolerance tolerance band in which we return 0 + * @return +1 if point is to right of line, -1 if left, and 0 if on line or within tolerance band + */ + static int WhichSide(const FVector2& StartPt, const FVector2& EndPt, const FVector2& QueryPt, T Tolerance = (T)0) + { + T det = -FVector2::Orient(StartPt, EndPt, QueryPt); + return (det > Tolerance ? +1 : (det < -Tolerance ? -1 : 0)); + } + + + + + +protected: + + // update segment based on new endpoints + inline void update_from_endpoints(const FVector2& p0, const FVector2& p1) + { + Center = 0.5 * (p0 + p1); + Direction = p1 - p0; + Extent = 0.5 * Direction.Normalize(); + } + +}; +typedef TSegment2 FSegment2f; +typedef TSegment2 FSegment2d; + + + + + + + + +/* + * 3D Line Segmented stored as Center point, normalized Direction vector, and scalar Extent + */ +template +struct TSegment3 +{ +public: + /** Center point of segment */ + FVector3 Center; + /** normalized Direction vector of segment */ + FVector3 Direction; + /** Extent of segment, which is half the total length */ + T Extent; + + /** + * Construct a Segment from two Points + */ + TSegment3(const FVector3& Point0, const FVector3& Point1) + { + // set from endpoints + Center = T(.5) * (Point0 + Point1); + Direction = Point1 - Point0; + Extent = T(.5) * Direction.Normalize(); + } + + /** + * Construct a segment from a Center Point, normalized Direction, and scalar Extent + */ + TSegment3(const FVector3& CenterIn, const FVector3& DirectionIn, T ExtentIn) + { + Center = CenterIn; + Direction = DirectionIn; + Extent = ExtentIn; + } + + + + /** Update the Segment with a new start point */ + inline void SetStartPoint(const FVector3& Point) + { + update_from_endpoints(Point, EndPoint()); + } + + /** Update the Segment with a new end point */ + inline void SetEndPoint(const FVector3& Point) + { + update_from_endpoints(StartPoint(), Point); + } + + /** Reverse the segment */ + void Reverse() + { + update_from_endpoints(EndPoint(), StartPoint()); + } + + + + + /** @return start point of segment */ + inline FVector3 StartPoint() const + { + return Center - Extent * Direction; + } + + /** @return end point of segment */ + inline FVector3 EndPoint() const + { + return Center + Extent * Direction; + } + + + /** @return the Length of the segment */ + inline T Length() const + { + return (T)2 * Extent; + } + + /** @return first (i == 0) or second (i == 1) endpoint of the Segment */ + inline FVector3 GetPointFromIndex(int i) const + { + return (i == 0) ? (Center - Extent * Direction) : (Center + Extent * Direction); + } + + /** + * @return point on segment at given (signed) Distance from the segment Origin + */ + inline FVector3 PointAt(T DistanceParameter) const + { + return Center + DistanceParameter * Direction; + } + + /** + * @param UnitParameter value in range [0,1] + * @return point on segment at that linearly interpolates between start and end based on Unit Parameter + */ + inline FVector3 PointBetween(T UnitParameter) const + { + return Center + ((T)2 * UnitParameter - (T)1) * Extent * Direction; + } + + /** + * @return minimum squared distance from Point to segment + */ + inline T DistanceSquared(const FVector3& Point) const + { + T DistParameter; + return DistanceSquared(Point, DistParameter); + } + + + + /** + * @param Point query point + * @param DistParameterOut calculated distance parameter in range [-Extent,Extent] + * @return minimum squared distance from Point to Segment + */ + T DistanceSquared(const FVector3& Point, T& DistParameterOut) const + { + DistParameterOut = (Point - Center).Dot(Direction); + if (DistParameterOut >= Extent) + { + DistParameterOut = Extent; + return Point.DistanceSquared(EndPoint()); + } + else if (DistParameterOut <= -Extent) + { + DistParameterOut = -Extent; + return Point.DistanceSquared(StartPoint()); + } + FVector3 ProjectedPt = Center + DistParameterOut * Direction; + return ProjectedPt.DistanceSquared(Point); + } + + + /** + * @return nearest point on segment to QueryPoint + */ + inline FVector3 NearestPoint(const FVector3& QueryPoint) const + { + T t = (QueryPoint - Center).Dot(Direction); + if (t >= Extent) + { + return EndPoint(); + } + if (t <= -Extent) + { + return StartPoint(); + } + return Center + t * Direction; + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment (not clamped to Extents) + */ + inline T Project(const FVector3& QueryPoint) const + { + return (QueryPoint - Center).Dot(Direction); + } + + + /** + * @return scalar projection of QueryPoint onto line of Segment, mapped to [0,1] range along segment + */ + inline T ProjectUnitRange(const FVector3& QueryPoint) const + { + T ProjT = (QueryPoint - Center).Dot(Direction); + T Alpha = ((ProjT / Extent) + (T)1) * (T)0.5; + return TMathUtil::Clamp(Alpha, (T)0, (T)1); + } + + +protected: + + // update segment based on new endpoints + inline void update_from_endpoints(const FVector3& p0, const FVector3& p1) + { + Center = 0.5 * (p0 + p1); + Direction = p1 - p0; + Extent = 0.5 * Direction.Normalize(); + } + +}; +typedef TSegment3 FSegment3f; +typedef TSegment3 FSegment3d; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h new file mode 100644 index 000000000000..841bd44c1f51 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/FastWinding.h @@ -0,0 +1,461 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MathUtil.h" +#include "MeshQueries.h" +#include "Spatial/MeshAABBTree3.h" +#include "Util/MeshCaches.h" +#include "MatrixTypes.h" + +/** + * Formulas for triangle winding number approximation + */ +namespace FastTriWinding +{ + /** + * precompute constant coefficients of triangle winding number approximation + * P: 'Center' of expansion for Triangles (area-weighted centroid avg) + * R: max distance from P to Triangles + * Order1: first-order vector coeff + * Order2: second-order matrix coeff + * TriCache: precomputed triangle centroid/normal/area (todo @jimmy either support passing in as possibly-nullptr or remove commented-out null branches!) + */ + template + void ComputeCoeffs(const TriangleMeshType& Mesh, + const IterableTriangleIndices& Triangles, + const FMeshTriInfoCache& TriCache, + FVector3d& P, double& R, + FVector3d& Order1, FMatrix3d& Order2) + { + P = FVector3d::Zero(); + Order1 = FVector3d::Zero(); + Order2 = FMatrix3d::Zero(); + R = 0; + + // compute area-weighted centroid of Triangles, we use this as the expansion point + FVector3d P0 = FVector3d::Zero(), P1 = FVector3d::Zero(), P2 = FVector3d::Zero(); + double sum_area = 0; + for (int tid : Triangles) + { + //if (TriCache == null) + //{ + // double area = TriCache.Areas[tid]; + // sum_area += area; + // P += area * TriCache.Centroids[tid]; + //} + //else + { + Mesh.GetTriVertices(tid, P0, P1, P2); + double area = VectorUtil::Area(P0, P1, P2); + sum_area += area; + P += area * ((P0 + P1 + P2) / 3.0); + } + } + P /= sum_area; + + // compute first and second-order coefficients of FWN taylor expansion, as well as + // 'radius' value R, which is max dist from any tri vertex to P + FVector3d n, c; + double a = 0; + for (int tid : Triangles) + { + Mesh.GetTriVertices(tid, P0, P1, P2); + + //if (TriCache == null) + //{ + // c = (1.0 / 3.0) * (P0 + P1 + P2); + // n = FMathd::FastNormalArea(P0, P1, P2, out a); + //} + //else + { + TriCache.GetTriInfo(tid, n, a, c); + } + + Order1 += a * n; + + FVector3d dcp = c - P; + Order2 += a * FMatrix3d(dcp, n); + + // this is just for return value... + double maxdist = FMath::Max3(P0.DistanceSquared(P), P1.DistanceSquared(P), P2.DistanceSquared(P)); + R = FMath::Max(R, sqrt(maxdist)); + } + } + + /** + * Evaluate first-order FWN approximation at point Q, relative to Center c + */ + double EvaluateOrder1Approx(const FVector3d& Center, const FVector3d& Order1Coeff, const FVector3d& Q) + { + FVector3d dpq = (Center - Q); + double len = dpq.Length(); + + return (1.0 / FMathd::FourPi) * Order1Coeff.Dot(dpq / (len * len * len)); + } + + /** + * Evaluate second-order FWN approximation at point Q, relative to Center c + */ + double EvaluateOrder2Approx(const FVector3d& Center, const FVector3d& Order1Coeff, const FMatrix3d& Order2Coeff, const FVector3d& Q) + { + FVector3d dpq = (Center - Q); + double len = dpq.Length(); + double len3 = len * len * len; + double fourPi_len3 = 1.0 / (FMathd::FourPi * len3); + + double Order1 = fourPi_len3 * Order1Coeff.Dot(dpq); + + // second-order hessian \grad^2(G) + double c = -3.0 / (FMathd::FourPi * len3 * len * len); + + // expanded-out version below avoids extra constructors + //FMatrix3d xqxq(dpq, dpq); + //FMatrix3d hessian(fourPi_len3, fourPi_len3, fourPi_len3) - c * xqxq; + FMatrix3d hessian( + fourPi_len3 + c * dpq.X * dpq.X, c * dpq.X * dpq.Y, c * dpq.X * dpq.Z, + c * dpq.Y * dpq.X, fourPi_len3 + c * dpq.Y * dpq.Y, c * dpq.Y * dpq.Z, + c * dpq.Z * dpq.X, c * dpq.Z * dpq.Y, fourPi_len3 + c * dpq.Z * dpq.Z); + + double Order2 = Order2Coeff.InnerProduct(hessian); + + return Order1 + Order2; + } + + // triangle-winding-number first-order approximation. + // T is triangle, P is 'Center' of cluster of dipoles, Q is evaluation point + // (This is really just for testing) + double Order1Approx(const FTriangle3d& T, const FVector3d& P, const FVector3d& XN, double XA, const FVector3d& Q) + { + FVector3d at0 = XA * XN; + + FVector3d dpq = (P - Q); + double len = dpq.Length(); + double len3 = len * len * len; + + return (1.0 / FMathd::FourPi) * at0.Dot(dpq / (len * len * len)); + } + + // triangle-winding-number second-order approximation + // T is triangle, P is 'Center' of cluster of dipoles, Q is evaluation point + // (This is really just for testing) + double Order2Approx(const FTriangle3d& T, const FVector3d& P, const FVector3d& XN, double XA, const FVector3d& Q) + { + FVector3d dpq = (P - Q); + + double len = dpq.Length(); + double len3 = len * len * len; + + // first-order approximation - integrated_normal_area * \grad(G) + double Order1 = (XA / FMathd::FourPi) * XN.Dot(dpq / len3); + + // second-order hessian \grad^2(G) + FMatrix3d xqxq(dpq, dpq); + xqxq *= 3.0 / (FMathd::FourPi * len3 * len * len); + double diag = 1 / (FMathd::FourPi * len3); + FMatrix3d hessian = FMatrix3d(diag, diag, diag) - xqxq; + + // second-order LHS - integrated second-order area matrix (formula 26) + FVector3d centroid( + (T.V[0].X + T.V[1].X + T.V[2].X) / 3.0, (T.V[0].Y + T.V[1].Y + T.V[2].Y) / 3.0, (T.V[0].Z + T.V[1].Z + T.V[2].Z) / 3.0); + FVector3d dcp = centroid - P; + FMatrix3d o2_lhs(dcp, XN); + double Order2 = XA * o2_lhs.InnerProduct(hessian); + + return Order1 + Order2; + } +} // namespace FastTriWinding + +/** + * Fast Mesh Winding Number computation + * + * TODO: this is tightly coupled to the internal guts of TMeshAABBTree3 (because it is ported from code where it *was* the guts of the AABBTree) + * we should probably at least try to decouple it some + */ +template +class TFastWindingTree +{ + TMeshAABBTree3* Tree; + + struct FWNInfo + { + FVector3d Center; + double R; + FVector3d Order1Vec; + FMatrix3d Order2Mat; + }; + + TMap FastWindingCache; + int FastWindingCacheTimestamp = -1; + +public: + /** + * FWN beta parameter - is 2.0 in paper + */ + double FWNBeta = 2.0; + + /** + * FWN approximation order. Must be 1 or 2. 2 is more accurate, obviously. + */ + int FWNApproxOrder = 2; + + TFastWindingTree(TMeshAABBTree3* TreeToRef) + { + SetTree(TreeToRef); + } + + void SetTree(TMeshAABBTree3* TreeToRef) + { + this->Tree = TreeToRef; + Build(true); + } + + void Build(bool bAlwaysBuildRegardlessOfTimestamp = true) + { + check(Tree); + if (Tree->MeshTimestamp != Tree->Mesh->GetShapeTimestamp()) + { + Tree->Build(); + } + if (bAlwaysBuildRegardlessOfTimestamp || FastWindingCacheTimestamp != Tree->MeshTimestamp) + { + build_fast_winding_cache(); + FastWindingCacheTimestamp = Tree->MeshTimestamp; + } + } + + /** + * Fast approximation of winding number using far-field approximations + */ + double FastWindingNumber(const FVector3d& P) + { + Build(false); + double sum = branch_fast_winding_num(Tree->RootIndex, P); + return sum; + } + +private: + // evaluate winding number contribution for all Triangles below IBox + double branch_fast_winding_num(int IBox, FVector3d P) + { + double branch_sum = 0; + + int idx = Tree->BoxToIndex[IBox]; + if (idx < Tree->TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = Tree->IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + FVector3d a, b, c; + int ti = Tree->IndexList[idx + i]; + Tree->Mesh->GetTriVertices(ti, a, b, c); + double angle = VectorUtil::TriSolidAngle(a, b, c, P); + branch_sum += angle / FMathd::FourPi; + } + } + else + { // internal node, either 1 or 2 child boxes + int iChild1 = Tree->IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + + // if we have winding cache, we can more efficiently compute contribution of all Triangles + // below this box. Otherwise, recursively descend Tree. + bool contained = Tree->box_contains(iChild1, P); + if (contained == false && can_use_fast_winding_cache(iChild1, P)) + { + branch_sum += evaluate_box_fast_winding_cache(iChild1, P); + } + else + { + branch_sum += branch_fast_winding_num(iChild1, P); + } + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = Tree->IndexList[idx + 1] - 1; + + bool contained1 = Tree->box_contains(iChild1, P); + if (contained1 == false && can_use_fast_winding_cache(iChild1, P)) + { + branch_sum += evaluate_box_fast_winding_cache(iChild1, P); + } + else + { + branch_sum += branch_fast_winding_num(iChild1, P); + } + + bool contained2 = Tree->box_contains(iChild2, P); + if (contained2 == false && can_use_fast_winding_cache(iChild2, P)) + { + branch_sum += evaluate_box_fast_winding_cache(iChild2, P); + } + else + { + branch_sum += branch_fast_winding_num(iChild2, P); + } + } + } + + return branch_sum; + } + + void build_fast_winding_cache() + { + // set this to a larger number to ignore caches if number of Triangles is too small. + // (seems to be no benefit to doing this...is holdover from Tree-decomposition FWN code) + int WINDING_CACHE_THRESH = 1; + + //FMeshTriInfoCache TriCache = null; + FMeshTriInfoCache TriCache = FMeshTriInfoCache::BuildTriInfoCache(*Tree->Mesh); + + FastWindingCache.Empty(); // = TMap(); + TOptional> root_hash; + build_fast_winding_cache(Tree->RootIndex, 0, WINDING_CACHE_THRESH, root_hash, TriCache); + } + int build_fast_winding_cache(int IBox, int Depth, int TriCountThresh, TOptional>& TriHash, const FMeshTriInfoCache& TriCache) + { + TriHash.Reset(); + + int idx = Tree->BoxToIndex[IBox]; + if (idx < Tree->TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = Tree->IndexList[idx]; + return num_tris; + } + else + { // internal node, either 1 or 2 child boxes + int iChild1 = Tree->IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + int num_child_tris = build_fast_winding_cache(iChild1, Depth + 1, TriCountThresh, TriHash, TriCache); + + // if count in child is large enough, we already built a cache at lower node + return num_child_tris; + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = Tree->IndexList[idx + 1] - 1; + + // let each child build its own cache if it wants. If so, it will return the + // list of its child tris + TOptional> child2_hash; + int num_tris_1 = build_fast_winding_cache(iChild1, Depth + 1, TriCountThresh, TriHash, TriCache); + int num_tris_2 = build_fast_winding_cache(iChild2, Depth + 1, TriCountThresh, child2_hash, TriCache); + bool build_cache = (num_tris_1 + num_tris_2 > TriCountThresh); + + if (Depth == 0) + { + return num_tris_1 + num_tris_2; // cannot build cache at level 0... + } + + // collect up the Triangles we need. there are various cases depending on what children already did + if (TriHash.IsSet() || child2_hash.IsSet() || build_cache) + { + if (!TriHash.IsSet() && child2_hash.IsSet()) + { + collect_triangles(iChild1, child2_hash.GetValue()); + TriHash = child2_hash; + } + else + { + if (!TriHash.IsSet()) + { + TriHash.Emplace(); + collect_triangles(iChild1, TriHash.GetValue()); + } + if (!child2_hash.IsSet()) + { + collect_triangles(iChild2, TriHash.GetValue()); + } + else + { + TriHash->Append(child2_hash.GetValue()); + } + } + } + if (build_cache) + { + check(TriHash.IsSet()); + make_box_fast_winding_cache(IBox, TriHash.GetValue(), TriCache); + } + + return (num_tris_1 + num_tris_2); + } + } + } + + // check if value is in cache and far enough away from Q that we can use cached value + bool can_use_fast_winding_cache(int IBox, const FVector3d& Q) + { + FWNInfo* cacheInfo = FastWindingCache.Find(IBox); + if (!cacheInfo) + { + return false; + } + + double dist_qp = cacheInfo->Center.Distance(Q); + if (dist_qp > FWNBeta * cacheInfo->R) + { + return true; + } + + return false; + } + + // compute FWN cache for all Triangles underneath this box + void make_box_fast_winding_cache(int IBox, const TSet& Triangles, const FMeshTriInfoCache& TriCache) + { + check(FastWindingCache.Find(IBox) == nullptr); + + // construct cache + FWNInfo cacheInfo; + FastTriWinding::ComputeCoeffs(*Tree->Mesh, Triangles, TriCache, cacheInfo.Center, cacheInfo.R, cacheInfo.Order1Vec, cacheInfo.Order2Mat); + + FastWindingCache.Add(IBox, cacheInfo); + } + + // evaluate the FWN cache for IBox + double evaluate_box_fast_winding_cache(int IBox, const FVector3d& Q) + { + const FWNInfo& cacheInfo = FastWindingCache[IBox]; + + if (FWNApproxOrder == 2) + { + return FastTriWinding::EvaluateOrder2Approx(cacheInfo.Center, cacheInfo.Order1Vec, cacheInfo.Order2Mat, Q); + } + else + { + return FastTriWinding::EvaluateOrder1Approx(cacheInfo.Center, cacheInfo.Order1Vec, Q); + } + } + + // collect all the Triangles below IBox in a hash + void collect_triangles(int IBox, TSet& Triangles) + { + int idx = Tree->BoxToIndex[IBox]; + if (idx < Tree->TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = Tree->IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + Triangles.Add(Tree->IndexList[idx + i]); + } + } + else + { + int iChild1 = Tree->IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + collect_triangles((-iChild1) - 1, Triangles); + } + else + { // 2 children, descend closest first + collect_triangles(iChild1 - 1, Triangles); + collect_triangles(Tree->IndexList[idx + 1] - 1, Triangles); + } + } + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h new file mode 100644 index 000000000000..0187242af30b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/GeometrySet3.h @@ -0,0 +1,100 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "BoxTypes.h" +#include "RayTypes.h" +#include "Polyline3.h" + +/** + * FGeometrySet3 stores a set of 3D Points and Polyline curves, + * and supports spatial queries against these sets. + * + * Since Points and Curves have no area to hit, hit-tests are done via nearest-point-on-ray. + */ +class GEOMETRICOBJECTS_API FGeometrySet3 +{ +public: + /** + * @param bPoints if true, discard all points + * @param bCurves if true, discard all polycurves + */ + void Reset(bool bPoints = true, bool bCurves = true); + + /** Add a point with given PointID at the given Position*/ + void AddPoint(int PointID, const FVector3d& Position); + /** Add a polycurve with given CurveID and the give Polyline */ + void AddCurve(int CurveID, const FPolyline3d& Polyline); + + /** Update the Position of previously-added PointID */ + void UpdatePoint(int PointID, const FVector3d& Position); + /** Update the Polyline of previously-added CurveID */ + void UpdateCurve(int CurveID, const FPolyline3d& Polyline); + + /** + * FNearest is returned by nearest-point queries + */ + struct GEOMETRICOBJECTS_API FNearest + { + /** ID of point or curve */ + int ID; + /** true for point, false for polyline curve*/ + bool bIsPoint; + + /** Nearest point on ray */ + FVector3d NearestRayPoint; + /** Nearest point on geometry (ie the point, or point on curve)*/ + FVector3d NearestGeoPoint; + + /** parameter of nearest point on ray (equivalent to NearestRayPoint) */ + double RayParam; + + /** if bIsPoint=false, index of nearest segment on polyline curve */ + int PolySegmentIdx; + /** if bIsPoint=false, parameter of NearestGeoPoint along segment defined by PolySegmentIdx */ + double PolySegmentParam; + }; + + + /** + * @param Ray query ray + * @param ResultOut populated with information about successful nearest point result + * @param PointWithinToleranceTest should return true if two 3D points are "close enough" to be considered a hit + * @return true if the nearest point on Ray to some point in the set passed the PointWithinToleranceTest. + * @warning PointWithinToleranceTest is called in parallel and hence must be thread-safe/re-entrant! + */ + bool FindNearestPointToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const; + + /** + * @param Ray query ray + * @param ResultOut populated with information about successful nearest point result + * @param PointWithinToleranceTest should return true if two 3D points are "close enough" to be considered a hit + * @return true if the nearest point on Ray to some curve in the set passed the PointWithinToleranceTest. + * @warning PointWithinToleranceTest is called in parallel and hence must be thread-safe/re-entrant! + */ + bool FindNearestCurveToRay(const FRay3d& Ray, FNearest& ResultOut, + TFunction PointWithinToleranceTest) const; + + +protected: + + struct FPoint + { + int ID; + FVector3d Position; + }; + TArray Points; + TMap PointIDToIndex; + + struct FCurve + { + int ID; + FPolyline3d Geometry; + + FAxisAlignedBox3d Bounds; + }; + TArray Curves; + TMap CurveIDToIndex; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/MeshAABBTree3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/MeshAABBTree3.h new file mode 100644 index 000000000000..480046595e20 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/MeshAABBTree3.h @@ -0,0 +1,921 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp TMeshAABBTree3 + +#pragma once + +#include "Util/DynamicVector.h" +#include "Intersection/IntrRay3AxisAlignedBox3.h" +#include "MeshQueries.h" +#include "Spatial/SpatialInterfaces.h" +#include "Distance/DistTriangle3Triangle3.h" + +template +class TFastWindingTree; + +template +class TMeshAABBTree3 : public IMeshSpatial +{ + friend class TFastWindingTree; + +protected: + TriangleMeshType* Mesh; + int MeshTimestamp; + int TopDownLeafMaxTriCount = 4; + +public: + static constexpr double DOUBLE_MAX = TNumericLimits::Max(); + + /** + * If non-null, only triangle IDs that pass this filter (ie filter is true) are considered + */ + TFunction TriangleFilterF = nullptr; + + TMeshAABBTree3() + { + Mesh = nullptr; + } + + TMeshAABBTree3(TriangleMeshType* SourceMesh, bool bAutoBuild = true) + { + SetMesh(SourceMesh, bAutoBuild); + } + + void SetMesh(TriangleMeshType* SourceMesh, bool bAutoBuild = true) + { + Mesh = SourceMesh; + if (bAutoBuild) + { + Build(); + } + } + + const TriangleMeshType* GetMesh() const + { + return Mesh; + } + + void Build() + { + BuildTopDown(false); + MeshTimestamp = Mesh->GetShapeTimestamp(); + } + + virtual bool SupportsNearestTriangle() override + { + return true; + } + + /** + * Find the triangle closest to P, and distance to it, within distance MaxDist, or return InvalidID + * Use MeshQueries.TriangleDistance() to get more information + */ + virtual int FindNearestTriangle(const FVector3d& P, double& NearestDistSqr, double MaxDist = TNumericLimits::Max()) override + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + + NearestDistSqr = (MaxDist < DOUBLE_MAX) ? MaxDist * MaxDist : DOUBLE_MAX; + int tNearID = IndexConstants::InvalidID; + find_nearest_tri(RootIndex, P, NearestDistSqr, tNearID); + return tNearID; + } + void find_nearest_tri(int IBox, const FVector3d& P, double& NearestDistSqr, int& TID) + { + int idx = BoxToIndex[IBox]; + if (idx < TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + int num_tris = IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + double fTriDistSqr = TMeshQueries::TriDistanceSqr(*Mesh, ti, P); + if (fTriDistSqr < NearestDistSqr) + { + NearestDistSqr = fTriDistSqr; + TID = ti; + } + } + } + else + { // internal node, either 1 or 2 child boxes + int iChild1 = IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + double fChild1DistSqr = BoxDistanceSqr(iChild1, P); + if (fChild1DistSqr <= NearestDistSqr) + { + find_nearest_tri(iChild1, P, NearestDistSqr, TID); + } + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = IndexList[idx + 1] - 1; + + double fChild1DistSqr = BoxDistanceSqr(iChild1, P); + double fChild2DistSqr = BoxDistanceSqr(iChild2, P); + if (fChild1DistSqr < fChild2DistSqr) + { + if (fChild1DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild1, P, NearestDistSqr, TID); + if (fChild2DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild2, P, NearestDistSqr, TID); + } + } + } + else + { + if (fChild2DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild2, P, NearestDistSqr, TID); + if (fChild1DistSqr < NearestDistSqr) + { + find_nearest_tri(iChild1, P, NearestDistSqr, TID); + } + } + } + } + } + } + + virtual bool SupportsTriangleRayIntersection() override + { + return true; + } + + virtual int FindNearestHitTriangle(const FRay3d& Ray, double MaxDist = TNumericLimits::Max()) override + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + // TODO: check( ray_is_normalized) + + // [RMS] note: using float.MaxValue here because we need to use <= to compare Box hit + // to NearestT, and Box hit returns double.MaxValue on no-hit. So, if we set + // nearestT to double.MaxValue, then we will test all boxes (!) + double NearestT = (MaxDist < TNumericLimits::Max()) ? MaxDist : TNumericLimits::Max(); + int tNearID = IndexConstants::InvalidID; + FindHitTriangle(RootIndex, Ray, NearestT, tNearID); + return tNearID; + } + + void FindHitTriangle(int IBox, const FRay3d& Ray, double& NearestT, int& TID) + { + int idx = BoxToIndex[IBox]; + if (idx < TrianglesEnd) + { // triangle-list case, array is [N t1 t2 ... tN] + FTriangle3d Triangle; + int num_tris = IndexList[idx]; + for (int i = 1; i <= num_tris; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + + Mesh->GetTriVertices(ti, Triangle.V[0], Triangle.V[1], Triangle.V[2]); + FIntrRay3Triangle3d Query = FIntrRay3Triangle3d(Ray, Triangle); + if (Query.Find()) + { + if (Query.RayParameter < NearestT) + { + NearestT = Query.RayParameter; + TID = ti; + } + } + } + } + else + { // internal node, either 1 or 2 child boxes + double e = FMathd::ZeroTolerance; + + int iChild1 = IndexList[idx]; + if (iChild1 < 0) + { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + double fChild1T = box_ray_intersect_t(iChild1, Ray); + if (fChild1T <= NearestT + e) + { + FindHitTriangle(iChild1, Ray, NearestT, TID); + } + } + else + { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = IndexList[idx + 1] - 1; + + double fChild1T = box_ray_intersect_t(iChild1, Ray); + double fChild2T = box_ray_intersect_t(iChild2, Ray); + if (fChild1T < fChild2T) + { + if (fChild1T <= NearestT + e) + { + FindHitTriangle(iChild1, Ray, NearestT, TID); + if (fChild2T <= NearestT + e) + { + FindHitTriangle(iChild2, Ray, NearestT, TID); + } + } + } + else + { + if (fChild2T <= NearestT + e) + { + FindHitTriangle(iChild2, Ray, NearestT, TID); + if (fChild1T <= NearestT + e) + { + FindHitTriangle(iChild1, Ray, NearestT, TID); + } + } + } + } + } + } + + /** + * Find nearest pair of triangles on this tree with otherTree, within max_dist. + * TransformF transforms vertices of otherTree into our coordinates. can be null. + * returns triangle-id pair (my_tri,other_tri), or Index2i::Invalid if not found within max_dist + * Use MeshQueries.TrianglesDistance() to get more information + */ + virtual FIndex2i FindNearestTriangles(TMeshAABBTree3& OtherTree, TFunction TransformF, double& Distance, double MaxDist = FMathd::MaxReal) + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + + double NearestSqr = FMathd::MaxReal; + if (MaxDist < FMathd::MaxReal) + { + NearestSqr = MaxDist * MaxDist; + } + FIndex2i NearestPair = FIndex2i::Invalid(); + + find_nearest_triangles(RootIndex, OtherTree, TransformF, OtherTree.RootIndex, 0, NearestSqr, NearestPair); + Distance = (NearestSqr < FMathd::MaxReal) ? FMathd::Sqrt(NearestSqr) : FMathd::MaxReal; + return NearestPair; + } + + + + virtual bool SupportsPointContainment() override + { + return false; + } + + virtual bool IsInside(const FVector3d& P) override + { + return false; + } + + class FTreeTraversal + { + public: + // return false to terminate this branch + // arguments are Box and Depth in tree + TFunction NextBoxF = [](const FAxisAlignedBox3d& Box, int Depth) { return true; }; + TFunction NextTriangleF = [](int TID) {}; + }; + + /** + * Hierarchically descend through the tree Nodes, calling the TreeTrversal functions at each level + */ + virtual void DoTraversal(FTreeTraversal& Traversal) + { + check(MeshTimestamp == Mesh->GetShapeTimestamp()); + TreeTraversalImpl(RootIndex, 0, Traversal); + } + + // Traversal implementation. you can override to customize this if necessary. + virtual void TreeTraversalImpl(int IBox, int Depth, FTreeTraversal& Traversal) + { + int idx = BoxToIndex[IBox]; + + if (idx < TrianglesEnd) + { + // triangle-list case, array is [N t1 t2 ... tN] + int n = IndexList[idx]; + for (int i = 1; i <= n; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + Traversal.NextTriangleF(ti); + } + } + else + { + int i0 = IndexList[idx]; + if (i0 < 0) + { + // negative index means we only have one 'child' Box to descend into + i0 = (-i0) - 1; + if (Traversal.NextBoxF(GetBox(i0), Depth + 1)) + { + TreeTraversalImpl(i0, Depth + 1, Traversal); + } + } + else + { + // positive index, two sequential child Box indices to descend into + i0 = i0 - 1; + if (Traversal.NextBoxF(GetBox(i0), Depth + 1)) + { + TreeTraversalImpl(i0, Depth + 1, Traversal); + } + int i1 = IndexList[idx + 1] - 1; + if (Traversal.NextBoxF(GetBox(i1), Depth + 1)) + { + TreeTraversalImpl(i1, Depth + 1, Traversal); + } + } + } + } + +protected: + // + // Internals - data structures, construction, etc + // + + FAxisAlignedBox3d GetBox(int IBox) const + { + const FVector3d& c = BoxCenters[IBox]; + const FVector3d& e = BoxExtents[IBox]; + FVector3d Min = c - e, Max = c + e; + return FAxisAlignedBox3d(Min, Max); + } + FAxisAlignedBox3d GetBox(int iBox, TFunction TransformF) + { + if (TransformF != nullptr) + { + FAxisAlignedBox3d box = GetBox(iBox); + return FAxisAlignedBox3d(box, TransformF); + } + else + { + return GetBox(iBox); + } + } + + FAxisAlignedBox3d GetBoxEps(int IBox, double Epsilon = FMathd::ZeroTolerance) const + { + const FVector3d& c = BoxCenters[IBox]; + FVector3d e = BoxExtents[IBox]; + e[0] += Epsilon; + e[1] += Epsilon; + e[2] += Epsilon; + FVector3d Min = c - e, Max = c + e; + return FAxisAlignedBox3d(Min, Max); + } + + const double BoxEps = 50.0 * FMathd::Epsilon; + + double BoxDistanceSqr(int IBox, const FVector3d& V) const + { + const FVector3d& c = BoxCenters[IBox]; + const FVector3d& e = BoxExtents[IBox]; + + // per-axis delta is max(abs(P-c) - e, 0)... ? + double dx = FMath::Max(fabs(V.X - c.X) - e.X, 0.0); + double dy = FMath::Max(fabs(V.Y - c.Y) - e.Y, 0.0); + double dz = FMath::Max(fabs(V.Z - c.Z) - e.Z, 0.0); + return dx * dx + dy * dy + dz * dz; + } + + bool box_contains(int IBox, const FVector3d& P) const + { + FAxisAlignedBox3d Box = GetBoxEps(IBox, BoxEps); + return Box.Contains(P); + } + + double box_ray_intersect_t(int IBox, const FRay3d& Ray) const + { + const FVector3d& c = BoxCenters[IBox]; + FVector3d e = BoxExtents[IBox] + BoxEps; + FAxisAlignedBox3d Box(c - e, c + e); + + double ray_t = TNumericLimits::Max(); + if (FIntrRay3AxisAlignedBox3d::FindIntersection(Ray, Box, ray_t)) + { + return ray_t; + } + else + { + return TNumericLimits::Max(); + } + } + + // storage for Box Nodes. + // - BoxToIndex is a pointer into IndexList + // - BoxCenters and BoxExtents are the Centers/extents of the bounding boxes + TDynamicVector BoxToIndex; + TDynamicVector BoxCenters; + TDynamicVector BoxExtents; + + // list of indices for a given Box. There is *no* marker/sentinel between + // boxes, you have to get the starting index from BoxToIndex[] + // + // There are three kinds of records: + // - if i < TrianglesEnd, then the list is a number of Triangles, + // stored as [N t1 t2 t3 ... tN] + // - if i > TrianglesEnd and IndexList[i] < 0, this is a single-child + // internal Box, with index (-IndexList[i])-1 (shift-by-one in case actual value is 0!) + // - if i > TrianglesEnd and IndexList[i] > 0, this is a two-child + // internal Box, with indices IndexList[i]-1 and IndexList[i+1]-1 + TDynamicVector IndexList; + + // IndexList[i] for i < TrianglesEnd is a triangle-index list, otherwise Box-index pair/single + int TrianglesEnd = -1; + + // BoxToIndex[RootIndex] is the root node of the tree + int RootIndex = -1; + + struct FBoxesSet + { + TDynamicVector BoxToIndex; + TDynamicVector BoxCenters; + TDynamicVector BoxExtents; + TDynamicVector IndexList; + int IBoxCur; + int IIndicesCur; + FBoxesSet() + { + IBoxCur = 0; + IIndicesCur = 0; + } + }; + + void BuildTopDown(bool bSorted) + { + // build list of valid Triangles & Centers. We skip any + // Triangles that have infinite/garbage vertices... + int i = 0; + TArray Triangles; + Triangles.SetNumUninitialized(Mesh->TriangleCount()); + TArray Centers; + Centers.SetNumUninitialized(Mesh->TriangleCount()); + for (int ti = 0; ti < Mesh->MaxTriangleID(); ti++) + { + if (!Mesh->IsTriangle(ti)) + { + continue; + } + FVector3d centroid = TMeshQueries::GetTriCentroid(*Mesh, ti); + double d2 = centroid.SquaredLength(); + bool bInvalid = FMathd::IsNaN(d2) || (FMathd::IsFinite(d2) == false); + check(bInvalid == false); + if (bInvalid == false) + { + Triangles[i] = ti; + Centers[i] = TMeshQueries::GetTriCentroid(*Mesh, ti); + i++; + } // otherwise skip this tri + } + + FBoxesSet Tris; + FBoxesSet Nodes; + FAxisAlignedBox3d rootBox; + int rootnode = + //(bSorted) ? split_tri_set_sorted(Triangles, Centers, 0, Mesh->TriangleCount, 0, TopDownLeafMaxTriCount, Tris, Nodes, out rootBox) : + SplitTriSetMidpoint(Triangles, Centers, 0, Mesh->TriangleCount(), 0, TopDownLeafMaxTriCount, Tris, Nodes, rootBox); + + BoxToIndex = Tris.BoxToIndex; + BoxCenters = Tris.BoxCenters; + BoxExtents = Tris.BoxExtents; + IndexList = Tris.IndexList; + TrianglesEnd = Tris.IIndicesCur; + int iIndexShift = TrianglesEnd; + int iBoxShift = Tris.IBoxCur; + + // ok now append internal node boxes & index ptrs + for (i = 0; i < Nodes.IBoxCur; ++i) + { + BoxCenters.InsertAt(Nodes.BoxCenters[i], iBoxShift + i); + BoxExtents.InsertAt(Nodes.BoxExtents[i], iBoxShift + i); + // internal node indices are shifted + BoxToIndex.InsertAt(iIndexShift + Nodes.BoxToIndex[i], iBoxShift + i); + } + + // now append index list + for (i = 0; i < Nodes.IIndicesCur; ++i) + { + int child_box = Nodes.IndexList[i]; + if (child_box < 0) + { // this is a Triangles Box + child_box = (-child_box) - 1; + } + else + { + child_box += iBoxShift; + } + child_box = child_box + 1; + IndexList.InsertAt(child_box, iIndexShift + i); + } + + RootIndex = rootnode + iBoxShift; + } + + int SplitTriSetMidpoint( + TArray& Triangles, + TArray& Centers, + int IStart, int ICount, int Depth, int MinTriCount, + FBoxesSet& Tris, FBoxesSet& Nodes, FAxisAlignedBox3d& Box) + { + Box = FAxisAlignedBox3d::Empty(); + int IBox = -1; + + if (ICount < MinTriCount) + { + // append new Triangles Box + IBox = Tris.IBoxCur++; + Tris.BoxToIndex.InsertAt(Tris.IIndicesCur, IBox); + + Tris.IndexList.InsertAt(ICount, Tris.IIndicesCur++); + for (int i = 0; i < ICount; ++i) + { + Tris.IndexList.InsertAt(Triangles[IStart + i], Tris.IIndicesCur++); + Box.Contain(TMeshQueries::GetTriBounds(*Mesh, Triangles[IStart + i])); + } + + Tris.BoxCenters.InsertAt(Box.Center(), IBox); + Tris.BoxExtents.InsertAt(Box.Extents(), IBox); + + return -(IBox + 1); + } + + //compute interval along an axis and find midpoint + int axis = Depth % 3; + FInterval1d interval = FInterval1d::Empty(); + for (int i = 0; i < ICount; ++i) + { + interval.Contain(Centers[IStart + i][axis]); + } + double midpoint = interval.Center(); + + int n0, n1; + if (interval.Length() > FMathd::ZeroTolerance) + { + // we have to re-sort the Centers & Triangles lists so that Centers < midpoint + // are first, so that we can recurse on the two subsets. We walk in from each side, + // until we find two out-of-order locations, then we swap them. + int l = 0; + int r = ICount - 1; + while (l < r) + { + // [RMS] is <= right here? if V.axis == midpoint, then this loop + // can get stuck unless one of these has an equality test. But + // I did not think enough about if this is the right thing to do... + while (Centers[IStart + l][axis] <= midpoint) + { + l++; + } + while (Centers[IStart + r][axis] > midpoint) + { + r--; + } + if (l >= r) + { + break; //done! + //swap + } + FVector3d tmpc = Centers[IStart + l]; + Centers[IStart + l] = Centers[IStart + r]; + Centers[IStart + r] = tmpc; + int tmpt = Triangles[IStart + l]; + Triangles[IStart + l] = Triangles[IStart + r]; + Triangles[IStart + r] = tmpt; + } + + n0 = l; + n1 = ICount - n0; + check(n0 >= 1 && n1 >= 1); + } + else + { + // interval is near-empty, so no point trying to do sorting, just split half and half + n0 = ICount / 2; + n1 = ICount - n0; + } + + // create child boxes + FAxisAlignedBox3d box1; + int child0 = SplitTriSetMidpoint(Triangles, Centers, IStart, n0, Depth + 1, MinTriCount, Tris, Nodes, Box); + int child1 = SplitTriSetMidpoint(Triangles, Centers, IStart + n0, n1, Depth + 1, MinTriCount, Tris, Nodes, box1); + Box.Contain(box1); + + // append new Box + IBox = Nodes.IBoxCur++; + Nodes.BoxToIndex.InsertAt(Nodes.IIndicesCur, IBox); + + Nodes.IndexList.InsertAt(child0, Nodes.IIndicesCur++); + Nodes.IndexList.InsertAt(child1, Nodes.IIndicesCur++); + + Nodes.BoxCenters.InsertAt(Box.Center(), IBox); + Nodes.BoxExtents.InsertAt(Box.Extents(), IBox); + + return IBox; + } + + + void find_nearest_triangles(int iBox, TMeshAABBTree3& otherTree, TFunction TransformF, int oBox, int depth, double &nearest_sqr, FIndex2i &nearest_pair) + { + int idx = BoxToIndex[iBox]; + int odx = otherTree.BoxToIndex[oBox]; + + if (idx < TrianglesEnd && odx < otherTree.TrianglesEnd) + { + // ok we are at triangles for both trees, do triangle-level testing + FTriangle3d tri, otri; + int num_tris = IndexList[idx], onum_tris = otherTree.IndexList[odx]; + + FDistTriangle3Triangle3d dist; + + // outer iteration is "other" tris that need to be transformed (more expensive) + for (int j = 1; j <= onum_tris; ++j) + { + int tj = otherTree.IndexList[odx + j]; + if (otherTree.TriangleFilterF != nullptr && otherTree.TriangleFilterF(tj) == false) + { + continue; + } + otherTree.Mesh->GetTriVertices(tj, otri.V[0], otri.V[1], otri.V[2]); + if (TransformF != nullptr) + { + otri.V[0] = TransformF(otri.V[0]); + otri.V[1] = TransformF(otri.V[1]); + otri.V[2] = TransformF(otri.V[2]); + } + dist.Triangle[0] = otri; + + // inner iteration over "our" triangles + for (int i = 1; i <= num_tris; ++i) + { + int ti = IndexList[idx + i]; + if (TriangleFilterF != nullptr && TriangleFilterF(ti) == false) + { + continue; + } + Mesh->GetTriVertices(ti, tri.V[0], tri.V[1], tri.V[2]); + dist.Triangle[1] = tri; + double dist_sqr = dist.GetSquared(); + if (dist_sqr < nearest_sqr) + { + nearest_sqr = dist_sqr; + nearest_pair = FIndex2i(ti, tj); + } + } + } + + return; + } + + // we either descend "our" tree or the other tree + // - if we have hit triangles on "our" tree, we have to descend other + // - if we hit triangles on "other", we have to descend ours + // - otherwise, we alternate at each depth. This produces wider + // branching but is significantly faster (~10x) for both hits and misses + bool bDescendOther = (idx < TrianglesEnd || depth % 2 == 0); + if (bDescendOther && odx < otherTree.TrianglesEnd) + { + bDescendOther = false; // can't + } + + if (bDescendOther) + { + // ok we reached triangles on our side but we need to still reach triangles on + // the other side, so we descend "their" children + FAxisAlignedBox3d bounds = GetBox(iBox); + + int oChild1 = otherTree.IndexList[odx]; + if (oChild1 < 0) // 1 child, descend if nearer than cur min-dist + { + oChild1 = (-oChild1) - 1; + FAxisAlignedBox3d oChild1Box = otherTree.GetBox(oChild1, TransformF); + if (oChild1Box.DistanceSquared(bounds) < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild1, depth + 1, nearest_sqr, nearest_pair); + } + } + else // 2 children + { + oChild1 = oChild1 - 1; + int oChild2 = otherTree.IndexList[odx + 1] - 1; + + FAxisAlignedBox3d oChild1Box = otherTree.GetBox(oChild1, TransformF); + FAxisAlignedBox3d oChild2Box = otherTree.GetBox(oChild2, TransformF); + + // descend closer box first + double d1Sqr = oChild1Box.DistanceSquared(bounds); + double d2Sqr = oChild2Box.DistanceSquared(bounds); + if (d2Sqr < d1Sqr) + { + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild2, depth + 1, nearest_sqr, nearest_pair); + } + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild1, depth + 1, nearest_sqr, nearest_pair); + } + } + else + { + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild1, depth + 1, nearest_sqr, nearest_pair); + } + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iBox, otherTree, TransformF, oChild2, depth + 1, nearest_sqr, nearest_pair); + } + } + } + } + else + { + // descend our tree nodes if they intersect w/ current bounds of other tree + FAxisAlignedBox3d oBounds = otherTree.GetBox(oBox, TransformF); + + int iChild1 = IndexList[idx]; + if (iChild1 < 0) // 1 child, descend if nearer than cur min-dist + { + iChild1 = (-iChild1) - 1; + if (box_box_distsqr(iChild1, oBounds) < nearest_sqr) + { + find_nearest_triangles(iChild1, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + } + else // 2 children + { + iChild1 = iChild1 - 1; + int iChild2 = IndexList[idx + 1] - 1; + + // descend closer box first + double d1Sqr = box_box_distsqr(iChild1, oBounds); + double d2Sqr = box_box_distsqr(iChild2, oBounds); + if (d2Sqr < d1Sqr) + { + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iChild2, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iChild1, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + } + else + { + if (d1Sqr < nearest_sqr) + { + find_nearest_triangles(iChild1, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + if (d2Sqr < nearest_sqr) + { + find_nearest_triangles(iChild2, otherTree, TransformF, oBox, depth + 1, nearest_sqr, nearest_pair); + } + } + } + } + } + + double box_box_distsqr(int iBox, const FAxisAlignedBox3d& testBox) + { + // [TODO] could compute this w/o constructing box + FAxisAlignedBox3d box = GetBoxEps(iBox, BoxEps); + return box.DistanceSquared(testBox); + } + + + +public: + // 1) make sure we can reach every tri in Mesh through tree (also demo of how to traverse tree...) + // 2) make sure that Triangles are contained in parent boxes + void TestCoverage() + { + TArray tri_counts; + tri_counts.SetNumZeroed(Mesh->MaxTriangleID()); + + TArray parent_indices; + parent_indices.SetNumZeroed(BoxToIndex.GetLength()); + + test_coverage(tri_counts, parent_indices, RootIndex); + + for (int ti = 0; ti < Mesh->MaxTriangleID(); ti++) + { + if (!Mesh->IsTriangle(ti)) + { + continue; + } + check(tri_counts[ti] == 1); + } + } + + /** + * Total sum of volumes of all boxes in the tree. Mainly useful to evaluate tree quality. + */ + double TotalVolume() + { + double volSum = 0; + FTreeTraversal t; + t.NextBoxF = [&](const FAxisAlignedBox3d& Box, int) + { + volSum += Box.Volume(); + return true; + }; + DoTraversal(t); + return volSum; + } + +private: + // accumulate triangle counts and track each Box-parent index. + // also checks that Triangles are contained in boxes + void test_coverage(TArray& tri_counts, TArray& parent_indices, int IBox) + { + int idx = BoxToIndex[IBox]; + + debug_check_child_tris_in_box(IBox); + + if (idx < TrianglesEnd) + { + // triangle-list case, array is [N t1 t2 ... tN] + int n = IndexList[idx]; + FAxisAlignedBox3d Box = GetBoxEps(IBox); + for (int i = 1; i <= n; ++i) + { + int ti = IndexList[idx + i]; + tri_counts[ti]++; + + FIndex3i tv = Mesh->GetTriangle(ti); + for (int j = 0; j < 3; ++j) + { + FVector3d V = Mesh->GetVertex(tv[j]); + check(Box.Contains(V)); + } + } + } + else + { + int i0 = IndexList[idx]; + if (i0 < 0) + { + // negative index means we only have one 'child' Box to descend into + i0 = (-i0) - 1; + parent_indices[i0] = IBox; + test_coverage(tri_counts, parent_indices, i0); + } + else + { + // positive index, two sequential child Box indices to descend into + i0 = i0 - 1; + parent_indices[i0] = IBox; + test_coverage(tri_counts, parent_indices, i0); + int i1 = IndexList[idx + 1]; + i1 = i1 - 1; + parent_indices[i1] = IBox; + test_coverage(tri_counts, parent_indices, i1); + } + } + } + // do full tree Traversal below IBox and make sure that all Triangles are further + // than Box-distance-sqr + void debug_check_child_tri_distances(int IBox, const FVector3d& P) + { + double fBoxDistSqr = BoxDistanceSqr(IBox, P); + + // [TODO] + FTreeTraversal t; + t.NextTriangleF = [&](int TID) + { + double fTriDistSqr = TMeshQueries::TriDistanceSqr(*Mesh, TID, P); + if (fTriDistSqr < fBoxDistSqr) + { + check(fabs(fTriDistSqr - fBoxDistSqr) <= FMathd::ZeroTolerance * 100); + } + }; + TreeTraversalImpl(IBox, 0, t); + } + + // do full tree Traversal below IBox to make sure that all child Triangles are contained + void debug_check_child_tris_in_box(int IBox) + { + FAxisAlignedBox3d Box = GetBoxEps(IBox); + FTreeTraversal t; + t.NextTriangleF = [&](int TID) + { + FIndex3i tv = Mesh->GetTriangle(TID); + for (int j = 0; j < 3; ++j) + { + FVector3d V = Mesh->GetVertex(tv[j]); + check(Box.Contains(V)); + } + }; + TreeTraversalImpl(IBox, 0, t); + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointHashGrid2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointHashGrid2.h new file mode 100644 index 000000000000..fc464e66ff1c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointHashGrid2.h @@ -0,0 +1,207 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp PointHashGrid2 + +#include "CoreMinimal.h" +#include "Misc/ScopeLock.h" +#include "Util/GridIndexing2.h" + +#pragma once + + +/** + * Hash Grid for values associated with 2D points. + * + * This class addresses the situation where you have a list of (point, point_data) and you + * would like to be able to do efficient proximity queries, ie find the nearest point_data + * for a given query point. + * + * We don't store copies of the 2D points. You provide a point_data type. This could just be the + * integer index into your list for example, a pointer to something more complex, etc. + * Insert and Remove functions require you to pass in the 2D point for the point_data. + * To Update a point you need to know it's old and new 2D coordinates. + * + * @todo also template on point realtype? + */ +template +class TPointHashGrid2d +{ +private: + TMultiMap Hash; + FCriticalSection CriticalSection; + FScaleGridIndexer2d Indexer; + PointDataType invalidValue; + +public: + + /** + * Construct 2D hashgrid + * @param cellSize size of grid cells + * @param invalidValue this value will be returned by queries if no valid result is found (eg bounded-distance query) + */ + TPointHashGrid2d(double cellSize, PointDataType InvalidValue) : Indexer(cellSize), invalidValue(InvalidValue) + { + } + + /** Invalid grid value */ + PointDataType InvalidValue() const + { + return invalidValue; + } + + /** + * Insert at given position. This function is thread-safe. + * @param Value the point/value to insert + * @param Position the position associated with this value + */ + void InsertPoint(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + { + FScopeLock Lock(&CriticalSection); + Hash.Add(idx, Value); + } + } + + /** + * Insert at given position, without locking / thread-safety + * @param Value the point/value to insert + * @param Position the position associated with this value + */ + void InsertPointUnsafe(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + Hash.Add(idx, Value); + } + + + /** + * Remove at given position. This function is thread-safe. + * @param Value the point/value to remove + * @param Position the position associated with this value + * @return true if the value existed at this position + */ + bool RemovePoint(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + { + FScopeLock Lock(&CriticalSection); + return Hash.RemoveSingle(idx, Value) > 0; + } + } + + /** + * Remove at given position, without locking / thread-safety + * @param Value the point/value to remove + * @param Position the position associated with this value + * @return true if the value existed at this position + */ + bool RemovePointUnsafe(const PointDataType& Value, const FVector2d& Position) + { + FVector2i idx = Indexer.ToGrid(Position); + return Hash.RemoveSingle(idx, Value) > 0; + } + + + /** + * Move value from old to new position. This function is thread-safe. + * @param Value the point/value to update + * @param OldPosition the current position associated with this value + * @param NewPosition the new position for this value + */ + void UpdatePoint(const PointDataType& Value, const FVector2d& OldPosition, const FVector2d& NewPosition) + { + FVector2i old_idx = Indexer.ToGrid(OldPosition); + FVector2i new_idx = Indexer.ToGrid(NewPosition); + if (old_idx == new_idx) + { + return; + } + bool bWasAtOldPos; + { + FScopeLock Lock(&CriticalSection); + bWasAtOldPos = Hash.RemoveSingle(old_idx, Value); + } + check(bWasAtOldPos); + { + FScopeLock Lock(&CriticalSection); + Hash.Add(new_idx, Value); + } + return; + } + + + /** + * Move value from old to new position, without locking / thread-safety + * @param Value the point/value to update + * @param OldPosition the current position associated with this value + * @param NewPosition the new position for this value + */ + void UpdatePointUnsafe(const PointDataType& Value, const FVector2d& OldPosition, const FVector2d& NewPosition) + { + FVector2i old_idx = Indexer.ToGrid(OldPosition); + FVector2i new_idx = Indexer.ToGrid(NewPosition); + if (old_idx == new_idx) + { + return; + } + bool bWasAtOldPos = Hash.RemoveSingle(old_idx, Value); + check(bWasAtOldPos); + Hash.Add(new_idx, Value); + return; + } + + + /** + * Find nearest point in grid, within a given sphere, without locking / thread-safety. + * @param QueryPoint the center of the query sphere + * @param Radius the radius of the query sphere + * @param DistanceFunc Function you provide which measures the distance between QueryPoint and a Value + * @param IgnoreFunc optional Function you may provide which will result in a Value being ignored if IgnoreFunc(Value) returns true + * @return the found pair (Value,DistanceFunc(Value)), or (InvalidValue,MaxDouble) if not found + */ + TPair FindNearestInRadius( + const FVector2d& QueryPoint, double Radius, + TFunction DistanceSqFunc, + TFunction IgnoreFunc = [](const PointDataType& data) { return false; }) const + { + if (!Hash.Num()) + { + return TPair(InvalidValue(), TNumericLimits::Max()); + } + + FVector2i min_idx = Indexer.ToGrid(QueryPoint - Radius * FVector2d::One()); + FVector2i max_idx = Indexer.ToGrid(QueryPoint + Radius * FVector2d::One()); + + double min_distsq = TNumericLimits::Max(); + PointDataType nearest = InvalidValue(); + double RadiusSquared = Radius * Radius; + + TArray Values; + for (int yi = min_idx.Y; yi <= max_idx.Y; yi++) + { + for (int xi = min_idx.X; xi <= max_idx.X; xi++) + { + FVector2i idx(xi, yi); + Values.Reset(); + Hash.MultiFind(idx, Values); + for (PointDataType Value : Values) + { + if (IgnoreFunc(Value)) + { + continue; + } + double distsq = DistanceSqFunc(Value); + if (distsq < RadiusSquared && distsq < min_distsq) + { + nearest = Value; + min_distsq = distsq; + } + } + } + } + + return TPair(nearest, min_distsq); + } +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointSetHashTable.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointSetHashTable.h new file mode 100644 index 000000000000..9688ac9b0df9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/PointSetHashTable.h @@ -0,0 +1,61 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp PointSetHashTable + +#pragma once + +#include "CoreMinimal.h" +#include "BoxTypes.h" +#include "PointSetAdapter.h" +#include "Spatial/SparseGrid3.h" +#include "Util/GridIndexing3.h" + +/** + * FPointSetHashTable builds a spatial data structure that supports efficient + * range queries on a point set (in FPointSetAdapterd form). The spatial data + * structure is currently a uniform sparse 3D grid. + * + * @todo support larger search radius than cellsize + */ +class GEOMETRICOBJECTS_API FPointSetHashtable +{ +protected: + typedef TArray PointList; + + /** Input point set */ + FPointSetAdapterd* Points; + /** Sparse grid, each voxel contains lists of contained points */ + TSparseGrid3 Grid; + /** index mapping object */ + FShiftGridIndexer3d GridIndexer; + + /** World origin of sparse grid */ + FVector3d Origin; + /** Cell size of grid */ + double CellSize; + +public: + FPointSetHashtable(FPointSetAdapterd* PointSetIn) + { + Points = PointSetIn; + } + + /** + * Construct the spatial data structure for the current point set + * @param CellSize the size of the cells/voxels in the grid. + * @param Origin World origin of the grid (not strictly necessary since grid is sparse, can set to origin for example) + */ + void Build(double CellSize, const FVector3d& Origin); + + /** + * Find all points within given query distance from query point. + * Note that in current implementation the distance must be less than the CellSize used in construction, + * ie at most the directly adjacent neighbours next to the cell containing QueryPt can be searched + * @param QueryPt center of search sphere/ball + * @param QueryRadius radius of search sphere/ball. Points within this distance of QueryPt are returned. + * @param ResultOut indices of discovered points are stored in this list + * @return true on success + */ + bool FindPointsInBall(const FVector3d& QueryPt, double QueryRadius, TArray& ResultOut); + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SparseGrid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SparseGrid3.h new file mode 100644 index 000000000000..3707c7dbbf21 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SparseGrid3.h @@ -0,0 +1,147 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp DSparseGrid3 + +#pragma once + +#include "CoreMinimal.h" +#include "BoxTypes.h" + +/** + * Dynamic sparse 3D grid. Useful in cases where we have grid of some type of non-trivial + * object and we don't want to pre-allocate full grid of them. So we allocate on-demand. + * This can be used to implement multi-grid schemes, eg for example the ElemType + * could be sub-grid of fixed dimension. + * + * @todo support pooling/re-use of allocated elements? currently not required by any use cases. + * @todo port AllocatedIndices() and Allocated() iterators + */ +template +class TSparseGrid3 +{ +protected: + /** sparse grid of allocated elements */ + TMap Elements; + /** accumulated bounds of all indices inserted into Elements. Not currently used internally. */ + FAxisAlignedBox3i Bounds; + +public: + + /** + * Create empty grid + */ + TSparseGrid3() + { + Bounds = FAxisAlignedBox3i::Empty(); + } + + /** + * Deletes all grid elements + */ + ~TSparseGrid3() + { + FreeAll(); + } + + + /** + * @param Index an integer grid index + * @return true if there is an allocated element at this index + */ + bool Has(const FVector3i& Index) const + { + return Elements.Contains(Index); + } + + + /** + * Get the grid element at this index, and optionally allocate it if it doesn't exist + * @param Index integer grid index + * @param bAllocateIfMissing if the element at this index is null, allocate a new one + * @return pointer to ElemType instance, or nullptr if element doesn't exist + */ + ElemType* Get(const FVector3i& Index, bool bAllocateIfMissing = true) + { + ElemType* result = Elements.FindRef(Index); + if (result != nullptr) + { + return result; + } + if (bAllocateIfMissing == true) + { + return Allocate(Index); + } + return nullptr; + } + + /** + * Delete an element in the grid + * @param Index integer grid index + * @return true if the element existed, false if it did not + */ + bool Free(const FVector3i& Index) + { + if (Elements.Contains(Index)) + { + delete Elements[Index]; + Elements.Remove(Index); + return true; + } + return false; + } + + /** + * Delete all elements in the grid + */ + void FreeAll() + { + for (auto pair : Elements) + { + delete pair.Value; + } + Elements.Reset(); + } + + /** + * @return number of allocated elements in the grid + */ + int GetCount() const + { + return Elements.Num(); + } + + /** + * @return ratio of allocated elements to total number of possible cells for current bounds + */ + float GetDensity() const + { + return (float)Elements.Num() / (float)Bounds.Volume(); + } + + /** + * @return integer range of valid grid indices [min,max] (inclusive) + */ + FAxisAlignedBox3i GetBoundsInclusive() const + { + return Bounds; + } + + /** + * @return dimensions of grid along each axis + */ + FVector3i GetDimensions() const + { + return Bounds.Diagonal() + FVector3i::One(); + } + + +protected: + + ElemType* Allocate(const FVector3i& index) + { + ElemType* NewElem = new ElemType(); + Elements.Add(index, NewElem); + Bounds.Contain(index); + return NewElem; + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SpatialInterfaces.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SpatialInterfaces.h new file mode 100644 index 000000000000..caf532cc59c5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Spatial/SpatialInterfaces.h @@ -0,0 +1,133 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp SpatialInterfaces + +#pragma once + +#include "VectorTypes.h" +#include "RayTypes.h" + + +/** + * ISpatial is a base interface for spatial queries + */ +class ISpatial +{ +public: + virtual ~ISpatial() {} + + /** + * @return true if this object supports point-containment inside/outside queries + */ + virtual bool SupportsPointContainment() = 0; + + /** + * @return true if the query point is inside the object + */ + virtual bool IsInside(const FVector3d & Point) = 0; +}; + + + +/** + * IMeshSpatial is an extension of ISpatial specifically for meshes + */ +class IMeshSpatial : public ISpatial +{ +public: + virtual ~IMeshSpatial() {} + + /** + * @return true if this object supports nearest-triangle queries + */ + virtual bool SupportsNearestTriangle() = 0; + + /** + * @param Query point + * @param NearestDistSqrOut returned nearest squared distance, if triangle is found + * @param MaxDistance maximum search distance + * @return ID of triangle nearest to Point within MaxDistance, or InvalidID if not found + */ + virtual int FindNearestTriangle(const FVector3d& Point, double& NearestDistSqrOut, double MaxDistance = TNumericLimits::Max()) = 0; + + + /** + * @return true if this object supports ray-triangle intersection queries + */ + virtual bool SupportsTriangleRayIntersection() = 0; + + /** + * @param Ray query ray + * @param MaxDistance maximum hit distance + * @return ID of triangle intersected by ray within MaxDistance, or InvalidID if not found + */ + virtual int FindNearestHitTriangle(const FRay3d& Ray, double MaxDistance = TNumericLimits::Max()) = 0; +}; + + + +/** + * IProjectionTarget is an object that supports projecting a 3D point onto it + */ +class IProjectionTarget +{ +public: + virtual ~IProjectionTarget() {} + + /** + * @param Point the point to project onto the target + * @param Identifier client-defined integer identifier of the point (may not be used) + * @return position of Point projected onto the target + */ + virtual FVector3d Project(const FVector3d& Point, int Identifier = -1) = 0; +}; + + + +/** + * IOrientedProjectionTarget is a projection target that can return a normal in addition to the projected point + */ +class IOrientedProjectionTarget : public IProjectionTarget +{ +public: + virtual ~IOrientedProjectionTarget() {} + + /** + * @param Point the point to project onto the target + * @param Identifier client-defined integer identifier of the point (may not be used) + * @return position of Point projected onto the target + */ + virtual FVector3d Project(const FVector3d& Point, int Identifier = -1) override = 0; + + /** + * @param Point the point to project onto the target + * @param ProjectNormalOut the normal at the projection point + * @param Identifier client-defined integer identifier of the point (may not be used) + * @return position of Point projected onto the target + */ + virtual FVector3d Project(const FVector3d& Point, FVector3d& ProjectNormalOut, int Identifier = -1) = 0; +}; + + + +/** + * IIntersectionTarget is an object that can be intersected with a ray + */ +class IIntersectionTarget +{ +public: + virtual ~IIntersectionTarget() {} + + /** + * @return true if RayIntersect will return a normal + */ + virtual bool HasNormal() = 0; + + /** + * @param Ray query ray + * @param HitOut returned hit point + * @param HitNormalOut returned hit point normal + * @return true if ray hit the object + */ + virtual bool RayIntersect(const FRay3d& Ray, FVector3d& HitOut, FVector3d& HitNormalOut) = 0; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/TriangleTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/TriangleTypes.h new file mode 100644 index 000000000000..d665f3eadfc3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/TriangleTypes.h @@ -0,0 +1,174 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of WildMagic DistPoint3TTriangle3 + +#pragma once + +#include "VectorTypes.h" +#include "VectorUtil.h" + +/** + * Triangle utility functions + */ +namespace TriangleUtil +{ + /** + * @return the edge length of an equilateral/regular triangle with the given area + */ + template + RealType EquilateralEdgeLengthForArea(RealType TriArea) + { + return TMathUtil::Sqrt(((RealType)4 * TriArea) / TMathUtil::Sqrt3); + } + +}; + + + + +template +struct TTriangle2 +{ + FVector2 V[3]; + + TTriangle2() {} + + TTriangle2(const FVector2& V0, const FVector2& V1, const FVector2& V2) + { + V[0] = V0; + V[1] = V1; + V[2] = V2; + } + + TTriangle2(const FVector2 VIn[3]) + { + V[0] = VIn[0]; + V[1] = VIn[1]; + V[2] = VIn[2]; + } + + FVector2 BarycentricPoint(RealType Bary0, RealType Bary1, RealType Bary2) const + { + return Bary0 * V[0] + Bary1 * V[1] + Bary2 * V[2]; + } + + FVector2 BarycentricPoint(const FVector3& BaryCoords) const + { + return BaryCoords[0] * V[0] + BaryCoords[1] * V[1] + BaryCoords[2] * V[2]; + } + + + /** + * @param A first vertex of triangle + * @param B second vertex of triangle + * @param C third vertex of triangle + * @return signed area of triangle + */ + static RealType SignedArea(const FVector2& A, const FVector2& B, const FVector2& C) + { + return ((RealType)0.5) * ((A.X*B.Y - A.Y*B.X) + (B.X*C.Y - B.Y*C.X) + (C.X*A.Y - C.Y*A.X)); + } + + /** @return signed area of triangle */ + RealType SignedArea() const + { + return SignedArea(V[0], V[1], V[2]); + } + + + /** + * @param A first vertex of triangle + * @param B second vertex of triangle + * @param C third vertex of triangle + * @param QueryPoint test point + * @return true if QueryPoint is inside triangle + */ + static bool IsInside(const FVector2& A, const FVector2& B, const FVector2& C, const FVector2& QueryPoint) + { + RealType Sign1 = FVector2::Orient(A, B, QueryPoint); + RealType Sign2 = FVector2::Orient(B, C, QueryPoint); + RealType Sign3 = FVector2::Orient(C, A, QueryPoint); + return (Sign1*Sign2 > 0) && (Sign2*Sign3 > 0) && (Sign3*Sign1 > 0); + } + + /** @return true if QueryPoint is inside triangle */ + bool IsInside(const FVector2& QueryPoint) const + { + return IsInside(V[0], V[1], V[2], QueryPoint); + } + + +}; + +typedef TTriangle2 FTriangle2f; +typedef TTriangle2 FTriangle2d; +typedef TTriangle2 FTriangle2i; + + + + + + +template +struct TTriangle3 +{ + FVector3 V[3]; + + TTriangle3() {} + + TTriangle3(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + V[0] = V0; + V[1] = V1; + V[2] = V2; + } + + TTriangle3(const FVector3 VIn[3]) + { + V[0] = VIn[0]; + V[1] = VIn[1]; + V[2] = VIn[2]; + } + + FVector3 BarycentricPoint(RealType Bary0, RealType Bary1, RealType Bary2) const + { + return Bary0*V[0] + Bary1*V[1] + Bary2*V[2]; + } + + FVector3 BarycentricPoint(const FVector3 & BaryCoords) const + { + return BaryCoords[0]*V[0] + BaryCoords[1]*V[1] + BaryCoords[2]*V[2]; + } + + + /** @return vector that is perpendicular to the plane of this triangle */ + FVector3 Normal() const + { + return VectorUtil::Normal(V[0], V[1], V[2]); + } + + /** @return centroid of this triangle */ + FVector3 Centroid() const + { + constexpr RealType f = 1.0 / 3.0; + return FVector3( + (V[0].X + V[1].X + V[2].X) * f, + (V[0].Y + V[1].Y + V[2].Y) * f, + (V[0].Z + V[1].Z + V[2].Z) * f + ); + } + + /** grow the triangle around the centroid */ + void Expand(RealType Delta) + { + FVector3 Centroid(Centroid()); + V[0] += Delta * ((V[0] - Centroid).Normalized()); + V[1] += Delta * ((V[1] - Centroid).Normalized()); + V[2] += Delta * ((V[2] - Centroid).Normalized()); + } +}; + +typedef TTriangle3 FTriangle3f; +typedef TTriangle3 FTriangle3d; +typedef TTriangle3 FTriangle3i; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/BufferUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/BufferUtil.h new file mode 100644 index 000000000000..041328b42a32 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/BufferUtil.h @@ -0,0 +1,79 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +/** + * Utility functions for processing arrays of objects + * @todo possibly these functions should go in Algo:: or somewhere else in Core? + */ +namespace BufferUtil +{ + + + /** + * Count number of elements in array (or within requested range) that pass Predicate test + * @param Data Array to process + * @param Predicate filtering function, return true indicates valid + * @param MaxIndex optional maximum index in array (exclusive, default is to use array size) + * @param StartIndex optional start index in array (default 0) + * @return number of values in array that returned true for Predicate + */ + template + int CountValid(const TArray& Data, const TFunction& Predicate, int MaxIndex = -1, int StartIndex = 0) + { + int StopIndex = (MaxIndex == -1) ? Data.Num() : MaxIndex; + int NumValid = 0; + for (int i = StartIndex; i < StopIndex; ++i) + { + if (Predicate(Data[i]) == true) + { + NumValid++; + } + } + return NumValid; + } + + + /** + * Removes elements of array (or within requested range) that do not pass Predicate, by shifting forward. + * Does not remove remaining elements + * @param Data Array to process + * @param Predicate filtering function, return true indicates valid + * @param MaxIndex optional maximum index in array (exclusive, default is to use array size) + * @param StartIndex optional start index in array (default 0) + * @return Number of valid elements in Data after filtering + */ + template + int FilterInPlace(TArray& Data, const TFunction& Predicate, int MaxIndex = -1, int StartIndex = 0) + { + int StopIndex = (MaxIndex == -1) ? Data.Num() : MaxIndex; + int StoreIndex = StartIndex; + for (int i = StartIndex; i < StopIndex; ++i) + { + if (Predicate(Data[i]) == true) + { + Data[StoreIndex++] = Data[i]; + } + } + return StoreIndex; + } + + + + /** + * Append enumerable elements to an array + * @param Data array to append to + * @param Enumerable object that can be iterated over with a range-based for loop + */ + template + void AppendElements(TArray& AppendTo, EnumerableType Enumerable) + { + for ( T value : Enumerable ) + { + AppendTo.Add(value); + } + } + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/DynamicVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/DynamicVector.h new file mode 100644 index 000000000000..28480429224e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/DynamicVector.h @@ -0,0 +1,498 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp dvector + +#pragma once + +#include +#include "Containers/StaticArray.h" +#include "VectorTypes.h" +#include "IndexTypes.h" + + +/* + * Blocked array with fixed, power-of-two sized blocks. + * + * Iterator functions suitable for use with range-based for are provided + */ +template +class TDynamicVector +{ +public: + TDynamicVector() + { + CurBlock = 0; + CurBlockUsed = 0; + Blocks.Emplace(); + } + + TDynamicVector(const TDynamicVector& Copy) + { + *this = Copy; + } + + TDynamicVector(TDynamicVector&& Moved) + { + *this = MoveTemp(Moved); + } + + ~TDynamicVector() {} + + const TDynamicVector& operator=(const TDynamicVector& Copy); + const TDynamicVector& operator=(TDynamicVector&& Moved); + + inline void Clear(); + inline void Fill(const Type& Value); + inline void Resize(size_t Count); + inline void Resize(size_t Count, const Type& InitValue); + inline void SetNum(size_t Count) { Resize(Count); } + + inline bool IsEmpty() const { return CurBlock == 0 && CurBlockUsed == 0; } + inline size_t GetLength() const { return CurBlock * BlockSize + CurBlockUsed; } + inline size_t Num() const { return CurBlock * BlockSize + CurBlockUsed; } + inline int GetBlockSize() const { return BlockSize; } + inline size_t GetByteCount() const { return Blocks.Num() * BlockSize * sizeof(Type); } + + inline void Add(const Type& Data); + inline void Add(const TDynamicVector& Data); + inline void PopBack(); + + inline void InsertAt(const Type& Data, unsigned int Index); + + inline const Type& Front() const { return Blocks[0][0]; } + inline const Type& Back() const { return Blocks[CurBlock][CurBlockUsed - 1]; } + + inline Type& operator[](unsigned int Index) + { + return Blocks[Index >> nShiftBits][Index & BlockIndexBitmask]; + } + inline const Type& operator[](unsigned int Index) const + { + return Blocks[Index >> nShiftBits][Index & BlockIndexBitmask]; + } + + // apply f() to each member sequentially + template + void Apply(const Func& f); + + +protected: + // [RMS] BlockSize must be a power-of-two, so we can use bit-shifts in operator[] + static constexpr int BlockSize = 2048; // (1 << 11) + static constexpr int nShiftBits = 11; + static constexpr int BlockIndexBitmask = 2047; // low 11 bits + + unsigned int CurBlock; + unsigned int CurBlockUsed; + + using BlockType = TStaticArray; + TArray Blocks; + + friend class FIterator; + + +public: + /* + * FIterator class iterates over values of vector + */ + class FIterator + { + public: + inline const Type& operator*() const + { + return (*DVector)[Idx]; + } + inline Type& operator*() + { + return (*DVector)[Idx]; + } + inline FIterator& operator++() // prefix + { + Idx++; + return *this; + } + inline FIterator operator++(int) // postfix + { + FIterator Copy(*this); + Idx++; + return Copy; + } + inline bool operator==(const FIterator& Itr2) + { + return DVector == Itr2.DVector && Idx == Itr2.Idx; + } + inline bool operator!=(const FIterator& Itr2) + { + return DVector != Itr2.DVector || Idx != Itr2.Idx; + } + + protected: + TDynamicVector* DVector; + int Idx; + inline FIterator(TDynamicVector* Parent, int ICur) + { + DVector = Parent; + Idx = ICur; + } + friend class TDynamicVector; + }; + + /** @return iterator at beginning of vector */ + FIterator begin() + { + return IsEmpty() ? end() : FIterator(this, 0); + } + /** @return iterator at end of vector */ + FIterator end() + { + return FIterator(this, (int)GetLength()); + } +}; + + + + + + +template +class TDynamicVectorN +{ +public: + TDynamicVectorN() + { + } + TDynamicVectorN(const TDynamicVectorN& Copy) + : Data(Copy.Data) + { + } + TDynamicVectorN(TDynamicVectorN&& Moved) + : Data(MoveTemp(Moved.Data)) + { + } + ~TDynamicVectorN() + { + } + + const TDynamicVectorN& operator=(const TDynamicVectorN& Copy) + { + Data = Copy.Data; + return *this; + } + const TDynamicVectorN& operator=(TDynamicVectorN&& Moved) + { + Data = MoveTemp(Moved.Data); + return *this; + } + + inline void Clear() + { + Data.Clear(); + } + inline void Fill(const Type& Value) + { + Data.Fill(Value); + } + inline void Resize(size_t Count) + { + Data.Resize(Count * N); + } + inline void Resize(size_t Count, const Type& InitValue) + { + Data.Resize(Count * N, InitValue); + } + + inline bool IsEmpty() const + { + return Data.IsEmpty(); + } + inline size_t GetLength() const + { + return Data.GetLength() / N; + } + inline int GetBlockSize() const + { + return Data.GetBlockSize(); + } + inline size_t GetByteCount() const + { + return Data.GetByteCount(); + } + + // simple struct to help pass N-dimensional data without presuming a vector type (e.g. just via initializer list) + struct ElementVectorN + { + Type Data[N]; + }; + + inline void Add(const ElementVectorN& AddData) + { + // todo specialize for N=2,3,4 + for (int i = 0; i < N; i++) + { + Data.Add(AddData.Data[i]); + } + } + + inline void PopBack() + { + for (int i = 0; i < N; i++) + { + PopBack(); + } + } // TODO specialize + + inline void InsertAt(const ElementVectorN& AddData, unsigned int Index) + { + for (int i = 1; i <= N; i++) + { + Data.InsertAt(AddData.Data[N - i], N * (Index + 1) - i); + } + } + + inline Type& operator()(unsigned int TopIndex, unsigned int SubIndex) + { + return Data[TopIndex * N + SubIndex]; + } + inline const Type& operator()(unsigned int TopIndex, unsigned int SubIndex) const + { + return Data[TopIndex * N + SubIndex]; + } + inline void SetVector2(unsigned int TopIndex, const FVector2& V) + { + check(N >= 2); + unsigned int i = TopIndex * N; + Data[i] = V.X; + Data[i + 1] = V.Y; + } + inline void SetVector3(unsigned int TopIndex, const FVector3& V) + { + check(N >= 3); + unsigned int i = TopIndex * N; + Data[i] = V.X; + Data[i + 1] = V.Y; + Data[i + 2] = V.Z; + } + inline FVector2 AsVector2(unsigned int TopIndex) const + { + check(N >= 2); + return FVector2( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1]); + } + inline FVector3 AsVector3(unsigned int TopIndex) const + { + check(N >= 3); + return FVector3( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1], + Data[TopIndex * N + 2]); + } + inline FIndex2i AsIndex2(unsigned int TopIndex) const + { + check(N >= 2); + return FIndex2i( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1]); + } + inline FIndex3i AsIndex3(unsigned int TopIndex) const + { + check(N >= 3); + return FIndex3i( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1], + Data[TopIndex * N + 2]); + } + inline FIndex4i AsIndex4(unsigned int TopIndex) const + { + check(N >= 4); + return FIndex4i( + Data[TopIndex * N + 0], + Data[TopIndex * N + 1], + Data[TopIndex * N + 2], + Data[TopIndex * N + 3]); + } + +protected: + TDynamicVector Data; + + friend class FIterator; +}; + +template class TDynamicVectorN; + +typedef TDynamicVectorN TDynamicVector3f; +typedef TDynamicVectorN TDynamicVector2f; +typedef TDynamicVectorN TDynamicVector3d; +typedef TDynamicVectorN TDynamicVector2d; +typedef TDynamicVectorN TDynamicVector3i; +typedef TDynamicVectorN TDynamicVector2i; + + + + + +template +const TDynamicVector& TDynamicVector::operator=(const TDynamicVector& Copy) +{ + Blocks = Copy.Blocks; + CurBlock = Copy.CurBlock; + CurBlockUsed = Copy.CurBlockUsed; + return *this; +} + +template +const TDynamicVector& TDynamicVector::operator=(TDynamicVector&& Moved) +{ + Blocks = MoveTemp(Moved.Blocks); + CurBlock = Moved.CurBlock; + CurBlockUsed = Moved.CurBlockUsed; + return *this; +} + +template +void TDynamicVector::Clear() +{ + Blocks.Empty(); + CurBlock = 0; + CurBlockUsed = 0; + Blocks.Add(BlockType()); +} + +template +void TDynamicVector::Fill(const Type& Value) +{ + size_t Count = Blocks.Num(); + for (unsigned int i = 0; i < Count; ++i) + { + for (uint32 ElementIndex = 0; ElementIndex < BlockSize; ++ElementIndex) + { + Blocks[i][ElementIndex] = Value; + } + } +} + +template +void TDynamicVector::Resize(size_t Count) +{ + if (GetLength() == Count) + { + return; + } + + // figure out how many segments we need + int nNumSegs = 1 + (int)Count / BlockSize; + + // figure out how many are currently allocated... + size_t nCurCount = Blocks.Num(); + + // resize to right number of segments + if (nNumSegs >= Blocks.Num()) + { + // allocate new segments + for (int i = (int)nCurCount; i < nNumSegs; ++i) + { + Blocks.Emplace(); + } + } + else + { + //Blocks.RemoveRange(nNumSegs, Blocks.Count - nNumSegs); + Blocks.SetNum(nNumSegs); + } + + // mark last segment + CurBlockUsed = (unsigned int)(Count - (nNumSegs - 1) * BlockSize); + CurBlock = nNumSegs - 1; +} + +template +void TDynamicVector::Resize(size_t Count, const Type& InitValue) +{ + size_t nCurSize = GetLength(); + Resize(Count); + for (size_t Index = nCurSize; Index < Count; ++Index) + { + this->operator[](Index) = InitValue; + } +} + +template +void TDynamicVector::Add(const Type& Value) +{ + if (CurBlockUsed == BlockSize) + { + if (CurBlock == Blocks.Num() - 1) + { + Blocks.Emplace(); + } + CurBlock++; + CurBlockUsed = 0; + } + Blocks[CurBlock][CurBlockUsed] = Value; + CurBlockUsed++; +} + + +template +void TDynamicVector::Add(const TDynamicVector& AddData) +{ + // @todo it could be more efficient to use memcopies here... + size_t nSize = AddData.Num(); + for (unsigned int k = 0; k < nSize; ++k) + { + Add(AddData[k]); + } +} + +template +void TDynamicVector::PopBack() +{ + if (CurBlockUsed > 0) + { + CurBlockUsed--; + } + if (CurBlockUsed == 0 && CurBlock > 0) + { + CurBlock--; + CurBlockUsed = BlockSize; + // remove block ?? + } +} + +template +void TDynamicVector::InsertAt(const Type& AddData, unsigned int Index) +{ + size_t s = GetLength(); + if (Index == s) + { + Add(AddData); + } + else if (Index > s) + { + Resize(Index); + Add(AddData); + } + else + { + (*this)[Index] = AddData; + } +} + +template +template +void TDynamicVector::Apply(const Func& applyF) +{ + for (int bi = 0; bi < CurBlock; ++bi) + { + auto block = Blocks[bi]; + for (int k = 0; k < BlockSize; ++k) + { + applyF(block[k], k); + } + } + auto lastblock = Blocks[CurBlock]; + for (int k = 0; k < CurBlockUsed; ++k) + { + applyF(lastblock[k], k); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing2.h new file mode 100644 index 000000000000..b63bafde3424 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing2.h @@ -0,0 +1,86 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp GridIndexing2 + +#pragma once + +#include "VectorTypes.h" + + +/** + * Convert between integer grid coordinates and scaled real-valued coordinates (ie assumes integer grid origin == real origin) + */ +template +struct TScaleGridIndexer2 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + + TScaleGridIndexer2(RealType CellSize) : CellSize(CellSize) + { + ensure(CellSize >= TMathUtil::ZeroTolerance); + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector2i ToGrid(const FVector2& P) const + { + return FVector2i(int(P.X / CellSize), int(P.Y / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector2 FromGrid(const FVector2i& GridPoint) const + { + return FVector2(GridPoint.X*CellSize, GridPoint.Y*CellSize); + } +}; +typedef TScaleGridIndexer2 FScaleGridIndexer2f; +typedef TScaleGridIndexer2 FScaleGridIndexer2d; + + +/** + * Convert between integer grid coordinates and scaled+translated real-valued coordinates + */ +template +struct TShiftGridIndexer2 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + /** Real-valued origin of grid, position of integer grid origin */ + FVector2 Origin; + + TShiftGridIndexer2(const FVector2& origin, RealType cellSize) + { + Origin = origin; + CellSize = cellSize; + ensure(CellSize >= TMathUtil::ZeroTolerance); + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector2i ToGrid(const FVector2& point) const + { + return FVector2i( + (int)((point.X - Origin.X) / CellSize), + (int)((point.Y - Origin.Y) / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector2 FromGrid(const FVector2i& gridpoint) const + { + return FVector2( + ((RealType)gridpoint.X * CellSize) + Origin.X, + ((RealType)gridpoint.Y * CellSize) + Origin.Y); + } + + /** Convert real-valued grid coordinates to real-valued point */ + inline FVector2 FromGrid(const FVector2& gridpointf) const + { + return FVector2( + ((RealType)gridpointf.X * CellSize) + Origin.X, + ((RealType)gridpointf.Y * CellSize) + Origin.Y); + } +}; +typedef TShiftGridIndexer2 FShiftGridIndexer2f; +typedef TShiftGridIndexer2 FShiftGridIndexer2d; + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing3.h new file mode 100644 index 000000000000..cd139c06984c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/GridIndexing3.h @@ -0,0 +1,96 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp GridIndexing3 + +#pragma once + +#include "VectorTypes.h" + + +/** + * Convert between integer grid coordinates and scaled real-valued coordinates (ie assumes integer grid origin == real origin) + */ +template +struct TScaleGridIndexer3 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + + TScaleGridIndexer3() : CellSize((RealType)1) + { + } + + TScaleGridIndexer3(RealType CellSize) : CellSize(CellSize) + { + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector3i ToGrid(const FVector3& P) const + { + return FVector3i((int)(P.X / CellSize), (int)(P.Y / CellSize), (int)(P.Z / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector3 FromGrid(const FVector3i& GridPoint) const + { + return FVector3(GridPoint.X*CellSize, GridPoint.Y*CellSize, GridPoint.Z*CellSize); + } +}; +typedef TScaleGridIndexer3 FScaleGridIndexer3f; +typedef TScaleGridIndexer3 FScaleGridIndexer3d; + + + +/** + * Convert between integer grid coordinates and scaled+translated real-valued coordinates + */ +template +struct TShiftGridIndexer3 +{ + /** Real-valued size of an integer grid cell */ + RealType CellSize; + /** Real-valued origin of grid, position of integer grid origin */ + FVector3 Origin; + + TShiftGridIndexer3() + : CellSize((RealType)1), Origin(FVector3::Zero()) + { + } + + TShiftGridIndexer3(const FVector3& origin, RealType cellSize) + : CellSize(cellSize), Origin(origin) + { + } + + /** Convert real-valued point to integer grid coordinates */ + inline FVector3i ToGrid(const FVector3& point) const + { + return FVector3i( + (int)((point.X - Origin.X) / CellSize), + (int)((point.Y - Origin.Y) / CellSize), + (int)((point.Z - Origin.Z) / CellSize)); + } + + /** Convert integer grid coordinates to real-valued point */ + inline FVector3 FromGrid(const FVector3i& gridpoint) const + { + return FVector3( + ((RealType)gridpoint.X * CellSize) + Origin.X, + ((RealType)gridpoint.Y * CellSize) + Origin.Y, + ((RealType)gridpoint.Z * CellSize) + Origin.Z); + } + + /** Convert real-valued grid coordinates to real-valued point */ + inline FVector3 FromGrid(const FVector3& gridpointf) const + { + return FVector3d( + ((RealType)gridpointf.X * CellSize) + Origin.X, + ((RealType)gridpointf.Y * CellSize) + Origin.Y, + ((RealType)gridpointf.Z * CellSize) + Origin.Z); + } +}; +typedef TShiftGridIndexer3 FShiftGridIndexer3f; +typedef TShiftGridIndexer3 FShiftGridIndexer3d; + + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexPriorityQueue.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexPriorityQueue.h new file mode 100644 index 000000000000..e6c243b5d29c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexPriorityQueue.h @@ -0,0 +1,389 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "Util/DynamicVector.h" + + +/** + * This is a min-heap priority queue class that does not use an object for each queue node. + * Integer IDs must be provided by the user to identify unique nodes. + * Internally an array is used to keep track of the mapping from ids to internal indices, so the max ID must also be provided. + * + * @todo based on C# code where TDynamicVector could not return a reference to internal element. In C++ we can and code could be updated to be more efficient in many places. + * @todo id_to_index could be sparse in many situations... + */ +class FIndexPriorityQueue +{ +public: + /** set this to true during development to catch issues */ + bool EnableDebugChecks = false; + + struct FQueueNode + { + int id; // external id + + float priority; // the priority of this id + int index; // index in tree data structure (tree is stored as flat array) + }; + + /** tree of allocated nodes, stored linearly. active up to num_nodes (allocated may be larger) */ + TDynamicVector nodes; + /** count of active nodes */ + int num_nodes; + /** mapping from external ids to internal node indices */ + TArray id_to_index; + + + /** + * This constructor is provided for convenience, you must call Initialize() + */ + FIndexPriorityQueue() + { + Initialize(0); + } + + /** + * Calls Initialize() + * @param maxID maximum external ID that will be passed to any public functions + */ + FIndexPriorityQueue(int maxID) + { + Initialize(maxID); + } + + + /** + * Initialize internal data structures. Internally a fixed-size array is used to track mapping + * from IDs to internal node indices, so maxID must be provided up-front. + * If this seems problematic or inefficient, this is not the Priority Queue for you. + * @param MaxNodeID maximum external ID that will be passed to any public functions + */ + void Initialize(int MaxNodeID) + { + nodes.Clear(); + id_to_index.Init(-1, MaxNodeID); + num_nodes = 0; + } + + + /** + * Reset the queue to empty state. + * if bFreeMemory is false, we don't discard internal data structures, so there will be less allocation next time + */ + void Clear(bool bFreeMemory = true) + { + check(bFreeMemory); // not sure exactly how to implement bFreeMemory=false w/ classes below... + nodes.Clear(); + id_to_index.Init(-1, id_to_index.Num()); + num_nodes = 0; + } + + /** @return current size of queue */ + int GetCount() const + { + return num_nodes; + } + + /** @return id of node at head of queue */ + int GetFirstNodeID() const + { + return nodes[1].id; + } + + /** @return id of node at head of queue */ + float GetFirstNodePriority() const + { + return nodes[1].priority; + } + + /** @return true if id is already in queue */ + bool Contains(int NodeID) const + { + int NodeIndex = id_to_index[NodeID]; + if (NodeIndex <= 0 || NodeIndex > num_nodes) + return false; + return nodes[NodeIndex].index > 0; + } + + + /** + * Add id to list w/ given priority. Do not call with same id twice! + */ + void Insert(int NodeID, float priority) + { + if (EnableDebugChecks) + { + check(Contains(NodeID) == false); + } + + FQueueNode node; + node.id = NodeID; + node.priority = priority; + num_nodes++; + node.index = num_nodes; + id_to_index[NodeID] = node.index; + nodes.InsertAt(node, num_nodes); + move_up(nodes[num_nodes].index); + } + + /** + * Remove node at head of queue, update queue, and return id for that node + * @return ID of node at head of queue + */ + int Dequeue() + { + if (EnableDebugChecks) + { + check(GetCount() > 0); + } + + int NodeID = nodes[1].id; + remove_at_index(1); + return NodeID; + } + + /** + * Remove node associated with given ID from queue. Behavior is undefined if you call w/ id that is not in queue + */ + void Remove(int NodeID) + { + if (EnableDebugChecks) + { + check(Contains(NodeID) == true); + } + + int NodeIndex = id_to_index[NodeID]; + remove_at_index(NodeIndex); + } + + + /** + * Update priority at node id, and then move it to correct position in queue. Behavior is undefined if you call w/ id that is not in queue + */ + void Update(int NodeID, float Priority) + { + if (EnableDebugChecks) + { + check(Contains(NodeID) == true); + } + + int NodeIndex = id_to_index[NodeID]; + + FQueueNode n = nodes[NodeIndex]; + n.priority = Priority; + nodes[NodeIndex] = n; + + on_node_updated(NodeIndex); + } + + + /** + * Query the priority at node id, assuming it exists in queue. Behavior is undefined if you call w/ id that is not in queue + * @return priority for node + */ + float GetPriority(int id) + { + if (EnableDebugChecks) + { + check(Contains(id) == true); + } + + int iNode = id_to_index[id]; + return nodes[iNode].priority; + } + + + /* + * Internals + */ +private: + + /** remove node at index and update tree */ + void remove_at_index(int NodeIndex) + { + // node-is-last-node case + if (NodeIndex == num_nodes) + { + nodes[NodeIndex] = FQueueNode(); + num_nodes--; + return; + } + + // [RMS] is there a better way to do this? seems random to move the last node to + // top of tree? But I guess otherwise we might have to shift entire branches?? + + //Swap the node with the last node + swap_nodes_at_indices(NodeIndex, num_nodes); + // after swap, NodeIndex is the one we want to keep, and numNodes is the one we discard + nodes[num_nodes] = FQueueNode(); + num_nodes--; + + //Now shift iNode (ie the former last node) up or down as appropriate + on_node_updated(NodeIndex); + } + + + /** swap two nodes in the true */ + void swap_nodes_at_indices(int i1, int i2) + { + FQueueNode n1 = nodes[i1]; + n1.index = i2; + FQueueNode n2 = nodes[i2]; + n2.index = i1; + nodes[i1] = n2; + nodes[i2] = n1; + + id_to_index[n2.id] = i1; + id_to_index[n1.id] = i2; + } + + /** move node at iFrom to iTo */ + void move(int iFrom, int iTo) + { + FQueueNode n = nodes[iFrom]; + n.index = iTo; + nodes[iTo] = n; + id_to_index[n.id] = iTo; + } + + /** set node at iTo */ + void set(int iTo, FQueueNode& n) + { + n.index = iTo; + nodes[iTo] = n; + id_to_index[n.id] = iTo; + } + + + /** move iNode up tree to correct position by iteratively swapping w/ parent */ + void move_up(int iNode) + { + // save start node, we will move this one to correct position in tree + int iStart = iNode; + FQueueNode iStartNode = nodes[iStart]; + + // while our priority is lower than parent, we swap upwards, ie move parent down + int iParent = iNode / 2; + while (iParent >= 1) + { + if (nodes[iParent].priority < iStartNode.priority) + { + break; + } + move(iParent, iNode); + iNode = iParent; + iParent = nodes[iNode].index / 2; + } + + // write input node into final position, if we moved it + if (iNode != iStart) + { + set(iNode, iStartNode); + } + } + + + /** move iNode down tree branches to correct position, by iteratively swapping w/ children */ + void move_down(int iNode) + { + // save start node, we will move this one to correct position in tree + int iStart = iNode; + FQueueNode iStartNode = nodes[iStart]; + + // keep moving down until lower nodes have higher priority + while (true) + { + int iMoveTo = iNode; + int iLeftChild = 2 * iNode; + + // past end of tree, must be in the right spot + if (iLeftChild > num_nodes) + { + break; + } + + // check if priority is larger than either child - if so we want to swap + float min_priority = iStartNode.priority; + float left_child_priority = nodes[iLeftChild].priority; + if (left_child_priority < min_priority) + { + iMoveTo = iLeftChild; + min_priority = left_child_priority; + } + int iRightChild = iLeftChild + 1; + if (iRightChild <= num_nodes) + { + if (nodes[iRightChild].priority < min_priority) + { + iMoveTo = iRightChild; + } + } + + // if we found node with higher priority, swap with it (ie move it up) and take its place + // (but we only write start node to final position, not intermediary slots) + if (iMoveTo != iNode) + { + move(iMoveTo, iNode); + iNode = iMoveTo; + } + else + { + break; + } + } + + // if we moved node, write it to its new position + if (iNode != iStart) + { + set(iNode, iStartNode); + } + } + + + /** call this after node is modified, to move it to correct position in queue */ + void on_node_updated(int iNode) + { + int iParent = iNode / 2; + if (iParent > 0 && has_higher_priority(iNode, iParent)) + move_up(iNode); + else + move_down(iNode); + } + + + /** @return true if priority at iHigher is less than at iLower */ + bool has_higher_priority(int iHigher, int iLower) const + { + return (nodes[iHigher].priority < nodes[iLower].priority); + } + + + + +public: + /** + * Check if node ordering is correct (for debugging/testing) + */ + bool IsValidQueue() const + { + for (int i = 1; i < num_nodes; i++) { + int childLeftIndex = 2 * i; + if (childLeftIndex < num_nodes && has_higher_priority(childLeftIndex, i)) + return false; + + int childRightIndex = childLeftIndex + 1; + if (childRightIndex < num_nodes && has_higher_priority(childRightIndex, i)) + return false; + } + return true; + } + + //void DebugPrint() { + // for (int i = 1; i <= num_nodes; ++i) + // WriteLine("{0} : p {1} index {2} id {3}", i, nodes[i].priority, nodes[i].index, nodes[i].id); + //} + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexUtil.h new file mode 100644 index 000000000000..3685166aa8a0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IndexUtil.h @@ -0,0 +1,284 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp index_util.h + +#pragma once + +#include "Util/DynamicVector.h" +#include "IndexTypes.h" +#include "VectorTypes.h" + + +namespace IndexUtil +{ + + + /** + * @return true if [a0,a1] and [b0,b1] are the same pair, ignoring order + */ + template + inline bool SamePairUnordered(T a0, T a1, T b0, T b1) + { + return (a0 == b0) ? + (a1 == b1) : + (a0 == b1 && a1 == b0); + } + + + /** + * @return the vertex that is the same in both EdgeVerts1 and EdgeVerts2, or InvalidID if not found + */ + inline int FindSharedEdgeVertex(const FIndex2i & EdgeVerts1, const FIndex2i & EdgeVerts2) + { + if (EdgeVerts1.A == EdgeVerts2.A) return EdgeVerts1.A; + else if (EdgeVerts1.A == EdgeVerts2.B) return EdgeVerts1.A; + else if (EdgeVerts1.B == EdgeVerts2.A) return EdgeVerts1.B; + else if (EdgeVerts1.B == EdgeVerts2.B) return EdgeVerts1.B; + else return IndexConstants::InvalidID; + } + + /** + * @return the vertex in the pair ev that is not v, or InvalidID if not found + */ + inline int FindEdgeOtherVertex(const FIndex2i & EdgeVerts, int VertexID) + { + if (EdgeVerts.A == VertexID) return EdgeVerts.B; + else if (EdgeVerts.B == VertexID) return EdgeVerts.A; + else return IndexConstants::InvalidID; + } + + + /** + * @return index 0/1/2 of VertexID in TriangleVerts, or InvalidID if not found + */ + template + inline int FindTriIndex(T VertexID, const Vec & TriangleVerts) + { + if (TriangleVerts[0] == VertexID) return 0; + if (TriangleVerts[1] == VertexID) return 1; + if (TriangleVerts[2] == VertexID) return 2; + return IndexConstants::InvalidID; + } + + /** + * Find unordered edge [VertexID1,VertexID2] in TriangleVerts + * @return index in range 0-2, or InvalidID if not found + */ + template + inline int FindEdgeIndexInTri(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[0], TriangleVerts[1])) return 0; + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[1], TriangleVerts[2])) return 1; + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[2], TriangleVerts[0])) return 2; + return IndexConstants::InvalidID; + } + + + /** + * Find ordered edge [VertexID1,VertexID2] in TriangleVerts + * @return index in range 0-2, or InvalidID if not found + */ + template + inline int FindTriOrderedEdge(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + if (TriangleVerts[0] == VertexID1 && TriangleVerts[1] == VertexID2) return 0; + if (TriangleVerts[1] == VertexID1 && TriangleVerts[2] == VertexID2) return 1; + if (TriangleVerts[2] == VertexID1 && TriangleVerts[0] == VertexID2) return 2; + return IndexConstants::InvalidID; + } + + + /** + * Find ordered edge [VertexID1,VertexID2] in TriangleVerts and then return the remaining third vertex + * @return vertex id of other vertex, or InvalidID if not found + */ + template + inline int FindTriOtherVtx(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[j], TriangleVerts[(j + 1) % 3])) + { + return TriangleVerts[(j + 2) % 3]; + } + } + return IndexConstants::InvalidID; + } + + /** + * Find ordered edge [VertexID1,VertexID2] in a triangle that is in an array of triangles, and return remaining third vertex + * @param VertexID1 first vertex of edge + * @param VertexID2 second vertex of edge + * @param TriIndexArray array of triangle tuples + * @param TriangleIndex which triangle in array to search + * @return vertex id of other vertex, or InvalidID if not found + */ + template + inline int FindTriOtherVtx(T VertexID1, T VertexID2, const TDynamicVector & TriIndexArray, T TriangleIndex) + { + int i = 3 * TriangleIndex; + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(VertexID1, VertexID2, TriIndexArray[i + j], TriIndexArray[i + ((j+1)%3)])) + { + return TriIndexArray[i + ((j + 2) % 3)]; + } + } + return IndexConstants::InvalidID; + } + + + + /** + * Find ordered edge [VertexID1,VertexID2] in TriangleVerts and then return the index of the remaining third vertex + * @return index of third vertex, or InvalidID if not found + */ + template + inline int FindTriOtherIndex(T VertexID1, T VertexID2, const Vec & TriangleVerts) + { + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(VertexID1, VertexID2, TriangleVerts[j], TriangleVerts[(j + 1) % 3])) + { + return (j + 2) % 3; + } + } + return IndexConstants::InvalidID; + } + + + /** + * If i0 and i1 are unordered indices into a triangle, each in range 0-2, return the third index + */ + inline int GetOtherTriIndex(int i0, int i1) + { + // @todo can we do this with a formula? I don't see it right now. + static const int values[4] = { 0, 2, 1, 0 }; + return values[i0+i1]; + } + + + /** + * Assuming [Vertex1,Vertex2] is an unordered edge in TriangleVerts, return Vertex1 and Vertex2 in the correct order (ie the same as TriangleVerts) + * @warning result is garbage if either vertex is not an edge in TriangleVerts + * @return true if order was swapped + */ + template + inline bool OrientTriEdge(T & Vertex1, T & Vertex2, const Vec & TriangleVerts) + { + if (Vertex1 == TriangleVerts[0]) + { + if (TriangleVerts[2] == Vertex2) + { + T Temp = Vertex1; Vertex1 = Vertex2; Vertex2 = Temp; + return true; + } + } + else if (Vertex1 == TriangleVerts[1]) + { + if (TriangleVerts[0] == Vertex2) + { + T Temp = Vertex1; Vertex1 = Vertex2; Vertex2 = Temp; + return true; + } + } + else if (Vertex1 == TriangleVerts[2]) + { + if (TriangleVerts[1] == Vertex2) + { + T Temp = Vertex1; Vertex1 = Vertex2; Vertex2 = Temp; + return true; + } + } + return false; + } + + + /** + * Assuming [Vertex1,Vertex2] is an unordered edge in TriangleVerts, return Vertex1 and Vertex2 in the correct order (ie the same as TriangleVerts), + * and returns the vertex ID of the other vertex + * @return ID of third vertex, or InvalidID if the edge was not found + */ + template + inline int OrientTriEdgeAndFindOtherVtx(T & Vertex1, T & Vertex2, const Vec & TriangleVerts) + { + for (int j = 0; j < 3; ++j) + { + if (SamePairUnordered(Vertex1, Vertex2, TriangleVerts[j], TriangleVerts[(j + 1) % 3])) + { + Vertex1 = TriangleVerts[j]; + Vertex2 = TriangleVerts[(j + 1) % 3]; + return TriangleVerts[(j + 2) % 3]; + } + } + return IndexConstants::InvalidID; + } + + + /** + * Replace Val with MapFunc[Val] using index operator + */ + template + void ApplyMap(FIndex3i & Val, Func MapFunc) + { + Val[0] = MapFunc[Val[0]]; + Val[1] = MapFunc[Val[1]]; + Val[2] = MapFunc[Val[2]]; + } + + /** + * Replace Val with MapFunc[Val] using index operator + */ + template + void ApplyMap(FVector3 & Val, Func MapFunc) + { + Val[0] = MapFunc[Val[0]]; + Val[1] = MapFunc[Val[1]]; + Val[2] = MapFunc[Val[2]]; + } + + + /** + * @return MapFunc[Val] using index operator + */ + template + FIndex3i ApplyMap(const FIndex3i & Val, Func MapFunc) + { + return FIndex3i(MapFunc[Val[0]], MapFunc[Val[1]], MapFunc[Val[2]]); + } + + + /** + * @return MapFunc[Val] using index operator + */ + template + FVector3 ApplyMap(const FVector3 & Val, Func MapFunc) + { + return FVector3(MapFunc[Val[0]], MapFunc[Val[1]], MapFunc[Val[2]]); + } + + + + /** + * all permutations of (+-1, +-1, +-1), can be used to iterate over connected face/edge/corner neighbours of a grid cell + */ + extern GEOMETRICOBJECTS_API const FVector3i GridOffsets26[26]; + + /** + * Corner vertices of box faces - see FOrientedBox3.GetCorner for points associated w/ indexing + */ + extern GEOMETRICOBJECTS_API const int BoxFaces[6][4]; + + /** + * Corner unit-UV ordering of box faces - {0,0}, {1,0}, {1,1}, {0,1} + */ + extern GEOMETRICOBJECTS_API const FVector2i BoxFacesUV[4]; + + /** + * Box Face Normal Axes associated with BoxFaces. Use Sign(BoxFaceNormals[i]) * Box.Axis( Abs(BoxFaceNormals[i])-1 ) to get vector + */ + extern GEOMETRICOBJECTS_API const int BoxFaceNormals[6]; + + + // @todo other index array constants +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IteratorUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IteratorUtil.h new file mode 100644 index 000000000000..6b3306adb9e4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/IteratorUtil.h @@ -0,0 +1,243 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp iterator_util.h + +#pragma once + + +/** + * Wrapper around an object of type IteratorT that provides STL + * iterator-like semantics, that converts from the iteration type + * (FromType) to a new type (ToType). + * + * Conversion is done via a provided mapping function + */ +template +class MappedIterator +{ + using MapFunctionT = TFunction; + +public: + inline MappedIterator() { } + + inline bool operator==(const MappedIterator& Other) const + { + return Cur == Other.Cur; + } + inline bool operator!=(const MappedIterator& Other) const + { + return Cur != Other.Cur; + } + + inline ToType operator*() const + { + return MapFunction(*Cur); + } + + inline const MappedIterator& operator++() // prefix + { + Cur++; + return *this; + } + + inline MappedIterator(const IteratorT& CurItr, const MapFunctionT& MapFunctionIn) + { + Cur = CurItr; + MapFunction = MapFunctionIn; + } + + IteratorT Cur; + MapFunctionT MapFunction; +}; + + + + + + + +/** + * Wrapper around an existing iterator that skips over + * values for which the filter_func returns false. + */ +template +class FilteredIterator +{ + using FilterFunctionT = TFunction; + +public: + inline FilteredIterator() { } + + inline bool operator==(const FilteredIterator& Other) const + { + return Cur == Other.Cur; + } + inline bool operator!=(const FilteredIterator& Other) const + { + return Cur != Other.Cur; + } + + inline ValueType operator*() const + { + return *Cur; + } + + inline const FilteredIterator& operator++() // prefix + { + GotoNextElement(); + return *this; + } + + inline void GotoNextElement() + { + do { + Cur++; + } while (Cur != End && FilterFunc(*Cur) == false); + } + + inline FilteredIterator(const IteratorT& CurItr, const IteratorT& EndItr, const FilterFunctionT& FilterFuncIn) + { + Cur = CurItr; + End = EndItr; + this->FilterFunc = FilterFuncIn; + if (FilterFunc(*Cur) == false) + { + GotoNextElement(); + } + } + + IteratorT Cur; + IteratorT End; + FilterFunctionT FilterFunc; +}; + + + + + + + + + + +/** + * Wrapper around existing iterator that returns multiple values, of potentially + * different type, for each value that input iterator returns. + * + * This is done via an "expansion" function that takes an int reference which + * indicates "where" we are in the expansion (eg like a state machine). + * How you use this value is up to you. + * + * When the input is -1, you should interpret this as the "beginning" of + * handling the input value (ie we have not returned any values yet for + * this input value) + * + * When you are "done" with an input value, set the outgoing int reference to -1 + * and the base iterator will be incremented. + * + * If you have more values to return for this input value, set it to some positive + * number of your choosing. + * + * See FDynamicMesh3::VtxTrianglesItr for an example + */ +template +class ExpandIterator +{ + using ExpandFunctionT = TFunction; + +public: + inline ExpandIterator() { } + + inline bool operator==(const ExpandIterator& Other) const + { + return Cur == Other.Cur; + } + inline bool operator!=(const ExpandIterator & Other) const + { + return Cur != Other.Cur; + } + + inline OutputType operator*() const + { + return CurValue; + } + + inline const ExpandIterator& operator++() // prefix + { + goto_next(); + return *this; + } + + inline void goto_next() + { + while (Cur != End) + { + CurValue = ExpandFunc(*Cur, CurExpandI); + if (CurExpandI == -1) + { + ++Cur; // done with this base value + } + else + { + break; // want caller to see current output value + } + } + } + + inline ExpandIterator(const InputIteratorT& CurItr, const InputIteratorT& EndItr, const ExpandFunctionT& ExpandFuncIn) + { + Cur = CurItr; + End = EndItr; + ExpandFunc = ExpandFuncIn; + CurExpandI = -1; + goto_next(); + } + + InputIteratorT Cur; + InputIteratorT End; + OutputType CurValue; + int CurExpandI; + ExpandFunctionT ExpandFunc; +}; + + + +/** + * Generic "enumerable" object that provides begin/end semantics for an ExpandIterator suitable for use with range-based for. + * You can either provide begin/end iterators, or another "enumerable" object that has begin()/end() functions. + */ +template +class ExpandEnumerable +{ + using ExpandFunctionT = TFunction; + using ExpandIteratorT = ExpandIterator; + +public: + ExpandFunctionT ExpandFunc; + InputIteratorT BeginItr, EndItr; + + ExpandEnumerable(const InputIteratorT& BeginIn, const InputIteratorT& EndIn, ExpandFunctionT ExpandFuncIn) + { + this->BeginItr = BeginIn; + this->EndItr = EndIn; + this->ExpandFunc = ExpandFuncIn; + } + + template + ExpandEnumerable(const IteratorSource& Source, ExpandFunctionT ExpandFuncIn) + { + this->BeginItr = Source.begin(); + this->EndItr = Source.end(); + this->ExpandFunc = ExpandFuncIn; + } + + ExpandIteratorT begin() + { + return ExpandIteratorT(BeginItr, EndItr, ExpandFunc); + } + + ExpandIteratorT end() + { + return ExpandIteratorT(EndItr, EndItr, ExpandFunc); + } +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/MeshCaches.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/MeshCaches.h new file mode 100644 index 000000000000..de71ee2d78f4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/MeshCaches.h @@ -0,0 +1,40 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicVector.h" + +#include "Async/ParallelFor.h" + +/* + * Basic cache of per-triangle information for a mesh + */ +struct FMeshTriInfoCache +{ + TDynamicVector Centroids; + TDynamicVector Normals; + TDynamicVector Areas; + + void GetTriInfo(int TriangleID, FVector3d& NormalOut, double& AreaOut, FVector3d& CentroidOut) const + { + NormalOut = Normals[TriangleID]; + AreaOut = Areas[TriangleID]; + CentroidOut = Centroids[TriangleID]; + } + + template + static FMeshTriInfoCache BuildTriInfoCache(const TriangleMeshType& Mesh) + { + FMeshTriInfoCache Cache; + int NT = Mesh.TriangleCount(); + Cache.Centroids.Resize(NT); + Cache.Normals.Resize(NT); + Cache.Areas.Resize(NT); + + ParallelFor(NT, [&](int TriIdx) + { + TMeshQueries::GetTriNormalAreaCentroid(Mesh, TriIdx, Cache.Normals[TriIdx], Cache.Areas[TriIdx], Cache.Centroids[TriIdx]); + }); + + return Cache; + } +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ProgressCancel.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ProgressCancel.h new file mode 100644 index 000000000000..6f2904d48a80 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/ProgressCancel.h @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp ProgressCancel + +#pragma once + + +/** + * ICancelSource is an object that provides a cancel function + */ +class ICancelSource +{ +public: + virtual ~ICancelSource() {} + + /** + * @return true if object wishes to cancel expensive operation + */ + virtual bool Cancelled() = 0; +}; + + +/** + * FCancelFunction uses a TFunction to implement the ICancelSource interface + */ +class FCancelFunction : public ICancelSource +{ +public: + virtual ~FCancelFunction() {} + + /** function that returns true if object wishes to cancel operation */ + TFunction CancelF; + + FCancelFunction(const TFunction & cancelF) : CancelF(cancelF) + { + } + + /** + * @return true if object wishes to cancel expensive operation + */ + bool Cancelled() { return CancelF(); } +}; + + + +/** + * FProgressCancel is an obejct that is intended to be passed to long-running + * computes to do two things: + * 1) provide progress info back to caller (not ported yet) + * 2) allow caller to cancel the computation + */ +class FProgressCancel +{ +public: + TSharedPtr Source; + + bool WasCancelled = false; // will be set to true if CancelF() ever returns true + + FProgressCancel(TSharedPtr source) + { + Source = source; + } + + /** + * @return true if client would like to cancel operation + */ + bool Cancelled() + { + if (WasCancelled) + { + return true; + } + WasCancelled = Source->Cancelled(); + return WasCancelled; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/RefCountVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/RefCountVector.h new file mode 100644 index 000000000000..c4ea8081fd15 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/RefCountVector.h @@ -0,0 +1,487 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp FRefCountVector + +#pragma once + +#include "CoreMinimal.h" +#include "Util/DynamicVector.h" +#include "Util/IteratorUtil.h" + + +/** + * FRefCountVector is used to keep track of which indices in a linear Index list are in use/referenced. + * A free list is tracked so that unreferenced indices can be re-used. + * + * The enumerator iterates over valid indices (ie where refcount > 0) + * @warning refcounts are 16-bit ints (shorts) so the maximum count is 65536. behavior is undefined if this overflows. + * @warning No overflow checking is done in release builds. + */ +class FRefCountVector +{ +public: + static constexpr short INVALID_REF_COUNT = -1; + + TDynamicVector RefCounts; + TDynamicVector FreeIndices; + int UsedCount; + + FRefCountVector() + { + RefCounts = TDynamicVector(); + FreeIndices = TDynamicVector(); + UsedCount = 0; + } + + FRefCountVector(const FRefCountVector& Copy) + { + RefCounts = TDynamicVector(Copy.RefCounts); + FreeIndices = TDynamicVector(Copy.FreeIndices); + UsedCount = Copy.UsedCount; + } + + + bool IsEmpty() const + { + return UsedCount == 0; + } + + size_t GetCount() const + { + return UsedCount; + } + + size_t GetMaxIndex() const + { + return RefCounts.GetLength(); + } + + bool IsDense() const + { + return FreeIndices.GetLength() == 0; + } + + + bool IsValid(int Index) const + { + return (Index >= 0 && Index < (int)RefCounts.GetLength() && RefCounts[Index] > 0); + } + + bool IsValidUnsafe(int Index) const + { + return RefCounts[Index] > 0; + } + + + int GetRefCount(int Index) const + { + int n = RefCounts[Index]; + return (n == INVALID_REF_COUNT) ? 0 : n; + } + + int GetRawRefCount(int Index) const + { + return RefCounts[Index]; + } + + + int Allocate() + { + UsedCount++; + if (FreeIndices.IsEmpty()) + { + // [RMS] do we need this branch anymore? + RefCounts.Add(1); + return (int)RefCounts.GetLength() - 1; + } + else + { + int iFree = INVALID_REF_COUNT; + while (iFree == INVALID_REF_COUNT && FreeIndices.IsEmpty() == false) + { + iFree = FreeIndices.Back(); + FreeIndices.PopBack(); + } + if (iFree != INVALID_REF_COUNT) + { + RefCounts[iFree] = 1; + return iFree; + } + else + { + RefCounts.Add(1); + return (int)RefCounts.GetLength() - 1; + } + } + } + + + + int Increment(int Index, short IncrementCount = 1) + { + check(IsValid(Index)); + // debug check for overflow... + check((short)(RefCounts[Index] + IncrementCount) > 0); + RefCounts[Index] += IncrementCount; + return RefCounts[Index]; + } + + void Decrement(int Index, short DecrementCount = 1) + { + check(IsValid(Index)); + RefCounts[Index] -= DecrementCount; + check(RefCounts[Index] >= 0); + if (RefCounts[Index] == 0) + { + FreeIndices.Add(Index); + RefCounts[Index] = INVALID_REF_COUNT; + UsedCount--; + } + } + + + + /** + * allocate at specific Index, which must either be larger than current max Index, + * or on the free list. If larger, all elements up to this one will be pushed onto + * free list. otherwise we have to do a linear search through free list. + * If you are doing many of these, it is likely faster to use + * AllocateAtUnsafe(), and then RebuildFreeList() after you are done. + */ + bool AllocateAt(int Index) + { + if (Index >= (int)RefCounts.GetLength()) + { + int j = (int)RefCounts.GetLength(); + while (j < Index) + { + int InvalidCount = INVALID_REF_COUNT; // required on older clang because a constexpr can't be passed by ref + RefCounts.Add(InvalidCount); + FreeIndices.Add(j); + ++j; + } + RefCounts.Add(1); + UsedCount++; + return true; + + } + else + { + if (RefCounts[Index] > 0) + { + return false; + } + + int N = (int)FreeIndices.GetLength(); + for (int i = 0; i < N; ++i) + { + if (FreeIndices[i] == Index) + { + FreeIndices[i] = INVALID_REF_COUNT; + RefCounts[Index] = 1; + UsedCount++; + return true; + } + } + return false; + } + } + + + /** + * allocate at specific Index, which must be free or larger than current max Index. + * However, we do not update free list. So, you probably need to do RebuildFreeList() after calling this. + */ + bool AllocateAtUnsafe(int Index) + { + if (Index >= (int)RefCounts.GetLength()) + { + int j = (int)RefCounts.GetLength(); + while (j < Index) + { + int InvalidCount = INVALID_REF_COUNT; // required on older clang because a constexpr can't be passed by ref + RefCounts.Add(InvalidCount); + ++j; + } + RefCounts.Add(1); + UsedCount++; + return true; + + } + else + { + if (RefCounts[Index] > 0) + { + return false; + } + RefCounts[Index] = 1; + UsedCount++; + return true; + } + } + + + + const TDynamicVector& GetRawRefCounts() const + { + return RefCounts; + } + + /** + * @warning you should not use this! + */ + TDynamicVector& GetRawRefCountsUnsafe() + { + return RefCounts; + } + + /** + * @warning you should not use this! + */ + void SetRefCountUnsafe(int Index, short ToCount) + { + RefCounts[Index] = ToCount; + } + + // todo: + // remove + // clear + + + void RebuildFreeList() + { + FreeIndices = TDynamicVector(); + UsedCount = 0; + + int N = (int)RefCounts.GetLength(); + for (int i = 0; i < N; ++i) + { + if (RefCounts[i] > 0) + { + UsedCount++; + } + else + { + FreeIndices.Add(i); + } + } + } + + + + void Trim(int maxIndex) + { + FreeIndices = TDynamicVector(); + RefCounts.Resize(maxIndex); + UsedCount = maxIndex; + } + + + + // + // Iterators + // + + + + /** + * base iterator for indices with valid refcount (skips zero-refcount indices) + */ + class BaseIterator + { + public: + inline BaseIterator() + { + Vector = nullptr; + Index = 0; + LastIndex = 0; + } + + inline bool operator==(const BaseIterator& Other) const + { + return Index == Other.Index; + } + inline bool operator!=(const BaseIterator& Other) const + { + return Index != Other.Index; + } + + protected: + inline void goto_next() + { + if (Index != LastIndex) + { + Index++; + } + while (Index != LastIndex && Vector->IsValidUnsafe(Index) == false) + { + Index++; + } + } + + inline BaseIterator(const FRefCountVector* VectorIn, int IndexIn, int LastIn) + { + Vector = VectorIn; + Index = IndexIn; + LastIndex = LastIn; + if (Index != LastIndex && Vector->IsValidUnsafe(Index) == false) + { + goto_next(); // initialize + } + } + const FRefCountVector * Vector; + int Index; + int LastIndex; + friend class FRefCountVector; + }; + + + /* + * iterator over valid indices (ie non-zero refcount) + */ + class IndexIterator : public BaseIterator + { + public: + inline IndexIterator() : BaseIterator() {} + + inline int operator*() const + { + return this->Index; + } + + inline IndexIterator & operator++() // prefix + { + this->goto_next(); + return *this; + } + inline IndexIterator operator++(int) // postfix + { + IndexIterator copy(*this); + this->goto_next(); + return copy; + } + + protected: + inline IndexIterator(const FRefCountVector* VectorIn, int Index, int Last) : BaseIterator(VectorIn, Index, Last) + {} + friend class FRefCountVector; + }; + + + inline IndexIterator BeginIndices() const + { + return IndexIterator(this, (int)0, (int)RefCounts.GetLength()); + } + + inline IndexIterator EndIndices() const + { + return IndexIterator(this, (int)RefCounts.GetLength(), (int)RefCounts.GetLength()); + } + + + + /** + * enumerable object that provides begin()/end() semantics, so + * you can iterate over valid indices using range-based for loop + */ + class IndexEnumerable + { + public: + const FRefCountVector* Vector; + IndexEnumerable() { Vector = nullptr; } + IndexEnumerable(const FRefCountVector* VectorIn) { Vector = VectorIn; } + typename FRefCountVector::IndexIterator begin() { return Vector->BeginIndices(); } + typename FRefCountVector::IndexIterator end() { return Vector->EndIndices(); } + }; + + /** + * returns iteration object over valid indices + * usage: for (int idx : indices()) { ... } + */ + inline IndexEnumerable Indices() const + { + return IndexEnumerable(this); + } + + + /* + * enumerable object that maps indices output by Index_iteration to a second type + */ + template + class MappedEnumerable + { + public: + TFunction MapFunc; + IndexEnumerable enumerable; + + MappedEnumerable(const IndexEnumerable& enumerable, TFunction MapFunc) + { + this->enumerable = enumerable; + this->MapFunc = MapFunc; + } + + MappedIterator begin() + { + return MappedIterator(enumerable.begin(), MapFunc); + } + + MappedIterator end() + { + return MappedIterator(enumerable.end(), MapFunc); + } + }; + + + /** + * returns iteration object over mapping applied to valid indices + * eg usage: for (FVector3d v : mapped_indices(fn_that_looks_up_mesh_vtx_from_id)) { ... } + */ + template + inline MappedEnumerable MappedIndices(TFunction MapFunc) const + { + return MappedEnumerable(Indices(), MapFunc); + } + + + + + /* + * iteration object that maps indices output by Index_iteration to a second type + */ + class FilteredEnumerable + { + public: + TFunction FilterFunc; + IndexEnumerable enumerable; + + FilteredEnumerable(const IndexEnumerable& enumerable, TFunction FilterFuncIn) + { + this->enumerable = enumerable; + this->FilterFunc = FilterFuncIn; + } + + FilteredIterator begin() + { + return FilteredIterator(enumerable.begin(), enumerable.end(), FilterFunc); + } + + FilteredIterator end() + { + return FilteredIterator(enumerable.end(), enumerable.end(), FilterFunc); + } + }; + + inline FilteredEnumerable FilteredIndices(TFunction FilterFunc) const + { + return FilteredEnumerable(Indices(), FilterFunc); + } + + + FString UsageStats() + { + return FString::Printf(TEXT("RefCountSize %d FreeSize %d FreeMem %dkb"), + RefCounts.GetLength(), FreeIndices.GetLength(), (FreeIndices.GetByteCount() / 1024)); + } + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/SmallListSet.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/SmallListSet.h new file mode 100644 index 000000000000..f397fee629d5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/Util/SmallListSet.h @@ -0,0 +1,384 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3cpp small_list_set + +#pragma once + +#include "Util/DynamicVector.h" + +/** + * FSmallListSet stores a set of short integer-valued variable-size lists. + * The lists are encoded into a few large TDynamicVector buffers, with internal pooling, + * so adding/removing lists usually does not involve any or delete ops. + * + * The lists are stored in two parts. The first N elements are stored in a linear + * subset of a TDynamicVector. If the list spills past these N elements, the extra elements + * are stored in a linked list (which is also stored in a flat array). + * + * Each list stores its count, so list-size operations are constant time. + * All the internal "pointers" are 32-bit. + * + * @todo look at usage of TFunction, are we making unnecessary copies? + */ +class GEOMETRICOBJECTS_API FSmallListSet +{ +protected: + /** This value is used to indicate Null in internal pointers */ + static const int32 NullValue; // = -1; // note: cannot be constexpr because we pass as reference to several functions, requires C++17 + + /** size of initial linear-memory portion of lists */ + static constexpr int32 BLOCKSIZE = 8; + /** offset from start of linear-memory portion of list that contains pointer to head of variable-length linked list */ + static constexpr int32 BLOCK_LIST_OFFSET = BLOCKSIZE + 1; + + /** mapping from list index to offset into block_store that contains list data */ + TDynamicVector ListHeads; + + /** + * flat buffer used to store per-list linear-memory blocks. + * blocks are BLOCKSIZE+2 long, elements are [CurrentCount, item0...itemN, LinkedListPtr] + */ + TDynamicVector ListBlocks; + + /** list of free blocks as indices/offsets into block_store */ + TDynamicVector FreeBlocks; + + /** number of allocated lists */ + int32 AllocatedCount = 0; + + /** + * flat buffer used to store linked-list "spill" elements + * each element is [value, next_ptr] + */ + TDynamicVector LinkedListElements; + + /** index of first free element in linked_store */ + int32 FreeHeadIndex; + + +public: + FSmallListSet(); + + FSmallListSet(const FSmallListSet& copy); + + + /** + * @return largest current list index + */ + size_t Size() const + { + return ListHeads.GetLength(); + } + + /** + * set new number of lists + */ + void Resize(int32 NewSize); + + + /** + * @return true if a list has been allocated at the given ListIndex + */ + bool IsAllocated(int32 ListIndex) const + { + return (ListIndex >= 0 && ListIndex < (int32)ListHeads.GetLength() && ListHeads[ListIndex] != NullValue); + } + + /** + * Create a list at the given ListIndex + */ + void AllocateAt(int32 ListIndex); + + + /** + * Insert Value into list at ListIndex + */ + void Insert(int32 ListIndex, int32 Value); + + + + /** + * remove Value from the list at ListIndex + * @return false if Value was not in this list + */ + bool Remove(int32 ListIndex, int32 Value); + + + + /** + * Move list at FromIndex to ToIndex + */ + void Move(int32 FromIndex, int32 ToIndex); + + + + /** + * Remove all elements from the list at ListIndex + */ + void Clear(int32 ListIndex); + + + /** + * @return the size of the list at ListIndex + */ + inline int32 GetCount(int32 ListIndex) const + { + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + return (block_ptr == NullValue) ? 0 : ListBlocks[block_ptr]; + } + + + /** + * @return the first item in the list at ListIndex + * @warning does not check for zero-size-list! + */ + inline int32 First(int32 ListIndex) const + { + check(ListIndex >= 0); + int32 block_ptr = ListHeads[ListIndex]; + return ListBlocks[block_ptr + 1]; + } + + + /** + * Search for the given Value in list at ListIndex + * @return true if found + */ + bool Contains(int32 ListIndex, int32 Value) const; + + + /** + * Search the list at ListIndex for a value where PredicateFunc(value) returns true + * @return the found value, or the InvalidValue argument if not found + */ + int32 Find(int32 ListIndex, const TFunction& PredicateFunc, int32 InvalidValue = -1) const; + + + + /** + * Search the list at ListIndex for a value where PredicateFunc(value) returns true, and replace it with NewValue + * @return true if the value was found and replaced + */ + bool Replace(int32 ListIndex, const TFunction& PredicateFunc, int32 NewValue); + + + + // + // iterator support + // + + + friend class ValueIterator; + + /** + * ValueIterator iterates over the values of a small list + * An optional mapping function can be provided which will then be applied to the values returned by the * operator + */ + class ValueIterator + { + public: + inline ValueIterator() + { + ListSet = nullptr; + MapFunc = nullptr; + ListIndex = 0; + } + + inline bool operator==(const ValueIterator& Other) const + { + return ListSet == Other.ListSet && ListIndex == Other.ListIndex; + } + inline bool operator!=(const ValueIterator& Other) const + { + return ListSet != Other.ListSet || ListIndex != Other.ListIndex || iCur != Other.iCur || cur_ptr != Other.cur_ptr; + } + + inline int32 operator*() const + { + return (MapFunc == nullptr) ? cur_value : MapFunc(cur_value); + } + + inline const ValueIterator& operator++() // prefix + { + this->GotoNext(); + return *this; + } + //inline ValueIterator operator++(int32) { // postfix + // index_iterator copy(*this); + // this->GotoNext(); + // return copy; + //} + + + protected: + inline void GotoNext() + { + if (N == 0) + { + SetToEnd(); + return; + } + GotoNextOverflow(); + } + + inline void GotoNextOverflow() + { + if (iCur <= iEnd) + { + cur_value = ListSet->ListBlocks[iCur]; + iCur++; + } + else if (cur_ptr != NullValue) + { + cur_value = ListSet->LinkedListElements[cur_ptr]; + cur_ptr = ListSet->LinkedListElements[cur_ptr + 1]; + } + else + { + SetToEnd(); + } + } + + inline ValueIterator( + const FSmallListSet* ListSetIn, + int32 ListIndex, + bool is_end, + const TFunction& MapFuncIn = nullptr) + { + this->ListSet = ListSetIn; + this->MapFunc = MapFuncIn; + this->ListIndex = ListIndex; + if (is_end) + { + SetToEnd(); + } + else + { + block_ptr = ListSet->ListHeads[ListIndex]; + if (block_ptr != ListSet->NullValue) + { + N = ListSet->ListBlocks[block_ptr]; + iEnd = (N < BLOCKSIZE) ? (block_ptr + N) : (block_ptr + BLOCKSIZE); + iCur = block_ptr + 1; + cur_ptr = (N < BLOCKSIZE) ? NullValue : ListSet->ListBlocks[block_ptr + BLOCK_LIST_OFFSET]; + GotoNext(); + } + else + { + SetToEnd(); + } + } + } + + inline void SetToEnd() + { + block_ptr = ListSet->NullValue; + N = 0; + iCur = -1; + cur_ptr = -1; + } + + const FSmallListSet * ListSet; + TFunction MapFunc; + int32 ListIndex; + int32 block_ptr; + int32 N; + int32 iEnd; + int32 iCur; + int32 cur_ptr; + int32 cur_value; + friend class FSmallListSet; + }; + + /** + * @return iterator for start of list at ListIndex + */ + inline ValueIterator BeginValues(int32 ListIndex) const + { + return ValueIterator(this, ListIndex, false); + } + + /** + * @return iterator for start of list at ListIndex, with given value mapping function + */ + inline ValueIterator BeginValues(int32 ListIndex, const TFunction& MapFunc) const + { + return ValueIterator(this, ListIndex, false, MapFunc); + } + + /** + * @return iterator for end of list at ListIndex + */ + inline ValueIterator EndValues(int32 ListIndex) const + { + return ValueIterator(this, ListIndex, true); + } + + /** + * ValueEnumerable is an object that provides begin/end semantics for a small list, suitable for use with a range-based for loop + */ + class ValueEnumerable + { + public: + const FSmallListSet* ListSet; + int32 ListIndex; + TFunction MapFunc; + ValueEnumerable() {} + ValueEnumerable(const FSmallListSet* ListSetIn, int32 ListIndex, TFunction MapFunc = nullptr) + { + this->ListSet = ListSetIn; + this->ListIndex = ListIndex; + this->MapFunc = MapFunc; + } + typename FSmallListSet::ValueIterator begin() const { return ListSet->BeginValues(ListIndex, MapFunc); } + typename FSmallListSet::ValueIterator end() const { return ListSet->EndValues(ListIndex); } + }; + + /** + * @return a value enumerable for the given ListIndex + */ + inline ValueEnumerable Values(int32 ListIndex) const + { + return ValueEnumerable(this, ListIndex); + } + + /** + * @return a value enumerable for the given ListIndex, with the given value mapping function + */ + inline ValueEnumerable Values(int32 ListIndex, const TFunction& MapFunc) const + { + return ValueEnumerable(this, ListIndex, MapFunc); + } + + + + +protected: + + + // grab a block from the free list, or allocate a one + int32 AllocateBlock(); + + // push a link-node onto the free list + inline void AddFreeLink(int32 ptr) + { + LinkedListElements[ptr + 1] = FreeHeadIndex; + FreeHeadIndex = ptr; + } + + + // remove val from the linked-list attached to block_ptr + bool RemoveFromLinkedList(int32 block_ptr, int32 val); + + +public: + inline FString MemoryUsage() + { + return FString::Printf(TEXT("ListSize %d Blocks Count %d Free %d Mem %dkb Linked Mem %dkb"), + ListHeads.GetLength(), AllocatedCount, (FreeBlocks.GetLength() * sizeof(int32) / 1024), + ListBlocks.GetLength(), (LinkedListElements.GetLength() * sizeof(int32) / 1024)); + } + + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h new file mode 100644 index 000000000000..7612f6265b2e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorTypes.h @@ -0,0 +1,604 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Vector.h" +#include "MathUtil.h" + + +/** + * Templated 3D Vector. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + * + * Convenience typedefs for FVector3f/FVector3d/FVector3i are defined, and + * should be preferentially used over the base template type + * + * Implicit casts to/from FVector are defined, for all types. + * + * @todo Possibly can be replaced/merged with Chaos TVector + */ +template +struct FVector3 +{ + T X, Y, Z; + + FVector3() + { + } + FVector3(T ValX, T ValY, T ValZ) + : X(ValX), Y(ValY), Z(ValZ) + { + } + FVector3(const T* Data) + { + X = Data[0]; + Y = Data[1]; + Z = Data[2]; + } + + inline operator const T*() const + { + return &X; + }; + inline operator T*() + { + return &X; + } + + inline operator FVector() const + { + return FVector((float)X, (float)Y, (float)Z); + } + inline FVector3(const FVector& Vec) + { + X = (T)Vec.X; + Y = (T)Vec.Y; + Z = (T)Vec.Z; + } + + + explicit inline operator FLinearColor() const + { + return FLinearColor((float)X, (float)Y, (float)Z); + } + inline FVector3(const FLinearColor& Color) + { + X = (T)Color.R; + Y = (T)Color.G; + Z = (T)Color.B; + } + + explicit inline operator FColor() const + { + return FColor( + FMathf::Clamp((int)((float)X*255.0f), 0, 255), + FMathf::Clamp((int)((float)Y*255.0f), 0, 255), + FMathf::Clamp((int)((float)Z*255.0f), 0, 255)); + } + + + static FVector3 Zero() + { + return FVector3((T)0, (T)0, (T)0); + } + static FVector3 One() + { + return FVector3((T)1, (T)1, (T)1); + } + static FVector3 UnitX() + { + return FVector3((T)1, (T)0, (T)0); + } + static FVector3 UnitY() + { + return FVector3((T)0, (T)1, (T)0); + } + static FVector3 UnitZ() + { + return FVector3((T)0, (T)0, (T)1); + } + static FVector3 Max() + { + return FVector3(TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max()); + } + + FVector3& operator=(const FVector3& V2) + { + X = V2.X; + Y = V2.Y; + Z = V2.Z; + return *this; + } + + T& operator[](int Idx) + { + return (&X)[Idx]; + } + const T& operator[](int Idx) const + { + return (&X)[Idx]; + } + + T Length() const + { + return TMathUtil::Sqrt(X * X + Y * Y + Z * Z); + } + T SquaredLength() const + { + return X * X + Y * Y + Z * Z; + } + + inline T Distance(const FVector3& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + T dz = V2.Z - Z; + return TMathUtil::Sqrt(dx * dx + dy * dy + dz * dz); + } + inline T DistanceSquared(const FVector3& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + T dz = V2.Z - Z; + return dx * dx + dy * dy + dz * dz; + } + + inline FVector3 operator-() const + { + return FVector3(-X, -Y, -Z); + } + + inline FVector3 operator+(const FVector3& V2) const + { + return FVector3(X + V2.X, Y + V2.Y, Z + V2.Z); + } + + inline FVector3 operator-(const FVector3& V2) const + { + return FVector3(X - V2.X, Y - V2.Y, Z - V2.Z); + } + + inline FVector3 operator+(const T& Scalar) const + { + return FVector3(X + Scalar, Y + Scalar, Z + Scalar); + } + + inline FVector3 operator-(const T& Scalar) const + { + return FVector3(X - Scalar, Y - Scalar, Z - Scalar); + } + + inline FVector3 operator*(const T& Scalar) const + { + return FVector3(X * Scalar, Y * Scalar, Z * Scalar); + } + + inline FVector3 operator*(const FVector3& V2) const // component-wise + { + return FVector3(X * V2.X, Y * V2.Y, Z * V2.Z); + } + + inline FVector3 operator/(const T& Scalar) const + { + return FVector3(X / Scalar, Y / Scalar, Z / Scalar); + } + + inline FVector3& operator+=(const FVector3& V2) + { + X += V2.X; + Y += V2.Y; + Z += V2.Z; + return *this; + } + + inline FVector3& operator-=(const FVector3& V2) + { + X -= V2.X; + Y -= V2.Y; + Z -= V2.Z; + return *this; + } + + inline FVector3& operator*=(const T& Scalar) + { + X *= Scalar; + Y *= Scalar; + Z *= Scalar; + return *this; + } + + inline FVector3& operator/=(const T& Scalar) + { + X /= Scalar; + Y /= Scalar; + Z /= Scalar; + return *this; + } + + T Dot(const FVector3& V2) const + { + return X * V2.X + Y * V2.Y + Z * V2.Z; + } + + FVector3 Cross(const FVector3& V2) const + { + return FVector3( + Y * V2.Z - Z * V2.Y, + Z * V2.X - X * V2.Z, + X * V2.Y - Y * V2.X); + } + + // Angle in Degrees + T AngleD(const FVector3& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)(acos(ClampedDot) * (T)(180.0 / 3.14159265358979323846)); + } + + // Angle in Radians + T AngleR(const FVector3& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)acos(ClampedDot); + } + + inline bool IsNormalized() + { + return TMathUtil::Abs((X * X + Y * Y + Z * Z) - 1) < TMathUtil::ZeroTolerance; + } + + T Normalize(const T Epsilon = 0) + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + X *= invLength; + Y *= invLength; + Z *= invLength; + return length; + } + X = Y = Z = (T)0; + return 0; + } + + inline FVector3 Normalized(const T Epsilon = 0) const + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + return FVector3(X * invLength, Y * invLength, Z * invLength); + } + return FVector3((T)0, (T)0, (T)0); + } + + inline T MaxAbs() const + { + return TMathUtil::Max3(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); + } + + inline T MinAbs() const + { + return TMathUtil::Min3(TMathUtil::Abs(X), TMathUtil::Abs(Y), TMathUtil::Abs(Z)); + } + + static FVector3 Lerp(const FVector3& A, const FVector3& B, T Alpha) + { + T OneMinusAlpha = (T)1 - Alpha; + return FVector3(OneMinusAlpha * A.X + Alpha * B.X, + OneMinusAlpha * A.Y + Alpha * B.Y, + OneMinusAlpha * A.Z + Alpha * B.Z); + } + + inline bool operator==(const FVector3& Other) const + { + return X == Other.X && Y == Other.Y && Z == Other.Z; + } +}; + +template +inline FVector3 operator*(RealType Scalar, const FVector3& V) +{ + return FVector3(Scalar * V.X, Scalar * V.Y, Scalar * V.Z); +} + +typedef FVector3 FVector3f; +typedef FVector3 FVector3d; +typedef FVector3 FVector3i; + +template +FORCEINLINE uint32 GetTypeHash(const FVector3& Vector) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Vector, sizeof(FVector3)); +} + + + + + + +/** + * Templated 2D Vector. Ported from g3Sharp library, with the intention of + * maintaining compatibility with existing g3Sharp code. Has an API + * similar to WildMagic, GTEngine, Eigen, etc. + * + * Convenience typedefs for FVector2f/FVector2d/FVector2i are defined, and + * should be preferentially used over the base template type + * + * Implicit casts to/from FVector2D are defined, for all types. + * + * @todo Possibly can be replaced/merged with Chaos TVector + */ +template +struct FVector2 +{ + T X, Y; + + FVector2() + { + } + FVector2(T ValX, T ValY) + : X(ValX), Y(ValY) + { + } + FVector2(const T* Data) + { + X = Data[0]; + Y = Data[1]; + } + + inline operator const T*() const + { + return &X; + }; + inline operator T*() + { + return &X; + } + + operator FVector2D() const + { + return FVector2D((float)X, (float)Y); + } + FVector2(const FVector2D& Vec) + { + X = (T)Vec.X; + Y = (T)Vec.Y; + } + + static FVector2 Zero() + { + return FVector2((T)0, (T)0); + } + static FVector2 One() + { + return FVector2((T)1, (T)1); + } + static FVector2 UnitX() + { + return FVector2((T)1, (T)0); + } + static FVector2 UnitY() + { + return FVector2((T)0, (T)1); + } + + FVector2& operator=(const FVector2& V2) + { + X = V2.X; + Y = V2.Y; + return *this; + } + + T& operator[](int Idx) + { + return (&X)[Idx]; + } + const T& operator[](int Idx) const + { + return (&X)[Idx]; + } + + T Length() const + { + return TMathUtil::Sqrt(X * X + Y * Y); + } + T SquaredLength() const + { + return X * X + Y * Y; + } + + inline T Distance(const FVector2& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + return TMathUtil::Sqrt(dx * dx + dy * dy); + } + inline T DistanceSquared(const FVector2& V2) const + { + T dx = V2.X - X; + T dy = V2.Y - Y; + return dx * dx + dy * dy; + } + + T Dot(const FVector2& V2) const + { + return X * V2.X + Y * V2.Y; + } + + // Note: DotPerp and FVector2's version of Cross are the same function + inline T DotPerp(const FVector2& V2) const + { + return X * V2.Y - Y * V2.X; + } + inline T Cross(const FVector2& V2) const + { + return X * V2.Y - Y * V2.X; + } + + FVector2 Perp() const + { + return FVector2(Y, -X); + } + + inline FVector2 operator-() const + { + return FVector2(-X, -Y); + } + + inline FVector2 operator+(const FVector2& V2) const + { + return FVector2(X + V2.X, Y + V2.Y); + } + + inline FVector2 operator-(const FVector2& V2) const + { + return FVector2(X - V2.X, Y - V2.Y); + } + + inline FVector2 operator+(const T& Scalar) const + { + return FVector2(X + Scalar, Y + Scalar); + } + + inline FVector2 operator-(const T& Scalar) const + { + return FVector2(X - Scalar, Y - Scalar); + } + + inline FVector2 operator*(const T& Scalar) const + { + return FVector2(X * Scalar, Y * Scalar); + } + + inline FVector2 operator*(const FVector2& V2) const // component-wise + { + return FVector2(X * V2.X, Y * V2.Y); + } + + inline FVector2 operator/(const T& Scalar) const + { + return FVector2(X / Scalar, Y / Scalar); + } + + inline FVector2& operator+=(const FVector2& V2) + { + X += V2.X; + Y += V2.Y; + return *this; + } + + inline FVector2& operator-=(const FVector2& V2) + { + X -= V2.X; + Y -= V2.Y; + return *this; + } + + inline FVector2& operator*=(const T& Scalar) + { + X *= Scalar; + Y *= Scalar; + return *this; + } + + inline FVector2& operator/=(const T& Scalar) + { + X /= Scalar; + Y /= Scalar; + return *this; + } + + inline bool IsNormalized() + { + return TMathUtil::Abs((X * X + Y * Y) - 1) < TMathUtil::ZeroTolerance; + } + + // Angle in Degrees + T AngleD(const FVector2& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)(acos(ClampedDot) * (T)(180.0 / 3.14159265358979323846)); + } + + // Angle in Radians + T AngleR(const FVector2& V2) const + { + T DotVal = Dot(V2); + T ClampedDot = (DotVal < (T)-1) ? (T)-1 : ((DotVal > (T)1) ? (T)1 : DotVal); + return (T)acos(ClampedDot); + } + + T Normalize(const T Epsilon = 0) + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + X *= invLength; + Y *= invLength; + return length; + } + X = Y = (T)0; + return 0; + } + + inline FVector2 Normalized(const T Epsilon = 0) const + { + T length = Length(); + if (length > Epsilon) + { + T invLength = ((T)1) / length; + return FVector2(X * invLength, Y * invLength); + } + return FVector2((T)0, (T)0); + } + + + + bool operator==(const FVector2& Other) const + { + return X == Other.X && Y == Other.Y; + } + + + static FVector2 Lerp(const FVector2& A, const FVector2& B, T Alpha) + { + T OneMinusAlpha = (T)1 - Alpha; + return FVector2(OneMinusAlpha * A.X + Alpha * B.X, + OneMinusAlpha * A.Y + Alpha * B.Y); + } + + // Note: This was called "IsLeft" in the Geometry3Sharp code (where it also went through Math.Sign) + // Returns >0 if C is to the left of the A->B Line, <0 if to the right, 0 if on the line + static T Orient(const FVector2& A, const FVector2& B, const FVector2& C) + { + return (B - A).DotPerp(C - A); + } +}; + +template +FORCEINLINE uint32 GetTypeHash(const FVector2& Vector) +{ + // (this is how FIntVector and all the other FVectors do their hash functions) + // Note: this assumes there's no padding that could contain uncompared data. + return FCrc::MemCrc_DEPRECATED(&Vector, sizeof(FVector2)); +} + +template +inline FVector2 operator*(RealType Scalar, const FVector2& V) +{ + return FVector2(Scalar * V.X, Scalar * V.Y); +} + +typedef FVector2 FVector2f; +typedef FVector2 FVector2d; +typedef FVector2 FVector2i; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorUtil.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorUtil.h new file mode 100644 index 000000000000..39c285c09694 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometricObjects/Public/VectorUtil.h @@ -0,0 +1,348 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MathUtil.h" +#include "VectorTypes.h" + + +enum class EIntersectionResult +{ + NotComputed, + Intersects, + NoIntersection, + InvalidQuery +}; + +enum class EIntersectionType +{ + Empty, + Point, + Segment, + Line, + Polygon, + Plane, + Unknown +}; + +namespace VectorUtil +{ + /** + * @return true if all components of V are finite + */ + template + inline bool IsFinite(const FVector2& V) + { + return TMathUtil::IsFinite(V.X) && TMathUtil::IsFinite(V.Y); + } + + /** + * @return true if all components of V are finite + */ + template + inline bool IsFinite(const FVector3& V) + { + return TMathUtil::IsFinite(V.X) && TMathUtil::IsFinite(V.Y) && TMathUtil::IsFinite(V.Z); + } + + + /** + * @return input Value clamped to range [MinValue, MaxValue] + */ + template + inline RealType Clamp(RealType Value, RealType MinValue, RealType MaxValue) + { + return (Value < MinValue) ? MinValue : ((Value > MaxValue) ? MaxValue : Value); + } + + /** + * @return normalized vector that is perpendicular to triangle V0,V1,V2 (triangle normal) + */ + template + inline FVector3 Normal(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + FVector3 edge1(V1 - V0); + FVector3 edge2(V2 - V0); + edge1.Normalize(); + edge2.Normalize(); + // Unreal has Left-Hand Coordinate System so we need to reverse this cross-product to get proper triangle normal + FVector3 vCross(edge2.Cross(edge1)); + //FVector3 vCross(edge1.Cross(edge2)); + return vCross.Normalized(); + } + + /** + * @return un-normalized direction that is parallel to normal of triangle V0,V1,V2 + */ + template + inline FVector3 FastNormalDirection(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + // Unreal has Left-Hand Coordinate System so we need to reverse this cross-product to get proper triangle normal + return (V2 - V0).Cross(V1 - V0); + //return (V1 - V0).Cross(V2 - V0); + } + + /** + * @return area of 3D triangle V0,V1,V2 + */ + template + inline RealType Area(const FVector3& V0, const FVector3& V1, const FVector3& V2) + { + FVector3 edge1(V1 - V0); + FVector3 edge2(V2 - V0); + RealType Dot = edge1.Dot(edge2); + return (RealType)0.5 * TMathUtil::Sqrt(edge1.SquaredLength() * edge2.SquaredLength() - Dot * Dot); + } + + /** + * @return area of 2D triangle V0,V1,V2 + */ + template + inline RealType Area(const FVector2& V0, const FVector2& V1, const FVector2& V2) + { + FVector2 edge1(V1 - V0); + FVector2 edge2(V2 - V0); + RealType Dot = edge1.Dot(edge2); + return (RealType)0.5 * TMathUtil::Sqrt(edge1.SquaredLength() * edge2.SquaredLength() - Dot * Dot); + } + + /** + * @return true if triangle V1,V2,V3 is obtuse + */ + template + inline bool IsObtuse(const FVector3& V1, const FVector3& V2, const FVector3& V3) + { + RealType a2 = V1.DistanceSquared(V2); + RealType b2 = V1.DistanceSquared(V3); + RealType c2 = V2.DistanceSquared(V3); + return (a2 + b2 < c2) || (b2 + c2 < a2) || (c2 + a2 < b2); + } + + /** + * Calculate Normal and Area of triangle V0,V1,V2 + * @return triangle normal + */ + template + inline FVector3 FastNormalArea(const FVector3& V0, const FVector3& V1, const FVector3& V2, RealType& AreaOut) + { + FVector3 edge1(V1 - V0); + FVector3 edge2(V2 - V0); + // Unreal has Left-Hand Coordinate System so we need to reverse this cross-product to get proper triangle normal + FVector3d vCross = edge2.Cross(edge1); + //FVector3d vCross = edge1.Cross(edge2); + AreaOut = RealType(0.5) * vCross.Normalize(); + return vCross; + } + + /** @return true if Abs(A-B) is less than Epsilon */ + template + inline bool EpsilonEqual(RealType A, RealType B, RealType Epsilon) + { + return TMathUtil::Abs(A - B) < Epsilon; + } + + /** @return true if all coordinates of V0 and V1 are within Epsilon of eachother */ + template + inline bool EpsilonEqual(const FVector2& V0, const FVector2& V1, RealType Epsilon) + { + return EpsilonEqual(V0.X, V1.X, Epsilon) && EpsilonEqual(V0.Y, V1.Y, Epsilon); + } + + /** @return true if all coordinates of V0 and V1 are within Epsilon of eachother */ + template + inline bool EpsilonEqual(const FVector3& V0, const FVector3& V1, RealType Epsilon) + { + return EpsilonEqual(V0.X, V1.X, Epsilon) && EpsilonEqual(V0.Y, V1.Y, Epsilon) && EpsilonEqual(V0.Z, V1.Z, Epsilon); + } + + + /** @return 0/1/2 index of smallest value in Vector3 */ + template + inline int Min3Index(const ValueVecType& Vector3) + { + if (Vector3[0] <= Vector3[1]) + { + return Vector3[0] <= Vector3[2] ? 0 : 2; + } + else + { + return (Vector3[1] <= Vector3[2]) ? 1 : 2; + } + } + + + /** + * Calculates two vectors perpendicular to input Normal, as efficiently as possible. + */ + template + inline void MakePerpVectors(const FVector3& Normal, FVector3& OutPerp1, FVector3& OutPerp2) + { + // Duff et al method, from https://graphics.pixar.com/library/OrthonormalB/paper.pdf + if (Normal.Z < (RealType)0) + { + RealType A = (RealType)1 / ((RealType)1 - Normal.Z); + RealType B = Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = -B; + OutPerp1.Z = Normal.X; + OutPerp2.X = B; + OutPerp2.Y = Normal.Y * Normal.Y * A - (RealType)1; + OutPerp2.Z = -Normal.Y; + } + else + { + RealType A = (RealType)1 / ((RealType)1 + Normal.Z); + RealType B = -Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = B; + OutPerp1.Z = -Normal.X; + OutPerp2.X = B; + OutPerp2.Y = (RealType)1 - Normal.Y * Normal.Y * A; + OutPerp2.Z = -Normal.Y; + } + } + + /** + * Calculates one vector perpendicular to input Normal, as efficiently as possible. + */ + template + inline void MakePerpVector(const FVector3& Normal, FVector3& OutPerp1) + { + // Duff et al method, from https://graphics.pixar.com/library/OrthonormalB/paper.pdf + if (Normal.Z < (RealType)0) + { + RealType A = (RealType)1 / ((RealType)1 - Normal.Z); + RealType B = Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = -B; + OutPerp1.Z = Normal.X; + } + else + { + RealType A = (RealType)1 / ((RealType)1 + Normal.Z); + RealType B = -Normal.X * Normal.Y * A; + OutPerp1.X = (RealType)1 - Normal.X * Normal.X * A; + OutPerp1.Y = B; + OutPerp1.Z = -Normal.X; + } + } + + + /** + * Calculates angle between VFrom and VTo after projection onto plane with normal defined by PlaneN + * @return angle in degrees + */ + template + inline RealType PlaneAngleSignedD(const FVector3& VFrom, const FVector3& VTo, const FVector3& PlaneN) + { + FVector3 vFrom = VFrom - VFrom.Dot(PlaneN) * PlaneN; + FVector3 vTo = VTo - VTo.Dot(PlaneN) * PlaneN; + vFrom.Normalize(); + vTo.Normalize(); + FVector3 C = vFrom.Cross(vTo); + if (C.SquaredLength() < TMathUtil::ZeroTolerance) + { // vectors are parallel + return vFrom.Dot(vTo) < 0 ? (RealType)180 : (RealType)0; + } + RealType fSign = C.Dot(PlaneN) < 0 ? (RealType)-1 : (RealType)1; + return (RealType)(fSign * vFrom.AngleD(vTo)); + } + + /** + * tan(theta/2) = +/- sqrt( (1-cos(theta)) / (1+cos(theta)) ) + * @return positive value of tan(theta/2) where theta is angle between normalized vectors A and B + */ + template + RealType VectorTanHalfAngle(const FVector3& A, const FVector3& B) + { + RealType cosAngle = A.Dot(B); + RealType sqr = ((RealType)1 - cosAngle) / ((RealType)1 + cosAngle); + sqr = Clamp(sqr, (RealType)0, TMathUtil::MaxReal); + return TMathUtil::Sqrt(sqr); + } + + /** + * Fast cotangent of angle between two vectors. + * cot = cos/sin, both of which can be computed from vector identities + * @return cotangent of angle between V1 and V2, or zero if result would be unstable (eg infinity) + */ + template + RealType VectorCot(const FVector3& V1, const FVector3& V2) + { + // formula from http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + RealType fDot = V1.Dot(V2); + RealType lensqr1 = V1.SquaredLength(); + RealType lensqr2 = V2.SquaredLength(); + RealType d = Clamp(lensqr1 * lensqr2 - fDot * fDot, (RealType)0.0, TMathUtil::MaxReal); + if (d < TMathUtil::ZeroTolerance) + { + return (RealType)0; + } + else + { + return fDot / TMathUtil::Sqrt(d); + } + } + + /** + * @return solid angle at point P for triangle A,B,C + */ + template + inline RealType TriSolidAngle(FVector3 A, FVector3 B, FVector3 C, const FVector3& P) + { + // Formula from https://igl.ethz.ch/projects/winding-number/ + A -= P; + B -= P; + C -= P; + RealType la = A.Length(), lb = B.Length(), lc = C.Length(); + RealType top = (la * lb * lc) + A.Dot(B) * lc + B.Dot(C) * la + C.Dot(A) * lb; + RealType bottom = A.X * (B.Y * C.Z - C.Y * B.Z) - A.Y * (B.X * C.Z - C.X * B.Z) + A.Z * (B.X * C.Y - C.X * B.Y); + // -2 instead of 2 to account for UE winding + return RealType(-2.0) * atan2(bottom, top); + } + + /** + * @return angle between vectors (A-CornerPt) and (B-CornerPt) + */ + template + inline RealType OpeningAngleD(FVector3 A, FVector3 B, const FVector3& P) + { + A -= P; + A.Normalize(); + B -= P; + B.Normalize(); + return A.AngleD(B); + } + + + + /** + * @return sign of Binormal/Bitangent relative to Normal and Tangent + */ + template + inline RealType BinormalSign(const FVector3& NormalIn, const FVector3& TangentIn, const FVector3& BinormalIn) + { + // following math from RenderUtils.h::GetBasisDeterminantSign() + RealType Cross00 = BinormalIn.Y*NormalIn.Z - BinormalIn.Z*NormalIn.Y; + RealType Cross10 = BinormalIn.Z*NormalIn.X - BinormalIn.X*NormalIn.Z; + RealType Cross20 = BinormalIn.X*NormalIn.Y - BinormalIn.Y*NormalIn.X; + RealType Determinant = TangentIn.X*Cross00 + TangentIn.Y*Cross10 + TangentIn.Z*Cross20; + return (Determinant < 0) ? (RealType)-1 : (RealType)1; + } + + /** + * @return Binormal vector based on given Normal, Tangent, and Sign value (+1/-1) + */ + template + inline FVector3 Binormal(const FVector3& NormalIn, const FVector3& TangentIn, RealType BinormalSign) + { + return BinormalSign * FVector3( + NormalIn.Y*TangentIn.Z - NormalIn.Z*TangentIn.Y, + NormalIn.Z*TangentIn.X - NormalIn.X*TangentIn.Z, + NormalIn.X*TangentIn.Y - NormalIn.Y*TangentIn.X); + } + + + + +}; // namespace VectorUtil diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/GeometryAlgorithms.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/GeometryAlgorithms.Build.cs new file mode 100644 index 000000000000..a768c29fb1fc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/GeometryAlgorithms.Build.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class GeometryAlgorithms : ModuleRules +{ + public GeometryAlgorithms(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "GeometricObjects" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/Arrangement2d.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/Arrangement2d.cpp new file mode 100644 index 000000000000..4e382d11cbbe --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/Arrangement2d.cpp @@ -0,0 +1,253 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp Arrangement2d + +#include "Arrangement2d.h" +#include "ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h" +#include "ThirdParty/GTEngine/Mathematics/GteBSNumber.h" +#include "ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h" +#include + + + +namespace +{ +//#define DEBUG_FILE_DUMPING 1 +#ifndef DEBUG_FILE_DUMPING + void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase) + { + } + void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase) + { + } + void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) + { + } +#else +#include + static int num = 0; + void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase) + { + num++; + FString Path = PathBase + FString::FromInt(num) + ".txt"; + std::ofstream f(*Path); + + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " " << std::endl; + } + for (const FDynamicGraph::FEdge& Edge : Graph.Edges()) + { + f << "e " << Edge.A << " " << Edge.B << std::endl; + } + f.close(); + } + void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase) + { + num++; + FString Path = PathBase + FString::FromInt(num) + ".obj"; + std::ofstream f(*Path); + + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; + } + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " .5" << std::endl; + } + for (const FDynamicGraph::FEdge& Edge : Graph.Edges()) + { + f << "f " << Edge.A + 1 << " " << Edge.B + 1 << " " << 1 + Edge.A + Graph.MaxVertexID() << std::endl; + } + f.close(); + } + void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) + { + num++; + FString Path = PathBase + FString::FromInt(num) + ".obj"; + std::ofstream f(*Path); + for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) + { + const FVector2d& Vertex = Graph.GetVertex(VertexIdx); + f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; + } + for (const FIntVector& Tri : Triangles) + { + f << "f " << 1 + Tri.X << " " << 1 + Tri.Y << " " << 1 + Tri.Z << std::endl; + } + f.close(); + } +#endif +} + +bool FArrangement2d::AttemptTriangulate(TArray &Triangles, TArray &SkippedEdges, int32 BoundaryEdgeGroupID) +{ + Triangles.Empty(); + + // Use this declaration for the non-robust version of the algorithm; this is very non-robust, and not recommended! + //gte::ConstrainedDelaunay2 Delaunay; + + // Here are the types you could use for robust math (in theory makes the algorithm overall robust; in practice I am not sure) + gte::ConstrainedDelaunay2>> Delaunay; // Value of 263 is from comment in GTEngine/Mathematics/GteDelaunay2.h + //gte::ConstrainedDelaunay2> Delaunay; // Full arbitrary precision (slowest method, creates a std::vector per number) + + std::vector> InputVertices; + + // If there are unused vertices, define a vertex index remapping so the delaunay code won't have to understand that + bool bNeedsRemap = Graph.MaxVertexID() != Graph.VertexCount(); + TArray InputIndices, OutputIndices; + if (bNeedsRemap) + { + InputIndices.SetNumZeroed(Graph.MaxVertexID()); + OutputIndices.SetNumZeroed(Graph.VertexCount()); + int TotalOffset = 0; + for (int i = 0; i < Graph.MaxVertexID(); i++) + { + if (Graph.IsVertex(i)) + { + OutputIndices[i - TotalOffset] = i; + InputIndices[i] = i - TotalOffset; + FVector2d Vertex = Graph.GetVertex(i); + InputVertices.push_back(gte::Vector2 {{Vertex.X, Vertex.Y}}); + } + else + { + InputIndices[i] = -1; + TotalOffset++; + } + } + } + else + { // no index remapping needed; just copy the vertices + for (int i = 0; i < Graph.MaxVertexID(); i++) + { + FVector2d Vertex = Graph.GetVertex(i); + InputVertices.push_back(gte::Vector2 {{Vertex.X, Vertex.Y}}); + } + } + if (!Delaunay(Graph.VertexCount(), &InputVertices[0], 0.001f)) + { + return false; + } + std::vector OutEdges; + bool InsertConstraintFailure = false; + TSet> BoundarySet; // tracks all the boundary edges as they are added, so we can later eat what's outside them + bool bBoundaryTrackingFailure = false; + + for (int EdgeIdx : Graph.EdgeIndices()) + { + FDynamicGraph::FEdge Edge = Graph.GetEdge(EdgeIdx); + if (bNeedsRemap) + { + Edge.A = InputIndices[Edge.A]; + Edge.B = InputIndices[Edge.B]; + } + if (!Delaunay.Insert({{Edge.A, Edge.B}}, OutEdges)) + { + // Note the failed edge; we will try to proceed anyway, just without this edge. Hopefully the CDT is robust and this never happens! + ensureMsgf(false, TEXT("CDT edge insertion failed")); + InsertConstraintFailure = true; + SkippedEdges.Add(EdgeIdx); + // specially note if we failed to add a boundary edge; in this case removing the outside-boundary triangles would likely delete all triangles from the output + // in this case I choose (below) to just add all triangles to the output, ignoring boundaries, as this may create less noticeable artifacts in the typical cases + if (Edge.Group == BoundaryEdgeGroupID) + { + bBoundaryTrackingFailure = true; + } + } + else + { + if (Edge.Group == BoundaryEdgeGroupID) + { + for (size_t i = 0; i + 1 < OutEdges.size(); i++) + { + int MinV = FMath::Min(OutEdges[i], OutEdges[i + 1]); + int MaxV = FMath::Max(OutEdges[i], OutEdges[i + 1]); + BoundarySet.Add(TPair(MinV, MaxV)); + } + } + } + } + const std::vector& Indices = Delaunay.GetIndices(); + if (!bBoundaryTrackingFailure && BoundarySet.Num() > 0) { + auto IsBoundaryEdge = [&BoundarySet](int V0, int V1) + { + int MinV = FMath::Min(V0, V1); + int MaxV = FMath::Max(V0, V1); + return BoundarySet.Contains(TPair(MinV, MaxV)); + }; + + // eat hull to boundary + const std::vector& Adj = Delaunay.GetAdjacencies(); + int TriNum = int(Adj.size() / 3); + TArray Eaten; + Eaten.SetNumZeroed(TriNum); + + TArray ToEatQ; + // seed the feed queue with any triangles that are on hull but w/ an edge that is not on intended boundary + for (int TriIdx = 0; TriIdx < TriNum; TriIdx++) + { + int BaseIdx = TriIdx * 3; + bool bEatIt = false; + for (int SubIdx = 0, NextIdx = 2; !bEatIt && SubIdx < 3; NextIdx = SubIdx++) + { + // eat the triangle if it has a hull edge that is not a boundary edge + bEatIt = Adj[BaseIdx + NextIdx] == -1 && !IsBoundaryEdge(Indices[BaseIdx + SubIdx], Indices[BaseIdx + NextIdx]); + } + if (bEatIt) + { + ToEatQ.Add(TriIdx); + Eaten[TriIdx] = true; + } + } + + // eat any triangle that we can get to by crossing a non-boundary edge from an already-been-eaten triangle + while (ToEatQ.Num()) + { + int EatTri = ToEatQ.Pop(); + int BaseIdx = EatTri * 3; + for (int SubIdx = 0, NextIdx = 2; SubIdx < 3; NextIdx = SubIdx++) + { + int AdjTri = Adj[BaseIdx + NextIdx]; + if (AdjTri != -1 && !Eaten[AdjTri] && !IsBoundaryEdge(Indices[BaseIdx + SubIdx], Indices[BaseIdx + NextIdx])) + { + ToEatQ.Add(AdjTri); + Eaten[AdjTri] = true; + } + } + } + + // copy uneaten triangles + for (int i = 0; i < TriNum; i++) + { + if (!Eaten[i]) + { + Triangles.Add(FIntVector(Indices[i * 3], Indices[i * 3 + 1], Indices[i * 3 + 2])); + } + } + ensure(Triangles.Num()); // technically it could be valid to not have any triangles after eating the outside-boundary ones, but it would be unusual and could also be a bug + } + else + { + // copy all triangles over + for (size_t i = 0, n = Indices.size() / 3; i < n; i++) + { + Triangles.Add(FIntVector(Indices[i * 3], Indices[i * 3 + 1], Indices[i * 3 + 2])); + } + } + if (bNeedsRemap) + { + for (FIntVector& Face : Triangles) + { + Face.X = OutputIndices[Face.X]; + Face.Y = OutputIndices[Face.Y]; + Face.Z = OutputIndices[Face.Z]; + } + } + return !InsertConstraintFailure; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GeometryAlgorithmsModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GeometryAlgorithmsModule.cpp new file mode 100644 index 000000000000..8eab6723c498 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/GeometryAlgorithmsModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GeometryAlgorithmsModule.h" + +#define LOCTEXT_NAMESPACE "FGeometryAlgorithmsModule" + +void FGeometryAlgorithmsModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FGeometryAlgorithmsModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FGeometryAlgorithmsModule, GeometryAlgorithms) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngine.h new file mode 100644 index 000000000000..95dab42643b0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngine.h @@ -0,0 +1,11 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2019/02/12) + +#pragma once + +#include +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngineDEF.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngineDEF.h new file mode 100644 index 000000000000..5bffe76cfb04 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEngineDEF.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +//---------------------------------------------------------------------------- +// The platform specification. +// +// __MSWINDOWS__ : Microsoft Windows (WIN32 or WIN64) +// __APPLE__ : Macintosh OS X +// __LINUX__ : Linux or Cygwin +//---------------------------------------------------------------------------- + +#if !defined(__LINUX__) && (defined(WIN32) || defined(_WIN64)) +#define __MSWINDOWS__ + +#if !defined(_MSC_VER) +#error Microsoft Visual Studio 2013 or later is required. +#endif + +// MSVC 6 is version 12.0 +// MSVC 7.0 is version 13.0 (MSVS 2002) +// MSVC 7.1 is version 13.1 (MSVS 2003) +// MSVC 8.0 is version 14.0 (MSVS 2005) +// MSVC 9.0 is version 15.0 (MSVS 2008) +// MSVC 10.0 is version 16.0 (MSVS 2010) +// MSVC 11.0 is version 17.0 (MSVS 2012) +// MSVC 12.0 is version 18.0 (MSVS 2013) +// MSVC 14.0 is version 19.0 (MSVS 2015) +// Currently, projects are provided only for MSVC 12.0 and 14.0. +#if _MSC_VER < 1800 +#error Microsoft Visual Studio 2013 or later is required. +#endif + +// Debug build values (choose_your_value is 0, 1, or 2) +// 0: Disables checked iterators and disables iterator debugging. +// 1: Enables checked iterators and disables iterator debugging. +// 2: (default) Enables iterator debugging; checked iterators are not relevant. +// +// Release build values (choose_your_value is 0 or 1) +// 0: (default) Disables checked iterators. +// 1: Enables checked iterators; iterator debugging is not relevant. +// +// #define _ITERATOR_DEBUG_LEVEL choose_your_value + +#endif // WIN32 or _WIN64 + +// TODO: Windows DLL configurations have not yet been added to the project, +// but these defines are required to support them (when we do add them). +// +// Add GTE_EXPORT to project preprocessor options for dynamic library +// configurations to export their symbols. +#if defined(GTE_EXPORT) + // For the dynamic library configurations. + #define GTE_IMPEXP __declspec(dllexport) +#else + // For a client of the dynamic library or for the static library + // configurations. + #define GTE_IMPEXP +#endif + +// Expose exactly one of these. +#define GTE_USE_ROW_MAJOR +//#define GTE_USE_COL_MAJOR + +// Expose exactly one of these. +#define GTE_USE_MAT_VEC +//#define GTE_USE_VEC_MAT + +#if (defined(GTE_USE_ROW_MAJOR) && defined(GTE_USE_COL_MAJOR)) || (!defined(GTE_USE_ROW_MAJOR) && !defined(GTE_USE_COL_MAJOR)) +#error Exactly one storage order must be specified. +#endif + +#if (defined(GTE_USE_MAT_VEC) && defined(GTE_USE_VEC_MAT)) || (!defined(GTE_USE_MAT_VEC) && !defined(GTE_USE_VEC_MAT)) +#error Exactly one multiplication convention must be specified. +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.cpp new file mode 100644 index 000000000000..61878e84167e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.cpp @@ -0,0 +1,8 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.h new file mode 100644 index 000000000000..30b7bd3d407b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTEnginePCH.h @@ -0,0 +1,13 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +// Define GTE_DISABLE_PCH to turn off the precompiled header system. +#ifndef GTE_DISABLE_PCH +#include +#endif + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTLowLevel.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTLowLevel.h new file mode 100644 index 000000000000..e89f91035c12 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTLowLevel.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2019/01/09) + +#pragma once + +// DataTypes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Logger +#include +#include +#include +#include +#include + +// Timer +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTMathematics.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTMathematics.h new file mode 100644 index 000000000000..ca2775b7e12d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/GTMathematics.h @@ -0,0 +1,398 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.29 (2019/02/13) + +#pragma once + +// I used to have these sections ordered alphabetically by the names in +// the comments. Two phase name lookups for template matching by the +// MSVS 2017 compiler had no problems with the ordering, but the Linux +// g++ compiler does. This occurred when trying to match std::sqrt(...) +// and other math functions when the inputs are based on BSNumber or +// BSRational. The "Arithmetic" section has been moved before all other +// headers, and the UInteger* files have been moved before the BS* files. + +// Arithmetic +#include +#include +#include +#include +#include +#include + +// Algebra +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Approximation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ComputationalGeometry +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Containment +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// CurvesSurfacesVolumes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Distance +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Functions +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// GeometricPrimitives +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Interpolation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Intersection +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NumericalMethods +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Projection +#include + +// SIMD (removed due to compile issues + not needed currently) +// #if defined(__MSWINDOWS__) && !defined(MINGW) +// #include +// #include +// #endif + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray2.h new file mode 100644 index 000000000000..2da04998e363 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray2.h @@ -0,0 +1,171 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The Array2 class represents a 2-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace gte +{ + +template +class Array2 +{ +public: + // Construction. The first constructor generates an array of objects that + // are owned by Array2. The second constructor is given an array of + // objects that are owned by the caller. The array has bound0 columns and + // bound1 rows. + Array2(size_t bound0, size_t bound1); + Array2(size_t bound0, size_t bound1, T* objects); + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the assignment + // operator. + Array2(); + Array2(Array2 const& other); + Array2& operator=(Array2 const& other); + Array2(Array2&& other); + Array2& operator=(Array2&& other); + + // Access to the array. Sample usage is + // Array2 myArray(3, 2); + // T* row1 = myArray[1]; + // T row1Col2 = myArray[1][2]; + inline size_t GetBound0() const; + inline size_t GetBound1() const; + inline T const* operator[] (int row) const; + inline T* operator[] (int row); + +private: + void SetPointers(T* objects); + void SetPointers(Array2 const& other); + + size_t mBound0, mBound1; + std::vector mObjects; + std::vector mIndirect1; +}; + +template +Array2::Array2(size_t bound0, size_t bound1) + : + mBound0(bound0), + mBound1(bound1), + mObjects(bound0 * bound1), + mIndirect1(bound1) +{ + SetPointers(mObjects.data()); +} + +template +Array2::Array2(size_t bound0, size_t bound1, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mIndirect1(bound1) +{ + SetPointers(objects); +} + +template +Array2::Array2() + : + mBound0(0), + mBound1(0) +{ +} + +template +Array2::Array2(Array2 const& other) +{ + *this = other; +} + +template +Array2& Array2::operator=(Array2 const& other) +{ + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; +} + +template +Array2::Array2(Array2&& other) +{ + *this = std::move(other); +} + +template +Array2& Array2::operator=(Array2&& other) +{ + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; +} + +template inline +size_t Array2::GetBound0() const +{ + return mBound0; +} + +template inline +size_t Array2::GetBound1() const +{ + return mBound1; +} + +template inline +T const* Array2::operator[] (int row) const +{ + return mIndirect1[row]; +} + +template inline +T* Array2::operator[] (int row) +{ + return mIndirect1[row]; +} + +template +void Array2::SetPointers(T* objects) +{ + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * i1; // = bound0 * (i1 + j1) where j1 = 0 + mIndirect1[i1] = &objects[j0]; + } +} + +template +void Array2::SetPointers(Array2 const& other) +{ + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mIndirect1.resize(mBound1); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect1[0]); + } + // else 'other' is an empty Array2. +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray3.h new file mode 100644 index 000000000000..9cccb47f0618 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray3.h @@ -0,0 +1,192 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The Array3 class represents a 3-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace gte +{ + +template +class Array3 +{ +public: + // Construction. The first constructor generates an array of objects that + // are owned by Array3. The second constructor is given an array of + // objects that are owned by the caller. The array has bound0 columns, + // bound1 rows, and bound2 slices. + Array3(size_t bound0, size_t bound1, size_t bound2); + Array3(size_t bound0, size_t bound1, size_t bound2, T* objects); + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the assignment + // operator. + Array3(); + Array3(Array3 const&); + Array3& operator=(Array3 const&); + Array3(Array3&&); + Array3& operator=(Array3&&); + + // Access to the array. Sample usage is + // Array3 myArray(4, 3, 2); + // T** slice1 = myArray[1]; + // T* slice1row2 = myArray[1][2]; + // T slice1Row2Col3 = myArray[1][2][3]; + inline size_t GetBound0() const; + inline size_t GetBound1() const; + inline size_t GetBound2() const; + inline T* const* operator[] (int slice) const; + inline T** operator[] (int slice); + +private: + void SetPointers(T* objects); + void SetPointers(Array3 const& other); + + size_t mBound0, mBound1, mBound2; + std::vector mObjects; + std::vector mIndirect1; + std::vector mIndirect2; +}; + +template +Array3::Array3(size_t bound0, size_t bound1, size_t bound2) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mObjects(bound0 * bound1 * bound2), + mIndirect1(bound1 * bound2), + mIndirect2(bound2) +{ + SetPointers(mObjects.data()); +} + +template +Array3::Array3(size_t bound0, size_t bound1, size_t bound2, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mIndirect1(bound1 * bound2), + mIndirect2(bound2) +{ + SetPointers(objects); +} + +template +Array3::Array3() + : + mBound0(0), + mBound1(0), + mBound2(0) +{ +} + +template +Array3::Array3(Array3 const& other) +{ + *this = other; +} + +template +Array3& Array3::operator=(Array3 const& other) +{ + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; +} + +template +Array3::Array3(Array3&& other) +{ + *this = std::move(other); +} + +template +Array3& Array3::operator=(Array3&& other) +{ + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; +} + +template inline +size_t Array3::GetBound0() const +{ + return mBound0; +} + +template inline +size_t Array3::GetBound1() const +{ + return mBound1; +} + +template inline +size_t Array3::GetBound2() const +{ + return mBound2; +} + +template inline +T* const* Array3::operator[] (int slice) const +{ + return mIndirect2[slice]; +} + +template inline +T** Array3::operator[] (int slice) +{ + return mIndirect2[slice]; +} + +template +void Array3::SetPointers(T* objects) +{ + for (size_t i2 = 0; i2 < mBound2; ++i2) + { + size_t j1 = mBound1 * i2; // = bound1 * (i2 + j2) where j2 = 0 + mIndirect2[i2] = &mIndirect1[j1]; + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * (i1 + j1); + mIndirect2[i2][i1] = &objects[j0]; + } + } +} + +template +void Array3::SetPointers(Array3 const& other) +{ + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mBound2 = other.mBound2; + mIndirect1.resize(mBound1 * mBound2); + mIndirect2.resize(mBound2); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect2[0][0]); + } + // else 'other' is an empty Array3. +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray4.h new file mode 100644 index 000000000000..93dc6d8a1321 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteArray4.h @@ -0,0 +1,213 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The Array4 class represents a 4-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace gte +{ + +template +class Array4 +{ +public: + // Construction. The first constructor generates an array of objects that + // are owned by Array4. The second constructor is given an array of + // objects that are owned by the caller. The array has bound0 columns, + // bound1 rows, bound2 slices, and bound3 cuboids. + Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3); + Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3, T* objects); + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the assignment + // operator. + Array4(); + Array4(Array4 const&); + Array4& operator=(Array4 const&); + Array4(Array4&&); + Array4& operator=(Array4&&); + + // Access to the array. Sample usage is + // Array4 myArray(5, 4, 3, 2); + // T*** cuboid1 = myArray[1]; + // T** cuboid1Slice2 = myArray[1][2]; + // T* cuboid1Slice2Row3 = myArray[1][2][3]; + // T cuboid1Slice2Row3Col4 = myArray[1][2][3][4]; + inline size_t GetBound0() const; + inline size_t GetBound1() const; + inline size_t GetBound2() const; + inline size_t GetBound3() const; + inline T** const* operator[] (int cuboid) const; + inline T*** operator[] (int cuboid); + +private: + void SetPointers(T* objects); + void SetPointers(Array4 const& other); + + size_t mBound0, mBound1, mBound2, mBound3; + std::vector mObjects; + std::vector mIndirect1; + std::vector mIndirect2; + std::vector mIndirect3; +}; + +template +Array4::Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mBound3(bound3), + mObjects(bound0 * bound1 * bound2 * bound3), + mIndirect1(bound1 * bound2 * bound3), + mIndirect2(bound2 * bound3), + mIndirect3(bound3) +{ + SetPointers(mObjects.data()); +} + +template +Array4::Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mBound3(bound3), + mIndirect1(bound1 * bound2 * bound3), + mIndirect2(bound2 * bound3), + mIndirect3(bound3) +{ + SetPointers(objects); +} + +template +Array4::Array4() + : + mBound0(0), + mBound1(0), + mBound2(0), + mBound3(0) +{ +} + +template +Array4::Array4(Array4 const& other) +{ + *this = other; +} + +template +Array4& Array4::operator=(Array4 const& other) +{ + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; +} + +template +Array4::Array4(Array4&& other) +{ + *this = std::move(other); +} + +template +Array4& Array4::operator=(Array4&& other) +{ + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; +} + +template inline +size_t Array4::GetBound0() const +{ + return mBound0; +} + +template inline +size_t Array4::GetBound1() const +{ + return mBound1; +} + +template inline +size_t Array4::GetBound2() const +{ + return mBound2; +} + +template inline +size_t Array4::GetBound3() const +{ + return mBound3; +} + +template inline +T** const* Array4::operator[] (int cuboid) const +{ + return mIndirect3[cuboid]; +} + +template inline +T*** Array4::operator[] (int cuboid) +{ + return mIndirect3[cuboid]; +} + +template +void Array4::SetPointers(T* objects) +{ + for (size_t i3 = 0; i3 < mBound3; ++i3) + { + size_t j2 = mBound2 * i3; // = bound2 * (i3 + j3) where j3 = 0 + mIndirect3[i3] = &mIndirect2[j2]; + for (size_t i2 = 0; i2 < mBound2; ++i2) + { + size_t j1 = mBound1 * (i2 + j2); + mIndirect3[i3][i2] = &mIndirect1[j1]; + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * (i1 + j1); + mIndirect3[i3][i2][i1] = &objects[j0]; + } + } + } +} + +template +void Array4::SetPointers(Array4 const& other) +{ + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mBound2 = other.mBound2; + mBound3 = other.mBound3; + mIndirect1.resize(mBound1 * mBound2 * mBound3); + mIndirect2.resize(mBound2 * mBound3); + mIndirect3.resize(mBound3); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect3[0][0][0]); + } + // else 'other' is an empty Array3. +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteAtomicMinMax.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteAtomicMinMax.h new file mode 100644 index 000000000000..d30031972d6d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteAtomicMinMax.h @@ -0,0 +1,52 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Implementations of atomic minimum and atomic maximum computations. These +// are based on std::atomic_compare_exchange_strong. + +namespace gte +{ + +template +T AtomicMin(std::atomic& v0, T const& v1); + +template +T AtomicMax(std::atomic& v0, T const& v1); + + +template +T AtomicMin(std::atomic& v0, T const& v1) +{ + T vInitial, vMin; + do + { + vInitial = v0; + vMin = std::min(vInitial, v1); + } while (!std::atomic_compare_exchange_strong(&v0, &vInitial, vMin)); + return vInitial; +} + +template +T AtomicMax(std::atomic& v0, T const& v1) +{ + T vInitial, vMax; + do + { + vInitial = v0; + vMax = std::max(vInitial, v1); + } while (!std::atomic_compare_exchange_strong(&v0, &vInitial, vMax)); + return vInitial; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteComputeModel.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteComputeModel.h new file mode 100644 index 000000000000..3185964c4dfa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteComputeModel.h @@ -0,0 +1,102 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Expose this define if you want GPGPU support in computing any algorithms +// that have a GPU implemetnation. Alternatively, your application can +// define this in the project settings so that you do not have to edit this +// source file. +// +//#define GTE_COMPUTE_MODEL_ALLOW_GPGPU + +// The ComputeModel class allows you to select the type of hardware to use +// in your computational algorithms. +// +// If your computational algorithm requires the GPU, set 'inEngine' to the +// object that will execute the compute shaders. If your algorithm +// requires CPU multithreading, set the 'inNumThreads' to the desired +// number of threads, presumably 2 or larger. You can query for the number +// of concurrent hardware threads using std::thread::hardware_concurrency(). +// If you want single-threaded computations (on the main thread), set +// inNumThreads to 1. An example of using this class is +// +// ComputeModel cmodel(...); +// if (cmodel.engine) +// { +// ComputeUsingGPU(...); +// } +// else if (cmodel.numThreads > 1) +// { +// ComputeUsingCPUMultipleThreads(); +// } +// else +// { +// ComputeUsingCPUSingleThread(); +// } +// See GenerateMeshUV::SolveSystem(...) for a concrete example. +// +// Of course, your algorithm can interpret cmodel anyway it likes. For +// example, you might ignore cmodel.engine if all you care about is +// multithreading on the CPU. + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) +#include +#endif + +namespace gte +{ + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) +class GraphicsEngine; +class ProgramFactory; +#endif + +class GTE_IMPEXP ComputeModel +{ +public: + // Construction and destruction. You may derive from this class to make + // additional behavior available to your algorithms. For example, you + // might add a callback function to report progress of the algorithm. + virtual ~ComputeModel() + { + } + + ComputeModel() + : + numThreads(1) + { + } + + ComputeModel(unsigned int inNumThreads) + : + numThreads(inNumThreads > 0 ? inNumThreads : 1) + { + } + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + ComputeModel(unsigned int inNumThreads, + std::shared_ptr const& inEngine, + std::shared_ptr const& inFactory) + : + numThreads(inNumThreads > 0 ? inNumThreads : 1), + engine(inEngine), + factory(inFactory) + { + } +#endif + + unsigned int numThreads; +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + std::shared_ptr engine; + std::shared_ptr factory; +#endif +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLexicoArray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLexicoArray2.h new file mode 100644 index 000000000000..6e1e19030ed6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLexicoArray2.h @@ -0,0 +1,219 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// A template class to provide 2D array access that conforms to row-major +// order (RowMajor = true) or column-major order (RowMajor = false). The +template +class LexicoArray2 {}; + +// The array dimensions are known only at run time. +template +class LexicoArray2 +{ +public: + inline LexicoArray2(int numRows, int numCols, Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + int mNumRows, mNumCols; + Real* mMatrix; +}; + +template +class LexicoArray2 +{ +public: + inline LexicoArray2(int numRows, int numCols, Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + int mNumRows, mNumCols; + Real* mMatrix; +}; + +// The array dimensions are known at compile time. +template +class LexicoArray2 +{ +public: + inline LexicoArray2(Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + Real* mMatrix; +}; + +template +class LexicoArray2 +{ +public: + inline LexicoArray2(Real* matrix); + + inline int GetNumRows() const; + inline int GetNumCols() const; + inline Real& operator()(int r, int c); + inline Real const& operator()(int r, int c) const; + +private: + Real* mMatrix; +}; + + +template inline +LexicoArray2::LexicoArray2(int numRows, int numCols, Real* matrix) + : + mNumRows(numRows), + mNumCols(numCols), + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return mNumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return mNumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[c + mNumCols*r]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[c + mNumCols*r]; +} + + + +template inline +LexicoArray2::LexicoArray2(int numRows, int numCols, Real* matrix) + : + mNumRows(numRows), + mNumCols(numCols), + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return mNumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return mNumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[r + mNumRows*c]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[r + mNumRows*c]; +} + + + +template inline +LexicoArray2::LexicoArray2(Real* matrix) + : + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return NumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return NumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[c + NumCols*r]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[c + NumCols*r]; +} + + + +template inline +LexicoArray2::LexicoArray2(Real* matrix) + : + mMatrix(matrix) +{ +} + +template inline +int LexicoArray2::GetNumRows() const +{ + return NumRows; +} + +template inline +int LexicoArray2::GetNumCols() const +{ + return NumCols; +} + +template inline +Real& LexicoArray2::operator()(int r, int c) +{ + return mMatrix[r + NumRows*c]; +} + +template inline +Real const& LexicoArray2::operator()(int r, int c) const +{ + return mMatrix[r + NumRows*c]; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.cpp new file mode 100644 index 000000000000..8f708ce68238 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.cpp @@ -0,0 +1,47 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + +LogReporter::~LogReporter() +{ + if (mLogToStdout) + { + Logger::Unsubscribe(mLogToStdout.get()); + } + + if (mLogToFile) + { + Logger::Unsubscribe(mLogToFile.get()); + } + + +} + +LogReporter::LogReporter(std::string const& logFile, int logFileFlags, + int logStdoutFlags) + : + mLogToFile(nullptr), + mLogToStdout(nullptr) + +{ + if (logFileFlags != Logger::Listener::LISTEN_FOR_NOTHING) + { + mLogToFile = std::make_unique(logFile, logFileFlags); + Logger::Subscribe(mLogToFile.get()); + } + + if (logStdoutFlags != Logger::Listener::LISTEN_FOR_NOTHING) + { + mLogToStdout = std::make_unique(logStdoutFlags); + Logger::Subscribe(mLogToStdout.get()); + } + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.h new file mode 100644 index 000000000000..5686c4ba5571 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogReporter.h @@ -0,0 +1,34 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP LogReporter +{ +public: + // Construction and destruction. Create one of these objects in an + // application for logging. The GenerateProject tool creates such code. + // If you do not want a particular logger, set the flags to + // LISTEN_FOR_NOTHING and set logFile to "" if you do not want a file. + ~LogReporter(); + + LogReporter(std::string const& logFile, int logFileFlags, int logStdoutFlags); + +private: + std::unique_ptr mLogToFile; + std::unique_ptr mLogToStdout; + +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.cpp new file mode 100644 index 000000000000..4825c9068cd6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.cpp @@ -0,0 +1,53 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + + +LogToFile::LogToFile(std::string const& filename, int flags) + : + Logger::Listener(flags), + mFilename(filename) +{ + std::ofstream logFile(filename); + if (logFile) + { + // This clears the file contents from any previous runs. + logFile.close(); + } + else + { + // The file cannot be opened. Use a null string for Report to know + // not to attempt opening the file for append. + mFilename = ""; + } +} + +void LogToFile::Report(std::string const& message) +{ + if (mFilename != "") + { + // Open for append. + std::ofstream logFile(mFilename, + std::ios_base::out | std::ios_base::app); + if (logFile) + { + logFile << message.c_str(); + logFile.close(); + } + else + { + // The file cannot be opened. Use a null string for Report not + // to attempt opening the file for append on the next call. + mFilename = ""; + } + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.h new file mode 100644 index 000000000000..86a58a77fa34 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToFile.h @@ -0,0 +1,26 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +class GTE_IMPEXP LogToFile : public Logger::Listener +{ +public: + LogToFile(std::string const& filename, int flags); + +private: + virtual void Report(std::string const& message); + + std::string mFilename; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.cpp new file mode 100644 index 000000000000..5d29845f0d99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.cpp @@ -0,0 +1,24 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + + +LogToStdout::LogToStdout(int flags) + : + Logger::Listener(flags) +{ +} + +void LogToStdout::Report(std::string const& message) +{ + std::cout << message.c_str() << std::flush; +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.h new file mode 100644 index 000000000000..0b390b649275 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStdout.h @@ -0,0 +1,24 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +class GTE_IMPEXP LogToStdout : public Logger::Listener +{ +public: + LogToStdout(int flags); + +private: + virtual void Report(std::string const& message); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.cpp new file mode 100644 index 000000000000..28b47ac2fe1a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.cpp @@ -0,0 +1,39 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + + +LogToStringArray::LogToStringArray(std::string const& name, int flags) + : + Logger::Listener(flags), + mName(name) +{ +} + +std::string const& LogToStringArray::GetName() const +{ + return mName; +} + +std::vector const& LogToStringArray::GetMessages() const +{ + return mMessages; +} + +std::vector& LogToStringArray::GetMessages() +{ + return mMessages; +} + +void LogToStringArray::Report(std::string const& message) +{ + mMessages.push_back(message); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.h new file mode 100644 index 000000000000..1fa0fe2092b6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogToStringArray.h @@ -0,0 +1,32 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +class GTE_IMPEXP LogToStringArray : public Logger::Listener +{ +public: + LogToStringArray(std::string const& name, int flags); + + std::string const& GetName() const; + std::vector const& GetMessages() const; + std::vector& GetMessages(); + +private: + virtual void Report(std::string const& message); + + std::string mName; + std::vector mMessages; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.cpp new file mode 100644 index 000000000000..11190b18503f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.cpp @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + + +Logger::Logger(char const* file, char const* function, int line, + std::string const& message) +{ + mMessage = + "File: " + std::string(file) + "\n" + + "Func: " + std::string(function) + "\n" + +#ifdef _MSC_VER // std::to_string not available on some platforms + "Line: " + std::to_string(line) + "\n" + +#endif + message + "\n\n"; +} + +void Logger::Assertion() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_ASSERTION) + { + listener->Assertion(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Error() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_ERROR) + { + listener->Error(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Warning() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_WARNING) + { + listener->Warning(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Information() +{ + msMutex.lock(); + for (auto listener : msListeners) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_INFORMATION) + { + listener->Information(mMessage); + } + } + msMutex.unlock(); +} + +void Logger::Subscribe(Listener* listener) +{ + msMutex.lock(); + msListeners.insert(listener); + msMutex.unlock(); +} + +void Logger::Unsubscribe(Listener* listener) +{ + msMutex.lock(); + msListeners.erase(listener); + msMutex.unlock(); +} + + + +// Logger::Listener + +Logger::Listener::~Listener() +{ +} + +Logger::Listener::Listener(int flags) + : + mFlags(flags) +{ +} + +int Logger::Listener::GetFlags() const +{ + return mFlags; +} + +void Logger::Listener::Assertion(std::string const& message) +{ + Report("\nGTE ASSERTION:\n" + message); +} + +void Logger::Listener::Error(std::string const& message) +{ + Report("\nGTE ERROR:\n" + message); +} + +void Logger::Listener::Warning(std::string const& message) +{ + Report("\nGTE WARNING:\n" + message); +} + +void Logger::Listener::Information(std::string const& message) +{ + Report("\nGTE INFORMATION:\n" + message); +} + +void Logger::Listener::Report(std::string const&) +{ + // Stub for derived classes. +} + + +std::mutex Logger::msMutex; +std::set Logger::msListeners; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.h new file mode 100644 index 000000000000..aa574cd29a8f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteLogger.h @@ -0,0 +1,110 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Uncomment this to turn off the logging system. The macros LogAssert, +// LogError, LogWarning, and LogInformation expand to nothing. (Do this for +// optimal performance.) +//#define GTE_NO_LOGGER + +namespace gte +{ + +class GTE_IMPEXP Logger +{ +public: + // Construction. The Logger object is designed to exist only for a + // single-line call. A string is generated from the input parameters and + // is used for reporting. + Logger(char const* file, char const* function, int line, + std::string const& message); + + // Notify current listeners about the logged information. + void Assertion(); + void Error(); + void Warning(); + void Information(); + + // Listeners subscribe to Logger to receive message strings. + class Listener + { + public: + enum + { + LISTEN_FOR_NOTHING = 0x00000000, + LISTEN_FOR_ASSERTION = 0x00000001, + LISTEN_FOR_ERROR = 0x00000002, + LISTEN_FOR_WARNING = 0x00000004, + LISTEN_FOR_INFORMATION = 0x00000008, + LISTEN_FOR_ALL = 0xFFFFFFFF + }; + + // Construction and destruction. + virtual ~Listener(); + Listener(int flags = LISTEN_FOR_NOTHING); + + // What the listener wants to hear. + int GetFlags() const; + + // Handlers for the messages received from the logger. + void Assertion(std::string const& message); + void Error(std::string const& message); + void Warning(std::string const& message); + void Information(std::string const& message); + + private: + virtual void Report(std::string const& message); + + int mFlags; + }; + + static void Subscribe(Listener* listener); + static void Unsubscribe(Listener* listener); + +private: + std::string mMessage; + + static std::mutex msMutex; + static std::set msListeners; +}; + +} + + +#if !defined(GTE_NO_LOGGER) + +#define LogAssert(condition, message) \ + if (!(condition)) \ + { \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Assertion(); \ + } + +#define LogError(message) \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Error() + +#define LogWarning(message) \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Warning() + +#define LogInformation(message) \ + gte::Logger(__FILE__, __FUNCTION__, __LINE__, message).Information() + +#else + +// No logging of assertions, warnings, errors, or information. +#define LogAssert(condition, message) +#define LogError(message) +#define LogWarning(message) +#define LogInformation(message) + +#endif + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteMinHeap.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteMinHeap.h new file mode 100644 index 000000000000..d44bc8d78b58 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteMinHeap.h @@ -0,0 +1,442 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// A min-heap is a binary tree whose nodes have weights and with the +// constraint that the weight of a parent node is less than or equal to the +// weights of its children. This data structure may be used as a priority +// queue. If the std::priority_queue interface suffices for your needs, use +// that instead. However, for some geometric algorithms, that interface is +// insufficient for optimal performance. For example, if you have a polyline +// vertices that you want to decimate, each vertex's weight depends on its +// neighbors' locations. If the minimum-weight vertex is removed from the +// min-heap, the neighboring vertex weights must be updated--something that +// is O(1) time when you store the vertices as a doubly linked list. The +// neighbors are already in the min-heap, so modifying their weights without +// removing then from--and then reinserting into--the min-heap requires they +// must be moved to their proper places to restore the invariant of the +// min-heap. With std::priority_queue, you have no direct access to the +// modified vertices, forcing you to search for those vertices, remove them, +// update their weights, and re-insert them. The min-heap implementation here +// does support the update without removal and reinsertion. +// +// The ValueType represents the weight and it must support comparisons +// "<" and "<=". Additional information can be stored in the min-heap for +// convenient access; this is stored as the KeyType. In the (open) polyline +// decimation example, the KeyType is a structure that stores indices to +// a vertex and its neighbors. The following code illustrates the creation +// and use of the min-heap. The Weight() function is whatever you choose to +// guide which vertices are removed first from the polyline. +// +// struct Vertex { int previous, current, next; }; +// int numVertices = ; +// std::vector> positions(numVertices); +// ; +// MinHeap minHeap(numVertices); +// std::vector::Record*> records(numVertices); +// for (int i = 0; i < numVertices; ++i) +// { +// Vertex vertex; +// vertex.previous = (i + numVertices - 1) % numVertices; +// vertex.current = i; +// vertex.next = (i + 1) % numVertices; +// records[i] = minHeap.Insert(vertex, Weight(positions, vertex)); +// } +// +// while (minHeap.GetNumElements() >= 2) +// { +// Vertex vertex; +// Real weight; +// minHeap.Remove(vertex, weight); +// ; +// +// // Remove 'vertex' from the doubly linked list. +// Vertex& vp = records[vertex.previous]->key; +// Vertex& vc = records[vertex.current]->key; +// Vertex& vn = records[vertex.next]->key; +// vp.next = vc.next; +// vn.previous = vc.previous; +// +// // Update the neighbors' weights in the min-heap. +// minHeap.Update(records[vertex.previous], Weight(positions, vp)); +// minHeap.Update(records[vertex.next], Weight(positions, vn)); +// } + +namespace gte +{ + +template +class MinHeap +{ +public: + struct Record + { + KeyType key; + ValueType value; + int index; + }; + + // Construction. The record 'value' members are uninitialized for native + // types chosen for ValueType. If ValueType is of class type, then the + // default constructor is used to set the 'value' members. + MinHeap(MinHeap const& minHeap); + MinHeap(int maxElements = 0); + + // Assignment. + MinHeap& operator=(MinHeap const& minHeap); + + // Clear the min-heap so that it has the specified max elements, + // mNumElements is zero, and mPointers are set to the natural ordering + // of mRecords. + void Reset(int maxElements); + + // Get the remaining number of elements in the min-heap. This number is + // in the range {0..maxElements}. + inline int GetNumElements() const; + + // Get the root of the min-heap. The return value is 'true' whenever the + // min-heap is not empty. This function reads the root but does not + // remove the element from the min-heap. + inline bool GetMinimum(KeyType& key, ValueType& value) const; + + // Insert into the min-heap the 'value' that corresponds to the 'key'. + // The return value is a pointer to the heap record that stores a copy of + // 'value', and the pointer value is constant for the life of the min-heap. + // If you must update a member of the min-heap, say, as illustrated in the + // polyline decimation example, pass the pointer to Update: + // auto* valueRecord = minHeap.Insert(key, value); + // ; + // minHeap.Update(valueRecord, newValue). + Record* Insert(KeyType const& key, ValueType const& value); + + // Remove the root of the heap and return its 'key' and 'value members. + // The root contains the minimum value of all heap elements. The return + // value is 'true' whenever the min-heap was not empty before the Remove + // call. + bool Remove(KeyType& key, ValueType& value); + + // The value of a heap record must be modified through this function call. + // The side effect is that the heap is updated accordingly to restore the + // data structure to a min-heap. The input 'record' should be a pointer + // returned by Insert(value); see the comments for the Insert() function. + void Update(Record* record, ValueType const& value); + + // Support for debugging. The functions test whether the data structure + // is a valid min-heap. + bool IsValid() const; + +private: + // A 2-level storage system is used. The pointers have two roles. + // Firstly, they are unique to each inserted value in order to support + // the Update() capability of the min-heap. Secondly, they avoid + // potentially expensive copying of Record objects as sorting occurs in + // the heap. + int mNumElements; + std::vector mRecords; + std::vector mPointers; +}; + + +template +MinHeap::MinHeap(int maxElements) +{ + Reset(maxElements); +} + +template +MinHeap::MinHeap(MinHeap const& minHeap) +{ + *this = minHeap; +} + +template +MinHeap& MinHeap::operator=(MinHeap const& minHeap) +{ + mNumElements = minHeap.mNumElements; + mRecords = minHeap.mRecords; + mPointers.resize(minHeap.mPointers.size()); + for (auto& record : mRecords) + { + mPointers[record.index] = &record; + } + return *this; +} + +template +void MinHeap::Reset(int maxElements) +{ + mNumElements = 0; + if (maxElements > 0) + { + mRecords.resize(maxElements); + mPointers.resize(maxElements); + for (int i = 0; i < maxElements; ++i) + { + mPointers[i] = &mRecords[i]; + mPointers[i]->index = i; + } + } + else + { + mRecords.clear(); + mPointers.clear(); + } +} + +template +inline int MinHeap::GetNumElements() const +{ + return mNumElements; +} + +template +inline bool MinHeap::GetMinimum(KeyType& key, ValueType& value) const +{ + if (mNumElements > 0) + { + key = mPointers[0]->key; + value = mPointers[0]->value; + return true; + } + else + { + return false; + } +} + +template +typename MinHeap::Record* +MinHeap::Insert(KeyType const& key, ValueType const& value) +{ + // Return immediately when the heap is full. + if (mNumElements == static_cast(mRecords.size())) + { + return nullptr; + } + + // Store the input information in the last heap record, which is the last + // leaf in the tree. + int child = mNumElements++; + Record * record = mPointers[child]; + record->key = key; + record->value = value; + + // Propagate the information toward the root of the tree until it reaches + // its correct position, thus restoring the tree to a valid heap. + while (child > 0) + { + int parent = (child - 1) / 2; + if (mPointers[parent]->value <= value) + { + // The parent has a value smaller than or equal to the child's + // value, so we now have a valid heap. + break; + } + + // The parent has a larger value than the child's value. Swap the + // parent and child: + + // Move the parent into the child's slot. + mPointers[child] = mPointers[parent]; + mPointers[child]->index = child; + + // Move the child into the parent's slot. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + child = parent; + } + + return mPointers[child]; +} + +template +bool MinHeap::Remove(KeyType& key, ValueType& value) +{ + // Return immediately when the heap is empty. + if (mNumElements == 0) + { + return false; + } + + // Get the information from the root of the heap. + Record* root = mPointers[0]; + key = root->key; + value = root->value; + + // Restore the tree to a heap. Abstractly, record is the new root of + // the heap. It is moved down the tree via parent-child swaps until it + // is in a location that restores the tree to a heap. + int last = --mNumElements; + Record* record = mPointers[last]; + int parent = 0, child = 1; + while (child <= last) + { + if (child < last) + { + // Select the child with smallest value to be the one that is + // swapped with the parent, if necessary. + int childP1 = child + 1; + if (mPointers[childP1]->value < mPointers[child]->value) + { + child = childP1; + } + } + + if (record->value <= mPointers[child]->value) + { + // The tree is now a heap. + break; + } + + // Move the child into the parent's slot. + mPointers[parent] = mPointers[child]; + mPointers[parent]->index = parent; + + parent = child; + child = 2 * child + 1; + } + + // The previous 'last' record was moved to the root and propagated down + // the tree to its final resting place, restoring the tree to a heap. + // The slot mPointers[parent] is that resting place. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + // The old root record must not be lost. Attach it to the slot that + // contained the old last record. + mPointers[last] = root; + mPointers[last]->index = last; + return true; +} + +template +void MinHeap::Update(Record* record, ValueType const& value) +{ + // Return immediately on invalid record. + if (!record) + { + return; + } + + int parent, child, childP1, maxChild; + + if (record->value < value) + { + record->value = value; + + // The new value is larger than the old value. Propagate it toward + // the leaves. + parent = record->index; + child = 2 * parent + 1; + while (child < mNumElements) + { + // At least one child exists. Locate the one of maximum value. + childP1 = child + 1; + if (childP1 < mNumElements) + { + // Two children exist. + if (mPointers[child]->value <= mPointers[childP1]->value) + { + maxChild = child; + } + else + { + maxChild = childP1; + } + } + else + { + // One child exists. + maxChild = child; + } + + if (value <= mPointers[maxChild]->value) + { + // The new value is in the correct place to restore the tree + // to a heap. + break; + } + + // The child has a larger value than the parent's value. Swap + // the parent and child: + + // Move the child into the parent's slot. + mPointers[parent] = mPointers[maxChild]; + mPointers[parent]->index = parent; + + // Move the parent into the child's slot. + mPointers[maxChild] = record; + mPointers[maxChild]->index = maxChild; + + parent = maxChild; + child = 2 * parent + 1; + } + } + else if (value < record->value) + { + record->value = value; + + // The new weight is smaller than the old weight. Propagate it + // toward the root. + child = record->index; + while (child > 0) + { + // A parent exists. + parent = (child - 1) / 2; + + if (mPointers[parent]->value <= value) + { + // The new value is in the correct place to restore the tree + // to a heap. + break; + } + + // The parent has a smaller value than the child's value. Swap + // the child and parent: + + // Move the parent into the child's slot. + mPointers[child] = mPointers[parent]; + mPointers[child]->index = child; + + // Move the child into the parent's slot. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + child = parent; + } + } +} + +template +bool MinHeap::IsValid() const +{ + for (int child = 0; child < mNumElements; ++child) + { + int parent = (child - 1) / 2; + if (parent > 0) + { + if (mPointers[child]->value < mPointers[parent]->value) + { + return false; + } + + if (mPointers[parent]->index != parent) + { + return false; + } + } + } + + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteRangeIteration.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteRangeIteration.h new file mode 100644 index 000000000000..28a915b3161b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteRangeIteration.h @@ -0,0 +1,69 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// For information on range-based for-loops, see +// http://en.cppreference.com/w/cpp/language/range-for + +namespace gte +{ + +// The function gte::reverse supports reverse iteration in range-based +// for-loops using the auto keyword. For example, +// +// std::vector numbers(4); +// int i = 0; +// for (auto& number : numbers) +// { +// number = i++; +// std::cout << number << ' '; +// } +// // Output: 0 1 2 3 +// +// for (auto& number : gte::reverse(numbers)) +// { +// std::cout << number << ' '; +// } +// // Output: 3 2 1 0 + +template +class ReversalObject +{ +public: + ReversalObject(Iterator begin, Iterator end) + : + mBegin(begin), + mEnd(end) + { + } + + Iterator begin() const { return mBegin; } + Iterator end() const { return mEnd; } + +private: + Iterator mBegin, mEnd; +}; + +template +< + typename Iterable, + typename Iterator = decltype(std::begin(std::declval())), + typename ReverseIterator = std::reverse_iterator +> +ReversalObject reverse(Iterable&& range) +{ + return ReversalObject( + ReverseIterator(std::end(range)), + ReverseIterator(std::begin(range))); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteSharedPtrCompare.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteSharedPtrCompare.h new file mode 100644 index 000000000000..e076a86cf2d6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteSharedPtrCompare.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include + +// Comparison operators for std::shared_ptr objects. The type T must implement +// comparison operators. You must be careful when managing containers whose +// ordering is based on std::shared_ptr comparisons. The underlying objects +// can change, which invalidates the container ordering. If objects do not +// change while the container persists, these are safe to use. +// +// NOTE: std::shared_ptr already has comparison operators, but these +// compare pointer values instead of comparing the objects referenced by the +// pointers. If a container sorted using std::shared_ptr is created for +// two different executions of a program, the object ordering implied by the +// pointer ordering can differ. This might be undesirable for reproducibility +// of results between executions. + +namespace gte +{ + // sp0 == sp1 + template + struct SharedPtrEQ + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return (sp0 ? (sp1 ? *sp0 == *sp1 : false) : !sp1); + } + }; + + // sp0 != sp1 + template + struct SharedPtrNEQ + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrEQ()(sp0, sp1); + } + }; + + // sp0 < sp1 + template + struct SharedPtrLT + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return (sp1 ? (!sp0 || *sp0 < *sp1) : false); + } + }; + + // sp0 <= sp1 + template + struct SharedPtrLTE + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrLT()(sp1, sp0); + } + }; + + // sp0 > sp1 + template + struct SharedPtrGT + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return SharedPtrLT()(sp1, sp0); + } + }; + + // sp0 >= sp1 + template + struct SharedPtrGTE + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrLT()(sp0, sp1); + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeMap.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeMap.h new file mode 100644 index 000000000000..1edf007033fa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeMap.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class ThreadSafeMap +{ +public: + // Construction and destruction. + virtual ~ThreadSafeMap(); + ThreadSafeMap(); + + // All the operations are thread-safe. + bool HasElements() const; + bool Exists(Key key) const; + void Insert(Key key, Value value); + bool Remove(Key key, Value& value); + void RemoveAll(); + bool Get(Key key, Value& value) const; + void GatherAll(std::vector& values) const; + +protected: + std::map mMap; + mutable std::mutex mMutex; +}; + + +template +ThreadSafeMap::~ThreadSafeMap() +{ +} + +template +ThreadSafeMap::ThreadSafeMap() +{ +} + +template +bool ThreadSafeMap::HasElements() const +{ + bool hasElements; + mMutex.lock(); + { + hasElements = (mMap.size() > 0); + } + mMutex.unlock(); + return hasElements; +} + +template +bool ThreadSafeMap::Exists(Key key) const +{ + bool exists; + mMutex.lock(); + { + exists = (mMap.find(key) != mMap.end()); + } + mMutex.unlock(); + return exists; +} + +template +void ThreadSafeMap::Insert(Key key, Value value) +{ + mMutex.lock(); + { + mMap[key] = value; + } + mMutex.unlock(); +} + +template +bool ThreadSafeMap::Remove(Key key, Value& value) +{ + bool exists; + mMutex.lock(); + { + auto iter = mMap.find(key); + if (iter != mMap.end()) + { + value = iter->second; + mMap.erase(iter); + exists = true; + } + else + { + exists = false; + } + } + mMutex.unlock(); + return exists; +} + +template +void ThreadSafeMap::RemoveAll() +{ + mMutex.lock(); + { + mMap.clear(); + } + mMutex.unlock(); +} + +template +bool ThreadSafeMap::Get(Key key, Value& value) const +{ + bool exists; + mMutex.lock(); + { + auto iter = mMap.find(key); + if (iter != mMap.end()) + { + value = iter->second; + exists = true; + } + else + { + exists = false; + } + } + mMutex.unlock(); + return exists; +} + +template +void ThreadSafeMap::GatherAll(std::vector& values) const +{ + mMutex.lock(); + { + if (mMap.size() > 0) + { + values.resize(mMap.size()); + auto viter = values.begin(); + for (auto const& m : mMap) + { + *viter++ = m.second; + } + } + else + { + values.clear(); + } + } + mMutex.unlock(); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeQueue.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeQueue.h new file mode 100644 index 000000000000..570e43bfe7f8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteThreadSafeQueue.h @@ -0,0 +1,116 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class ThreadSafeQueue +{ +public: + // Construction and destruction. + virtual ~ThreadSafeQueue(); + ThreadSafeQueue(size_t maxNumElements = 0); + + // All the operations are thread-safe. + size_t GetMaxNumElements() const; + size_t GetNumElements() const; + bool Push(Element const& element); + bool Pop(Element& element); + +protected: + size_t mMaxNumElements; + std::queue mQueue; + mutable std::mutex mMutex; +}; + + +template +ThreadSafeQueue::~ThreadSafeQueue() +{ +} + +template +ThreadSafeQueue::ThreadSafeQueue(size_t maxNumElements) + : + mMaxNumElements(maxNumElements) +{ +} + +template +size_t ThreadSafeQueue::GetMaxNumElements() const +{ + size_t maxNumElements; + mMutex.lock(); + { + maxNumElements = mMaxNumElements; + } + mMutex.unlock(); + return maxNumElements; +} + +template +size_t ThreadSafeQueue::GetNumElements() const +{ + size_t numElements; + mMutex.lock(); + { + numElements = mQueue.size(); + } + mMutex.unlock(); + return numElements; +} + +template +bool ThreadSafeQueue::Push(Element const& element) +{ + bool pushed; + mMutex.lock(); + { + if (mQueue.size() < mMaxNumElements) + { + mQueue.push(element); + pushed = true; + } + else + { + pushed = false; + } + } + mMutex.unlock(); + return pushed; +} + +template +bool ThreadSafeQueue::Pop(Element& element) +{ + bool popped; + mMutex.lock(); + { + if (mQueue.size() > 0) + { + element = mQueue.front(); + mQueue.pop(); + popped = true; + } + else + { + popped = false; + } + } + mMutex.unlock(); + return popped; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.cpp new file mode 100644 index 000000000000..051a851777f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.cpp @@ -0,0 +1,115 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +using namespace gte; + +#if defined(GTE_NEEDS_64_BIT_CLOCK) + +#include +#include + +Timer::Timer() + : + mFrequency(0), + mInitialTicks(0), + mInvFrequency(0.0) +{ + LARGE_INTEGER frequency = { 1 }, counter = { 0 }; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&counter); + mFrequency = static_cast(frequency.QuadPart); + mInitialTicks = static_cast(counter.QuadPart); + mInvFrequency = 1.0 / static_cast(mFrequency); +} + +int64_t Timer::GetNanoseconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + int64_t nanoseconds = static_cast(std::ceil(1000000000.0 * seconds)); + return nanoseconds; +} + +int64_t Timer::GetMicroseconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + int64_t microseconds = static_cast(std::ceil(1000000.0 * seconds)); + return microseconds; +} + +int64_t Timer::GetMilliseconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + int64_t milliseconds = static_cast(std::ceil(1000.0 * seconds)); + return milliseconds; +} + +double Timer::GetSeconds() const +{ + int64_t ticks = GetTicks(); + double seconds = mInvFrequency * static_cast(ticks); + return seconds; +} + +void Timer::Reset() +{ + LARGE_INTEGER counter = { 0 }; + QueryPerformanceCounter(&counter); + mInitialTicks = static_cast(counter.QuadPart); +} + +int64_t Timer::GetTicks() const +{ + LARGE_INTEGER counter = { 0 }; + QueryPerformanceCounter(&counter); + return static_cast(counter.QuadPart) - mInitialTicks; +} + +#else + +Timer::Timer() +{ + Reset(); +} + +int64_t Timer::GetNanoseconds() const +{ + auto currentTime = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + currentTime - mInitialTime).count(); +} + +int64_t Timer::GetMicroseconds() const +{ + auto currentTime = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + currentTime - mInitialTime).count(); +} + +int64_t Timer::GetMilliseconds() const +{ + auto currentTime = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + currentTime - mInitialTime).count(); +} + +double Timer::GetSeconds() const +{ + int64_t msecs = GetMilliseconds(); + return static_cast(msecs) / 1000.0; +} + +void Timer::Reset() +{ + mInitialTime = std::chrono::high_resolution_clock::now(); +} + +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.h new file mode 100644 index 000000000000..ca5c41fc5571 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteTimer.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +#if defined(__MSWINDOWS__) && _MSC_VER < 1900 +// MSVS 2013 has an implementation of std::chrono::high_resolution_clock +// that does not use a 64-bit clock (QueryPerformanceCounter). Instead, +// it appears to use a file-system clock that is 24 bits. To obtain +// accurate measurements, we need the 64-bit clock. However, MSVS 2015 +// does use a 64-bit clock. +#define GTE_NEEDS_64_BIT_CLOCK +#endif + +#if !defined(GTE_NEEDS_64_BIT_CLOCK) +#include +#endif + +namespace gte +{ + +class GTE_IMPEXP Timer +{ +public: + // Construction of a high-resolution timer (64-bit). + Timer(); + + // Get the current time relative to the initial time. + int64_t GetNanoseconds() const; + int64_t GetMicroseconds() const; + int64_t GetMilliseconds() const; + double GetSeconds() const; + + // Reset so that the current time is the initial time. + void Reset(); + +private: +#if defined(GTE_NEEDS_64_BIT_CLOCK) + // Internally use QueryPerformanceCounter. + int64_t GetTicks() const; + + int64_t mFrequency, mInitialTicks; + double mInvFrequency; +#else + std::chrono::high_resolution_clock::time_point mInitialTime; +#endif +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWeakPtrCompare.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWeakPtrCompare.h new file mode 100644 index 000000000000..1801137faecf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWeakPtrCompare.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include + +// Comparison operators for std::weak_ptr objects. The type T must implement +// comparison operators. You must be careful when managing containers whose +// ordering is based on std::weak_ptr comparisons. The underlying objects +// can change, which invalidates the container ordering. If objects do not +// change while the container persists, these are safe to use. + +namespace gte +{ + // wp0 == wp1 + template + struct WeakPtrEQ + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + auto sp0 = wp0.lock(), sp1 = wp1.lock(); + return (sp0 ? (sp1 ? *sp0 == *sp1 : false) : !sp1); + } + }; + + // wp0 != wp1 + template + struct WeakPtrNEQ + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrEQ()(wp0, wp1); + } + }; + + // wp0 < wp1 + template + struct WeakPtrLT + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + auto sp0 = wp0.lock(), sp1 = wp1.lock(); + return (sp1 ? (!sp0 || *sp0 < *sp1) : false); + } + }; + + // wp0 <= wp1 + template + struct WeakPtrLTE + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrLT()(wp1, wp0); + } + }; + + // wp0 > wp1 + template + struct WeakPtrGT + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return WeakPtrLT()(wp1, wp0); + } + }; + + // wp0 >= wp1 + template + struct WeakPtrGTE + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrLT()(wp0, wp1); + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.cpp new file mode 100644 index 000000000000..333f6fecacb1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.cpp @@ -0,0 +1,36 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +#include + +namespace gte +{ + +void Memcpy(void* target, void const* source, size_t count) +{ +#if defined(__MSWINDOWS__) + errno_t result = memcpy_s(target, count, source, count); + (void)result; // 0 on success +#else + memcpy(target, source, count); +#endif +} + +void Memcpy(wchar_t* target, wchar_t const* source, size_t count) +{ +#if defined(__MSWINDOWS__) + errno_t result = wmemcpy_s(target, count, source, count); + (void)result; // 0 on success +#else + wmemcpy(target, source, count); +#endif +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.h new file mode 100644 index 000000000000..23efe5c742db --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/LowLevel/GteWrapper.h @@ -0,0 +1,21 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Wrappers around platform-specific low-level library calls. + +namespace gte +{ + +void Memcpy(void* target, void const* source, size_t count); +void Memcpy(wchar_t* target, wchar_t const* source, size_t count); + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteACosEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteACosEstimate.h new file mode 100644 index 000000000000..9e90bf62fa54 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteACosEstimate.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Approximations to acos(x) of the form f(x) = sqrt(1-x)*p(x) +// where the polynomial p(x) of degree D minimizes the quantity +// maximum{|acos(x)/sqrt(1-x) - p(x)| : x in [0,1]} over all +// polynomials of degree D. + +namespace gte +{ + +template +class ACosEstimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ACosEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real x); + inline static Real Evaluate(degree<2>, Real x); + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<4>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<6>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<8>, Real x); +}; + + +template +template +inline Real ACosEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +inline Real ACosEstimate::Evaluate(degree<1>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG1_C1; + poly = (Real)GTE_C_ACOS_DEG1_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<2>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG2_C2; + poly = (Real)GTE_C_ACOS_DEG2_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG2_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<3>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG3_C3; + poly = (Real)GTE_C_ACOS_DEG3_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG3_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG3_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<4>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG4_C4; + poly = (Real)GTE_C_ACOS_DEG4_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<5>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG5_C5; + poly = (Real)GTE_C_ACOS_DEG5_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<6>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG6_C6; + poly = (Real)GTE_C_ACOS_DEG6_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<7>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG7_C7; + poly = (Real)GTE_C_ACOS_DEG7_C6 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + +template +inline Real ACosEstimate::Evaluate(degree<8>, Real x) +{ + Real poly; + poly = (Real)GTE_C_ACOS_DEG8_C8; + poly = (Real)GTE_C_ACOS_DEG8_C7 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C6 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteASinEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteASinEstimate.h new file mode 100644 index 000000000000..5bd83bb95dbb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteASinEstimate.h @@ -0,0 +1,40 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Approximations to asin(x) of the form f(x) = pi/2 - sqrt(1-x)*p(x) +// where the polynomial p(x) of degree D minimizes the quantity +// maximum{|acos(x)/sqrt(1-x) - p(x)| : x in [0,1]} over all +// polynomials of degree D. We use the identity asin(x) = pi/2 - acos(x). + +namespace gte +{ + +template +class ASinEstimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ASinEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); +}; + + +template +template +inline Real ASinEstimate::Degree(Real x) +{ + return (Real)GTE_C_HALF_PI - ACosEstimate::Degree(x); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteATanEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteATanEstimate.h new file mode 100644 index 000000000000..2492610619de --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteATanEstimate.h @@ -0,0 +1,159 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to atan(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(1) = atan(1) = pi/4. It minimizes the quantity +// maximum{|atan(x) - p(x)| : x in [-1,1]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class ATanEstimate +{ +public: + // The input constraint is x in [-1,1]. For example, + // float x; // in [-1,1] + // float result = ATanEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used via + // the identities atan(x) = pi/2 - atan(1/x) for x > 0, and + // atan(x) = -pi/2 - atan(1/x) for x < 0. For example, + // float x; // x any real number + // float result = ATanEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<9>, Real x); + inline static Real Evaluate(degree<11>, Real x); + inline static Real Evaluate(degree<13>, Real x); +}; + + +template +template +inline Real ATanEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real ATanEstimate::DegreeRR(Real x) +{ + if (std::abs(x) <= (Real)1) + { + return Degree(x); + } + else if (x > (Real)1) + { + return (Real)GTE_C_HALF_PI - Degree((Real)1 / x); + } + else + { + return (Real)-GTE_C_HALF_PI - Degree((Real)1 / x); + } +} + +template +inline Real ATanEstimate::Evaluate(degree<3>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG3_C1; + poly = (Real)GTE_C_ATAN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<5>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG5_C2; + poly = (Real)GTE_C_ATAN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<7>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG7_C3; + poly = (Real)GTE_C_ATAN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<9>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG9_C4; + poly = (Real)GTE_C_ATAN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<11>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG11_C5; + poly = (Real)GTE_C_ATAN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real ATanEstimate::Evaluate(degree<13>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG13_C6; + poly = (Real)GTE_C_ATAN_DEG13_C5 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C4 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAlignedBox.h new file mode 100644 index 000000000000..723650e84c26 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAlignedBox.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The box is aligned with the standard coordinate axes, which allows us to +// represent it using minimum and maximum values along each axis. Some +// algorithms prefer the centered representation that is used for oriented +// boxes. The center is C and the extents are the half-lengths in each +// coordinate-axis direction. + +namespace gte +{ + +template +class AlignedBox +{ +public: + // Construction and destruction. The default constructor sets the + // minimum values to -1 and the maximum values to +1. + AlignedBox(); + + // Please ensure that inMin[i] <= inMax[i] for all i. + AlignedBox(Vector const& inMin, Vector const& inMax); + + // Compute the centered representation. NOTE: If you set the minimum + // and maximum values, compute C and extents, and then recompute the + // minimum and maximum values, the numerical round-off errors can lead to + // results different from what you started with. + void GetCenteredForm(Vector& center, Vector& extent) + const; + + // Public member access. It is required that min[i] <= max[i]. + Vector min, max; + +public: + // Comparisons to support sorted containers. + bool operator==(AlignedBox const& box) const; + bool operator!=(AlignedBox const& box) const; + bool operator< (AlignedBox const& box) const; + bool operator<=(AlignedBox const& box) const; + bool operator> (AlignedBox const& box) const; + bool operator>=(AlignedBox const& box) const; +}; + +// Template aliases for convenience. +template +using AlignedBox2 = AlignedBox<2, Real>; + +template +using AlignedBox3 = AlignedBox<3, Real>; + + +template +AlignedBox::AlignedBox() +{ + for (int i = 0; i < N; ++i) + { + min[i] = (Real)-1; + max[i] = (Real)+1; + } +} + +template +AlignedBox::AlignedBox(Vector const& inMin, + Vector const& inMax) +{ + for (int i = 0; i < N; ++i) + { + min[i] = inMin[i]; + max[i] = inMax[i]; + } +} + +template +void AlignedBox::GetCenteredForm(Vector& center, + Vector& extent) const +{ + center = (max + min) * (Real)0.5; + extent = (max - min) * (Real)0.5; +} + +template +bool AlignedBox::operator==(AlignedBox const& box) const +{ + return min == box.min && max == box.max; +} + +template +bool AlignedBox::operator!=(AlignedBox const& box) const +{ + return !operator==(box); +} + +template +bool AlignedBox::operator<(AlignedBox const& box) const +{ + if (min < box.min) + { + return true; + } + + if (min > box.min) + { + return false; + } + + return max < box.max; +} + +template +bool AlignedBox::operator<=(AlignedBox const& box) const +{ + return operator<(box) || operator==(box); +} + +template +bool AlignedBox::operator>(AlignedBox const& box) const +{ + return !operator<=(box); +} + +template +bool AlignedBox::operator>=(AlignedBox const& box) const +{ + return !operator<(box); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCircle2.h new file mode 100644 index 000000000000..568d2aa80cfe --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCircle2.h @@ -0,0 +1,101 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Least-squares fit of a circle to a set of points. A successful fit is +// indicated by the return value of 'true'. The return value is the number +// of iterations used. If the number is the maximum, you can either accept +// the result or try increasing the maximum number and calling the function +// again. TODO: Currently, the test for terminating the algorithm is exact +// (diff == (0,0)). Expose an epsilon for this? +// +// If initialCenterIsAverage is set to 'true', the initial guess for the +// circle center is the average of the data points. If the data points are +// clustered along a small arc, ApprCircle2 is very slow to converge. If +// initialCenterIsAverage is set to 'false', the initial guess for the +// circle center is computed using a least-squares estimate of the +// coefficients for a quadratic equation that represents a circle. This +// approach tends to converge rapidly. + +namespace gte +{ + +template +class ApprCircle2 +{ +public: + unsigned int operator()(int numPoints, Vector2 const* points, + unsigned int maxIterations, bool initialCenterIsAverage, + Circle2& circle); +}; + + +template +unsigned int ApprCircle2::operator()(int numPoints, + Vector2 const* points, unsigned int maxIterations, + bool initialCenterIsAverage, Circle2& circle) +{ + // Compute the average of the data points. + Vector2 average = points[0]; + for (int i = 1; i < numPoints; ++i) + { + average += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + average *= invNumPoints; + + // The initial guess for the center. + if (initialCenterIsAverage) + { + circle.center = average; + } + else + { + ApprQuadraticCircle2()(numPoints, points, circle); + } + + unsigned int iteration; + for (iteration = 0; iteration < maxIterations; ++iteration) + { + // Update the iterates. + Vector2 current = circle.center; + + // Compute average L, dL/da, dL/db. + Real lenAverage = (Real)0; + Vector2 derLenAverage = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - circle.center; + Real length = Length(diff); + if (length >(Real)0) + { + lenAverage += length; + Real invLength = ((Real)1) / length; + derLenAverage -= invLength * diff; + } + } + lenAverage *= invNumPoints; + derLenAverage *= invNumPoints; + + circle.center = average + lenAverage * derLenAverage; + circle.radius = lenAverage; + + Vector2 diff = circle.center - current; + if (diff == Vector2::Zero()) + { + break; + } + } + + return ++iteration; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCone3.h new file mode 100644 index 000000000000..de09603371eb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCone3.h @@ -0,0 +1,340 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.2 (2018/10/30) + +#pragma once + +#include +#include +#include +#include + +// The cone vertex is V, the unit-length axis direction is U and the +// cone angle is A in (0,pi/2). The cone is defined algebraically by +// those points X for which +// Dot(U,X-V)/Length(X-V) = cos(A) +// This can be written as a quadratic equation +// (V-X)^T * (cos(A)^2 - U * U^T) * (V-X) = 0 +// with the implicit constraint that Dot(U, X-V) > 0 (X is on the +// "positive" cone). Define W = U/cos(A), so Length(W) > 1 and +// F(X;V,W) = (V-X)^T * (I - W * W^T) * (V-X) = 0 +// The nonlinear least squares fitting of points {X[i]}_{i=0}^{n-1} +// computes V and W to minimize the error function +// E(V,W) = sum_{i=0}^{n-1} F(X[i];V,W)^2 +// I recommend using the Gauss-Newton minimizer when your cone points +// are truly nearly a cone; otherwise, try the Levenberg-Marquardt +// minimizer. +// +// The mathematics used in this implementation are found in +// http://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf +// In particular, the details for choosing an initial cone for fitting +// are somewhat complicated. + +namespace gte +{ + template + class ApprCone3 + { + public: + ApprCone3() + : + mNumPoints(0), + mPoints(nullptr) + { + // F[i](V,W) = D^T * (I - W * W^T) * D, D = V - X[i], P = (V,W) + mFFunction = [this](GVector const& P, GVector& F) + { + Vector<3, Real> V = { P[0], P[1], P[2] }; + Vector<3, Real> W = { P[3], P[4], P[5] }; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> delta = V - mPoints[i]; + Real deltaDotW = Dot(delta, W); + F[i] = Dot(delta, delta) - deltaDotW * deltaDotW; + } + }; + + // dF[i]/dV = 2 * (D - Dot(W, D) * W) + // dF[i]/dW = -2 * Dot(W, D) * D + mJFunction = [this](GVector const& P, GMatrix& J) + { + Vector<3, Real> V = { P[0], P[1], P[2] }; + Vector<3, Real> W = { P[3], P[4], P[5] }; + for (int row = 0; row < mNumPoints; ++row) + { + Vector<3, Real> delta = V - mPoints[row]; + Real deltaDotW = Dot(delta, W); + Vector<3, Real> temp0 = delta - deltaDotW * W; + Vector<3, Real> temp1 = deltaDotW * delta; + for (int col = 0; col < 3; ++col) + { + J(row, col) = (Real)2 * temp0[col]; + J(row, col + 3) = (Real)-2 * temp1[col]; + } + } + }; + } + + // If you want to specify that coneVertex, coneAxis and coneAngle + // are the initial guesses for the minimizer, set the parameter + // useConeInputAsInitialGuess to 'true'. If you want the function + // to compute initial guesses, set that parameter to 'false'. + // A Gauss-Newton minimizer is used to fit a cone using nonlinear + // least-squares. The fitted cone is returned in coneVertex, + // coneAxis and coneAngle. See GteGaussNewtonMinimizer.h for a + // description of the least-squares algorithm and the parameters + // that it requires. + typename GaussNewtonMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + bool useConeInputAsInitialGuess, + Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle) + { + mNumPoints = numPoints; + mPoints = points; + GaussNewtonMinimizer minimizer(6, mNumPoints, mFFunction, mJFunction); + + Real coneCosAngle; + if (useConeInputAsInitialGuess) + { + Normalize(coneAxis); + coneCosAngle = std::cos(coneAngle); + } + else + { + ComputeInitialCone(coneVertex, coneAxis, coneCosAngle); + } + + // The initial guess for the cone vertex. + GVector initial(6); + initial[0] = coneVertex[0]; + initial[1] = coneVertex[1]; + initial[2] = coneVertex[2]; + + // The initial guess for the weighted cone axis. + initial[3] = coneAxis[0] / coneCosAngle; + initial[4] = coneAxis[1] / coneCosAngle; + initial[5] = coneAxis[2] / coneCosAngle; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance); + + // No test is made for result.converged so that we return some + // estimates of the cone. The caller can decide how to respond + // when result.converged is false. + for (int i = 0; i < 3; ++i) + { + coneVertex[i] = result.minLocation[i]; + coneAxis[i] = result.minLocation[i + 3]; + } + + // We know that coneCosAngle will be nonnegative. The std::min + // call guards against rounding errors leading to a number + // slightly larger than 1. The clamping ensures std::acos will + // not return a NaN. + coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1); + coneAngle = std::acos(coneCosAngle); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + // The parameters coneVertex, coneAxis and coneAngle are in/out + // variables. The caller must provide initial guesses for these. + // The function estimates the cone parameters and returns them. See + // GteGaussNewtonMinimizer.h for a description of the least-squares + // algorithm and the parameters that it requires. + typename LevenbergMarquardtMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments, + bool useConeInputAsInitialGuess, + Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle) + { + mNumPoints = numPoints; + mPoints = points; + LevenbergMarquardtMinimizer minimizer(6, mNumPoints, mFFunction, mJFunction); + + Real coneCosAngle; + if (useConeInputAsInitialGuess) + { + Normalize(coneAxis); + coneCosAngle = std::cos(coneAngle); + } + else + { + ComputeInitialCone(coneVertex, coneAxis, coneCosAngle); + } + + // The initial guess for the cone vertex. + GVector initial(6); + initial[0] = coneVertex[0]; + initial[1] = coneVertex[1]; + initial[2] = coneVertex[2]; + + // The initial guess for the weighted cone axis. + initial[3] = coneAxis[0] / coneCosAngle; + initial[4] = coneAxis[1] / coneCosAngle; + initial[5] = coneAxis[2] / coneCosAngle; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments); + + // No test is made for result.converged so that we return some + // estimates of the cone. The caller can decide how to respond + // when result.converged is false. + for (int i = 0; i < 3; ++i) + { + coneVertex[i] = result.minLocation[i]; + coneAxis[i] = result.minLocation[i + 3]; + } + + // We know that coneCosAngle will be nonnegative. The std::min + // call guards against rounding errors leading to a number + // slightly larger than 1. The clamping ensures std::acos will + // not return a NaN. + coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1); + coneAngle = std::acos(coneCosAngle); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + private: + void ComputeInitialCone(Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneCosAngle) + { + // Compute the average of the sample points. + Vector<3, Real> center{ (Real)0, (Real)0, (Real)0 }; + Real const invNumPoints = (Real)1 / (Real)mNumPoints; + for (int i = 0; i < mNumPoints; ++i) + { + center += mPoints[i]; + } + center *= invNumPoints; + + // The cone axis is estimated from ZZTZ (see the PDF). + coneAxis = { (Real)0, (Real)0, (Real)0 }; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + coneAxis += Dot(diff, diff) * diff; + } + coneAxis *= invNumPoints; + Normalize(coneAxis); + + // Compute the averages of powers and products of powers of + // a[i] = Dot(U,X[i]-C) and b[i] = Dot(X[i]-C,X[i]-C). + Real c10 = (Real)0, c20 = (Real)0, c30 = (Real)0, c01 = (Real)0; + Real c02 = (Real)0, c11 = (Real)0, c21 = (Real)0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + Real ai = Dot(coneAxis, diff); + Real aisqr = ai * ai; + Real bi = Dot(diff, diff); + c10 += ai; + c20 += aisqr; + c30 += aisqr * ai; + c01 += bi; + c02 += bi * bi; + c11 += ai * bi; + c21 += aisqr * bi; + } + c10 *= invNumPoints; + c20 *= invNumPoints; + c30 *= invNumPoints; + c01 *= invNumPoints; + c02 *= invNumPoints; + c11 *= invNumPoints; + c21 *= invNumPoints; + + // Compute the coefficients of p3(t) and q3(t). + Real e0 = (Real)3 * c10; + Real e1 = (Real)2 * c20 + c01; + Real e2 = c11; + Real e3 = (Real)3 * c20; + Real e4 = c30; + + // Compute the coefficients of g(t). + Real g0 = c11 * c21 - c02 * c30; + Real g1 = c01 * c21 - (Real)3 * c02 * c20 + (Real)2 * (c20 * c21 - c11 * (c30 - c11)); + Real g2 = (Real)3 * (c11 * (c01 - c20) + c10 * (c21 - c02)); + Real g3 = c21 - c02 + c01 * (c01 + c20) + (Real)2 * (c10 * (c30 - c11) - c20 * c20); + Real g4 = c30 - c11 + c10 * (c01 - c20); + + // Compute the roots of g(t) = 0. + std::map rmMap; + RootsPolynomial::SolveQuartic(g0, g1, g2, g3, g4, rmMap); + + // Locate the positive root t that leads to an s = cos(theta) + // in (0,1) and that has minimum least-squares error. In theory, + // there must always be such a root, but floating-point rounding + // errors might lead to no such root. The implementation returns + // the center as the estimate of V and pi/4 as the estimate of + // the angle (s = 1/2). + std::vector> info; + Real s, t; + for (auto const& element : rmMap) + { + t = element.first; + if (t > (Real)0) + { + Real p3 = e2 + t * (e1 + t * (e0 + t)); + if (p3 != (Real)0) + { + Real q3 = e4 + t * (e3 + t * (e0 + t)); + s = q3 / p3; + if ((Real)0 < s && s < (Real)1) + { + Real error(0); + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + Real ai = Dot(coneAxis, diff); + Real bi = Dot(diff, diff); + Real tpai = t + ai; + Real Fi = s * (bi + t * ((Real)2 * ai + t)) - tpai * tpai; + error += Fi * Fi; + } + error *= invNumPoints; + std::array item = {{ s, t, error }}; + info.push_back(item); + } + } + } + } + + Real minError = std::numeric_limits::max(); + std::array minItem = {{ (Real)0, (Real)0, minError }}; + for (auto const& item : info) + { + if (item[2] < minError) + { + minItem = item; + } + } + + if (minItem[2] < std::numeric_limits::max()) + { + // minItem = { minS, minT, minError } + coneVertex = center - minItem[1] * coneAxis; + coneCosAngle = std::sqrt(minItem[0]); + } + else + { + coneVertex = center; + coneCosAngle = (Real)GTE_C_INV_SQRT_2; + } + } + + int mNumPoints; + Vector<3, Real> const* mPoints; + std::function const&, GVector&)> mFFunction; + std::function const&, GMatrix&)> mJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCylinder3.h new file mode 100644 index 000000000000..35a8c3e131be --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprCylinder3.h @@ -0,0 +1,462 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.5 (2018/10/26) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The algorithm for least-squares fitting of a point set by a cylinder is +// described in +// http://www.geometrictools.com/Documentation/CylinderFitting.pdf +// This document shows how to compute the cylinder radius r and the cylinder +// axis as a line C+t*W with origin C, unit-length direction W, and any +// real-valued t. The implementation here adds one addition step. It +// projects the point set onto the cylinder axis, computes the bounding +// t-interval [tmin,tmax] for the projections, and sets the cylinder center +// to C + 0.5*(tmin+tmax)*W and the cylinder height to tmax-tmin. + +namespace gte +{ + template + class ApprCylinder3 + { + public: + // Search the hemisphere for a minimum, choose numThetaSamples and + // numPhiSamples to be positive (and preferably large). These are used + // to generate a hemispherical grid of samples to be evaluated to find + // the cylinder axis-direction W. If the grid samples is quite large + // and the number of points to be fitted is large, you most likely will + // want to run multithreaded. Set numThreads to 0 to run single-threaded + // in the main process. Set numThreads > 0 to run multithreaded. If + // either of numThetaSamples or numPhiSamples is zero, the operator() sets + // the cylinder origin and axis to the zero vectors, the radius and height + // to zero, and returns std::numeric_limits::max(). + ApprCylinder3(unsigned int numThreads, unsigned int numThetaSamples, unsigned int numPhiSamples) + : + mConstructorType(FIT_BY_HEMISPHERE_SEARCH), + mNumThreads(numThreads), + mNumThetaSamples(numThetaSamples), + mNumPhiSamples(numPhiSamples), + mEigenIndex(0), + mInvNumPoints((Real)0) + { + mCylinderAxis = { (Real)0, (Real)0, (Real)0 }; + } + + // Choose one of the eigenvectors for the covariance matrix as the + // cylinder axis direction. If eigenIndex is 0, the eigenvector + // associated with the smallest eigenvalue is chosen. If eigenIndex is 2, + // the eigenvector associated with the largest eigenvalue is chosen. If + // eigenIndex is 1, the eigenvector associated with the median eigenvalue + // is chosen; keep in mind that this could be the minimum or maximum + // eigenvalue if the eigenspace has dimension 2 or 3. If eigenIndex is + // 3 or larger, the operator() sets the cylinder origin and axis to the + // zero vectors, the radius and height to zero, and returns + // std::numeric_limits::max(). + ApprCylinder3(unsigned int eigenIndex) + : + mConstructorType(FIT_USING_COVARIANCE_EIGENVECTOR), + mNumThreads(0), + mNumThetaSamples(0), + mNumPhiSamples(0), + mEigenIndex(eigenIndex), + mInvNumPoints((Real)0) + { + mCylinderAxis = { (Real)0, (Real)0, (Real)0 }; + } + + // Choose the cylinder axis. If cylinderAxis is not the zero vector, + // the constructor will normalize it. If cylinderAxis is the zero vector, + // the operator() sets the cylinder origin and axis to the zero vectors, + // the radius and height to zero, and returns + // std::numeric_limits::max(). + ApprCylinder3(Vector3 const& cylinderAxis) + : + mConstructorType(FIT_USING_SPECIFIED_AXIS), + mNumThreads(0), + mNumThetaSamples(0), + mNumPhiSamples(0), + mEigenIndex(0), + mCylinderAxis(cylinderAxis), + mInvNumPoints((Real)0) + { + Normalize(mCylinderAxis, true); + } + + // The algorithm must estimate 6 parameters, so the number of points must + // be at least 6 but preferably larger. The returned value is the + // root-mean-square of the least-squares error. If numPoints is less than + // 6 or if points is a null pointer, the operator() sets the cylinder origin + // and axis to the zero vectors, the radius and height to zero, and returns + // std::numeric_limits::max(). + Real operator()(unsigned int numPoints, Vector3 const* points, Cylinder3& cylinder) + { + mX.clear(); + mInvNumPoints = (Real)0; + cylinder.axis.origin = Vector3::Zero(); + cylinder.axis.direction = Vector3::Zero(); + cylinder.radius = (Real)0; + cylinder.height = (Real)0; + + // Validate the input parameters. + if (numPoints < 6 || !points) + { + return std::numeric_limits::max(); + } + + Vector3 average; + Preprocess(numPoints, points, average); + + // Fit the points based on which constructor the caller used. The + // direction is either estimated or selected directly or indirectly + // by the caller. The center and squared radius are estimated. + Vector3 minPC, minW; + Real minRSqr, minError; + + if (mConstructorType == FIT_BY_HEMISPHERE_SEARCH) + { + // Validate the relevant internal parameters. + if (mNumThetaSamples == 0 || mNumPhiSamples == 0) + { + return std::numeric_limits::max(); + } + + // Search the hemisphere for the vector that leads to minimum error + // and use it for the cylinder axis. + if (mNumThreads == 0) + { + // Execute the algorithm in the main process. + minError = ComputeSingleThreaded(minPC, minW, minRSqr); + } + else + { + // Execute the algorithm in multiple threads. + minError = ComputeMultiThreaded(minPC, minW, minRSqr); + } + } + else if (mConstructorType == FIT_USING_COVARIANCE_EIGENVECTOR) + { + // Validate the relevant internal parameters. + if (mEigenIndex >= 3) + { + return std::numeric_limits::max(); + } + + // Use the eigenvector corresponding to the mEigenIndex of the + // eigenvectors of the covariance matrix as the cylinder axis + // direction. The eigenvectors are sorted from smallest + // eigenvalue (mEigenIndex = 0) to largest eigenvalue + // (mEigenIndex = 2). + minError = ComputeUsingCovariance(minPC, minW, minRSqr); + } + else // mConstructorType == FIT_USING_SPECIFIED_AXIS + { + // Validate the relevant internal parameters. + if (mCylinderAxis == Vector3::Zero()) + { + return std::numeric_limits::max(); + } + + minError = ComputeUsingDirection(minPC, minW, minRSqr); + } + + // Translate back to the original space by the average of the points. + cylinder.axis.origin = minPC + average; + cylinder.axis.direction = minW; + + // Compute the cylinder radius. + cylinder.radius = std::sqrt(minRSqr); + + // Project the points onto the cylinder axis and choose the cylinder + // center and cylinder height as described in the comments at the top of + // this header file. + Real tmin = (Real)0, tmax = (Real)0; + for (unsigned int i = 0; i < numPoints; ++i) + { + Real t = Dot(cylinder.axis.direction, points[i] - cylinder.axis.origin); + tmin = std::min(t, tmin); + tmax = std::max(t, tmax); + } + + cylinder.axis.origin += ((tmin + tmax) * (Real)0.5) * cylinder.axis.direction; + cylinder.height = tmax - tmin; + return minError; + } + + private: + enum ConstructorType + { + FIT_BY_HEMISPHERE_SEARCH, + FIT_USING_COVARIANCE_EIGENVECTOR, + FIT_USING_SPECIFIED_AXIS + }; + + void Preprocess(unsigned int numPoints, Vector3 const* points, Vector3& average) + { + mX.resize(numPoints); + mInvNumPoints = (Real)1 / (Real)numPoints; + + // Copy the points and translate by the average for numerical robustness. + average.MakeZero(); + for (unsigned int i = 0; i < numPoints; ++i) + { + average += points[i]; + } + average *= mInvNumPoints; + for (unsigned int i = 0; i < numPoints; ++i) + { + mX[i] = points[i] - average; + } + + Vector<6, Real> zero{ (Real)0 }; + std::vector> products(mX.size(), zero); + mMu = zero; + for (size_t i = 0; i < mX.size(); ++i) + { + products[i][0] = mX[i][0] * mX[i][0]; + products[i][1] = mX[i][0] * mX[i][1]; + products[i][2] = mX[i][0] * mX[i][2]; + products[i][3] = mX[i][1] * mX[i][1]; + products[i][4] = mX[i][1] * mX[i][2]; + products[i][5] = mX[i][2] * mX[i][2]; + mMu[0] += products[i][0]; + mMu[1] += (Real)2 * products[i][1]; + mMu[2] += (Real)2 * products[i][2]; + mMu[3] += products[i][3]; + mMu[4] += (Real)2 * products[i][4]; + mMu[5] += products[i][5]; + } + mMu *= mInvNumPoints; + + mF0.MakeZero(); + mF1.MakeZero(); + mF2.MakeZero(); + for (size_t i = 0; i < mX.size(); ++i) + { + Vector<6, Real> delta; + delta[0] = products[i][0] - mMu[0]; + delta[1] = (Real)2 * products[i][1] - mMu[1]; + delta[2] = (Real)2 * products[i][2] - mMu[2]; + delta[3] = products[i][3] - mMu[3]; + delta[4] = (Real)2 * products[i][4] - mMu[4]; + delta[5] = products[i][5] - mMu[5]; + mF0(0, 0) += products[i][0]; + mF0(0, 1) += products[i][1]; + mF0(0, 2) += products[i][2]; + mF0(1, 1) += products[i][3]; + mF0(1, 2) += products[i][4]; + mF0(2, 2) += products[i][5]; + mF1 += OuterProduct(mX[i], delta); + mF2 += OuterProduct(delta, delta); + } + mF0 *= mInvNumPoints; + mF0(1, 0) = mF0(0, 1); + mF0(2, 0) = mF0(0, 2); + mF0(2, 1) = mF0(1, 2); + mF1 *= mInvNumPoints; + mF2 *= mInvNumPoints; + } + + Real ComputeUsingDirection(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + minW = mCylinderAxis; + return G(minW, minPC, minRSqr); + } + + Real ComputeUsingCovariance(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Matrix3x3 covar = Matrix3x3::Zero(); + for (auto const& X : mX) + { + covar += OuterProduct(X, X); + } + covar *= mInvNumPoints; + std::array eval; + std::array, 3> evec; + SymmetricEigensolver3x3()( + covar(0, 0), covar(0, 1), covar(0, 2), covar(1, 1), covar(1, 2), covar(2, 2), + true, +1, eval, evec); + minW = evec[mEigenIndex]; + return G(minW, minPC, minRSqr); + } + + Real ComputeSingleThreaded(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples; + Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples; + + // Handle the north pole (0,0,1) separately. + minW = { (Real)0, (Real)0, (Real)1 }; + Real minError = G(minW, minPC, minRSqr); + + for (unsigned int j = 1; j <= mNumPhiSamples; ++j) + { + Real phi = jMultiplier * static_cast(j); // in [0,pi/2] + Real csphi = std::cos(phi); + Real snphi = std::sin(phi); + for (unsigned int i = 0; i < mNumThetaSamples; ++i) + { + Real theta = iMultiplier * static_cast(i); // in [0,2*pi) + Real cstheta = std::cos(theta); + Real sntheta = std::sin(theta); + Vector3 W{ cstheta * snphi, sntheta * snphi, csphi }; + Vector3 PC; + Real rsqr; + Real error = G(W, PC, rsqr); + if (error < minError) + { + minError = error; + minRSqr = rsqr; + minW = W; + minPC = PC; + } + } + } + + return minError; + } + + Real ComputeMultiThreaded(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples; + Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples; + + // Handle the north pole (0,0,1) separately. + minW = { (Real)0, (Real)0, (Real)1 }; + Real minError = G(minW, minPC, minRSqr); + + struct Local + { + Real error; + Real rsqr; + Vector3 W; + Vector3 PC; + unsigned int jmin; + unsigned int jmax; + }; + + std::vector local(mNumThreads); + unsigned int numPhiSamplesPerThread = mNumPhiSamples / mNumThreads; + for (unsigned int t = 0; t < mNumThreads; ++t) + { + local[t].error = std::numeric_limits::max(); + local[t].rsqr = (Real)0; + local[t].W = Vector3::Zero(); + local[t].PC = Vector3::Zero(); + local[t].jmin = numPhiSamplesPerThread * t; + local[t].jmax = numPhiSamplesPerThread * (t + 1); + } + local[mNumThreads - 1].jmax = mNumPhiSamples + 1; + + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread + ( + [this, t, iMultiplier, jMultiplier, &local]() + { + for (unsigned int j = local[t].jmin; j < local[t].jmax; ++j) + { + Real phi = jMultiplier * static_cast(j); // in [0,pi/2] + Real csphi = std::cos(phi); + Real snphi = std::sin(phi); + for (unsigned int i = 0; i < mNumThetaSamples; ++i) + { + Real theta = iMultiplier * static_cast(i); // in [0,2*pi) + Real cstheta = std::cos(theta); + Real sntheta = std::sin(theta); + Vector3 W{ cstheta * snphi, sntheta * snphi, csphi }; + Vector3 PC; + Real rsqr; + Real error = G(W, PC, rsqr); + if (error < local[t].error) + { + local[t].error = error; + local[t].rsqr = rsqr; + local[t].W = W; + local[t].PC = PC; + } + } + } + } + ); + } + + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + + if (local[t].error < minError) + { + minError = local[t].error; + minRSqr = local[t].rsqr; + minW = local[t].W; + minPC = local[t].PC; + } + } + + return minError; + } + + Real G(Vector3 const& W, Vector3& PC, Real& rsqr) + { + Matrix3x3 P = Matrix3x3::Identity() - OuterProduct(W, W); + Matrix3x3 S + { + (Real)0, -W[2], W[1], + W[2], (Real)0, -W[0], + -W[1], W[0], (Real)0 + }; + + Matrix<3, 3, Real> A = P * mF0 * P; + Matrix<3, 3, Real> hatA = -(S * A * S); + Matrix<3, 3, Real> hatAA = hatA * A; + Real trace = Trace(hatAA); + Matrix<3, 3, Real> Q = hatA / trace; + Vector<6, Real> pVec{ P(0, 0), P(0, 1), P(0, 2), P(1, 1), P(1, 2), P(2, 2) }; + Vector<3, Real> alpha = mF1 * pVec; + Vector<3, Real> beta = Q * alpha; + Real G = (Dot(pVec, mF2 * pVec) - (Real)4 * Dot(alpha, beta) + (Real)4 * Dot(beta, mF0 * beta)) / (Real)mX.size(); + + PC = beta; + rsqr = Dot(pVec, mMu) + Dot(PC, PC); + return G; + } + + ConstructorType mConstructorType; + + // Parameters for the hemisphere-search constructor. + unsigned int mNumThreads; + unsigned int mNumThetaSamples; + unsigned int mNumPhiSamples; + + // Parameters for the eigenvector-index constructor. + unsigned int mEigenIndex; + + // Parameters for the specified-axis constructor. + Vector3 mCylinderAxis; + + // A copy of the input points but translated by their average for + // numerical robustness. + std::vector> mX; + Real mInvNumPoints; + + // Preprocessed information that depends only on the sample points. + // This allows precomputed summations so that G(...) can be evaluated + // extremely fast. + Vector<6, Real> mMu; + Matrix<3, 3, Real> mF0; + Matrix<3, 6, Real> mF1; + Matrix<6, 6, Real> mF2; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipse2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipse2.h new file mode 100644 index 000000000000..ab4c158751eb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipse2.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +// The ellipse in general form is X^t A X + B^t X + C = 0 where A is a +// positive definite 2x2 matrix, B is a 2x1 vector, C is a scalar, and X is +// a 2x1 vector X. Completing the square, (X-U)^t A (X-U) = U^t A U - C +// where U = -0.5 A^{-1} B. Define M = A/(U^t A U - C). The ellipse is +// (X-U)^t M (X-U) = 1. Factor M = R^t D R where R is orthonormal and D is +// diagonal with positive diagonal terms. The ellipse in factored form is +// (X-U)^t R^t D^t R (X-U) = 1. +// +// Find the least squares fit of a set of N points P[0] through P[N-1]. +// The return value is the least-squares energy function at (U,R,D). + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ApprEllipse2 +{ +public: + Real operator()(int numPoints, Vector2 const* points, + Vector2& center, Matrix2x2& rotate, Real diagonal[2]); + +private: + static Real Energy(int numPoints, Vector2 const* points, + Real const* input); +}; + + +template +Real ApprEllipse2::operator()(int numPoints, + Vector2 const* points, Vector2& center, + Matrix2x2& rotate, Real diagonal[2]) +{ + // Energy function is E : R^5 -> R where + // V = (V0, V1, V2, V3, V4) + // = (D[0], D[1], U.x, U.y, atan2(R(0,1),R(0,0))). + std::function energy = + [numPoints, points](Real const* input) + { + return Energy(numPoints, points, input); + }; + + MinimizeN minimizer(5, energy, 8, 8, 32); + + // The initial guess for the minimizer is based on an oriented box that + // contains the points. + OrientedBox2 box; + GetContainer(numPoints, points, box); + center = box.center; + for (int i = 0; i < 2; ++i) + { + rotate.SetRow(i, box.axis[i]); + diagonal[i] = box.extent[i]; + } + + Real angle = std::atan2(rotate(0, 1), rotate(0, 0)); + Real e0 = + diagonal[0] * std::abs(rotate(0, 0)) + + diagonal[1] * std::abs(rotate(1, 0)); + Real e1 = + diagonal[0] * std::abs(rotate(0, 1)) + + diagonal[1] * std::abs(rotate(1, 1)); + + Real v0[5] = + { + ((Real)0.5)*diagonal[0], + ((Real)0.5)*diagonal[1], + center[0] - e0, + center[1] - e1, + -(Real)GTE_C_PI + }; + + Real v1[5] = + { + ((Real)2)*diagonal[0], + ((Real)2)*diagonal[1], + center[0] + e0, + center[1] + e1, + (Real)GTE_C_PI + }; + + Real vInitial[5] = + { + diagonal[0], + diagonal[1], + center[0], + center[1], + angle + }; + + Real vMin[5], error; + minimizer.GetMinimum(v0, v1, vInitial, vMin, error); + + diagonal[0] = vMin[0]; + diagonal[1] = vMin[1]; + center[0] = vMin[2]; + center[1] = vMin[3]; + MakeRotation(-vMin[4], rotate); + + return error; +} + +template +Real ApprEllipse2::Energy(int numPoints, Vector2 const* points, + Real const* input) +{ + // Build rotation matrix. + Matrix2x2 rotate; + MakeRotation(-input[4], rotate); + + Ellipse2 ellipse(Vector2::Zero(), { Vector2::Unit(0), + Vector2::Unit(1) }, { input[0], input[1] }); + + // Transform the points to the coordinate system of center C and + // columns of rotation R. + DCPQuery, Ellipse2> peQuery; + Real energy = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - Vector2{ input[2], input[3] }; + Vector2 prod = rotate * diff; + Real dist = peQuery(prod, ellipse).distance; + energy += dist; + } + + return energy; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipseByArcs.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipseByArcs.h new file mode 100644 index 000000000000..89354ad7e313 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipseByArcs.h @@ -0,0 +1,127 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// The ellipse is (x/a)^2 + (y/b)^2 = 1, but only the portion in the first +// quadrant (x >= 0 and y >= 0) is approximated. Generate numArcs >= 2 arcs +// by constructing points corresponding to the weighted averages of the +// curvatures at the ellipse points (a,0) and (0,b). The returned input point +// array has numArcs+1 elements and the returned input center and radius +// arrays each have numArc elements. The arc associated with points[i] and +// points[i+1] has center centers[i] and radius radii[i]. The algorithm +// is described in +// http://www.geometrictools.com/Documentation/ApproximateEllipse.pdf +// +// The function returns 'true' when the approximation succeeded, in which +// case the output arrays are nonempty. If the 'numArcs' is smaller than +// 2 or a == b or one of the calls to Circumscribe fails, the function +// returns 'false'. + +template +bool ApproximateEllipseByArcs(Real a, Real b, int numArcs, + std::vector>& points, std::vector>& centers, + std::vector& radii); + + +template +bool ApproximateEllipseByArcs(Real a, Real b, int numArcs, + std::vector>& points, std::vector>& centers, + std::vector& radii) +{ + if (numArcs < 2 || a == b) + { + // At least 2 arcs are required. The ellipse cannot already be a + // circle. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + + points.resize(numArcs + 1); + centers.resize(numArcs); + radii.resize(numArcs); + + // Compute intermediate ellipse quantities. + Real a2 = a * a, b2 = b * b, ab = a * b; + Real invB2mA2 = ((Real)1) / (b2 - a2); + + // Compute the endpoints of the ellipse in the first quadrant. The + // points are generated in counterclockwise order. + points[0] = { a, (Real)0 }; + points[numArcs] = { (Real)0, b }; + + // Compute the curvature at the endpoints. These are used when computing + // the arcs. + Real curv0 = a / b2; + Real curv1 = b / a2; + + // Select the ellipse points based on curvature properties. + Real invNumArcs = ((Real)1) / numArcs; + for (int i = 1; i < numArcs; ++i) + { + // The curvature at a new point is a weighted average of curvature + // at the endpoints. + Real weight1 = static_cast(i) * invNumArcs; + Real weight0 = (Real)1 - weight1; + Real curv = weight0 * curv0 + weight1 * curv1; + + // Compute point having this curvature. + Real tmp = std::pow(ab / curv, (Real)2 / (Real)3); + points[i][0] = a * std::sqrt(std::abs((tmp - a2) * invB2mA2)); + points[i][1] = b * std::sqrt(std::abs((tmp - b2) * invB2mA2)); + } + + // Compute the arc at (a,0). + Circle2 circle; + Vector2 const& p0 = points[0]; + Vector2 const& p1 = points[1]; + if (!Circumscribe(Vector2{ p1[0], -p1[1] }, p0, p1, circle)) + { + // This should not happen for the arc-fitting algorithm. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + centers[0] = circle.center; + radii[0] = circle.radius; + + // Compute arc at (0,b). + int last = numArcs - 1; + Vector2 const& pNm1 = points[last]; + Vector2 const& pN = points[numArcs]; + if (!Circumscribe(Vector2{ -pNm1[0], pNm1[1] }, pN, pNm1, circle)) + { + // This should not happen for the arc-fitting algorithm. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + + centers[last] = circle.center; + radii[last] = circle.radius; + + // Compute arcs at intermediate points between (a,0) and (0,b). + for (int iM = 0, i = 1, iP = 2; i < last; ++iM, ++i, ++iP) + { + Circumscribe(points[iM], points[i], points[iP], circle); + centers[i] = circle.center; + radii[i] = circle.radius; + } + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipsoid3.h new file mode 100644 index 000000000000..ec4919b86fa6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprEllipsoid3.h @@ -0,0 +1,227 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +// The ellipsoid in general form is X^t A X + B^t X + C = 0 where +// A is a positive definite 3x3 matrix, B is a 3x1 vector, C is a +// scalar, and X is a 3x1 vector. Completing the square, +// (X-U)^t A (X-U) = U^t A U - C where U = -0.5 A^{-1} B. Define +// M = A/(U^t A U - C). The ellipsoid is (X-U)^t M (X-U) = 1. Factor +// M = R^t D R where R is orthonormal and D is diagonal with positive +// diagonal terms. The ellipsoid in factored form is +// (X-U)^t R^t D^t R (X-U) = 1 +// +// Find the least squares fit of a set of N points P[0] through P[N-1]. +// The error return value is the least-squares energy function at (U,R,D). + +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ApprEllipsoid3 +{ +public: + Real operator()(int numPoints, Vector3 const* points, + Vector3& center, Matrix3x3& rotate, Real diagonal[3]); + +private: + static void MatrixToAngles(Matrix3x3 const& rotate, Real angle[3]); + static void AnglesToMatrix(Real const angle[3], Matrix3x3& rotate); + static Real Energy(int numPoints, Vector3 const* points, + Real const* input); +}; + + +template +Real ApprEllipsoid3::operator()(int numPoints, + Vector3 const* points, Vector3& center, + Matrix3x3& rotate, Real diagonal[3]) +{ + // Energy function is E : R^9 -> R where + // V = (V0,V1,V2,V3,V4,V5,V6,V7,V8) + // = (D[0],D[1],D[2],U[0],U,y,U[2],A0,A1,A2). + std::function energy = + [numPoints, points](Real const* input) + { + return Energy(numPoints, points, input); + }; + + MinimizeN minimizer(9, energy, 8, 8, 32); + + // The initial guess for the minimizer is based on an oriented box that + // contains the points. + OrientedBox3 box; + GetContainer(numPoints, points, box); + center = box.center; + for (int i = 0; i < 3; ++i) + { + rotate.SetRow(i, box.axis[i]); + diagonal[i] = box.extent[i]; + } + + Real angle[3]; + MatrixToAngles(rotate, angle); + + Real extent[3] = + { + diagonal[0] * std::abs(rotate(0, 0)) + + diagonal[1] * std::abs(rotate(0, 1)) + + diagonal[2] * std::abs(rotate(0, 2)), + + diagonal[0] * std::abs(rotate(1, 0)) + + diagonal[1] * std::abs(rotate(1, 1)) + + diagonal[2] * std::abs(rotate(1, 2)), + + diagonal[0] * std::abs(rotate(2, 0)) + + diagonal[1] * std::abs(rotate(2, 1)) + + diagonal[2] * std::abs(rotate(2, 2)) + }; + + Real v0[9] = + { + ((Real)0.5)*diagonal[0], + ((Real)0.5)*diagonal[1], + ((Real)0.5)*diagonal[2], + center[0] - extent[0], + center[1] - extent[1], + center[2] - extent[2], + -(Real)GTE_C_PI, + (Real)0, + (Real)0 + }; + + Real v1[9] = + { + ((Real)2)*diagonal[0], + ((Real)2)*diagonal[1], + ((Real)2)*diagonal[2], + center[0] + extent[0], + center[1] + extent[1], + center[2] + extent[2], + (Real)GTE_C_PI, + (Real)GTE_C_PI, + (Real)GTE_C_PI + }; + + Real vInitial[9] = + { + diagonal[0], + diagonal[1], + diagonal[2], + center[0], + center[1], + center[2], + angle[0], + angle[1], + angle[2] + }; + + Real vMin[9], error; + minimizer.GetMinimum(v0, v1, vInitial, vMin, error); + + diagonal[0] = vMin[0]; + diagonal[1] = vMin[1]; + diagonal[2] = vMin[2]; + center[0] = vMin[3]; + center[1] = vMin[4]; + center[2] = vMin[5]; + AnglesToMatrix(&vMin[6], rotate); + + return error; +} + +template +void ApprEllipsoid3::MatrixToAngles(Matrix3x3 const& rotate, + Real angle[3]) +{ + // rotation axis = (cos(a0)sin(a1),sin(a0)sin(a1),cos(a1)) + // a0 in [-pi,pi], a1 in [0,pi], a2 in [0,pi] + + Real const zero = (Real)0; + Real const one = (Real)1; + AxisAngle<3, Real> aa = Rotation<3, Real>(rotate); + + if (-one < aa.axis[2]) + { + if (aa.axis[2] < one) + { + angle[0] = std::atan2(aa.axis[1], aa.axis[0]); + angle[1] = std::acos(aa.axis[2]); + } + else + { + angle[0] = zero; + angle[1] = zero; + } + } + else + { + angle[0] = zero; + angle[1] = (Real)GTE_C_PI; + } +} + +template +void ApprEllipsoid3::AnglesToMatrix(Real const angle[3], + Matrix3x3& rotate) +{ + // rotation axis = (cos(a0)sin(a1),sin(a0)sin(a1),cos(a1)) + // a0 in [-pi,pi], a1 in [0,pi], a2 in [0,pi] + + Real cs0 = std::cos(angle[0]); + Real sn0 = std::sin(angle[0]); + Real cs1 = std::cos(angle[1]); + Real sn1 = std::sin(angle[1]); + AxisAngle<3, Real> aa; + aa.axis = { cs0*sn1, sn0*sn1, cs1 }; + aa.angle = angle[2]; + rotate = Rotation<3, Real>(aa); +} + +template +Real ApprEllipsoid3::Energy(int numPoints, Vector3 const* points, + Real const* input) +{ + // Build rotation matrix. + Matrix3x3 rotate; + AnglesToMatrix(&input[6], rotate); + + // Uniformly scale the extents to keep reasonable floating point values + // in the distance calculations. + Real maxValue = std::max(std::max(input[0], input[1]), input[2]); + Real invMax = ((Real)1) / maxValue; + Ellipsoid3 ellipsoid(Vector3::Zero(), { Vector3::Unit(0), + Vector3::Unit(1), Vector3::Unit(2) }, { invMax*input[0], + invMax*input[1], invMax*input[2] }); + + // Transform the points to the coordinate system of center C and columns + // of rotation R. + DCPQuery, Ellipsoid3> peQuery; + Real energy = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - + Vector3{ input[3], input[4], input[5] }; + + Vector3 prod = invMax * (diff * rotate); + Real dist = peQuery(prod, ellipsoid).distance; + energy += maxValue * dist; + } + + return energy; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian2.h new file mode 100644 index 000000000000..b3299800c8f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian2.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit points with a Gaussian distribution. The center is the mean of the +// points, the axes are the eigenvectors of the covariance matrix, and the +// extents are the eigenvalues of the covariance matrix and are returned in +// increasing order. An oriented box is used to store the mean, axes, and +// extents. + +namespace gte +{ + +template +class ApprGaussian2 + : + public ApprQuery, Vector2> +{ +public: + // Initialize the model parameters to zero. + ApprGaussian2(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector2 const* points); + OrientedBox2 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector2 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + OrientedBox2 mParameters; +}; + + +template +ApprGaussian2::ApprGaussian2() +{ + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); +} + +template +bool ApprGaussian2::Fit(int numPoints, Vector2 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + Real invSize = ((Real)1) / (Real)numPoints; + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + covar00 *= invSize; + covar01 *= invSize; + covar11 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.extent = eval; + return true; + } + + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); + return false; +} + +template +OrientedBox2 const& ApprGaussian2::GetParameters() const +{ + return mParameters; +} + +template +int ApprGaussian2::GetMinimumRequired() const +{ + return 2; +} + +template +bool ApprGaussian2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + Real invSize = ((Real)1) / (Real)indices.size(); + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (auto index : indices) + { + Vector2 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + covar00 *= invSize; + covar01 *= invSize; + covar11 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.extent = eval; + } + + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); + return false; +} + +template +Real ApprGaussian2::Error(Vector2 const& observation) const +{ + Vector2 diff = observation - mParameters.center; + Real error = (Real)0; + for (int i = 0; i < 2; ++i) + { + if (mParameters.extent[i] >(Real)0) + { + Real ratio = Dot(diff, mParameters.axis[i]) / + mParameters.extent[i]; + error += ratio * ratio; + } + } + return error; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian3.h new file mode 100644 index 000000000000..3f00fba88256 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGaussian3.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit points with a Gaussian distribution. The center is the mean of the +// points, the axes are the eigenvectors of the covariance matrix, and the +// extents are the eigenvalues of the covariance matrix and are returned in +// increasing order. An oriented box is used to store the mean, axes, and +// extents. + +namespace gte +{ + +template +class ApprGaussian3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprGaussian3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + OrientedBox3 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + OrientedBox3 mParameters; +}; + + +template +ApprGaussian3::ApprGaussian3() +{ + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); +} + +template +bool ApprGaussian3::Fit(int numPoints, Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + Real invSize = ((Real)1) / (Real)numPoints; + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.axis[2] = evec[2]; + mParameters.extent = eval; + return true; + } + + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); + return false; +} + +template +OrientedBox3 const& ApprGaussian3::GetParameters() const +{ + return mParameters; +} + +template +int ApprGaussian3::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprGaussian3::Error(Vector3 const& observation) const +{ + Vector3 diff = observation - mParameters.center; + Real error = (Real)0; + for (int i = 0; i < 3; ++i) + { + if (mParameters.extent[i] >(Real)0) + { + Real ratio = Dot(diff, mParameters.axis[i]) / + mParameters.extent[i]; + error += ratio * ratio; + } + } + return error; +} + +template +bool ApprGaussian3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mParameters.center += observations[index]; + } + Real invSize = ((Real)1) / (Real)indices.size(); + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.axis[2] = evec[2]; + mParameters.extent = eval; + } + + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGreatCircle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGreatCircle3.h new file mode 100644 index 000000000000..1f11fea318da --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprGreatCircle3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// Least-squares fit of a great circle to unit-length vectors (x,y,z) by +// using distance measurements orthogonal (and measured along great circles) +// to the proposed great circle. The inputs akPoint[] are unit length. The +// returned value is unit length, call it N. The fitted great circle is +// defined by Dot(N,X) = 0, where X is a unit-length vector on the great +// circle. +template +class ApprGreatCircle3 +{ +public: + void operator()(int numPoints, Vector3 const* points, + Vector3& normal); +}; + + +// In addition to the least-squares fit of a great circle, the input vectors +// are projected onto that circle. The sector of smallest angle (possibly +// obtuse) that contains the points is computed. The endpoints of the arc +// of the sector are returned. The returned endpoints A0 and A1 are +// perpendicular to the returned normal N. Moreover, when you view the +// arc by looking at the plane of the great circle with a view direction +// of -N, the arc is traversed counterclockwise starting at A0 and ending +// at A1. +template +class ApprGreatArc3 +{ +public: + void operator()(int numPoints, Vector3 const* points, + Vector3& normal, Vector3& arcEnd0, + Vector3& arcEnd1); +}; + + +template +void ApprGreatCircle3::operator()(int numPoints, + Vector3 const* points, Vector3& normal) +{ + // Compute the covariance matrix of the vectors. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; i++) + { + Vector3 diff = points[i]; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + covar00 *= invNumPoints; + covar01 *= invNumPoints; + covar02 *= invNumPoints; + covar11 *= invNumPoints; + covar12 *= invNumPoints; + covar22 *= invNumPoints; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + normal = evec[0]; +} + +template +void ApprGreatArc3::operator()(int numPoints, + Vector3 const* points, Vector3& normal, + Vector3& arcEnd0, Vector3& arcEnd1) +{ + // Get the least-squares great circle for the vectors. The circle is on + // the plane Dot(N,X) = 0. Generate a basis from N. + Vector3 basis[3]; // { N, U, V } + ApprGreatCircle3()(numPoints, points, basis[0]); + ComputeOrthogonalComplement(1, basis); + + // The vectors are X[i] = u[i]*U + v[i]*V + w[i]*N. The projections + // are P[i] = (u[i]*U + v[i]*V)/sqrt(u[i]*u[i] + v[i]*v[i]). The great + // circle is parameterized by C(t) = cos(t)*U + sin(t)*V. Compute the + // angles t in [-pi,pi] for the projections onto the great circle. It + // is not necesarily to normalize (u[i],v[i]), instead computing + // t = atan2(v[i],u[i]). + std::vector> items(numPoints); // item (u, v, angle) + for (int i = 0; i < numPoints; ++i) + { + items[i][0] = Dot(basis[1], points[i]); + items[i][1] = Dot(basis[2], points[i]); + items[i][2] = std::atan2(items[i][1], items[i][0]); + } + std::sort(items.begin(), items.end(), + [](std::array const& item0, std::array const& item1) + { + return item0[2] < item1[2]; + } + ); + + // Locate the pair of consecutive angles whose difference is a maximum. + // Effectively, we are constructing a cone of minimum angle that contains + // the unit-length vectors. + int numPointsM1 = numPoints - 1; + Real maxDiff = (Real)GTE_C_TWO_PI + items[0][2] - items[numPointsM1][2]; + int end0 = 0, end1 = numPointsM1; + for (int i0 = 0, i1 = 1; i0 < numPointsM1; i0 = i1++) + { + Real diff = items[i1][2] - items[i0][2]; + if (diff > maxDiff) + { + maxDiff = diff; + end0 = i1; + end1 = i0; + } + } + + normal = basis[0]; + arcEnd0 = items[end0][0] * basis[1] + items[end0][1] * basis[2]; + arcEnd1 = items[end1][0] * basis[1] + items[end1][1] * basis[2]; + Normalize(arcEnd0); + Normalize(arcEnd1); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightLine2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightLine2.h new file mode 100644 index 000000000000..1cff8cbc838e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightLine2.h @@ -0,0 +1,153 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Least-squares fit of a line to height data (x,f(x)). The line is of the +// form: (y - yAvr) = a*(x - xAvr), where (xAvr,yAvr) is the average of the +// sample points. The return value of Fit is 'true' iff the fit is successful +// (the input points are not degenerate to a single point). The mParameters +// values are ((xAvr,yAvr),(a,-1)) on success and ((0,0),(0,0)) on failure. +// The error for (x0,y0) is [a*(x0-xAvr)-(y0-yAvr)]^2. + +namespace gte +{ + +template +class ApprHeightLine2 + : + public ApprQuery, Vector2> +{ +public: + // Initialize the model parameters to zero. + ApprHeightLine2(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector2 const* points); + std::pair, Vector2> const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector2 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + std::pair, Vector2> mParameters; +}; + + +template +ApprHeightLine2::ApprHeightLine2() +{ + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); +} + +template +bool ApprHeightLine2::Fit(int numPoints, Vector2 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + } + + // Decompose the covariance matrix. + if (covar00 >(Real)0) + { + mParameters.first = mean; + mParameters.second[0] = covar01 / covar00; + mParameters.second[1] = (Real)-1; + return true; + } + } + + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); + return false; +} + +template +std::pair, Vector2> const& +ApprHeightLine2::GetParameters() const +{ + return mParameters; +} + +template +int ApprHeightLine2::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprHeightLine2::Error(Vector2 const& observation) const +{ + Real d = Dot(observation - mParameters.first, mParameters.second); + Real error = d*d; + return error; +} + +template +bool ApprHeightLine2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0; + for (auto index : indices) + { + Vector2 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + } + + // Decompose the covariance matrix. + if (covar00 > (Real)0) + { + mParameters.first = mean; + mParameters.second[0] = covar01 / covar00; + mParameters.second[1] = (Real)-1; + return true; + } + } + + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightPlane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightPlane3.h new file mode 100644 index 000000000000..a90c4bc7d7ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprHeightPlane3.h @@ -0,0 +1,171 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Least-squares fit of a plane to height data (x,y,f(x,y)). The plane is of +// the form (z - zAvr) = a*(x - xAvr) + b*(y - yAvr), where (xAvr,yAvr,zAvr) +// is the average of the sample points. The return value is 'true' iff the +// fit is successful (the input points are noncollinear). The mParameters +// values are ((xAvr,yAvr,zAvr),(a,b,-1)) on success and ((0,0,0),(0,0,0)) on +// failure. The error for (x0,y0,z0) is [a*(x0-xAvr)+b*(y0-yAvr)-(z0-zAvr)]^2. + +namespace gte +{ + +template +class ApprHeightPlane3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprHeightPlane3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + std::pair, Vector3> const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + std::pair, Vector3> mParameters; +}; + + +template +ApprHeightPlane3::ApprHeightPlane3() +{ + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); +} + +template +bool ApprHeightPlane3::Fit(int numPoints, Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + } + + // Decompose the covariance matrix. + Real det = covar00*covar11 - covar01*covar01; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + mParameters.first = mean; + mParameters.second[0] = + (covar11*covar02 - covar01*covar12)*invDet; + mParameters.second[1] = + (covar00*covar12 - covar01*covar02)*invDet; + mParameters.second[2] = (Real)-1; + return true; + } + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + +template +std::pair, Vector3> const& +ApprHeightPlane3::GetParameters() const +{ + return mParameters; +} + +template +int ApprHeightPlane3::GetMinimumRequired() const +{ + return 3; +} + +template +Real ApprHeightPlane3::Error(Vector3 const& observation) const +{ + Real d = Dot(observation - mParameters.first, mParameters.second); + Real error = d*d; + return error; +} + +template +bool ApprHeightPlane3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + } + + // Decompose the covariance matrix. + Real det = covar00*covar11 - covar01*covar01; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + mParameters.first = mean; + mParameters.second[0] = + (covar11*covar02 - covar01*covar12)*invDet; + mParameters.second[1] = + (covar00*covar12 - covar01*covar02)*invDet; + mParameters.second[2] = (Real)-1; + return true; + } + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine2.h new file mode 100644 index 000000000000..2ae547c6361f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine2.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Least-squares fit of a line to (x,y) data by using distance measurements +// orthogonal to the proposed line. The return value is 'true' iff the fit +// is unique (always successful, 'true' when a minimum eigenvalue is unique). +// The mParameters value is a line with (P,D) = (origin,direction). The +// error for S = (x0,y0) is (S-P)^T*(I - D*D^T)*(S-P). + +namespace gte +{ + +template +class ApprOrthogonalLine2 + : + public ApprQuery, Vector2> +{ +public: + // Initialize the model parameters to zero. + ApprOrthogonalLine2(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector2 const* points); + Line2 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector2 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + Line2 mParameters; +}; + + +template +ApprOrthogonalLine2::ApprOrthogonalLine2() + : + mParameters(Vector2::Zero(), Vector2::Zero()) +{ +} + +template +bool ApprOrthogonalLine2::Fit(int numPoints, + Vector2 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[1]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters = Line2(Vector2::Zero(), Vector2::Zero()); + return false; +} + +template +Line2 const& ApprOrthogonalLine2::GetParameters() const +{ + return mParameters; +} + +template +int ApprOrthogonalLine2::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprOrthogonalLine2::Error(Vector2 const& observation) const +{ + Vector2 diff = observation - mParameters.origin; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.direction); + Real error = std::abs(sqrlen - dot*dot); + return error; +} + +template +bool ApprOrthogonalLine2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + for (auto index : indices) + { + Vector2 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + + // Solve the eigensystem. + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[1]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters = Line2(Vector2::Zero(), Vector2::Zero()); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine3.h new file mode 100644 index 000000000000..099d442adfa3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalLine3.h @@ -0,0 +1,191 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Least-squares fit of a line to (x,y,z) data by using distance measurements +// orthogonal to the proposed line. The return value is 'true' iff the fit +// is unique (always successful, 'true' when a minimum eigenvalue is unique). +// The mParameters value is a line with (P,D) = (origin,direction). The +// error for S = (x0,y0,z0) is (S-P)^T*(I - D*D^T)*(S-P). + +namespace gte +{ + +template +class ApprOrthogonalLine3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprOrthogonalLine3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + Line3 const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + Line3 mParameters; +}; + + +template +ApprOrthogonalLine3::ApprOrthogonalLine3() + : + mParameters(Vector3::Zero(), Vector3::Zero()) +{ +} + +template +bool ApprOrthogonalLine3::Fit(int numPoints, Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + Real invSize = ((Real)1) / (Real)numPoints; + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[2]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[1]< eval[2]; + } + + mParameters = Line3(Vector3::Zero(), Vector3::Zero()); + return false; +} + +template +Line3 const& ApprOrthogonalLine3::GetParameters() const +{ + return mParameters; +} + +template +int ApprOrthogonalLine3::GetMinimumRequired() const +{ + return 2; +} + +template +Real ApprOrthogonalLine3::Error(Vector3 const& observation) const +{ + Vector3 diff = observation - mParameters.origin; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.direction); + Real error = std::abs(sqrlen - dot*dot); + return error; +} + +template +bool ApprOrthogonalLine3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + Real invSize = ((Real)1) / (Real)indices.size(); + mean *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The line direction is the eigenvector in the direction of largest + // variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[2]; + + // The fitted line is unique when the maximum eigenvalue has + // multiplicity 1. + return eval[1]< eval[2]; + } + + mParameters = Line3(Vector3::Zero(), Vector3::Zero()); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalPlane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalPlane3.h new file mode 100644 index 000000000000..09ed3129488b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprOrthogonalPlane3.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Least-squares fit of a plane to (x,y,z) data by using distance measurements +// orthogonal to the proposed plane. The return value is 'true' iff the fit +// is unique (always successful, 'true' when a minimum eigenvalue is unique). +// The mParameters value is (P,N) = (origin,normal). The error for +// S = (x0,y0,z0) is (S-P)^T*(I - N*N^T)*(S-P). + +namespace gte +{ + +template +class ApprOrthogonalPlane3 + : + public ApprQuery, Vector3> +{ +public: + // Initialize the model parameters to zero. + ApprOrthogonalPlane3(); + + // Basic fitting algorithm. + bool Fit(int numPoints, Vector3 const* points); + std::pair, Vector3> const& GetParameters() const; + + // Functions called by ApprQuery::RANSAC. See GteApprQuery.h for a + // detailed description. + int GetMinimumRequired() const; + Real Error(Vector3 const& observation) const; + bool Fit(std::vector> const& observations, + std::vector const& indices); + +private: + std::pair, Vector3> mParameters; +}; + + +template +ApprOrthogonalPlane3::ApprOrthogonalPlane3() +{ + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); +} + +template +bool ApprOrthogonalPlane3::Fit(int numPoints, + Vector3 const* points) +{ + if (numPoints >= GetMinimumRequired() && points) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + mean += points[i]; + } + mean /= (Real)numPoints; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The plane normal is the eigenvector in the direction of smallest + // variance of the points. + mParameters.first = mean; + mParameters.second = evec[0]; + + // The fitted plane is unique when the minimum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + +template +std::pair, Vector3> const& +ApprOrthogonalPlane3::GetParameters() const +{ + return mParameters; +} + +template +int ApprOrthogonalPlane3::GetMinimumRequired() const +{ + return 3; +} + +template +Real ApprOrthogonalPlane3::Error(Vector3 const& observation) +const +{ + Vector3 diff = observation - mParameters.first; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.second); + Real error = std::abs(sqrlen - dot*dot); + return error; +} + +template +bool ApprOrthogonalPlane3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (static_cast(indices.size()) >= GetMinimumRequired()) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + for (auto index : indices) + { + mean += observations[index]; + } + mean /= (Real)indices.size(); + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (auto index : indices) + { + Vector3 diff = observations[index] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + + // The plane normal is the eigenvector in the direction of smallest + // variance of the points. + mParameters.first = mean; + mParameters.second = evec[0]; + + // The fitted plane is unique when the minimum eigenvalue has + // multiplicity 1. + return eval[0] < eval[1]; + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprParaboloid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprParaboloid3.h new file mode 100644 index 000000000000..6b84fc8b374d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprParaboloid3.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Least-squares fit of a paraboloid to a set of point. The paraboloid is +// of the form z = c0*x^2+c1*x*y+c2*y^2+c3*x+c4*y+c5. A successful fit is +// indicated by return value of 'true'. +// +// Given a set of samples (x_i,y_i,z_i) for 0 <= i < N, and assuming +// that the true values lie on a paraboloid +// z = p0*x*x + p1*x*y + p2*y*y + p3*x + p4*y + p5 = Dot(P,Q(x,y)) +// where P = (p0,p1,p2,p3,p4,p5) and Q(x,y) = (x*x,x*y,y*y,x,y,1), +// select P to minimize the sum of squared errors +// E(P) = sum_{i=0}^{N-1} [Dot(P,Q_i)-z_i]^2 +// where Q_i = Q(x_i,y_i). +// +// The minimum occurs when the gradient of E is the zero vector, +// grad(E) = 2 sum_{i=0}^{N-1} [Dot(P,Q_i)-z_i] Q_i = 0 +// Some algebra converts this to a system of 6 equations in 6 unknowns: +// [(sum_{i=0}^{N-1} Q_i Q_i^t] P = sum_{i=0}^{N-1} z_i Q_i +// The product Q_i Q_i^t is a product of the 6x1 matrix Q_i with the +// 1x6 matrix Q_i^t, the result being a 6x6 matrix. +// +// Define the 6x6 symmetric matrix A = sum_{i=0}^{N-1} Q_i Q_i^t and the 6x1 +// vector B = sum_{i=0}^{N-1} z_i Q_i. The choice for P is the solution to +// the linear system of equations A*P = B. The entries of A and B indicate +// summations over the appropriate product of variables. For example, +// s(x^3 y) = sum_{i=0}^{N-1} x_i^3 y_i. +// +// +- -++ + +- -+ +// | s(x^4) s(x^3 y) s(x^2 y^2) s(x^3) s(x^2 y) s(x^2) ||p0| |s(z x^2)| +// | s(x^2 y^2) s(x y^3) s(x^2 y) s(x y^2) s(x y) ||p1| |s(z x y)| +// | s(y^4) s(x y^2) s(y^3) s(y^2) ||p2| = |s(z y^2)| +// | s(x^2) s(x y) s(x) ||p3| |s(z x) | +// | s(y^2) s(y) ||p4| |s(z y) | +// | s(1) ||p5| |s(z) | +// +- -++ + +- -+ + +namespace gte +{ + +template +class ApprParaboloid3 +{ +public: + bool operator()(int numPoints, Vector3 const* points, + Real coefficients[6]); +}; + + +template +bool ApprParaboloid3::operator()(int numPoints, + Vector3 const* points, Real coefficients[6]) +{ + Matrix<6, 6, Real> A; + Vector<6, Real> B; + for (int i = 0; i < numPoints; i++) + { + Real x2 = points[i][0] * points[i][0]; + Real xy = points[i][0] * points[i][1]; + Real y2 = points[i][1] * points[i][1]; + Real zx = points[i][2] * points[i][0]; + Real zy = points[i][2] * points[i][1]; + Real x3 = points[i][0] * x2; + Real x2y = x2*points[i][1]; + Real xy2 = points[i][0] * y2; + Real y3 = points[i][1] * y2; + Real zx2 = points[i][2] * x2; + Real zxy = points[i][2] * xy; + Real zy2 = points[i][2] * y2; + Real x4 = x2*x2; + Real x3y = x3*points[i][1]; + Real x2y2 = x2*y2; + Real xy3 = points[i][0] * y3; + Real y4 = y2*y2; + + A(0, 0) += x4; + A(0, 1) += x3y; + A(0, 2) += x2y2; + A(0, 3) += x3; + A(0, 4) += x2y; + A(0, 5) += x2; + A(1, 2) += xy3; + A(1, 4) += xy2; + A(1, 5) += xy; + A(2, 2) += y4; + A(2, 4) += y3; + A(2, 5) += y2; + A(3, 3) += x2; + A(3, 5) += points[i][0]; + A(4, 5) += points[i][1]; + + B[0] += zx2; + B[1] += zxy; + B[2] += zy2; + B[3] += zx; + B[4] += zy; + B[5] += points[i][2]; + } + + A(1, 0) = A(0, 1); + A(1, 1) = A(0, 2); + A(1, 3) = A(0, 4); + A(2, 0) = A(0, 2); + A(2, 1) = A(1, 2); + A(2, 3) = A(1, 4); + A(3, 0) = A(0, 3); + A(3, 1) = A(1, 3); + A(3, 2) = A(2, 3); + A(3, 4) = A(1, 5); + A(4, 0) = A(0, 4); + A(4, 1) = A(1, 4); + A(4, 2) = A(2, 4); + A(4, 3) = A(3, 4); + A(4, 4) = A(2, 5); + A(5, 0) = A(0, 5); + A(5, 1) = A(1, 5); + A(5, 2) = A(2, 5); + A(5, 3) = A(3, 5); + A(5, 4) = A(4, 5); + A(5, 5) = static_cast(numPoints); + + return LinearSystem().Solve(6, &A[0], &B[0], &coefficients[0]); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial2.h new file mode 100644 index 000000000000..aac79a8b84b5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial2.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],w[i]) for 0 <= i < S. Think of w as a function of +// x, say w = f(x). The function fits the samples with a polynomial of +// degree d, say w = sum_{i=0}^d c[i]*x^i. The method is a least-squares +// fitting algorithm. The mParameters stores the coefficients c[i] for +// 0 <= i <= d. The observation type is std::array, which represents +// a pair (x,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d](x)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',w') = ((x-xcen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, and +// rng = xmax-xmin. Fit the (x',w') points, +// w' = sum_{i=0}^d c'[i]*(x')^i. +// The original polynomial is evaluated as +// w = rng*sum_{i=0}^d c'[i]*((x-xcen)/rng)^i + +namespace gte +{ + +template +class ApprPolynomial2 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. + ApprPolynomial2(int degree); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate (x in domain) or extrapolate (x not in domain). + std::array const& GetXDomain() const; + Real Evaluate(Real x) const; + +private: + int mDegree, mSize; + std::array mXDomain; + std::vector mParameters; +}; + + +template +ApprPolynomial2::ApprPolynomial2(int degree) + : + mDegree(degree), + mSize(degree + 1), + mParameters(mSize) +{ + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); +} + +template +int ApprPolynomial2::GetMinimumRequired() const +{ + return mSize; +} + +template +bool ApprPolynomial2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + int s, i0, i1; + + // Compute the powers of x. + int numSamples = static_cast(indices.size()); + int twoDegree = 2 * mDegree; + Array2 xPower(twoDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the right-hand + // side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (i0 = 0; i0 <= mDegree; ++i0) + { + Real sum = (Real)0; + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][1]; + sum += w * xPower[s][i0]; + } + + B[i0] = sum; + + for (i1 = 0; i1 <= mDegree; ++i1) + { + sum = (Real)0; + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1]; + } + + A(i0, i1) = sum; + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomial2::Error(std::array const& observation) +const +{ + Real w = Evaluate(observation[0]); + Real error = std::abs(w - observation[1]); + return error; +} + +template +std::vector const& ApprPolynomial2::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomial2::GetXDomain() const +{ + return mXDomain; +} + +template +Real ApprPolynomial2::Evaluate(Real x) const +{ + int i = mDegree; + Real w = mParameters[i]; + while (--i >= 0) + { + w = mParameters[i] + w * x; + } + return w; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial3.h new file mode 100644 index 000000000000..ccab10357519 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial3.h @@ -0,0 +1,256 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],y[i],w[i]) for 0 <= i < S. Think of w as a function +// of x and y, say w = f(x,y). The function fits the samples with a +// polynomial of degree d0 in x and degree d1 in y, say +// w = sum_{i=0}^{d0} sum_{j=0}^{d1} c[i][j]*x^i*y^j +// The method is a least-squares fitting algorithm. The mParameters stores +// c[i][j] = mParameters[i+(d0+1)*j] for a total of (d0+1)*(d1+1) +// coefficients. The observation type is std::array, which represents +// a triple (x,y,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d0), (1,y,y^2,...,y^d1) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d0](x)), (g[0](y),...,g[d1](y)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',y',w') = ((x-xcen)/rng, (y-ycen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, +// ymin = min(y[i]), ymax = max(y[i]), ycen = (ymin+ymax)/2, and +// rng = max(xmax-xmin,ymax-ymin). Fit the (x',y',w') points, +// w' = sum_{i=0}^{d0} sum_{j=0}^{d1} c'[i][j]*(x')^i*(y')^j +// The original polynomial is evaluated as +// w = rng * sum_{i=0}^{d0} sum_{j=0}^{d1} c'[i][j] * +// ((x-xcen)/rng)^i * ((y-ycen)/rng)^j + +namespace gte +{ + +template +class ApprPolynomial3 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. + ApprPolynomial3(int xDegree, int yDegree); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,y0,w0) is + // |w(x0,y0) - w0|, where w(x,y) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain intervals are provided so you can + // interpolate ((x,y) in domain) or extrapolate ((x,y) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + Real Evaluate(Real x, Real y) const; + +private: + int mXDegree, mYDegree, mXDegreeP1, mYDegreeP1, mSize; + std::array mXDomain, mYDomain; + std::vector mParameters; + + // This array is used by Evaluate() to avoid reallocation of the 'vector' + // for each call. The member is mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mYCoefficient; +}; + + +template +ApprPolynomial3::ApprPolynomial3(int xDegree, int yDegree) + : + mXDegree(xDegree), + mYDegree(yDegree), + mXDegreeP1(xDegree + 1), + mYDegreeP1(yDegree + 1), + mSize(mXDegreeP1 * mYDegreeP1), + mParameters(mSize), + mYCoefficient(mYDegreeP1) +{ + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + std::fill(mYCoefficient.begin(), mYCoefficient.end(), (Real)0); +} + +template +int ApprPolynomial3::GetMinimumRequired() const +{ + return mSize; +} + +template +bool ApprPolynomial3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + int s, i0, j0, k0, i1, j1, k1; + + // Compute the powers of x and y. + int numSamples = static_cast(indices.size()); + int twoXDegree = 2 * mXDegree; + int twoYDegree = 2 * mYDegree; + Array2 xPower(twoXDegree + 1, numSamples); + Array2 yPower(twoYDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + Real y = observations[indices[s]][1]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + mYDomain[0] = std::min(y, mYDomain[0]); + mYDomain[1] = std::max(y, mYDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoXDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + + yPower[s][0] = (Real)1; + for (j0 = 1; j0 <= twoYDegree; ++j0) + { + yPower[s][j0] = y * yPower[s][j0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the right-hand + // side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (j0 = 0; j0 <= mYDegree; ++j0) + { + for (i0 = 0; i0 <= mXDegree; ++i0) + { + Real sum = (Real)0; + k0 = i0 + mXDegreeP1 * j0; + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][2]; + sum += w * xPower[s][i0] * yPower[s][j0]; + } + + B[k0] = sum; + + for (j1 = 0; j1 <= mYDegree; ++j1) + { + for (i1 = 0; i1 <= mXDegree; ++i1) + { + sum = (Real)0; + k1 = i1 + mXDegreeP1 * j1; + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1] * yPower[s][j0 + j1]; + } + + A(k0, k1) = sum; + } + } + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomial3::Error(std::array const& observation) +const +{ + Real w = Evaluate(observation[0], observation[1]); + Real error = std::abs(w - observation[2]); + return error; +} + +template +std::vector const& ApprPolynomial3::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomial3::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomial3::GetYDomain() const +{ + return mYDomain; +} + +template +Real ApprPolynomial3::Evaluate(Real x, Real y) const +{ + int i0, i1; + Real w; + + for (i1 = 0; i1 <= mYDegree; ++i1) + { + i0 = mXDegree; + w = mParameters[i0 + mXDegreeP1 * i1]; + while (--i0 >= 0) + { + w = mParameters[i0 + mXDegreeP1 * i1] + w * x; + } + mYCoefficient[i1] = w; + } + + i1 = mYDegree; + w = mYCoefficient[i1]; + while (--i1 >= 0) + { + w = mYCoefficient[i1] + w * y; + } + + return w; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial4.h new file mode 100644 index 000000000000..6726d67d719f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomial4.h @@ -0,0 +1,310 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],y[i],z[i],w[i]) for 0 <= i < S. Think of w as a +// function of x, y, and z, say w = f(x,y,z). The function fits the samples +// with a polynomial of degree d0 in x, degree d1 in y, and degree d2 in z, +// say +// w = sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c[i][j][k]*x^i*y^j*z^k +// The method is a least-squares fitting algorithm. The mParameters stores +// c[i][j][k] = mParameters[i+(d0+1)*(j+(d1+1)*k)] for a total of +// (d0+1)*(d1+1)*(d2+1) coefficients. The observation type is +// std::array, which represents a tuple (x,y,z,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d0), (1,y,y^2,...,y^d1), (1,z,z^2,...,z^d2) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d0](x)), (g[0](y),...,g[d1](y)), (h[0](z),...,h[d2](z)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',y',z',w') = ((x-xcen)/rng, (y-ycen)/rng, (z-zcen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, +// ymin = min(y[i]), ymax = max(y[i]), ycen = (ymin+ymax)/2, zmin = min(z[i]), +// zmax = max(z[i]), zcen = (zmin+zmax)/2, and +// rng = max(xmax-xmin,ymax-ymin,zmax-zmin). Fit the (x',y',z',w') points, +// w' = sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c'[i][j][k] * +// (x')^i*(y')^j*(z')^k +// The original polynomial is evaluated as +// w = rng * sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c'[i][j][k] * +// ((x-xcen)/rng)^i * ((y-ycen)/rng)^j * ((z-zcen)/rng)^k + +namespace gte +{ + +template +class ApprPolynomial4 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. + ApprPolynomial4(int xDegree, int yDegree, int zDegree); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,y0,z0,w0) is + // |w(x0,y0,z0) - w0|, where w(x,y,z) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain intervals are provided so you can + // interpolate ((x,y,z) in domain) or extrapolate ((x,y,z) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + std::array const& GetZDomain() const; + Real Evaluate(Real x, Real y, Real z) const; + +private: + int mXDegree, mYDegree, mZDegree; + int mXDegreeP1, mYDegreeP1, mZDegreeP1, mSize; + std::array mXDomain, mYDomain, mZDomain; + std::vector mParameters; + + // These arrays are used by Evaluate() to avoid reallocation of the + // 'vector's for each call. The member is mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mYZCoefficient; + mutable std::vector mZCoefficient; +}; + + +template +ApprPolynomial4::ApprPolynomial4(int xDegree, int yDegree, int zDegree) + : + mXDegree(xDegree), + mYDegree(yDegree), + mZDegree(zDegree), + mXDegreeP1(xDegree + 1), + mYDegreeP1(yDegree + 1), + mZDegreeP1(zDegree + 1), + mSize(mXDegreeP1 * mYDegreeP1 * mZDegreeP1), + mParameters(mSize), + mYZCoefficient(mYDegreeP1 * mZDegreeP1), + mZCoefficient(mZDegreeP1) +{ + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mZDomain[0] = std::numeric_limits::max(); + mZDomain[1] = -mZDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + std::fill(mYZCoefficient.begin(), mYZCoefficient.end(), (Real)0); + std::fill(mZCoefficient.begin(), mZCoefficient.end(), (Real)0); +} + +template +int ApprPolynomial4::GetMinimumRequired() const +{ + return mSize; +} + +template +bool ApprPolynomial4::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + int s, i0, j0, k0, n0, i1, j1, k1, n1; + + // Compute the powers of x, y, and z. + int numSamples = static_cast(indices.size()); + int twoXDegree = 2 * mXDegree; + int twoYDegree = 2 * mYDegree; + int twoZDegree = 2 * mZDegree; + Array2 xPower(twoXDegree + 1, numSamples); + Array2 yPower(twoYDegree + 1, numSamples); + Array2 zPower(twoZDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + Real y = observations[indices[s]][1]; + Real z = observations[indices[s]][2]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + mYDomain[0] = std::min(y, mYDomain[0]); + mYDomain[1] = std::max(y, mYDomain[1]); + mZDomain[0] = std::min(z, mZDomain[0]); + mZDomain[1] = std::max(z, mZDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoXDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + + yPower[s][0] = (Real)1; + for (j0 = 1; j0 <= twoYDegree; ++j0) + { + yPower[s][j0] = y * yPower[s][j0 - 1]; + } + + zPower[s][0] = (Real)1; + for (k0 = 1; k0 <= twoZDegree; ++k0) + { + zPower[s][k0] = z * zPower[s][k0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the right-hand + // side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (k0 = 0; k0 <= mZDegree; ++k0) + { + for (j0 = 0; j0 <= mYDegree; ++j0) + { + for (i0 = 0; i0 <= mXDegree; ++i0) + { + Real sum = (Real)0; + n0 = i0 + mXDegreeP1*(j0 + mYDegreeP1*k0); + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][3]; + sum += w * xPower[s][i0] * yPower[s][j0] * + zPower[s][k0]; + } + + B[n0] = sum; + + for (k1 = 0; k1 <= mZDegree; ++k1) + { + for (j1 = 0; j1 <= mYDegree; ++j1) + { + for (i1 = 0; i1 <= mXDegree; ++i1) + { + sum = (Real)0; + n1 = i1 + mXDegreeP1*(j1 + mYDegreeP1*k1); + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1] * + yPower[s][j0 + j1] * + zPower[s][k0 + k1]; + } + + A(n0, n1) = sum; + } + } + } + } + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomial4::Error(std::array const& observation) +const +{ + Real w = Evaluate(observation[0], observation[1], observation[2]); + Real error = std::abs(w - observation[3]); + return error; +} + +template +std::vector const& ApprPolynomial4::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomial4::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomial4::GetYDomain() const +{ + return mYDomain; +} + +template +std::array const& ApprPolynomial4::GetZDomain() const +{ + return mZDomain; +} + +template +Real ApprPolynomial4::Evaluate(Real x, Real y, Real z) const +{ + int i0, i1, i2; + Real w; + + for (i2 = 0; i2 <= mZDegree; ++i2) + { + for (i1 = 0; i1 <= mYDegree; ++i1) + { + i0 = mXDegree; + w = mParameters[i0 + mXDegreeP1 * (i1 + mYDegreeP1 * i2)]; + while (--i0 >= 0) + { + w = mParameters[i0 + mXDegreeP1 * (i1 + mYDegreeP1 * i2)] + + w * x; + } + // @todo original line here did no assignment, is this correct? Assume will be fixed in future versions of GTEngine. + mYZCoefficient[i1 + mYDegree * i2] = w; + } + } + + for (i2 = 0; i2 <= mZDegree; ++i2) + { + i1 = mYDegree; + w = mYZCoefficient[i1 + mYDegreeP1 * i2]; + while (--i1 >= 0) + { + w = mParameters[i1 + mYDegreeP1 * i2] + w * y; + } + mZCoefficient[i2] = w; + } + + i2 = mZDegree; + w = mZCoefficient[i2]; + while (--i2 >= 0) + { + w = mZCoefficient[i2] + w * z; + } + + return w; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial2.h new file mode 100644 index 000000000000..db83dd02e2fb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial2.h @@ -0,0 +1,305 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]} +// where p[i] are distinct nonnegative powers provided by the caller. A +// least-squares fitting algorithm is used, but the input data is first +// mapped to (x,w) in [-1,1]^2 for numerical robustness. + +namespace gte +{ + +template +class ApprPolynomialSpecial2 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial2(std::vector const& degrees); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate (x in domain) or extrapolate (x not in domain). + std::array const& GetXDomain() const; + Real Evaluate(Real x) const; + +private: + // Transform the (x,w) values to (x',w') in [-1,1]^2. + void Transform(std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed); + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector>& transformed); + + std::vector mDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^2. The Evaluate() function must transform + // x to x' in [-1,1], compute w' in [-1,1], then transform w' to w. + std::array mXDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the 'vector' + // for each call. The member is mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mXPowers; +}; + + +template +ApprPolynomialSpecial2::ApprPolynomialSpecial2( + std::vector const& degrees) + : + mDegrees(degrees), + mParameters(degrees.size()) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(mDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x are computed up to twice the powers when constructing the + // fitted polynomial. Powers of x are computed up to the powers for the + // evaluation of the fitted polynomial. + mXPowers.resize(2 * mDegrees.back() + 1); + mXPowers[0] = (Real)1; +} + +template +int ApprPolynomialSpecial2::GetMinimumRequired() const +{ + return static_cast(mParameters.size()); +} + +template +bool ApprPolynomialSpecial2::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + // Transform the observations to [-1,1]^2 for numerical robustness. + std::vector> transformed; + Transform(observations, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomialSpecial2::Error( + std::array const& observation) const +{ + Real w = Evaluate(observation[0]); + Real error = std::abs(w - observation[1]); + return error; +} + +template +std::vector const& ApprPolynomialSpecial2::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomialSpecial2::GetXDomain() const +{ + return mXDomain; +} + +template +Real ApprPolynomialSpecial2::Evaluate(Real x) const +{ + // Transform x to x' in [-1,1]. + x = (Real)-1 + ((Real)2) * mScale[0] * (x - mXDomain[0]); + + // Compute relevant powers of x. + int jmax = mDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + Real w = (Real)0; + int isup = static_cast(mDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mDegrees[i]]; + w += mParameters[i] * xp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; +} + +template +void ApprPolynomialSpecial2::Transform( + std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed) +{ + int numSamples = static_cast(indices.size()); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 2; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mWDomain[0] = omin[1]; + mWDomain[1] = omax[1]; + for (i = 0; i < 2; ++i) + { + mScale[i] = ((Real)1) / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 2; ++i) + { + transformed[s][i] = (Real)-1 + ((Real)2) * mScale[i] * + (obs[i] - omin[i]); + } + } + mInvTwoWScale = ((Real)0.5) / mScale[1]; +} + +template +bool ApprPolynomialSpecial2::DoLeastSquares( + std::vector>& transformed) +{ + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x. + Real x = transformed[i][0]; + Real w = transformed[i][1]; + for (int j = 0; j <= twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric matrix. + for (col = row; col < size; ++col) + { + A(row, col) += mXPowers[mDegrees[row] + mDegrees[col]]; + } + + // Update the right-hand side of the system. + B[row] += mXPowers[mDegrees[row]] * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to the + // lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = ((Real)1) / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial3.h new file mode 100644 index 000000000000..a50cf266b1c5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial3.h @@ -0,0 +1,352 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]}*y^{q[i]} +// where are distinct pairs of nonnegative powers provided by the +// caller. A least-squares fitting algorithm is used, but the input data is +// first mapped to (x,y,w) in [-1,1]^3 for numerical robustness. + +namespace gte +{ + +template +class ApprPolynomialSpecial3 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial3(std::vector const& xDegrees, + std::vector const& yDegrees); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate ((x,y) in domain) or extrapolate ((x,y) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + Real Evaluate(Real x, Real y) const; + +private: + // Transform the (x,y,w) values to (x',y',w') in [-1,1]^3. + void Transform(std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed); + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector>& transformed); + + std::vector mXDegrees, mYDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^3. The Evaluate() function must transform + // (x,y) to (x',y') in [-1,1]^2, compute w' in [-1,1], then transform w' + // to w. + std::array mXDomain, mYDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the 'vector's + // for each call. The members are mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mXPowers, mYPowers; +}; + + +template +ApprPolynomialSpecial3::ApprPolynomialSpecial3( + std::vector const& xDegrees, std::vector const& yDegrees) + : + mXDegrees(xDegrees), + mYDegrees(yDegrees), + mParameters(mXDegrees.size() * mYDegrees.size()) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(mXDegrees.size() == mYDegrees.size(), + "The input arrays must have the same size."); + + LogAssert(mXDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mXDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mYDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mYDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mScale[2] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x and y are computed up to twice the powers when constructing + // the fitted polynomial. Powers of x and y are computed up to the powers + // for the evaluation of the fitted polynomial. + mXPowers.resize(2 * mXDegrees.back() + 1); + mXPowers[0] = (Real)1; + mYPowers.resize(2 * mYDegrees.back() + 1); + mYPowers[0] = (Real)1; +} + +template +int ApprPolynomialSpecial3::GetMinimumRequired() const +{ + return static_cast(mParameters.size()); +} + +template +bool ApprPolynomialSpecial3::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + // Transform the observations to [-1,1]^3 for numerical robustness. + std::vector> transformed; + Transform(observations, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomialSpecial3::Error( + std::array const& observation) const +{ + Real w = Evaluate(observation[0], observation[1]); + Real error = std::abs(w - observation[2]); + return error; +} + +template +std::vector const& ApprPolynomialSpecial3::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomialSpecial3::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomialSpecial3::GetYDomain() const +{ + return mYDomain; +} + +template +Real ApprPolynomialSpecial3::Evaluate(Real x, Real y) const +{ + // Transform (x,y) to (x',y') in [-1,1]^2. + x = (Real)-1 + ((Real)2) * mScale[0] * (x - mXDomain[0]); + y = (Real)-1 + ((Real)2) * mScale[1] * (y - mYDomain[0]); + + // Compute relevant powers of x and y. + int jmax = mXDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + jmax = mYDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + Real w = (Real)0; + int isup = static_cast(mXDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mXDegrees[i]]; + Real yp = mYPowers[mYDegrees[i]]; + w += mParameters[i] * xp * yp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; +} + +template +void ApprPolynomialSpecial3::Transform( + std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed) +{ + int numSamples = static_cast(indices.size()); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 3; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mYDomain[0] = omin[1]; + mYDomain[1] = omax[1]; + mWDomain[0] = omin[2]; + mWDomain[1] = omax[2]; + for (i = 0; i < 3; ++i) + { + mScale[i] = ((Real)1) / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 3; ++i) + { + transformed[s][i] = (Real)-1 + ((Real)2) * mScale[i] * + (obs[i] - omin[i]); + } + } + mInvTwoWScale = ((Real)0.5) / mScale[2]; +} + +template +bool ApprPolynomialSpecial3::DoLeastSquares( + std::vector>& transformed) +{ + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mXDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mXDegrees.back(); + int twoMaxYDegree = 2 * mYDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x and y. + Real x = transformed[i][0]; + Real y = transformed[i][1]; + Real w = transformed[i][2]; + for (int j = 1; j <= 2 * twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + for (int j = 1; j <= 2 * twoMaxYDegree; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric matrix. + Real xp, yp; + for (col = row; col < size; ++col) + { + xp = mXPowers[mXDegrees[row] + mXDegrees[col]]; + yp = mYPowers[mYDegrees[row] + mYDegrees[col]]; + A(row, col) += xp * yp; + } + + // Update the right-hand side of the system. + xp = mXPowers[mXDegrees[row]]; + yp = mYPowers[mYDegrees[row]]; + B[row] += xp * yp * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to the + // lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = ((Real)1) / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial4.h new file mode 100644 index 000000000000..4bbcf10c51c8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprPolynomialSpecial4.h @@ -0,0 +1,393 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]}*y^{q[i]}*z^{r[i]} +// where are distinct triples of nonnegative powers provided +// by the caller. A least-squares fitting algorithm is used, but the input +// data is first mapped to (x,y,z,w) in [-1,1]^4 for numerical robustness. + +namespace gte +{ + +template +class ApprPolynomialSpecial4 + : + public ApprQuery, std::array> +{ +public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial4(std::vector const& xDegrees, + std::vector const& yDegrees, std::vector const& zDegrees); + + // The minimum number of observations required to fit the model. + int GetMinimumRequired() const; + + // Estimate the model parameters for all observations specified by the + // indices. This function is called by the base-class Fit(...) functions. + bool Fit(std::vector> const& observations, + std::vector const& indices); + + // Compute the model error for the specified observation for the current + // model parameters. The returned value for observation (x0,w0) is + // |w(x0) - w0|, where w(x) is the fitted polynomial. + Real Error(std::array const& observation) const; + + // Get the parameters of the model. + std::vector const& GetParameters() const; + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate ((x,y,z) in domain) or extrapolate ((x,y,z) not in domain). + std::array const& GetXDomain() const; + std::array const& GetYDomain() const; + std::array const& GetZDomain() const; + Real Evaluate(Real x, Real y, Real z) const; + +private: + // Transform the (x,y,z,w) values to (x',y',z',w') in [-1,1]^4. + void Transform(std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed); + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector>& transformed); + + std::vector mXDegrees, mYDegrees, mZDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^4. The Evaluate() function must transform + // (x,y,z) to (x',y',z') in [-1,1]^3, compute w' in [-1,1], then transform + // w' to w. + std::array mXDomain, mYDomain, mZDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the 'vector's + // for each call. The members are mutable because, to the user, the call + // to Evaluate does not modify the polynomial. + mutable std::vector mXPowers, mYPowers, mZPowers; +}; + + +template +ApprPolynomialSpecial4::ApprPolynomialSpecial4( + std::vector const& xDegrees, std::vector const& yDegrees, + std::vector const& zDegrees) + : + mXDegrees(xDegrees), + mYDegrees(yDegrees), + mZDegrees(zDegrees), + mParameters(mXDegrees.size() * mYDegrees.size() * mZDegrees.size()) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(mXDegrees.size() == mYDegrees.size() + && mXDegrees.size() == mZDegrees.size(), + "The input arrays must have the same size."); + + LogAssert(mXDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mXDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mYDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mYDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mZDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mZDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mZDomain[0] = std::numeric_limits::max(); + mZDomain[1] = -mZDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mScale[2] = (Real)0; + mScale[3] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x, y, and z are computed up to twice the powers when + // constructing the fitted polynomial. Powers of x, y, and z are + // computed up to the powers for the evaluation of the fitted polynomial. + mXPowers.resize(2 * mXDegrees.back() + 1); + mXPowers[0] = (Real)1; + mYPowers.resize(2 * mYDegrees.back() + 1); + mYPowers[0] = (Real)1; + mZPowers.resize(2 * mZDegrees.back() + 1); + mZPowers[0] = (Real)1; +} + +template +int ApprPolynomialSpecial4::GetMinimumRequired() const +{ + return static_cast(mParameters.size()); +} + +template +bool ApprPolynomialSpecial4::Fit( + std::vector> const& observations, + std::vector const& indices) +{ + if (indices.size() > 0) + { + // Transform the observations to [-1,1]^4 for numerical robustness. + std::vector> transformed; + Transform(observations, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; +} + +template +Real ApprPolynomialSpecial4::Error( + std::array const& observation) const +{ + Real w = Evaluate(observation[0], observation[1], observation[2]); + Real error = std::abs(w - observation[3]); + return error; +} + +template +std::vector const& ApprPolynomialSpecial4::GetParameters() const +{ + return mParameters; +} + +template +std::array const& ApprPolynomialSpecial4::GetXDomain() const +{ + return mXDomain; +} + +template +std::array const& ApprPolynomialSpecial4::GetYDomain() const +{ + return mYDomain; +} + +template +std::array const& ApprPolynomialSpecial4::GetZDomain() const +{ + return mZDomain; +} + +template +Real ApprPolynomialSpecial4::Evaluate(Real x, Real y, Real z) const +{ + // Transform (x,y,z) to (x',y',z') in [-1,1]^3. + x = (Real)-1 + ((Real)2) * mScale[0] * (x - mXDomain[0]); + y = (Real)-1 + ((Real)2) * mScale[1] * (y - mYDomain[0]); + z = (Real)-1 + ((Real)2) * mScale[2] * (z - mZDomain[0]); + + // Compute relevant powers of x, y, and z. + int jmax = mXDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + jmax = mYDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + jmax = mZDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mZPowers[j] = mZPowers[j - 1] * z; + } + + Real w = (Real)0; + int isup = static_cast(mXDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mXDegrees[i]]; + Real yp = mYPowers[mYDegrees[i]]; + Real zp = mYPowers[mZDegrees[i]]; + w += mParameters[i] * xp * yp * zp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; +} + +template +void ApprPolynomialSpecial4::Transform( + std::vector> const& observations, + std::vector const& indices, + std::vector>& transformed) +{ + int numSamples = static_cast(indices.size()); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 4; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mYDomain[0] = omin[1]; + mYDomain[1] = omax[1]; + mZDomain[0] = omin[2]; + mZDomain[1] = omax[2]; + mWDomain[0] = omin[3]; + mWDomain[1] = omax[3]; + for (i = 0; i < 4; ++i) + { + mScale[i] = ((Real)1) / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 4; ++i) + { + transformed[s][i] = (Real)-1 + ((Real)2) * mScale[i] * + (obs[i] - omin[i]); + } + } + mInvTwoWScale = ((Real)0.5) / mScale[3]; +} + +template +bool ApprPolynomialSpecial4::DoLeastSquares( + std::vector>& transformed) +{ + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mXDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mXDegrees.back(); + int twoMaxYDegree = 2 * mYDegrees.back(); + int twoMaxZDegree = 2 * mZDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x, y, and z. + Real x = transformed[i][0]; + Real y = transformed[i][1]; + Real z = transformed[i][2]; + Real w = transformed[i][3]; + for (int j = 1; j <= twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + for (int j = 1; j <= twoMaxYDegree; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + for (int j = 1; j <= twoMaxZDegree; ++j) + { + mZPowers[j] = mZPowers[j - 1] * z; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric matrix. + Real xp, yp, zp; + for (col = row; col < size; ++col) + { + xp = mXPowers[mXDegrees[row] + mXDegrees[col]]; + yp = mYPowers[mYDegrees[row] + mYDegrees[col]]; + zp = mZPowers[mZDegrees[row] + mZDegrees[col]]; + A(row, col) += xp * yp * zp; + } + + // Update the right-hand side of the system. + xp = mXPowers[mXDegrees[row]]; + yp = mYPowers[mYDegrees[row]]; + zp = mZPowers[mZDegrees[row]]; + B[row] += xp * yp * zp * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to the + // lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = ((Real)1) / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic2.h new file mode 100644 index 000000000000..94c622aa4dda --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic2.h @@ -0,0 +1,218 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +// The quadratic fit is +// +// 0 = C[0] + C[1]*X + C[2]*Y + C[3]*X^2 + C[4]*Y^2 + C[5]*X*Y +// +// subject to Length(C) = 1. Minimize E(C) = C^t M C with Length(C) = 1 +// and M = (sum_i V_i)(sum_i V_i)^t where +// +// V = (1, X, Y, X^2, Y^2, X*Y) +// +// The minimum value is the smallest eigenvalue of M and C is a corresponding +// unit length eigenvector. +// +// Input: +// n = number of points to fit +// p[0..n-1] = array of points to fit +// +// Output: +// c[0..5] = coefficients of quadratic fit (the eigenvector) +// return value of function is nonnegative and a measure of the fit +// (the minimum eigenvalue; 0 = exact fit, positive otherwise) + +// Canonical forms. The quadratic equation can be factored into +// P^T A P + B^T P + K = 0 where P = (X,Y,Z), K = C[0], B = (C[1],C[2],C[3]), +// and A is a 3x3 symmetric matrix with A00 = C[4], A11 = C[5], A22 = C[6], +// A01 = C[7]/2, A02 = C[8]/2, and A12 = C[9]/2. Matrix A = R^T D R where +// R is orthogonal and D is diagonal (using an eigendecomposition). Define +// V = R P = (v0,v1,v2), E = R B = (e0,e1,e2), D = diag(d0,d1,d2), and f = K +// to obtain +// +// d0 v0^2 + d1 v1^2 + d2 v^2 + e0 v0 + e1 v1 + e2 v2 + f = 0 +// +// The characterization depends on the signs of the d_i. + +template +class ApprQuadratic2 +{ +public: + Real operator()(int numPoints, Vector2 const* points, + Real coefficients[6]); +}; + + +// If you think your points are nearly circular, use this. The circle is of +// the form C'[0]+C'[1]*X+C'[2]*Y+C'[3]*(X^2+Y^2), where Length(C') = 1. The +// function returns C = (C'[0]/C'[3],C'[1]/C'[3],C'[2]/C'[3]), so the fitted +// circle is C[0]+C[1]*X+C[2]*Y+X^2+Y^2. The center is (xc,yc) = +// -0.5*(C[1],C[2]) and the radius is r = sqrt(xc*xc+yc*yc-C[0]). + +template +class ApprQuadraticCircle2 +{ +public: + Real operator()(int numPoints, Vector2 const* points, + Circle2& circle); +}; + + +template +Real ApprQuadratic2::operator()(int numPoints, + Vector2 const* points, Real coefficients[6]) +{ + Matrix<6, 6, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real x2 = x*x; + Real y2 = y*y; + Real xy = x*y; + Real x3 = x*x2; + Real xy2 = x*y2; + Real x2y = x*xy; + Real y3 = y*y2; + Real x4 = x*x3; + Real x2y2 = x*xy2; + Real x3y = x*x2y; + Real y4 = y*y3; + Real xy3 = x*y3; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += x2; + A(0, 4) += y2; + A(0, 5) += xy; + A(1, 3) += x3; + A(1, 4) += xy2; + A(1, 5) += x2y; + A(2, 4) += y3; + A(3, 3) += x4; + A(3, 4) += x2y2; + A(3, 5) += x3y; + A(4, 4) += y4; + A(4, 5) += xy3; + } + + A(0, 0) = static_cast(numPoints); + A(1, 1) = A(0, 3); + A(1, 2) = A(0, 5); + A(2, 2) = A(0, 4); + A(2, 3) = A(1, 5); + A(2, 5) = A(1, 4); + A(5, 5) = A(3, 4); + + for (int row = 0; row < 6; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 6; ++row) + { + for (int col = 0; col < 6; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(6, 1024); + es.Solve(&A[0], +1); + es.GetEigenvector(0, &coefficients[0]); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + +template +Real ApprQuadraticCircle2::operator()(int numPoints, + Vector2 const* points, Circle2& circle) +{ + Matrix<4, 4, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real x2 = x*x; + Real y2 = y*y; + Real xy = x*y; + Real r2 = x2 + y2; + Real xr2 = x*r2; + Real yr2 = y*r2; + Real r4 = r2*r2; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += r2; + A(1, 1) += x2; + A(1, 2) += xy; + A(1, 3) += xr2; + A(2, 2) += y2; + A(2, 3) += yr2; + A(3, 3) += r4; + } + + A(0, 0) = static_cast(numPoints); + + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < 4; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(4, 1024); + es.Solve(&A[0], +1); + Vector<4, Real> evector; + es.GetEigenvector(0, &evector[0]); + + Real inv = ((Real)1) / evector[3]; // TODO: Guard against zero divide? + Real coefficients[3]; + for (int row = 0; row < 3; ++row) + { + coefficients[row] = inv * evector[row]; + } + + circle.center[0] = ((Real)-0.5) * coefficients[1]; + circle.center[1] = ((Real)-0.5) * coefficients[2]; + circle.radius = std::sqrt(std::abs(Dot(circle.center, circle.center) - coefficients[0])); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic3.h new file mode 100644 index 000000000000..b2372c5958d3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuadratic3.h @@ -0,0 +1,285 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +// The quadratic fit is +// +// 0 = C[0] + C[1]*X + C[2]*Y + C[3]*Z + C[4]*X^2 + C[5]*Y^2 +// + C[6]*Z^2 + C[7]*X*Y + C[8]*X*Z + C[9]*Y*Z +// +// subject to Length(C) = 1. Minimize E(C) = C^t M C with Length(C) = 1 +// and M = (sum_i V_i)(sum_i V_i)^t where +// +// V = (1, X, Y, Z, X^2, Y^2, Z^2, X*Y, X*Z, Y*Z) +// +// The minimum value is the smallest eigenvalue of M and C is a corresponding +// unit length eigenvector. +// +// Input: +// n = number of points to fit +// p[0..n-1] = array of points to fit +// +// Output: +// c[0..9] = coefficients of quadratic fit (the eigenvector) +// return value of function is nonnegative and a measure of the fit +// (the minimum eigenvalue; 0 = exact fit, positive otherwise) + +// Canonical forms. The quadratic equation can be factored into +// P^T A P + B^T P + K = 0 where P = (X,Y,Z), K = C[0], B = (C[1],C[2],C[3]), +// and A is a 3x3 symmetric matrix with A00 = C[4], A11 = C[5], A22 = C[6], +// A01 = C[7]/2, A02 = C[8]/2, and A12 = C[9]/2. Matrix A = R^T D R where +// R is orthogonal and D is diagonal (using an eigendecomposition). Define +// V = R P = (v0,v1,v2), E = R B = (e0,e1,e2), D = diag(d0,d1,d2), and f = K +// to obtain +// +// d0 v0^2 + d1 v1^2 + d2 v^2 + e0 v0 + e1 v1 + e2 v2 + f = 0 +// +// The characterization depends on the signs of the d_i. + +template +class ApprQuadratic3 +{ +public: + Real operator()(int numPoints, Vector3 const* points, + Real coefficidents[10]); +}; + + +// If you think your points are nearly spherical, use this. Sphere is of form +// C'[0]+C'[1]*X+C'[2]*Y+C'[3]*Z+C'[4]*(X^2+Y^2+Z^2) where Length(C') = 1. +// Function returns C = (C'[0]/C'[4],C'[1]/C'[4],C'[2]/C'[4],C'[3]/C'[4]), so +// fitted sphere is C[0]+C[1]*X+C[2]*Y+C[3]*Z+X^2+Y^2+Z^2. Center is +// (xc,yc,zc) = -0.5*(C[1],C[2],C[3]) and radius is rad = +// sqrt(xc*xc+yc*yc+zc*zc-C[0]). +template +class ApprQuadraticSphere3 +{ +public: + Real operator()(int numPoints, Vector3 const* points, + Sphere3& sphere); +}; + + +template +Real ApprQuadratic3::operator()(int numPoints, + Vector3 const* points, Real coefficients[10]) +{ + Matrix<10, 10, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real z = points[i][2]; + Real x2 = x*x; + Real y2 = y*y; + Real z2 = z*z; + Real xy = x*y; + Real xz = x*z; + Real yz = y*z; + Real x3 = x*x2; + Real xy2 = x*y2; + Real xz2 = x*z2; + Real x2y = x*xy; + Real x2z = x*xz; + Real xyz = x*y*z; + Real y3 = y*y2; + Real yz2 = y*z2; + Real y2z = y*yz; + Real z3 = z*z2; + Real x4 = x*x3; + Real x2y2 = x*xy2; + Real x2z2 = x*xz2; + Real x3y = x*x2y; + Real x3z = x*x2z; + Real x2yz = x*xyz; + Real y4 = y*y3; + Real y2z2 = y*yz2; + Real xy3 = x*y3; + Real xy2z = x*y2z; + Real y3z = y*y2z; + Real z4 = z*z3; + Real xyz2 = x*yz2; + Real xz3 = x*z3; + Real yz3 = y*z3; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += z; + A(0, 4) += x2; + A(0, 5) += y2; + A(0, 6) += z2; + A(0, 7) += xy; + A(0, 8) += xz; + A(0, 9) += yz; + A(1, 4) += x3; + A(1, 5) += xy2; + A(1, 6) += xz2; + A(1, 7) += x2y; + A(1, 8) += x2z; + A(1, 9) += xyz; + A(2, 5) += y3; + A(2, 6) += yz2; + A(2, 9) += y2z; + A(3, 6) += z3; + A(4, 4) += x4; + A(4, 5) += x2y2; + A(4, 6) += x2z2; + A(4, 7) += x3y; + A(4, 8) += x3z; + A(4, 9) += x2yz; + A(5, 5) += y4; + A(5, 6) += y2z2; + A(5, 7) += xy3; + A(5, 8) += xy2z; + A(5, 9) += y3z; + A(6, 6) += z4; + A(6, 7) += xyz2; + A(6, 8) += xz3; + A(6, 9) += yz3; + A(9, 9) += y2z2; + } + + A(0, 0) = static_cast(numPoints); + A(1, 1) = A(0, 4); + A(1, 2) = A(0, 7); + A(1, 3) = A(0, 8); + A(2, 2) = A(0, 5); + A(2, 3) = A(0, 9); + A(2, 4) = A(1, 7); + A(2, 7) = A(1, 5); + A(2, 8) = A(1, 9); + A(3, 3) = A(0, 6); + A(3, 4) = A(1, 8); + A(3, 5) = A(2, 9); + A(3, 7) = A(1, 9); + A(3, 8) = A(1, 6); + A(3, 9) = A(2, 6); + A(7, 7) = A(4, 5); + A(7, 8) = A(4, 9); + A(7, 9) = A(5, 8); + A(8, 8) = A(4, 6); + A(8, 9) = A(6, 7); + A(9, 9) = A(5, 6); + + for (int row = 0; row < 10; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 10; ++row) + { + for (int col = 0; col < 10; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(10, 1024); + es.Solve(&A[0], +1); + es.GetEigenvector(0, &coefficients[0]); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + +template +Real ApprQuadraticSphere3::operator()(int numPoints, + Vector3 const* points, Sphere3& sphere) +{ + Matrix<5, 5, Real> A; + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real z = points[i][2]; + Real x2 = x*x; + Real y2 = y*y; + Real z2 = z*z; + Real xy = x*y; + Real xz = x*z; + Real yz = y*z; + Real r2 = x2 + y2 + z2; + Real xr2 = x*r2; + Real yr2 = y*r2; + Real zr2 = z*r2; + Real r4 = r2*r2; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += z; + A(0, 4) += r2; + A(1, 1) += x2; + A(1, 2) += xy; + A(1, 3) += xz; + A(1, 4) += xr2; + A(2, 2) += y2; + A(2, 3) += yz; + A(2, 4) += yr2; + A(3, 3) += z2; + A(3, 4) += zr2; + A(4, 4) += r4; + } + + A(0, 0) = static_cast(numPoints); + + for (int row = 0; row < 5; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = ((Real)1) / static_cast(numPoints); + for (int row = 0; row < 5; ++row) + { + for (int col = 0; col < 5; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(5, 1024); + es.Solve(&A[0], +1); + Vector<5, Real> evector; + es.GetEigenvector(0, &evector[0]); + + Real inv = ((Real)1) / evector[4]; // TODO: Guard against zero divide? + Real coefficients[4]; + for (int row = 0; row < 4; ++row) + { + coefficients[row] = inv * evector[row]; + } + + sphere.center[0] = ((Real)-0.5) * coefficients[1]; + sphere.center[1] = ((Real)-0.5) * coefficients[2]; + sphere.center[2] = ((Real)-0.5) * coefficients[3]; + sphere.radius = std::sqrt(std::abs(Dot(sphere.center, sphere.center) - coefficients[0])); + + // For an exact fit, numeric round-off errors might make the minimum + // eigenvalue just slightly negative. Return the absolute value since + // the application might rely on the return value being nonnegative. + return std::abs(es.GetEigenvalue(0)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuery.h new file mode 100644 index 000000000000..1559c67f7464 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprQuery.h @@ -0,0 +1,207 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Class ApprQuery supports the RANSAC algorithm for fitting and uses the +// Curiously Recurring Template Pattern. The ModelType must be a class or +// struct with the following interfaces: +// +// // The minimum number of observations required to fit the model. +// int ModelType::GetMinimumRequired() const; +// +// // Compute the model error for the specified observation for the current +// // model parameters. +// Real Error(ObservationType const& observation) const; +// +// // Estimate the model parameters for all observations specified by the +// // indices. The three Fit() functions of ApprQuery manipulate their +// // inputs in order to pass them to ModelType::Fit(). +// ModelType::Fit(std::vector const& observations, +// std::vector const& indices); + +namespace gte +{ + +template +class ApprQuery +{ +public: + // Estimate the model parameters for all observations. + bool Fit(std::vector const& observations); + + // Estimate the model parameters for a contiguous subset of observations. + bool Fit(std::vector const& observations, + int const imin, int const imax); + + // Estimate the model parameters for the subset of observations specified + // by the indices and the number of indices that is possibly smaller than + // indices.size(). + bool Fit(std::vector const& observations, + std::vector const& indices, int const numIndices); + + // Apply the RANdom SAmple Consensus algorithm for fitting a model to + // observations. + static bool RANSAC( + ModelType& candidateModel, + std::vector const& observations, + int const numRequiredForGoodFit, Real const maxErrorForGoodFit, + int const numIterations, std::vector& bestConsensus, + ModelType& bestModel); +}; + + +template +bool ApprQuery::Fit( + std::vector const& observations) +{ + std::vector indices(observations.size()); + int i = 0; + for (auto& index : indices) + { + index = i++; + } + + return ((ModelType*)this)->Fit(observations, indices); +} + +template +bool ApprQuery::Fit( + std::vector const& observations, + int const imin, int const imax) +{ + if (imin <= imax) + { + int numIndices = imax - imin + 1; + std::vector indices(numIndices); + int i = imin; + for (auto& index : indices) + { + index = i++; + } + + return ((ModelType*)this)->Fit(observations, indices); + } + else + { + return false; + } +} + +template +bool ApprQuery::Fit( + std::vector const& observations, + std::vector const& indices, int const numIndices) +{ + int imax = std::min(numIndices, static_cast(observations.size())); + std::vector localindices(imax); + int i = 0; + for (auto& index : localindices) + { + index = indices[i++]; + } + + return ((ModelType*)this)->Fit(observations, indices); +} + +template +bool ApprQuery::RANSAC( + ModelType& candidateModel, + std::vector const& observations, + int const numRequiredForGoodFit, Real const maxErrorForGoodFit, + int const numIterations, std::vector& bestConsensus, + ModelType& bestModel) +{ + int const numObservations = static_cast(observations.size()); + int const minRequired = candidateModel.GetMinimumRequired(); + if (numObservations < minRequired) + { + // Too few observations for model fitting. + return false; + } + + // The first part of the array will store the consensus set, initially + // filled with the minimumu number of indices that correspond to the + // candidate inliers. The last part will store the remaining indices. + // These points are tested against the model and are added to the + // consensus set when they fit. All the index manipulation is done + // in place. Initially, the candidates are the identity permutation. + std::vector candidates(numObservations); + int j = 0; + for (auto& c : candidates) + { + c = j++; + } + + if (numObservations == minRequired) + { + // We have the minimum number of observations to generate the model, + // so RANSAC cannot be used. Compute the model with the entire set + // of observations. + bestConsensus = candidates; + return bestModel.Fit(observations); + } + + int bestNumFittedObservations = minRequired; + + for (int i = 0; i < numIterations; ++i) + { + // Randomly permute the previous candidates, partitioning the array + // into GetMinimumRequired() indices (the candidate inliers) followed + // by the remaining indices (candidates for testing against the + // model). + std::shuffle(candidates.begin(), candidates.end(), + std::default_random_engine()); + + // Fit the model to the inliers. + if (candidateModel.Fit(observations, candidates, minRequired)) + { + // Test each remaining observation whether it fits the model. If + // it does, include it in the consensus set. + int numFittedObservations = minRequired; + for (j = minRequired; j < numObservations; ++j) + { + if (candidateModel.Error(observations[candidates[j]]) + <= maxErrorForGoodFit) + { + std::swap(candidates[j], + candidates[numFittedObservations]); + ++numFittedObservations; + } + } + + if (numFittedObservations >= numRequiredForGoodFit) + { + // We have observations that fit the model. Update the best + // model using the consensus set. + candidateModel.Fit(observations, candidates, + numFittedObservations); + if (numFittedObservations > bestNumFittedObservations) + { + // The consensus set is larger than the previous consensus + // set, so its model becomes the best one. + bestModel = candidateModel; + bestConsensus.resize(numFittedObservations); + std::copy(candidates.begin(), + candidates.begin() + numFittedObservations, + bestConsensus.begin()); + bestNumFittedObservations = numFittedObservations; + } + } + } + } + + return bestNumFittedObservations >= numRequiredForGoodFit; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprSphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprSphere3.h new file mode 100644 index 000000000000..476516fb8fde --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprSphere3.h @@ -0,0 +1,101 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Least-squares fit of a sphere to a set of points. A successful fit is +// indicated by the return value of 'true'. The return value is the number +// of iterations used. If the number is the maximum, you can either accept +// the result or try increasing the maximum number and calling the function +// again. TODO: Currently, the test for terminating the algorithm is exact +// (diff == (0,0,0)). Expose an epsilon for this? +// +// If initialCenterIsAverage is set to 'true', the initial guess for the +// sphere center is the average of the data points. If the data points are +// clustered along a solid angle, ApprSphere32 is very slow to converge. If +// initialCenterIsAverage is set to 'false', the initial guess for the +// sphere center is computed using a least-squares estimate of the +// coefficients for a quadratic equation that represents a sphere. This +// approach tends to converge rapidly. + +namespace gte +{ + +template +class ApprSphere3 +{ +public: + unsigned int operator()(int numPoints, Vector3 const* points, + unsigned int maxIterations, bool initialCenterIsAverage, + Sphere3& sphere); +}; + + +template +unsigned int ApprSphere3::operator()(int numPoints, + Vector3 const* points, unsigned int maxIterations, + bool initialCenterIsAverage, Sphere3& sphere) +{ + // Compute the average of the data points. + Vector3 average = points[0]; + for (int i = 1; i < numPoints; ++i) + { + average += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + average *= invNumPoints; + + // The initial guess for the center. + if (initialCenterIsAverage) + { + sphere.center = average; + } + else + { + ApprQuadraticSphere3()(numPoints, points, sphere); + } + + unsigned int iteration; + for (iteration = 0; iteration < maxIterations; ++iteration) + { + // Update the iterates. + Vector3 current = sphere.center; + + // Compute average L, dL/da, dL/db, dL/dc. + Real lenAverage = (Real)0; + Vector3 derLenAverage = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - sphere.center; + Real length = Length(diff); + if (length >(Real)0) + { + lenAverage += length; + Real invLength = ((Real)1) / length; + derLenAverage -= invLength * diff; + } + } + lenAverage *= invNumPoints; + derLenAverage *= invNumPoints; + + sphere.center = average + lenAverage * derLenAverage; + sphere.radius = lenAverage; + + Vector3 diff = sphere.center - current; + if (diff == Vector3::Zero()) + { + break; + } + } + + return ++iteration; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprTorus3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprTorus3.h new file mode 100644 index 000000000000..358cf831f16c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteApprTorus3.h @@ -0,0 +1,397 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.3 (2018/10/30) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Let the torus center be C with plane of symmetry containing C and having +// directions D0 and D1. The axis of symmetry is the line containing C and +// having direction N (the plane normal). The radius from the center of the +// torus is r0 and the radius of the tube of the torus is r1. A point P may +// be written as P = C + x*D0 + y*D1 + z*N, where matrix [D0 D1 N] is +// orthogonal and has determinant 1. Thus, x = Dot(D0,P-C), y = Dot(D1,P-C) +// and z = Dot(N,P-C). The implicit equation defining the torus is +// (|P-C|^2 + r0^2 - r1^2)^2 - 4*r0^2*(|P-C|^2 - (Dot(N,P-C))^2) = 0 +// Observe that D0 and D1 are not present in the equation, which is to be +// expected by the symmetry. +// +// Define u = r0^2 and v = r0^2 - r1^2. Define +// F(X;C,N,u,v) = (|P-C|^2 + v)^2 - 4*u*(|P-C|^2 - (Dot(N,P-C))^2) +// The nonlinear least-squares fitting of points {X[i]}_{i=0}^{n-1} computes +// C, N, u and v to minimize the error function +// E(C,N,u,v) = sum_{i=0}^{n-1} F(X[i];C,N,u,v)^2 +// When the sample points are distributed so that there is large coverage +// by a purported fitted torus, a variation on fitting is the following. +// Compute the least-squares plane with origin C and normal N that fits the +// points. Define G(X;u,v) = F(X;C,N,u,v); the only variables now are u and +// v. Define L[i] = |X[i]-C|^2 and S[i] = 4 * (L[i] - (Dot(N,X[i]-C))^2). +// Define the error function +// H(u,v) = sum_{i=0}^{n-1} G(X[i];u,v)^2 +// = sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u)^2 +// The first-order partial derivatives are +// dH/du = -2 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * S[i] +// dH/dv = 4 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * (v + L[i]) +// Setting these to zero and expanding the terms, we have +// 0 = a2 * v^2 + a1 * v + a0 - b0 * u +// 0 = c3 * v^3 + c2 * v^2 + c1 * v + c0 - u * (d1 * v + d0) +// where a2 = sum(S[i]), a1 = 2*sum(S[i]*L[i]), a2 = sum(S[i]*L[i]^2), +// b0 = sum(S[i]^2), c3 = sum(1) = n, c2 = 3*sum(L[i]), c1 = 3*sum(L[i]^2), +// c0 = sum(L[i]^3), d1 = sum(S[i]) = a2 and d0 = sum(S[i]*L[i]) = a1/2. +// The first equation is solved for +// u = (a2 * v^2 + a1 * v + a0) / b0 = e2 * v^2 + e1 * v + e0 +// and substituted into the second equation to obtain a cubic polynomial +// equation +// 0 = f3 * v^3 + f2 * v^2 + f1 * v + f0 +// where f3 = c3 - d1 * e2, f2 = c2 - d1 * e1 - d0 * e2, +// f1 = c1 - d1 * e0 - d0 * e1 and f0 = c0 - d0 * e0. The positive v-roots +// are computed. For each root compute the corresponding u. For all pairs +// (u,v) with u > v > 0, evaluate H(u,v) and choose the pair that minimizes +// H(u,v). The torus radii are r0 = sqrt(u) and r1 = sqrt(u - v). + +namespace gte +{ + template + class ApprTorus3 + { + public: + ApprTorus3() + { + // The unit-length normal is + // N = (cos(theta)*sin(phi), sin(theta)*sin(phi), cos(phi) + // for theta in [0,2*pi) and phi in [0,*pi). The radii are + // encoded as + // u = r0^2, v = r0^2 - r1^2 + // with 0 < v < u. Let D = C - X[i] where X[i] is a sample point. + // The parameters P = (C0,C1,C2,theta,phi,u,v). + + // F[i](C,theta,phi,u,v) = + // (|D|^2 + v)^2 - 4*u*(|D|^2 - Dot(N,D)^2) + mFFunction = [this](GVector const& P, GVector& F) + { + Real csTheta = std::cos(P[3]); + Real snTheta = std::sin(P[3]); + Real csPhi = std::cos(P[4]); + Real snPhi = std::sin(P[4]); + Vector<3, Real> C = { P[0], P[1], P[2] }; + Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi }; + Real u = P[5]; + Real v = P[6]; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> D = C - mPoints[i]; + Real DdotD = Dot(D, D), NdotD = Dot(N, D); + Real sum = DdotD + v; + F[i] = sum * sum - (Real)4 * u * (DdotD - NdotD * NdotD); + } + }; + + // dF[i]/dC = 4 * (|D|^2 + v) * D - 8 * u * (I - N*N^T) * D + // dF[i]/dTheta = 8 * u * Dot(dN/dTheta, D) + // dF[i]/dPhi = 8 * u * Dot(dN/dPhi, D) + // dF[i]/du = -4 * u * (|D|^2 - Dot(N,D)^2) + // dF[i]/dv = 2 * (|D|^2 + v) + mJFunction = [this](GVector const& P, GMatrix& J) + { + Real const r2(2), r4(4), r8(8); + Real csTheta = std::cos(P[3]); + Real snTheta = std::sin(P[3]); + Real csPhi = std::cos(P[4]); + Real snPhi = std::sin(P[4]); + Vector<3, Real> C = { P[0], P[1], P[2] }; + Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi }; + Real u = P[5]; + Real v = P[6]; + for (int row = 0; row < mNumPoints; ++row) + { + Vector<3, Real> D = C - mPoints[row]; + Real DdotD = Dot(D, D), NdotD = Dot(N, D); + Real sum = DdotD + v; + Vector<3, Real> dNdTheta{ -snTheta * snPhi, csTheta * snPhi, (Real)0 }; + Vector<3, Real> dNdPhi{ csTheta * csPhi, snTheta * csPhi, -snPhi }; + Vector<3, Real> temp = r4 * sum * D - r8 * u * (D - NdotD * N); + J(row, 0) = temp[0]; + J(row, 1) = temp[1]; + J(row, 2) = temp[2]; + J(row, 3) = r8 * u * Dot(dNdTheta, D); + J(row, 4) = r8 * u * Dot(dNdPhi, D); + J(row, 5) = -r4 * u * (DdotD - NdotD * NdotD); + J(row, 6) = r2 * sum; + } + }; + } + + // When the samples are distributed approximately uniformly near a + // torus, use this method. For example, if the purported torus has + // center (0,0,0) and normal (0,0,1), you want the (x,y,z) samples + // to occur in all 8 octants. If the samples occur, say, only in + // one octant, this method will estimate a C and N that are nowhere + // near (0,0,0) and (0,0,1). The function sets the output variables + // C, N, r0 and r1 as the fitted torus. + // + // The return value is a pair . The first element is + // 'true' when the estimate is valid, in which case the second + // element is the least-squares error for that estimate. If any + // unexpected condition occurs that prevents computing an estimate, + // the first element is 'false' and the second element is + // std::numeric_limits::max(). + std::pair + operator()(int numPoints, Vector<3, Real> const* points, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) + { + ApprOrthogonalPlane3 fitter; + if (!fitter.Fit(numPoints, points)) + { + return std::make_pair(false, std::numeric_limits::max()); + } + C = fitter.GetParameters().first; + N = fitter.GetParameters().second; + + Real const zero(0); + Real a0 = zero, a1 = zero, a2 = zero, b0 = zero; + Real c0 = zero, c1 = zero, c2 = zero, c3 = (Real)numPoints; + for (int i = 0; i < numPoints; ++i) + { + Vector<3, Real> delta = points[i] - C; + Real dot = Dot(N, delta); + Real L = Dot(delta, delta), L2 = L * L, L3 = L * L2; + Real S = (Real)4 * (L - dot * dot), S2 = S * S; + a2 += S; + a1 += S * L; + a0 += S * L2; + b0 += S2; + c2 += L; + c1 += L2; + c0 += L3; + } + Real d1 = a2; + Real d0 = a1; + a1 *= (Real)2; + c2 *= (Real)3; + c1 *= (Real)3; + Real invB0 = (Real)1 / b0; + Real e0 = a0 * invB0; + Real e1 = a1 * invB0; + Real e2 = a2 * invB0; + + Rational f0 = c0 - d0 * e0; + Rational f1 = c1 - d1 * e0 - d0 * e1; + Rational f2 = c2 - d1 * e1 - d0 * e2; + Rational f3 = c3 - d1 * e2; + std::map rmMap; + RootsPolynomial::SolveCubic(f0, f1, f2, f3, rmMap); + + Real hmin = std::numeric_limits::max(); + Real umin = zero, vmin = zero; + for (auto const& element : rmMap) + { + Real v = element.first; + if (v > zero) + { + Real u = e0 + v * (e1 + v * e2); + if (u > v) + { + Real h = zero; + for (int i = 0; i < numPoints; ++i) + { + Vector<3, Real> delta = points[i] - C; + Real dot = Dot(N, delta); + Real L = Dot(delta, delta); + Real S = (Real)4 * (L - dot * dot); + Real sum = v + L; + Real term = sum * sum - S * u; + h += term * term; + } + if (h < hmin) + { + hmin = h; + umin = u; + vmin = v; + } + } + } + } + + if (hmin == std::numeric_limits::max()) + { + return std::make_pair(false, std::numeric_limits::max()); + } + + r0 = std::sqrt(umin); + r1 = std::sqrt(umin - vmin); + return std::make_pair(true, hmin); + } + + // If you want to specify that C, N, r0 and r1 are the initial guesses + // for the minimizer, set the parameter useTorusInputAsInitialGuess to + // 'true'. If you want the function to compute initial guesses, set + // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer + // is used to fit a torus using nonlinear least-squares. The fitted + // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h + // for a description of the least-squares algorithm and the parameters + // that it requires. + typename GaussNewtonMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + bool useTorusInputAsInitialGuess, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) + { + mNumPoints = numPoints; + mPoints = points; + GaussNewtonMinimizer minimizer(7, mNumPoints, mFFunction, mJFunction); + + if (!useTorusInputAsInitialGuess) + { + operator()(numPoints, points, C, N, r0, r1); + } + + GVector initial(7); + + // The initial guess for the plane origin. + initial[0] = C[0]; + initial[1] = C[1]; + initial[2] = C[2]; + + // The initial guess for the plane normal. The angles must be + // extracted for spherical coordinates. + if (std::abs(N[2]) < (Real)1) + { + initial[3] = std::atan2(N[1], N[0]); + initial[4] = std::acos(N[2]); + } + else + { + initial[3] = (Real)0; + initial[4] = (Real)0; + } + + // The initial guess for the radii-related parameters. + initial[5] = r0 * r0; + initial[6] = initial[5] - r1 * r1; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance); + + // No test is made for result.converged so that we return some + // estimates of the torus. The caller can decide how to respond + // when result.converged is false. + C[0] = result.minLocation[0]; + C[1] = result.minLocation[1]; + C[2] = result.minLocation[2]; + + Real theta = result.minLocation[3]; + Real phi = result.minLocation[4]; + Real csTheta = std::cos(theta); + Real snTheta = std::sin(theta); + Real csPhi = std::cos(phi); + Real snPhi = std::sin(phi); + N[0] = csTheta * snPhi; + N[1] = snTheta * snPhi; + N[2] = csPhi; + + Real u = result.minLocation[5]; + Real v = result.minLocation[6]; + r0 = std::sqrt(u); + r1 = std::sqrt(u - v); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + // If you want to specify that C, N, r0 and r1 are the initial guesses + // for the minimizer, set the parameter useTorusInputAsInitialGuess to + // 'true'. If you want the function to compute initial guesses, set + // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer + // is used to fit a torus using nonlinear least-squares. The fitted + // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h + // for a description of the least-squares algorithm and the parameters + // that it requires. + typename LevenbergMarquardtMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments, + bool useTorusInputAsInitialGuess, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) + { + mNumPoints = numPoints; + mPoints = points; + LevenbergMarquardtMinimizer minimizer(7, mNumPoints, mFFunction, mJFunction); + + if (!useTorusInputAsInitialGuess) + { + operator()(numPoints, points, C, N, r0, r1); + } + + GVector initial(7); + + // The initial guess for the plane origin. + initial[0] = C[0]; + initial[1] = C[1]; + initial[2] = C[2]; + + // The initial guess for the plane normal. The angles must be + // extracted for spherical coordinates. + if (std::abs(N[2]) < (Real)1) + { + initial[3] = std::atan2(N[1], N[0]); + initial[4] = std::acos(N[2]); + } + else + { + initial[3] = (Real)0; + initial[4] = (Real)0; + } + + // The initial guess for the radii-related parameters. + initial[5] = r0 * r0; + initial[6] = initial[5] - r1 * r1; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments); + + // No test is made for result.converged so that we return some + // estimates of the torus. The caller can decide how to respond + // when result.converged is false. + C[0] = result.minLocation[0]; + C[1] = result.minLocation[1]; + C[2] = result.minLocation[2]; + + Real theta = result.minLocation[3]; + Real phi = result.minLocation[4]; + Real csTheta = std::cos(theta); + Real snTheta = std::sin(theta); + Real csPhi = std::cos(phi); + Real snPhi = std::sin(phi); + N[0] = csTheta * snPhi; + N[1] = snTheta * snPhi; + N[2] = csPhi; + + Real u = result.minLocation[5]; + Real v = result.minLocation[6]; + r0 = std::sqrt(u); + r1 = std::sqrt(u - v); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + private: + typedef BSRational Rational; + + int mNumPoints; + Vector<3, Real> const* mPoints; + std::function const&, GVector&)> mFFunction; + std::function const&, GMatrix&)> mJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArbitraryPrecision.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArbitraryPrecision.h new file mode 100644 index 000000000000..ea7ff7c33565 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArbitraryPrecision.h @@ -0,0 +1,15 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.21.0 (2019/01/17) + +#pragma once + +#include +#include +#include +#include +#include +#include diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArc2.h new file mode 100644 index 000000000000..7d5ccc0a1fac --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteArc2.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// The circle containing the arc is represented as |X-C| = R where C is the +// center and R is the radius. The arc is defined by two points end0 and end1 +// on the circle so that end1 is obtained from end0 by traversing +// counterclockwise. The application is responsible for ensuring that end0 +// and end1 are on the circle and that they are properly ordered. + +template +class Arc2 +{ +public: + // Construction and destruction. The default constructor sets the center + // to (0,0), radius to 1, end0 to (1,0), and end1 to (0,1). + Arc2(); + Arc2(Vector2 const& inCenter, Real inRadius, + Vector2const& inEnd0, Vector2const & inEnd1); + + // Test whether P is on the arc. The application must ensure that P is on + // the circle; that is, |P-C| = R. This test works for any angle between + // B-C and A-C, not just those between 0 and pi radians. + bool Contains(Vector2 const& p) const; + + Vector2 center; + Real radius; + Vector2 end[2]; + +public: + // Comparisons to support sorted containers. + bool operator==(Arc2 const& arc) const; + bool operator!=(Arc2 const& arc) const; + bool operator< (Arc2 const& arc) const; + bool operator<=(Arc2 const& arc) const; + bool operator> (Arc2 const& arc) const; + bool operator>=(Arc2 const& arc) const; +}; + + +template +Arc2::Arc2() + : + center(Vector2::Zero()), + radius((Real)1) +{ + end[0] = Vector2::Unit(0); + end[1] = Vector2::Unit(1); +} + +template +Arc2::Arc2(Vector2 const& inCenter, Real inRadius, + Vector2const& inEnd0, Vector2const & inEnd1) + : + center(inCenter), + radius(inRadius) +{ + end[0] = inEnd0; + end[1] = inEnd1; +} + +template +bool Arc2::Contains(Vector2 const& p) const +{ + // Assert: |P-C| = R where P is the input point, C is the circle center, + // and R is the circle radius. For P to be on the arc from A to B, it + // must be on the side of the plane containing A with normal N = Perp(B-A) + // where Perp(u,v) = (v,-u). + + Vector2 diffPE0 = p - end[0]; + Vector2 diffE1E0 = end[1] - end[0]; + Real dotPerp = DotPerp(diffPE0, diffE1E0); + return dotPerp >= (Real)0; +} + +template +bool Arc2::operator==(Arc2 const& arc) const +{ + return center == arc.center && radius == arc.radius + && end[0] == arc.end[0] && end[1] == arc.end[1]; +} + +template +bool Arc2::operator!=(Arc2 const& arc) const +{ + return !operator==(arc); +} + +template +bool Arc2::operator<(Arc2 const& arc) const +{ + if (center < arc.center) + { + return true; + } + + if (center > arc.center) + { + return false; + } + + if (radius < arc.radius) + { + return true; + } + + if (radius > arc.radius) + { + return false; + } + + if (end[0] < arc.end[0]) + { + return true; + } + + if (end[0] > arc.end[0]) + { + return false; + } + + return end[1] < arc.end[1]; +} + +template +bool Arc2::operator<=(Arc2 const& arc) const +{ + return operator<(arc) || operator==(arc); +} + +template +bool Arc2::operator>(Arc2 const& arc) const +{ + return !operator<=(arc); +} + +template +bool Arc2::operator>=(Arc2 const& arc) const +{ + return !operator<(arc); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAxisAngle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAxisAngle.h new file mode 100644 index 000000000000..2f28a39bbc2d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteAxisAngle.h @@ -0,0 +1,48 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// Axis-angle representation for N = 3 or N = 4. When N = 4, the axis +// must be a vector of the form (x,y,z,0) [affine representation of the +// 3-tuple direction]. + +template +class AxisAngle +{ +public: + AxisAngle(); + AxisAngle(Vector const& inAxis, Real inAngle); + + Vector axis; + Real angle; +}; + + +template +AxisAngle::AxisAngle() +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + // Uninitialized. +} + +template +AxisAngle::AxisAngle(Vector const& inAxis, Real inAngle) + : + axis(inAxis), + angle(inAngle) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSNumber.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSNumber.h new file mode 100644 index 000000000000..4f99397d7dd7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSNumber.h @@ -0,0 +1,1106 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2019/01/17) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The class BSNumber (binary scientific number) is designed to provide exact +// arithmetic for robust algorithms, typically those for which we need to know +// the exact sign of determinants. The template parameter UIntegerType must +// have support for at least the following public interface. The fstream +// objects for Write/Read must be created using std::ios::binary. The return +// value of Write/Read is 'true' iff the operation was successful. +// +// class UIntegerType +// { +// public: +// UIntegerType(); +// UIntegerType(UIntegerType const& number); +// UIntegerType(uint32_t number); +// UIntegerType(uint64_t number); +// UIntegerType(int numBits); +// UIntegerType& operator=(UIntegerType const& number); +// UIntegerType(UIntegerType&& number); +// UIntegerType& operator=(UIntegerType&& number); +// int32_t GetNumBits() const; +// bool operator==(UIntegerType const& number) const; +// bool operator< (UIntegerType const& number) const; +// void Add(UIntegerType const& n0, UIntegerType const& n1); +// void Sub(UIntegerType const& n0, UIntegerType const& n1); +// void Mul(UIntegerType const& n0, UIntegerType const& n1); +// void ShiftLeft(UIntegerType const& number, int shift); +// int32_t ShiftRightToOdd(UIntegerType const& number); +// uint64_t GetPrefix(int numRequested) const; +// bool Write(std::ofstream& output) const; +// bool Read(std::ifstream& input); +// }; +// +// GTEngine currently has 32-bits-per-word storage for UIntegerType. See the +// classes UIntegerAP32 (arbitrary precision), UIntegerFP32 (fixed +// precision), and UIntegerALU32 (arithmetic logic unit shared by the previous +// two classes). The document at the following link describes the design, +// implementation, and use of BSNumber and BSRational. +// http://www.geometrictools.com/Documentation/ArbitraryPrecision.pdf +// +// Support for debugging algorithms that use exact rational arithmetic. Each +// BSNumber and BSRational has a double-precision member that is exposed when +// the conditional define is enabled. Be aware that this can be very slow +// because of the conversion to double-precision whenever new objects are +// created by arithmetic operations. As a faster alternative, you can add +// temporary code in your algorithms that explicitly convert specific rational +// numbers to double precision. +// +//#define GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE + +namespace gte +{ + template class BSRational; + + template + class BSNumber + { + public: + // Construction. The default constructor generates the zero BSNumber. + BSNumber() + : + mSign(0), + mBiasedExponent(0) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(BSNumber const& number) + { + *this = number; + } + + BSNumber(float number) + { + ConvertFrom(number); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(double number) + { + ConvertFrom(number); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(int32_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + if (number < 0) + { + mSign = -1; + number = -number; + } + else + { + mSign = 1; + } + + mBiasedExponent = GetTrailingBit(number); + mUInteger = (uint32_t)number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(uint32_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + mSign = 1; + mBiasedExponent = GetTrailingBit(number); + mUInteger = number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(int64_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + if (number < 0) + { + mSign = -1; + number = -number; + } + else + { + mSign = 1; + } + + mBiasedExponent = GetTrailingBit(number); + mUInteger = (uint64_t)number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(uint64_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + mSign = 1; + mBiasedExponent = GetTrailingBit(number); + mUInteger = number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + // Implicit conversions. + inline operator float() const + { + return ConvertTo(); + } + + inline operator double() const + { + return ConvertTo(); + } + + // Assignment. + BSNumber& operator=(BSNumber const& number) + { + mSign = number.mSign; + mBiasedExponent = number.mBiasedExponent; + mUInteger = number.mUInteger; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = number.mValue; +#endif + return *this; + } + + // Support for std::move. + BSNumber(BSNumber&& number) + { + *this = std::move(number); + } + + BSNumber& operator=(BSNumber&& number) + { + mSign = number.mSign; + mBiasedExponent = number.mBiasedExponent; + mUInteger = std::move(number.mUInteger); + number.mSign = 0; + number.mBiasedExponent = 0; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = number.mValue; +#endif + return *this; + } + + // Member access. + inline int32_t GetSign() const + { + return mSign; + } + + inline int32_t GetBiasedExponent() const + { + return mBiasedExponent; + } + + inline int32_t GetExponent() const + { + return mBiasedExponent + mUInteger.GetNumBits() - 1; + } + + inline UIntegerType const& GetUInteger() const + { + return mUInteger; + } + + // Comparisons. + bool operator==(BSNumber const& number) const + { + return (mSign == number.mSign ? EqualIgnoreSign(*this, number) : false); + } + + bool operator!=(BSNumber const& number) const + { + return !operator==(number); + } + + bool operator< (BSNumber const& number) const + { + if (mSign > 0) + { + if (number.mSign <= 0) + { + return false; + } + + // Both numbers are positive. + return LessThanIgnoreSign(*this, number); + } + else if (mSign < 0) + { + if (number.mSign >= 0) + { + return true; + } + + // Both numbers are negative. + return LessThanIgnoreSign(number, *this); + } + else + { + return number.mSign > 0; + } + } + + bool operator<=(BSNumber const& number) const + { + return !number.operator<(*this); + } + + bool operator> (BSNumber const& number) const + { + return number.operator<(*this); + } + + bool operator>=(BSNumber const& number) const + { + return !operator<(number); + } + + // Unary operations. + BSNumber operator+() const + { + return *this; + } + + BSNumber operator-() const + { + BSNumber result = *this; + result.mSign = -result.mSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Arithmetic. + BSNumber operator+(BSNumber const& n1) const + { + BSNumber const& n0 = *this; + + if (n0.mSign == 0) + { + return n1; + } + + if (n1.mSign == 0) + { + return n0; + } + + if (n0.mSign > 0) + { + if (n1.mSign > 0) + { + // n0 + n1 = |n0| + |n1| + return AddIgnoreSign(n0, n1, +1); + } + else // n1.mSign < 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 + n1 = |n0| - |n1| > 0 + return SubIgnoreSign(n0, n1, +1); + } + else + { + // n0 + n1 = -(|n1| - |n0|) < 0 + return SubIgnoreSign(n1, n0, -1); + } + } + // else n0 + n1 = 0 + } + } + else // n0.mSign < 0 + { + if (n1.mSign < 0) + { + // n0 + n1 = -(|n0| + |n1|) + return AddIgnoreSign(n0, n1, -1); + } + else // n1.mSign > 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 + n1 = -(|n0| - |n1|) < 0 + return SubIgnoreSign(n0, n1, -1); + } + else + { + // n0 + n1 = |n1| - |n0| > 0 + return SubIgnoreSign(n1, n0, +1); + } + } + // else n0 + n1 = 0 + } + } + + return BSNumber(); // = 0 + } + + BSNumber operator-(BSNumber const& n1) const + { + BSNumber const& n0 = *this; + + if (n0.mSign == 0) + { + return -n1; + } + + if (n1.mSign == 0) + { + return n0; + } + + if (n0.mSign > 0) + { + if (n1.mSign < 0) + { + // n0 - n1 = |n0| + |n1| + return AddIgnoreSign(n0, n1, +1); + } + else // n1.mSign > 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 - n1 = |n0| - |n1| > 0 + return SubIgnoreSign(n0, n1, +1); + } + else + { + // n0 - n1 = -(|n1| - |n0|) < 0 + return SubIgnoreSign(n1, n0, -1); + } + } + // else n0 - n1 = 0 + } + } + else // n0.mSign < 0 + { + if (n1.mSign > 0) + { + // n0 - n1 = -(|n0| + |n1|) + return AddIgnoreSign(n0, n1, -1); + } + else // n1.mSign < 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 - n1 = -(|n0| - |n1|) < 0 + return SubIgnoreSign(n0, n1, -1); + } + else + { + // n0 - n1 = |n1| - |n0| > 0 + return SubIgnoreSign(n1, n0, +1); + } + } + // else n0 - n1 = 0 + } + } + + return BSNumber(); // = 0 + } + + BSNumber operator*(BSNumber const& number) const + { + BSNumber result; // = 0 + int sign = mSign * number.mSign; + if (sign != 0) + { + result.mSign = sign; + result.mBiasedExponent = mBiasedExponent + number.mBiasedExponent; + result.mUInteger.Mul(mUInteger, number.mUInteger); + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + BSNumber& operator+=(BSNumber const& number) + { + *this = operator+(number); + return *this; + } + + BSNumber& operator-=(BSNumber const& number) + { + *this = operator-(number); + return *this; + } + + BSNumber& operator*=(BSNumber const& number) + { + *this = operator*(number); + return *this; + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const + { + if (output.write((char const*)&mSign, sizeof(mSign)).bad()) + { + return false; + } + + if (output.write((char const*)&mBiasedExponent, + sizeof(mBiasedExponent)).bad()) + { + return false; + } + + return mUInteger.Write(output); + } + + bool Read(std::ifstream& input) + { + if (input.read((char*)&mSign, sizeof(mSign)).bad()) + { + return false; + } + + if (input.read((char*)&mBiasedExponent, sizeof(mBiasedExponent)).bad()) + { + return false; + } + + return mUInteger.Read(input); + } + + private: + // Helpers for operator==, operator<, operator+, operator-. + static bool EqualIgnoreSign(BSNumber const& n0, BSNumber const& n1) + { + return n0.mBiasedExponent == n1.mBiasedExponent && n0.mUInteger == n1.mUInteger; + } + + static bool LessThanIgnoreSign(BSNumber const& n0, BSNumber const& n1) + { + int32_t e0 = n0.GetExponent(), e1 = n1.GetExponent(); + if (e0 < e1) + { + return true; + } + if (e0 > e1) + { + return false; + } + return n0.mUInteger < n1.mUInteger; + } + + // Add two positive numbers. + static BSNumber AddIgnoreSign(BSNumber const& n0, BSNumber const& n1, int32_t resultSign) + { + BSNumber result, temp; + + int32_t diff = n0.mBiasedExponent - n1.mBiasedExponent; + if (diff > 0) + { + temp.mUInteger.ShiftLeft(n0.mUInteger, diff); + result.mUInteger.Add(temp.mUInteger, n1.mUInteger); + result.mBiasedExponent = n1.mBiasedExponent; + } + else if (diff < 0) + { + temp.mUInteger.ShiftLeft(n1.mUInteger, -diff); + result.mUInteger.Add(n0.mUInteger, temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent; + } + else + { + temp.mUInteger.Add(n0.mUInteger, n1.mUInteger); + int32_t shift = result.mUInteger.ShiftRightToOdd(temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent + shift; + } + + result.mSign = resultSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Subtract two positive numbers where n0 > n1. + static BSNumber SubIgnoreSign(BSNumber const& n0, BSNumber const& n1, int32_t resultSign) + { + BSNumber result, temp; + + int32_t diff = n0.mBiasedExponent - n1.mBiasedExponent; + if (diff > 0) + { + temp.mUInteger.ShiftLeft(n0.mUInteger, diff); + result.mUInteger.Sub(temp.mUInteger, n1.mUInteger); + result.mBiasedExponent = n1.mBiasedExponent; + } + else if (diff < 0) + { + temp.mUInteger.ShiftLeft(n1.mUInteger, -diff); + result.mUInteger.Sub(n0.mUInteger, temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent; + } + else + { + temp.mUInteger.Sub(n0.mUInteger, n1.mUInteger); + int32_t shift = result.mUInteger.ShiftRightToOdd(temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent + shift; + } + + result.mSign = resultSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Support for conversions from floating-point numbers to BSNumber. + template + void ConvertFrom(typename IEEE::FloatType number) + { + IEEE x(number); + + // Extract sign s, biased exponent e, and trailing significand t. + typename IEEE::UIntType s = x.GetSign(); + typename IEEE::UIntType e = x.GetBiased(); + typename IEEE::UIntType t = x.GetTrailing(); + + if (e == 0) + { + if (t == 0) // zeros + { + // x = (-1)^s * 0 + mSign = 0; + mBiasedExponent = 0; + } + else // subnormal numbers + { + // x = (-1)^s * 0.t * 2^{1-EXPONENT_BIAS} + int32_t last = GetTrailingBit(t); + int32_t diff = IEEE::NUM_TRAILING_BITS - last; + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = IEEE::MIN_SUB_EXPONENT - diff; + mUInteger = (t >> last); + } + } + else if (e < IEEE::MAX_BIASED_EXPONENT) // normal numbers + { + // x = (-1)^s * 1.t * 2^{e-EXPONENT_BIAS} + if (t > 0) + { + int32_t last = GetTrailingBit(t); + int32_t diff = IEEE::NUM_TRAILING_BITS - last; + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = + static_cast(e) - IEEE::EXPONENT_BIAS - diff; + mUInteger = ((t | IEEE::SUP_TRAILING) >> last); + } + else + { + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = static_cast(e) - IEEE::EXPONENT_BIAS; + mUInteger = (typename IEEE::UIntType)1; + } + } + else // e == MAX_BIASED_EXPONENT, special numbers + { + if (t == 0) // infinities + { + // x = (-1)^s * infinity + LogWarning("Input is " + std::string(s > 0 ? "-" : "+") + + "infinity."); + + // Return (-1)^s * 2^{1+EXPONENT_BIAS} for a graceful exit. + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = 1 + IEEE::EXPONENT_BIAS; + mUInteger = (typename IEEE::UIntType)1; + } + else // not-a-number (NaN) + { + LogError("Input is a " + + std::string(t & IEEE::NAN_QUIET_MASK ? + "quiet" : "signaling") + " NaN with payload [redacted]" + // disabling this because std::to_string is not available on some platforms and mscver ifdef causes a weird error + //+ std::to_string(t & IEEE::NAN_PAYLOAD_MASK) + "." + ); + + // Return 0 for a graceful exit. + mSign = 0; + mBiasedExponent = 0; + } + } + } + + // Support for conversions from BSNumber to floating-point numbers. + template + typename IEEE::FloatType ConvertTo() const + { + typename IEEE::UIntType s = (mSign < 0 ? 1 : 0); + typename IEEE::UIntType e, t; + + if (mSign != 0) + { + // The conversions use round-to-nearest-ties-to-even semantics. + int32_t exponent = GetExponent(); + if (exponent < IEEE::MIN_EXPONENT) + { + if (exponent < IEEE::MIN_EXPONENT - 1 + || mUInteger.GetNumBits() == 1) // x = 1.0*2^{MIN_EXPONENT-1} + { + // Round to zero. + e = 0; + t = 0; + } + else + { + // Round to min subnormal. + e = 0; + t = 1; + } + } + else if (exponent < IEEE::MIN_SUB_EXPONENT) + { + // The second input is in {0, ..., NUM_TRAILING_BITS-1}. + t = GetTrailing(0, IEEE::MIN_SUB_EXPONENT - exponent - 1); + if (t & IEEE::SUP_TRAILING) + { + // Leading NUM_SIGNIFICAND_BITS bits were all 1, so round to + // min normal. + e = 1; + t = 0; + } + else + { + e = 0; + } + } + else if (exponent <= IEEE::EXPONENT_BIAS) + { + e = static_cast(exponent + IEEE::EXPONENT_BIAS); + t = GetTrailing(1, 0); + if (t & (IEEE::SUP_TRAILING << 1)) + { + // Carry-out occurred, so increase exponent by 1 and + // shift right to compensate. + ++e; + t >>= 1; + } + // Eliminate the leading 1 (implied for normals). + t &= ~IEEE::SUP_TRAILING; + } + else + { + // Set to infinity. + e = IEEE::MAX_BIASED_EXPONENT; + t = 0; + } + } + else + { + // The input is zero. + e = 0; + t = 0; + } + + IEEE x(s, e, t); + return x.number; + } + + template + typename IEEE::UIntType GetTrailing(int32_t normal, int32_t sigma) const + { + int32_t const numRequested = IEEE::NUM_SIGNIFICAND_BITS + normal; + + // We need numRequested bits to determine rounding direction. These are + // stored in the high-order bits of 'prefix'. + uint64_t prefix = mUInteger.GetPrefix(numRequested); + + // The first bit index after the implied binary point for rounding. + int32_t diff = numRequested - sigma; + int32_t roundBitIndex = 64 - diff; + + // Determine rounding value based on round-to-nearest-ties-to-even. + uint64_t mask = (1ull << roundBitIndex); + uint64_t round; + if (prefix & mask) + { + // The first bit of the remainder is 1. + if (mUInteger.GetNumBits() == diff) + { + // The first bit of the remainder is the lowest-order bit of + // mBits[0]. Apply the ties-to-even rule. + if (prefix & (mask << 1)) + { + // The last bit of the trailing significand is odd, so + // round up. + round = 1; + } + else + { + // The last bit of the trailing significand is even, so + // round down. + round = 0; + } + } + else + { + // The first bit of the remainder is not the lowest-order bit of + // mBits[0]. The remainder as a fraction is larger than 1/2, so + // round up. + round = 1; + } + } + else + { + // The first bit of the remainder is 0, so round down. + round = 0; + } + + // Get the unrounded trailing significand. + uint64_t trailing = prefix >> (roundBitIndex + 1); + + // Apply the rounding. + trailing += round; + return static_cast(trailing); + } + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + public: + // List this first so that it shows up first in the debugger watch window. + double mValue; + private: +#endif + + // The number 0 is represented by: mSign = 0, mBiasedExponent = 0, and + // mUInteger = 0. For nonzero numbers, mSign != 0 and mUInteger > 0. + int32_t mSign; + int32_t mBiasedExponent; + UIntegerType mUInteger; + + // Access to members to avoid exposing them publically when they are + // needed only internally. + friend class BSRational; + friend class UnitTestBSNumber; + }; +} + +namespace std +{ + // TODO: Allow for implementations of the math functions in which a + // specified precision is used when computing the result. + + template + inline gte::BSNumber abs(gte::BSNumber const& x) + { + return (x.GetSign() >= 0 ? x : -x); + } + + template + inline gte::BSNumber acos(gte::BSNumber const& x) + { + return (gte::BSNumber)std::acos((double)x); + } + + template + inline gte::BSNumber acosh(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::acosh((double)x); +#endif + } + + template + inline gte::BSNumber asin(gte::BSNumber const& x) + { + return (gte::BSNumber)std::asin((double)x); + } + + template + inline gte::BSNumber asinh(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::asinh((double)x); +#endif + } + + template + inline gte::BSNumber atan(gte::BSNumber const& x) + { + return (gte::BSNumber)std::atan((double)x); + } + + template + inline gte::BSNumber atanh(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::atanh((double)x); +#endif + } + + template + inline gte::BSNumber atan2(gte::BSNumber const& y, gte::BSNumber const& x) + { + return (gte::BSNumber)std::atan2((double)y, (double)x); + } + + template + inline gte::BSNumber ceil(gte::BSNumber const& x) + { + return (gte::BSNumber)std::ceil((double)x); + } + + template + inline gte::BSNumber cos(gte::BSNumber const& x) + { + return (gte::BSNumber)std::cos((double)x); + } + + template + inline gte::BSNumber cosh(gte::BSNumber const& x) + { + return (gte::BSNumber)std::cosh((double)x); + } + + template + inline gte::BSNumber exp(gte::BSNumber const& x) + { + return (gte::BSNumber)std::exp((double)x); + } + + template + inline gte::BSNumber exp2(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::exp2((double)x); +#endif + } + + template + inline gte::BSNumber floor(gte::BSNumber const& x) + { + return (gte::BSNumber)std::floor((double)x); + } + + template + inline gte::BSNumber fmod(gte::BSNumber const& x, gte::BSNumber const& y) + { + return (gte::BSNumber)std::fmod((double)x, (double)y); + } + + template + inline gte::BSNumber frexp(gte::BSNumber const& x, int* exponent) + { + return (gte::BSNumber)std::frexp((double)x, exponent); + } + + template + inline gte::BSNumber ldexp(gte::BSNumber const& x, int exponent) + { + return (gte::BSNumber)std::ldexp((double)x, exponent); + } + + template + inline gte::BSNumber log(gte::BSNumber const& x) + { + return (gte::BSNumber)std::log((double)x); + } + + template + inline gte::BSNumber log2(gte::BSNumber const& x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::BSNumber)0; +#else + return (gte::BSNumber)std::log2((double)x); +#endif + } + + template + inline gte::BSNumber log10(gte::BSNumber const& x) + { + return (gte::BSNumber)std::log10((double)x); + } + + template + inline gte::BSNumber pow(gte::BSNumber const& x, gte::BSNumber const& y) + { + return (gte::BSNumber)std::pow((double)x, (double)y); + } + + template + inline gte::BSNumber sin(gte::BSNumber const& x) + { + return (gte::BSNumber)std::sin((double)x); + } + + template + inline gte::BSNumber sinh(gte::BSNumber const& x) + { + return (gte::BSNumber)std::sinh((double)x); + } + + template + inline gte::BSNumber sqrt(gte::BSNumber const& x) + { + return (gte::BSNumber)std::sqrt((double)x); + } + + template + inline gte::BSNumber tan(gte::BSNumber const& x) + { + return (gte::BSNumber)std::tan((double)x); + } + + template + inline gte::BSNumber tanh(gte::BSNumber const& x) + { + return (gte::BSNumber)std::tanh((double)x); + } +} + +namespace gte +{ + template + inline BSNumber atandivpi(BSNumber const& x) + { + return (BSNumber)atandivpi((double)x); + } + + template + inline BSNumber atan2divpi(BSNumber const& y, BSNumber const& x) + { + return (BSNumber)atan2divpi((double)y, (double)x); + } + + template + inline BSNumber clamp(BSNumber const& x, BSNumber const& xmin, BSNumber const& xmax) + { + return (BSNumber)clamp((double)x, (double)xmin, (double)xmax); + } + + template + inline BSNumber cospi(BSNumber const& x) + { + return (BSNumber)cospi((double)x); + } + + template + inline BSNumber exp10(BSNumber const& x) + { + return (BSNumber)exp10((double)x); + } + + template + inline BSNumber invsqrt(BSNumber const& x) + { + return (BSNumber)invsqrt((double)x); + } + + template + inline int isign(BSNumber const& x) + { + return isign((double)x); + } + + template + inline BSNumber saturate(BSNumber const& x) + { + return (BSNumber)saturate((double)x); + } + + template + inline BSNumber sign(BSNumber const& x) + { + return (BSNumber)sign((double)x); + } + + template + inline BSNumber sinpi(BSNumber const& x) + { + return (BSNumber)sinpi((double)x); + } + + template + inline BSNumber sqr(BSNumber const& x) + { + return (BSNumber)sqr((double)x); + } + + // See the comments in GteMath.h about trait is_arbitrary_precision. + template + struct is_arbitrary_precision_internal> : std::true_type {}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.cpp new file mode 100644 index 000000000000..dcfec8373de9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.cpp @@ -0,0 +1,232 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + + +BSPrecision::BSPrecision(bool isFloat, bool forBSNumber) + : + mForBSNumber(forBSNumber) +{ + // NOTE: It is not clear what the rationale is for the C++ Standard + // Library to set numeric_limits::min_exponent to -125 + // instead of -126; same issue with numeric_limits::min_exponent + // set to -1021 instead of -1022. Similarly, it is not clear why + // numeric_limits::max_exponent is set to 128 when the maximum + // finite 'float' has exponent 127. The exponent 128 is used in the + // 'float' infinity, but treating this as 2^{128} does not seem to be + // consistent with the IEEE 754-2008 standard. Same issue with + // numeric_limits::max_exponent set to 1024 rather than 1023. + + if (isFloat) + { + mNumBits = std::numeric_limits::digits; // 24 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -149 + mMaxExponent = std::numeric_limits::max_exponent - 1; // 127 + } + else + { + mNumBits = std::numeric_limits::digits; // 53 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -1074 + mMaxExponent = std::numeric_limits::max_exponent - 1; // 1023 + } +} + +BSPrecision::BSPrecision(bool isFloat, int32_t maxExponent, bool forBSNumber) + : + mForBSNumber(forBSNumber) +{ + if (isFloat) + { + mNumBits = std::numeric_limits::digits; // 24 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -149 + mMaxExponent = maxExponent; + } + else + { + mNumBits = std::numeric_limits::digits; // 53 + mMinBiasedExponent = + std::numeric_limits::min_exponent - mNumBits; // -1074 + mMaxExponent = maxExponent; + } +} + +BSPrecision::BSPrecision(int32_t numBits, int32_t minBiasedExponent, + int32_t maxExponent, bool forBSNumber) + : + mNumBits(numBits), + mMinBiasedExponent(minBiasedExponent), + mMaxExponent(maxExponent), + mForBSNumber(forBSNumber) +{ +} + +int32_t BSPrecision::GetNumWords() const +{ + return mNumBits / 32 + ((mNumBits % 32) > 0 ? 1 : 0); +} + +int32_t BSPrecision::GetNumBits() const +{ + return mNumBits; +} + +int32_t BSPrecision::GetMinBiasedExponent() const +{ + return mMinBiasedExponent; +} + +int32_t BSPrecision::GetMinExponent() const +{ + return mMinBiasedExponent + mNumBits - 1; +} + +int32_t BSPrecision::GetMaxExponent() const +{ + return mMaxExponent; +} + +BSPrecision BSPrecision::operator*(BSPrecision const& precision) +{ + // BSRational operator* involves a product of numerators and a product + // of denominators. In worst case, the precision requirements are the + // same as a BSNumber operator*, so testing of mForBSNumber is not + // needed. + int32_t numBits = mNumBits + precision.mNumBits; + int32_t maxExponent = mMaxExponent + precision.mMaxExponent + 1; + int32_t minBiasedExponent = + mMinBiasedExponent + precision.mMinBiasedExponent; + return BSPrecision(numBits, minBiasedExponent, maxExponent, mForBSNumber); +} + +BSPrecision BSPrecision::operator+(BSPrecision const& precision) +{ + if (mForBSNumber) + { + int32_t minBiasedExponent = + std::min(mMinBiasedExponent, precision.mMinBiasedExponent); + int32_t maxExponent = + std::max(mMaxExponent, precision.mMaxExponent) + 1; + int32_t numBits = maxExponent - minBiasedExponent; + return BSPrecision(numBits, minBiasedExponent, maxExponent, + mForBSNumber); + } + else + { + // n0/d0 + n1/d1 = (n0*d1 + n1*d0) / (d0*d1) + BSPrecision product = operator*(precision); + product.mForBSNumber = true; + BSPrecision sum = product + product; + sum.mForBSNumber = false; + BSPrecision division = sum / product; + division.mForBSNumber = false; + return division; + } +} + +BSPrecision BSPrecision::operator-(BSPrecision const& precision) +{ + return operator+(precision); +} + +BSPrecision BSPrecision::operator/(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Division leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator==(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator!=(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator< (BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator<=(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator> (BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + +BSPrecision BSPrecision::operator>=(BSPrecision const& precision) +{ + if (mForBSNumber) + { + return precision; + } + else + { + // Comparison leads to multiplication of numerators and denominators. + return operator*(precision); + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.h new file mode 100644 index 000000000000..e451d2aeeacf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSPrecision.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Support for determining the number of bits of precision required to compute +// an expression. See the document +// http://www.geometrictools.com/Documentation/ArbitraryPrecision.pdf +// for an example of how to use this class. + +namespace gte +{ + +class BSPrecision +{ +public: + // This constructor is used for 'float' or 'double'. The floating-point + // inputs for the expressions have no restrictions; that is, the inputs + // can be any finite floating-point numbers (normal or subnormal). + BSPrecision(bool isFloat, bool forBSNumber); + + // If you know that your inputs are limited in magnitude, use this + // constructor. For example, if you know that your inputs x satisfy + // |x| <= 8, you can specify maxExponent of 3. The minimum power will + // still be that for the smallest positive subnormal. + BSPrecision(bool isFloat, int32_t maxExponent, bool forBSNumber); + + // You must use this constructor carefully based on knowledge of your + // expressions. For example, if you know that your inputs are 'float' + // and in the interval [1,2), you would choose 24 for the number of bits + // of precision, a minimum biased exponent of -23 because the largest + // 'float' smaller than 2 is 1.1^{23}*2^0 = 1^{24}*2^{-23}, and a maximum + // exponent of 0. These numbers work to determine bits of precision to + // compute x*y+z*w. However, if you then compute an expression such as + // x-y for x and y in [1,2) and multiply by powers of 1/2, the bit + // counting will not be correct because the results can be subnormals + // where the minimum biased exponent is -149, not -23. + BSPrecision(int32_t numBits, int32_t minBiasedExponent, + int32_t maxExponent, bool forBSNumber); + + // Member access. + int32_t GetNumWords() const; + int32_t GetNumBits() const; + int32_t GetMinBiasedExponent() const; + int32_t GetMinExponent() const; + int32_t GetMaxExponent() const; + + // Support for determining the number of bits of precision required to + // compute an expression using BSNumber or BSRational. + BSPrecision operator*(BSPrecision const& precision); + BSPrecision operator+(BSPrecision const& precision); + BSPrecision operator-(BSPrecision const& precision); + + // Support for determining the number of bits of precision required to + // compute a BSRational expression (operations not relevant for BSNumber). + BSPrecision operator/(BSPrecision const& precision); + BSPrecision operator==(BSPrecision const& precision); + BSPrecision operator!=(BSPrecision const& precision); + BSPrecision operator< (BSPrecision const& precision); + BSPrecision operator<=(BSPrecision const& precision); + BSPrecision operator> (BSPrecision const& precision); + BSPrecision operator>=(BSPrecision const& precision); + +private: + int32_t mNumBits, mMinBiasedExponent, mMaxExponent; + bool mForBSNumber; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSRational.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSRational.h new file mode 100644 index 000000000000..eeb543f603f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSRational.h @@ -0,0 +1,748 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2019/01/17) + +#pragma once + +#include + +// See the comments in GteBSNumber.h about the UIntegerType requirements. The +// denominator of a BSRational is chosen to be positive, which allows some +// simplification of comparisons. Also see the comments about exposing the +// GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE conditional define. + +namespace gte +{ + template + class BSRational + { + public: + // Construction. The default constructor generates the zero BSRational. + // The constructors that take only numerators set the denominators to one. + BSRational() + : + mNumerator(0), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSRational const& r) + { + *this = r; + } + + BSRational(float numerator) + : + mNumerator(numerator), + mDenominator(1.0f) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(double numerator) + : + mNumerator(numerator), + mDenominator(1.0) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int32_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint32_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int64_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint64_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSNumber const& numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(float numerator, float denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero not allowed."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(double numerator, double denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero not allowed."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSNumber const& numerator, BSNumber const& denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero not allowed."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } + + // Set the exponent of the denominator to zero, but you can do so only + // by modifying the biased exponent. Adjust the numerator accordingly. + // This prevents large growth of the exponents in both numerator and + // denominator simultaneously. + mNumerator.mBiasedExponent -= mDenominator.GetExponent(); + mDenominator.mBiasedExponent = + -(mDenominator.GetUInteger().GetNumBits() - 1); + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + // Implicit conversions. + operator float() const + { + return Convert(); + } + + operator double() const + { + return Convert(); + } + + // Assignment. + BSRational& operator=(BSRational const& r) + { + mNumerator = r.mNumerator; + mDenominator = r.mDenominator; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + return *this; + } + + // Support for std::move. + BSRational(BSRational&& r) + { + *this = std::move(r); + } + + BSRational& operator=(BSRational&& r) + { + mNumerator = std::move(r.mNumerator); + mDenominator = std::move(r.mDenominator); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + return *this; + } + + // Member access. + inline int GetSign() const + { + return mNumerator.GetSign() * mDenominator.GetSign(); + } + + inline BSNumber const& GetNumerator() const + { + return mNumerator; + } + + inline BSNumber const& GetDenominator() const + { + return mDenominator; + } + + // Comparisons. + bool operator==(BSRational const& r) const + { + // Do inexpensive sign tests first for optimum performance. + if (mNumerator.mSign != r.mNumerator.mSign) + { + return false; + } + if (mNumerator.mSign == 0) + { + // The numbers are both zero. + return true; + } + + return mNumerator * r.mDenominator == mDenominator * r.mNumerator; + } + + bool operator!=(BSRational const& r) const + { + return !operator==(r); + } + + bool operator< (BSRational const& r) const + { + // Do inexpensive sign tests first for optimum performance. + if (mNumerator.mSign > 0) + { + if (r.mNumerator.mSign <= 0) + { + return false; + } + } + else if (mNumerator.mSign == 0) + { + return r.mNumerator.mSign > 0; + } + else if (mNumerator.mSign < 0) + { + if (r.mNumerator.mSign >= 0) + { + return true; + } + } + + return mNumerator * r.mDenominator < mDenominator * r.mNumerator; + } + + bool operator<=(BSRational const& r) const + { + return !r.operator<(*this); + } + + bool operator> (BSRational const& r) const + { + return r.operator<(*this); + } + + bool operator>=(BSRational const& r) const + { + return !operator<(r); + } + + // Unary operations. + BSRational operator+() const + { + return *this; + } + + BSRational operator-() const + { + return BSRational(-mNumerator, mDenominator); + } + + // Arithmetic. + BSRational operator+(BSRational const& r) const + { + BSNumber product0 = mNumerator * r.mDenominator; + BSNumber product1 = mDenominator * r.mNumerator; + BSNumber numerator = product0 + product1; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator-(BSRational const& r) const + { + BSNumber product0 = mNumerator * r.mDenominator; + BSNumber product1 = mDenominator * r.mNumerator; + BSNumber numerator = product0 - product1; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator*(BSRational const& r) const + { + BSNumber numerator = mNumerator * r.mNumerator; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator/(BSRational const& r) const + { + LogAssert(r.mNumerator.mSign != 0, "Division by zero not allowed."); + + BSNumber numerator = mNumerator * r.mDenominator; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mNumerator; + if (denominator.mSign < 0) + { + numerator.mSign = -numerator.mSign; + denominator.mSign = 1; + } + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational& operator+=(BSRational const& r) + { + *this = operator+(r); + return *this; + } + + BSRational& operator-=(BSRational const& r) + { + *this = operator-(r); + return *this; + } + + BSRational& operator*=(BSRational const& r) + { + *this = operator*(r); + return *this; + } + + BSRational& operator/=(BSRational const& r) + { + *this = operator/(r); + return *this; + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const + { + return mNumerator.Write(output) && mDenominator.Write(output); + } + + bool Read(std::ifstream& input) + { + return mNumerator.Read(input) && mDenominator.Read(input); + } + + private: + // Generic conversion code that converts to the correctly rounded + // result using round-to-nearest-ties-to-even. + template + RealType Convert() const + { + if (mNumerator.mSign == 0) + { + return (RealType)0; + } + + // The ratio is abstractly of the form (1.u*2^p)/(1.v*2^q). + // Convert to the form (1.u/1.v)*2^{p-q}, if 1.u >= 1.v, or to the + // form (2*(1.u)/1.v)*2*{p-q-1}) if 1.u < 1.v. The final form + // n/d must be in the interval [1,2). + BSNumber n = mNumerator, d = mDenominator; + int32_t sign = n.mSign * d.mSign; + n.mSign = 1; + d.mSign = 1; + int32_t pmq = n.GetExponent() - d.GetExponent(); + n.mBiasedExponent = 1 - n.GetUInteger().GetNumBits(); + d.mBiasedExponent = 1 - d.GetUInteger().GetNumBits(); + if (BSNumber::LessThanIgnoreSign(n, d)) + { + ++n.mBiasedExponent; + --pmq; + } + + // At this time, n/d = 1.c in [1,2). Define the sequence of bits + // w = 1c = w_{imax} w_{imax-1} ... w_0 w_{-1} w_{-2} ... where + // imax = precision(RealType)-1 and w_{imax} = 1. + + // Compute 'precision' bits for w, the leading bit guaranteed to + // be 1 and occurring at index (1 << (precision-1)). + BSNumber one(1), two(2); + int const imax = std::numeric_limits::digits - 1; + UIntType w = 0; + UIntType mask = ((UIntType)1 << imax); + for (int i = imax; i >= 0; --i, mask >>= 1) + { + if (BSNumber::LessThanIgnoreSign(n, d)) + { + n = two * n; + } + else + { + n = two * (n - d); + w |= mask; + } + } + + // Apply the mode round-to-nearest-ties-to-even to decide whether + // to round down or up. We computed w = w_{imax} ... w_0. The + // remainder is n/d = w_{imax+1}.w_{imax+2}... in [0,2). Compute + // n'/d = (n-d)/d in [-1,1). Round-to-nearest-ties-to-even mode + // is the following, where we need only test the sign of n'. A + // remainder of "half" is the case n' = 0. + // Round down when n' < 0 or (n' = 0 and w_0 = 0): use w + // Round up when n' > 0 or (n' = 0 and w_0 == 1): use w+1 + n = n - d; + if (n.mSign > 0 || (n.mSign == 0 && (w & 1) == 1)) + { + ++w; + } + + if (w > 0) + { + // Ensure that the low-order bit of w is 1, which is required + // for the BSNumber integer part. + int32_t trailing = GetTrailingBit(w); + w >>= trailing; + pmq += trailing; + + // Compute a BSNumber with integer part w and the appropriate + // number of bits and exponents. + BSNumber result(w); + result.mBiasedExponent = pmq - imax; + RealType converted = (RealType)result; + if (sign < 0) + { + converted = -converted; + } + return converted; + } + else + { + return (RealType)0; + } + } + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + public: + // List this first so that it shows up first in the debugger watch + // window. + double mValue; + private: +#endif + + BSNumber mNumerator, mDenominator; + + friend class UnitTestBSRational; + }; +} + +namespace std +{ + // TODO: Allow for implementations of the math functions in which a + // specified precision is used when computing the result. + + template + inline gte::BSRational abs(gte::BSRational const& x) + { + return (x.GetSign() >= 0 ? x : -x); + } + + template + inline gte::BSRational acos(gte::BSRational const& x) + { + return (gte::BSRational)std::acos((double)x); + } + + template + inline gte::BSRational acosh(gte::BSRational const& x) + { + return (gte::BSRational)std::acosh((double)x); + } + + template + inline gte::BSRational asin(gte::BSRational const& x) + { + return (gte::BSRational)std::asin((double)x); + } + + template + inline gte::BSRational asinh(gte::BSRational const& x) + { + return (gte::BSRational)std::asinh((double)x); + } + + template + inline gte::BSRational atan(gte::BSRational const& x) + { + return (gte::BSRational)std::atan((double)x); + } + + template + inline gte::BSRational atanh(gte::BSRational const& x) + { + return (gte::BSRational)std::atanh((double)x); + } + + template + inline gte::BSRational atan2(gte::BSRational const& y, gte::BSRational const& x) + { + return (gte::BSRational)std::atan2((double)y, (double)x); + } + + template + inline gte::BSRational ceil(gte::BSRational const& x) + { + return (gte::BSRational)std::ceil((double)x); + } + + template + inline gte::BSRational cos(gte::BSRational const& x) + { + return (gte::BSRational)std::cos((double)x); + } + + template + inline gte::BSRational cosh(gte::BSRational const& x) + { + return (gte::BSRational)std::cosh((double)x); + } + + template + inline gte::BSRational exp(gte::BSRational const& x) + { + return (gte::BSRational)std::exp((double)x); + } + + template + inline gte::BSRational exp2(gte::BSRational const& x) + { + return (gte::BSRational)std::exp2((double)x); + } + + template + inline gte::BSRational floor(gte::BSRational const& x) + { + return (gte::BSRational)std::floor((double)x); + } + + template + inline gte::BSRational fmod(gte::BSRational const& x, gte::BSRational const& y) + { + return (gte::BSRational)std::fmod((double)x, (double)y); + } + + template + inline gte::BSRational frexp(gte::BSRational const& x, int* exponent) + { + return (gte::BSRational)std::frexp((double)x, exponent); + } + + template + inline gte::BSRational ldexp(gte::BSRational const& x, int exponent) + { + return (gte::BSRational)std::ldexp((double)x, exponent); + } + + template + inline gte::BSRational log(gte::BSRational const& x) + { + return (gte::BSRational)std::log((double)x); + } + + template + inline gte::BSRational log2(gte::BSRational const& x) + { + return (gte::BSRational)std::log2((double)x); + } + + template + inline gte::BSRational log10(gte::BSRational const& x) + { + return (gte::BSRational)std::log10((double)x); + } + + template + inline gte::BSRational pow(gte::BSRational const& x, gte::BSRational const& y) + { + return (gte::BSRational)std::pow((double)x, (double)y); + } + + template + inline gte::BSRational sin(gte::BSRational const& x) + { + return (gte::BSRational)std::sin((double)x); + } + + template + inline gte::BSRational sinh(gte::BSRational const& x) + { + return (gte::BSRational)std::sinh((double)x); + } + + template + inline gte::BSRational sqrt(gte::BSRational const& x) + { + return (gte::BSRational)std::sqrt((double)x); + } + + template + inline gte::BSRational tan(gte::BSRational const& x) + { + return (gte::BSRational)std::tan((double)x); + } + + template + inline gte::BSRational tanh(gte::BSRational const& x) + { + return (gte::BSRational)std::tanh((double)x); + } +} + +namespace gte +{ + template + inline BSRational atandivpi(BSRational const& x) + { + return (BSRational)atandivpi((double)x); + } + + template + inline BSRational atan2divpi(BSRational const& y, BSRational const& x) + { + return (BSRational)atan2divpi((double)y, (double)x); + } + + template + inline BSRational clamp(BSRational const& x, BSRational const& xmin, BSRational const& xmax) + { + return (BSRational)clamp((double)x, (double)xmin, (double)xmax); + } + + template + inline BSRational cospi(BSRational const& x) + { + return (BSRational)cospi((double)x); + } + + template + inline BSRational exp10(BSRational const& x) + { + return (BSRational)exp10((double)x); + } + + template + inline BSRational invsqrt(BSRational const& x) + { + return (BSRational)invsqrt((double)x); + } + + template + inline int isign(BSRational const& x) + { + return isign((double)x); + } + + template + inline BSRational saturate(BSRational const& x) + { + return (BSRational)saturate((double)x); + } + + template + inline BSRational sign(BSRational const& x) + { + return (BSRational)sign((double)x); + } + + template + inline BSRational sinpi(BSRational const& x) + { + return (BSRational)sinpi((double)x); + } + + template + inline BSRational sqr(BSRational const& x) + { + return (BSRational)sqr((double)x); + } + + // See the comments in GteMath.h about traits is_arbitrary_precision + // and has_division_operator. + template + struct is_arbitrary_precision_internal> : std::true_type {}; + + template + struct has_division_operator_internal> : std::true_type {}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurve.h new file mode 100644 index 000000000000..ec100830ad74 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurve.h @@ -0,0 +1,195 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/02/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineCurve : public ParametricCurve +{ +public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null pointer + // and later access the control points via GetControls() or SetControl() + // member functions. The domain is t in [t[d],t[n]], where t[d] and t[n] + // are knots with d the degree and n the number of control points. To + // validate construction, create an object as shown: + // BSplineCurve curve(parameters); + // if (!curve) { ; } + BSplineCurve(BasisFunctionInput const& input, + Vector const* controls); + + // Member access. + inline BasisFunction const& GetBasisFunction() const; + inline int GetNumControls() const; + inline Vector const* GetControls() const; + inline Vector* GetControls(); + void SetControl(int i, Vector const& control); + Vector const& GetControl(int i) const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +private: + // Support for Evaluate(...). + Vector Compute(unsigned int order, int imin, int imax) const; + + BasisFunction mBasisFunction; + std::vector> mControls; +}; + + +template +BSplineCurve::BSplineCurve(BasisFunctionInput const& input, + Vector const* controls) + : + ParametricCurve((Real)0, (Real)1), + mBasisFunction(input) +{ + if (!mBasisFunction) + { + // Errors were already generated during construction of the + // basis function. + return; + } + + // The mBasisFunction stores the domain but so does ParametricCurve. + this->mTime.front() = mBasisFunction.GetMinDomain(); + this->mTime.back() = mBasisFunction.GetMaxDomain(); + + // The replication of control points for periodic splines is avoided + // by wrapping the i-loop index in Evaluate. + mControls.resize(input.numControls); + if (controls) + { + std::copy(controls, controls + input.numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + this->mConstructed = true; +} + +template +BasisFunction const& BSplineCurve::GetBasisFunction() const +{ + return mBasisFunction; +} + +template +int BSplineCurve::GetNumControls() const +{ + return static_cast(mControls.size()); +} + +template +Vector const* BSplineCurve::GetControls() const +{ + return mControls.data(); +} + +template +Vector* BSplineCurve::GetControls() +{ + return mControls.data(); +} + +template +void BSplineCurve::SetControl(int i, Vector const& control) +{ + if (0 <= i && i < GetNumControls()) + { + mControls[i] = control; + } +} + +template +Vector const& BSplineCurve::GetControl(int i) const +{ + if (0 <= i && i < GetNumControls()) + { + return mControls[i]; + } + else + { + return mControls[0]; + } +} + +template +void BSplineCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int imin, imax; + mBasisFunction.Evaluate(t, maxOrder, imin, imax); + + // Compute position. + values[0] = Compute(0, imin, imax); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = Compute(1, imin, imax); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = Compute(2, imin, imax); + if (maxOrder == 3) + { + values[3] = Compute(3, imin, imax); + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +Vector BSplineCurve::Compute(unsigned int order, int imin, + int imax) const +{ + // The j-index introduces a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j = i + // always. + + int numControls = GetNumControls(); + Vector result; + result.MakeZero(); + for (int i = imin; i <= imax; ++i) + { + Real tmp = mBasisFunction.GetValue(order, i); + int j = (i >= numControls ? i - numControls : i); + result += tmp * mControls[j]; + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurveFit.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurveFit.h new file mode 100644 index 000000000000..9df8b3c33d02 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineCurveFit.h @@ -0,0 +1,257 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineCurveFit +{ +public: + // Construction. The preconditions for calling the constructor are + // 1 <= degree && degree < numControls <= numSamples + // The samples points are contiguous blocks of 'dimension' real values + // stored in sampleData. + BSplineCurveFit(int dimension, int numSamples, Real const* sampleData, + int degree, int numControls); + + // Access to input sample information. + inline int GetDimension() const; + inline int GetNumSamples() const; + inline Real const* GetSampleData() const; + + // Access to output control point and curve information. + inline int GetDegree() const; + inline int GetNumControls() const; + inline Real const* GetControlData() const; + inline BasisFunction const& GetBasis() const; + + // Evaluation of the B-spline curve. It is defined for 0 <= t <= 1. If a + // t-value is outside [0,1], an open spline clamps it to [0,1]. The + // caller must ensure that position[] has at least 'dimension' elements. + void GetPosition(Real t, Real* position) const; + +private: + // Input sample information. + int mDimension; + int mNumSamples; + Real const* mSampleData; + + // The fitted B-spline curve, open and with uniform knots. + int mDegree; + int mNumControls; + std::vector mControlData; + BasisFunction mBasis; +}; + + +template +BSplineCurveFit::BSplineCurveFit(int dimension, int numSamples, + Real const* sampleData, int degree, int numControls) + : + mDimension(dimension), + mNumSamples(numSamples), + mSampleData(sampleData), + mDegree(degree), + mNumControls(numControls), + mControlData(dimension * numControls) +{ + LogAssert(dimension >= 1, "Invalid dimension."); + LogAssert(1 <= degree && degree < numControls, "Invalid degree."); + LogAssert(sampleData, "Invalid sample data."); + LogAssert(numControls <= numSamples, "Invalid number of controls."); + + BasisFunctionInput input; + input.numControls = numControls; + input.degree = degree; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = numControls - degree + 1; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0].t = (Real)0; + input.uniqueKnots[0].multiplicity = degree + 1; + int last = input.numUniqueKnots - 1; + Real factor = ((Real)1) / (Real)last; + for (int i = 1; i < last; ++i) + { + input.uniqueKnots[i].t = factor * (Real)i; + input.uniqueKnots[i].multiplicity = 1; + } + input.uniqueKnots[last].t = (Real)1; + input.uniqueKnots[last].multiplicity = degree + 1; + mBasis.Create(input); + + // Fit the data points with a B-spline curve using a least-squares error + // metric. The problem is of the form A^T*A*Q = A^T*P, where A^T*A is a + // banded matrix, P contains the sample data, and Q is the unknown vector + // of control points. + Real tMultiplier = ((Real)1) / (Real)(mNumSamples - 1); + Real t; + int i0, i1, i2, imin, imax, j; + + // Construct the matrix A^T*A. + int degp1 = mDegree + 1; + int numBands = (mNumControls > degp1 ? degp1 : mDegree); + BandedMatrix ATAMat(mNumControls, numBands, numBands); + for (i0 = 0; i0 < mNumControls; ++i0) + { + for (i1 = 0; i1 < i0; ++i1) + { + ATAMat(i0, i1) = ATAMat(i1, i0); + } + + int i1Max = i0 + mDegree; + if (i1Max >= mNumControls) + { + i1Max = mNumControls - 1; + } + + for (i1 = i0; i1 <= i1Max; ++i1) + { + Real value = (Real)0; + for (i2 = 0; i2 < mNumSamples; ++i2) + { + t = tMultiplier * (Real)i2; + mBasis.Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax) + { + Real b0 = mBasis.GetValue(0, i0); + Real b1 = mBasis.GetValue(0, i1); + value += b0 * b1; + } + } + ATAMat(i0, i1) = value; + } + } + + // Construct the matrix A^T. + Array2 ATMat(mNumSamples, mNumControls); + memset(ATMat[0], 0, mNumControls * mNumSamples * sizeof(Real)); + for (i0 = 0; i0 < mNumControls; ++i0) + { + for (i1 = 0; i1 < mNumSamples; ++i1) + { + t = tMultiplier * (Real)i1; + mBasis.Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax) + { + ATMat[i0][i1] = mBasis.GetValue(0, i0); + } + } + } + + // Compute X0 = (A^T*A)^{-1}*A^T by solving the linear system + // A^T*A*X = A^T. + bool solved = ATAMat.template SolveSystem(ATMat[0], mNumSamples); + LogAssert(solved, "Failed to solve linear system."); + (void)solved; + + // The control points for the fitted curve are stored in the vector + // Q = X0*P, where P is the vector of sample data. + std::fill(mControlData.begin(), mControlData.end(), (Real)0); + for (i0 = 0; i0 < mNumControls; ++i0) + { + Real* Q = &mControlData[i0 * mDimension]; + for (i1 = 0; i1 < mNumSamples; ++i1) + { + Real const* P = mSampleData + i1 * mDimension; + Real xValue = ATMat[i0][i1]; + for (j = 0; j < mDimension; ++j) + { + Q[j] += xValue*P[j]; + } + } + } + + // Set the first and last output control points to match the first and + // last input samples. This supports the application of fitting keyframe + // data with B-spline curves. The user expects that the curve passes + // through the first and last positions in order to support matching two + // consecutive keyframe sequences. + Real* cEnd0 = &mControlData[0]; + Real const* sEnd0 = mSampleData; + Real* cEnd1 = &mControlData[mDimension * (mNumControls - 1)]; + Real const* sEnd1 = &mSampleData[mDimension * (mNumSamples - 1)]; + for (j = 0; j < mDimension; ++j) + { + *cEnd0++ = *sEnd0++; + *cEnd1++ = *sEnd1++; + } +} + +template inline +int BSplineCurveFit::GetDimension() const +{ + return mDimension; +} + +template inline +int BSplineCurveFit::GetNumSamples() const +{ + return mNumSamples; +} + +template inline +Real const* BSplineCurveFit::GetSampleData() const +{ + return mSampleData; +} + +template inline +int BSplineCurveFit::GetDegree() const +{ + return mDegree; +} + +template inline +int BSplineCurveFit::GetNumControls() const +{ + return mNumControls; +} + +template inline +Real const* BSplineCurveFit::GetControlData() const +{ + return &mControlData[0]; +} + +template inline +BasisFunction const& BSplineCurveFit::GetBasis() const +{ + return mBasis; +} + +template +void BSplineCurveFit::GetPosition(Real t, Real* position) const +{ + int imin, imax; + mBasis.Evaluate(t, 0, imin, imax); + + Real const* source = &mControlData[mDimension * imin]; + Real basisValue = mBasis.GetValue(0, imin); + for (int j = 0; j < mDimension; ++j) + { + position[j] = basisValue * (*source++); + } + + for (int i = imin + 1; i <= imax; ++i) + { + basisValue = mBasis.GetValue(0, i); + for (int j = 0; j < mDimension; ++j) + { + position[j] += basisValue * (*source++); + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurface.h new file mode 100644 index 000000000000..f787f62ffe99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurface.h @@ -0,0 +1,208 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/02/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineSurface : public ParametricSurface +{ +public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null pointer + // and later access the control points via GetControls() or SetControl() + // member functions. The input 'controls' must be stored in row-major + // order, control[i0 + numControls0*i1]. As a 2D array, this corresponds + // to control2D[i1][i0]. To validate construction, create an object as + // shown: + // BSplineSurface surface(parameters); + // if (!surface) { ; } + BSplineSurface(BasisFunctionInput const input[2], + Vector const* controls); + + // Member access. The index 'dim' must be in {0,1}. + inline BasisFunction const& GetBasisFunction(int dim) const; + inline int GetNumControls(int dim) const; + inline Vector* GetControls(); + inline Vector const* GetControls() const; + void SetControl(int i0, int i1, Vector const& control); + Vector const& GetControl(int i0, int i1) const; + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv; second-order derivatives d2X/du2, d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int maxOrder, + Vector values[6]) const; + +private: + // Support for Evaluate(...). + Vector Compute(unsigned int uOrder, unsigned int vOrder, + int iumin, int iumax, int ivmin, int ivmax) const; + + std::array, 2> mBasisFunction; + std::array mNumControls; + std::vector> mControls; +}; + + +template +BSplineSurface::BSplineSurface(BasisFunctionInput const input[2], + Vector const* controls) + : + ParametricSurface((Real)0, (Real)1, (Real)0, (Real)1, true) +{ + for (int i = 0; i < 2; ++i) + { + mNumControls[i] = input[i].numControls; + mBasisFunction[i].Create(input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of the + // basis functions. + return; + } + } + + // The mBasisFunction stores the domain but so does ParametricCurve. + this->mUMin = mBasisFunction[0].GetMinDomain(); + this->mUMax = mBasisFunction[0].GetMaxDomain(); + this->mVMin = mBasisFunction[1].GetMinDomain(); + this->mVMax = mBasisFunction[1].GetMaxDomain(); + + // The replication of control points for periodic splines is avoided + // by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1]; + mControls.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + this->mConstructed = true; +} + +template +BasisFunction const& BSplineSurface::GetBasisFunction(int dim) const +{ + return mBasisFunction[dim]; +} + +template +int BSplineSurface::GetNumControls(int dim) const +{ + return mNumControls[dim]; +} + +template +Vector const* BSplineSurface::GetControls() const +{ + return mControls.data(); +} + +template +Vector* BSplineSurface::GetControls() +{ + return mControls.data(); +} + +template +void BSplineSurface::SetControl(int i0, int i1, Vector const& control) +{ + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1)) + { + mControls[i0 + mNumControls[0] * i1] = control; + } +} + +template +Vector const& BSplineSurface::GetControl(int i0, int i1) const +{ + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mControls[i0 + mNumControls[0] * i1]; + } + else + { + return mControls[0]; + } +} + +template +void BSplineSurface::Evaluate(Real u, Real v, unsigned int maxOrder, + Vector values[6]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 6; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + + // Compute position. + values[0] = Compute(0, 0, iumin, iumax, ivmin, ivmax); + if (maxOrder >= 1) + { + // Compute first-order derivatives. + values[1] = Compute(1, 0, iumin, iumax, ivmin, ivmax); + values[2] = Compute(0, 1, iumin, iumax, ivmin, ivmax); + if (maxOrder >= 2) + { + // Compute second-order derivatives. + values[3] = Compute(2, 0, iumin, iumax, ivmin, ivmax); + values[4] = Compute(1, 1, iumin, iumax, ivmin, ivmax); + values[5] = Compute(0, 2, iumin, iumax, ivmin, ivmax); + } + } +} + +template +Vector BSplineSurface::Compute(unsigned int uOrder, + unsigned int vOrder, int iumin, int iumax, int ivmin, int ivmax) const +{ + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + Vector result; + result.MakeZero(); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + result += (tmpu * tmpv) * mControls[ju + numControls0 * jv]; + } + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurfaceFit.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurfaceFit.h new file mode 100644 index 000000000000..2fe716694409 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineSurfaceFit.h @@ -0,0 +1,268 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class BSplineSurfaceFit +{ +public: + // Construction. The preconditions for calling the constructor are + // 1 <= degree0 && degree0 + 1 < numControls0 <= numSamples0 + // 1 <= degree1 && degree1 + 1 < numControls1 <= numSamples1 + // The sample data must be in row-major order. The control data is + // also stored in row-major order. + BSplineSurfaceFit(int degree0, int numControls0, int numSamples0, + int degree1, int numControls1, int numSamples1, + Vector3 const* sampleData); + + // Access to input sample information. + inline int GetNumSamples(int dimension) const; + inline Vector3 const* GetSampleData() const; + + // Access to output control point and surface information. + inline int GetDegree(int dimension) const; + inline int GetNumControls(int dimension) const; + inline Vector3 const* GetControlData() const; + inline BasisFunction const& GetBasis(int dimension) const; + + // Evaluation of the B-spline surface. It is defined for 0 <= u <= 1 + // and 0 <= v <= 1. If a parameter value is outside [0,1], it is clamped + // to [0,1]. + Vector3 GetPosition(Real u, Real v) const; + +private: + // Input sample information. + int mNumSamples[2]; + Vector3 const* mSampleData; + + // The fitted B-spline surface, open and with uniform knots. + int mDegree[2]; + int mNumControls[2]; + std::vector> mControlData; + BasisFunction mBasis[2]; +}; + + +template +BSplineSurfaceFit::BSplineSurfaceFit(int degree0, int numControls0, + int numSamples0, int degree1, int numControls1, int numSamples1, + Vector3 const* sampleData) + : + mSampleData(sampleData), + mControlData(numControls0 * numControls1) +{ + LogAssert(1 <= degree0 && degree0 + 1 < numControls0, "Invalid degree."); + LogAssert(numControls0 <= numSamples0, "Invalid number of controls."); + LogAssert(1 <= degree1 && degree1 + 1 < numControls1, "Invalid degree."); + LogAssert(numControls1 <= numSamples1, "Invalid number of controls."); + LogAssert(sampleData, "Invalid sample data."); + + mDegree[0] = degree0; + mNumSamples[0] = numSamples0; + mNumControls[0] = numControls0; + mDegree[1] = degree1; + mNumSamples[1] = numSamples1; + mNumControls[1] = numControls1; + + BasisFunctionInput input; + Real tMultiplier[2]; + int dim; + for (dim = 0; dim < 2; ++dim) + { + input.numControls = mNumControls[dim]; + input.degree = mDegree[dim]; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = mNumControls[dim] - mDegree[dim] + 1; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0].t = (Real)0; + input.uniqueKnots[0].multiplicity = mDegree[dim] + 1; + int last = input.numUniqueKnots - 1; + Real factor = ((Real)1) / (Real)last; + for (int i = 1; i < last; ++i) + { + input.uniqueKnots[i].t = factor * (Real)i; + input.uniqueKnots[i].multiplicity = 1; + } + input.uniqueKnots[last].t = (Real)1; + input.uniqueKnots[last].multiplicity = mDegree[dim] + 1; + mBasis[dim].Create(input); + + tMultiplier[dim] = ((Real)1) / (Real)(mNumSamples[dim] - 1); + } + + // Fit the data points with a B-spline surface using a least-squares error + // metric. The problem is of the form A0^T*A0*Q*A1^T*A1 = A0^T*P*A1, where + // A0^T*A0 and A1^T*A1 are banded matrices, P contains the sample data, + // and Q is the unknown matrix of control points. + Real t; + int i0, i1, i2, imin, imax; + + // Construct the matrices A0^T*A0 and A1^T*A1. + BandedMatrix ATAMat[2] = + { + BandedMatrix(mNumControls[0], mDegree[0] + 1, mDegree[0] + 1), + BandedMatrix(mNumControls[1], mDegree[1] + 1, mDegree[1] + 1) + }; + + for (dim = 0; dim < 2; ++dim) + { + for (i0 = 0; i0 < mNumControls[dim]; ++i0) + { + for (i1 = 0; i1 < i0; ++i1) + { + ATAMat[dim](i0, i1) = ATAMat[dim](i1, i0); + } + + int i1Max = i0 + mDegree[dim]; + if (i1Max >= mNumControls[dim]) + { + i1Max = mNumControls[dim] - 1; + } + + for (i1 = i0; i1 <= i1Max; ++i1) + { + Real value = (Real)0; + for (i2 = 0; i2 < mNumSamples[dim]; ++i2) + { + t = tMultiplier[dim] * (Real)i2; + mBasis[dim].Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax) + { + Real b0 = mBasis[dim].GetValue(0, i0); + Real b1 = mBasis[dim].GetValue(0, i1); + value += b0 * b1; + } + } + ATAMat[dim](i0, i1) = value; + } + } + } + + // Construct the matrices A0^T and A1^T. A[d]^T has mNumControls[d] + // rows and mNumSamples[d] columns. + Array2 ATMat[2]; + for (dim = 0; dim < 2; dim++) + { + ATMat[dim] = Array2(mNumSamples[dim], mNumControls[dim]); + size_t numBytes = mNumControls[dim] * mNumSamples[dim] * sizeof(Real); + memset(ATMat[dim][0], 0, numBytes); + for (i0 = 0; i0 < mNumControls[dim]; ++i0) + { + for (i1 = 0; i1 < mNumSamples[dim]; ++i1) + { + t = tMultiplier[dim] * (Real)i1; + mBasis[dim].Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax) + { + ATMat[dim][i0][i1] = mBasis[dim].GetValue(0, i0); + } + } + } + } + + // Compute X0 = (A0^T*A0)^{-1}*A0^T and X1 = (A1^T*A1)^{-1}*A1^T by + // solving the linear systems A0^T*A0*X0 = A0^T and A1^T*A1*X1 = A1^T. + for (dim = 0; dim < 2; ++dim) + { + bool solved = ATAMat[dim].template SolveSystem(ATMat[dim][0], + mNumSamples[dim]); + LogAssert(solved, "Failed to solve linear system."); + (void)solved; + } + + // The control points for the fitted surface are stored in the matrix + // Q = X0*P*X1^T, where P is the matrix of sample data. + for (i1 = 0; i1 < mNumControls[1]; ++i1) + { + for (i0 = 0; i0 < mNumControls[0]; ++i0) + { + Vector3 sum = Vector3::Zero(); + for (int j1 = 0; j1 < mNumSamples[1]; ++j1) + { + Real x1Value = ATMat[1][i1][j1]; + for (int j0 = 0; j0 < mNumSamples[0]; ++j0) + { + Real x0Value = ATMat[0][i0][j0]; + Vector3 sample = + mSampleData[j0 + mNumSamples[0] * j1]; + sum += (x0Value * x1Value) * sample; + } + } + mControlData[i0 + mNumControls[0] * i1] = sum; + } + } +} + +template inline +int BSplineSurfaceFit::GetNumSamples(int dimension) const +{ + return mNumSamples[dimension]; +} + +template inline +Vector3 const* BSplineSurfaceFit::GetSampleData() const +{ + return mSampleData; +} + +template inline +int BSplineSurfaceFit::GetDegree(int dimension) const +{ + return mDegree[dimension]; +} + +template inline +int BSplineSurfaceFit::GetNumControls(int dimension) const +{ + return mNumControls[dimension]; +} + +template inline +Vector3 const* BSplineSurfaceFit::GetControlData() const +{ + return &mControlData[0]; +} + +template inline +BasisFunction const& BSplineSurfaceFit::GetBasis(int dimension) +const +{ + return mBasis[dimension]; +} + +template +Vector3 BSplineSurfaceFit::GetPosition(Real u, Real v) const +{ + int iumin, iumax, ivmin, ivmax; + mBasis[0].Evaluate(u, 0, iumin, iumax); + mBasis[1].Evaluate(v, 0, ivmin, ivmax); + + Vector3 position = Vector3::Zero(); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real value1 = mBasis[1].GetValue(0, iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real value0 = mBasis[0].GetValue(0, iu); + Vector3 control = mControlData[iu + mNumControls[0] * iv]; + position += (value0 * value1) * control; + } + } + return position; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineVolume.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineVolume.h new file mode 100644 index 000000000000..85d2c36e3591 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBSplineVolume.h @@ -0,0 +1,254 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/02/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class BSplineVolume +{ +public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null pointer + // and later access the control points via GetControls() or SetControl() + // member functions. The input 'controls' must be stored in + // lexicographical order, control[i0+numControls0*(i1+numControls1*i2)]. + // As a 3D array, this corresponds to control3D[i2][i1][i0]. + BSplineVolume(BasisFunctionInput const input[3], + Vector const* controls); + + // To validate construction, create an object as shown: + // BSplineVolume volume(parameters); + // if (!volume) { ; } + inline operator bool() const; + + // Member access. The index 'dim' must be in {0,1,2}. + inline BasisFunction const& GetBasisFunction(int dim) const; + inline Real GetMinDomain(int dim) const; + inline Real GetMaxDomain(int dim) const; + inline int GetNumControls(int dim) const; + inline Vector const* GetControls() const; + inline Vector* GetControls(); + void SetControl(int i0, int i1, int i2, Vector const& control); + Vector const& GetControl(int i0, int i1, int i2) const; + + // Evaluation of the volume. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv, dX/dw; second-order derivatives d2X/du2, d2X/dv2, + // d2X/dw2, d2X/dudv, d2X/dudw, d2X/dvdw. + void Evaluate(Real u, Real v, Real w, unsigned int maxOrder, + Vector values[10]) const; + +private: + // Support for Evaluate(...). + Vector Compute(unsigned int uOrder, unsigned int vOrder, + unsigned int wOrder, int iumin, int iumax, int ivmin, int ivmax, + int iwmin, int iwmax) const; + + std::array, 3> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + bool mConstructed; +}; + + +template +BSplineVolume::BSplineVolume(BasisFunctionInput const input[3], + Vector const* controls) + : + mConstructed(false) +{ + for (int i = 0; i < 3; ++i) + { + mNumControls[i] = input[i].numControls; + mBasisFunction[i].Create(input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of the + // basis functions. + return; + } + } + + // The replication of control points for periodic splines is avoided + // by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1] * mNumControls[2]; + mControls.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + mConstructed = true; +} + +template +BasisFunction const& BSplineVolume::GetBasisFunction(int dim) const +{ + return mBasisFunction[dim]; +} + +template +Real BSplineVolume::GetMinDomain(int dim) const +{ + return mBasisFunction[dim].GetMinDomain(); +} + +template +Real BSplineVolume::GetMaxDomain(int dim) const +{ + return mBasisFunction[dim].GetMaxDomain(); +} + +template +BSplineVolume::operator bool() const +{ + return mConstructed; +} + +template +int BSplineVolume::GetNumControls(int dim) const +{ + return mNumControls[dim]; +} + +template +Vector const* BSplineVolume::GetControls() const +{ + return mControls.data(); +} + +template +Vector* BSplineVolume::GetControls() +{ + return mControls.data(); +} + +template +void BSplineVolume::SetControl(int i0, int i1, int i2, + Vector const& control) +{ + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1) + && 0 <= i2 && i2 < GetNumControls(2)) + { + mControls[i0 + mNumControls[0] * (i1 + mNumControls[1] * i2)] = control; + } +} + +template +Vector const& BSplineVolume::GetControl(int i0, int i1, int i2) const +{ + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1) + && 0 <= i2 && i2 < GetNumControls(2)) + { + return mControls[i0 + mNumControls[0] * (i1 + mNumControls[1] * i2)]; + } + else + { + return mControls[0]; + } +} + +template +void BSplineVolume::Evaluate(Real u, Real v, Real w, + unsigned int maxOrder, Vector values[10]) const +{ + if (!mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 10; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax, iwmin, iwmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + mBasisFunction[2].Evaluate(w, maxOrder, iwmin, iwmax); + + // Compute position. + values[0] = Compute(0, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + if (maxOrder >= 1) + { + // Compute first-order derivatives. + values[1] = + Compute(1, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[2] = + Compute(0, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[3] = + Compute(0, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + if (maxOrder >= 2) + { + // Compute second-order derivatives. + values[4] = + Compute(2, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[5] = + Compute(0, 2, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[6] = + Compute(0, 0, 2, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[7] = + Compute(1, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[8] = + Compute(1, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + values[9] = + Compute(0, 1, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + } + } +} + +template +Vector BSplineVolume::Compute(unsigned int uOrder, + unsigned int vOrder, unsigned int wOrder, int iumin, int iumax, int ivmin, + int ivmax, int iwmin, int iwmax) const +{ + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + int const numControls2 = mNumControls[2]; + Vector result; + result.MakeZero(); + for (int iw = iwmin; iw <= iwmax; ++iw) + { + Real tmpw = mBasisFunction[2].GetValue(wOrder, iw); + int jw = (iw >= numControls2 ? iw - numControls2 : iw); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + Real tmpvw = tmpv * tmpw; + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + result += (tmpu * tmpvw) * + mControls[ju + numControls0*(jv + numControls1*jw)]; + } + } + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBandedMatrix.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBandedMatrix.h new file mode 100644 index 000000000000..d738db62540d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBandedMatrix.h @@ -0,0 +1,573 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class BandedMatrix +{ +public: + // Construction and destruction. + ~BandedMatrix(); + BandedMatrix(int size, int numLBands, int numUBands); + + // Member access. + inline int GetSize() const; + inline std::vector& GetDBand(); + inline std::vector const& GetDBand() const; + inline std::vector>& GetLBands(); + inline std::vector> const& GetLBands() const; + inline std::vector>& GetUBands(); + inline std::vector> const& GetUBands() const; + Real& operator()(int r, int c); + Real const& operator()(int r, int c) const; + + // Factor the square banded matrix A into A = L*L^T, where L is a + // lower-triangular matrix (L^T is an upper-triangular matrix). This is + // an LU decomposition that allows for stable inversion of A to solve + // A*X = B. The return value is 'true' iff the factorizing is successful + // (L is invertible). If successful, A contains the Cholesky + // factorization: L in the lower-triangular part of A and L^T in the + // upper-triangular part of A. + bool CholeskyFactor(); + + // Solve the linear system A*X = B, where A is an NxN banded matrix and B + // is an Nx1 vector. The unknown X is also Nx1. The input to this + // function is B. The output X is computed and stored in B. The return + // value is 'true' iff the system has a solution. The matrix A and the + // vector B are both modified by this function. If successful, A contains + // the Cholesky factorization: L in the lower-triangular part of A and + // L^T in the upper-triangular part of A. + bool SolveSystem(Real* bVector); + + // Solve the linear system A*X = B, where A is an NxN banded matrix and + // B is an NxM matrix. The unknown X is also NxM. The input to this + // function is B. The output X is computed and stored in B. The return + // value is 'true' iff the system has a solution. The matrix A and the + // vector B are both modified by this function. If successful, A contains + // the Cholesky factorization: L in the lower-triangular part of A and + // L^T in the upper-triangular part of A. + // + // 'bMatrix' must have the storage order specified by the template + // parameter. + template + bool SolveSystem(Real* bMatrix, int numBColumns); + + // Compute the inverse of the banded matrix. The return value is 'true' + // when the matrix is invertible, in which case the 'inverse' output is + // valid. The return value is 'false' when the matrix is not invertible, + // in which case 'inverse' is invalid and should not be used. The + // input matrix 'inverse' must be the same size as 'this'. + // + // 'bMatrix' must have the storage order specified by the template + // parameter. + template + bool ComputeInverse(Real* inverse) const; + +private: + // The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce this + // to U*X = L^{-1}*B. The return value is 'true' iff the operation is + // successful. + bool SolveLower(Real* dataVector) const; + + // The linear system is U*X = L^{-1}*B. Reduce this to + // X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation is + // successful. + bool SolveUpper(Real* dataVector) const; + + // The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce this + // to U*X = L^{-1}*B. The return value is 'true' iff the operation is + // successful. See the comments for SolveSystem(Real*,int) about the + // storage for dataMatrix. + template + bool SolveLower(Real* dataMatrix, int numColumns) const; + + // The linear system is U*X = L^{-1}*B. Reduce this to + // X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation is + // successful. See the comments for SolveSystem(Real*,int) about the + // storage for dataMatrix. + template + bool SolveUpper(Real* dataMatrix, int numColumns) const; + + int mSize; + std::vector mDBand; + std::vector> mLBands, mUBands; + + // For return by operator()(int,int) for valid indices not in the bands, + // in which case the matrix entries are zero, + mutable Real mZero; +}; + + +template +BandedMatrix::~BandedMatrix() +{ +} + +template +BandedMatrix::BandedMatrix(int size, int numLBands, int numUBands) + : + mSize(size), + mZero((Real)0) +{ + if (size > 0 + && 0 <= numLBands && numLBands < size + && 0 <= numUBands && numUBands < size) + { + mDBand.resize(size); + std::fill(mDBand.begin(), mDBand.end(), (Real)0); + + if (numLBands > 0) + { + mLBands.resize(numLBands); + int numElements = size - 1; + for (auto& band : mLBands) + { + band.resize(numElements--); + std::fill(band.begin(), band.end(), (Real)0); + } + } + + if (numUBands > 0) + { + mUBands.resize(numUBands); + int numElements = size - 1; + for (auto& band : mUBands) + { + band.resize(numElements--); + std::fill(band.begin(), band.end(), (Real)0); + } + } + } + else + { + // Invalid argument to BandedMatrix constructor. + mSize = 0; + } +} + +template inline +int BandedMatrix::GetSize() const +{ + return mSize; +} + +template inline +std::vector& BandedMatrix::GetDBand() +{ + return mDBand; +} + +template inline +std::vector const& BandedMatrix::GetDBand() const +{ + return mDBand; +} + +template inline +std::vector>& BandedMatrix::GetLBands() +{ + return mLBands; +} + +template inline +std::vector> const& BandedMatrix::GetLBands() const +{ + return mLBands; +} + +template inline +std::vector>& BandedMatrix::GetUBands() +{ + return mUBands; +} + +template inline +std::vector> const& BandedMatrix::GetUBands() const +{ + return mUBands; +} + +template +Real& BandedMatrix::operator()(int r, int c) +{ + if (0 <= r && r < mSize && 0 <= c && c < mSize) + { + int band = c - r; + if (band > 0) + { + int const numUBands = static_cast(mUBands.size()); + if (--band < numUBands && r < mSize - 1 - band) + { + return mUBands[band][r]; + } + } + else if (band < 0) + { + band = -band; + int const numLBands = static_cast(mLBands.size()); + if (--band < numLBands && c < mSize - 1 - band) + { + return mLBands[band][c]; + } + } + else + { + return mDBand[r]; + } + } + // else invalid index + + + // Set the value to zero in case someone unknowingly modified mZero on a + // previous call to operator(int,int). + mZero = (Real)0; + return mZero; +} + +template +Real const& BandedMatrix::operator()(int r, int c) const +{ + if (0 <= r && r < mSize && 0 <= c && c < mSize) + { + int band = c - r; + if (band > 0) + { + int const numUBands = static_cast(mUBands.size()); + if (--band < numUBands && r < mSize - 1 - band) + { + return mUBands[band][r]; + } + } + else if (band < 0) + { + band = -band; + int const numLBands = static_cast(mLBands.size()); + if (--band < numLBands && c < mSize - 1 - band) + { + return mLBands[band][c]; + } + } + else + { + return mDBand[r]; + } + } + // else invalid index + + + // Set the value to zero in case someone unknowingly modified mZero on a + // previous call to operator(int,int). + mZero = (Real)0; + return mZero; +} + +template +bool BandedMatrix::CholeskyFactor() +{ + if (mDBand.size() == 0 || mLBands.size() != mUBands.size()) + { + // Invalid number of bands. + return false; + } + + int const sizeM1 = mSize - 1; + int const numBands = static_cast(mLBands.size()); + + int k, kMax; + for (int i = 0; i < mSize; ++i) + { + int jMin = i - numBands; + if (jMin < 0) + { + jMin = 0; + } + + int j; + for (j = jMin; j < i; ++j) + { + kMax = j + numBands; + if (kMax > sizeM1) + { + kMax = sizeM1; + } + + for (k = i; k <= kMax; ++k) + { + operator()(k, i) -= operator()(i, j)*operator()(k, j); + } + } + + kMax = j + numBands; + if (kMax > sizeM1) + { + kMax = sizeM1; + } + + for (k = 0; k < i; ++k) + { + operator()(k, i) = operator()(i, k); + } + + Real diagonal = operator()(i, i); + if (diagonal <= (Real)0) + { + return false; + } + Real invSqrt = ((Real)1) / std::sqrt(diagonal); + for (k = i; k <= kMax; ++k) + { + operator()(k, i) *= invSqrt; + } + } + + return true; +} + +template +bool BandedMatrix::SolveSystem(Real* bVector) +{ + return CholeskyFactor() + && SolveLower(bVector) + && SolveUpper(bVector); +} + +template +template +bool BandedMatrix::SolveSystem(Real* bMatrix, int numBColumns) +{ + return CholeskyFactor() + && SolveLower(bMatrix, numBColumns) + && SolveUpper(bMatrix, numBColumns); +} + +template +template +bool BandedMatrix::ComputeInverse(Real* inverse) const +{ + LexicoArray2 invA(mSize, mSize, inverse); + + BandedMatrix tmpA = *this; + for (int row = 0; row < mSize; ++row) + { + for (int col = 0; col < mSize; ++col) + { + if (row != col) + { + invA(row, col) = (Real)0; + } + else + { + invA(row, row) = (Real)1; + } + } + } + + // Forward elimination. + for (int row = 0; row < mSize; ++row) + { + // The pivot must be nonzero in order to proceed. + Real diag = tmpA(row, row); + if (diag == (Real)0) + { + return false; + } + + Real invDiag = ((Real)1) / diag; + tmpA(row, row) = (Real)1; + + // Multiply the row to be consistent with diagonal term of 1. + int colMin = row + 1; + int colMax = colMin + static_cast(mUBands.size()); + if (colMax > mSize) + { + colMax = mSize; + } + + int c; + for (c = colMin; c < colMax; ++c) + { + tmpA(row, c) *= invDiag; + } + for (c = 0; c <= row; ++c) + { + invA(row, c) *= invDiag; + } + + // Reduce the remaining rows. + int rowMin = row + 1; + int rowMax = rowMin + static_cast(mLBands.size()); + if (rowMax > mSize) + { + rowMax = mSize; + } + + for (int r = rowMin; r < rowMax; ++r) + { + Real mult = tmpA(r, row); + tmpA(r, row) = (Real)0; + for (c = colMin; c < colMax; ++c) + { + tmpA(r, c) -= mult*tmpA(row, c); + } + for (c = 0; c <= row; ++c) + { + invA(r, c) -= mult*invA(row, c); + } + } + } + + // Backward elimination. + for (int row = mSize - 1; row >= 1; --row) + { + int rowMax = row - 1; + int rowMin = row - static_cast(mUBands.size()); + if (rowMin < 0) + { + rowMin = 0; + } + + for (int r = rowMax; r >= rowMin; --r) + { + Real mult = tmpA(r, row); + tmpA(r, row) = (Real)0; + for (int c = 0; c < mSize; ++c) + { + invA(r, c) -= mult*invA(row, c); + } + } + } + + return true; +} + +template +bool BandedMatrix::SolveLower(Real* dataVector) const +{ + int const size = static_cast(mDBand.size()); + for (int r = 0; r < size; ++r) + { + Real lowerRR = operator()(r, r); + if (lowerRR >(Real)0) + { + for (int c = 0; c < r; ++c) + { + Real lowerRC = operator()(r, c); + dataVector[r] -= lowerRC * dataVector[c]; + } + dataVector[r] /= lowerRR; + } + else + { + return false; + } + } + return true; +} + +template +bool BandedMatrix::SolveUpper(Real* dataVector) const +{ + int const size = static_cast(mDBand.size()); + for (int r = size - 1; r >= 0; --r) + { + Real upperRR = operator()(r, r); + if (upperRR > (Real)0) + { + for (int c = r + 1; c < size; ++c) + { + Real upperRC = operator()(r, c); + dataVector[r] -= upperRC * dataVector[c]; + } + + dataVector[r] /= upperRR; + } + else + { + return false; + } + } + return true; +} + +template +template +bool BandedMatrix::SolveLower(Real* dataMatrix, int numColumns) const +{ + LexicoArray2 data(mSize, numColumns, dataMatrix); + + for (int r = 0; r < mSize; ++r) + { + Real lowerRR = operator()(r, r); + if (lowerRR >(Real)0) + { + for (int c = 0; c < r; ++c) + { + Real lowerRC = operator()(r, c); + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) -= lowerRC * data(c, bCol); + } + } + + Real inverse = ((Real)1) / lowerRR; + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) *= inverse; + } + } + else + { + return false; + } + } + return true; +} + +template +template +bool BandedMatrix::SolveUpper(Real* dataMatrix, int numColumns) const +{ + LexicoArray2 data(mSize, numColumns, dataMatrix); + + for (int r = mSize - 1; r >= 0; --r) + { + Real upperRR = operator()(r, r); + if (upperRR > (Real)0) + { + for (int c = r + 1; c < mSize; ++c) + { + Real upperRC = operator()(r, c); + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) -= upperRC * data(c, bCol); + } + } + + Real inverse = ((Real)1) / upperRR; + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) *= inverse; + } + } + else + { + return false; + } + } + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBasisFunction.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBasisFunction.h new file mode 100644 index 000000000000..84bf1c5c3576 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBasisFunction.h @@ -0,0 +1,596 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +struct UniqueKnot +{ + Real t; + int multiplicity; +}; + +template +struct BasisFunctionInput +{ + // The members are uninitialized. + BasisFunctionInput(); + + // Construct an open uniform curve with t in [0,1]. + BasisFunctionInput(int inNumControls, int inDegree); + + int numControls; + int degree; + bool uniform; + bool periodic; + int numUniqueKnots; + std::vector> uniqueKnots; +}; + +template +class BasisFunction +{ +public: + // Let n be the number of control points. Let d be the degree, where + // 1 <= d <= n-1. The number of knots is k = n + d + 1. The knots are + // t[i] for 0 <= i < k and must be nondecreasing, t[i] <= t[i+1], but a + // knot value can be repeated. Let s be the number of distinct knots. + // Let the distinct knots be u[j] for 0 <= j < s, so u[j] < u[j+1] for + // all j. The set of u[j] is called a 'breakpoint sequence'. Let + // m[j] >= 1 be the multiplicity; that is, if t[i] is the first occurrence + // of u[j], then t[i+r] = t[i] for 1 <= r < m[j]. The multiplicities have + // the constraints m[0] <= d+1, m[s-1] <= d+1, and m[j] <= d for + // 1 <= j <= s-2. Also, k = sum_{j=0}^{s-1} m[j], which says the + // multiplicities account for all k knots. + // + // Given a knot vector (t[0],...,t[n+d]), the domain of the corresponding + // B-spline curve is the interval [t[d],t[n]]. + // + // The corresponding B-spline or NURBS curve is characterized as follows. + // See "Geometric Modeling with Splines: An Introduction" by Elaine Cohen, + // Richard F. Riesenfeld, and Gershon Elber, AK Peters, 2001, Natick MA. + // The curve is 'open' when m[0] = m[s-1] = d+1; otherwise, it is + // 'floating'. An open curve is uniform when the knots t[d] through + // t[n] are equally spaced; that is, t[i+1] - t[i] are a common value + // for d <= i <= n-1. By implication, s = n-d+1 and m[j] = 1 for + // 1 <= j <= s-2. An open curve that does not satisfy these conditions + // is said to be nonuniform. A floating curve is uniform when + // m[j] = 1 for 0 <= j <= s-1 and t[i+1] - t[i] are a common value for + // 0 <= i <= k-2; otherwise, the floating curve is nonuniform. + // + // A special case of a floating curve is a periodic curve. The intent + // is that the curve is closed, so the first and last control points + // should be the same, which ensures C^{0} continuity. Higher-order + // continuity is obtained by repeating more control points. If the + // control points are P[0] through P[n-1], append the points P[0] + // through P[d-1] to ensure C^{d-1} continuity. Additionally the knots + // must be chosen properly. You may choose t[d] through t[n] as you + // wish. The other knots are defined by + // t[i] - t[i-1] = t[n-d+i] - t[n-d+i-1] + // t[n+i] - t[n+i-1] = t[d+i] - t[d+i-1] + // for 1 <= i <= d. + + // Construction and destruction. The determination that the curve is + // open or floating is based on the multiplicities. The 'uniform' input + // is used to avoid misclassifications due to floating-point rounding + // errors. Specifically, the breakpoints might be equally spaced + // (uniform) as real numbers, but the floating-point representations can + // have rounding errors that cause the knot differences not to be exactly + // the same constant. A periodic curve can have uniform or nonuniform + // knots. This object makes copies of the input arrays. + + ~BasisFunction(); + BasisFunction(); + BasisFunction(BasisFunctionInput const& input); + + // No copying is allowed. + BasisFunction(BasisFunction const&) = delete; + BasisFunction& operator=(BasisFunction const&) = delete; + + // Support for explicit creation in classes that have std::array + // members involving BasisFunction. This is a call-once function. + void Create(BasisFunctionInput const& input); + + // The inputs have complicated validation tests. You should create a + // BasisFunction object as shown: + // BasisFunction function(parameters); + // if (!function) { ; } + inline operator bool() const; + + // Member access. + inline int GetNumControls() const; + inline int GetDegree() const; + inline int GetNumUniqueKnots() const; + inline int GetNumKnots() const; + inline Real GetMinDomain() const; + inline Real GetMaxDomain() const; + inline bool IsOpen() const; + inline bool IsUniform() const; + inline bool IsPeriodic() const; + inline UniqueKnot const* GetUniqueKnots() const; + inline Real const* GetKnots() const; + + // Evaluation of the basis function and its derivatives through order 3. + // For the function value only, pass order 0. For the function and first + // derivative, pass order 1, and so on. + void Evaluate(Real t, unsigned int order, int& minIndex, int& maxIndex) + const; + + // Access the results of the call to Evaluate(...). The index i must + // satisfy minIndex <= i <= maxIndex. If it is not, the function returns + // zero. The separation of evaluation and access is based on local + // control of the basis function; that is, only the accessible values are + // (potentially) not zero. + Real GetValue(unsigned int order, int i) const; + +private: + // Determine the index i for which knot[i] <= t < knot[i+1]. The t-value + // is modified (wrapped for periodic splines, clamped for nonperiodic + // splines). + int GetIndex(Real& t) const; + + // Constructor inputs and values derived from them. + int mNumControls; + int mDegree; + Real mTMin, mTMax, mTLength; + bool mOpen; + bool mUniform; + bool mPeriodic; + bool mConstructed; + std::vector> mUniqueKnots; + std::vector mKnots; + + // Lookup information for the GetIndex() function. The first element of + // the pair is a unique knot value. The second element is the index in + // mKnots[] for the last occurrence of that knot value. NOTE: This is + // a heavily used function during Evaluate calls. This member was + // initially set to be std::vector<*>, but in debug mode the range-based + // for-loop in GetIndex was taking an extremely long time due to checked + // calls involving iterators. We modified this to a regular array to + // ensure that debug performance is better. + std::vector> mKeys; + + // Storage for the basis functions and their first three derivatives. + mutable Array2 mValue[4]; // mValue[i] is array[d+1][n+d] +}; + + +template +BasisFunctionInput::BasisFunctionInput() +{ +} + +template +BasisFunctionInput::BasisFunctionInput(int inNumControls, int inDegree) + : + numControls(inNumControls), + degree(inDegree), + uniform(true), + periodic(false), + numUniqueKnots(numControls - degree + 1), + uniqueKnots(numUniqueKnots) +{ + uniqueKnots.front().t = (Real)0; + uniqueKnots.front().multiplicity = degree + 1; + for (int i = 1; i <= numUniqueKnots - 2; ++i) + { + uniqueKnots[i].t = i / (numUniqueKnots - (Real)1); + uniqueKnots[i].multiplicity = 1; + } + uniqueKnots.back().t = (Real)1; + uniqueKnots.back().multiplicity = degree + 1; +} + +template +BasisFunction::~BasisFunction() +{ +} + +template +BasisFunction::BasisFunction() + : + mNumControls(0), + mDegree(0), + mTMin((Real)0), + mTMax((Real)0), + mTLength((Real)0), + mOpen(false), + mUniform(false), + mPeriodic(false), + mConstructed(false) +{ +} + +template +BasisFunction::BasisFunction(BasisFunctionInput const& input) + : + mConstructed(false) +{ + Create(input); +} + + +template +void BasisFunction::Create(BasisFunctionInput const& input) +{ + if (mConstructed) + { + LogError("Object already created."); + return; + } + + mNumControls = (input.periodic ? input.numControls + input.degree : input.numControls); + mDegree = input.degree; + mTMin = (Real)0; + mTMax = (Real)0; + mTLength = (Real)0; + mOpen = false; + mUniform = input.uniform; + mPeriodic = input.periodic; + for (int i = 0; i < 4; ++i) + { + mValue[i] = Array2(); + } + + if (input.numControls < 2) + { + LogError("Invalid number of control points."); + return; + } + + if (input.degree < 1 || input.degree >= input.numControls) + { + LogError("Invalid degree."); + return; + } + + if (input.numUniqueKnots < 2) + { + LogError("Invalid number of unique knots."); + return; + } + + mUniqueKnots.resize(input.numUniqueKnots); + std::copy(input.uniqueKnots.begin(), input.uniqueKnots.begin() + input.numUniqueKnots, + mUniqueKnots.begin()); + + Real u = mUniqueKnots.front().t; + for (int i = 1; i < input.numUniqueKnots - 1; ++i) + { + Real uNext = mUniqueKnots[i].t; + if (u >= uNext) + { + LogError("Unique knots are not strictly increasing."); + return; + } + u = uNext; + } + + int mult0 = mUniqueKnots.front().multiplicity; + if (mult0 < 1 || mult0 > mDegree + 1) + { + LogError("Invalid first multiplicity."); + return; + } + + int mult1 = mUniqueKnots.back().multiplicity; + if (mult1 < 1 || mult1 > mDegree + 1) + { + LogError("Invalid last multiplicity."); + return; + } + + for (int i = 1; i <= input.numUniqueKnots - 2; ++i) + { + int mult = mUniqueKnots[i].multiplicity; + if (mult < 1 || mult > mDegree) + { + LogError("Invalid interior multiplicity."); + return; + } + } + + mOpen = (mult0 == mult1 && mult0 == mDegree + 1); + + mKnots.resize(mNumControls + mDegree + 1); + mKeys.resize(input.numUniqueKnots); + int sum = 0; + for (int i = 0, j = 0; i < input.numUniqueKnots; ++i) + { + Real tCommon = mUniqueKnots[i].t; + int mult = mUniqueKnots[i].multiplicity; + for (int k = 0; k < mult; ++k, ++j) + { + mKnots[j] = tCommon; + } + + mKeys[i].first = tCommon; + mKeys[i].second = sum - 1; + sum += mult; + } + + mTMin = mKnots[mDegree]; + mTMax = mKnots[mNumControls]; + mTLength = mTMax - mTMin; + + size_t numRows = mDegree + 1; + size_t numCols = mNumControls + mDegree; + size_t numBytes = numRows * numCols * sizeof(Real); + for (int i = 0; i < 4; ++i) + { + mValue[i] = Array2(numCols, numRows); + memset(mValue[i][0], 0, numBytes); + } + + mConstructed = true; +} + +template inline +BasisFunction::operator bool() const +{ + return mConstructed; +} + +template inline +int BasisFunction::GetNumControls() const +{ + return mNumControls; +} + +template inline +int BasisFunction::GetDegree() const +{ + return mDegree; +} + +template inline +int BasisFunction::GetNumUniqueKnots() const +{ + return static_cast(mUniqueKnots.size()); +} + +template inline +int BasisFunction::GetNumKnots() const +{ + return static_cast(mKnots.size()); +} + +template inline +Real BasisFunction::GetMinDomain() const +{ + return mTMin; +} + +template inline +Real BasisFunction::GetMaxDomain() const +{ + return mTMax; +} + +template inline +bool BasisFunction::IsOpen() const +{ + return mOpen; +} + +template inline +bool BasisFunction::IsUniform() const +{ + return mUniform; +} + +template inline +bool BasisFunction::IsPeriodic() const +{ + return mPeriodic; +} + +template inline +UniqueKnot const* BasisFunction::GetUniqueKnots() const +{ + return &mUniqueKnots[0]; +} + +template inline +Real const* BasisFunction::GetKnots() const +{ + return &mKnots[0]; +} + +template +void BasisFunction::Evaluate(Real t, unsigned int order, int& minIndex, + int& maxIndex) const +{ + if (!mConstructed) + { + // Errors were already generated during construction. Return an index + // range that leads to zero-valued positions and derivatives. + minIndex = -1; + maxIndex = -1; + return; + } + + if (order > 3) + { + LogError("Only derivatives through order 3 are supported."); + minIndex = 0; + maxIndex = 0; + return; + } + + int i = GetIndex(t); + mValue[0][0][i] = (Real)1; + + if (order >= 1) + { + mValue[1][0][i] = (Real)0; + if (order >= 2) + { + mValue[2][0][i] = (Real)0; + if (order >= 3) + { + mValue[3][0][i] = (Real)0; + } + } + } + + Real n0 = t - mKnots[i], n1 = mKnots[i + 1] - t; + Real e0, e1, d0, d1, invD0, invD1; + int j; + for (j = 1; j <= mDegree; j++) + { + d0 = mKnots[i + j] - mKnots[i]; + d1 = mKnots[i + 1] - mKnots[i - j + 1]; + invD0 = (d0 > (Real)0 ? (Real)1 / d0 : (Real)0); + invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0); + + e0 = n0*mValue[0][j - 1][i]; + mValue[0][j][i] = e0*invD0; + e1 = n1*mValue[0][j - 1][i - j + 1]; + mValue[0][j][i - j] = e1*invD1; + + if (order >= 1) + { + e0 = n0 * mValue[1][j - 1][i] + mValue[0][j - 1][i]; + mValue[1][j][i] = e0 * invD0; + e1 = n1 * mValue[1][j - 1][i - j + 1] - mValue[0][j - 1][i - j + 1]; + mValue[1][j][i - j] = e1 * invD1; + + if (order >= 2) + { + e0 = n0 * mValue[2][j - 1][i] + ((Real)2) * mValue[1][j - 1][i]; + mValue[2][j][i] = e0 * invD0; + e1 = n1 * mValue[2][j - 1][i - j + 1] - ((Real)2) * mValue[1][j - 1][i - j + 1]; + mValue[2][j][i - j] = e1 * invD1; + + if (order >= 3) + { + e0 = n0 * mValue[3][j - 1][i] + ((Real)3) * mValue[2][j - 1][i]; + mValue[3][j][i] = e0 * invD0; + e1 = n1 * mValue[3][j - 1][i - j + 1] - ((Real)3) * mValue[2][j - 1][i - j + 1]; + mValue[3][j][i - j] = e1 * invD1; + } + } + } + } + + for (j = 2; j <= mDegree; ++j) + { + for (int k = i - j + 1; k < i; ++k) + { + n0 = t - mKnots[k]; + n1 = mKnots[k + j + 1] - t; + d0 = mKnots[k + j] - mKnots[k]; + d1 = mKnots[k + j + 1] - mKnots[k + 1]; + invD0 = (d0 >(Real)0 ? (Real)1 / d0 : (Real)0); + invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0); + + e0 = n0*mValue[0][j - 1][k]; + e1 = n1*mValue[0][j - 1][k + 1]; + mValue[0][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 1) + { + e0 = n0 * mValue[1][j - 1][k] + mValue[0][j - 1][k]; + e1 = n1 * mValue[1][j - 1][k + 1] - mValue[0][j - 1][k + 1]; + mValue[1][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 2) + { + e0 = n0 * mValue[2][j - 1][k] + ((Real)2) * mValue[1][j - 1][k]; + e1 = n1 * mValue[2][j - 1][k + 1] - ((Real)2) * mValue[1][j - 1][k + 1]; + mValue[2][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 3) + { + e0 = n0 * mValue[3][j - 1][k] + ((Real)3) * mValue[2][j - 1][k]; + e1 = n1 * mValue[3][j - 1][k + 1] - ((Real)3) * mValue[2][j - 1][k + 1]; + mValue[3][j][k] = e0 * invD0 + e1 * invD1; + } + } + } + } + } + + minIndex = i - mDegree; + maxIndex = i; +} + +template +Real BasisFunction::GetValue(unsigned int order, int i) const +{ + if (!mConstructed) + { + // Errors were already generated during construction. Return a value + // that leads to zero-valued positions and derivatives. + return (Real)0; + } + + if (order < 4) + { + if (0 <= i && i < mNumControls + mDegree) + { + return mValue[order][mDegree][i]; + } + } + + LogError("Invalid input."); + return (Real)0; +} + +template +int BasisFunction::GetIndex(Real& t) const +{ + // Find the index i for which knot[i] <= t < knot[i+1]. + if (mPeriodic) + { + // Wrap to [tmin,tmax]. + Real r = std::fmod(t - mTMin, mTLength); + if (r < (Real)0) + { + r += mTLength; + } + t = mTMin + r; + } + + // Clamp to [tmin,tmax]. For the periodic case, this handles small + // numerical rounding errors near the domain endpoints. + if (t <= mTMin) + { + t = mTMin; + return mDegree; + } + if (t >= mTMax) + { + t = mTMax; + return mNumControls - 1; + } + + // At this point, tmin < t < tmax. + for (auto const& key : mKeys) + { + if (t < key.first) + { + return key.second; + } + } + + // We should not reach this code. + LogError("Unexpected condition."); + t = mTMin; + return mDegree; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBezierCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBezierCurve.h new file mode 100644 index 000000000000..36fb9687d2aa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBezierCurve.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class BezierCurve : public ParametricCurve +{ +public: + // Construction and destruction. The number of control points must be + // degree + 1. This object copies the input array. The domain is t + // in [0,1]. To validate construction, create an object as shown: + // BezierCurve curve(parameters); + // if (!curve) { ; } + virtual ~BezierCurve(); + BezierCurve(int degree, Vector const* controls); + + // Member access. + inline int GetDegree() const; + inline int GetNumControls() const; + inline Vector const* GetControls() const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +protected: + // Support for Evaluate(...). + Vector Compute(Real t, Real omt, int order) const; + + int mDegree, mNumControls; + std::vector> mControls[4]; + Array2 mChoose; +}; + + +template +BezierCurve::~BezierCurve() +{ +} + +template +BezierCurve::BezierCurve(int degree, Vector const* controls) + : + ParametricCurve((Real)0, (Real)1), + mDegree(degree), + mNumControls(degree + 1), + mChoose(mNumControls, mNumControls) +{ + if (degree < 2 || !controls) + { + LogError("Invalid input."); + return; + } + + // Copy the controls. + mControls[0].resize(mNumControls); + std::copy(controls, controls + mNumControls, mControls[0].begin()); + + // Compute first-order differences. + mControls[1].resize(mNumControls - 1); + for (int i = 0; i < mNumControls - 1; ++i) + { + mControls[1][i] = mControls[0][i + 1] - mControls[0][i]; + } + + // Compute second-order differences. + mControls[2].resize(mNumControls - 2); + for (int i = 0; i < mNumControls - 2; ++i) + { + mControls[2][i] = mControls[1][i + 1] - mControls[1][i]; + } + + // Compute third-order differences. + if (degree >= 3) + { + mControls[3].resize(mNumControls - 3); + for (int i = 0; i < mNumControls - 3; ++i) + { + mControls[3][i] = mControls[2][i + 1] - mControls[2][i]; + } + } + + // Compute combinatorial values Choose(n,k) and store in mChoose[n][k]. + // The values mChoose[r][c] are invalid for r < c; that is, we use only + // the entries for r >= c. + mChoose[0][0] = (Real)1; + mChoose[1][0] = (Real)1; + mChoose[1][1] = (Real)1; + for (int i = 2; i <= mDegree; ++i) + { + mChoose[i][0] = (Real)1; + mChoose[i][i] = (Real)1; + for (int j = 1; j < i; ++j) + { + mChoose[i][j] = mChoose[i - 1][j - 1] + mChoose[i - 1][j]; + } + } + + this->mConstructed = true; +} + +template inline +int BezierCurve::GetDegree() const +{ + return mDegree; +} + +template inline +int BezierCurve::GetNumControls() const +{ + return mNumControls; +} + +template inline +Vector const* BezierCurve::GetControls() const +{ + return &mControls[0][0]; +} + +template +void BezierCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + // Compute position. + Real omt = (Real)1 - t; + values[0] = Compute(t, omt, 0); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = Compute(t, omt, 1); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = Compute(t, omt, 2); + if (maxOrder >= 3 && mDegree >= 3) + { + // Compute third derivative. + values[3] = Compute(t, omt, 3); + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +Vector BezierCurve::Compute(Real t, Real omt, int order) +const +{ + Vector result = omt * mControls[order][0]; + + Real tpow = t; + int isup = mDegree - order; + for (int i = 1; i < isup; ++i) + { + Real c = mChoose[isup][i] * tpow; + result = (result + c * mControls[order][i]) * omt; + tpow *= t; + } + result = (result + tpow * mControls[order][isup]); + + int multiplier = 1; + for (int i = 0; i < order; ++i) + { + multiplier *= mDegree - i; + } + result *= (Real)multiplier; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.cpp new file mode 100644 index 000000000000..1960fe6dde93 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.cpp @@ -0,0 +1,191 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2017/10/22) + +#include +#include + +namespace gte +{ + +static int32_t const gsLeadingBitTable[32] = +{ + 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31 +}; + +static int32_t const gsTrailingBitTable[32] = +{ + 0, 1, 28, 2, 29, 14, 24, 3, + 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, + 26, 12, 18, 6, 11, 5, 10, 9 +}; + + +bool IsPowerOfTwo(uint32_t value) +{ + return (value > 0) && ((value & (value - 1)) == 0); +} + +bool IsPowerOfTwo(int32_t value) +{ + return (value > 0) && ((value & (value - 1)) == 0); +} + +uint32_t Log2OfPowerOfTwo(uint32_t powerOfTwo) +{ + uint32_t log2 = (powerOfTwo & 0xAAAAAAAAu) != 0; + log2 |= ((powerOfTwo & 0xFFFF0000u) != 0) << 4; + log2 |= ((powerOfTwo & 0xFF00FF00u) != 0) << 3; + log2 |= ((powerOfTwo & 0xF0F0F0F0u) != 0) << 2; + log2 |= ((powerOfTwo & 0xCCCCCCCCu) != 0) << 1; + return log2; +} + +int32_t Log2OfPowerOfTwo(int32_t powerOfTwo) +{ + uint32_t log2 = (powerOfTwo & 0xAAAAAAAAu) != 0; + log2 |= ((powerOfTwo & 0xFFFF0000u) != 0) << 4; + log2 |= ((powerOfTwo & 0xFF00FF00u) != 0) << 3; + log2 |= ((powerOfTwo & 0xF0F0F0F0u) != 0) << 2; + log2 |= ((powerOfTwo & 0xCCCCCCCCu) != 0) << 1; + return static_cast(log2); +} + +int32_t GetLeadingBit(uint32_t value) +{ + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + uint32_t key = (value * 0x07C4ACDDu) >> 27; + return gsLeadingBitTable[key]; +} + +int32_t GetLeadingBit(int32_t value) +{ + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + uint32_t key = (value * 0x07C4ACDDu) >> 27; + return gsLeadingBitTable[key]; +} + +int32_t GetLeadingBit(uint64_t value) +{ + uint32_t v1 = (uint32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetLeadingBit(v1) + 32; + } + + uint32_t v0 = (uint32_t)(value & 0x00000000FFFFFFFFull); + return GetLeadingBit(v0); +} + +int32_t GetLeadingBit(int64_t value) +{ + int32_t v1 = (int32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetLeadingBit(v1) + 32; + } + + int32_t v0 = (int32_t)(value & 0x00000000FFFFFFFFull); + return GetLeadingBit(v0); +} + +int32_t GetTrailingBit(uint32_t value) +{ + // Avoid warning for negation of unsigned 'value'. + int32_t const& iValue = reinterpret_cast(value); + uint32_t key = ((uint32_t)((iValue & -iValue) * 0x077CB531u)) >> 27; + return gsTrailingBitTable[key]; +} + +int32_t GetTrailingBit(int32_t value) +{ + uint32_t key = ((uint32_t)((value & -value) * 0x077CB531u)) >> 27; + return gsTrailingBitTable[key]; +} + +int32_t GetTrailingBit(uint64_t value) +{ + uint32_t v0 = (uint32_t)(value & 0x00000000FFFFFFFFull); + if (v0 != 0) + { + return GetTrailingBit(v0); + } + + uint32_t v1 = (uint32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetTrailingBit(v1) + 32; + } + return 0; +} + +int32_t GetTrailingBit(int64_t value) +{ + int32_t v0 = (int32_t)(value & 0x00000000FFFFFFFFull); + if (v0 != 0) + { + return GetTrailingBit(v0); + } + + int32_t v1 = (int32_t)((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetTrailingBit(v1) + 32; + } + return 0; +} + +uint64_t RoundUpToPowerOfTwo(uint32_t value) +{ + if (value > 0) + { + int32_t leading = GetLeadingBit(value); + uint32_t mask = (1 << leading); + if ((value & ~mask) == 0) + { + // value is a power of two + return static_cast(value); + } + else + { + // round up to a power of two + return (static_cast(mask) << 1); + } + + } + else + { + return 1ull; + } +} + +uint32_t RoundDownToPowerOfTwo(uint32_t value) +{ + if (value > 0) + { + int32_t leading = GetLeadingBit(value); + uint32_t mask = (1 << leading); + return mask; + } + else + { + return 0; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.h new file mode 100644 index 000000000000..fc2421f21e74 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteBitHacks.h @@ -0,0 +1,40 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#pragma once + +#include +#include + +namespace gte +{ + +GTE_IMPEXP bool IsPowerOfTwo(uint32_t value); +GTE_IMPEXP bool IsPowerOfTwo(int32_t value); + +GTE_IMPEXP uint32_t Log2OfPowerOfTwo(uint32_t powerOfTwo); +GTE_IMPEXP int32_t Log2OfPowerOfTwo(int32_t powerOfTwo); + +// Call these only for nonzero values. If value is zero, then GetLeadingBit +// and GetTrailingBit return zero. +GTE_IMPEXP int32_t GetLeadingBit(uint32_t value); +GTE_IMPEXP int32_t GetLeadingBit(int32_t value); +GTE_IMPEXP int32_t GetLeadingBit(uint64_t value); +GTE_IMPEXP int32_t GetLeadingBit(int64_t value); +GTE_IMPEXP int32_t GetTrailingBit(uint32_t value); +GTE_IMPEXP int32_t GetTrailingBit(int32_t value); +GTE_IMPEXP int32_t GetTrailingBit(uint64_t value); +GTE_IMPEXP int32_t GetTrailingBit(int64_t value); + +// Round up to a power of two. If input is zero, the return is 1. If input +// is larger than 2^{31}, the return is 2^{32}. +GTE_IMPEXP uint64_t RoundUpToPowerOfTwo(uint32_t value); + +// Round down to a power of two. If input is zero, the return is 0. +GTE_IMPEXP uint32_t RoundDownToPowerOfTwo(uint32_t value); + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCapsule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCapsule.h new file mode 100644 index 000000000000..17e6304dd01b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCapsule.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// A capsule is the set of points that are equidistant from a segment, the +// common distance called the radius. + +template +class Capsule +{ +public: + // Construction and destruction. The default constructor sets the segment + // to have endpoints p0 = (-1,0,...,0) and p1 = (1,0,...,0), and the + // radius is 1. + Capsule(); + Capsule(Segment const& inSegment, Real inRadius); + + // Public member access. + Segment segment; + Real radius; + +public: + // Comparisons to support sorted containers. + bool operator==(Capsule const& capsule) const; + bool operator!=(Capsule const& capsule) const; + bool operator< (Capsule const& capsule) const; + bool operator<=(Capsule const& capsule) const; + bool operator> (Capsule const& capsule) const; + bool operator>=(Capsule const& capsule) const; +}; + +// Template alias for convenience. +template +using Capsule3 = Capsule<3, Real>; + + +template +Capsule::Capsule() + : + radius((Real)1) +{ +} + +template +Capsule::Capsule(Segment const& inSegment, Real inRadius) + : + segment(inSegment), + radius(inRadius) +{ +} + +template +bool Capsule::operator==(Capsule const& capsule) const +{ + return segment == capsule.segment && radius == capsule.radius; +} + +template +bool Capsule::operator!=(Capsule const& capsule) const +{ + return !operator==(capsule); +} + +template +bool Capsule::operator<(Capsule const& capsule) const +{ + if (segment < capsule.segment) + { + return true; + } + + if (segment > capsule.segment) + { + return false; + } + + return radius < capsule.radius; +} + +template +bool Capsule::operator<=(Capsule const& capsule) const +{ + return operator<(capsule) || operator==(capsule); +} + +template +bool Capsule::operator>(Capsule const& capsule) const +{ + return !operator<=(capsule); +} + +template +bool Capsule::operator>=(Capsule const& capsule) const +{ + return !operator<(capsule); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteChebyshevRatio.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteChebyshevRatio.h new file mode 100644 index 000000000000..44492055aaa8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteChebyshevRatio.h @@ -0,0 +1,192 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +// Let f(t,A) = sin(t*A)/sin(A). The slerp of quaternions q0 and q1 is +// slerp(t,q0,q1) = f(1-t,A)*q0 + f(t,A)*q1. +// Let y = 1-cos(A); we allow A in [0,pi], so y in [0,1]. As a function of y, +// a series representation for f(t,y) is +// f(t,y) = sum_{i=0}^{infinity} c_{i}(t) y^{i} +// where c_0(t) = t, c_{i}(t) = c_{i-1}(t)*(i^2 - t^2)/(i*(2*i+1)) for i >= 1. +// The c_{i}(t) are polynomials in t of degree 2*i+1. The paper +// +// A Fast and Accurate Algorithm for Computing SLERP, +// David Eberly, +// Journal of Graphics, GPU, and Game Tools, 15 : 3, 161 - 176, 2011 +// http://www.tandfonline.com/doi/abs/10.1080/2151237X.2011.610255#.VHRB7ouUd8E +// +// derives an approximation +// g(t,y) = sum_{i=0}^{n-1} c_{i}(t) y^{i} + (1+u_n) c_{n}(t) y^n +// which has degree 2*n+1 in t and degree n in y. The u_n were chosen to +// balance the error at y = 1 (A = pi/2). Unfortunately, the analysis for the +// global error bounds (0 <= y <= 1) is flawed; the error bounds printed in +// the paper are too small by about one order of magnitude (factor of 10). +// Instead they are the following, verified by Mathematica using larger +// precision than 'float' or 'double': +// +// n | error | n | error | n | error | n | error +// --+------------+----+------------+----+------------+----+------------- +// 1 | 2.60602e-2 | 5 | 3.63188e-4 | 9 | 1.12223e-5 | 13 | 4.37180e-7 +// 2 | 7.43321e-3 | 6 | 1.47056e-4 | 10 | 4.91138e-6 | 14 | 1.98230e-7 +// 3 | 2.51798e-3 | 7 | 6.11808e-5 | 11 | 2.17345e-6 | 15 | 9.04302e-8 +// 4 | 9.30819e-4 | 8 | 2.59880e-5 | 12 | 9.70876e-7 | 16 | 4.03665e-8 +// +// The maximum errors all occur for an angle that is nearly pi/2. The +// approximation is much more accurate when for angles A in [0,pi/4]. With +// this restriction, the global error bounds are +// +// n | error | n | error | n | error | n | error +// --+------------+----+------------+----+-------------+----+------------- +// 1 | 1.90648e-2 | 5 | 5.83197e-6 | 9 | 6.80465e-10 | 13 | 3.39728e-13 +// 2 | 2.43581e-3 | 6 | 8.00278e-6 | 10 | 9.12477e-11 | 14 | 4.70735e-14 +// 3 | 3.20235e-4 | 7 | 1.10649e-7 | 11 | 4.24608e-11 | 15 | 1.55431e-15 +// 4 | 4.29242e-5 | 8 | 1.53828e-7 | 12 | 5.93392e-12 | 16 | 1.11022e-16 +// +// Given q0 and q1 such that cos(A) = dot(q0,q1) in [0,1], in which case +// A in [0,pi/2], let qh = (q0+q1)/|q0 + q1| = slerp(1/2,q0,q1). Note that +// |q0 + q1| = 2*cos(A/2) because +// sin(A/2)/sin(A) = sin(A/2)/(2*sin(A/2)*cos(A/2)) = 1/(2*cos(A/2)) +// The angle between q0 and qh is the same as the angle between qh and q1, +// namely, A/2 in [0,pi/4]. It may be shown that +// +-- +// slerp(t,q0,q1) = | slerp(2*t,q0,qh), 0 <= t <= 1/2 +// | slerp(2*t-1,qh,q1), 1/2 <= t <= 1 +// +-- +// The slerp functions on the right-hand side involve angles in [0,pi/4], so +// the approximation is more accurate for those evaluations than using the +// original. + +namespace gte +{ + +template +class ChebyshevRatio +{ +public: + // Compute f(t,A) = sin(t*A)/sin(A), where t in [0,1], A in [0,pi/2], + // cosA = cos(A), f0 = f(1-t,A), and f1 = f(t,A). + inline static void Get(Real t, Real cosA, Real& f0, Real& f1); + + // Compute estimates for f(t,y) = sin(t*A)/sin(A), where t in [0,1], + // A in [0,pi/2], y = 1 - cos(A), f0 is the estimate for f(1-t,y), and + // f1 is the estimate for f(t,y). The approximating function is a + // polynomial of two variables. The template parameter N must be in + // {1..16}. The degree in t is 2*N+1 and the degree in Y is N. + template + inline static void GetEstimate(Real t, Real y, Real& f0, Real& f1); +}; + + +template inline +void ChebyshevRatio::Get(Real t, Real cosA, Real& f0, Real& f1) +{ + if (cosA < (Real)1) + { + // The angle A is in (0,pi/2]. + Real A = std::acos(cosA); + Real invSinA = ((Real)1) / std::sin(A); + f0 = std::sin(((Real)1 - t) * A) * invSinA; + f1 = std::sin(t * A) * invSinA; + } + else + { + // The angle theta is 0. + f0 = (Real)1 - t; + f1 = (Real)t; + } +} + +template +template inline +void ChebyshevRatio::GetEstimate(Real t, Real y, Real& f0, Real& f1) +{ + static_assert(1 <= N && N <= 16, "Invalid degree."); + + // The ASM output of the MSVS 2013 Release build shows that the constants + // in these arrays are loaded to XMM registers as literal values, and only + // those constants required for the specified degree D are loaded. That + // is, the compiler does a good job of optimizing the code. + + Real const onePlusMu[16] = + { + (Real)1.62943436108234530, + (Real)1.73965850021313961, + (Real)1.79701067629566813, + (Real)1.83291820510335812, + (Real)1.85772477879039977, + (Real)1.87596835698904785, + (Real)1.88998444919711206, + (Real)1.90110745351730037, + (Real)1.91015881189952352, + (Real)1.91767344933047190, + (Real)1.92401541194159076, + (Real)1.92944142668012797, + (Real)1.93413793373091059, + (Real)1.93824371262559758, + (Real)1.94186426368404708, + (Real)1.94508125972497303 + }; + + Real const a[16] = + { + (N != 1 ? (Real)1 : onePlusMu[0]) / ((Real)1 * (Real)3), + (N != 2 ? (Real)1 : onePlusMu[1]) / ((Real)2 * (Real)5), + (N != 3 ? (Real)1 : onePlusMu[2]) / ((Real)3 * (Real)7), + (N != 4 ? (Real)1 : onePlusMu[3]) / ((Real)4 * (Real)9), + (N != 5 ? (Real)1 : onePlusMu[4]) / ((Real)5 * (Real)11), + (N != 6 ? (Real)1 : onePlusMu[5]) / ((Real)6 * (Real)13), + (N != 7 ? (Real)1 : onePlusMu[6]) / ((Real)7 * (Real)15), + (N != 8 ? (Real)1 : onePlusMu[7]) / ((Real)8 * (Real)17), + (N != 9 ? (Real)1 : onePlusMu[8]) / ((Real)9 * (Real)19), + (N != 10 ? (Real)1 : onePlusMu[9]) / ((Real)10 * (Real)21), + (N != 11 ? (Real)1 : onePlusMu[10]) / ((Real)11 * (Real)23), + (N != 12 ? (Real)1 : onePlusMu[11]) / ((Real)12 * (Real)25), + (N != 13 ? (Real)1 : onePlusMu[12]) / ((Real)13 * (Real)27), + (N != 14 ? (Real)1 : onePlusMu[13]) / ((Real)14 * (Real)29), + (N != 15 ? (Real)1 : onePlusMu[14]) / ((Real)15 * (Real)31), + (N != 16 ? (Real)1 : onePlusMu[15]) / ((Real)16 * (Real)33) + }; + + Real const b[16] = + { + (N != 1 ? (Real)1 : onePlusMu[0]) * (Real)1 / (Real)3, + (N != 2 ? (Real)1 : onePlusMu[1]) * (Real)2 / (Real)5, + (N != 3 ? (Real)1 : onePlusMu[2]) * (Real)3 / (Real)7, + (N != 4 ? (Real)1 : onePlusMu[3]) * (Real)4 / (Real)9, + (N != 5 ? (Real)1 : onePlusMu[4]) * (Real)5 / (Real)11, + (N != 6 ? (Real)1 : onePlusMu[5]) * (Real)6 / (Real)13, + (N != 7 ? (Real)1 : onePlusMu[6]) * (Real)7 / (Real)15, + (N != 8 ? (Real)1 : onePlusMu[7]) * (Real)8 / (Real)17, + (N != 9 ? (Real)1 : onePlusMu[8]) * (Real)9 / (Real)19, + (N != 10 ? (Real)1 : onePlusMu[9]) * (Real)10 / (Real)21, + (N != 11 ? (Real)1 : onePlusMu[10]) * (Real)11 / (Real)23, + (N != 12 ? (Real)1 : onePlusMu[11]) * (Real)12 / (Real)25, + (N != 13 ? (Real)1 : onePlusMu[12]) * (Real)13 / (Real)27, + (N != 14 ? (Real)1 : onePlusMu[13]) * (Real)14 / (Real)29, + (N != 15 ? (Real)1 : onePlusMu[14]) * (Real)15 / (Real)31, + (N != 16 ? (Real)1 : onePlusMu[15]) * (Real)16 / (Real)33 + }; + + Real term0 = (Real)1 - t, term1 = t; + Real sqr0 = term0 * term0, sqr1 = term1 * term1; + f0 = term0; + f1 = term1; + for (int i = 0; i < N; ++i) + { + term0 *= (b[i] - a[i] * sqr0) * y; + term1 *= (b[i] - a[i] * sqr1) * y; + f0 += term0; + f1 += term1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCholeskyDecomposition.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCholeskyDecomposition.h new file mode 100644 index 000000000000..64be7cd44d86 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCholeskyDecomposition.h @@ -0,0 +1,560 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + // Implementation for size known at compile time. + template + class CholeskyDecomposition + { + public: + // Ensure that N > 0 at compile time. + CholeskyDecomposition() + { + static_assert(N > 0, "Invalid size in CholeskyDecomposition constructor."); + } + + // Disallow copies and moves. + CholeskyDecomposition(CholeskyDecomposition const&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete; + CholeskyDecomposition(CholeskyDecomposition&&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete; + + // On input, A is symmetric. Only the lower-triangular portion is + // modified. On output, the lower-triangular portion is L where + // A = L * L^T. + bool Factor(Matrix& A) + { + for (int c = 0; c < N; ++c) + { + if (A(c, c) <= (Real)0) + { + return false; + } + A(c, c) = std::sqrt(A(c, c)); + + for (int r = c + 1; r < N; ++r) + { + A(r, c) /= A(c, c); + } + + for (int k = c + 1; k < N; ++k) + { + for (int r = k; r < N; ++r) + { + A(r, k) -= A(r, c) * A(k, c); + } + } + } + return true; + } + + // Solve L*Y = B, where L is lower triangular and invertible. The + // input value of Y is B. On output, Y is the solution. + void SolveLower(Matrix const& L, Vector& Y) + { + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + Y[r] -= L(r, c) * Y[c]; + } + Y[r] /= L(r, r); + } + } + + // Solve L^T*X = Y, where L is lower triangular (L^T is upper + // triangular) and invertible. The input value of X is Y. On + // output, X is the solution. + void SolveUpper(Matrix const& L, Vector& X) + { + for (int r = N - 1; r >= 0; --r) + { + for (int c = r + 1; c < N; ++c) + { + X[r] -= L(c, r) * X[c]; + } + X[r] /= L(r, r); + } + } + }; + + // Implementation for size known only at run time. + template + class CholeskyDecomposition + { + public: + int const N; + + // Ensure that N > 0 at run time. + CholeskyDecomposition(int n) + : + N(n) + { + } + + // Disallow copies and moves. This is required to avoid compiler + // complaints about the 'int const N' member. + CholeskyDecomposition(CholeskyDecomposition const&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete; + CholeskyDecomposition(CholeskyDecomposition&&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete; + + // On input, A is symmetric. Only the lower-triangular portion is + // modified. On output, the lower-triangular portion is L where + // A = L * L^T. + bool Factor(GMatrix& A) + { + if (A.GetNumRows() == N && A.GetNumCols() == N) + { + for (int c = 0; c < N; ++c) + { + if (A(c, c) <= (Real)0) + { + return false; + } + A(c, c) = std::sqrt(A(c, c)); + + for (int r = c + 1; r < N; ++r) + { + A(r, c) /= A(c, c); + } + + for (int k = c + 1; k < N; ++k) + { + for (int r = k; r < N; ++r) + { + A(r, k) -= A(r, c) * A(k, c); + } + } + } + return true; + } + else + { + LogError("Matrix must be square in CholeskyDecomposition::Factor."); + return false; + } + } + + // Solve L*Y = B, where L is lower triangular and invertible. The + // input value of Y is B. On output, Y is the solution. + void SolveLower(GMatrix const& L, GVector& Y) + { + if (L.GetNumRows() == N && L.GetNumCols() == N && Y.GetSize() == N) + { + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + Y[r] -= L(r, c) * Y[c]; + } + Y[r] /= L(r, r); + } + } + else + { + LogError("Invalid size in CholeskyDecomposition::SolveLower."); + Y.MakeZero(); + } + } + + // Solve L^T*X = Y, where L is lower triangular (L^T is upper + // triangular) and invertible. The input value of X is Y. On + // output, X is the solution. + void SolveUpper(GMatrix const& L, GVector& X) + { + if (L.GetNumRows() == N && L.GetNumCols() == N && X.GetSize() == N) + { + for (int r = N - 1; r >= 0; --r) + { + for (int c = r + 1; c < N; ++c) + { + X[r] -= L(c, r) * X[c]; + } + X[r] /= L(r, r); + } + } + else + { + LogError("Invalid size in CholeskyDecomposition::SolveLower."); + X.MakeZero(); + } + } + }; + + + // Implementation for sizes known at compile time. + template + class BlockCholeskyDecomposition + { + public: + // Let B represent the block size and N represent the number of + // blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an + // N-by-N matrix of blocks, each block of size B-by-B. The value + // N*B is NumDimensions. + enum + { + NumDimensions = NumBlocks * BlockSize + }; + + typedef std::array, NumBlocks> BlockVector; + typedef std::array, NumBlocks>, NumBlocks> BlockMatrix; + + // Ensure that BlockSize > 0 and NumBlocks > 0 at compile time. + BlockCholeskyDecomposition() + { + static_assert(BlockSize > 0 && NumBlocks > 0, "Invalid size in BlockCholeskyDecomposition constructor."); + } + + // Disallow copies and moves. + BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete; + + // Treating the matrix as a 2D table of scalars with NUM_DIMENSIONS + // rows and NUM_DIMENSIONS columns, look up the correct block that + // stores the requested element and return a reference. + Real Get(BlockMatrix const& M, int row, int col) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto const& block = M[b1][b0]; + return block(i1, i0); + } + + void Set(BlockMatrix& M, int row, int col, Real value) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto& block = M[b1][b0]; + block(i1, i0) = value; + } + + bool Factor(BlockMatrix& A) + { + for (int c = 0; c < NumBlocks; ++c) + { + if (!mDecomposer.Factor(A[c][c])) + { + return false; + } + + for (int r = c + 1; r < NumBlocks; ++r) + { + LowerTriangularSolver(r, c, A); + } + + for (int k = c + 1; k < NumBlocks; ++k) + { + for (int r = k; r < NumBlocks; ++r) + { + SubtractiveUpdate(r, k, c, A); + } + } + } + return true; + } + + // Solve L*Y = B, where L is an invertible lower-triangular block + // matrix whose diagonal blocks are lower-triangular matrices. + // The input B is a block vector of commensurate size. The input + // value of Y is B. On output, Y is the solution. + void SolveLower(BlockMatrix const& L, BlockVector& Y) + { + for (int r = 0; r < NumBlocks; ++r) + { + auto& Yr = Y[r]; + for (int c = 0; c < r; ++c) + { + auto const& Lrc = L[r][c]; + auto const& Yc = Y[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Yr[i] -= Lrc(i, j) * Yc[j]; + } + } + } + mDecomposer.SolveLower(L[r][r], Yr); + } + } + + // Solve L^T*X = Y, where L is an invertible lower-triangular block + // matrix (L^T is an upper-triangular block matrix) whose diagonal + // blocks are lower-triangular matrices. The input value of X is Y. + // On output, X is the solution. + void SolveUpper(BlockMatrix const& L, BlockVector& X) + { + for (int r = NumBlocks - 1; r >= 0; --r) + { + auto& Xr = X[r]; + for (int c = r + 1; c < NumBlocks; ++c) + { + auto const& Lcr = L[c][r]; + auto const& Xc = X[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Xr[i] -= Lcr(j, i) * Xc[j]; + } + } + } + mDecomposer.SolveUpper(L[r][r], Xr); + } + } + + private: + // Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices + // G(c,c) and A(r,c) are known quantities, and G(c,c) occupies + // the lower triangular portion of A(c,c). The solver stores + // its results in-place, so A(r,c) stores the G(r,c) result. + void LowerTriangularSolver(int r, int c, BlockMatrix& A) + { + auto const& Acc = A[c][c]; + auto& Arc = A[r][c]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < j; ++i) + { + Real Lji = Acc(j, i); + for (int k = 0; k < BlockSize; ++k) + { + Arc(k, j) -= Lji * Arc(k, i); + } + } + + Real Ljj = Acc(j, j); + for (int k = 0; k < BlockSize; ++k) + { + Arc(k, j) /= Ljj; + } + } + } + + void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A) + { + auto const& Arc = A[r][c]; + auto const& Akc = A[k][c]; + auto& Ark = A[r][k]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < BlockSize; ++i) + { + for (int m = 0; m < BlockSize; ++m) + { + Ark(j, i) -= Arc(j, m) * Akc(i, m); + } + } + } + } + + CholeskyDecomposition mDecomposer; + }; + + // Implementation for sizes known only at run time. + template + class BlockCholeskyDecomposition + { + public: + // Let B represent the block size and N represent the number of + // blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an + // N-by-N matrix of blocks, each block of size B-by-B. The value + // N*B is NumDimensions. + int const BlockSize; + int const NumBlocks; + int const NumDimensions; + + // The number of elements in a BlockVector object must be NumBlocks + // and each GVector element has BlockSize components. + typedef std::vector> BlockVector; + + // The BlockMatrix is an array of NumBlocks-by-NumBlocks matrices. + // Each block matrix is stored in row-major order. The BlockMatrix + // elements themselves are stored in row-major order. The block + // matrix element M = BlockMatrix[col + NumBlocks * row] is of size + // BlockSize-by-BlockSize (in row-major order) and is in the (row,col) + // location of the full matrix of blocks. + typedef std::vector> BlockMatrix; + + // Ensure that BlockSize > 0 and NumDimensions > 0 at run time. + BlockCholeskyDecomposition(int blockSize, int numBlocks) + : + BlockSize(blockSize), + NumBlocks(numBlocks), + NumDimensions(numBlocks * blockSize), + mDecomposer(blockSize) + { + LogAssert(blockSize > 0 && numBlocks > 0, "Invalid size in BlockCholeskyDecomposition constructor."); + } + + // Disallow copies and moves. This is required to avoid compiler + // complaints about the 'int const' members. + BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete; + + // Treating the matrix as a 2D table of scalars with NumDimensions + // rows and NumDimensions columns, look up the correct block that + // stores the requested element and return a reference. + Real Get(BlockMatrix const& M, int row, int col) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto const& block = M[GetIndex(b1, b0)]; + return block(i1, i0); + } + + void Set(BlockMatrix& M, int row, int col, Real value) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto& block = M[GetIndex(b1, b0)]; + block(i1, i0) = value; + } + + bool Factor(BlockMatrix& A) + { + for (int c = 0; c < NumBlocks; ++c) + { + if (!mDecomposer.Factor(A[GetIndex(c, c)])) + { + return false; + } + + for (int r = c + 1; r < NumBlocks; ++r) + { + LowerTriangularSolver(r, c, A); + } + + for (int k = c + 1; k < NumBlocks; ++k) + { + for (int r = k; r < NumBlocks; ++r) + { + SubtractiveUpdate(r, k, c, A); + } + } + } + return true; + } + + // Solve L*Y = B, where L is an invertible lower-triangular block + // matrix whose diagonal blocks are lower-triangular matrices. + // The input B is a block vector of commensurate size. The input + // value of Y is B. On output, Y is the solution. + void SolveLower(BlockMatrix const& L, BlockVector& Y) + { + for (int r = 0; r < NumBlocks; ++r) + { + auto& Yr = Y[r]; + for (int c = 0; c < r; ++c) + { + auto const& Lrc = L[GetIndex(r, c)]; + auto const& Yc = Y[c]; + for (int i = 0; i < NumBlocks; ++i) + { + for (int j = 0; j < NumBlocks; ++j) + { + Yr[i] -= Lrc[GetIndex(i, j)] * Yc[j]; + } + } + } + mDecomposer.SolveLower(L[GetIndex(r, r)], Yr); + } + } + + // Solve L^T*X = Y, where L is an invertible lower-triangular block + // matrix (L^T is an upper-triangular block matrix) whose diagonal + // blocks are lower-triangular matrices. The input value of X is Y. + // On output, X is the solution. + void SolveUpper(BlockMatrix const& L, BlockVector& X) + { + for (int r = NumBlocks - 1; r >= 0; --r) + { + auto& Xr = X[r]; + for (int c = r + 1; c < NumBlocks; ++c) + { + auto const& Lcr = L[GetIndex(c, r)]; + auto const& Xc = X[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Xr[i] -= Lcr[GetIndex(j, i)] * Xc[j]; + } + } + } + mDecomposer.SolveUpper(L[GetIndex(r, r)], Xr); + } + } + + private: + // Compute the 1-dimensional index of the block matrix in a + // 2-dimensional BlockMatrix object. + inline int GetIndex(int row, int col) const + { + return col + row * NumBlocks; + } + + // Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices + // G(c,c) and A(r,c) are known quantities, and G(c,c) occupies + // the lower triangular portion of A(c,c). The solver stores + // its results in-place, so A(r,c) stores the G(r,c) result. + void LowerTriangularSolver(int r, int c, BlockMatrix& A) + { + auto const& Acc = A[GetIndex(c, c)]; + auto& Arc = A[GetIndex(r, c)]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < j; ++i) + { + Real Lji = Acc[GetIndex(j, i)]; + for (int k = 0; k < BlockSize; ++k) + { + Arc[GetIndex(k, j)] -= Lji * Arc[GetIndex(k, i)]; + } + } + + Real Ljj = Acc[GetIndex(j, j)]; + for (int k = 0; k < BlockSize; ++k) + { + Arc[GetIndex(k, j)] /= Ljj; + } + } + } + + void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A) + { + auto const& Arc = A[GetIndex(r, c)]; + auto const& Akc = A[GetIndex(k, c)]; + auto& Ark = A[GetIndex(r, k)]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < BlockSize; ++i) + { + for (int m = 0; m < BlockSize; ++m) + { + Ark[GetIndex(j, i)] -= Arc[GetIndex(j, m)] * Akc[GetIndex(i, m)]; + } + } + } + } + + // The decomposer has size BlockSize. + CholeskyDecomposition mDecomposer; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCircle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCircle3.h new file mode 100644 index 000000000000..0e13b200f15d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCircle3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The circle is the intersection of the sphere |X-C|^2 = r^2 and the +// plane Dot(N,X-C) = 0, where C is the circle center, r is the radius, +// and N is a unit-length plane normal. + +namespace gte +{ + +template +class Circle3 +{ +public: + + // Construction and destruction. The default constructor sets center to + // (0,0,0), normal to (0,0,1), and radius to 1. + Circle3(); + Circle3(Vector3 const& inCenter, Vector3 const& inNormal, + Real inRadius); + + // Public member access. + Vector3 center, normal; + Real radius; + +public: + // Comparisons to support sorted containers. + bool operator==(Circle3 const& circle) const; + bool operator!=(Circle3 const& circle) const; + bool operator< (Circle3 const& circle) const; + bool operator<=(Circle3 const& circle) const; + bool operator> (Circle3 const& circle) const; + bool operator>=(Circle3 const& circle) const; +}; + + +template +Circle3::Circle3() + : + center(Vector3::Zero()), + normal(Vector3::Unit(2)), + radius((Real)1) +{ +} + +template +Circle3::Circle3(Vector3 const& inCenter, + Vector3 const& inNormal, Real inRadius) + : + center(inCenter), + normal(inNormal), + radius(inRadius) +{ +} + +template +bool Circle3::operator==(Circle3 const& circle) const +{ + return center == circle.center + && normal == circle.normal + && radius == circle.radius; +} + +template +bool Circle3::operator!=(Circle3 const& circle) const +{ + return !operator==(circle); +} + +template +bool Circle3::operator<(Circle3 const& circle) const +{ + if (center < circle.center) + { + return true; + } + + if (center > circle.center) + { + return false; + } + + if (normal < circle.normal) + { + return true; + } + + if (normal > circle.normal) + { + return false; + } + + return radius < circle.radius; +} + +template +bool Circle3::operator<=(Circle3 const& circle) const +{ + return operator<(circle) || operator==(circle); +} + +template +bool Circle3::operator>(Circle3 const& circle) const +{ + return !operator<=(circle); +} + +template +bool Circle3::operator>=(Circle3 const& circle) const +{ + return !operator<(circle); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCone.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCone.h new file mode 100644 index 000000000000..cc30d5331c40 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCone.h @@ -0,0 +1,175 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2019/02/15) + +#pragma once + +#include +#include +#include + +// An acute cone is Dot(A,X-V) = |X-V| cos(t) where V is the vertex, A is the +// unit-length direction of the axis of the cone, and T is the cone angle with +// 0 < t < pi/2. The cone interior is defined by the inequality +// Dot(A,X-V) >= |X-V| cos(t). Since cos(t) > 0, we can avoid computing +// square roots. The solid cone is defined by the inequality +// Dot(A,X-V)^2 >= Dot(X-V,X-V) cos(t)^2. I refer to this object as an +// "infinite cone." Cone axis points are V + h * A, where h is referred to +// as height and 0 <= h < +infinity. +// +// The cone can be truncated by a plane perpendicular to its axis at a height +// hmax with 0 < hmax < +infinity. I refer to this object as a "finite cone." +// The finite cone is capped by has a circular disk opposite the vertex; the +// disk has radius hmax*tan(t). +// +// The finite cone can be additionally truncated at a height hmin with +// 0 < hmin < hmax < +infinity. I refer to this a a "cone frustum." + +namespace gte +{ + template + class Cone + { + public: + // The default constructor creates an infinite cone with + // vertex = (0,...,0) + // axis = (0,...,0,1) + // angle = pi/4 + // minimum height = 0 + // maximum height = std::numeric_limits::max() + Cone() + : + minHeight((Real)0), + maxHeight(std::numeric_limits::max()) + { + ray.origin.MakeZero(); + ray.direction.MakeUnit(N - 1); + SetAngle((Real)GTE_C_QUARTER_PI); + } + + // This constructor creates an infinite cone with the specified + // vertex, axis direction and angle, and with heights + // minimum height = 0 + // maximum height = std::numeric_limits::max() + Cone(Ray const& inRay, Real inAngle) + : + ray(inRay), + minHeight((Real)0), + maxHeight(std::numeric_limits::max()) + { + SetAngle(inAngle); + } + + // This constructor creates a cone with all parameters specified. + Cone(Ray const& inRay, Real inAngle, Real inMinHeight, Real inMaxHeight) + : + ray(inRay), + minHeight(inMinHeight), + maxHeight(inMaxHeight) + { + LogAssert((Real)0 <= minHeight && minHeight < maxHeight, "Invalid height interval."); + SetAngle(inAngle); + } + + // The angle must be in (0,pi/2). The function sets 'angle' and + // computes 'cosAngle', 'sinAngle', 'tanAngle', 'cosAngleSqr', + // 'sinAngleSqr' and 'invSinAngle'. + void SetAngle(Real inAngle) + { + LogAssert((Real)0 < inAngle && inAngle < (Real)GTE_C_HALF_PI, "Invalid angle."); + angle = inAngle; + cosAngle = std::cos(angle); + sinAngle = std::sin(angle); + tanAngle = std::tan(angle); + cosAngleSqr = cosAngle * cosAngle; + sinAngleSqr = sinAngle * sinAngle; + invSinAngle = (Real)1 / sinAngle; + } + + // The cone axis direction must be unit length. The angle must + // be in (0,pi/2). The heights must satisfy + // 0 <= minHeight < maxHeight <= std::numeric_limits::max(). + Ray ray; + Real angle; + Real minHeight, maxHeight; + + // Members derived from 'angle', to avoid calling trigonometric + // functions in geometric queries (for speed). You may set 'angle' + // and compute these by calling SetAngle(inAngle). + Real cosAngle, sinAngle, tanAngle; + Real cosAngleSqr, sinAngleSqr, invSinAngle; + + public: + // Comparisons to support sorted containers. These based only on + // 'ray', 'angle', 'minHeight' and 'maxHeight'. + bool operator==(Cone const& cone) const + { + return ray == cone.ray + && angle == cone.angle + && minHeight == cone.minHeight + && maxHeight == cone.maxHeight; + } + + bool operator!=(Cone const& cone) const + { + return !operator==(cone); + } + + bool operator< (Cone const& cone) const + { + if (ray < cone.ray) + { + return true; + } + + if (ray > cone.ray) + { + return false; + } + + if (angle < cone.angle) + { + return true; + } + + if (angle > cone.angle) + { + return false; + } + + if (minHeight < cone.minHeight) + { + return true; + } + + if (minHeight > cone.minHeight) + { + return false; + } + + return maxHeight < cone.maxHeight; + } + + bool operator<=(Cone const& cone) const + { + return !cone.operator<(*this); + } + + bool operator> (Cone const& cone) const + { + return cone.operator<(*this); + } + + bool operator>=(Cone const& cone) const + { + return !operator<(cone); + } + }; + + // Template alias for convenience. + template + using Cone3 = Cone<3, Real>; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h new file mode 100644 index 000000000000..c33fd5312b3b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h @@ -0,0 +1,790 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Various parts of the code have this trap for error conditions. With a +// correct algorithm and exact arithmetic, we do not expect to trigger the +// error. However, with floating-point arithmetic, it is possible that the +// triangulation becomes malformed. The constrained Delaunay triangulation +// implementation is designed to return gracefully with such a failure. The +// following macros are used to make the code more readable. Do NOT disable +// them, because they have necessary side effects. +#define GTE_CDT_REQUIRE(c) { if (!(c)) { Trap(); return false; } } +#define GTE_CDT_FAILURE { Trap(); return false; } +#define GTE_CDT_REQUIRE_RET(c, r) { if (!(c)) { Trap(); return r; } } +#define GTE_CDT_FAILURE_RET(r) { Trap(); return r; } + +namespace gte +{ + +template +class ConstrainedDelaunay2 : public Delaunay2 +{ +public: + // The class is a functor to support computing the constrained Delaunay + // triangulation of multiple data sets using the same class object. + virtual ~ConstrainedDelaunay2(); + ConstrainedDelaunay2(); + + // This operator computes the Delaunay triangulation only. Read the + // Delaunay2 constructor comments about 'vertices' and 'epsilon'. The + // 'edges' array has indices into the 'vertices' array. No two edges + // should intersect except at endpoints. + bool operator()(int numVertices, Vector2 const* vertices, + InputType epsilon); + + // Insert required edges into the triangulation. For correctness of the + // algorithm, if two edges passed to this function intersect, they must + // do so only at vertices passed to operator(). If you have two edges + // that intersect at a point not in the vertices, compute that point of + // intersection and subdivide the edges at that intersection (to form + // more edges), and add the point to the vertices before calling + // operator(). This function has an output array that contains the + // input edge when the only vertices on the edge are its endpoints. If + // the input edge passes through more vertices, the edge is subdivided + // in this function. The output edge is that subdivision with first + // vertex edge[0] and last vertex edge[1], and the other vertices are + // correctly ordered along the edge. + bool Insert(std::array const& edge, std::vector& outEdge); + +private: + // The top-level entry point for inserting an edge in the triangulation. + bool Insert(std::array const& edge, int v0Triangle, + std::vector& outEdge); + + // Process the coincident edge. + bool ProcessCoincident(int tri, int v0, int v1, int vOther, + std::vector& outEdge); + + // Process the triangle strip originating at the first endpoint of the + // edge. + bool ProcessInterior(int tri, int v0, int v1, int vNext, int vPrev, + std::vector& outEdge); + + // Remove the triangles in the triangle strip and retriangulate the + // left and right polygons using the empty circumcircle condition. + bool Retriangulate(std::vector& polygon, + std::vector> const& lBoundary, + std::vector> const& rBoundary); + + int RetriangulateLRecurse( + std::vector> const& lBoundary, + int i0, int i1, int a0, std::vector& polygon); + + int RetriangulateRRecurse( + std::vector> const& rBoundary, + int i0, int i1, int a0, std::vector& polygon); + + int SelectSplit(std::vector> const& boundary, int i0, + int i1) const; + + // Compute a pseudosquared distance from the vertex at v2 to the edge + // . + ComputeType ComputePSD(int v0, int v1, int v2) const; + + // Search the triangulation for a triangle that contains the specified + // vertex. + int GetLinkTriangle(int v) const; + + // Determine the index in {0,1,2} for the triangle 'tri' that contains + // the vertex 'v'. + int GetIndexOfVertex(int tri, int v) const; + + // Given a triangle 'tri' with CCW-edge , return where + // 'adj' is the index of the triangle adjacent to 'tri' that shares the + // edge and 'v2' is the vertex of the adjacent triangle opposite the + // edge. This function supports traversing a triangle strip that contains + // a constraint edge, so it is called only when an adjacent triangle + // actually exists. + std::array GetAdjInterior(int tri, int v0, int v1) const; + + // Given a triangle 'tri' of the triangle strip, the boundary edge must + // contain the vertex with index 'needBndVertex'. The input + // 'needAdjVIndex' specifies where to look for the index of the triangle + // outside the strip but adjacent to the boundary edge. The return + // value is and is used to connect 'tri' and 'adj' + // across a triangle strip boundary. + std::array GetAdjBoundary(int tri, int needBndVertex, + int needAdjVIndex) const; + + // Set the indices and adjacencies arrays so that 'tri' and 'adj' share + // the common edge; 'tri' has CCW-edge and 'adj' has CCW-edge + // . + bool Connect(int tri, int adj, int v0, int v1); + + // Create an ordered list of triangles forming the link of a vertex. The + // pair of the list is . This allows us + // to cache the index of v relative to each triangle in the link. The + // vertex v might be a boundary vertex, in which case the neighborhood is + // open; otherwise, v is an interior vertex and the neighborhood is + // closed. The 'isOpen' parameter specifies the case. + bool BuildLink(int v, int vTriangle, std::list>& link, + bool& isOpen) const; + + // Support for return-false-on-error. This allows us to investigate the + // error by setting a break point, trigger an assert when the logger is + // active, and get a call stack. + void Trap() const; +}; + + +template +ConstrainedDelaunay2::~ConstrainedDelaunay2() +{ +} + +template +ConstrainedDelaunay2::ConstrainedDelaunay2() + : + Delaunay2() +{ +} + +template +bool ConstrainedDelaunay2::operator()( + int numVertices, Vector2 const* vertices, InputType epsilon) +{ + return Delaunay2::operator()(numVertices, + vertices, epsilon); +} + +template +bool ConstrainedDelaunay2::Insert( + std::array const& edge, std::vector& outEdge) +{ + int v0 = edge[0], v1 = edge[1]; + if (0 <= v0 && v0 < this->mNumVertices + && 0 <= v1 && v1 < this->mNumVertices) + { + int v0Triangle = GetLinkTriangle(v0); + if (v0Triangle >= 0) + { + // Once an edge is inserted, the base-class mGraph no longer + // represents the triangulation. Clear it in case the user tries + // to access it. + this->mGraph.Clear(); + + outEdge.clear(); + return Insert(edge, v0Triangle, outEdge); + } + } + return false; +} + +template +bool ConstrainedDelaunay2::Insert( + std::array const& edge, int v0Triangle, std::vector& outEdge) +{ + // TODO: this check helps avoid cases where the algorithm gets stuck in endless recursion walking the mesh (resulting in a stack overflow) ... overflow could still happen for a large mesh, so more work to prevent such an endless loop would be valuable! + // TODO: (and/or: rewrite the insert function to not be recursive!) + GTE_CDT_REQUIRE(outEdge.size() <= this->mComputeVertices.size()); + + // Create the neighborhood of triangles that share the vertex v0. On + // entry we already know one such triangle (v0Triangle). + int v0 = edge[0], v1 = edge[1]; + std::list> link; + bool isOpen = true; + GTE_CDT_REQUIRE(BuildLink(v0, v0Triangle, link, isOpen)); + + // Determine which triangle contains the edge. Process the edge according + // to whether it is strictly between two triangle edges or is coincident + // with a triangle edge. + auto item = link.begin(); + std::array indices; + GTE_CDT_REQUIRE(this->GetIndices(item->first, indices)); + int vNext = indices[(item->second + 1) % 3]; + int qr0 = this->mQuery.ToLine(v1, v0, vNext); + while (item != link.end()) + { + if (qr0 == 0) + { + // We have to be careful about parallel edges that point in + // the opposite direction of . + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctvnext = + this->mComputeVertices[vNext]; + if (Dot(ctv1 - ctv0, ctvnext - ctv0) > (ComputeType)0) + { + // is coincident to triangle edge0. + return ProcessCoincident(item->first, v0, v1, vNext, + outEdge); + } + + // Make sure we enter the next "if" statement to continue + // traversing the link. + qr0 = 1; + } + + if (qr0 > 0) + { + // is not in triangle. Visit the next triangle. + if (++item == link.end()) + { + return false; + } + GTE_CDT_REQUIRE(this->GetIndices(item->first, indices)); + vNext = indices[(item->second + 1) % 3]; + qr0 = this->mQuery.ToLine(v1, v0, vNext); + continue; + } + + int vPrev = indices[(item->second + 2) % 3]; + int qr1 = this->mQuery.ToLine(v1, v0, vPrev); + while (item != link.end()) + { + if (qr1 == 0) + { + // We have to be careful about parallel edges that point in + // the opposite direction of . + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctvprev = + this->mComputeVertices[vPrev]; + if (Dot(ctv1 - ctv0, ctvprev - ctv0) > (ComputeType)0) + { + // is coincident to triangle edge1. + return ProcessCoincident(item->first, v0, v1, vPrev, + outEdge); + } + + // Make sure we enter the next "if" statement to continue + // traversing the link. + qr1 = -1; + } + + if (qr1 < 0) + { + // is not in triangle. Visit the next triangle. + if (++item == link.end()) + { + return false; + } + this->GetIndices(item->first, indices); + vNext = vPrev; + vPrev = indices[(item->second + 2) % 3]; + qr1 = this->mQuery.ToLine(v1, v0, vPrev); + continue; + } + + // is interior to triangle . + return ProcessInterior(item->first, v0, v1, vNext, vPrev, + outEdge); + } + break; + } + + // The edge must be contained in some link triangle. + GTE_CDT_FAILURE; +} + +template +bool ConstrainedDelaunay2::ProcessCoincident(int tri, + int v0, int v1, int vOther, std::vector& outEdge) +{ + outEdge.push_back(v0); + if (v1 != vOther) + { + // Decompose the edge and process the right-most subedge. + return Insert({{ vOther, v1 }}, tri, outEdge); + } + else + { + // is already in the triangulation. + outEdge.push_back(v1); + return true; + } +} + +template +bool ConstrainedDelaunay2::ProcessInterior(int tri, + int v0, int v1, int vNext, int vPrev, std::vector& outEdge) +{ + // The triangles of the strip are stored in 'polygon'. The + // retriangulation leads to the same number of triangles, so we can reuse + // the mIndices[] and mAdjacencies[] locations implied by the 'polygons' + // indices. + std::vector polygon; + + // The sBoundary[i] (s in {l,r}) array element is ; see the + // header comments for GetAdjBoundary about what these mean. The + // boundary vertex is 'v0', the adjacent triangle 'adj' is outside the + // strip and shares the edge with a + // triangle in 'polygon'. This information allows us to connect the + // adjacent triangles outside the strip to new triangles inserted by + // the retriangulation. The value sBoundary[0][1,2] values are set to + // -1 but they are not used in the construction. + std::vector> lBoundary, rBoundary; + std::array binfo; + + polygon.push_back(tri); + + lBoundary.push_back({{ v0, -1 }}); + binfo = GetAdjBoundary(tri, vPrev, vPrev); + GTE_CDT_REQUIRE(binfo[0] != -2); + lBoundary.push_back(binfo); + + rBoundary.push_back({{ v0, -1 }}); + binfo = GetAdjBoundary(tri, vNext, v0); + GTE_CDT_REQUIRE(binfo[0] != -2); + rBoundary.push_back(binfo); + + // Visit the triangles in the strip. Guard against an infinite loop. + for (int i = 0; i < this->mNumTriangles; ++i) + { + // Find the vertex of the adjacent triangle that is opposite the + // edge shared with the current triangle. + auto iinfo = GetAdjInterior(tri, vNext, vPrev); + int adj = iinfo[0], vOpposite = iinfo[1]; + GTE_CDT_REQUIRE(vOpposite >= 0); + + // Visit the adjacent triangle and insert it into the polygon. + tri = adj; + polygon.push_back(tri); + + int qr = this->mQuery.ToLine(vOpposite, v0, v1); + if (qr == 0) + { + // We have encountered a vertex that terminates the triangle + // strip. Retriangulate the polygon. If the edge continues + // through vOpposite, decompose the edge and insert the + // right-most subedge. + binfo = GetAdjBoundary(tri, vOpposite, vOpposite); + GTE_CDT_REQUIRE(binfo[0] != -2); + lBoundary.push_back(binfo); + + binfo = GetAdjBoundary(tri, vOpposite, vNext); + GTE_CDT_REQUIRE(binfo[0] != -2); + rBoundary.push_back(binfo); + + Retriangulate(polygon, lBoundary, rBoundary); + if (vOpposite != v1) + { + outEdge.push_back(v0); + return Insert({{ vOpposite, v1 }}, tri, outEdge); + } + else + { + outEdge.push_back(v0); + outEdge.push_back(v1); + return true; + } + } + + if (qr < 0) + { + binfo = GetAdjBoundary(tri, vOpposite, vOpposite); + GTE_CDT_REQUIRE(binfo[0] != -2); + lBoundary.push_back(binfo); + vPrev = vOpposite; + } + else // qr > 0 + { + binfo = GetAdjBoundary(tri, vOpposite, vNext); + GTE_CDT_REQUIRE(binfo[0] != -2); + rBoundary.push_back(binfo); + vNext = vOpposite; + } + } + + // The triangle strip should have been located in the loop. + GTE_CDT_FAILURE; +} + +template +bool ConstrainedDelaunay2::Retriangulate( + std::vector& polygon, + std::vector> const& lBoundary, + std::vector> const& rBoundary) +{ + int t0 = RetriangulateLRecurse(lBoundary, 0, + static_cast(lBoundary.size()) - 1, -1, polygon); + + int t1 = RetriangulateRRecurse(rBoundary, 0, + static_cast(rBoundary.size()) - 1, -1, polygon); + + int v0 = lBoundary.front()[0]; + int v1 = lBoundary.back()[0]; + GTE_CDT_REQUIRE(Connect(t0, t1, v0, v1)); + return true; +} + +template +int ConstrainedDelaunay2::RetriangulateLRecurse( + std::vector> const& lBoundary, int i0, int i1, int a0, + std::vector& polygon) +{ + // Create triangles when recursing down, connect adjacent triangles when + // returning. + + int v0 = lBoundary[i0][0]; + int v1 = lBoundary[i1][0]; + + if (i1 - i0 == 1) + { + GTE_CDT_REQUIRE_RET(Connect(a0, lBoundary[i1][1], v1, v0), -2); + return -1; // No triangle created. + } + else + { + // Select i2 in [i0+1,i1-1] for minimum distance to edge . + int i2 = SelectSplit(lBoundary, i0, i1); + int v2 = lBoundary[i2][0]; + + // Reuse a triangle and fill in its new vertices. + int tri = polygon.back(); + polygon.pop_back(); + this->mIndices[3 * tri + 0] = v0; + this->mIndices[3 * tri + 1] = v1; + this->mIndices[3 * tri + 2] = v2; + + // Recurse downward and create triangles. + int ret0 = RetriangulateLRecurse(lBoundary, i0, i2, tri, polygon); + GTE_CDT_REQUIRE_RET(ret0 >= -1, -2); + int ret1 = RetriangulateLRecurse(lBoundary, i2, i1, tri, polygon); + GTE_CDT_REQUIRE_RET(ret1 >= -1, -2); + + // Return and connect triangles. + GTE_CDT_REQUIRE_RET(Connect(a0, tri, v1, v0), -2); + return tri; + } +} + +template +int ConstrainedDelaunay2::RetriangulateRRecurse( + std::vector> const& rBoundary, int i0, int i1, int a0, + std::vector& polygon) +{ + // Create triangles when recursing down, connect adjacent triangles when + // returning. + + int v0 = rBoundary[i0][0]; + int v1 = rBoundary[i1][0]; + + if (i1 - i0 == 1) + { + GTE_CDT_REQUIRE_RET(Connect(a0, rBoundary[i1][1], v0, v1), -2); + return -1; // No triangle created. + } + else + { + // Select i2 in [i0+1,i1-1] for minimum distance to edge . + int i2 = SelectSplit(rBoundary, i0, i1); + int v2 = rBoundary[i2][0]; + + // Reuse a triangle and fill in its new vertices. + int tri = polygon.back(); + polygon.pop_back(); + this->mIndices[3 * tri + 0] = v1; + this->mIndices[3 * tri + 1] = v0; + this->mIndices[3 * tri + 2] = v2; + + // Recurse downward and create triangles. + int ret0 = RetriangulateRRecurse(rBoundary, i0, i2, tri, polygon); + GTE_CDT_REQUIRE_RET(ret0 >= -1, -2); + int ret1 = RetriangulateRRecurse(rBoundary, i2, i1, tri, polygon); + GTE_CDT_REQUIRE_RET(ret1 >= -1, -2); + + // Return and connect triangles. + GTE_CDT_REQUIRE_RET(Connect(a0, tri, v0, v1), -2); + return tri; + } +} + +template +int ConstrainedDelaunay2::SelectSplit( + std::vector> const& boundary, int i0, int i1) const +{ + int i2; + if (i1 - i0 == 2) + { + // This is the only candidate. + i2 = i0 + 1; + } + else // i1 - i0 > 2 + { + // Select the index i2 in [i0+1,i1-1] for which the distance from the + // vertex v2 at i2 to the edge is minimized. To allow exact + // arithmetic, use a pseudosquared distance that avoids divisions and + // square roots. + i2 = i0 + 1; + int v0 = boundary[i0][0]; + int v1 = boundary[i1][0]; + int v2 = boundary[i2][0]; + ComputeType minpsd = ComputePSD(v0, v1, v2); + for (int i = i2 + 1; i < i1; ++i) + { + v2 = boundary[i][0]; + ComputeType psd = ComputePSD(v0, v1, v2); + if (psd < minpsd) + { + minpsd = psd; + i2 = i; + } + } + } + return i2; +} + +template +ComputeType ConstrainedDelaunay2::ComputePSD(int v0, + int v1, int v2) const +{ + ComputeType psd; + + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctv2 = this->mComputeVertices[v2]; + + Vector2 V1mV0 = ctv1 - ctv0; + Vector2 V2mV0 = ctv2 - ctv0; + ComputeType sqrlen10 = Dot(V1mV0, V1mV0); + ComputeType dot = Dot(V1mV0, V2mV0); + ComputeType zero(0); + + if (dot <= zero) + { + ComputeType sqrlen20 = Dot(V2mV0, V2mV0); + psd = sqrlen10 * sqrlen20; + } + else + { + Vector2 V2mV1 = ctv2 - ctv1; + dot = Dot(V1mV0, V2mV1); + if (dot >= zero) + { + ComputeType sqrlen21 = Dot(V2mV1, V2mV1); + psd = sqrlen10 * sqrlen21; + } + else + { + dot = DotPerp(V2mV0, V1mV0); + psd = dot * dot; + } + } + + return psd; +} + +template +int ConstrainedDelaunay2::GetLinkTriangle(int v) const +{ + // Remap in case an edge vertex was specified that is a duplicate. + v = this->mDuplicates[v]; + + // TODO: this algorithm does not work for point location in a CDT; it's only guaranteed for a (non-C)DT. + // See https://hal.inria.fr/inria-00072509/document for a report on different approaches to walking a triangulation, + // including an example of where this approach (visibility walk) fails. + // For now, I've added a brute-force fallback below. It would be better to choose a correct algorithm to put here though! + int tri = 0; + for (int i = 0; i < this->mNumTriangles; ++i) + { + // Test whether v is a vertex of the triangle. + std::array indices; + GTE_CDT_REQUIRE(this->GetIndices(tri, indices)); + for (int j = 0; j < 3; ++j) + { + if (v == indices[j]) + { + return tri; + } + } + + // v must be outside the triangle. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + if (this->mQuery.ToLine(v, indices[j0], indices[j1]) > 0) + { + // Vertex v sees the edge from outside, so traverse to the + // triangle sharing the edge. + std::array adjacencies; + GTE_CDT_REQUIRE(this->GetAdjacencies(tri, adjacencies)); + int adj = adjacencies[j0]; + GTE_CDT_REQUIRE_RET(adj >= 0, -1); + tri = adj; + break; + } + } + } + + // fallback to brute force search + // TODO: rm this after fixing the above point location to something that works for all triangulations + for (tri = 0; tri < this->mNumTriangles; ++tri) + { + // Test whether v is a vertex of the triangle. + std::array indices; + GTE_CDT_REQUIRE(this->GetIndices(tri, indices)); + for (int j = 0; j < 3; ++j) + { + if (v == indices[j]) + { + return tri; + } + } + } + + // The vertex must be in the triangulation. + GTE_CDT_FAILURE_RET(-1); +} + +template +int ConstrainedDelaunay2::GetIndexOfVertex(int tri, + int v) const +{ + std::array indices; + GTE_CDT_REQUIRE_RET(this->GetIndices(tri, indices), -1); + int indexOfV; + for (indexOfV = 0; indexOfV < 3; ++indexOfV) + { + if (v == indices[indexOfV]) + { + return indexOfV; + } + } + + // Unexpected failure. + GTE_CDT_FAILURE_RET(-1); +} + +template +std::array ConstrainedDelaunay2:: +GetAdjInterior(int tri, int v0, int v1) const +{ + std::array error = {{ -2, -2 }}; + int vIndex = GetIndexOfVertex(tri, v0); + GTE_CDT_REQUIRE_RET(vIndex >= 0, error); + int adj = this->mAdjacencies[3 * tri + vIndex]; + if (adj >= 0) + { + for (int v2Index = 0; v2Index < 3; ++v2Index) + { + int v2 = this->mIndices[3 * adj + v2Index]; + if (v2 != v0 && v2 != v1) + { + return {{ adj, v2 }}; + } + } + } + else + { + return {{ -1, -1 }}; + } + + GTE_CDT_FAILURE_RET(error); +} + +template +std::array ConstrainedDelaunay2:: +GetAdjBoundary(int tri, int needBndVertex, int needAdjVIndex) const +{ + std::array error = {{ -2, -2 }}; + int vIndex = GetIndexOfVertex(tri, needAdjVIndex); + GTE_CDT_REQUIRE_RET(vIndex >= 0, error); + int adj = this->mAdjacencies[3 * tri + vIndex]; + return {{ needBndVertex, adj }}; +} + +template +bool ConstrainedDelaunay2::Connect(int tri, int adj, + int v0, int v1) +{ + if (tri >= 0) + { + int v0Index = GetIndexOfVertex(tri, v0); + GTE_CDT_REQUIRE(v0Index >= 0); + if (adj >= 0) + { + int v1Index = GetIndexOfVertex(adj, v1); + GTE_CDT_REQUIRE(v1Index >= 0); + this->mAdjacencies[3 * adj + v1Index] = tri; + } + + this->mAdjacencies[3 * tri + v0Index] = adj; + } + // else: tri = -1, which occurs in the top-level call to retriangulate + return true; +} + +template +bool ConstrainedDelaunay2::BuildLink(int v, + int vTriangle, std::list>& link, bool& isOpen) const +{ + // The list starts with a known triangle in the link of v. + int vStartIndex = GetIndexOfVertex(vTriangle, v); + GTE_CDT_REQUIRE(vStartIndex >= 0); + link.push_front(std::make_pair(vTriangle, vStartIndex)); + + // Traverse adjacent triangles to the "left" of v. Guard against an + // infinite loop. + int tri = vTriangle, vIndex = vStartIndex; + std::array adjacencies; + for (int i = 0; i < this->mNumTriangles; ++i) + { + GTE_CDT_REQUIRE(this->GetAdjacencies(tri, adjacencies)); + int adjPrev = adjacencies[(vIndex + 2) % 3]; + if (adjPrev >= 0) + { + if (adjPrev != vTriangle) + { + tri = adjPrev; + vIndex = GetIndexOfVertex(tri, v); + GTE_CDT_REQUIRE(vIndex >= 0); + link.push_back(std::make_pair(tri, vIndex)); + } + else + { + // We have reached the starting triangle, so v is an interior + // vertex. + isOpen = false; + return true; + } + } + else + { + // We have reached a triangle with boundary edge, so v is a + // boundary vertex. We mush find more triangles by searching + // to the "right" of v. Guard against an infinite loop. + isOpen = true; + tri = vTriangle; + vIndex = vStartIndex; + for (int j = 0; j < this->mNumTriangles; ++j) + { + this->GetAdjacencies(tri, adjacencies); + int adjNext = adjacencies[vIndex]; + if (adjNext >= 0) + { + tri = adjNext; + vIndex = GetIndexOfVertex(tri, v); + GTE_CDT_REQUIRE(vIndex >= 0); + link.push_front(std::make_pair(tri, vIndex)); + } + else + { + // We have reached the other boundary edge that shares v. + return true; + } + } + break; + } + } + + // Unexpected failure. + GTE_CDT_FAILURE; +} + +template +void ConstrainedDelaunay2::Trap() const +{ + LogError("CDT failure."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContAlignedBox.h new file mode 100644 index 000000000000..95de78920280 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContAlignedBox.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.13.0 (2018/04/15) + +#pragma once + +#include + +namespace gte +{ + +// Compute the minimum size aligned bounding box of the points. The extreme +// values are the minima and maxima of the point coordinates. +template +bool GetContainer(int numPoints, Vector const* points, + AlignedBox& box); + +// Test for containment. +template +bool InContainer(Vector const& point, AlignedBox const& box); + +// Construct an aligned box that contains two other aligned boxes. The +// result is the minimum size box containing the input boxes. +template +bool MergeContainers(AlignedBox const& box0, + AlignedBox const& box1, AlignedBox& merge); + + +template +bool GetContainer(int numPoints, Vector const* points, + AlignedBox& box) +{ + return ComputeExtremes(numPoints, points, box.min, box.max); +} + +template +bool InContainer(Vector const& point, AlignedBox const& box) +{ + for (int i = 0; i < N; ++i) + { + Real value = point[i]; + if (value < box.min[i] || value > box.max[i]) + { + return false; + } + } + return true; +} + +template +bool MergeContainers(AlignedBox const& box0, + AlignedBox const& box1, AlignedBox& merge) +{ + for (int i = 0; i < N; ++i) + { + merge.min[i] = std::min(box0.min[i], box1.min[i]); + merge.max[i] = std::max(box0.max[i], box1.max[i]); + } + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCapsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCapsule3.h new file mode 100644 index 000000000000..52c6e7baf120 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCapsule3.h @@ -0,0 +1,290 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// Compute the axis of the capsule segment using least-squares fitting. The +// radius is the maximum distance from the points to the axis. Hemispherical +// caps are chosen as close together as possible. +template +bool GetContainer(int numPoints, Vector3 const* points, + Capsule3& capsule); + +// Test for containment of a point by a capsule. +template +bool InContainer(Vector3 const& point, Capsule3 const& capsule); + +// Test for containment of a sphere by a capsule. +template +bool InContainer(Sphere3 const& sphere, Capsule3 const& capsule); + +// Test for containment of a capsule by a capsule. +template +bool InContainer(Capsule3 const& testCapsule, + Capsule3 const& capsule); + +// Compute a capsule that contains the input capsules. The returned capsule +// is not necessarily the one of smallest volume that contains the inputs. +template +bool MergeContainers(Capsule3 const& capsule0, + Capsule3 const& capsule1, Capsule3& merge); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + Capsule3& capsule) +{ + ApprOrthogonalLine3 fitter; + fitter.Fit(numPoints, points); + Line3 line = fitter.GetParameters(); + + DCPQuery, Line3> plQuery; + Real maxRadiusSqr = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + auto result = plQuery(points[i], line); + if (result.sqrDistance > maxRadiusSqr) + { + maxRadiusSqr = result.sqrDistance; + } + } + + Vector3 basis[3]; + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + + Real minValue = std::numeric_limits::max(); + Real maxValue = -std::numeric_limits::max(); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - line.origin; + Real uDotDiff = Dot(diff, basis[1]); + Real vDotDiff = Dot(diff, basis[2]); + Real wDotDiff = Dot(diff, basis[0]); + Real discr = maxRadiusSqr - (uDotDiff*uDotDiff + vDotDiff*vDotDiff); + Real radical = std::sqrt(std::max(discr, (Real)0)); + + Real test = wDotDiff + radical; + if (test < minValue) + { + minValue = test; + } + + test = wDotDiff - radical; + if (test > maxValue) + { + maxValue = test; + } + } + + Vector3 center = line.origin + + (((Real)0.5)*(minValue + maxValue))*line.direction; + + Real extent; + if (maxValue > minValue) + { + // Container is a capsule. + extent = ((Real)0.5)*(maxValue - minValue); + } + else + { + // Container is a sphere. + extent = (Real)0; + } + + capsule.segment = Segment3(center, line.direction, extent); + capsule.radius = std::sqrt(maxRadiusSqr); + return true; +} + +template +bool InContainer(Vector3 const& point, Capsule3 const& capsule) +{ + DCPQuery, Segment3> psQuery; + auto result = psQuery(point, capsule.segment); + return result.distance <= capsule.radius; +} + +template +bool InContainer(Sphere3 const& sphere, Capsule3 const& capsule) +{ + Real rDiff = capsule.radius - sphere.radius; + if (rDiff >= (Real)0) + { + DCPQuery, Segment3> psQuery; + auto result = psQuery(sphere.center, capsule.segment); + return result.distance <= rDiff; + } + return false; +} + +template +bool InContainer(Capsule3 const& testCapsule, + Capsule3 const& capsule) +{ + Sphere3 spherePosEnd(testCapsule.segment.p[1], testCapsule.radius); + Sphere3 sphereNegEnd(testCapsule.segment.p[0], testCapsule.radius); + return InContainer(spherePosEnd, capsule) + && InContainer(sphereNegEnd, capsule); +} + +template +bool MergeContainers(Capsule3 const& capsule0, + Capsule3 const& capsule1, Capsule3& merge) +{ + if (InContainer(capsule0, capsule1)) + { + merge = capsule1; + return true; + } + + if (InContainer(capsule1, capsule0)) + { + merge = capsule0; + return true; + } + + Vector3 P0, P1, D0, D1; + Real extent0, extent1; + capsule0.segment.GetCenteredForm(P0, D0, extent0); + capsule1.segment.GetCenteredForm(P1, D1, extent1); + + // Axis of final capsule. + Line3 line; + + // Axis center is average of input axis centers. + line.origin = ((Real)0.5)*(P0 + P1); + + // Axis unit direction is average of input axis unit directions. + if (Dot(D0, D1) >= (Real)0) + { + line.direction = D0 + D1; + } + else + { + line.direction = D0 - D1; + } + Normalize(line.direction); + + // Cylinder with axis 'line' must contain the spheres centered at the + // endpoints of the input capsules. + DCPQuery, Line3> plQuery; + Vector3 posEnd0 = capsule0.segment.p[1]; + Real radius = plQuery(posEnd0, line).distance + capsule0.radius; + + Vector3 negEnd0 = capsule0.segment.p[0]; + Real tmp = plQuery(negEnd0, line).distance + capsule0.radius; + + Vector3 posEnd1 = capsule1.segment.p[1]; + tmp = plQuery(posEnd1, line).distance + capsule1.radius; + if (tmp > radius) + { + radius = tmp; + } + + Vector3 negEnd1 = capsule1.segment.p[0]; + tmp = plQuery(negEnd1, line).distance + capsule1.radius; + if (tmp > radius) + { + radius = tmp; + } + + // Process sphere . + Real rDiff = radius - capsule0.radius; + Real rDiffSqr = rDiff*rDiff; + Vector3 diff = line.origin - posEnd0; + Real k0 = Dot(diff, diff) - rDiffSqr; + Real k1 = Dot(diff, line.direction); + Real discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + Real root = std::sqrt(std::max(discr, (Real)0)); + Real tPos = -k1 - root; + Real tNeg = -k1 + root; + + // Process sphere . + diff = line.origin - negEnd0; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + // Process sphere . + rDiff = radius - capsule1.radius; + rDiffSqr = rDiff*rDiff; + diff = line.origin - posEnd1; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + // Process sphere . + diff = line.origin - negEnd1; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1*k1 - k0; // assert: k1*k1-k0 >= 0, guard against anyway + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + Vector3 center = line.origin + + ((Real)0.5)*(tPos + tNeg)*line.direction; + + Real extent; + if (tPos > tNeg) + { + // Container is a capsule. + extent = ((Real)0.5)*(tPos - tNeg); + } + else + { + // Container is a sphere. + extent = (Real)0; + } + + merge.segment = Segment3(center, line.direction, extent); + merge.radius = radius; + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCircle2.h new file mode 100644 index 000000000000..1063035785ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCircle2.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/16) + +#pragma once + +#include +#include + +namespace gte +{ + // Compute the smallest bounding circle whose center is the average of + // the input points. + template + bool GetContainer(int numPoints, Vector2 const* points, Circle2& circle) + { + circle.center = points[0]; + for (int i = 1; i < numPoints; ++i) + { + circle.center += points[i]; + } + circle.center /= (Real)numPoints; + + circle.radius = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - circle.center; + Real radiusSqr = Dot(diff, diff); + if (radiusSqr > circle.radius) + { + circle.radius = radiusSqr; + } + } + + circle.radius = std::sqrt(circle.radius); + return true; + } + + // Test for containment of a point inside a circle. + template + bool InContainer(Vector2 const& point, Circle2 const& circle) + { + Vector2 diff = point - circle.center; + return Length(diff) <= circle.radius; + } + + // Compute the smallest bounding circle that contains the input circles. + template + bool MergeContainers(Circle2 const& circle0, Circle2 const& circle1, Circle2& merge) + { + Vector2 cenDiff = circle1.center - circle0.center; + Real lenSqr = Dot(cenDiff, cenDiff); + Real rDiff = circle1.radius - circle0.radius; + Real rDiffSqr = rDiff * rDiff; + + if (rDiffSqr >= lenSqr) + { + merge = (rDiff >= (Real)0 ? circle1 : circle0); + } + else + { + Real length = std::sqrt(lenSqr); + if (length > (Real)0) + { + Real coeff = (length + rDiff) / (((Real)2)*length); + merge.center = circle0.center + coeff * cenDiff; + } + else + { + merge.center = circle0.center; + } + + merge.radius = ((Real)0.5)*(length + circle0.radius + circle1.radius); + } + + return true; + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCone.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCone.h new file mode 100644 index 000000000000..a80d062c1225 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCone.h @@ -0,0 +1,22 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.19.0 (2018/11/30) + +#pragma once + +#include + +namespace gte +{ + // Test for containment of a point by a cone. + template + bool InContainer(Vector const& point, Cone const& cone) + { + Vector diff = point - cone.ray.origin; + Real h = Dot(cone.ray.direction, diff); + return cone.minHeight <= h && h <= cone.maxHeight && h >= cone.cosAngle * Length(diff); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCylinder3.h new file mode 100644 index 000000000000..ce56f8d043a1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContCylinder3.h @@ -0,0 +1,88 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// Compute the cylinder axis segment using least-squares fit. The radius is +// the maximum distance from points to the axis. The height is determined by +// projection of points onto the axis and determining the containing interval. +template +bool GetContainer(int numPoints, Vector3 const* points, + Cylinder3& cylinder); + +// Test for containment of a point by a cylinder. +template +bool InContainer(Vector3 const& point, Cylinder3 const& cylinder); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + Cylinder3& cylinder) +{ + ApprOrthogonalLine3 fitter; + fitter.Fit(numPoints, points); + Line3 line = fitter.GetParameters(); + + DCPQuery, Line3> plQuery; + Real maxRadiusSqr = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + auto result = plQuery(points[i], line); + if (result.sqrDistance > maxRadiusSqr) + { + maxRadiusSqr = result.sqrDistance; + } + } + + Vector3 diff = points[0] - line.origin; + Real wMin = Dot(line.direction, diff); + Real wMax = wMin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - line.origin; + Real w = Dot(line.direction, diff); + if (w < wMin) + { + wMin = w; + } + else if (w > wMax) + { + wMax = w; + } + } + + cylinder.axis.origin = line.origin + + (((Real)0.5)*(wMax + wMin))*line.direction; + cylinder.axis.direction = line.direction; + cylinder.radius = std::sqrt(maxRadiusSqr); + cylinder.height = wMax - wMin; + return true; +} + +template +bool InContainer(Vector3 const& point, Cylinder3 const& cylinder) +{ + Vector3 diff = point - cylinder.axis.origin; + Real zProj = Dot(diff, cylinder.axis.direction); + if (std::abs(zProj)*(Real)2 > cylinder.height) + { + return false; + } + + Vector3 xyProj = diff - zProj*cylinder.axis.direction; + return Length(xyProj) <= cylinder.radius; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2.h new file mode 100644 index 000000000000..96660de251c8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2.h @@ -0,0 +1,164 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// The input points are fit with a Gaussian distribution. The center C of the +// ellipsoid is chosen to be the mean of the distribution. The axes of the +// ellipsoid are chosen to be the eigenvectors of the covariance matrix M. +// The shape of the ellipsoid is determined by the absolute values of the +// eigenvalues. NOTE: The construction is ill-conditioned if the points are +// (nearly) collinear. In this case M has a (nearly) zero eigenvalue, so +// inverting M can be a problem numerically. +template +bool GetContainer(int numPoints, Vector2 const* points, + Ellipse2& ellipse); + +// Test for containment of a point inside an ellipse. +template +bool InContainer(Vector2 const& point, Ellipse2 const& ellipse); + +// Construct a bounding ellipse for the two input ellipses. The result is +// not necessarily the minimum-area ellipse containing the two ellipses. +template +bool MergeContainers(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1, Ellipse2& merge); + + +template +bool GetContainer(int numPoints, Vector2 const* points, + Ellipse2& ellipse) +{ + // Fit the points with a Gaussian distribution. The covariance matrix + // is M = sum_j D[j]*U[j]*U[j]^T, where D[j] are the eigenvalues and U[j] + // are corresponding unit-length eigenvectors. + ApprGaussian2 fitter; + if (fitter.Fit(numPoints, points)) + { + OrientedBox2 box = fitter.GetParameters(); + + // If either eigenvalue is nonpositive, adjust the D[] values so that + // we actually build an ellipse. + for (int j = 0; j < 2; ++j) + { + if (box.extent[j] < (Real)0) + { + box.extent[j] = -box.extent[j]; + } + } + + // Grow the ellipse, while retaining its shape determined by the + // covariance matrix, to enclose all the input points. The quadratic + // form that is used for the ellipse construction is + // Q(X) = (X-C)^T*M*(X-C) + // = (X-C)^T*(sum_j D[j]*U[j]*U[j]^T)*(X-C) + // = sum_j D[j]*Dot(U[j],X-C)^2 + // If the maximum value of Q(X[i]) for all input points is V^2, then a + // bounding ellipse is Q(X) = V^2, because Q(X[i]) <= V^2 for all i. + + Real maxValue = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - box.center; + Real dot[2] = + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff) + }; + + Real value = + box.extent[0] * dot[0] * dot[0] + + box.extent[1] * dot[1] * dot[1]; + + if (value > maxValue) + { + maxValue = value; + } + } + + // Arrange for the quadratic to satisfy Q(X) <= 1. + ellipse.center = box.center; + for (int j = 0; j < 2; ++j) + { + ellipse.axis[j] = box.axis[j]; + ellipse.extent[j] = std::sqrt(maxValue / box.extent[j]); + } + return true; + + } + + return false; +} + +template +bool InContainer(Vector2 const& point, Ellipse2 const& ellipse) +{ + Vector2 diff = point - ellipse.center; + Vector2 standardized{ + Dot(diff, ellipse.axis[0]) / ellipse.extent[0], + Dot(diff, ellipse.axis[1]) / ellipse.extent[1] }; + return Length(standardized) <= (Real)1; +} + +template +bool MergeContainers(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1, Ellipse2& merge) +{ + // Compute the average of the input centers. + merge.center = ((Real)0.5)*(ellipse0.center + ellipse1.center); + + // The bounding ellipse orientation is the average of the input + // orientations. + if (Dot(ellipse0.axis[0], ellipse1.axis[0]) >= (Real)0) + { + merge.axis[0] = ((Real)0.5)*(ellipse0.axis[0] + ellipse1.axis[0]); + } + else + { + merge.axis[0] = ((Real)0.5)*(ellipse0.axis[0] - ellipse1.axis[0]); + } + Normalize(merge.axis[0]); + merge.axis[1] = -Perp(merge.axis[0]); + + // Project the input ellipses onto the axes obtained by the average of the + // orientations and that go through the center obtained by the average of + // the centers. + for (int j = 0; j < 2; ++j) + { + // Projection axis. + Line2 line(merge.center, merge.axis[j]); + + // Project ellipsoids onto the axis. + Real min0, max0, min1, max1; + Project(ellipse0, line, min0, max0); + Project(ellipse1, line, min1, max1); + + // Determine the smallest interval containing the projected + // intervals. + Real maxIntr = (max0 >= max1 ? max0 : max1); + Real minIntr = (min0 <= min1 ? min0 : min1); + + // Update the average center to be the center of the bounding box + // defined by the projected intervals. + merge.center += line.direction*(((Real)0.5)*(minIntr + maxIntr)); + + // Compute the extents of the box based on the new center. + merge.extent[j] = ((Real)0.5)*(maxIntr - minIntr); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2MinCR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2MinCR.h new file mode 100644 index 000000000000..38dc5e541414 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipse2MinCR.h @@ -0,0 +1,233 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Compute the minimum-area ellipse, (X-C)^T R D R^T (X-C) = 1, given the +// center C and the orientation matrix R. The columns of R are the axes of +// the ellipse. The algorithm computes the diagonal matrix D. The minimum +// area is pi/sqrt(D[0]*D[1]), where D = diag(D[0],D[1]). The problem is +// equivalent to maximizing the product D[0]*D[1] for a given C and R, and +// subject to the constraints +// (P[i]-C)^T R D R^T (P[i]-C) <= 1 +// for all input points P[i] with 0 <= i < N. Each constraint has the form +// A[0]*D[0] + A[1]*D[1] <= 1 +// where A[0] >= 0 and A[1] >= 0. + +namespace gte +{ + +template +class ContEllipse2MinCR +{ +public: + void operator()(int numPoints, Vector2 const* points, + Vector2 const& C, Matrix2x2 const& R, Real D[2]) const; + +private: + static void MaxProduct(std::vector>& A, Real D[2]); +}; + + +template +void ContEllipse2MinCR::operator()(int numPoints, + Vector2 const* points, Vector2 const& C, + Matrix2x2const & R, Real D[2]) const +{ + // Compute the constraint coefficients, of the form (A[0],A[1]) for + // each i. + std::vector> A(numPoints); + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - C; // P[i] - C + Vector2 prod = diff*R; // R^T*(P[i] - C) = (u,v) + A[i] = prod * prod; // (u^2, v^2) + } + + // Sort to eliminate redundant constraints. Use a lexicographical sort, + // (x0,y0) > (x1,y1) if x0 > x1 or if x0 = x1 and y0 > y1. Remove all + // but the first entry in blocks with x0 = x1 because the corresponding + // constraint lines for the first entry "hides" all the others from the + // origin. + std::sort(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + if (P0[0] > P1[0]) { return true; } + if (P0[0] < P1[0]) { return false; } + return P0[1] > P1[1]; + } + ); + auto end = std::unique(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + return P0[0] == P1[0]; + } + ); + A.erase(end, A.end()); + + // Lexicographical sort, (x0,y0) > (x1,y1) if y0 > y1 or if y0 = y1 and + // x0 > x1. Remove all but first entry in blocks with y0 = y1 since the + // corresponding constraint lines for the first entry "hides" all the + // others from the origin. + std::sort(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + if (P0[1] > P1[1]) + { + return true; + } + + if (P0[1] < P1[1]) + { + return false; + } + + return P0[0] > P1[0]; + } + ); + end = std::unique(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + return P0[1] == P1[1]; + } + ); + A.erase(end, A.end()); + + MaxProduct(A, D); +} + +template +void ContEllipse2MinCR::MaxProduct(std::vector>& A, + Real D[2]) +{ + // Keep track of which constraint lines have already been used in the + // search. + int numConstraints = static_cast(A.size()); + std::vector used(A.size()); + std::fill(used.begin(), used.end(), false); + + // Find the constraint line whose y-intercept (0,ymin) is closest to the + // origin. This line contributes to the convex hull of the constraints + // and the search for the maximum starts here. Also find the constraint + // line whose x-intercept (xmin,0) is closest to the origin. This line + // contributes to the convex hull of the constraints and the search for + // the maximum terminates before or at this line. + int i, iYMin = -1; + int iXMin = -1; + Real axMax = (Real)0, ayMax = (Real)0; // A[i] >= (0,0) by design + for (i = 0; i < numConstraints; ++i) + { + // The minimum x-intercept is 1/A[iXMin][0] for A[iXMin][0] the + // maximum of the A[i][0]. + if (A[i][0] > axMax) + { + axMax = A[i][0]; + iXMin = i; + } + + // The minimum y-intercept is 1/A[iYMin][1] for A[iYMin][1] the + // maximum of the A[i][1]. + if (A[i][1] > ayMax) + { + ayMax = A[i][1]; + iYMin = i; + } + } + LogAssert(iXMin != -1 && iYMin != -1, "Unexpected condition."); + used[iYMin] = true; + + // The convex hull is searched in a clockwise manner starting with the + // constraint line constructed above. The next vertex of the hull occurs + // as the closest point to the first vertex on the current constraint + // line. The following loop finds each consecutive vertex. + Real x0 = (Real)0, xMax = ((Real)1) / axMax; + int j; + for (j = 0; j < numConstraints; ++j) + { + // Find the line whose intersection with the current line is closest + // to the last hull vertex. The last vertex is at (x0,y0) on the + // current line. + Real x1 = xMax; + int line = -1; + for (i = 0; i < numConstraints; ++i) + { + if (!used[i]) + { + // This line not yet visited, process it. Given current + // constraint line a0*x+b0*y =1 and candidate line + // a1*x+b1*y = 1, find the point of intersection. The + // determinant of the system is d = a0*b1-a1*b0. We only + // care about lines that have more negative slope than the + // previous one, that is, -a1/b1 < -a0/b0, in which case we + // process only lines for which d < 0. + Real det = DotPerp(A[iYMin], A[i]); + if (det < (Real)0) + { + // Compute the x-value for the point of intersection, + // (x1,y1). There may be floating point error issues in + // the comparision 'D[0] <= fX1'. Consider modifying to + // 'D[0] <= fX1+epsilon'. + D[0] = (A[i][1] - A[iYMin][1]) / det; + if (x0 < D[0] && D[0] <= x1) + { + line = i; + x1 = D[0]; + } + } + } + } + + // Next vertex is at (x1,y1) whose x-value was computed above. First + // check for the maximum of x*y on the current line for x in [x0,x1]. + // On this interval the function is f(x) = x*(1-a0*x)/b0. The + // derivative is f'(x) = (1-2*a0*x)/b0 and f'(r) = 0 when + // r = 1/(2*a0). The three candidates for the maximum are f(x0), + // f(r), and f(x1). Comparisons are made between r and the end points + // x0 and x1. Since a0 = 0 is possible (constraint line is horizontal + // and f is increasing on line), the division in r is not performed + // and the comparisons are made between 1/2 = a0*r and a0*x0 or a0*x1. + + // Compare r < x0. + if ((Real)0.5 < A[iYMin][0] * x0) + { + // The maximum is f(x0) since the quadratic f decreases for + // x > r. + D[0] = x0; + D[1] = ((Real)1 - A[iYMin][0] * D[0]) / A[iYMin][1]; // = f(x0) + break; + } + + // Compare r < x1. + if ((Real)0.5 < A[iYMin][0] * x1) + { + // The maximum is f(r). The search ends here because the + // current line is tangent to the level curve of f(x)=f(r) + // and x*y can therefore only decrease as we traverse further + // around the hull in the clockwise direction. + D[0] = ((Real)0.5) / A[iYMin][0]; + D[1] = ((Real)0.5) / A[iYMin][1]; // = f(r) + break; + } + + // The maximum is f(x1). The function x*y is potentially larger + // on the next line, so continue the search. + LogAssert(line != -1, "Unexpected condition."); + x0 = x1; + x1 = xMax; + used[line] = true; + iYMin = line; + } + + LogAssert(j < numConstraints, "Unexpected condition."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3.h new file mode 100644 index 000000000000..c3bedcef3bf1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// The input points are fit with a Gaussian distribution. The center C of the +// ellipsoid is chosen to be the mean of the distribution. The axes of the +// ellipsoid are chosen to be the eigenvectors of the covariance matrix M. +// The shape of the ellipsoid is determined by the absolute values of the +// eigenvalues. NOTE: The construction is ill-conditioned if the points are +// (nearly) collinear or (nearly) planar. In this case M has a (nearly) zero +// eigenvalue, so inverting M is problematic. +template +bool GetContainer(int numPoints, Vector3 const* points, + Ellipsoid3& ellipsoid); + +// Test for containment of a point inside an ellipsoid. +template +bool InContainer(Vector3 const& point, + Ellipsoid3 const& ellipsoid); + +// Construct a bounding ellipsoid for the two input ellipsoids. The result is +// not necessarily the minimum-volume ellipsoid containing the two ellipsoids. +template +bool MergeContainers(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1, Ellipsoid3& merge); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + Ellipsoid3& ellipsoid) +{ + // Fit the points with a Gaussian distribution. The covariance matrix + // is M = sum_j D[j]*U[j]*U[j]^T, where D[j] are the eigenvalues and U[j] + // are corresponding unit-length eigenvectors. + ApprGaussian3 fitter; + if (fitter.Fit(numPoints, points)) + { + OrientedBox3 box = fitter.GetParameters(); + + // If either eigenvalue is nonpositive, adjust the D[] values so that + // we actually build an ellipse. + for (int j = 0; j < 3; ++j) + { + if (box.extent[j] < (Real)0) + { + box.extent[j] = -box.extent[j]; + } + } + + // Grow the ellipsoid, while retaining its shape determined by the + // covariance matrix, to enclose all the input points. The quadratic + // form that is used for the ellipsoid construction is + // Q(X) = (X-C)^T*M*(X-C) + // = (X-C)^T*(sum_j D[j]*U[j]*U[j]^T)*(X-C) + // = sum_j D[j]*Dot(U[j],X-C)^2 + // If the maximum value of Q(X[i]) for all input points is V^2, then a + // bounding ellipsoid is Q(X) = V^2 since Q(X[i]) <= V^2 for all i. + + Real maxValue = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - box.center; + Real dot[3] = + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff), + Dot(box.axis[2], diff) + }; + + Real value = + box.extent[0] * dot[0] * dot[0] + + box.extent[1] * dot[1] * dot[1] + + box.extent[2] * dot[2] * dot[2]; + + if (value > maxValue) + { + maxValue = value; + } + } + + // Arrange for the quadratic to satisfy Q(X) <= 1. + ellipsoid.center = box.center; + for (int j = 0; j < 3; ++j) + { + ellipsoid.axis[j] = box.axis[j]; + ellipsoid.extent[j] = std::sqrt(maxValue / box.extent[j]); + } + return true; + } + + return false; +} + +template +bool InContainer(Vector3 const& point, + Ellipsoid3 const& ellipsoid) +{ + Vector3 diff = point - ellipsoid.center; + Vector3 standardized{ + Dot(diff, ellipsoid.axis[0]) / ellipsoid.extent[0], + Dot(diff, ellipsoid.axis[1]) / ellipsoid.extent[1], + Dot(diff, ellipsoid.axis[2]) / ellipsoid.extent[2] }; + return Length(standardized) <= (Real)1; +} + +template +bool MergeContainers(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1, Ellipsoid3& merge) +{ + // Compute the average of the input centers + merge.center = ((Real)0.5)*(ellipsoid0.center + ellipsoid1.center); + + // The bounding ellipsoid orientation is the average of the input + // orientations. + Matrix3x3 rot0, rot1; + rot0.SetCol(0, ellipsoid0.axis[0]); + rot0.SetCol(1, ellipsoid0.axis[1]); + rot0.SetCol(2, ellipsoid0.axis[2]); + rot1.SetCol(0, ellipsoid1.axis[0]); + rot1.SetCol(1, ellipsoid1.axis[1]); + rot1.SetCol(2, ellipsoid1.axis[2]); + Quaternion q0 = Rotation<3, Real>(rot0); + Quaternion q1 = Rotation<3, Real>(rot1); + if (Dot(q0, q1) < (Real)0) + { + q1 = -q1; + } + + Quaternion q = q0 + q1; + Normalize(q); + Matrix3x3 rot = Rotation<3, Real>(q); + for (int j = 0; j < 3; ++j) + { + merge.axis[j] = rot.GetCol(j); + } + + // Project the input ellipsoids onto the axes obtained by the average + // of the orientations and that go through the center obtained by the + // average of the centers. + for (int i = 0; i < 3; ++i) + { + // Projection axis. + Line3 line(merge.center, merge.axis[i]); + + // Project ellipsoids onto the axis. + Real min0, max0, min1, max1; + Project(ellipsoid0, line, min0, max0); + Project(ellipsoid1, line, min1, max1); + + // Determine the smallest interval containing the projected + // intervals. + Real maxIntr = (max0 >= max1 ? max0 : max1); + Real minIntr = (min0 <= min1 ? min0 : min1); + + // Update the average center to be the center of the bounding box + // defined by the projected intervals. + merge.center += line.direction*(((Real)0.5)*(minIntr + maxIntr)); + + // Compute the extents of the box based on the new center. + merge.extent[i] = ((Real)0.5)*(maxIntr - minIntr); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3MinCR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3MinCR.h new file mode 100644 index 000000000000..814a0baae4d4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContEllipsoid3MinCR.h @@ -0,0 +1,331 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Compute the minimum-volume ellipsoid, (X-C)^T R D R^T (X-C) = 1, given the +// center C and orientation matrix R. The columns of R are the axes of the +// ellipsoid. The algorithm computes the diagonal matrix D. The minimum +// volume is (4*pi/3)/sqrt(D[0]*D[1]*D[2]), where D = diag(D[0],D[1],D[2]). +// The problem is equivalent to maximizing the product D[0]*D[1]*D[2] for a +// given C and R, and subject to the constraints +// (P[i]-C)^T R D R^T (P[i]-C) <= 1 +// for all input points P[i] with 0 <= i < N. Each constraint has the form +// A[0]*D[0] + A[1]*D[1] + A[2]*D[2] <= 1 +// where A[0] >= 0, A[1] >= 0, and A[2] >= 0. + +namespace gte +{ + +template +class ContEllipsoid3MinCR +{ +public: + void operator()(int numPoints, Vector3 const* points, + Vector3 const& C, Matrix3x3 const& R, Real D[3]); + +private: + void FindEdgeMax(std::vector>& A, int& plane0, + int& plane1, Real D[3]); + + void FindFacetMax(std::vector>& A, int& plane0, + Real D[3]); + + void MaxProduct(std::vector>& A, Real D[3]); +}; + + +template +void ContEllipsoid3MinCR::operator()(int numPoints, + Vector3 const* points, Vector3 const& C, + Matrix3x3 const& R, Real D[3]) +{ + // Compute the constraint coefficients, of the form (A[0],A[1]) for + // each i. + std::vector> A(numPoints); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - C; // P[i] - C + Vector3 prod = diff*R; // R^T*(P[i] - C) = (u,v,w) + A[i] = prod * prod; // (u^2, v^2, w^2) + } + + // TODO: Sort the constraints to eliminate redundant ones. It is clear + // how to do this in ContEllipse2MinCR. How to do this in 3D? + + MaxProduct(A, D); +} + +template +void ContEllipsoid3MinCR::FindEdgeMax(std::vector>& A, + int& plane0, int& plane1, Real D[3]) +{ + // Compute direction to local maximum point on line of intersection. + Real xDir = A[plane0][1] * A[plane1][2] - A[plane1][1] * A[plane0][2]; + Real yDir = A[plane0][2] * A[plane1][0] - A[plane1][2] * A[plane0][0]; + Real zDir = A[plane0][0] * A[plane1][1] - A[plane1][0] * A[plane0][1]; + + // Build quadratic Q'(t) = (d/dt)(x(t)y(t)z(t)) = a0+a1*t+a2*t^2. + Real a0 = D[0] * D[1] * zDir + D[0] * D[2] * yDir + D[1] * D[2] * xDir; + Real a1 = ((Real)2)*(D[2] * xDir*yDir + D[1] * xDir*zDir + D[0] * yDir*zDir); + Real a2 = ((Real)3)*(xDir*yDir*zDir); + + // Find root to Q'(t) = 0 corresponding to maximum. + Real tFinal; + if (a2 != (Real)0) + { + Real invA2 = ((Real)1) / a2; + Real discr = a1*a1 - ((Real)4)*a0*a2; + discr = std::sqrt(std::max(discr, (Real)0)); + tFinal = -((Real)0.5)*(a1 + discr)*invA2; + if (a1 + ((Real)2)*a2*tFinal > (Real)0) + { + tFinal = ((Real)0.5)*(-a1 + discr)*invA2; + } + } + else if (a1 != (Real)0) + { + tFinal = -a0 / a1; + } + else if (a0 != (Real)0) + { + Real fmax = std::numeric_limits::max(); + tFinal = (a0 >= (Real)0 ? fmax : -fmax); + } + else + { + return; + } + + if (tFinal < (Real)0) + { + // Make (xDir,yDir,zDir) point in direction of increase of Q. + tFinal = -tFinal; + xDir = -xDir; + yDir = -yDir; + zDir = -zDir; + } + + // Sort remaining planes along line from current point to local maximum. + Real tMax = tFinal; + int plane2 = -1; + int numPoints = static_cast(A.size()); + for (int i = 0; i < numPoints; ++i) + { + if (i == plane0 || i == plane1) + { + continue; + } + + Real norDotDir = A[i][0] * xDir + A[i][1] * yDir + A[i][2] * zDir; + if (norDotDir <= (Real)0) + { + continue; + } + + // Theoretically the numerator must be nonnegative since an invariant + // in the algorithm is that (x0,y0,z0) is on the convex hull of the + // constraints. However, some numerical error may make this a small + // negative number. In that case set tmax = 0 (no change in + // position). + Real numer = (Real)1 - A[i][0] * D[0] - A[i][1] * D[1] - A[i][2] * D[2]; + if (numer < (Real)0) + { + LogError("Unexpected condition."); + plane2 = i; + tMax = (Real)0; + break; + } + + Real t = numer / norDotDir; + if (0 <= t && t < tMax) + { + plane2 = i; + tMax = t; + } + } + + D[0] += tMax*xDir; + D[1] += tMax*yDir; + D[2] += tMax*zDir; + + if (tMax == tFinal) + { + return; + } + + if (tMax > (Real)0) + { + plane0 = plane2; + FindFacetMax(A, plane0, D); + return; + } + + // tmax == 0, so return with D[0], D[1], and D[2] unchanged. +} + +template +void ContEllipsoid3MinCR::FindFacetMax(std::vector>& A, + int& plane0, Real D[3]) +{ + Real tFinal, xDir, yDir, zDir; + + if (A[plane0][0] > (Real)0 + && A[plane0][1] > (Real)0 + && A[plane0][2] > (Real)0) + { + // Compute local maximum point on plane. + Real oneThird = ((Real)1) / (Real)3; + Real xMax = oneThird / A[plane0][0]; + Real yMax = oneThird / A[plane0][1]; + Real zMax = oneThird / A[plane0][2]; + + // Compute direction to local maximum point on plane. + tFinal = (Real)1; + xDir = xMax - D[0]; + yDir = yMax - D[1]; + zDir = zMax - D[2]; + } + else + { + tFinal = std::numeric_limits::max(); + + if (A[plane0][0] > (Real)0) + { + xDir = (Real)0; + } + else + { + xDir = (Real)1; + } + + if (A[plane0][1] > (Real)0) + { + yDir = (Real)0; + } + else + { + yDir = (Real)1; + } + + if (A[plane0][2] > (Real)0) + { + zDir = (Real)0; + } + else + { + zDir = (Real)1; + } + } + + // Sort remaining planes along line from current point. + Real tMax = tFinal; + int plane1 = -1; + int numPoints = static_cast(A.size()); + for (int i = 0; i < numPoints; ++i) + { + if (i == plane0) + { + continue; + } + + Real norDotDir = A[i][0] * xDir + A[i][1] * yDir + A[i][2] * zDir; + if (norDotDir <= (Real)0) + { + continue; + } + + // Theoretically the numerator must be nonnegative since an invariant + // in the algorithm is that (x0,y0,z0) is on the convex hull of the + // constraints. However, some numerical error may make this a small + // negative number. In that case, set tmax = 0 (no change in + // position). + Real numer = (Real)1 - A[i][0] * D[0] - A[i][1] * D[1] - A[i][2] * D[2]; + if (numer < (Real)0) + { + LogError("Unexpected condition."); + plane1 = i; + tMax = (Real)0; + break; + } + + Real t = numer / norDotDir; + if (0 <= t && t < tMax) + { + plane1 = i; + tMax = t; + } + } + + D[0] += tMax*xDir; + D[1] += tMax*yDir; + D[2] += tMax*zDir; + + if (tMax == (Real)1) + { + return; + } + + if (tMax > (Real)0) + { + plane0 = plane1; + FindFacetMax(A, plane0, D); + return; + } + + FindEdgeMax(A, plane0, plane1, D); +} + +template +void ContEllipsoid3MinCR::MaxProduct(std::vector>& A, + Real D[3]) +{ + // Maximize x*y*z subject to x >= 0, y >= 0, z >= 0, and + // A[i]*x+B[i]*y+C[i]*z <= 1 for 0 <= i < N where A[i] >= 0, + // B[i] >= 0, and C[i] >= 0. + + // Jitter the lines to avoid cases where more than three planes + // intersect at the same point. Should also break parallelism + // and planes parallel to the coordinate planes. + std::mt19937 mte; + std::uniform_real_distribution rnd((Real)0, (Real)1); + Real maxJitter = (Real)1e-12; + int numPoints = static_cast(A.size()); + int i; + for (i = 0; i < numPoints; ++i) + { + A[i][0] += maxJitter*rnd(mte); + A[i][1] += maxJitter*rnd(mte); + A[i][2] += maxJitter*rnd(mte); + } + + // Sort lines along the z-axis (x = 0 and y = 0). + int plane = -1; + Real zmax = (Real)0; + for (i = 0; i < numPoints; ++i) + { + if (A[i][2] > zmax) + { + zmax = A[i][2]; + plane = i; + } + } + LogAssert(plane != -1, "Unexpected condition."); + + // Walk along convex hull searching for maximum. + D[0] = (Real)0; + D[1] = (Real)0; + D[2] = ((Real)1) / zmax; + FindFacetMax(A, plane, D); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox2.h new file mode 100644 index 000000000000..0a7c0d6017ec --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox2.h @@ -0,0 +1,186 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// Compute an oriented bounding box of the points. The box center is the +// average of the points. The box axes are the eigenvectors of the covariance +// matrix. +template +bool GetContainer(int numPoints, Vector2 const* points, + OrientedBox2& box); + +// Test for containment. Let X = C + y0*U0 + y1*U1 where C is the box center +// and U0 and U1 are the orthonormal axes of the box. X is in the box when +// |y_i| <= E_i for all i, where E_i are the extents of the box. +template +bool InContainer(Vector2 const& point, OrientedBox2 const& box); + +// Construct an oriented box that contains two other oriented boxes. The +// result is not guaranteed to be the minimum area box containing the input +// boxes. +template +bool MergeContainers(OrientedBox2 const& box0, + OrientedBox2 const& box1, OrientedBox2& merge); + + +template +bool GetContainer(int numPoints, Vector2 const* points, + OrientedBox2& box) +{ + // Fit the points with a Gaussian distribution. + ApprGaussian2 fitter; + if (fitter.Fit(numPoints, points)) + { + box = fitter.GetParameters(); + + // Let C be the box center and let U0 and U1 be the box axes. Each + // input point is of the form X = C + y0*U0 + y1*U1. The following + // code computes min(y0), max(y0), min(y1), and max(y1). The box + // center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + Vector2 diff = points[0] - box.center; + Vector2 pmin{ Dot(diff, box.axis[0]), Dot(diff, box.axis[1]) }; + Vector2 pmax = pmin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + for (int j = 0; j < 2; ++j) + { + box.center += (((Real)0.5)*(pmin[j] + pmax[j]))*box.axis[j]; + box.extent[j] = ((Real)0.5)*(pmax[j] - pmin[j]); + } + return true; + } + + return false; +} + +template +bool InContainer(Vector2 const& point, OrientedBox2 const& box) +{ + Vector2 diff = point - box.center; + for (int i = 0; i < 2; ++i) + { + Real coeff = Dot(diff, box.axis[i]); + if (std::abs(coeff) > box.extent[i]) + { + return false; + } + } + return true; +} + +template +bool MergeContainers(OrientedBox2 const& box0, + OrientedBox2 const& box1, OrientedBox2& merge) +{ + // The first guess at the box center. This value will be updated later + // after the input box vertices are projected onto axes determined by an + // average of box axes. + merge.center = ((Real)0.5)*(box0.center + box1.center); + + // The merged box axes are the averages of the input box axes. The axes + // of the second box are negated, if necessary, so they form acute angles + // with the axes of the first box. + if (Dot(box0.axis[0], box1.axis[0]) >= (Real)0) + { + merge.axis[0] = ((Real)0.5)*(box0.axis[0] + box1.axis[0]); + } + else + { + merge.axis[0] = ((Real)0.5)*(box0.axis[0] - box1.axis[0]); + } + Normalize(merge.axis[0]); + merge.axis[1] = -Perp(merge.axis[0]); + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // min[i] and a maximum projected value max[i]. The corresponding + // endpoints on the axes are C+min[i]*D[i] and C+max[i]*D[i]. The point + // C is not necessarily the midpoint for any of the intervals. The actual + // box center will be adjusted from C to a point C' that is the midpoint + // of each interval, + // C' = C + sum_{i=0}^1 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + std::array, 4> vertex; + Vector2 pmin{ (Real)0, (Real)0 }; + Vector2 pmax{ (Real)0, (Real)0 }; + + box0.GetVertices(vertex); + for (int i = 0; i < 4; ++i) + { + Vector2 diff = vertex[i] - merge.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + box1.GetVertices(vertex); + for (int i = 0; i < 4; ++i) + { + Vector2 diff = vertex[i] - merge.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + Real const half = (Real)0.5; + for (int j = 0; j < 2; ++j) + { + merge.center += half*(pmax[j] + pmin[j])*merge.axis[j]; + merge.extent[j] = half*(pmax[j] - pmin[j]); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox3.h new file mode 100644 index 000000000000..1b4840b157b8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContOrientedBox3.h @@ -0,0 +1,204 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// Compute an oriented bounding box of the points. The box center is the +// average of the points. The box axes are the eigenvectors of the covariance +// matrix. +template +bool GetContainer(int numPoints, Vector3 const* points, + OrientedBox3& box); + +// Test for containment. Let X = C + y0*U0 + y1*U1 + y2*U2 where C is the +// box center and U0, U1, U2 are the orthonormal axes of the box. X is in +// the box if |y_i| <= E_i for all i where E_i are the extents of the box. +template +bool InContainer(Vector3 const& point, OrientedBox3 const& box); + +// Construct an oriented box that contains two other oriented boxes. The +// result is not guaranteed to be the minimum volume box containing the +// input boxes. +template +bool MergeContainers(OrientedBox3 const& box0, + OrientedBox3 const& box1, OrientedBox3& merge); + + +template +bool GetContainer(int numPoints, Vector3 const* points, + OrientedBox3& box) +{ + // Fit the points with a Gaussian distribution. + ApprGaussian3 fitter; + if (fitter.Fit(numPoints, points)) + { + box = fitter.GetParameters(); + + // Let C be the box center and let U0, U1, and U2 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. + // The following code computes min(y0), max(y0), min(y1), max(y1), + // min(y2), and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + + Vector3 diff = points[0] - box.center; + Vector3 pmin{ Dot(diff, box.axis[0]), Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) }; + Vector3 pmax = pmin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + for (int j = 0; j < 3; ++j) + { + box.center += (((Real)0.5)*(pmin[j] + pmax[j]))*box.axis[j]; + box.extent[j] = ((Real)0.5)*(pmax[j] - pmin[j]); + } + return true; + } + + return false; +} + +template +bool InContainer(Vector3 const& point, OrientedBox3 const& box) +{ + Vector3 diff = point - box.center; + for (int i = 0; i < 3; ++i) + { + Real coeff = Dot(diff, box.axis[i]); + if (std::abs(coeff) > box.extent[i]) + { + return false; + } + } + return true; +} + +template +bool MergeContainers(OrientedBox3 const& box0, + OrientedBox3 const& box1, OrientedBox3& merge) +{ + // The first guess at the box center. This value will be updated later + // after the input box vertices are projected onto axes determined by an + // average of box axes. + merge.center = ((Real)0.5)*(box0.center + box1.center); + + // A box's axes, when viewed as the columns of a matrix, form a rotation + // matrix. The input box axes are converted to quaternions. The average + // quaternion is computed, then normalized to unit length. The result is + // the slerp of the two input quaternions with t-value of 1/2. The result + // is converted back to a rotation matrix and its columns are selected as + // the merged box axes. + Matrix3x3 rot0, rot1; + rot0.SetCol(0, box0.axis[0]); + rot0.SetCol(1, box0.axis[1]); + rot0.SetCol(2, box0.axis[2]); + rot1.SetCol(0, box1.axis[0]); + rot1.SetCol(1, box1.axis[1]); + rot1.SetCol(2, box1.axis[2]); + Quaternion q0 = Rotation<3, Real>(rot0); + Quaternion q1 = Rotation<3, Real>(rot1); + if (Dot(q0, q1) < (Real)0) + { + q1 = -q1; + } + + Quaternion q = q0 + q1; + Normalize(q); + Matrix3x3 rot = Rotation<3, Real>(q); + for (int j = 0; j < 3; ++j) + { + merge.axis[j] = rot.GetCol(j); + } + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // min[i] and a maximum projected value max[i]. The corresponding end + // points on the axes are C+min[i]*D[i] and C+max[i]*D[i]. The point C + // is not necessarily the midpoint for any of the intervals. The actual + // box center will be adjusted from C to a point C' that is the midpoint + // of each interval, + // C' = C + sum_{i=0}^2 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + std::array, 8> vertex; + Vector3 pmin{ (Real)0, (Real)0, (Real)0 }; + Vector3 pmax{ (Real)0, (Real)0, (Real)0 }; + + box0.GetVertices(vertex); + for (int i = 0; i < 8; ++i) + { + Vector3 diff = vertex[i] - merge.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + box1.GetVertices(vertex); + for (int i = 0; i < 8; ++i) + { + Vector3 diff = vertex[i] - merge.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + Real const half = (Real)0.5; + for (int j = 0; j < 3; ++j) + { + merge.center += half*(pmax[j] + pmin[j])*merge.axis[j]; + merge.extent[j] = half*(pmax[j] - pmin[j]); + } + + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolygon2.h new file mode 100644 index 000000000000..c5b0460b32bd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolygon2.h @@ -0,0 +1,227 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Given a polygon as an order list of vertices (x[i],y[i]) for 0 <= i < N +// and a test point (xt,yt), return 'true' if (xt,yt) is in the polygon and +// 'false' if it is not. All queries require that the number of vertices +// satisfies N >= 3. + +namespace gte +{ + +template +class PointInPolygon2 +{ +public: + // The class object stores a copy of 'points', so be careful about the + // persistence of 'points' when you have created a PointInPolygon2 object. + PointInPolygon2(int numPoints, Vector2 const* points); + + // Simple polygons (ray-intersection counting). + bool Contains(Vector2 const& p) const; + + // Algorithms for convex polygons. The input polygons must have vertices + // in counterclockwise order. + + // O(N) algorithm (which-side-of-edge tests) + bool ContainsConvexOrderN(Vector2 const& p) const; + + // O(log N) algorithm (bisection and recursion, like BSP tree) + bool ContainsConvexOrderLogN(Vector2 const& p) const; + + // The polygon must have exactly four vertices. This method is like the + // O(log N) and uses three which-side-of-segment test instead of four + // which-side-of-edge tests. If the polygon does not have four vertices, + // the function returns false. + bool ContainsQuadrilateral(Vector2 const& p) const; + +private: + // For recursion in ContainsConvexOrderLogN. + bool SubContainsPoint(Vector2 const& p, int i0, int i1) const; + + int mNumPoints; + Vector2 const* mPoints; +}; + + +template +PointInPolygon2::PointInPolygon2(int numPoints, + Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) +{ +} + +template +bool PointInPolygon2::Contains(Vector2 const& p) const +{ + bool inside = false; + for (int i = 0, j = mNumPoints - 1; i < mNumPoints; j = i++) + { + Vector2 const& U0 = mPoints[i]; + Vector2 const& U1 = mPoints[j]; + Real rhs, lhs; + + if (p[1] < U1[1]) // U1 above ray + { + if (U0[1] <= p[1]) // U0 on or below ray + { + lhs = (p[1] - U0[1])*(U1[0] - U0[0]); + rhs = (p[0] - U0[0])*(U1[1] - U0[1]); + if (lhs > rhs) + { + inside = !inside; + } + } + } + else if (p[1] < U0[1]) // U1 on or below ray, U0 above ray + { + lhs = (p[1] - U0[1])*(U1[0] - U0[0]); + rhs = (p[0] - U0[0])*(U1[1] - U0[1]); + if (lhs < rhs) + { + inside = !inside; + } + } + } + return inside; +} + +template +bool PointInPolygon2::ContainsConvexOrderN(Vector2 const& p) const +{ + for (int i1 = 0, i0 = mNumPoints - 1; i1 < mNumPoints; i0 = i1++) + { + Real nx = mPoints[i1][1] - mPoints[i0][1]; + Real ny = mPoints[i0][0] - mPoints[i1][0]; + Real dx = p[0] - mPoints[i0][0]; + Real dy = p[1] - mPoints[i0][1]; + if (nx*dx + ny*dy >(Real)0) + { + return false; + } + } + return true; +} + +template +bool PointInPolygon2::ContainsConvexOrderLogN(Vector2 const& p) +const +{ + return SubContainsPoint(p, 0, 0); +} + +template +bool PointInPolygon2::ContainsQuadrilateral(Vector2 const& p) +const +{ + if (mNumPoints != 4) + { + return false; + } + + Real nx = mPoints[2][1] - mPoints[0][1]; + Real ny = mPoints[0][0] - mPoints[2][0]; + Real dx = p[0] - mPoints[0][0]; + Real dy = p[1] - mPoints[0][1]; + + if (nx*dx + ny*dy > (Real)0) + { + // P potentially in + nx = mPoints[1][1] - mPoints[0][1]; + ny = mPoints[0][0] - mPoints[1][0]; + if (nx*dx + ny*dy > (Real)0.0) + { + return false; + } + + nx = mPoints[2][1] - mPoints[1][1]; + ny = mPoints[1][0] - mPoints[2][0]; + dx = p[0] - mPoints[1][0]; + dy = p[1] - mPoints[1][1]; + if (nx*dx + ny*dy > (Real)0) + { + return false; + } + } + else + { + // P potentially in + nx = mPoints[0][1] - mPoints[3][1]; + ny = mPoints[3][0] - mPoints[0][0]; + if (nx*dx + ny*dy > (Real)0) + { + return false; + } + + nx = mPoints[3][1] - mPoints[2][1]; + ny = mPoints[2][0] - mPoints[3][0]; + dx = p[0] - mPoints[3][0]; + dy = p[1] - mPoints[3][1]; + if (nx*dx + ny*dy > (Real)0) + { + return false; + } + } + return true; +} + +template +bool PointInPolygon2::SubContainsPoint(Vector2 const& p, int i0, + int i1) const +{ + Real nx, ny, dx, dy; + + int diff = i1 - i0; + if (diff == 1 || (diff < 0 && diff + mNumPoints == 1)) + { + nx = mPoints[i1][1] - mPoints[i0][1]; + ny = mPoints[i0][0] - mPoints[i1][0]; + dx = p[0] - mPoints[i0][0]; + dy = p[1] - mPoints[i0][1]; + return nx*dx + ny*dy <= (Real)0; + } + + // Bisect the index range. + int mid; + if (i0 < i1) + { + mid = (i0 + i1) >> 1; + } + else + { + mid = ((i0 + i1 + mNumPoints) >> 1); + if (mid >= mNumPoints) + { + mid -= mNumPoints; + } + } + + // Determine which side of the splitting line contains the point. + nx = mPoints[mid][1] - mPoints[i0][1]; + ny = mPoints[i0][0] - mPoints[mid][0]; + dx = p[0] - mPoints[i0][0]; + dy = p[1] - mPoints[i0][1]; + if (nx*dx + ny*dy > (Real)0) + { + // P potentially in + return SubContainsPoint(p, i0, mid); + } + else + { + // P potentially in + return SubContainsPoint(p, mid, i1); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolyhedron3.h new file mode 100644 index 000000000000..ea3bbd16ebbc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContPointInPolyhedron3.h @@ -0,0 +1,613 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/11/25) + +#pragma once + +#include +#include +#include +#include +#include + +// This class contains various implementations for point-in-polyhedron +// queries. The planes stored with the faces are used in all cases to +// reject ray-face intersection tests, a quick culling operation. +// +// The algorithm is to cast a ray from the input point P and test for +// intersection against each face of the polyhedron. If the ray only +// intersects faces at interior points (not vertices, not edge points), +// then the point is inside when the number of intersections is odd and +// the point is outside when the number of intersections is even. If the +// ray intersects an edge or a vertex, then the counting must be handled +// differently. The details are tedious. As an alternative, the approach +// here is to allow you to specify 2*N+1 rays, where N >= 0. You should +// choose these rays randomly. Each ray reports "inside" or "outside". +// Whichever result occurs N+1 or more times is the "winner". The input +// rayQuantity is 2*N+1. The input array Direction must have rayQuantity +// elements. If you are feeling lucky, choose rayQuantity to be 1. + +namespace gte +{ + +template +class PointInPolyhedron3 +{ +public: + // For simple polyhedra with triangle faces. + class TriangleFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + int indices[3]; + + // The normal vector is unit length and points to the outside of the + // polyhedron. + Plane3 plane; + }; + + // The Contains query will use ray-triangle intersection queries. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, TriangleFace const* faces, int numRays, + Vector3 const* directions); + + // For simple polyhedra with convex polygon faces. + class ConvexFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + std::vector indices; + + // The normal vector is unit length and points to the outside of the + // polyhedron. + Plane3 plane; + }; + + // The Contains query will use ray-convexpolygon intersection queries. A + // ray-convexpolygon intersection query can be implemented in many ways. + // In this context, uiMethod is one of three value: + // 0 : Use a triangle fan and perform a ray-triangle intersection query + // for each triangle. + // 1 : Find the point of intersection of ray and plane of polygon. Test + // whether that point is inside the convex polygon using an O(N) + // test. + // 2 : Find the point of intersection of ray and plane of polygon. Test + // whether that point is inside the convex polygon using an O(log N) + // test. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, ConvexFace const* faces, int numRays, + Vector3 const* directions, unsigned int method); + + // For simple polyhedra with simple polygon faces that are generally + // not all convex. + class SimpleFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + std::vector indices; + + // The normal vector is unit length and points to the outside of the + // polyhedron. + Plane3 plane; + + // Each simple face may be triangulated. The indices are relative to + // the vertex array. Each triple of indices represents a triangle in + // the triangulation. + std::vector triangles; + }; + + // The Contains query will use ray-simplepolygon intersection queries. A + // ray-simplepolygon intersection query can be implemented in a couple of + // ways. In this context, uiMethod is one of two value: + // 0 : Iterate over the triangles of each face and perform a + // ray-triangle intersection query for each triangle. This requires + // that the SimpleFace::Triangles array be initialized for each + // face. + // 1 : Find the point of intersection of ray and plane of polygon. Test + // whether that point is inside the polygon using an O(N) test. The + // SimpleFace::Triangles array is not used for this method, so it + // does not have to be initialized for each face. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, SimpleFace const* faces, int numRays, + Vector3 const* directions, unsigned int method); + + // This function will select the actual algorithm based on which + // constructor you used for this class. + bool Contains(Vector3 const& p) const; + +private: + // For all types of faces. The ray origin is the test point. The ray + // direction is one of those passed to the constructors. The plane origin + // is a point on the plane of the face. The plane normal is a unit-length + // normal to the face and that points outside the polyhedron. + static bool FastNoIntersect(Ray3 const& ray, + Plane3 const& plane); + + // For triangle faces. + bool ContainsT0(Vector3 const& p) const; + + // For convex faces. + bool ContainsC0(Vector3 const& p) const; + bool ContainsC1C2(Vector3 const& p, unsigned int method) const; + + // For simple faces. + bool ContainsS0(Vector3 const& p) const; + bool ContainsS1(Vector3 const& p) const; + + int mNumPoints; + Vector3 const* mPoints; + + int mNumFaces; + TriangleFace const* mTFaces; + ConvexFace const* mCFaces; + SimpleFace const* mSFaces; + + unsigned int mMethod; + int mNumRays; + Vector3 const* mDirections; + + // Temporary storage for those methods that reduce the problem to 2D + // point-in-polygon queries. The array stores the projections of + // face vertices onto the plane of the face. It is resized as needed. + mutable std::vector> mProjVertices; +}; + + +template +PointInPolyhedron3::PointInPolyhedron3(int numPoints, + Vector3 const* points, int numFaces, TriangleFace const* faces, + int numRays, Vector3 const* directions) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(faces), + mCFaces(nullptr), + mSFaces(nullptr), + mMethod(0), + mNumRays(numRays), + mDirections(directions) +{ +} + +template +PointInPolyhedron3::PointInPolyhedron3(int numPoints, + Vector3 const* points, int numFaces, ConvexFace const* faces, + int numRays, Vector3 const* directions, unsigned int method) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(nullptr), + mCFaces(faces), + mSFaces(nullptr), + mMethod(method), + mNumRays(numRays), + mDirections(directions) +{ +} + +template +PointInPolyhedron3::PointInPolyhedron3(int numPoints, + Vector3 const* points, int numFaces, SimpleFace const* faces, + int numRays, Vector3 const* directions, unsigned int method) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(nullptr), + mCFaces(nullptr), + mSFaces(faces), + mMethod(method), + mNumRays(numRays), + mDirections(directions) +{ +} + +template +bool PointInPolyhedron3::Contains(Vector3 const& p) const +{ + if (mTFaces) + { + return ContainsT0(p); + } + + if (mCFaces) + { + if (mMethod == 0) + { + return ContainsC0(p); + } + + return ContainsC1C2(p, mMethod); + } + + if (mSFaces) + { + if (mMethod == 0) + { + return ContainsS0(p); + } + + if (mMethod == 1) + { + return ContainsS1(p); + } + } + + return false; +} + +template +bool PointInPolyhedron3::FastNoIntersect(Ray3 const& ray, + Plane3 const& plane) +{ + Real planeDistance = Dot(plane.normal, ray.origin) - plane.constant; + Real planeAngle = Dot(plane.normal, ray.direction); + + if (planeDistance < (Real)0) + { + // The ray origin is on the negative side of the plane. + if (planeAngle <= (Real)0) + { + // The ray points away from the plane. + return true; + } + } + + if (planeDistance >(Real)0) + { + // The ray origin is on the positive side of the plane. + if (planeAngle >= (Real)0) + { + // The ray points away from the plane. + return true; + } + } + + return false; +} + +template +bool PointInPolyhedron3::ContainsT0(Vector3 const& p) const +{ + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + TriangleFace const* face = mTFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Get the triangle vertices. + for (int k = 0; k < 3; ++k) + { + triangle.v[k] = mPoints[face->indices[k]]; + } + + // Test for intersection. + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsC0(Vector3 const& p) const +{ + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + ConvexFace const* face = mCFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Process the triangles in a trifan of the face. + size_t numVerticesM1 = face->indices.size() - 1; + triangle.v[0] = mPoints[face->indices[0]]; + for (size_t k = 1; k < numVerticesM1; ++k) + { + triangle.v[1] = mPoints[face->indices[k]]; + triangle.v[2] = mPoints[face->indices[k + 1]]; + + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsS0(Vector3 const& p) const +{ + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + SimpleFace const* face = mSFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // The triangulation must exist to use it. + size_t numTriangles = face->triangles.size() / 3; + LogAssert(numTriangles > 0, "Triangulation must exist."); + + // Process the triangles in a triangulation of the face. + int const* currIndex = &face->triangles[0]; + for (size_t t = 0; t < numTriangles; ++t) + { + // Get the triangle vertices. + for (int k = 0; k < 3; ++k) + { + triangle.v[k] = mPoints[*currIndex++]; + } + + // Test for intersection. + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsC1C2(Vector3 const& p, + unsigned int method) const +{ + int insideCount = 0; + + FIQuery, Plane3> rpQuery; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + ConvexFace const* face = mCFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Compute the ray-plane intersection. + auto result = rpQuery(ray, face->plane); + + // If you trigger this assertion, numerical round-off errors have + // led to a discrepancy between FastNoIntersect and the Find() + // result. + LogAssert(result.intersect, "Unexpected condition."); + + // Get a coordinate system for the plane. Use vertex 0 as the + // origin. + Vector3 const& V0 = mPoints[face->indices[0]]; + Vector3 basis[3]; + basis[0] = face->plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the intersection onto the plane. + Vector3 diff = result.point - V0; + Vector2 projIntersect{ + Dot(basis[1], diff), Dot(basis[2], diff) }; + + // Project the face vertices onto the plane of the face. + if (face->indices.size() > mProjVertices.size()) + { + mProjVertices.resize(face->indices.size()); + } + + // Project the remaining vertices. Vertex 0 is always the origin. + size_t numIndices = face->indices.size(); + mProjVertices[0] = Vector2::Zero(); + for (size_t k = 1; k < numIndices; ++k) + { + diff = mPoints[face->indices[k]] - V0; + mProjVertices[k][0] = Dot(basis[1], diff); + mProjVertices[k][1] = Dot(basis[2], diff); + } + + // Test whether the intersection point is in the convex polygon. + PointInPolygon2 PIP(static_cast(mProjVertices.size()), + &mProjVertices[0]); + + if (method == 1) + { + if (PIP.ContainsConvexOrderN(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + else + { + if (PIP.ContainsConvexOrderLogN(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + +template +bool PointInPolyhedron3::ContainsS1(Vector3 const& p) const +{ + int insideCount = 0; + + FIQuery, Plane3> rpQuery; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + SimpleFace const* face = mSFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Compute the ray-plane intersection. + auto result = rpQuery(ray, face->plane); + + // If you trigger this assertion, numerical round-off errors have + // led to a discrepancy between FastNoIntersect and the Find() + // result. + LogAssert(result.intersect, "Unexpected condition."); + + // Get a coordinate system for the plane. Use vertex 0 as the + // origin. + Vector3 const& V0 = mPoints[face->indices[0]]; + Vector3 basis[3]; + basis[0] = face->plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the intersection onto the plane. + Vector3 diff = result.point - V0; + Vector2 projIntersect{ + Dot(basis[1], diff), Dot(basis[2], diff) }; + + // Project the face vertices onto the plane of the face. + if (face->indices.size() > mProjVertices.size()) + { + mProjVertices.resize(face->indices.size()); + } + + // Project the remaining vertices. Vertex 0 is always the origin. + size_t numIndices = face->indices.size(); + mProjVertices[0] = Vector2::Zero(); + for (size_t k = 1; k < numIndices; ++k) + { + diff = mPoints[face->indices[k]] - V0; + mProjVertices[k][0] = Dot(basis[1], diff); + mProjVertices[k][1] = Dot(basis[2], diff); + } + + // Test whether the intersection point is in the convex polygon. + PointInPolygon2 PIP(static_cast(mProjVertices.size()), + &mProjVertices[0]); + + if (PIP.Contains(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle2.h new file mode 100644 index 000000000000..431b11ad9604 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle2.h @@ -0,0 +1,73 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +// All functions return 'true' if the circle has been constructed, 'false' +// otherwise (input points are linearly dependent). + +// Circle circumscribing triangle. +template +bool Circumscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle); + +// Circle inscribing triangle. +template +bool Inscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle); + + +template +bool Circumscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle) +{ + Vector2 e10 = v1 - v0; + Vector2 e20 = v2 - v0; + Matrix2x2 A{ e10[0], e10[1], e20[0], e20[1] }; + Vector2 B{ ((Real)0.5)*Dot(e10, e10), ((Real)0.5)*Dot(e20, e20) }; + Vector2 solution; + if (LinearSystem::Solve(A, B, solution)) + { + circle.center = v0 + solution; + circle.radius = Length(solution); + return true; + } + return false; +} + +template +bool Inscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle) +{ + Vector2 d10 = v1 - v0; + Vector2 d20 = v2 - v0; + Vector2 d21 = v2 - v1; + Real len10 = Length(d10); + Real len20 = Length(d20); + Real len21 = Length(d21); + Real perimeter = len10 + len20 + len21; + if (perimeter > (Real)0) + { + Real inv = ((Real)1) / perimeter; + len10 *= inv; + len20 *= inv; + len21 *= inv; + circle.center = len21*v0 + len20*v1 + len10*v2; + circle.radius = inv*std::abs(DotPerp(d10, d20)); + return circle.radius > (Real)0; + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle3Sphere3.h new file mode 100644 index 000000000000..86291c4bd932 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContScribeCircle3Sphere3.h @@ -0,0 +1,189 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// All functions return 'true' if circle/sphere has been constructed, +// 'false' otherwise (input points are linearly dependent). + +// Circle circumscribing a triangle in 3D. +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle); + +// Sphere circumscribing a tetrahedron. +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere); + +// Circle inscribing a triangle in 3D. +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle); + +// Sphere inscribing tetrahedron. +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere); + + +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle) +{ + Vector3 E02 = v0 - v2; + Vector3 E12 = v1 - v2; + Real e02e02 = Dot(E02, E02); + Real e02e12 = Dot(E02, E12); + Real e12e12 = Dot(E12, E12); + Real det = e02e02*e12e12 - e02e12*e02e12; + if (det != (Real)0) + { + Real halfInvDet = ((Real)0.5) / det; + Real u0 = halfInvDet*e12e12*(e02e02 - e02e12); + Real u1 = halfInvDet*e02e02*(e12e12 - e02e12); + Vector3 tmp = u0*E02 + u1*E12; + circle.center = v2 + tmp; + circle.normal = UnitCross(E02, E12); + circle.radius = Length(tmp); + return true; + } + return false; +} + +template +bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere) +{ + Vector3 E10 = v1 - v0; + Vector3 E20 = v2 - v0; + Vector3 E30 = v3 - v0; + + Matrix3x3 A; + A.SetRow(0, E10); + A.SetRow(1, E20); + A.SetRow(2, E30); + + Vector3 B{ + ((Real)0.5)*Dot(E10, E10), + ((Real)0.5)*Dot(E20, E20), + ((Real)0.5)*Dot(E30, E30) }; + + Vector3 solution; + if (LinearSystem::Solve(A, B, solution)) + { + sphere.center = v0 + solution; + sphere.radius = Length(solution); + return true; + } + return false; +} + +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle) +{ + // Edges. + Vector3 E0 = v1 - v0; + Vector3 E1 = v2 - v1; + Vector3 E2 = v0 - v2; + + // Plane normal. + circle.normal = Cross(E1, E0); + + // Edge normals within the plane. + Vector3 N0 = UnitCross(circle.normal, E0); + Vector3 N1 = UnitCross(circle.normal, E1); + Vector3 N2 = UnitCross(circle.normal, E2); + + Real a0 = Dot(N1, E0); + if (a0 == (Real)0) + { + return false; + } + + Real a1 = Dot(N2, E1); + if (a1 == (Real)0) + { + return false; + } + + Real a2 = Dot(N0, E2); + if (a2 == (Real)0) + { + return false; + } + + Real invA0 = ((Real)1) / a0; + Real invA1 = ((Real)1) / a1; + Real invA2 = ((Real)1) / a2; + + circle.radius = ((Real)1) / (invA0 + invA1 + invA2); + circle.center = circle.radius*(invA0*v0 + invA1*v1 + invA2*v2); + Normalize(circle.normal); + return true; +} + +template +bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere) +{ + // Edges. + Vector3 E10 = v1 - v0; + Vector3 E20 = v2 - v0; + Vector3 E30 = v3 - v0; + Vector3 E21 = v2 - v1; + Vector3 E31 = v3 - v1; + + // Normals. + Vector3 N0 = Cross(E31, E21); + Vector3 N1 = Cross(E20, E30); + Vector3 N2 = Cross(E30, E10); + Vector3 N3 = Cross(E10, E20); + + // Normalize the normals. + if (Normalize(N0) == (Real)0) + { + return false; + } + if (Normalize(N1) == (Real)0) + { + return false; + } + if (Normalize(N2) == (Real)0) + { + return false; + } + if (Normalize(N3) == (Real)0) + { + return false; + } + + Matrix3x3 A; + A.SetRow(0, N1 - N0); + A.SetRow(1, N2 - N0); + A.SetRow(2, N3 - N0); + Vector3 B{ (Real)0, (Real)0, -Dot(N3, E30) }; + Vector3 solution; + if (LinearSystem::Solve(A, B, solution)) + { + sphere.center = v3 + solution; + sphere.radius = std::abs(Dot(N0, solution)); + return true; + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContSphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContSphere3.h new file mode 100644 index 000000000000..bcf246fdcbf5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteContSphere3.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/16) + +#pragma once + +#include +#include + +namespace gte +{ + // Compute the smallest bounding sphere whose center is the average of + // the input points. + template + bool GetContainer(int numPoints, Vector3 const* points, Sphere3& sphere) + { + sphere.center = points[0]; + for (int i = 1; i < numPoints; ++i) + { + sphere.center += points[i]; + } + sphere.center /= (Real)numPoints; + + sphere.radius = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - sphere.center; + Real radiusSqr = Dot(diff, diff); + if (radiusSqr > sphere.radius) + { + sphere.radius = radiusSqr; + } + } + + sphere.radius = std::sqrt(sphere.radius); + return true; + } + + // Test for containment of a point inside a sphere. + template + bool InContainer(Vector3 const& point, Sphere3 const& sphere) + { + Vector3 diff = point - sphere.center; + return Length(diff) <= sphere.radius; + } + + // Compute the smallest bounding sphere that contains the input spheres. + template + bool MergeContainers(Sphere3 const& sphere0, Sphere3 const& sphere1, Sphere3& merge) + { + Vector3 cenDiff = sphere1.center - sphere0.center; + Real lenSqr = Dot(cenDiff, cenDiff); + Real rDiff = sphere1.radius - sphere0.radius; + Real rDiffSqr = rDiff * rDiff; + + if (rDiffSqr >= lenSqr) + { + merge = (rDiff >= (Real)0 ? sphere1 : sphere0); + } + else + { + Real length = std::sqrt(lenSqr); + if (length > (Real)0) + { + Real coeff = (length + rDiff) / (((Real)2)*length); + merge.center = sphere0.center + coeff * cenDiff; + } + else + { + merge.center = sphere0.center; + } + + merge.radius = ((Real)0.5)*(length + sphere0.radius + sphere1.radius); + } + + return true; + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvertCoordinates.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvertCoordinates.h new file mode 100644 index 000000000000..c816d2f80c76 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvertCoordinates.h @@ -0,0 +1,353 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/16) + +#pragma once + +#include +#include + +// Convert points and transformations between two coordinate systems. +// The mathematics involves a change of basis. See the document +// http://www.geometrictools.com/Documentation/ConvertingBetweenCoordinateSystems.pdf +// for the details. Typical usage for 3D conversion is shown next. +// +// // Linear change of basis. The columns of U are the basis vectors for the +// // source coordinate system. A vector X = { x0, x1, x2 } in the source +// // coordinate system is represented by +// // X = x0*(1,0,0) + x1*(0,1,0) + x2*(0,0,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // X = (x0, x1, x2) +// // The columns of V are the basis vectors for the target coordinate system. +// // A vector Y = { y0, y1, y2 } in the target coordinate system is +// // represented by +// // Y = y0*(1,0,0,0) + y1*(0,0,1) + y2*(0,1,0) +// // The Cartesian coordinates for the vector are the combination of these +// // terms, +// // Y = (y0, y2, y1) +// // The call Y = convert.UToV(X) computes y0, y1 and y2 so that the Cartesian +// // coordinates for X and for Y are the same. For example, +// // X = { 1.0, 2.0, 3.0 } +// // = 1.0*(1,0,0) + 2.0*(0,1,0) + 3.0*(0,0,1) +// // = (1, 2, 3) +// // Y = { 1.0, 3.0, 2.0 } +// // = 1.0*(1,0,0) + 3.0*(0,0,1) + 2.0*(0,1,0) +// // = (1, 2, 3) +// // X and Y represent the same vector (equal Cartesian coordinates) but have +// // different representations in the source and target coordinates. +// +// ConvertCoordinates<3, double> convert; +// Vector<3, double> X, Y, P0, P1, diff; +// Matrix<3, 3, double> U, V, A, B; +// bool isRHU, isRHV; +// U.SetCol(0, Vector3{1.0, 0.0, 0.0}); +// U.SetCol(1, Vector3{0.0, 1.0, 0.0}); +// U.SetCol(2, Vector3{0.0, 0.0, 1.0}); +// V.SetCol(0, Vector3{1.0, 0.0, 0.0}); +// V.SetCol(1, Vector3{0.0, 0.0, 1.0}); +// V.SetCol(2, Vector3{0.0, 1.0, 0.0}); +// convert(U, true, V, true); +// isRHU = convert.IsRightHandedU(); // true +// isRHV = convert.IsRightHandedV(); // false +// X = { 1.0, 2.0, 3.0 }; +// Y = convert.UToV(X); // { 1.0, 3.0, 2.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// Y = { 0.0, 1.0, 2.0 }; +// X = convert.VToU(Y); // { 0.0, 2.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// double cs = 0.6, sn = 0.8; // cs*cs + sn*sn = 1 +// A.SetCol(0, Vector3{ c, s, 0.0}); +// A.SetCol(1, Vector3{ -s, c, 0.0}); +// A.SetCol(2, Vector3{0.0, 0.0, 1.0}); +// B = convert.UToV(A); +// // B.GetCol(0) = { c, 0, s} +// // B.GetCol(1) = { 0, 1, 0} +// // B.GetCol(2) = {-s, 0, c} +// X = A*X; // U is VOR +// Y = B*Y; // V is VOR +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// +// // Affine change of basis. The first three columns of U are the basis +// // vectors for the source coordinate system and must have last components +// // set to 0. The last column is the origin for that system and must have +// // last component set to 1. A point X = { x0, x1, x2, 1 } in the source +// // coordinate system is represented by +// // X = x0*(-1,0,0,0) + x1*(0,0,1,0) + x2*(0,-1,0,0) + 1*(1,2,3,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // X = (-x0 + 1, -x2 + 2, x1 + 3, 1) +// // The first three columns of V are the basis vectors for the target +// // coordinate system and must have last components set to 0. The last +// // column is the origin for that system and must have last component set +// // to 1. A point Y = { y0, y1, y2, 1 } in the target coordinate system is +// // represented by +// // Y = y0*(0,1,0,0) + y1*(-1,0,0,0) + y2*(0,0,1,0) + 1*(4,5,6,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // Y = (-y1 + 4, y0 + 5, y2 + 6, 1) +// // The call Y = convert.UToV(X) computes y0, y1 and y2 so that the Cartesian +// // coordinates for X and for Y are the same. For example, +// // X = { -1.0, 4.0, -3.0, 1.0 } +// // = -1.0*(-1,0,0,0) + 4.0*(0,0,1,0) - 3.0*(0,-1,0,0) + 1.0*(1,2,3,1) +// // = (2, 5, 7, 1) +// // Y = { 0.0, 2.0, 1.0, 1.0 } +// // = 0.0*(0,1,0,0) + 2.0*(-1,0,0,0) + 1.0*(0,0,1,0) + 1.0*(4,5,6,1) +// // = (2, 5, 7, 1) +// // X and Y represent the same point (equal Cartesian coordinates) but have +// // different representations in the source and target affine coordinates. +// +// ConvertCoordinates<4, double> convert; +// Vector<4, double> X, Y, P0, P1, diff; +// Matrix<4, 4, double> U, V, A, B; +// bool isRHU, isRHV; +// U.SetCol(0, Vector4{-1.0, 0.0, 0.0, 0.0}); +// U.SetCol(1, Vector4{0.0, 0.0, 1.0, 0.0}); +// U.SetCol(2, Vector4{0.0, -1.0, 0.0, 0.0}); +// U.SetCol(3, Vector4{1.0, 2.0, 3.0, 1.0}); +// V.SetCol(0, Vector4{0.0, 1.0, 0.0, 0.0}); +// V.SetCol(1, Vector4{-1.0, 0.0, 0.0, 0.0}); +// V.SetCol(2, Vector4{0.0, 0.0, 1.0, 0.0}); +// V.SetCol(3, Vector4{4.0, 5.0, 6.0, 1.0}); +// convert(U, true, V, false); +// isRHU = convert.IsRightHandedU(); // false +// isRHV = convert.IsRightHandedV(); // true +// X = { -1.0, 4.0, -3.0, 1.0 }; +// Y = convert.UToV(X); // { 0.0, 2.0, 1.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } +// Y = { 1.0, 2.0, 3.0, 1.0 }; +// X = convert.VToU(Y); // { -1.0, 6.0, -4.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } +// double c = 0.6, s = 0.8; // c*c + s*s = 1 +// A.SetCol(0, Vector4{ c, s, 0.0, 0.0}); +// A.SetCol(1, Vector4{ -s, c, 0.0, 0.0}); +// A.SetCol(2, Vector4{0.0, 0.0, 1.0, 0.0}); +// A.SetCol(3, Vector4{0.3, 1.0, -2.0, 1.0}); +// B = convert.UToV(A); +// // B.GetCol(0) = { 1, 0, 0, 0 } +// // B.GetCol(1) = { 0, c, s, 0 } +// // B.GetCol(2) = { 0, -s, c, 0 } +// // B.GetCol(3) = { 2.0, -0.9, -2.6, 1 } +// X = A*X; // U is VOR +// Y = Y*B; // V is VOL (not VOR) +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } + +namespace gte +{ + template + class ConvertCoordinates + { + public: + // Construction of the change of basis matrix. The implementation + // supports both linear change of basis and affine change of basis. + ConvertCoordinates() + : + mIsVectorOnRightU(true), + mIsVectorOnRightV(true), + mIsRightHandedU(true), + mIsRightHandedV(true) + { + mC.MakeIdentity(); + mInverseC.MakeIdentity(); + } + + // Compute a change of basis between two coordinate systems. The return + // value is 'true' iff U and V are invertible. The matrix-vector + // multiplication conventions affect the conversion of matrix + // transformations. The Boolean inputs indicate how you want the matrices + // to be interpreted when applied as transformations of a vector. + bool operator()( + Matrix const& U, bool vectorOnRightU, + Matrix const& V, bool vectorOnRightV) + { + // Initialize in case of early exit. + mC.MakeIdentity(); + mInverseC.MakeIdentity(); + mIsVectorOnRightU = true; + mIsVectorOnRightV = true; + mIsRightHandedU = true; + mIsRightHandedV = true; + + Matrix inverseU; + Real determinantU; + bool invertibleU = GaussianElimination()(N, &U[0], &inverseU[0], + determinantU, nullptr, nullptr, nullptr, 0, nullptr); + if (!invertibleU) + { + return false; + } + + Matrix inverseV; + Real determinantV; + bool invertibleV = GaussianElimination()(N, &V[0], &inverseV[0], + determinantV, nullptr, nullptr, nullptr, 0, nullptr); + if (!invertibleV) + { + return false; + } + + mC = inverseU * V; + mInverseC = inverseV * U; + mIsVectorOnRightU = vectorOnRightU; + mIsVectorOnRightV = vectorOnRightV; + mIsRightHandedU = (determinantU > (Real)0); + mIsRightHandedV = (determinantV > (Real)0); + return true; + } + + // Member access. + inline Matrix const& GetC() const + { + return mC; + } + + inline Matrix const& GetInverseC() const + { + return mInverseC; + } + + inline bool IsVectorOnRightU() const + { + return mIsVectorOnRightU; + } + + inline bool IsVectorOnRightV() const + { + return mIsVectorOnRightV; + } + + inline bool IsRightHandedU() const + { + return mIsRightHandedU; + } + + inline bool IsRightHandedV() const + { + return mIsRightHandedV; + } + + // Convert points between coordinate systems. The names of the systems + // are U and V to make it clear which inputs of operator() they are + // associated with. The X vector stores coordinates for the U-system and + // the Y vector stores coordinates for the V-system. + + // Y = C^{-1}*X + inline Vector UToV(Vector const& X) const + { + return mInverseC * X; + } + + // X = C*Y + inline Vector VToU(Vector const& Y) const + { + return mC * Y; + } + + // Convert transformations between coordinate systems. The outputs are + // computed according to the tables shown before the function + // declarations. The superscript T denotes the transpose operator. + // vectorOnRightU = true: transformation is X' = A*X + // vectorOnRightU = false: transformation is (X')^T = X^T*A + // vectorOnRightV = true: transformation is Y' = B*Y + // vectorOnRightV = false: transformation is (Y')^T = Y^T*B + + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C^{-1} * A * C + // true | false | (C^{-1} * A * C)^T + // false | true | C^{-1} * A^T * C + // false | false | (C^{-1} * A^T * C)^T + Matrix UToV(Matrix const& A) const + { + Matrix product; + + if (mIsVectorOnRightU) + { + product = mInverseC * A * mC; + if (mIsVectorOnRightV) + { + return product; + } + else + { + return Transpose(product); + } + } + else + { + product = mInverseC * MultiplyATB(A, mC); + if (mIsVectorOnRightV) + { + return product; + } + else + { + return Transpose(product); + } + } + } + + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C * B * C^{-1} + // true | false | C * B^T * C^{-1} + // false | true | (C * B * C^{-1})^T + // false | false | (C * B^T * C^{-1})^T + Matrix VToU(Matrix const& B) const + { + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C * B * C^{-1} + // true | false | C * B^T * C^{-1} + // false | true | (C * B * C^{-1})^T + // false | false | (C * B^T * C^{-1})^T + Matrix product; + + if (mIsVectorOnRightV) + { + product = mC * B * mInverseC; + if (mIsVectorOnRightU) + { + return product; + } + else + { + return Transpose(product); + } + } + else + { + product = mC * MultiplyATB(B, mInverseC); + if (mIsVectorOnRightU) + { + return product; + } + else + { + return Transpose(product); + } + } + } + + private: + // C = U^{-1}*V, C^{-1} = V^{-1}*U + Matrix mC, mInverseC; + bool mIsVectorOnRightU, mIsVectorOnRightV; + bool mIsRightHandedU, mIsRightHandedV; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h new file mode 100644 index 000000000000..7432b77c0822 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull2.h @@ -0,0 +1,409 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Compute the convex hull of 2D points using a divide-and-conquer algorithm. +// This is an O(N log N) algorithm for N input points. The only way to ensure +// a correct result for the input vertices (assumed to be exact) is to choose +// ComputeType for exact rational arithmetic. You may use BSNumber. No +// divisions are performed in this computation, so you do not have to use +// BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery2::ToLineExtended. We +// recommend using only BSNumber, because no divisions are performed in the +// convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 18 +// double | BSNumber | 132 +// float | BSRational | 214 +// double | BSRational | 1587 + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ConvexHull2 +{ +public: + // The class is a functor to support computing the convex hull of multiple + // data sets using the same class object. + ConvexHull2(); + + // The input is the array of points whose convex hull is required. The + // epsilon value is used to determine the intrinsic dimensionality of the + // vertices (d = 0, 1, or 2). When epsilon is positive, the determination + // is fuzzy--points approximately the same point, approximately on a + // line, or planar. The return value is 'true' if and only if the hull + // construction is successful. + bool operator()(int numPoints, Vector2 const* points, InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line2 const& GetLine() const; + + // Member access. + inline int GetNumPoints() const; + inline int GetNumUniquePoints() const; + inline Vector2 const* GetPoints() const; + inline PrimalQuery2 const& GetQuery() const; + + // The convex hull is a convex polygon whose vertices are listed in + // counterclockwise order. + inline std::vector const& GetHull() const; + +private: + // Support for divide-and-conquer. + void GetHull(int& i0, int& i1); + void Merge(int j0, int j1, int j2, int j3, int& i0, int& i1); + void GetTangent(int j0, int j1, int j2, int j3, int& i0, int& i1); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0 or 1, the constructor returns + // early. The caller is responsible for retrieving the dimension and + // taking an alternate path should the dimension be smaller than 2. If + // the dimension is 0, the caller may as well treat all points[] as a + // single point, say, points[0]. If the dimension is 1, the caller can + // query for the approximating line and project points[] onto it for + // further processing. + InputType mEpsilon; + int mDimension; + Line2 mLine; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputePoints; + PrimalQuery2 mQuery; + + int mNumPoints; + int mNumUniquePoints; + Vector2 const* mPoints; + std::vector mMerged, mHull; +}; + + +template +ConvexHull2::ConvexHull2() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector2::Zero(), Vector2::Zero()), + mNumPoints(0), + mNumUniquePoints(0), + mPoints(nullptr) +{ +} + +template +bool ConvexHull2::operator()(int numPoints, + Vector2 const* points, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector2::Zero(); + mLine.direction = Vector2::Zero(); + mNumPoints = numPoints; + mNumUniquePoints = 0; + mPoints = points; + mMerged.clear(); + mHull.clear(); + + int i, j; + if (mNumPoints < 3) + { + // ConvexHull2 should be called with at least three points. + return false; + } + + IntrinsicsVector2 info(mNumPoints, mPoints, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0 + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line2(info.origin, info.direction[0]); + return false; + } + + mDimension = 2; + + // Compute the points for the queries. + mComputePoints.resize(mNumPoints); + mQuery.Set(mNumPoints, &mComputePoints[0]); + for (i = 0; i < mNumPoints; ++i) + { + for (j = 0; j < 2; ++j) + { + mComputePoints[i][j] = points[i][j]; + } + } + + // Sort the points. + mHull.resize(mNumPoints); + for (i = 0; i < mNumPoints; ++i) + { + mHull[i] = i; + } + std::sort(mHull.begin(), mHull.end(), + [points](int i0, int i1) + { + if (points[i0][0] < points[i1][0]) { return true; } + if (points[i0][0] > points[i1][0]) { return false; } + return points[i0][1] < points[i1][1]; + } + ); + + // Remove duplicates. + auto newEnd = std::unique(mHull.begin(), mHull.end(), + [points](int i0, int i1) + { + return points[i0] == points[i1]; + } + ); + mHull.erase(newEnd, mHull.end()); + mNumUniquePoints = static_cast(mHull.size()); + + // Use a divide-and-conquer algorithm. The merge step computes the + // convex hull of two convex polygons. + mMerged.resize(mNumUniquePoints); + int i0 = 0, i1 = mNumUniquePoints - 1; + GetHull(i0, i1); + mHull.resize(i1 - i0 + 1); + return true; +} + +template inline +InputType ConvexHull2::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int ConvexHull2::GetDimension() const +{ + return mDimension; +} + +template inline +Line2 const& ConvexHull2::GetLine() const +{ + return mLine; +} + +template inline +int ConvexHull2::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +int ConvexHull2::GetNumUniquePoints() const +{ + return mNumUniquePoints; +} + +template inline +Vector2 const* ConvexHull2::GetPoints() const +{ + return mPoints; +} + +template inline +PrimalQuery2 const& ConvexHull2::GetQuery() const +{ + return mQuery; +} + +template inline +std::vector const& ConvexHull2::GetHull() const +{ + return mHull; +} + +template inline +void ConvexHull2::GetHull(int& i0, int& i1) +{ + int numVertices = i1 - i0 + 1; + if (numVertices > 1) + { + // Compute the middle index of input range. + int mid = (i0 + i1) / 2; + + // Compute the hull of subsets (mid-i0+1 >= i1-mid). + int j0 = i0, j1 = mid, j2 = mid + 1, j3 = i1; + GetHull(j0, j1); + GetHull(j2, j3); + + // Merge the convex hulls into a single convex hull. + Merge(j0, j1, j2, j3, i0, i1); + } + // else: The convex hull is a single point. +} + +template inline +void ConvexHull2::Merge(int j0, int j1, int j2, int j3, + int& i0, int& i1) +{ + // Subhull0 is to the left of subhull1 because of the initial sorting of + // the points by x-components. We need to find two mutually visible + // points, one on the left subhull and one on the right subhull. + int size0 = j1 - j0 + 1; + int size1 = j3 - j2 + 1; + + int i; + Vector2 p; + + // Find the right-most point of the left subhull. + Vector2 pmax0 = mComputePoints[mHull[j0]]; + int imax0 = j0; + for (i = j0 + 1; i <= j1; ++i) + { + p = mComputePoints[mHull[i]]; + if (pmax0 < p) + { + pmax0 = p; + imax0 = i; + } + } + + // Find the left-most point of the right subhull. + Vector2 pmin1 = mComputePoints[mHull[j2]]; + int imin1 = j2; + for (i = j2 + 1; i <= j3; ++i) + { + p = mComputePoints[mHull[i]]; + if (p < pmin1) + { + pmin1 = p; + imin1 = i; + } + } + + // Get the lower tangent to hulls (LL = lower-left, LR = lower-right). + int iLL = imax0, iLR = imin1; + GetTangent(j0, j1, j2, j3, iLL, iLR); + + // Get the upper tangent to hulls (UL = upper-left, UR = upper-right). + int iUL = imax0, iUR = imin1; + GetTangent(j2, j3, j0, j1, iUR, iUL); + + // Construct the counterclockwise-ordered merged-hull vertices. + int k; + int numMerged = 0; + + i = iUL; + for (k = 0; k < size0; ++k) + { + mMerged[numMerged++] = mHull[i]; + if (i == iLL) + { + break; + } + i = (i < j1 ? i + 1 : j0); + } + LogAssert(k < size0, "Unexpected condition."); + + i = iLR; + for (k = 0; k < size1; ++k) + { + mMerged[numMerged++] = mHull[i]; + if (i == iUR) + { + break; + } + i = (i < j3 ? i + 1 : j2); + } + LogAssert(k < size1, "Unexpected condition."); + + int next = j0; + for (k = 0; k < numMerged; ++k) + { + mHull[next] = mMerged[k]; + ++next; + } + + i0 = j0; + i1 = next - 1; +} + +template inline +void ConvexHull2::GetTangent(int j0, int j1, int j2, int j3, + int& i0, int& i1) +{ + // In theory the loop terminates in a finite number of steps, but the + // upper bound for the loop variable is used to trap problems caused by + // floating point roundoff errors that might lead to an infinite loop. + + int size0 = j1 - j0 + 1; + int size1 = j3 - j2 + 1; + int const imax = size0 + size1; + int i, iLm1, iRp1; + Vector2 L0, L1, R0, R1; + + for (i = 0; i < imax; i++) + { + // Get the endpoints of the potential tangent. + L1 = mComputePoints[mHull[i0]]; + R0 = mComputePoints[mHull[i1]]; + + // Walk along the left hull to find the point of tangency. + if (size0 > 1) + { + iLm1 = (i0 > j0 ? i0 - 1 : j1); + L0 = mComputePoints[mHull[iLm1]]; + auto order = mQuery.ToLineExtended(R0, L0, L1); + if (order == PrimalQuery2::ORDER_NEGATIVE + || order == PrimalQuery2::ORDER_COLLINEAR_RIGHT) + { + i0 = iLm1; + continue; + } + } + + // Walk along right hull to find the point of tangency. + if (size1 > 1) + { + iRp1 = (i1 < j3 ? i1 + 1 : j2); + R1 = mComputePoints[mHull[iRp1]]; + auto order = mQuery.ToLineExtended(L1, R0, R1); + if (order == PrimalQuery2::ORDER_NEGATIVE + || order == PrimalQuery2::ORDER_COLLINEAR_LEFT) + { + i1 = iRp1; + continue; + } + } + + // The tangent segment has been found. + break; + } + + // Detect an "infinite loop" caused by floating point round-off errors. + LogAssert(i < imax, "Unexpected condition."); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull3.h new file mode 100644 index 000000000000..4dc95f347090 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexHull3.h @@ -0,0 +1,430 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Compute the convex hull of 3D points using incremental insertion. The only +// way to ensure a correct result for the input vertices (assumed to be exact) +// is to choose ComputeType for exact rational arithmetic. You may use +// BSNumber. No divisions are performed in this computation, so you do not +// have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery3::ToPlane. We recommend +// using only BSNumber, because no divisions are performed in the convex-hull +// computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 27 +// double | BSNumber | 197 +// float | BSRational | 2882 +// double | BSRational | 21688 + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ConvexHull3 +{ +public: + // The class is a functor to support computing the convex hull of multiple + // data sets using the same class object. For multithreading in Update, + // choose 'numThreads' subject to the constraints + // 1 <= numThreads <= std::thread::hardware_concurrency(). + ConvexHull3(unsigned int numThreads = 1); + + // The input is the array of points whose convex hull is required. The + // epsilon value is used to determine the intrinsic dimensionality of the + // vertices (d = 0, 1, 2, or 3). When epsilon is positive, the + // determination is fuzzy--points approximately the same point, + // approximately on a line, approximately planar, or volumetric. + bool operator()(int numPoints, Vector3 const* points, + InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). If GetDimension() returns 2, + // the points line on a plane P+s*U+t*V (fuzzy comparison when + // epsilon > 0). You can project each point X = P+s*U+t*V, where + // s = Dot(U,X-P) and t = Dot(V,X-P), then apply ConvexHull2 to the (s,t) + // tuples. + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line3 const& GetLine() const; + inline Plane3 const& GetPlane() const; + + // Member access. + inline int GetNumPoints() const; + inline int GetNumUniquePoints() const; + inline Vector3 const* GetPoints() const; + inline PrimalQuery3 const& GetQuery() const; + + // The convex hull is a convex polyhedron with triangular faces. + inline std::vector> const& GetHullUnordered() const; + ETManifoldMesh const& GetHullMesh() const; + +private: + // Support for incremental insertion. + void Update(int i); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0, 1, or 2, the constructor + // returns early. The caller is responsible for retrieving the dimension + // and taking an alternate path should the dimension be smaller than 3. + // If the dimension is 0, the caller may as well treat all points[] as a + // single point, say, points[0]. If the dimension is 1, the caller can + // query for the approximating line and project points[] onto it for + // further processing. If the dimension is 2, the caller can query for + // the approximating plane and project points[] onto it for further + // processing. + InputType mEpsilon; + int mDimension; + Line3 mLine; + Plane3 mPlane; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputePoints; + PrimalQuery3 mQuery; + + int mNumPoints; + int mNumUniquePoints; + Vector3 const* mPoints; + std::vector> mHullUnordered; + mutable ETManifoldMesh mHullMesh; + unsigned int mNumThreads; +}; + + +template +ConvexHull3::ConvexHull3(unsigned int numThreads) + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector3::Zero(), Vector3::Zero()), + mPlane(Vector3::Zero(), (InputType)0), + mNumPoints(0), + mNumUniquePoints(0), + mPoints(nullptr), + mNumThreads(numThreads) +{ +} + +template +bool ConvexHull3::operator()(int numPoints, + Vector3 const* points, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector3::Zero(); + mLine.direction = Vector3::Zero(); + mPlane.normal = Vector3::Zero(); + mPlane.constant = (InputType)0; + mNumPoints = numPoints; + mNumUniquePoints = 0; + mPoints = points; + mHullUnordered.clear(); + mHullMesh.Clear(); + + int i, j; + if (mNumPoints < 4) + { + // ConvexHull3 should be called with at least four points. + return false; + } + + IntrinsicsVector3 info(mNumPoints, mPoints, mEpsilon); + if (info.dimension == 0) + { + // The set is (nearly) a point. + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line3(info.origin, info.direction[0]); + return false; + } + + if (info.dimension == 2) + { + // The set is (nearly) coplanar. + mDimension = 2; + mPlane = Plane3(UnitCross(info.direction[0], + info.direction[1]), info.origin); + return false; + } + + mDimension = 3; + + // Compute the vertices for the queries. + mComputePoints.resize(mNumPoints); + mQuery.Set(mNumPoints, &mComputePoints[0]); + for (i = 0; i < mNumPoints; ++i) + { + for (j = 0; j < 3; ++j) + { + mComputePoints[i][j] = points[i][j]; + } + } + + // Insert the faces of the (nondegenerate) tetrahedron constructed by the + // call to GetInformation. + if (!info.extremeCCW) + { + std::swap(info.extreme[2], info.extreme[3]); + } + + mHullUnordered.push_back(TriangleKey(info.extreme[1], + info.extreme[2], info.extreme[3])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[3], info.extreme[2])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[1], info.extreme[3])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[2], info.extreme[1])); + + // Incrementally update the hull. The set of processed points is + // maintained to eliminate duplicates, either in the original input + // points or in the points obtained by snap rounding. + std::set> processed; + for (i = 0; i < 4; ++i) + { + processed.insert(points[info.extreme[i]]); + } + for (i = 0; i < mNumPoints; ++i) + { + if (processed.find(points[i]) == processed.end()) + { + Update(i); + processed.insert(points[i]); + } + } + mNumUniquePoints = static_cast(processed.size()); + return true; +} + +template inline +InputType ConvexHull3::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int ConvexHull3::GetDimension() const +{ + return mDimension; +} + +template inline +Line3 const& ConvexHull3::GetLine() const +{ + return mLine; +} + +template inline +Plane3 const& ConvexHull3::GetPlane() const +{ + return mPlane; +} + +template inline +int ConvexHull3::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +int ConvexHull3::GetNumUniquePoints() const +{ + return mNumUniquePoints; +} + +template inline +Vector3 const* ConvexHull3::GetPoints() const +{ + return mPoints; +} + +template inline +PrimalQuery3 const& ConvexHull3::GetQuery() const +{ + return mQuery; +} + +template inline +std::vector> const& ConvexHull3::GetHullUnordered() const +{ + return mHullUnordered; +} + +template +ETManifoldMesh const& ConvexHull3::GetHullMesh() const +{ + // Create the mesh only on demand. + if (mHullMesh.GetTriangles().size() == 0) + { + for (auto const& tri : mHullUnordered) + { + mHullMesh.Insert(tri.V[0], tri.V[1], tri.V[2]); + } + } + + return mHullMesh; +} + +template +void ConvexHull3::Update(int i) +{ + // The terminator that separates visible faces from nonvisible faces is + // constructed by this code. Visible faces for the incoming hull are + // removed, and the boundary of that set of triangles is the terminator. + // New visible faces are added using the incoming point and the edges of + // the terminator. + // + // A simple algorithm for computing terminator edges is the following. + // Back-facing triangles are located and the three edges are processed. + // The first time an edge is visited, insert it into the terminator. If + // it is visited a second time, the edge is removed because it is shared + // by another back-facing triangle and, therefore, cannot be a terminator + // edge. After visiting all back-facing triangles, the only remaining + // edges in the map are the terminator edges. + // + // The order of vertices of an edge is important for adding new faces with + // the correct vertex winding. However, the edge "toggle" (insert edge, + // remove edge) should use edges with unordered vertices, because the + // edge shared by one triangle has opposite ordering relative to that of + // the other triangle. The map uses unordered edges as the keys but + // stores the ordered edge as the value. This avoids having to look up + // an edge twice in a map with ordered edge keys. + + unsigned int numFaces = static_cast(mHullUnordered.size()); + std::vector queryResult(numFaces); + if (mNumThreads > 1 && numFaces >= mNumThreads) + { + // Partition the data for multiple threads. + unsigned int numFacesPerThread = numFaces / mNumThreads; + std::vector jmin(mNumThreads), jmax(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + jmin[t] = t * numFacesPerThread; + jmax[t] = jmin[t] + numFacesPerThread - 1; + } + jmax[mNumThreads - 1] = numFaces - 1; + + // Execute the point-plane queries in multiple threads. + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, i, t, &jmin, &jmax, + &queryResult]() + { + for (unsigned int j = jmin[t]; j <= jmax[t]; ++j) + { + TriangleKey const& tri = mHullUnordered[j]; + queryResult[j] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]); + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + } + } + else + { + unsigned int j = 0; + for (auto const& tri : mHullUnordered) + { + queryResult[j++] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]); + } + } + + std::map, std::pair> terminator; + std::vector> backFaces; + bool existsFrontFacingTriangle = false; + unsigned int j = 0; + for (auto const& tri : mHullUnordered) + { + if (queryResult[j++] <= 0) + { + // The triangle is back facing. These include triangles that + // are coplanar with the incoming point. + backFaces.push_back(tri); + + // The current hull is a 2-manifold watertight mesh. The + // terminator edges are those shared with a front-facing triangle. + // The first time an edge of a back-facing triangle is visited, + // insert it into the terminator. If it is visited a second time, + // the edge is removed because it is shared by another back-facing + // triangle. After all back-facing triangles are visited, the + // only remaining edges are shared by a single back-facing + // triangle, which makes them terminator edges. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int v0 = tri.V[j0], v1 = tri.V[j1]; + EdgeKey edge(v0, v1); + auto iter = terminator.find(edge); + if (iter == terminator.end()) + { + // The edge is visited for the first time. + terminator.insert(std::make_pair(edge, std::make_pair(v0, v1))); + } + else + { + // The edge is visited for the second time. + terminator.erase(edge); + } + } + } + else + { + // If there are no strictly front-facing triangles, then the + // incoming point is inside or on the convex hull. If we get + // to this code, then the point is truly outside and we can + // update the hull. + existsFrontFacingTriangle = true; + } + } + + if (!existsFrontFacingTriangle) + { + // The incoming point is inside or on the current hull, so no + // update of the hull is necessary. + return; + } + + // The updated hull contains the triangles not visible to the incoming + // point. + mHullUnordered = backFaces; + + // Insert the triangles formed by the incoming point and the terminator + // edges. + for (auto const& edge : terminator) + { + mHullUnordered.push_back(TriangleKey(i, edge.second.second, edge.second.first)); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexPolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexPolyhedron3.h new file mode 100644 index 000000000000..95194e3dac6f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteConvexPolyhedron3.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.0 (2016/11/25) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class ConvexPolyhedron3 +{ +public: + // Construction. The convex polyhedra represented by this class has + // triangle faces that are counterclockwise ordered when viewed from + // outside the polyhedron. No attempt is made to verify that the + // polyhedron is convex; the caller is responsible for enforcing this. + // The constructors move (not copy!) the input arrays. The constructor + // succeeds when the number of vertices is at least 4 and the number of + // indices is at least 12. If the constructor fails, no move occurs + // and the member arrays have no elements. + // + // To support geometric algorithms that are formulated using convex + // quadratic programming (such as computing the distance from a point to + // a convex polyhedron), it is necessary to know the planes of the faces + // and an axis-aligned bounding box. If you want either the faces or the + // box, pass 'true' to the appropriate parameters. When planes are + // generated, the normals are not created to be unit length in order to + // support queries using exact rational arithmetic. If a normal to a + // face is N = (n0,n1,n2) and V is a vertex of the face, the plane is + // Dot(N,X-V) = 0 and is stored as Dot(n0,n1,n2,-Dot(N,V)). The normals + // are computed to be outer pointing. + ConvexPolyhedron3(); + ConvexPolyhedron3(std::vector>&& inVertices, std::vector&& inIndices, + bool wantPlanes, bool wantAlignedBox); + + // If you modifty the vertices or indices and you want the new face + // planes or aligned box computed, call these functions. + void GeneratePlanes(); + void GenerateAlignedBox(); + + std::vector> vertices; + std::vector indices; + std::vector> planes; + AlignedBox3 alignedBox; +}; + + +template +ConvexPolyhedron3::ConvexPolyhedron3() +{ +} + +template +ConvexPolyhedron3::ConvexPolyhedron3(std::vector>&& inVertices, + std::vector&& inIndices, bool wantPlanes, bool wantAlignedBox) +{ + if (inVertices.size() >= 4 && inIndices.size() >= 12) + { + vertices = std::move(inVertices); + indices = std::move(inIndices); + + if (wantPlanes) + { + GeneratePlanes(); + } + + if (wantAlignedBox) + { + GenerateAlignedBox(); + } + } +} + +template +void ConvexPolyhedron3::GeneratePlanes() +{ + if (vertices.size() > 0 && indices.size() > 0) + { + uint32_t const numTriangles = static_cast(indices.size()) / 3; + planes.resize(numTriangles); + for (uint32_t t = 0, i = 0; t < numTriangles; ++t) + { + Vector3 V0 = vertices[indices[i++]]; + Vector3 V1 = vertices[indices[i++]]; + Vector3 V2 = vertices[indices[i++]]; + Vector3 E1 = V1 - V0; + Vector3 E2 = V2 - V0; + Vector3 N = Cross(E1, E2); + planes[t] = HLift(N, -Dot(N, V0)); + } + } +} + +template +void ConvexPolyhedron3::GenerateAlignedBox() +{ + if (vertices.size() > 0 && indices.size() > 0) + { + ComputeExtremes(static_cast(vertices.size()), vertices.data(), + alignedBox.min, alignedBox.max); + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCosEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCosEstimate.h new file mode 100644 index 000000000000..ed2d326b178d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCosEstimate.h @@ -0,0 +1,164 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to cos(x). The polynomial p(x) of +// degree D has only even-power terms, is required to have constant term 1, +// and p(pi/2) = cos(pi/2) = 0. It minimizes the quantity +// maximum{|cos(x) - p(x)| : x in [-pi/2,pi/2]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class CosEstimate +{ +public: + // The input constraint is x in [-pi/2,pi/2]. For example, + // float x; // in [-pi/2,pi/2] + // float result = CosEstimate::Degree<4>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2] and a sign s for which + // cos(y) = s*cos(x). For example, + // float x; // x any real number + // float result = CosEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<2>, Real x); + inline static Real Evaluate(degree<4>, Real x); + inline static Real Evaluate(degree<6>, Real x); + inline static Real Evaluate(degree<8>, Real x); + inline static Real Evaluate(degree<10>, Real x); + + // Support for range reduction. + inline static void Reduce(Real x, Real& y, Real& sign); +}; + + +template +template +inline Real CosEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real CosEstimate::DegreeRR(Real x) +{ + Real y, sign; + Reduce(x, y, sign); + Real poly = sign * Degree(y); + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<2>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG2_C1; + poly = (Real)GTE_C_COS_DEG2_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<4>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG4_C2; + poly = (Real)GTE_C_COS_DEG4_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG4_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<6>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG6_C3; + poly = (Real)GTE_C_COS_DEG6_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG6_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG6_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<8>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG8_C4; + poly = (Real)GTE_C_COS_DEG8_C3 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C0 + poly * xsqr; + return poly; +} + +template +inline Real CosEstimate::Evaluate(degree<10>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG10_C5; + poly = (Real)GTE_C_COS_DEG10_C4 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C3 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C0 + poly * xsqr; + return poly; +} + +template +inline void CosEstimate::Reduce(Real x, Real& y, Real& sign) +{ + // Map x to y in [-pi,pi], x = 2*pi*quotient + remainder. + Real quotient = (Real)GTE_C_INV_TWO_PI * x; + if (x >= (Real)0) + { + quotient = (Real)((int)(quotient + (Real)0.5)); + } + else + { + quotient = (Real)((int)(quotient - (Real)0.5)); + } + y = x - (Real)GTE_C_TWO_PI * quotient; + + // Map y to [-pi/2,pi/2] with cos(y) = sign*cos(x). + if (y > (Real)GTE_C_HALF_PI) + { + y = (Real)GTE_C_PI - y; + sign = (Real)-1; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y = (Real)-GTE_C_PI - y; + sign = (Real)-1; + } + else + { + sign = (Real)1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCubicRootsQR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCubicRootsQR.h new file mode 100644 index 000000000000..0d680782f121 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCubicRootsQR.h @@ -0,0 +1,241 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). The algorithm is +// specialized for the companion matrix associated with a cubic polynomial. + +namespace gte +{ + +template +class CubicRootsQR +{ +public: + typedef std::array, 3> Matrix; + + // Solve p(x) = c0 + c1 * x + c2 * x^2 + x^3 = 0. + uint32_t operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, + uint32_t& numRoots, std::array& roots); + + // Compute the real eigenvalues of the upper Hessenberg matrix A. The + // matrix is modified by in-place operations, so if you need to remember + // A, you must make your own copy before calling this function. + uint32_t operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots); + +private: + void DoIteration(std::array const& V, Matrix& A); + + template + std::array House(std::array const& X); + + template + void RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + template + void ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + void GetQuadraticRoots(int i0, int i1, Matrix const & A, + uint32_t& numRoots, std::array& roots); +}; + + +template +uint32_t CubicRootsQR::operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, + uint32_t& numRoots, std::array& roots) +{ + // Create the companion matrix for the polynomial. The matrix is in upper + // Hessenberg form. + Matrix A; + A[0][0] = (Real)0; + A[0][1] = (Real)0; + A[0][2] = -c0; + A[1][0] = (Real)1; + A[1][1] = (Real)0; + A[1][2] = -c1; + A[2][0] = (Real)0; + A[2][1] = (Real)1; + A[2][2] = -c2; + + // Avoid the QR-cycle when c1 = c2 = 0 and avoid the slow convergence + // when c1 and c2 are nearly zero. + std::array V{ + (Real)1, + (Real)0.36602540378443865, + (Real)0.36602540378443865 }; + DoIteration(V, A); + + return operator()(maxIterations, A, numRoots, roots); +} + +template +uint32_t CubicRootsQR::operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots) +{ + numRoots = 0; + std::fill(roots.begin(), roots.end(), (Real)0); + + for (uint32_t numIterations = 0; numIterations < maxIterations; ++numIterations) + { + // Apply a Francis QR iteration. + Real tr = A[1][1] + A[2][2]; + Real det = A[1][1] * A[2][2] - A[1][2] * A[2][1]; + std::array X{ + A[0][0] * A[0][0] + A[0][1] * A[1][0] - tr * A[0][0] + det, + A[1][0] * (A[0][0] + A[1][1] - tr), + A[1][0] * A[2][1] }; + std::array V = House<3>(X); + DoIteration(V, A); + + // Test for uncoupling of A. + Real tr01 = A[0][0] + A[1][1]; + if (tr01 + A[1][0] == tr01) + { + numRoots = 1; + roots[0] = A[0][0]; + GetQuadraticRoots(1, 2, A, numRoots, roots); + return numIterations; + } + + Real tr12 = A[1][1] + A[2][2]; + if (tr12 + A[2][1] == tr12) + { + numRoots = 1; + roots[0] = A[2][2]; + GetQuadraticRoots(0, 1, A, numRoots, roots); + return numIterations; + } + } + return maxIterations; +} + +template +void CubicRootsQR::DoIteration(std::array const& V, Matrix& A) +{ + Real multV = ((Real)-2) / (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]); + std::array MV{ multV * V[0], multV * V[1], multV * V[2] }; + RowHouse<3>(0, 2, 0, 2, V, MV, A); + ColHouse<3>(0, 2, 0, 2, V, MV, A); + + std::array Y{ A[1][0], A[2][0] }; + std::array W = House<2>(Y); + Real multW = ((Real)-2) / (W[0] * W[0] + W[1] * W[1]); + std::array MW{ multW * W[0], multW * W[1] }; + RowHouse<2>(1, 2, 0, 2, W, MW, A); + ColHouse<2>(0, 2, 1, 2, W, MW, A); +} + +template +template +std::array CubicRootsQR::House(std::array const & X) +{ + std::array V; + Real length = (Real)0; + for (int i = 0; i < N; ++i) + { + length += X[i] * X[i]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (X[0] >= (Real)0 ? (Real)1 : (Real)-1); + Real denom = X[0] + sign * length; + for (int i = 1; i < N; ++i) + { + V[i] = X[i] / denom; + } + } + V[0] = (Real)1; + return V; +} + +template +template +void CubicRootsQR::RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements cmin through cmax are used + + for (int c = cmin; c <= cmax; ++c) + { + W[c] = (Real)0; + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + W[c] += V[k] * A[r][c]; + } + } + + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + for (int c = cmin; c <= cmax; ++c) + { + A[r][c] += MV[k] * W[c]; + } + } +} + +template +template +void CubicRootsQR::ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements rmin through rmax are used + + for (int r = rmin; r <= rmax; ++r) + { + W[r] = (Real)0; + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + W[r] += V[k] * A[r][c]; + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + A[r][c] += W[r] * MV[k]; + } + } +} + +template +void CubicRootsQR::GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots) +{ + // Solve x^2 - t * x + d = 0, where t is the trace and d is the + // determinant of the 2x2 matrix defined by indices i0 and i1. The + // discriminant is D = (t/2)^2 - d. When D >= 0, the roots are real + // values t/2 - sqrt(D) and t/2 + sqrt(D). To avoid potential numerical + // issues with subtractive cancellation, the roots are computed as + // r0 = t/2 + sign(t/2)*sqrt(D), r1 = trace - r0. + Real trace = A[i0][i0] + A[i1][i1]; + Real halfTrace = trace * (Real)0.5; + Real determinant = A[i0][i0] * A[i1][i1] - A[i0][i1] * A[i1][i0]; + Real discriminant = halfTrace * halfTrace - determinant; + if (discriminant >= (Real)0) + { + Real sign = (trace >= (Real)0 ? (Real)1 : (Real)-1); + Real root = halfTrace + sign * std::sqrt(discriminant); + roots[numRoots++] = root; + roots[numRoots++] = trace - root; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCylinder3.h new file mode 100644 index 000000000000..a60e8c559242 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteCylinder3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The cylinder axis is a line. The origin of the cylinder is chosen to be +// the line origin. The cylinder wall is at a distance R units from the axis. +// An infinite cylinder has infinite height. A finite cylinder has center C +// at the line origin and has a finite height H. The segment for the finite +// cylinder has endpoints C-(H/2)*D and C+(H/2)*D where D is a unit-length +// direction of the line. + +namespace gte +{ + +template +class Cylinder3 +{ +public: + // Construction and destruction. The default constructor sets axis to + // (0,0,1), radius to 1, and height to 1. + Cylinder3(); + Cylinder3(Line3 const& inAxis, Real inRadius, Real inHeight); + + Line3 axis; + Real radius, height; + +public: + // Comparisons to support sorted containers. + bool operator==(Cylinder3 const& cylinder) const; + bool operator!=(Cylinder3 const& cylinder) const; + bool operator< (Cylinder3 const& cylinder) const; + bool operator<=(Cylinder3 const& cylinder) const; + bool operator> (Cylinder3 const& cylinder) const; + bool operator>=(Cylinder3 const& cylinder) const; +}; + + +template +Cylinder3::Cylinder3() + : + axis(Line3()), + radius((Real)1), + height((Real)1) +{ +} + +template +Cylinder3::Cylinder3(Line3 const& inAxis, Real inRadius, + Real inHeight) + : + axis(inAxis), + radius(inRadius), + height(inHeight) +{ +} + +template +bool Cylinder3::operator==(Cylinder3 const& cylinder) const +{ + return axis == cylinder.axis + && radius == cylinder.radius + && height == cylinder.height; +} + +template +bool Cylinder3::operator!=(Cylinder3 const& cylinder) const +{ + return !operator==(cylinder); +} + +template +bool Cylinder3::operator<(Cylinder3 const& cylinder) const +{ + if (axis < cylinder.axis) + { + return true; + } + + if (axis > cylinder.axis) + { + return false; + } + + if (radius < cylinder.radius) + { + return true; + } + + if (radius > cylinder.radius) + { + return false; + } + + return height < cylinder.height; +} + +template +bool Cylinder3::operator<=(Cylinder3 const& cylinder) const +{ + return operator<(cylinder) || operator==(cylinder); +} + +template +bool Cylinder3::operator>(Cylinder3 const& cylinder) const +{ + return !operator<=(cylinder); +} + +template +bool Cylinder3::operator>=(Cylinder3 const& cylinder) const +{ + return !operator<(cylinder); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDCPQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDCPQuery.h new file mode 100644 index 000000000000..d439a2d6c6e1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDCPQuery.h @@ -0,0 +1,32 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +namespace gte +{ + +// Distance and closest-point queries. +template +class DCPQuery +{ +public: + struct Result + { + // A DCPQuery-base class B must define a B::Result struct with member + // 'Real distance'. A DCPQuery-derived class D must also derive a + // D::Result from B:Result but may have no members. The idea is to + // allow Result to store closest-point information in addition to the + // distance. The operator() is non-const to allow DCPQuery to store + // and modify private state that supports the query. + }; + Result operator()(Type0 const& primitive0, Type1 const& primitive1); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDarbouxFrame.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDarbouxFrame.h new file mode 100644 index 000000000000..900c38d33951 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDarbouxFrame.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ +template +class DarbouxFrame3 +{ +public: + // Construction. The curve must persist as long as the DarbouxFrame3 + // object does. + DarbouxFrame3(std::shared_ptr> const& surface); + + // Get a coordinate frame, {T0, T1, N}. At a nondegenerate surface + // points, dX/du and dX/dv are linearly independent tangent vectors. + // The frame is constructed as + // T0 = (dX/du)/|dX/du| + // N = Cross(dX/du,dX/dv)/|Cross(dX/du,dX/dv)| + // T1 = Cross(N, T0) + // so that {T0, T1, N} is a right-handed orthonormal set. + void operator()(Real u, Real v, Vector3& position, + Vector3& tangent0, Vector3& tangent1, Vector3& normal) const; + + // Compute the principal curvatures and principal directions. + void GetPrincipalInformation(Real u, Real v, Real& curvature0, Real& curvature1, + Vector3& direction0, Vector3& direction1) const; + +private: + std::shared_ptr> mSurface; +}; + + +template +DarbouxFrame3::DarbouxFrame3( + std::shared_ptr> const& surface) + : + mSurface(surface) +{ +} + +template +void DarbouxFrame3::operator()(Real u, Real v, Vector3& position, + Vector3& tangent0, Vector3& tangent1, Vector3& normal) const +{ + Vector3 values[6]; + mSurface->Evaluate(u, v, 1, values); + position = values[0]; + tangent0 = values[1]; + Normalize(tangent0); + tangent1 = values[2]; + Normalize(tangent1); + normal = UnitCross(tangent0, tangent1); + tangent1 = Cross(normal, tangent0); +} + +template +void DarbouxFrame3::GetPrincipalInformation(Real u, Real v, Real& curvature0, + Real& curvature1, Vector3& direction0, Vector3& direction1) const +{ + // Tangents: T0 = (x_u,y_u,z_u), T1 = (x_v,y_v,z_v) + // Normal: N = Cross(T0,T1)/Length(Cross(T0,T1)) + // Metric Tensor: G = +- -+ + // | Dot(T0,T0) Dot(T0,T1) | + // | Dot(T1,T0) Dot(T1,T1) | + // +- -+ + // + // Curvature Tensor: B = +- -+ + // | -Dot(N,T0_u) -Dot(N,T0_v) | + // | -Dot(N,T1_u) -Dot(N,T1_v) | + // +- -+ + // + // Principal curvatures k are the generalized eigenvalues of + // + // Bw = kGw + // + // If k is a curvature and w=(a,b) is the corresponding solution to + // Bw = kGw, then the principal direction as a 3D vector is d = a*U+b*V. + // + // Let k1 and k2 be the principal curvatures. The mean curvature + // is (k1+k2)/2 and the Gaussian curvature is k1*k2. + + // Compute derivatives. + Vector3 values[6]; + mSurface->Evaluate(u, v, 2, values); + Vector3 derU = values[1]; + Vector3 derV = values[2]; + Vector3 derUU = values[3]; + Vector3 derUV = values[4]; + Vector3 derVV = values[5]; + + // Compute the metric tensor. + Matrix2x2 metricTensor; + metricTensor(0, 0) = Dot(values[1], values[1]); + metricTensor(0, 1) = Dot(values[1], values[2]); + metricTensor(1, 0) = metricTensor(0, 1); + metricTensor(1, 1) = Dot(values[2], values[2]); + + // Compute the curvature tensor. + Vector3 normal = UnitCross(values[1], values[2]); + Matrix2x2 curvatureTensor; + curvatureTensor(0, 0) = -Dot(normal, derUU); + curvatureTensor(0, 1) = -Dot(normal, derUV); + curvatureTensor(1, 0) = curvatureTensor(0, 1); + curvatureTensor(1, 1) = -Dot(normal, derVV); + + // Characteristic polynomial is 0 = det(B-kG) = c2*k^2+c1*k+c0. + Real c0 = Determinant(curvatureTensor); + Real c1 = ((Real)2) * curvatureTensor(0, 1) * metricTensor(0, 1) - + curvatureTensor(0, 0) * metricTensor(1, 1) - + curvatureTensor(1, 1) * metricTensor(0, 0); + Real c2 = Determinant(metricTensor); + + // Principal curvatures are roots of characteristic polynomial. + Real temp = std::sqrt(std::abs(c1 * c1 - ((Real)4) * c0 * c2)); + Real mult = ((Real)0.5) / c2; + curvature0 = -mult * (c1 + temp); + curvature1 = -mult * (c1 - temp); + + // Principal directions are solutions to (B-kG)w = 0, + // w1 = (b12-k1*g12,-(b11-k1*g11)) OR (b22-k1*g22,-(b12-k1*g12)). + Real a0 = curvatureTensor(0, 1) - curvature0 * metricTensor(0, 1); + Real a1 = curvature0 * metricTensor(0, 0) - curvatureTensor(0, 0); + Real length = std::sqrt(a0 * a0 + a1 * a1); + if (length > (Real)0) + { + direction0 = a0 * derU + a1 * derV; + } + else + { + a0 = curvatureTensor(1, 1) - curvature0 * metricTensor(1, 1); + a1 = curvature0 * metricTensor(0, 1) - curvatureTensor(0, 1); + length = std::sqrt(a0 * a0 + a1 * a1); + if (length > (Real)0) + { + direction0 = a0*derU + a1*derV; + } + else + { + // Umbilic (surface is locally sphere, any direction principal). + direction0 = derU; + } + } + Normalize(direction0); + + // Second tangent is cross product of first tangent and normal. + direction1 = Cross(direction0, normal); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2.h new file mode 100644 index 000000000000..bbbae367ca42 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2.h @@ -0,0 +1,862 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Delaunay triangulation of points (intrinsic dimensionality 2). +// VQ = number of vertices +// V = array of vertices +// TQ = number of triangles +// I = Array of 3-tuples of indices into V that represent the triangles +// (3*TQ total elements). Access via GetIndices(*). +// A = Array of 3-tuples of indices into I that represent the adjacent +// triangles (3*TQ total elements). Access via GetAdjacencies(*). +// The i-th triangle has vertices +// vertex[0] = V[I[3*i+0]] +// vertex[1] = V[I[3*i+1]] +// vertex[2] = V[I[3*i+2]] +// and edge index pairs +// edge[0] = +// edge[1] = +// edge[2] = +// The triangles adjacent to these edges have indices +// adjacent[0] = A[3*i+0] is the triangle sharing edge[0] +// adjacent[1] = A[3*i+1] is the triangle sharing edge[1] +// adjacent[2] = A[3*i+2] is the triangle sharing edge[2] +// If there is no adjacent triangle, the A[*] value is set to -1. The +// triangle adjacent to edge[j] has vertices +// adjvertex[0] = V[I[3*adjacent[j]+0]] +// adjvertex[1] = V[I[3*adjacent[j]+1]] +// adjvertex[2] = V[I[3*adjacent[j]+2]] +// The only way to ensure a correct result for the input vertices (assumed to +// be exact) is to choose ComputeType for exact rational arithmetic. You may +// use BSNumber. No divisions are performed in this computation, so you do +// not have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery2::ToLine and +// PrimalQuery2::ToCircumcircle, the latter query the dominant one in +// determining N. We recommend using only BSNumber, because no divisions are +// performed in the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 35 +// double | BSNumber | 263 +// float | BSRational | 12302 +// double | BSRational | 92827 + +namespace gte +{ + +template +class Delaunay2 +{ +public: + // The class is a functor to support computing the Delaunay triangulation + // of multiple data sets using the same class object. + virtual ~Delaunay2(); + Delaunay2(); + + // The input is the array of vertices whose Delaunay triangulation is + // required. The epsilon value is used to determine the intrinsic + // dimensionality of the vertices (d = 0, 1, or 2). When epsilon is + // positive, the determination is fuzzy--vertices approximately the same + // point, approximately on a line, or planar. The return value is 'true' + // if and only if the hull construction is successful. + bool operator()(int numVertices, Vector2 const* vertices, InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line2 const& GetLine() const; + + // Member access. + inline int GetNumVertices() const; + inline int GetNumUniqueVertices() const; + inline int GetNumTriangles() const; + inline Vector2 const* GetVertices() const; + inline PrimalQuery2 const& GetQuery() const; + inline ETManifoldMesh const& GetGraph() const; + inline std::vector const& GetIndices() const; + inline std::vector const& GetAdjacencies() const; + + // If 'vertices' has no duplicates, GetDuplicates()[i] = i for all i. + // If vertices[i] is the first occurrence of a vertex and if vertices[j] + // is found later, then GetDuplicates()[j] = i. + inline std::vector const& GetDuplicates() const; + + // Locate those triangle edges that do not share other triangles. The + // returned array has hull.size() = 2*numEdges, each pair representing an + // edge. The edges are not ordered, but the pair of vertices for an edge + // is ordered so that they conform to a counterclockwise traversal of the + // hull. The return value is 'true' iff the dimension is 2. + bool GetHull(std::vector& hull) const; + + // Get the vertex indices for triangle i. The function returns 'true' + // when the dimension is 2 and i is a valid triangle index, in which case + // the vertices are valid; otherwise, the function returns 'false' and the + // vertices are invalid. + bool GetIndices(int i, std::array& indices) const; + + // Get the indices for triangles adjacent to triangle i. The function + // returns 'true' when the dimension is 2 and if i is a valid triangle + // index, in which case the adjacencies are valid; otherwise, the function + // returns 'false' and the adjacencies are invalid. + bool GetAdjacencies(int i, std::array& adjacencies) const; + + // Support for searching the triangulation for a triangle that contains + // a point. If there is a containing triangle, the returned value is a + // triangle index i with 0 <= i < GetNumTriangles(). If there is not a + // containing triangle, -1 is returned. The computations are performed + // using exact rational arithmetic. + // + // The SearchInfo input stores information about the triangle search when + // looking for the triangle (if any) that contains p. The first triangle + // searched is 'initialTriangle'. On return 'path' stores those (ordered) + // triangle indices visited during the search. The last visited triangle + // has index 'finalTriangle and vertex indices 'finalV[0,1,2]', stored in + // counterclockwise order. The last edge of the search is + // . For spatially coherent inputs p for numerous + // calls to this function, you will want to specify 'finalTriangle' from + // the previous call as 'initialTriangle' for the next call, which should + // reduce search times. + struct SearchInfo + { + int initialTriangle; + int numPath; + std::vector path; + int finalTriangle; + std::array finalV; + }; + int GetContainingTriangle(Vector2 const& p, SearchInfo& info) const; + +protected: + // Support for incremental Delaunay triangulation. + typedef ETManifoldMesh::Triangle Triangle; + bool GetContainingTriangle(int i, std::shared_ptr& tri) const; + bool GetAndRemoveInsertionPolygon(int i, std::set>& candidates, + std::set>& boundary); + bool Update(int i); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0 or 1, the constructor returns + // early. The caller is responsible for retrieving the dimension and + // taking an alternate path should the dimension be smaller than 2. + // If the dimension is 0, the caller may as well treat all vertices[] + // as a single point, say, vertices[0]. If the dimension is 1, the + // caller can query for the approximating line and project vertices[] + // onto it for further processing. + InputType mEpsilon; + int mDimension; + Line2 mLine; + + // The array of vertices used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputeVertices; + PrimalQuery2 mQuery; + + // The graph information. + int mNumVertices; + int mNumUniqueVertices; + int mNumTriangles; + Vector2 const* mVertices; + ETManifoldMesh mGraph; + std::vector mIndices; + std::vector mAdjacencies; + + // If a vertex occurs multiple times in the 'vertices' input to the + // constructor, the first processed occurrence of that vertex has an + // index stored in this array. If there are no duplicates, then + // mDuplicates[i] = i for all i. + + struct ProcessedVertex + { + ProcessedVertex(); + ProcessedVertex(Vector2 const& inVertex, int inLocation); + bool operator<(ProcessedVertex const& v) const; + + Vector2 vertex; + int location; + }; + + std::vector mDuplicates; + + // Indexing for the vertices of the triangle adjacent to a vertex. The + // edge adjacent to vertex j is and is listed + // so that the triangle interior is to your left as you walk around the + // edges. TODO: Use the "opposite edge" to be consistent with that of + // TetrahedronKey. The "opposite" idea extends easily to higher + // dimensions. + std::array, 3> mIndex; +}; + + +template +Delaunay2::~Delaunay2() +{ +} + +template +Delaunay2::Delaunay2() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector2::Zero(), Vector2::Zero()), + mNumVertices(0), + mNumUniqueVertices(0), + mNumTriangles(0), + mVertices(nullptr) +{ + // INVESTIGATE. If the initialization of mIndex is placed in the + // constructor initializer list, MSVS 2012 generates an internal + // compiler error. + mIndex = { { { {0, 1} }, { {1, 2} }, { {2, 0} } } }; +} + +template +bool Delaunay2::operator()(int numVertices, + Vector2 const* vertices, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector2::Zero(); + mLine.direction = Vector2::Zero(); + mNumVertices = numVertices; + mNumUniqueVertices = 0; + mNumTriangles = 0; + mVertices = vertices; + mGraph.Clear(); + mIndices.clear(); + mAdjacencies.clear(); + mDuplicates.resize(std::max(numVertices, 3)); + + int i, j; + if (mNumVertices < 3) + { + // Delaunay2 should be called with at least three points. + return false; + } + + IntrinsicsVector2 info(mNumVertices, vertices, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0; mGraph, mIndices, and mAdjacencies are empty + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line2(info.origin, info.direction[0]); + return false; + } + + mDimension = 2; + + // Compute the vertices for the queries. + mComputeVertices.resize(mNumVertices); + mQuery.Set(mNumVertices, &mComputeVertices[0]); + for (i = 0; i < mNumVertices; ++i) + { + for (j = 0; j < 2; ++j) + { + mComputeVertices[i][j] = vertices[i][j]; + } + } + + // Insert the (nondegenerate) triangle constructed by the call to + // GetInformation. This is necessary for the circumcircle-visibility + // algorithm to work correctly. + if (!info.extremeCCW) + { + std::swap(info.extreme[1], info.extreme[2]); + } + if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2])) + { + return false; + } + + // Incrementally update the triangulation. The set of processed points + // is maintained to eliminate duplicates, either in the original input + // points or in the points obtained by snap rounding. + std::set processed; + for (i = 0; i < 3; ++i) + { + j = info.extreme[i]; + processed.insert(ProcessedVertex(vertices[j], j)); + mDuplicates[j] = j; + } + for (i = 0; i < mNumVertices; ++i) + { + ProcessedVertex v(vertices[i], i); + auto iter = processed.find(v); + if (iter == processed.end()) + { + if (!Update(i)) + { + // A failure can occur if ComputeType is not an exact + // arithmetic type. + return false; + } + processed.insert(v); + mDuplicates[i] = i; + } + else + { + mDuplicates[i] = iter->location; + } + } + mNumUniqueVertices = static_cast(processed.size()); + + // Assign integer values to the triangles for use by the caller. + std::map, int> permute; + i = -1; + permute[nullptr] = i++; + for (auto const& element : mGraph.GetTriangles()) + { + permute[element.second] = i++; + } + + // Put Delaunay triangles into an array (vertices and adjacency info). + mNumTriangles = static_cast(mGraph.GetTriangles().size()); + int numindices = 3 * mNumTriangles; + if (numindices > 0) + { + mIndices.resize(numindices); + mAdjacencies.resize(numindices); + i = 0; + for (auto const& element : mGraph.GetTriangles()) + { + std::shared_ptr tri = element.second; + for (j = 0; j < 3; ++j, ++i) + { + mIndices[i] = tri->V[j]; + mAdjacencies[i] = permute[tri->T[j].lock()]; + } + } + } + + return true; +} + +template inline +InputType Delaunay2::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int Delaunay2::GetDimension() const +{ + return mDimension; +} + +template inline +Line2 const& Delaunay2::GetLine() const +{ + return mLine; +} + +template inline +int Delaunay2::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +int Delaunay2::GetNumUniqueVertices() const +{ + return mNumUniqueVertices; +} + +template inline +int Delaunay2::GetNumTriangles() const +{ + return mNumTriangles; +} + +template inline +Vector2 const* Delaunay2::GetVertices() const +{ + return mVertices; +} + +template inline +PrimalQuery2 const& Delaunay2::GetQuery() const +{ + return mQuery; +} + +template inline +ETManifoldMesh const& Delaunay2::GetGraph() const +{ + return mGraph; +} + +template inline +std::vector const& Delaunay2::GetIndices() const +{ + return mIndices; +} + +template inline +std::vector const& Delaunay2::GetAdjacencies() const +{ + return mAdjacencies; +} + +template inline +std::vector const& Delaunay2::GetDuplicates() const +{ + return mDuplicates; +} + +template +bool Delaunay2::GetHull(std::vector& hull) const +{ + if (mDimension == 2) + { + // Count the number of edges that are not shared by two triangles. + int numEdges = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + ++numEdges; + } + } + + if (numEdges > 0) + { + // Enumerate the edges. + hull.resize(2 * numEdges); + int current = 0, i = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + int tri = i / 3, j = i % 3; + hull[current++] = mIndices[3 * tri + j]; + hull[current++] = mIndices[3 * tri + ((j + 1) % 3)]; + } + ++i; + } + return true; + } + else + { + LogError("Unexpected. There must be at least one triangle."); + } + } + else + { + LogError("The dimension must be 2."); + } + return false; +} + +template +bool Delaunay2::GetIndices(int i, std::array& indices) const +{ + if (mDimension == 2) + { + int numTriangles = static_cast(mIndices.size() / 3); + if (0 <= i && i < numTriangles) + { + indices[0] = mIndices[3 * i]; + indices[1] = mIndices[3 * i + 1]; + indices[2] = mIndices[3 * i + 2]; + return true; + } + } + else + { + LogError("The dimension must be 2."); + } + return false; +} + +template +bool Delaunay2::GetAdjacencies(int i, std::array& adjacencies) const +{ + if (mDimension == 2) + { + int numTriangles = static_cast(mIndices.size() / 3); + if (0 <= i && i < numTriangles) + { + adjacencies[0] = mAdjacencies[3 * i]; + adjacencies[1] = mAdjacencies[3 * i + 1]; + adjacencies[2] = mAdjacencies[3 * i + 2]; + return true; + } + } + else + { + LogError("The dimension must be 2."); + } + return false; +} + +template +int Delaunay2::GetContainingTriangle( + Vector2 const& p, SearchInfo& info) const +{ + if (mDimension == 2) + { + Vector2 test{ p[0], p[1] }; + + int numTriangles = static_cast(mIndices.size() / 3); + info.path.resize(numTriangles); + info.numPath = 0; + int triangle; + if (0 <= info.initialTriangle && info.initialTriangle < numTriangles) + { + triangle = info.initialTriangle; + } + else + { + info.initialTriangle = 0; + triangle = 0; + } + + // Use triangle edges as binary separating lines. + for (int i = 0; i < numTriangles; ++i) + { + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + info.path[info.numPath++] = triangle; + info.finalTriangle = triangle; + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1) + { + info.finalV[0] = v[1]; + info.finalV[1] = v[2]; + info.finalV[2] = v[0]; + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1) + { + info.finalV[0] = v[2]; + info.finalV[1] = v[0]; + info.finalV[2] = v[1]; + return -1; + } + continue; + } + + return triangle; + } + } + else + { + LogError("The dimension must be 2."); + } + return -1; +} + +template +bool Delaunay2::GetContainingTriangle(int i, std::shared_ptr& tri) const +{ + int numTriangles = static_cast(mGraph.GetTriangles().size()); + for (int t = 0; t < numTriangles; ++t) + { + int j; + for (j = 0; j < 3; ++j) + { + int v0 = tri->V[mIndex[j][0]]; + int v1 = tri->V[mIndex[j][1]]; + if (mQuery.ToLine(i, v0, v1) > 0) + { + // Point i sees edge from outside the triangle. + auto adjTri = tri->T[j].lock(); + if (adjTri) + { + // Traverse to the triangle sharing the face. + tri = adjTri; + break; + } + else + { + // We reached a hull edge, so the point is outside the + // hull. TODO: Once a hull data structure is in place, + // return tri->T[j] as the candidate for starting a search + // for visible hull edges. + return false; + } + } + + } + + if (j == 3) + { + // The point is inside all four edges, so the point is inside + // a triangle. + return true; + } + } + + LogError("Unexpected termination of GetContainingTriangle."); + return false; +} + +template +bool Delaunay2::GetAndRemoveInsertionPolygon(int i, + std::set>& candidates, std::set>& boundary) +{ + // Locate the triangles that make up the insertion polygon. + ETManifoldMesh polygon; + while (candidates.size() > 0) + { + std::shared_ptr tri = *candidates.begin(); + candidates.erase(candidates.begin()); + + for (int j = 0; j < 3; ++j) + { + auto adj = tri->T[j].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0) + { + // Point i is in the circumcircle. + candidates.insert(adj); + } + } + } + + if (!polygon.Insert(tri->V[0], tri->V[1], tri->V[2])) + { + return false; + } + if (!mGraph.Remove(tri->V[0], tri->V[1], tri->V[2])) + { + return false; + } + } + + // Get the boundary edges of the insertion polygon. + for (auto const& element : polygon.GetTriangles()) + { + std::shared_ptr tri = element.second; + for (int j = 0; j < 3; ++j) + { + if (!tri->T[j].lock()) + { + boundary.insert(EdgeKey(tri->V[mIndex[j][0]], tri->V[mIndex[j][1]])); + } + } + } + return true; +} + +template +bool Delaunay2::Update(int i) +{ + // The return value of mGraph.Insert(...) is nullptr if there was a + // failure to insert. The Update function will return 'false' when + // the insertion fails. + + auto const& tmap = mGraph.GetTriangles(); + std::shared_ptr tri = tmap.begin()->second; + if (GetContainingTriangle(i, tri)) + { + // The point is inside the convex hull. The insertion polygon + // contains only triangles in the current triangulation; the + // hull does not change. + + // Use a depth-first search for those triangles whose circumcircles + // contain point i. + std::set> candidates; + candidates.insert(tri); + + // Get the boundary of the insertion polygon C that contains the + // triangles whose circumcircles contain point i. C contains the + // point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolygon(i, candidates, boundary)) + { + return false; + } + + // The insertion polygon consists of the triangles formed by + // point i and the faces of C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) < 0) + { + if (!mGraph.Insert(i, v0, v1)) + { + return false; + } + } + // else: Point i is on an edge of 'tri', so the + // subdivision has degenerate triangles. Ignore these. + } + } + else + { + // The point is outside the convex hull. The insertion polygon + // is formed by point i and any triangles in the current + // triangulation whose circumcircles contain point i. + + // Locate the convex hull of the triangles. TODO: Maintain a hull + // data structure that is updated incrementally. + std::set> hull; + for (auto const& element : tmap) + { + std::shared_ptr t = element.second; + for (int j = 0; j < 3; ++j) + { + if (!t->T[j].lock()) + { + hull.insert(EdgeKey(t->V[mIndex[j][0]], t->V[mIndex[j][1]])); + } + } + } + + // TODO: Until the hull change, for now just iterate over all the + // hull edges and use the ones visible to point i to locate the + // insertion polygon. + auto const& emap = mGraph.GetEdges(); + std::set> candidates; + std::set> visible; + for (auto const& key : hull) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) > 0) + { + auto iter = emap.find(EdgeKey(v0, v1)); + if (iter != emap.end() && iter->second->T[1].lock() == nullptr) + { + auto adj = iter->second->T[0].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0) + { + // Point i is in the circumcircle. + candidates.insert(adj); + } + else + { + // Point i is not in the circumcircle but the hull + // edge is visible. + visible.insert(key); + } + } + } + else + { + LogError("Unexpected condition (ComputeType not exact?)"); + return false; + } + } + } + + // Get the boundary of the insertion subpolygon C that contains the + // triangles whose circumcircles contain point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolygon(i, candidates, boundary)) + { + return false; + } + + // The insertion polygon P consists of the triangles formed by point i + // and the back edges of C *and* the visible edges of mGraph-C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) < 0) + { + // This is a back edge of the boundary. + if (!mGraph.Insert(i, v0, v1)) + { + return false; + } + } + } + for (auto const& key : visible) + { + if (!mGraph.Insert(i, key.V[1], key.V[0])) + { + return false; + } + } + } + + return true; +} + +template +Delaunay2::ProcessedVertex::ProcessedVertex() +{ +} + +template +Delaunay2::ProcessedVertex::ProcessedVertex( + Vector2 const& inVertex, int inLocation) + : + vertex(inVertex), + location(inLocation) +{ +} + +template +bool Delaunay2::ProcessedVertex::operator<( + ProcessedVertex const& v) const +{ + return vertex < v.vertex; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2Mesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2Mesh.h new file mode 100644 index 000000000000..3114760fc043 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay2Mesh.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class Delaunay2Mesh +{ +public: + // Construction. + Delaunay2Mesh(Delaunay2 const& delaunay); + + // Mesh information. + inline int GetNumVertices() const; + inline int GetNumTriangles() const; + inline Vector2 const* GetVertices() const; + inline int const* GetIndices() const; + inline int const* GetAdjacencies() const; + + // Containment queries. + int GetContainingTriangle(Vector2 const& P) const; + bool GetVertices(int t, std::array, 3>& vertices) + const; + bool GetIndices(int t, std::array& indices) const; + bool GetAdjacencies(int t, std::array& adjacencies) const; + bool GetBarycentrics(int t, Vector2 const& P, + std::array& bary) const; + +private: + Delaunay2 const* mDelaunay; +}; + + +template +Delaunay2Mesh::Delaunay2Mesh( + Delaunay2 const& delaunay) + : + mDelaunay(&delaunay) +{ +} + +template +inline int Delaunay2Mesh:: +GetNumVertices() const +{ + return mDelaunay->GetNumVertices(); +} + +template +inline int Delaunay2Mesh:: +GetNumTriangles() const +{ + return mDelaunay->GetNumTriangles(); +} + +template +inline Vector2 const* +Delaunay2Mesh:: +GetVertices() const +{ + return mDelaunay->GetVertices(); +} + +template +inline int const* Delaunay2Mesh:: +GetIndices() const +{ + return &mDelaunay->GetIndices()[0]; +} + +template +inline int const* Delaunay2Mesh:: +GetAdjacencies() const +{ + return &mDelaunay->GetAdjacencies()[0]; +} + +template +int Delaunay2Mesh:: +GetContainingTriangle(Vector2 const& P) const +{ + typename Delaunay2::SearchInfo info; + return mDelaunay->GetContainingTriangle(P, info); +} + +template +bool Delaunay2Mesh:: +GetVertices(int t, std::array, 3>& vertices) const +{ + if (mDelaunay->GetDimension() == 2) + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery2 const& query = mDelaunay->GetQuery(); + Vector2 const* ctVertices = query.GetVertices(); + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = ctVertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + vertices[i][j] = (InputType)V[j]; + } + } + return true; + } + } + return false; +} + +template +bool Delaunay2Mesh:: +GetIndices(int t, std::array& indices) const +{ + return mDelaunay->GetIndices(t, indices); +} + +template +bool Delaunay2Mesh:: +GetAdjacencies(int t, std::array& indices) const +{ + return mDelaunay->GetAdjacencies(t, indices); +} + +template +bool Delaunay2Mesh:: +GetBarycentrics(int t, Vector2 const& P, +std::array& bary) const +{ + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery2 const& query = mDelaunay->GetQuery(); + Vector2 const* vertices = query.GetVertices(); + Vector2 rtP{ P[0], P[1] }; + std::array, 3> rtV; + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = vertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[3]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtBary)) + { + for (int i = 0; i < 3; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3.h new file mode 100644 index 000000000000..0e1bf06ac472 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3.h @@ -0,0 +1,884 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Delaunay tetrahedralization of points (intrinsic dimensionality 3). +// VQ = number of vertices +// V = array of vertices +// TQ = number of tetrahedra +// I = Array of 4-tuples of indices into V that represent the tetrahedra +// (4*TQ total elements). Access via GetIndices(*). +// A = Array of 4-tuples of indices into I that represent the adjacent +// tetrahedra (4*TQ total elements). Access via GetAdjacencies(*). +// The i-th tetrahedron has vertices +// vertex[0] = V[I[4*i+0]] +// vertex[1] = V[I[4*i+1]] +// vertex[2] = V[I[4*i+2]] +// vertex[3] = V[I[4*i+3]] +// and face index triples listed below. The face vertex ordering when +// viewed from outside the tetrahedron is counterclockwise. +// face[0] = +// face[1] = +// face[2] = +// face[3] = +// The tetrahedra adjacent to these faces have indices +// adjacent[0] = A[4*i+0] is the tetrahedron opposite vertex[0], so it +// is the tetrahedron sharing face[0]. +// adjacent[1] = A[4*i+1] is the tetrahedron opposite vertex[1], so it +// is the tetrahedron sharing face[1]. +// adjacent[2] = A[4*i+2] is the tetrahedron opposite vertex[2], so it +// is the tetrahedron sharing face[2]. +// adjacent[3] = A[4*i+3] is the tetrahedron opposite vertex[3], so it +// is the tetrahedron sharing face[3]. +// If there is no adjacent tetrahedron, the A[*] value is set to -1. The +// tetrahedron adjacent to face[j] has vertices +// adjvertex[0] = V[I[4*adjacent[j]+0]] +// adjvertex[1] = V[I[4*adjacent[j]+1]] +// adjvertex[2] = V[I[4*adjacent[j]+2]] +// adjvertex[3] = V[I[4*adjacent[j]+3]] +// The only way to ensure a correct result for the input vertices (assumed to +// be exact) is to choose ComputeType for exact rational arithmetic. You may +// use BSNumber. No divisions are performed in this computation, so you do +// not have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery3::ToPlane and +// PrimalQuery3::ToCircumsphere, the latter query the dominant one in +// determining N. We recommend using only BSNumber, because no divisions are +// performed in the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+-------- +// float | BSNumber | 44 +// float | BSRational | 329 +// double | BSNumber | 298037 +// double | BSRational | 2254442 + +namespace gte +{ + +template +class Delaunay3 +{ +public: + // The class is a functor to support computing the Delaunay + // tetrahedralization of multiple data sets using the same class object. + Delaunay3(); + + // The input is the array of vertices whose Delaunay tetrahedralization + // is required. The epsilon value is used to determine the intrinsic + // dimensionality of the vertices (d = 0, 1, 2, or 3). When epsilon is + // positive, the determination is fuzzy--vertices approximately the same + // point, approximately on a line, approximately planar, or volumetric. + bool operator()(int numVertices, Vector3 const* vertices, InputType epsilon); + + // Dimensional information. If GetDimension() returns 1, the points lie + // on a line P+t*D (fuzzy comparison when epsilon > 0). You can sort + // these if you need a polyline output by projecting onto the line each + // vertex X = P+t*D, where t = Dot(D,X-P). If GetDimension() returns 2, + // the points line on a plane P+s*U+t*V (fuzzy comparison when + // epsilon > 0). You can project each vertex X = P+s*U+t*V, where + // s = Dot(U,X-P) and t = Dot(V,X-P), then apply Delaunay2 to the (s,t) + // tuples. + inline InputType GetEpsilon() const; + inline int GetDimension() const; + inline Line3 const& GetLine() const; + inline Plane3 const& GetPlane() const; + + // Member access. + inline int GetNumVertices() const; + inline int GetNumUniqueVertices() const; + inline int GetNumTetrahedra() const; + inline Vector3 const* GetVertices() const; + inline PrimalQuery3 const& GetQuery() const; + inline TSManifoldMesh const& GetGraph() const; + inline std::vector const& GetIndices() const; + inline std::vector const& GetAdjacencies() const; + + // Locate those tetrahedra faces that do not share other tetrahedra. The + // returned array has hull.size() = 3*numFaces indices, each triple + // representing a triangle. The triangles are counterclockwise ordered + // when viewed from outside the hull. The return value is 'true' iff the + // dimension is 3. + bool GetHull(std::vector& hull) const; + + // Get the vertex indices for tetrahedron i. The function returns 'true' + // when the dimension is 3 and i is a valid tetrahedron index, in which + // case the vertices are valid; otherwise, the function returns 'false' + // and the vertices are invalid. + bool GetIndices(int i, std::array& indices) const; + + // Get the indices for tetrahedra adjacent to tetrahedron i. The function + // returns 'true' when the dimension is 3 and i is a valid tetrahedron + // index, in which case the adjacencies are valid; otherwise, the function + // returns 'false' and the adjacencies are invalid. + bool GetAdjacencies(int i, std::array& adjacencies) const; + + // Support for searching the tetrahedralization for a tetrahedron that + // contains a point. If there is a containing tetrahedron, the returned + // value is a tetrahedron index i with 0 <= i < GetNumTetrahedra(). If + // there is not a containing tetrahedron, -1 is returned. The + // computations are performed using exact rational arithmetic. + // + // The SearchInfo input stores information about the tetrahedron search + // when looking for the tetrahedron (if any) that contains p. The first + // tetrahedron searched is 'initialTetrahedron'. On return 'path' stores + // those (ordered) tetrahedron indices visited during the search. The + // last visited tetrahedron has index 'finalTetrahedron and vertex indices + // 'finalV[0,1,2,3]', stored in volumetric counterclockwise order. The + // last face of the search is . For + // spatially coherent inputs p for numerous calls to this function, you + // will want to specify 'finalTetrahedron' from the previous call as + // 'initialTetrahedron' for the next call, which should reduce search + // times. + struct SearchInfo + { + int initialTetrahedron; + int numPath; + std::vector path; + int finalTetrahedron; + std::array finalV; + }; + int GetContainingTetrahedron(Vector3 const& p, SearchInfo& info) const; + +private: + // Support for incremental Delaunay tetrahedralization. + typedef TSManifoldMesh::Tetrahedron Tetrahedron; + bool GetContainingTetrahedron(int i, std::shared_ptr& tetra) const; + bool GetAndRemoveInsertionPolyhedron(int i, std::set>& candidates, + std::set>& boundary); + bool Update(int i); + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0, 1, or 2, the constructor + // returns early. The caller is responsible for retrieving the dimension + // and taking an alternate path should the dimension be smaller than 3. + // If the dimension is 0, the caller may as well treat all vertices[] + // as a single point, say, vertices[0]. If the dimension is 1, the + // caller can query for the approximating line and project vertices[] + // onto it for further processing. If the dimension is 2, the caller can + // query for the approximating plane and project vertices[] onto it for + // further processing. + InputType mEpsilon; + int mDimension; + Line3 mLine; + Plane3 mPlane; + + // The array of vertices used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputeVertices; + PrimalQuery3 mQuery; + + // The graph information. + int mNumVertices; + int mNumUniqueVertices; + int mNumTetrahedra; + Vector3 const* mVertices; + TSManifoldMesh mGraph; + std::vector mIndices; + std::vector mAdjacencies; +}; + + +template +Delaunay3::Delaunay3() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector3::Zero(), Vector3::Zero()), + mPlane(Vector3::Zero(), (InputType)0), + mNumVertices(0), + mNumUniqueVertices(0), + mNumTetrahedra(0), + mVertices(nullptr) +{ +} + +template +bool Delaunay3::operator()(int numVertices, + Vector3 const* vertices, InputType epsilon) +{ + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector3::Zero(); + mLine.direction = Vector3::Zero(); + mPlane.normal = Vector3::Zero(); + mPlane.constant = (InputType)0; + mNumVertices = numVertices; + mNumUniqueVertices = 0; + mNumTetrahedra = 0; + mVertices = vertices; + mGraph = TSManifoldMesh(); + mIndices.clear(); + mAdjacencies.clear(); + + int i, j; + if (mNumVertices < 4) + { + // Delaunay3 should be called with at least four points. + return false; + } + + IntrinsicsVector3 info(mNumVertices, vertices, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0; mGraph, mIndices, and mAdjacencies are empty + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line3(info.origin, info.direction[0]); + return false; + } + + if (info.dimension == 2) + { + // The set is (nearly) coplanar. + mDimension = 2; + mPlane = Plane3(UnitCross(info.direction[0], + info.direction[1]), info.origin); + return false; + } + + mDimension = 3; + + // Compute the vertices for the queries. + mComputeVertices.resize(mNumVertices); + mQuery.Set(mNumVertices, &mComputeVertices[0]); + for (i = 0; i < mNumVertices; ++i) + { + for (j = 0; j < 3; ++j) + { + mComputeVertices[i][j] = vertices[i][j]; + } + } + + // Insert the (nondegenerate) tetrahedron constructed by the call to + // GetInformation. This is necessary for the circumsphere-visibility + // algorithm to work correctly. + if (!info.extremeCCW) + { + std::swap(info.extreme[2], info.extreme[3]); + } + if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2], info.extreme[3])) + { + return false; + } + + // Incrementally update the tetrahedralization. The set of processed + // points is maintained to eliminate duplicates, either in the original + // input points or in the points obtained by snap rounding. + std::set> processed; + for (i = 0; i < 4; ++i) + { + processed.insert(vertices[info.extreme[i]]); + } + for (i = 0; i < mNumVertices; ++i) + { + if (processed.find(vertices[i]) == processed.end()) + { + if (!Update(i)) + { + // A failure can occur if ComputeType is not an exact + // arithmetic type. + return false; + } + processed.insert(vertices[i]); + } + } + mNumUniqueVertices = static_cast(processed.size()); + + // Assign integer values to the tetrahedra for use by the caller. + std::map, int> permute; + i = -1; + permute[nullptr] = i++; + for (auto const& element : mGraph.GetTetrahedra()) + { + permute[element.second] = i++; + } + + // Put Delaunay tetrahedra into an array (vertices and adjacency info). + mNumTetrahedra = static_cast(mGraph.GetTetrahedra().size()); + int numIndices = 4 * mNumTetrahedra; + if (mNumTetrahedra > 0) + { + mIndices.resize(numIndices); + mAdjacencies.resize(numIndices); + i = 0; + for (auto const& element : mGraph.GetTetrahedra()) + { + std::shared_ptr tetra = element.second; + for (j = 0; j < 4; ++j, ++i) + { + mIndices[i] = tetra->V[j]; + mAdjacencies[i] = permute[tetra->S[j].lock()]; + } + } + } + + return true; +} + +template inline +InputType Delaunay3::GetEpsilon() const +{ + return mEpsilon; +} + +template inline +int Delaunay3::GetDimension() const +{ + return mDimension; +} + +template inline +Line3 const& Delaunay3::GetLine() const +{ + return mLine; +} + +template inline +Plane3 const& Delaunay3::GetPlane() const +{ + return mPlane; +} + +template inline +int Delaunay3::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +int Delaunay3::GetNumUniqueVertices() const +{ + return mNumUniqueVertices; +} + +template inline +int Delaunay3::GetNumTetrahedra() const +{ + return mNumTetrahedra; +} + +template inline +Vector3 const* Delaunay3::GetVertices() const +{ + return mVertices; +} + +template inline +PrimalQuery3 const& Delaunay3::GetQuery() const +{ + return mQuery; +} + +template inline +TSManifoldMesh const& Delaunay3::GetGraph() const +{ + return mGraph; +} + +template inline +std::vector const& Delaunay3::GetIndices() const +{ + return mIndices; +} + +template inline +std::vector const& Delaunay3::GetAdjacencies() const +{ + return mAdjacencies; +} + +template +bool Delaunay3::GetHull(std::vector& hull) const +{ + if (mDimension == 3) + { + // Count the number of triangles that are not shared by two + // tetrahedra. + int numTriangles = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + ++numTriangles; + } + } + + if (numTriangles > 0) + { + // Enumerate the triangles. The prototypical case is the single + // tetrahedron V[0] = (0,0,0), V[1] = (1,0,0), V[2] = (0,1,0), and + // V[3] = (0,0,1) with no adjacent tetrahedra. The mIndices[] + // array is <0,1,2,3>. + // i = 0, face = 0: + // skip index 0, , no swap, triangle = <1,2,3> + // i = 1, face = 1: + // skip index 1, <0,x,2,3>, swap, triangle = <0,3,2> + // i = 2, face = 2: + // skip index 2, <0,1,x,3>, no swap, triangle = <0,1,3> + // i = 3, face = 3: + // skip index 3, <0,1,2,x>, swap, triangle = <0,2,1> + // To guarantee counterclockwise order of triangles when viewed + // outside the tetrahedron, the swap of the last two indices + // occurs when face is an odd number; (face % 2) != 0. + hull.resize(3 * numTriangles); + int current = 0, i = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + int tetra = i / 4, face = i % 4; + for (int j = 0; j < 4; ++j) + { + if (j != face) + { + hull[current++] = mIndices[4 * tetra + j]; + } + } + if ((face % 2) != 0) + { + std::swap(hull[current - 1], hull[current - 2]); + } + } + ++i; + } + return true; + } + else + { + LogError("Unexpected. There must be at least one tetrahedron."); + } + } + else + { + LogError("The dimension must be 3."); + } + return false; +} + +template +bool Delaunay3::GetIndices(int i, std::array& indices) const +{ + if (mDimension == 3) + { + int numTetrahedra = static_cast(mIndices.size() / 4); + if (0 <= i && i < numTetrahedra) + { + indices[0] = mIndices[4 * i]; + indices[1] = mIndices[4 * i + 1]; + indices[2] = mIndices[4 * i + 2]; + indices[3] = mIndices[4 * i + 3]; + return true; + } + } + else + { + LogError("The dimension must be 3."); + } + return false; +} + +template +bool Delaunay3::GetAdjacencies(int i, std::array& adjacencies) const +{ + if (mDimension == 3) + { + int numTetrahedra = static_cast(mIndices.size() / 4); + if (0 <= i && i < numTetrahedra) + { + adjacencies[0] = mAdjacencies[4 * i]; + adjacencies[1] = mAdjacencies[4 * i + 1]; + adjacencies[2] = mAdjacencies[4 * i + 2]; + adjacencies[3] = mAdjacencies[4 * i + 3]; + return true; + } + } + else + { + LogError("The dimension must be 3."); + } + return false; +} + +template +int Delaunay3::GetContainingTetrahedron(Vector3 const& p, SearchInfo& info) const +{ + if (mDimension == 3) + { + Vector3 test{ p[0], p[1], p[2] }; + + int numTetrahedra = static_cast(mIndices.size() / 4); + info.path.resize(numTetrahedra); + info.numPath = 0; + int tetrahedron; + if (0 <= info.initialTetrahedron + && info.initialTetrahedron < numTetrahedra) + { + tetrahedron = info.initialTetrahedron; + } + else + { + info.initialTetrahedron = 0; + tetrahedron = 0; + } + + // Use tetrahedron faces as binary separating planes. + for (int i = 0; i < numTetrahedra; ++i) + { + int ibase = 4 * tetrahedron; + int const* v = &mIndices[ibase]; + + info.path[info.numPath++] = tetrahedron; + info.finalTetrahedron = tetrahedron; + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + info.finalV[3] = v[3]; + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[1], v[2], v[3]) > 0) + { + tetrahedron = mAdjacencies[ibase]; + if (tetrahedron == -1) + { + info.finalV[0] = v[1]; + info.finalV[1] = v[2]; + info.finalV[2] = v[3]; + info.finalV[3] = v[0]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[0], v[2], v[3]) < 0) + { + tetrahedron = mAdjacencies[ibase + 1]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[2]; + info.finalV[2] = v[3]; + info.finalV[3] = v[1]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[0], v[1], v[3]) > 0) + { + tetrahedron = mAdjacencies[ibase + 2]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[3]; + info.finalV[3] = v[2]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside tetrahedron. + if (mQuery.ToPlane(test, v[0], v[1], v[2]) < 0) + { + tetrahedron = mAdjacencies[ibase + 3]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + info.finalV[3] = v[3]; + return -1; + } + continue; + } + + return tetrahedron; + } + } + else + { + LogError("The dimension must be 3."); + } + return -1; +} + +template +bool Delaunay3::GetContainingTetrahedron(int i, std::shared_ptr& tetra) const +{ + int numTetrahedra = static_cast(mGraph.GetTetrahedra().size()); + for (int t = 0; t < numTetrahedra; ++t) + { + int j; + for (j = 0; j < 4; ++j) + { + auto const& opposite = TetrahedronKey::oppositeFace; + int v0 = tetra->V[opposite[j][0]]; + int v1 = tetra->V[opposite[j][1]]; + int v2 = tetra->V[opposite[j][2]]; + if (mQuery.ToPlane(i, v0, v1, v2) > 0) + { + // Point i sees face from outside the tetrahedron. + auto adjTetra = tetra->S[j].lock(); + if (adjTetra) + { + // Traverse to the tetrahedron sharing the face. + tetra = adjTetra; + break; + } + else + { + // We reached a hull face, so the point is outside the + // hull. TODO: Once a hull data structure is in place, + // return tetra->S[j] as the candidate for starting a + // search for visible hull faces. + return false; + } + } + + } + + if (j == 4) + { + // The point is inside all four faces, so the point is inside + // a tetrahedron. + return true; + } + } + + LogError("Unexpected termination of GetContainingTetrahedron."); + return false; +} + +template +bool Delaunay3::GetAndRemoveInsertionPolyhedron(int i, + std::set>& candidates, std::set>& boundary) +{ + // Locate the tetrahedra that make up the insertion polyhedron. + TSManifoldMesh polyhedron; + while (candidates.size() > 0) + { + std::shared_ptr tetra = *candidates.begin(); + candidates.erase(candidates.begin()); + + for (int j = 0; j < 4; ++j) + { + auto adj = tetra->S[j].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + int a3 = adj->V[3]; + if (mQuery.ToCircumsphere(i, a0, a1, a2, a3) <= 0) + { + // Point i is in the circumsphere. + candidates.insert(adj); + } + } + } + + int v0 = tetra->V[0]; + int v1 = tetra->V[1]; + int v2 = tetra->V[2]; + int v3 = tetra->V[3]; + if (!polyhedron.Insert(v0, v1, v2, v3)) + { + return false; + } + if (!mGraph.Remove(v0, v1, v2, v3)) + { + return false; + } + } + + // Get the boundary triangles of the insertion polyhedron. + for (auto const& element : polyhedron.GetTetrahedra()) + { + std::shared_ptr tetra = element.second; + for (int j = 0; j < 4; ++j) + { + if (!tetra->S[j].lock()) + { + auto const& opposite = TetrahedronKey::oppositeFace; + int v0 = tetra->V[opposite[j][0]]; + int v1 = tetra->V[opposite[j][1]]; + int v2 = tetra->V[opposite[j][2]]; + boundary.insert(TriangleKey(v0, v1, v2)); + } + } + } + return true; +} + +template +bool Delaunay3::Update(int i) +{ + auto const& smap = mGraph.GetTetrahedra(); + std::shared_ptr tetra = smap.begin()->second; + if (GetContainingTetrahedron(i, tetra)) + { + // The point is inside the convex hull. The insertion polyhedron + // contains only tetrahedra in the current tetrahedralization; the + // hull does not change. + + // Use a depth-first search for those tetrahedra whose circumspheres + // contain point i. + std::set> candidates; + candidates.insert(tetra); + + // Get the boundary of the insertion polyhedron C that contains the + // tetrahedra whose circumspheres contain point i. C contains the + // point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolyhedron(i, candidates, boundary)) + { + return false; + } + + // The insertion polyhedron consists of the tetrahedra formed by + // point i and the faces of C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) < 0) + { + if (!mGraph.Insert(i, v0, v1, v2)) + { + return false; + } + } + // else: Point i is on an edge or face of 'tetra', so the + // subdivision has degenerate tetrahedra. Ignore these. + } + } + else + { + // The point is outside the convex hull. The insertion polyhedron + // is formed by point i and any tetrahedra in the current + // tetrahedralization whose circumspheres contain point i. + + // Locate the convex hull of the tetrahedra. TODO: Maintain a hull + // data structure that is updated incrementally. + std::set> hull; + for (auto const& element : smap) + { + std::shared_ptr t = element.second; + for (int j = 0; j < 4; ++j) + { + if (!t->S[j].lock()) + { + auto const& opposite = TetrahedronKey::oppositeFace; + int v0 = t->V[opposite[j][0]]; + int v1 = t->V[opposite[j][1]]; + int v2 = t->V[opposite[j][2]]; + hull.insert(TriangleKey(v0, v1, v2)); + } + } + } + + // TODO: Until the hull change, for now just iterate over all the + // hull faces and use the ones visible to point i to locate the + // insertion polyhedron. + auto const& tmap = mGraph.GetTriangles(); + std::set> candidates; + std::set> visible; + for (auto const& key : hull) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) > 0) + { + auto iter = tmap.find(TriangleKey(v0, v1, v2)); + if (iter != tmap.end() && iter->second->T[1].lock() == nullptr) + { + auto adj = iter->second->T[0].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + int a3 = adj->V[3]; + if (mQuery.ToCircumsphere(i, a0, a1, a2, a3) <= 0) + { + // Point i is in the circumsphere. + candidates.insert(adj); + } + else + { + // Point i is not in the circumsphere but the hull + // face is visible. + visible.insert(key); + } + } + } + else + { + LogError("Unexpected condition (ComputeType not exact?)"); + return false; + } + } + } + + // Get the boundary of the insertion subpolyhedron C that contains the + // tetrahedra whose circumspheres contain point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolyhedron(i, candidates, boundary)) + { + return false; + } + + // The insertion polyhedron P consists of the tetrahedra formed by + // point i and the back faces of C *and* the visible faces of + // mGraph-C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) < 0) + { + // This is a back face of the boundary. + if (!mGraph.Insert(i, v0, v1, v2)) + { + return false; + } + } + } + for (auto const& key : visible) + { + if (!mGraph.Insert(i, key.V[0], key.V[2], key.V[1])) + { + return false; + } + } + } + + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3Mesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3Mesh.h new file mode 100644 index 000000000000..9e5e29aaada5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDelaunay3Mesh.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class Delaunay3Mesh +{ +public: + // Construction. + Delaunay3Mesh(Delaunay3 const& delaunay); + + // Mesh information. + inline int GetNumVertices() const; + inline int GetNumTetrahedra() const; + inline Vector3 const* GetVertices() const; + inline int const* GetIndices() const; + inline int const* GetAdjacencies() const; + + // Containment queries. + int GetContainingTetrahedron(Vector3 const& P) const; + bool GetVertices(int t, std::array, 4>& vertices) + const; + bool GetIndices(int t, std::array& indices) const; + bool GetAdjacencies(int t, std::array& adjacencies) const; + bool GetBarycentrics(int t, Vector3 const& P, + std::array& bary) const; + +private: + Delaunay3 const* mDelaunay; +}; + + +template +Delaunay3Mesh::Delaunay3Mesh( + Delaunay3 const& delaunay) + : + mDelaunay(&delaunay) +{ +} + +template +inline int Delaunay3Mesh:: +GetNumVertices() const +{ + return mDelaunay->GetNumVertices(); +} + +template +inline int Delaunay3Mesh:: +GetNumTetrahedra() const +{ + return mDelaunay->GetNumTetrahedra(); +} + +template +inline Vector3 const* +Delaunay3Mesh:: +GetVertices() const +{ + return mDelaunay->GetVertices(); +} + +template +inline int const* Delaunay3Mesh:: +GetIndices() const +{ + return &mDelaunay->GetIndices()[0]; +} + +template +inline int const* Delaunay3Mesh:: +GetAdjacencies() const +{ + return &mDelaunay->GetAdjacencies()[0]; +} + +template +int Delaunay3Mesh:: +GetContainingTetrahedron(Vector3 const& P) const +{ + typename Delaunay3::SearchInfo info; + return mDelaunay->GetContainingTetrahedron(P, info); +} + +template +bool Delaunay3Mesh:: +GetVertices(int t, std::array, 4>& vertices) const +{ + if (mDelaunay->GetDimension() == 3) + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery3 const& query = mDelaunay->GetQuery(); + Vector3 const* ctVertices = query.GetVertices(); + for (int i = 0; i < 4; ++i) + { + Vector3 const& V = ctVertices[indices[i]]; + for (int j = 0; j < 3; ++j) + { + vertices[i][j] = (InputType)V[j]; + } + } + return true; + } + } + return false; +} + +template +bool Delaunay3Mesh:: +GetIndices(int t, std::array& indices) const +{ + return mDelaunay->GetIndices(t, indices); +} + +template +bool Delaunay3Mesh:: +GetAdjacencies(int t, std::array& indices) const +{ + return mDelaunay->GetAdjacencies(t, indices); +} + +template +bool Delaunay3Mesh:: +GetBarycentrics(int t, Vector3 const& P, +std::array& bary) const +{ + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery3 const& query = mDelaunay->GetQuery(); + Vector3 const* vertices = query.GetVertices(); + Vector3 rtP{ P[0], P[1], P[2] }; + std::array, 4> rtV; + for (int i = 0; i < 4; ++i) + { + Vector3 const& V = vertices[indices[i]]; + for (int j = 0; j < 3; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[4]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtV[3], rtBary)) + { + for (int i = 0; i < 4; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBox3OrientedBox3.h new file mode 100644 index 000000000000..11b61e1faec0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBox3OrientedBox3.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array box0Parameter, box1Parameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 144 (n = 12, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(AlignedBox3 const& box0, OrientedBox3 const& box1); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result + DCPQuery, OrientedBox3>::operator()( + AlignedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Translate the boxes so that the aligned box becomes a canonical box. + // Modify the oriented box coefficients to be nonnegative. + Vector3 K = box0.max - box0.min; + Vector3 delta = box1.center - box0.min; + for (int i = 0; i < 3; ++i) + { + delta -= box1.extent[i] * box1.axis[i]; + } + + Vector3 rotDelta; + for (int i = 0; i < 3; ++i) + { + rotDelta[i] = Dot(box1.axis[i], delta); + } + + Vector3 twoExtent = box1.extent * (Real)2; + + // The LCP has 6 variables and 6 (nontrivial) inequality constraints. + std::array q = + { + -delta[0], -delta[1], -delta[2], rotDelta[0], rotDelta[1], rotDelta[2], + K[0], K[1], K[2], twoExtent[0], twoExtent[1], twoExtent[2] + }; + + std::array, 12> M; + { + Real const z = (Real)0; + Real const p = (Real)1; + Real const m = (Real)-1; + Vector3 const& U0 = box1.axis[0]; + Vector3 const& U1 = box1.axis[1]; + Vector3 const& U2 = box1.axis[2]; + M[ 0] = { p, z, z, -U0[0], -U1[0], -U1[2], p, z, z, z, z, z }; + M[ 1] = { z, p, z, -U0[1], -U1[1], -U1[1], z, p, z, z, z, z }; + M[ 2] = { z, z, p, -U0[2], -U1[2], -U1[2], z, z, p, z, z, z }; + M[ 3] = { -U0[0], -U0[1], -U0[2], p, z, z, z, z, z, p, z, z }; + M[ 4] = { -U1[0], -U1[1], -U1[2], z, p, z, z, z, z, z, p, z }; + M[ 5] = { -U2[0], -U2[1], -U2[2], z, z, p, z, z, z, z, z, p }; + M[ 6] = { m, z, z, z, z, z, z, z, z, z, z, z }; + M[ 7] = { z, m, z, z, z, z, z, z, z, z, z, z }; + M[ 8] = { z, z, m, z, z, z, z, z, z, z, z, z }; + M[ 9] = { z, z, z, m, z, z, z, z, z, z, z, z }; + M[10] = { z, z, z, z, m, z, z, z, z, z, z, z }; + M[11] = { z, z, z, z, z, m, z, z, z, z, z, z }; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = z[i] + box0.min[i]; + result.closestPoint[0][i] = result.box0Parameter[i]; + } + + result.closestPoint[1] = box1.center; + for (int i = 0, j = 3; i < 3; ++i, ++j) + { + result.box1Parameter[i] = z[j] - box1.extent[i]; + result.closestPoint[1] += result.box1Parameter[i] * box1.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = (Real)0; + result.box1Parameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBoxAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBoxAlignedBox.h new file mode 100644 index 000000000000..09c6e037087d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistAlignedBoxAlignedBox.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox> +{ +public: + struct Result + { + Real distance, sqrDistance; + + // To compute a single closest point on each box, use + // Vector closest0 = (closestPoints[0].min + closestPoints[0].max)/2; + // Vector closest1 = (closestPoints[1].min + closestPoints[1].max)/2; + AlignedBox closestPoints[2]; + }; + + Result operator()(AlignedBox const& box0, AlignedBox const& box1); +}; + + +template +typename DCPQuery, AlignedBox>::Result +DCPQuery, AlignedBox>::operator()( + AlignedBox const& box0, AlignedBox const& box1) +{ + Result result; + result.sqrDistance = (Real)0; + for (int i = 0; i < N; ++i) + { + if (box0.min[i] >= box1.max[i]) + { + Real delta = box0.min[i] - box1.min[i]; + result.sqrDistance += delta * delta; + result.closestPoints[0].min[i] = box0.min[i]; + result.closestPoints[0].max[i] = box0.min[i]; + result.closestPoints[1].min[i] = box1.max[i]; + result.closestPoints[1].max[i] = box1.max[i]; + } + else if (box1.min[i] >= box0.max[i]) + { + Real delta = box1.min[i] - box0.max[i]; + result.sqrDistance += delta * delta; + result.closestPoints[0].min[i] = box0.max[i]; + result.closestPoints[0].max[i] = box0.max[i]; + result.closestPoints[1].min[i] = box1.min[i]; + result.closestPoints[1].max[i] = box1.min[i]; + } + else + { + std::array intr0 = {{ box0.min[i], box0.max[i] }}; + std::array intr1 = {{ box1.min[i], box1.max[i] }}; + FIQuery, std::array> query; + auto iiResult = query(intr0, intr1); + for (int j = 0; j < 2; ++j) + { + result.closestPoints[j].min[i] = iiResult.overlap[0]; + result.closestPoints[j].max[i] = iiResult.overlap[1]; + } + } + } + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistCircle3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistCircle3Circle3.h new file mode 100644 index 000000000000..c40558be0e61 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistCircle3Circle3.h @@ -0,0 +1,382 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/17) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The 3D circle-circle distance algorithm is described in +// http://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace gte +{ + template + class DCPQuery, Circle3> + { + public: + struct Result + { + Real distance, sqrDistance; + int numClosestPairs; + Vector3 circle0Closest[2], circle1Closest[2]; + bool equidistant; + }; + + Result operator()(Circle3 const& circle0, Circle3 const& circle1) + { + Result result; + Vector3 const vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 N0 = circle0.normal, N1 = circle1.normal; + Real r0 = circle0.radius, r1 = circle1.radius; + Vector3 D = circle1.center - circle0.center; + Vector3 N0xN1 = Cross(N0, N1); + + if (N0xN1 != vzero) + { + // Get parameters for constructing the degree-8 polynomial phi. + Real const one = (Real)1, two = (Real)2; + Real r0sqr = r0 * r0, r1sqr = r1 * r1; + + // Compute U1 and V1 for the plane of circle1. + Vector3 basis[3]; + basis[0] = circle1.normal; + ComputeOrthogonalComplement(1, basis); + Vector3 U1 = basis[1], V1 = basis[2]; + + // Construct the polynomial phi(cos(theta)). + Vector3 N0xD = Cross(N0, D); + Vector3 N0xU1 = Cross(N0, U1), N0xV1 = Cross(N0, V1); + Real a0 = r1 * Dot(D, U1), a1 = r1 * Dot(D, V1); + Real a2 = Dot(N0xD, N0xD), a3 = r1 * Dot(N0xD, N0xU1); + Real a4 = r1 * Dot(N0xD, N0xV1), a5 = r1sqr * Dot(N0xU1, N0xU1); + Real a6 = r1sqr * Dot(N0xU1, N0xV1), a7 = r1sqr * Dot(N0xV1, N0xV1); + Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 }; + Polynomial1 p1{ two * a4, two * a6 }; + Polynomial1 p2{ zero, a1 }; + Polynomial1 p3{ -a0 }; + Polynomial1 p4{ -a6, a4, two * a6 }; + Polynomial1 p5{ -a3, a7 - a5 }; + Polynomial1 tmp0{ one, zero, -one }; + Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3; + Polynomial1 tmp2 = two * p2 * p3; + Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5; + Polynomial1 tmp4 = two * p4 * p5; + Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3; + Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4; + + // The use of 'double' is intentional in case Real is a BSNumber or + // BSRational type. We want the bisections to terminate in a + // reasonable amount of time. + unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC; + Real roots[8], sn, temp; + int i, degree, numRoots; + + // The RootsPolynomial::Find(...) function currently does not + // combine duplicate roots. We need only the unique ones here. + std::set uniqueRoots; + + std::array, 16> pairs; + int numPairs = 0; + if (p7.GetDegree() > 0 || p7[0] != zero) + { + // H(cs,sn) = p6(cs) + sn * p7(cs) + Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7; + degree = static_cast(phi.GetDegree()); + LogAssert(degree > 0, "Unexpected degree for phi."); + numRoots = RootsPolynomial::Find(degree, &phi[0], + maxIterations, roots); + for (i = 0; i < numRoots; ++i) + { + uniqueRoots.insert(roots[i]); + } + + for (auto cs : uniqueRoots) + { + if (std::abs(cs) <= one) + { + temp = p7(cs); + if (temp != zero) + { + sn = -p6(cs) / temp; + pairs[numPairs++] = std::make_pair(cs, sn); + } + else + { + temp = std::max(one - cs * cs, zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + { + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + } + else + { + // H(cs,sn) = p6(cs) + degree = static_cast(p6.GetDegree()); + LogAssert(degree > 0, "Unexpected degree for p6."); + numRoots = RootsPolynomial::Find(degree, &p6[0], + maxIterations, roots); + for (i = 0; i < numRoots; ++i) + { + uniqueRoots.insert(roots[i]); + } + + for (auto cs : uniqueRoots) + { + if (std::abs(cs) <= one) + { + temp = std::max(one - cs * cs, zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + { + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + + std::array candidates; + for (i = 0; i < numPairs; ++i) + { + ClosestInfo& info = candidates[i]; + Vector3 delta = + D + r1 * (pairs[i].first * U1 + pairs[i].second * V1); + info.circle1Closest = circle0.center + delta; + Real N0dDelta = Dot(N0, delta); + Real lenN0xDelta = Length(Cross(N0, delta)); + if (lenN0xDelta > 0) + { + Real diff = lenN0xDelta - r0; + info.sqrDistance = N0dDelta * N0dDelta + diff * diff; + delta -= N0dDelta * circle0.normal; + Normalize(delta); + info.circle0Closest = circle0.center + r0 * delta; + info.equidistant = false; + } + else + { + Vector3 r0U0 = r0 * GetOrthogonal(N0, true); + Vector3 diff = delta - r0U0; + info.sqrDistance = Dot(diff, diff); + info.circle0Closest = circle0.center + r0U0; + info.equidistant = true; + } + } + + std::sort(candidates.begin(), candidates.begin() + numPairs); + + result.numClosestPairs = 1; + result.sqrDistance = candidates[0].sqrDistance; + result.circle0Closest[0] = candidates[0].circle0Closest; + result.circle1Closest[0] = candidates[0].circle1Closest; + result.equidistant = candidates[0].equidistant; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.circle0Closest[1] = candidates[1].circle0Closest; + result.circle1Closest[1] = candidates[1].circle1Closest; + } + } + else + { + // The planes of the circles are parallel. Whether the planes are the + // same or different, the problem reduces to determining how two + // circles in the same plane are separated, tangent with one circle + // outside the other, overlapping, or one circle contained inside the + // other circle. + DoQueryParallelPlanes(circle0, circle1, D, result); + } + + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + private: + class SCPolynomial + { + public: + SCPolynomial() + { + } + + SCPolynomial(Real oneTerm, Real cosTerm, Real sinTerm) + { + mPoly[0] = Polynomial1{ oneTerm, cosTerm }; + mPoly[1] = Polynomial1{ sinTerm }; + } + + inline Polynomial1 const& operator[] (unsigned int i) const + { + return mPoly[i]; + } + + inline Polynomial1& operator[] (unsigned int i) + { + return mPoly[i]; + } + + SCPolynomial operator+(SCPolynomial const& object) const + { + SCPolynomial result; + result.mPoly[0] = mPoly[0] + object.mPoly[0]; + result.mPoly[1] = mPoly[1] + object.mPoly[1]; + return result; + } + + SCPolynomial operator-(SCPolynomial const& object) const + { + SCPolynomial result; + result.mPoly[0] = mPoly[0] - object.mPoly[0]; + result.mPoly[1] = mPoly[1] - object.mPoly[1]; + return result; + } + + SCPolynomial operator*(SCPolynomial const& object) const + { + Polynomial1 omcsqr{ (Real)1, (Real)0, (Real)-1 }; // 1 - c^2 + SCPolynomial result; + result.mPoly[0] = mPoly[0] * object.mPoly[0] + omcsqr * mPoly[1] * object.mPoly[1]; + result.mPoly[1] = mPoly[0] * object.mPoly[1] + mPoly[1] * object.mPoly[0]; + return result; + } + + SCPolynomial operator*(Real scalar) const + { + SCPolynomial result; + result.mPoly[0] = scalar * mPoly[0]; + result.mPoly[1] = scalar * mPoly[1]; + return result; + } + + private: + Polynomial1 mPoly[2]; // poly0(c) + s * poly1(c) + }; + + struct ClosestInfo + { + Real sqrDistance; + Vector3 circle0Closest, circle1Closest; + bool equidistant; + + inline bool operator< (ClosestInfo const& info) const + { + return sqrDistance < info.sqrDistance; + } + }; + + // The two circles are in parallel planes where D = C1 - C0, the + // difference of circle centers. + void DoQueryParallelPlanes(Circle3 const& circle0, Circle3 const& circle1, + Vector3 const& D, Result& result) + { + Real N0dD = Dot(circle0.normal, D); + Vector3 normProj = N0dD * circle0.normal; + Vector3 compProj = D - normProj; + Vector3 U = compProj; + Real d = Normalize(U); + + // The configuration is determined by the relative location of the + // intervals of projection of the circles on to the D-line. Circle0 + // projects to [-r0,r0] and circle1 projects to [d-r1,d+r1]. + Real r0 = circle0.radius, r1 = circle1.radius; + Real dmr1 = d - r1; + Real distance; + if (dmr1 >= r0) // d >= r0 + r1 + { + // The circles are separated (d > r0 + r1) or tangent with one + // outside the other (d = r0 + r1). + distance = dmr1 - r0; + result.numClosestPairs = 1; + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center - r1 * U; + result.equidistant = false; + } + else // d < r0 + r1 + { + // The cases implicitly use the knowledge that d >= 0. + Real dpr1 = d + r1; + if (dpr1 <= r0) + { + // Circle1 is inside circle0. + distance = r0 - dpr1; + result.numClosestPairs = 1; + if (d > (Real)0) + { + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = false; + } + else + { + // The circles are concentric, so U = (0,0,0). Construct + // a vector perpendicular to N0 to use for closest points. + U = GetOrthogonal(circle0.normal, true); + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = true; + } + } + else if (dmr1 <= -r0) + { + // Circle0 is inside circle1. + distance = -r0 - dmr1; + result.numClosestPairs = 1; + if (d > (Real)0) + { + result.circle0Closest[0] = circle0.center - r0 * U; + result.circle1Closest[0] = circle1.center - r1 * U; + result.equidistant = false; + } + else + { + // The circles are concentric, so U = (0,0,0). Construct + // a vector perpendicular to N0 to use for closest points. + U = GetOrthogonal(circle0.normal, true); + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = true; + } + } + else + { + // The circles are overlapping. The two points of intersection + // are C0 + s*(C1-C0) +/- h*Cross(N,U), where + // s = (1 + (r0^2 - r1^2)/d^2)/2 and h = sqrt(r0^2 - s^2 * d^2). + Real r0sqr = r0 * r0, r1sqr = r1 * r1, dsqr = d * d; + Real s = ((Real)1 + (r0sqr - r1sqr) / dsqr) / (Real)2; + Real arg = std::max(r0sqr - dsqr * s * s, (Real)0); + Real h = std::sqrt(arg); + Vector3 midpoint = circle0.center + s * compProj; + Vector3 hNxU = h * Cross(circle0.normal, U); + distance = (Real)0; + result.numClosestPairs = 2; + result.circle0Closest[0] = midpoint + hNxU; + result.circle0Closest[1] = midpoint - hNxU; + result.circle1Closest[0] = result.circle0Closest[0] + normProj; + result.circle1Closest[1] = result.circle0Closest[1] + normProj; + result.equidistant = false; + } + } + + result.sqrDistance = distance * distance + N0dD * N0dD; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3AlignedBox3.h new file mode 100644 index 000000000000..0ea921d56a5c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3AlignedBox3.h @@ -0,0 +1,553 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box); + +protected: + // Compute the distance and closest point between a line and an + // axis-aligned box whose center is the origin. On input, 'point' is the + // line origin and 'direction' is the line direction. On output, 'point' + // is the point on the box closest to the line. The 'direction' is + // non-const to allow transforming the problem into the first octant. + void DoQuery(Vector3& point, Vector3& direction, + Vector3 const& boxExtent, Result& result); + +private: + void Face(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& PmE, + Vector3 const& boxExtent, Result& result); + + void CaseNoZeros(Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result); + + void Case0(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& boxExtent, + Result& result); + + void Case00(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& boxExtent, + Result& result); + + void Case000(Vector3& pnt, Vector3 const& boxExtent, + Result& result); +}; + + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3>::operator()( + Line3 const& line, AlignedBox3 const& box) +{ + // Translate the line and box so that the box has center at the origin. + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + Vector3 point = line.origin - boxCenter; + Vector3 direction = line.direction; + + Result result; + DoQuery(point, direction, boxExtent, result); + + // Compute the closest point on the line. + result.closestPoint[0] = + line.origin + result.lineParameter*line.direction; + + // Compute the closest point on the box. + result.closestPoint[1] = boxCenter + point; + return result; +} + +template +void DCPQuery, AlignedBox3>::DoQuery( + Vector3& point, Vector3& direction, + Vector3 const& boxExtent, Result& result) +{ + result.sqrDistance = (Real)0; + result.lineParameter = (Real)0; + + // Apply reflections so that direction vector has nonnegative components. + bool reflect[3]; + for (int i = 0; i < 3; ++i) + { + if (direction[i] < (Real)0) + { + point[i] = -point[i]; + direction[i] = -direction[i]; + reflect[i] = true; + } + else + { + reflect[i] = false; + } + } + + if (direction[0] > (Real)0) + { + if (direction[1] > (Real)0) + { + if (direction[2] > (Real)0) // (+,+,+) + { + CaseNoZeros(point, direction, boxExtent, result); + } + else // (+,+,0) + { + Case0(0, 1, 2, point, direction, boxExtent, result); + } + } + else + { + if (direction[2] > (Real)0) // (+,0,+) + { + Case0(0, 2, 1, point, direction, boxExtent, result); + } + else // (+,0,0) + { + Case00(0, 1, 2, point, direction, boxExtent, result); + } + } + } + else + { + if (direction[1] > (Real)0) + { + if (direction[2] > (Real)0) // (0,+,+) + { + Case0(1, 2, 0, point, direction, boxExtent, result); + } + else // (0,+,0) + { + Case00(1, 0, 2, point, direction, boxExtent, result); + } + } + else + { + if (direction[2] > (Real)0) // (0,0,+) + { + Case00(2, 0, 1, point, direction, boxExtent, result); + } + else // (0,0,0) + { + Case000(point, boxExtent, result); + } + } + } + + // Undo the reflections applied previously. + for (int i = 0; i < 3; ++i) + { + if (reflect[i]) + { + point[i] = -point[i]; + } + } + + result.distance = std::sqrt(result.sqrDistance); +} + +template +void DCPQuery, AlignedBox3>::Face(int i0, int i1, + int i2, Vector3& pnt, Vector3 const& dir, + Vector3 const& PmE, Vector3 const& boxExtent, Result& result) +{ + Vector3 PpE; + Real lenSqr, inv, tmp, param, t, delta; + + PpE[i1] = pnt[i1] + boxExtent[i1]; + PpE[i2] = pnt[i2] + boxExtent[i2]; + if (dir[i0] * PpE[i1] >= dir[i1] * PmE[i0]) + { + if (dir[i0] * PpE[i2] >= dir[i2] * PmE[i0]) + { + // v[i1] >= -e[i1], v[i2] >= -e[i2] (distance = 0) + pnt[i0] = boxExtent[i0]; + inv = ((Real)1) / dir[i0]; + pnt[i1] -= dir[i1] * PmE[i0] * inv; + pnt[i2] -= dir[i2] * PmE[i0] * inv; + result.lineParameter = -PmE[i0] * inv; + } + else + { + // v[i1] >= -e[i1], v[i2] < -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i2] * dir[i2]; + tmp = lenSqr*PpE[i1] - dir[i1] * (dir[i0] * PmE[i0] + + dir[i2] * PpE[i2]); + if (tmp <= ((Real)2)*lenSqr*boxExtent[i1]) + { + t = tmp / lenSqr; + lenSqr += dir[i1] * dir[i1]; + tmp = PpE[i1] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * tmp + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + tmp*tmp + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = t - boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + else + { + lenSqr += dir[i1] * dir[i1]; + delta = dir[i0] * PmE[i0] + dir[i1] * PmE[i1] + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PmE[i1] * PmE[i1] + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + } + } + else + { + if (dir[i0] * PpE[i2] >= dir[i2] * PmE[i0]) + { + // v[i1] < -e[i1], v[i2] >= -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i1] * dir[i1]; + tmp = lenSqr*PpE[i2] - dir[i2] * (dir[i0] * PmE[i0] + + dir[i1] * PpE[i1]); + if (tmp <= ((Real)2)*lenSqr*boxExtent[i2]) + { + t = tmp / lenSqr; + lenSqr += dir[i2] * dir[i2]; + tmp = PpE[i2] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * tmp; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + tmp*tmp + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = t - boxExtent[i2]; + } + else + { + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * PmE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PmE[i2] * PmE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = boxExtent[i2]; + } + } + else + { + // v[i1] < -e[i1], v[i2] < -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i2] * dir[i2]; + tmp = lenSqr*PpE[i1] - dir[i1] * (dir[i0] * PmE[i0] + + dir[i2] * PpE[i2]); + if (tmp >= (Real)0) + { + // v[i1]-edge is closest + if (tmp <= ((Real)2)*lenSqr*boxExtent[i1]) + { + t = tmp / lenSqr; + lenSqr += dir[i1] * dir[i1]; + tmp = PpE[i1] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * tmp + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + tmp*tmp + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = t - boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + else + { + lenSqr += dir[i1] * dir[i1]; + delta = dir[i0] * PmE[i0] + dir[i1] * PmE[i1] + + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PmE[i1] * PmE[i1] + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + return; + } + + lenSqr = dir[i0] * dir[i0] + dir[i1] * dir[i1]; + tmp = lenSqr*PpE[i2] - dir[i2] * (dir[i0] * PmE[i0] + + dir[i1] * PpE[i1]); + if (tmp >= (Real)0) + { + // v[i2]-edge is closest + if (tmp <= ((Real)2)*lenSqr*boxExtent[i2]) + { + t = tmp / lenSqr; + lenSqr += dir[i2] * dir[i2]; + tmp = PpE[i2] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * tmp; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + tmp*tmp + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = t - boxExtent[i2]; + } + else + { + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + + dir[i2] * PmE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PmE[i2] * PmE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = boxExtent[i2]; + } + return; + } + + // (v[i1],v[i2])-corner is closest + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PpE[i2] * PpE[i2] + delta*param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + } +} + +template +void DCPQuery, AlignedBox3>::CaseNoZeros( + Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) +{ + Vector3 PmE = pnt - boxExtent; + Real prodDxPy = dir[0] * PmE[1]; + Real prodDyPx = dir[1] * PmE[0]; + Real prodDzPx, prodDxPz, prodDzPy, prodDyPz; + + if (prodDyPx >= prodDxPy) + { + prodDzPx = dir[2] * PmE[0]; + prodDxPz = dir[0] * PmE[2]; + if (prodDzPx >= prodDxPz) + { + // line intersects x = e0 + Face(0, 1, 2, pnt, dir, PmE, boxExtent, result); + } + else + { + // line intersects z = e2 + Face(2, 0, 1, pnt, dir, PmE, boxExtent, result); + } + } + else + { + prodDzPy = dir[2] * PmE[1]; + prodDyPz = dir[1] * PmE[2]; + if (prodDzPy >= prodDyPz) + { + // line intersects y = e1 + Face(1, 2, 0, pnt, dir, PmE, boxExtent, result); + } + else + { + // line intersects z = e2 + Face(2, 0, 1, pnt, dir, PmE, boxExtent, result); + } + } +} + +template +void DCPQuery, AlignedBox3>::Case0(int i0, int i1, + int i2, Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) +{ + Real PmE0 = pnt[i0] - boxExtent[i0]; + Real PmE1 = pnt[i1] - boxExtent[i1]; + Real prod0 = dir[i1] * PmE0; + Real prod1 = dir[i0] * PmE1; + Real delta, invLSqr, inv; + + if (prod0 >= prod1) + { + // line intersects P[i0] = e[i0] + pnt[i0] = boxExtent[i0]; + + Real PpE1 = pnt[i1] + boxExtent[i1]; + delta = prod0 - dir[i0] * PpE1; + if (delta >= (Real)0) + { + invLSqr = ((Real)1) / (dir[i0] * dir[i0] + dir[i1] * dir[i1]); + result.sqrDistance += delta*delta*invLSqr; + pnt[i1] = -boxExtent[i1]; + result.lineParameter = -(dir[i0] * PmE0 + dir[i1] * PpE1)*invLSqr; + } + else + { + inv = ((Real)1) / dir[i0]; + pnt[i1] -= prod0*inv; + result.lineParameter = -PmE0*inv; + } + } + else + { + // line intersects P[i1] = e[i1] + pnt[i1] = boxExtent[i1]; + + Real PpE0 = pnt[i0] + boxExtent[i0]; + delta = prod1 - dir[i1] * PpE0; + if (delta >= (Real)0) + { + invLSqr = ((Real)1) / (dir[i0] * dir[i0] + dir[i1] * dir[i1]); + result.sqrDistance += delta*delta*invLSqr; + pnt[i0] = -boxExtent[i0]; + result.lineParameter = -(dir[i0] * PpE0 + dir[i1] * PmE1)*invLSqr; + } + else + { + inv = ((Real)1) / dir[i1]; + pnt[i0] -= prod1*inv; + result.lineParameter = -PmE1*inv; + } + } + + if (pnt[i2] < -boxExtent[i2]) + { + delta = pnt[i2] + boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = -boxExtent[i2]; + } + else if (pnt[i2] > boxExtent[i2]) + { + delta = pnt[i2] - boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = boxExtent[i2]; + } +} + +template +void DCPQuery, AlignedBox3>::Case00(int i0, int i1, + int i2, Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) +{ + Real delta; + + result.lineParameter = (boxExtent[i0] - pnt[i0]) / dir[i0]; + + pnt[i0] = boxExtent[i0]; + + if (pnt[i1] < -boxExtent[i1]) + { + delta = pnt[i1] + boxExtent[i1]; + result.sqrDistance += delta*delta; + pnt[i1] = -boxExtent[i1]; + } + else if (pnt[i1] > boxExtent[i1]) + { + delta = pnt[i1] - boxExtent[i1]; + result.sqrDistance += delta*delta; + pnt[i1] = boxExtent[i1]; + } + + if (pnt[i2] < -boxExtent[i2]) + { + delta = pnt[i2] + boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = -boxExtent[i2]; + } + else if (pnt[i2] > boxExtent[i2]) + { + delta = pnt[i2] - boxExtent[i2]; + result.sqrDistance += delta*delta; + pnt[i2] = boxExtent[i2]; + } +} + +template +void DCPQuery, AlignedBox3>::Case000( + Vector3& pnt, Vector3 const& boxExtent, Result& result) +{ + Real delta; + + if (pnt[0] < -boxExtent[0]) + { + delta = pnt[0] + boxExtent[0]; + result.sqrDistance += delta*delta; + pnt[0] = -boxExtent[0]; + } + else if (pnt[0] > boxExtent[0]) + { + delta = pnt[0] - boxExtent[0]; + result.sqrDistance += delta*delta; + pnt[0] = boxExtent[0]; + } + + if (pnt[1] < -boxExtent[1]) + { + delta = pnt[1] + boxExtent[1]; + result.sqrDistance += delta*delta; + pnt[1] = -boxExtent[1]; + } + else if (pnt[1] > boxExtent[1]) + { + delta = pnt[1] - boxExtent[1]; + result.sqrDistance += delta*delta; + pnt[1] = boxExtent[1]; + } + + if (pnt[2] < -boxExtent[2]) + { + delta = pnt[2] + boxExtent[2]; + result.sqrDistance += delta*delta; + pnt[2] = -boxExtent[2]; + } + else if (pnt[2] > boxExtent[2]) + { + delta = pnt[2] - boxExtent[2]; + result.sqrDistance += delta*delta; + pnt[2] = boxExtent[2]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Circle3.h new file mode 100644 index 000000000000..2f735b1162a5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Circle3.h @@ -0,0 +1,465 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The 3D line-circle distance algorithm is described in +// http://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace gte +{ + +template +class DCPQuery, Circle3> +{ +public: + // The possible number of closest line-circle pairs is 1, 2 or all circle + // points. If 1 or 2, numClosestPairs is set to this number and + // 'equidistant' is false; the number of valid elements in lineClosest[] + // and circleClosest[] is numClosestPairs. If all circle points are + // closest, the line must be C+t*N where C is the circle center, N is + // the normal to the plane of the circle, and lineClosest[0] is set to C. + // In this case, 'equidistant' is true and circleClosest[0] is set to + // C+r*U, where r is the circle radius and U is a vector perpendicular + // to N. + struct Result + { + Real distance, sqrDistance; + int numClosestPairs; + Vector3 lineClosest[2], circleClosest[2]; + bool equidistant; + }; + + // The polynomial-based algorithm. Type Real can be floating-point or + // rational. + Result operator()(Line3 const& line, Circle3 const& circle); + + // The nonpolynomial-based algorithm that uses bisection. Because the + // bisection is iterative, you should choose Real to be a floating-point + // type. However, the algorithm will still work for a rational type, but + // it is costly because of the increase in arbitrary-size integers used + // during the bisection. + Result Robust(Line3 const& line, Circle3 const& circle); + +private: + // Support for operator(...). + struct ClosestInfo + { + Real sqrDistance; + Vector3 lineClosest, circleClosest; + bool equidistant; + + bool operator< (ClosestInfo const& info) const + { + return sqrDistance < info.sqrDistance; + } + }; + + void GetPair(Line3 const& line, Circle3 const& circle, + Vector3 const& D, Real t, Vector3& lineClosest, + Vector3& circleClosest); + + // Support for Robust(...). Bisect the function + // F(s) = s + m2b2 - r*m0sqr*s/sqrt(m0sqr*s*s + b1sqr) + // on the specified interval [smin,smax]. + Real Bisect(Real m2b2, Real rm0sqr, Real m0sqr, Real b1sqr, + Real smin, Real smax); +}; + + +template +typename DCPQuery, Circle3>::Result +DCPQuery, Circle3>::operator()( + Line3 const& line, Circle3 const& circle) +{ + Result result; + Vector3 const vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 D = line.origin - circle.center; + Vector3 NxM = Cross(circle.normal, line.direction); + Vector3 NxD = Cross(circle.normal, D); + Real t; + + if (NxM != vzero) + { + if (NxD != vzero) + { + Real NdM = Dot(circle.normal, line.direction); + if (NdM != zero) + { + // H(t) = (a*t^2 + 2*b*t + c)*(t + d)^2 - r^2*(a*t + b)^2 + // = h0 + h1*t + h2*t^2 + h3*t^3 + h4*t^4 + Real a = Dot(NxM, NxM), b = Dot(NxM, NxD); + Real c = Dot(NxD, NxD), d = Dot(line.direction, D); + Real rsqr = circle.radius * circle.radius; + Real asqr = a * a, bsqr = b * b, dsqr = d * d; + Real h0 = c * dsqr - bsqr * rsqr; + Real h1 = 2 * (c * d + b * dsqr - a * b * rsqr); + Real h2 = c + 4 * b * d + a * dsqr - asqr * rsqr; + Real h3 = 2 * (b + a * d); + Real h4 = a; + + std::map rmMap; + RootsPolynomial::template SolveQuartic( + h0, h1, h2, h3, h4, rmMap); + std::array candidates; + int numRoots = 0; + for (auto const& rm : rmMap) + { + t = rm.first; + ClosestInfo info; + Vector3 NxDelta = NxD + t * NxM; + if (NxDelta != vzero) + { + GetPair(line, circle, D,t, info.lineClosest, + info.circleClosest); + info.equidistant = false; + } + else + { + Vector3 U = GetOrthogonal(circle.normal, true); + info.lineClosest = circle.center; + info.circleClosest = + circle.center + circle.radius * U; + info.equidistant = true; + } + Vector3 diff = info.lineClosest - info.circleClosest; + info.sqrDistance = Dot(diff, diff); + candidates[numRoots++] = info; + } + + std::sort(candidates.begin(), candidates.begin() + numRoots); + + result.numClosestPairs = 1; + result.lineClosest[0] = candidates[0].lineClosest; + result.circleClosest[0] = candidates[0].circleClosest; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.lineClosest[1] = candidates[1].lineClosest; + result.circleClosest[1] = candidates[1].circleClosest; + } + } + else + { + // The line is parallel to the plane of the circle. The + // polynomial has the form H(t) = (t+v)^2*[(t+v)^2-(r^2-u^2)]. + Real u = Dot(NxM, D), v = Dot(line.direction, D); + Real discr = circle.radius * circle.radius - u * u; + if (discr > zero) + { + result.numClosestPairs = 2; + Real rootDiscr = std::sqrt(discr); + t = -v + rootDiscr; + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + t = -v - rootDiscr; + GetPair(line, circle, D, t, result.lineClosest[1], + result.circleClosest[1]); + } + else + { + result.numClosestPairs = 1; + t = -v; + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + } + } + } + else + { + // The line is C+t*M, where M is not parallel to N. The polynomial + // is H(t) = |Cross(N,M)|^2*t^2*(t^2 - r^2*|Cross(N,M)|^2), where + // root t = 0 does not correspond to the global minimum. The other + // roots produce the global minimum. + result.numClosestPairs = 2; + t = circle.radius * Length(NxM); + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + t = -t; + GetPair(line, circle, D, t, result.lineClosest[1], + result.circleClosest[1]); + } + result.equidistant = false; + } + else + { + if (NxD != vzero) + { + // The line is A+t*N (perpendicular to plane) but with A != C. + // The polyhomial is H(t) = |Cross(N,D)|^2*(t + Dot(M,D))^2. + result.numClosestPairs = 1; + t = -Dot(line.direction, D); + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + result.equidistant = false; + } + else + { + // The line is C+t*N, so C is the closest point for the line and + // all circle points are equidistant from it. + Vector3 U = GetOrthogonal(circle.normal, true); + result.numClosestPairs = 1; + result.lineClosest[0] = circle.center; + result.circleClosest[0] = circle.center + circle.radius * U; + result.equidistant = true; + } + } + + Vector3 diff = result.lineClosest[0] - result.circleClosest[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +typename DCPQuery, Circle3>::Result +DCPQuery, Circle3>::Robust( + Line3 const& line, Circle3 const& circle) +{ + // The line is P(t) = B+t*M. The circle is |X-C| = r with Dot(N,X-C)=0. + Result result; + Vector3 vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 D = line.origin - circle.center; + Vector3 MxN = Cross(line.direction, circle.normal); + Vector3 DxN = Cross(D, circle.normal); + + Real m0sqr = Dot(MxN, MxN); + if (m0sqr > zero) + { + // Compute the critical points s for F'(s) = 0. + Vector3 P0, P1; + Real s, t; + int numRoots = 0; + std::array roots; + + // The line direction M and the plane normal N are not parallel. Move + // the line origin B = (b0,b1,b2) to B' = B + lambda*line.direction = + // (0,b1',b2'). + Real m0 = std::sqrt(m0sqr); + Real rm0 = circle.radius * m0; + Real lambda = -Dot(MxN, DxN) / m0sqr; + Vector3 oldD = D; + D += lambda * line.direction; + DxN += lambda * MxN; + Real m2b2 = Dot(line.direction, D); + Real b1sqr = Dot(DxN, DxN); + if (b1sqr > zero) + { + // B' = (0,b1',b2') where b1' != 0. See Sections 1.1.2 and 1.2.2 + // of the PDF documentation. + Real b1 = std::sqrt(b1sqr); + Real rm0sqr = circle.radius * m0sqr; + if (rm0sqr > b1) + { + Real const twoThirds = (Real)2 / (Real)3; + Real sHat = std::sqrt(std::pow(rm0sqr * b1sqr, twoThirds) - b1sqr) / m0; + Real gHat = rm0sqr * sHat / std::sqrt(m0sqr * sHat * sHat + b1sqr); + Real cutoff = gHat - sHat; + if (m2b2 <= -cutoff) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, + -m2b2 + rm0); + roots[numRoots++] = s; + if (m2b2 == -cutoff) + { + roots[numRoots++] = -sHat; + } + } + else if (m2b2 >= cutoff) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -m2b2); + roots[numRoots++] = s; + if (m2b2 == cutoff) + { + roots[numRoots++] = sHat; + } + } + else + { + if (m2b2 <= zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, + -m2b2 + rm0); + roots[numRoots++] = s; + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -sHat); + roots[numRoots++] = s; + } + else + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -m2b2); + roots[numRoots++] = s; + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, sHat, + -m2b2 + rm0); + roots[numRoots++] = s; + } + } + } + else + { + if (m2b2 < zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, + -m2b2 + rm0); + } + else if (m2b2 > zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, + -m2b2); + } + else + { + s = zero; + } + roots[numRoots++] = s; + } + } + else + { + // The new line origin is B' = (0,0,b2'). + if (m2b2 < zero) + { + s = -m2b2 + rm0; + roots[numRoots++] = s; + } + else if (m2b2 > zero) + { + s = -m2b2 - rm0; + roots[numRoots++] = s; + } + else + { + s = -m2b2 + rm0; + roots[numRoots++] = s; + s = -m2b2 - rm0; + roots[numRoots++] = s; + } + } + + std::array candidates; + for (int i = 0; i < numRoots; ++i) + { + t = roots[i] + lambda; + ClosestInfo info; + Vector3 NxDelta = + Cross(circle.normal, oldD + t * line.direction); + if (NxDelta != vzero) + { + GetPair(line, circle, oldD, t, info.lineClosest, + info.circleClosest); + info.equidistant = false; + } + else + { + Vector3 U = GetOrthogonal(circle.normal, true); + info.lineClosest = circle.center; + info.circleClosest = circle.center + circle.radius * U; + info.equidistant = true; + } + Vector3 diff = info.lineClosest - info.circleClosest; + info.sqrDistance = Dot(diff, diff); + candidates[i] = info; + } + + std::sort(candidates.begin(), candidates.begin() + numRoots); + + result.numClosestPairs = 1; + result.lineClosest[0] = candidates[0].lineClosest; + result.circleClosest[0] = candidates[0].circleClosest; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.lineClosest[1] = candidates[1].lineClosest; + result.circleClosest[1] = candidates[1].circleClosest; + } + + result.equidistant = false; + } + else + { + // The line direction and the plane normal are parallel. + if (DxN != vzero) + { + // The line is A+t*N but with A != C. + result.numClosestPairs = 1; + GetPair(line, circle, D, -Dot(line.direction, D), + result.lineClosest[0], result.circleClosest[0]); + result.equidistant = false; + } + else + { + // The line is C+t*N, so C is the closest point for the line and + // all circle points are equidistant from it. + Vector3 U = GetOrthogonal(circle.normal, true); + result.numClosestPairs = 1; + result.lineClosest[0] = circle.center; + result.circleClosest[0] = circle.center + circle.radius * U; + result.equidistant = true; + } + } + + Vector3 diff = result.lineClosest[0] - result.circleClosest[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +void DCPQuery, Circle3>::GetPair( + Line3 const& line, Circle3 const& circle, + Vector3 const& D, Real t, Vector3& lineClosest, + Vector3& circleClosest) +{ + Vector3 delta = D + t * line.direction; + lineClosest = circle.center + delta; + delta -= Dot(circle.normal, delta) * circle.normal; + Normalize(delta); + circleClosest = circle.center + circle.radius * delta; +} + +template +Real DCPQuery, Circle3>::Bisect(Real m2b2, + Real rm0sqr, Real m0sqr, Real b1sqr, Real smin, Real smax) +{ + std::function G = [&, m2b2, rm0sqr, m0sqr, b1sqr](Real s) + { + return s + m2b2 - rm0sqr*s / std::sqrt(m0sqr*s*s + b1sqr); + }; + + // The function is known to be increasing, so we can specify -1 and +1 + // as the function values at the bounding interval endpoints. The use + // of 'double' is intentional in case Real is a BSNumber or BSRational + // type. We want the bisections to terminate in a reasonable amount of + // time. + unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC; + Real root; + RootsBisection::Find(G, smin, smax, (Real)-1, (Real)+1, + maxIterations, root); + Real gmin; + gmin = G(root); + return root; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3OrientedBox3.h new file mode 100644 index 000000000000..94f8244e3cd5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3OrientedBox3.h @@ -0,0 +1,66 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> + : + public DCPQuery, AlignedBox3> +{ +public: + struct Result + : + public DCPQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box); +}; + + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3>::operator()( + Line3 const& line, OrientedBox3 const& box) +{ + // Transform the line to the coordinate system of the oriented box. + // In this system, the box is axis-aligned with center at the origin. + Vector3 diff = line.origin - box.center; + Vector3 point, direction; + for (int i = 0; i < 3; ++i) + { + point[i] = Dot(diff, box.axis[i]); + direction[i] = Dot(line.direction, box.axis[i]); + } + + Result result; + this->DoQuery(point, direction, box.extent, result); + + // Compute the closest point on the line. + result.closestPoint[0] = + line.origin + result.lineParameter*line.direction; + + // Compute the closest point on the box. + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[1] += point[i] * box.axis[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Rectangle3.h new file mode 100644 index 000000000000..daeeb9b4e9f7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Rectangle3.h @@ -0,0 +1,140 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, + Rectangle3 const& rectangle); +}; + +// Template alias for convenience. +template +using DCPLine3Rectangle3 = +DCPQuery, Rectangle3>; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Line3 const& line, Rectangle3 const& rectangle) +{ + Result result; + + // Test if line intersects rectangle. If so, the squared distance is + // zero. + Vector3 N = Cross(rectangle.axis[0], rectangle.axis[1]); + Real NdD = Dot(N, line.direction); + if (std::abs(NdD) > (Real)0) + { + // The line and rectangle are not parallel, so the line intersects + // the plane of the rectangle. + Vector3 diff = line.origin - rectangle.center; + Vector3 basis[3]; // {D, U, V} + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + Real UdD0 = Dot(basis[1], rectangle.axis[0]); + Real UdD1 = Dot(basis[1], rectangle.axis[1]); + Real UdPmC = Dot(basis[1], diff); + Real VdD0 = Dot(basis[2], rectangle.axis[0]); + Real VdD1 = Dot(basis[2], rectangle.axis[1]); + Real VdPmC = Dot(basis[2], diff); + Real invDet = ((Real)1) / (UdD0*VdD1 - UdD1*VdD0); + + // Rectangle coordinates for the point of intersection. + Real s0 = (VdD1*UdPmC - UdD1*VdPmC)*invDet; + Real s1 = (UdD0*VdPmC - VdD0*UdPmC)*invDet; + + if (std::abs(s0) <= rectangle.extent[0] + && std::abs(s1) <= rectangle.extent[1]) + { + // Line parameter for the point of intersection. + Real DdD0 = Dot(line.direction, rectangle.axis[0]); + Real DdD1 = Dot(line.direction, rectangle.axis[1]); + Real DdDiff = Dot(line.direction, diff); + result.lineParameter = s0*DdD0 + s1*DdD1 - DdDiff; + + // Rectangle coordinates for the point of intersection. + result.rectangleParameter[0] = s0; + result.rectangleParameter[1] = s1; + + // The intersection point is inside or on the rectangle. + result.closestPoint[0] = line.origin + + result.lineParameter*line.direction; + + result.closestPoint[1] = rectangle.center + + s0*rectangle.axis[0] + s1*rectangle.axis[1]; + + result.distance = (Real)0; + result.sqrDistance = (Real)0; + return result; + } + } + + // Either (1) the line is not parallel to the rectangle and the point of + // intersection of the line and the plane of the rectangle is outside the + // rectangle or (2) the line and rectangle are parallel. Regardless, the + // closest point on the rectangle is on an edge of the rectangle. Compare + // the line to all four edges of the rectangle. + result.distance = std::numeric_limits::max(); + result.sqrDistance = std::numeric_limits::max(); + Vector3 scaledDir[2] = + { + rectangle.extent[0] * rectangle.axis[0], + rectangle.extent[1] * rectangle.axis[1] + }; + for (int i1 = 0, omi1 = 1; i1 <= 1; ++i1, --omi1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Vector3 segCenter = + rectangle.center + scaledDir[i1] * (Real)i0; + Vector3 segDirection = rectangle.axis[omi1]; + Real segExtent = rectangle.extent[omi1]; + Segment3 segment(segCenter, segDirection, segExtent); + + DCPQuery, Segment3> query; + auto lsResult = query(line, segment); + if (lsResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = lsResult.sqrDistance; + result.distance = lsResult.distance; + result.lineParameter = lsResult.parameter[0]; + Real ratio = lsResult.parameter[1] / segExtent; // in [-1,1] + result.rectangleParameter[0] = + rectangle.extent[0] * (omi1 * i0 + i1 * ratio); + result.rectangleParameter[1] = + rectangle.extent[1] * (i1 * i0 + omi1 * ratio); + result.closestPoint[0] = lsResult.closestPoint[0]; + result.closestPoint[1] = lsResult.closestPoint[1]; + } + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Triangle3.h new file mode 100644 index 000000000000..30486a3b7384 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLine3Triangle3.h @@ -0,0 +1,127 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, + Triangle3 const& triangle); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Line3 const& line, Triangle3 const& triangle) +{ + Result result; + + // Test if line intersects triangle. If so, the squared distance is zero. + Vector3 edge0 = triangle.v[1] - triangle.v[0]; + Vector3 edge1 = triangle.v[2] - triangle.v[0]; + Vector3 normal = UnitCross(edge0, edge1); + Real NdD = Dot(normal, line.direction); + if (std::abs(NdD) > (Real)0) + { + // The line and triangle are not parallel, so the line intersects + // the plane of the triangle. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 basis[3]; // {D, U, V} + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + Real UdE0 = Dot(basis[1], edge0); + Real UdE1 = Dot(basis[1], edge1); + Real UdDiff = Dot(basis[1], diff); + Real VdE0 = Dot(basis[2], edge0); + Real VdE1 = Dot(basis[2], edge1); + Real VdDiff = Dot(basis[2], diff); + Real invDet = ((Real)1) / (UdE0*VdE1 - UdE1*VdE0); + + // Barycentric coordinates for the point of intersection. + Real b1 = (VdE1*UdDiff - UdE1*VdDiff)*invDet; + Real b2 = (UdE0*VdDiff - VdE0*UdDiff)*invDet; + Real b0 = (Real)1 - b1 - b2; + + if (b0 >= (Real)0 && b1 >= (Real)0 && b2 >= (Real)0) + { + // Line parameter for the point of intersection. + Real DdE0 = Dot(line.direction, edge0); + Real DdE1 = Dot(line.direction, edge1); + Real DdDiff = Dot(line.direction, diff); + result.lineParameter = b1*DdE0 + b2*DdE1 - DdDiff; + + // Barycentric coordinates for the point of intersection. + result.triangleParameter[0] = b0; + result.triangleParameter[1] = b1; + result.triangleParameter[2] = b2; + + // The intersection point is inside or on the triangle. + result.closestPoint[0] = line.origin + + result.lineParameter*line.direction; + result.closestPoint[1] = triangle.v[0] + b1*edge0 + b2*edge1; + + result.distance = (Real)0; + result.sqrDistance = (Real)0; + return result; + } + } + + // Either (1) the line is not parallel to the triangle and the point of + // intersection of the line and the plane of the triangle is outside the + // triangle or (2) the line and triangle are parallel. Regardless, the + // closest point on the triangle is on an edge of the triangle. Compare + // the line to all three edges of the triangle. + result.distance = std::numeric_limits::max(); + result.sqrDistance = std::numeric_limits::max(); + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle.v[i0] + + triangle.v[i1]); + Vector3 segDirection = triangle.v[i1] - triangle.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 segment(segCenter, segDirection, segExtent); + + DCPQuery, Segment3> query; + auto lsResult = query(line, segment); + if (lsResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = lsResult.sqrDistance; + result.distance = lsResult.distance; + result.lineParameter = lsResult.parameter[0]; + result.triangleParameter[i0] = ((Real)0.5)*((Real)1 - + lsResult.parameter[0] / segExtent); + result.triangleParameter[i1] = (Real)1 - + result.triangleParameter[i0]; + result.triangleParameter[3 - i0 - i1] = (Real)0; + result.closestPoint[0] = lsResult.closestPoint[0]; + result.closestPoint[1] = lsResult.closestPoint[1]; + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineLine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineLine.h new file mode 100644 index 000000000000..0d83130dd760 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineLine.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Line> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Line const& line0, Line const& line1); +}; + +// Template aliases for convenience. +template +using DCPLineLine = DCPQuery, Line>; + +template +using DCPLine2Line2 = DCPLineLine<2, Real>; + +template +using DCPLine3Line3 = DCPLineLine<3, Real>; + + +template +typename DCPQuery, Line>::Result +DCPQuery, Line>::operator()( + Line const& line0, Line const& line1) +{ + Result result; + + Vector diff = line0.origin - line1.origin; + Real a01 = -Dot(line0.direction, line1.direction); + Real b0 = Dot(diff, line0.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // Lines are not parallel. + Real det = (Real)1 - a01 * a01; + Real b1 = -Dot(diff, line1.direction); + s0 = (a01 * b1 - b0) / det; + s1 = (a01 * b0 - b1) / det; + } + else + { + // Lines are parallel, select any pair of closest points. + s0 = -b0; + s1 = (Real)0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line0.origin + s0 * line0.direction; + result.closestPoint[1] = line1.origin + s1 * line1.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineRay.h new file mode 100644 index 000000000000..6227b0bc5629 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineRay.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Ray> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Line const& line, Ray const& ray); +}; + +// Template aliases for convenience. +template +using DCPLineRay = DCPQuery, Ray>; + +template +using DCPLine2Ray2 = DCPLineRay<2, Real>; + +template +using DCPLine3Ray3 = DCPLineRay<3, Real>; + + +template +typename DCPQuery, Ray>::Result +DCPQuery, Ray>::operator()( + Line const& line, Ray const& ray) +{ + Result result; + + Vector diff = line.origin - ray.origin; + Real a01 = -Dot(line.direction, ray.direction); + Real b0 = Dot(diff, line.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + Real b1 = -Dot(diff, ray.direction); + s1 = a01 * b0 - b1; + + if (s1 >= (Real)0) + { + // Two interior points are closest, one on line and one on ray. + Real det = (Real)1 - a01 * a01; + s0 = (a01 * b1 - b0) / det; + s1 /= det; + } + else + { + // Origin of ray and interior point of line are closest. + s0 = -b0; + s1 = (Real)0; + } + } + else + { + // Lines are parallel, closest pair with one point at ray origin. + s0 = -b0; + s1 = (Real)0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line.origin + s0 * line.direction; + result.closestPoint[1] = ray.origin + s1 * ray.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineSegment.h new file mode 100644 index 000000000000..4f6129468f1e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistLineSegment.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + // The centered form of the 'segment' is used. Thus, parameter[1] of + // the result is in [-e,e], where e = |segment.p[1] - segment.p[0]|/2. + Result operator()(Line const& line, + Segment const& segment); +}; + +// Template aliases for convenience. +template +using DCPLineSegment = DCPQuery, Segment>; + +template +using DCPLine2Segment2 = DCPLineSegment<2, Real>; + +template +using DCPLine3Segment3 = DCPLineSegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Line const& line, Segment const& segment) +{ + Result result; + + Vector segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Vector diff = line.origin - segCenter; + Real a01 = -Dot(line.direction, segDirection); + Real b0 = Dot(diff, line.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // The line and segment are not parallel. + Real det = (Real)1 - a01 * a01; + Real extDet = segExtent * det; + Real b1 = -Dot(diff, segDirection); + s1 = a01 * b0 - b1; + + if (s1 >= -extDet) + { + if (s1 <= extDet) + { + // Two interior points are closest, one on the line and one + // on the segment. + s0 = (a01 * b1 - b0) / det; + s1 /= det; + } + else + { + // The endpoint e1 of the segment and an interior point of + // the line are closest. + s1 = segExtent; + s0 = -(a01 * s1 + b0); + } + } + else + { + // The endpoint e0 of the segment and an interior point of the + // line are closest. + s1 = -segExtent; + s0 = -(a01 * s1 + b0); + } + } + else + { + // The line and segment are parallel. Choose the closest pair so that + // one point is at segment origin. + s1 = (Real)0; + s0 = -b0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line.origin + s0 * line.direction; + result.closestPoint[1] = segCenter + s1 * segDirection; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistOrientedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistOrientedBox3OrientedBox3.h new file mode 100644 index 000000000000..d23b64b20b62 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistOrientedBox3OrientedBox3.h @@ -0,0 +1,162 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array box0Parameter, box1Parameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 144 (n = 12, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(OrientedBox3 const& box0, OrientedBox3 const& box1); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result + DCPQuery, OrientedBox3>::operator()( + OrientedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Translate the center of box0 to the origin. Modify the oriented box + // coefficients to be nonnegative. + Vector3 delta = box1.center - box0.center; + for (int i = 0; i < 3; ++i) + { + delta += box0.extent[i] * box0.axis[i]; + delta -= box1.extent[i] * box1.axis[i]; + } + + Vector3 R0Delta, R1Delta; + for (int i = 0; i < 3; ++i) + { + R0Delta[i] = Dot(box0.axis[i], delta); + R1Delta[i] = Dot(box1.axis[i], delta); + } + + std::array, 3> R0TR1; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + R0TR1[r][c] = Dot(box0.axis[r], box1.axis[c]); + } + } + + Vector3 twoExtent0 = box0.extent * (Real)2; + Vector3 twoExtent1 = box1.extent * (Real)2; + + // The LCP has 6 variables and 6 (nontrivial) inequality constraints. + std::array q = + { + -R0Delta[0], -R0Delta[1], -R0Delta[2], R1Delta[0], R1Delta[1], R1Delta[2], + twoExtent0[0], twoExtent0[1], twoExtent0[2], twoExtent1[0], twoExtent1[1], twoExtent1[2] + }; + + std::array, 12> M; + { + Real const z = (Real)0; + Real const p = (Real)1; + Real const m = (Real)-1; + M[ 0] = { p, z, z, -R0TR1[0][0], -R0TR1[0][1], -R0TR1[0][2], p, z, z, z, z, z }; + M[ 1] = { z, p, z, -R0TR1[1][0], -R0TR1[1][1], -R0TR1[1][2], z, p, z, z, z, z }; + M[ 2] = { z, z, p, -R0TR1[2][0], -R0TR1[2][1], -R0TR1[2][2], z, z, p, z, z, z }; + M[ 3] = { -R0TR1[0][0], -R0TR1[1][0], -R0TR1[2][0], p, z, z, z, z, z, p, z, z }; + M[ 4] = { -R0TR1[0][1], -R0TR1[1][1], -R0TR1[2][1], z, p, z, z, z, z, z, p, z }; + M[ 5] = { -R0TR1[0][2], -R0TR1[1][2], -R0TR1[2][2], z, z, p, z, z, z, z, z, p }; + M[ 6] = { m, z, z, z, z, z, z, z, z, z, z, z }; + M[ 7] = { z, m, z, z, z, z, z, z, z, z, z, z }; + M[ 8] = { z, z, m, z, z, z, z, z, z, z, z, z }; + M[ 9] = { z, z, z, m, z, z, z, z, z, z, z, z }; + M[10] = { z, z, z, z, m, z, z, z, z, z, z, z }; + M[11] = { z, z, z, z, z, m, z, z, z, z, z, z }; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.closestPoint[0] = box0.center; + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = z[i] - box0.extent[i]; + result.closestPoint[0] += result.box0Parameter[i] * box0.axis[i]; + } + + result.closestPoint[1] = box1.center; + for (int i = 0, j = 3; i < 3; ++i, ++j) + { + result.box1Parameter[i] = z[j] - box1.extent[i]; + result.closestPoint[1] += result.box1Parameter[i] * box1.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = (Real)0; + result.box1Parameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Circle3.h new file mode 100644 index 000000000000..9eacd922bc00 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Circle3.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +// The 3D point-circle distance algorithm is described in +// http://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace gte +{ + +template +class DCPQuery, Circle3> +{ +public: + // Either a single point on the circle is closest to 'point', in which + // case 'equidistant' is false, or the entire circle is closest to + // 'point', in which case 'equidistant' is true. In the latter case, the + // query returns the circle point C+r*U, where C is the circle center, r + // is the circle radius, and U is a vector perpendicular to the normal N + // for the plane of the circle. + struct Result + { + Real distance, sqrDistance; + Vector3 circleClosest; + bool equidistant; + }; + + Result operator()(Vector3 const& point, + Circle3 const& circle); +}; + + +template +typename DCPQuery, Circle3>::Result +DCPQuery, Circle3>::operator()( + Vector3 const& point, Circle3 const& circle) +{ + Result result; + + // Projection of P-C onto plane is Q-C = P-C - Dot(N,P-C)*N. + Vector3 PmC = point - circle.center; + Vector3 QmC = PmC - Dot(circle.normal, PmC)*circle.normal; + Real lengthQmC = Length(QmC); + if (lengthQmC > (Real)0) + { + result.circleClosest = + circle.center + (circle.radius / lengthQmC) * QmC; + result.equidistant = false; + } + else + { + // All circle points are equidistant from P. Return one of them. + Vector3 basis[3]; + basis[0] = circle.normal; + ComputeOrthogonalComplement(1, basis); + result.circleClosest = circle.center + circle.radius * basis[1]; + result.equidistant = true; + } + + Vector3 diff = point - result.circleClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3ConvexPolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3ConvexPolyhedron3.h new file mode 100644 index 000000000000..fd3e051a9dd8 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3ConvexPolyhedron3.h @@ -0,0 +1,188 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, ConvexPolyhedron3> +{ +public: + // Construction. If you have no knowledge of the number of faces for the + // convex polyhedra you plan on applying the query to, pass 'numTriangles' + // of zero. This is a request to the operator() function to create the + // LCP solver for each query, and this requires memory allocation and + // deallocation per query. If you plan on applying the query multiple + // times to a single polyhedron, even if the vertices of the polyhedron + // are modified for each query, then pass 'numTriangles' to be the number + // of triangle faces for that polyhedron. This lets the operator() + // function know to create the LCP solver once at construction time, thus + // avoiding the memory management costs during the query. + DCPQuery(int numTriangles = 0); + + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Vector3 const& point, ConvexPolyhedron3 const& polyhedron); + +private: + int mMaxLCPIterations; + std::unique_ptr> mLCP; +}; + + +template +DCPQuery, ConvexPolyhedron3>::DCPQuery(int numTriangles) +{ + if (numTriangles > 0) + { + int const n = numTriangles + 3; + mLCP = std::make_unique>(n); + mMaxLCPIterations = mLCP->GetMaxIterations(); + } + else + { + mMaxLCPIterations = 0; + } +} + +template +void DCPQuery, ConvexPolyhedron3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mMaxLCPIterations = maxLCPIterations; + if (mLCP) + { + mLCP->SetMaxIterations(mMaxLCPIterations); + } +} + +template +typename DCPQuery, ConvexPolyhedron3>::Result +DCPQuery, ConvexPolyhedron3>::operator()( + Vector3 const& point, ConvexPolyhedron3 const& polyhedron) +{ + Result result; + + int const numTriangles = static_cast(polyhedron.planes.size()); + if (numTriangles == 0) + { + // The polyhedron planes and aligned box need to be created. + result.queryIsSuccessful = false; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + result.numLCPIterations = 0; + return result; + } + + int const n = numTriangles + 3; + + // Translate the point and convex polyhedron so that the polyhedron is in + // the first octant. The translation is not explicit; rather, the q and M + // for the LCP are initialized using the translation information. + Vector4 hmin = HLift(polyhedron.alignedBox.min, (Real)1); + + std::vector q(n); + for (int r = 0; r < 3; ++r) + { + q[r] = polyhedron.alignedBox.min[r] - point[r]; + } + for (int r = 3, t = 0; r < n; ++r, ++t) + { + q[r] = -Dot(polyhedron.planes[t], hmin); + } + + std::vector M(n * n); + M[0] = (Real)1; M[1] = (Real)0; M[2] = (Real)0; + M[n] = (Real)0; M[n + 1] = (Real)1; M[n + 2] = (Real)0; + M[2 * n] = (Real)0; M[2 * n + 1] = (Real)0; M[2 * n + 2] = (Real)1; + for (int t = 0, c = 3; t < numTriangles; ++t, ++c) + { + Vector3 normal = HProject(polyhedron.planes[t]); + for (int r = 0; r < 3; ++r) + { + M[c + n * r] = normal[r]; + M[r + n * c] = -normal[r]; + } + } + for (int r = 3; r < n; ++r) + { + for (int c = 3; c < n; ++c) + { + M[c + n * r] = (Real)0; + } + } + + bool needsLCP = (mLCP == nullptr); + if (needsLCP) + { + mLCP = std::make_unique>(n); + if (mMaxLCPIterations > 0) + { + mLCP->SetMaxIterations(mMaxLCPIterations); + } + } + + std::vector w(n), z(n); + if (mLCP->Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + result.closestPoint[0] = point; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[1][i] = z[i] + polyhedron.alignedBox.min[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + } + + result.numLCPIterations = mLCP->GetNumIterations(); + if (needsLCP) + { + mLCP = nullptr; + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Cylinder3.h new file mode 100644 index 000000000000..da08b60cb210 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Cylinder3.h @@ -0,0 +1,126 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class DCPQuery, Cylinder3> +{ +public: + struct Result + { + Real distance; + Vector3 cylinderClosest; + }; + + Result operator()(Vector3 const& point, + Cylinder3 const& cylinder); + +private: + void DoQueryInfiniteCylinder(Vector3 const& P, Real radius, + Result& result); + + void DoQueryFiniteCylinder(Vector3 const& P, Real radius, + Real height, Result& result); +}; + + +template +typename DCPQuery, Cylinder3>::Result +DCPQuery, Cylinder3>::operator()( + Vector3 const& point, Cylinder3 const& cylinder) +{ + Result result; + + // Convert the point to the cylinder coordinate system. In this system, + // the point believes (0,0,0) is the cylinder axis origin and (0,0,1) is + // the cylinder axis direction. + Vector3 basis[3]; + basis[0] = cylinder.axis.direction; + ComputeOrthogonalComplement(1, basis); + + Vector3 delta = point - cylinder.axis.origin; + Vector3 P + { + Dot(basis[1], delta), + Dot(basis[2], delta), + Dot(basis[0], delta) + }; + + if (cylinder.height == std::numeric_limits::max()) + { + DoQueryInfiniteCylinder(P, cylinder.radius, result); + } + else + { + DoQueryFiniteCylinder(P, cylinder.radius, cylinder.height, result); + } + + // Convert the closest point from the cylinder coordinate system to the + // original coordinate system. + result.cylinderClosest = cylinder.axis.origin + + result.cylinderClosest[0] * basis[1] + + result.cylinderClosest[1] * basis[2] + + result.cylinderClosest[2] * basis[0]; + + return result; +} + +template +void DCPQuery, Cylinder3>::DoQueryInfiniteCylinder( + Vector3 const& P, Real radius, Result& result) +{ + Real sqrRadius = radius * radius; + Real sqrDistance = P[0] * P[0] + P[1] * P[1]; + if (sqrDistance >= sqrRadius) + { + // The point is outside the cylinder or on the cylinder wall. + Real distance = std::sqrt(sqrDistance); + result.distance = distance - radius; + Real temp = radius / distance; + result.cylinderClosest[0] = P[0] * temp; + result.cylinderClosest[1] = P[1] * temp; + result.cylinderClosest[2] = P[2]; + } + else + { + // The point is inside the cylinder. + result.distance = (Real)0; + result.cylinderClosest = P; + } +} + +template +void DCPQuery, Cylinder3>::DoQueryFiniteCylinder( + Vector3 const& P, Real radius, Real height, Result& result) +{ + DoQueryInfiniteCylinder(P, radius, result); + + // Clamp the infinite cylinder's closest point to the finite cylinder. + if (result.cylinderClosest[2] > height) + { + result.cylinderClosest[2] = height; + result.distance = Length(result.cylinderClosest - P); + } + else if (result.cylinderClosest[2] < -height) + { + result.cylinderClosest[2] = -height; + result.distance = Length(result.cylinderClosest - P); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Frustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Frustum3.h new file mode 100644 index 000000000000..3e69cbea750a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Frustum3.h @@ -0,0 +1,430 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Frustum3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector3 frustumClosestPoint; + }; + + Result operator()(Vector3 const& point, + Frustum3 const& frustum); +}; + + +template +typename DCPQuery, Frustum3>::Result +DCPQuery, Frustum3>::operator()( + Vector3 const& point, Frustum3 const& frustum) +{ + Result result; + + // Compute coordinates of point with respect to frustum coordinate system. + Vector3 diff = point - frustum.origin; + Vector3 test = { + Dot(diff, frustum.rVector), + Dot(diff, frustum.uVector), + Dot(diff, frustum.dVector) }; + + // Perform calculations in octant with nonnegative R and U coordinates. + bool rSignChange; + if (test[0] < (Real)0) + { + rSignChange = true; + test[0] = -test[0]; + } + else + { + rSignChange = false; + } + + bool uSignChange; + if (test[1] < (Real)0) + { + uSignChange = true; + test[1] = -test[1]; + } + else + { + uSignChange = false; + } + + // Frustum derived parameters. + Real rmin = frustum.rBound; + Real rmax = frustum.GetDRatio()*rmin; + Real umin = frustum.uBound; + Real umax = frustum.GetDRatio()*umin; + Real dmin = frustum.dMin; + Real dmax = frustum.dMax; + Real rminSqr = rmin*rmin; + Real uminSqr = umin*umin; + Real dminSqr = dmin*dmin; + Real minRDDot = rminSqr + dminSqr; + Real minUDDot = uminSqr + dminSqr; + Real minRUDDot = rminSqr + minUDDot; + Real maxRDDot = frustum.GetDRatio()*minRDDot; + Real maxUDDot = frustum.GetDRatio()*minUDDot; + Real maxRUDDot = frustum.GetDRatio()*minRUDDot; + + // Algorithm computes closest point in all cases by determining in which + // Voronoi region of the vertices, edges, and faces of the frustum that + // the test point lives. + Vector3 closest; + Real rDot, uDot, rdDot, udDot, rudDot, rEdgeDot, uEdgeDot, t; + if (test[2] >= dmax) + { + if (test[0] <= rmax) + { + if (test[1] <= umax) + { + // F-face + closest[0] = test[0]; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + } + else + { + if (test[1] <= umax) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + } + } + else if (test[2] <= dmin) + { + if (test[0] <= rmin) + { + if (test[1] <= umin) + { + // N-face + closest[0] = test[0]; + closest[1] = test[1]; + closest[2] = dmin; + } + else + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else if (udDot >= minUDDot) + { + // U-face + uDot = dmin*test[1] - umin*test[2]; + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + else + { + // UN-edge + closest[0] = test[0]; + closest[1] = umin; + closest[2] = dmin; + } + } + } + else + { + if (test[1] <= umin) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else if (rdDot >= minRDDot) + { + // L-face + rDot = dmin*test[0] - rmin*test[2]; + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + else + { + // LN-edge + closest[0] = rmin; + closest[1] = test[1]; + closest[2] = dmin; + } + } + else + { + rudDot = rmin*test[0] + umin*test[1] + dmin*test[2]; + rEdgeDot = umin*rudDot - minRUDDot*test[1]; + if (rEdgeDot >= (Real)0) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else if (rdDot >= minRDDot) + { + // L-face + rDot = dmin*test[0] - rmin*test[2]; + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + else + { + // LN-edge + closest[0] = rmin; + closest[1] = test[1]; + closest[2] = dmin; + } + } + else + { + uEdgeDot = rmin*rudDot - minRUDDot*test[0]; + if (uEdgeDot >= (Real)0) + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else if (udDot >= minUDDot) + { + // U-face + uDot = dmin*test[1] - umin*test[2]; + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + else + { + // UN-edge + closest[0] = test[0]; + closest[1] = umin; + closest[2] = dmin; + } + } + else + { + if (rudDot >= maxRUDDot) + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + else if (rudDot >= minRUDDot) + { + // LU-edge + t = rudDot / minRUDDot; + closest[0] = t*rmin; + closest[1] = t*umin; + closest[2] = t*dmin; + } + else + { + // LUN-vertex + closest[0] = rmin; + closest[1] = umin; + closest[2] = dmin; + } + } + } + } + } + } + else + { + rDot = dmin*test[0] - rmin*test[2]; + uDot = dmin*test[1] - umin*test[2]; + if (rDot <= (Real)0.0) + { + if (uDot <= (Real)0) + { + // point inside frustum + closest = test; + } + else + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else + { + // U-face + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + } + } + else + { + if (uDot <= (Real)0) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // L-face + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + } + else + { + rudDot = rmin*test[0] + umin*test[1] + dmin*test[2]; + rEdgeDot = umin*rudDot - minRUDDot*test[1]; + if (rEdgeDot >= (Real)0) + { + rdDot = rmin*test[0] + dmin*test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else // assert( rdDot >= minRDDot ) from geometry + { + // L-face + t = rDot / minRDDot; + closest[0] = test[0] - t*dmin; + closest[1] = test[1]; + closest[2] = test[2] + t*rmin; + } + } + else + { + uEdgeDot = rmin*rudDot - minRUDDot*test[0]; + if (uEdgeDot >= (Real)0) + { + udDot = umin*test[1] + dmin*test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else // assert( udDot >= minUDDot ) from geometry + { + // U-face + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t*dmin; + closest[2] = test[2] + t*umin; + } + } + else + { + if (rudDot >= maxRUDDot) + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + else // assert( rudDot >= minRUDDot ) from geometry + { + // LU-edge + t = rudDot / minRUDDot; + closest[0] = t*rmin; + closest[1] = t*umin; + closest[2] = t*dmin; + } + } + } + } + } + } + + diff = test - closest; + + // Convert back to original quadrant. + if (rSignChange) + { + closest[0] = -closest[0]; + } + + if (uSignChange) + { + closest[1] = -closest[1]; + } + + // Convert back to original coordinates. + result.frustumClosestPoint = frustum.origin + + closest[0] * frustum.rVector + + closest[1] * frustum.uVector + + closest[2] * frustum.dVector; + + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Plane3.h new file mode 100644 index 000000000000..cf36fab76a99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Plane3.h @@ -0,0 +1,44 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Plane3> +{ +public: + struct Result + { + Real distance, signedDistance; + Vector3 planeClosestPoint; + }; + + Result operator()(Vector3 const& point, Plane3 const& plane); +}; + + +template +typename DCPQuery, Plane3>::Result +DCPQuery, Plane3>::operator()( + Vector3 const& point, Plane3 const& plane) +{ + Result result; + result.signedDistance = Dot(plane.normal, point) - plane.constant; + result.distance = std::abs(result.signedDistance); + result.planeClosestPoint = point - result.signedDistance*plane.normal; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Rectangle3.h new file mode 100644 index 000000000000..205c7ebd8343 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Rectangle3.h @@ -0,0 +1,85 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rectangleParameter[2]; + Vector3 rectangleClosestPoint; + }; + + Result operator()(Vector3 const& point, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Vector3 const& point, Rectangle3 const& rectangle) +{ + Result result; + + Vector3 diff = rectangle.center - point; + Real b0 = Dot(diff, rectangle.axis[0]); + Real b1 = Dot(diff, rectangle.axis[1]); + Real s0 = -b0, s1 = -b1; + result.sqrDistance = Dot(diff, diff); + + if (s0 < -rectangle.extent[0]) + { + s0 = -rectangle.extent[0]; + } + else if (s0 > rectangle.extent[0]) + { + s0 = rectangle.extent[0]; + } + result.sqrDistance += s0*(s0 + ((Real)2)*b0); + + if (s1 < -rectangle.extent[1]) + { + s1 = -rectangle.extent[1]; + } + else if (s1 > rectangle.extent[1]) + { + s1 = rectangle.extent[1]; + } + result.sqrDistance += s1*(s1 + ((Real)2)*b1); + + // Account for numerical round-off error. + if (result.sqrDistance < (Real)0) + { + result.sqrDistance = (Real)0; + } + + result.distance = std::sqrt(result.sqrDistance); + result.rectangleParameter[0] = s0; + result.rectangleParameter[1] = s1; + result.rectangleClosestPoint = rectangle.center; + for (int i = 0; i < 2; ++i) + { + result.rectangleClosestPoint += + result.rectangleParameter[i] * rectangle.axis[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Tetrahedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Tetrahedron3.h new file mode 100644 index 000000000000..c591557aebfb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPoint3Tetrahedron3.h @@ -0,0 +1,83 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Tetrahedron3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector3 tetrahedronClosestPoint; + }; + + Result operator()(Vector3 const& point, + Tetrahedron3 const& tetrahedron); +}; + + +template +typename DCPQuery, Tetrahedron3>::Result +DCPQuery, Tetrahedron3>::operator()( + Vector3 const& point, Tetrahedron3 const& tetrahedron) +{ + Result result; + + // Construct the planes for the faces of the tetrahedron. The normals + // are outer pointing, but specified not to be unit length. We only need + // to know sidedness of the query point, so we will save cycles by not + // computing unit-length normals. + Plane3 planes[4]; + tetrahedron.GetPlanes(planes); + + // Determine which faces are visible to the query point. Only these + // need to be processed by point-to-triangle distance queries. + result.sqrDistance = std::numeric_limits::max(); + result.tetrahedronClosestPoint = Vector3::Zero(); + for (int i = 0; i < 4; ++i) + { + if (Dot(planes[i].normal, point) >= planes[i].constant) + { + int indices[3] = { 0, 0, 0 }; + tetrahedron.GetFaceIndices(i, indices); + Triangle3 triangle( + tetrahedron.v[indices[0]], + tetrahedron.v[indices[1]], + tetrahedron.v[indices[2]]); + + DCPQuery, Triangle3> query; + auto ptResult = query(point, triangle); + if (ptResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = ptResult.sqrDistance; + result.tetrahedronClosestPoint = ptResult.closest; + } + } + } + + if (result.sqrDistance == std::numeric_limits::max()) + { + // The query point is inside the solid tetrahedron. Report a zero + // distance. The closest points are identical. + result.sqrDistance = (Real)0; + result.tetrahedronClosestPoint = point; + } + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointAlignedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointAlignedBox.h new file mode 100644 index 000000000000..6eea3ddaa848 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointAlignedBox.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector boxClosest; + }; + + Result operator()(Vector const& point, + AlignedBox const& box); + +protected: + // On input, 'point' is the difference of the query point and the box + // center. On output, 'point' is the point on the box closest to the + // query point. + void DoQuery(Vector& point, Vector const& boxExtent, + Result& result); +}; + +// Template aliases for convenience. +template +using DCPPointAlignedBox = +DCPQuery, AlignedBox>; + +template +using DCPPoint2AlignedBox2 = DCPPointAlignedBox<2, Real>; + +template +using DCPPoint3AlignedBox3 = DCPPointAlignedBox<3, Real>; + + +template +typename DCPQuery, AlignedBox>::Result +DCPQuery, AlignedBox>::operator()( + Vector const& point, AlignedBox const& box) +{ + // Translate the point and box so that the box has center at the origin. + Vector boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + Vector closest = point - boxCenter; + + Result result; + DoQuery(closest, boxExtent, result); + + // Compute the closest point on the box. + result.boxClosest = boxCenter + closest; + return result; +} + +template +void DCPQuery, AlignedBox>::DoQuery( + Vector& point, Vector const& boxExtent, Result& result) +{ + result.sqrDistance = (Real)0; + for (int i = 0; i < N; ++i) + { + if (point[i] < -boxExtent[i]) + { + Real delta = point[i] + boxExtent[i]; + result.sqrDistance += delta * delta; + point[i] = -boxExtent[i]; + } + else if (point[i] > boxExtent[i]) + { + Real delta = point[i] - boxExtent[i]; + result.sqrDistance += delta * delta; + point[i] = boxExtent[i]; + } + } + result.distance = std::sqrt(result.sqrDistance); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointHyperellipsoid.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointHyperellipsoid.h new file mode 100644 index 000000000000..a78daa3d3e68 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointHyperellipsoid.h @@ -0,0 +1,382 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// Compute the distance from a point to a hyperellipsoid. In 2D, this is a +// point-ellipse distance query. In 3D, this is a point-ellipsoid distance +// query. The following document describes the algorithm. +// http://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf +// The hyperellipsoid can have arbitrary center and orientation; that is, it +// does not have to be axis-aligned with center at the origin. +// +// For the 2D query, +// Vector2 point; // initialized to something +// Ellipse2 ellipse; // initialized to something +// DCPPoint2Ellipse2 query; +// auto result = query(point, ellipse); +// Real distance = result.distance; +// Vector2 closestEllipsePoint = result.closest; +// +// For the 3D query, +// Vector3 point; // initialized to something +// Ellipsoid3 ellipsoid; // initialized to something +// DCPPoint3Ellipsoid3 query; +// auto result = query(point, ellipsoid); +// Real distance = result.distance; +// Vector3 closestEllipsoidPoint = result.closest; + +namespace gte +{ + +template +class DCPQuery, Hyperellipsoid> +{ +public: + struct Result + { + Real distance, sqrDistance; + Vector closest; + }; + + // The query for any hyperellipsoid. + Result operator()(Vector const& point, + Hyperellipsoid const& hyperellipsoid); + + // The 'hyperellipsoid' is assumed to be axis-aligned and centered at the + // origin , so only the extent[] values are used. + Result operator()(Vector const& point, + Vector const& extent); + +private: + // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with no + // constraints on the orderind of the e[d]. The query point is + // (y[0],...,y[N-1]) with no constraints on the signs of the components. + // The function returns the squared distance from the query point to the + // hyperellipsoid. It also computes the hyperellipsoid point + // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]). + Real SqrDistance(Vector const& e, + Vector const& y, Vector& x); + + // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with the e[d] + // positive and nonincreasing: e[d] >= e[d + 1] for all d. The query + // point is (y[0],...,y[N-1]) with y[d] >= 0 for all d. The function + // returns the squared distance from the query point to the + // hyperellipsoid. It also computes the hyperellipsoid point + // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]), where + // x[d] >= 0 for all d. + Real SqrDistanceSpecial(Vector const& e, + Vector const& y, Vector& x); + + // The bisection algorithm to find the unique root of F(t). + Real Bisector(int numComponents, Vector const& e, + Vector const& y, Vector& x); +}; + +// Template aliases for convenience. +template +using DCPPointHyperellipsoid = +DCPQuery, Hyperellipsoid>; + +template +using DCPPoint2Ellipse2 = DCPPointHyperellipsoid<2, Real>; + +template +using DCPPoint3Ellipsoid3 = DCPPointHyperellipsoid<3, Real>; + + +template +typename DCPQuery, Hyperellipsoid>::Result +DCPQuery, Hyperellipsoid>::operator()( + Vector const& point, + Hyperellipsoid const& hyperellipsoid) +{ + Result result; + + // Compute the coordinates of Y in the hyperellipsoid coordinate system. + Vector diff = point - hyperellipsoid.center; + Vector y; + for (int i = 0; i < N; ++i) + { + y[i] = Dot(diff, hyperellipsoid.axis[i]); + } + + // Compute the closest hyperellipsoid point in the axis-aligned + // coordinate system. + Vector x; + result.sqrDistance = SqrDistance(hyperellipsoid.extent, y, x); + result.distance = std::sqrt(result.sqrDistance); + + // Convert back to the original coordinate system. + result.closest = hyperellipsoid.center; + for (int i = 0; i < N; ++i) + { + result.closest += x[i] * hyperellipsoid.axis[i]; + } + + return result; +} + +template +typename DCPQuery, Hyperellipsoid>::Result +DCPQuery, Hyperellipsoid>::operator()( + Vector const& point, Vector const& extent) +{ + Result result; + result.sqrDistance = SqrDistance(extent, point, result.closest); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +Real DCPQuery, Hyperellipsoid>::SqrDistance( + Vector const& e, Vector const& y, Vector& x) +{ + // Determine negations for y to the first octant. + std::array negate; + int i, j; + for (i = 0; i < N; ++i) + { + negate[i] = (y[i] < (Real)0); + } + + // Determine the axis order for decreasing extents. + std::array, N> permute; + for (i = 0; i < N; ++i) + { + permute[i].first = -e[i]; + permute[i].second = i; + } + std::sort(permute.begin(), permute.end()); + + std::array invPermute; + for (i = 0; i < N; ++i) + { + invPermute[permute[i].second] = i; + } + + Vector locE, locY; + for (i = 0; i < N; ++i) + { + j = permute[i].second; + locE[i] = e[j]; + locY[i] = std::abs(y[j]); + } + + Vector locX; + Real sqrDistance = SqrDistanceSpecial(locE, locY, locX); + + // Restore the axis order and reflections. + for (i = 0; i < N; ++i) + { + j = invPermute[i]; + if (negate[i]) + { + locX[j] = -locX[j]; + } + x[i] = locX[j]; + } + + return sqrDistance; +} + +template +Real DCPQuery, Hyperellipsoid>:: +SqrDistanceSpecial(Vector const& e, Vector const& y, + Vector& x) +{ + Real sqrDistance = (Real)0; + + Vector ePos, yPos, xPos; + int numPos = 0; + int i; + for (i = 0; i < N; ++i) + { + if (y[i] >(Real)0) + { + ePos[numPos] = e[i]; + yPos[numPos] = y[i]; + ++numPos; + } + else + { + x[i] = (Real)0; + } + } + + if (y[N - 1] > (Real)0) + { + sqrDistance = Bisector(numPos, ePos, yPos, xPos); + } + else // y[N-1] = 0 + { + Vector numer, denom; + Real eNm1Sqr = e[N - 1] * e[N - 1]; + for (i = 0; i < numPos; ++i) + { + numer[i] = ePos[i] * yPos[i]; + denom[i] = ePos[i] * ePos[i] - eNm1Sqr; + } + + bool inSubHyperbox = true; + for (i = 0; i < numPos; ++i) + { + if (numer[i] >= denom[i]) + { + inSubHyperbox = false; + break; + } + } + + bool inSubHyperellipsoid = false; + if (inSubHyperbox) + { + // yPos[] is inside the axis-aligned bounding box of the + // subhyperellipsoid. This intermediate test is designed to guard + // against the division by zero when ePos[i] == e[N-1] for some i. + Vector xde; + Real discr = (Real)1; + for (i = 0; i < numPos; ++i) + { + xde[i] = numer[i] / denom[i]; + discr -= xde[i] * xde[i]; + } + if (discr >(Real)0) + { + // yPos[] is inside the subhyperellipsoid. The closest + // hyperellipsoid point has x[N-1] > 0. + sqrDistance = (Real)0; + for (i = 0; i < numPos; ++i) + { + xPos[i] = ePos[i] * xde[i]; + Real diff = xPos[i] - yPos[i]; + sqrDistance += diff*diff; + } + x[N - 1] = e[N - 1] * std::sqrt(discr); + sqrDistance += x[N - 1] * x[N - 1]; + inSubHyperellipsoid = true; + } + } + + if (!inSubHyperellipsoid) + { + // yPos[] is outside the subhyperellipsoid. The closest + // hyperellipsoid point has x[N-1] == 0 and is on the + // domain-boundary hyperellipsoid. + x[N - 1] = (Real)0; + sqrDistance = Bisector(numPos, ePos, yPos, xPos); + } + } + + // Fill in those x[] values that were not zeroed out initially. + for (i = 0, numPos = 0; i < N; ++i) + { + if (y[i] >(Real)0) + { + x[i] = xPos[numPos]; + ++numPos; + } + } + + return sqrDistance; +} + +template +Real DCPQuery, Hyperellipsoid>::Bisector( + int numComponents, Vector const& e, Vector const& y, + Vector& x) +{ + Vector z; + Real sumZSqr = (Real)0; + int i; + for (i = 0; i < numComponents; ++i) + { + z[i] = y[i] / e[i]; + sumZSqr += z[i] * z[i]; + } + + if (sumZSqr == (Real)1) + { + // The point is on the hyperellipsoid. + for (i = 0; i < numComponents; ++i) + { + x[i] = y[i]; + } + return (Real)0; + } + + Real emin = e[numComponents - 1]; + Vector pSqr, numerator; + for (i = 0; i < numComponents; ++i) + { + Real p = e[i] / emin; + pSqr[i] = p * p; + numerator[i] = pSqr[i] * z[i]; + } + + Real s = (Real)0, smin = z[numComponents - 1] - (Real)1, smax; + if (sumZSqr < (Real)1) + { + // The point is strictly inside the hyperellipsoid. + smax = (Real)0; + } + else + { + // The point is strictly outside the hyperellipsoid. + smax = Length(numerator, true) - (Real)1; + } + + // The use of 'double' is intentional in case Real is a BSNumber or + // BSRational type. We want the bisections to terminate in a reasonable + // amount of time. + unsigned int const jmax = GTE_C_MAX_BISECTIONS_GENERIC; + for (unsigned int j = 0; j < jmax; ++j) + { + s = (smin + smax) * (Real)0.5; + if (s == smin || s == smax) + { + break; + } + + Real g = (Real)-1; + for (i = 0; i < numComponents; ++i) + { + Real ratio = numerator[i] / (s + pSqr[i]); + g += ratio * ratio; + } + + if (g >(Real)0) + { + smin = s; + } + else if (g < (Real)0) + { + smax = s; + } + else + { + break; + } + } + + Real sqrDistance = (Real)0; + for (i = 0; i < numComponents; ++i) + { + x[i] = pSqr[i] * y[i] / (s + pSqr[i]); + Real diff = x[i] - y[i]; + sqrDistance += diff*diff; + } + return sqrDistance; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointLine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointLine.h new file mode 100644 index 000000000000..46c6509ed89f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointLine.h @@ -0,0 +1,62 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Line> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter; // t in (-infinity,+infinity) + Vector lineClosest; // origin + t * direction + }; + + Result operator()(Vector const& point, + Line const& line); +}; + +// Template aliases for convenience. +template +using DCPPointLine = +DCPQuery, Line>; + +template +using DCPPoint2Line2 = DCPPointLine<2, Real>; + +template +using DCPPoint3Line3 = DCPPointLine<3, Real>; + + +template +typename DCPQuery, Line>::Result +DCPQuery, Line>::operator()( + Vector const& point, Line const& line) +{ + Result result; + + Vector diff = point - line.origin; + result.lineParameter = Dot(line.direction, diff); + result.lineClosest = line.origin + result.lineParameter*line.direction; + + diff = point - result.lineClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointOrientedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointOrientedBox.h new file mode 100644 index 000000000000..89ece503a327 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointOrientedBox.h @@ -0,0 +1,72 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox> + : + public DCPQuery, AlignedBox> +{ +public: + struct Result + : + public DCPQuery, AlignedBox>::Result + { + // No additional information to compute. + }; + + Result operator()(Vector const& point, + OrientedBox const& box); +}; + +// Template aliases for convenience. +template +using DCPPointOrientedBox = +DCPQuery, AlignedBox>; + +template +using DCPPoint2OrientedBox2 = DCPPointOrientedBox<2, Real>; + +template +using DCPPoint3OrientedBox3 = DCPPointOrientedBox<3, Real>; + + +template +typename DCPQuery, OrientedBox>::Result +DCPQuery, OrientedBox>::operator()( + Vector const& point, OrientedBox const& box) +{ + // Translate the point to the coordinate system of the box. In this + // system, the box is axis-aligned with center at the origin. + Vector diff = point - box.center; + Vector closest; + for (int i = 0; i < N; ++i) + { + closest[i] = Dot(diff, box.axis[i]); + } + + Result result; + this->DoQuery(closest, box.extent, result); + + // Compute the closest point on the box. + result.boxClosest = box.center; + for (int i = 0; i < N; ++i) + { + result.boxClosest += closest[i] * box.axis[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointRay.h new file mode 100644 index 000000000000..e7a0b9fc8277 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointRay.h @@ -0,0 +1,68 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Ray> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; // t in [0,+infinity) + Vector rayClosest; // origin + t * direction + }; + + Result operator()(Vector const& point, Ray const& ray); +}; + +// Template aliases for convenience. +template +using DCPPointRay = +DCPQuery, Ray>; + +template +using DCPPoint2Ray2 = DCPPointRay<2, Real>; + +template +using DCPPoint3Ray3 = DCPPointRay<3, Real>; + + +template +typename DCPQuery, Ray>::Result +DCPQuery, Ray>::operator()( + Vector const& point, Ray const& ray) +{ + Result result; + + Vector diff = point - ray.origin; + result.rayParameter = Dot(ray.direction, diff); + if (result.rayParameter > (Real)0) + { + result.rayClosest = ray.origin + result.rayParameter*ray.direction; + } + else + { + result.rayClosest = ray.origin; + } + + diff = point - result.rayClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointSegment.h new file mode 100644 index 000000000000..e44589620cfc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointSegment.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; // t in [0,1] + Vector segmentClosest; // (1-t)*p[0] + t*p[1] + }; + + Result operator()(Vector const& point, + Segment const& segment); +}; + +// Template aliases for convenience. +template +using DCPPointSegment = +DCPQuery, Segment>; + +template +using DCPPoint2Segment2 = DCPPointSegment<2, Real>; + +template +using DCPPoint3Segment3 = DCPPointSegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Vector const& point, Segment const& segment) +{ + Result result; + + // The direction vector is not unit length. The normalization is deferred + // until it is needed. + Vector direction = segment.p[1] - segment.p[0]; + Vector diff = point - segment.p[1]; + Real t = Dot(direction, diff); + if (t >= (Real)0) + { + result.segmentParameter = (Real)1; + result.segmentClosest = segment.p[1]; + } + else + { + diff = point - segment.p[0]; + t = Dot(direction, diff); + if (t <= (Real)0) + { + result.segmentParameter = (Real)0; + result.segmentClosest = segment.p[0]; + } + else + { + Real sqrLength = Dot(direction, direction); + if (sqrLength > (Real)0) + { + t /= sqrLength; + result.segmentParameter = t; + result.segmentClosest = segment.p[0] + t * direction; + } + else + { + result.segmentParameter = (Real)0; + result.segmentClosest = segment.p[0]; + } + } + } + + diff = point - result.segmentClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangle.h new file mode 100644 index 000000000000..c5a513e8d33c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangle.h @@ -0,0 +1,266 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[3]; // barycentric coordinates for triangle.v[3] + Vector closest; + }; + + Result operator()(Vector const& point, + Triangle const& triangle); + +private: + inline void GetMinEdge02(Real const& a11, Real const& b1, + Vector<2, Real>& p) const; + + inline void GetMinEdge12(Real const& a01, Real const& a11, Real const& b1, + Real const& f10, Real const& f01, Vector<2, Real>& p) const; + + inline void GetMinInterior(Vector<2, Real> const& p0, Real const& h0, + Vector<2, Real> const& p1, Real const& h1, Vector<2, Real>& p) const; +}; + +// Template aliases for convenience. +template +using DCPPointTriangle = +DCPQuery, Triangle>; + +template +using DCPPoint2Triangle2 = DCPPointTriangle<2, Real>; + +template +using DCPPoint3Triangle3 = DCPPointTriangle<3, Real>; + + +template inline +void DCPQuery, Triangle>::GetMinEdge02( + Real const& a11, Real const& b1, Vector<2, Real>& p) const +{ + p[0] = (Real)0; + if (b1 >= (Real)0) + { + p[1] = (Real)0; + } + else if (a11 + b1 <= (Real)0) + { + p[1] = (Real)1; + } + else + { + p[1] = -b1 / a11; + } +} + +template inline +void DCPQuery, Triangle>::GetMinEdge12( + Real const& a01, Real const& a11, Real const& b1, Real const& f10, + Real const& f01, Vector<2, Real>& p) const +{ + Real h0 = a01 + b1 - f10; + if (h0 >= (Real)0) + { + p[1] = (Real)0; + } + else + { + Real h1 = a11 + b1 - f01; + if (h1 <= (Real)0) + { + p[1] = (Real)1; + } + else + { + p[1] = h0 / (h0 - h1); + } + } + p[0] = (Real)1 - p[1]; +} + +template inline +void DCPQuery, Triangle>::GetMinInterior( + Vector<2, Real> const& p0, Real const& h0, Vector<2, Real> const& p1, + Real const& h1, Vector<2, Real>& p) const +{ + Real z = h0 / (h0 - h1); + p = ((Real)1 - z) * p0 + z * p1; +} + +template +typename DCPQuery, Triangle>::Result +DCPQuery, Triangle>::operator()( + Vector const& point, Triangle const& triangle) +{ + Vector diff = point - triangle.v[0]; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Real a00 = Dot(edge0, edge0); + Real a01 = Dot(edge0, edge1); + Real a11 = Dot(edge1, edge1); + Real b0 = -Dot(diff, edge0); + Real b1 = -Dot(diff, edge1); + + Real f00 = b0; + Real f10 = b0 + a00; + Real f01 = b0 + a01; + + Vector<2, Real> p0, p1, p; + Real dt1, h0, h1; + + // Compute the endpoints p0 and p1 of the segment. The segment is + // parameterized by L(z) = (1-z)*p0 + z*p1 for z in [0,1] and the + // directional derivative of half the quadratic on the segment is + // H(z) = Dot(p1-p0,gradient[Q](L(z))/2), where gradient[Q]/2 = (F,G). + // By design, F(L(z)) = 0 for cases (2), (4), (5), and (6). Cases (1) and + // (3) can correspond to no-intersection or intersection of F = 0 with the + // triangle. + if (f00 >= (Real)0) + { + if (f01 >= (Real)0) + { + // (1) p0 = (0,0), p1 = (0,1), H(z) = G(L(z)) + GetMinEdge02(a11, b1, p); + } + else + { + // (2) p0 = (0,t10), p1 = (t01,1-t01), H(z) = (t11 - t10)*G(L(z)) + p0[0] = (Real)0; + p0[1] = f00 / (f00 - f01); + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + dt1 = p1[1] - p0[1]; + h0 = dt1 * (a11 * p0[1] + b1); + if (h0 >= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + h1 = dt1 * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + } + else if (f01 <= (Real)0) + { + if (f10 <= (Real)0) + { + // (3) p0 = (1,0), p1 = (0,1), H(z) = G(L(z)) - F(L(z)) + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + // (4) p0 = (t00,0), p1 = (t01,1-t01), H(z) = t11*G(L(z)) + p0[0] = f00 / (f00 - f10); + p0[1] = (Real)0; + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + h0 = p1[1] * (a01 * p0[0] + b1); + if (h0 >= (Real)0) + { + p = p0; // GetMinEdge01 + } + else + { + h1 = p1[1] * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + } + else if (f10 <= (Real)0) + { + // (5) p0 = (0,t10), p1 = (t01,1-t01), H(z) = (t11 - t10)*G(L(z)) + p0[0] = (Real)0; + p0[1] = f00 / (f00 - f01); + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + dt1 = p1[1] - p0[1]; + h0 = dt1 * (a11 * p0[1] + b1); + if (h0 >= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + h1 = dt1 * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + else + { + // (6) p0 = (t00,0), p1 = (0,t11), H(z) = t11*G(L(z)) + p0[0] = f00 / (f00 - f10); + p0[1] = (Real)0; + p1[0] = (Real)0; + p1[1] = f00 / (f00 - f01); + h0 = p1[1] * (a01 * p0[0] + b1); + if (h0 >= (Real)0) + { + p = p0; // GetMinEdge01 + } + else + { + h1 = p1[1] * (a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + + Result result; + result.parameter[0] = (Real)1 - p[0] - p[1]; + result.parameter[1] = p[0]; + result.parameter[2] = p[1]; + result.closest = triangle.v[0] + p[0] * edge0 + p[1] * edge1; + diff = point - result.closest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangleExact.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangleExact.h new file mode 100644 index 000000000000..587b8f9f1c8d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistPointTriangleExact.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DistancePointTriangleExact +{ +public: + struct Result + { + Rational sqrDistance; + Rational parameter[3]; // barycentric coordinates for triangle.v[3] + Vector closest; + }; + + Result operator()(Vector const& point, + Triangle const& triangle); +}; + + +template +typename DistancePointTriangleExact::Result +DistancePointTriangleExact::operator()( + Vector const& point, Triangle const& triangle) +{ + Vector diff = point - triangle.v[0]; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Rational a00 = Dot(edge0, edge0); + Rational a01 = Dot(edge0, edge1); + Rational a11 = Dot(edge1, edge1); + Rational b0 = -Dot(diff, edge0); + Rational b1 = -Dot(diff, edge1); + Rational const zero = (Rational)0; + Rational const one = (Rational)1; + Rational det = a00 * a11 - a01 * a01; + Rational t0 = a01 * b1 - a11 * b0; + Rational t1 = a01 * b0 - a00 * b1; + + if (t0 + t1 <= det) + { + if (t0 < zero) + { + if (t1 < zero) // region 4 + { + if (b0 < zero) + { + t1 = zero; + if (-b0 >= a00) // V1 + { + t0 = one; + } + else // E01 + { + t0 = -b0 / a00; + } + } + else + { + t0 = zero; + if (b1 >= zero) // V0 + { + t1 = zero; + } + else if (-b1 >= a11) // V2 + { + t1 = one; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else // region 3 + { + t0 = zero; + if (b1 >= zero) // V0 + { + t1 = zero; + } + else if (-b1 >= a11) // V2 + { + t1 = one; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else if (t1 < zero) // region 5 + { + t1 = zero; + if (b0 >= zero) // V0 + { + t0 = zero; + } + else if (-b0 >= a00) // V1 + { + t0 = one; + } + else // E01 + { + t0 = -b0 / a00; + } + } + else // region 0, interior + { + Rational invDet = one / det; + t0 *= invDet; + t1 *= invDet; + } + } + else + { + Rational tmp0, tmp1, numer, denom; + + if (t0 < zero) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Rational)2)*a01 + a11; + if (numer >= denom) // V1 + { + t0 = one; + t1 = zero; + } + else // E12 + { + t0 = numer / denom; + t1 = one - t0; + } + } + else + { + t0 = zero; + if (tmp1 <= zero) // V2 + { + t1 = one; + } + else if (b1 >= zero) // V0 + { + t1 = zero; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else if (t1 < zero) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - ((Rational)2)*a01 + a11; + if (numer >= denom) // V2 + { + t1 = one; + t0 = zero; + } + else // E12 + { + t1 = numer / denom; + t0 = one - t1; + } + } + else + { + t1 = zero; + if (tmp1 <= zero) // V1 + { + t0 = one; + } + else if (b0 >= zero) // V0 + { + t0 = zero; + } + else // E01 + { + t0 = -b0 / a00; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= zero) // V2 + { + t0 = zero; + t1 = one; + } + else + { + denom = a00 - ((Rational)2)*a01 + a11; + if (numer >= denom) // V1 + { + t0 = one; + t1 = zero; + } + else // 12 + { + t0 = numer / denom; + t1 = one - t0; + } + } + } + } + + Result result; + result.parameter[0] = one - t0 - t1; + result.parameter[1] = t0; + result.parameter[2] = t1; + result.closest = triangle.v[0] + t0 * edge0 + t1 * edge1; + diff = point - result.closest; + result.sqrDistance = Dot(diff, diff); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3AlignedBox3.h new file mode 100644 index 000000000000..605d949eab7b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3AlignedBox3.h @@ -0,0 +1,64 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box); +}; + + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3> ::operator()( + Ray3 const& ray, AlignedBox3 const& box) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, AlignedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= (Real)0) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.rayParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, AlignedBox3> pbQuery; + auto pbResult = pbQuery(ray.origin, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.rayParameter = (Real)0; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3OrientedBox3.h new file mode 100644 index 000000000000..13f8a6fa5142 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3OrientedBox3.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box); +}; + + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3> ::operator()( + Ray3 const& ray, OrientedBox3 const& box) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, OrientedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= (Real)0) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.rayParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, OrientedBox3> pbQuery; + auto pbResult = pbQuery(ray.origin, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.rayParameter = (Real)0; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Rectangle3.h new file mode 100644 index 000000000000..cb399d2525e4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Rectangle3.h @@ -0,0 +1,70 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Ray3 const& ray, Rectangle3 const& rectangle) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, Rectangle3> lrQuery; + auto lrResult = lrQuery(line, rectangle); + + if (lrResult.lineParameter >= (Real)0) + { + result.distance = lrResult.distance; + result.sqrDistance = lrResult.sqrDistance; + result.rayParameter = lrResult.lineParameter; + result.rectangleParameter[0] = lrResult.rectangleParameter[0]; + result.rectangleParameter[1] = lrResult.rectangleParameter[1]; + result.closestPoint[0] = lrResult.closestPoint[0]; + result.closestPoint[1] = lrResult.closestPoint[1]; + } + else + { + DCPQuery, Rectangle3> prQuery; + auto prResult = prQuery(ray.origin, rectangle); + result.distance = prResult.distance; + result.sqrDistance = prResult.sqrDistance; + result.rayParameter = (Real)0; + result.rectangleParameter[0] = prResult.rectangleParameter[0]; + result.rectangleParameter[1] = prResult.rectangleParameter[1]; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Triangle3.h new file mode 100644 index 000000000000..595cf8e45baa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRay3Triangle3.h @@ -0,0 +1,71 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Ray3 const& ray, Triangle3 const& triangle) +{ + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, Triangle3> ltQuery; + auto ltResult = ltQuery(line, triangle); + + if (ltResult.lineParameter >= (Real)0) + { + result.distance = ltResult.distance; + result.sqrDistance = ltResult.sqrDistance; + result.rayParameter = ltResult.lineParameter; + result.triangleParameter[0] = ltResult.triangleParameter[0]; + result.triangleParameter[1] = ltResult.triangleParameter[1]; + result.triangleParameter[2] = ltResult.triangleParameter[2]; + result.closestPoint[0] = ltResult.closestPoint[0]; + result.closestPoint[1] = ltResult.closestPoint[1]; + } + else + { + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(ray.origin, triangle); + result.distance = ptResult.distance; + result.sqrDistance = ptResult.sqrDistance; + result.rayParameter = (Real)0; + result.triangleParameter[0] = ptResult.parameter[0]; + result.triangleParameter[1] = ptResult.parameter[1]; + result.triangleParameter[2] = ptResult.parameter[2]; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = ptResult.closest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRayRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRayRay.h new file mode 100644 index 000000000000..450ef2348a71 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRayRay.h @@ -0,0 +1,162 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Ray> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Ray const& ray0, Ray const& ray1); +}; + +// Template aliases for convenience. +template +using DCPRayRay = DCPQuery, Ray>; + +template +using DCPRay2Ray2 = DCPRayRay<2, Real>; + +template +using DCPRay3Ray3 = DCPRayRay<3, Real>; + + +template +typename DCPQuery, Ray>::Result +DCPQuery, Ray>::operator()( + Ray const& ray0, Ray const& ray1) +{ + Result result; + + Vector diff = ray0.origin - ray1.origin; + Real a01 = -Dot(ray0.direction, ray1.direction); + Real b0 = Dot(diff, ray0.direction), b1; + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // Rays are not parallel. + b1 = -Dot(diff, ray1.direction); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + + if (s0 >= (Real)0) + { + if (s1 >= (Real)0) // region 0 (interior) + { + // Minimum at two interior points of rays. + Real det = (Real)1 - a01 * a01; + s0 /= det; + s1 /= det; + } + else // region 3 (side) + { + s1 = (Real)0; + if (b0 >= (Real)0) + { + s0 = (Real)0; + } + else + { + s0 = -b0; + } + } + } + else + { + if (s1 >= (Real)0) // region 1 (side) + { + s0 = (Real)0; + if (b1 >= (Real)0) + { + s1 = (Real)0; + } + else + { + s1 = -b1; + } + } + else // region 2 (corner) + { + if (b0 < (Real)0) + { + s0 = -b0; + s1 = (Real)0; + } + else + { + s0 = (Real)0; + if (b1 >= (Real)0) + { + s1 = (Real)0; + } + else + { + s1 = -b1; + } + } + } + } + } + else + { + // Rays are parallel. + if (a01 > (Real)0) + { + // Opposite direction vectors. + s1 = (Real)0; + if (b0 >= (Real)0) + { + s0 = (Real)0; + } + else + { + s0 = -b0; + } + } + else + { + // Same direction vectors. + if (b0 >= (Real)0) + { + b1 = -Dot(diff, ray1.direction); + s0 = (Real)0; + s1 = -b1; + } + else + { + s0 = -b0; + s1 = (Real)0; + } + } + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = ray0.origin + s0 * ray0.direction; + result.closestPoint[1] = ray1.origin + s1 * ray1.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRaySegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRaySegment.h new file mode 100644 index 000000000000..f07fcc4bda05 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRaySegment.h @@ -0,0 +1,179 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + // The centered form of the 'segment' is used. Thus, parameter[1] of + // the result is in [-e,e], where e = |segment.p[1] - segment.p[0]|/2. + Result operator()(Ray const& ray, + Segment const& segment); +}; + +// Template aliases for convenience. +template +using DCPRaySegment = DCPQuery, Segment>; + +template +using DCPRay2Segment2 = DCPRaySegment<2, Real>; + +template +using DCPRay3Segment3 = DCPRaySegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Ray const& ray, Segment const& segment) +{ + Result result; + + Vector segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Vector diff = ray.origin - segCenter; + Real a01 = -Dot(ray.direction, segDirection); + Real b0 = Dot(diff, ray.direction); + Real s0, s1; + + if (std::abs(a01) < (Real)1) + { + // The ray and segment are not parallel. + Real det = (Real)1 - a01 * a01; + Real extDet = segExtent*det; + Real b1 = -Dot(diff, segDirection); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + + if (s0 >= (Real)0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of ray and segment. + s0 /= det; + s1 /= det; + } + else // region 1 + { + s1 = segExtent; + s0 = std::max(-(a01*s1 + b0), (Real)0); + } + } + else // region 5 + { + s1 = -segExtent; + s0 = std::max(-(a01*s1 + b0), (Real)0); + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01*segExtent + b0); + if (s0 > (Real)0) + { + s1 = -segExtent; + } + else + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + } + else if (s1 <= extDet) // region 3 + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + else // region 2 + { + s0 = -(a01*segExtent + b0); + if (s0 > (Real)0) + { + s1 = segExtent; + } + else + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + } + } + } + else + { + // Ray and segment are parallel. + if (a01 > (Real)0) + { + // Opposite direction vectors. + s1 = -segExtent; + } + else + { + // Same direction vectors. + s1 = segExtent; + } + + s0 = std::max(-(a01*s1 + b0), (Real)0); + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = ray.origin + s0 * ray.direction; + result.closestPoint[1] = segCenter + s1 * segDirection; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3AlignedBox3.h new file mode 100644 index 000000000000..74e2a667711c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3AlignedBox3.h @@ -0,0 +1,145 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array rectangleParameter; + std::array boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Rectangle3 const& rectangle, AlignedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, AlignedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3>::operator()( + Rectangle3 const& rectangle, AlignedBox3 const& box) +{ + Result result; + + // Translate the rectangle and aligned box so that the aligned box becomes + // a canonical box. + Vector3 K = box.max - box.min; + Vector3 V = rectangle.center - box.min; + + // Convert the oriented rectangle to a regular one (origin at a corner). + Vector3 scaledE0 = rectangle.axis[0] * rectangle.extent[0]; + Vector3 scaledE1 = rectangle.axis[1] * rectangle.extent[1]; + Vector3 E0 = scaledE0 * (Real)2; + Vector3 E1 = scaledE1 * (Real)2; + V -= scaledE0 + scaledE1; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 5 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1, (Real)1 + }; + + std::array, 10> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 , (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1, (Real)0 }; + M[4] = { -E1[0], -E1[1], -E1[2], (Real)0, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[9] = { (Real)0, (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + Real t0 = (z[3] * (Real)2 - (Real)1) * rectangle.extent[0]; + Real t1 = (z[4] * (Real)2 - (Real)1) * rectangle.extent[1]; + result.rectangleParameter[0] = t0; + result.rectangleParameter[1] = t1; + result.closestPoint[0] = rectangle.center + t0 * rectangle.axis[0] + t1 * rectangle.axis[1]; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] + box.min[i]; + result.closestPoint[1][i] = result.boxParameter[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 2; ++i) + { + result.rectangleParameter[i] = (Real)0; + } + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3OrientedBox3.h new file mode 100644 index 000000000000..67922f959fb9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3OrientedBox3.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array rectangleParameter; + std::array boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Rectangle3 const& rectangle, OrientedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3>::operator()( + Rectangle3 const& rectangle, OrientedBox3 const& box) +{ + Result result; + + // Rigidly transform the rectangle and oriented box so that the oriented + // box becomes a canonical box. + Vector3 K = box.extent * (Real)2; + Vector3 tempV = rectangle.center - box.center; + Vector3 V, E0, E1; + for (int i = 0; i < 3; ++i) + { + V[i] = Dot(box.axis[i], tempV) + box.extent[i]; + E0[i] = Dot(box.axis[i], rectangle.axis[0]); + E1[i] = Dot(box.axis[i], rectangle.axis[1]); + } + + // Convert the oriented rectangle to a regular one (origin at a corner). + Vector3 scaledE0 = E0 * rectangle.extent[0]; + Vector3 scaledE1 = E1 * rectangle.extent[1]; + V -= scaledE0 + scaledE1; + E0 = scaledE0 * (Real)2; + E1 = scaledE1 * (Real)2; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 5 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1, (Real)1 + }; + + std::array, 10> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 , (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1, (Real)0 }; + M[4] = { -E1[0], -E1[1], -E1[2], (Real)0, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[9] = { (Real)0, (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + Real t0 = (z[3] * (Real)2 - (Real)1) * rectangle.extent[0]; + Real t1 = (z[4] * (Real)2 - (Real)1) * rectangle.extent[1]; + result.rectangleParameter[0] = t0; + result.rectangleParameter[1] = t1; + result.closestPoint[0] = rectangle.center + t0 * rectangle.axis[0] + t1 * rectangle.axis[1]; + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] - box.extent[i]; + result.closestPoint[1] += result.boxParameter[i] * box.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 2; ++i) + { + result.rectangleParameter[i] = (Real)0; + } + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3Rectangle3.h new file mode 100644 index 000000000000..18fcb2c5927a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistRectangle3Rectangle3.h @@ -0,0 +1,104 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real rectangle0Parameter[2], rectangle1Parameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Rectangle3 const& rectangle0, + Rectangle3 const& rectangle1); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Rectangle3 const& rectangle0, Rectangle3 const& rectangle1) +{ + Result result; + + DCPQuery, Rectangle3> srQuery; + typename DCPQuery, Rectangle3>::Result + srResult; + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of rectangle0 to the interior of rectangle1. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle0.extent[1 - i1]; + Vector3 segCenter = rectangle0.center + + s * rectangle0.axis[1 - i1]; + Segment3 edge(segCenter, rectangle0.axis[i1], + rectangle0.extent[i1]); + + srResult = srQuery(edge, rectangle1); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + result.rectangle0Parameter[i1] = s; + result.rectangle0Parameter[1 - i1] = + srResult.segmentParameter; + result.rectangle1Parameter[0] = + srResult.rectangleParameter[0]; + result.rectangle1Parameter[1] = + srResult.rectangleParameter[1]; + result.closestPoint[0] = srResult.closestPoint[0]; + result.closestPoint[1] = srResult.closestPoint[1]; + } + } + } + + // Compare edges of rectangle1 to the interior of rectangle0. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle1.extent[1 - i1]; + Vector3 segCenter = rectangle1.center + + s * rectangle1.axis[1 - i1]; + Segment3 edge(segCenter, rectangle0.axis[i1], + rectangle0.extent[i1]); + + srResult = srQuery(edge, rectangle0); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + result.rectangle0Parameter[0] = + srResult.rectangleParameter[0]; + result.rectangle0Parameter[1] = + srResult.rectangleParameter[1]; + result.rectangle1Parameter[i1] = s; + result.rectangle1Parameter[1 - i1] = + srResult.segmentParameter; + result.closestPoint[0] = srResult.closestPoint[1]; + result.closestPoint[1] = srResult.closestPoint[0]; + } + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3AlignedBox3.h new file mode 100644 index 000000000000..78947e7eaca6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3AlignedBox3.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/11/17) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + AlignedBox3 const& box); +}; + + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3> ::operator()( + Segment3 const& segment, AlignedBox3 const& box) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, AlignedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= -segExtent) + { + if (lbResult.lineParameter <= segExtent) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.segmentParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, AlignedBox3> pbQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + } + else + { + DCPQuery, AlignedBox3> pbQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = -segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3OrientedBox3.h new file mode 100644 index 000000000000..bf4bb07b4a5b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3OrientedBox3.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + OrientedBox3 const& box); +}; + + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3> ::operator()( + Segment3 const& segment, OrientedBox3 const& box) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, OrientedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= -segExtent) + { + if (lbResult.lineParameter <= segExtent) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.segmentParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, OrientedBox3> pbQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + } + else + { + DCPQuery, OrientedBox3> pbQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Rectangle3.h new file mode 100644 index 000000000000..d7b0082f6cb2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Rectangle3.h @@ -0,0 +1,87 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Segment3 const& segment, Rectangle3 const& rectangle) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, Rectangle3> lrQuery; + auto lrResult = lrQuery(line, rectangle); + + if (lrResult.lineParameter >= -segExtent) + { + if (lrResult.lineParameter <= segExtent) + { + result.distance = lrResult.distance; + result.sqrDistance = lrResult.sqrDistance; + result.segmentParameter = lrResult.lineParameter; + result.rectangleParameter[0] = lrResult.rectangleParameter[0]; + result.rectangleParameter[1] = lrResult.rectangleParameter[1]; + result.closestPoint[0] = lrResult.closestPoint[0]; + result.closestPoint[1] = lrResult.closestPoint[1]; + } + else + { + DCPQuery, Rectangle3> prQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto prResult = prQuery(point, rectangle); + result.sqrDistance = prResult.sqrDistance; + result.distance = prResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + } + else + { + DCPQuery, Rectangle3> prQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto prResult = prQuery(point, rectangle); + result.sqrDistance = prResult.sqrDistance; + result.distance = prResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Triangle3.h new file mode 100644 index 000000000000..e1091d9ec585 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegment3Triangle3.h @@ -0,0 +1,88 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, + Triangle3 const& triangle); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Segment3 const& segment, Triangle3 const& triangle) +{ + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, Triangle3> ltQuery; + auto ltResult = ltQuery(line, triangle); + + if (ltResult.lineParameter >= -segExtent) + { + if (ltResult.lineParameter <= segExtent) + { + result.distance = ltResult.distance; + result.sqrDistance = ltResult.sqrDistance; + result.segmentParameter = ltResult.lineParameter; + result.triangleParameter[0] = ltResult.triangleParameter[0]; + result.triangleParameter[1] = ltResult.triangleParameter[1]; + result.triangleParameter[2] = ltResult.triangleParameter[2]; + result.closestPoint[0] = ltResult.closestPoint[0]; + result.closestPoint[1] = ltResult.closestPoint[1]; + } + else + { + DCPQuery, Triangle3> ptQuery; + Vector3 point = segCenter + segExtent*segDirection; + auto ptResult = ptQuery(point, triangle); + result.sqrDistance = ptResult.sqrDistance; + result.distance = ptResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = ptResult.closest; + } + } + else + { + DCPQuery, Triangle3> ptQuery; + Vector3 point = segCenter - segExtent*segDirection; + auto ptResult = ptQuery(point, triangle); + result.sqrDistance = ptResult.sqrDistance; + result.distance = ptResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = ptResult.closest; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegment.h new file mode 100644 index 000000000000..efb833c0dde9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegment.h @@ -0,0 +1,427 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +// Compute the closest points on the line segments P(s) = (1-s)*P0 + s*P1 and +// Q(t) = (1-t)*Q0 + t*Q1 for 0 <= s <= 1 and 0 <= t <= 1. The algorithm is +// robust even for nearly parallel segments. Effectively, it uses a conjugate +// gradient search for the minimum of the squared distance function, which +// avoids the numerical problems introduced by divisions in the case the +// minimum is located at an interior point of the domain. See the document +// http://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf +// for details. + +namespace gte +{ + +template +class DCPQuery, Segment> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closest[2]; + }; + + Result operator()(Segment const& segment0, + Segment const& segment1); + + Result operator()(Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1); + +private: + // Compute the root of h(z) = h0 + slope*z and clamp it to the interval + // [0,1]. It is required that for h1 = h(1), either (h0 < 0 and h1 > 0) + // or (h0 > 0 and h1 < 0). + Real GetClampedRoot(Real slope, Real h0, Real h1); + + // Compute the intersection of the line dR/ds = 0 with the domain [0,1]^2. + // The direction of the line dR/ds is conjugate to (1,0), so the algorithm + // for minimization is effectively the conjugate gradient algorithm for a + // quadratic function. + void ComputeIntersection(Real const sValue[2], int const classify[2], + int edge[2], Real end[2][2]); + + // Compute the location of the minimum of R on the segment of intersection + // for the line dR/ds = 0 and the domain [0,1]^2. + void ComputeMinimumParameters(int const edge[2], Real const end[2][2], + Real parameter[2]); + + // The coefficients of R(s,t), not including the constant term. + Real mA, mB, mC, mD, mE; + + // dR/ds(i,j) at the four corners of the domain + Real mF00, mF10, mF01, mF11; + + // dR/dt(i,j) at the four corners of the domain + Real mG00, mG10, mG01, mG11; +}; + +// Template aliases for convenience. +template +using DCPSegmentSegment = DCPQuery, Segment>; + +template +using DCPSegment2Segment2 = DCPSegmentSegment<2, Real>; + +template +using DCPSegment3Segment3 = DCPSegmentSegment<3, Real>; + + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Segment const& segment0, Segment const& segment1) +{ + return operator()(segment0.p[0], segment0.p[1], segment1.p[0], + segment1.p[1]); +} + +template +typename DCPQuery, Segment>::Result +DCPQuery, Segment>::operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1) +{ + Result result; + + // The code allows degenerate line segments; that is, P0 and P1 can be + // the same point or Q0 and Q1 can be the same point. The quadratic + // function for squared distance between the segment is + // R(s,t) = a*s^2 - 2*b*s*t + c*t^2 + 2*d*s - 2*e*t + f + // for (s,t) in [0,1]^2 where + // a = Dot(P1-P0,P1-P0), b = Dot(P1-P0,Q1-Q0), c = Dot(Q1-Q0,Q1-Q0), + // d = Dot(P1-P0,P0-Q0), e = Dot(Q1-Q0,P0-Q0), f = Dot(P0-Q0,P0-Q0) + Vector P1mP0 = P1 - P0; + Vector Q1mQ0 = Q1 - Q0; + Vector P0mQ0 = P0 - Q0; + mA = Dot(P1mP0, P1mP0); + mB = Dot(P1mP0, Q1mQ0); + mC = Dot(Q1mQ0, Q1mQ0); + mD = Dot(P1mP0, P0mQ0); + mE = Dot(Q1mQ0, P0mQ0); + + mF00 = mD; + mF10 = mF00 + mA; + mF01 = mF00 - mB; + mF11 = mF10 - mB; + + mG00 = -mE; + mG10 = mG00 - mB; + mG01 = mG00 + mC; + mG11 = mG10 + mC; + + if (mA > (Real)0 && mC > (Real)0) + { + // Compute the solutions to dR/ds(s0,0) = 0 and dR/ds(s1,1) = 0. The + // location of sI on the s-axis is stored in classifyI (I = 0 or 1). If + // sI <= 0, classifyI is -1. If sI >= 1, classifyI is 1. If 0 < sI < 1, + // classifyI is 0. This information helps determine where to search for + // the minimum point (s,t). The fij values are dR/ds(i,j) for i and j in + // {0,1}. + + Real sValue[2]; + sValue[0] = GetClampedRoot(mA, mF00, mF10); + sValue[1] = GetClampedRoot(mA, mF01, mF11); + + int classify[2]; + for (int i = 0; i < 2; ++i) + { + if (sValue[i] <= (Real)0) + { + classify[i] = -1; + } + else if (sValue[i] >= (Real)1) + { + classify[i] = +1; + } + else + { + classify[i] = 0; + } + } + + if (classify[0] == -1 && classify[1] == -1) + { + // The minimum must occur on s = 0 for 0 <= t <= 1. + result.parameter[0] = (Real)0; + result.parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (classify[0] == +1 && classify[1] == +1) + { + // The minimum must occur on s = 1 for 0 <= t <= 1. + result.parameter[0] = (Real)1; + result.parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + // The line dR/ds = 0 intersects the domain [0,1]^2 in a + // nondegenerate segment. Compute the endpoints of that segment, + // end[0] and end[1]. The edge[i] flag tells you on which domain + // edge end[i] lives: 0 (s=0), 1 (s=1), 2 (t=0), 3 (t=1). + int edge[2]; + Real end[2][2]; + ComputeIntersection(sValue, classify, edge, end); + + // The directional derivative of R along the segment of + // intersection is + // H(z) = (end[1][1]-end[1][0])*dR/dt((1-z)*end[0] + z*end[1]) + // for z in [0,1]. The formula uses the fact that dR/ds = 0 on + // the segment. Compute the minimum of H on [0,1]. + ComputeMinimumParameters(edge, end, result.parameter); + } + } + else + { + if (mA > (Real)0) + { + // The Q-segment is degenerate (Q0 and Q1 are the same point) and + // the quadratic is R(s,0) = a*s^2 + 2*d*s + f and has (half) + // first derivative F(t) = a*s + d. The closest P-point is + // interior to the P-segment when F(0) < 0 and F(1) > 0. + result.parameter[0] = GetClampedRoot(mA, mF00, mF10); + result.parameter[1] = (Real)0; + } + else if (mC > (Real)0) + { + // The P-segment is degenerate (P0 and P1 are the same point) and + // the quadratic is R(0,t) = c*t^2 - 2*e*t + f and has (half) + // first derivative G(t) = c*t - e. The closest Q-point is + // interior to the Q-segment when G(0) < 0 and G(1) > 0. + result.parameter[0] = (Real)0; + result.parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else + { + // P-segment and Q-segment are degenerate. + result.parameter[0] = (Real)0; + result.parameter[1] = (Real)0; + } + } + + + result.closest[0] = + ((Real)1 - result.parameter[0]) * P0 + result.parameter[0] * P1; + result.closest[1] = + ((Real)1 - result.parameter[1]) * Q0 + result.parameter[1] * Q1; + Vector diff = result.closest[0] - result.closest[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; +} + +template +Real DCPQuery, Segment>::GetClampedRoot( + Real slope, Real h0, Real h1) +{ + // Theoretically, r is in (0,1). However, when the slope is nearly zero, + // then so are h0 and h1. Significant numerical rounding problems can + // occur when using floating-point arithmetic. If the rounding causes r + // to be outside the interval, clamp it. It is possible that r is in + // (0,1) and has rounding errors, but because h0 and h1 are both nearly + // zero, the quadratic is nearly constant on (0,1). Any choice of p + // should not cause undesirable accuracy problems for the final distance + // computation. + // + // NOTE: You can use bisection to recompute the root or even use + // bisection to compute the root and skip the division. This is generally + // slower, which might be a problem for high-performance applications. + + Real r; + if (h0 < (Real)0) + { + if (h1 > (Real)0) + { + r = -h0 / slope; + if (r > (Real)1) + { + r = (Real)0.5; + } + // The slope is positive and -h0 is positive, so there is no + // need to test for a negative value and clamp it. + } + else + { + r = (Real)1; + } + } + else + { + r = (Real)0; + } + return r; +} + +template +void DCPQuery, Segment>::ComputeIntersection( + Real const sValue[2], int const classify[2], int edge[2], Real end[2][2]) +{ + // The divisions are theoretically numbers in [0,1]. Numerical rounding + // errors might cause the result to be outside the interval. When this + // happens, it must be that both numerator and denominator are nearly + // zero. The denominator is nearly zero when the segments are nearly + // perpendicular. The numerator is nearly zero when the P-segment is + // nearly degenerate (mF00 = a is small). The choice of 0.5 should not + // cause significant accuracy problems. + // + // NOTE: You can use bisection to recompute the root or even use + // bisection to compute the root and skip the division. This is generally + // slower, which might be a problem for high-performance applications. + + if (classify[0] < 0) + { + edge[0] = 0; + end[0][0] = (Real)0; + end[0][1] = mF00 / mB; + if (end[0][1] < (Real)0 || end[0][1] > (Real)1) + { + end[0][1] = (Real)0.5; + } + + if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else // classify[1] > 0 + { + edge[1] = 1; + end[1][0] = (Real)1; + end[1][1] = mF10 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + else if (classify[0] == 0) + { + edge[0] = 2; + end[0][0] = sValue[0]; + end[0][1] = (Real)0; + + if (classify[1] < 0) + { + edge[1] = 0; + end[1][0] = (Real)0; + end[1][1] = mF00 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + else if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else + { + edge[1] = 1; + end[1][0] = (Real)1; + end[1][1] = mF10 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + else // classify[0] > 0 + { + edge[0] = 1; + end[0][0] = (Real)1; + end[0][1] = mF10 / mB; + if (end[0][1] < (Real)0 || end[0][1] > (Real)1) + { + end[0][1] = (Real)0.5; + } + + if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else + { + edge[1] = 0; + end[1][0] = (Real)0; + end[1][1] = mF00 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } +} + +template +void DCPQuery, Segment>:: +ComputeMinimumParameters(int const edge[2], Real const end[2][2], + Real parameter[2]) +{ + Real delta = end[1][1] - end[0][1]; + Real h0 = delta * (-mB * end[0][0] + mC * end[0][1] - mE); + if (h0 >= (Real)0) + { + if (edge[0] == 0) + { + parameter[0] = (Real)0; + parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (edge[0] == 1) + { + parameter[0] = (Real)1; + parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + parameter[0] = end[0][0]; + parameter[1] = end[0][1]; + } + } + else + { + Real h1 = delta * (-mB * end[1][0] + mC * end[1][1] - mE); + if (h1 <= (Real)0) + { + if (edge[1] == 0) + { + parameter[0] = (Real)0; + parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (edge[1] == 1) + { + parameter[0] = (Real)1; + parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + parameter[0] = end[1][0]; + parameter[1] = end[1][1]; + } + } + else // h0 < 0 and h1 > 0 + { + Real z = std::min(std::max(h0 / (h0 - h1), (Real)0), (Real)1); + Real omz = (Real)1 - z; + parameter[0] = omz * end[0][0] + z * end[1][0]; + parameter[1] = omz * end[0][1] + z * end[1][1]; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegmentExact.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegmentExact.h new file mode 100644 index 000000000000..49b024579636 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistSegmentSegmentExact.h @@ -0,0 +1,289 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Compute the closest points on the line segments P(s) = (1-s)*P0 + s*P1 and +// Q(t) = (1-t)*Q0 + t*Q1 for 0 <= s <= 1 and 0 <= t <= 1. The algorithm +// relies on exact rational arithmetic. + +namespace gte +{ + +template +class DistanceSegmentSegmentExact +{ +public: + struct Result + { + Rational sqrDistance; + Rational parameter[2]; + Vector closest[2]; + }; + + Result operator()(Segment const& segment0, + Segment const& segment1); + + Result operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1); +}; + + +template +typename DistanceSegmentSegmentExact::Result +DistanceSegmentSegmentExact::operator()( + Segment const& segment0, + Segment const& segment1) +{ + return operator()(segment0.p[0], segment0.p[1], segment1.p[0], + segment1.p[1]); +} + +template +typename DistanceSegmentSegmentExact::Result +DistanceSegmentSegmentExact::operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1) +{ + Vector P1mP0 = P1 - P0; + Vector Q1mQ0 = Q1 - Q0; + Vector P0mQ0 = P0 - Q0; + Rational a = Dot(P1mP0, P1mP0); + Rational b = Dot(P1mP0, Q1mQ0); + Rational c = Dot(Q1mQ0, Q1mQ0); + Rational d = Dot(P1mP0, P0mQ0); + Rational e = Dot(Q1mQ0, P0mQ0); + Rational const zero = (Rational)0; + Rational const one = (Rational)1; + Rational det = a * c - b * b; + Rational s, t, nd, bmd, bte, ctd, bpe, ate, btd; + + if (det > zero) + { + bte = b * e; + ctd = c * d; + if (bte <= ctd) // s <= 0 + { + s = zero; + if (e <= zero) // t <= 0 + { + // region 6 + t = zero; + nd = -d; + if (nd >= a) + { + s = one; + } + else if (nd > zero) + { + s = nd / a; + } + // else: s is already zero + } + else if (e < c) // 0 < t < 1 + { + // region 5 + t = e / c; + } + else // t >= 1 + { + // region 4 + t = one; + bmd = b - d; + if (bmd >= a) + { + s = one; + } + else if (bmd > zero) + { + s = bmd / a; + } + // else: s is already zero + } + } + else // s > 0 + { + s = bte - ctd; + if (s >= det) // s >= 1 + { + // s = 1 + s = one; + bpe = b + e; + if (bpe <= zero) // t <= 0 + { + // region 8 + t = zero; + nd = -d; + if (nd <= zero) + { + s = zero; + } + else if (nd < a) + { + s = nd / a; + } + // else: s is already one + } + else if (bpe < c) // 0 < t < 1 + { + // region 1 + t = bpe / c; + } + else // t >= 1 + { + // region 2 + t = one; + bmd = b - d; + if (bmd <= zero) + { + s = zero; + } + else if (bmd < a) + { + s = bmd / a; + } + // else: s is already one + } + } + else // 0 < s < 1 + { + ate = a * e; + btd = b * d; + if (ate <= btd) // t <= 0 + { + // region 7 + t = zero; + nd = -d; + if (nd <= zero) + { + s = zero; + } + else if (nd >= a) + { + s = one; + } + else + { + s = nd / a; + } + } + else // t > 0 + { + t = ate - btd; + if (t >= det) // t >= 1 + { + // region 3 + t = one; + bmd = b - d; + if (bmd <= zero) + { + s = zero; + } + else if (bmd >= a) + { + s = one; + } + else + { + s = bmd / a; + } + } + else // 0 < t < 1 + { + // region 0 + s /= det; + t /= det; + } + } + } + } + } + else + { + // The segments are parallel. The quadratic factors to R(s,t) = + // a*(s-(b/a)*t)^2 + 2*d*(s - (b/a)*t) + f, where a*c = b^2, + // e = b*d/a, f = |P0-Q0|^2, and b is not zero. R is constant along + // lines of the form s-(b/a)*t = k, and the minimum of R occurs on the + // line a*s - b*t + d = 0. This line must intersect both the s-axis + // and the t-axis because 'a' and 'b' are not zero. Because of + // parallelism, the line is also represented by -b*s + c*t - e = 0. + // + // The code determines an edge of the domain [0,1]^2 that intersects + // the minimum line, or if none of the edges intersect, it determines + // the closest corner to the minimum line. The conditionals are + // designed to test first for intersection with the t-axis (s = 0) + // using -b*s + c*t - e = 0 and then with the s-axis (t = 0) using + // a*s - b*t + d = 0. + + // When s = 0, solve c*t - e = 0 (t = e/c). + if (e <= zero) // t <= 0 + { + // Now solve a*s - b*t + d = 0 for t = 0 (s = -d/a). + t = zero; + nd = -d; + if (nd <= zero) // s <= 0 + { + // region 6 + s = zero; + } + else if (nd >= a) // s >= 1 + { + // region 8 + s = one; + } + else // 0 < s < 1 + { + // region 7 + s = nd / a; + } + } + else if (e >= c) // t >= 1 + { + // Now solve a*s - b*t + d = 0 for t = 1 (s = (b-d)/a). + t = one; + bmd = b - d; + if (bmd <= zero) // s <= 0 + { + // region 4 + s = zero; + } + else if (bmd >= a) // s >= 1 + { + // region 2 + s = one; + } + else // 0 < s < 1 + { + // region 3 + s = bmd / a; + } + } + else // 0 < t < 1 + { + // The point (0,e/c) is on the line and domain, so we have one + // point at which R is a minimum. + s = zero; + t = e / c; + } + } + + Result result; + result.parameter[0] = s; + result.parameter[1] = t; + result.closest[0] = P0 + s * P1mP0; + result.closest[1] = Q0 + t * Q1mQ0; + Vector diff = result.closest[1] - result.closest[0]; + result.sqrDistance = Dot(diff, diff); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3AlignedBox3.h new file mode 100644 index 000000000000..66e357d05c05 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3AlignedBox3.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, AlignedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array triangleParameter, boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Triangle3 const& triangle, AlignedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, AlignedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, AlignedBox3>::Result +DCPQuery, AlignedBox3>::operator()( + Triangle3 const& triangle, AlignedBox3 const& box) +{ + Result result; + + // Translate the triangle and aligned box so that the aligned box becomes + // a canonical box. + Vector3 K = box.max - box.min; + Vector3 V = triangle.v[0] - box.min; + Vector3 E0 = triangle.v[1] - triangle.v[0]; + Vector3 E1 = triangle.v[2] - triangle.v[0]; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE0E1 = Dot(E0, E1); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 4 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1 + }; + + std::array, 9> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, dotE0E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[4] = { -E1[0], -E1[1], -E1[2], dotE0E1, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.triangleParameter[0] = (Real)1 - z[3] - z[4]; + result.triangleParameter[1] = z[3]; + result.triangleParameter[2] = z[4]; + result.closestPoint[0] = triangle.v[0] + z[3] * E0 + z[4] * E1; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] + box.min[i]; + result.closestPoint[1][i] = result.boxParameter[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.triangleParameter[i] = (Real)0; + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3OrientedBox3.h new file mode 100644 index 000000000000..a0f0507f1613 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3OrientedBox3.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.5.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class DCPQuery, OrientedBox3> +{ +public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array triangleParameter, boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of whether + // the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). If + // the solver fails to converge, try increasing the maximum number of + // iterations. + void SetMaxLCPIterations(int maxLCPIterations); + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box); + +private: + LCPSolver mLCP; +}; + + +template +void DCPQuery, OrientedBox3>::SetMaxLCPIterations(int maxLCPIterations) +{ + mLCP.SetMaxIterations(maxLCPIterations); +} + +template +typename DCPQuery, OrientedBox3>::Result +DCPQuery, OrientedBox3>::operator()( + Triangle3 const& triangle, OrientedBox3 const& box) +{ + Result result; + + // Rigidly transform the triangle and oriented box so that the oriented + // box becomes a canonical box. + Vector3 K = box.extent * (Real)2; + Vector3 tempV = triangle.v[0] - box.center; + Vector3 tempE0 = triangle.v[1] - triangle.v[0]; + Vector3 tempE1 = triangle.v[2] - triangle.v[0]; + Vector3 V, E0, E1; + for (int i = 0; i < 3; ++i) + { + V[i] = Dot(box.axis[i], tempV) + box.extent[i]; + E0[i] = Dot(box.axis[i], tempE0); + E1[i] = Dot(box.axis[i], tempE1); + } + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE0E1 = Dot(E0, E1); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 4 (nontrivial) inequality constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1 + }; + + std::array, 9> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, dotE0E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[4] = { -E1[0], -E1[1], -E1[2], dotE0E1, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.triangleParameter[0] = (Real)1 - z[3] - z[4]; + result.triangleParameter[1] = z[3]; + result.triangleParameter[2] = z[4]; + result.closestPoint[0] = triangle.v[0] + z[3] * tempE0 + z[4] * tempE1; + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] - box.extent[i]; + result.closestPoint[1] += result.boxParameter[i] * box.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations was not + // specified to be large enough or there is a problem due to + // floating-point rounding errors. If you believe the latter is + // true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.triangleParameter[i] = (Real)0; + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Rectangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Rectangle3.h new file mode 100644 index 000000000000..765191e175fb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Rectangle3.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class DCPQuery, Rectangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real triangleParameter[3], rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Triangle3 const& triangle, + Rectangle3 const& rectangle); +}; + + +template +typename DCPQuery, Rectangle3>::Result +DCPQuery, Rectangle3>::operator()( + Triangle3 const& triangle, Rectangle3 const& rectangle) +{ + Result result; + + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of triangle to the interior of rectangle. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle.v[i0] + + triangle.v[i1]); + Vector3 segDirection = triangle.v[i1] - triangle.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + DCPQuery, Rectangle3> srQuery; + auto srResult = srQuery(edge, rectangle); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + Real ratio = srResult.segmentParameter / segExtent; // in [-1,1] + result.triangleParameter[i0] = ((Real)0.5)*((Real)1 - ratio); + result.triangleParameter[i1] = + (Real)1 - result.triangleParameter[i0]; + result.triangleParameter[3 - i0 - i1] = (Real)0; + result.rectangleParameter[0] = srResult.rectangleParameter[0]; + result.rectangleParameter[1] = srResult.rectangleParameter[1]; + result.closestPoint[0] = srResult.closestPoint[0]; + result.closestPoint[1] = srResult.closestPoint[1]; + } + } + + // Compare edges of rectangle to the interior of triangle. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle.extent[1 - i1]; + Vector3 segCenter = rectangle.center + + s * rectangle.axis[1 - i1]; + Segment3 edge(segCenter, rectangle.axis[i1], + rectangle.extent[i1]); + + DCPQuery, Triangle3> stQuery; + auto stResult = stQuery(edge, triangle); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + result.triangleParameter[0] = stResult.triangleParameter[0]; + result.triangleParameter[1] = stResult.triangleParameter[1]; + result.triangleParameter[2] = stResult.triangleParameter[2]; + result.rectangleParameter[i1] = s; + result.rectangleParameter[1 - i1] = stResult.segmentParameter; + result.closestPoint[0] = stResult.closestPoint[1]; + result.closestPoint[1] = stResult.closestPoint[0]; + } + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Triangle3.h new file mode 100644 index 000000000000..88f4d50bb140 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteDistTriangle3Triangle3.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class DCPQuery, Triangle3> +{ +public: + struct Result + { + Real distance, sqrDistance; + Real triangle0Parameter[3], triangle1Parameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Triangle3 const& triangle0, + Triangle3 const& triangle1); +}; + + +template +typename DCPQuery, Triangle3>::Result +DCPQuery, Triangle3>::operator()( + Triangle3 const& triangle0, Triangle3 const& triangle1) +{ + Result result; + + DCPQuery, Triangle3> stQuery; + typename DCPQuery, Triangle3>::Result + stResult; + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of triangle0 to the interior of triangle1. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle0.v[i0] + + triangle0.v[i1]); + Vector3 segDirection = triangle0.v[i1] - triangle0.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + stResult = stQuery(edge, triangle1); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + Real ratio = stResult.segmentParameter / segExtent; // in [-1,1] + result.triangle0Parameter[i0] = ((Real)0.5)*((Real)1 - ratio); + result.triangle0Parameter[i1] = + (Real)1 - result.triangle0Parameter[i0]; + result.triangle0Parameter[3 - i0 - i1] = (Real)0; + result.triangle1Parameter[0] = stResult.triangleParameter[0]; + result.triangle1Parameter[1] = stResult.triangleParameter[1]; + result.triangle1Parameter[2] = stResult.triangleParameter[2]; + result.closestPoint[0] = stResult.closestPoint[0]; + result.closestPoint[1] = stResult.closestPoint[1]; + } + } + + // Compare edges of triangle1 to the interior of triangle0. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = ((Real)0.5)*(triangle1.v[i0] + + triangle1.v[i1]); + Vector3 segDirection = triangle1.v[i1] - triangle1.v[i0]; + Real segExtent = ((Real)0.5)*Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + stResult = stQuery(edge, triangle0); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + Real ratio = stResult.segmentParameter / segExtent; // in [-1,1] + result.triangle0Parameter[0] = stResult.triangleParameter[0]; + result.triangle0Parameter[1] = stResult.triangleParameter[1]; + result.triangle0Parameter[2] = stResult.triangleParameter[2]; + result.triangle1Parameter[i0] = ((Real)0.5)*((Real)1 - ratio); + result.triangle1Parameter[i1] = + (Real)1 - result.triangle0Parameter[i0]; + result.triangle1Parameter[3 - i0 - i1] = (Real)0; + result.closestPoint[0] = stResult.closestPoint[0]; + result.closestPoint[1] = stResult.closestPoint[1]; + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.cpp new file mode 100644 index 000000000000..5a6fb5e882a9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.cpp @@ -0,0 +1,384 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/01/02) + +#include +#include +#include + +using namespace gte; + +ETManifoldMesh::~ETManifoldMesh() +{ +} + +ETManifoldMesh::ETManifoldMesh(ECreator eCreator, TCreator tCreator) + : + mECreator(eCreator ? eCreator : CreateEdge), + mTCreator(tCreator ? tCreator : CreateTriangle), + mAssertOnNonmanifoldInsertion(true) +{ +} + +ETManifoldMesh::ETManifoldMesh(ETManifoldMesh const& mesh) +{ + *this = mesh; +} + +ETManifoldMesh& ETManifoldMesh::operator=(ETManifoldMesh const& mesh) +{ + Clear(); + + mECreator = mesh.mECreator; + mTCreator = mesh.mTCreator; + mAssertOnNonmanifoldInsertion = mesh.mAssertOnNonmanifoldInsertion; + for (auto const& element : mesh.mTMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2]); + } + + return *this; +} + +ETManifoldMesh::EMap const& ETManifoldMesh::GetEdges() const +{ + return mEMap; +} + +ETManifoldMesh::TMap const& ETManifoldMesh::GetTriangles() const +{ + return mTMap; +} + +bool ETManifoldMesh::AssertOnNonmanifoldInsertion(bool doAssert) +{ + std::swap(doAssert, mAssertOnNonmanifoldInsertion); + return doAssert; // return the previous state +} + +std::shared_ptr ETManifoldMesh::Insert(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + if (mTMap.find(tkey) != mTMap.end()) + { + // The triangle already exists. Return a null pointer as a signal to + // the caller that the insertion failed. + return nullptr; + } + + // Create the new triangle. It will be added to mTMap at the end of the + // function so that if an assertion is triggered and the function returns + // early, the (bad) triangle will not be part of the mesh. + std::shared_ptr tri = mTCreator(v0, v1, v2); + + // Add the edges to the mesh if they do not already exist. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + EdgeKey ekey(tri->V[i0], tri->V[i1]); + std::shared_ptr edge; + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // This is the first time the edge is encountered. + edge = mECreator(tri->V[i0], tri->V[i1]); + mEMap[ekey] = edge; + + // Update the edge and triangle. + edge->T[0] = tri; + tri->E[i0] = edge; + } + else + { + // This is the second time the edge is encountered. + edge = eiter->second; + if (!edge) + { + LogError("Unexpected condition."); + return nullptr; + } + + // Update the edge. + if (edge->T[1].lock()) + { + if (mAssertOnNonmanifoldInsertion) + { + LogInformation("The mesh must be manifold."); + } + return nullptr; + } + edge->T[1] = tri; + + // Update the adjacent triangles. + auto adjacent = edge->T[0].lock(); + if (!adjacent) + { + LogError("Unexpected condition."); + return nullptr; + } + for (int j = 0; j < 3; ++j) + { + if (adjacent->E[j].lock() == edge) + { + adjacent->T[j] = tri; + break; + } + } + + // Update the triangle. + tri->E[i0] = edge; + tri->T[i0] = adjacent; + } + } + + mTMap[tkey] = tri; + return tri; +} + +bool ETManifoldMesh::Remove(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // The triangle does not exist. + return false; + } + + // Get the triangle. + std::shared_ptr tri = titer->second; + + // Remove the edges and update adjacent triangles if necessary. + for (int i = 0; i < 3; ++i) + { + // Inform the edges the triangle is being deleted. + auto edge = tri->E[i].lock(); + if (!edge) + { + // The triangle edge should be nonnull. + LogError("Unexpected condition."); + return false; + } + + if (edge->T[0].lock() == tri) + { + // One-triangle edges always have pointer at index zero. + edge->T[0] = edge->T[1]; + edge->T[1].reset(); + } + else if (edge->T[1].lock() == tri) + { + edge->T[1].reset(); + } + else + { + LogError("Unexpected condition."); + return false; + } + + // Remove the edge if you have the last reference to it. + if (!edge->T[0].lock() && !edge->T[1].lock()) + { + EdgeKey ekey(edge->V[0], edge->V[1]); + mEMap.erase(ekey); + } + + // Inform adjacent triangles the triangle is being deleted. + auto adjacent = tri->T[i].lock(); + if (adjacent) + { + for (int j = 0; j < 3; ++j) + { + if (adjacent->T[j].lock() == tri) + { + adjacent->T[j].reset(); + break; + } + } + } + } + + mTMap.erase(tkey); + return true; +} + +void ETManifoldMesh::Clear() +{ + mEMap.clear(); + mTMap.clear(); +} + +bool ETManifoldMesh::IsClosed() const +{ + for (auto const& element : mEMap) + { + auto edge = element.second; + if (!edge->T[0].lock() || !edge->T[1].lock()) + { + return false; + } + } + return true; +} + +bool ETManifoldMesh::IsOriented() const +{ + for (auto const& element : mEMap) + { + auto edge = element.second; + if (edge->T[0].lock() && edge->T[1].lock()) + { + // In each triangle, find the ordered edge that corresponds to the + // unordered edge element.first. Also find the vertex opposite + // that edge. + bool edgePositive[2] = { false, false }; + int vOpposite[2] = { -1, -1 }; + for (int j = 0; j < 2; ++j) + { + auto tri = edge->T[j].lock(); + for (int i = 0; i < 3; ++i) + { + if (tri->V[i] == element.first.V[0]) + { + int vNext = tri->V[(i + 1) % 3]; + if (vNext == element.first.V[1]) + { + edgePositive[j] = true; + vOpposite[j] = tri->V[(i + 2) % 3]; + } + else + { + edgePositive[j] = false; + vOpposite[j] = vNext; + } + break; + } + } + } + + // To be oriented consistently, the edges must have reversed + // ordering and the oppositive vertices cannot match. + if (edgePositive[0] == edgePositive[1] || vOpposite[0] == vOpposite[1]) + { + return false; + } + } + } + return true; +} + +void ETManifoldMesh::GetComponents( + std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + auto tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + components.push_back(component); + } + } +} + +void ETManifoldMesh::GetComponents( + std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + std::shared_ptr tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + + std::vector> keyComponent; + keyComponent.reserve(component.size()); + for (auto const& t : component) + { + keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); + } + components.push_back(keyComponent); + } + } +} + +void ETManifoldMesh::DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const +{ + // Allocate the maximum-size stack that can occur in the depth-first + // search. The stack is empty when the index top is -1. + std::vector> tStack(mTMap.size()); + int top = -1; + tStack[++top] = tInitial; + while (top >= 0) + { + std::shared_ptr tri = tStack[top]; + visited[tri] = 1; + int i; + for (i = 0; i < 3; ++i) + { + std::shared_ptr adj = tri->T[i].lock(); + if (adj && visited[adj] == 0) + { + tStack[++top] = adj; + break; + } + } + if (i == 3) + { + visited[tri] = 2; + component.push_back(tri); + --top; + } + } +} + +std::shared_ptr ETManifoldMesh::CreateEdge(int v0, int v1) +{ + return std::make_shared(v0, v1); +} + +std::shared_ptr ETManifoldMesh::CreateTriangle(int v0, int v1, int v2) +{ + return std::make_shared(v0, v1, v2); +} + +ETManifoldMesh::Edge::~Edge() +{ +} + +ETManifoldMesh::Edge::Edge(int v0, int v1) +{ + V[0] = v0; + V[1] = v1; +} + +ETManifoldMesh::Triangle::~Triangle() +{ +} + +ETManifoldMesh::Triangle::Triangle(int v0, int v1, int v2) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.h new file mode 100644 index 000000000000..e891009912fc --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETManifoldMesh.h @@ -0,0 +1,145 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/01/02) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP ETManifoldMesh +{ +public: + // Edge data types. + class Edge; + typedef std::shared_ptr (*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Triangle data types. + class Triangle; + typedef std::shared_ptr (*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Edge object. + class GTE_IMPEXP Edge + { + public: + virtual ~Edge(); + Edge(int v0, int v1); + + // Vertices of the edge. + int V[2]; + + // Triangles sharing the edge. + std::weak_ptr T[2]; + }; + + // Triangle object. + class GTE_IMPEXP Triangle + { + public: + virtual ~Triangle(); + Triangle(int v0, int v1, int v2); + + // Vertices, listed in counterclockwise order (V[0],V[1],V[2]). + int V[3]; + + // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). + std::weak_ptr E[3]; + + // Adjacent triangles. T[i] points to the adjacent triangle + // sharing edge E[i]. + std::weak_ptr T[3]; + }; + + + // Construction and destruction. + virtual ~ETManifoldMesh(); + ETManifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mEMap and mTMap objects have + // dynamically allocated memory for edges and triangles. A shallow copy + // of the pointers to this memory is problematic. Allowing sharing, say, + // via std::shared_ptr, is an option but not really the intent of copying + // the mesh graph. + ETManifoldMesh(ETManifoldMesh const& mesh); + ETManifoldMesh& operator=(ETManifoldMesh const& mesh); + + // Member access. + EMap const& GetEdges() const; + TMap const& GetTriangles() const; + + // If the insertion of a triangle fails because the mesh would become + // nonmanifold, the default behavior is to trigger a LogError message. + // You can disable this behavior in situations where you want the Logger + // system on but you want to continue gracefully without an assertion. + // The return value is the previous value of the internal state + // mAssertOnNonmanifoldInsertion. + bool AssertOnNonmanifoldInsertion(bool doAssert); + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is returned. + // If the insertion leads to a nonmanifold mesh, the call fails with a + // nullptr returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2); + + // Destroy the edges and triangles to obtain an empty mesh. + virtual void Clear(); + + // A manifold mesh is closed if each edge is shared twice. A closed + // mesh is not necessarily oriented. For example, you could have a + // mesh with spherical topology. The upper hemisphere has outer-facing + // normals and the lower hemisphere has inner-facing normals. The + // discontinuity in orientation occurs on the circle shared by the + // hemispheres. + bool IsClosed() const; + + // Test whether all triangles in the mesh are oriented consistently and + // that no two triangles are coincident. The latter means that you + // cannot have both triangles and in the mesh to + // be considered oriented. + bool IsOriented() const; + + // Compute the connected components of the edge-triangle graph that the + // mesh represents. The first function returns pointers into 'this' + // object's containers, so you must consume the components before + // clearing or destroying 'this'. The second function returns triangle + // keys, which requires three times as much storage as the pointers but + // allows you to clear or destroy 'this' before consuming the components. + void GetComponents(std::vector>>& components) const; + void GetComponents(std::vector>>& components) const; + +protected: + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1); + ECreator mECreator; + EMap mEMap; + + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2); + TCreator mTCreator; + TMap mTMap; + bool mAssertOnNonmanifoldInsertion; // default: true + + // Support for computing connected components. This is a straightforward + // depth-first search of the graph but uses a preallocated stack rather + // than a recursive function that could possibly overflow the call stack. + void DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.cpp new file mode 100644 index 000000000000..105bf5a96196 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.cpp @@ -0,0 +1,310 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#include +#include +#include + +using namespace gte; + +ETNonmanifoldMesh::~ETNonmanifoldMesh() +{ +} + +ETNonmanifoldMesh::ETNonmanifoldMesh(ECreator eCreator, TCreator tCreator) + : + mECreator(eCreator ? eCreator : CreateEdge), + mTCreator(tCreator ? tCreator : CreateTriangle) +{ +} + +ETNonmanifoldMesh::ETNonmanifoldMesh(ETNonmanifoldMesh const& mesh) +{ + *this = mesh; +} + +ETNonmanifoldMesh& ETNonmanifoldMesh::operator=(ETNonmanifoldMesh const& mesh) +{ + Clear(); + + mECreator = mesh.mECreator; + mTCreator = mesh.mTCreator; + for (auto const& element : mesh.mTMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2]); + } + + return *this; +} + +ETNonmanifoldMesh::EMap const& ETNonmanifoldMesh::GetEdges() const +{ + return mEMap; +} + +ETNonmanifoldMesh::TMap const& ETNonmanifoldMesh::GetTriangles() const +{ + return mTMap; +} + +std::shared_ptr ETNonmanifoldMesh::Insert(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + if (mTMap.find(tkey) != mTMap.end()) + { + // The triangle already exists. Return a null pointer as a signal to + // the caller that the insertion failed. + return nullptr; + } + + // Create the new triangle. It will be added to mTMap at the end of the + // function so that if an assertion is triggered and the function returns + // early, the (bad) triangle will not be part of the mesh. + std::shared_ptr tri = mTCreator(v0, v1, v2); + + // Add the edges to the mesh if they do not already exist. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + EdgeKey ekey(tri->V[i0], tri->V[i1]); + std::shared_ptr edge; + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // This is the first time the edge is encountered. + edge = mECreator(tri->V[i0], tri->V[i1]); + mEMap[ekey] = edge; + } + else + { + // The edge was previously encountered and created. + edge = eiter->second; + if (!edge) + { + LogError("Unexpected condition."); + return nullptr; + } + } + + // Associate the edge with the triangle. + tri->E[i0] = edge; + + // Update the adjacent set of triangles for the edge. + edge->T.insert(tri); + } + + mTMap[tkey] = tri; + return tri; +} + +bool ETNonmanifoldMesh::Remove(int v0, int v1, int v2) +{ + TriangleKey tkey(v0, v1, v2); + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // The triangle does not exist. + return false; + } + + // Get the triangle. + std::shared_ptr tri = titer->second; + + // Remove the edges and update adjacent triangles if necessary. + for (int i = 0; i < 3; ++i) + { + // Inform the edges the triangle is being deleted. + auto edge = tri->E[i].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return false; + } + + // Remove the triangle from the edge's set of adjacent triangles. + size_t numRemoved = edge->T.erase(tri); + if (numRemoved == 0) + { + LogError("Unexpected condition."); + return false; + } + + // Remove the edge if you have the last reference to it. + if (edge->T.size() == 0) + { + EdgeKey ekey(edge->V[0], edge->V[1]); + mEMap.erase(ekey); + } + } + + // Remove the triangle from the graph. + mTMap.erase(tkey); + return true; +} + +void ETNonmanifoldMesh::Clear() +{ + mEMap.clear(); + mTMap.clear(); +} + +bool ETNonmanifoldMesh::IsManifold() const +{ + for (auto const& element : mEMap) + { + if (element.second->T.size() > 2) + { + return false; + } + } + return true; +} + +bool ETNonmanifoldMesh::IsClosed() const +{ + for (auto const& element : mEMap) + { + if (element.second->T.size() != 2) + { + return false; + } + } + return true; +} + +void ETNonmanifoldMesh::GetComponents(std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + auto tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + components.push_back(component); + } + } +} + +void ETNonmanifoldMesh::GetComponents(std::vector>>& components) const +{ + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + std::shared_ptr tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + + std::vector> keyComponent; + keyComponent.reserve(component.size()); + for (auto const& t : component) + { + keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); + } + components.push_back(keyComponent); + } + } +} + +void ETNonmanifoldMesh::DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, std::vector>& component) const +{ + // Allocate the maximum-size stack that can occur in the depth-first + // search. The stack is empty when the index top is -1. + std::vector> tStack(mTMap.size()); + int top = -1; + tStack[++top] = tInitial; + while (top >= 0) + { + std::shared_ptr tri = tStack[top]; + visited[tri] = 1; + int i; + for (i = 0; i < 3; ++i) + { + auto edge = tri->E[i].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return; + } + + bool foundUnvisited = false; + for (auto const& adjw : edge->T) + { + auto adj = adjw.lock(); + if (!adj) + { + LogError("Unexpected condition."); + return; + } + + if (visited[adj] == 0) + { + tStack[++top] = adj; + foundUnvisited = true; + break; + } + } + + if (foundUnvisited) + { + break; + } + } + if (i == 3) + { + visited[tri] = 2; + component.push_back(tri); + --top; + } + } +} + +std::shared_ptr ETNonmanifoldMesh::CreateEdge(int v0, int v1) +{ + return std::make_shared(v0, v1); +} + +std::shared_ptr ETNonmanifoldMesh::CreateTriangle(int v0, int v1, int v2) +{ + return std::make_shared(v0, v1, v2); +} + +ETNonmanifoldMesh::Edge::~Edge() +{ +} + +ETNonmanifoldMesh::Edge::Edge(int v0, int v1) +{ + V[0] = v0; + V[1] = v1; +} + +ETNonmanifoldMesh::Triangle::~Triangle() +{ +} + +ETNonmanifoldMesh::Triangle::Triangle(int v0, int v1, int v2) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.h new file mode 100644 index 000000000000..83a216b2a426 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteETNonmanifoldMesh.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + class GTE_IMPEXP ETNonmanifoldMesh + { + public: + // Edge data types. + class Edge; + typedef std::shared_ptr(*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Triangle data types. + class Triangle; + typedef std::shared_ptr(*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Edge object. + class GTE_IMPEXP Edge + { + public: + virtual ~Edge(); + Edge(int v0, int v1); + + bool operator<(Edge const& other) const + { + return EdgeKey(V[0], V[1]) < EdgeKey(other.V[0], other.V[1]); + } + + // Vertices of the edge. + int V[2]; + + // Triangles sharing the edge. + std::set, WeakPtrLT> T; + }; + + // Triangle object. + class GTE_IMPEXP Triangle + { + public: + virtual ~Triangle(); + Triangle(int v0, int v1, int v2); + + bool operator<(Triangle const& other) const + { + return TriangleKey(V[0], V[1], V[2]) < TriangleKey(other.V[0], other.V[1], other.V[2]); + } + + // Vertices listed in counterclockwise order (V[0],V[1],V[2]). + int V[3]; + + // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). + std::weak_ptr E[3]; + }; + + + // Construction and destruction. + virtual ~ETNonmanifoldMesh(); + ETNonmanifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mEMap and mTMap objects + // have dynamically allocated memory for edges and triangles. A + // shallow copy of the pointers to this memory is problematic. + // Allowing sharing, say, via std::shared_ptr, is an option but not + // really the intent of copying the mesh graph. + ETNonmanifoldMesh(ETNonmanifoldMesh const& mesh); + ETNonmanifoldMesh& operator=(ETNonmanifoldMesh const& mesh); + + // Member access. + EMap const& GetEdges() const; + TMap const& GetTriangles() const; + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2); + + // Destroy the edges and triangles to obtain an empty mesh. + virtual void Clear(); + + // A manifold mesh has the property that an edge is shared by at most + // two triangles sharing. + bool IsManifold() const; + + // A manifold mesh is closed if each edge is shared twice. A closed + // mesh is not necessarily oriented. For example, you could have a + // mesh with spherical topology. The upper hemisphere has outer-facing + // normals and the lower hemisphere has inner-facing normals. The + // discontinuity in orientation occurs on the circle shared by the + // hemispheres. + bool IsClosed() const; + + // Compute the connected components of the edge-triangle graph that + // the mesh represents. The first function returns pointers into + // 'this' object's containers, so you must consume the components + // before clearing or destroying 'this'. The second function returns + // triangle keys, which requires three times as much storage as the + // pointers but allows you to clear or destroy 'this' before consuming + // the components. + void GetComponents(std::vector>>& components) const; + void GetComponents(std::vector>>& components) const; + + protected: + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1); + ECreator mECreator; + EMap mEMap; + + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2); + TCreator mTCreator; + TMap mTMap; + + // Support for computing connected components. This is a + // straightforward depth-first search of the graph but uses a + // preallocated stack rather than a recursive function that could + // possibly overflow the call stack. + void DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEdgeKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEdgeKey.h new file mode 100644 index 000000000000..71848f3b11be --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEdgeKey.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#pragma once + +#include + +namespace gte +{ + template + class EdgeKey : public FeatureKey<2, Ordered> + { + public: + // An ordered edge has (V[0],V[1]) = (v0,v1). An unordered edge has + // (V[0],V[1]) = (min(V[0],V[1]),max(V[0],V[1])). + EdgeKey(); // creates key (-1,-1) + explicit EdgeKey(int v0, int v1); + }; + + template<> + inline + EdgeKey::EdgeKey() + { + V[0] = -1; + V[1] = -1; + } + + template<> + inline + EdgeKey::EdgeKey(int v0, int v1) + { + V[0] = v0; + V[1] = v1; + } + + template<> + inline + EdgeKey::EdgeKey() + { + V[0] = -1; + V[1] = -1; + } + + template<> + inline + EdgeKey::EdgeKey(int v0, int v1) + { + if (v0 < v1) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + } + else + { + // v1 is minimum + V[0] = v1; + V[1] = v0; + } + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEllipse3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEllipse3.h new file mode 100644 index 000000000000..356c40de979b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEllipse3.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The plane containing ellipse is Dot(N,X-C) = 0 where X is any point in the +// plane, C is the ellipse center, and N is a unit-length normal to the plane. +// Vectors A0, A1, and N form an orthonormal right-handed set. The ellipse in +// the plane is parameterized by X = C + e0*cos(t)*A0 + e1*sin(t)*A1, where A0 +// is the major axis, A1 is the minor axis, and e0 and e1 are the extents +// along those axes. The angle t is in [-pi,pi) and e0 >= e1 > 0. + +namespace gte +{ + +template +class Ellipse3 +{ +public: + // Construction and destruction. The default constructor sets center to + // (0,0,0), A0 to (1,0,0), A1 to (0,1,0), normal to (0,0,1), e0 to 1, and + // e1 to 1. + Ellipse3(); + Ellipse3(Vector3 const& inCenter, Vector3 const& inNormal, + Vector3 const inAxis[2], Vector2 const& inExtent); + + // Public member access. + Vector3 center, normal; + Vector3 axis[2]; + Vector2 extent; + +public: + // Comparisons to support sorted containers. + bool operator==(Ellipse3 const& ellipse) const; + bool operator!=(Ellipse3 const& ellipse) const; + bool operator< (Ellipse3 const& ellipse) const; + bool operator<=(Ellipse3 const& ellipse) const; + bool operator> (Ellipse3 const& ellipse) const; + bool operator>=(Ellipse3 const& ellipse) const; +}; + + +template +Ellipse3::Ellipse3() + : + center(Vector3::Zero()), + normal(Vector3::Unit(2)), + extent({ (Real)1, (Real)1 }) +{ + axis[0] = Vector3::Unit(0); + axis[1] = Vector3::Unit(1); +} + +template +Ellipse3::Ellipse3(Vector3 const& inCenter, + Vector3 const& inNormal, Vector3 const inAxis[2], + Vector2 const& inExtent) + : + center(inCenter), + normal(inNormal), + extent(inExtent) +{ + for (int i = 0; i < 2; ++i) + { + axis[i] = inAxis[i]; + } +} + +template +bool Ellipse3::operator==(Ellipse3 const& ellipse) const +{ + return center == ellipse.center + && normal == ellipse.normal + && axis[0] == ellipse.axis[0] + && axis[1] == ellipse.axis[1] + && extent == ellipse.extent; +} + +template +bool Ellipse3::operator!=(Ellipse3 const& ellipse) const +{ + return !operator==(ellipse); +} + +template +bool Ellipse3::operator<(Ellipse3 const& ellipse) const +{ + if (center < ellipse.center) + { + return true; + } + + if (center > ellipse.center) + { + return false; + } + + if (normal < ellipse.normal) + { + return true; + } + + if (normal > ellipse.normal) + { + return false; + } + + if (axis[0] < ellipse.axis[0]) + { + return true; + } + + if (axis[0] > ellipse.axis[0]) + { + return false; + } + + if (axis[1] < ellipse.axis[1]) + { + return true; + } + + if (axis[1] > ellipse.axis[1]) + { + return false; + } + + return extent < ellipse.extent; +} + +template +bool Ellipse3::operator<=(Ellipse3 const& ellipse) const +{ + return operator<(ellipse) || operator==(ellipse); +} + +template +bool Ellipse3::operator>(Ellipse3 const& ellipse) const +{ + return !operator<=(ellipse); +} + +template +bool Ellipse3::operator>=(Ellipse3 const& ellipse) const +{ + return !operator<(ellipse); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEulerAngles.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEulerAngles.h new file mode 100644 index 000000000000..6d5d9e7f0067 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteEulerAngles.h @@ -0,0 +1,82 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +// Factorization into Euler angles is not necessarily unique. Let the +// integer indices for the axes be (N0,N1,N2), which must be in the set +// {(0,1,2),(0,2,1),(1,0,2),(1,2,0),(2,0,1),(2,1,0), +// (0,1,0),(0,2,0),(1,0,1),(1,2,1),(2,0,2),(2,1,2)} +// Let the corresponding angles be (angleN0,angleN1,angleN2). If the +// result is ER_NOT_UNIQUE_SUM, then the multiple solutions occur because +// angleN2+angleN0 is constant. If the result is ER_NOT_UNIQUE_DIF, then +// the multiple solutions occur because angleN2-angleN0 is constant. In +// either type of nonuniqueness, the function returns angleN0=0. +enum EulerResult +{ + // The solution is invalid (incorrect axis indices). + ER_INVALID, + + // The solution is unique. + ER_UNIQUE, + + // The solution is not unique. A sum of angles is constant. + ER_NOT_UNIQUE_SUM, + + // The solution is not unique. A difference of angles is constant. + ER_NOT_UNIQUE_DIF +}; + +template +class EulerAngles +{ +public: + EulerAngles(); + EulerAngles(int i0, int i1, int i2, Real a0, Real a1, Real a2); + + int axis[3]; + Real angle[3]; + + // This member is set during conversions from rotation matrices, + // quaternions, or axis-angles. + EulerResult result; +}; + + +template +EulerAngles::EulerAngles() + : + result(ER_INVALID) +{ + for (int i = 0; i < 3; ++i) + { + axis[i] = 0; + angle[i] = (Real)0; + } +} + +template +EulerAngles::EulerAngles(int i0, int i1, int i2, Real a0, Real a1, + Real a2) + : + result(ER_UNIQUE) +{ + axis[0] = i0; + axis[1] = i1; + axis[2] = i2; + angle[0] = a0; + angle[1] = a1; + angle[2] = a2; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExp2Estimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExp2Estimate.h new file mode 100644 index 000000000000..c3983ad088d5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExp2Estimate.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to 2^x. The polynomial p(x) of +// degree D minimizes the quantity maximum{|2^x - p(x)| : x in [0,1]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class Exp2Estimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = Exp2Estimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the output + // with the proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = Exp2Estimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); +}; + + +template +template +inline Real Exp2Estimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real Exp2Estimate::DegreeRR(Real x) +{ + Real p = std::floor(x); + Real y = x - p; + Real poly = Degree(y); + Real result = std::ldexp(poly, (int)p); + return result; +} + +template +inline Real Exp2Estimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG1_C1; + poly = (Real)GTE_C_EXP2_DEG1_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG2_C2; + poly = (Real)GTE_C_EXP2_DEG2_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG2_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG3_C3; + poly = (Real)GTE_C_EXP2_DEG3_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG3_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG3_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG4_C4; + poly = (Real)GTE_C_EXP2_DEG4_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG5_C5; + poly = (Real)GTE_C_EXP2_DEG5_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG6_C6; + poly = (Real)GTE_C_EXP2_DEG6_C5 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C0 + poly * t; + return poly; +} + +template +inline Real Exp2Estimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_EXP2_DEG7_C7; + poly = (Real)GTE_C_EXP2_DEG7_C6 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C5 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C0 + poly * t; + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExpEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExpEstimate.h new file mode 100644 index 000000000000..53de0dfb3cae --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteExpEstimate.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Minimax polynomial approximations to 2^x. The polynomial p(x) of +// degree D minimizes the quantity maximum{|2^x - p(x)| : x in [0,1]} +// over all polynomials of degree D. The natural exponential is +// computed using exp(x) = 2^{x log(2)}, where log(2) is the natural +// logarithm of 2. + +namespace gte +{ + +template +class ExpEstimate +{ +public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ExpEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the output + // with the proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = ExpEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); +}; + + +template +template +inline Real ExpEstimate::Degree(Real x) +{ + return Exp2Estimate::Degree((Real)GTE_C_LN_2 * x); +} + +template +template +inline Real ExpEstimate::DegreeRR(Real x) +{ + return Exp2Estimate::DegreeRR((Real)GTE_C_LN_2 * x); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFIQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFIQuery.h new file mode 100644 index 000000000000..4a7233c90d06 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFIQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include + +namespace gte +{ + +// Find-intersection queries. + +template +class FIQuery +{ +public: + struct Result + { + // A FIQuery-base class B must define a B::Result struct with member + // 'bool intersect'. A FIQuery-derived class D must also derive a + // D::Result from B:Result but may have no members. The member + // 'intersect' is 'true' iff the primitives intersect. The operator() + // is non-const to allow FIQuery to store and modify private state + // that supports the query. + }; + Result operator()(Type0 const& primitive0, Type1 const& primitive1); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFeatureKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFeatureKey.h new file mode 100644 index 000000000000..41ba56178c87 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFeatureKey.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class FeatureKey +{ +protected: + // Abstract base class with V[] uninitialized. The derived classes must + // set the V[] values accordingly. + // + // An ordered feature key has V[0] = min(V[]) with (V[0],V[1],...,V[N-1]) a + // permutation of N inputs with an even number of transpositions. + // + // An unordered feature key has V[0] < V[1] < ... < V[N-1]. + // + // Note that the word 'order' is about the geometry of the feature, not + // the comparison order for any sorting. + FeatureKey(); + +public: + bool operator<(FeatureKey const& key) const; + bool operator==(FeatureKey const& key) const; + + int V[N]; +}; + + +template +FeatureKey::FeatureKey() +{ +} + +template +bool FeatureKey::operator<(FeatureKey const& key) const +{ + for (int i = N - 1; i >= 0; --i) + { + if (V[i] < key.V[i]) + { + return true; + } + + if (V[i] > key.V[i]) + { + return false; + } + } + return false; +} + +template +bool FeatureKey::operator==(FeatureKey const& key) const +{ + for (int i = 0; i < N; ++i) + { + if (V[i] != key.V[i]) + { + return false; + } + } + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrenetFrame.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrenetFrame.h new file mode 100644 index 000000000000..a2f86803fa90 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrenetFrame.h @@ -0,0 +1,159 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class FrenetFrame2 +{ +public: + // Construction. The curve must persist as long as the FrenetFrame2 + // object does. + FrenetFrame2(std::shared_ptr> const& curve); + + // The normal is perpendicular to the tangent, rotated clockwise by + // pi/2 radians. + void operator()(Real t, Vector2& position, Vector2& tangent, + Vector2& normal) const; + + Real GetCurvature(Real t) const; + +private: + std::shared_ptr> mCurve; +}; + + +template +class FrenetFrame3 +{ +public: + // Construction. The curve must persist as long as the FrenetFrame3 + // object does. + FrenetFrame3(std::shared_ptr> const& curve); + + // The binormal is Cross(tangent, normal). + void operator()(Real t, Vector3& position, Vector3& tangent, + Vector3& normal, Vector3& binormal) const; + + Real GetCurvature(Real t) const; + Real GetTorsion(Real t) const; + +private: + std::shared_ptr> mCurve; +}; + + +template +FrenetFrame2::FrenetFrame2(std::shared_ptr> const& curve) + : + mCurve(curve) +{ +} + +template +void FrenetFrame2::operator()(Real t, Vector2& position, + Vector2& tangent, Vector2& normal) const +{ + Vector2 values[4]; + mCurve->Evaluate(t, 1, values); + position = values[0]; + tangent = values[1]; + Normalize(tangent); + normal = Perp(tangent); +} + +template +Real FrenetFrame2::GetCurvature(Real t) const +{ + Vector2 values[4]; + mCurve->Evaluate(t, 2, values); + Real speedSqr = Dot(values[1], values[1]); + if (speedSqr > (Real)0) + { + Real numer = DotPerp(values[1], values[2]); + Real denom = std::pow(speedSqr, (Real)1.5); + return numer / denom; + } + else + { + // Curvature is indeterminate, just return 0. + return (Real)0; + } +} + + + +template +FrenetFrame3::FrenetFrame3(std::shared_ptr> const& curve) + : + mCurve(curve) +{ +} + +template +void FrenetFrame3::operator()(Real t, Vector3& position, + Vector3& tangent, Vector3& normal, Vector3& binormal) const +{ + Vector3 values[4]; + mCurve->Evaluate(t, 2, values); + position = values[0]; + Real VDotV = Dot(values[1], values[1]); + Real VDotA = Dot(values[1], values[2]); + normal = VDotV * values[2] - VDotA * values[1]; + Normalize(normal); + tangent = values[1]; + Normalize(tangent); + binormal = Cross(tangent, normal); +} + +template +Real FrenetFrame3::GetCurvature(Real t) const +{ + Vector3 values[4]; + mCurve->Evaluate(t, 2, values); + Real speedSqr = Dot(values[1], values[1]); + if (speedSqr > (Real)0) + { + Real numer = Length(Cross(values[1], values[2])); + Real denom = std::pow(speedSqr, (Real)1.5); + return numer / denom; + } + else + { + // Curvature is indeterminate, just return 0. + return (Real)0; + } +} + +template +Real FrenetFrame3::GetTorsion(Real t) const +{ + Vector3 values[4]; + mCurve->Evaluate(t, 3, values); + Vector3 cross = Cross(values[1], values[2]); + Real denom = Dot(cross, cross); + if (denom > (Real)0) + { + Real numer = Dot(cross, values[3]); + return numer / denom; + } + else + { + // Torsion is indeterminate, just return 0. + return (Real)0; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrustum3.h new file mode 100644 index 000000000000..e674452cc646 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteFrustum3.h @@ -0,0 +1,257 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Orthogonal frustum. Let E be the origin, D be the direction vector, U be +// the up vector, and R be the right vector. Let u > 0 and r > 0 be the +// extents in the U and R directions, respectively. Let n and f be the +// extents in the D direction with 0 < n < f. The four corners of the frustum +// in the near plane are E + n*D + s0*u*U + s1*r*R where |s0| = |s1| = 1 (four +// choices). The four corners of the frustum in the far plane are +// E + f*D + (f/n)*(s0*u*U + s1*r*R) where |s0| = |s1| = 1 (four choices). + +namespace gte +{ + +template +class Frustum3 +{ +public: + // Construction and destruction. The default constructor sets the + // following values: origin (E) to (0,0,0), dVector (D) to (0,0,1), + // uVector (U) to (0,1,0), rVector (R) to (1,0,0), dMin (n) to 1, + // dMax (f) to 2, uBound (u) to 1, and rBound (r) to 1. + Frustum3(); + Frustum3(Vector3 const& inOrigin, Vector3 const& inDVector, + Vector3 const& inUVector, Vector3 const& inRVector, + Real inDMin, Real inDMax, Real inUBound, Real inRBound); + + // The Update() function must be called whenever changes are made to DMIN, + // DMax, UBound, or RBound. The values mDRatio, mMTwoUF, and mMTwoRF are + // dependent on the changes, so call the Get*() accessors only after the + // Update() call. + void Update(); + inline Real GetDRatio() const; + inline Real GetMTwoUF() const; + inline Real GetMTwoRF() const; + + void ComputeVertices(Vector3 vertex[8]) const; + + Vector3 origin, dVector, uVector, rVector; + Real dMin, dMax, uBound, rBound; + +public: + // Comparisons to support sorted containers. + bool operator==(Frustum3 const& frustum) const; + bool operator!=(Frustum3 const& frustum) const; + bool operator< (Frustum3 const& frustum) const; + bool operator<=(Frustum3 const& frustum) const; + bool operator> (Frustum3 const& frustum) const; + bool operator>=(Frustum3 const& frustum) const; + +protected: + // Quantities derived from the constructor inputs. + Real mDRatio, mMTwoUF, mMTwoRF; +}; + + +template +Frustum3::Frustum3() + : + origin(Vector3::Zero()), + dVector(Vector3::Unit(2)), + uVector(Vector3::Unit(1)), + rVector(Vector3::Unit(0)), + dMin((Real)1), + dMax((Real)2), + uBound((Real)1), + rBound((Real)1) +{ + Update(); +} + +template +Frustum3::Frustum3(Vector3 const& inOrigin, + Vector3 const& inDVector, Vector3 const& inUVector, + Vector3 const& inRVector, Real inDMin, Real inDMax, Real inUBound, + Real inRBound) + : + origin(inOrigin), + dVector(inDVector), + uVector(inUVector), + rVector(inRVector), + dMin(inDMin), + dMax(inDMax), + uBound(inUBound), + rBound(inRBound) +{ + Update(); +} + +template +void Frustum3::Update() +{ + mDRatio = dMax / dMin; + mMTwoUF = ((Real)-2) * uBound * dMax; + mMTwoRF = ((Real)-2) * rBound * dMax; +} + +template inline +Real Frustum3::GetDRatio() const +{ + return mDRatio; +} + +template inline +Real Frustum3::GetMTwoUF() const +{ + return mMTwoUF; +} + +template inline +Real Frustum3::GetMTwoRF() const +{ + return mMTwoRF; +} + +template +void Frustum3::ComputeVertices(Vector3 vertex[8]) const +{ + Vector3 dScaled = dMin * dVector; + Vector3 uScaled = uBound * uVector; + Vector3 rScaled = rBound * rVector; + + vertex[0] = dScaled - uScaled - rScaled; + vertex[1] = dScaled - uScaled + rScaled; + vertex[2] = dScaled + uScaled + rScaled; + vertex[3] = dScaled + uScaled - rScaled; + + for (int i = 0, ip = 4; i < 4; ++i, ++ip) + { + vertex[ip] = origin + mDRatio * vertex[i]; + vertex[i] += origin; + } +} + +template +bool Frustum3::operator==(Frustum3 const& frustum) const +{ + return origin == frustum.origin + && dVector == frustum.dVector + && uVector == frustum.uVector + && rVector == frustum.rVector + && dMin == frustum.dMin + && dMax == frustum.dMax + && uBound == frustum.uBound + && rBound == frustum.rBound; +} + +template +bool Frustum3::operator!=(Frustum3 const& frustum) const +{ + return !operator==(frustum); +} + +template +bool Frustum3::operator<(Frustum3 const& frustum) const +{ + if (origin < frustum.origin) + { + return true; + } + + if (origin > frustum.origin) + { + return false; + } + + if (dVector < frustum.dVector) + { + return true; + } + + if (dVector > frustum.dVector) + { + return false; + } + + if (uVector < frustum.uVector) + { + return true; + } + + if (uVector > frustum.uVector) + { + return false; + } + + if (rVector < frustum.rVector) + { + return true; + } + + if (rVector > frustum.rVector) + { + return false; + } + + if (dMin < frustum.dMin) + { + return true; + } + + if (dMin > frustum.dMin) + { + return false; + } + + if (dMax < frustum.dMax) + { + return true; + } + + if (dMax > frustum.dMax) + { + return false; + } + + if (uBound < frustum.uBound) + { + return true; + } + + if (uBound > frustum.uBound) + { + return false; + } + + return rBound < frustum.rBound; +} + +template +bool Frustum3::operator<=(Frustum3 const& frustum) const +{ + return operator<(frustum) || operator==(frustum); +} + +template +bool Frustum3::operator>(Frustum3 const& frustum) const +{ + return !operator<=(frustum); +} + +template +bool Frustum3::operator>=(Frustum3 const& frustum) const +{ + return !operator<(frustum); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGMatrix.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGMatrix.h new file mode 100644 index 000000000000..b1ab4a21d696 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGMatrix.h @@ -0,0 +1,847 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Uncomment these to test for out-of-range indices and size mismatches. +//#define GTE_ASSERT_ON_GMATRIX_INDEX_OUT_OF_RANGE +//#define GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH + +namespace gte +{ + +template +class GMatrix +{ +public: + // The table is length zero and mNumRows and mNumCols are set to zero. + GMatrix(); + + // The table is length numRows*numCols and the elements are initialized + // to zero. + GMatrix(int numRow, int numCols); + + // For 0 <= r < numRows and 0 <= c < numCols, element (r,c) is 1 and all + // others are 0. If either of r or c is invalid, the zero matrix is + // created. This is a convenience for creating the standard Euclidean + // basis matrices; see also MakeUnit(int,int) and Unit(int,int). + GMatrix(int numRows, int numCols, int r, int c); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access for which the storage representation is transparent. The + // matrix entry in row r and column c is A(r,c). The first operator() + // returns a const reference rather than a Real value. This supports + // writing via standard file operations that require a const pointer to + // data. + void SetSize(int numRows, int numCols); + inline void GetSize(int& numRows, int& numCols) const; + inline int GetNumRows() const; + inline int GetNumCols() const; + inline int GetNumElements() const; + inline Real const& operator()(int r, int c) const; + inline Real& operator()(int r, int c); + + // Member access by rows or by columns. The input vectors must have the + // correct number of elements for the matrix size. + void SetRow(int r, GVector const& vec); + void SetCol(int c, GVector const& vec); + GVector GetRow(int r) const; + GVector GetCol(int c) const; + + // Member access by 1-dimensional index. NOTE: These accessors are + // useful for the manipulation of matrix entries when it does not + // matter whether storage is row-major or column-major. Do not use + // constructs such as M[c+NumCols*r] or M[r+NumRows*c] that expose the + // storage convention. + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(GMatrix const& mat) const; + inline bool operator!=(GMatrix const& mat) const; + inline bool operator< (GMatrix const& mat) const; + inline bool operator<=(GMatrix const& mat) const; + inline bool operator> (GMatrix const& mat) const; + inline bool operator>=(GMatrix const& mat) const; + + // Special matrices. + void MakeZero(); // All components are 0. + void MakeUnit(int r, int c); // Component (r,c) is 1, all others zero. + void MakeIdentity(); // Diagonal entries 1, others 0, even when nonsquare + static GMatrix Zero(int numRows, int numCols); + static GMatrix Unit(int numRows, int numCols, int r, int c); + static GMatrix Identity(int numRows, int numCols); + +protected: + // The matrix is stored as a 1-dimensional array. The convention of + // row-major or column-major is your choice. + int mNumRows, mNumCols; + std::vector mElements; +}; + +// Unary operations. +template +GMatrix operator+(GMatrix const& M); + +template +GMatrix operator-(GMatrix const& M); + +// Linear-algebraic operations. +template +GMatrix operator+(GMatrix const& M0, GMatrix const& M1); + +template +GMatrix operator-(GMatrix const& M0, GMatrix const& M1); + +template +GMatrix operator*(GMatrix const& M, Real scalar); + +template +GMatrix operator*(Real scalar, GMatrix const& M); + +template +GMatrix operator/(GMatrix const& M, Real scalar); + +template +GMatrix& operator+=(GMatrix& M0, GMatrix const& M1); + +template +GMatrix& operator-=(GMatrix& M0, GMatrix const& M1); + +template +GMatrix& operator*=(GMatrix& M, Real scalar); + +template +GMatrix& operator/=(GMatrix& M, Real scalar); + +// Geometric operations. +template +Real L1Norm(GMatrix const& M); + +template +Real L2Norm(GMatrix const& M); + +template +Real LInfinityNorm(GMatrix const& M); + +template +GMatrix Inverse(GMatrix const& M, + bool* reportInvertibility = nullptr); + +template +Real Determinant(GMatrix const& M); + +// M^T +template +GMatrix Transpose(GMatrix const& M); + +// M*V +template +GVector operator*(GMatrix const& M, GVector const& V); + +// V^T*M +template +GVector operator*(GVector const& V, GMatrix const& M); + +// A*B +template +GMatrix operator*(GMatrix const& A, GMatrix const& B); + +template +GMatrix MultiplyAB(GMatrix const& A, GMatrix const& B); + +// A*B^T +template +GMatrix MultiplyABT(GMatrix const& A, GMatrix const& B); + +// A^T*B +template +GMatrix MultiplyATB(GMatrix const& A, GMatrix const& B); + +// A^T*B^T +template +GMatrix MultiplyATBT(GMatrix const& A, GMatrix const& B); + +// M*D, D is square diagonal (stored as vector) +template +GMatrix MultiplyMD(GMatrix const& M, GVector const& D); + +// D*M, D is square diagonal (stored as vector) +template +GMatrix MultiplyDM(GVector const& D, GMatrix const& M); + +// U*V^T, U is N-by-1, V is M-by-1, result is N-by-M. +template +GMatrix OuterProduct(GVector const& U, GVector const& V); + +// Initialization to a diagonal matrix whose diagonal entries are the +// components of D. +template +void MakeDiagonal(GVector const& D, GMatrix& M); + + +template +GMatrix::GMatrix() + : + mNumRows(0), + mNumCols(0) +{ +} + +template +GMatrix::GMatrix(int numRows, int numCols) +{ + SetSize(numRows, numCols); + std::fill(mElements.begin(), mElements.end(), (Real)0); +} + +template +GMatrix::GMatrix(int numRows, int numCols, int r, int c) +{ + SetSize(numRows, numCols); + MakeUnit(r, c); +} + +template +void GMatrix::SetSize(int numRows, int numCols) +{ + if (numRows > 0 && numCols > 0) + { + mNumRows = numRows; + mNumCols = numCols; + mElements.resize(mNumRows * mNumCols); + } + else + { + mNumRows = 0; + mNumCols = 0; + mElements.clear(); + } +} + +template inline +void GMatrix::GetSize(int& numRows, int& numCols) const +{ + numRows = mNumRows; + numCols = mNumCols; +} + +template inline +int GMatrix::GetNumRows() const +{ + return mNumRows; +} + +template inline +int GMatrix::GetNumCols() const +{ + return mNumCols; +} + +template inline +int GMatrix::GetNumElements() const +{ + return static_cast(mElements.size()); +} + +template inline +Real const& GMatrix::operator()(int r, int c) const +{ +#if defined(GTE_ASSERT_ON_GMATRIX_INDEX_OUT_OF_RANGE) + LogAssert(0 <= r && r < GetNumRows() && 0 <= c && c < GetNumCols(), + "Invalid index."); +#endif + +#if defined(GTE_USE_ROW_MAJOR) + return mElements[c + mNumCols*r]; +#else + return mElements[r + mNumRows*c]; +#endif +} + +template inline +Real& GMatrix::operator()(int r, int c) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_INDEX_OUT_OF_RANGE) + LogAssert(0 <= r && r < GetNumRows() && 0 <= c && c < GetNumCols(), + "Invalid index."); +#endif + +#if defined(GTE_USE_ROW_MAJOR) + return mElements[c + mNumCols*r]; +#else + return mElements[r + mNumRows*c]; +#endif +} + +template +void GMatrix::SetRow(int r, GVector const& vec) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(vec.GetSize() == GetNumCols(), "Mismatched size."); +#endif + for (int c = 0; c < mNumCols; ++c) + { + operator()(r, c) = vec[c]; + } +} + +template +void GMatrix::SetCol(int c, GVector const& vec) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(vec.GetSize() == GetNumRows(), "Mismatched size."); +#endif + for (int r = 0; r < mNumRows; ++r) + { + operator()(r, c) = vec[r]; + } +} + +template +GVector GMatrix::GetRow(int r) const +{ + GVector vec(mNumCols); + for (int c = 0; c < mNumCols; ++c) + { + vec[c] = operator()(r, c); + } + return vec; +} + +template +GVector GMatrix::GetCol(int c) const +{ + GVector vec(mNumRows); + for (int r = 0; r < mNumRows; ++r) + { + vec[r] = operator()(r, c); + } + return vec; +} + +template inline +Real const& GMatrix::operator[](int i) const +{ + return mElements[i]; +} + +template inline +Real& GMatrix::operator[](int i) +{ + return mElements[i]; +} + +template inline +bool GMatrix::operator==(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements == mat.mElements; +} + +template inline +bool GMatrix::operator!=(GMatrix const& mat) const +{ + return !operator==(mat); +} + +template inline +bool GMatrix::operator<(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements < mat.mElements; +} + +template inline +bool GMatrix::operator<=(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements <= mat.mElements; +} + +template inline +bool GMatrix::operator>(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements > mat.mElements; +} + +template inline +bool GMatrix::operator>=(GMatrix const& mat) const +{ + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements >= mat.mElements; +} + +template +void GMatrix::MakeZero() +{ + std::fill(mElements.begin(), mElements.end(), (Real)0); +} + +template +void GMatrix::MakeUnit(int r, int c) +{ + MakeZero(); + if (0 <= r && r < mNumRows && 0 <= c && c < mNumCols) + { + operator()(r, c) = (Real)1; + } +} + +template +void GMatrix::MakeIdentity() +{ + MakeZero(); + int const numDiagonal = (mNumRows <= mNumCols ? mNumRows : mNumCols); + for (int i = 0; i < numDiagonal; ++i) + { + operator()(i, i) = (Real)1; + } +} + +template +GMatrix GMatrix::Zero(int numRows, int numCols) +{ + GMatrix M(numRows, numCols); + M.MakeZero(); + return M; +} + +template +GMatrix GMatrix::Unit(int numRows, int numCols, int r, int c) +{ + GMatrix M(numRows, numCols); + M.MakeUnit(r, c); + return M; +} + +template +GMatrix GMatrix::Identity(int numRows, int numCols) +{ + GMatrix M(numRows, numCols); + M.MakeIdentity(); + return M; +} + + + +template +GMatrix operator+(GMatrix const& M) +{ + return M; +} + +template +GMatrix operator-(GMatrix const& M) +{ + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int i = 0; i < M.GetNumElements(); ++i) + { + result[i] = -M[i]; + } + return result; +} + +template +GMatrix operator+(GMatrix const& M0, GMatrix const& M1) +{ + GMatrix result = M0; + return result += M1; +} + +template +GMatrix operator-(GMatrix const& M0, GMatrix const& M1) +{ + GMatrix result = M0; + return result -= M1; +} + +template +GMatrix operator*(GMatrix const& M, Real scalar) +{ + GMatrix result = M; + return result *= scalar; +} + +template +GMatrix operator*(Real scalar, GMatrix const& M) +{ + GMatrix result = M; + return result *= scalar; +} + +template +GMatrix operator/(GMatrix const& M, Real scalar) +{ + GMatrix result = M; + return result /= scalar; +} + +template +GMatrix& operator+=(GMatrix& M0, GMatrix const& M1) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(M0.GetNumRows() == M1.GetNumRows() && + M0.GetNumCols() == M1.GetNumCols(), "Mismatched size."); +#endif + + for (int i = 0; i < M0.GetNumElements(); ++i) + { + M0[i] += M1[i]; + } + return M0; +} + +template +GMatrix& operator-=(GMatrix& M0, GMatrix const& M1) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(M0.GetNumRows() == M1.GetNumRows() && + M0.GetNumCols() == M1.GetNumCols(), "Mismatched size."); +#endif + + for (int i = 0; i < M0.GetNumElements(); ++i) + { + M0[i] -= M1[i]; + } + return M0; +} + +template +GMatrix& operator*=(GMatrix& M, Real scalar) +{ + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] *= scalar; + } + return M; +} + +template +GMatrix& operator/=(GMatrix& M, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] *= invScalar; + } + } + else + { + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] = (Real)0; + } + } + return M; +} + +template +Real L1Norm(GMatrix const& M) +{ + Real sum = std::abs(M[0]); + for (int i = 1; i < M.GetNumElements(); ++i) + { + sum += std::abs(M[i]); + } + return sum; +} + +template +Real L2Norm(GMatrix const& M) +{ + Real sum = M[0] * M[0]; + for (int i = 1; i < M.GetNumElements(); ++i) + { + sum += M[i] * M[i]; + } + return std::sqrt(sum); +} + +template +Real LInfinityNorm(GMatrix const& M) +{ + Real maxAbsElement = M[0]; + for (int i = 1; i < M.GetNumElements(); ++i) + { + Real absElement = std::abs(M[i]); + if (absElement > maxAbsElement) + { + maxAbsElement = absElement; + } + } + return maxAbsElement; +} + +template +GMatrix Inverse(GMatrix const& M, bool* reportInvertibility) +{ + GMatrix invM(M.GetNumRows(), M.GetNumCols()); + if (M.GetNumRows() == M.GetNumCols()) + { + Real determinant; + bool invertible = GaussianElimination()(M.GetNumRows(), &M[0], + &invM[0], determinant, nullptr, nullptr, nullptr, 0, nullptr); + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + } + else + { +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogError("Matrix must be square."); +#endif + invM.MakeZero(); + if (reportInvertibility) + { + *reportInvertibility = false; + } + } + return invM; +} + +template +Real Determinant(GMatrix const& M) +{ + Real determinant; + if (M.GetNumRows() == M.GetNumCols()) + { + GaussianElimination()(M.GetNumRows(), &M[0], nullptr, + determinant, nullptr, nullptr, nullptr, 0, nullptr); + } + else + { +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogError("Matrix must be square."); +#endif + determinant = (Real)0; + } + return determinant; +} + +template +GMatrix Transpose(GMatrix const& M) +{ + GMatrix result(M.GetNumCols(), M.GetNumRows()); + for (int r = 0; r < M.GetNumRows(); ++r) + { + for (int c = 0; c < M.GetNumCols(); ++c) + { + result(c, r) = M(r, c); + } + } + return result; +} + +template +GVector operator*(GMatrix const& M, GVector const& V) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(V.GetSize() == M.GetNumRows(), "Mismatched size."); +#endif + GVector result(M.GetNumRows()); + for (int r = 0; r < M.GetNumRows(); ++r) + { + result[r] = (Real)0; + for (int c = 0; c < M.GetNumCols(); ++c) + { + result[r] += M(r, c) * V[c]; + } + } + return result; +} + +template +GVector operator*(GVector const& V, GMatrix const& M) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(V.GetSize() == M.GetNumCols(), "Mismatched size."); +#endif + GVector result(M.GetNumCols()); + for (int c = 0; c < M.GetNumCols(); ++c) + { + result[c] = (Real)0; + for (int r = 0; r < M.GetNumRows(); ++r) + { + result[c] += V[r] * M(r, c); + } + } + return result; +} + +template +GMatrix operator*(GMatrix const& A, GMatrix const& B) +{ + return MultiplyAB(A, B); +} + +template +GMatrix MultiplyAB(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumCols() == B.GetNumRows(), "Mismatched size."); +#endif + int const numCommon = A.GetNumCols(); + GMatrix result(A.GetNumRows(), B.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(r, i) * B(i, c); + } + } + } + return result; +} + +template +GMatrix MultiplyABT(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumCols() == B.GetNumCols(), "Mismatched size."); +#endif + int const numCommon = A.GetNumCols(); + GMatrix result(A.GetNumRows(), B.GetNumRows()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(r, i) * B(c, i); + } + } + } + return result; +} + +template +GMatrix MultiplyATB(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumRows() == B.GetNumRows(), "Mismatched size."); +#endif + int const numCommon = A.GetNumRows(); + GMatrix result(A.GetNumCols(), B.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(i, r) * B(i, c); + } + } + } + return result; +} + +template +GMatrix MultiplyATBT(GMatrix const& A, GMatrix const& B) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(A.GetNumRows() == B.GetNumCols(), "Mismatched size."); +#endif + int const numCommon = A.GetNumRows(); + GMatrix result(A.GetNumCols(), B.GetNumRows()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(i, r) * B(c, i); + } + } + } + return result; +} + +template +GMatrix MultiplyMD(GMatrix const& M, GVector const& D) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(D.GetSize() == M.GetNumCols(), "Mismatched size."); +#endif + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = M(r, c) * D[c]; + } + } + return result; +} + +template +GMatrix MultiplyDM(GVector const& D, GMatrix const& M) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(D.GetSize() == M.GetNumRows(), "Mismatched size."); +#endif + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = D[r] * M(r, c); + } + } + return result; +} + +template +GMatrix OuterProduct(GVector const& U, GVector const& V) +{ + GMatrix result(U.GetSize(), V.GetSize()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = U[r] * V[c]; + } + } + return result; +} + +template +void MakeDiagonal(GVector const& D, GMatrix& M) +{ +#if defined(GTE_ASSERT_ON_GMATRIX_SIZE_MISMATCH) + LogAssert(M.GetNumRows() == M.GetNumCols(), "Mismatched size."); +#endif + int const N = M.GetNumRows(); + for (int i = 0; i < N*N; ++i) + { + M[i] = (Real)0; + } + + for (int i = 0; i < N; ++i) + { + M(i, i) = D[i]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGVector.h new file mode 100644 index 000000000000..01f8306aa04d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGVector.h @@ -0,0 +1,646 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Uncomment this to test for size mismatches that are wrapped by +// GVector::ValidateSize. If the test is not enabled, the compiler should +// not generate any code for ValidateSize in a release build. +//#define GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH + +namespace gte +{ + +template +class GVector +{ +public: + // The tuple is length zero (uninitialized). + GVector(); + + // The tuple is length 'size' and the elements are uninitialized. + GVector(int size); + + // For 0 <= d <= size, element d is 1 and all others are zero. If d is + // invalid, the zero vector is created. This is a convenience for + // creating the standard Euclidean basis vectors; see also + // MakeUnit(int,int) and Unit(int,int). + GVector(int size, int d); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access. SetSize(int) does not initialize the tuple. The first + // operator[] returns a const reference rather than a Real value. This + // supports writing via standard file operations that require a const + // pointer to data. + void SetSize(int size); + inline int GetSize() const; + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparison (for use by STL containers). + inline bool operator==(GVector const& vec) const; + inline bool operator!=(GVector const& vec) const; + inline bool operator< (GVector const& vec) const; + inline bool operator<=(GVector const& vec) const; + inline bool operator> (GVector const& vec) const; + inline bool operator>=(GVector const& vec) const; + + // Special vectors. + void MakeZero(); // All components are 0. + void MakeUnit(int d); // Component d is 1, all others are zero. + static GVector Zero(int size); + static GVector Unit(int size, int d); + +protected: + // This data structure takes advantage of the built-in operator[], + // range checking, and visualizers in MSVS. + std::vector mTuple; +}; + +// Unary operations. +template +GVector operator+(GVector const& v); + +template +GVector operator-(GVector const& v); + +// Linear-algebraic operations. +template +GVector operator+(GVector const& v0, GVector const& v1); + +template +GVector operator-(GVector const& v0, GVector const& v1); + +template +GVector operator*(GVector const& v, Real scalar); + +template +GVector operator*(Real scalar, GVector const& v); + +template +GVector operator/(GVector const& v, Real scalar); + +template +GVector& operator+=(GVector& v0, GVector const& v1); + +template +GVector& operator-=(GVector& v0, GVector const& v1); + +template +GVector& operator*=(GVector& v, Real scalar); + +template +GVector& operator/=(GVector& v, Real scalar); + +// Geometric operations. The functions with 'robust' set to 'false' use the +// standard algorithm for normalizing a vector by computing the length as a +// square root of the squared length and dividing by it. The results can be +// infinite (or NaN) if the length is zero. When 'robust' is set to 'true', +// the algorithm is designed to avoid floating-point overflow and sets the +// normalized vector to zero when the length is zero. +template +Real Dot(GVector const& v0, GVector const& v1); + +template +Real Length(GVector const& v, bool robust = false); + +template +Real Normalize(GVector& v, bool robust = false); + +// Gram-Schmidt orthonormalization to generate orthonormal vectors from the +// linearly independent inputs. The function returns the smallest length of +// the unnormalized vectors computed during the process. If this value is +// nearly zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, 1 <= numElements <= N and v[0] +// through v[numElements-1] must be initialized. On output, the vectors +// v[0] through v[numElements-1] form an orthonormal set. +template +Real Orthonormalize(int numElements, GVector* v, bool robust = false); + +// Compute the axis-aligned bounding box of the vectors. The return value is +// 'true' iff the inputs are valid, in which case vmin and vmax have valid +// values. +template +bool ComputeExtremes(int numVectors, GVector const* v, + GVector& vmin, GVector& vmax); + +// Lift n-tuple v to homogeneous (n+1)-tuple (v,last). +template +GVector HLift(GVector const& v, Real last); + +// Project homogeneous n-tuple v = (u,v[n-1]) to (n-1)-tuple u. +template +GVector HProject(GVector const& v); + +// Lift n-tuple v = (w0,w1) to (n+1)-tuple u = (w0,u[inject],w1). By +// inference, w0 is a (inject)-tuple [nonexistent when inject=0] and w1 is a +// (n-inject)-tuple [nonexistent when inject=n]. +template +GVector Lift(GVector const& v, int inject, Real value); + +// Project n-tuple v = (w0,v[reject],w1) to (n-1)-tuple u = (w0,w1). By +// inference, w0 is a (reject)-tuple [nonexistent when reject=0] and w1 is a +// (n-1-reject)-tuple [nonexistent when reject=n-1]. +template +GVector Project(GVector const& v, int reject); + + +template +GVector::GVector() +{ + // Uninitialized. +} + +template +GVector::GVector(int size) +{ + SetSize(size); +} + +template +GVector::GVector(int size, int d) +{ + SetSize(size); + MakeUnit(d); +} + +template +void GVector::SetSize(int size) +{ +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogAssert(size >= 0, "Mismatched size."); +#endif + if (size > 0) + { + mTuple.resize(size); + } +} + +template inline +int GVector::GetSize() const +{ + return static_cast(mTuple.size()); +} + +template inline +Real const& GVector::operator[](int i) const +{ + return mTuple[i]; +} + +template inline +Real& GVector::operator[](int i) +{ + return mTuple[i]; +} + +template inline +bool GVector::operator==(GVector const& vec) const +{ + return mTuple == vec.mTuple; +} + +template inline +bool GVector::operator!=(GVector const& vec) const +{ + return mTuple != vec.mTuple; +} + +template inline +bool GVector::operator<(const GVector& vec) const +{ + return mTuple < vec.mTuple; +} + +template inline +bool GVector::operator<=(const GVector& vec) const +{ + return mTuple <= vec.mTuple; +} + +template inline +bool GVector::operator>(const GVector& vec) const +{ + return mTuple > vec.mTuple; +} + +template inline +bool GVector::operator>=(const GVector& vec) const +{ + return mTuple >= vec.mTuple; +} + +template +void GVector::MakeZero() +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); +} + +template +void GVector::MakeUnit(int d) +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + if (0 <= d && d < (int)mTuple.size()) + { + mTuple[d] = (Real)1; + } +} + +template +GVector GVector::Zero(int size) +{ + GVector v(size); + v.MakeZero(); + return v; +} + +template +GVector GVector::Unit(int size, int d) +{ + GVector v(size); + v.MakeUnit(d); + return v; +} + + + +template +GVector operator+(GVector const& v) +{ + return v; +} + +template +GVector operator-(GVector const& v) +{ + GVector result(v.GetSize()); + for (int i = 0; i < v.GetSize(); ++i) + { + result[i] = -v[i]; + } + return result; +} + +template +GVector operator+(GVector const& v0, GVector const& v1) +{ + GVector result = v0; + return result += v1; +} + +template +GVector operator-(GVector const& v0, GVector const& v1) +{ + GVector result = v0; + return result -= v1; +} + +template +GVector operator*(GVector const& v, Real scalar) +{ + GVector result = v; + return result *= scalar; +} + +template +GVector operator*(Real scalar, GVector const& v) +{ + GVector result = v; + return result *= scalar; +} + +template +GVector operator/(GVector const& v, Real scalar) +{ + GVector result = v; + return result /= scalar; +} + +template +GVector& operator+=(GVector& v0, GVector const& v1) +{ + if (v0.GetSize() == v1.GetSize()) + { + for (int i = 0; i < v0.GetSize(); ++i) + { + v0[i] += v1[i]; + } + } + else + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogError("Mismatched size."); +#endif + } + return v0; +} + +template +GVector& operator-=(GVector& v0, GVector const& v1) +{ + if (v0.GetSize() == v1.GetSize()) + { + for (int i = 0; i < v0.GetSize(); ++i) + { + v0[i] -= v1[i]; + } + } + else + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogError("Mismatched size."); +#endif + } + return v0; +} + +template +GVector& operator*=(GVector& v, Real scalar) +{ + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] *= scalar; + } + return v; +} + +template +GVector& operator/=(GVector& v, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] *= invScalar; + } + } + else + { + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return v; +} + +template +Real Dot(GVector const& v0, GVector const& v1) +{ + if (v0.GetSize() == v1.GetSize()) + { + Real dot = v0[0] * v1[0]; + for (int i = 1; i < v0.GetSize(); ++i) + { + dot += v0[i] * v1[i]; + } + return dot; + } + else + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + LogError("Mismatched size."); +#endif + return (Real)0; + } +} + +template +Real Length(GVector const& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < v.GetSize(); ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + GVector scaled = v / maxAbsComp; + length = maxAbsComp * std::sqrt(Dot(scaled, scaled)); + } + else + { + length = (Real)0; + } + return length; + } + else + { + return std::sqrt(Dot(v, v)); + } +} + +template +Real Normalize(GVector& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < v.GetSize(); ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + v /= maxAbsComp; + length = std::sqrt(Dot(v, v)); + v /= length; + length *= maxAbsComp; + } + else + { + length = (Real)0; + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return length; + } + else + { + Real length = std::sqrt(Dot(v, v)); + if (length > (Real)0) + { + v /= length; + } + else + { + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return length; + } +} + +template +Real Orthonormalize(int numInputs, GVector* v, bool robust) +{ + if (v && 1 <= numInputs && numInputs <= v[0].GetSize()) + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + for (int i = 1; i < numInputs; ++i) + { + LogAssert(v[0].GetSize() == v[i].GetSize(), "Mismatched size."); + } +#endif + Real minLength = Normalize(v[0], robust); + for (int i = 1; i < numInputs; ++i) + { + for (int j = 0; j < i; ++j) + { + Real dot = Dot(v[i], v[j]); + v[i] -= v[j] * dot; + } + Real length = Normalize(v[i], robust); + if (length < minLength) + { + minLength = length; + } + } + return minLength; + } + + LogError("Invalid input."); + return (Real)0; +} + +template +bool ComputeExtremes(int numVectors, GVector const* v, + GVector& vmin, GVector& vmax) +{ + if (v && numVectors > 0) + { +#if defined(GTE_ASSERT_ON_GVECTOR_SIZE_MISMATCH) + for (int i = 1; i < numVectors; ++i) + { + LogAssert(v[0].GetSize() == v[i].GetSize(), "Mismatched size."); + } +#endif + int const size = v[0].GetSize(); + vmin = v[0]; + vmax = vmin; + for (int j = 1; j < numVectors; ++j) + { + GVector const& vec = v[j]; + for (int i = 0; i < size; ++i) + { + if (vec[i] < vmin[i]) + { + vmin[i] = vec[i]; + } + else if (vec[i] > vmax[i]) + { + vmax[i] = vec[i]; + } + } + } + return true; + } + + LogError("Invalid input."); + return false; +} + +template +GVector HLift(GVector const& v, Real last) +{ + int const size = v.GetSize(); + GVector result(size + 1); + for (int i = 0; i < size; ++i) + { + result[i] = v[i]; + } + result[size] = last; + return result; +} + +template +GVector HProject(GVector const& v) +{ + int const size = v.GetSize(); + if (size > 1) + { + GVector result(size - 1); + for (int i = 0; i < size - 1; ++i) + { + result[i] = v[i]; + } + return result; + } + else + { + return GVector(); + } +} + +template +GVector Lift(GVector const& v, int inject, Real value) +{ + int const size = v.GetSize(); + GVector result(size + 1); + int i; + for (i = 0; i < inject; ++i) + { + result[i] = v[i]; + } + result[i] = value; + int j = i; + for (++j; i < size; ++i, ++j) + { + result[j] = v[i]; + } + return result; +} + +template +GVector Project(GVector const& v, int reject) +{ + int const size = v.GetSize(); + if (size > 1) + { + GVector result(size - 1); + for (int i = 0, j = 0; i < size - 1; ++i, ++j) + { + if (j == reject) + { + ++j; + } + result[i] = v[j]; + } + return result; + } + else + { + return GVector(); + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussNewtonMinimizer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussNewtonMinimizer.h new file mode 100644 index 000000000000..38b1f922ddcd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussNewtonMinimizer.h @@ -0,0 +1,228 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.0 (2018/07/16) + +#pragma once + +#include +#include + +// Let F(p) = (F_{0}(p), F_{1}(p), ..., F_{n-1}(p)) be a vector-valued +// function of the parameters p = (p_{0}, p_{1}, ..., p_{m-1}). The +// nonlinear least-squares problem is to minimize the real-valued error +// function E(p) = |F(p)|^2, which is the squared length of F(p). +// +// Let J = dF/dp = [dF_{r}/dp_{c}] denote the Jacobian matrix, which is the +// matrix of first-order partial derivatives of F. The matrix has n rows and +// m columns, and the indexing (r,c) refers to row r and column c. A +// first-order approximation is F(p + d) = F(p) + J(p)d, where d is an m-by-1 +// vector with small length. Consequently, an approximation to E is E(p + d) +// = |F(p + d)|^2 = |F(p) + J(p)d|^2. The goal is to choose d to minimize +// |F(p) + J(p)d|^2 and, hopefully, with E(p + d) < E(p). Choosing an initial +// p_{0}, the hope is that the algorithm generates a sequence p_{i} for which +// E(p_{i+1}) < E(p_{i}) and, in the limit, E(p_{j}) approaches the global +// minimum of E. The algorithm is referred to as Gauss-Newton iteration. If +// E does not decrease for a step of the algorithm, one can modify the +// algorithm to the Levenberg-Marquardt iteration. See +// GteLevenbergMarquardtMinimizer.h for a description and an implementation. +// +// For a single Gauss-Newton iteration, we need to choose d to minimize +// |F(p) + J(p)d|^2 where p is fixed. This is a linear least squares problem +// which can be formulated using the normal equations +// (J^T(p)*J(p))*d = -J^T(p)*F(p). The matrix J^T*J is positive semidefinite. +// If it is invertible, then d = -(J^T(p)*J(p))^{-1}*F(p). If it is not +// invertible, some other algorithm must be used to choose d; one option is +// to use gradient descent for the step. A Cholesky decomposition can be +// used to solve the linear system. +// +// Although an implementation can allow the caller to pass an array of +// functions F_{i}(p) and an array of derivatives dF_{r}/dp_{c}, some +// applications might involve a very large n that precludes storing all +// the computed Jacobian matrix entries because of excessive memory +// requirements. In such an application, it is better to compute instead +// the entries of the m-by-m matrix J^T*J and the m-by-1 vector J^T*F. +// Typically, m is small, so the memory requirements are not excessive. Also, +// there might be additional structure to F for which the caller can take +// advantage; for example, 3-tuples of components of F(p) might correspond to +// vectors that can be manipulated using an already existing mathematics +// library. The implementation here supports both approaches. + +namespace gte +{ + template + class GaussNewtonMinimizer + { + public: + // Convenient types for the domain vectors, the range vectors, the + // function F and the Jacobian J. + typedef GVector DVector; // NumPDimensions elements + typedef GVector RVector; // NumFDimensions elements + typedef GMatrix JMatrix; // NumFDimensions-by-NumPDimensions elements + typedef GMatrix JTJMatrix; // NumPDimensions-by-NumPDimensions + typedef GVector JTFVector; // NumPDimensions elements + typedef std::function FFunction; + typedef std::function JFunction; + typedef std::function JPlusFunction; + + // NOTE: The C++ compiler for Microsoft Visual Studio 12.0.21005.1 REL + // (MSVS 2013) appears to have a bug regarding passing std::function + // arguments. The unit-test code for the first GaussNewtonMinimizer + // constructor was passed std::function objects of type FFunction and + // JFunction. The compiler claimed that the constructor call is + // ambiguous because the JFunction portion of the first constructor's + // signature matches the JPlusFunction portion of the second + // constructor's signature, which is clearly not correct. When using + // MSVS 2013, you need to explicitly typecast using + // GaussNewtonMinimizer minimizer( + // numPDimensions, numFDimensions, + // (GaussNewtonMinimizer::FFunction const&) myFFunction, + // (GaussNewtonMinimizer::JFunction const&) myJFunction); + // The same typecasting is also required for the second constructor. + + // Create the minimizer that computes F(p) and J(p) directly. + GaussNewtonMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JFunction const& inJFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJFunction(inJFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(true) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Create the minimizer that computes J^T(p)*J(p) and -J(p)*F(p). + GaussNewtonMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JPlusFunction const& inJPlusFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJPlusFunction(inJPlusFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(false) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Disallow copy, assignment and move semantics. + GaussNewtonMinimizer(GaussNewtonMinimizer const&) = delete; + GaussNewtonMinimizer& operator=(GaussNewtonMinimizer const&) = delete; + GaussNewtonMinimizer(GaussNewtonMinimizer&&) = delete; + GaussNewtonMinimizer& operator=(GaussNewtonMinimizer&&) = delete; + + inline int GetNumPDimensions() const { return mNumPDimensions; } + inline int GetNumFDimensions() const { return mNumFDimensions; } + + struct Result + { + DVector minLocation; + Real minError; + Real minErrorDifference; + Real minUpdateLength; + size_t numIterations; + bool converged; + }; + + Result operator()(DVector const& p0, size_t maxIterations, + Real updateLengthTolerance, Real errorDifferenceTolerance) + { + Result result; + result.minLocation = p0; + result.minError = std::numeric_limits::max(); + result.minErrorDifference = std::numeric_limits::max(); + result.minUpdateLength = (Real)0; + result.numIterations = 0; + result.converged = false; + + // As a simple precaution, ensure the tolerances are nonnegative. + updateLengthTolerance = std::max(updateLengthTolerance, (Real)0); + errorDifferenceTolerance = std::max(errorDifferenceTolerance, (Real)0); + + // Compute the initial error. + mFFunction(p0, mF); + result.minError = Dot(mF, mF); + + // Do the Gauss-Newton iterations. + auto pCurrent = p0; + for (result.numIterations = 1; result.numIterations <= maxIterations; ++result.numIterations) + { + ComputeLinearSystemInputs(pCurrent); + if (!mDecomposer.Factor(mJTJ)) + { + // TODO: The matrix mJTJ is positive semi-definite, so the + // failure can occur when mJTJ has a zero eigenvalue in + // which case mJTJ is not invertible. Generate an iterate + // anyway, perhaps using gradient descent? + return result; + } + mDecomposer.SolveLower(mJTJ, mNegJTF); + mDecomposer.SolveUpper(mJTJ, mNegJTF); + + auto pNext = pCurrent + mNegJTF; + mFFunction(pNext, mF); + Real error = Dot(mF, mF); + if (error < result.minError) + { + result.minErrorDifference = result.minError - error; + result.minUpdateLength = Length(mNegJTF); + result.minLocation = pNext; + result.minError = error; + if (result.minErrorDifference <= errorDifferenceTolerance + || result.minUpdateLength <= updateLengthTolerance) + { + result.converged = true; + return result; + } + } + + pCurrent = pNext; + } + + return result; + } + + private: + void ComputeLinearSystemInputs(DVector const& pCurrent) + { + if (mUseJFunction) + { + mJFunction(pCurrent, mJ); + mJTJ = MultiplyATB(mJ, mJ); + mNegJTF = -(mF * mJ); + } + else + { + mJPlusFunction(pCurrent, mJTJ, mNegJTF); + } + } + + int mNumPDimensions, mNumFDimensions; + FFunction mFFunction; + JFunction mJFunction; + JPlusFunction mJPlusFunction; + + // Storage for J^T(p)*J(p) and -J^T(p)*F(p) during the iterations. + RVector mF; + JMatrix mJ; + JTJMatrix mJTJ; + JTFVector mNegJTF; + + CholeskyDecomposition mDecomposer; + + bool mUseJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussianElimination.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussianElimination.h new file mode 100644 index 000000000000..2e8306ec466f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGaussianElimination.h @@ -0,0 +1,290 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The input matrix M must be NxN. The storage convention for element lookup +// is determined by GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR, whichever is +// active. If you want the inverse of M, pass a nonnull pointer inverseM; +// this matrix must also be NxN and use the same storage convention as M. If +// you do not want the inverse of M, pass a nullptr for inverseM. If you want +// to solve M*X = B for X, where X and B are Nx1, pass nonnull pointers for B +// and X. If you want to solve M*Y = C for Y, where X and C are NxK, pass +// nonnull pointers for C and Y and pass K to numCols. In all cases, pass +// N to numRows. + +namespace gte +{ + +template +class GaussianElimination +{ +public: + bool operator()(int numRows, + Real const* M, Real* inverseM, Real& determinant, + Real const* B, Real* X, + Real const* C, int numCols, Real* Y) const; + +private: + // Support for copying source to target or to set target to zero. If + // source is nullptr, then target is set to zero; otherwise source is + // copied to target. This function hides the type traits used to + // determine whether Real is native floating-point or otherwise (such + // as BSNumber or BSRational). + void Set(int numElements, Real const* source, Real* target) const; +}; + + +template +bool GaussianElimination::operator()(int numRows, Real const* M, + Real* inverseM, Real& determinant, Real const* B, Real* X, Real const* C, + int numCols, Real* Y) const +{ + if (numRows <= 0 || !M + || ((B != nullptr) != (X != nullptr)) + || ((C != nullptr) != (Y != nullptr)) + || (C != nullptr && numCols < 1)) + { + LogError("Invalid input."); + return false; + } + + int numElements = numRows * numRows; + bool wantInverse = (inverseM != nullptr); + std::vector localInverseM; + if (!wantInverse) + { + localInverseM.resize(numElements); + inverseM = localInverseM.data(); + } + Set(numElements, M, inverseM); + + if (B) + { + Set(numRows, B, X); + } + + if (C) + { + Set(numRows * numCols, C, Y); + } + +#if defined(GTE_USE_ROW_MAJOR) + LexicoArray2 matInvM(numRows, numRows, inverseM); + LexicoArray2 matY(numRows, numCols, Y); +#else + LexicoArray2 matInvM(numRows, numRows, inverseM); + LexicoArray2 matY(numRows, numCols, Y); +#endif + + std::vector colIndex(numRows), rowIndex(numRows), pivoted(numRows); + std::fill(pivoted.begin(), pivoted.end(), 0); + + Real const zero = (Real)0; + Real const one = (Real)1; + bool odd = false; + determinant = one; + + // Elimination by full pivoting. + int i1, i2, row = 0, col = 0; + for (int i0 = 0; i0 < numRows; ++i0) + { + // Search matrix (excluding pivoted rows) for maximum absolute entry. + Real maxValue = zero; + for (i1 = 0; i1 < numRows; ++i1) + { + if (!pivoted[i1]) + { + for (i2 = 0; i2 < numRows; ++i2) + { + if (!pivoted[i2]) + { + Real absValue = std::abs(matInvM(i1, i2)); + if (absValue > maxValue) + { + maxValue = absValue; + row = i1; + col = i2; + } + } + } + } + } + + if (maxValue == zero) + { + // The matrix is not invertible. + if (wantInverse) + { + Set(numElements, nullptr, inverseM); + } + determinant = zero; + + if (B) + { + Set(numRows, nullptr, X); + } + + if (C) + { + Set(numRows * numCols, nullptr, Y); + } + return false; + } + + pivoted[col] = true; + + // Swap rows so that the pivot entry is in row 'col'. + if (row != col) + { + odd = !odd; + for (int i = 0; i < numRows; ++i) + { + std::swap(matInvM(row, i), matInvM(col, i)); + } + + if (B) + { + std::swap(X[row], X[col]); + } + + if (C) + { + for (int i = 0; i < numCols; ++i) + { + std::swap(matY(row, i), matY(col, i)); + } + } + } + + // Keep track of the permutations of the rows. + rowIndex[i0] = row; + colIndex[i0] = col; + + // Scale the row so that the pivot entry is 1. + Real diagonal = matInvM(col, col); + determinant *= diagonal; + Real inv = one / diagonal; + matInvM(col, col) = one; + for (i2 = 0; i2 < numRows; ++i2) + { + matInvM(col, i2) *= inv; + } + + if (B) + { + X[col] *= inv; + } + + if (C) + { + for (i2 = 0; i2 < numCols; ++i2) + { + matY(col, i2) *= inv; + } + } + + // Zero out the pivot column locations in the other rows. + for (i1 = 0; i1 < numRows; ++i1) + { + if (i1 != col) + { + Real save = matInvM(i1, col); + matInvM(i1, col) = zero; + for (i2 = 0; i2 < numRows; ++i2) + { + matInvM(i1, i2) -= matInvM(col, i2) * save; + } + + if (B) + { + X[i1] -= X[col] * save; + } + + if (C) + { + for (i2 = 0; i2 < numCols; ++i2) + { + matY(i1, i2) -= matY(col, i2) * save; + } + } + } + } + } + + if (wantInverse) + { + // Reorder rows to undo any permutations in Gaussian elimination. + for (i1 = numRows - 1; i1 >= 0; --i1) + { + if (rowIndex[i1] != colIndex[i1]) + { + for (i2 = 0; i2 < numRows; ++i2) + { + std::swap(matInvM(i2, rowIndex[i1]), + matInvM(i2, colIndex[i1])); + } + } + } + } + + if (odd) + { + determinant = -determinant; + } + + return true; +} + +template +void GaussianElimination::Set(int numElements, Real const* source, + Real* target) const +{ + if (std::is_floating_point() == std::true_type()) + { + // Fast set/copy for native floating-point. + size_t numBytes = numElements * sizeof(Real); + if (source) + { + Memcpy(target, source, numBytes); + } + else + { + memset(target, 0, numBytes); + } + } + else + { + // The inputs are not std containers, so ensure assignment works + // correctly. + if (source) + { + for (int i = 0; i < numElements; ++i) + { + target[i] = source[i]; + } + } + else + { + Real const zero = (Real)0; + for (int i = 0; i < numElements; ++i) + { + target[i] = zero; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.cpp new file mode 100644 index 000000000000..2d2553bdb477 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.cpp @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include + +using namespace gte; + +std::string const GenerateMeshUVBase::msGLSLSource = +"uniform Bounds\n" +"{\n" +" ivec2 bound;\n" +" int numBoundaryEdges;\n" +" int numInputs;\n" +"};\n" +"\n" +"struct VertexGraphData\n" +"{\n" +" int adjacent;\n" +" Real weight;\n" +"};\n" +"\n" +"buffer vertexGraph { ivec3 data[]; } vertexGraphSB;\n" +"buffer vertexGraphData { VertexGraphData data[]; } vertexGraphDataSB;\n" +"buffer orderedVertices { int data[]; } orderedVerticesSB;\n" +"buffer inTCoords { Real2 data[]; } inTCoordsSB;\n" +"buffer outTCoords { Real2 data[]; } outTCoordsSB;\n" +"\n" +"layout (local_size_x = NUM_X_THREADS, local_size_y = NUM_Y_THREADS, local_size_z = 1) in;\n" +"void main()\n" +"{\n" +" ivec2 t = ivec2(gl_GlobalInvocationID.xy);\n" +" int index = t.x + bound.x * t.y;\n" +" if (step(index, numInputs-1) == 1)\n" +" {\n" +" int v = orderedVerticesSB.data[numBoundaryEdges + index];\n" +" ivec2 range = vertexGraphSB.data[v].yz;\n" +" Real2 tcoord = Real2(0, 0);\n" +" Real weightSum = 0;\n" +" for (int j = 0; j < range.y; ++j)\n" +" {\n" +" VertexGraphData vgd = vertexGraphDataSB.data[range.x + j];\n" +" weightSum += vgd.weight;\n" +" tcoord += vgd.weight * inTCoordsSB.data[vgd.adjacent];\n" +" }\n" +" tcoord /= weightSum;\n" +" outTCoordsSB.data[v] = tcoord;\n" +" }\n" +"}\n"; + +std::string const GenerateMeshUVBase::msHLSLSource = +"cbuffer Bounds\n" +"{\n" +" int2 bound;\n" +" int numBoundaryEdges;\n" +" int numInputs;\n" +"};\n" +"\n" +"struct VertexGraphData\n" +"{\n" +" int adjacent;\n" +" Real weight;\n" +"};\n" +"\n" +"StructuredBuffer vertexGraph;\n" +"StructuredBuffer vertexGraphData;\n" +"StructuredBuffer orderedVertices;\n" +"StructuredBuffer inTCoords;\n" +"RWStructuredBuffer outTCoords;\n" +"\n" +"[numthreads(NUM_X_THREADS, NUM_Y_THREADS, 1)]\n" +"void CSMain(int2 t : SV_DispatchThreadID)\n" +"{\n" +" int index = t.x + bound.x * t.y;\n" +" if (step(index, numInputs-1))\n" +" {\n" +" int v = orderedVertices[numBoundaryEdges + index];\n" +" int2 range = vertexGraph[v].yz;\n" +" Real2 tcoord = Real2(0, 0);\n" +" Real weightSum = 0;\n" +" for (int j = 0; j < range.y; ++j)\n" +" {\n" +" VertexGraphData data = vertexGraphData[range.x + j];\n" +" weightSum += data.weight;\n" +" tcoord += data.weight * inTCoords[data.adjacent];\n" +" }\n" +" tcoord /= weightSum;\n" +" outTCoords[v] = tcoord;\n" +" }\n" +"}\n"; + +std::string const* GenerateMeshUVBase::msSource[] = +{ + &msGLSLSource, + &msHLSLSource +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.h new file mode 100644 index 000000000000..3c9b15024358 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteGenerateMeshUV.h @@ -0,0 +1,851 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +// This class is an implementation of the barycentric mapping algorithm +// described in Section 5.3 of the book +// Polygon Mesh Processing +// Mario Botsch, Leif Kobbelt, Mark Pauly, Pierre Alliez, Bruno Levy +// AK Peters, Ltd., Natick MA, 2010 +// It uses the mean value weights described in Section 5.3.1 to allow the mesh +// geometry to influence the texture coordinate generation, and it uses +// Gauss-Seidel iteration to solve the sparse linear system. The authors' +// advice is that the Gauss-Seidel approach works well for at most about 5000 +// vertices, presumably the convergence rate degrading as the number of +// vertices increases. +// +// The algorithm implemented here has an additional preprocessing step that +// computes a topological distance transform of the vertices. The boundary +// texture coordinates are propagated inward by updating the vertices in +// topological distance order, leading to fast convergence for large numbers +// of vertices. + +namespace gte +{ + +class GenerateMeshUVBase +{ +protected: + static std::string const msGLSLSource; + static std::string const msHLSLSource; + static std::string const* msSource[]; +}; + +template +class GenerateMeshUV : public GenerateMeshUVBase +{ +public: + class UVComputeModel : public ComputeModel + { + public: + virtual ~UVComputeModel(); + + UVComputeModel(); + + UVComputeModel(unsigned int inNumThreads, + std::function const* inProgress); + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + UVComputeModel(unsigned int inNumThreads, + std::shared_ptr const& inEngine, + std::shared_ptr const& inFactory, + std::function const* inProgress); +#endif + + std::function const* progress; + }; + + // Construction. + GenerateMeshUV(std::shared_ptr const& cmodel); + + // The incoming mesh must be edge-triangle manifold and have rectangle + // topology (simply connected, closed polyline boundary). The arrays + // 'vertices' and 'tcoords' must both have 'numVertices' elements. Set + // 'useSquareTopology' to true for the generated coordinates to live + // in the uv-square [0,1]^2. Set it to false for the generated + // coordinates to live in a convex polygon that inscribes the uv-disk + // of center (1/2,1/2) and radius 1/2. + void operator()(unsigned int numIterations, bool useSquareTopology, + int numVertices, Vector3 const* vertices, int numIndices, + int const* indices, Vector2* tcoords); + +private: + void TopologicalVertexDistanceTransform(); + void AssignBoundaryTextureCoordinatesSquare(); + void AssignBoundaryTextureCoordinatesDisk(); + void ComputeMeanValueWeights(); + void SolveSystem(unsigned int numIterations); + void SolveSystemCPUSingle(unsigned int numIterations); + void SolveSystemCPUMultiple(unsigned int numIterations); + + // Convenience members that store the input parameters to operator(). + int mNumVertices; + Vector3 const* mVertices; + Vector2* mTCoords; + + // The edge-triangle manifold graph, where each edge is shared by at most + // two triangles. + ETManifoldMesh mGraph; + + // The mVertexInfo array stores -1 for the interior vertices. For a + // boundary edge that is counterclockwise, mVertexInfo[v0] = v1, + // which gives us an orded boundary polyline. + enum { INTERIOR_VERTEX = -1 }; + std::vector mVertexInfo; + int mNumBoundaryEdges, mBoundaryStart; + typedef ETManifoldMesh::Edge Edge; + std::set> mInteriorEdges; + + // The vertex graph required to set up a sparse linear system of equations + // to determine the texture coordinates. + struct Vertex + { + // The topological distance from the boundary of the mesh. + int distance; + + // The value range[0] is the index into mVertexGraphData for the first + // adjacent vertex. The value range[1] is the number of adjacent + // vertices. + std::array range; + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) && defined(GTE_DEV_OPENGL) + int _padding; // GLSL will map the ivec3 Vertex to ivec4 in an array +#endif + }; + std::vector mVertexGraph; + std::vector> mVertexGraphData; + + // The vertices are listed in the order determined by a topological distance + // transform. Boundary vertices have 'distance' 0. Any vertices that are + // not boundary vertices but are edge-adjacent to boundary vertices have + // 'distance' 1. Neighbors of those have distance '2', and so on. The + // mOrderedVertices array stores distance-0 vertices first, distance-1 + // vertices second, and so on. + std::vector mOrderedVertices; + + std::shared_ptr mCModel; + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + // Support for solving the sparse linear system on the GPU. + void SolveSystemGPU(unsigned int numIterations); + std::shared_ptr mSolveSystem; + std::shared_ptr mBoundBuffer; + std::shared_ptr mVGBuffer; + std::shared_ptr mVGDBuffer; + std::shared_ptr mOVBuffer; + std::shared_ptr mTCoordsBuffer[2]; +#endif +}; + + +template +GenerateMeshUV::UVComputeModel::~UVComputeModel() +{ +} + +template +GenerateMeshUV::UVComputeModel::UVComputeModel() + : + progress(nullptr) +{ +} + +template +GenerateMeshUV::UVComputeModel::UVComputeModel(unsigned int inNumThreads, + std::function const* inProgress) + : + ComputeModel(inNumThreads), + progress(inProgress) +{ +} + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + +template +GenerateMeshUV::UVComputeModel::UVComputeModel(unsigned int inNumThreads, + std::shared_ptr const& inEngine, + std::shared_ptr const& inFactory, + std::function const* inProgress) + : + ComputeModel(inNumThreads, inEngine, inFactory), + progress(inProgress) +{ +} + +#endif + +template +GenerateMeshUV::GenerateMeshUV(std::shared_ptr const& cmodel) + : + mNumVertices(0), + mVertices(nullptr), + mTCoords(nullptr), + mNumBoundaryEdges(0), + mBoundaryStart(0), + mCModel(cmodel) +{ +} + +template +void GenerateMeshUV::operator()(unsigned int numIterations, + bool useSquareTopology, int numVertices, Vector3 const* vertices, + int numIndices, int const* indices, Vector2* tcoords) +{ + // Ensure that numIterations is even, which avoids having a memory + // copy from the temporary ping-pong buffer to 'tcoords'. + if (numIterations & 1) + { + ++numIterations; + } + + mNumVertices = numVertices; + mVertices = vertices; + mTCoords = tcoords; + + // The linear system solver has a first pass to initialize the texture + // coordinates to ensure the Gauss-Seidel iteration converges rapidly. + // This requires the texture coordinates all start as (-1,-1). + for (int i = 0; i < numVertices; ++i) + { + mTCoords[i][0] = (Real)-1; + mTCoords[i][1] = (Real)-1; + } + + // Create the manifold mesh data structure. + mGraph.Clear(); + int const numTriangles = numIndices / 3; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + mGraph.Insert(v0, v1, v2); + } + + TopologicalVertexDistanceTransform(); + + if (useSquareTopology) + { + AssignBoundaryTextureCoordinatesSquare(); + } + else + { + AssignBoundaryTextureCoordinatesDisk(); + } + + ComputeMeanValueWeights(); + SolveSystem(numIterations); +} + +template +void GenerateMeshUV::TopologicalVertexDistanceTransform() +{ + // Initialize the graph information. + mVertexInfo.resize(mNumVertices); + std::fill(mVertexInfo.begin(), mVertexInfo.end(), INTERIOR_VERTEX); + mVertexGraph.resize(mNumVertices); + mVertexGraphData.resize(2 * mGraph.GetEdges().size()); + std::pair initialData = std::make_pair(-1, (Real)-1); + std::fill(mVertexGraphData.begin(), mVertexGraphData.end(), initialData); + mOrderedVertices.resize(mNumVertices); + mInteriorEdges.clear(); + mNumBoundaryEdges = 0; + mBoundaryStart = std::numeric_limits::max(); + + // Count the number of adjacent vertices for each vertex. For data sets + // with a large number of vertices, this is a preprocessing step to avoid + // a dynamic data structure that has a large number of std:map objects + // that take a very long time to destroy when a debugger is attached to + // the executable. Instead, we allocate a single array that stores all + // the adjacency information. It is also necessary to bundle the data + // this way for a GPU version of the algorithm. + std::vector numAdjacencies(mNumVertices); + std::fill(numAdjacencies.begin(), numAdjacencies.end(), 0); + + for (auto const& element : mGraph.GetEdges()) + { + ++numAdjacencies[element.first.V[0]]; + ++numAdjacencies[element.first.V[1]]; + + if (element.second->T[1].lock()) + { + // This is an interior edge. + mInteriorEdges.insert(element.second); + } + else + { + // This is a boundary edge. Determine the ordering of the + // vertex indices to make the edge counterclockwise. + ++mNumBoundaryEdges; + int v0 = element.second->V[0], v1 = element.second->V[1]; + auto tri = element.second->T[0].lock(); + int i; + for (i = 0; i < 3; ++i) + { + int v2 = tri->V[i]; + if (v2 != v0 && v2 != v1) + { + // The vertex is opposite the boundary edge. + v0 = tri->V[(i + 1) % 3]; + v1 = tri->V[(i + 2) % 3]; + mVertexInfo[v0] = v1; + mBoundaryStart = std::min(mBoundaryStart, v0); + break; + } + } + } + } + + // Set the range data for each vertex. + for (int vIndex = 0, aIndex = 0; vIndex < mNumVertices; ++vIndex) + { + int numAdjacent = numAdjacencies[vIndex]; + mVertexGraph[vIndex].range = { { aIndex, numAdjacent } }; + aIndex += numAdjacent; + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) && defined(GTE_DEV_OPENGL) + // Initialize the padding, even though it is unused. + mVertexGraph[vIndex]._padding = 0; +#endif + } + + // Compute a topological distance transform of the vertices. + std::set currFront; + for (auto const& element : mGraph.GetEdges()) + { + int v0 = element.second->V[0], v1 = element.second->V[1]; + for (int i = 0; i < 2; ++i) + { + if (mVertexInfo[v0] == INTERIOR_VERTEX) + { + mVertexGraph[v0].distance = -1; + } + else + { + mVertexGraph[v0].distance = 0; + currFront.insert(v0); + } + + // Insert v1 into the first available slot of the adjacency array. + std::array range = mVertexGraph[v0].range; + for (int j = 0; j < range[1]; ++j) + { + std::pair& data = mVertexGraphData[range[0] + j]; + if (data.second == (Real)-1) + { + data.first = v1; + data.second = (Real)0; + break; + } + } + + std::swap(v0, v1); + } + } + + // Use a breadth-first search to propagate the distance information. + int nextDistance = 1; + size_t numFrontVertices = currFront.size(); + std::copy(currFront.begin(), currFront.end(), mOrderedVertices.begin()); + while (currFront.size() > 0) + { + std::set nextFront; + for (auto v : currFront) + { + std::array range = mVertexGraph[v].range; + auto* current = &mVertexGraphData[range[0]]; + for (int j = 0; j < range[1]; ++j, ++current) + { + int a = current->first; + if (mVertexGraph[a].distance == -1) + { + mVertexGraph[a].distance = nextDistance; + nextFront.insert(a); + } + } + } + std::copy(nextFront.begin(), nextFront.end(), mOrderedVertices.begin() + numFrontVertices); + numFrontVertices += nextFront.size(); + currFront = std::move(nextFront); + ++nextDistance; + } +} + +template +void GenerateMeshUV::AssignBoundaryTextureCoordinatesSquare() +{ + // Map the boundary of the mesh to the unit square [0,1]^2. The selection + // of square vertices is such that the relative distances between boundary + // vertices and the relative distances between polygon vertices is + // preserved, except that the four corners of the square are required to + // have boundary points mapped to them. The first boundary point has an + // implied distance of zero. The value distance[i] is the length of the + // boundary polyline from vertex 0 to vertex i+1. + std::vector distance(mNumBoundaryEdges); + Real total = (Real)0; + int v0 = mBoundaryStart, v1, i; + for (i = 0; i < mNumBoundaryEdges; ++i) + { + v1 = mVertexInfo[v0]; + total += Length(mVertices[v1] - mVertices[v0]); + distance[i] = total; + v0 = v1; + } + + Real invTotal = ((Real)1) / total; + for (auto& d : distance) + { + d *= invTotal; + } + + auto begin = distance.begin(), end = distance.end(); + int endYMin = (int)(std::lower_bound(begin, end, (Real)0.25) - begin); + int endXMax = (int)(std::lower_bound(begin, end, (Real)0.50) - begin); + int endYMax = (int)(std::lower_bound(begin, end, (Real)0.75) - begin); + int endXMin = (int)distance.size() - 1; + + // The first polygon vertex is (0,0). The remaining vertices are chosen + // counterclockwise around the square. + v0 = mBoundaryStart; + mTCoords[v0][0] = (Real)0; + mTCoords[v0][1] = (Real)0; + for (i = 0; i < endYMin; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = distance[i] * (Real)4; + mTCoords[v1][1] = (Real)0; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = (Real)0; + v0 = v1; + for (++i; i < endXMax; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = distance[i] * (Real)4 - (Real)1; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = (Real)1; + v0 = v1; + for (++i; i < endYMax; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)3 - distance[i] * (Real)4; + mTCoords[v1][1] = (Real)1; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)0; + mTCoords[v1][1] = (Real)1; + v0 = v1; + for (++i; i < endXMin; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)0; + mTCoords[v1][1] = (Real)4 - distance[i] * (Real)4; + v0 = v1; + } +} + +template +void GenerateMeshUV::AssignBoundaryTextureCoordinatesDisk() +{ + // Map the boundary of the mesh to a convex polygon. The selection of + // convex polygon vertices is such that the relative distances between + // boundary vertices and the relative distances between polygon vertices + // is preserved. The first boundary point has an implied distance of + // zero. The value distance[i] is the length of the boundary polyline + // from vertex 0 to vertex i+1. + std::vector distance(mNumBoundaryEdges); + Real total = (Real)0; + int v0 = mBoundaryStart; + for (int i = 0; i < mNumBoundaryEdges; ++i) + { + int v1 = mVertexInfo[v0]; + total += Length(mVertices[v1] - mVertices[v0]); + distance[i] = total; + v0 = v1; + } + + // The convex polygon lives in [0,1]^2 and inscribes a circle with center + // (1/2,1/2) and radius 1/2. The polygon center is not necessarily the + // circle center! This is the case when a boundary edge has length larger + // than half the total length of the boundary polyline; we do not expect + // such data for our meshes. The first polygon vertex is (1/2,0). The + // remaining vertices are chosen counterclockwise around the polygon. + Real multiplier = ((Real)GTE_C_TWO_PI) / total; + v0 = mBoundaryStart; + mTCoords[v0][0] = (Real)1; + mTCoords[v0][1] = (Real)0.5; + for (int i = 1; i < mNumBoundaryEdges; ++i) + { + int v1 = mVertexInfo[v0]; + Real angle = multiplier * distance[i - 1]; + mTCoords[v1][0] = (std::cos(angle) + (Real)1) * (Real)0.5; + mTCoords[v1][1] = (std::sin(angle) + (Real)1) * (Real)0.5; + v0 = v1; + } +} + +template +void GenerateMeshUV::ComputeMeanValueWeights() +{ + for (auto const& edge : mInteriorEdges) + { + int v0 = edge->V[0], v1 = edge->V[1]; + for (int i = 0; i < 2; ++i) + { + // Compute the direction from X0 to X1 and compute the length + // of the edge (X0,X1). + Vector3 X0 = mVertices[v0]; + Vector3 X1 = mVertices[v1]; + Vector3 X1mX0 = X1 - X0; + Real x1mx0length = Normalize(X1mX0); + Real weight; + if (x1mx0length >(Real)0) + { + // Compute the weight for X0 associated with X1. + weight = (Real)0; + for (int j = 0; j < 2; ++j) + { + // Find the vertex of triangle T[j] opposite edge . + auto tri = edge->T[j].lock(); + int k; + for (k = 0; k < 3; ++k) + { + int v2 = tri->V[k]; + if (v2 != v0 && v2 != v1) + { + Vector3 X2 = mVertices[v2]; + Vector3 X2mX0 = X2 - X0; + Real x2mx0Length = Normalize(X2mX0); + if (x2mx0Length >(Real)0) + { + Real dot = Dot(X2mX0, X1mX0); + Real cs = std::min(std::max(dot, (Real)-1), (Real)1); + Real angle = std::acos(cs); + weight += std::tan(angle * (Real)0.5); + } + else + { + weight += (Real)1; + } + break; + } + } + } + weight /= x1mx0length; + } + else + { + weight = (Real)1; + } + + std::array range = mVertexGraph[v0].range; + for (int j = 0; j < range[1]; ++j) + { + std::pair& data = mVertexGraphData[range[0] + j]; + if (data.first == v1) + { + data.second = weight; + } + } + + std::swap(v0, v1); + } + } +} + +template +void GenerateMeshUV::SolveSystem(unsigned int numIterations) +{ + // On the first pass, average only neighbors whose texture coordinates + // have been computed. This is a good initial guess for the linear system + // and leads to relatively fast convergence of the Gauss-Seidel iterates. + Real zero = (Real)0; + for (int i = mNumBoundaryEdges; i < mNumVertices; ++i) + { + int v0 = mOrderedVertices[i]; + std::array range = mVertexGraph[v0].range; + auto const* current = &mVertexGraphData[range[0]]; + Vector2 tcoord{ zero, zero }; + Real weight, weightSum = zero; + for (int j = 0; j < range[1]; ++j, ++current) + { + int v1 = current->first; + if (mTCoords[v1][0] != -1.0f) + { + weight = current->second; + weightSum += weight; + tcoord += weight * mTCoords[v1]; + } + } + tcoord /= weightSum; + mTCoords[v0] = tcoord; + } + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + if (mCModel->engine) + { + SolveSystemGPU(numIterations); + } + else +#endif + { + if (mCModel->numThreads > 1) + { + SolveSystemCPUMultiple(numIterations); + } + else + { + SolveSystemCPUSingle(numIterations); + } + } +} + +template +void GenerateMeshUV::SolveSystemCPUSingle(unsigned int numIterations) +{ + // Use ping-pong buffers for the texture coordinates. + std::vector> tcoords(mNumVertices); + size_t numBytes = mNumVertices * sizeof(Vector2); + Memcpy(&tcoords[0], mTCoords, numBytes); + Vector2* inTCoords = mTCoords; + Vector2* outTCoords = &tcoords[0]; + + // The value numIterations is even, so we always swap an even number + // of times. This ensures that on exit from the loop, outTCoords is + // tcoords. + for (unsigned int i = 1; i <= numIterations; ++i) + { + if (mCModel->progress) + { + (*mCModel->progress)(i); + } + + for (int j = mNumBoundaryEdges; j < mNumVertices; ++j) + { + int v0 = mOrderedVertices[j]; + std::array range = mVertexGraph[v0].range; + auto const* current = &mVertexGraphData[range[0]]; + Vector2 tcoord{ (Real)0, (Real)0 }; + Real weight, weightSum = (Real)0; + for (int k = 0; k < range[1]; ++k, ++current) + { + int v1 = current->first; + weight = current->second; + weightSum += weight; + tcoord += weight * inTCoords[v1]; + } + tcoord /= weightSum; + outTCoords[v0] = tcoord; + } + + std::swap(inTCoords, outTCoords); + } +} + +template +void GenerateMeshUV::SolveSystemCPUMultiple(unsigned int numIterations) +{ + // Use ping-pong buffers for the texture coordinates. + std::vector> tcoords(mNumVertices); + size_t numBytes = mNumVertices * sizeof(Vector2); + Memcpy(&tcoords[0], mTCoords, numBytes); + Vector2* inTCoords = mTCoords; + Vector2* outTCoords = &tcoords[0]; + + // Partition the data for multiple threads. + int numV = mNumVertices - mNumBoundaryEdges; + int numVPerThread = numV / mCModel->numThreads; + std::vector vmin(mCModel->numThreads), vmax(mCModel->numThreads); + for (unsigned int t = 0; t < mCModel->numThreads; ++t) + { + vmin[t] = mNumBoundaryEdges + t * numVPerThread; + vmax[t] = vmin[t] + numVPerThread - 1; + } + vmax[mCModel->numThreads - 1] = mNumVertices - 1; + + // The value numIterations is even, so we always swap an even number + // of times. This ensures that on exit from the loop, outTCoords is + // tcoords. + for (unsigned int i = 1; i <= numIterations; ++i) + { + if (mCModel->progress) + { + (*mCModel->progress)(i); + } + + // Execute Gauss-Seidel iterations in multiple threads. + std::vector process(mCModel->numThreads); + for (unsigned int t = 0; t < mCModel->numThreads; ++t) + { + process[t] = std::thread([this, t, &vmin, &vmax, inTCoords, + outTCoords]() + { + for (int j = vmin[t]; j <= vmax[t]; ++j) + { + int v0 = mOrderedVertices[j]; + std::array range = mVertexGraph[v0].range; + auto const* current = &mVertexGraphData[range[0]]; + Vector2 tcoord{ (Real)0, (Real)0 }; + Real weight, weightSum = (Real)0; + for (int k = 0; k < range[1]; ++k, ++current) + { + int v1 = current->first; + weight = current->second; + weightSum += weight; + tcoord += weight * inTCoords[v1]; + } + tcoord /= weightSum; + outTCoords[v0] = tcoord; + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mCModel->numThreads; ++t) + { + process[t].join(); + } + + std::swap(inTCoords, outTCoords); + } +} + +#if defined(GTE_COMPUTE_MODEL_ALLOW_GPGPU) + +template +void GenerateMeshUV::SolveSystemGPU(unsigned int numIterations) +{ + mCModel->factory->defines.Set("NUM_X_THREADS", 8); + mCModel->factory->defines.Set("NUM_Y_THREADS", 8); + if (std::numeric_limits::max() == std::numeric_limits::max()) + { + mCModel->factory->defines.Set("Real", "float"); +#if defined(GTE_DEV_OPENGL) + mCModel->factory->defines.Set("Real2", "vec2"); +#else + mCModel->factory->defines.Set("Real2", "float2"); +#endif + } + else + { + mCModel->factory->defines.Set("Real", "double"); +#if defined(GTE_DEV_OPENGL) + mCModel->factory->defines.Set("Real2", "dvec2"); +#else + mCModel->factory->defines.Set("Real2", "double2"); +#endif + } + + // TODO: Test mSolveSystem for null and respond accordingly. + int api = mCModel->factory->GetAPI(); + mSolveSystem = mCModel->factory->CreateFromSource(*msSource[api]); + std::shared_ptr cshader = mSolveSystem->GetCShader(); + + // Compute the number of thread groups. + int numInputs = mNumVertices - mNumBoundaryEdges; + Real factor0 = std::ceil(std::sqrt((Real)numInputs)); + Real factor1 = std::ceil((Real)numInputs / factor0); + int xElements = static_cast(factor0); + int yElements = static_cast(factor1); + int xRem = (xElements % 8); + if (xRem > 0) + { + xElements += 8 - xRem; + } + int yRem = (yElements % 8); + if (yRem > 0) + { + yElements += 8 - yRem; + } + unsigned int numXGroups = xElements / 8; + unsigned int numYGroups = yElements / 8; + + mBoundBuffer = std::make_shared(4 * sizeof(int), false); + int* data = mBoundBuffer->Get(); + data[0] = xElements; + data[1] = yElements; + data[2] = mNumBoundaryEdges; + data[3] = numInputs; + cshader->Set("Bounds", mBoundBuffer); + + unsigned int const vgSize = static_cast(mVertexGraph.size()); + mVGBuffer = std::make_shared(vgSize, sizeof(Vertex)); + Memcpy(mVGBuffer->GetData(), &mVertexGraph[0], mVGBuffer->GetNumBytes()); + cshader->Set("vertexGraph", mVGBuffer); + + unsigned int const vgdSize = static_cast(mVertexGraphData.size()); + mVGDBuffer = std::make_shared(vgdSize, sizeof(std::pair)); + Memcpy(mVGDBuffer->GetData(), &mVertexGraphData[0], mVGDBuffer->GetNumBytes()); + cshader->Set("vertexGraphData", mVGDBuffer); + + unsigned int const ovSize = static_cast(mOrderedVertices.size()); + mOVBuffer = std::make_shared(ovSize, sizeof(int)); + Memcpy(mOVBuffer->GetData(), &mOrderedVertices[0], mOVBuffer->GetNumBytes()); + cshader->Set("orderedVertices", mOVBuffer); + + for (int j = 0; j < 2; ++j) + { + mTCoordsBuffer[j] = std::make_shared(mNumVertices, sizeof(Vector2)); + mTCoordsBuffer[j]->SetUsage(Resource::SHADER_OUTPUT); + Memcpy(mTCoordsBuffer[j]->GetData(), mTCoords, mTCoordsBuffer[j]->GetNumBytes()); + } + mTCoordsBuffer[0]->SetCopyType(Resource::COPY_STAGING_TO_CPU); + + // The value numIterations is even, so we always swap an even number + // of times. This ensures that on exit from the loop, + // mTCoordsBuffer[0] has the final output. + for (unsigned int i = 1; i <= numIterations; ++i) + { + if (mCModel->progress) + { + (*mCModel->progress)(i); + } + + cshader->Set("inTCoords", mTCoordsBuffer[0]); + cshader->Set("outTCoords", mTCoordsBuffer[1]); + mCModel->engine->Execute(mSolveSystem, numXGroups, numYGroups, 1); + std::swap(mTCoordsBuffer[0], mTCoordsBuffer[1]); + } + + mCModel->engine->CopyGpuToCpu(mTCoordsBuffer[0]); + Memcpy(mTCoords, mTCoordsBuffer[0]->GetData(), mTCoordsBuffer[0]->GetNumBytes()); +} + +#endif + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHalfspace.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHalfspace.h new file mode 100644 index 000000000000..2d1d38ed5452 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHalfspace.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The halfspace is represented as Dot(N,X) >= c where N is a unit-length +// normal vector, c is the plane constant, and X is any point in space. +// The user must ensure that the normal vector is unit length. + +namespace gte +{ + +template +class Halfspace +{ +public: + // Construction and destruction. The default constructor sets the normal + // to (0,...,0,1) and the constant to zero (halfspace x[N-1] >= 0). + Halfspace(); + + // Specify N and c directly. + Halfspace(Vector const& inNormal, Real inConstant); + + // Public member access. + Vector normal; + Real constant; + +public: + // Comparisons to support sorted containers. + bool operator==(Halfspace const& halfspace) const; + bool operator!=(Halfspace const& halfspace) const; + bool operator< (Halfspace const& halfspace) const; + bool operator<=(Halfspace const& halfspace) const; + bool operator> (Halfspace const& halfspace) const; + bool operator>=(Halfspace const& halfspace) const; +}; + +// Template alias for convenience. +template +using Halfspace3 = Halfspace<3, Real>; + + +template +Halfspace::Halfspace() + : + constant((Real)0) +{ + normal.MakeUnit(N - 1); +} + +template +Halfspace::Halfspace(Vector const& inNormal, + Real inConstant) + : + normal(inNormal), + constant(inConstant) +{ +} + +template +bool Halfspace::operator==(Halfspace const& halfspace) const +{ + return normal == halfspace.normal && constant == halfspace.constant; +} + +template +bool Halfspace::operator!=(Halfspace const& halfspace) const +{ + return !operator==(halfspace); +} + +template +bool Halfspace::operator<(Halfspace const& halfspace) const +{ + if (normal < halfspace.normal) + { + return true; + } + + if (normal > halfspace.normal) + { + return false; + } + + return constant < halfspace.constant; +} + +template +bool Halfspace::operator<=(Halfspace const& halfspace) const +{ + return operator<(halfspace) || operator==(halfspace); +} + +template +bool Halfspace::operator>(Halfspace const& halfspace) const +{ + return !operator<=(halfspace); +} + +template +bool Halfspace::operator>=(Halfspace const& halfspace) const +{ + return !operator<(halfspace); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperellipsoid.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperellipsoid.h new file mode 100644 index 000000000000..1d74c87adee5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperellipsoid.h @@ -0,0 +1,383 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +// A hyperellipsoid has center K; axis directions U[0] through U[N-1], all +// unit-length vectors; and extents e[0] through e[N-1], all positive numbers. +// A point X = K + sum_{d=0}^{N-1} y[d]*U[d] is on the hyperellipsoid whenever +// sum_{d=0}^{N-1} (y[d]/e[d])^2 = 1. An algebraic representation for the +// hyperellipsoid is (X-K)^T * M * (X-K) = 1, where M is the NxN symmetric +// matrix M = sum_{d=0}^{N-1} U[d]*U[d]^T/e[d]^2, where the superscript T +// denotes transpose. Observe that U[i]*U[i]^T is a matrix, not a scalar dot +// product. The hyperellipsoid is also represented by a quadratic equation +// 0 = C + B^T*X + X^T*A*X, where C is a scalar, B is an Nx1 vector, and A is +// an NxN symmetric matrix with positive eigenvalues. The coefficients can be +// stored from lowest degree to highest degree, +// C = k[0] +// B = k[1], ..., k[N] +// A = k[N+1], ..., k[(N+1)(N+2)/2 - 1] +// where the A-coefficients are the upper-triangular elements of A listed in +// row-major order. For N = 2, X = (x[0],x[1]) and +// 0 = k[0] + +// k[1]*x[0] + k[2]*x[1] + +// k[3]*x[0]*x[0] + k[4]*x[0]*x[1] +// + k[5]*x[1]*x[1] +// For N = 3, X = (x[0],x[1],x[2]) and +// 0 = k[0] + +// k[1]*x[0] + k[2]*x[1] + k[3]*x[2] + +// k[4]*x[0]*x[0] + k[5]*x[0]*x[1] + k[6]*x[0]*x[2] + +// + k[7]*x[1]*x[1] + k[8]*x[1]*x[2] + +// + k[9]*x[2]*x[2] +// This equation can be factored to the form (X-K)^T * M * (X-K) = 1, where +// K = -A^{-1}*B/2, M = A/(B^T*A^{-1}*B/4-C). + +namespace gte +{ + +template +class Hyperellipsoid +{ +public: + + // Construction and destruction. The default constructor sets the center + // to Vector::Zero(), the axes to Vector::Unit(d), and all + // extents to 1. + Hyperellipsoid(); + Hyperellipsoid(Vector const& inCenter, + std::array, N> const inAxis, + Vector const& inExtent); + + // Compute M = sum_{d=0}^{N-1} U[d]*U[d]^T/e[d]^2. + void GetM(Matrix& M) const; + + // Compute M^{-1} = sum_{d=0}^{N-1} U[d]*U[d]^T*e[d]^2. + void GetMInverse(Matrix& MInverse) const; + + // Construct the coefficients in the quadratic equation that represents + // the hyperellipsoid. + void ToCoefficients(std::array& coeff) const; + void ToCoefficients(Matrix& A, Vector& B, + Real& C) const; + + // Construct C, U[i], and e[i] from the equation. The return value is + // 'true' if and only if the input coefficients represent a + // hyperellipsoid. If the function returns 'false', the hyperellipsoid + // data members are undefined. + bool FromCoefficients(std::array const& coeff); + bool FromCoefficients(Matrix const& A, + Vector const& B, Real C); + + // Public member access. + Vector center; + std::array, N> axis; + Vector extent; + +private: + static void Convert(std::array const& coeff, + Matrix& A, Vector& B, Real& C); + + static void Convert(Matrix const& A, Vector const& B, + Real C, std::array& coeff); + +public: + // Comparisons to support sorted containers. + bool operator==(Hyperellipsoid const& hyperellipsoid) const; + bool operator!=(Hyperellipsoid const& hyperellipsoid) const; + bool operator< (Hyperellipsoid const& hyperellipsoid) const; + bool operator<=(Hyperellipsoid const& hyperellipsoid) const; + bool operator> (Hyperellipsoid const& hyperellipsoid) const; + bool operator>=(Hyperellipsoid const& hyperellipsoid) const; +}; + +// Template aliases for convenience. +template +using Ellipse2 = Hyperellipsoid<2, Real>; + +template +using Ellipsoid3 = Hyperellipsoid<3, Real>; + + +template +Hyperellipsoid::Hyperellipsoid() +{ + center.MakeZero(); + for (int d = 0; d < N; ++d) + { + axis[d].MakeUnit(d); + extent[d] = (Real)1; + } +} + +template +Hyperellipsoid::Hyperellipsoid(Vector const& inCenter, + std::array, N> const inAxis, + Vector const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) +{ +} + +template +void Hyperellipsoid::GetM(Matrix& M) const +{ + M.MakeZero(); + for (int d = 0; d < N; ++d) + { + Vector ratio = axis[d] / extent[d]; + M += OuterProduct(ratio, ratio); + } +} + +template +void Hyperellipsoid::GetMInverse(Matrix& MInverse) const +{ + MInverse.MakeZero(); + for (int d = 0; d < N; ++d) + { + Vector product = axis[d] * extent[d]; + MInverse += OuterProduct(product, product); + } +} + +template +void Hyperellipsoid::ToCoefficients( + std::array& coeff) const +{ + int const numCoefficients = (N + 1)*(N + 2) / 2; + Matrix A; + Vector B; + Real C; + ToCoefficients(A, B, C); + Convert(A, B, C, coeff); + + // Arrange for one of the coefficients of the quadratic terms to be 1. + int quadIndex = numCoefficients - 1; + int maxIndex = quadIndex; + Real maxValue = std::abs(coeff[quadIndex]); + for (int d = 2; d < N; ++d) + { + quadIndex -= d; + Real absValue = std::abs(coeff[quadIndex]); + if (absValue > maxValue) + { + maxIndex = quadIndex; + maxValue = absValue; + } + } + + Real invMaxValue = ((Real)1) / maxValue; + for (int i = 0; i < numCoefficients; ++i) + { + if (i != maxIndex) + { + coeff[i] *= invMaxValue; + } + else + { + coeff[i] = (Real)1; + } + } +} + +template +void Hyperellipsoid::ToCoefficients(Matrix& A, + Vector& B, Real& C) const +{ + GetM(A); + Vector product = A * center; + B = ((Real)-2) * product; + C = Dot(center, product) - (Real)1; +} + +template +bool Hyperellipsoid::FromCoefficients( + std::array const& coeff) +{ + Matrix A; + Vector B; + Real C; + Convert(coeff, A, B, C); + return FromCoefficients(A, B, C); +} + +template +bool Hyperellipsoid::FromCoefficients(Matrix const& A, + Vector const& B, Real C) +{ + // Compute the center K = -A^{-1}*B/2. + bool invertible; + Matrix invA = Inverse(A, &invertible); + if (!invertible) + { + return false; + } + + center = ((Real)-0.5) * (invA * B); + + // Compute B^T*A^{-1}*B/4 - C = K^T*A*K - C = -K^T*B/2 - C. + Real rightSide = -((Real)0.5) * Dot(center, B) - C; + if (rightSide == (Real)0) + { + return false; + } + + // Compute M = A/(K^T*A*K - C). + Real invRightSide = ((Real)1) / rightSide; + Matrix M = invRightSide * A; + + // Factor into M = R*D*R^T. M is symmetric, so it does not matter whether + // the matrix is stored in row-major or column-major order; they are + // equivalent. The output R, however, is in row-major order. + SymmetricEigensolver es(N, 32); + Matrix rotation; + std::array diagonal; + es.Solve(&M[0], +1); // diagonal[i] are nondecreasing + es.GetEigenvalues(&diagonal[0]); + es.GetEigenvectors(&rotation[0]); + if (es.GetEigenvectorMatrixType() == 0) + { + auto negLast = -rotation.GetCol(N - 1); + rotation.SetCol(N - 1, negLast); + } + + for (int d = 0; d < N; ++d) + { + if (diagonal[d] <= (Real)0) + { + return false; + } + + extent[d] = ((Real)1) / std::sqrt(diagonal[d]); + axis[d] = rotation.GetCol(d); + } + + return true; +} + +template +void Hyperellipsoid::Convert( + std::array const& coeff, Matrix& A, + Vector& B, Real& C) +{ + int i = 0; + C = coeff[i++]; + + for (int j = 0; j < N; ++j) + { + B[j] = coeff[i++]; + } + + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + A(r, c) = A(c, r); + } + + A(r, r) = coeff[i++]; + + for (int c = r + 1; c < N; ++c) + { + A(r, c) = coeff[i++] * (Real)0.5; + } + } +} + +template +void Hyperellipsoid::Convert(Matrix const& A, + Vector const& B, Real C, + std::array& coeff) +{ + int i = 0; + coeff[i++] = C; + + for (int j = 0; j < N; ++j) + { + coeff[i++] = B[j]; + } + + for (int r = 0; r < N; ++r) + { + coeff[i++] = A(r, r); + for (int c = r + 1; c < N; ++c) + { + coeff[i++] = A(r, c) * (Real)2; + } + } +} + +template +bool Hyperellipsoid::operator==( + Hyperellipsoid const& hyperellipsoid) const +{ + return center == hyperellipsoid.center && axis == hyperellipsoid.axis + && extent == hyperellipsoid.extent; +} + +template +bool Hyperellipsoid::operator!=( + Hyperellipsoid const& hyperellipsoid) const +{ + return !operator==(hyperellipsoid); +} + +template +bool Hyperellipsoid::operator<( + Hyperellipsoid const& hyperellipsoid) const +{ + if (center < hyperellipsoid.center) + { + return true; + } + + if (center > hyperellipsoid.center) + { + return false; + } + + if (axis < hyperellipsoid.axis) + { + return true; + } + + if (axis > hyperellipsoid.axis) + { + return false; + } + + return extent < hyperellipsoid.extent; +} + +template +bool Hyperellipsoid::operator<=( + Hyperellipsoid const& hyperellipsoid) const +{ + return operator<(hyperellipsoid) || operator==(hyperellipsoid); +} + +template +bool Hyperellipsoid::operator>( + Hyperellipsoid const& hyperellipsoid) const +{ + return !operator<=(hyperellipsoid); +} + +template +bool Hyperellipsoid::operator>=( + Hyperellipsoid const& hyperellipsoid) const +{ + return !operator<(hyperellipsoid); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperplane.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperplane.h new file mode 100644 index 000000000000..54a2a382b678 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHyperplane.h @@ -0,0 +1,149 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/07/28) + +#pragma once + +#include +#include + +// The plane is represented as Dot(U,X) = c where U is a unit-length normal +// vector, c is the plane constant, and X is any point on the plane. The user +// must ensure that the normal vector is unit length. + +namespace gte +{ + +template +class Hyperplane +{ +public: + // Construction and destruction. The default constructor sets the normal + // to (0,...,0,1) and the constant to zero (plane z = 0). + Hyperplane(); + + // Specify U and c directly. + Hyperplane(Vector const& inNormal, Real inConstant); + + // U is specified, c = Dot(U,p) where p is a point on the hyperplane. + Hyperplane(Vector const& inNormal, Vector const& p); + + // U is a unit-length vector in the orthogonal complement of the set + // {p[1]-p[0],...,p[n-1]-p[0]} and c = Dot(U,p[0]), where the p[i] are + // pointson the hyperplane. + Hyperplane(std::array, N> const& p); + + // Public member access. + Vector normal; + Real constant; + +public: + // Comparisons to support sorted containers. + bool operator==(Hyperplane const& hyperplane) const; + bool operator!=(Hyperplane const& hyperplane) const; + bool operator< (Hyperplane const& hyperplane) const; + bool operator<=(Hyperplane const& hyperplane) const; + bool operator> (Hyperplane const& hyperplane) const; + bool operator>=(Hyperplane const& hyperplane) const; +}; + +// Template alias for convenience. +template +using Plane3 = Hyperplane<3, Real>; + + +template +Hyperplane::Hyperplane() + : + constant((Real)0) +{ + normal.MakeUnit(N - 1); +} + +template +Hyperplane::Hyperplane(Vector const& inNormal, + Real inConstant) + : + normal(inNormal), + constant(inConstant) +{ +} + +template +Hyperplane::Hyperplane(Vector const& inNormal, + Vector const& p) + : + normal(inNormal), + constant(Dot(inNormal, p)) +{ +} + +template +Hyperplane::Hyperplane(std::array, N> const& p) +{ + Matrix edge; + for (int i = 0; i < N - 1; ++i) + { + edge.SetCol(i, p[i + 1] - p[0]); + } + + // Compute the 1-dimensional orthogonal complement of the edges of the + // simplex formed by the points p[]. + SingularValueDecomposition svd(N, N - 1, 32); + svd.Solve(&edge[0], -1); + svd.GetUColumn(N - 1, &normal[0]); + + constant = Dot(normal, p[0]); +} + +template +bool Hyperplane::operator==(Hyperplane const& hyperplane) const +{ + return normal == hyperplane.normal && constant == hyperplane.constant; +} + +template +bool Hyperplane::operator!=(Hyperplane const& hyperplane) const +{ + return !operator==(hyperplane); +} + +template +bool Hyperplane::operator<(Hyperplane const& hyperplane) const +{ + if (normal < hyperplane.normal) + { + return true; + } + + if (normal > hyperplane.normal) + { + return false; + } + + return constant < hyperplane.constant; +} + +template +bool Hyperplane::operator<=(Hyperplane const& hyperplane) const +{ + return operator<(hyperplane) || operator==(hyperplane); +} + +template +bool Hyperplane::operator>(Hyperplane const& hyperplane) const +{ + return !operator<=(hyperplane); +} + +template +bool Hyperplane::operator>=(Hyperplane const& hyperplane) const +{ + return !operator<(hyperplane); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHypersphere.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHypersphere.h new file mode 100644 index 000000000000..b23b00b13ace --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteHypersphere.h @@ -0,0 +1,114 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The hypersphere is represented as |X-C| = R where C is the center and R is +// the radius. The hypersphere is a circle for dimension 2 or a sphere for +// dimension 3. + +namespace gte +{ + +template +class Hypersphere +{ +public: + // Construction and destruction. The default constructor sets the center + // to (0,...,0) and the radius to 1. + Hypersphere(); + Hypersphere(Vector const& inCenter, Real inRadius); + + // Public member access. + Vector center; + Real radius; + +public: + // Comparisons to support sorted containers. + bool operator==(Hypersphere const& hypersphere) const; + bool operator!=(Hypersphere const& hypersphere) const; + bool operator< (Hypersphere const& hypersphere) const; + bool operator<=(Hypersphere const& hypersphere) const; + bool operator> (Hypersphere const& hypersphere) const; + bool operator>=(Hypersphere const& hypersphere) const; +}; + +// Template aliases for convenience. +template +using Circle2 = Hypersphere<2, Real>; + +template +using Sphere3 = Hypersphere<3, Real>; + + +template +Hypersphere::Hypersphere() + : + radius((Real)1) +{ + center.MakeZero(); +} + +template +Hypersphere::Hypersphere(Vector const& inCenter, + Real inRadius) + : + center(inCenter), + radius(inRadius) +{ +} + +template +bool Hypersphere::operator==(Hypersphere const& hypersphere) const +{ + return center == hypersphere.center && radius == hypersphere.radius; +} + +template +bool Hypersphere::operator!=(Hypersphere const& hypersphere) const +{ + return !operator==(hypersphere); +} + +template +bool Hypersphere::operator<(Hypersphere const& hypersphere) const +{ + if (center < hypersphere.center) + { + return true; + } + + if (center > hypersphere.center) + { + return false; + } + + return radius < hypersphere.radius; +} + +template +bool Hypersphere::operator<=(Hypersphere const& hypersphere) const +{ + return operator<(hypersphere) || operator==(hypersphere); +} + +template +bool Hypersphere::operator>(Hypersphere const& hypersphere) const +{ + return !operator<=(hypersphere); +} + +template +bool Hypersphere::operator>=(Hypersphere const& hypersphere) const +{ + return !operator<(hypersphere); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary.h new file mode 100644 index 000000000000..1ddc8cb70fc0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary.h @@ -0,0 +1,448 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class IEEEBinary +{ +public: + // For generic access of the template types. + typedef Float FloatType; + typedef UInt UIntType; + + // Construction from an encoding. Copy constructor, destructor, and + // assignment operator are implicitly generated. For the 3-parameter + // constructor, see the comments for SetEncoding(...). + ~IEEEBinary (); + IEEEBinary (); // The encoding is uninitialized. + IEEEBinary (IEEEBinary const& object); + IEEEBinary (UInt inEncoding); + IEEEBinary (UInt inSign, UInt inBiased, UInt inTrailing); + IEEEBinary (Float inNumber); + + // Implicit conversion to floating-point type. + operator UInt () const; + operator Float () const; + + // Assignment. + IEEEBinary& operator= (IEEEBinary const& object); + + // Special constants. + static int const NUM_ENCODING_BITS = NumBits; + static int const NUM_EXPONENT_BITS = NumBits - Precision; + static int const NUM_SIGNIFICAND_BITS = Precision; + static int const NUM_TRAILING_BITS = Precision - 1; + static int const EXPONENT_BIAS = (1 << (NUM_EXPONENT_BITS - 1)) - 1; + static int const MAX_BIASED_EXPONENT = (1 << NUM_EXPONENT_BITS) - 1; + static int const MIN_SUB_EXPONENT = 1 - EXPONENT_BIAS; + static int const MIN_EXPONENT = MIN_SUB_EXPONENT - NUM_TRAILING_BITS; + static int const SIGN_SHIFT = NumBits - 1; + + static UInt const SIGN_MASK = (UInt(1) << (NumBits - 1)); + static UInt const NOT_SIGN_MASK = UInt(~SIGN_MASK); + static UInt const TRAILING_MASK = (UInt(1) << NUM_TRAILING_BITS) - 1; + static UInt const EXPONENT_MASK = NOT_SIGN_MASK & ~TRAILING_MASK; + static UInt const NAN_QUIET_MASK = (UInt(1) << (NUM_TRAILING_BITS - 1)); + static UInt const NAN_PAYLOAD_MASK = (TRAILING_MASK >> 1); + static UInt const MAX_TRAILING = TRAILING_MASK; + static UInt const SUP_TRAILING = (UInt(1) << NUM_TRAILING_BITS); + static UInt const POS_ZERO = UInt(0); + static UInt const NEG_ZERO = SIGN_MASK; + static UInt const MIN_SUBNORMAL = UInt(1); + static UInt const MAX_SUBNORMAL = TRAILING_MASK; + static UInt const MIN_NORMAL = SUP_TRAILING; + static UInt const MAX_NORMAL = NOT_SIGN_MASK & ~SUP_TRAILING; + static UInt const POS_INFINITY = EXPONENT_MASK; + static UInt const NEG_INFINITY = SIGN_MASK | EXPONENT_MASK; + + // The types of numbers. + enum Classification + { + CLASS_NEG_INFINITY, + CLASS_NEG_SUBNORMAL, + CLASS_NEG_NORMAL, + CLASS_NEG_ZERO, + CLASS_POS_ZERO, + CLASS_POS_SUBNORMAL, + CLASS_POS_NORMAL, + CLASS_POS_INFINITY, + CLASS_QUIET_NAN, + CLASS_SIGNALING_NAN + }; + + Classification GetClassification () const; + bool IsZero () const; + bool IsSignMinus () const; + bool IsSubnormal () const; + bool IsNormal () const; + bool IsFinite () const; + bool IsInfinite () const; + bool IsNaN () const; + bool IsSignalingNaN () const; + + // Get neighboring numbers. + UInt GetNextUp () const; + UInt GetNextDown () const; + + // Encode and decode the binary representation. The sign is 0 (number + // is nonnegative) or 1 (number is negative). The biased exponent is in + // the range [0,MAX_BIASED_EXPONENT]. The trailing significand is in the + // range [0,MAX_TRAILING]. + UInt GetSign () const; + UInt GetBiased () const; + UInt GetTrailing () const; + void SetEncoding (UInt sign, UInt biased, UInt trailing); + void GetEncoding (UInt& sign, UInt& biased, UInt& trailing) const; + + // Access for direct manipulation of the object. + union + { + UInt encoding; + Float number; + }; +}; + +typedef IEEEBinary IEEEBinary32; +typedef IEEEBinary IEEEBinary64; + + +template +IEEEBinary::~IEEEBinary() +{ +} + +template +IEEEBinary::IEEEBinary() +{ + // The member mEncoding is uninitialized. +} + +template +IEEEBinary::IEEEBinary( + IEEEBinary const& object) + : + encoding(object.encoding) +{ +} + +template +IEEEBinary::IEEEBinary(UInt inEncoding) + : + encoding(inEncoding) +{ +} + +template +IEEEBinary::IEEEBinary(UInt inSign, + UInt inBiased, UInt inTrailing) +{ + SetEncoding(inSign, inBiased, inTrailing); +} + +template +IEEEBinary::IEEEBinary(Float inNumber) + : + number(inNumber) +{ +} + +template +IEEEBinary::operator UInt () const +{ + return encoding; +} + +template +IEEEBinary::operator Float () const +{ + return number; +} + +template +IEEEBinary& +IEEEBinary::operator= (IEEEBinary const& object) +{ + encoding = object.encoding; + return *this; +} + +template +typename IEEEBinary::Classification +IEEEBinary::GetClassification() const +{ + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + return (sign != 0 ? CLASS_NEG_ZERO : CLASS_POS_ZERO); + } + else + { + return (sign != 0 ? CLASS_NEG_SUBNORMAL : CLASS_POS_SUBNORMAL); + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + return (sign != 0 ? CLASS_NEG_NORMAL : CLASS_POS_NORMAL); + } + else if (trailing == 0) + { + return (sign != 0 ? CLASS_NEG_INFINITY : CLASS_POS_INFINITY); + } + else if (trailing & NAN_QUIET_MASK) + { + return CLASS_QUIET_NAN; + } + else + { + return CLASS_SIGNALING_NAN; + } +} + +template +bool IEEEBinary::IsZero() const +{ + return encoding == POS_ZERO || encoding == NEG_ZERO; +} + +template +bool IEEEBinary::IsSignMinus() const +{ + return (encoding & SIGN_MASK) != 0; +} + +template +bool IEEEBinary::IsSubnormal() const +{ + return GetBiased() == 0 && GetTrailing() > 0; +} + +template +bool IEEEBinary::IsNormal() const +{ + UInt biased = GetBiased(); + return 0 < biased && biased < MAX_BIASED_EXPONENT; +} + +template +bool IEEEBinary::IsFinite() const +{ + return GetBiased() < MAX_BIASED_EXPONENT; +} + +template +bool IEEEBinary::IsInfinite() const +{ + return GetBiased() == MAX_BIASED_EXPONENT && GetTrailing() == 0; +} + +template +bool IEEEBinary::IsNaN() const +{ + return GetBiased() == MAX_BIASED_EXPONENT && GetTrailing() != 0; +} + +template +bool IEEEBinary::IsSignalingNaN() const +{ + UInt trailing = GetTrailing(); + return GetBiased() == MAX_BIASED_EXPONENT + && (trailing & NAN_QUIET_MASK) == 0 + && (trailing & NAN_PAYLOAD_MASK) != 0; +} + +template +UInt IEEEBinary::GetNextUp() const +{ + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + // The next-up for both -0 and +0 is MIN_SUBNORMAL. + return MIN_SUBNORMAL; + } + else + { + if (sign != 0) + { + // When trailing is 1, 'this' is -MIN_SUBNORMAL and next-up + // is -0. + --trailing; + return SIGN_MASK | trailing; + } + else + { + // When trailing is MAX_TRAILING, 'this' is MAX_SUBNORMAL + // and next-up is MIN_NORMAL. + ++trailing; + return trailing; + } + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + UInt nonnegative = (encoding & NOT_SIGN_MASK); + if (sign != 0) + { + --nonnegative; + return SIGN_MASK | nonnegative; + } + else + { + ++nonnegative; + return nonnegative; + } + } + else if (trailing == 0) + { + if (sign != 0) + { + // The next-up of -INFINITY is -MAX_NORMAL. + return SIGN_MASK | MAX_NORMAL; + } + else + { + // The next-up of +INFINITY is +INFINITY. + return POS_INFINITY; + } + } + else if (trailing & NAN_QUIET_MASK) + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + else + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } +} + +template +UInt IEEEBinary::GetNextDown() const +{ + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + // The next-down for both -0 and +0 is -MIN_SUBNORMAL. + return SIGN_MASK | MIN_SUBNORMAL; + } + else + { + if (sign == 0) + { + // When trailing is 1, 'this' is MIN_SUBNORMAL and next-down + // is +0. + --trailing; + return trailing; + } + else + { + // When trailing is MAX_TRAILING, 'this' is -MAX_SUBNORMAL + // and next-down is -MIN_NORMAL. + ++trailing; + return SIGN_MASK | trailing; + } + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + UInt nonnegative = (encoding & NOT_SIGN_MASK); + if (sign == 0) + { + --nonnegative; + return nonnegative; + } + else + { + ++nonnegative; + return SIGN_MASK | nonnegative; + } + } + else if (trailing == 0) + { + if (sign == 0) + { + // The next-down of +INFINITY is +MAX_NORMAL. + return MAX_NORMAL; + } + else + { + // The next-down of -INFINITY is -INFINITY. + return NEG_INFINITY; + } + } + else if (trailing & NAN_QUIET_MASK) + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + else + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } +} + +template +UInt IEEEBinary::GetSign() const +{ + return (encoding & SIGN_MASK) >> SIGN_SHIFT; +} + +template +UInt IEEEBinary::GetBiased() const +{ + return (encoding & EXPONENT_MASK) >> NUM_TRAILING_BITS; +} + +template +UInt IEEEBinary::GetTrailing() const +{ + return encoding & TRAILING_MASK; +} + +template +void IEEEBinary::SetEncoding(UInt sign, + UInt biased, UInt trailing) +{ + encoding = + (sign << SIGN_SHIFT) | (biased << NUM_TRAILING_BITS) | trailing; +} + +template +void IEEEBinary::GetEncoding(UInt& sign, + UInt& biased, UInt& trailing) const +{ + sign = GetSign(); + biased = GetBiased(); + trailing = GetTrailing(); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.cpp new file mode 100644 index 000000000000..fc4d51287ec4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.cpp @@ -0,0 +1,368 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#include +#include +#include + +namespace gte +{ + +IEEEBinary16::~IEEEBinary16() +{ +} + +IEEEBinary16::IEEEBinary16() + : + IEEEBinary() +{ + // uninitialized +} + +IEEEBinary16::IEEEBinary16(IEEEBinary16 const& object) + : + IEEEBinary(object) +{ +} + +IEEEBinary16::IEEEBinary16(float number) + : + IEEEBinary() +{ + union { float n; uint32_t e; } temp = { number }; + encoding = Convert32To16(temp.e); +} + +IEEEBinary16::IEEEBinary16(double number) + : + IEEEBinary() +{ + union { float n; uint32_t e; } temp; + temp.n = (float)number; + encoding = Convert32To16(temp.e); +} + +IEEEBinary16::IEEEBinary16(uint16_t encoding) + : + IEEEBinary(encoding) +{ +} + +IEEEBinary16::operator float() const +{ + union { uint32_t e; float n; } temp = { Convert16To32(encoding) }; + return temp.n; +} + +IEEEBinary16::operator double() const +{ + union { uint32_t e; float n; } temp = { Convert16To32(encoding) }; + return (double)temp.n; +} + +IEEEBinary16& IEEEBinary16::operator= (IEEEBinary16 const& object) +{ + IEEEBinary::operator=(object); + return *this; +} + +bool IEEEBinary16::operator==(IEEEBinary16 const& object) const +{ + return (float)*this == (float)object; +} + +bool IEEEBinary16::operator!=(IEEEBinary16 const& object) const +{ + return (float)*this != (float)object; +} + +bool IEEEBinary16::operator< (IEEEBinary16 const& object) const +{ + return (float)*this < (float)object; +} + +bool IEEEBinary16::operator<=(IEEEBinary16 const& object) const +{ + return (float)*this <= (float)object; +} + +bool IEEEBinary16::operator> (IEEEBinary16 const& object) const +{ + return (float)*this > (float)object; +} + +bool IEEEBinary16::operator>=(IEEEBinary16 const& object) const +{ + return (float)*this >= (float)object; +} + +uint16_t IEEEBinary16::Convert32To16(uint32_t encoding) +{ + // Extract the channels for the binary32 number. + uint32_t sign32 = (encoding & F32_SIGN_MASK); + uint32_t biased32 = + ((encoding & F32_BIASED_EXPONENT_MASK) >> F32_NUM_TRAILING_BITS); + uint32_t trailing32 = (encoding & F32_TRAILING_MASK); + uint32_t nonneg32 = (encoding & F32_NOT_SIGN_MASK); + + // Generate the channels for the IEEEBinary16 number. + uint16_t sign16 = static_cast(sign32 >> DIFF_NUM_ENCODING_BITS); + uint16_t biased16, trailing16; + uint32_t frcpart; + + if (biased32 == 0) + { + // nonneg32 is 32-zero or 32-subnormal, nearest is 16-zero. + return sign16; + } + + if (biased32 < F32_MAX_BIASED_EXPONENT) + { + // nonneg32 is 32-normal. + if (nonneg32 <= F16_AVR_MIN_SUBNORMAL_ZERO) + { + // nonneg32 <= 2^{-25}, nearest is 16-zero. + return sign16; + } + + if (nonneg32 <= F16_MIN_SUBNORMAL) + { + // 2^{-25} < nonneg32 <= 2^{-24}, nearest is 16-min-subnormal. + return sign16 | IEEEBinary16::MIN_SUBNORMAL; + } + + if (nonneg32 < F16_MIN_NORMAL) + { + // 2^{-24} < nonneg32 < 2^{-14}, round to nearest + // 16-subnormal with ties to even. Note that biased16 is zero. + trailing16 = static_cast(((trailing32 & INT_PART_MASK) >> DIFF_NUM_TRAILING_BITS)); + frcpart = (trailing32 & FRC_PART_MASK); + if (frcpart > FRC_HALF || (frcpart == FRC_HALF && (trailing16 & 1))) + { + // If there is a carry into the exponent, the nearest is + // actually 16-min-normal 1.0*2^{-14}, so the high-order bit + // of trailing16 makes biased16 equal to 1 and the result is + // correct. + ++trailing16; + } + return sign16 | trailing16; + } + + if (nonneg32 <= F16_MAX_NORMAL) + { + // 2^{-14} <= nonneg32 <= 1.1111111111*2^{15}, round to nearest + // 16-normal with ties to even. + biased16 = static_cast((biased32 - F32_EXPONENT_BIAS + + IEEEBinary16::EXPONENT_BIAS) + << IEEEBinary16::NUM_TRAILING_BITS); + trailing16 = static_cast(((trailing32 & INT_PART_MASK) >> DIFF_NUM_TRAILING_BITS)); + frcpart = (trailing32 & FRC_PART_MASK); + if (frcpart > FRC_HALF || (frcpart == FRC_HALF && (trailing16 & 1))) + { + // If there is a carry into the exponent, the addition of + // trailing16 to biased16 (rather than or-ing) produces the + // correct result. + ++trailing16; + } + return sign16 | (biased16 + trailing16); + } + + if (nonneg32 < F16_AVR_MAX_NORMAL_INFINITY) + { + // 1.1111111111*2^{15} < nonneg32 < (MAX_NORMAL+INFINITY)/2, so + // the number is closest to 16-max-normal. + return sign16 | IEEEBinary16::MAX_NORMAL; + } + + // nonneg32 >= (MAX_NORMAL+INFINITY)/2, so convert to 16-infinite. + return sign16 | IEEEBinary16::POS_INFINITY; + } + + if (trailing32 == 0) + { + // The number is 32-infinite. Convert to 16-infinite. + return sign16 | IEEEBinary16::POS_INFINITY; + } + + // The number is 32-NaN. Convert to 16-NaN with 16-payload the high-order + // 9 bits of the 32-payload. The code also grabs the 32-quietNaN mask + // bit. + uint16_t maskPayload = static_cast( + (trailing32 & 0x007FE000u) >> 13); + return sign16 | IEEEBinary16::EXPONENT_MASK | maskPayload; +} + +uint32_t IEEEBinary16::Convert16To32(uint16_t encoding) +{ + // Extract the channels for the IEEEBinary16 number. + uint16_t sign16 = (encoding & IEEEBinary16::SIGN_MASK); + uint16_t biased16 = ((encoding & IEEEBinary16::EXPONENT_MASK) + >> IEEEBinary16::NUM_TRAILING_BITS); + uint16_t trailing16 = (encoding & IEEEBinary16::TRAILING_MASK); + + // Generate the channels for the binary32 number. + uint32_t sign32 = static_cast(sign16 << DIFF_NUM_ENCODING_BITS); + uint32_t biased32, trailing32; + + if (biased16 == 0) + { + if (trailing16 == 0) + { + // The number is 16-zero. Convert to 32-zero. + return sign32; + } + else + { + // The number is 16-subnormal. Convert to 32-normal. + trailing32 = static_cast(trailing16); + int32_t leading = GetLeadingBit(trailing32); + int32_t shift = 23 - leading; + biased32 = static_cast(F32_EXPONENT_BIAS - 1 - shift); + trailing32 = (trailing32 << shift) & F32_TRAILING_MASK; + return sign32 | (biased32 << F32_NUM_TRAILING_BITS) | trailing32; + } + } + + if (biased16 < IEEEBinary16::MAX_BIASED_EXPONENT) + { + // The number is 16-normal. Convert to 32-normal. + biased32 = static_cast(biased16 - IEEEBinary16::EXPONENT_BIAS + + F32_EXPONENT_BIAS); + trailing32 = (static_cast( + trailing16) << DIFF_NUM_TRAILING_BITS); + return sign32 | (biased32 << F32_NUM_TRAILING_BITS) | trailing32; + } + + if (trailing16 == 0) + { + // The number is 16-infinite. Convert to 32-infinite. + return sign32 | F32_BIASED_EXPONENT_MASK; + } + + // The number is 16-NaN. Convert to 32-NaN with 16-payload embedded in + // the high-order 9 bits of the 32-payload. The code also copies the + // 16-quietNaN mask bit. + uint32_t maskPayload = + ((trailing16 & IEEEBinary16::TRAILING_MASK) << DIFF_PAYLOAD_SHIFT); + return sign32 | F32_BIASED_EXPONENT_MASK | maskPayload; +} + +IEEEBinary16 operator- (IEEEBinary16 x) +{ + uint16_t result = static_cast(x) ^ IEEEBinary16::SIGN_MASK; + return result; +} + +float operator+ (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x)+static_cast(y); +} + +float operator- (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x)-static_cast(y); +} + +float operator* (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x)* static_cast(y); +} + +float operator/ (IEEEBinary16 x, IEEEBinary16 y) +{ + return static_cast(x) / static_cast(y); +} + +float operator+ (IEEEBinary16 x, float y) +{ + return static_cast(x)+y; +} + +float operator- (IEEEBinary16 x, float y) +{ + return static_cast(x)-y; +} + +float operator* (IEEEBinary16 x, float y) +{ + return static_cast(x)* y; +} + +float operator/ (IEEEBinary16 x, float y) +{ + return static_cast(x) / y; +} + +float operator+ (float x, IEEEBinary16 y) +{ + return x + static_cast(y); +} + +float operator- (float x, IEEEBinary16 y) +{ + return x - static_cast(y); +} + +float operator* (float x, IEEEBinary16 y) +{ + return x * static_cast(y); +} + +float operator/ (float x, IEEEBinary16 y) +{ + return x / static_cast(y); +} + +IEEEBinary16& operator+= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x)+static_cast(y); + return x; +} + +IEEEBinary16& operator-= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x)-static_cast(y); + return x; +} + +IEEEBinary16& operator*= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x)* static_cast(y); + return x; +} + +IEEEBinary16& operator/= (IEEEBinary16& x, IEEEBinary16 y) +{ + x = static_cast(x) / static_cast(y); + return x; +} + +IEEEBinary16& operator+= (IEEEBinary16& x, float y) +{ + x = static_cast(x)+y; + return x; +} + +IEEEBinary16& operator-= (IEEEBinary16& x, float y) +{ + x = static_cast(x)-y; + return x; +} + +IEEEBinary16& operator*= (IEEEBinary16& x, float y) +{ + x = static_cast(x)* y; + return x; +} + +IEEEBinary16& operator/= (IEEEBinary16& x, float y) +{ + x = static_cast(x) / y; + return x; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.h new file mode 100644 index 000000000000..0582cfb58f56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIEEEBinary16.h @@ -0,0 +1,313 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + class IEEEBinary16 : public IEEEBinary + { + public: + // Construction and destruction. The base class destructor is hidden, + // but this is safe because there are no side effects of the + // destruction. + ~IEEEBinary16(); + IEEEBinary16(); // uninitialized + IEEEBinary16(IEEEBinary16 const& object); + IEEEBinary16(float number); + IEEEBinary16(double number); + IEEEBinary16(uint16_t encoding); + + // Implicit conversions. + operator float() const; + operator double() const; + + // Assignment. + IEEEBinary16& operator=(IEEEBinary16 const& object); + + // Comparison. + bool operator==(IEEEBinary16 const& object) const; + bool operator!=(IEEEBinary16 const& object) const; + bool operator< (IEEEBinary16 const& object) const; + bool operator<=(IEEEBinary16 const& object) const; + bool operator> (IEEEBinary16 const& object) const; + bool operator>=(IEEEBinary16 const& object) const; + + private: + // Support for conversions between encodings. + enum + { + F32_NUM_ENCODING_BITS = 32, + F32_NUM_TRAILING_BITS = 23, + F32_EXPONENT_BIAS = 127, + F32_MAX_BIASED_EXPONENT = 255, + F32_SIGN_MASK = 0x80000000, + F32_NOT_SIGN_MASK = 0x7FFFFFFF, + F32_BIASED_EXPONENT_MASK = 0x7F800000, + F32_TRAILING_MASK = 0x007FFFFF, + F16_AVR_MIN_SUBNORMAL_ZERO = 0x33000000, + F16_MIN_SUBNORMAL = 0x33800000, + F16_MIN_NORMAL = 0x38800000, + F16_MAX_NORMAL = 0x477FE000, + F16_AVR_MAX_NORMAL_INFINITY = 0x477FF000, + DIFF_NUM_ENCODING_BITS = 16, + DIFF_NUM_TRAILING_BITS = 13, + DIFF_PAYLOAD_SHIFT = 13, + INT_PART_MASK = 0x007FE000, + FRC_PART_MASK = 0x00001FFF, + FRC_HALF = 0x00001000 + }; + + static uint16_t Convert32To16(uint32_t encoding); + static uint32_t Convert16To32(uint16_t encoding); + }; + + // Arithmetic operations (high-precision). + IEEEBinary16 operator-(IEEEBinary16 x); + float operator+(IEEEBinary16 x, IEEEBinary16 y); + float operator-(IEEEBinary16 x, IEEEBinary16 y); + float operator*(IEEEBinary16 x, IEEEBinary16 y); + float operator/(IEEEBinary16 x, IEEEBinary16 y); + float operator+(IEEEBinary16 x, float y); + float operator-(IEEEBinary16 x, float y); + float operator*(IEEEBinary16 x, float y); + float operator/(IEEEBinary16 x, float y); + float operator+(float x, IEEEBinary16 y); + float operator-(float x, IEEEBinary16 y); + float operator*(float x, IEEEBinary16 y); + float operator/(float x, IEEEBinary16 y); + + // Arithmetic updates. + IEEEBinary16& operator+=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator-=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator*=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator/=(IEEEBinary16& x, IEEEBinary16 y); + IEEEBinary16& operator+=(IEEEBinary16& x, float y); + IEEEBinary16& operator-=(IEEEBinary16& x, float y); + IEEEBinary16& operator*=(IEEEBinary16& x, float y); + IEEEBinary16& operator/=(IEEEBinary16& x, float y); + +} + +namespace std +{ + inline gte::IEEEBinary16 abs(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::abs((float)x); + } + + inline gte::IEEEBinary16 acos(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::acos((float)x); + } + + inline gte::IEEEBinary16 acosh(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::acosh((float)x); +#endif + } + + inline gte::IEEEBinary16 asin(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::asin((float)x); + } + + inline gte::IEEEBinary16 asinh(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::asinh((float)x); +#endif + } + + inline gte::IEEEBinary16 atan(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::atan((float)x); + } + + inline gte::IEEEBinary16 atanh(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::atanh((float)x); +#endif + } + + inline gte::IEEEBinary16 atan2(gte::IEEEBinary16 y, gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::atan2((float)y, (float)x); + } + + inline gte::IEEEBinary16 ceil(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::ceil((float)x); + } + + inline gte::IEEEBinary16 cos(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::cos((float)x); + } + + inline gte::IEEEBinary16 cosh(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::cosh((float)x); + } + + inline gte::IEEEBinary16 exp(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::exp((float)x); + } + + inline gte::IEEEBinary16 exp2(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::exp2((float)x); +#endif + } + + inline gte::IEEEBinary16 floor(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::floor((float)x); + } + + inline gte::IEEEBinary16 fmod(gte::IEEEBinary16 x, gte::IEEEBinary16 y) + { + return (gte::IEEEBinary16)std::fmod((float)x, (float)y); + } + + inline gte::IEEEBinary16 frexp(gte::IEEEBinary16 x, int* exponent) + { + return (gte::IEEEBinary16)std::frexp((float)x, exponent); + } + + inline gte::IEEEBinary16 ldexp(gte::IEEEBinary16 x, int exponent) + { + return (gte::IEEEBinary16)std::ldexp((float)x, exponent); + } + + inline gte::IEEEBinary16 log(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::log((float)x); + } + + inline gte::IEEEBinary16 log2(gte::IEEEBinary16 x) + { +#if defined(__ANDROID__) + checkf(false, TEXT("not supported on Android")); + return (gte::IEEEBinary16)0.0f; +#else + return (gte::IEEEBinary16)std::log2((float)x); +#endif + } + + inline gte::IEEEBinary16 log10(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::log10((float)x); + } + + inline gte::IEEEBinary16 pow(gte::IEEEBinary16 x, gte::IEEEBinary16 y) + { + return (gte::IEEEBinary16)std::pow((float)x, (float)y); + } + + inline gte::IEEEBinary16 sin(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::sin((float)x); + } + + inline gte::IEEEBinary16 sinh(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::sinh((float)x); + } + + inline gte::IEEEBinary16 sqrt(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::sqrt((float)x); + } + + inline gte::IEEEBinary16 tan(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::tan((float)x); + } + + inline gte::IEEEBinary16 tanh(gte::IEEEBinary16 x) + { + return (gte::IEEEBinary16)std::tanh((float)x); + } +} + +namespace gte +{ + inline IEEEBinary16 atandivpi(IEEEBinary16 x) + { + return (IEEEBinary16)atandivpi((float)x); + } + + inline IEEEBinary16 atan2divpi(IEEEBinary16 y, IEEEBinary16 x) + { + return (IEEEBinary16)atan2divpi((float)y, (float)x); + } + + inline IEEEBinary16 clamp(IEEEBinary16 x, IEEEBinary16 xmin, IEEEBinary16 xmax) + { + return (IEEEBinary16)clamp((float)x, (float)xmin, (float)xmax); + } + + inline IEEEBinary16 cospi(IEEEBinary16 x) + { + return (IEEEBinary16)cospi((float)x); + } + + inline IEEEBinary16 exp10(IEEEBinary16 x) + { + return (IEEEBinary16)exp10((float)x); + } + + inline IEEEBinary16 invsqrt(IEEEBinary16 x) + { + return (IEEEBinary16)invsqrt((float)x); + } + + inline int isign(IEEEBinary16 x) + { + return isign((float)x); + } + + inline IEEEBinary16 saturate(IEEEBinary16 x) + { + return (IEEEBinary16)saturate((float)x); + } + + inline IEEEBinary16 sign(IEEEBinary16 x) + { + return (IEEEBinary16)sign((float)x); + } + + inline IEEEBinary16 sinpi(IEEEBinary16 x) + { + return (IEEEBinary16)sinpi((float)x); + } + + inline IEEEBinary16 sqr(IEEEBinary16 x) + { + return (IEEEBinary16)sqr((float)x); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIndexAttribute.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIndexAttribute.h new file mode 100644 index 000000000000..a66f03a51cc6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIndexAttribute.h @@ -0,0 +1,102 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.1 (2016/11/14) + +#pragma once + +#include +#include +#include + +// The IndexAttribute class represents an array of triples of indices into a +// vertex array for an indexed triangle mesh. For now, the source must be +// either uint16_t or uint32_t. + +namespace gte +{ + +struct IndexAttribute +{ + // Construction. + inline IndexAttribute(); + inline IndexAttribute(void* inSource, size_t inSize); + + // Triangle access. + inline void SetTriangle(uint32_t t, uint32_t v0, uint32_t v1, uint32_t v2); + inline void GetTriangle(uint32_t t, uint32_t& v0, uint32_t& v1, uint32_t& v2) const; + + // The source pointer must be 4-byte aligned, which is guaranteed on + // 32-bit and 64-bit architectures. The size is the number of bytes + // per index. + void* source; + size_t size; +}; + + +inline IndexAttribute::IndexAttribute() + : + source(nullptr), + size(0) +{ +} + +inline IndexAttribute::IndexAttribute(void* inSource, size_t inSize) + : + source(inSource), + size(inSize) +{ +} + +inline void IndexAttribute::SetTriangle(uint32_t t, uint32_t v0, uint32_t v1, uint32_t v2) +{ + if (size == sizeof(uint32_t)) + { + uint32_t* index = reinterpret_cast(source) + 3 * t; + index[0] = v0; + index[1] = v1; + index[2] = v2; + return; + } + + if (size == sizeof(uint16_t)) + { + uint16_t* index = reinterpret_cast(source) + 3 * t; + index[0] = static_cast(v0); + index[1] = static_cast(v1); + index[2] = static_cast(v2); + return; + } + + // Unsupported type. +} + +inline void IndexAttribute::GetTriangle(uint32_t t, uint32_t& v0, uint32_t& v1, uint32_t& v2) const +{ + if (size == sizeof(uint32_t)) + { + uint32_t* index = reinterpret_cast(source) + 3 * t; + v0 = index[0]; + v1 = index[1]; + v2 = index[2]; + return; + } + + if (size == sizeof(uint16_t)) + { + uint16_t* index = reinterpret_cast(source) + 3 * t; + v0 = static_cast(index[0]); + v1 = static_cast(index[1]); + v2 = static_cast(index[2]); + return; + } + + // Unsupported type. + v0 = 0; + v1 = 0; + v2 = 0; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntegration.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntegration.h new file mode 100644 index 000000000000..78f515c85d69 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntegration.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class Integration +{ +public: + // A simple algorithm, but slow to converge as the number of samples is + // increased. The 'numSamples' needs to be two or larger. + static Real TrapezoidRule(int numSamples, Real a, Real b, + std::function const& integrand); + + // The trapezoid rule is used to generate initial estimates, but then + // Richardson extrapolation is used to improve the estimates. This is + // preferred over TrapezoidRule. The 'order' must be positive. + static Real Romberg(int order, Real a, Real b, + std::function const& integrand); + + // Gaussian quadrature estimates the integral of a function f(x) defined + // on [-1,1] using + // integral_{-1}^{1} f(t) dt = sum_{i=0}^{n-1} c[i]*f(r[i]) + // where r[i] are the roots to the Legendre polynomial p(t) of degree n + // and c[i] = integral_{-1}^{1} prod_{j=0,j!=i} (t-r[j]/(r[i]-r[j]) dt. + // To integrate over [a,b], a transformation to [-1,1] is applied + // internally: x - ((b-a)*t + (b+a))/2. The Legendre polynomials are + // generated by + // P[0](x) = 1, P[1](x) = x, + // P[k](x) = ((2*k-1)*x*P[k-1](x) - (k-1)*P[k-2](x))/k, k >= 2 + // Implementing the polynomial generation is simple, and computing the + // roots requires a numerical method for finding polynomial roots. The + // challenging task is to develop an efficient algorithm for computing + // the coefficients c[i] for a specified degree. The 'degree' must be + // two or larger. + + static void ComputeQuadratureInfo(int degree, std::vector& roots, + std::vector& coefficients); + + static Real GaussianQuadrature(std::vector const& roots, + std::vectorconst & coefficients, Real a, Real b, + std::function const& integrand); +}; + + +template +Real Integration::TrapezoidRule(int numSamples, Real a, Real b, + std::function const& integrand) +{ + Real h = (b - a) / (Real)(numSamples - 1); + Real result = ((Real)0.5) * (integrand(a) + integrand(b)); + for (int i = 1; i <= numSamples - 2; ++i) + { + result += integrand(a + i*h); + } + result *= h; + return result; +} + +template +Real Integration::Romberg(int order, Real a, Real b, + std::function const& integrand) +{ + Real const half = (Real)0.5; + std::vector> rom(order); + Real h = b - a; + rom[0][0] = half * h * (integrand(a) + integrand(b)); + for (int i0 = 2, p0 = 1; i0 <= order; ++i0, p0 *= 2, h *= half) + { + // Approximations via the trapezoid rule. + Real sum = (Real)0; + int i1; + for (i1 = 1; i1 <= p0; ++i1) + { + sum += integrand(a + h * (i1 - half)); + } + + // Richardson extrapolation. + rom[0][1] = half * (rom[0][0] + h * sum); + for (int i2 = 1, p2 = 4; i2 < i0; ++i2, p2 *= 4) + { + rom[i2][1] = (p2*rom[i2 - 1][1] - rom[i2 - 1][0]) / (p2 - 1); + } + + for (i1 = 0; i1 < i0; ++i1) + { + rom[i1][0] = rom[i1][1]; + } + } + + Real result = rom[order - 1][0]; + return result; +} + +template +void Integration::ComputeQuadratureInfo(int degree, + std::vector& roots, std::vector& coefficients) +{ + Real const zero = (Real)0; + Real const one = (Real)1; + Real const half = (Real)0.5; + + std::vector> poly(degree + 1); + + poly[0].resize(1); + poly[0][0] = one; + + poly[1].resize(2); + poly[1][0] = zero; + poly[1][1] = one; + + for (int n = 2; n <= degree; ++n) + { + Real mult0 = (Real)(n - 1) / (Real)n; + Real mult1 = (Real)(2 * n - 1) / (Real)n; + + poly[n].resize(n + 1); + poly[n][0] = -mult0 * poly[n - 2][0]; + for (int i = 1; i <= n - 2; ++i) + { + poly[n][i] = mult1 * poly[n - 1][i - 1] - mult0 * poly[n - 2][i]; + } + poly[n][n - 1] = mult1 * poly[n - 1][n - 2]; + poly[n][n] = mult1 * poly[n - 1][n - 1]; + } + + roots.resize(degree); + RootsPolynomial::Find(degree, &poly[degree][0], 2048, &roots[0]); + + coefficients.resize(roots.size()); + size_t n = roots.size() - 1; + std::vector subroots(n); + for (size_t i = 0; i < roots.size(); ++i) + { + Real denominator = (Real)1; + for (size_t j = 0, k = 0; j < roots.size(); ++j) + { + if (j != i) + { + subroots[k++] = roots[j]; + denominator *= roots[i] - roots[j]; + } + } + + std::array delta = + { + -one - subroots.back(), + +one - subroots.back() + }; + + std::vector> weights(n); + weights[0][0] = half * delta[0] * delta[0]; + weights[0][1] = half * delta[1] * delta[1]; + for (size_t k = 1; k < n; ++k) + { + Real dk = (Real)k; + Real mult = -dk / (dk + (Real)2); + weights[k][0] = mult * delta[0] * weights[k - 1][0]; + weights[k][1] = mult * delta[1] * weights[k - 1][1]; + } + + struct Info + { + int numBits; + std::array product; + }; + + int numElements = (1 << static_cast(n - 1)); + std::vector info(numElements); + info[0].numBits = 0; + info[0].product[0] = one; + info[0].product[1] = one; + for (int ipow = 1, r = 0; ipow < numElements; ipow <<= 1, ++r) + { + info[ipow].numBits = 1; + info[ipow].product[0] = -one - subroots[r]; + info[ipow].product[1] = +one - subroots[r]; + for (int m = 1, j = ipow + 1; m < ipow; ++m, ++j) + { + info[j].numBits = info[m].numBits + 1; + info[j].product[0] = + info[ipow].product[0] * info[m].product[0]; + info[j].product[1] = + info[ipow].product[1] * info[m].product[1]; + } + } + + std::vector> sum(n); + std::array zero2 = { zero, zero }; + std::fill(sum.begin(), sum.end(), zero2); + for (size_t k = 0; k < info.size(); ++k) + { + sum[info[k].numBits][0] += info[k].product[0]; + sum[info[k].numBits][1] += info[k].product[1]; + } + + std::array total = zero2; + for (size_t k = 0; k < n; ++k) + { + total[0] += weights[n - 1 - k][0] * sum[k][0]; + total[1] += weights[n - 1 - k][1] * sum[k][1]; + } + + coefficients[i] = (total[1] - total[0]) / denominator; + } +} + +template +Real Integration::GaussianQuadrature(std::vector const& roots, + std::vectorconst & coefficients, Real a, Real b, + std::function const& integrand) +{ + Real const half = (Real)0.5; + Real radius = half * (b - a); + Real center = half * (b + a); + Real result = (Real)0; + for (size_t i = 0; i < roots.size(); ++i) + { + result += coefficients[i] * integrand(radius*roots[i] + center); + } + result *= radius; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkima1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkima1.h new file mode 100644 index 000000000000..f9f977a2cc13 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkima1.h @@ -0,0 +1,181 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class IntpAkima1 +{ +protected: + // Construction (abstract base class). + IntpAkima1(int quantity, Real const* F); +public: + // Abstract base class. + virtual ~IntpAkima1(); + + // Member access. + inline int GetQuantity() const; + inline Real const* GetF() const; + virtual Real GetXMin() const = 0; + virtual Real GetXMax() const = 0; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax. The first operator is for function + // evaluation. The second operator is for function or derivative + // evaluations. The 'order' argument is the order of the derivative or + // zero for the function itself. + Real operator()(Real x) const; + Real operator()(int order, Real x) const; + +protected: + class Polynomial + { + public: + // P(x) = c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + inline Real& operator[](int i); + Real operator()(Real x) const; + Real operator()(int order, Real x) const; + + private: + Real mCoeff[4]; + }; + + Real ComputeDerivative(Real* slope) const; + virtual void Lookup(Real x, int& index, Real& dx) const = 0; + + int mQuantity; + Real const* mF; + std::vector mPoly; +}; + + +template +IntpAkima1::IntpAkima1(int quantity, Real const* F) + : + mQuantity(quantity), + mF(F) +{ + // At least three data points are needed to construct the estimates of + // the boundary derivatives. + LogAssert(mQuantity >= 3 && F, "Invalid input."); + + mPoly.resize(mQuantity - 1); +} + +template +IntpAkima1::~IntpAkima1() +{ +} + +template inline +int IntpAkima1::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpAkima1::GetF() const +{ + return mF; +} + +template +Real IntpAkima1::operator()(Real x) const +{ + x = std::min(std::max(x, GetXMin()), GetXMax()); + int index; + Real dx; + Lookup(x, index, dx); + return mPoly[index](dx); +} + +template +Real IntpAkima1::operator()(int order, Real x) const +{ + x = std::min(std::max(x, GetXMin()), GetXMax()); + int index; + Real dx; + Lookup(x, index, dx); + return mPoly[index](order, dx); +} + +template +Real IntpAkima1::ComputeDerivative(Real* slope) const +{ + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::abs(slope[3] - slope[2]); + Real ad1 = std::abs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5) * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } +} + +template inline +Real& IntpAkima1::Polynomial::operator[](int i) +{ + return mCoeff[i]; +} + +template +Real IntpAkima1::Polynomial::operator()(Real x) const +{ + return mCoeff[0] + x * (mCoeff[1] + x * (mCoeff[2] + x * mCoeff[3])); +} + +template +Real IntpAkima1::Polynomial::operator()(int order, Real x) const +{ + switch (order) + { + case 0: + return mCoeff[0] + x * (mCoeff[1] + x * (mCoeff[2] + x * mCoeff[3])); + case 1: + return mCoeff[1] + x * (((Real)2)*mCoeff[2] + x * ((Real)3) * mCoeff[3]); + case 2: + return ((Real)2) * mCoeff[2] + x * ((Real)6) * mCoeff[3]; + case 3: + return ((Real)6) * mCoeff[3]; + } + + return (Real)0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaNonuniform1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaNonuniform1.h new file mode 100644 index 000000000000..65883f1b3a49 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaNonuniform1.h @@ -0,0 +1,128 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class IntpAkimaNonuniform1 : public IntpAkima1 +{ +public: + // Construction. The interpolator is for arbitrarily spaced x-values. + // The input arrays must have 'quantity' elements and the X[] array + // must store increasing values: X[i + 1] > X[i] for all i. + IntpAkimaNonuniform1(int quantity, Real const* X, Real const* F); + + // Member access. + Real const* GetX() const; + virtual Real GetXMin() const override; + virtual Real GetXMax() const override; + +protected: + virtual void Lookup(Real x, int& index, Real& dx) const override; + + Real const* mX; +}; + +template +IntpAkimaNonuniform1::IntpAkimaNonuniform1(int quantity, Real const* X, + Real const* F) + : + IntpAkima1(quantity, F), + mX(X) +{ +#if !defined(GTE_NO_LOGGER) + LogAssert(X != nullptr, "Invalid input."); + for (int j0 = 0, j1 = 1; j1 < quantity; ++j0, ++j1) + { + LogAssert(X[j1] > X[j0], "Invalid input."); + } +#endif + + // Compute slopes. + std::vector slope(quantity + 3); + int i, ip1, ip2; + for (i = 0, ip1 = 1, ip2 = 2; i < quantity - 1; ++i, ++ip1, ++ip2) + { + Real dx = X[ip1] - X[i]; + Real df = F[ip1] - F[i]; + slope[ip2] = df / dx; + } + + slope[1] = ((Real)2) * slope[2] - slope[3]; + slope[0] = ((Real)2) * slope[1] - slope[2]; + slope[quantity + 1] = ((Real)2) * slope[quantity] - slope[quantity - 1]; + slope[quantity + 2] = ((Real)2) * slope[quantity + 1] - slope[quantity]; + + // Construct derivatives. + std::vector FDer(quantity); + for (i = 0; i < quantity; ++i) + { + FDer[i] = this->ComputeDerivative(&slope[i]); + } + + // Construct polynomials. + for (i = 0, ip1 = 1; i < quantity - 1; ++i, ++ip1) + { + auto& poly = this->mPoly[i]; + + Real F0 = F[i]; + Real F1 = F[ip1]; + Real FDer0 = FDer[i]; + Real FDer1 = FDer[ip1]; + Real df = F1 - F0; + Real dx = X[ip1] - X[i]; + Real dx2 = dx * dx; + Real dx3 = dx2 * dx; + + poly[0] = F0; + poly[1] = FDer0; + poly[2] = (((Real)3) * df - dx * (FDer1 + ((Real)2) * FDer0)) / dx2; + poly[3] = (dx * (FDer0 + FDer1) - ((Real)2) * df) / dx3; + } +} + +template +Real const* IntpAkimaNonuniform1::GetX() const +{ + return mX; +} + +template +Real IntpAkimaNonuniform1::GetXMin() const +{ + return mX[0]; +} + +template +Real IntpAkimaNonuniform1::GetXMax() const +{ + return mX[this->mQuantity - 1]; +} + +template +void IntpAkimaNonuniform1::Lookup(Real x, int& index, Real& dx) const +{ + // The caller has ensured that mXMin <= x <= mXMax. + for (index = 0; index + 1 < this->mQuantity; ++index) + { + if (x < mX[index + 1]) + { + dx = x - mX[index]; + return; + } + } + + --index; + dx = x - mX[index]; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform1.h new file mode 100644 index 000000000000..62ccae41511f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform1.h @@ -0,0 +1,130 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class IntpAkimaUniform1 : public IntpAkima1 +{ +public: + // Construction and destruction. The interpolator is for uniformly + // spaced x-values. + virtual ~IntpAkimaUniform1(); + IntpAkimaUniform1(int quantity, Real xMin, Real xSpacing, Real const* F); + + // Member access. + inline virtual Real GetXMin() const override; + inline virtual Real GetXMax() const override; + inline Real GetXSpacing() const; + +protected: + virtual void Lookup(Real x, int& index, Real& dx) const override; + + Real mXMin, mXMax, mXSpacing; +}; + + +template +IntpAkimaUniform1::~IntpAkimaUniform1() +{ +} + +template +IntpAkimaUniform1::IntpAkimaUniform1(int quantity, Real xMin, + Real xSpacing, Real const* F) + : + IntpAkima1(quantity, F), + mXMin(xMin), + mXSpacing(xSpacing) +{ + LogAssert(mXSpacing > (Real)0, "Spacing must be positive."); + + mXMax = mXMin + mXSpacing * static_cast(quantity - 1); + + // Compute slopes. + Real invDX = ((Real)1) / mXSpacing; + std::vector slope(quantity + 3); + int i, ip1, ip2; + for (i = 0, ip1 = 1, ip2 = 2; i < quantity - 1; ++i, ++ip1, ++ip2) + { + slope[ip2] = (this->mF[ip1] - this->mF[i]) * invDX; + } + + slope[1] = ((Real)2) * slope[2] - slope[3]; + slope[0] = ((Real)2) * slope[1] - slope[2]; + slope[quantity + 1] = ((Real)2) * slope[quantity] - slope[quantity - 1]; + slope[quantity + 2] = ((Real)2) * slope[quantity + 1] - slope[quantity]; + + // Construct derivatives. + std::vector FDer(quantity); + for (i = 0; i < quantity; ++i) + { + FDer[i] = this->ComputeDerivative(&slope[i]); + } + + // Construct polynomials. + Real invDX2 = ((Real)1) / (mXSpacing * mXSpacing); + Real invDX3 = invDX2 / mXSpacing; + for (i = 0, ip1 = 1; i < quantity - 1; ++i, ++ip1) + { + auto& poly = this->mPoly[i]; + + Real F0 = F[i]; + Real F1 = F[ip1]; + Real df = F1 - F0; + Real FDer0 = FDer[i]; + Real FDer1 = FDer[ip1]; + + poly[0] = F0; + poly[1] = FDer0; + poly[2] = (((Real)3) * df - mXSpacing * (FDer1 + ((Real)2) * FDer0)) * invDX2; + poly[3] = (mXSpacing * (FDer0 + FDer1) - ((Real)2) * df) * invDX3; + } +} + +template inline +Real IntpAkimaUniform1::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpAkimaUniform1::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpAkimaUniform1::GetXSpacing() const +{ + return mXSpacing; +} + +template +void IntpAkimaUniform1::Lookup(Real x, int& index, Real& dx) const +{ + // The caller has ensured that mXMin <= x <= mXMax. + for (index = 0; index + 1 < this->mQuantity; ++index) + { + if (x < mXMin + mXSpacing * (index + 1)) + { + dx = x - (mXMin + mXSpacing * index); + return; + } + } + + --index; + dx = x - (mXMin + mXSpacing * index); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform2.h new file mode 100644 index 000000000000..ea380b2fee90 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform2.h @@ -0,0 +1,627 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. + +namespace gte +{ + +template +class IntpAkimaUniform2 +{ +public: + // Construction and destruction. + ~IntpAkimaUniform2(); + IntpAkimaUniform2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first operator + // is for function evaluation. The second operator is for function or + // derivative evaluations. The xOrder argument is the order of the + // x-derivative and the yOrder argument is the order of the y-derivative. + // Both orders are zero to get the function value itself. + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + +private: + class Polynomial + { + public: + Polynomial(); + + // P(x,y) = (1,x,x^2,x^3)*A*(1,y,y^2,y^3). The matrix term A[ix][iy] + // corresponds to the polynomial term x^{ix} y^{iy}. + Real& A(int ix, int iy); + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + + private: + Real mCoeff[4][4]; + }; + + // Support for construction. + void GetFX(Array2 const& F, Array2& FX); + void GetFY(Array2 const& F, Array2& FY); + void GetFXY(Array2 const& F, Array2& FXY); + void GetPolynomials(Array2 const& F, Array2 const& FX, + Array2 const& FY, Array2 const& FXY); + Real ComputeDerivative(Real const* slope) const; + void Construct(Polynomial& poly, Real const F[2][2], Real const FX[2][2], + Real const FY[2][2], Real const FXY[2][2]); + + // Support for evaluation. + void XLookup(Real x, int& xIndex, Real& dx) const; + void YLookup(Real y, int& yIndex, Real& dy) const; + + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing; + Real mYMin, mYMax, mYSpacing; + Real const* mF; + Array2 mPoly; +}; + + +template +IntpAkimaUniform2::~IntpAkimaUniform2() +{ +} + +template +IntpAkimaUniform2::IntpAkimaUniform2(int xBound, int yBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound * yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F), + mPoly(xBound - 1, mYBound - 1) +{ + // At least a 3x3 block of data points is needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mF, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + + // Create a 2D wrapper for the 1D samples. + Array2 Fmap(mXBound, mYBound, const_cast(mF)); + + // Construct first-order derivatives. + Array2 FX(mXBound, mYBound), FY(mXBound, mYBound); + GetFX(Fmap, FX); + GetFY(Fmap, FY); + + // Construct second-order derivatives. + Array2 FXY(mXBound, mYBound); + GetFXY(Fmap, FXY); + + // Construct polynomials. + GetPolynomials(Fmap, FX, FY, FXY); +} + +template inline +int IntpAkimaUniform2::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpAkimaUniform2::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpAkimaUniform2::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpAkimaUniform2::GetF() const +{ + return mF; +} + +template inline +Real IntpAkimaUniform2::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpAkimaUniform2::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpAkimaUniform2::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpAkimaUniform2::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpAkimaUniform2::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpAkimaUniform2::GetYSpacing() const +{ + return mYSpacing; +} + +template +Real IntpAkimaUniform2::operator()(Real x, Real y) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + int ix, iy; + Real dx, dy; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + return mPoly[iy][ix](dx, dy); +} + +template +Real IntpAkimaUniform2::operator()(int xOrder, int yOrder, Real x, + Real y) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + int ix, iy; + Real dx, dy; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + return mPoly[iy][ix](xOrder, yOrder, dx, dy); +} + +template +void IntpAkimaUniform2::GetFX(Array2 const& F, Array2& FX) +{ + Array2 slope(mXBound + 3, mYBound); + Real invDX = ((Real)1) / mXSpacing; + int ix, iy; + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound - 1; ++ix) + { + slope[iy][ix + 2] = (F[iy][ix + 1] - F[iy][ix]) * invDX; + } + + slope[iy][1] = ((Real)2) * slope[iy][2] - slope[iy][3]; + slope[iy][0] = ((Real)2) * slope[iy][1] - slope[iy][2]; + slope[iy][mXBound + 1] = ((Real)2) * slope[iy][mXBound] - slope[iy][mXBound - 1]; + slope[iy][mXBound + 2] = ((Real)2) * slope[iy][mXBound + 1] - slope[iy][mXBound]; + } + + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + FX[iy][ix] = ComputeDerivative(slope[iy] + ix); + } + } +} + +template +void IntpAkimaUniform2::GetFY(Array2 const& F, Array2& FY) +{ + Array2 slope(mYBound + 3, mXBound); + Real invDY = ((Real)1) / mYSpacing; + int ix, iy; + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound - 1; ++iy) + { + slope[ix][iy + 2] = (F[iy + 1][ix] - F[iy][ix]) * invDY; + } + + slope[ix][1] = ((Real)2) * slope[ix][2] - slope[ix][3]; + slope[ix][0] = ((Real)2) * slope[ix][1] - slope[ix][2]; + slope[ix][mYBound + 1] = ((Real)2) * slope[ix][mYBound] - slope[ix][mYBound - 1]; + slope[ix][mYBound + 2] = ((Real)2) * slope[ix][mYBound + 1] - slope[ix][mYBound]; + } + + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound; ++iy) + { + FY[iy][ix] = ComputeDerivative(slope[ix] + iy); + } + } +} + +template +void IntpAkimaUniform2::GetFXY(Array2 const& F, Array2& FXY) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int ix, iy; + + Real invDXDY = ((Real)1) / (mXSpacing * mYSpacing); + + // corners + FXY[0][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[0][0] + - ((Real)12)*F[0][1] + + ((Real)3)*F[0][2] + - ((Real)12)*F[1][0] + + ((Real)16)*F[1][1] + - ((Real)4)*F[1][2] + + ((Real)3)*F[2][0] + - ((Real)4)*F[2][1] + + F[2][2]); + + FXY[0][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[0][ix0] + - ((Real)12)*F[0][ix1] + + ((Real)3)*F[0][ix2] + - ((Real)12)*F[1][ix0] + + ((Real)16)*F[1][ix1] + - ((Real)4)*F[1][ix2] + + ((Real)3)*F[2][ix0] + - ((Real)4)*F[2][ix1] + + F[2][ix2]); + + FXY[yBoundM1][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iy0][0] + - ((Real)12)*F[iy0][1] + + ((Real)3)*F[iy0][2] + - ((Real)12)*F[iy1][0] + + ((Real)16)*F[iy1][1] + - ((Real)4)*F[iy1][2] + + ((Real)3)*F[iy2][0] + - ((Real)4)*F[iy2][1] + + F[iy2][2]); + + FXY[yBoundM1][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iy0][ix0] + - ((Real)12)*F[iy0][ix1] + + ((Real)3)*F[iy0][ix2] + - ((Real)12)*F[iy1][ix0] + + ((Real)16)*F[iy1][ix1] + - ((Real)4)*F[iy1][ix2] + + ((Real)3)*F[iy2][ix0] + - ((Real)4)*F[iy2][ix1] + + F[iy2][ix2]); + + // x-edges + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[0][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[0][ix - 1] - F[0][ix + 1]) + - ((Real)4)*(F[1][ix - 1] - F[1][ix + 1]) + + (F[2][ix - 1] - F[2][ix + 1])); + + FXY[yBoundM1][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iy0][ix - 1] - F[iy0][ix + 1]) + - ((Real)4)*(F[iy1][ix - 1] - F[iy1][ix + 1]) + + (F[iy2][ix - 1] - F[iy2][ix + 1])); + } + + // y-edges + for (iy = 1; iy < yBoundM1; ++iy) + { + FXY[iy][0] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iy - 1][0] - F[iy + 1][0]) + - ((Real)4)*(F[iy - 1][1] - F[iy + 1][1]) + + (F[iy - 1][2] - F[iy + 1][2])); + + FXY[iy][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iy - 1][ix0] - F[iy + 1][ix0]) + - ((Real)4)*(F[iy - 1][ix1] - F[iy + 1][ix1]) + + (F[iy - 1][ix2] - F[iy + 1][ix2])); + } + + // interior + for (iy = 1; iy < yBoundM1; ++iy) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iy][ix] = ((Real)0.25)*invDXDY*(F[iy - 1][ix - 1] - + F[iy - 1][ix + 1] - F[iy + 1][ix - 1] + F[iy + 1][ix + 1]); + } + } +} + +template +void IntpAkimaUniform2::GetPolynomials(Array2 const& F, + Array2 const& FX, Array2 const& FY, Array2 const& FXY) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + for (int iy = 0; iy < yBoundM1; ++iy) + { + for (int ix = 0; ix < xBoundM1; ++ix) + { + // Note the 'transposing' of the 2x2 blocks (to match notation + // used in the polynomial definition). + Real G[2][2] = + { + { F[iy][ix], F[iy + 1][ix] }, + { F[iy][ix + 1], F[iy + 1][ix + 1] } + }; + + Real GX[2][2] = + { + { FX[iy][ix], FX[iy + 1][ix] }, + { FX[iy][ix + 1], FX[iy + 1][ix + 1] } + }; + + Real GY[2][2] = + { + { FY[iy][ix], FY[iy + 1][ix] }, + { FY[iy][ix + 1], FY[iy + 1][ix + 1] } + }; + + Real GXY[2][2] = + { + { FXY[iy][ix], FXY[iy + 1][ix] }, + { FXY[iy][ix + 1], FXY[iy + 1][ix + 1] } + }; + + Construct(mPoly[iy][ix], G, GX, GY, GXY); + } + } +} + +template +Real IntpAkimaUniform2::ComputeDerivative(Real const* slope) const +{ + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::abs(slope[3] - slope[2]); + Real ad1 = std::abs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5) * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } +} + +template +void IntpAkimaUniform2::Construct(Polynomial& poly, Real const F[2][2], + Real const FX[2][2], Real const FY[2][2], Real const FXY[2][2]) +{ + Real dx = mXSpacing; + Real dy = mYSpacing; + Real invDX = ((Real)1) / dx, invDX2 = invDX*invDX; + Real invDY = ((Real)1) / dy, invDY2 = invDY*invDY; + Real b0, b1, b2, b3; + + poly.A(0, 0) = F[0][0]; + poly.A(1, 0) = FX[0][0]; + poly.A(0, 1) = FY[0][0]; + poly.A(1, 1) = FXY[0][0]; + + b0 = (F[1][0] - poly(0, 0, dx, (Real)0))*invDX2; + b1 = (FX[1][0] - poly(1, 0, dx, (Real)0))*invDX; + poly.A(2, 0) = ((Real)3)*b0 - b1; + poly.A(3, 0) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (F[0][1] - poly(0, 0, (Real)0, dy))*invDY2; + b1 = (FY[0][1] - poly(0, 1, (Real)0, dy))*invDY; + poly.A(0, 2) = ((Real)3)*b0 - b1; + poly.A(0, 3) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FY[1][0] - poly(0, 1, dx, (Real)0))*invDX2; + b1 = (FXY[1][0] - poly(1, 1, dx, (Real)0))*invDX; + poly.A(2, 1) = ((Real)3)*b0 - b1; + poly.A(3, 1) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FX[0][1] - poly(1, 0, (Real)0, dy))*invDY2; + b1 = (FXY[0][1] - poly(1, 1, (Real)0, dy))*invDY; + poly.A(1, 2) = ((Real)3)*b0 - b1; + poly.A(1, 3) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (F[1][1] - poly(0, 0, dx, dy))*invDX2*invDY2; + b1 = (FX[1][1] - poly(1, 0, dx, dy))*invDX*invDY2; + b2 = (FY[1][1] - poly(0, 1, dx, dy))*invDX2*invDY; + b3 = (FXY[1][1] - poly(1, 1, dx, dy))*invDX*invDY; + poly.A(2, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDY; + poly.A(3, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDY; +} + +template +void IntpAkimaUniform2::XLookup(Real x, int& xIndex, Real& dx) const +{ + for (xIndex = 0; xIndex + 1 < mXBound; ++xIndex) + { + if (x < mXMin + mXSpacing*(xIndex + 1)) + { + dx = x - (mXMin + mXSpacing*xIndex); + return; + } + } + + --xIndex; + dx = x - (mXMin + mXSpacing*xIndex); +} + +template +void IntpAkimaUniform2::YLookup(Real y, int& yIndex, Real& dy) const +{ + for (yIndex = 0; yIndex + 1 < mYBound; ++yIndex) + { + if (y < mYMin + mYSpacing*(yIndex + 1)) + { + dy = y - (mYMin + mYSpacing*yIndex); + return; + } + } + + yIndex--; + dy = y - (mYMin + mYSpacing*yIndex); +} + +template +IntpAkimaUniform2::Polynomial::Polynomial() +{ + memset(&mCoeff[0][0], 0, 16 * sizeof(Real)); +} + +template +Real& IntpAkimaUniform2::Polynomial::A(int ix, int iy) +{ + return mCoeff[ix][iy]; +} + +template +Real IntpAkimaUniform2::Polynomial::operator()(Real x, Real y) const +{ + Real B[4]; + for (int i = 0; i <= 3; ++i) + { + B[i] = mCoeff[i][0] + y*(mCoeff[i][1] + y*(mCoeff[i][2] + y*mCoeff[i][3])); + } + + return B[0] + x*(B[1] + x*(B[2] + x*B[3])); +} + +template +Real IntpAkimaUniform2::Polynomial::operator()(int xOrder, int yOrder, + Real x, Real y) const +{ + Real xPow[4]; + switch (xOrder) + { + case 0: + xPow[0] = (Real)1; + xPow[1] = x; + xPow[2] = x * x; + xPow[3] = x * x * x; + break; + case 1: + xPow[0] = (Real)0; + xPow[1] = (Real)1; + xPow[2] = ((Real)2) * x; + xPow[3] = ((Real)3) * x * x; + break; + case 2: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)2; + xPow[3] = ((Real)6) * x; + break; + case 3: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)0; + xPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real yPow[4]; + switch (yOrder) + { + case 0: + yPow[0] = (Real)1; + yPow[1] = y; + yPow[2] = y * y; + yPow[3] = y * y * y; + break; + case 1: + yPow[0] = (Real)0; + yPow[1] = (Real)1; + yPow[2] = ((Real)2) * y; + yPow[3] = ((Real)3) * y * y; + break; + case 2: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)2; + yPow[3] = ((Real)6) * y; + break; + case 3: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)0; + yPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real p = (Real)0; + for (int iy = 0; iy <= 3; ++iy) + { + for (int ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy] * xPow[ix] * yPow[iy]; + } + } + + return p; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform3.h new file mode 100644 index 000000000000..ab3517f49261 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpAkimaUniform3.h @@ -0,0 +1,1503 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. + +namespace gte +{ + +template +class IntpAkimaUniform3 +{ +public: + // Construction and destruction. + ~IntpAkimaUniform3(); + IntpAkimaUniform3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetZBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + inline Real GetZMin() const; + inline Real GetZMax() const; + inline Real GetZSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const; + +private: + class Polynomial + { + public: + Polynomial(); + + // P(x,y,z) = sum_{i=0}^3 sum_{j=0}^3 sum_{k=0}^3 a_{ijk} x^i y^j z^k. + // The tensor term A[ix][iy][iz] corresponds to the polynomial term + // x^{ix} y^{iy} z^{iz}. + Real& A(int ix, int iy, int iz); + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const; + + private: + Real mCoeff[4][4][4]; + }; + + // Support for construction. + void GetFX(Array3 const& F, Array3& FX); + void GetFY(Array3 const& F, Array3& FY); + void GetFZ(Array3 const& F, Array3& FZ); + void GetFXY(Array3 const& F, Array3& FXY); + void GetFXZ(Array3 const& F, Array3& FXZ); + void GetFYZ(Array3 const& F, Array3& FYZ); + void GetFXYZ(Array3 const& F, Array3& FXYZ); + void GetPolynomials(Array3 const& F, Array3 const& FX, + Array3 const& FY, Array3 const& FZ, Array3 const& FXY, + Array3 const& FXZ, Array3 const& FYZ, Array3 const& FXYZ); + + Real ComputeDerivative(Real const* slope) const; + void Construct(Polynomial& poly, Real const F[2][2][2], + Real const FX[2][2][2], Real const FY[2][2][2], + Real const FZ[2][2][2], Real const FXY[2][2][2], + Real const FXZ[2][2][2], Real const FYZ[2][2][2], + Real const FXYZ[2][2][2]); + + void XLookup(Real x, int& xIndex, Real& dx) const; + void YLookup(Real y, int& yIndex, Real& dy) const; + void ZLookup(Real z, int& zIndex, Real& dz) const; + + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing; + Real mYMin, mYMax, mYSpacing; + Real mZMin, mZMax, mZSpacing; + Real const* mF; + Array3 mPoly; +}; + + +template +IntpAkimaUniform3::~IntpAkimaUniform3() +{ +} + +template +IntpAkimaUniform3::IntpAkimaUniform3(int xBound, int yBound, int zBound, + Real xMin, Real xSpacing, Real yMin, Real ySpacing, Real zMin, + Real zSpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F), + mPoly(xBound - 1, yBound - 1, zBound - 1) +{ + // At least a 3x3x3 block of data points is needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mZBound >= 3 && mF, + "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && + mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + + // Create a 3D wrapper for the 1D samples. + Array3 Fmap(mXBound, mYBound, mZBound, const_cast(mF)); + + // Construct first-order derivatives. + Array3 FX(mXBound, mYBound, mZBound); + Array3 FY(mXBound, mYBound, mZBound); + Array3 FZ(mXBound, mYBound, mZBound); + GetFX(Fmap, FX); + GetFX(Fmap, FY); + GetFX(Fmap, FZ); + + // Construct second-order derivatives. + Array3 FXY(mXBound, mYBound, mZBound); + Array3 FXZ(mXBound, mYBound, mZBound); + Array3 FYZ(mXBound, mYBound, mZBound); + GetFX(Fmap, FXY); + GetFX(Fmap, FXZ); + GetFX(Fmap, FYZ); + + // Construct third-order derivatives. + Array3 FXYZ(mXBound, mYBound, mZBound); + GetFXYZ(Fmap, FXYZ); + + // Construct polynomials. + GetPolynomials(Fmap, FX, FY, FZ, FXY, FXZ, FYZ, FXYZ); +} + +template inline +int IntpAkimaUniform3::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpAkimaUniform3::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpAkimaUniform3::GetZBound() const +{ + return mZBound; +} + +template inline +int IntpAkimaUniform3::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpAkimaUniform3::GetF() const +{ + return mF; +} + +template inline +Real IntpAkimaUniform3::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpAkimaUniform3::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpAkimaUniform3::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpAkimaUniform3::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpAkimaUniform3::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpAkimaUniform3::GetYSpacing() const +{ + return mYSpacing; +} + +template inline +Real IntpAkimaUniform3::GetZMin() const +{ + return mZMin; +} + +template inline +Real IntpAkimaUniform3::GetZMax() const +{ + return mZMax; +} + +template inline +Real IntpAkimaUniform3::GetZSpacing() const +{ + return mZSpacing; +} + +template +Real IntpAkimaUniform3::operator()(Real x, Real y, Real z) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + z = std::min(std::max(z, mZMin), mZMax); + int ix, iy, iz; + Real dx, dy, dz; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + ZLookup(z, iz, dz); + return mPoly[iz][iy][ix](dx, dy, dz); +} + +template +Real IntpAkimaUniform3::operator()(int xOrder, int yOrder, int zOrder, + Real x, Real y, Real z) const +{ + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + z = std::min(std::max(z, mZMin), mZMax); + int ix, iy, iz; + Real dx, dy, dz; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + ZLookup(z, iz, dz); + return mPoly[iz][iy][ix](xOrder, yOrder, zOrder, dx, dy, dz); +} + +template +void IntpAkimaUniform3::GetFX(Array3 const& F, Array3& FX) +{ + Array3 slope(mXBound + 3, mYBound, mZBound); + Real invDX = ((Real)1) / mXSpacing; + int ix, iy, iz; + for (iz = 0; iz < mZBound; ++iz) + { + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound - 1; ++ix) + { + slope[iz][iy][ix + 2] = (F[iz][iy][ix + 1] - F[iz][iy][ix]) * invDX; + } + + slope[iz][iy][1] = ((Real)2) * slope[iz][iy][2] - slope[iz][iy][3]; + slope[iz][iy][0] = ((Real)2) * slope[iz][iy][1] - slope[iz][iy][2]; + slope[iz][iy][mXBound + 1] = ((Real)2) * slope[iz][iy][mXBound] - slope[iz][iy][mXBound - 1]; + slope[iz][iy][mXBound + 2] = ((Real)2) * slope[iz][iy][mXBound + 1] - slope[iz][iy][mXBound]; + } + } + + for (iz = 0; iz < mZBound; ++iz) + { + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + FX[iz][iy][ix] = ComputeDerivative(slope[iz][iy] + ix); + } + } + } +} + +template +void IntpAkimaUniform3::GetFY(Array3 const& F, Array3& FY) +{ + Array3 slope(mYBound + 3, mXBound, mZBound); + Real invDY = ((Real)1) / mYSpacing; + int ix, iy, iz; + for (iz = 0; iz < mZBound; ++iz) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound - 1; ++iy) + { + slope[iz][ix][iy + 2] = (F[iz][iy + 1][ix] - F[iz][iy][ix]) * invDY; + } + + slope[iz][ix][1] = ((Real)2) * slope[iz][ix][2] - slope[iz][ix][3]; + slope[iz][ix][0] = ((Real)2) * slope[iz][ix][1] - slope[iz][ix][2]; + slope[iz][ix][mYBound + 1] = ((Real)2) * slope[iz][ix][mYBound] - slope[iz][ix][mYBound - 1]; + slope[iz][ix][mYBound + 2] = ((Real)2) * slope[iz][ix][mYBound + 1] - slope[iz][ix][mYBound]; + } + } + + for (iz = 0; iz < mZBound; ++iz) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound; ++iy) + { + FY[iz][iy][ix] = ComputeDerivative(slope[iz][ix] + iy); + } + } + } +} + +template +void IntpAkimaUniform3::GetFZ(Array3 const& F, Array3& FZ) +{ + Array3 slope(mZBound + 3, mXBound, mYBound); + Real invDZ = ((Real)1) / mZSpacing; + int ix, iy, iz; + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iz = 0; iz < mZBound - 1; ++iz) + { + slope[iy][ix][iz + 2] = (F[iz + 1][iy][ix] - F[iz][iy][ix]) * invDZ; + } + + slope[iy][ix][1] = ((Real)2) * slope[iy][ix][2] - slope[iy][ix][3]; + slope[iy][ix][0] = ((Real)2) * slope[iy][ix][1] - slope[iy][ix][2]; + slope[iy][ix][mZBound + 1] = ((Real)2) * slope[iy][ix][mZBound] - slope[iy][ix][mZBound - 1]; + slope[iy][ix][mZBound + 2] = ((Real)2) * slope[iy][ix][mZBound + 1] - slope[iy][ix][mZBound]; + } + } + + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iz = 0; iz < mZBound; ++iz) + { + FZ[iz][iy][ix] = ComputeDerivative(slope[iy][ix] + iz); + } + } + } +} + +template +void IntpAkimaUniform3::GetFXY(Array3 const& F, Array3& FXY) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int ix, iy, iz; + + Real invDXDY = ((Real)1) / (mXSpacing * mYSpacing); + for (iz = 0; iz < mZBound; ++iz) + { + // corners of z-slice + FXY[iz][0][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][0][0] + - ((Real)12)*F[iz][0][1] + + ((Real)3)*F[iz][0][2] + - ((Real)12)*F[iz][1][0] + + ((Real)16)*F[iz][1][1] + - ((Real)4)*F[iz][1][2] + + ((Real)3)*F[iz][2][0] + - ((Real)4)*F[iz][2][1] + + F[iz][2][2]); + + FXY[iz][0][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][0][ix0] + - ((Real)12)*F[iz][0][ix1] + + ((Real)3)*F[iz][0][ix2] + - ((Real)12)*F[iz][1][ix0] + + ((Real)16)*F[iz][1][ix1] + - ((Real)4)*F[iz][1][ix2] + + ((Real)3)*F[iz][2][ix0] + - ((Real)4)*F[iz][2][ix1] + + F[iz][2][ix2]); + + FXY[iz][yBoundM1][0] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][iy0][0] + - ((Real)12)*F[iz][iy0][1] + + ((Real)3)*F[iz][iy0][2] + - ((Real)12)*F[iz][iy1][0] + + ((Real)16)*F[iz][iy1][1] + - ((Real)4)*F[iz][iy1][2] + + ((Real)3)*F[iz][iy2][0] + - ((Real)4)*F[iz][iy2][1] + + F[iz][iy2][2]); + + FXY[iz][yBoundM1][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)9)*F[iz][iy0][ix0] + - ((Real)12)*F[iz][iy0][ix1] + + ((Real)3)*F[iz][iy0][ix2] + - ((Real)12)*F[iz][iy1][ix0] + + ((Real)16)*F[iz][iy1][ix1] + - ((Real)4)*F[iz][iy1][ix2] + + ((Real)3)*F[iz][iy2][ix0] + - ((Real)4)*F[iz][iy2][ix1] + + F[iz][iy2][ix2]); + + // x-edges of z-slice + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iz][0][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][0][ix - 1] - F[iz][0][ix + 1]) - + ((Real)4)*(F[iz][1][ix - 1] - F[iz][1][ix + 1]) + + (F[iz][2][ix - 1] - F[iz][2][ix + 1])); + + FXY[iz][yBoundM1][ix] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][iy0][ix - 1] - F[iz][iy0][ix + 1]) + - ((Real)4)*(F[iz][iy1][ix - 1] - F[iz][iy1][ix + 1]) + + (F[iz][iy2][ix - 1] - F[iz][iy2][ix + 1])); + } + + // y-edges of z-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + FXY[iz][iy][0] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][iy - 1][0] - F[iz][iy + 1][0]) - + ((Real)4)*(F[iz][iy - 1][1] - F[iz][iy + 1][1]) + + (F[iz][iy - 1][2] - F[iz][iy + 1][2])); + + FXY[iz][iy][xBoundM1] = ((Real)0.25)*invDXDY*( + ((Real)3)*(F[iz][iy - 1][ix0] - F[iz][iy + 1][ix0]) + - ((Real)4)*(F[iz][iy - 1][ix1] - F[iz][iy + 1][ix1]) + + (F[iz][iy - 1][ix2] - F[iz][iy + 1][ix2])); + } + + // interior of z-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iz][iy][ix] = ((Real)0.25)*invDXDY*( + F[iz][iy - 1][ix - 1] - F[iz][iy - 1][ix + 1] - + F[iz][iy + 1][ix - 1] + F[iz][iy + 1][ix + 1]); + } + } + } +} + +template +void IntpAkimaUniform3::GetFXZ(Array3 const& F, Array3& FXZ) +{ + int xBoundM1 = mXBound - 1; + int zBoundM1 = mZBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iz0 = zBoundM1, iz1 = iz0 - 1, iz2 = iz1 - 1; + int ix, iy, iz; + + Real invDXDZ = ((Real)1) / (mXSpacing * mZSpacing); + for (iy = 0; iy < mYBound; ++iy) + { + // corners of z-slice + FXZ[0][iy][0] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[0][iy][0] + - ((Real)12)*F[0][iy][1] + + ((Real)3)*F[0][iy][2] + - ((Real)12)*F[1][iy][0] + + ((Real)16)*F[1][iy][1] + - ((Real)4)*F[1][iy][2] + + ((Real)3)*F[2][iy][0] + - ((Real)4)*F[2][iy][1] + + F[2][iy][2]); + + FXZ[0][iy][xBoundM1] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[0][iy][ix0] + - ((Real)12)*F[0][iy][ix1] + + ((Real)3)*F[0][iy][ix2] + - ((Real)12)*F[1][iy][ix0] + + ((Real)16)*F[1][iy][ix1] + - ((Real)4)*F[1][iy][ix2] + + ((Real)3)*F[2][iy][ix0] + - ((Real)4)*F[2][iy][ix1] + + F[2][iy][ix2]); + + FXZ[zBoundM1][iy][0] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[iz0][iy][0] + - ((Real)12)*F[iz0][iy][1] + + ((Real)3)*F[iz0][iy][2] + - ((Real)12)*F[iz1][iy][0] + + ((Real)16)*F[iz1][iy][1] + - ((Real)4)*F[iz1][iy][2] + + ((Real)3)*F[iz2][iy][0] + - ((Real)4)*F[iz2][iy][1] + + F[iz2][iy][2]); + + FXZ[zBoundM1][iy][xBoundM1] = ((Real)0.25)*invDXDZ*( + ((Real)9)*F[iz0][iy][ix0] + - ((Real)12)*F[iz0][iy][ix1] + + ((Real)3)*F[iz0][iy][ix2] + - ((Real)12)*F[iz1][iy][ix0] + + ((Real)16)*F[iz1][iy][ix1] + - ((Real)4)*F[iz1][iy][ix2] + + ((Real)3)*F[iz2][iy][ix0] + - ((Real)4)*F[iz2][iy][ix1] + + F[iz2][iy][ix2]); + + // x-edges of y-slice + for (ix = 1; ix < xBoundM1; ++ix) + { + FXZ[0][iy][ix] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[0][iy][ix - 1] - F[0][iy][ix + 1]) - + ((Real)4)*(F[1][iy][ix - 1] - F[1][iy][ix + 1]) + + (F[2][iy][ix - 1] - F[2][iy][ix + 1])); + + FXZ[zBoundM1][iy][ix] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[iz0][iy][ix - 1] - F[iz0][iy][ix + 1]) + - ((Real)4)*(F[iz1][iy][ix - 1] - F[iz1][iy][ix + 1]) + + (F[iz2][iy][ix - 1] - F[iz2][iy][ix + 1])); + } + + // z-edges of y-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + FXZ[iz][iy][0] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[iz - 1][iy][0] - F[iz + 1][iy][0]) - + ((Real)4)*(F[iz - 1][iy][1] - F[iz + 1][iy][1]) + + (F[iz - 1][iy][2] - F[iz + 1][iy][2])); + + FXZ[iz][iy][xBoundM1] = ((Real)0.25)*invDXDZ*( + ((Real)3)*(F[iz - 1][iy][ix0] - F[iz + 1][iy][ix0]) + - ((Real)4)*(F[iz - 1][iy][ix1] - F[iz + 1][iy][ix1]) + + (F[iz - 1][iy][ix2] - F[iz + 1][iy][ix2])); + } + + // interior of y-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXZ[iz][iy][ix] = ((Real)0.25)*invDXDZ*( + F[iz - 1][iy][ix - 1] - F[iz - 1][iy][ix + 1] - + F[iz + 1][iy][ix - 1] + F[iz + 1][iy][ix + 1]); + } + } + } +} + +template +void IntpAkimaUniform3::GetFYZ(Array3 const& F, Array3& FYZ) +{ + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int iz0 = zBoundM1, iz1 = iz0 - 1, iz2 = iz1 - 1; + int ix, iy, iz; + + Real invDYDZ = ((Real)1) / (mYSpacing * mZSpacing); + for (ix = 0; ix < mXBound; ++ix) + { + // corners of x-slice + FYZ[0][0][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[0][0][ix] + - ((Real)12)*F[0][1][ix] + + ((Real)3)*F[0][2][ix] + - ((Real)12)*F[1][0][ix] + + ((Real)16)*F[1][1][ix] + - ((Real)4)*F[1][2][ix] + + ((Real)3)*F[2][0][ix] + - ((Real)4)*F[2][1][ix] + + F[2][2][ix]); + + FYZ[0][yBoundM1][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[0][iy0][ix] + - ((Real)12)*F[0][iy1][ix] + + ((Real)3)*F[0][iy2][ix] + - ((Real)12)*F[1][iy0][ix] + + ((Real)16)*F[1][iy1][ix] + - ((Real)4)*F[1][iy2][ix] + + ((Real)3)*F[2][iy0][ix] + - ((Real)4)*F[2][iy1][ix] + + F[2][iy2][ix]); + + FYZ[zBoundM1][0][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[iz0][0][ix] + - ((Real)12)*F[iz0][1][ix] + + ((Real)3)*F[iz0][2][ix] + - ((Real)12)*F[iz1][0][ix] + + ((Real)16)*F[iz1][1][ix] + - ((Real)4)*F[iz1][2][ix] + + ((Real)3)*F[iz2][0][ix] + - ((Real)4)*F[iz2][1][ix] + + F[iz2][2][ix]); + + FYZ[zBoundM1][yBoundM1][ix] = ((Real)0.25)*invDYDZ*( + ((Real)9)*F[iz0][iy0][ix] + - ((Real)12)*F[iz0][iy1][ix] + + ((Real)3)*F[iz0][iy2][ix] + - ((Real)12)*F[iz1][iy0][ix] + + ((Real)16)*F[iz1][iy1][ix] + - ((Real)4)*F[iz1][iy2][ix] + + ((Real)3)*F[iz2][iy0][ix] + - ((Real)4)*F[iz2][iy1][ix] + + F[iz2][iy2][ix]); + + // y-edges of x-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + FYZ[0][iy][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[0][iy - 1][ix] - F[0][iy + 1][ix]) - + ((Real)4)*(F[1][iy - 1][ix] - F[1][iy + 1][ix]) + + (F[2][iy - 1][ix] - F[2][iy + 1][ix])); + + FYZ[zBoundM1][iy][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[iz0][iy - 1][ix] - F[iz0][iy + 1][ix]) + - ((Real)4)*(F[iz1][iy - 1][ix] - F[iz1][iy + 1][ix]) + + (F[iz2][iy - 1][ix] - F[iz2][iy + 1][ix])); + } + + // z-edges of x-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + FYZ[iz][0][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[iz - 1][0][ix] - F[iz + 1][0][ix]) - + ((Real)4)*(F[iz - 1][1][ix] - F[iz + 1][1][ix]) + + (F[iz - 1][2][ix] - F[iz + 1][2][ix])); + + FYZ[iz][yBoundM1][ix] = ((Real)0.25)*invDYDZ*( + ((Real)3)*(F[iz - 1][iy0][ix] - F[iz + 1][iy0][ix]) + - ((Real)4)*(F[iz - 1][iy1][ix] - F[iz + 1][iy1][ix]) + + (F[iz - 1][iy2][ix] - F[iz + 1][iy2][ix])); + } + + // interior of x-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + for (iy = 1; iy < yBoundM1; ++iy) + { + FYZ[iz][iy][ix] = ((Real)0.25)*invDYDZ*( + F[iz - 1][iy - 1][ix] - F[iz - 1][iy + 1][ix] - + F[iz + 1][iy - 1][ix] + F[iz + 1][iy + 1][ix]); + } + } + } +} + +template +void IntpAkimaUniform3::GetFXYZ(Array3 const& F, Array3& FXYZ) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + int ix, iy, iz, ix0, iy0, iz0; + + Real invDXDYDZ = ((Real)1) / (mXSpacing * mYSpacing * mZSpacing); + + // convolution masks + // centered difference, O(h^2) + Real CDer[3] = { -(Real)0.5, (Real)0, (Real)0.5 }; + // one-sided difference, O(h^2) + Real ODer[3] = { -(Real)1.5, (Real)2, -(Real)0.5 }; + Real mask; + + // corners + FXYZ[0][0][0] = (Real)0; + FXYZ[0][0][xBoundM1] = (Real)0; + FXYZ[0][yBoundM1][0] = (Real)0; + FXYZ[0][yBoundM1][xBoundM1] = (Real)0; + FXYZ[zBoundM1][0][0] = (Real)0; + FXYZ[zBoundM1][0][xBoundM1] = (Real)0; + FXYZ[zBoundM1][yBoundM1][0] = (Real)0; + FXYZ[zBoundM1][yBoundM1][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * ODer[iy] * ODer[iz]; + FXYZ[0][0][0] += mask * F[iz][iy][ix]; + FXYZ[0][0][xBoundM1] += mask * F[iz][iy][xBoundM1 - ix]; + FXYZ[0][yBoundM1][0] += mask * F[iz][yBoundM1 - iy][ix]; + FXYZ[0][yBoundM1][xBoundM1] += mask * F[iz][yBoundM1 - iy][xBoundM1 - ix]; + FXYZ[zBoundM1][0][0] += mask * F[zBoundM1 - iz][iy][ix]; + FXYZ[zBoundM1][0][xBoundM1] += mask * F[zBoundM1 - iz][iy][xBoundM1 - ix]; + FXYZ[zBoundM1][yBoundM1][0] += mask * F[zBoundM1 - iz][yBoundM1 - iy][ix]; + FXYZ[zBoundM1][yBoundM1][xBoundM1] += mask * F[zBoundM1 - iz][yBoundM1 - iy][xBoundM1 - ix]; + } + } + } + + // x-edges + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[0][0][ix0] = (Real)0; + FXYZ[0][yBoundM1][ix0] = (Real)0; + FXYZ[zBoundM1][0][ix0] = (Real)0; + FXYZ[zBoundM1][yBoundM1][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * ODer[iy] * ODer[iz]; + FXYZ[0][0][ix0] += mask * F[iz][iy][ix0 + ix - 1]; + FXYZ[0][yBoundM1][ix0] += mask * F[iz][yBoundM1 - iy][ix0 + ix - 1]; + FXYZ[zBoundM1][0][ix0] += mask * F[zBoundM1 - iz][iy][ix0 + ix - 1]; + FXYZ[zBoundM1][yBoundM1][ix0] += mask * F[zBoundM1 - iz][yBoundM1 - iy][ix0 + ix - 1]; + } + } + } + } + + // y-edges + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + FXYZ[0][iy0][0] = (Real)0; + FXYZ[0][iy0][xBoundM1] = (Real)0; + FXYZ[zBoundM1][iy0][0] = (Real)0; + FXYZ[zBoundM1][iy0][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * CDer[iy] * ODer[iz]; + FXYZ[0][iy0][0] += mask * F[iz][iy0 + iy - 1][ix]; + FXYZ[0][iy0][xBoundM1] += mask * F[iz][iy0 + iy - 1][xBoundM1 - ix]; + FXYZ[zBoundM1][iy0][0] += mask * F[zBoundM1 - iz][iy0 + iy - 1][ix]; + FXYZ[zBoundM1][iy0][xBoundM1] += mask * F[zBoundM1 - iz][iy0 + iy - 1][xBoundM1 - ix]; + } + } + } + } + + // z-edges + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + FXYZ[iz0][0][0] = (Real)0; + FXYZ[iz0][0][xBoundM1] = (Real)0; + FXYZ[iz0][yBoundM1][0] = (Real)0; + FXYZ[iz0][yBoundM1][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * ODer[iy] * CDer[iz]; + FXYZ[iz0][0][0] += mask * F[iz0 + iz - 1][iy][ix]; + FXYZ[iz0][0][xBoundM1] += mask * F[iz0 + iz - 1][iy][xBoundM1 - ix]; + FXYZ[iz0][yBoundM1][0] += mask * F[iz0 + iz - 1][yBoundM1 - iy][ix]; + FXYZ[iz0][yBoundM1][xBoundM1] += mask * F[iz0 + iz - 1][yBoundM1 - iy][xBoundM1 - ix]; + } + } + } + } + + // xy-faces + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[0][iy0][ix0] = (Real)0; + FXYZ[zBoundM1][iy0][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * CDer[iy] * ODer[iz]; + FXYZ[0][iy0][ix0] += mask * F[iz][iy0 + iy - 1][ix0 + ix - 1]; + FXYZ[zBoundM1][iy0][ix0] += mask * F[zBoundM1 - iz][iy0 + iy - 1][ix0 + ix - 1]; + } + } + } + } + } + + // xz-faces + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[iz0][0][ix0] = (Real)0; + FXYZ[iz0][yBoundM1][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * ODer[iy] * CDer[iz]; + FXYZ[iz0][0][ix0] += mask * F[iz0 + iz - 1][iy][ix0 + ix - 1]; + FXYZ[iz0][yBoundM1][ix0] += mask * F[iz0 + iz - 1][yBoundM1 - iy][ix0 + ix - 1]; + } + } + } + } + } + + // yz-faces + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + FXYZ[iz0][iy0][0] = (Real)0; + FXYZ[iz0][iy0][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*ODer[ix] * CDer[iy] * CDer[iz]; + FXYZ[iz0][iy0][0] += mask * F[iz0 + iz - 1][iy0 + iy - 1][ix]; + FXYZ[iz0][iy0][xBoundM1] += mask * F[iz0 + iz - 1][iy0 + iy - 1][xBoundM1 - ix]; + } + } + } + } + } + + // interiors + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[iz0][iy0][ix0] = (Real)0; + + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ*CDer[ix] * CDer[iy] * CDer[iz]; + FXYZ[iz0][iy0][ix0] += mask * F[iz0 + iz - 1][iy0 + iy - 1][ix0 + ix - 1]; + } + } + } + } + } + } +} + +template +void IntpAkimaUniform3::GetPolynomials(Array3 const& F, Array3 const& FX, + Array3 const& FY, Array3 const& FZ, Array3 const& FXY, + Array3 const& FXZ, Array3 const& FYZ, Array3 const& FXYZ) +{ + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + for (int iz = 0; iz < zBoundM1; ++iz) + { + for (int iy = 0; iy < yBoundM1; ++iy) + { + for (int ix = 0; ix < xBoundM1; ++ix) + { + // Note the 'transposing' of the 2x2x2 blocks (to match + // notation used in the polynomial definition). + Real G[2][2][2] = + { + { + { + F[iz][iy][ix], + F[iz + 1][iy][ix] + }, + { + F[iz][iy + 1][ix], + F[iz + 1][iy + 1][ix] + } + }, + { + { + F[iz][iy][ix + 1], + F[iz + 1][iy][ix + 1] + }, + { + F[iz][iy + 1][ix + 1], + F[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GX[2][2][2] = + { + { + { + FX[iz][iy][ix], + FX[iz + 1][iy][ix] + }, + { + FX[iz][iy + 1][ix], + FX[iz + 1][iy + 1][ix] + } + }, + { + { + FX[iz][iy][ix + 1], + FX[iz + 1][iy][ix + 1] + }, + { + FX[iz][iy + 1][ix + 1], + FX[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GY[2][2][2] = + { + { + { + FY[iz][iy][ix], + FY[iz + 1][iy][ix] + }, + { + FY[iz][iy + 1][ix], + FY[iz + 1][iy + 1][ix] + } + }, + { + { + FY[iz][iy][ix + 1], + FY[iz + 1][iy][ix + 1] + }, + { + FY[iz][iy + 1][ix + 1], + FY[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GZ[2][2][2] = + { + { + { + FZ[iz][iy][ix], + FZ[iz + 1][iy][ix] + }, + { + FZ[iz][iy + 1][ix], + FZ[iz + 1][iy + 1][ix] + } + }, + { + { + FZ[iz][iy][ix + 1], + FZ[iz + 1][iy][ix + 1] + }, + { + FZ[iz][iy + 1][ix + 1], + FZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXY[2][2][2] = + { + { + { + FXY[iz][iy][ix], + FXY[iz + 1][iy][ix] + }, + { + FXY[iz][iy + 1][ix], + FXY[iz + 1][iy + 1][ix] + } + }, + { + { + FXY[iz][iy][ix + 1], + FXY[iz + 1][iy][ix + 1] + }, + { + FXY[iz][iy + 1][ix + 1], + FXY[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXZ[2][2][2] = + { + { + { + FXZ[iz][iy][ix], + FXZ[iz + 1][iy][ix] + }, + { + FXZ[iz][iy + 1][ix], + FXZ[iz + 1][iy + 1][ix] + } + }, + { + { + FXZ[iz][iy][ix + 1], + FXZ[iz + 1][iy][ix + 1] + }, + { + FXZ[iz][iy + 1][ix + 1], + FXZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GYZ[2][2][2] = + { + { + { + FYZ[iz][iy][ix], + FYZ[iz + 1][iy][ix] + }, + { + FYZ[iz][iy + 1][ix], + FYZ[iz + 1][iy + 1][ix] + } + }, + { + { + FYZ[iz][iy][ix + 1], + FYZ[iz + 1][iy][ix + 1] + }, + { + FYZ[iz][iy + 1][ix + 1], + FYZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXYZ[2][2][2] = + { + { + { + FXYZ[iz][iy][ix], + FXYZ[iz + 1][iy][ix] + }, + { + FXYZ[iz][iy + 1][ix], + FXYZ[iz + 1][iy + 1][ix] + } + }, + { + { + FXYZ[iz][iy][ix + 1], + FXYZ[iz + 1][iy][ix + 1] + }, + { + FXYZ[iz][iy + 1][ix + 1], + FXYZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Construct(mPoly[iz][iy][ix], G, GX, GY, GZ, GXY, GXZ, GYZ, GXYZ); + } + } + } +} + +template +Real IntpAkimaUniform3::ComputeDerivative(Real const* slope) const +{ + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::abs(slope[3] - slope[2]); + Real ad1 = std::abs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5) * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } +} + +template +void IntpAkimaUniform3::Construct(Polynomial& poly, + Real const F[2][2][2], Real const FX[2][2][2], Real const FY[2][2][2], + Real const FZ[2][2][2], Real const FXY[2][2][2], Real const FXZ[2][2][2], + Real const FYZ[2][2][2], Real const FXYZ[2][2][2]) +{ + Real dx = mXSpacing, dy = mYSpacing, dz = mZSpacing; + Real invDX = ((Real)1) / dx, invDX2 = invDX * invDX; + Real invDY = ((Real)1) / dy, invDY2 = invDY * invDY; + Real invDZ = ((Real)1) / dz, invDZ2 = invDZ * invDZ; + Real b0, b1, b2, b3, b4, b5, b6, b7; + + poly.A(0, 0, 0) = F[0][0][0]; + poly.A(1, 0, 0) = FX[0][0][0]; + poly.A(0, 1, 0) = FY[0][0][0]; + poly.A(0, 0, 1) = FZ[0][0][0]; + poly.A(1, 1, 0) = FXY[0][0][0]; + poly.A(1, 0, 1) = FXZ[0][0][0]; + poly.A(0, 1, 1) = FYZ[0][0][0]; + poly.A(1, 1, 1) = FXYZ[0][0][0]; + + // solve for Aij0 + b0 = (F[1][0][0] - poly(0, 0, 0, dx, (Real)0, (Real)0))*invDX2; + b1 = (FX[1][0][0] - poly(1, 0, 0, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 0, 0) = ((Real)3)*b0 - b1; + poly.A(3, 0, 0) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (F[0][1][0] - poly(0, 0, 0, (Real)0, dy, (Real)0))*invDY2; + b1 = (FY[0][1][0] - poly(0, 1, 0, (Real)0, dy, (Real)0))*invDY; + poly.A(0, 2, 0) = ((Real)3)*b0 - b1; + poly.A(0, 3, 0) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FY[1][0][0] - poly(0, 1, 0, dx, (Real)0, (Real)0))*invDX2; + b1 = (FXY[1][0][0] - poly(1, 1, 0, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 1, 0) = ((Real)3)*b0 - b1; + poly.A(3, 1, 0) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FX[0][1][0] - poly(1, 0, 0, (Real)0, dy, (Real)0))*invDY2; + b1 = (FXY[0][1][0] - poly(1, 1, 0, (Real)0, dy, (Real)0))*invDY; + poly.A(1, 2, 0) = ((Real)3)*b0 - b1; + poly.A(1, 3, 0) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (F[1][1][0] - poly(0, 0, 0, dx, dy, (Real)0))*invDX2*invDY2; + b1 = (FX[1][1][0] - poly(1, 0, 0, dx, dy, (Real)0))*invDX*invDY2; + b2 = (FY[1][1][0] - poly(0, 1, 0, dx, dy, (Real)0))*invDX2*invDY; + b3 = (FXY[1][1][0] - poly(1, 1, 0, dx, dy, (Real)0))*invDX*invDY; + poly.A(2, 2, 0) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 2, 0) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 3, 0) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDY; + poly.A(3, 3, 0) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDY; + + // solve for Ai0k + b0 = (F[0][0][1] - poly(0, 0, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FZ[0][0][1] - poly(0, 0, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(0, 0, 2) = ((Real)3)*b0 - b1; + poly.A(0, 0, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (FZ[1][0][0] - poly(0, 0, 1, dx, (Real)0, (Real)0))*invDX2; + b1 = (FXZ[1][0][0] - poly(1, 0, 1, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 0, 1) = ((Real)3)*b0 - b1; + poly.A(3, 0, 1) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FX[0][0][1] - poly(1, 0, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FXZ[0][0][1] - poly(1, 0, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(1, 0, 2) = ((Real)3)*b0 - b1; + poly.A(1, 0, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (F[1][0][1] - poly(0, 0, 0, dx, (Real)0, dz))*invDX2*invDZ2; + b1 = (FX[1][0][1] - poly(1, 0, 0, dx, (Real)0, dz))*invDX*invDZ2; + b2 = (FZ[1][0][1] - poly(0, 0, 1, dx, (Real)0, dz))*invDX2*invDZ; + b3 = (FXZ[1][0][1] - poly(1, 0, 1, dx, (Real)0, dz))*invDX*invDZ; + poly.A(2, 0, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 0, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 0, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(3, 0, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDZ; + + // solve for A0jk + b0 = (FZ[0][1][0] - poly(0, 0, 1, (Real)0, dy, (Real)0))*invDY2; + b1 = (FYZ[0][1][0] - poly(0, 1, 1, (Real)0, dy, (Real)0))*invDY; + poly.A(0, 2, 1) = ((Real)3)*b0 - b1; + poly.A(0, 3, 1) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FY[0][0][1] - poly(0, 1, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FYZ[0][0][1] - poly(0, 1, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(0, 1, 2) = ((Real)3)*b0 - b1; + poly.A(0, 1, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (F[0][1][1] - poly(0, 0, 0, (Real)0, dy, dz))*invDY2*invDZ2; + b1 = (FY[0][1][1] - poly(0, 1, 0, (Real)0, dy, dz))*invDY*invDZ2; + b2 = (FZ[0][1][1] - poly(0, 0, 1, (Real)0, dy, dz))*invDY2*invDZ; + b3 = (FYZ[0][1][1] - poly(0, 1, 1, (Real)0, dy, dz))*invDY*invDZ; + poly.A(0, 2, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(0, 3, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDY; + poly.A(0, 2, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(0, 3, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDY*invDZ; + + // solve for Aij1 + b0 = (FYZ[1][0][0] - poly(0, 1, 1, dx, (Real)0, (Real)0))*invDX2; + b1 = (FXYZ[1][0][0] - poly(1, 1, 1, dx, (Real)0, (Real)0))*invDX; + poly.A(2, 1, 1) = ((Real)3)*b0 - b1; + poly.A(3, 1, 1) = (-((Real)2)*b0 + b1)*invDX; + + b0 = (FXZ[0][1][0] - poly(1, 0, 1, (Real)0, dy, (Real)0))*invDY2; + b1 = (FXYZ[0][1][0] - poly(1, 1, 1, (Real)0, dy, (Real)0))*invDY; + poly.A(1, 2, 1) = ((Real)3)*b0 - b1; + poly.A(1, 3, 1) = (-((Real)2)*b0 + b1)*invDY; + + b0 = (FZ[1][1][0] - poly(0, 0, 1, dx, dy, (Real)0))*invDX2*invDY2; + b1 = (FXZ[1][1][0] - poly(1, 0, 1, dx, dy, (Real)0))*invDX*invDY2; + b2 = (FYZ[1][1][0] - poly(0, 1, 1, dx, dy, (Real)0))*invDX2*invDY; + b3 = (FXYZ[1][1][0] - poly(1, 1, 1, dx, dy, (Real)0))*invDX*invDY; + poly.A(2, 2, 1) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 2, 1) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 3, 1) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDY; + poly.A(3, 3, 1) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDY; + + // solve for Ai1k + b0 = (FXY[0][0][1] - poly(1, 1, 0, (Real)0, (Real)0, dz))*invDZ2; + b1 = (FXYZ[0][0][1] - poly(1, 1, 1, (Real)0, (Real)0, dz))*invDZ; + poly.A(1, 1, 2) = ((Real)3)*b0 - b1; + poly.A(1, 1, 3) = (-((Real)2)*b0 + b1)*invDZ; + + b0 = (FY[1][0][1] - poly(0, 1, 0, dx, (Real)0, dz))*invDX2*invDZ2; + b1 = (FXY[1][0][1] - poly(1, 1, 0, dx, (Real)0, dz))*invDX*invDZ2; + b2 = (FYZ[1][0][1] - poly(0, 1, 1, dx, (Real)0, dz))*invDX2*invDZ; + b3 = (FXYZ[1][0][1] - poly(1, 1, 1, dx, (Real)0, dz))*invDX*invDZ; + poly.A(2, 1, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(3, 1, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDX; + poly.A(2, 1, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(3, 1, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDX*invDZ; + + // solve for A1jk + b0 = (FX[0][1][1] - poly(1, 0, 0, (Real)0, dy, dz))*invDY2*invDZ2; + b1 = (FXY[0][1][1] - poly(1, 1, 0, (Real)0, dy, dz))*invDY*invDZ2; + b2 = (FXZ[0][1][1] - poly(1, 0, 1, (Real)0, dy, dz))*invDY2*invDZ; + b3 = (FXYZ[0][1][1] - poly(1, 1, 1, (Real)0, dy, dz))*invDY*invDZ; + poly.A(1, 2, 2) = ((Real)9)*b0 - ((Real)3)*b1 - ((Real)3)*b2 + b3; + poly.A(1, 3, 2) = (-((Real)6)*b0 + ((Real)3)*b1 + ((Real)2)*b2 - b3)*invDY; + poly.A(1, 2, 3) = (-((Real)6)*b0 + ((Real)2)*b1 + ((Real)3)*b2 - b3)*invDZ; + poly.A(1, 3, 3) = (((Real)4)*b0 - ((Real)2)*b1 - ((Real)2)*b2 + b3)*invDY*invDZ; + + // solve for remaining Aijk with i >= 2, j >= 2, k >= 2 + b0 = (F[1][1][1] - poly(0, 0, 0, dx, dy, dz))*invDX2*invDY2*invDZ2; + b1 = (FX[1][1][1] - poly(1, 0, 0, dx, dy, dz))*invDX*invDY2*invDZ2; + b2 = (FY[1][1][1] - poly(0, 1, 0, dx, dy, dz))*invDX2*invDY*invDZ2; + b3 = (FZ[1][1][1] - poly(0, 0, 1, dx, dy, dz))*invDX2*invDY2*invDZ; + b4 = (FXY[1][1][1] - poly(1, 1, 0, dx, dy, dz))*invDX*invDY*invDZ2; + b5 = (FXZ[1][1][1] - poly(1, 0, 1, dx, dy, dz))*invDX*invDY2*invDZ; + b6 = (FYZ[1][1][1] - poly(0, 1, 1, dx, dy, dz))*invDX2*invDY*invDZ; + b7 = (FXYZ[1][1][1] - poly(1, 1, 1, dx, dy, dz))*invDX*invDY*invDZ; + poly.A(2, 2, 2) = ((Real)27)*b0 - ((Real)9)*b1 - ((Real)9)*b2 - + ((Real)9)*b3 + ((Real)3)*b4 + ((Real)3)*b5 + ((Real)3)*b6 - b7; + poly.A(3, 2, 2) = (((Real)-18)*b0 + ((Real)9)*b1 + ((Real)6)*b2 + + ((Real)6)*b3 - ((Real)3)*b4 - ((Real)3)*b5 - ((Real)2)*b6 + b7)*invDX; + poly.A(2, 3, 2) = (((Real)-18)*b0 + ((Real)6)*b1 + ((Real)9)*b2 + + ((Real)6)*b3 - ((Real)3)*b4 - ((Real)2)*b5 - ((Real)3)*b6 + b7)*invDY; + poly.A(2, 2, 3) = (((Real)-18)*b0 + ((Real)6)*b1 + ((Real)6)*b2 + + ((Real)9)*b3 - ((Real)2)*b4 - ((Real)3)*b5 - ((Real)3)*b6 + b7)*invDZ; + poly.A(3, 3, 2) = (((Real)12)*b0 - ((Real)6)*b1 - ((Real)6)*b2 - + ((Real)4)*b3 + ((Real)3)*b4 + ((Real)2)*b5 + ((Real)2)*b6 - b7)* + invDX*invDY; + poly.A(3, 2, 3) = (((Real)12)*b0 - ((Real)6)*b1 - ((Real)4)*b2 - + ((Real)6)*b3 + ((Real)2)*b4 + ((Real)3)*b5 + ((Real)2)*b6 - b7)* + invDX*invDZ; + poly.A(2, 3, 3) = (((Real)12)*b0 - ((Real)4)*b1 - ((Real)6)*b2 - + ((Real)6)*b3 + ((Real)2)*b4 + ((Real)2)*b5 + ((Real)3)*b6 - b7)* + invDY*invDZ; + poly.A(3, 3, 3) = (((Real)-8)*b0 + ((Real)4)*b1 + ((Real)4)*b2 + + ((Real)4)*b3 - ((Real)2)*b4 - ((Real)2)*b5 - ((Real)2)*b6 + b7)* + invDX*invDY*invDZ; +} + +template +void IntpAkimaUniform3::XLookup(Real x, int& xIndex, Real& dx) const +{ + for (xIndex = 0; xIndex + 1 < mXBound; ++xIndex) + { + if (x < mXMin + mXSpacing*(xIndex + 1)) + { + dx = x - (mXMin + mXSpacing*xIndex); + return; + } + } + + --xIndex; + dx = x - (mXMin + mXSpacing*xIndex); +} + +template +void IntpAkimaUniform3::YLookup(Real y, int& yIndex, Real& dy) const +{ + for (yIndex = 0; yIndex + 1 < mYBound; ++yIndex) + { + if (y < mYMin + mYSpacing*(yIndex + 1)) + { + dy = y - (mYMin + mYSpacing*yIndex); + return; + } + } + + --yIndex; + dy = y - (mYMin + mYSpacing*yIndex); +} + +template +void IntpAkimaUniform3::ZLookup(Real z, int& zIndex, Real& dz) const +{ + for (zIndex = 0; zIndex + 1 < mZBound; ++zIndex) + { + if (z < mZMin + mZSpacing*(zIndex + 1)) + { + dz = z - (mZMin + mZSpacing*zIndex); + return; + } + } + + --zIndex; + dz = z - (mZMin + mZSpacing*zIndex); +} + +template +IntpAkimaUniform3::Polynomial::Polynomial() +{ + memset(&mCoeff[0][0][0], 0, 64 * sizeof(Real)); +} + +template +Real& IntpAkimaUniform3::Polynomial::A(int ix, int iy, int iz) +{ + return mCoeff[ix][iy][iz]; +} + +template +Real IntpAkimaUniform3::Polynomial::operator()(Real x, Real y, Real z) +const +{ + Real xPow[4] = { (Real)1, x, x * x, x * x * x }; + Real yPow[4] = { (Real)1, y, y * y, y * y * y }; + Real zPow[4] = { (Real)1, z, z * z, z * z * z }; + + Real p = (Real)0; + for (int iz = 0; iz <= 3; ++iz) + { + for (int iy = 0; iy <= 3; ++iy) + { + for (int ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy][iz] * xPow[ix] * yPow[iy] * zPow[iz]; + } + } + } + + return p; +} + +template +Real IntpAkimaUniform3::Polynomial::operator()(int xOrder, int yOrder, + int zOrder, Real x, Real y, Real z) const +{ + Real xPow[4]; + switch (xOrder) + { + case 0: + xPow[0] = (Real)1; + xPow[1] = x; + xPow[2] = x * x; + xPow[3] = x * x * x; + break; + case 1: + xPow[0] = (Real)0; + xPow[1] = (Real)1; + xPow[2] = ((Real)2) * x; + xPow[3] = ((Real)3) * x * x; + break; + case 2: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)2; + xPow[3] = ((Real)6) * x; + break; + case 3: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)0; + xPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real yPow[4]; + switch (yOrder) + { + case 0: + yPow[0] = (Real)1; + yPow[1] = y; + yPow[2] = y * y; + yPow[3] = y * y * y; + break; + case 1: + yPow[0] = (Real)0; + yPow[1] = (Real)1; + yPow[2] = ((Real)2) * y; + yPow[3] = ((Real)3) * y * y; + break; + case 2: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)2; + yPow[3] = ((Real)6) * y; + break; + case 3: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)0; + yPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real zPow[4]; + switch (zOrder) + { + case 0: + zPow[0] = (Real)1; + zPow[1] = z; + zPow[2] = z * z; + zPow[3] = z * z * z; + break; + case 1: + zPow[0] = (Real)0; + zPow[1] = (Real)1; + zPow[2] = ((Real)2) * z; + zPow[3] = ((Real)3) * z * z; + break; + case 2: + zPow[0] = (Real)0; + zPow[1] = (Real)0; + zPow[2] = (Real)2; + zPow[3] = ((Real)6) * z; + break; + case 3: + zPow[0] = (Real)0; + zPow[1] = (Real)0; + zPow[2] = (Real)0; + zPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real p = (Real)0; + + for (int iz = 0; iz <= 3; ++iz) + { + for (int iy = 0; iy <= 3; ++iy) + { + for (int ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy][iz] * xPow[ix] * yPow[iy] * zPow[iz]; + } + } + } + + return p; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBSplineUniform.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBSplineUniform.h new file mode 100644 index 000000000000..bc8204b961ac --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBSplineUniform.h @@ -0,0 +1,1455 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.13.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +// IntpBSplineUniform is the class for B-spline interpolation of uniformly +// spaced N-dimensional data. The algorithm is described in +// https://www.geometrictools.com/Documentation/BSplineInterpolation.pdf +// A sample application for this topic is +// GeometricTools/GTEngine/Samples/Imagics/BSplineInterpolation +// +// The Controls adapter allows access to your control points without regard +// to how you organize your data. You can even defer the computation of a +// control point until it is needed via the operator()(...) calls that +// Controls must provide, and you can cache the points according to your own +// needs. The minimal interface for a Controls adapter is +// +// struct Controls +// { +// // The control_point_type is of your choosing. It must support +// // assignment, scalar multiplication and addition. Specifically, if +// // C0, C1 and C2 are control points and s is a scalar, the interpolator +// // needs to perform operations +// // C1 = C0; +// // C1 = C0 * s; +// // C2 = C0 + C1; +// typedef control_point_type Type; +// +// // The number of elements in the specified dimension. +// int GetSize(int dimension) const; +// +// // Get a control point based on an n-tuple lookup. The interpolator +// // does not need to know your organization; all it needs is the +// // desired control point. The 'tuple' input must have n elements. +// Type operator() (int const* tuple) const; +// +// // If you choose to use the specialized interpolators for dimensions +// // 1, 2 or 3, you must also provide the following accessor, where +// // the input is an n-tuple listed component-by-component (1, 2 or 3 +// // components). +// Type operator() (int i0, int i1, ..., int inm1) const; + +namespace gte +{ + template + class IntpBSplineUniformShared + { + protected: + // Abstract base class construction. A virtual destructor is not + // provided because there are no required side effects in the base + // class when destroying objects from the derived classes. + IntpBSplineUniformShared(int numDimensions, int const* degrees, + Controls const& controls, typename Controls::Type ctZero, int cacheMode) + : + mNumDimensions(numDimensions), + mDegree(numDimensions), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mNumLocalControls(0), + mDegreeP1(numDimensions), + mNumControls(numDimensions), + mTMin(numDimensions), + mTMax(numDimensions), + mBlender(numDimensions), + mDCoefficient(numDimensions), + mLMax(numDimensions), + mPowerDSDT(numDimensions), + mITuple(numDimensions), + mJTuple(numDimensions), + mKTuple(numDimensions), + mLTuple(numDimensions), + mSumIJTuple(numDimensions), + mUTuple(numDimensions), + mPTuple(numDimensions), + mValid(IsValid(degrees)) + { + if (!mValid) + { + return; + } + + mNumLocalControls = 1; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mDegree[dim] = degrees[dim]; + mDegreeP1[dim] = degrees[dim] + 1; + mNumLocalControls *= mDegreeP1[dim]; + mNumControls[dim] = controls.GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == NO_CACHING) + { + mPhi.resize(mNumDimensions); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + } + +#if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) + IntpBSplineUniformShared() + : + mNumDimensions(0), + mControls(nullptr), + mCTZero(), + mCacheMode(0), + mNumLocalControls(0) + { + } +#endif + + public: + // Support for caching the intermediate tensor product of control + // points with the blending matrices. A precached container has all + // elements precomputed before any Evaluate(...) calls. The 'bool' + // flags are all set to 'true'. A cached container fills the elements + // on demand. The 'bool' flags are initially 'false', indicating the + // EvalType component has not yet been computed. After it is computed + // and stored, the flag is set to 'true'. + enum + { + NO_CACHING, + PRE_CACHING, + ON_DEMAND_CACHING + }; + + // Member access. + inline int GetDegree(int dim) const { return mDegree[dim]; } + inline int GetNumControls(int dim) const { return mNumControls[dim]; } + inline Real GetTMin(int dim) const { return mTMin[dim]; } + inline Real GetTMax(int dim) const { return mTMax[dim]; } + inline int GetCacheMode() const { return mCacheMode; } + + protected: + // Disallow copying and moving. + IntpBSplineUniformShared(IntpBSplineUniformShared const&) = delete; + IntpBSplineUniformShared& operator=(IntpBSplineUniformShared const&) = delete; + IntpBSplineUniformShared(IntpBSplineUniformShared&&) = delete; + IntpBSplineUniformShared& operator=(IntpBSplineUniformShared&&) = delete; + + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid(int const* degrees) + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (mControls->GetSize(dim) <= degrees[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + } + return true; + } + + // ComputeBlendingMatrix, ComputeDCoefficients, ComputePowers and + // GetKey are used by the general-dimension derived classes and by + // the specializations for dimensions 1, 2 and 3. + + // Compute the blending matrix that combines the control points and + // the polynomial vector. The matrix A is stored in row-major order. + static void ComputeBlendingMatrix(int degree, std::vector& A) + { + int const degreeP1 = degree + 1; + A.resize(degreeP1 * degreeP1); + + if (degree == 0) + { + A[0] = (Real)1; + return; + } + + // P_{0,0}(s) + std::vector> P(degreeP1); + P[0][0] = (Real)1; + + // L0 = s/j + Polynomial1 L0(1); + L0[0] = (Real)0; + + // L1(s) = (j + 1 - s)/j + Polynomial1 L1(1); + + // s-1 is used in computing translated P_{j-1,k-1}(s-1) + Polynomial1 sm1 = { (Real)-1, (Real)1 }; + + // Compute + // P_{j,k}(s) = L0(s)*P_{j-1,k}(s) + L1(s)*P_{j-1,k-1}(s-1) + // for 0 <= k <= j where 1 <= j <= degree. When k = 0, + // P_{j-1,-1}(s) = 0, so P_{j,0}(s) = L0(s)*P_{j-1,0}(s). When + // k = j, P_{j-1,j}(s) = 0, so P_{j,j}(s) = L1(s)*P_{j-1,j-1}(s). + // The polynomials at level j-1 are currently stored in P[0] + // through P[j-1]. The polynomials at level j are computed and + // stored in P[0] through P[j]; that is, they are computed in + // place to reduce memory usage and copying. This requires + // computing P[k] (level j) from P[k] (level j-1) and P[k-1] + // (level j-1), which means we have to process k = j down to + // k = 0. + for (int j = 1; j <= degree; ++j) + { + Real invJ = (Real)1 / (Real)j; + L0[1] = invJ; + L1[0] = (Real)1 + invJ; + L1[1] = -invJ; + + for (int k = j; k >= 0; --k) + { + Polynomial1 result = { (Real)0 }; + + if (k > 0) + { + result += L1 * P[k - 1].GetTranslation((Real)1); + } + + if (k < j) + { + result += L0 * P[k]; + } + + P[k] = result; + } + } + + // Compute Q_{d,k}(s) = P_{d,k}(s + k). + std::vector> Q(degreeP1); + for (int k = 0; k <= degree; ++k) + { + Q[k] = P[k].GetTranslation(static_cast(-k)); + } + + // Extract the matrix A from the Q-polynomials. Row r of A + // contains the coefficients of Q_{d,d-r}(s). + for (int k = 0, row = degree; k <= degree; ++k, --row) + { + for (int col = 0; col <= degree; ++col) + { + A[col + degreeP1 * row] = Q[k][col]; + } + } + } + + // Compute the coefficients for the derivative polynomial terms. + static void ComputeDCoefficients(int degree, std::vector& dCoefficients, + std::vector& ellMax) + { + int numDCoefficients = (degree + 1) * (degree + 2) / 2; + dCoefficients.resize(numDCoefficients); + for (int i = 0; i < numDCoefficients; ++i) + { + dCoefficients[i] = 1; + } + + for (int order = 1, col0 = 0, col1 = degree + 1; order <= degree; ++order) + { + ++col0; + for (int c = order, m = 1; c <= degree; ++c, ++m, ++col0, ++col1) + { + dCoefficients[col1] = dCoefficients[col0] * m; + } + } + + ellMax.resize(degree + 1); + ellMax[0] = degree; + for (int i0 = 0, i1 = 1; i1 <= degree; i0 = i1++) + { + ellMax[i1] = ellMax[i0] + degree - i0; + } + } + + // Compute powers of ds/dt. + void ComputePowers(int degree, int numControls, Real tmin, Real tmax, + std::vector& powerDSDT) + { + Real dsdt = static_cast(numControls - degree) / (tmax - tmin); + powerDSDT.resize(degree + 1); + powerDSDT[0] = (Real)1; + powerDSDT[1] = dsdt; + for (int i = 2; i <= degree; ++i) + { + powerDSDT[i] = powerDSDT[i - 1] * dsdt; + } + } + + // Determine the interval [index,index+1) corresponding to the + // specified value of t and compute u in that interval. + static void GetKey(Real t, Real tmin, Real tmax, Real dsdt, int numControls, + int degree, int& index, Real& u) + { + // Compute s - d = ((c + 1 - d)/(c + 1))(t + 1/2), the index for + // which d + index <= s < d + index + 1. Let u = s - d - index so + // that 0 <= u < 1. + if (t > tmin) + { + if (t < tmax) + { + Real smd = dsdt * (t - tmin); + index = static_cast(std::floor(smd)); + u = smd - static_cast(index); + } + else + { + // In the evaluation, s = c + 1 - d and i = c - d. This + // causes s-d-i to be 1 in G_c(c+1-d). Effectively, the + // selection of i extends the s-domain [d,c+1) to its + // support [d,c+1]. + index = numControls - 1 - degree; + u = (Real)1; + } + } + else + { + index = 0; + u = (Real)0; + } + } + + // The remaining functions are used only by the general-dimension + // derived classes when caching is enabled. + + // For the multidimensional tensor Phi{iTuple, kTuple), compute the + // portion of the 1-dimensional index that corresponds to iTuple. + int GetRowIndex(std::vector const& i) const + { + int rowIndex = i[mNumDimensions - 1]; + int j1 = 2 * mNumDimensions - 2; + for (int j0 = mNumDimensions - 2; j0 >= 0; --j0, --j1) + { + rowIndex = mTBound[j1] * rowIndex + i[j0]; + } + rowIndex = mTBound[j1] * rowIndex; + return rowIndex; + } + + // For the multidimensional tensor Phi{iTuple, kTuple), combine the + // GetRowIndex(...) output with kTuple to produce the full + // 1-dimensional index. + int GetIndex(int rowIndex, std::vector const& k) const + { + int index = rowIndex + k[mNumDimensions - 1]; + for (int j = mNumDimensions - 2; j >= 0; --j) + { + index = mTBound[j] * index + k[j]; + } + return index; + } + + // Compute Phi(iTuple, kTuple). The 'index' value is an already + // computed 1-dimensional index for the tensor. + void ComputeTensor(int const* i, int const* k, int index) + { + auto element = mCTZero; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mComputeJTuple[dim] = 0; + } + for (int iterate = 0; iterate < mNumLocalControls; ++iterate) + { + Real blend(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + blend *= mBlender[dim][k[dim] + mDegreeP1[dim] * mComputeJTuple[dim]]; + mComputeSumIJTuple[dim] = i[dim] + mComputeJTuple[dim]; + } + element = element + (*mControls)(mComputeSumIJTuple.data()) * blend; + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mComputeJTuple[dim] < mDegreeP1[dim]) + { + break; + } + mComputeJTuple[dim] = 0; + } + } + mTensor[index] = element; + } + + // Allocate the containers used for caching and fill in the tensor + // for precaching when that mode is selected. + void InitializeTensors() + { + mTBound.resize(2 * mNumDimensions); + mComputeJTuple.resize(mNumDimensions); + mComputeSumIJTuple.resize(mNumDimensions); + mDegreeMinusOrder.resize(mNumDimensions); + mTerm.resize(mNumDimensions); + + int current = 0; + int numCached = 1; + for (int dim = 0; dim < mNumDimensions; ++dim, ++current) + { + mTBound[current] = mDegreeP1[dim]; + numCached *= mTBound[current]; + } + for (int dim = 0; dim < mNumDimensions; ++dim, ++current) + { + mTBound[current] = mNumControls[dim] - mDegree[dim]; + numCached *= mTBound[current]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == PRE_CACHING) + { + std::vector tuple(2 * mNumDimensions, 0); + for (int index = 0; index < numCached; ++index) + { + ComputeTensor(&tuple[mNumDimensions], &tuple[0], index); + for (int i = 0; i < 2 * mNumDimensions; ++i) + { + if (++tuple[i] < mTBound[i]) + { + break; + } + tuple[i] = 0; + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type EvaluateNoCaching(int const* order, Real const* t) + { + auto result = mCTZero; + if (!mValid) + { + return result; + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (order[dim] < 0 || order[dim] > mDegree[dim]) + { + return result; + } + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * mUTuple[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mJTuple[dim] = 0; + mSumIJTuple[dim] = mITuple[dim]; + mPTuple[dim] = mPhi[dim][0]; + } + for (int iterate = 0; iterate < mNumLocalControls; ++iterate) + { + Real product(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + product *= mPTuple[dim]; + } + + result = result + (*mControls)(mSumIJTuple.data()) * product; + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mJTuple[dim] <= mDegree[dim]) + { + mSumIJTuple[dim] = mITuple[dim] + mJTuple[dim]; + mPTuple[dim] = mPhi[dim][mJTuple[dim]]; + break; + } + mJTuple[dim] = 0; + mSumIJTuple[dim] = mITuple[dim]; + mPTuple[dim] = mPhi[dim][0]; + } + } + + Real adjust(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + return result; + } + + typename Controls::Type EvaluateCaching(int const* order, Real const* t) + { + if (!mValid) + { + return mCTZero; + } + + int numIterates = 1; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mDegreeMinusOrder[dim] = mDegree[dim] - order[dim]; + if (mDegreeMinusOrder[dim] < 0 || mDegreeMinusOrder[dim] > mDegree[dim]) + { + return mCTZero; + } + numIterates *= mDegreeMinusOrder[dim] + 1; + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); + } + + int rowIndex = GetRowIndex(mITuple); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mJTuple[dim] = 0; + mKTuple[dim] = mDegree[dim]; + mLTuple[dim] = mLMax[dim][order[dim]]; + mTerm[dim] = mCTZero; + } + for (int iterate = 0; iterate < numIterates; ++iterate) + { + int index = GetIndex(rowIndex, mKTuple); + if (mCacheMode == ON_DEMAND_CACHING && !mCached[index]) + { + ComputeTensor(mITuple.data(), mKTuple.data(), index); + mCached[index] = true; + } + mTerm[0] = mTerm[0] * mUTuple[0] + mTensor[index] * mDCoefficient[0][mLTuple[0]]; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mJTuple[dim] <= mDegreeMinusOrder[dim]) + { + --mKTuple[dim]; + --mLTuple[dim]; + break; + } + int dimp1 = dim + 1; + if (dimp1 < mNumDimensions) + { + mTerm[dimp1] = mTerm[dimp1] * mUTuple[dimp1] + mTerm[dim] * mDCoefficient[dimp1][mLTuple[dimp1]]; + mTerm[dim] = mCTZero; + mJTuple[dim] = 0; + mKTuple[dim] = mDegree[dim]; + mLTuple[dim] = mLMax[dim][order[dim]]; + } + } + } + auto result = mTerm[mNumDimensions - 1]; + + Real adjust(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + return result; + } + + // Constructor inputs. + int const mNumDimensions; // N + std::vector mDegree; // degree[N] + Controls const* mControls; + typename Controls::Type const mCTZero; + int const mCacheMode; + + // Parameters for B-spline evaluation. All std::vector containers + // have N elements. + int mNumLocalControls; // product of (degree[]+1) + std::vector mDegreeP1; + std::vector mNumControls; + std::vector mTMin; + std::vector mTMax; + std::vector> mBlender; + std::vector> mDCoefficient; + std::vector> mLMax; + std::vector> mPowerDSDT; + std::vector mITuple; + std::vector mJTuple; + std::vector mKTuple; + std::vector mLTuple; + std::vector mSumIJTuple; + std::vector mUTuple; + std::vector mPTuple; + + // Support for no-cached B-spline evaluation. The std::vector + // container has N elements. + std::vector> mPhi; + + // Support for cached B-spline evaluation. + std::vector mTBound; // tbound[2*N] + std::vector mComputeJTuple; // computejtuple[N] + std::vector mComputeSumIJTuple; // computesumijtuple[N] + std::vector mDegreeMinusOrder; // degreeminusorder[N] + std::vector mTerm; // mTerm[N] + std::vector mTensor; // depends on numcontrols + std::vector mCached; // same size as mTensor + + bool mValid; + }; +} + +// Implementation for B-spline interpolation whose dimension is known at +// compile time. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(N, degrees.data(), controls, + ctZero, cacheMode) + { + if (!this->mValid) + { + return; + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + if (this->mValid) + { + if (this->mCacheMode == this->NO_CACHING) + { + return this->EvaluateNoCaching(order.data(), t.data()); + } + else + { + return this->EvaluateCaching(order.data(), t.data()); + } + } + else + { + return this->mCTZero; + } + } + }; +} + +// Implementation for B-spline interpolation whose dimension is known only +// at run time. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::vector const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(static_cast(degrees.size()), + degrees.data(), controls, ctZero, cacheMode) + { + if (!this->mValid) + { + return; + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type Evaluate(std::vector const& order, + std::vector const& t) + { + if (this->mValid + && static_cast(order.size()) >= this->mNumDimensions + && static_cast(t.size()) >= this->mNumDimensions) + { + if (this->mCacheMode == this->NO_CACHING) + { + return this->EvaluateNoCaching(order.data(), t.data()); + } + else + { + return this->EvaluateCaching(order.data(), t.data()); + } + } + else + { + return this->mCTZero; + } + } + }; +} + +// To use only the N-dimensional template code above, define the symbol +// GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION to disable the specializations +// that occur below. Notice that the interfaces are different between the +// specializations and the general code. +#if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) + +// Specialization for 1-dimensional data. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(int degree, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degree), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mValid(IsValid()) + { + if (!mValid) + { + return; + } + + mDegreeP1 = mDegree + 1; + mNumControls = mControls->GetSize(0); + mTMin = (Real)-0.5; + mTMax = static_cast(mNumControls) - (Real)0.5; + mNumTRows = 0; + mNumTCols = 0; + this->ComputeBlendingMatrix(mDegree, mBlender); + this->ComputeDCoefficients(mDegree, mDCoefficient, mLMax); + this->ComputePowers(mDegree, mNumControls, mTMin, mTMax, mPowerDSDT); + if (mCacheMode == this->NO_CACHING) + { + mPhi.resize(mDegreeP1); + } + else + { + InitializeTensors(); + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. + inline int GetDegree(int) const { return mDegree; } + inline int GetNumControls(int) const { return mNumControls; } + inline Real GetTMin(int) const { return mTMin; } + inline Real GetTMax(int) const { return mTMax; } + inline int GetCacheMode() const { return mCacheMode; } + + // Evaluate the interpolator. The order is 0 when you want the B-spline + // function value itself. The order is 1 for the first derivative of the + // function, and so on. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (mValid + && 0 <= order[0] && order[0] <= mDegree) + { + int i; + Real u; + this->GetKey(t[0], mTMin, mTMax, mPowerDSDT[1], mNumControls, mDegree, i, u); + + if (mCacheMode == this->NO_CACHING) + { + int jIndex = 0; + for (int j = 0; j <= mDegree; ++j) + { + int kjIndex = mDegree + jIndex; + int ell = mLMax[order[0]]; + mPhi[j] = (Real)0; + for (int k = mDegree; k >= order[0]; --k) + { + mPhi[j] = mPhi[j] * u + mBlender[kjIndex--] * mDCoefficient[ell--]; + } + jIndex += mDegreeP1; + } + + for (int j = 0; j <= mDegree; ++j) + { + result = result + (*mControls)(i + j) * mPhi[j]; + } + } + else + { + int iIndex = mNumTCols * i; + int kiIndex = mDegree + iIndex; + int ell = mLMax[order[0]]; + for (int k = mDegree; k >= order[0]; --k) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[kiIndex]) + { + ComputeTensor(i, k, kiIndex); + mCached[kiIndex] = true; + } + + result = result * u + mTensor[kiIndex--] * mDCoefficient[ell--]; + } + } + + result = result * mPowerDSDT[order[0]]; + } + return result; + } + + protected: + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid() + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + if (mControls->GetSize(0) <= mDegree + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + return true; + } + + void ComputeTensor(int r, int c, int index) + { + auto element = mCTZero; + for (int j = 0; j <= mDegree; ++j) + { + element = element + (*mControls)(r + j) * mBlender[c + mDegreeP1 * j]; + } + mTensor[index] = element; + } + + void InitializeTensors() + { + mNumTRows = mNumControls - mDegree; + mNumTCols = mDegreeP1; + int numCached = mNumTRows * mNumTCols; + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r = 0, index = 0; r < mNumTRows; ++r) + { + for (int c = 0; c < mNumTCols; ++c, ++index) + { + ComputeTensor(r, c, index); + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + int mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + int mDegreeP1; + int mNumControls; + Real mTMin, mTMax; + std::vector mBlender; + std::vector mDCoefficient; + std::vector mLMax; + std::vector mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::vector mPhi; + + // Support for cached B-spline evaluation. + int mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + + bool mValid; + }; +} + +// Specialization for 2-dimensional data. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform2 + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degrees), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mValid(IsValid()) + { + if (!mValid) + { + return; + } + + for (int dim = 0; dim < 2; ++dim) + { + mDegreeP1[dim] = mDegree[dim] + 1; + mNumControls[dim] = mControls->GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + mNumTRows[dim] = 0; + mNumTCols[dim] = 0; + this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 2; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. + inline int GetDegree(int dim) const { return mDegree[dim]; } + inline int GetNumControls(int dim) const { return mNumControls[dim]; } + inline Real GetTMin(int dim) const { return mTMin[dim]; } + inline Real GetTMax(int dim) const { return mTMax[dim]; } + inline int GetCacheMode() const { return mCacheMode; } + + // Evaluate the interpolator. The order is (0,0) when you want the + // B-spline function value itself. The order0 is 1 for the first + // derivative with respect to t0 and the order1 is 1 for the first + // derivative with respect to t1. Higher-order derivatives in other + // t-inputs are computed similarly. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (mValid + && 0 <= order[0] && order[0] <= mDegree[0] + && 0 <= order[1] && order[1] <= mDegree[1]) + { + std::array i; + std::array u; + for (int dim = 0; dim < 2; ++dim) + { + this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], i[dim], u[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 2; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * u[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real phi1 = mPhi[1][j1]; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real phi0 = mPhi[0][j0]; + Real phi01 = phi0 * phi1; + result = result + (*mControls)(i[0] + j0, i[1] + j1) * phi01; + } + } + } + else + { + int i0i1Index = mNumTCols[1] * (i[0] + mNumTRows[0] * i[1]); + int k1i0i1Index = mDegree[1] + i0i1Index; + int ell1 = mLMax[1][order[1]]; + for (int k1 = mDegree[1]; k1 >= order[1]; --k1) + { + int k0k1i0i1Index = mDegree[0] + mNumTCols[0] * k1i0i1Index; + int ell0 = mLMax[0][order[0]]; + auto term = mCTZero; + for (int k0 = mDegree[0]; k0 >= order[0]; --k0) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1i0i1Index]) + { + ComputeTensor(i[0], i[1], k0, k1, k0k1i0i1Index); + mCached[k0k1i0i1Index] = true; + } + term = term * u[0] + mTensor[k0k1i0i1Index--] * mDCoefficient[0][ell0--]; + } + result = result * u[1] + term * mDCoefficient[1][ell1--]; + --k1i0i1Index; + } + } + + Real adjust(1); + for (int dim = 0; dim < 2; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + } + return result; + } + + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid() + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < 2; ++dim) + { + if (mControls->GetSize(dim) <= mDegree[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + } + return true; + } + + void ComputeTensor(int r0, int r1, int c0, int c1, int index) + { + auto element = mCTZero; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; + Real blend01 = blend0 * blend1; + element = element + (*mControls)(r0 + j0, r1 + j1) * blend01; + } + } + mTensor[index] = element; + } + + void InitializeTensors() + { + int numCached = 1; + for (int dim = 0; dim < 2; ++dim) + { + mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; + mNumTCols[dim] = mDegreeP1[dim]; + numCached *= mNumTRows[dim] * mNumTCols[dim]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r1 = 0, index = 0; r1 < mNumTRows[1]; ++r1) + { + for (int r0 = 0; r0 < mNumTRows[0]; ++r0) + { + for (int c1 = 0; c1 < mNumTCols[1]; ++c1) + { + for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) + { + ComputeTensor(r0, r1, c0, c1, index); + } + } + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + std::array mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + std::array mDegreeP1; + std::array mNumControls; + std::array mTMin, mTMax; + std::array, 2> mBlender; + std::array, 2> mDCoefficient; + std::array, 2> mLMax; + std::array, 2> mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::array, 2> mPhi; + + // Support for cached B-spline evaluation. + std::array mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + + bool mValid; + }; +} + +// Specialization for 3-dimensional data. +namespace gte +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform3 + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degrees), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mValid(IsValid()) + { + if (!mValid) + { + return; + } + + for (int dim = 0; dim < 3; ++dim) + { + mDegreeP1[dim] = mDegree[dim] + 1; + mNumControls[dim] = mControls->GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + mNumTRows[dim] = 0; + mNumTCols[dim] = 0; + this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 3; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. The input i specifies the dimension (0, 1, 2). + inline int GetDegree(int dim) const { return mDegree[dim]; } + inline int GetNumControls(int dim) const { return mNumControls[dim]; } + inline Real GetTMin(int dim) const { return mTMin[dim]; } + inline Real GetTMax(int dim) const { return mTMax[dim]; } + + // Evaluate the interpolator. The order is (0,0,0) when you want the + // B-spline function value itself. The order0 is 1 for the first + // derivative with respect to t0, the order1 is 1 for the first + // derivative with respect to t1 or the order2 is 1 for the first + // derivative with respect to t2. Higher-order derivatives in other + // t-inputs are computed similarly. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (mValid + && 0 <= order[0] && order[0] <= mDegree[0] + && 0 <= order[1] && order[1] <= mDegree[1] + && 0 <= order[2] && order[2] <= mDegree[2]) + { + std::array i; + std::array u; + for (int dim = 0; dim < 3; ++dim) + { + this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], i[dim], u[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 3; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * u[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int j2 = 0; j2 <= mDegree[2]; ++j2) + { + Real phi2 = mPhi[2][j2]; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real phi1 = mPhi[1][j1]; + Real phi12 = phi1 * phi2; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real phi0 = mPhi[0][j0]; + Real phi012 = phi0 * phi12; + result = result + (*mControls)(i[0] + j0, i[1] + j1, i[2] + j2) * phi012; + } + } + } + } + else + { + int i0i1i2Index = mNumTCols[2] * (i[0] + mNumTRows[0] * (i[1] + mNumTRows[1] * i[2])); + int k2i0i1i2Index = mDegree[2] + i0i1i2Index; + int ell2 = mLMax[2][order[2]]; + for (int k2 = mDegree[2]; k2 >= order[2]; --k2) + { + int k1k2i0i1i2Index = mDegree[1] + mNumTCols[1] * k2i0i1i2Index; + int ell1 = mLMax[1][order[1]]; + auto term1 = mCTZero; + for (int k1 = mDegree[1]; k1 >= order[1]; --k1) + { + int k0k1k2i0i1i2Index = mDegree[0] + mNumTCols[0] * k1k2i0i1i2Index; + int ell0 = mLMax[0][order[0]]; + auto term0 = mCTZero; + for (int k0 = mDegree[0]; k0 >= order[0]; --k0) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1k2i0i1i2Index]) + { + ComputeTensor(i[0], i[1], i[2], k0, k1, k2, k0k1k2i0i1i2Index); + mCached[k0k1k2i0i1i2Index] = true; + } + + term0 = term0 * u[0] + mTensor[k0k1k2i0i1i2Index--] * mDCoefficient[0][ell0--]; + } + term1 = term1 * u[1] + term0 * mDCoefficient[1][ell1--]; + --k1k2i0i1i2Index; + } + result = result * u[2] + term1 * mDCoefficient[2][ell2--]; + --k2i0i1i2Index; + } + } + + Real adjust(1); + for (int dim = 0; dim < 3; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + } + return result; + } + + protected: + // Verify that the constraints for number of controls and degrees + // are satisfied. + bool IsValid() + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < 3; ++dim) + { + if (mControls->GetSize(dim) <= mDegree[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + return false; + } + } + return true; + } + + void ComputeTensor(int r0, int r1, int r2, int c0, int c1, int c2, int index) + { + auto element = mCTZero; + for (int j2 = 0; j2 <= mDegree[2]; ++j2) + { + Real blend2 = mBlender[2][c2 + mDegreeP1[2] * j2]; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; + Real blend12 = blend1 * blend2; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; + Real blend012 = blend0 * blend12; + element = element + (*mControls)(r0 + j0, r1 + j1, r2 + j2) * blend012; + } + } + } + mTensor[index] = element; + } + + void InitializeTensors() + { + int numCached = 1; + for (int dim = 0; dim < 3; ++dim) + { + mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; + mNumTCols[dim] = mDegreeP1[dim]; + numCached *= mNumTRows[dim] * mNumTCols[dim]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r2 = 0, index = 0; r2 < mNumTRows[2]; ++r2) + { + for (int r1 = 0; r1 < mNumTRows[1]; ++r1) + { + for (int r0 = 0; r0 < mNumTRows[0]; ++r0) + { + for (int c2 = 0; c2 < mNumTCols[2]; ++c2) + { + for (int c1 = 0; c1 < mNumTCols[1]; ++c1) + { + for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) + { + ComputeTensor(r0, r1, r2, c0, c1, c2, index); + } + } + } + } + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + std::array mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + std::array mDegreeP1; + std::array mNumControls; + std::array mTMin, mTMax; + std::array, 3> mBlender; + std::array, 3> mDCoefficient; + std::array, 3> mLMax; + std::array, 3> mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::array, 3> mPhi; + + // Support for cached B-spline evaluation. + std::array mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + + bool mValid; + }; +} + +#endif diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBicubic2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBicubic2.h new file mode 100644 index 000000000000..e92425e14a0d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBicubic2.h @@ -0,0 +1,425 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. Exact interpolation is +// achieved by setting catmullRom to 'true', giving you the Catmull-Rom +// blending matrix. If a smooth interpolation is desired, set catmullRom to +// 'false' to obtain B-spline blending. + +namespace gte +{ + +template +class IntpBicubic2 +{ +public: + // Construction. + IntpBicubic2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F, bool catmullRom); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first operator + // is for function evaluation. The second operator is for function or + // derivative evaluations. The xOrder argument is the order of the + // x-derivative and the yOrder argument is the order of the y-derivative. + // Both orders are zero to get the function value itself. + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + +private: + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real const* mF; + Real mBlend[4][4]; +}; + + +template +IntpBicubic2::IntpBicubic2(int xBound, int yBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real const* F, bool catmullRom) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound * yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F) +{ + // At least a 3x3 block of data points are needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mF, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + + if (catmullRom) + { + mBlend[0][0] = (Real)0; + mBlend[0][1] = (Real)-0.5; + mBlend[0][2] = (Real)1; + mBlend[0][3] = (Real)-0.5; + mBlend[1][0] = (Real)1; + mBlend[1][1] = (Real)0; + mBlend[1][2] = (Real)-2.5; + mBlend[1][3] = (Real)1.5; + mBlend[2][0] = (Real)0; + mBlend[2][1] = (Real)0.5; + mBlend[2][2] = (Real)2; + mBlend[2][3] = (Real)-1.5; + mBlend[3][0] = (Real)0; + mBlend[3][1] = (Real)0; + mBlend[3][2] = (Real)-0.5; + mBlend[3][3] = (Real)0.5; + } + else + { + mBlend[0][0] = (Real)1 / (Real)6; + mBlend[0][1] = (Real)-3 / (Real)6; + mBlend[0][2] = (Real)3 / (Real)6; + mBlend[0][3] = (Real)-1 / (Real)6;; + mBlend[1][0] = (Real)4 / (Real)6; + mBlend[1][1] = (Real)0 / (Real)6; + mBlend[1][2] = (Real)-6 / (Real)6; + mBlend[1][3] = (Real)3 / (Real)6; + mBlend[2][0] = (Real)1 / (Real)6; + mBlend[2][1] = (Real)3 / (Real)6; + mBlend[2][2] = (Real)3 / (Real)6; + mBlend[2][3] = (Real)-3 / (Real)6; + mBlend[3][0] = (Real)0 / (Real)6; + mBlend[3][1] = (Real)0 / (Real)6; + mBlend[3][2] = (Real)0 / (Real)6; + mBlend[3][3] = (Real)1 / (Real)6; + } +} + +template inline +int IntpBicubic2::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpBicubic2::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpBicubic2::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpBicubic2::GetF() const +{ + return mF; +} + +template inline +Real IntpBicubic2::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpBicubic2::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpBicubic2::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpBicubic2::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpBicubic2::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpBicubic2::GetYSpacing() const +{ + return mYSpacing; +} + +template +Real IntpBicubic2::operator()(Real x, Real y) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[4]; + U[0] = (Real)1; + U[1] = xIndex - ix; + U[2] = U[1] * U[1]; + U[3] = U[1] * U[2]; + + Real V[4]; + V[0] = (Real)1; + V[1] = yIndex - iy; + V[2] = V[1] * V[1]; + V[3] = V[1] * V[2]; + + // Compute P = M*U and Q = M*V. + Real P[4], Q[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 4x4 subimage containing (x,y). + --ix; + --iy; + Real result = (Real)0; + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + + return result; +} + +template +Real IntpBicubic2::operator()(int xOrder, int yOrder, Real x, Real y) +const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[4], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + U[2] = dx * U[1]; + U[3] = dx * U[2]; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + U[2] = ((Real)2) * dx; + U[3] = ((Real)3) * dx * dx; + xMult = mInvXSpacing; + break; + case 2: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)2; + U[3] = (Real)6 * dx; + xMult = mInvXSpacing * mInvXSpacing; + break; + case 3: + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)0; + U[3] = (Real)6; + xMult = mInvXSpacing * mInvXSpacing * mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[4], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + V[2] = dy * V[1]; + V[3] = dy * V[2]; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + V[2] = ((Real)2) * dy; + V[3] = ((Real)3) * dy * dy; + yMult = mInvYSpacing; + break; + case 2: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)2; + V[3] = ((Real)6) * dy; + yMult = mInvYSpacing * mInvYSpacing; + break; + case 3: + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)0; + V[3] = (Real)6; + yMult = mInvYSpacing * mInvYSpacing * mInvYSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U and Q = M*V. + Real P[4], Q[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 4x4 subimage containing (x,y). + --ix; + --iy; + Real result = (Real)0; + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + result *= xMult * yMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBilinear2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBilinear2.h new file mode 100644 index 000000000000..f982084b9374 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpBilinear2.h @@ -0,0 +1,325 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. + +namespace gte +{ + +template +class IntpBilinear2 +{ +public: + // Construction. + IntpBilinear2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first operator + // is for function evaluation. The second operator is for function or + // derivative evaluations. The xOrder argument is the order of the + // x-derivative and the yOrder argument is the order of the y-derivative. + // Both orders are zero to get the function value itself. + Real operator()(Real x, Real y) const; + Real operator()(int xOrder, int yOrder, Real x, Real y) const; + +private: + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real const* mF; + Real mBlend[2][2]; +}; + + +template +IntpBilinear2::IntpBilinear2(int xBound, int yBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound * yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F) +{ + // At least a 2x2 block of data points are needed. + LogAssert(mXBound >= 2 && mYBound >= 2 && mF, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + + mBlend[0][0] = (Real)1; + mBlend[0][1] = (Real)-1; + mBlend[1][0] = (Real)0; + mBlend[1][1] = (Real)1; +} + +template inline +int IntpBilinear2::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpBilinear2::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpBilinear2::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpBilinear2::GetF() const +{ + return mF; +} + +template inline +Real IntpBilinear2::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpBilinear2::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpBilinear2::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpBilinear2::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpBilinear2::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpBilinear2::GetYSpacing() const +{ + return mYSpacing; +} + +template +Real IntpBilinear2::operator()(Real x, Real y) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[2]; + U[0] = (Real)1; + U[1] = xIndex - ix; + + Real V[2]; + V[0] = (Real)1.0; + V[1] = yIndex - iy; + + // Compute P = M*U and Q = M*V. + Real P[2], Q[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 2x2 subimage containing (x,y). + Real result = (Real)0; + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + + return result; +} + +template +Real IntpBilinear2::operator()(int xOrder, int yOrder, Real x, Real y) +const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + Real U[2], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + xMult = mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[2], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + yMult = mInvYSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U and Q = M*V. + Real P[2], Q[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 2x2 subimage containing (x,y). + Real result = (Real)0; + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + result *= xMult * yMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform2.h new file mode 100644 index 000000000000..9f0c0afca2f0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform2.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Linear interpolation of a network of triangles whose vertices are of the +// form (x,y,f(x,y)). The function samples are F[i] and represent +// f(x[i],y[i]), where i is the index of the input vertex (x[i],y[i]) to +// Delaunay2. +// +// The TriangleMesh interface must support the following: +// bool GetIndices(int, std::array&) const; +// bool GetBarycentrics(int, Vector2 const&, +// std::array&) const; +// int GetContainingTriangle(Vector2 const&) const; + +#include +#include + +namespace gte +{ + +template +class IntpLinearNonuniform2 +{ +public: + // Construction. + IntpLinearNonuniform2(TriangleMesh const& mesh, Real const* F); + + // Linear interpolation. The return value is 'true' if and only if the + // input point P is in the convex hull of the input vertices, in which + // case the interpolation is valid. + bool operator()(Vector2 const& P, Real& F) const; + +private: + TriangleMesh const* mMesh; + Real const* mF; +}; + + +template +IntpLinearNonuniform2::IntpLinearNonuniform2( + TriangleMesh const& mesh, Real const* F) + : + mMesh(&mesh), + mF(F) +{ + LogAssert(mF != nullptr, "Invalid input."); +} + +template +bool IntpLinearNonuniform2::operator()( + Vector2 const& P, Real& F) const +{ + int t = mMesh->GetContainingTriangle(P); + if (t == -1) + { + // The point is outside the triangulation. + return false; + } + + // Get the barycentric coordinates of P with respect to the triangle, + // P = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1. + std::array bary; + if (!mMesh->GetBarycentrics(t, P, bary)) + { + LogWarning("P is in a needle-like or degenerate triangle."); + return false; + } + + // The result is a barycentric combination of function values. + std::array indices; + mMesh->GetIndices(t, indices); + F = bary[0] * mF[indices[0]] + bary[1] * mF[indices[1]] + + bary[2] * mF[indices[2]]; + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform3.h new file mode 100644 index 000000000000..d8af848c3535 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpLinearNonuniform3.h @@ -0,0 +1,83 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +// Linear interpolation of a network of triangles whose vertices are of the +// form (x,y,z,f(x,y,z)). The function samples are F[i] and represent +// f(x[i],y[i],z[i]), where i is the index of the input vertex +// (x[i],y[i],z[i]) to Delaunay3. +// +// The TetrahedronMesh interface must support the following: +// int GetContainingTetrahedron(Vector3 const&) const; +// bool GetIndices(int, std::array&) const; +// bool GetBarycentrics(int, Vector3 const&, Real[4]) const; + +#include +#include + +namespace gte +{ + +template +class IntpLinearNonuniform3 +{ +public: + // Construction. + IntpLinearNonuniform3(TetrahedronMesh const& mesh, Real const* F); + + // Linear interpolation. The return value is 'true' if and only if the + // input point is in the convex hull of the input vertices, in which case + // the interpolation is valid. + bool operator()(Vector3 const& P, Real& F) const; + +private: + TetrahedronMesh const* mMesh; + Real const* mF; +}; + + +template +IntpLinearNonuniform3::IntpLinearNonuniform3( + TetrahedronMesh const& mesh, Real const* F) + : + mMesh(&mesh), + mF(F) +{ + LogAssert(mF != nullptr, "Invalid input."); +} + +template +bool IntpLinearNonuniform3::operator()( + Vector3 const& P, Real& F) const +{ + int t = mMesh->GetContainingTetrahedron(P); + if (t == -1) + { + // The point is outside the tetrahedralization. + return false; + } + + // Get the barycentric coordinates of P with respect to the tetrahedron, + // P = b0*V0 + b1*V1 + b2*V2 + b3*V3, where b0 + b1 + b2 + b3 = 1. + std::array bary; + if (!mMesh->GetBarycentrics(t, P, bary)) + { + LogWarning("P is in a needle-like, flat, or degenerate tetrahedron."); + return false; + } + + // The result is a barycentric combination of function values. + std::array indices; + mMesh->GetIndices(t, indices); + F = bary[0] * mF[indices[0]] + bary[1] * mF[indices[1]] + + bary[2] * mF[indices[2]] + bary[3] * mF[indices[4]]; + return true; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpQuadraticNonuniform2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpQuadraticNonuniform2.h new file mode 100644 index 000000000000..a2bf9c2390b9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpQuadraticNonuniform2.h @@ -0,0 +1,488 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Quadratic interpolation of a network of triangles whose vertices are of +// the form (x,y,f(x,y)). This code is an implementation of the algorithm +// found in +// +// Zoltan J. Cendes and Steven H. Wong, +// C1 quadratic interpolation over arbitrary point sets, +// IEEE Computer Graphics & Applications, +// pp. 8-16, 1987 +// +// The TriangleMesh interface must support the following: +// int GetNumVertices() const; +// int GetNumTriangles() const; +// Vector2 const* GetVertices() const; +// int const* GetIndices() const; +// bool GetVertices(int, std::array, 3>&) const; +// bool GetIndices(int, std::array&) const; +// bool GetAdjacencies(int, std::array&) const; +// bool GetBarycentrics(int, Vector2 const&, +// std::array&) const; +// int GetContainingTriangle(Vector2 const&) const; + +namespace gte +{ + +template +class IntpQuadraticNonuniform2 +{ +public: + // Construction. + // + // The first constructor requires only F and a measure of the rate of + // change of the function values relative to changes in the spatial + // variables. The df/dx and df/dy values are estimated at the sample + // points using mesh normals and spatialDelta. + // + // The second constructor requires you to specify function values F and + // first-order partial derivative values df/dx and df/dy. + + IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, + Real spatialDelta); + + IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, + Real const* FX, Real const* FY); + + // Quadratic interpolation. The return value is 'true' if and only if the + // input point is in the convex hull of the input vertices, in which case + // the interpolation is valid. + bool operator()(Vector2 const & P, Real& F, Real& FX, Real& FY) + const; + +private: + void EstimateDerivatives(Real spatialDelta); + void ProcessTriangles(); + void ComputeCrossEdgeIntersections(int t); + void ComputeCoefficients(int t); + + class TriangleData + { + public: + Vector2 center; + Vector2 intersect[3]; + Real coeff[19]; + }; + + class Jet + { + public: + Real F, FX, FY; + }; + + TriangleMesh const* mMesh; + Real const* mF; + Real const* mFX; + Real const* mFY; + std::vector mFXStorage; + std::vector mFYStorage; + std::vector mTData; +}; + + +template +IntpQuadraticNonuniform2::IntpQuadraticNonuniform2( + TriangleMesh const& mesh, Real const* F, Real spatialDelta) + : + mMesh(&mesh), + mF(F), + mFX(nullptr), + mFY(nullptr) +{ + EstimateDerivatives(spatialDelta); + ProcessTriangles(); +} + +template +IntpQuadraticNonuniform2::IntpQuadraticNonuniform2( + TriangleMesh const& mesh, Real const* F, Real const* FX, Real const* FY) + : + mMesh(&mesh), + mF(F), + mFX(FX), + mFY(FY) +{ + ProcessTriangles(); +} + +template +bool IntpQuadraticNonuniform2::operator()( + Vector2 const& P, Real& F, Real& FX, Real& FY) const +{ + int t = mMesh->GetContainingTriangle(P); + if (t == -1) + { + // The point is outside the triangulation. + return false; + } + + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the additional information for the triangle. + TriangleData const& tData = mTData[t]; + + // Determine which of the six subtriangles contains the target point. + // Theoretically, P must be in one of these subtriangles. + Vector2 sub0 = tData.center; + Vector2 sub1; + Vector2 sub2 = tData.intersect[2]; + Vector3 bary; + int index; + + Real const zero = (Real)0, one = (Real)1; + AlignedBox3 barybox({ zero, zero, zero }, { one, one, one }); + DCPQuery, AlignedBox3> pbQuery; + int minIndex = 0; + Real minDistance = (Real)-1; + Vector3 minBary; + Vector2 minSub0, minSub1, minSub2; + + for (index = 1; index <= 6; ++index) + { + sub1 = sub2; + if (index % 2) + { + sub2 = V[index / 2]; + } + else + { + sub2 = tData.intersect[index / 2 - 1]; + } + + bool valid = ComputeBarycentrics(P, sub0, sub1, sub2, &bary[0]); + if (valid + && zero <= bary[0] && bary[0] <= one + && zero <= bary[1] && bary[1] <= one + && zero <= bary[2] && bary[2] <= one) + { + // P is in triangle + break; + } + + // When computing with floating-point arithmetic, rounding errors + // can cause us to reach this code when, theoretically, the point + // is in the subtriangle. Keep track of the (b0,b1,b2) that is + // closest to the barycentric cube [0,1]^3 and choose the triangle + // corresponding to it when all 6 tests previously fail. + Real distance = pbQuery(bary, barybox).distance; + if (minIndex == 0 || distance < minDistance) + { + minDistance = distance; + minIndex = index; + minBary = bary; + minSub0 = sub0; + minSub1 = sub1; + minSub2 = sub2; + } + } + + // If the subtriangle was not found, rounding errors caused problems. + // Choose the barycentric point closest to the box. + if (index > 6) + { + index = minIndex; + bary = minBary; + sub0 = minSub0; + sub1 = minSub1; + sub2 = minSub2; + } + + // Fetch Bezier control points. + Real bez[6] = + { + tData.coeff[0], + tData.coeff[12 + index], + tData.coeff[13 + (index % 6)], + tData.coeff[index], + tData.coeff[6 + index], + tData.coeff[1 + (index % 6)] + }; + + // Evaluate Bezier quadratic. + F = bary[0] * (bez[0] * bary[0] + bez[1] * bary[1] + bez[2] * bary[2]) + + bary[1] * (bez[1] * bary[0] + bez[3] * bary[1] + bez[4] * bary[2]) + + bary[2] * (bez[2] * bary[0] + bez[4] * bary[1] + bez[5] * bary[2]); + + // Evaluate barycentric derivatives of F. + Real FU = ((Real)2)*(bez[0] * bary[0] + bez[1] * bary[1] + + bez[2] * bary[2]); + Real FV = ((Real)2)*(bez[1] * bary[0] + bez[3] * bary[1] + + bez[4] * bary[2]); + Real FW = ((Real)2)*(bez[2] * bary[0] + bez[4] * bary[1] + + bez[5] * bary[2]); + Real duw = FU - FW; + Real dvw = FV - FW; + + // Convert back to (x,y) coordinates. + Real m00 = sub0[0] - sub2[0]; + Real m10 = sub0[1] - sub2[1]; + Real m01 = sub1[0] - sub2[0]; + Real m11 = sub1[1] - sub2[1]; + Real inv = ((Real)1) / (m00*m11 - m10*m01); + + FX = inv*(m11*duw - m10*dvw); + FY = inv*(m00*dvw - m01*duw); + return true; +} + +template +void IntpQuadraticNonuniform2::EstimateDerivatives( + Real spatialDelta) +{ + int numVertices = mMesh->GetNumVertices(); + Vector2 const* vertices = mMesh->GetVertices(); + int numTriangles = mMesh->GetNumTriangles(); + int const* indices = mMesh->GetIndices(); + + mFXStorage.resize(numVertices); + mFYStorage.resize(numVertices); + std::vector FZ(numVertices); + std::fill(mFXStorage.begin(), mFXStorage.end(), (Real)0); + std::fill(mFYStorage.begin(), mFYStorage.end(), (Real)0); + std::fill(FZ.begin(), FZ.end(), (Real)0); + + mFX = &mFXStorage[0]; + mFY = &mFYStorage[0]; + + // Accumulate normals at spatial locations (averaging process). + for (int t = 0; t < numTriangles; ++t) + { + // Get three vertices of triangle. + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + + // Compute normal vector of triangle (with positive z-component). + Real dx1 = vertices[v1][0] - vertices[v0][0]; + Real dy1 = vertices[v1][1] - vertices[v0][1]; + Real dz1 = mF[v1] - mF[v0]; + Real dx2 = vertices[v2][0] - vertices[v0][0]; + Real dy2 = vertices[v2][1] - vertices[v0][1]; + Real dz2 = mF[v2] - mF[v0]; + Real nx = dy1*dz2 - dy2*dz1; + Real ny = dz1*dx2 - dz2*dx1; + Real nz = dx1*dy2 - dx2*dy1; + if (nz < (Real)0) + { + nx = -nx; + ny = -ny; + nz = -nz; + } + + mFXStorage[v0] += nx; mFYStorage[v0] += ny; FZ[v0] += nz; + mFXStorage[v1] += nx; mFYStorage[v1] += ny; FZ[v1] += nz; + mFXStorage[v2] += nx; mFYStorage[v2] += ny; FZ[v2] += nz; + } + + // Scale the normals to form (x,y,-1). + for (int i = 0; i < numVertices; ++i) + { + if (FZ[i] != (Real)0) + { + Real inv = -spatialDelta / FZ[i]; + mFXStorage[i] *= inv; + mFYStorage[i] *= inv; + } + else + { + mFXStorage[i] = (Real)0; + mFYStorage[i] = (Real)0; + } + } +} + +template +void IntpQuadraticNonuniform2::ProcessTriangles() +{ + // Add degenerate triangles to boundary triangles so that interpolation + // at the boundary can be treated in the same way as interpolation in + // the interior. + + // Compute centers of inscribed circles for triangles. + Vector2 const* vertices = mMesh->GetVertices(); + int numTriangles = mMesh->GetNumTriangles(); + int const* indices = mMesh->GetIndices(); + mTData.resize(numTriangles); + int t; + for (t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + Circle2 circle; + Inscribe(vertices[v0], vertices[v1], vertices[v2], circle); + mTData[t].center = circle.center; + } + + // Compute cross-edge intersections. + for (t = 0; t < numTriangles; ++t) + { + ComputeCrossEdgeIntersections(t); + } + + // Compute Bezier coefficients. + for (t = 0; t < numTriangles; ++t) + { + ComputeCoefficients(t); + } +} + +template +void IntpQuadraticNonuniform2:: +ComputeCrossEdgeIntersections(int t) +{ + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the centers of adjacent triangles. + TriangleData& tData = mTData[t]; + std::array adjacencies; + mMesh->GetAdjacencies(t, adjacencies); + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int a = adjacencies[j0]; + if (a >= 0) + { + // Get center of adjacent triangle's inscribing circle. + Vector2 U = mTData[a].center; + Real m00 = V[j0][1] - V[j1][1]; + Real m01 = V[j1][0] - V[j0][0]; + Real m10 = tData.center[1] - U[1]; + Real m11 = U[0] - tData.center[0]; + Real r0 = m00*V[j0][0] + m01*V[j0][1]; + Real r1 = m10*tData.center[0] + m11*tData.center[1]; + Real invDet = ((Real)1) / (m00*m11 - m01*m10); + tData.intersect[j0][0] = (m11*r0 - m01*r1)*invDet; + tData.intersect[j0][1] = (m00*r1 - m10*r0)*invDet; + } + else + { + // No adjacent triangle, use center of edge. + tData.intersect[j0] = ((Real)0.5)*(V[j0] + V[j1]); + } + } +} + +template +void IntpQuadraticNonuniform2::ComputeCoefficients(int t) +{ + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the additional information for the triangle. + TriangleData& tData = mTData[t]; + + // Get the sample data at main triangle vertices. + std::array indices; + mMesh->GetIndices(t, indices); + Jet jet[3]; + for (int j = 0; j < 3; ++j) + { + int k = indices[j]; + jet[j].F = mF[k]; + jet[j].FX = mFX[k]; + jet[j].FY = mFY[k]; + } + + // Get centers of adjacent triangles. + std::array adjacencies; + mMesh->GetAdjacencies(t, adjacencies); + Vector2 U[3]; + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int a = adjacencies[j0]; + if (a >= 0) + { + // Get center of adjacent triangle's circumscribing circle. + U[j0] = mTData[a].center; + } + else + { + // No adjacent triangle, use center of edge. + U[j0] = ((Real)0.5)*(V[j0] + V[j1]); + } + } + + // Compute intermediate terms. + std::array cenT, cen0, cen1, cen2; + mMesh->GetBarycentrics(t, tData.center, cenT); + mMesh->GetBarycentrics(t, U[0], cen0); + mMesh->GetBarycentrics(t, U[1], cen1); + mMesh->GetBarycentrics(t, U[2], cen2); + + Real alpha = (cenT[1] * cen1[0] - cenT[0] * cen1[1]) + / (cen1[0] - cenT[0]); + Real beta = (cenT[2] * cen2[1] - cenT[1] * cen2[2]) + / (cen2[1] - cenT[1]); + Real gamma = (cenT[0] * cen0[2] - cenT[2] * cen0[0]) + / (cen0[2] - cenT[2]); + Real oneMinusAlpha = (Real)1 - alpha; + Real oneMinusBeta = (Real)1 - beta; + Real oneMinusGamma = (Real)1 - gamma; + + Real tmp, A[9], B[9]; + + tmp = cenT[0] * V[0][0] + cenT[1] * V[1][0] + cenT[2] * V[2][0]; + A[0] = ((Real)0.5)*(tmp - V[0][0]); + A[1] = ((Real)0.5)*(tmp - V[1][0]); + A[2] = ((Real)0.5)*(tmp - V[2][0]); + A[3] = ((Real)0.5)*beta*(V[2][0] - V[0][0]); + A[4] = ((Real)0.5)*oneMinusGamma*(V[1][0] - V[0][0]); + A[5] = ((Real)0.5)*gamma*(V[0][0] - V[1][0]); + A[6] = ((Real)0.5)*oneMinusAlpha*(V[2][0] - V[1][0]); + A[7] = ((Real)0.5)*alpha*(V[1][0] - V[2][0]); + A[8] = ((Real)0.5)*oneMinusBeta*(V[0][0] - V[2][0]); + + tmp = cenT[0] * V[0][1] + cenT[1] * V[1][1] + cenT[2] * V[2][1]; + B[0] = ((Real)0.5)*(tmp - V[0][1]); + B[1] = ((Real)0.5)*(tmp - V[1][1]); + B[2] = ((Real)0.5)*(tmp - V[2][1]); + B[3] = ((Real)0.5)*beta*(V[2][1] - V[0][1]); + B[4] = ((Real)0.5)*oneMinusGamma*(V[1][1] - V[0][1]); + B[5] = ((Real)0.5)*gamma*(V[0][1] - V[1][1]); + B[6] = ((Real)0.5)*oneMinusAlpha*(V[2][1] - V[1][1]); + B[7] = ((Real)0.5)*alpha*(V[1][1] - V[2][1]); + B[8] = ((Real)0.5)*oneMinusBeta*(V[0][1] - V[2][1]); + + // Compute Bezier coefficients. + tData.coeff[2] = jet[0].F; + tData.coeff[4] = jet[1].F; + tData.coeff[6] = jet[2].F; + + tData.coeff[14] = jet[0].F + A[0] * jet[0].FX + B[0] * jet[0].FY; + tData.coeff[7] = jet[0].F + A[3] * jet[0].FX + B[3] * jet[0].FY; + tData.coeff[8] = jet[0].F + A[4] * jet[0].FX + B[4] * jet[0].FY; + tData.coeff[16] = jet[1].F + A[1] * jet[1].FX + B[1] * jet[1].FY; + tData.coeff[9] = jet[1].F + A[5] * jet[1].FX + B[5] * jet[1].FY; + tData.coeff[10] = jet[1].F + A[6] * jet[1].FX + B[6] * jet[1].FY; + tData.coeff[18] = jet[2].F + A[2] * jet[2].FX + B[2] * jet[2].FY; + tData.coeff[11] = jet[2].F + A[7] * jet[2].FX + B[7] * jet[2].FY; + tData.coeff[12] = jet[2].F + A[8] * jet[2].FX + B[8] * jet[2].FY; + + tData.coeff[5] = alpha*tData.coeff[10] + oneMinusAlpha*tData.coeff[11]; + tData.coeff[17] = alpha*tData.coeff[16] + oneMinusAlpha*tData.coeff[18]; + tData.coeff[1] = beta*tData.coeff[12] + oneMinusBeta*tData.coeff[7]; + tData.coeff[13] = beta*tData.coeff[18] + oneMinusBeta*tData.coeff[14]; + tData.coeff[3] = gamma*tData.coeff[8] + oneMinusGamma*tData.coeff[9]; + tData.coeff[15] = gamma*tData.coeff[14] + oneMinusGamma*tData.coeff[16]; + tData.coeff[0] = cenT[0] * tData.coeff[14] + cenT[1] * tData.coeff[16] + + cenT[2] * tData.coeff[18]; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpSphere2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpSphere2.h new file mode 100644 index 000000000000..a602f92fa2d0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpSphere2.h @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// Interpolation of a scalar-valued function defined on a sphere. Although +// the sphere lives in 3D, the interpolation is a 2D method whose input +// points are angles (theta,phi) from spherical coordinates. The domains of +// the angles are -pi <= theta <= pi and 0 <= phi <= pi. + +namespace gte +{ + +template +class IntpSphere2 +{ +public: + // Construction and destruction. For complete spherical coverage, include + // the two antipodal (theta,phi) points (-pi,0,F(-pi,0)) and + // (-pi,pi,F(-pi,pi)) in the input data. These correspond to the sphere + // poles x = 0, y = 0, and |z| = 1. + ~IntpSphere2(); + IntpSphere2(int numPoints, InputType const* theta, InputType const* phi, + InputType const* F); + + // Spherical coordinates are + // x = cos(theta)*sin(phi) + // y = sin(theta)*sin(phi) + // z = cos(phi) + // for -pi <= theta <= pi, 0 <= phi <= pi. The application can use this + // function to convert unit length vectors (x,y,z) to (theta,phi). + static void GetSphericalCoordinates(InputType x, InputType y, InputType z, + InputType& theta, InputType& phi); + + // The return value is 'true' if and only if the input point is in the + // convex hull of the input (theta,pi) array, in which case the + // interpolation is valid. + bool operator()(InputType theta, InputType phi, InputType& F) const; + +private: + typedef Delaunay2Mesh TriangleMesh; + + std::vector> mWrapAngles; + Delaunay2 mDelaunay; + TriangleMesh mMesh; + std::vector mWrapF; + std::unique_ptr> mInterp; +}; + + +template +IntpSphere2::~IntpSphere2() +{ +} + +template +IntpSphere2::IntpSphere2(int numPoints, + InputType const* theta, InputType const* phi, InputType const* F) + : + mMesh(mDelaunay) +{ + // Copy the input data. The larger arrays are used to support wrap-around + // in the Delaunay triangulation for the interpolator. + int totalPoints = 3 * numPoints; + mWrapAngles.resize(totalPoints); + mWrapF.resize(totalPoints); + for (int i = 0; i < numPoints; ++i) + { + mWrapAngles[i][0] = theta[i]; + mWrapAngles[i][1] = phi[i]; + mWrapF[i] = F[i]; + } + + // Use periodicity to get wrap-around in the Delaunay triangulation. + int i0 = 0, i1 = numPoints, i2 = 2 * numPoints; + for (/**/; i0 < numPoints; ++i0, ++i1, ++i2) + { + mWrapAngles[i1][0] = mWrapAngles[i0][0] + (InputType)GTE_C_TWO_PI; + mWrapAngles[i2][0] = mWrapAngles[i0][0] - (InputType)GTE_C_TWO_PI; + mWrapAngles[i1][1] = mWrapAngles[i0][1]; + mWrapAngles[i2][1] = mWrapAngles[i0][1]; + mWrapF[i1] = mWrapF[i0]; + mWrapF[i2] = mWrapF[i0]; + } + + mDelaunay(totalPoints, &mWrapAngles[0], (ComputeType)0); + mInterp = std::make_unique>( + mMesh, &mWrapF[0], (InputType)1); +} + +template +void IntpSphere2:: +GetSphericalCoordinates(InputType x, InputType y, InputType z, +InputType& theta, InputType& phi) +{ + // Assumes (x,y,z) is unit length. Returns -pi <= theta <= pi and + // 0 <= phiAngle <= pi. + + if (z < (InputType)1) + { + if (z > -(InputType)1) + { + theta = std::atan2(y, x); + phi = std::acos(z); + } + else + { + theta = -(InputType)GTE_C_PI; + phi = (InputType)GTE_C_PI; + } + } + else + { + theta = -(InputType)GTE_C_PI; + phi = (InputType)0; + } +} + +template +bool IntpSphere2::operator()( + InputType theta, InputType phi, InputType& F) const +{ + Vector2 angles{ theta, phi }; + InputType thetaDeriv, phiDeriv; + return (*mInterp)(angles, F, thetaDeriv, phiDeriv); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline2.h new file mode 100644 index 000000000000..463b085a9e93 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline2.h @@ -0,0 +1,295 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// WARNING. The implementation allows you to transform the inputs (x,y) to +// the unit square and perform the interpolation in that space. The idea is +// to keep the floating-point numbers to order 1 for numerical stability of +// the algorithm. The classical thin-plate spline algorithm does not include +// this transformation. The interpolation is invariant to translations and +// rotations of (x,y) but not to scaling. + +namespace gte +{ + +template +class IntpThinPlateSpline2 +{ +public: + // Construction. Data points are (x,y,f(x,y)). The smoothing parameter + // must be nonnegative. + IntpThinPlateSpline2(int numPoints, Real const* X, Real const* Y, + Real const* F, Real smooth, bool transformToUnitSquare); + + // Check this after the constructor call to see whether the thin plate + // spline coefficients were successfully computed. If so, then calls to + // operator()(Real,Real) will work properly. + inline bool IsInitialized() const; + + // Evaluate the interpolator. If IsInitialized() returns 'false', the + // operator will return std::numeric_limits::max(). + Real operator()(Real x, Real y) const; + + // Compute the functional value a^T*M*a when lambda is zero or + // lambda*w^T*(M+lambda*I)*w when lambda is positive. See the thin plate + // splines PDF for a description of these quantities. + Real ComputeFunctional() const; + +private: + // Kernel(t) = t^2 * log(t^2) + static Real Kernel(Real t); + + // Input data. + int mNumPoints; + std::vector mX; + std::vector mY; + Real mSmooth; + + // Thin plate spline coefficients. The A[] coefficients are associated + // with the Green's functions G(x,y,*) and the B[] coefficients are + // associated with the affine term B[0] + B[1]*x + B[2]*y. + std::vector mA; // mNumPoints elements + Real mB[3]; + + // Extent of input data. + Real mXMin, mXMax, mXInvRange; + Real mYMin, mYMax, mYInvRange; + + bool mInitialized; +}; + + +template +IntpThinPlateSpline2::IntpThinPlateSpline2(int numPoints, Real const* X, + Real const* Y, Real const* F, Real smooth, bool transformToUnitSquare) + : + mNumPoints(numPoints), + mX(numPoints), + mY(numPoints), + mSmooth(smooth), + mA(numPoints), + mInitialized(false) +{ + if (numPoints < 3 || !X || !Y || !F || smooth < (Real)0) + { + LogError("Invalid input."); + return; + } + + int i, row, col; + + if (transformToUnitSquare) + { + // Map input (x,y) to unit square. This is not part of the classical + // thin-plate spline algorithm because the interpolation is not + // invariant to scalings. + auto extreme = std::minmax_element(X, X + mNumPoints); + mXMin = *extreme.first; + mXMax = *extreme.second; + mXInvRange = ((Real)1) / (mXMax - mXMin); + for (i = 0; i < mNumPoints; ++i) + { + mX[i] = (X[i] - mXMin) * mXInvRange; + } + + extreme = std::minmax_element(Y, Y + mNumPoints); + mYMin = *extreme.first; + mYMax = *extreme.second; + mYInvRange = ((Real)1) / (mYMax - mYMin); + for (i = 0; i < mNumPoints; ++i) + { + mY[i] = (Y[i] - mYMin) * mYInvRange; + } + } + else + { + // The classical thin-plate spline uses the data as is. The values + // mXMax and mYMax are not used, but they are initialized anyway + // (to irrelevant numbers). + mXMin = (Real)0; + mXMax = (Real)1; + mXInvRange = (Real)1; + mYMin = (Real)0; + mYMax = (Real)1; + mYInvRange = (Real)1; + std::copy(X, X + mNumPoints, mX.begin()); + std::copy(Y, Y + mNumPoints, mY.begin()); + } + + // Compute matrix A = M + lambda*I [NxN matrix]. + GMatrix AMat(mNumPoints, mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + for (col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + AMat(row, col) = mSmooth; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real t = std::sqrt(dx*dx + dy*dy); + AMat(row, col) = Kernel(t); + } + } + } + + // Compute matrix B [Nx3 matrix]. + GMatrix BMat(mNumPoints, 3); + for (row = 0; row < mNumPoints; ++row) + { + BMat(row, 0) = (Real)1; + BMat(row, 1) = mX[row]; + BMat(row, 2) = mY[row]; + } + + // Compute A^{-1}. + bool invertible; + GMatrix invAMat = Inverse(AMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P = B^T A^{-1} [3xN matrix]. + GMatrix PMat = MultiplyATB(BMat, invAMat); + + // Compute Q = P B = B^T A^{-1} B [3x3 matrix]. + GMatrix QMat = PMat * BMat; + + // Compute Q^{-1}. + GMatrix invQMat = Inverse(QMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P*z. + Real prod[3]; + for (row = 0; row < 3; ++row) + { + prod[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + prod[row] += PMat(row, i) * F[i]; + } + } + + // Compute 'b' vector for smooth thin plate spline. + for (row = 0; row < 3; ++row) + { + mB[row] = (Real)0; + for (i = 0; i < 3; ++i) + { + mB[row] += invQMat(row, i) * prod[i]; + } + } + + // Compute z-B*b. + std::vector tmp(mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + tmp[row] = F[row]; + for (i = 0; i < 3; ++i) + { + tmp[row] -= BMat(row, i) * mB[i]; + } + } + + // Compute 'a' vector for smooth thin plate spline. + for (row = 0; row < mNumPoints; ++row) + { + mA[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + mA[row] += invAMat(row, i) * tmp[i]; + } + } + + mInitialized = true; +} + +template inline +bool IntpThinPlateSpline2::IsInitialized() const +{ + return mInitialized; +} + +template +Real IntpThinPlateSpline2::operator()(Real x, Real y) const +{ + if (mInitialized) + { + // Map (x,y) to the unit square. + x = (x - mXMin) * mXInvRange; + y = (y - mYMin) * mYInvRange; + + Real result = mB[0] + mB[1] * x + mB[2] * y; + for (int i = 0; i < mNumPoints; ++i) + { + Real dx = x - mX[i]; + Real dy = y - mY[i]; + Real t = std::sqrt(dx*dx + dy*dy); + result += mA[i] * Kernel(t); + } + return result; + } + + return std::numeric_limits::max(); +} + +template +Real IntpThinPlateSpline2::ComputeFunctional() const +{ + Real functional = (Real)0; + for (int row = 0; row < mNumPoints; ++row) + { + for (int col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + functional += mSmooth * mA[row] * mA[col]; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real t = std::sqrt(dx * dx + dy * dy); + functional += Kernel(t) * mA[row] * mA[col]; + } + } + } + + if (mSmooth > (Real)0) + { + functional *= mSmooth; + } + + return functional; +} + +template +Real IntpThinPlateSpline2::Kernel(Real t) +{ + if (t > (Real)0) + { + Real t2 = t * t; + return t2 * std::log(t2); + } + return (Real)0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline3.h new file mode 100644 index 000000000000..3c5e8adfc7ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpThinPlateSpline3.h @@ -0,0 +1,312 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// WARNING. The implementation allows you to transform the inputs (x,y,z) to +// the unit cube and perform the interpolation in that space. The idea is +// to keep the floating-point numbers to order 1 for numerical stability of +// the algorithm. The classical thin-plate spline algorithm does not include +// this transformation. The interpolation is invariant to translations and +// rotations of (x,y,z) but not to scaling. + +namespace gte +{ + +template +class IntpThinPlateSpline3 +{ +public: + // Construction. Data points are (x,y,z,f(x,y,z)). The smoothing parameter + // must be nonnegative + IntpThinPlateSpline3(int numPoints, Real const* X, Real const* Y, + Real const* Z, Real const* F, Real smooth, bool transformToUnitCube); + + // Check this after the constructor call to see whether the thin plate + // spline coefficients were successfully computed. If so, then calls to + // operator()(Real,Real,Real) will work properly. + inline bool IsInitialized() const; + + // Evaluate the interpolator. If IsInitialized()returns 'false', the + // operator will return std::numeric_limits::max(). + Real operator()(Real x, Real y, Real z) const; + + // Compute the functional value a^T*M*a when lambda is zero or + // lambda*w^T*(M+lambda*I)*w when lambda is positive. See the thin plate + // splines PDF for a description of these quantities. + Real ComputeFunctional() const; + +private: + // Kernel(t) = -|t| + static Real Kernel(Real t); + + // Input data. + int mNumPoints; + std::vector mX; + std::vector mY; + std::vector mZ; + Real mSmooth; + + // Thin plate spline coefficients. The A[] coefficients are associated + // with the Green's functions G(x,y,z,*) and the B[] coefficients are + // associated with the affine term B[0] + B[1]*x + B[2]*y + B[3]*z. + std::vector mA; // mNumPoints elements + Real mB[4]; + + // Extent of input data. + Real mXMin, mXMax, mXInvRange; + Real mYMin, mYMax, mYInvRange; + Real mZMin, mZMax, mZInvRange; + + bool mInitialized; +}; + + +template +IntpThinPlateSpline3::IntpThinPlateSpline3(int numPoints, Real const* X, + Real const* Y, Real const* Z, Real const* F, Real smooth, + bool transformToUnitCube) + : + mNumPoints(numPoints), + mX(numPoints), + mY(numPoints), + mZ(numPoints), + mSmooth(smooth), + mA(numPoints), + mInitialized(false) +{ + if (numPoints < 4 || !X || !Y || !Z || !F || smooth < (Real)0) + { + LogError("Invalid input."); + return; + } + + int i, row, col; + + if (transformToUnitCube) + { + // Map input (x,y,z) to unit cube. This is not part of the classical + // thin-plate spline algorithm, because the interpolation is not + // invariant to scalings. + auto extreme = std::minmax_element(X, X + mNumPoints); + mXMin = *extreme.first; + mXMax = *extreme.second; + mXInvRange = ((Real)1) / (mXMax - mXMin); + for (i = 0; i < mNumPoints; ++i) + { + mX[i] = (X[i] - mXMin) * mXInvRange; + } + + extreme = std::minmax_element(Y, Y + mNumPoints); + mYMin = *extreme.first; + mYMax = *extreme.second; + mYInvRange = ((Real)1) / (mYMax - mYMin); + for (i = 0; i < mNumPoints; ++i) + { + mY[i] = (Y[i] - mYMin) * mYInvRange; + } + + extreme = std::minmax_element(Z, Z + mNumPoints); + mZMin = *extreme.first; + mZMax = *extreme.second; + mZInvRange = ((Real)1) / (mZMax - mZMin); + for (i = 0; i < mNumPoints; ++i) + { + mZ[i] = (Z[i] - mZMin) * mZInvRange; + } + } + else + { + // The classical thin-plate spline uses the data as is. The values + // mXMax, mYMax, and mZMax are not used, but they are initialized + // anyway (to irrelevant numbers). + mXMin = (Real)0; + mXMax = (Real)1; + mXInvRange = (Real)1; + mYMin = (Real)0; + mYMax = (Real)1; + mYInvRange = (Real)1; + mZMin = (Real)0; + mZMax = (Real)1; + mZInvRange = (Real)1; + std::copy(X, X + mNumPoints, mX.begin()); + std::copy(Y, Y + mNumPoints, mY.begin()); + std::copy(Z, Z + mNumPoints, mZ.begin()); + } + + // Compute matrix A = M + lambda*I [NxN matrix]. + GMatrix AMat(mNumPoints, mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + for (col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + AMat(row, col) = mSmooth; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real dz = mZ[row] - mZ[col]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + AMat(row, col) = Kernel(t); + } + } + } + + // Compute matrix B [Nx4 matrix]. + GMatrix BMat(mNumPoints, 4); + for (row = 0; row < mNumPoints; ++row) + { + BMat(row, 0) = (Real)1; + BMat(row, 1) = mX[row]; + BMat(row, 2) = mY[row]; + BMat(row, 3) = mZ[row]; + } + + // Compute A^{-1}. + bool invertible; + GMatrix invAMat = Inverse(AMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P = B^t A^{-1} [4xN matrix]. + GMatrix PMat = MultiplyATB(BMat, invAMat); + + // Compute Q = P B = B^t A^{-1} B [4x4 matrix]. + GMatrix QMat = PMat * BMat; + + // Compute Q^{-1}. + GMatrix invQMat = Inverse(QMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P*w. + Real prod[4]; + for (row = 0; row < 4; ++row) + { + prod[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + prod[row] += PMat(row, i) * F[i]; + } + } + + // Compute 'b' vector for smooth thin plate spline. + for (row = 0; row < 4; ++row) + { + mB[row] = (Real)0; + for (i = 0; i < 4; ++i) + { + mB[row] += invQMat(row, i) * prod[i]; + } + } + + // Compute w-B*b. + std::vector tmp(mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + tmp[row] = F[row]; + for (i = 0; i < 4; ++i) + { + tmp[row] -= BMat(row, i) * mB[i]; + } + } + + // Compute 'a' vector for smooth thin plate spline. + for (row = 0; row < mNumPoints; ++row) + { + mA[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + mA[row] += invAMat(row, i) * tmp[i]; + } + } + + mInitialized = true; +} + +template +bool IntpThinPlateSpline3::IsInitialized() const +{ + return mInitialized; +} + +template +Real IntpThinPlateSpline3::operator()(Real x, Real y, Real z) const +{ + if (mInitialized) + { + // Map (x,y,z) to the unit cube. + x = (x - mXMin) * mXInvRange; + y = (y - mYMin) * mYInvRange; + z = (z - mZMin) * mZInvRange; + + Real result = mB[0] + mB[1] * x + mB[2] * y + mB[3] * z; + for (int i = 0; i < mNumPoints; ++i) + { + Real dx = x - mX[i]; + Real dy = y - mY[i]; + Real dz = z - mZ[i]; + Real t = std::sqrt(dx*dx + dy*dy + dz*dz); + result += mA[i] * Kernel(t); + } + return result; + } + + return std::numeric_limits::max(); +} + +template +Real IntpThinPlateSpline3::ComputeFunctional() const +{ + Real functional = (Real)0; + for (int row = 0; row < mNumPoints; ++row) + { + for (int col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + functional += mSmooth * mA[row] * mA[col]; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real dz = mZ[row] - mZ[col]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + functional += Kernel(t) * mA[row] * mA[col]; + } + } + } + + if (mSmooth > (Real)0) + { + functional *= mSmooth; + } + + return functional; +} + +template +Real IntpThinPlateSpline3::Kernel(Real t) +{ + return -std::abs(t); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTricubic3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTricubic3.h new file mode 100644 index 000000000000..05d7248472de --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTricubic3.h @@ -0,0 +1,570 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. Exact interpolation is achieved by setting catmullRom +// to 'true', giving you the Catmull-Rom blending matrix. If a smooth +// interpolation is desired, set catmullRom to 'false' to obtain B-spline +// blending. + +namespace gte +{ + +template +class IntpTricubic3 +{ +public: + // Construction. + IntpTricubic3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F, bool catmullRom); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetZBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + inline Real GetZMin() const; + inline Real GetZMax() const; + inline Real GetZSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, + Real z) const; + +private: + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real mZMin, mZMax, mZSpacing, mInvZSpacing; + Real const* mF; + Real mBlend[4][4]; +}; + + +template +IntpTricubic3::IntpTricubic3(int xBound, int yBound, int zBound, + Real xMin, Real xSpacing, Real yMin, Real ySpacing, Real zMin, + Real zSpacing, Real const* F, bool catmullRom) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F) +{ + // At least a 4x4x4 block of data points are needed to construct the + // tricubic interpolation. + LogAssert(mXBound >= 4 && mYBound >= 4 && mZBound >= 4 && mF, + "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && + mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + mInvZSpacing = ((Real)1) / mZSpacing; + + if (catmullRom) + { + mBlend[0][0] = (Real)0; + mBlend[0][1] = (Real)-0.5; + mBlend[0][2] = (Real)1; + mBlend[0][3] = (Real)-0.5; + mBlend[1][0] = (Real)1; + mBlend[1][1] = (Real)0; + mBlend[1][2] = (Real)-2.5; + mBlend[1][3] = (Real)1.5; + mBlend[2][0] = (Real)0; + mBlend[2][1] = (Real)0.5; + mBlend[2][2] = (Real)2; + mBlend[2][3] = (Real)-1.5; + mBlend[3][0] = (Real)0; + mBlend[3][1] = (Real)0; + mBlend[3][2] = (Real)-0.5; + mBlend[3][3] = (Real)0.5; + } + else + { + mBlend[0][0] = (Real)1 / (Real)6; + mBlend[0][1] = (Real)-3 / (Real)6; + mBlend[0][2] = (Real)3 / (Real)6; + mBlend[0][3] = (Real)-1 / (Real)6;; + mBlend[1][0] = (Real)4 / (Real)6; + mBlend[1][1] = (Real)0 / (Real)6; + mBlend[1][2] = (Real)-6 / (Real)6; + mBlend[1][3] = (Real)3 / (Real)6; + mBlend[2][0] = (Real)1 / (Real)6; + mBlend[2][1] = (Real)3 / (Real)6; + mBlend[2][2] = (Real)3 / (Real)6; + mBlend[2][3] = (Real)-3 / (Real)6; + mBlend[3][0] = (Real)0 / (Real)6; + mBlend[3][1] = (Real)0 / (Real)6; + mBlend[3][2] = (Real)0 / (Real)6; + mBlend[3][3] = (Real)1 / (Real)6; + } +} + +template inline +int IntpTricubic3::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpTricubic3::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpTricubic3::GetZBound() const +{ + return mZBound; +} + +template inline +int IntpTricubic3::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpTricubic3::GetF() const +{ + return mF; +} + +template inline +Real IntpTricubic3::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpTricubic3::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpTricubic3::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpTricubic3::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpTricubic3::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpTricubic3::GetYSpacing() const +{ + return mYSpacing; +} + +template inline +Real IntpTricubic3::GetZMin() const +{ + return mZMin; +} + +template inline +Real IntpTricubic3::GetZMax() const +{ + return mZMax; +} + +template inline +Real IntpTricubic3::GetZSpacing() const +{ + return mZSpacing; +} + +template +Real IntpTricubic3::operator()(Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[4]; + U[0] = (Real)1; + U[1] = xIndex - ix; + U[2] = U[1] * U[1]; + U[3] = U[1] * U[2]; + + Real V[4]; + V[0] = (Real)1; + V[1] = yIndex - iy; + V[2] = V[1] * V[1]; + V[3] = V[1] * V[2]; + + Real W[4]; + W[0] = (Real)1; + W[1] = zIndex - iz; + W[2] = W[1] * W[1]; + W[3] = W[1] * W[2]; + + // Compute P = M*U, Q = M*V, R = M*W. + Real P[4], Q[4], R[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 4x4x4 + // subimage containing (x,y,z). + --ix; + --iy; + --iz; + Real result = (Real)0; + for (int slice = 0; slice < 4; ++slice) + { + int zClamp = iz + slice; + if (zClamp < 0) + { + zClamp = 0; + } + else if (zClamp > mZBound - 1) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + + return result; +} + +template +Real IntpTricubic3::operator()(int xOrder, int yOrder, int zOrder, + Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[4], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + U[2] = dx * U[1]; + U[3] = dx * U[2]; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + U[2] = ((Real)2) * dx; + U[3] = ((Real)3) * dx * dx; + xMult = mInvXSpacing; + break; + case 2: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)2; + U[3] = ((Real)6) * dx; + xMult = mInvXSpacing * mInvXSpacing; + break; + case 3: + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)0; + U[3] = (Real)6; + xMult = mInvXSpacing * mInvXSpacing * mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[4], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + V[2] = dy * V[1]; + V[3] = dy * V[2]; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + V[2] = ((Real)2) * dy; + V[3] = ((Real)3) * dy * dy; + yMult = mInvYSpacing; + break; + case 2: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)2; + V[3] = ((Real)6) * dy; + yMult = mInvYSpacing * mInvYSpacing; + break; + case 3: + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)0; + V[3] = (Real)6; + yMult = mInvYSpacing * mInvYSpacing * mInvYSpacing; + break; + default: + return (Real)0; + } + + Real W[4], dz, zMult; + switch (zOrder) + { + case 0: + dz = zIndex - iz; + W[0] = (Real)1; + W[1] = dz; + W[2] = dz * W[1]; + W[3] = dz * W[2]; + zMult = (Real)1; + break; + case 1: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)1; + W[2] = ((Real)2) * dz; + W[3] = ((Real)3) * dz * dz; + zMult = mInvZSpacing; + break; + case 2: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)0; + W[2] = (Real)2; + W[3] = ((Real)6) * dz; + zMult = mInvZSpacing * mInvZSpacing; + break; + case 3: + W[0] = (Real)0; + W[1] = (Real)0; + W[2] = (Real)0; + W[3] = (Real)6; + zMult = mInvZSpacing * mInvZSpacing * mInvZSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U, Q = M*V, and R = M*W. + Real P[4], Q[4], R[4]; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 4x4x4 + // subimage containing (x,y,z). + --ix; + --iy; + --iz; + Real result = (Real)0; + for (int slice = 0; slice < 4; ++slice) + { + int zClamp = iz + slice; + if (zClamp < 0) + { + zClamp = 0; + } + else if (zClamp > mZBound - 1) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + result *= xMult * yMult * zMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTrilinear3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTrilinear3.h new file mode 100644 index 000000000000..505bbe854c9f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpTrilinear3.h @@ -0,0 +1,440 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. + +namespace gte +{ + +template +class IntpTrilinear3 +{ +public: + // Construction. + IntpTrilinear3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F); + + // Member access. + inline int GetXBound() const; + inline int GetYBound() const; + inline int GetZBound() const; + inline int GetQuantity() const; + inline Real const* GetF() const; + inline Real GetXMin() const; + inline Real GetXMax() const; + inline Real GetXSpacing() const; + inline Real GetYMin() const; + inline Real GetYMax() const; + inline Real GetYSpacing() const; + inline Real GetZMin() const; + inline Real GetZMax() const; + inline Real GetZSpacing() const; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const; + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, + Real z) const; + +private: + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real mZMin, mZMax, mZSpacing, mInvZSpacing; + Real const* mF; + Real mBlend[2][2]; +}; + + +template +IntpTrilinear3::IntpTrilinear3(int xBound, int yBound, int zBound, + Real xMin, Real xSpacing, Real yMin, Real ySpacing, Real zMin, + Real zSpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F) +{ + // At least a 2x2x2 block of data points are needed to construct the + // trilinear interpolation. + LogAssert(mXBound >= 2 && mYBound >= 2 && mZBound >= 2 && mF, + "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && + mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = ((Real)1) / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = ((Real)1) / mYSpacing; + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + mInvZSpacing = ((Real)1) / mZSpacing; + + mBlend[0][0] = (Real)1; + mBlend[0][1] = (Real)-1; + mBlend[1][0] = (Real)0; + mBlend[1][1] = (Real)1; +} + +template inline +int IntpTrilinear3::GetXBound() const +{ + return mXBound; +} + +template inline +int IntpTrilinear3::GetYBound() const +{ + return mYBound; +} + +template inline +int IntpTrilinear3::GetZBound() const +{ + return mZBound; +} + +template inline +int IntpTrilinear3::GetQuantity() const +{ + return mQuantity; +} + +template inline +Real const* IntpTrilinear3::GetF() const +{ + return mF; +} + +template inline +Real IntpTrilinear3::GetXMin() const +{ + return mXMin; +} + +template inline +Real IntpTrilinear3::GetXMax() const +{ + return mXMax; +} + +template inline +Real IntpTrilinear3::GetXSpacing() const +{ + return mXSpacing; +} + +template inline +Real IntpTrilinear3::GetYMin() const +{ + return mYMin; +} + +template inline +Real IntpTrilinear3::GetYMax() const +{ + return mYMax; +} + +template inline +Real IntpTrilinear3::GetYSpacing() const +{ + return mYSpacing; +} + +template inline +Real IntpTrilinear3::GetZMin() const +{ + return mZMin; +} + +template inline +Real IntpTrilinear3::GetZMax() const +{ + return mZMax; +} + +template inline +Real IntpTrilinear3::GetZSpacing() const +{ + return mZSpacing; +} + +template +Real IntpTrilinear3::operator()(Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[2]; + U[0] = (Real)1; + U[1] = xIndex - ix; + + Real V[2]; + V[0] = (Real)1; + V[1] = yIndex - iy; + + Real W[2]; + W[0] = (Real)1; + W[1] = zIndex - iz; + + // Compute P = M*U, Q = M*V, R = M*W. + Real P[2], Q[2], R[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // compute the tensor product (M*U)(M*V)(M*W)*D where D is the 2x2x2 + // subimage containing (x,y,z) + Real result = (Real)0; + for (int slice = 0; slice < 2; ++slice) + { + int zClamp = iz + slice; + if (zClamp >= mZBound) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + + return result; +} + +template +Real IntpTrilinear3::operator()(int xOrder, int yOrder, int zOrder, + Real x, Real y, Real z) const +{ + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + Real U[2], dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + xMult = mInvXSpacing; + break; + default: + return (Real)0; + } + + Real V[2], dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + yMult = mInvYSpacing; + break; + default: + return (Real)0; + } + + Real W[2], dz, zMult; + switch (zOrder) + { + case 0: + dz = zIndex - iz; + W[0] = (Real)1; + W[1] = dz; + zMult = (Real)1; + break; + case 1: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)1; + zMult = mInvZSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U, Q = M*V, and R = M*W. + Real P[2], Q[2], R[2]; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 2x2x2 + // subimage containing (x,y,z). + Real result = (Real)0; + for (int slice = 0; slice < 2; ++slice) + { + int zClamp = iz + slice; + if (zClamp >= mZBound) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + result *= xMult * yMult * zMult; + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpVectorField2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpVectorField2.h new file mode 100644 index 000000000000..67d4c4e62634 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntpVectorField2.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Given points (x0[i],y0[i]) which are mapped to (x1[i],y1[i]) for +// 0 <= i < N, interpolate positions (xIn,yIn) to (xOut,yOut). + +namespace gte +{ + +template +class IntpVectorField2 +{ +public: + // Construction and destruction. + ~IntpVectorField2(); + IntpVectorField2(int numPoints, Vector2 const* domain, + Vector2 const* range); + + // The return value is 'true' if and only if (xIn,yIn) is in the convex + // hull of the input domain points, in which case the interpolation is + // valid. + bool operator()(Vector2 const& input, + Vector2& output) const; + +protected: + typedef Delaunay2Mesh TriangleMesh; + Delaunay2 mDelaunay; + TriangleMesh mMesh; + + std::vector mXRange; + std::vector mYRange; + std::unique_ptr> mXInterp; + std::unique_ptr> mYInterp; +}; + + +template +IntpVectorField2::~IntpVectorField2() +{ +} + +template +IntpVectorField2::IntpVectorField2( + int numPoints, Vector2 const* domain, + Vector2 const* range) + : + mMesh(mDelaunay) +{ + // Repackage the output vectors into individual components. This is + // required because of the format that the quadratic interpolator expects + // for its input data. + mXRange.resize(numPoints); + mYRange.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + mXRange[i] = range[i][0]; + mYRange[i] = range[i][1]; + } + + // Common triangulator for the interpolators. + mDelaunay(numPoints, &domain[0], (ComputeType)0); + + // Create interpolator for x-coordinate of vector field. + mXInterp = std::make_unique>( + mMesh, &mXRange[0], (InputType)1); + + // Create interpolator for y-coordinate of vector field, but share the + // already created triangulation for the x-interpolator. + mYInterp = std::make_unique>( + mMesh, &mYRange[0], (InputType)1); +} + +template +bool IntpVectorField2::operator()( + Vector2 const& input, Vector2& output) const +{ + InputType xDeriv, yDeriv; + return (*mXInterp)(input, output[0], xDeriv, yDeriv) + && (*mYInterp)(input, output[1], xDeriv, yDeriv); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2AlignedBox2.h new file mode 100644 index 000000000000..71ed456b957d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2AlignedBox2.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The aligned-aligned queries use simple min-max comparisions. The +// interesection of aligned boxes is an aligned box, possibly degenerate, +// where min[d] == max[d] for at least one dimension d. + +namespace gte +{ + +template +class TIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox2 const& box0, + AlignedBox2 const& box1); +}; + +template +class FIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + AlignedBox2 box; + }; + + Result operator()(AlignedBox2 const& box0, + AlignedBox2 const& box1); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + AlignedBox2 const& box0, AlignedBox2 const& box1) +{ + Result result; + for (int i = 0; i < 2; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + result.intersect = true; + return result; +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + AlignedBox2 const& box0, AlignedBox2 const& box1) +{ + Result result; + for (int i = 0; i < 2; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + + for (int i = 0; i < 2; i++) + { + if (box0.max[i] <= box1.max[i]) + { + result.box.max[i] = box0.max[i]; + } + else + { + result.box.max[i] = box1.max[i]; + } + + if (box0.min[i] <= box1.min[i]) + { + result.box.min[i] = box1.min[i]; + } + else + { + result.box.min[i] = box0.min[i]; + } + } + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2Circle2.h new file mode 100644 index 000000000000..a32457376624 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2Circle2.h @@ -0,0 +1,348 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.15.3 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingCircleRectangle.pdf + +namespace gte +{ + template + class TIQuery, Circle2> + { + public: + // The intersection query considers the box and circle to be solids; + // that is, the circle object includes the region inside the circular + // boundary and the box object includes the region inside the + // rectangular boundary. If the circle object and box object + // overlap, the objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox2 const& box, Circle2 const& circle) + { + DCPQuery, AlignedBox2> pbQuery; + auto pbResult = pbQuery(circle.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= circle.radius * circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + { + public: + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of (solid) box and circle. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = circle.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector2 contactPoint; + + // TODO: To support arbitrary precision for the contactTime, + // return q0, q1 and q2 where contactTime = (q0 - sqrt(q1)) / q2. + // The caller can compute contactTime to desired number of digits + // of precision. These are valid when intersectionType is +1 but + // are set to zero (invalid) in the other cases. Do the same for + // the contactPoint. + }; + + Result operator()(AlignedBox2 const& box, Vector2 const& boxVelocity, + Circle2 const& circle, Vector2 const& circleVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0 } }; + + // Translate the circle and box so that the box center becomes + // the origin. Compute the velocity of the circle relative to + // the box. + Vector2 boxCenter = (box.max + box.min) * (Real)0.5; + Vector2 extent = (box.max - box.min) * (Real)0.5; + Vector2 C = circle.center - boxCenter; + Vector2 V = circleVelocity - boxVelocity; + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[2]; + for (int i = 0; i < 2; ++i) + { + if (C[i] >= (Real)0) + { + sign[i] = (Real)1; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = (Real)-1; + } + } + + DoQuery(extent, C, circle.radius, V, result); + + if (result.intersectionType != 0) + { + // Translate back to the original coordinate system. + for (int i = 0; i < 2; ++i) + { + if (sign[i] < (Real)0) + { + result.contactPoint[i] = -result.contactPoint[i]; + } + } + + result.contactPoint += boxCenter; + } + return result; + } + + protected: + void DoQuery(Vector2 const& K, Vector2 const& C, + Real radius, Vector2 const& V, Result& result) + { + Vector2 delta = C - K; + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + InteriorOverlap(C, result); + } + else + { + EdgeOverlap(0, 1, K, C, delta, radius, result); + } + } + else + { + if (delta[0] <= (Real)0) + { + EdgeOverlap(1, 0, K, C, delta, radius, result); + } + else + { + if (Dot(delta, delta) <= radius * radius) + { + VertexOverlap(K, delta, radius, result); + } + else + { + VertexSeparated(K, delta, V, radius, result); + } + } + + } + } + else + { + EdgeUnbounded(0, 1, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + EdgeUnbounded(1, 0, K, C, radius, delta, V, result); + } + else + { + VertexUnbounded(K, C, radius, delta, V, result); + } + } + } + + private: + void InteriorOverlap(Vector2 const& C, Result& result) + { + result.intersectionType = -1; + result.contactTime = (Real)0; + result.contactPoint = C; + } + + void EdgeOverlap(int i0, int i1, Vector2 const& K, Vector2 const& C, + Vector2 const& delta, Real radius, Result& result) + { + result.intersectionType = (delta[i0] < radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = C[i1]; + } + + void VertexOverlap(Vector2 const& K0, Vector2 const& delta, + Real radius, Result& result) + { + Real sqrDistance = delta[0] * delta[0] + delta[1] * delta[1]; + Real sqrRadius = radius * radius; + result.intersectionType = (sqrDistance < sqrRadius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint = K0; + } + + void VertexSeparated(Vector2 const& K0, Vector2 const& delta0, + Vector2 const& V, Real radius, Result& result) + { + Real q0 = -Dot(V, delta0); + if (q0 > (Real)0) + { + Real dotVPerpD0 = Dot(V, Perp(delta0)); + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + if (q1 >= (Real)0) + { + IntersectsVertex(0, 1, K0, q0, q1, q2, result); + } + } + } + + void EdgeUnbounded(int i0, int i1, Vector2 const& K0, Vector2 const& C, + Real radius, Vector2 const& delta0, Vector2 const& V, Result& result) + { + if (V[i0] < (Real)0) + { + Real dotVPerpD0 = V[i0] * delta0[i1] - V[i1] * delta0[i0]; + if (radius * V[i1] + dotVPerpD0 >= (Real)0) + { + Vector2 K1, delta1; + K1[i0] = K0[i0]; + K1[i1] = -K0[i1]; + delta1[i0] = C[i0] - K1[i0]; + delta1[i1] = C[i1] - K1[i1]; + Real dotVPerpD1 = V[i0] * delta1[i1] - V[i1] * delta1[i0]; + if (radius * V[i1] + dotVPerpD1 <= (Real)0) + { + IntersectsEdge(i0, i1, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD1 * dotVPerpD1; + if (q1 >= (Real)0) + { + Real q0 = -(V[i0] * delta1[i0] + V[i1] * delta1[i1]); + IntersectsVertex(i0, i1, K1, q0, q1, q2, result); + } + } + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + if (q1 >= (Real)0) + { + Real q0 = -(V[i0] * delta0[i0] + V[i1] * delta0[i1]); + IntersectsVertex(i0, i1, K0, q0, q1, q2, result); + } + } + } + } + + void VertexUnbounded(Vector2 const& K0, Vector2 const& C, Real radius, + Vector2 const& delta0, Vector2 const& V, Result& result) + { + if (V[0] < (Real)0 && V[1] < (Real)0) + { + Real dotVPerpD0 = Dot(V, Perp(delta0)); + if (radius * V[0] - dotVPerpD0 <= (Real)0) + { + if (-radius * V[1] - dotVPerpD0 >= (Real)0) + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + Real q0 = -Dot(V, delta0); + IntersectsVertex(0, 1, K0, q0, q1, q2, result); + } + else + { + Vector2 K1{ K0[0], -K0[1] }; + Vector2 delta1 = C - K1; + Real dotVPerpD1 = Dot(V, Perp(delta1)); + if (-radius * V[1] - dotVPerpD1 >= (Real)0) + { + IntersectsEdge(0, 1, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD1 * dotVPerpD1; + if (q1 >= (Real)0) + { + Real q0 = -Dot(V, delta1); + IntersectsVertex(0, 1, K1, q0, q1, q2, result); + } + } + } + } + else + { + Vector2 K2{ -K0[0], K0[1] }; + Vector2 delta2 = C - K2; + Real dotVPerpD2 = Dot(V, Perp(delta2)); + if (radius * V[0] - dotVPerpD2 <= (Real)0) + { + IntersectsEdge(1, 0, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD2 * dotVPerpD2; + if (q1 >= (Real)0) + { + Real q0 = -Dot(V, delta2); + IntersectsVertex(1, 0, K2, q0, q1, q2, result); + } + } + } + } + } + + void IntersectsVertex(int i0, int i1, Vector2 const& K, + Real q0, Real q1, Real q2, Result& result) + { + result.intersectionType = +1; + result.contactTime = (q0 - std::sqrt(q1)) / q2; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + } + + void IntersectsEdge(int i0, int i1, Vector2 const& K0, Vector2 const& C, + Real radius, Vector2 const& V, Result& result) + { + result.intersectionType = +1; + result.contactTime = (K0[i0] + radius - C[i0]) / V[i0]; + result.contactPoint[i0] = K0[i0]; + result.contactPoint[i1] = C[i1] + result.contactTime * V[i1]; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2OrientedBox2.h new file mode 100644 index 000000000000..69e84713aa6b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox2OrientedBox2.h @@ -0,0 +1,110 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 2 edge normals of box0 and the +// 2 edge normals of box1. The integer 'separating' identifies the axis that +// reported separation; there may be more than one but only one is reported. +// The value is 0 when box0.axis[0] separates, 1 when box0.axis[1] separates, +// 2 when box1.axis[0] separates, or 3 when box1.axis[1] separates. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> +{ +public: + struct Result + { + bool intersect; + int separating; + }; + + Result operator()(AlignedBox2 const& box0, + OrientedBox2 const& box1); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + AlignedBox2 const& box0, OrientedBox2 const& box1) +{ + Result result; + + // Get the centered form of the aligned box. The axes are implicitly + // A0[0] = (1,0) and A0[1] = (0,1). + Vector2 C0, E0; + box0.GetCenteredForm(C0, E0); + + // Convenience variables. + Vector2 const& C1 = box1.center; + Vector2 const* A1 = &box1.axis[0]; + Vector2 const& E1 = box1.extent; + + // Compute difference of box centers. + Vector2 D = C1 - C0; + + Real absDot01[2][2], rSum; + + // Test box0.axis[0] = (1,0). + absDot01[0][0] = std::abs(A1[0][0]); + absDot01[0][1] = std::abs(A1[1][0]); + rSum = E0[0] + E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1]; + if (std::abs(D[0]) > rSum) + { + result.intersect = false; + result.separating = 0; + return result; + } + + // Test axis box0.axis[1] = (0,1). + absDot01[1][0] = std::abs(A1[0][1]); + absDot01[1][1] = std::abs(A1[1][1]); + rSum = E0[1] + E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1]; + if (std::abs(D[1]) > rSum) + { + result.intersect = false; + result.separating = 1; + return result; + } + + // Test axis box1.axis[0]. + rSum = E1[0] + E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0]; + if (std::abs(Dot(A1[0], D)) > rSum) + { + result.intersect = false; + result.separating = 2; + return result; + } + + // Test axis box1.axis[1]. + rSum = E1[1] + E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1]; + if (std::abs(Dot(A1[1], D)) > rSum) + { + result.intersect = false; + result.separating = 3; + return result; + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3AlignedBox3.h new file mode 100644 index 000000000000..5e5bd173c747 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3AlignedBox3.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The aligned-aligned queries use simple min-max comparisions. The +// interesection of aligned boxes is an aligned box, possibly degenerate, +// where min[d] == max[d] for at least one dimension d. + +namespace gte +{ + +template +class TIQuery, AlignedBox3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box0, + AlignedBox3 const& box1); +}; + +template +class FIQuery, AlignedBox3> +{ +public: + struct Result + { + bool intersect; + AlignedBox3 box; + }; + + Result operator()(AlignedBox3 const& box0, + AlignedBox3 const& box1); +}; + + +template +typename TIQuery, AlignedBox3>::Result +TIQuery, AlignedBox3>::operator()( + AlignedBox3 const& box0, AlignedBox3 const& box1) +{ + Result result; + for (int i = 0; i < 3; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + result.intersect = true; + return result; +} + +template +typename FIQuery, AlignedBox3>::Result +FIQuery, AlignedBox3>::operator()( + AlignedBox3 const& box0, AlignedBox3 const& box1) +{ + Result result; + for (int i = 0; i < 3; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + + for (int i = 0; i < 3; i++) + { + if (box0.max[i] <= box1.max[i]) + { + result.box.max[i] = box0.max[i]; + } + else + { + result.box.max[i] = box1.max[i]; + } + + if (box0.min[i] <= box1.min[i]) + { + result.box.min[i] = box1.min[i]; + } + else + { + result.box.min[i] = box0.min[i]; + } + } + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cone3.h new file mode 100644 index 000000000000..5fac3a925b78 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cone3.h @@ -0,0 +1,984 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/29) + +#pragma once + +#include +#include +#include +#include + +// Test for intersection of a box and a cone. The cone can be infinite +// 0 <= minHeight < maxHeight = std::numeric_limits::max() +// or finite (cone frustum) +// 0 <= minHeight < maxHeight < std::numeric_limits::max(). +// The algorithm is described in +// https://www.geometrictools.com/Documentation/IntersectionBoxCone.pdf +// and reports an intersection only when the intersection set has positive +// volume. For example, let the box be outside the cone. If the box is +// below the minHeight plane at the cone vertex and just touches the cone +// vertex, no intersection is reported. If the box is above the maxHeight +// plane and just touches the disk capping the cone, either at a single +// point, a line segment of points or a polygon of points, no intersection +// is reported. + +namespace gte +{ + template + class TIQuery, Cone<3, Real>> + { + public: + struct Result + { + bool intersect; + }; + + TIQuery() + : + mNumCandidateEdges(0) + { + // An edge is { v0, v1 }, where v0 and v1 are relative to mVertices + // with v0 < v1. + mEdges[ 0] = { 0, 1 }; + mEdges[ 1] = { 1, 3 }; + mEdges[ 2] = { 2, 3 }; + mEdges[ 3] = { 0, 2 }; + mEdges[ 4] = { 4, 5 }; + mEdges[ 5] = { 5, 7 }; + mEdges[ 6] = { 6, 7 }; + mEdges[ 7] = { 4, 6 }; + mEdges[ 8] = { 0, 4 }; + mEdges[ 9] = { 1, 5 }; + mEdges[10] = { 3, 7 }; + mEdges[11] = { 2, 6 }; + + // A face is { { v0, v1, v2, v3 }, { e0, e1, e2, e3 } }, where + // { v0, v1, v2, v3 } are relative to mVertices with + // v0 = min(v0,v1,v2,v3) and where { e0, e1, e2, e3 } are relative + // to mEdges. For example, mFaces[0] has vertices { 0, 4, 6, 2 }. + // The edge { 0, 4 } is mEdges[8], the edge { 4, 6 } is mEdges[7], + // the edge { 6, 2 } is mEdges[11] and the edge { 2, 0 } is + // mEdges[3]; thus, the edge indices are { 8, 7, 11, 3 }. + mFaces[0] = { { 0, 4, 6, 2 }, { 8, 7, 11, 3 } }; + mFaces[1] = { { 1, 3, 7, 5 }, { 1, 10, 5, 9 } }; + mFaces[2] = { { 0, 1, 5, 4 }, { 0, 9, 4, 8 } }; + mFaces[3] = { { 2, 6, 7, 3 }, { 11, 6, 10, 2 } }; + mFaces[4] = { { 0, 2, 3, 1 }, { 3, 2, 1, 0 } }; + mFaces[5] = { { 4, 5, 7, 6 }, { 4, 5, 6, 7 } }; + + // Clear the edges. + std::array ezero = {{ 0, 0 }}; + mCandidateEdges.fill(ezero); + for (size_t r = 0; r < MAX_VERTICES; ++r) + { + mAdjacencyMatrix[r].fill(0); + } + + mConfiguration[ 0] = &TIQuery::NNNN_0; + mConfiguration[ 1] = &TIQuery::NNNZ_1; + mConfiguration[ 2] = &TIQuery::NNNP_2; + mConfiguration[ 3] = &TIQuery::NNZN_3; + mConfiguration[ 4] = &TIQuery::NNZZ_4; + mConfiguration[ 5] = &TIQuery::NNZP_5; + mConfiguration[ 6] = &TIQuery::NNPN_6; + mConfiguration[ 7] = &TIQuery::NNPZ_7; + mConfiguration[ 8] = &TIQuery::NNPP_8; + mConfiguration[ 9] = &TIQuery::NZNN_9; + mConfiguration[10] = &TIQuery::NZNZ_10; + mConfiguration[11] = &TIQuery::NZNP_11; + mConfiguration[12] = &TIQuery::NZZN_12; + mConfiguration[13] = &TIQuery::NZZZ_13; + mConfiguration[14] = &TIQuery::NZZP_14; + mConfiguration[15] = &TIQuery::NZPN_15; + mConfiguration[16] = &TIQuery::NZPZ_16; + mConfiguration[17] = &TIQuery::NZPP_17; + mConfiguration[18] = &TIQuery::NPNN_18; + mConfiguration[19] = &TIQuery::NPNZ_19; + mConfiguration[20] = &TIQuery::NPNP_20; + mConfiguration[21] = &TIQuery::NPZN_21; + mConfiguration[22] = &TIQuery::NPZZ_22; + mConfiguration[23] = &TIQuery::NPZP_23; + mConfiguration[24] = &TIQuery::NPPN_24; + mConfiguration[25] = &TIQuery::NPPZ_25; + mConfiguration[26] = &TIQuery::NPPP_26; + mConfiguration[27] = &TIQuery::ZNNN_27; + mConfiguration[28] = &TIQuery::ZNNZ_28; + mConfiguration[29] = &TIQuery::ZNNP_29; + mConfiguration[30] = &TIQuery::ZNZN_30; + mConfiguration[31] = &TIQuery::ZNZZ_31; + mConfiguration[32] = &TIQuery::ZNZP_32; + mConfiguration[33] = &TIQuery::ZNPN_33; + mConfiguration[34] = &TIQuery::ZNPZ_34; + mConfiguration[35] = &TIQuery::ZNPP_35; + mConfiguration[36] = &TIQuery::ZZNN_36; + mConfiguration[37] = &TIQuery::ZZNZ_37; + mConfiguration[38] = &TIQuery::ZZNP_38; + mConfiguration[39] = &TIQuery::ZZZN_39; + mConfiguration[40] = &TIQuery::ZZZZ_40; + mConfiguration[41] = &TIQuery::ZZZP_41; + mConfiguration[42] = &TIQuery::ZZPN_42; + mConfiguration[43] = &TIQuery::ZZPZ_43; + mConfiguration[44] = &TIQuery::ZZPP_44; + mConfiguration[45] = &TIQuery::ZPNN_45; + mConfiguration[46] = &TIQuery::ZPNZ_46; + mConfiguration[47] = &TIQuery::ZPNP_47; + mConfiguration[48] = &TIQuery::ZPZN_48; + mConfiguration[49] = &TIQuery::ZPZZ_49; + mConfiguration[50] = &TIQuery::ZPZP_50; + mConfiguration[51] = &TIQuery::ZPPN_51; + mConfiguration[52] = &TIQuery::ZPPZ_52; + mConfiguration[53] = &TIQuery::ZPPP_53; + mConfiguration[54] = &TIQuery::PNNN_54; + mConfiguration[55] = &TIQuery::PNNZ_55; + mConfiguration[56] = &TIQuery::PNNP_56; + mConfiguration[57] = &TIQuery::PNZN_57; + mConfiguration[58] = &TIQuery::PNZZ_58; + mConfiguration[59] = &TIQuery::PNZP_59; + mConfiguration[60] = &TIQuery::PNPN_60; + mConfiguration[61] = &TIQuery::PNPZ_61; + mConfiguration[62] = &TIQuery::PNPP_62; + mConfiguration[63] = &TIQuery::PZNN_63; + mConfiguration[64] = &TIQuery::PZNZ_64; + mConfiguration[65] = &TIQuery::PZNP_65; + mConfiguration[66] = &TIQuery::PZZN_66; + mConfiguration[67] = &TIQuery::PZZZ_67; + mConfiguration[68] = &TIQuery::PZZP_68; + mConfiguration[69] = &TIQuery::PZPN_69; + mConfiguration[70] = &TIQuery::PZPZ_70; + mConfiguration[71] = &TIQuery::PZPP_71; + mConfiguration[72] = &TIQuery::PPNN_72; + mConfiguration[73] = &TIQuery::PPNZ_73; + mConfiguration[74] = &TIQuery::PPNP_74; + mConfiguration[75] = &TIQuery::PPZN_75; + mConfiguration[76] = &TIQuery::PPZZ_76; + mConfiguration[77] = &TIQuery::PPZP_77; + mConfiguration[78] = &TIQuery::PPPN_78; + mConfiguration[79] = &TIQuery::PPPZ_79; + mConfiguration[80] = &TIQuery::PPPP_80; + } + + Result operator()(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + Result result; + + // Quick-rejectance test. Determine whether the box is outside + // the slab bounded by the minimum and maximum height planes. + // When outside the slab, the box vertices are not required by the + // cone-box intersection query, so the vertices are not yet + // computed. + Real boxMinHeight(0), boxMaxHeight(0); + ComputeBoxHeightInterval(box, cone, boxMinHeight, boxMaxHeight); + if (boxMaxHeight <= cone.minHeight || boxMinHeight >= cone.maxHeight) + { + // There is no volumetric overlap of the box and the cone. The + // box is clipped entirely. + result.intersect = false; + return result; + } + + // Quick-acceptance test. Determine whether the cone axis + // intersects the box. + if (ConeAxisIntersectsBox(box, cone)) + { + result.intersect = true; + return result; + } + + // Test for box fully inside the slab. When inside the slab, the + // box vertices are required by the cone-box intersection query, + // so they are computed here; they are also required in the + // remaining cases. Also when inside the slab, the box edges are + // the candidates, so they are copied to mCandidateEdges. + if (BoxFullyInConeSlab(box, boxMinHeight, boxMaxHeight, cone)) + { + result.intersect = CandidatesHavePointInsideCone(cone); + return result; + } + + // Clear the candidates array and adjacency matrix. + ClearCandidates(); + + // The box intersects at least one plane. Compute the box-plane + // edge-interior intersection points. Insert the box subedges into + // the array of candidate edges. + ComputeCandidatesOnBoxEdges(cone); + + // Insert any relevant box face-interior clipped edges into the array + // of candidate edges. + ComputeCandidatesOnBoxFaces(); + + result.intersect = CandidatesHavePointInsideCone(cone); + return result; + } + + protected: + // The constants here are described in the comments below. + enum + { + NUM_BOX_VERTICES = 8, + NUM_BOX_EDGES = 12, + NUM_BOX_FACES = 6, + MAX_VERTICES = 32, + VERTEX_MIN_BASE = 8, + VERTEX_MAX_BASE = 20, + MAX_CANDIDATE_EDGES = 496, + NUM_CONFIGURATIONS = 81 + }; + + // The box topology is that of a cube whose vertices have components + // in {0,1}. The cube vertices are indexed by + // 0: (0,0,0), 1: (1,0,0), 2: (1,1,0), 3: (0,1,0) + // 4: (0,0,1), 5: (1,0,1), 6: (1,1,1), 7: (0,1,1) + + // The first 8 vertices are the box corners, the next 12 vertices are + // reserved for hmin-edge points and the final 12 vertices are reserved + // for the hmax-edge points. The conservative upper bound of the number + // of vertices is 8 + 12 + 12 = 32. + std::array, MAX_VERTICES> mVertices; + + // The box has 12 edges stored in mEdges. An edge is mEdges[i] = + // { v0, v1 }, where the indices v0 and v1 are relative to mVertices + // with v0 < v1. + std::array, NUM_BOX_EDGES> mEdges; + + // The box has 6 faces stored in mFaces. A face is mFaces[i] = + // { { v0, v1, v2, v3 }, { e0, e1, e2, e3 } }, where the face corner + // vertices are { v0, v1, v2, v3 }. These indices are relative to + // mVertices. The indices { e0, e1, e2, e3 } are relative to mEdges. + // The index e0 refers to edge { v0, v1 }, the index e1 refers to edge + // { v1, v2 }, the index e2 refers to edge { v2, v3 } and the index e3 + // refers to edge { v3, v0 }. The ordering of vertices for the faces + // is/ counterclockwise when viewed from outside the box. The choice + // of initial vertex affects how you implement the graph data + // structure. In this implementation, the initial vertex has minimum + // index for all vertices of that face. The faces themselves are + // listed as -x face, +x face, -y face, +y face, -z face and +z face. + struct Face + { + std::array v, e; + }; + std::array mFaces; + + // Store the signed distances from the minimum and maximum height + // planes for the cone to the projection of the box vertices onto the + // cone axis. + std::array mProjectionMin, mProjectionMax; + + // The mCandidateEdges array stores the edges of the clipped box that + // are candidates for containing the optimizing point. The maximum + // number of candidate edges is 1 + 2 + ... + 31 = 496, which is a + // conservative bound because not all pairs of vertices form edges on + // box faces. The candidate edges are stored as (v0,v1) with v0 < v1. + // The implementation is designed so that during a single query, the + // number of candidate edges can only grow. + size_t mNumCandidateEdges; + std::array, MAX_CANDIDATE_EDGES> mCandidateEdges; + + // The mAdjancencyMatrix is a simple representation of edges in the + // graph G = (V,E) that represents the (wireframe) clipped box. An + // edge (r,c) does not exist when mAdjancencyMatrix[r][c] = 0. If an + // edge (r,c) does exist, it is appended to mCandidateEdges at index k + // and the adjacency matrix is set to mAdjacencyMatrix[r][c] = 1. + // This allows for a fast edge-in-graph test and a fast and efficient + // clear of mCandidateEdges and mAdjacencyMatrix. + std::array, MAX_VERTICES> mAdjacencyMatrix; + + typedef void (TIQuery::*ConfigurationFunction)(size_t, Face const&); + std::array mConfiguration; + + static void ComputeBoxHeightInterval(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone, + Real& boxMinHeight, Real& boxMaxHeight) + { + Vector<3, Real> C, e; + box.GetCenteredForm(C, e); + Vector<3, Real> const& V = cone.ray.origin; + Vector<3, Real> const& U = cone.ray.direction; + Vector<3, Real> CmV = C - V; + Real DdCmV = Dot(U, CmV); + Real radius = e[0] * std::abs(U[0]) + e[1] * std::abs(U[1]) + e[2] * std::abs(U[2]); + boxMinHeight = DdCmV - radius; + boxMaxHeight = DdCmV + radius; + } + + static bool ConeAxisIntersectsBox(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + if (cone.maxHeight < std::numeric_limits::max()) + { + Segment<3, Real> segment; + segment.p[0] = cone.ray.origin + cone.minHeight * cone.ray.direction; + segment.p[1] = cone.ray.origin + cone.maxHeight * cone.ray.direction; + auto sbResult = TIQuery, AlignedBox<3, Real>>()(segment, box); + if (sbResult.intersect) + { + return true; + } + } + else + { + Ray<3, Real> ray; + ray.origin = cone.ray.origin + cone.minHeight * cone.ray.direction; + ray.direction = cone.ray.direction; + auto rbResult = TIQuery, AlignedBox<3, Real>>()(ray, box); + if (rbResult.intersect) + { + return true; + } + } + return false; + } + + bool BoxFullyInConeSlab(AlignedBox<3, Real> const& box, Real boxMinHeight, Real boxMaxHeight, Cone<3, Real> const& cone) + { + // Compute the box vertices relative to cone vertex as origin. + mVertices[0] = { box.min[0], box.min[1], box.min[2] }; + mVertices[1] = { box.max[0], box.min[1], box.min[2] }; + mVertices[2] = { box.min[0], box.max[1], box.min[2] }; + mVertices[3] = { box.max[0], box.max[1], box.min[2] }; + mVertices[4] = { box.min[0], box.min[1], box.max[2] }; + mVertices[5] = { box.max[0], box.min[1], box.max[2] }; + mVertices[6] = { box.min[0], box.max[1], box.max[2] }; + mVertices[7] = { box.max[0], box.max[1], box.max[2] }; + for (int i = 0; i < NUM_BOX_VERTICES; ++i) + { + mVertices[i] -= cone.ray.origin; + } + + if (cone.minHeight <= boxMinHeight && boxMaxHeight <= cone.maxHeight) + { + // The box is fully inside, so no clipping is necessary. + std::copy(mEdges.begin(), mEdges.end(), mCandidateEdges.begin()); + mNumCandidateEdges = 12; + return true; + } + return false; + } + + static bool HasPointInsideCone(Vector<3, Real> const& P0, Vector<3, Real> const& P1, + Cone<3, Real> const& cone) + { + // Define F(X) = Dot(U,X - V)/|X - V|, where U is the unit-length + // cone axis direction and V is the cone vertex. The incoming + // points P0 and P1 are relative to V; that is, the original + // points are X0 = P0 + V and X1 = P1 + V. The segment + // and cone intersect when a segment point X is inside the cone; + // that is, when F(X) > cosAngle. The comparison is converted to + // an equivalent one that does not involve divisions in order to + // avoid a division by zero if a vertex or edge contain (0,0,0). + // The function is G(X) = Dot(U,X-V) - cosAngle*Length(X-V). + Vector<3, Real> const& U = cone.ray.direction; + + // Test whether P0 or P1 is inside the cone. + Real g = Dot(U, P0) - cone.cosAngle * Length(P0); + if (g > (Real)0) + { + // X0 = P0 + V is inside the cone. + return true; + } + + g = Dot(U, P1) - cone.cosAngle * Length(P1); + if (g > (Real)0) + { + // X1 = P1 + V is inside the cone. + return true; + } + + // Test whether an interior segment point is inside the cone. + Vector<3, Real> E = P1 - P0; + Vector<3, Real> crossP0U = Cross(P0, U); + Vector<3, Real> crossP0E = Cross(P0, E); + Real dphi0 = Dot(crossP0E, crossP0U); + if (dphi0 > (Real)0) + { + Vector3 crossP1U = Cross(P1, U); + Real dphi1 = Dot(crossP0E, crossP1U); + if (dphi1 < (Real)0) + { + Real t = dphi0 / (dphi0 - dphi1); + Vector<3, Real> PMax = P0 + t * E; + g = Dot(U, PMax) - cone.cosAngle * Length(PMax); + if (g > (Real)0) + { + // The edge point XMax = Pmax + V is inside the cone. + return true; + } + } + } + + return false; + } + + bool CandidatesHavePointInsideCone(Cone<3, Real> const& cone) const + { + for (size_t i = 0; i < mNumCandidateEdges; ++i) + { + auto const& edge = mCandidateEdges[i]; + Vector<3, Real> const& P0 = mVertices[edge[0]]; + Vector<3, Real> const& P1 = mVertices[edge[1]]; + if (HasPointInsideCone(P0, P1, cone)) + { + return true; + } + } + return false; + } + + void ComputeCandidatesOnBoxEdges(Cone<3, Real> const& cone) + { + for (size_t i = 0; i < NUM_BOX_VERTICES; ++i) + { + Real h = Dot(cone.ray.direction, mVertices[i]); + mProjectionMin[i] = cone.minHeight - h; + mProjectionMax[i] = h - cone.maxHeight; + } + + size_t v0 = VERTEX_MIN_BASE, v1 = VERTEX_MAX_BASE; + for (size_t i = 0; i < NUM_BOX_EDGES; ++i, ++v0, ++v1) + { + auto const& edge = mEdges[i]; + + // In the next blocks, the sign comparisons can be expressed + // instead as "s0 * s1 < 0". The multiplication could lead to + // floating-point underflow where the product becomes 0, so I + // avoid that approach. + + // Process the hmin-plane. + Real p0Min = mProjectionMin[edge[0]]; + Real p1Min = mProjectionMin[edge[1]]; + bool clipMin = (p0Min < (Real)0 && p1Min > (Real)0) || (p0Min > (Real)0 && p1Min < (Real)0); + if (clipMin) + { + mVertices[v0] = (p1Min * mVertices[edge[0]] - p0Min * mVertices[edge[1]]) / (p1Min - p0Min); + } + + // Process the hmax-plane. + Real p0Max = mProjectionMax[edge[0]]; + Real p1Max = mProjectionMax[edge[1]]; + bool clipMax = (p0Max < (Real)0 && p1Max > (Real)0) || (p0Max > (Real)0 && p1Max < (Real)0); + if (clipMax) + { + mVertices[v1] = (p1Max * mVertices[edge[0]] - p0Max * mVertices[edge[1]]) / (p1Max - p0Max); + } + + // Get the candidate edges that are contained by the box edges. + if (clipMin) + { + if (clipMax) + { + InsertEdge(v0, v1); + } + else + { + if (p0Min < (Real)0) + { + InsertEdge(edge[0], v0); + } + else // p1Min < 0 + { + InsertEdge(edge[1], v0); + } + } + } + else if (clipMax) + { + if (p0Max < (Real)0) + { + InsertEdge(edge[0], v1); + } + else // p1Max < 0 + { + InsertEdge(edge[1], v1); + } + } + else + { + // No clipping has occurred. If the edge is inside the box, + // it is a candidate edge. To be inside the box, the p*min + // and p*max values must be nonpositive. + if (p0Min <= (Real)0 && p1Min <= (Real)0 && p0Max <= (Real)0 && p1Max <= (Real)0) + { + InsertEdge(edge[0], edge[1]); + } + } + } + } + + void ComputeCandidatesOnBoxFaces() + { + Real p0, p1, p2, p3; + size_t b0, b1, b2, b3, index; + for (size_t i = 0; i < NUM_BOX_FACES; ++i) + { + auto const& face = mFaces[i]; + + // Process the hmin-plane. + p0 = mProjectionMin[face.v[0]]; + p1 = mProjectionMin[face.v[1]]; + p2 = mProjectionMin[face.v[2]]; + p3 = mProjectionMin[face.v[3]]; + b0 = (p0 < (Real)0 ? 0 : (p0 > (Real)0 ? 2 : 1)); + b1 = (p1 < (Real)0 ? 0 : (p1 > (Real)0 ? 2 : 1)); + b2 = (p2 < (Real)0 ? 0 : (p2 > (Real)0 ? 2 : 1)); + b3 = (p3 < (Real)0 ? 0 : (p3 > (Real)0 ? 2 : 1)); + index = b3 + 3 * (b2 + 3 * (b1 + 3 * b0)); + (this->*mConfiguration[index])(VERTEX_MIN_BASE, face); + + // Process the hmax-plane. + p0 = mProjectionMax[face.v[0]]; + p1 = mProjectionMax[face.v[1]]; + p2 = mProjectionMax[face.v[2]]; + p3 = mProjectionMax[face.v[3]]; + b0 = (p0 < (Real)0 ? 0 : (p0 > (Real)0 ? 2 : 1)); + b1 = (p1 < (Real)0 ? 0 : (p1 > (Real)0 ? 2 : 1)); + b2 = (p2 < (Real)0 ? 0 : (p2 > (Real)0 ? 2 : 1)); + b3 = (p3 < (Real)0 ? 0 : (p3 > (Real)0 ? 2 : 1)); + index = b3 + 3 * (b2 + 3 * (b1 + 3 * b0)); + (this->*mConfiguration[index])(VERTEX_MAX_BASE, face); + } + } + + void ClearCandidates() + { + for (size_t i = 0; i < mNumCandidateEdges; ++i) + { + auto const& edge = mCandidateEdges[i]; + mAdjacencyMatrix[edge[0]][edge[1]] = 0; + mAdjacencyMatrix[edge[1]][edge[0]] = 0; + } + mNumCandidateEdges = 0; + } + + void InsertEdge(size_t v0, size_t v1) + { + if (mAdjacencyMatrix[v0][v1] == 0) + { + mAdjacencyMatrix[v0][v1] = 1; + mAdjacencyMatrix[v1][v0] = 1; + mCandidateEdges[mNumCandidateEdges] = { v0, v1 }; + ++mNumCandidateEdges; + } + } + + // The 81 possible configurations for a box face. The N stands for a + // '-', the Z stands for '0' and the P stands for '+'. These are + // listed in the order that maps to the array mConfiguration. Thus, + // NNNN maps to mConfiguration[0], NNNZ maps to mConfiguration[1], and + // so on. + void NNNN_0(size_t, Face const&) + { + } + + void NNNZ_1(size_t, Face const&) + { + } + + void NNNP_2(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[3]); + } + + void NNZN_3(size_t, Face const&) + { + } + + void NNZZ_4(size_t, Face const&) + { + } + + void NNZP_5(size_t base, Face const& face) + { + InsertEdge(face.v[2], base + face.e[3]); + } + + void NNPN_6(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], base + face.e[2]); + } + + void NNPZ_7(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[3]); + } + + void NNPP_8(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], base + face.e[3]); + } + + void NZNN_9(size_t, Face const&) + { + } + + void NZNZ_10(size_t, Face const&) + { + } + + void NZNP_11(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NZZN_12(size_t, Face const&) + { + } + + void NZZZ_13(size_t, Face const&) + { + } + + void NZZP_14(size_t base, Face const& face) + { + InsertEdge(face.v[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NZPN_15(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[1]); + } + + void NZPZ_16(size_t, Face const& face) + { + InsertEdge(face.v[1], face.v[3]); + } + + void NZPP_17(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[1]); + } + + void NPNN_18(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[1]); + } + + void NPNZ_19(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(base + face.e[1], face.v[1]); + } + + void NPNP_20(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(base + face.e[1], face.v[1]); + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NPZN_21(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[2]); + } + + void NPZZ_22(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + } + + void NPZP_23(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + InsertEdge(base + face.e[3], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void NPPN_24(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[2]); + } + + void NPPZ_25(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[3]); + } + + void NPPP_26(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[3]); + } + + void ZNNN_27(size_t, Face const&) + { + } + + void ZNNZ_28(size_t, Face const&) + { + } + + void ZNNP_29(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[0]); + } + + void ZNZN_30(size_t, Face const&) + { + } + + void ZNZZ_31(size_t, Face const&) + { + } + + void ZNZP_32(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[2]); + } + + void ZNPN_33(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(base + face.e[2], face.v[2]); + } + + void ZNPZ_34(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void ZNPP_35(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[1]); + } + + void ZZNN_36(size_t, Face const&) + { + } + + void ZZNZ_37(size_t, Face const&) + { + } + + void ZZNP_38(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[3]); + InsertEdge(face.v[3], base + face.e[2]); + } + + void ZZZN_39(size_t, Face const&) + { + } + + void ZZZZ_40(size_t, Face const&) + { + } + + void ZZZP_41(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[3]); + InsertEdge(face.v[3], face.v[2]); + } + + void ZZPN_42(size_t base, Face const& face) + { + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void ZZPZ_43(size_t, Face const& face) + { + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void ZZPP_44(size_t, Face const&) + { + } + + void ZPNN_45(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[1]); + } + + void ZPNZ_46(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], base + face.e[1]); + } + + void ZPNP_47(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], base + face.e[1]); + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(face.v[3], face.v[0]); + } + + void ZPZN_48(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[2]); + } + + void ZPZZ_49(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + } + + void ZPZP_50(size_t, Face const&) + { + } + + void ZPPN_51(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[2]); + } + + void ZPPZ_52(size_t, Face const&) + { + } + + void ZPPP_53(size_t, Face const&) + { + } + + void PNNN_54(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[0]); + } + + void PNNZ_55(size_t base, Face const& face) + { + InsertEdge(face.v[3], base + face.e[0]); + } + + void PNNP_56(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[0]); + } + + void PNZN_57(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + } + + void PNZZ_58(size_t base, Face const& face) + { + InsertEdge(face.v[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + } + + void PNZP_59(size_t base, Face const& face) + { + InsertEdge(face.v[2], base + face.e[0]); + } + + void PNPN_60(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void PNPZ_61(size_t base, Face const& face) + { + InsertEdge(face.v[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void PNPP_62(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[1]); + } + + void PZNN_63(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[1]); + } + + void PZNZ_64(size_t, Face const& face) + { + InsertEdge(face.v[3], face.v[1]); + } + + void PZNP_65(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[1]); + } + + void PZZN_66(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], face.v[1]); + } + + void PZZZ_67(size_t, Face const&) + { + } + + void PZZP_68(size_t, Face const&) + { + } + + void PZPN_69(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void PZPZ_70(size_t, Face const&) + { + } + + void PZPP_71(size_t, Face const&) + { + } + + void PPNN_72(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[1]); + } + + void PPNZ_73(size_t base, Face const& face) + { + InsertEdge(face.v[3], base + face.e[1]); + } + + void PPNP_74(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[1]); + } + + void PPZN_75(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[2]); + } + + void PPZZ_76(size_t, Face const&) + { + } + + void PPZP_77(size_t, Face const&) + { + } + + void PPPN_78(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[2]); + } + + void PPPZ_79(size_t, Face const&) + { + } + + void PPPP_80(size_t, Face const&) + { + } + }; + + // Template alias for convenience. + template + using TIAlignedBox3Cone3 = TIQuery, Cone<3, Real>>; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cylinder3.h new file mode 100644 index 000000000000..640623c8c4cd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Cylinder3.h @@ -0,0 +1,126 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.4.0 (2016/11/08) + +#pragma once + +#include +#include +#include +#include +#include + +// The query considers the cylinder and box to be solids. + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box, Cylinder3 const& cylinder); + +private: +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + AlignedBox3 const& box, Cylinder3 const& cylinder) +{ + Result result; + + // Translate the box and cylinder so that the box is in the first octant + // where all points in the box have nonnegative components. + Vector3 corner = box.max - box.min; + Vector3 origin = cylinder.axis.origin - box.min; + Vector3 direction = cylinder.axis.direction; + + // Compute quantities to initialize q and M in the LCP. + Real halfHeight = cylinder.height * (Real)0.5; + Matrix3x3 P = (Matrix3x3::Identity() - OuterProduct(direction, direction)); + Vector3 C = -(P * origin); + Real originDotDirection = Dot(origin, direction); + + Matrix<5, 3, Real> A; + A.SetRow(0, { (Real)-1, (Real) 0, (Real) 0 }); + A.SetRow(1, { (Real) 0, (Real)-1, (Real) 0 }); + A.SetRow(2, { (Real) 0, (Real) 0, (Real)-1 }); + A.SetRow(3, direction); + A.SetRow(4, -direction); + + Vector<5, Real> B = + { + -corner[0], + -corner[1], + -corner[2], + originDotDirection - halfHeight, + -originDotDirection - halfHeight + }; + + std::array, 8> M; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + M[r][c] = P(r, c); + } + + for (int c = 3, i = 0; c < 8; ++c, ++i) + { + M[r][c] = -A(i, r); + } + } + + for (int r = 3, i = 0; r < 8; ++r, ++i) + { + for (int c = 0; c < 3; ++c) + { + M[r][c] = A(i, c); + } + + for (int c = 3; c < 8; ++c) + { + M[r][c] = (Real)0; + } + } + + std::array q; + for (int r = 0; r < 3; ++r) + { + q[r] = C[r]; + } + + for (int r = 3, i = 0; r < 8; ++r, ++i) + { + q[r] = -B[i]; + } + + std::array w, z; + LCPSolver LCP; + if (LCP.Solve(q, M, w, z)) + { + Vector3 zSolution{ z[0], z[1], z[2] }; + Vector3 diff = zSolution - origin; + Real qform = Dot(diff, P * diff); + result.intersect = (qform <= cylinder.radius * cylinder.radius); + } + else + { + result.intersect = false; + } + + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3OrientedBox3.h new file mode 100644 index 000000000000..7baeac7d6624 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3OrientedBox3.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 3 face normals of box0, the 3 +// face normals of box1, and 9 directions, each of which is the cross product +// of an edge of box0 and and an edge of box1. +// +// The separating axes involving cross products of edges has numerical +// robustness problems when the two edges are nearly parallel. The cross +// product of the edges is nearly the zero vector, so normalization of the +// cross product may produce unit-length directions that are not close to the +// true direction. Such a pair of edges occurs when a box0 face normal N0 and +// a box1 face normal N1 are nearly parallel. In this case, you may skip the +// edge-edge directions, which is equivalent to projecting the boxes onto the +// plane with normal N0 and applying a 2D separating axis test. The ability +// to do so involves choosing a small nonnegative epsilon . It is used to +// determine whether two face normals, one from each box, are nearly parallel: +// |Dot(N0,N1)| >= 1 - epsilon. If the input is negative, it is clamped to +// zero. +// +// The pair of integers 'separating', say, (i0,i1), identify the axis that +// reported separation; there may be more than one but only one is +// reported. If the separating axis is a face normal N[i0] of the aligned +// box0 in dimension i0, then (i0,-1) is returned. If the axis is a face +// normal box1.Axis[i1], then (-1,i1) is returned. If the axis is a cross +// product of edges, Cross(N[i0],box1.Axis[i1]), then (i0,i1) is returned. + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + // The 'epsilon' value must be nonnegative. + Result(Real inEpsilon = (Real)0); + + bool intersect; + Real epsilon; + int separating[2]; + }; + + Result operator()(AlignedBox3 const& box0, + OrientedBox3 const& box1); +}; + + +template +TIQuery, OrientedBox3>::Result:: +Result(Real inEpsilon) +{ + if (inEpsilon >= (Real)0) + { + epsilon = inEpsilon; + } + else + { + epsilon = (Real)0; + } +} + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + AlignedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Get the centered form of the aligned box. The axes are implicitly + // A0[0] = (1,0,0), A0[1] = (0,1,0), and A0[2] = (0,0,1). + Vector3 C0, E0; + box0.GetCenteredForm(C0, E0); + + // Convenience variables. + Vector3 const& C1 = box1.center; + Vector3 const* A1 = &box1.axis[0]; + Vector3 const& E1 = box1.extent; + + Real const cutoff = (Real)1 - result.epsilon; + bool existsParallelPair = false; + + // Compute the difference of box centers. + Vector3 D = C1 - C0; + + Real dot01[3][3]; // dot01[i][j] = Dot(A0[i],A1[j]) = A1[j][i] + Real absDot01[3][3]; // |dot01[i][j]| + Real r0, r1, r; // interval radii and distance between centers + Real r01; // r0 + r1 + + // Test for separation on the axis C0 + t*A0[0]. + for (int i = 0; i < 3; ++i) + { + dot01[0][i] = A1[i][0]; + absDot01[0][i] = std::abs(A1[i][0]); + if (absDot01[0][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::abs(D[0]); + r1 = E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1] + E1[2] * absDot01[0][2]; + r01 = E0[0] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]. + for (int i = 0; i < 3; ++i) + { + dot01[1][i] = A1[i][1]; + absDot01[1][i] = std::abs(A1[i][1]); + if (absDot01[1][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::abs(D[1]); + r1 = E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1] + E1[2] * absDot01[1][2]; + r01 = E0[1] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]. + for (int i = 0; i < 3; ++i) + { + dot01[2][i] = A1[i][2]; + absDot01[2][i] = std::abs(A1[i][2]); + if (absDot01[2][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::abs(D[2]); + r1 = E1[0] * absDot01[2][0] + E1[1] * absDot01[2][1] + E1[2] * absDot01[2][2]; + r01 = E0[2] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A1[0]. + r = std::abs(Dot(D, A1[0])); + r0 = E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0] + E0[2] * absDot01[2][0]; + r01 = r0 + E1[0]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A1[1]. + r = std::abs(Dot(D, A1[1])); + r0 = E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1] + E0[2] * absDot01[2][1]; + r01 = r0 + E1[1]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A1[2]. + r = std::abs(Dot(D, A1[2])); + r0 = E0[0] * absDot01[0][2] + E0[1] * absDot01[1][2] + E0[2] * absDot01[2][2]; + r01 = r0 + E1[2]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 2; + return result; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D. The edge-edge axes do not need to be tested. + if (existsParallelPair) + { + result.intersect = true; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[0]. + r = std::abs(D[2] * dot01[1][0] - D[1] * dot01[2][0]); + r0 = E0[1] * absDot01[2][0] + E0[2] * absDot01[1][0]; + r1 = E1[1] * absDot01[0][2] + E1[2] * absDot01[0][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[1]. + r = std::abs(D[2] * dot01[1][1] - D[1] * dot01[2][1]); + r0 = E0[1] * absDot01[2][1] + E0[2] * absDot01[1][1]; + r1 = E1[0] * absDot01[0][2] + E1[2] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[2]. + r = std::abs(D[2] * dot01[1][2] - D[1] * dot01[2][2]); + r0 = E0[1] * absDot01[2][2] + E0[2] * absDot01[1][2]; + r1 = E1[0] * absDot01[0][1] + E1[1] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[0]. + r = std::abs(D[0] * dot01[2][0] - D[2] * dot01[0][0]); + r0 = E0[0] * absDot01[2][0] + E0[2] * absDot01[0][0]; + r1 = E1[1] * absDot01[1][2] + E1[2] * absDot01[1][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[1]. + r = std::abs(D[0] * dot01[2][1] - D[2] * dot01[0][1]); + r0 = E0[0] * absDot01[2][1] + E0[2] * absDot01[0][1]; + r1 = E1[0] * absDot01[1][2] + E1[2] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[2]. + r = std::abs(D[0] * dot01[2][2] - D[2] * dot01[0][2]); + r0 = E0[0] * absDot01[2][2] + E0[2] * absDot01[0][2]; + r1 = E1[0] * absDot01[1][1] + E1[1] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[0]. + r = std::abs(D[1] * dot01[0][0] - D[0] * dot01[1][0]); + r0 = E0[0] * absDot01[1][0] + E0[1] * absDot01[0][0]; + r1 = E1[1] * absDot01[2][2] + E1[2] * absDot01[2][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[1]. + r = std::abs(D[1] * dot01[0][1] - D[0] * dot01[1][1]); + r0 = E0[0] * absDot01[1][1] + E0[1] * absDot01[0][1]; + r1 = E1[0] * absDot01[2][2] + E1[2] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[2]. + r = std::abs(D[1] * dot01[0][2] - D[0] * dot01[1][2]); + r0 = E0[0] * absDot01[1][2] + E0[1] * absDot01[0][2]; + r1 = E1[0] * absDot01[2][1] + E1[1] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 2; + return result; + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Sphere3.h new file mode 100644 index 000000000000..a9fd022924c6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrAlignedBox3Sphere3.h @@ -0,0 +1,595 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingSphereBox.pdf + +namespace gte +{ + template + class TIQuery, Sphere3> + { + public: + // The intersection query considers the box and sphere to be solids; + // that is, the sphere object includes the region inside the spherical + // boundary and the box object includes the region inside the cuboid + // boundary. If the sphere object and box object object overlap, the + // objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box, Sphere3 const& sphere) + { + DCPQuery, AlignedBox3> pbQuery; + auto pbResult = pbQuery(sphere.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= sphere.radius * sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + { + public: + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of (solid) box and sphere. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector3 contactPoint; + + // TODO: To support arbitrary precision for the contactTime, + // return q0, q1 and q2 where contactTime = (q0 - sqrt(q1)) / q2. + // The caller can compute contactTime to desired number of digits + // of precision. These are valid when intersectionType is +1 but + // are set to zero (invalid) in the other cases. Do the same for + // the contactPoint. + }; + + Result operator()(AlignedBox3 const& box, Vector3 const& boxVelocity, + Sphere3 const& sphere, Vector3 const& sphereVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0, (Real)0 } }; + + // Translate the sphere and box so that the box center becomes + // the origin. Compute the velocity of the sphere relative to + // the box. + Vector3 boxCenter = (box.max + box.min) * (Real)0.5; + Vector3 extent = (box.max - box.min) * (Real)0.5; + Vector3 C = sphere.center - boxCenter; + Vector3 V = sphereVelocity - boxVelocity; + + // Test for no-intersection that leads to an early exit. The test + // is fast, using the method of separating axes. + AlignedBox3 superBox; + for (int i = 0; i < 3; ++i) + { + superBox.max[i] = extent[i] + sphere.radius; + superBox.min[i] = -superBox.max[i]; + } + TIQuery, AlignedBox3> rbQuery; + auto rbResult = rbQuery(Ray3(C, V), superBox); + if (!rbResult.intersect) + { + return result; + } + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[3]; + for (int i = 0; i < 3; ++i) + { + if (C[i] >= (Real)0) + { + sign[i] = (Real)1; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = (Real)-1; + } + } + + DoQuery(extent, C, sphere.radius, V, result); + + if (result.intersectionType != 0) + { + // Translate back to the original coordinate system. + for (int i = 0; i < 3; ++i) + { + if (sign[i] < (Real)0) + { + result.contactPoint[i] = -result.contactPoint[i]; + } + } + + result.contactPoint += boxCenter; + } + + return result; + } + + protected: + // The query assumes the box is axis-aligned with center at the + // origin. Callers need to convert the results back to the original + // coordinate system of the query. + void DoQuery(Vector3 const& K, Vector3 const& C, + Real radius, Vector3 const& V, Result& result) + { + Vector3 delta = C - K; + if (delta[2] <= radius) + { + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + if (delta[2] <= (Real)0) + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + InteriorOverlap(C, result); + } + else + { + // x-face + FaceOverlap(0, 1, 2, K, C, radius, delta, result); + } + } + else + { + if (delta[0] <= (Real)0) + { + // y-face + FaceOverlap(1, 2, 0, K, C, radius, delta, result); + } + else + { + // xy-edge + if (delta[0] * delta[0] + delta[1] * delta[1] <= radius * radius) + { + EdgeOverlap(0, 1, 2, K, C, radius, delta, result); + } + else + { + EdgeSeparated(0, 1, 2, K, C, radius, delta, V, result); + } + } + } + } + else + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + // z-face + FaceOverlap(2, 0, 1, K, C, radius, delta, result); + } + else + { + // xz-edge + if (delta[0] * delta[0] + delta[2] * delta[2] <= radius * radius) + { + EdgeOverlap(2, 0, 1, K, C, radius, delta, result); + } + else + { + EdgeSeparated(2, 0, 1, K, C, radius, delta, V, result); + } + } + } + else + { + if (delta[0] <= (Real)0) + { + // yz-edge + if (delta[1] * delta[1] + delta[2] * delta[2] <= radius * radius) + { + EdgeOverlap(1, 2, 0, K, C, radius, delta, result); + } + else + { + EdgeSeparated(1, 2, 0, K, C, radius, delta, V, result); + } + } + else + { + // xyz-vertex + if (Dot(delta, delta) <= radius * radius) + { + VertexOverlap(K, radius, delta, result); + } + else + { + VertexSeparated(K, radius, delta, V, result); + } + } + } + } + } + else + { + // x-face + FaceUnbounded(0, 1, 2, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + // y-face + FaceUnbounded(1, 2, 0, K, C, radius, delta, V, result); + } + else + { + // xy-edge + EdgeUnbounded(0, 1, 2, K, C, radius, delta, V, result); + } + } + } + else + { + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + // z-face + FaceUnbounded(2, 0, 1, K, C, radius, delta, V, result); + } + else + { + // xz-edge + EdgeUnbounded(2, 0, 1, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + // yz-edge + EdgeUnbounded(1, 2, 0, K, C, radius, delta, V, result); + } + else + { + // xyz-vertex + VertexUnbounded(K, C, radius, delta, V, result); + } + } + } + } + + private: + void InteriorOverlap(Vector3 const& C, Result& result) + { + result.intersectionType = -1; + result.contactTime = (Real)0; + result.contactPoint = C; + } + + void VertexOverlap(Vector3 const& K, Real radius, + Vector3 const& delta, Result& result) + { + result.intersectionType = (Dot(delta, delta) < radius * radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint = K; + } + + void EdgeOverlap(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Result& result) + { + result.intersectionType = (delta[i0] * delta[i0] + delta[i1] * delta[i1] < radius * radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + result.contactPoint[i2] = C[i2]; + } + + void FaceOverlap(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Result& result) + { + result.intersectionType = (delta[i0] < radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = C[i1]; + result.contactPoint[i2] = C[i2]; + } + + void VertexSeparated(Vector3 const& K, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + if (V[0] < (Real)0 || V[1] < (Real)0 || V[2] < (Real)0) + { + DoQueryRayRoundedVertex(K, radius, delta, V, result); + } + } + + void EdgeSeparated(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0 || V[i1] < (Real)0) + { + DoQueryRayRoundedEdge(i0, i1, i2, K, C, radius, delta, V, result); + } + } + + void VertexUnbounded(Vector3 const& K, Vector3 const& C, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + if (V[0] < (Real)0 && V[1] < (Real)0 && V[2] < (Real)0) + { + // Determine the face of the rounded box that is intersected + // by the ray C+T*V. + Real T = (radius - delta[0]) / V[0]; + int j0 = 0; + Real temp = (radius - delta[1]) / V[1]; + if (temp > T) + { + T = temp; + j0 = 1; + } + temp = (radius - delta[2]) / V[2]; + if (temp > T) + { + T = temp; + j0 = 2; + } + + // The j0-rounded face is the candidate for intersection. + int j1 = (j0 + 1) % 3; + int j2 = (j1 + 1) % 3; + DoQueryRayRoundedFace(j0, j1, j2, K, C, radius, delta, V, result); + } + } + + void EdgeUnbounded(int i0, int i1, int /* i2 */, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0 && V[i1] < (Real)0) + { + // Determine the face of the rounded box that is intersected + // by the ray C+T*V. + Real T = (radius - delta[i0]) / V[i0]; + int j0 = i0; + Real temp = (radius - delta[i1]) / V[i1]; + if (temp > T) + { + T = temp; + j0 = i1; + } + + // The j0-rounded face is the candidate for intersection. + int j1 = (j0 + 1) % 3; + int j2 = (j1 + 1) % 3; + DoQueryRayRoundedFace(j0, j1, j2, K, C, radius, delta, V, result); + } + } + + void FaceUnbounded(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0) + { + DoQueryRayRoundedFace(i0, i1, i2, K, C, radius, delta, V, result); + } + } + + void DoQueryRayRoundedVertex(Vector3 const& K, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + Real a1 = Dot(V, delta); + if (a1 < (Real)0) + { + // The caller must ensure that a0 > 0 and a2 > 0. + Real a0 = Dot(delta, delta) - radius * radius; + Real a2 = Dot(V, V); + Real adiscr = a1 * a1 - a2 * a0; + if (adiscr >= (Real)0) + { + // The ray intersects the rounded vertex, so the sphere-box + // contact point is the vertex. + result.intersectionType = 1; + result.contactTime = -(a1 + std::sqrt(adiscr)) / a2; + result.contactPoint = K; + } + } + } + + void DoQueryRayRoundedEdge(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + Real b1 = V[i0] * delta[i0] + V[i1] * delta[i1]; + if (b1 < (Real)0) + { + // The caller must ensure that b0 > 0 and b2 > 0. + Real b0 = delta[i0] * delta[i0] + delta[i1] * delta[i1] - radius * radius; + Real b2 = V[i0] * V[i0] + V[i1] * V[i1]; + Real bdiscr = b1 * b1 - b2 * b0; + if (bdiscr >= (Real)0) + { + Real T = -(b1 + std::sqrt(bdiscr)) / b2; + Real p2 = C[i2] + T * V[i2]; + if (-K[i2] <= p2) + { + if (p2 <= K[i2]) + { + // The ray intersects the finite cylinder of the + // rounded edge, so the sphere-box contact point + // is on the corresponding box edge. + result.intersectionType = 1; + result.contactTime = T; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + result.contactPoint[i2] = p2; + } + else + { + // The ray intersects the infinite cylinder but + // not the finite cylinder of the rounded edge. + // It is possible the ray intersects the rounded + // vertex for K. + DoQueryRayRoundedVertex(K, radius, delta, V, result); + } + } + else + { + // The ray intersects the infinite cylinder but + // not the finite cylinder of the rounded edge. + // It is possible the ray intersects the rounded + // vertex for otherK. + Vector3 otherK, otherDelta; + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedVertex(otherK, radius, otherDelta, V, result); + } + } + } + } + + void DoQueryRayRoundedFace(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + Vector3 otherK, otherDelta; + + Real T = (radius - delta[i0]) / V[i0]; + Real p1 = C[i1] + T * V[i1]; + Real p2 = C[i2] + T * V[i2]; + + if (p1 < -K[i1]) + { + // The ray potentially intersects the rounded (i0,i1)-edge + // whose top-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = -K[i1]; + otherK[i2] = K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i0, i1, i2, otherK, C, radius, otherDelta, V, result); + if (result.intersectionType == 0) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 > K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + } + else if (p1 <= K[i1]) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 <= K[i2]) + { + // The ray intersects the i0-face of the rounded box, so + // the sphere-box contact point is on the corresponding + // box face. + result.intersectionType = 1; + result.contactTime = T; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = p1; + result.contactPoint[i2] = p2; + } + else // p2 > K[i2] + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + else // p1 > K[i1] + { + // The ray potentially intersects the rounded (i0,i1)-edge + // whose top-most vertex is K. + DoQueryRayRoundedEdge(i0, i1, i2, K, C, radius, delta, V, result); + if (result.intersectionType == 0) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 > K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrArc2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrArc2Arc2.h new file mode 100644 index 000000000000..160fcb29cb9f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrArc2Arc2.h @@ -0,0 +1,212 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/02/23) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class FIQuery, Arc2> +{ +public: + // The possible 'configuration' in Result are listed as an enumeration. + // The valid array elements are listed in the comments. + enum + { + NO_INTERSECTION, + NONCOCIRCULAR_ONE_POINT, // point[0] + NONCOCIRCULAR_TWO_POINTS, // point[0], point[1] + COCIRCULAR_ONE_POINT, // point[0] + COCIRCULAR_TWO_POINTS, // point[0], point[1] + COCIRCULAR_ONE_POINT_ONE_ARC, // point[0], arc[0] + COCIRCULAR_ONE_ARC, // arc[0] + COCIRCULAR_TWO_ARCS // arc[0], arc[1] + }; + + struct Result + { + bool intersect; // 'true' iff configuration != NO_INTERSECTION + int configuration; // one of the enumerations listed previously + Vector2 point[2]; + Arc2 arc[2]; + }; + + Result operator()(Arc2 const& arc0, Arc2 const& arc1); +}; + + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()(Arc2 const& arc0, Arc2 const& arc1) +{ + // Assume initially there are no intersections. If we find at least one + // intersection, we will set result.intersect to 'true'. + Result result; + result.intersect = false; + + Circle2 circle0(arc0.center, arc0.radius); + Circle2 circle1(arc1.center, arc1.radius); + FIQuery, Circle2> ccQuery; + auto ccResult = ccQuery(circle0, circle1); + if (!ccResult.intersect) + { + // The arcs do not intersect. + result.configuration = NO_INTERSECTION; + return result; + } + + if (ccResult.numIntersections == std::numeric_limits::max()) + { + // The arcs are cocircular. Determine whether they overlap. Let + // arc0 be and arc1 be . The points are ordered + // counterclockwise around the circle of the arc. + if (arc1.Contains(arc0.end[0])) + { + result.intersect = true; + if (arc1.Contains(arc0.end[1])) + { + if (arc0.Contains(arc1.end[0]) && arc0.Contains(arc1.end[1])) + { + if (arc0.end[0] == arc1.end[0] && arc0.end[1] == arc1.end[1]) + { + // The arcs are the same. + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc0; + } + else + { + // arc0 and arc1 overlap in two disjoint subsets. + if (arc0.end[0] != arc1.end[1]) + { + if (arc1.end[0] != arc0.end[1]) + { + // The arcs overlap in two disjoint subarcs, each + // of positive subtended angle: , + result.configuration = COCIRCULAR_TWO_ARCS; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + result.arc[1] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else // B0 = A1 + { + // The intersection is a point {A1} and an + // arc . + result.configuration = COCIRCULAR_ONE_POINT_ONE_ARC; + result.point[0] = arc0.end[1]; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + } + } + else // A0 = B1 + { + if (arc1.end[0] != arc0.end[1]) + { + // The intersection is a point {A0} and an + // arc . + result.configuration = COCIRCULAR_ONE_POINT_ONE_ARC; + result.point[0] = arc0.end[0]; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else + { + // The arcs shared endpoints, so the union is a circle. + result.configuration = COCIRCULAR_TWO_POINTS; + result.point[0] = arc0.end[0]; + result.point[1] = arc0.end[1]; + } + } + } + } + else + { + // Arc0 inside arc1, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc0; + } + } + else + { + if (arc0.end[0] != arc1.end[1]) + { + // Arc0 and arc1 overlap, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + } + else + { + // Arc0 and arc1 share endpoint, , A0 = B1. + result.configuration = COCIRCULAR_ONE_POINT; + result.point[0] = arc0.end[0]; + } + } + return result; + } + + if (arc1.Contains(arc0.end[1])) + { + result.intersect = true; + if (arc0.end[1] != arc1.end[0]) + { + // Arc0 and arc1 overlap in a single arc, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else + { + // Arc0 and arc1 share endpoint, , B0 = A1. + result.configuration = COCIRCULAR_ONE_POINT; + result.point[0] = arc1.end[0]; + } + return result; + } + + if (arc0.Contains(arc1.end[0])) + { + // Arc1 inside arc0, . + result.intersect = true; + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc1; + } + else + { + // Arcs do not overlap, . + result.configuration = NO_INTERSECTION; + } + return result; + } + + // Test whether circle-circle intersection points are on the arcs. + int numIntersections = 0; + for (int i = 0; i < ccResult.numIntersections; ++i) + { + if (arc0.Contains(ccResult.point[i]) && arc1.Contains(ccResult.point[i])) + { + result.point[numIntersections++] = ccResult.point[i]; + result.intersect = true; + } + } + + if (numIntersections == 2) + { + result.configuration = NONCOCIRCULAR_TWO_POINTS; + } + else if (numIntersections == 1) + { + result.configuration = NONCOCIRCULAR_ONE_POINT; + } + else + { + result.configuration = NO_INTERSECTION; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCapsule3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCapsule3Capsule3.h new file mode 100644 index 000000000000..02eda4900c29 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCapsule3Capsule3.h @@ -0,0 +1,45 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Capsule3 const& capsule0, + Capsule3 const& capsule1); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Capsule3 const& capsule0, Capsule3 const& capsule1) +{ + Result result; + DCPQuery, Segment3> ssQuery; + auto ssResult = ssQuery(capsule0.segment, capsule1.segment); + Real rSum = capsule0.radius + capsule1.radius; + result.intersect = (ssResult.distance <= rSum); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Arc2.h new file mode 100644 index 000000000000..1fed9c2a9e63 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Arc2.h @@ -0,0 +1,82 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + + // The number of intersections is 0, 1, 2, or maxInt = + // std::numeric_limits::max(). When 1, the arc and circle + // intersect in a single point. When 2, the arc is not on the circle + // and they intersect in two points. When maxInt, the arc is on the + // circle. + int numIntersections; + + // Valid only when numIntersections = 1 or 2. + Vector2 point[2]; + + // Valid only when numIntersections = maxInt. + Arc2 arc; + }; + + Result operator()(Circle2 const& circle, Arc2 const& arc); +}; + + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Circle2 const& circle, Arc2 const& arc) +{ + Result result; + + Circle2 circleOfArc(arc.center, arc.radius); + FIQuery, Circle2> ccQuery; + auto ccResult = ccQuery(circle, circleOfArc); + if (!ccResult.intersect) + { + result.intersect = false; + result.numIntersections = 0; + return result; + } + + if (ccResult.numIntersections == std::numeric_limits::max()) + { + // The arc is on the circle. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.arc = arc; + return result; + } + + // Test whether circle-circle intersection points are on the arc. + for (int i = 0; i < ccResult.numIntersections; ++i) + { + result.numIntersections = 0; + if (arc.Contains(ccResult.point[i])) + { + result.point[result.numIntersections++] = ccResult.point[i]; + result.intersect = true; + } + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Circle2.h new file mode 100644 index 000000000000..8babe879dbc2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrCircle2Circle2.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Circle2 const& circle0, + Circle2 const& circle1); +}; + +template +class FIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + + // The number of intersections is 0, 1, 2, or maxInt = + // std::numeric_limits::max(). When 1, the circles are tangent + // and intersect in a single point. When 2, circles have two + // transverse intersection points. When maxInt, the circles are the + // same. + int numIntersections; + + // Valid only when numIntersections = 1 or 2. + Vector2 point[2]; + + // Valid only when numIntersections = maxInt. + Circle2 circle; + }; + + Result operator()(Circle2 const& circle0, + Circle2 const& circle1); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Circle2 const& circle0, Circle2 const& circle1) +{ + Result result; + Vector2 diff = circle0.center - circle1.center; + result.intersect = (Length(diff) <= circle0.radius + circle1.radius); + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Circle2 const& circle0, Circle2 const& circle1) +{ + // The two circles are |X-C0| = R0 and |X-C1| = R1. Define U = C1 - C0 + // and V = Perp(U) where Perp(x,y) = (y,-x). Note that Dot(U,V) = 0 and + // |V|^2 = |U|^2. The intersection points X can be written in the form + // X = C0+s*U+t*V and X = C1+(s-1)*U+t*V. Squaring the circle equations + // and substituting these formulas into them yields + // R0^2 = (s^2 + t^2)*|U|^2 + // R1^2 = ((s-1)^2 + t^2)*|U|^2. + // Subtracting and solving for s yields + // s = ((R0^2-R1^2)/|U|^2 + 1)/2 + // Then replace in the first equation and solve for t^2 + // t^2 = (R0^2/|U|^2) - s^2. + // In order for there to be solutions, the right-hand side must be + // nonnegative. Some algebra leads to the condition for existence of + // solutions, + // (|U|^2 - (R0+R1)^2)*(|U|^2 - (R0-R1)^2) <= 0. + // This reduces to + // |R0-R1| <= |U| <= |R0+R1|. + // If |U| = |R0-R1|, then the circles are side-by-side and just tangent. + // If |U| = |R0+R1|, then the circles are nested and just tangent. + // If |R0-R1| < |U| < |R0+R1|, then the two circles to intersect in two + // points. + + Result result; + + Vector2 U = circle1.center - circle0.center; + Real USqrLen = Dot(U, U); + Real R0 = circle0.radius, R1 = circle1.radius; + Real R0mR1 = R0 - R1; + if (USqrLen == (Real)0 && R0mR1 == (Real)0) + { + // Circles are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.circle = circle0; + return result; + } + + Real R0mR1Sqr = R0mR1*R0mR1; + if (USqrLen < R0mR1Sqr) + { + // The circles do not intersect. + result.intersect = false; + result.numIntersections = 0; + return result; + } + + Real R0pR1 = R0 + R1; + Real R0pR1Sqr = R0pR1*R0pR1; + if (USqrLen > R0pR1Sqr) + { + // The circles do not intersect. + result.intersect = false; + result.numIntersections = 0; + return result; + } + + if (USqrLen < R0pR1Sqr) + { + if (R0mR1Sqr < USqrLen) + { + Real invUSqrLen = ((Real)1) / USqrLen; + Real s = ((Real)0.5)*((R0*R0 - R1*R1)*invUSqrLen + (Real)1); + Vector2 tmp = circle0.center + s*U; + + // In theory, discr is nonnegative. However, numerical round-off + // errors can make it slightly negative. Clamp it to zero. + Real discr = R0*R0*invUSqrLen - s*s; + if (discr < (Real)0) + { + discr = (Real)0; + } + Real t = std::sqrt(discr); + Vector2 V{ U[1], -U[0] }; + result.point[0] = tmp - t*V; + result.point[1] = tmp + t*V; + result.numIntersections = (t >(Real)0 ? 2 : 1); + } + else + { + // |U| = |R0-R1|, circles are tangent. + result.numIntersections = 1; + result.point[0] = circle0.center + (R0 / R0mR1)*U; + } + } + else + { + // |U| = |R0+R1|, circles are tangent. + result.numIntersections = 1; + result.point[0] = circle0.center + (R0 / R0pR1)*U; + } + + // The circles intersect in 1 or 2 points. + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrConvexPolygonHyperplane.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrConvexPolygonHyperplane.h new file mode 100644 index 000000000000..eaa0933d4a36 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrConvexPolygonHyperplane.h @@ -0,0 +1,402 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2019/02/13) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The intersection queries are based on the document +// https://www.geometrictools.com/Documentation/ClipConvexPolygonByHyperplane.pdf + +namespace gte +{ + template + class TIQuery>, Hyperplane> + { + public: + enum class Configuration + { + SPLIT, + POSITIVE_SIDE_VERTEX, + POSITIVE_SIDE_EDGE, + POSITIVE_SIDE_STRICT, + NEGATIVE_SIDE_VERTEX, + NEGATIVE_SIDE_EDGE, + NEGATIVE_SIDE_STRICT, + CONTAINED, + INVALID_POLYGON + }; + + struct Result + { + bool intersect; + Configuration configuration; + }; + + Result operator()(std::vector> const& polygon, Hyperplane const& hyperplane) + { + Result result; + + size_t const numVertices = polygon.size(); + if (numVertices < 3) + { + // The convex polygon must have at least 3 vertices. + result.intersect = false; + result.configuration = Configuration::INVALID_POLYGON; + return result; + } + + // Determine on which side of the hyperplane each vertex lies. + size_t numPositive = 0, numNegative = 0, numZero = 0; + for (size_t i = 0; i < numVertices; ++i) + { + Real h = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant; + if (h > (Real)0) + { + ++numPositive; + } + else if (h < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numPositive > 0) + { + if (numNegative > 0) + { + result.intersect = true; + result.configuration = Configuration::SPLIT; + } + else if (numZero == 0) + { + result.intersect = false; + result.configuration = Configuration::POSITIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.intersect = true; + result.configuration = Configuration::POSITIVE_SIDE_VERTEX; + } + else // numZero > 1 + { + result.intersect = true; + result.configuration = Configuration::POSITIVE_SIDE_EDGE; + } + } + else if (numNegative > 0) + { + if (numZero == 0) + { + result.intersect = false; + result.configuration = Configuration::NEGATIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + // The polygon touches the plane in a vertex or an edge. + result.intersect = true; + result.configuration = Configuration::NEGATIVE_SIDE_VERTEX; + } + else // numZero > 1 + { + result.intersect = true; + result.configuration = Configuration::NEGATIVE_SIDE_EDGE; + } + } + else // numZero == numVertices + { + result.intersect = true; + result.configuration = Configuration::CONTAINED; + } + + return result; + } + }; + + template + class FIQuery>, Hyperplane> + { + public: + enum class Configuration + { + SPLIT, + POSITIVE_SIDE_VERTEX, + POSITIVE_SIDE_EDGE, + POSITIVE_SIDE_STRICT, + NEGATIVE_SIDE_VERTEX, + NEGATIVE_SIDE_EDGE, + NEGATIVE_SIDE_STRICT, + CONTAINED, + INVALID_POLYGON + }; + + struct Result + { + // The intersection is either empty, a single vertex, a single + // edge or the polygon is contained by the hyperplane. + Configuration configuration; + std::vector> intersection; + + // If 'configuration' is POSITIVE_* or SPLIT, this polygon is the + // portion of the query input 'polygon' on the positive side of + // the hyperplane with possibly a vertex or edge on the hyperplane. + std::vector> positivePolygon; + + // If 'configuration' is NEGATIVE_* or SPLIT, this polygon is the + // portion of the query input 'polygon' on the negative side of + // the hyperplane with possibly a vertex or edge on the hyperplane. + std::vector> negativePolygon; + }; + + Result operator()(std::vector> const& polygon, Hyperplane const& hyperplane) + { + Result result; + + size_t const numVertices = polygon.size(); + if (numVertices < 3) + { + // The convex polygon must have at least 3 vertices. + result.configuration = Configuration::INVALID_POLYGON; + return result; + } + + // Determine on which side of the hyperplane the vertices live. + // The index maxPosIndex stores the index of the vertex on the + // positive side of the hyperplane that is farthest from the + // hyperplane. The index maxNegIndex stores the index of the + // vertex on the negative side of the hyperplane that is farthest + // from the hyperplane. If one or the other such vertex does not + // exist, the corresponding index will remain its initial value of + // max(size_t). + std::vector height(numVertices); + std::vector zeroHeightIndices; + zeroHeightIndices.reserve(numVertices); + size_t numPositive = 0, numNegative = 0; + Real maxPosHeight = -std::numeric_limits::max(); + Real maxNegHeight = std::numeric_limits::max(); + size_t maxPosIndex = std::numeric_limits::max(); + size_t maxNegIndex = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + height[i] = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant; + if (height[i] > (Real)0) + { + ++numPositive; + if (height[i] > maxPosHeight) + { + maxPosHeight = height[i]; + maxPosIndex = i; + } + } + else if (height[i] < (Real)0) + { + ++numNegative; + if (height[i] < maxNegHeight) + { + maxNegHeight = height[i]; + maxNegIndex = i; + } + } + else + { + zeroHeightIndices.push_back(i); + } + } + + if (numPositive > 0) + { + if (numNegative > 0) + { + result.configuration = Configuration::SPLIT; + + bool doSwap = (maxPosHeight < -maxNegHeight); + if (doSwap) + { + for (auto& h : height) + { + h = -h; + } + std::swap(maxPosIndex, maxNegIndex); + } + + SplitPolygon(polygon, height, maxPosIndex, result); + + if (doSwap) + { + std::swap(result.positivePolygon, result.negativePolygon); + } + } + else + { + size_t numZero = zeroHeightIndices.size(); + if (numZero == 0) + { + result.configuration = Configuration::POSITIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.configuration = Configuration::POSITIVE_SIDE_VERTEX; + result.intersection = + { + polygon[zeroHeightIndices[0]] + }; + } + else // numZero > 1 + { + result.configuration = Configuration::POSITIVE_SIDE_EDGE; + result.intersection = + { + polygon[zeroHeightIndices[0]], + polygon[zeroHeightIndices[1]] + }; + } + result.positivePolygon = polygon; + } + } + else if (numNegative > 0) + { + size_t numZero = zeroHeightIndices.size(); + if (numZero == 0) + { + result.configuration = Configuration::NEGATIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.configuration = Configuration::NEGATIVE_SIDE_VERTEX; + result.intersection = + { + polygon[zeroHeightIndices[0]] + }; + } + else // numZero > 1 + { + result.configuration = Configuration::NEGATIVE_SIDE_EDGE; + result.intersection = + { + polygon[zeroHeightIndices[0]], + polygon[zeroHeightIndices[1]] + }; + } + result.negativePolygon = polygon; + } + else // numZero == numVertices + { + result.configuration = Configuration::CONTAINED; + result.intersection = polygon; + } + + return result; + } + + protected: + void SplitPolygon(std::vector> const& polygon, + std::vector const& height, size_t maxPosIndex, Result& result) + { + // Find the largest contiguous subset of indices for which + // height[i] >= 0. + size_t const numVertices = polygon.size(); + std::list> positiveList; + positiveList.push_back(polygon[maxPosIndex]); + size_t end0 = maxPosIndex; + size_t end0prev = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + end0prev = (end0 + numVertices - 1) % numVertices; + if (height[end0prev] >= (Real)0) + { + positiveList.push_front(polygon[end0prev]); + end0 = end0prev; + } + else + { + break; + } + } + + size_t end1 = maxPosIndex; + size_t end1next = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + end1next = (end1 + 1) % numVertices; + if (height[end1next] >= (Real)0) + { + positiveList.push_back(polygon[end1next]); + end1 = end1next; + } + else + { + break; + } + } + + size_t index = end1next; + std::list> negativeList; + for (size_t i = 0; i < numVertices; ++i) + { + negativeList.push_back(polygon[index]); + index = (index + 1) % numVertices; + if (index == end0) + { + break; + } + } + + // Clip the polygon. + if (height[end0] > (Real)0) + { + Real t = -height[end0prev] / (height[end0] - height[end0prev]); + Real omt = (Real)1 - t; + Vector V = omt * polygon[end0prev] + t * polygon[end0]; + positiveList.push_front(V); + negativeList.push_back(V); + result.intersection.push_back(V); + } + else + { + negativeList.push_back(polygon[end0]); + result.intersection.push_back(polygon[end0]); + } + + if (height[end1] > (Real)0) + { + Real t = -height[end1next] / (height[end1] - height[end1next]); + Real omt = (Real)1 - t; + Vector V = omt * polygon[end1next] + t * polygon[end1]; + positiveList.push_back(V); + negativeList.push_front(V); + result.intersection.push_back(V); + } + else + { + negativeList.push_front(polygon[end1]); + result.intersection.push_back(polygon[end1]); + } + + result.positivePolygon.reserve(positiveList.size()); + for (auto const& p : positiveList) + { + result.positivePolygon.push_back(p); + } + + result.negativePolygon.reserve(negativeList.size()); + for (auto const& p : negativeList) + { + result.negativePolygon.push_back(p); + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrDisk2Sector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrDisk2Sector2.h new file mode 100644 index 000000000000..6bd9aa1078f0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrDisk2Sector2.h @@ -0,0 +1,185 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include + +// The Circle2 object is considered to be a disk whose points X satisfy the +// constraint |X-C| <= R, where C is the disk center and R is the disk +// radius. The Sector2 object is also considered to be a solid. Also, +// the Sector2 object is required to be convex, so the sector angle must +// be in (0,pi/2], even though the Sector2 definition allows for angles +// larger than pi/2 (leading to nonconvex sectors). The sector vertex is +// V, the radius is L, the axis direction is D, and the angle is A. Sector +// points X satisfy |X-V| <= L and Dot(D,X-V) >= cos(A)|X-V| >= 0. +// +// A subproblem for the test-intersection query is to determine whether +// the disk intersects the cone of the sector. Although the query is in +// 2D, it is analogous to the 3D problem of determining whether a sphere +// and cone overlap. That algorithm is described in +// http://www.geometrictools.com/Documentation/IntersectionSphereCone.pdf +// The algorithm leads to coordinate-free pseudocod which applies to 2D +// as well as 3D. That function is the first SphereIntersectsCone on +// page 4 of the PDF. +// +// If the disk is outside the cone, there is no intersection. If the disk +// overlaps the cone, we then need to test whether the disk overlaps the +// disk of the sector. + +namespace gte +{ + +template +class TIQuery, Sector2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Circle2 const& disk, Sector2 const& sector); +}; + +template +typename TIQuery, Sector2>::Result +TIQuery, Sector2>::operator()( + Circle2 const& disk, Sector2 const& sector) +{ + Result result; + + // Test whether the disk and the disk of the sector overlap. + Vector2 CmV = disk.center - sector.vertex; + Real sqrLengthCmV = Dot(CmV, CmV); + Real lengthCmV = std::sqrt(sqrLengthCmV); + if (lengthCmV > disk.radius + sector.radius) + { + // The disk is outside the disk of the sector. + result.intersect = false; + return result; + } + + // Test whether the disk and cone of the sector overlap. The comments + // about K, K', and K" refer to the PDF mentioned previously. + Vector2 U = sector.vertex - (disk.radius / sector.sinAngle) * sector.direction; + Vector2 CmU = disk.center - U; + Real lengthCmU = Length(CmU); + if (Dot(sector.direction, CmU) < lengthCmU * sector.cosAngle) + { + // The disk center is outside K" (in the white or gray regions). + result.intersect = false; + return result; + } + // The disk center is inside K" (in the red, orange, blue, or green regions). + Real dotDirCmV = Dot(sector.direction, CmV); + if (-dotDirCmV >= lengthCmV * sector.sinAngle) + { + // The disk center is inside K" and inside K' (in the blue or green regions). + if (lengthCmV <= disk.radius) + { + // The disk center is in the blue region, in which case the disk + // contains the sector's vertex. + result.intersect = true; + } + else + { + // The disk center is in the green region. + result.intersect = false; + } + return result; + } + + // To reach here, we know that the disk overlaps the sector's disk and + // the sector's cone. The disk center is in the orange region or in + // in the red region (not including the segments that separate the red + // and blue regions). + + // Test whether the ray of the right boundary of the sector overlaps + // the disk. The ray direction U0 is a clockwise rotation of the + // cone axis by the cone angle. + Vector2 U0 + { + +sector.cosAngle * sector.direction[0] + sector.sinAngle * sector.direction[1], + -sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Real dp0 = Dot(U0, CmV); + Real discr0 = disk.radius * disk.radius + dp0 * dp0 - sqrLengthCmV; + if (discr0 >= (Real)0) + { + // The ray intersects the disk. Now test whether the sector boundary + // segment contained by the ray overlaps the disk. The quadratic + // root tmin generates the ray-disk point of intersection closest to + // the sector vertex. + Real tmin = dp0 - std::sqrt(discr0); + if (sector.radius >= tmin) + { + // The segment overlaps the disk. + result.intersect = true; + return result; + } + else + { + // The segment does not overlap the disk. We know the disks + // overlap, so if the disk center is outside the sector cone + // or on the right-boundary ray, the overlap occurs outside the + // cone, which implies the disk and sector do not intersect. + if (dotDirCmV <= lengthCmV * sector.cosAngle) + { + // The disk center is not inside the sector cone. + result.intersect = false; + return result; + } + } + } + + // Test whether the ray of the left boundary of the sector overlaps + // the disk. The ray direction U1 is a counterclockwise rotation of the + // cone axis by the cone angle. + Vector2 U1 + { + +sector.cosAngle * sector.direction[0] - sector.sinAngle * sector.direction[1], + +sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Real dp1 = Dot(U1, CmV); + Real discr1 = disk.radius * disk.radius + dp1 * dp1 - sqrLengthCmV; + if (discr1 >= (Real)0) + { + // The ray intersects the disk. Now test whether the sector boundary + // segment contained by the ray overlaps the disk. The quadratic + // root tmin generates the ray-disk point of intersection closest to + // the sector vertex. + Real tmin = dp1 - std::sqrt(discr1); + if (sector.radius >= tmin) + { + result.intersect = true; + return result; + } + else + { + // The segment does not overlap the disk. We know the disks + // overlap, so if the disk center is outside the sector cone + // or on the right-boundary ray, the overlap occurs outside the + // cone, which implies the disk and sector do not intersect. + if (dotDirCmV <= lengthCmV * sector.cosAngle) + { + // The disk center is not inside the sector cone. + result.intersect = false; + return result; + } + } + } + + // To reach here, a strict subset of the sector's arc boundary must + // intersect the disk. + result.intersect = true; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipse2Ellipse2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipse2Ellipse2.h new file mode 100644 index 000000000000..291ffed9dba6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipse2Ellipse2.h @@ -0,0 +1,1369 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// The test-intersection and find-intersection queries implemented here are +// discussed in the document +// http://www.geometrictools.com/Documentation/IntersectionOfEllipses.pdf +// The Real type should support exact rational arithmetic in order for the +// polynomial root construction to be robust. The classification of the +// intersections depends on various sign tests of computed values. If these +// values are computed with floating-point arithmetic, the sign tests can +// lead to misclassification. +// +// The area-of-intersection query is discussed in the document +// http://www.geometrictools.com/Documentation/AreaIntersectingEllipses.pdf + +namespace gte +{ + +template +class TIQuery, Ellipse2> +{ +public: + // The query tests the relationship between the ellipses as solid + // objects. + enum + { + ELLIPSES_SEPARATED, + ELLIPSES_OVERLAP, + ELLIPSE0_OUTSIDE_ELLIPSE1_BUT_TANGENT, + ELLIPSE0_STRICTLY_CONTAINS_ELLIPSE1, + ELLIPSE0_CONTAINS_ELLIPSE1_BUT_TANGENT, + ELLIPSE1_STRICTLY_CONTAINS_ELLIPSE0, + ELLIPSE1_CONTAINS_ELLIPSE0_BUT_TANGENT, + ELLIPSES_EQUAL + }; + + // The ellipse axes are already normalized, which most likely introduced + // rounding errors. + int operator()(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1); + +private: + void GetRoots(Real d0, Real c0, int& numRoots, Real* roots); + void GetRoots(Real d0, Real d1, Real c0, Real c1, int& numRoots, + Real* roots); + + int Classify(Real minSqrDistance, Real maxSqrDistance, Real d0c0pd1c1); +}; + +template +class FIQuery, Ellipse2> +{ +public: + // The queries find the intersections (if any) of the ellipses treated as + // hollow objects. The implementations use the same concepts. + FIQuery(); + + struct Result + { + // This value is true when the ellipses intersect in at least one + // point. + bool intersect; + + // If the ellipses are not the same, numPoints is 0 through 4 and + // that number of elements of 'points' are valid. If the ellipses + // are the same, numPoints is set to maxInt and 'points' is invalid. + int numPoints; + std::array, 4> points; + std::array isTransverse; + }; + + // The ellipse axes are already normalized, which most likely introduced + // rounding errors. + Result operator()(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1); + + // The axis directions do not have to be unit length. The quadratic + // equations are constructed according to the details of the PDF document + // about the intersection of ellipses. + Result operator()(Vector2 const& center0, + Vector2 const axis0[2], Vector2 const& sqrExtent0, + Vector2 const& center1, Vector2 const axis1[2], + Vector2 const& sqrExtent1); + + + // Compute the area of intersection of ellipses. + struct AreaResult + { + // The configuration of the two ellipses. + enum + { + ELLIPSES_ARE_EQUAL, + ELLIPSES_ARE_SEPARATED, + E0_CONTAINS_E1, + E1_CONTAINS_E0, + ONE_CHORD_REGION, + FOUR_CHORD_REGION + }; + + // One of the enumerates, determined in the call to AreaDispatch. + int configuration; + + // Information about the ellipse-ellipse intersection points. + Result findResult; + + // The area of intersection of the ellipses. + Real area; + }; + + // The ellipse axes are already normalized, which most likely introduced + // rounding errors. + AreaResult AreaOfIntersection(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1); + + // The axis directions do not have to be unit length. The positive + // definite matrices are constructed according to the details of the PDF + // documentabout the intersection of ellipses. + AreaResult AreaOfIntersection(Vector2 const& center0, + Vector2 const axis0[2], Vector2 const& sqrExtent0, + Vector2 const& center1, Vector2 const axis1[2], + Vector2 const& sqrExtent1); + +private: + // Compute the coefficients of the quadratic equation but with the + // y^2-coefficient of 1. + void ToCoefficients(Vector2 const& center, + Vector2 const axis[2], Vector2 const& SqrExtent, + std::array& coeff); + + void DoRootFinding(Result& result); + void D4NotZeroEBarNotZero(Result& result); + void D4NotZeroEBarZero(Real const& xbar, Result& result); + void D4ZeroD2NotZeroE2NotZero(Result& result); + void D4ZeroD2NotZeroE2Zero(Result& result); + void D4ZeroD2Zero(Result& result); + + // Helper function for D4ZeroD2Zero. + void SpecialIntersection(Real const& x, bool transverse, Result& result); + + + // Helper functions for AreaOfIntersection. + struct EllipseInfo + { + Vector2 center; + std::array, 2> axis; + Vector2 extent, sqrExtent; + Matrix2x2 M; + Real AB; // extent[0] * extent[1] + Real halfAB; // extent[0] * extent[1] / 2 + Real BpA; // extent[1] + extent[0] + Real BmA; // extent[1] - extent[0] + }; + + // The axis, extent, and sqrExtent members of E must be set before this + // function is called. + void FinishEllipseInfo(EllipseInfo& E); + + void AreaDispatch(EllipseInfo const& E0, EllipseInfo const& E1, + AreaResult& ar); + + void AreaCS(EllipseInfo const& E0, EllipseInfo const& E1, + AreaResult& ar); + + void Area2(EllipseInfo const& E0, EllipseInfo const& E1, int i0, int i1, + AreaResult& ar); + + void Area4(EllipseInfo const& E0, EllipseInfo const& E1, + AreaResult& ar); + + Real ComputeAreaChordRegion(EllipseInfo const& E, + Vector2 const& P0mC, Vector2 const& P1mC); + + Real ComputeIntegral(EllipseInfo const& E, Real const& theta); + + + // Constants that are set up once (optimization for rational arithmetic). + Real mZero, mOne, mTwo, mPi, mTwoPi; + + // Various polynomial coefficients. The short names are meant to match + // the variable names used in the PDF documentation. + std::array mA, mB, mD, mF; + std::array mC, mE; + Real mA2Div2, mA4Div2; +}; + +// Template aliases for convenience. +template +using TIEllipses2 = TIQuery, Ellipse2>; + +template +using FIEllipses2 = FIQuery, Ellipse2>; + + +// TIQuery for intersections + +template +int TIQuery, Ellipse2>::operator()( + Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) +{ + Real const zero = 0, one = 1; + + // Get the parameters of ellipe0. + Vector2 K0 = ellipse0.center; + Matrix2x2 R0; + R0.SetCol(0, ellipse0.axis[0]); + R0.SetCol(1, ellipse0.axis[1]); + Matrix2x2 D0{ + one / (ellipse0.extent[0] * ellipse0.extent[0]), zero, + zero, one / (ellipse0.extent[1] * ellipse0.extent[1]) }; + + // Get the parameters of ellipse1. + Vector2 K1 = ellipse1.center; + Matrix2x2 R1; + R1.SetCol(0, ellipse1.axis[0]); + R1.SetCol(1, ellipse1.axis[1]); + Matrix2x2 D1{ + one / (ellipse1.extent[0] * ellipse1.extent[0]), zero, + zero, one / (ellipse1.extent[1] * ellipse1.extent[1]) }; + + // Compute K2. + Matrix2x2 D0NegHalf{ + ellipse0.extent[0], zero, + zero, ellipse0.extent[1] }; + Matrix2x2 D0Half{ + one / ellipse0.extent[0], zero, + zero, one / ellipse0.extent[1] }; + Vector2 K2 = D0Half*((K1 - K0)*R0); + + // Compute M2. + Matrix2x2 R1TR0D0NegHalf = MultiplyATB(R1, R0 * D0NegHalf); + Matrix2x2 M2 = MultiplyATB(R1TR0D0NegHalf, D1) * R1TR0D0NegHalf; + + // Factor M2 = R*D*R^T. + SymmetricEigensolver2x2 es; + std::array D; + std::array, 2> evec; + es(M2(0, 0), M2(0, 1), M2(1, 1), +1, D, evec); + Matrix2x2 R; + R.SetCol(0, evec[0]); + R.SetCol(1, evec[1]); + + // Compute K = R^T*K2. + Vector2 K = K2 * R; + + // Transformed ellipse0 is Z^T*Z = 1 and transformed ellipse1 is + // (Z-K)^T*D*(Z-K) = 0. + + // The minimum and maximum squared distances from the origin of points on + // transformed ellipse1 are used to determine whether the ellipses + // intersect, are separated, or one contains the other. + Real minSqrDistance = std::numeric_limits::max(); + Real maxSqrDistance = zero; + + if (K == Vector2::Zero()) + { + // The special case of common centers must be handled separately. It + // is not possible for the ellipses to be separated. + for (int i = 0; i < 2; ++i) + { + Real invD = one / D[i]; + if (invD < minSqrDistance) + { + minSqrDistance = invD; + } + if (invD > maxSqrDistance) + { + maxSqrDistance = invD; + } + } + return Classify(minSqrDistance, maxSqrDistance, zero); + } + + // The closest point P0 and farthest point P1 are solutions to + // s0*D*(P0 - K) = P0 and s1*D1*(P1 - K) = P1 for some scalars s0 and s1 + // that are roots to the function + // f(s) = d0*k0^2/(d0*s-1)^2 + d1*k1^2/(d1*s-1)^2 - 1 + // where D = diagonal(d0,d1) and K = (k0,k1). + Real d0 = D[0], d1 = D[1]; + Real c0 = K[0] * K[0], c1 = K[1] * K[1]; + + // Sort the values so that d0 >= d1. This allows us to bound the roots of + // f(s), of which there are at most 4. + std::vector> param(2); + if (d0 >= d1) + { + param[0] = std::make_pair(d0, c0); + param[1] = std::make_pair(d1, c1); + } + else + { + param[0] = std::make_pair(d1, c1); + param[1] = std::make_pair(d0, c0); + } + + std::vector> valid; + valid.reserve(2); + if (param[0].first > param[1].first) + { + // d0 > d1 + for (int i = 0; i < 2; ++i) + { + if (param[i].second > zero) + { + valid.push_back(param[i]); + } + } + } + else + { + // d0 = d1 + param[0].second += param[1].second; + if (param[0].second > zero) + { + valid.push_back(param[0]); + } + } + + size_t numValid = valid.size(); + int numRoots = 0; + Real roots[4]; + if (numValid == 2) + { + GetRoots(valid[0].first, valid[1].first, valid[0].second, + valid[1].second, numRoots, roots); + } + else if (numValid == 1) + { + GetRoots(valid[0].first, valid[0].second, numRoots, roots); + } + // else: numValid cannot be zero because we already handled case K = 0 + + for (int i = 0; i < numRoots; ++i) + { + Real s = roots[i]; + Real p0 = d0 * K[0] * s / (d0 * s - (Real)1); + Real p1 = d1 * K[1] * s / (d1 * s - (Real)1); + Real sqrDistance = p0 * p0 + p1 * p1; + if (sqrDistance < minSqrDistance) + { + minSqrDistance = sqrDistance; + } + if (sqrDistance > maxSqrDistance) + { + maxSqrDistance = sqrDistance; + } + } + + return Classify(minSqrDistance, maxSqrDistance, d0 * c0 + d1 * c1); +} + +template +void TIQuery, Ellipse2>::GetRoots(Real d0, Real c0, + int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 - 1 + Real const one = (Real)1; + Real temp = std::sqrt(d0*c0); + Real inv = one / d0; + numRoots = 2; + roots[0] = (one - temp) * inv; + roots[1] = (one + temp) * inv; +} + +template +void TIQuery, Ellipse2>::GetRoots(Real d0, Real d1, + Real c0, Real c1, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 - 1 + // with d0 > d1 + + Real const zero = 0, one = (Real)1; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + Real sum = d0c0 + d1c1; + Real sqrtsum = std::sqrt(sum); + + std::function F = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real f = term0 + term1 - one; + return f; + }; + + std::function DF = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real const two = 2; + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real df = -two * (term0 + term1); + return df; + }; + + unsigned int const maxIterations = (unsigned int)( + 3 + std::numeric_limits::digits - + std::numeric_limits::min_exponent); + unsigned int iterations; + numRoots = 0; + + Real invD0 = one / d0; + Real invD1 = one / d1; + Real smin, smax, s, fval; + + // Compute root in (-infinity,1/d0). Obtain a lower bound for the root + // better than -std::numeric_limits::max(). + smax = invD0; + fval = sum - one; + if (fval > zero) + { + smin = (one - sqrtsum) * invD1; // < 0 + fval = F(smin); + LogAssert(fval <= zero, "Unexpected condition."); + } + else + { + smin = zero; + } + iterations = RootsBisection::Find(F, smin, smax, -one, one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + Real const oneThird = (Real)(1.0 / 3.0); + Real rho = std::pow(d0 * d0c0 / (d1 * d1c1), oneThird); + Real smid = (one + rho) / (d0 + rho * d1); + Real fmid = F(smid); + if (fmid <= zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + else if (fmid == zero) + { + roots[numRoots++] = smid; + } + + // Compute root in (1/d1,+infinity). Obtain an upper bound for the root + // better than std::numeric_limits::max(). + smin = invD1; + smax = (one + sqrtsum) * invD1; // > 1/d1 + fval = F(smax); + LogAssert(fval <= zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, one, -one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; +} + +template +int TIQuery, Ellipse2>::Classify( + Real minSqrDistance, Real maxSqrDistance, Real d0c0pd1c1) +{ + Real const one = (Real)1; + + if (maxSqrDistance < one) + { + return ELLIPSE0_STRICTLY_CONTAINS_ELLIPSE1; + } + else if (maxSqrDistance > one) + { + if (minSqrDistance < one) + { + return ELLIPSES_OVERLAP; + } + else if (minSqrDistance > one) + { + if (d0c0pd1c1 > one) + { + return ELLIPSES_SEPARATED; + } + else + { + return ELLIPSE1_STRICTLY_CONTAINS_ELLIPSE0; + } + } + else // minSqrDistance = 1 + { + if (d0c0pd1c1 > one) + { + return ELLIPSE0_OUTSIDE_ELLIPSE1_BUT_TANGENT; + } + else + { + return ELLIPSE1_CONTAINS_ELLIPSE0_BUT_TANGENT; + } + } + } + else // maxSqrDistance = 1 + { + if (minSqrDistance < one) + { + return ELLIPSE0_CONTAINS_ELLIPSE1_BUT_TANGENT; + } + else // minSqrDistance = 1 + { + return ELLIPSES_EQUAL; + } + } +} + + + +// FIQuery constructor for both intersection and area queries. + +template +FIQuery, Ellipse2>::FIQuery() + : + mZero((Real)0), + mOne((Real)1), + mTwo((Real)2), + mPi((Real)GTE_C_PI), + mTwoPi((Real)GTE_C_TWO_PI) +{ +} + + + +// FIQuery functions for ellipse-ellipse intersection points. + +template +typename FIQuery, Ellipse2>::Result +FIQuery, Ellipse2>::operator()( + Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) +{ + Vector2 rCenter, rAxis[2], rSqrExtent; + + rCenter = { ellipse0.center[0], ellipse0.center[1] }; + rAxis[0] = { ellipse0.axis[0][0], ellipse0.axis[0][1] }; + rAxis[1] = { ellipse0.axis[1][0], ellipse0.axis[1][1] }; + rSqrExtent = + { + ellipse0.extent[0] * ellipse0.extent[0], + ellipse0.extent[1] * ellipse0.extent[1] + }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mA); + + rCenter = { ellipse1.center[0], ellipse1.center[1] }; + rAxis[0] = { ellipse1.axis[0][0], ellipse1.axis[0][1] }; + rAxis[1] = { ellipse1.axis[1][0], ellipse1.axis[1][1] }; + rSqrExtent = + { + ellipse1.extent[0] * ellipse1.extent[0], + ellipse1.extent[1] * ellipse1.extent[1] + }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mB); + + Result result; + DoRootFinding(result); + return result; +} + +template +typename FIQuery, Ellipse2>::Result +FIQuery, Ellipse2>::operator()( + Vector2 const& center0, Vector2 const axis0[2], + Vector2 const& sqrExtent0, Vector2 const& center1, + Vector2 const axis1[2], Vector2 const& sqrExtent1) +{ + Vector2 rCenter, rAxis[2], rSqrExtent; + + rCenter = { center0[0], center0[1] }; + rAxis[0] = { axis0[0][0], axis0[0][1] }; + rAxis[1] = { axis0[1][0], axis0[1][1] }; + rSqrExtent = { sqrExtent0[0], sqrExtent0[1] }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mA); + + rCenter = { center1[0], center1[1] }; + rAxis[0] = { axis1[0][0], axis1[0][1] }; + rAxis[1] = { axis1[1][0], axis1[1][1] }; + rSqrExtent = { sqrExtent1[0], sqrExtent1[1] }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mB); + + Result result; + DoRootFinding(result); + return result; +} + +template +void FIQuery, Ellipse2>::ToCoefficients( + Vector2 const& center, Vector2 const axis[2], + Vector2 const& sqrExtent, std::array& coeff) +{ + Real denom0 = Dot(axis[0], axis[0]) * sqrExtent[0]; + Real denom1 = Dot(axis[1], axis[1]) * sqrExtent[1]; + Matrix2x2 outer0 = OuterProduct(axis[0], axis[0]); + Matrix2x2 outer1 = OuterProduct(axis[1], axis[1]); + Matrix2x2 A = outer0 / denom0 + outer1 / denom1; + Vector2 product = A * center; + Vector2 B = -mTwo * product; + Real const& denom = A(1, 1); + coeff[0] = (Dot(center, product) - mOne) / denom; + coeff[1] = B[0] / denom; + coeff[2] = B[1] / denom; + coeff[3] = A(0, 0) / denom; + coeff[4] = mTwo * A(0, 1) / denom; + // coeff[5] = A(1, 1) / denom = 1; +} + +template +void FIQuery, Ellipse2>::DoRootFinding( + Result& result) +{ + bool allZero = true; + for (int i = 0; i < 5; ++i) + { + mD[i] = mA[i] - mB[i]; + if (mD[i] != mZero) + { + allZero = false; + } + } + if (allZero) + { + result.intersect = false; + result.numPoints = std::numeric_limits::max(); + return; + } + + result.numPoints = 0; + + mA2Div2 = mA[2] / mTwo; + mA4Div2 = mA[4] / mTwo; + mC[0] = mA[0] - mA2Div2 * mA2Div2; + mC[1] = mA[1] - mA2Div2 * mA[4]; + mC[2] = mA[3] - mA4Div2 * mA4Div2; // c[2] > 0 + mE[0] = mD[0] - mA2Div2 * mD[2]; + mE[1] = mD[1] - mA2Div2 * mD[4] - mA4Div2 * mD[2]; + mE[2] = mD[3] - mA4Div2 * mD[4]; + + if (mD[4] != mZero) + { + Real xbar = -mD[2] / mD[4]; + Real ebar = mE[0] + xbar * (mE[1] + xbar * mE[2]); + if (ebar != mZero) + { + D4NotZeroEBarNotZero(result); + } + else + { + D4NotZeroEBarZero(xbar, result); + } + } + else if (mD[2] != mZero) // d[4] = 0 + { + if (mE[2] != mZero) + { + D4ZeroD2NotZeroE2NotZero(result); + } + else + { + D4ZeroD2NotZeroE2Zero(result); + } + } + else // d[2] = d[4] = 0 + { + D4ZeroD2Zero(result); + } + + result.intersect = (result.numPoints > 0); +} + +template +void FIQuery, Ellipse2>::D4NotZeroEBarNotZero( + Result& result) +{ + // The graph of w = -e(x)/d(x) is a hyperbola. + Real d2d2 = mD[2] * mD[2], d2d4 = mD[2] * mD[4], d4d4 = mD[4] * mD[4]; + Real e0e0 = mE[0] * mE[0], e0e1 = mE[0] * mE[1], e0e2 = mE[0] * mE[2]; + Real e1e1 = mE[1] * mE[1], e1e2 = mE[1] * mE[2], e2e2 = mE[2] * mE[2]; + std::array f = + { + mC[0] * d2d2 + e0e0, + mC[1] * d2d2 + mTwo * (mC[0] * d2d4 + e0e1), + mC[2] * d2d2 + mC[0] * d4d4 + e1e1 + mTwo * (mC[1] * d2d4 + e0e2), + mC[1] * d4d4 + mTwo * (mC[2] * d2d4 + e1e2), + mC[2] * d4d4 + e2e2 // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuartic(f[0], f[1], f[2], + f[3], f[4], rmMap); + + // xbar cannot be a root of f(x), so d(x) != 0 and we can solve + // directly for w = -e(x)/d(x). + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real w = -(mE[0] + x * (mE[1] + x * mE[2])) / (mD[2] + mD[4] * x); + Real y = w - (mA2Div2 + x * mA4Div2); + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4NotZeroEBarZero( + Real const& xbar, Result& result) +{ + // Factor e(x) = (d2 + d4*x)*(h0 + h1*x). The w-equation has + // two solution components, x = xbar and w = -(h0 + h1*x). + Real translate, w, y; + + // Compute intersection of x = xbar with ellipse. + Real ncbar = -(mC[0] + xbar * (mC[1] + xbar * mC[2])); + if (ncbar >= mZero) + { + translate = mA2Div2 + xbar * mA4Div2; + w = std::sqrt(ncbar); + y = w - translate; + result.points[result.numPoints] = { xbar, y }; + if (w > mZero) + { + result.isTransverse[result.numPoints++] = true; + w = -w; + y = w - translate; + result.points[result.numPoints] = { xbar, y }; + result.isTransverse[result.numPoints++] = true; + } + else + { + result.isTransverse[result.numPoints++] = false; + } + } + + // Compute intersections of w = -(h0 + h1*x) with ellipse. + std::array h; + h[1] = mE[2] / mD[4]; + h[0] = (mE[1] - mD[2] * h[1]) / mD[4]; + std::array f = + { + mC[0] + h[0] * h[0], + mC[1] + mTwo * h[0] * h[1], + mC[2] + h[1] * h[1] // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuadratic(f[0], f[1], f[2], + rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + translate = mA2Div2 + x * mA4Div2; + w = -(h[0] + x * h[1]); + y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4ZeroD2NotZeroE2NotZero( + Result& result) +{ + Real d2d2 = mD[2] * mD[2]; + std::array f = + { + mC[0] * d2d2 + mE[0] * mE[0], + mC[1] * d2d2 + mTwo * mE[0] * mE[1], + mC[2] * d2d2 + mE[1] * mE[1] + mTwo * mE[0] * mE[2], + mTwo * mE[1] * mE[2], + mE[2] * mE[2] // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuartic(f[0], f[1], f[2], f[3], + f[4], rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real translate = mA2Div2 + x * mA4Div2; + Real w = -(mE[0] + x * (mE[1] + x * mE[2])) / mD[2]; + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4ZeroD2NotZeroE2Zero( + Result& result) +{ + Real d2d2 = mD[2] * mD[2]; + std::array f = + { + mC[0] * d2d2 + mE[0] * mE[0], + mC[1] * d2d2 + mTwo * mE[0] * mE[1], + mC[2] * d2d2 + mE[1] * mE[1] + }; + + std::map rmMap; + RootsPolynomial::template SolveQuadratic(f[0], f[1], f[2], + rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real translate = mA2Div2 + x * mA4Div2; + Real w = -(mE[0] + x * mE[1]) / mD[2]; + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } +} + +template +void FIQuery, Ellipse2>::D4ZeroD2Zero( + Result& result) +{ + // e(x) cannot be identically zero, because if it were, then all + // d[i] = 0. But we tested that case previously and exited. + + if (mE[2] != mZero) + { + // Make e(x) monic, f(x) = e(x)/e2 = x^2 + (e1/e2)*x + (e0/e2) + // = x^2 + f1*x + f0. + std::array f = { mE[0] / mE[2], mE[1] / mE[2] }; + + Real mid = -f[1] / mTwo; + Real discr = mid * mid - f[0]; + if (discr > mZero) + { + // The theoretical roots of e(x) are x = -f1/2 + s*sqrt(discr) + // where s in {-1,+1}. For each root, determine exactly the + // sign of c(x). We need c(x) <= 0 in order to solve for + // w^2 = -c(x). At the root, + // c(x) = c0 + c1*x + c2*x^2 = c0 + c1*x - c2*(f0 + f1*x) + // = (c0 - c2*f0) + (c1 - c2*f1)*x + // = g0 + g1*x + // We need g0 + g1*x <= 0. + Real sqrtDiscr = std::sqrt(discr); + std::array g = + { + mC[0] - mC[2] * f[0], + mC[1] - mC[2] * f[1] + }; + + if (g[1] > mZero) + { + // We need s*sqrt(discr) <= -g[0]/g[1] + f1/2. + Real r = -g[0] / g[1] - mid; + + // s = +1: + if (r >= mZero) + { + Real rsqr = r * r; + if (discr < rsqr) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + + // s = -1: + if (r > mZero) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else + { + Real rsqr = r * r; + if (discr > rsqr) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid - sqrtDiscr, false, result); + } + } + } + else if (g[1] < mZero) + { + // We need s*sqrt(discr) >= -g[0]/g[1] + f1/2. + Real r = -g[0] / g[1] - mid; + + // s = -1: + if (r <= mZero) + { + Real rsqr = r * r; + if (discr < rsqr) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else + { + SpecialIntersection(mid - sqrtDiscr, false, result); + } + } + + // s = +1: + if (r < mZero) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else + { + Real rsqr = r * r; + if (discr > rsqr) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + } + else // g[1] = 0 + { + // The graphs of c(x) and f(x) are parabolas of the same + // shape. One is a vertical translation of the other. + if (g[0] < mZero) + { + // The graph of f(x) is above that of c(x). + SpecialIntersection(mid - sqrtDiscr, true, result); + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (g[0] == mZero) + { + // The graphs of c(x) and f(x) are the same parabola. + SpecialIntersection(mid - sqrtDiscr, false, result); + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + } + else if (discr == mZero) + { + // The theoretical root of f(x) is x = -f1/2. + Real nchat = -(mC[0] + mid * (mC[1] + mid * mC[2])); + if (nchat > mZero) + { + SpecialIntersection(mid, true, result); + } + else if (nchat == mZero) + { + SpecialIntersection(mid, false, result); + } + } + } + else if (mE[1] != mZero) + { + Real xhat = -mE[0] / mE[1]; + Real nchat = -(mC[0] + xhat * (mC[1] + xhat * mC[2])); + if (nchat > mZero) + { + SpecialIntersection(xhat, true, result); + } + else if (nchat == mZero) + { + SpecialIntersection(xhat, false, result); + } + } +} + +template +void FIQuery, Ellipse2>::SpecialIntersection( + Real const& x, bool transverse, Result& result) +{ + if (transverse) + { + Real translate = mA2Div2 + x * mA4Div2; + Real nc = -(mC[0] + x * (mC[1] + x * mC[2])); + if (nc < mZero) + { + // Clamp to eliminate the rounding error, but duplicate the point + // because we know that it is a transverse intersection. + nc = mZero; + } + + Real w = std::sqrt(nc); + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = true; + w = -w; + y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = true; + } + else + { + // The vertical line at the root is tangent to the ellipse. + Real y = -(mA2Div2 + x * mA4Div2); // w = 0 + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = false; + } +} + + + +// FIQuery functions for area of intersection of ellipses. + +template +typename FIQuery, Ellipse2>::AreaResult +FIQuery, Ellipse2>::AreaOfIntersection( + Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) +{ + EllipseInfo E0; + E0.center = ellipse0.center; + E0.axis = ellipse0.axis; + E0.extent = ellipse0.extent; + E0.sqrExtent = ellipse0.extent * ellipse0.extent; + FinishEllipseInfo(E0); + + EllipseInfo E1; + E1.center = ellipse1.center; + E1.axis = ellipse1.axis; + E1.extent = ellipse1.extent; + E1.sqrExtent = ellipse1.extent * ellipse0.extent; + FinishEllipseInfo(E1); + + AreaResult ar; + ar.configuration = 0; + ar.findResult = operator()(ellipse0, ellipse1); + ar.area = mZero; + AreaDispatch(E0, E1, ar); + return ar; +} + +template +typename FIQuery, Ellipse2>::AreaResult +FIQuery, Ellipse2>::AreaOfIntersection( + Vector2 const& center0, Vector2 const axis0[2], + Vector2 const& sqrExtent0, Vector2 const& center1, + Vector2 const axis1[2], Vector2 const& sqrExtent1) +{ + EllipseInfo E0; + E0.center = center0; + E0.axis = { axis0[0], axis0[1] }; + E0.extent = + { + std::sqrt(sqrExtent0[0]), + std::sqrt(sqrExtent0[1]) + }; + E0.sqrExtent = sqrExtent0; + FinishEllipseInfo(E0); + + EllipseInfo E1; + E1.center = center1; + E1.axis = { axis1[0], axis1[1] }; + E1.extent = + { + std::sqrt(sqrExtent1[0]), + std::sqrt(sqrExtent1[1]) + }; + E1.sqrExtent = sqrExtent1; + FinishEllipseInfo(E1); + + AreaResult ar; + ar.configuration = 0; + ar.findResult = operator()(center0, axis0, sqrExtent0, center1, axis1, + sqrExtent1); + ar.area = mZero; + AreaDispatch(E0, E1, ar); + return ar; +} + +template +void FIQuery, Ellipse2>::FinishEllipseInfo( + EllipseInfo& E) +{ + E.M = OuterProduct(E.axis[0], E.axis[0]) / + (E.sqrExtent[0] * Dot(E.axis[0], E.axis[0])); + E.M += OuterProduct(E.axis[1], E.axis[1]) / + (E.sqrExtent[1] * Dot(E.axis[1], E.axis[1])); + E.AB = E.extent[0] * E.extent[1]; + E.halfAB = E.AB / mTwo; + E.BpA = E.extent[1] + E.extent[0]; + E.BmA = E.extent[1] - E.extent[0]; +} + +template +void FIQuery, Ellipse2>::AreaDispatch( + EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) +{ + if (ar.findResult.intersect) + { + if (ar.findResult.numPoints == 1) + { + // Containment or separation. + AreaCS(E0, E1, ar); + } + else if (ar.findResult.numPoints == 2) + { + if (ar.findResult.isTransverse[0]) + { + // Both intersection points are transverse. + Area2(E0, E1, 0, 1, ar); + } + else + { + // Both intersection points are tangential, so one ellipse + // is contained in the other. + AreaCS(E0, E1, ar); + } + } + else if (ar.findResult.numPoints == 3) + { + // The tangential intersection is irrelevant in the area + // computation. + if (!ar.findResult.isTransverse[0]) + { + Area2(E0, E1, 1, 2, ar); + } + else if (!ar.findResult.isTransverse[1]) + { + Area2(E0, E1, 2, 0, ar); + } + else // ar.findResult.isTransverse[2] == false + { + Area2(E0, E1, 0, 1, ar); + } + } + else // ar.findResult.numPoints == 4 + { + Area4(E0, E1, ar); + } + } + else + { + // Containment, separation, or same ellipse. + AreaCS(E0, E1, ar); + } +} + +template +void FIQuery, Ellipse2>::AreaCS( + EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) +{ + if (ar.findResult.numPoints <= 1) + { + Vector2 diff = E0.center - E1.center; + Real qform0 = Dot(diff, E0.M * diff); + Real qform1 = Dot(diff, E1.M * diff); + if (qform0 > mOne && qform1 > mOne) + { + // Each ellipse center is outside the other ellipse, so the + // ellipses are separated (numPoints == 0) or outside each + // other and just touching (numPoints == 1). + ar.configuration = AreaResult::ELLIPSES_ARE_SEPARATED; + ar.area = mZero; + } + else + { + // One ellipse is inside the other. Determine this indirectly by + // comparing areas. + if (E0.AB < E1.AB) + { + ar.configuration = AreaResult::E1_CONTAINS_E0; + ar.area = mPi * E0.AB; + } + else + { + ar.configuration = AreaResult::E0_CONTAINS_E1; + ar.area = mPi * E1.AB; + } + } + } + else + { + ar.configuration = AreaResult::ELLIPSES_ARE_EQUAL; + ar.area = mPi * E0.AB; + } +} + +template +void FIQuery, Ellipse2>::Area2( + EllipseInfo const& E0, EllipseInfo const& E1, int i0, int i1, + AreaResult& ar) +{ + ar.configuration = AreaResult::ONE_CHORD_REGION; + + // The endpoints of the chord. + Vector2 const& P0 = ar.findResult.points[i0]; + Vector2 const& P1 = ar.findResult.points[i1]; + + // Compute locations relative to the ellipses. + Vector2 P0mC0 = P0 - E0.center, P0mC1 = P0 - E1.center; + Vector2 P1mC0 = P1 - E0.center, P1mC1 = P1 - E1.center; + + // Compute the ellipse normal vectors at endpoint P0. This is sufficient + // information to determine chord endpoint order. + Vector2 N0 = E0.M * P0mC0, N1 = E1.M * P0mC1; + Real dotperp = DotPerp(N1, N0); + + // Choose the endpoint order for the chord region associated with E0. + if (dotperp > mZero) + { + // The chord order for E0 is and for E1 is . + ar.area = + ComputeAreaChordRegion(E0, P0mC0, P1mC0) + + ComputeAreaChordRegion(E1, P1mC1, P0mC1); + } + else + { + // The chord order for E0 is and for E1 is . + ar.area = + ComputeAreaChordRegion(E0, P1mC0, P0mC0) + + ComputeAreaChordRegion(E1, P0mC1, P1mC1); + } +} + +template +void FIQuery, Ellipse2>::Area4( + EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) +{ + ar.configuration = AreaResult::FOUR_CHORD_REGION; + + // Select a counterclockwise ordering of the points of intersection. Use + // the polar coordinates for E0 to do this. The multimap is used in the + // event that computing the intersections involved numerical rounding + // errors that lead to a duplicate intersection, even though the + // intersections are all labeled as transverse. [See the comment in the + // function SpecialIntersection.] + std::multimap ordering; + int i; + for (i = 0; i < 4; ++i) + { + Vector2 PmC = ar.findResult.points[i] - E0.center; + Real x = Dot(E0.axis[0], PmC); + Real y = Dot(E0.axis[1], PmC); + Real theta = std::atan2(y, x); + ordering.insert(std::make_pair(theta, i)); + } + + std::array permute; + i = 0; + for (auto const& element : ordering) + { + permute[i++] = element.second; + } + + // Start with the area of the convex quadrilateral. + Vector2 diag20 = + ar.findResult.points[permute[2]] - ar.findResult.points[permute[0]]; + Vector2 diag31 = + ar.findResult.points[permute[3]] - ar.findResult.points[permute[1]]; + ar.area = std::abs(DotPerp(diag20, diag31)) / mTwo; + + // Visit each pair of consecutive points. The selection of ellipse for + // the chord-region area calculation uses the "most counterclockwise" + // tangent vector. + for (int i0 = 3, i1 = 0; i1 < 4; i0 = i1++) + { + // Get a pair of consecutive points. + Vector2 const& P0 = ar.findResult.points[permute[i0]]; + Vector2 const& P1 = ar.findResult.points[permute[i1]]; + + // Compute locations relative to the ellipses. + Vector2 P0mC0 = P0 - E0.center, P0mC1 = P0 - E1.center; + Vector2 P1mC0 = P1 - E0.center, P1mC1 = P1 - E1.center; + + // Compute the ellipse normal vectors at endpoint P0. + Vector2 N0 = E0.M * P0mC0, N1 = E1.M * P0mC1; + Real dotperp = DotPerp(N1, N0); + if (dotperp > mZero) + { + // The chord goes with ellipse E0. + ar.area += ComputeAreaChordRegion(E0, P0mC0, P1mC0); + } + else + { + // The chord goes with ellipse E1. + ar.area += ComputeAreaChordRegion(E1, P0mC1, P1mC1); + } + } +} + +template +Real FIQuery, Ellipse2>::ComputeAreaChordRegion( + EllipseInfo const& E, Vector2 const& P0mC, + Vector2 const& P1mC) +{ + // Compute polar coordinates for P0 and P1 on the ellipse. + Real x0 = Dot(E.axis[0], P0mC); + Real y0 = Dot(E.axis[1], P0mC); + Real theta0 = std::atan2(y0, x0); + Real x1 = Dot(E.axis[0], P1mC); + Real y1 = Dot(E.axis[1], P1mC); + Real theta1 = std::atan2(y1, x1); + + // The arc straddles the atan2 discontinuity on the negative x-axis. Wrap + // the second angle to be larger than the first angle. + if (theta1 < theta0) + { + theta1 += mTwoPi; + } + + // Compute the area portion of the sector due to the triangle. + Real triArea = std::abs(DotPerp(P0mC, P1mC)) / mTwo; + + // Compute the chord region area. + Real dtheta = theta1 - theta0; + Real F0, F1, sectorArea; + if (dtheta <= mPi) + { + // Use the area formula directly. + // area(theta0,theta1) = F(theta1) - F(theta0) - area(triangle) + F0 = ComputeIntegral(E, theta0); + F1 = ComputeIntegral(E, theta1); + sectorArea = F1 - F0; + return sectorArea - triArea; + } + else + { + // The angle of the elliptical sector is larger than pi radians. + // Use the area formula + // area(theta0,theta1) = pi*a*b - area(theta1,theta0) + theta0 += mTwoPi; // ensure theta0 > theta1 + F0 = ComputeIntegral(E, theta0); + F1 = ComputeIntegral(E, theta1); + sectorArea = F0 - F1; + return mPi * E.AB - (sectorArea - triArea); + } +} + +template +Real FIQuery, Ellipse2>::ComputeIntegral( + EllipseInfo const& E, Real const& theta) +{ + Real twoTheta = mTwo * theta; + Real sn = std::sin(twoTheta); + Real cs = std::cos(twoTheta); + Real arg = E.BmA * sn / (E.BpA + E.BmA * cs); + return E.halfAB * (theta - std::atan(arg)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipsoid3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipsoid3Ellipsoid3.h new file mode 100644 index 000000000000..4989c8a691ef --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrEllipsoid3Ellipsoid3.h @@ -0,0 +1,544 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + enum + { + ELLIPSOIDS_SEPARATED, + ELLIPSOIDS_INTERSECTING, + ELLIPSOID0_CONTAINS_ELLIPSOID1, + ELLIPSOID1_CONTAINS_ELLIPSOID0 + }; + + struct Result + { + // As solids, the ellipsoids intersect as long as they are not + // separated. + bool intersect; + + // This is one of the four enumerations listed above. + int relationship; + }; + + Result operator()(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1); + +private: + void GetRoots(Real d0, Real c0, int& numRoots, Real* roots); + void GetRoots(Real d0, Real d1, Real c0, Real c1, int& numRoots, + Real* roots); + void GetRoots(Real d0, Real d1, Real d2, Real c0, Real c1, Real c2, + int& numRoots, Real* roots); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Ellipsoid3 const& ellipsoid0, Ellipsoid3 const& ellipsoid1) +{ + Result result; + + Real const zero = (Real)0; + Real const one = (Real)1; + + // Get the parameters of ellipsoid0. + Vector3 K0 = ellipsoid0.center; + Matrix3x3 R0; + R0.SetCol(0, ellipsoid0.axis[0]); + R0.SetCol(1, ellipsoid0.axis[1]); + R0.SetCol(2, ellipsoid0.axis[2]); + Matrix3x3 D0{ + one / (ellipsoid0.extent[0] * ellipsoid0.extent[0]), zero, zero, + zero, one / (ellipsoid0.extent[1] * ellipsoid0.extent[1]), zero, + zero, zero, one / (ellipsoid0.extent[2] * ellipsoid0.extent[2]) }; + + // Get the parameters of ellipsoid1. + Vector3 K1 = ellipsoid1.center; + Matrix3x3 R1; + R1.SetCol(0, ellipsoid1.axis[0]); + R1.SetCol(1, ellipsoid1.axis[1]); + R1.SetCol(2, ellipsoid1.axis[2]); + Matrix3x3 D1{ + one / (ellipsoid1.extent[0] * ellipsoid1.extent[0]), zero, zero, + zero, one / (ellipsoid1.extent[1] * ellipsoid1.extent[1]), zero, + zero, zero, one / (ellipsoid1.extent[2] * ellipsoid1.extent[2]) }; + + // Compute K2. + Matrix3x3 D0NegHalf{ + ellipsoid0.extent[0], zero, zero, + zero, ellipsoid0.extent[1], zero, + zero, zero, ellipsoid0.extent[2] }; + Matrix3x3 D0Half{ + one / ellipsoid0.extent[0], zero, zero, + zero, one / ellipsoid0.extent[1], zero, + zero, zero, one / ellipsoid0.extent[2] }; + Vector3 K2 = D0Half*((K1 - K0)*R0); + + // Compute M2. + Matrix3x3 R1TR0D0NegHalf = MultiplyATB(R1, R0*D0NegHalf); + Matrix3x3 M2 = MultiplyATB(R1TR0D0NegHalf, D1)*R1TR0D0NegHalf; + + // Factor M2 = R*D*R^T. + SymmetricEigensolver3x3 es; + std::array D; + std::array, 3> evec; + es(M2(0, 0), M2(0, 1), M2(0, 2), M2(1, 1), M2(1, 2), M2(2, 2), false, +1, + D, evec); + Matrix3x3 R; + R.SetCol(0, evec[0]); + R.SetCol(1, evec[1]); + R.SetCol(2, evec[2]); + + // Compute K = R^T*K2. + Vector3 K = K2*R; + + // Transformed ellipsoid0 is Z^T*Z = 1 and transformed ellipsoid1 is + // (Z-K)^T*D*(Z-K) = 0. + + // The minimum and maximum squared distances from the origin of points on + // transformed ellipsoid1 are used to determine whether the ellipsoids + // intersect, are separated, or one contains the other. + Real minSqrDistance = std::numeric_limits::max(); + Real maxSqrDistance = zero; + int i; + + if (K == Vector3::Zero()) + { + // The special case of common centers must be handled separately. It + // is not possible for the ellipsoids to be separated. + for (i = 0; i < 3; ++i) + { + Real invD = one / D[i]; + if (invD < minSqrDistance) + { + minSqrDistance = invD; + } + if (invD > maxSqrDistance) + { + maxSqrDistance = invD; + } + } + + if (maxSqrDistance < one) + { + result.relationship = ELLIPSOID0_CONTAINS_ELLIPSOID1; + } + else if (minSqrDistance >(Real)1) + { + result.relationship = ELLIPSOID1_CONTAINS_ELLIPSOID0; + } + else + { + result.relationship = ELLIPSOIDS_INTERSECTING; + } + result.intersect = true; + return result; + } + + // The closest point P0 and farthest point P1 are solutions to + // s0*D*(P0 - K) = P0 and s1*D*(P1 - K) = P1 for some scalars s0 and s1 + // that are roots to the function + // f(s) = d0*k0^2/(d0*s-1)^2+d1*k1^2/(d1*s-1)^2+d2*k2^2/(d2*s-1)^2-1 + // where D = diagonal(d0,d1,d2) and K = (k0,k1,k2). + Real d0 = D[0], d1 = D[1], d2 = D[2]; + Real c0 = K[0] * K[0], c1 = K[1] * K[1], c2 = K[2] * K[2]; + + // Sort the values so that d0 >= d1 >= d2. This allows us to bound the + // roots of f(s), of which there are at most 6. + std::vector> param(3); + param[0] = std::make_pair(d0, c0); + param[1] = std::make_pair(d1, c1); + param[2] = std::make_pair(d2, c2); + std::sort(param.begin(), param.end(), + std::greater>()); + + std::vector> valid; + valid.reserve(3); + if (param[0].first > param[1].first) + { + if (param[1].first > param[2].first) + { + // d0 > d1 > d2 + for (i = 0; i < 3; ++i) + { + if (param[i].second >(Real)0) + { + valid.push_back(param[i]); + } + } + } + else + { + // d0 > d1 = d2 + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + param[1].second += param[0].second; + if (param[1].second > (Real)0) + { + valid.push_back(param[1]); + } + } + } + else + { + if (param[1].first > param[2].first) + { + // d0 = d1 > d2 + param[0].second += param[1].second; + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + if (param[2].second > (Real)0) + { + valid.push_back(param[2]); + } + } + else + { + // d0 = d1 = d2 + param[0].second += param[1].second + param[2].second; + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + } + } + + size_t numValid = valid.size(); + int numRoots; + Real roots[6]; + if (numValid == 3) + { + GetRoots(valid[0].first, valid[1].first, valid[2].first, + valid[0].second, valid[1].second, valid[2].second, numRoots, + roots); + } + else if (numValid == 2) + { + GetRoots(valid[0].first, valid[1].first, valid[0].second, + valid[1].second, numRoots, roots); + } + else if (numValid == 1) + { + GetRoots(valid[0].first, valid[0].second, numRoots, roots); + } + else + { + // numValid cannot be zero because we already handled case K = 0 + LogError("Unexpected condition."); + result.intersect = true; + result.relationship = ELLIPSOIDS_INTERSECTING; + return result; + } + + for (i = 0; i < numRoots; ++i) + { + Real s = roots[i]; + Real p0 = d0*K[0] * s / (d0 * s - (Real)1); + Real p1 = d1*K[1] * s / (d1 * s - (Real)1); + Real p2 = d2*K[2] * s / (d2 * s - (Real)1); + Real sqrDistance = p0 * p0 + p1 * p1 + p2 * p2; + if (sqrDistance < minSqrDistance) + { + minSqrDistance = sqrDistance; + } + if (sqrDistance > maxSqrDistance) + { + maxSqrDistance = sqrDistance; + } + } + + if (maxSqrDistance < one) + { + result.intersect = true; + result.relationship = ELLIPSOID0_CONTAINS_ELLIPSOID1; + } + else if (minSqrDistance >(Real)1) + { + if (d0 * c0 + d1 * c1 + d2 * c2 > one) + { + result.intersect = false; + result.relationship = ELLIPSOIDS_SEPARATED; + } + else + { + result.intersect = true; + result.relationship = ELLIPSOID1_CONTAINS_ELLIPSOID0; + } + } + else + { + result.intersect = true; + result.relationship = ELLIPSOIDS_INTERSECTING; + } + + return result; +} + +template +void TIQuery, Ellipsoid3>::GetRoots(Real d0, + Real c0, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 - 1 + Real const one = (Real)1; + Real temp = std::sqrt(d0*c0); + Real inv = one / d0; + numRoots = 2; + roots[0] = (one - temp) * inv; + roots[1] = (one + temp) * inv; +} + +template +void TIQuery, Ellipsoid3>::GetRoots(Real d0, + Real d1, Real c0, Real c1, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 - 1 + // with d0 > d1 + + Real const zero = (Real)0; + Real const one = (Real)1; + Real const two = (Real)2; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + + std::function F = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real f = term0 + term1 - one; + return f; + }; + + std::function DF = [&one, &two, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real df = -two * (term0 + term1); + return df; + }; + + unsigned int const maxIterations = 1024; + unsigned int iterations; + numRoots = 0; + + // TODO: What role does epsilon play? + Real const epsilon = (Real)0.001; + Real multiplier0 = std::sqrt(two / (one - epsilon)); + Real multiplier1 = std::sqrt(one / (one + epsilon)); + Real sqrtd0c0 = std::sqrt(d0c0); + Real sqrtd1c1 = std::sqrt(d1c1); + Real invD0 = one / d0; + Real invD1 = one / d1; + Real temp0, temp1, smin, smax, s; + + // Compute root in (-infinity,1/d0). + temp0 = (one - multiplier0 * sqrtd0c0) * invD0; + temp1 = (one - multiplier0 * sqrtd1c1) * invD1; + smin = std::min(temp0, temp1); + LogAssert(F(smin) < zero, "Unexpected condition."); + smax = (one - multiplier1*sqrtd0c0)*invD0; + LogAssert(F(smax) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + Real smid; + iterations = RootsBisection::Find(DF, invD0, invD1, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute root in (1/d1,+infinity). + temp0 = (one + multiplier0 * sqrtd0c0) * invD0; + temp1 = (one + multiplier0 * sqrtd1c1) * invD1; + smax = std::max(temp0, temp1); + LogAssert(F(smax) < zero, "Unexpected condition."); + smin = (one + multiplier1 * sqrtd1c1) * invD1; + LogAssert(F(smin) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; +} + +template +void TIQuery, Ellipsoid3>::GetRoots(Real d0, + Real d1, Real d2, Real c0, Real c1, Real c2, int& numRoots, Real* roots) +{ + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 + d2*c2/(d2*s-1)^2 - 1 + // with d0 > d1 > d2 + + Real const zero = (Real)0; + Real const one = (Real)1; + Real const three = (Real)3; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + Real d2c2 = d2 * c2; + + std::function F = [&one, d0, d1, d2, d0c0, d1c1, d2c2](Real s) + { + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real invN2 = one / (d2*s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real term2 = d2c2 * invN2 * invN2; + Real f = term0 + term1 + term2 - one; + return f; + }; + + std::function DF = [&one, d0, d1, d2, d0c0, d1c1, d2c2](Real s) + { + Real const two = (Real)2; + Real invN0 = one / (d0*s - one); + Real invN1 = one / (d1*s - one); + Real invN2 = one / (d2*s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real term2 = d2 * d2c2 * invN2 * invN2 * invN2; + Real df = -two * (term0 + term1 + term2); + return df; + }; + + unsigned int const maxIterations = 1024; + unsigned int iterations; + numRoots = 0; + + // TODO: What role does epsilon play? + Real epsilon = (Real)0.001; + Real multiplier0 = std::sqrt(three / (one - epsilon)); + Real multiplier1 = std::sqrt(one / (one + epsilon)); + Real sqrtd0c0 = std::sqrt(d0c0); + Real sqrtd1c1 = std::sqrt(d1c1); + Real sqrtd2c2 = std::sqrt(d2c2); + Real invD0 = one / d0; + Real invD1 = one / d1; + Real invD2 = one / d2; + Real temp0, temp1, temp2, smin, smax, s; + + // Compute root in (-infinity,1/d0). + temp0 = (one - multiplier0*sqrtd0c0)*invD0; + temp1 = (one - multiplier0*sqrtd1c1)*invD1; + temp2 = (one - multiplier0*sqrtd2c2)*invD2; + smin = std::min(std::min(temp0, temp1), temp2); + LogAssert(F(smin) < zero, "Unexpected condition."); + smax = (one - multiplier1*sqrtd0c0)*invD0; + LogAssert(F(smax) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + Real smid; + iterations = RootsBisection::Find(DF, invD0, invD1, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute roots (if any) in (1/d1,1/d2). It is the case that + // F(1/d1) = +infinity, F'(1/d1) = -infinity + // F(1/d2) = +infinity, F'(1/d2) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d1,1/d2). The bisector needs + // only the signs at the endpoints, so we pass -1 and +1 instead of the + // infinite values. If F(r) < 0, F(s) has two roots in the interval. + // If F(r) = 0, F(s) has only one root in the interval. + iterations = RootsBisection::Find(DF, invD1, invD2, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD1, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD2, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute root in (1/d2,+infinity). + temp0 = (one + multiplier0*sqrtd0c0)*invD0; + temp1 = (one + multiplier0*sqrtd1c1)*invD1; + temp2 = (one + multiplier0*sqrtd2c2)*invD2; + smax = std::max(std::max(temp0, temp1), temp2); + LogAssert(F(smax) < zero, "Unexpected condition."); + smin = (one + multiplier1*sqrtd2c2)*invD2; + LogAssert(F(smin) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace2Polygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace2Polygon2.h new file mode 100644 index 000000000000..af56d39cf7af --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace2Polygon2.h @@ -0,0 +1,167 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid and the polygon to be a +// convex solid. + +namespace gte +{ + +template +class FIQuery, std::vector>> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the halfspace and polygon intersect in a + // convex polygon. + std::vector> polygon; + }; + + Result operator()(Halfspace<2, Real> const& halfspace, + std::vector> const& polygon); +}; + + +template +typename FIQuery, std::vector>>::Result +FIQuery, std::vector>>::operator()( + Halfspace<2, Real> const& halfspace, std::vector> const& polygon) +{ + Result result; + + // Determine whether the polygon vertices are outside the halfspace, + // inside the halfspace, or on the halfspace boundary. + int const numVertices = static_cast(polygon.size()); + std::vector distance(numVertices); + int positive = 0, negative = 0, positiveIndex = -1; + for (int i = 0; i < numVertices; ++i) + { + distance[i] = Dot(halfspace.normal, polygon[i]) - halfspace.constant; + if (distance[i] > (Real)0) + { + ++positive; + if (positiveIndex == -1) + { + positiveIndex = i; + } + } + else if (distance[i] < (Real)0) + { + ++negative; + } + } + + if (positive == 0) + { + // The polygon is strictly outside the halfspace. + result.intersect = false; + return result; + } + + if (negative == 0) + { + // The polygon is contained in the closed halfspace, so it is + // fully visible and no clipping is necessary. + result.intersect = true; + return result; + } + + // The line transversely intersects the polygon. Clip the polygon. + Vector2 vertex; + int curr, prev; + Real t; + + if (positiveIndex > 0) + { + // Compute the first clip vertex on the line. + curr = positiveIndex; + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Include the vertices on the positive side of line. + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + + // Compute the kast clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + } + else + { + curr = 0; + prev = numVertices - 1; + } + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + } + else // positiveIndex is 0 + { + // Include the vertices on the positive side of line. + curr = 0; + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + + // Compute the last clip vertex on the line. + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Skip the vertices on the negative side of the line. + while (curr < numVertices && distance[curr] <= (Real)0) + { + curr++; + } + + // Compute the first clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Keep the vertices on the positive side of the line. + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + } + else + { + curr = 0; + prev = numVertices - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + } + } + + result.intersect = true; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Capsule3.h new file mode 100644 index 000000000000..309405795f2c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Capsule3.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Capsule3 const& capsule); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Halfspace3 const& halfspace, Capsule3 const& capsule) +{ + Result result; + + // Project the capsule onto the normal line. The plane of the halfspace + // occurs at the origin (zero) of the normal line. + Real e0 = + Dot(halfspace.normal, capsule.segment.p[0]) - halfspace.constant; + Real e1 = + Dot(halfspace.normal, capsule.segment.p[1]) - halfspace.constant; + + // The capsule and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (std::max(e0, e1) + capsule.radius >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Cylinder3.h new file mode 100644 index 000000000000..dfcec0edd090 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Cylinder3.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Cylinder3 const& cylinder); +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + Halfspace3 const& halfspace, Cylinder3 const& cylinder) +{ + Result result; + + // Compute extremes of signed distance Dot(N,X)-d for points on the + // cylinder. These are + // min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)| + // max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)| + Real center = Dot(halfspace.normal, cylinder.axis.origin) - + halfspace.constant; + Real absNdW = std::abs(Dot(halfspace.normal, cylinder.axis.direction)); + Real root = std::sqrt(std::max((Real)1, (Real)1 - absNdW * absNdW)); + Real tmax = center + cylinder.radius*root + + ((Real)0.5)*cylinder.height*absNdW; + + // The cylinder and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (tmax >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Ellipsoid3.h new file mode 100644 index 000000000000..50ef09f754b1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Ellipsoid3.h @@ -0,0 +1,58 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Ellipsoid3 const& ellipsoid); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Halfspace3 const& halfspace, Ellipsoid3 const& ellipsoid) +{ + // Project the ellipsoid onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Result result; + Matrix3x3 MInverse; + ellipsoid.GetMInverse(MInverse); + Real discr = Dot(halfspace.normal, MInverse * halfspace.normal); + Real extent = std::sqrt(std::max(discr, (Real)0)); + Real center = Dot(halfspace.normal, ellipsoid.center) - halfspace.constant; + Real tmax = center + extent; + + // The ellipsoid and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (tmax >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3OrientedBox3.h new file mode 100644 index 000000000000..feae37b4eb07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3OrientedBox3.h @@ -0,0 +1,59 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + OrientedBox3 const& box); +}; + + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + Halfspace3 const& halfspace, OrientedBox3 const& box) +{ + Result result; + + // Project the box center onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real center = Dot(halfspace.normal, box.center) - halfspace.constant; + + // Compute the radius of the interval of projection. + Real radius = + std::abs(box.extent[0] * Dot(halfspace.normal, box.axis[0])) + + std::abs(box.extent[1] * Dot(halfspace.normal, box.axis[1])) + + std::abs(box.extent[2] * Dot(halfspace.normal, box.axis[2])); + + // The box and halfspace intersect when the projection interval maximum + // is nonnegative. + result.intersect = (center + radius >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Segment3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Segment3.h new file mode 100644 index 000000000000..c8c725032a0b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Segment3.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Segment3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Segment3 const& segment); +}; + +template +class FIQuery, Segment3> +{ +public: + struct Result + { + bool intersect; + + // The segment is clipped against the plane defining the halfspace. + // The 'numPoints' is either 0 (no intersection), 1 (point), or 2 + // (segment). + int numPoints; + Vector3 point[2]; + }; + + Result operator()(Halfspace3 const& halfspace, + Segment3 const& segment); +}; + + +template +typename TIQuery, Segment3>::Result +TIQuery, Segment3>::operator()( + Halfspace3 const& halfspace, Segment3 const& segment) +{ + Result result; + + // Project the segment endpoints onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real s[2]; + for (int i = 0; i < 2; ++i) + { + s[i] = Dot(halfspace.normal, segment.p[i]) - halfspace.constant; + } + + // The segment and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (std::max(s[0], s[1]) >= (Real)0); + return result; +} + +template +typename FIQuery, Segment3>::Result +FIQuery, Segment3>::operator()( + Halfspace3 const& halfspace, Segment3 const& segment) +{ + // Determine on which side of the plane the endpoints lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------- + // 0 2 0 segment (original) + // 0 1 1 segment (original) + // 0 0 2 segment (original) + // 1 1 0 segment (clipped) + // 1 0 1 point (endpoint) + // 2 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 2; ++i) + { + s[i] = Dot(halfspace.normal, segment.p[i]) - halfspace.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + Result result; + + if (numNegative == 0) + { + // The segment is in the halfspace. + result.intersect = true; + result.numPoints = 2; + result.point[0] = segment.p[0]; + result.point[1] = segment.p[1]; + } + else if (numNegative == 1) + { + result.intersect = true; + result.numPoints = 1; + if (numPositive == 1) + { + // The segment is intersected at an interior point. + result.point[0] = segment.p[0] + + (s[0] / (s[0] - s[1]))*(segment.p[1] - segment.p[0]); + } + else // numZero = 1 + { + // One segment endpoint is on the plane. + if (s[0] == (Real)0) + { + result.point[0] = segment.p[0]; + } + else + { + result.point[0] = segment.p[1]; + } + } + } + else // numNegative == 2 + { + // The segment is outside the halfspace. (numNegative == 2) + result.intersect = false; + result.numPoints = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Sphere3.h new file mode 100644 index 000000000000..7194389a096f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Sphere3.h @@ -0,0 +1,53 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Sphere3 const& sphere); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Halfspace3 const& halfspace, Sphere3 const& sphere) +{ + Result result; + + // Project the sphere center onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real center = Dot(halfspace.normal, sphere.center) - halfspace.constant; + + // The sphere and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (center + sphere.radius >= (Real)0); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Triangle3.h new file mode 100644 index 000000000000..74d613ffcc6f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrHalfspace3Triangle3.h @@ -0,0 +1,242 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + + // The triangle is clipped against the plane defining the halfspace. + // The 'numPoints' is either 0 (no intersection), 1 (point), 2 + // (segment), 3 (triangle), or 4 (quadrilateral). + int numPoints; + Vector3 point[4]; + }; + + Result operator()(Halfspace3 const& halfspace, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Halfspace3 const& halfspace, Triangle3 const& triangle) +{ + Result result; + + // Project the triangle vertices onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real s[3]; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(halfspace.normal, triangle.v[i]) - halfspace.constant; + } + + // The triangle and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (std::max(std::max(s[0], s[1]), s[2]) >= (Real)0); + return result; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Halfspace3 const& halfspace, Triangle3 const& triangle) +{ + Result result; + + // Determine on which side of the plane the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // --------------------------------- + // 0 3 0 triangle (original) + // 0 2 1 triangle (original) + // 0 1 2 triangle (original) + // 0 0 3 triangle (original) + // 1 2 0 quad (2 edges clipped) + // 1 1 1 triangle (1 edge clipped) + // 1 0 2 edge + // 2 1 0 triangle (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(halfspace.normal, triangle.v[i]) - halfspace.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numNegative == 0) + { + // The triangle is in the halfspace. + result.intersect = true; + result.numPoints = 3; + result.point[0] = triangle.v[0]; + result.point[1] = triangle.v[1]; + result.point[2] = triangle.v[2]; + } + else if (numNegative == 1) + { + result.intersect = true; + if (numPositive == 2) + { + // The portion of the triangle in the halfspace is a + // quadrilateral. + result.numPoints = 4; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] < (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i1]; + result.point[1] = triangle.v[i2]; + Real t2 = s[i2] / (s[i2] - s[i0]); + Real t0 = s[i0] / (s[i0] - s[i1]); + result.point[2] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + result.point[3] = triangle.v[i0] + t0 * + (triangle.v[i1] - triangle.v[i0]); + break; + } + } + } + else if (numPositive == 1) + { + // The portion of the triangle in the halfspace is a triangle + // with one vertex on the plane. + result.numPoints = 3; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + Real t1 = s[i1] / (s[i1] - s[i2]); + Vector3 p = triangle.v[i1] + t1 * + (triangle.v[i2] - triangle.v[i1]); + if (s[i1] >(Real)0) + { + result.point[1] = triangle.v[i1]; + result.point[2] = p; + } + else + { + result.point[1] = p; + result.point[2] = triangle.v[i2]; + } + break; + } + } + } + else + { + // Only an edge of the triangle is in the halfspace. + result.numPoints = 0; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + result.point[result.numPoints++] = triangle.v[i]; + } + } + } + } + else if (numNegative == 2) + { + result.intersect = true; + if (numPositive == 1) + { + // The portion of the triangle in the halfspace is a triangle. + result.numPoints = 3; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] >(Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + Real t0 = s[i0] / (s[i0] - s[i1]); + Real t2 = s[i2] / (s[i2] - s[i0]); + result.point[1] = triangle.v[i0] + t0 * + (triangle.v[i1] - triangle.v[i0]); + result.point[2] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + break; + } + } + } + else + { + // Only a vertex of the triangle is in the halfspace. + result.numPoints = 1; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + result.point[0] = triangle.v[i]; + break; + } + } + } + } + else // numNegative == 3 + { + // The triangle is outside the halfspace. (numNegative == 3) + result.intersect = false; + result.numPoints = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrIntervals.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrIntervals.h new file mode 100644 index 000000000000..a51791acf415 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrIntervals.h @@ -0,0 +1,331 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +// The intervals are [u0,u1] and [v0,v1], where u0 <= u1 and v0 <= v1, and +// where the endpoints are any finite floating-point numbers. Degenerate +// intervals are allowed (u0 = u1 or v0 = v1). The queries do not perform +// validation on the input intervals. In the comments, maxReal refers to +// std::numeric_limits::max(). + +template +class TIQuery, std::array> +{ +public: + // The query tests overlap, whether a single point or an entire interval. + struct Result + { + bool intersect; + + // Dynamic queries (intervals moving with constant speeds). If + // 'intersect' is true, the contact times are valid and + // 0 <= firstTime <= lastTime, firstTime <= maxTime + // If 'intersect' is false, there are two cases reported. If the + // intervals will intersect at firstTime > maxTime, the contact times + // are reported just as when 'intersect' is true. However, if the + // intervals will not intersect, then firstTime = maxReal and + // lastTime = -maxReal. + Real firstTime, lastTime; + }; + + // Static query. + Result operator()(std::array const& interval0, + std::array const& interval1); + + // Dynamic query. Current time is 0, maxTime > 0 is required. + Result operator()(Real maxTime, std::array const& interval0, + Real speed0, std::array const& interval1, Real speed1); +}; + +template +class FIQuery, std::array> +{ +public: + // The query finds overlap, whether a single point or an entire interval. + struct Result + { + bool intersect; + + // Static queries (no motion of intervals over time). The number of + // number of intersections is 0 (no overlap), 1 (intervals are just + // touching), or 2 (intervals overlap in an interval). If 'intersect' + // is false, numIntersections is 0 and 'overlap' is set to + // [maxReal,-maxReal]. If 'intersect' is true, numIntersections is + // 1 or 2. When 1, 'overlap' is set to [x,x], which is degenerate and + // represents the single intersection point x. When 2, 'overlap' is + // the interval of intersection. + int numIntersections; + std::array overlap; + + // Dynamic queries (intervals moving with constant speeds). If + // 'intersect' is true, the contact times are valid and + // 0 <= firstTime <= lastTime, firstTime <= maxTime + // If 'intersect' is false, there are two cases reported. If the + // intervals will intersect at firstTime > maxTime, the contact times + // are reported just as when 'intersect' is true. However, if the + // intervals will not intersect, then firstTime = maxReal and + // lastTime = -maxReal. + Real firstTime, lastTime; + }; + + // Static query. + Result operator()(std::array const& interval0, + std::array const& interval1); + + // Dynamic query. Current time is 0, maxTime > 0 is required. + Result operator()(Real maxTime, std::array const& interval0, + Real speed0, std::array const& interval1, Real speed1); +}; + +// Template aliases for convenience. +template +using TIIntervalInterval = +TIQuery, std::array>; + +template +using FIIntervalInterval = +FIQuery, std::array>; + + +template +typename TIQuery, std::array>::Result +TIQuery, std::array>::operator()( + std::array const& interval0, + std::array const& interval1) +{ + Result result; + result.intersect = + interval0[0] <= interval1[1] && interval0[1] >= interval1[0]; + return result; +} + +template +typename TIQuery, std::array>::Result +TIQuery, std::array>::operator()( + Real maxTime, std::array const& interval0, Real speed0, + std::array const& interval1, Real speed1) +{ + Result result; + + if (interval0[1] < interval1[0]) + { + // interval0 initially to the left of interval1. + Real diffSpeed = speed0 - speed1; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval1[0] - interval0[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval1[1] - interval0[0])*invDiffSpeed; + return result; + } + } + else if (interval0[0] > interval1[1]) + { + // interval0 initially to the right of interval1. + Real diffSpeed = speed1 - speed0; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval0[0] - interval1[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval0[1] - interval1[0])*invDiffSpeed; + return result; + } + } + else + { + // The intervals are initially intersecting. + result.intersect = true; + result.firstTime = (Real)0; + if (speed1 > speed0) + { + result.lastTime = (interval0[1] - interval1[0])/(speed1 - speed0); + } + else if (speed1 < speed0) + { + result.lastTime = (interval1[1] - interval0[0])/(speed0 - speed1); + } + else + { + result.lastTime = std::numeric_limits::max(); + } + return result; + } + + result.intersect = false; + result.firstTime = std::numeric_limits::max(); + result.lastTime = -std::numeric_limits::max(); + return result; +} + + + +template +typename FIQuery, std::array>::Result +FIQuery, std::array>::operator()( + std::array const& interval0, + std::array const& interval1) +{ + Result result; + result.firstTime = std::numeric_limits::max(); + result.lastTime = -std::numeric_limits::max(); + + if (interval0[1] < interval1[0] || interval0[0] > interval1[1]) + { + result.numIntersections = 0; + result.overlap[0] = std::numeric_limits::max(); + result.overlap[1] = -std::numeric_limits::max(); + } + else if (interval0[1] > interval1[0]) + { + if (interval0[0] < interval1[1]) + { + result.numIntersections = 2; + result.overlap[0] = + (interval0[0] < interval1[0] ? interval1[0] : interval0[0]); + result.overlap[1] = + (interval0[1] > interval1[1] ? interval1[1] : interval0[1]); + if (result.overlap[0] == result.overlap[1]) + { + result.numIntersections = 1; + } + } + else // interval0[0] == interval1[1] + { + result.numIntersections = 1; + result.overlap[0] = interval0[0]; + result.overlap[1] = result.overlap[0]; + } + } + else // interval0[1] == interval1[0] + { + result.numIntersections = 1; + result.overlap[0] = interval0[1]; + result.overlap[1] = result.overlap[0]; + } + + result.intersect = (result.numIntersections > 0); + return result; +} + +template +typename FIQuery, std::array>::Result +FIQuery, std::array>::operator()( + Real maxTime, std::array const& interval0, Real speed0, + std::array const& interval1, Real speed1) +{ + Result result; + + if (interval0[1] < interval1[0]) + { + // interval0 initially to the left of interval1. + Real diffSpeed = speed0 - speed1; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval1[0] - interval0[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.numIntersections = 1; + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval1[1] - interval0[0])*invDiffSpeed; + result.overlap[0] = interval0[0] + result.firstTime*speed0; + result.overlap[1] = result.overlap[0]; + return result; + } + } + else if (interval0[0] > interval1[1]) + { + // interval0 initially to the right of interval1. + Real diffSpeed = speed1 - speed0; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' is + // true when the intervals will intersect by maxTime. + Real diffPos = interval0[0] - interval1[1]; + Real invDiffSpeed = ((Real)1) / diffSpeed; + result.intersect = (diffPos <= maxTime*diffSpeed); + result.numIntersections = 1; + result.firstTime = diffPos*invDiffSpeed; + result.lastTime = (interval0[1] - interval1[0])*invDiffSpeed; + result.overlap[0] = interval1[1] + result.firstTime*speed1; + result.overlap[1] = result.overlap[0]; + return result; + } + } + else + { + // The intervals are initially intersecting. + result.intersect = true; + result.firstTime = (Real)0; + if (speed1 > speed0) + { + result.lastTime = (interval0[1] - interval1[0]) / (speed1 - speed0); + } + else if (speed1 < speed0) + { + result.lastTime = (interval1[1] - interval0[0]) / (speed0 - speed1); + } + else + { + result.lastTime = std::numeric_limits::max(); + } + + if (interval0[1] > interval1[0]) + { + if (interval0[0] < interval1[1]) + { + result.numIntersections = 2; + result.overlap[0] = (interval0[0] < interval1[0] ? + interval1[0] : interval0[0]); + result.overlap[1] = (interval0[1] > interval1[1] ? + interval1[1] : interval0[1]); + } + else // interval0[0] == interval1[1] + { + result.numIntersections = 1; + result.overlap[0] = interval0[0]; + result.overlap[1] = result.overlap[0]; + } + } + else // interval0[1] == interval1[0] + { + result.numIntersections = 1; + result.overlap[0] = interval0[1]; + result.overlap[1] = result.overlap[0]; + } + return result; + } + + result.intersect = false; + result.numIntersections = 0; + result.overlap[0] = std::numeric_limits::max(); + result.overlap[1] = -std::numeric_limits::max(); + result.firstTime = std::numeric_limits::max(); + result.lastTime = -std::numeric_limits::max(); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2AlignedBox2.h new file mode 100644 index 000000000000..c9860eeb0812 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2AlignedBox2.h @@ -0,0 +1,196 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Vector2 const& boxExtent, + Result& result); +}; + +template +class FIQuery, AlignedBox2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Vector2 const& boxExtent, + Result& result); + +private: + // Test whether the current clipped segment intersects the current test + // plane. If the return value is 'true', the segment does intersect the + // plane and is clipped; otherwise, the segment is culled (no intersection + // with box). + static bool Clip(Real denom, Real numer, Real& t0, Real& t1); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + Line2 const& line, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector2 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + return result; +} + +template +void TIQuery, AlignedBox2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Vector2 const& boxExtent, Result& result) +{ + Real LHS = std::abs(DotPerp(lineDirection, lineOrigin)); + Real RHS = + boxExtent[0] * std::abs(lineDirection[1]) + + boxExtent[1] * std::abs(lineDirection[0]); + result.intersect = (LHS <= RHS); +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + Line2 const& line, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector2 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, AlignedBox2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Vector2 const& boxExtent, Result& result) +{ + // The line t-values are in the interval (-infinity,+infinity). Clip the + // line against all four planes of an aligned box in centered form. The + // result.numPoints is + // 0, no intersection + // 1, intersect in a single point (t0 is line parameter of point) + // 2, intersect in a segment (line parameter interval is [t0,t1]) + Real t0 = -std::numeric_limits::max(); + Real t1 = std::numeric_limits::max(); + if (Clip(+lineDirection[0], -lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(-lineDirection[0], +lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(+lineDirection[1], -lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(-lineDirection[1], +lineOrigin[1] - boxExtent[1], t0, t1)) + { + result.intersect = true; + if (t1 > t0) + { + result.numIntersections = 2; + result.parameter[0] = t0; + result.parameter[1] = t1; + } + else + { + result.numIntersections = 1; + result.parameter[0] = t0; + result.parameter[1] = t0; // Used by derived classes. + } + return; + } + + result.intersect = false; + result.numIntersections = 0; +} + +template +bool FIQuery, AlignedBox2>::Clip(Real denom, + Real numer, Real& t0, Real& t1) +{ + if (denom > (Real)0) + { + if (numer > denom*t1) + { + return false; + } + if (numer > denom*t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (Real)0) + { + if (numer > denom*t0) + { + return false; + } + if (numer > denom*t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (Real)0; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Arc2.h new file mode 100644 index 000000000000..8c8bf23c043a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Arc2.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace gte +{ + +template +class TIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Arc2 const& arc); +}; + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Arc2 const& arc); +}; + + +template +typename TIQuery, Arc2>::Result +TIQuery, Arc2>::operator()( + Line2 const& line, Arc2 const& arc) +{ + Result result; + FIQuery, Arc2> laQuery; + auto laResult = laQuery(line, arc); + result.intersect = laResult.intersect; + return result; +} + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Line2 const& line, Arc2 const& arc) +{ + Result result; + + FIQuery, Circle2> lcQuery; + Circle2 circle(arc.center, arc.radius); + auto lcResult = lcQuery(line, circle); + if (lcResult.intersect) + { + // Test whether line-circle intersections are on the arc. + result.numIntersections = 0; + for (int i = 0; i < lcResult.numIntersections; ++i) + { + if (arc.Contains(lcResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = lcResult.parameter[i]; + result.point[result.numIntersections++] + = lcResult.point[i]; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Circle2.h new file mode 100644 index 000000000000..16f6465ba75a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Circle2.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Circle2 const& circle); +}; + +template +class FIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Circle2 const& circle); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Circle2 const& circle, + Result& result); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Line2 const& line, Circle2 const& circle) +{ + Result result; + DCPQuery, Line2> plQuery; + auto plResult = plQuery(circle.center, line); + result.intersect = (plResult.distance <= circle.radius); + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Line2 const& line, Circle2 const& circle) +{ + Result result; + DoQuery(line.origin, line.direction, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Circle2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Circle2 const& circle, Result& result) +{ + // Intersection of a the line P+t*D and the circle |X-C| = R. The line + // direction is unit length. The t-value is a real-valued root to the + // quadratic equation + // 0 = |t*D+P-C|^2 - R^2 + // = t^2 + 2*Dot(D,P-C)*t + |P-C|^2-R^2 + // = t^2 + 2*a1*t + a0 + // If there are two distinct roots, the order is t0 < t1. + Vector2 diff = lineOrigin - circle.center; + Real a0 = Dot(diff, diff) - circle.radius * circle.radius; + Real a1 = Dot(lineDirection, diff); + Real discr = a1 * a1 - a0; + if (discr > (Real)0) + { + Real root = std::sqrt(discr); + result.intersect = true; + result.numIntersections = 2; + result.parameter[0] = -a1 - root; + result.parameter[1] = -a1 + root; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // discr == 0 + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Line2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Line2.h new file mode 100644 index 000000000000..5f54ebc56336 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Line2.h @@ -0,0 +1,166 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Line2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (lines intersect in a single + // point) or std::numeric_limits::max() (lines are the same). + int numIntersections; + }; + + Result operator()(Line2 const& line0, Line2 const& line1); +}; + +template +class FIQuery, Line2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (lines intersect in a single + // point) or std::numeric_limits::max() (lines are the same). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line0.origin + line0parameter[0] * line0.direction + // = line1.origin + line1parameter[0] * line1.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // line0Parameter[] = { -maxReal, +maxReal } + // line1Parameter[] = { -maxReal, +maxReal } + Real line0Parameter[2], line1Parameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line0, Line2 const& line1); +}; + + +template +typename TIQuery, Line2>::Result +TIQuery, Line2>::operator()( + Line2 const& line0, Line2 const& line1) +{ + Result result; + + // The intersection of two lines is a solution to P0 + s0*D0 = P1 + s1*D1. + // Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If DotPerp(D0, D1)) = 0, + // the lines are parallel. Additionally, if DotPerp(Q, D1)) = 0, the + // lines are the same. If Dotperp(D0, D1)) is not zero, then + // s0 = DotPerp(Q, D1))/DotPerp(D0, D1)) + // produces the point of intersection. Also, + // s1 = DotPerp(Q, D0))/DotPerp(D0, D1)) + + Vector2 diff = line1.origin - line0.origin; + Real D0DotPerpD1 = DotPerp(line0.direction, line1.direction); + if (D0DotPerpD1 != (Real)0) + { + // The lines are not parallel. + result.intersect = true; + result.numIntersections = 1; + } + else + { + // The lines are parallel. + Normalize(diff); + Real diffNDotPerpD1 = DotPerp(diff, line1.direction); + if (diffNDotPerpD1 != (Real)0) + { + // The lines are parallel but distinct. + result.intersect = false; + result.numIntersections = 0; + } + else + { + // The lines are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + } + } + + return result; +} + +template +typename FIQuery, Line2>::Result +FIQuery, Line2>::operator()( + Line2 const& line0, Line2 const& line1) +{ + Result result; + + // The intersection of two lines is a solution to P0 + s0*D0 = P1 + s1*D1. + // Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If DotPerp(D0, D1)) = 0, + // the lines are parallel. Additionally, if DotPerp(Q, D1)) = 0, the + // lines are the same. If Dotperp(D0, D1)) is not zero, then + // s0 = DotPerp(Q, D1))/DotPerp(D0, D1)) + // produces the point of intersection. Also, + // s1 = DotPerp(Q, D0))/DotPerp(D0, D1)) + + Vector2 diff = line1.origin - line0.origin; + Real D0DotPerpD1 = DotPerp(line0.direction, line1.direction); + if (D0DotPerpD1 != (Real)0) + { + // The lines are not parallel. + result.intersect = true; + result.numIntersections = 1; + Real invD0DotPerpD1 = ((Real)1) / D0DotPerpD1; + Real diffDotPerpD0 = DotPerp(diff, line0.direction); + Real diffDotPerpD1 = DotPerp(diff, line1.direction); + Real s0 = diffDotPerpD1*invD0DotPerpD1; + Real s1 = diffDotPerpD0*invD0DotPerpD1; + result.line0Parameter[0] = s0; + result.line1Parameter[0] = s1; + result.point = line0.origin + s0 * line0.direction; + } + else + { + // The lines are parallel. + Normalize(diff); + Real diffNDotPerpD1 = DotPerp(diff, line1.direction); + if (std::abs(diffNDotPerpD1) != (Real)0) + { + // The lines are parallel but distinct. + result.intersect = false; + result.numIntersections = 0; + } + else + { + // The lines are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.line0Parameter[0] = -maxReal; + result.line0Parameter[1] = +maxReal; + result.line1Parameter[0] = -maxReal; + result.line1Parameter[1] = +maxReal; + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2OrientedBox2.h new file mode 100644 index 000000000000..2a7e9bf23d98 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2OrientedBox2.h @@ -0,0 +1,106 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line2 const& line, OrientedBox2 const& box); +}; + +template +class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line2 const& line, OrientedBox2 const& box); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + Line2 const& line, OrientedBox2 const& box) +{ + // Transform the line to the oriented-box coordinate system. + Vector2 diff = line.origin - box.center; + Vector2 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + Line2 const& line, OrientedBox2 const& box) +{ + // Transform the line to the oriented-box coordinate system. + Vector2 diff = line.origin - box.center; + Vector2 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Ray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Ray2.h new file mode 100644 index 000000000000..a5227d712b95 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Ray2.h @@ -0,0 +1,135 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and ray intersect in a + // single point) or std::numeric_limits::max() (line and ray + // are collinear). + int numIntersections; + }; + + Result operator()(Line2 const& line, Ray2 const& ray); +}; + +template +class FIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and ray intersect in a + // single point) or std::numeric_limits::max() (line and ray + // are collinear). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line.origin + lineParameter[0] * line.direction + // = ray.origin + rayParameter[0] * ray.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // lineParameter[] = { -maxReal, +maxReal } + // rayParameter[] = { 0, +maxReal } + Real lineParameter[2], rayParameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line, Ray2 const& ray); +}; + + +template +typename TIQuery, Ray2>::Result +TIQuery, Ray2>::operator()( + Line2 const& line, Ray2 const& ray) +{ + Result result; + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(ray.origin, ray.direction)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = llResult.intersect; + result.numIntersections = llResult.numIntersections; + } + return result; +} + +template +typename FIQuery, Ray2>::Result +FIQuery, Ray2>::operator()( + Line2 const& line, Ray2 const& ray) +{ + Result result; + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(ray.origin, ray.direction)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + result.lineParameter[0] = llResult.line0Parameter[0]; + result.rayParameter[0] = llResult.line1Parameter[0]; + result.point = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.lineParameter[0] = -maxReal; + result.lineParameter[1] = +maxReal; + result.rayParameter[0] = (Real)0; + result.rayParameter[1] = +maxReal; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Segment2.h new file mode 100644 index 000000000000..bd21e02c8176 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Segment2.h @@ -0,0 +1,143 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and segment intersect in + // a single point) or std::numeric_limits::max() (line and + // segment are collinear). + int numIntersections; + }; + + Result operator()(Line2 const& line, Segment2 const& segment); +}; + +template +class FIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and segment intersect in + // a single point) or std::numeric_limits::max() (line and + // segment are collinear). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line.origin + lineParameter[0] * line.direction + // = segment.origin + segmentParameter[0] * segment.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // lineParameter[] = { -maxReal, +maxReal } + // segmentParameter[] = { -segmentExtent, segmentExtent } + Real lineParameter[2], segmentParameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line, Segment2 const& segment); +}; + + +template +typename TIQuery, Segment2>::Result +TIQuery, Segment2>::operator()( + Line2 const& line, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(segOrigin, segDirection)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segment. + if (std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = llResult.intersect; + result.numIntersections = llResult.numIntersections; + } + return result; +} + +template +typename FIQuery, Segment2>::Result +FIQuery, Segment2>::operator()( + Line2 const& line, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(segOrigin, segDirection)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.lineParameter[0] = llResult.line0Parameter[0]; + result.segmentParameter[0] = llResult.line1Parameter[0]; + result.point = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.lineParameter[0] = -maxReal; + result.lineParameter[1] = +maxReal; + result.segmentParameter[0] = -segExtent; + result.segmentParameter[1] = +segExtent; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Triangle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Triangle2.h new file mode 100644 index 000000000000..0602a2f0de88 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine2Triangle2.h @@ -0,0 +1,243 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace gte +{ + +template +class TIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, + Triangle2 const& triangle); +}; + +template +class FIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, + Triangle2 const& triangle); + +protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Triangle2 const& triangle, + Result& result); +}; + + +template +typename TIQuery, Triangle2>::Result +TIQuery, Triangle2>::operator()( + Line2 const& line, Triangle2 const& triangle) +{ + Result result; + + // Determine on which side of the line the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 none (degenerate triangle) + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + Vector2 diff = triangle.v[i] - line.origin; + s[i] = DotPerp(line.direction, diff); + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + result.intersect = + (numZero == 0 && (numPositive == 0 || numNegative == 0)) || + (numZero == 3); + + return result; +} + +template +typename FIQuery, Triangle2>::Result +FIQuery, Triangle2>::operator()( + Line2 const& line, Triangle2 const& triangle) +{ + Result result; + DoQuery(line.origin, line.direction, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Triangle2>::DoQuery( + Vector2 const& lineOrigin, Vector2 const& lineDirection, + Triangle2 const& triangle, Result& result) +{ + // Determine on which side of the line the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 none (degenerate triangle) + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + Vector2 diff = triangle.v[i] - lineOrigin; + s[i] = DotPerp(lineDirection, diff); + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + Real sign = (Real)3 - numPositive * (Real)2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (sign * s[i0] >(Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + Real s1 = s[i1] / (s[i1] - s[i0]); + Vector2 p1 = (triangle.v[i1] - lineOrigin) + + s1 * (triangle.v[i0] - triangle.v[i1]); + result.parameter[0] = Dot(lineDirection, p1); + Real s2 = s[i2] / (s[i2] - s[i0]); + Vector2 p2 = (triangle.v[i2] - lineOrigin) + + s2 * (triangle.v[i0] - triangle.v[i2]); + result.parameter[1] = Dot(lineDirection, p2); + break; + } + } + return; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.parameter[0] = + Dot(lineDirection, triangle.v[i0] - lineOrigin); + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + else + { + result.numIntersections = 2; + Real s1 = s[i1] / (s[i1] - s[i2]); + Vector2 p1 = (triangle.v[i1] - lineOrigin) + + s1 * (triangle.v[i2] - triangle.v[i1]); + result.parameter[1] = Dot(lineDirection, p1); + } + break; + } + } + return; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] != (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.parameter[0] = + Dot(lineDirection, triangle.v[i1] - lineOrigin); + result.parameter[1] = + Dot(lineDirection, triangle.v[i2] - lineOrigin); + break; + } + } + return; + } + + // (n,p,z) one of (3,0,0), (0,3,0), (0,0,3) + result.intersect = false; + result.numIntersections = 0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3AlignedBox3.h new file mode 100644 index 000000000000..c3e54f630796 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3AlignedBox3.h @@ -0,0 +1,190 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/11/28) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector3 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Vector3 const& boxExtent, Result& result) + { + Vector3 WxD = Cross(lineDirection, lineOrigin); + Real absWdU[3] = + { + std::abs(lineDirection[0]), + std::abs(lineDirection[1]), + std::abs(lineDirection[2]) + }; + + if (std::abs(WxD[0]) > boxExtent[1] * absWdU[2] + boxExtent[2] * absWdU[1]) + { + result.intersect = false; + return; + } + + if (std::abs(WxD[1]) > boxExtent[0] * absWdU[2] + boxExtent[2] * absWdU[0]) + { + result.intersect = false; + return; + } + + if (std::abs(WxD[2]) > boxExtent[0] * absWdU[1] + boxExtent[1] * absWdU[0]) + { + result.intersect = false; + return; + } + + result.intersect = true; + } + }; + + template + class FIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + int numPoints; + Real lineParameter[2]; + Vector3 point[2]; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector3 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = line.origin + result.lineParameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Vector3 const& boxExtent, Result& result) + { + // The line t-values are in the interval (-infinity,+infinity). + // Clip the line against all six planes of an aligned box in + // centered form. The result.numPoints is + // 0, no intersection + // 1, intersect in a single point (t0 is line parameter of point) + // 2, intersect in a segment (line parameter interval is [t0,t1]) + Real t0 = -std::numeric_limits::max(); + Real t1 = std::numeric_limits::max(); + if (Clip(+lineDirection[0], -lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(-lineDirection[0], +lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(+lineDirection[1], -lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(-lineDirection[1], +lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(+lineDirection[2], -lineOrigin[2] - boxExtent[2], t0, t1) && + Clip(-lineDirection[2], +lineOrigin[2] - boxExtent[2], t0, t1)) + { + result.intersect = true; + if (t1 > t0) + { + result.numPoints = 2; + result.lineParameter[0] = t0; + result.lineParameter[1] = t1; + } + else + { + result.numPoints = 1; + result.lineParameter[0] = t0; + result.lineParameter[1] = t0; // Used by derived classes. + } + return; + } + + result.intersect = false; + result.numPoints = 0; + } + + private: + // Test whether the current clipped segment intersects the current + // test plane. If the return value is 'true', the segment does + // intersect the plane and is clipped; otherwise, the segment is + // culled (no intersection with box). + static bool Clip(Real denom, Real numer, Real& t0, Real& t1) + { + if (denom > (Real)0) + { + if (numer > denom * t1) + { + return false; + } + if (numer > denom * t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (Real)0) + { + if (numer > denom * t0) + { + return false; + } + if (numer > denom * t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (Real)0; + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Capsule3.h new file mode 100644 index 000000000000..80b654ff4a38 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Capsule3.h @@ -0,0 +1,341 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Capsule3 const& capsule); +}; + +template +class FIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Capsule3 const& capsule); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Capsule3 const& capsule, + Result& result); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Line3 const& line, Capsule3 const& capsule) +{ + Result result; + DCPQuery, Segment3> lsQuery; + auto lsResult = lsQuery(line, capsule.segment); + result.intersect = (lsResult.distance <= capsule.radius); + return result; +} + +template +typename FIQuery, Capsule3>::Result +FIQuery, Capsule3>::operator()( + Line3 const& line, Capsule3 const& capsule) +{ + Result result; + DoQuery(line.origin, line.direction, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Capsule3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Capsule3 const& capsule, Result& result) +{ + // Initialize the result as if there is no intersection. If we discover + // an intersection, these values will be modified accordingly. + result.intersect = false; + result.numIntersections = 0; + + // Create a coordinate system for the capsule. In this system, the + // capsule segment center C is the origin and the capsule axis direction + // W is the z-axis. U and V are the other coordinate axis directions. + // If P = x*U+y*V+z*W, the cylinder containing the capsule wall is + // x^2 + y^2 = r^2, where r is the capsule radius. The finite cylinder + // that makes up the capsule minus its hemispherical end caps has z-values + // |z| <= e, where e is the extent of the capsule segment. The top + // hemisphere cap is x^2+y^2+(z-e)^2 = r^2 for z >= e, and the bottom + // hemisphere cap is x^2+y^2+(z+e)^2 = r^2 for z <= -e. + + Vector3 segOrigin, segDirection; + Real segExtent; + capsule.segment.GetCenteredForm(segOrigin, segDirection, segExtent); + Vector3 basis[3]; // {W, U, V} + basis[0] = segDirection; + ComputeOrthogonalComplement(1, basis); + Real rSqr = capsule.radius * capsule.radius; + + // Convert incoming line origin to capsule coordinates. + Vector3 diff = lineOrigin - segOrigin; + Vector3 P{ Dot(basis[1], diff), Dot(basis[2], diff), + Dot(basis[0], diff) }; + + // Get the z-value, in capsule coordinates, of the incoming line's + // unit-length direction. + Real dz = Dot(basis[0], lineDirection); + if (std::abs(dz) == (Real)1) + { + // The line is parallel to the capsule axis. Determine whether the + // line intersects the capsule hemispheres. + Real radialSqrDist = rSqr - P[0] * P[0] - P[1] * P[1]; + if (radialSqrDist >= (Real)0) + { + // The line intersects the hemispherical caps. + result.intersect = true; + result.numIntersections = 2; + Real zOffset = std::sqrt(radialSqrDist) + segExtent; + if (dz > (Real)0) + { + result.parameter[0] = -P[2] - zOffset; + result.parameter[1] = -P[2] + zOffset; + } + else + { + result.parameter[0] = P[2] - zOffset; + result.parameter[1] = P[2] + zOffset; + } + } + // else: The line outside the capsule's cylinder, no intersection. + return; + } + + // Convert the incoming line unit-length direction to capsule coordinates. + Vector3 D{ Dot(basis[1], lineDirection), + Dot(basis[2], lineDirection), dz }; + + // Test intersection of line P+t*D with infinite cylinder x^2+y^2 = r^2. + // This reduces to computing the roots of a quadratic equation. If + // P = (px,py,pz) and D = (dx,dy,dz), then the quadratic equation is + // (dx^2+dy^2)*t^2 + 2*(px*dx+py*dy)*t + (px^2+py^2-r^2) = 0 + Real a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + Real a1 = P[0] * D[0] + P[1] * D[1]; + Real a2 = D[0] * D[0] + D[1] * D[1]; + Real discr = a1 * a1 - a0 * a2; + if (discr < (Real)0) + { + // The line does not intersect the infinite cylinder, so it + // cannot intersect the capsule. + return; + } + + Real root, inv, tValue, zValue; + if (discr >(Real)0) + { + // The line intersects the infinite cylinder in two places. + root = std::sqrt(discr); + inv = ((Real)1) / a2; + tValue = (-a1 - root) * inv; + zValue = P[2] + tValue * D[2]; + if (std::abs(zValue) <= segExtent) + { + result.intersect = true; + result.parameter[result.numIntersections++] = tValue; + } + + tValue = (-a1 + root) * inv; + zValue = P[2] + tValue * D[2]; + if (std::abs(zValue) <= segExtent) + { + result.intersect = true; + result.parameter[result.numIntersections++] = tValue; + } + + if (result.numIntersections == 2) + { + // The line intersects the capsule wall in two places. + return; + } + } + else + { + // The line is tangent to the infinite cylinder but intersects the + // cylinder in a single point. + tValue = -a1 / a2; + zValue = P[2] + tValue * D[2]; + if (std::abs(zValue) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = tValue; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + return; + } + } + + // Test intersection with bottom hemisphere. The quadratic equation is + // t^2 + 2*(px*dx+py*dy+(pz+e)*dz)*t + (px^2+py^2+(pz+e)^2-r^2) = 0 + // Use the fact that currently a1 = px*dx+py*dy and a0 = px^2+py^2-r^2. + // The leading coefficient is a2 = 1, so no need to include in the + // construction. + Real PZpE = P[2] + segExtent; + a1 += PZpE * D[2]; + a0 += PZpE * PZpE; + discr = a1 * a1 - a0; + if (discr > (Real)0) + { + root = std::sqrt(discr); + tValue = -a1 - root; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + + tValue = -a1 + root; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + else if (discr == (Real)0) + { + tValue = -a1; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + + // Test intersection with top hemisphere. The quadratic equation is + // t^2 + 2*(px*dx+py*dy+(pz-e)*dz)*t + (px^2+py^2+(pz-e)^2-r^2) = 0 + // Use the fact that currently a1 = px*dx+py*dy+(pz+e)*dz and + // a0 = px^2+py^2+(pz+e)^2-r^2. The leading coefficient is a2 = 1, so + // no need to include in the construction. + a1 -= ((Real)2) * segExtent * D[2]; + a0 -= ((Real)4) * segExtent * P[2]; + discr = a1 * a1 - a0; + if (discr > (Real)0) + { + root = std::sqrt(discr); + tValue = -a1 - root; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + + tValue = -a1 + root; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + else if (discr == (Real)0) + { + tValue = -a1; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + + if (result.numIntersections == 1) + { + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cone3.h new file mode 100644 index 000000000000..10c489d7a2f4 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cone3.h @@ -0,0 +1,320 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/11/29) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the cone to be single sided and solid. + +namespace gte +{ + +template +class FIQuery, Cone3> +{ +public: + struct Result + { + // Default construction. Set 'intersect' to false, 'type' to 0, + // 'parameter' to (0,0), and 'point' to (veczero,veczero). + Result(); + + bool intersect; + + // Because the intersection of line and cone with infinite height + // h > 0 can be a ray or a line, we use a 'type' value that allows + // you to decide how to interpret the parameter[] and point[] values. + // type intersect valid data + // 0 none none + // 1 point parameter[0] = parameter[1], finite + // point[0] = point[1] + // 2 segment parameter[0] < parameter[1], finite + // point[0,1] valid + // 3 ray parameter[0] finite, parameter[1] maxReal + // point[0] = rayOrigin, point[1] = lineDirection + // 4 ray parameter[0] -maxReal, parameter[1] finite + // point[0] = rayOrigin, point[1] = -lineDirection + // 5 line parameter[0] -maxReal, parameter[1] maxReal, + // point[0] = lineOrigin, point[1] = lineDirection + // If the cone height h is finite, only types 0, 1, or 2 can occur. + int type; + std::array parameter; // Relative to incoming line. + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Cone3 const& cone); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Cone3 const& cone, + Result& result); +}; + + +template +FIQuery, Cone3>::Result::Result() + : + intersect(false), + type(0) +{ + parameter.fill((Real)0); + point.fill({ (Real)0, (Real)0, (Real)0 }); +} + +template +typename FIQuery, Cone3>::Result +FIQuery, Cone3>::operator()(Line3 const& line, + Cone3 const& cone) +{ + Result result; + DoQuery(line.origin, line.direction, cone, result); + switch (result.type) + { + case 1: // point + result.point[0] = line.origin + result.parameter[0] * line.direction; + result.point[1] = result.point[0]; + break; + case 2: // segment + result.point[0] = line.origin + result.parameter[0] * line.direction; + result.point[1] = line.origin + result.parameter[1] * line.direction; + break; + case 3: // ray + result.point[0] = line.origin + result.parameter[0] * line.direction; + result.point[1] = line.direction; + break; + case 4: // ray + result.point[0] = line.origin + result.parameter[1] * line.direction; + result.point[1] = -line.direction; + break; + case 5: // line + result.point[0] = line.origin; + result.point[1] = line.direction; + break; + default: // no intersection + break; + } + return result; +} + +template +void FIQuery, Cone3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Cone3 const& cone, Result& result) +{ + // The cone has vertex V, unit-length axis direction D, angle theta in + // (0,pi/2), and height h in (0,+infinity). The line is P + t*U, where U + // is a unit-length direction vector. Define g = cos(theta). The cone + // is represented by + // (X-V)^T * (D*D^T - g^2*I) * (X-V) = 0, 0 <= Dot(D,X-V) <= h + // The first equation defines a double-sided cone. The first inequality + // in the second equation limits this to a single-sided cone containing + // the ray V + s*D with s >= 0. We will call this the 'positive cone'. + // The single-sided cone containing ray V + s * t with s <= 0 is called + // the 'negative cone'. The double-sided cone is the union of the + // positive cone and negative cone. The second inequality in the second + // equation limits the single-sided cone to the region bounded by the + // height. Setting X(t) = P + t*U, the equations are + // c2*t^2 + 2*c1*t + c0 = 0, 0 <= Dot(D,U)*t + Dot(D,P-V) <= h + // where + // c2 = Dot(D,U)^2 - g^2 + // c1 = Dot(D,U)*Dot(D,P-V) - g^2*Dot(U,P-V) + // c0 = Dot(D,P-V)^2 - g^2*Dot(P-V,P-V) + // The following code computes the t-interval that satisfies the quadratic + // equation subject to the linear inequality constraints. + + Vector3 PmV = lineOrigin - cone.ray.origin; + Real DdU = Dot(cone.ray.direction, lineDirection); + Real DdPmV = Dot(cone.ray.direction, PmV); + Real UdPmV = Dot(lineDirection, PmV); + Real PmVdPmV = Dot(PmV, PmV); + Real cosAngleSqr = cone.cosAngle * cone.cosAngle; + Real c2 = DdU * DdU - cosAngleSqr; + Real c1 = DdU * DdPmV - cosAngleSqr * UdPmV; + Real c0 = DdPmV * DdPmV - cosAngleSqr * PmVdPmV; + Real t; + + if (c2 != (Real)0) + { + Real discr = c1 * c1 - c0 * c2; + if (discr < (Real)0) + { + // The quadratic has no real-valued roots. The line does not + // intersect the double-sided cone. + result.intersect = false; + result.type = 0; + return; + } + else if (discr > (Real)0) + { + // The quadratic has two distinct real-valued roots. However, one + // or both of them might intersect the negative cone. We are + // interested only in those intersections with the positive cone. + Real root = std::sqrt(discr); + Real invC2 = ((Real)1) / c2; + int numParameters = 0; + + t = (-c1 - root) * invC2; + if (DdU * t + DdPmV >= (Real)0) + { + result.parameter[numParameters++] = t; + } + + t = (-c1 + root) * invC2; + if (DdU * t + DdPmV >= (Real)0) + { + result.parameter[numParameters++] = t; + } + + if (numParameters == 2) + { + // The line intersects the positive cone in two distinct + // points. + result.intersect = true; + result.type = 2; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + } + else if (numParameters == 1) + { + // The line intersects the positive cone in a single point and + // the negative cone in a single point. We report only the + // intersection with the positive cone. + result.intersect = true; + if (DdU > (Real)0) + { + result.type = 3; + result.parameter[1] = std::numeric_limits::max(); + } + else + { + result.type = 4; + result.parameter[1] = result.parameter[0]; + result.parameter[0] = -std::numeric_limits::max(); + + } + } + else + { + // The line intersects the negative cone in two distinct + // points, but we are interested only in the intersections + // with the positive cone. + result.intersect = false; + result.type = 0; + return; + } + } + else // discr == 0 + { + // One repeated real root; the line is tangent to the double-sided + // cone at a single point. Report only the point if it is on the + // positive cone. + t = -c1 / c2; + if (DdU * t + DdPmV >= (Real)0) + { + result.intersect = true; + result.type = 1; + result.parameter[0] = t; + result.parameter[1] = t; + } + else + { + result.intersect = false; + result.type = 0; + return; + } + } + } + else if (c1 != (Real)0) + { + // c2 = 0, c1 != 0; U is a direction vector on the cone boundary + t = -((Real)0.5)*c0 / c1; + if (DdU * t + DdPmV >= (Real)0) + { + // The line intersects the positive cone and the ray of + // intersection is interior to the positive cone. + result.intersect = true; + if (DdU > (Real)0) + { + result.type = 3; + result.parameter[0] = t; + result.parameter[1] = std::numeric_limits::max(); + } + else + { + result.type = 4; + result.parameter[0] = -std::numeric_limits::max(); + result.parameter[1] = t; + } + } + else + { + // The line intersects the negative cone and the ray of + // intersection is interior to the positive cone. + result.intersect = false; + result.type = 0; + return; + } + } + else if (c0 != (Real)0) + { + // c2 = c1 = 0, c0 != 0. Cross(D,U) is perpendicular to Cross(P-V,U) + result.intersect = false; + result.type = 0; + return; + } + else + { + // c2 = c1 = c0 = 0; the line is on the cone boundary. + result.intersect = true; + result.type = 5; + result.parameter[0] = -std::numeric_limits::max(); + result.parameter[1] = +std::numeric_limits::max(); + } + + if (cone.maxHeight < std::numeric_limits::max()) + { + if (DdU != (Real)0) + { + // Clamp the intersection to the height of the cone. + Real invDdU = ((Real)1) / DdU; + std::array hInterval; + if (DdU >(Real)0) + { + hInterval[0] = -DdPmV * invDdU; + hInterval[1] = (cone.maxHeight - DdPmV) * invDdU; + } + else // (DdU < (Real)0) + { + hInterval[0] = (cone.maxHeight - DdPmV) * invDdU; + hInterval[1] = -DdPmV * invDdU; + } + + FIIntervalInterval iiQuery; + auto iiResult = iiQuery(result.parameter, hInterval); + result.intersect = (iiResult.numIntersections > 0); + result.type = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else if (result.intersect) + { + if (DdPmV > cone.maxHeight) + { + result.intersect = false; + result.type = 0; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cylinder3.h new file mode 100644 index 000000000000..f058b1c47dfa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Cylinder3.h @@ -0,0 +1,264 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class FIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, + Cylinder3 const& cylinder); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Cylinder3 const& cylinder, + Result& result); +}; + + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Line3 const& line, Cylinder3 const& cylinder) +{ + Result result; + DoQuery(line.origin, line.direction, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Cylinder3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Cylinder3 const& cylinder, Result& result) +{ + // Initialize the result as if there is no intersection. If we discover + // an intersection, these values will be modified accordingly. + result.intersect = false; + result.numIntersections = 0; + + // Create a coordinate system for the cylinder. In this system, the + // cylinder segment center C is the origin and the cylinder axis direction + // W is the z-axis. U and V are the other coordinate axis directions. + // If P = x*U+y*V+z*W, the cylinder is x^2 + y^2 = r^2, where r is the + // cylinder radius. The end caps are |z| = h/2, where h is the cylinder + // height. + Vector3 basis[3]; // {W, U, V} + basis[0] = cylinder.axis.direction; + ComputeOrthogonalComplement(1, basis); + Real halfHeight = ((Real)0.5) * cylinder.height; + Real rSqr = cylinder.radius * cylinder.radius; + + // Convert incoming line origin to capsule coordinates. + Vector3 diff = lineOrigin - cylinder.axis.origin; + Vector3 P{ Dot(basis[1], diff), Dot(basis[2], diff), + Dot(basis[0], diff) }; + + // Get the z-value, in cylinder coordinates, of the incoming line's + // unit-length direction. + Real dz = Dot(basis[0], lineDirection); + if (std::abs(dz) == (Real)1) + { + // The line is parallel to the cylinder axis. Determine whether the + // line intersects the cylinder end disks. + Real radialSqrDist = rSqr - P[0] * P[0] - P[1] * P[1]; + if (radialSqrDist >= (Real)0) + { + // The line intersects the cylinder end disks. + result.intersect = true; + result.numIntersections = 2; + if (dz > (Real)0) + { + result.parameter[0] = -P[2] - halfHeight; + result.parameter[1] = -P[2] + halfHeight; + } + else + { + result.parameter[0] = P[2] - halfHeight; + result.parameter[1] = P[2] + halfHeight; + } + } + // else: The line is outside the cylinder, no intersection. + return; + } + + // Convert the incoming line unit-length direction to cylinder + // coordinates. + Vector3 D{ Dot(basis[1], lineDirection), + Dot(basis[2], lineDirection), dz }; + + Real a0, a1, a2, discr, root, inv, tValue; + + if (D[2] == (Real)0) + { + // The line is perpendicular to the cylinder axis. + if (std::abs(P[2]) <= halfHeight) + { + // Test intersection of line P+t*D with infinite cylinder + // x^2+y^2 = r^2. This reduces to computing the roots of a + // quadratic equation. If P = (px,py,pz) and D = (dx,dy,dz), + // then the quadratic equation is + // (dx^2+dy^2)*t^2 + 2*(px*dx+py*dy)*t + (px^2+py^2-r^2) = 0 + a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + a1 = P[0] * D[0] + P[1] * D[1]; + a2 = D[0] * D[0] + D[1] * D[1]; + discr = a1 * a1 - a0 * a2; + if (discr > (Real)0) + { + // The line intersects the cylinder in two places. + result.intersect = true; + result.numIntersections = 2; + root = std::sqrt(discr); + inv = ((Real)1) / a2; + result.parameter[0] = (-a1 - root) * inv; + result.parameter[1] = (-a1 + root) * inv; + } + else if (discr == (Real)0) + { + // The line is tangent to the cylinder. + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1 / a2; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + // else: The line does not intersect the cylinder. + } + // else: The line is outside the planes of the cylinder end disks. + return; + } + + // Test for intersections with the planes of the end disks. + inv = ((Real)1) / D[2]; + + Real t0 = (-halfHeight - P[2]) * inv; + Real xTmp = P[0] + t0 * D[0]; + Real yTmp = P[1] + t0 * D[1]; + if (xTmp * xTmp + yTmp * yTmp <= rSqr) + { + // Plane intersection inside the top cylinder end disk. + result.parameter[result.numIntersections++] = t0; + } + + Real t1 = (+halfHeight - P[2]) * inv; + xTmp = P[0] + t1 * D[0]; + yTmp = P[1] + t1 * D[1]; + if (xTmp * xTmp + yTmp * yTmp <= rSqr) + { + // Plane intersection inside the bottom cylinder end disk. + result.parameter[result.numIntersections++] = t1; + } + + if (result.numIntersections < 2) + { + // Test for intersection with the cylinder wall. + a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + a1 = P[0] * D[0] + P[1] * D[1]; + a2 = D[0] * D[0] + D[1] * D[1]; + discr = a1 * a1 - a0 * a2; + if (discr >(Real)0) + { + root = std::sqrt(discr); + inv = ((Real)1) / a2; + tValue = (-a1 - root) * inv; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + + if (result.numIntersections < 2) + { + tValue = (-a1 + root) * inv; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + } + // else: Line intersects end disk and cylinder wall. + } + else if (discr == (Real)0) + { + tValue = -a1 / a2; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + } + // else: Line does not intersect cylinder wall. + } + // else: Line intersects both top and bottom cylinder end disks. + + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + } + else if (result.numIntersections == 1) + { + result.intersect = true; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Ellipsoid3.h new file mode 100644 index 000000000000..a21ead986a81 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Ellipsoid3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, + Ellipsoid3 const& ellipsoid); +}; + +template +class FIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, + Ellipsoid3 const& ellipsoid); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Ellipsoid3 const& ellipsoid, + Result& result); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Line3 const& line, Ellipsoid3 const& ellipsoid) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = line.origin - ellipsoid.center; + Vector3 matDir = M*line.direction; + Vector3 matDiff = M*diff; + Real a2 = Dot(line.direction, matDir); + Real a1 = Dot(line.direction, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0*a2; + result.intersect = (discr >= (Real)0); + return result; +} + +template +typename FIQuery, Ellipsoid3>::Result +FIQuery, Ellipsoid3>::operator()( + Line3 const& line, Ellipsoid3 const& ellipsoid) +{ + Result result; + DoQuery(line.origin, line.direction, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Ellipsoid3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Ellipsoid3 const& ellipsoid, Result& result) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = lineOrigin - ellipsoid.center; + Vector3 matDir = M*lineDirection; + Vector3 matDiff = M*diff; + Real a2 = Dot(lineDirection, matDir); + Real a1 = Dot(lineDirection, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0*a2; + if (discr > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + Real root = std::sqrt(discr); + Real inv = ((Real)1) / a2; + result.parameter[0] = (-a1 - root)*inv; + result.parameter[1] = (-a1 + root)*inv; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1 / a2; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3OrientedBox3.h new file mode 100644 index 000000000000..c49d340d296d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3OrientedBox3.h @@ -0,0 +1,97 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2016/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector3 diff = line.origin - box.center; + Vector3 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]), + Dot(line.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector3 diff = line.origin - box.center; + Vector3 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]), + Dot(line.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = + line.origin + result.lineParameter[i] * line.direction; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Plane3.h new file mode 100644 index 000000000000..42cd93eac7bf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Plane3.h @@ -0,0 +1,133 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Plane3 const& plane); +}; + +template +class FIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + + // The number of intersections is 0 (no intersection), 1 (linear + // component and plane intersect in a point), or + // std::numeric_limits::max() (linear component is on the plane). + // If the linear component is on the plane, 'point' component's + // origin and 'parameter' is zero. + int numIntersections; + Real parameter; + Vector3 point; + }; + + Result operator()(Line3 const& line, Plane3 const& plane); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Plane3 const& plane, + Result& result); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Line3 const& line, Plane3 const& plane) +{ + Result result; + + Real DdN = Dot(line.direction, plane.normal); + if (DdN != (Real)0) + { + // The line is not parallel to the plane, so they must intersect. + result.intersect = true; + } + else + { + // The line and plane are parallel. + DCPQuery, Plane3> vpQuery; + result.intersect = (vpQuery(line.origin, plane).distance == (Real)0); + } + + return result; +} + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Line3 const& line, Plane3 const& plane) +{ + Result result; + DoQuery(line.origin, line.direction, plane, result); + if (result.intersect) + { + result.point = line.origin + result.parameter * line.direction; + } + return result; +} + +template +void FIQuery, Plane3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Plane3 const& plane, Result& result) +{ + Real DdN = Dot(lineDirection, plane.normal); + DCPQuery, Plane3> vpQuery; + auto vpResult = vpQuery(lineOrigin, plane); + + if (DdN != (Real)0) + { + // The line is not parallel to the plane, so they must intersect. + result.intersect = true; + result.numIntersections = 1; + result.parameter = -vpResult.signedDistance / DdN; + } + else + { + // The line and plane are parallel. Determine whether the line is on + // the plane. + if (vpResult.distance == (Real)0) + { + // The line is coincident with the plane, so choose t = 0 for the + // parameter. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.parameter = (Real)0; + } + else + { + // The line is not on the plane. + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Sphere3.h new file mode 100644 index 000000000000..ed3e62085ea1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Sphere3.h @@ -0,0 +1,124 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Sphere3 const& sphere); + +protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Sphere3 const& sphere, + Result& result); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Line3 const& line, Sphere3 const& sphere) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 diff = line.origin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(line.direction, diff); + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0; + result.intersect = (discr >= (Real)0); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Line3 const& line, Sphere3 const& sphere) +{ + Result result; + DoQuery(line.origin, line.direction, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; +} + +template +void FIQuery, Sphere3>::DoQuery( + Vector3 const& lineOrigin, Vector3 const& lineDirection, + Sphere3 const& sphere, Result& result) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Vector3 diff = lineOrigin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(lineDirection, diff); + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0; + if (discr > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + Real root = std::sqrt(discr); + result.parameter[0] = -a1 - root; + result.parameter[1] = -a1 + root; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Triangle3.h new file mode 100644 index 000000000000..3d1fdcc7d43c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrLine3Triangle3.h @@ -0,0 +1,190 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery,Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + Result(); + + bool intersect; + Real parameter; + Real triangleBary[3]; + Vector3 point; + }; + + Result operator()(Line3 const& line, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Line3 const& line, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = line direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(line.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Line and triangle are parallel, call it a "no intersection" + // even if the line and triangle are coplanar and intersecting. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(line.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(line.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle. + result.intersect = true; + return result; + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + +template +FIQuery, Triangle3>::Result::Result() + : + parameter((Real)0) +{ + triangleBary[0] = (Real)0; + triangleBary[1] = (Real)0; + triangleBary[2] = (Real)0; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Line3 const& line, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = line direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(line.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Line and triangle are parallel, call it a "no intersection" + // even if the line and triangle are coplanar and intersecting. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(line.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(line.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle. + Real QdN = -sign*Dot(diff, normal); + Real inv = ((Real)1) / DdN; + + result.intersect = true; + result.parameter = QdN*inv; + result.triangleBary[1] = DdQxE2*inv; + result.triangleBary[2] = DdE1xQ*inv; + result.triangleBary[0] = (Real)1 - result.triangleBary[1] + - result.triangleBary[2]; + result.point = line.origin + + result.parameter * line.direction; + return result; + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Circle2.h new file mode 100644 index 000000000000..aaf62aaebc07 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Circle2.h @@ -0,0 +1,96 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/08/01) + +#pragma once + +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingCircleRectangle.pdf + +namespace gte +{ + template + class TIQuery, Circle2> + { + public: + // The intersection query considers the box and circle to be solids; + // that is, the circle object includes the region inside the circular + // boundary and the box object includes the region inside the + // rectangular boundary. If the circle object and rectangle object + // overlap, the objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox2 const& box, Circle2 const& circle) + { + DCPQuery, OrientedBox2> pbQuery; + auto pbResult = pbQuery(circle.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= circle.radius * circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + : + public FIQuery, Circle2> + { + public: + // See the base class for the definition of 'struct Result'. + typename FIQuery, Circle2>::Result + operator()(OrientedBox2 const& box, Vector2 const& boxVelocity, + Circle2 const& circle, Vector2 const& circleVelocity) + { + // Transform the oriented box to an axis-aligned box centered at + // the origin and transform the circle accordingly. Compute the + // velocity of the circle relative to the box. + Real const zero(0), one(1), minusOne(-1); + Vector2 cdiff = circle.center - box.center; + Vector2 vdiff = circleVelocity - boxVelocity; + Vector2 C, V; + for (int i = 0; i < 2; ++i) + { + C[i] = Dot(cdiff, box.axis[i]); + V[i] = Dot(vdiff, box.axis[i]); + } + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[2]; + for (int i = 0; i < 2; ++i) + { + if (C[i] >= zero) + { + sign[i] = one; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = minusOne; + } + } + + typename FIQuery, Circle2>::Result result = { 0, zero, { zero, zero } }; + this->DoQuery(box.extent, C, circle.radius, V, result); + + if (result.intersectionType != 0) + { + // Transform back to the original coordinate system. + result.contactPoint = box.center + + (sign[0] * result.contactPoint[0]) * box.axis[0] + + (sign[1] * result.contactPoint[1]) * box.axis[1]; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Cone2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Cone2.h new file mode 100644 index 000000000000..425abbfb73a2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Cone2.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box and cone to be solids. +// +// Define V = cone.ray.origin, D = cone.ray.direction, and cs = cone.cosAngle. +// Define C = box.center, U0 = box.axis[0], U1 = box.axis[1], +// e0 = box.extent[0], and e1 = box.extent[1]. A box point is +// P = C + x*U0 + y*U1 where |x| <= e0 and |y| <= e1. Define the function +// F(P) = Dot(D, (P-V)/Length(P-V)) = F(x,y) +// = Dot(D, (x*U0 + y*U1 + (C-V))/|x*U0 + y*U1 + (C-V)| +// = (a0*x + a1*y + a2)/(x^2 + y^2 + 2*b0*x + 2*b1*y + b2)^{1/2} +// The function has an essential singularity when P = V. The box intersects +// the cone (with positive-area overlap) when at least one of the four box +// corners is strictly inside the cone. It is necessary that the numerator +// of F(P) be positive at such a corner. The (interior of the) solid cone +// is defined by the quadratic inequality +// (Dot(D,P-V))^2 > |P-V|^2*(cone.cosAngle)^2 +// This inequality is inexpensive to compute. In summary, overlap occurs +// when there is a box corner P for which +// F(P) > 0 and (Dot(D,P-V))^2 > |P-V|^2*(cone.cosAngle)^2 + +namespace gte +{ + +template +class TIQuery, Cone<2, Real>> +{ +public: + struct Result + { + // The value of 'intersect' is true when there is a box point that + // is strictly inside the cone. If the box just touches the cone + // from the outside, an intersection is not reported, which supports + // the common operation of culling objects outside a cone. + bool intersect; + }; + + Result operator()(OrientedBox<2, Real> const& box, Cone<2, Real>& cone); +}; + + +template +typename TIQuery, Cone<2, Real>>::Result +TIQuery, Cone<2, Real>>::operator()( + OrientedBox<2, Real> const& box, Cone<2, Real>& cone) +{ + Result result; + + TIQuery, OrientedBox<2, Real>> rbQuery; + auto rbResult = rbQuery(cone.ray, box); + if (rbResult.intersect) + { + // The cone intersects the box. + result.intersect = true; + return result; + } + + // Define V = cone.ray.origin, D = cone.ray.direction, and + // cs = cone.cosAngle. Define C = box.center, U0 = box.axis[0], + // U1 = box.axis[1], e0 = box.extent[0], and e1 = box.extent[1]. + // A box point is P = C + x*U0 + y*U1 where |x| <= e0 and |y| <= e1. + // Define the function + // F(x,y) = Dot(D, (P-V)/Length(P-V)) + // = Dot(D, (x*U0 + y*U1 + (C-V))/|x*U0 + y*U1 + (C-V)| + // = (a0*x + a1*y + a2)/(x^2 + y^2 + 2*b0*x + 2*b1*y + b2)^{1/2} + // The function has an essential singularity when P = V. + Vector<2, Real> diff = box.center - cone.ray.origin; + Real a0 = Dot(cone.ray.direction, box.axis[0]); + Real a1 = Dot(cone.ray.direction, box.axis[1]); + Real a2 = Dot(cone.ray.direction, diff); + Real b0 = Dot(box.axis[0], diff); + Real b1 = Dot(box.axis[1], diff); + Real b2 = Dot(diff, diff); + Real csSqr = cone.cosAngle * cone.cosAngle; + + for (int i1 = 0; i1 < 2; ++i1) + { + Real sign1 = i1 * (Real)2 - (Real)1; + Real y = sign1 * box.extent[1]; + for (int i0 = 0; i0 < 2; ++i0) + { + Real sign0 = i0 * (Real)2 - (Real)1; + Real x = sign0 * box.extent[0]; + Real fNumerator = a0 * x + a1 * y + a2; + if (fNumerator >(Real)0) + { + Real dSqr = x*x + y*y + (b0*x + b1*y)*(Real)2 + b2; + Real nSqr = fNumerator*fNumerator; + if (nSqr > dSqr * csSqr) + { + result.intersect = true; + return result; + } + } + } + } + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2OrientedBox2.h new file mode 100644 index 000000000000..217881ef9600 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2OrientedBox2.h @@ -0,0 +1,294 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 2 edge normals of box0 and the +// 2 edge normals of box1. The integer 'separating' identifies the axis that +// reported separation; there may be more than one but only one is reported. +// The value is 0 when box0.axis[0] separates, 1 when box0.axis[1] separates, +// 2 when box1.axis[0] separates, or 3 when box1.axis[1] separates. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> +{ +public: + struct Result + { + bool intersect; + int separating; + }; + + Result operator()(OrientedBox2 const& box0, + OrientedBox2 const& box1); +}; + +template +class FIQuery, OrientedBox2> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the boxes intersect in a convex 'polygon'. + std::vector> polygon; + }; + + Result operator()(OrientedBox2 const& box0, + OrientedBox2 const& box1); + +private: + // The line normals are inner pointing. The function returns true + // when the incoming polygon is outside the line, in which case the + // boxes do not intersect. If the function returns false, the outgoing + // polygon is the incoming polygon intersected with the closed halfspace + // defined by the line. + bool Outside(Vector2 const& origin, Vector2 const& normal, + std::vector>& polygon); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + OrientedBox2 const& box0, OrientedBox2 const& box1) +{ + Result result; + + // Convenience variables. + Vector2 const* A0 = &box0.axis[0]; + Vector2 const* A1 = &box1.axis[0]; + Vector2 const& E0 = box0.extent; + Vector2 const& E1 = box1.extent; + + // Compute difference of box centers, D = C1-C0. + Vector2 D = box1.center - box0.center; + + Real absA0dA1[2][2], rSum; + + // Test box0.axis[0]. + absA0dA1[0][0] = std::abs(Dot(A0[0], A1[0])); + absA0dA1[0][1] = std::abs(Dot(A0[0], A1[1])); + rSum = E0[0] + E1[0] * absA0dA1[0][0] + E1[1] * absA0dA1[0][1]; + if (std::abs(Dot(A0[0], D)) > rSum) + { + result.intersect = false; + result.separating = 0; + return result; + } + + // Test axis box0.axis[1]. + absA0dA1[1][0] = std::abs(Dot(A0[1], A1[0])); + absA0dA1[1][1] = std::abs(Dot(A0[1], A1[1])); + rSum = E0[1] + E1[0] * absA0dA1[1][0] + E1[1] * absA0dA1[1][1]; + if (std::abs(Dot(A0[1], D)) > rSum) + { + result.intersect = false; + result.separating = 1; + return result; + } + + // Test axis box1.axis[0]. + rSum = E1[0] + E0[0] * absA0dA1[0][0] + E0[1] * absA0dA1[1][0]; + if (std::abs(Dot(A1[0], D)) > rSum) + { + result.intersect = false; + result.separating = 2; + return result; + } + + // Test axis box1.axis[1]. + rSum = E1[1] + E0[0] * absA0dA1[0][1] + E0[1] * absA0dA1[1][1]; + if (std::abs(Dot(A1[1], D)) > rSum) + { + result.intersect = false; + result.separating = 3; + return result; + } + + result.intersect = true; + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + OrientedBox2 const& box0, OrientedBox2 const& box1) +{ + Result result; + result.intersect = true; + + // Initialize the intersection polygon to box0, listing the vertices + // in counterclockwise order. + std::array, 4> vertex; + box0.GetVertices(vertex); + result.polygon.push_back(vertex[0]); // C - e0 * U0 - e1 * U1 + result.polygon.push_back(vertex[1]); // C + e0 * U0 - e1 * U1 + result.polygon.push_back(vertex[3]); // C + e0 * U0 + e1 * U1 + result.polygon.push_back(vertex[2]); // C - e0 * U0 + e1 * U1 + + // Clip the polygon using the lines defining edges of box1. The + // line normal points inside box1. The line origin is the first + // vertex of the edge when traversing box1 counterclockwise. + box1.GetVertices(vertex); + std::array, 4> normal = + { + box1.axis[1], -box1.axis[0], box1.axis[0], -box1.axis[1] + }; + + for (int i = 0; i < 4; ++i) + { + if (Outside(vertex[i], normal[i], result.polygon)) + { + // The boxes are separated. + result.intersect = false; + result.polygon.clear(); + break; + } + } + + return result; +} + +template +bool FIQuery, OrientedBox2>::Outside( + Vector2 const& origin, Vector2 const& normal, + std::vector>& polygon) +{ + // Determine whether the polygon vertices are outside the polygon, inside + // the polygon, or on the polygon boundary. + int const numVertices = static_cast(polygon.size()); + std::vector distance(numVertices); + int positive = 0, negative = 0, positiveIndex = -1; + for (int i = 0; i < numVertices; ++i) + { + distance[i] = Dot(normal, polygon[i] - origin); + if (distance[i] > (Real)0) + { + ++positive; + if (positiveIndex == -1) + { + positiveIndex = i; + } + } + else if (distance[i] < (Real)0) + { + ++negative; + } + } + + if (positive == 0) + { + // The polygon is strictly outside the line. + return true; + } + + if (negative == 0) + { + // The polygon is contained in the closed halfspace whose boundary + // is the line. It is fully visible and no clipping is necessary. + return false; + } + + // The line transversely intersects the polygon. Clip the polygon. + std::vector> clipPolygon; + Vector2 vertex; + int curr, prev; + Real t; + + if (positiveIndex > 0) + { + // Compute the first clip vertex on the line. + curr = positiveIndex; + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Include the vertices on the positive side of line. + while (curr < numVertices && distance[curr] > (Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + + // Compute the kast clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + } + else + { + curr = 0; + prev = numVertices - 1; + } + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + } + else // positiveIndex is 0 + { + // Include the vertices on the positive side of line. + curr = 0; + while (curr < numVertices && distance[curr] > (Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + + // Compute the last clip vertex on the line. + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Skip the vertices on the negative side of the line. + while (curr < numVertices && distance[curr] <= (Real)0) + { + curr++; + } + + // Compute the first clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Keep the vertices on the positive side of the line. + while (curr < numVertices && distance[curr] > (Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + } + else + { + curr = 0; + prev = numVertices - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + } + } + + polygon = clipPolygon; + return false; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Sector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Sector2.h new file mode 100644 index 000000000000..20bd52cb88a6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox2Sector2.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include + +// The OrientedBox2 object is considered to be a solid. + +namespace gte +{ + +template +class TIQuery, Sector2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox2 const& box, Sector2 const& sector); +}; + +template +typename TIQuery, Sector2>::Result +TIQuery, Sector2>::operator()( + OrientedBox2 const& box, Sector2 const& sector) +{ + Result result; + + // Determine whether the vertex is inside the box. + Vector2 CmV = box.center - sector.vertex; + Vector2 P{ Dot(box.axis[0], CmV), Dot(box.axis[1], CmV) }; + if (std::abs(P[0]) <= box.extent[0] && std::abs(P[1]) <= box.extent[1]) + { + // The vertex is inside the box. + result.intersect = true; + return result; + } + + // Test whether the box is outside the right ray boundary of the sector. + Vector2 U0 + { + +sector.cosAngle * sector.direction[0] + sector.sinAngle * sector.direction[1], + -sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Vector2 N0 = Perp(U0); + Real prjcen0 = Dot(N0, CmV); + Real radius0 = box.extent[0] * std::abs(Dot(N0, box.axis[0])) + + box.extent[1] * std::abs(Dot(N0, box.axis[1])); + if (prjcen0 > radius0) + { + result.intersect = false; + return result; + } + + // Test whether the box is outside the ray of the left boundary of the + // sector. + Vector2 U1 + { + +sector.cosAngle * sector.direction[0] - sector.sinAngle * sector.direction[1], + +sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Vector2 N1 = -Perp(U1); + Real prjcen1 = Dot(N1, CmV); + Real radius1 = box.extent[0] * std::abs(Dot(N1, box.axis[0])) + + box.extent[1] * std::abs(Dot(N1, box.axis[1])); + if (prjcen1 > radius1) + { + result.intersect = false; + return result; + } + + // Initialize the polygon of intersection to be the box. + Vector2 e0U0 = box.extent[0] * box.axis[0]; + Vector2 e1U1 = box.extent[1] * box.axis[1]; + std::vector> polygon; + polygon.reserve(8); + polygon.push_back(box.center - e0U0 - e1U1); + polygon.push_back(box.center + e0U0 - e1U1); + polygon.push_back(box.center + e0U0 + e1U1); + polygon.push_back(box.center - e0U0 + e1U1); + + FIQuery, std::vector>> hpQuery; + typename FIQuery, std::vector>>::Result hpResult; + Halfspace<2, Real> halfspace; + + // Clip the box against the right-ray sector boundary. + if (prjcen0 >= -radius0) + { + halfspace.normal = -N0; + halfspace.constant = Dot(halfspace.normal, sector.vertex); + hpResult = hpQuery(halfspace, polygon); + polygon = std::move(hpResult.polygon); + } + + // Clip the box against the left-ray sector boundary. + if (prjcen1 >= -radius1) + { + halfspace.normal = -N1; + halfspace.constant = Dot(halfspace.normal, sector.vertex); + hpResult = hpQuery(halfspace, polygon); + polygon = std::move(hpResult.polygon); + } + + DCPQuery, Segment2> psQuery; + typename DCPQuery, Segment2>::Result psResult; + int const numVertices = static_cast(polygon.size()); + if (numVertices >= 2) + { + for (int i0 = numVertices - 1, i1 = 0; i1 < numVertices; i0 = i1++) + { + Segment2 segment(polygon[i0], polygon[i1]); + psResult = psQuery(sector.vertex, segment); + if (psResult.distance <= sector.radius) + { + result.intersect = true; + return result; + } + } + } + + result.intersect = false; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cone3.h new file mode 100644 index 000000000000..534d6593e390 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cone3.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/29) + +#pragma once + +#include +#include +#include + +// Test for intersection of a box and a cone. The cone can be infinite +// 0 <= minHeight < maxHeight = std::numeric_limits::max() +// or finite (cone frustum) +// 0 <= minHeight < maxHeight < std::numeric_limits::max(). +// The algorithm is described in +// http://www.geometrictools.com/Documentation/IntersectionBoxCone.pdf +// and reports an intersection only when th intersection set has positive +// volume. For example, let the box be outside the cone. If the box is +// below the minHeight plane at the cone vertex and just touches the cone +// vertex, no intersection is reported. If the box is above the maxHeight +// plane and just touches the disk capping the cone, either at a single +// point, a line segment of points or a polygon of points, no intersection +// is reported. However, if the box straddles the minHeight plane (part of +// the box strictly above the plane and part of the box strictly below the +// plane) and just touches the cone vertex, an intersection is reported. + +namespace gte +{ + template + class TIQuery, Cone<3, Real>> + : + public TIQuery, Cone<3, Real>> + { + public: + struct Result + : + public TIQuery, Cone<3, Real>>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + // Transform the cone and box so that the cone vertex is at the + // origin and the box is axis aligned. This allows us to call the + // base class operator()(...). + Vector<3, Real> diff = box.center - cone.ray.origin; + Vector<3, Real> xfrmBoxCenter + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff), + Dot(box.axis[2], diff) + }; + AlignedBox<3, Real> xfrmBox; + xfrmBox.min = xfrmBoxCenter - box.extent; + xfrmBox.max = xfrmBoxCenter + box.extent; + + Cone<3, Real> xfrmCone = cone; + for (int i = 0; i < 3; ++i) + { + xfrmCone.ray.origin[i] = (Real)0; + xfrmCone.ray.direction[i] = Dot(box.axis[i], cone.ray.direction); + } + + // Test for intersection between the aligned box and the cone. + auto bcResult = TIQuery, Cone<3, Real>>::operator()(xfrmBox, xfrmCone); + Result result; + result.intersect = bcResult.intersect; + return result; + } + }; + + // Template alias for convenience. + template + using TIOrientedBox3Cone3 = TIQuery, Cone<3, Real>>; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cylinder3.h new file mode 100644 index 000000000000..ff0f6c153795 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Cylinder3.h @@ -0,0 +1,55 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.4.0 (2016/11/08) + +#pragma once + +#include +#include + +// The query considers the cylinder and box to be solids. + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox3 const& box, Cylinder3 const& cylinder); +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + OrientedBox3 const& box, Cylinder3 const& cylinder) +{ + // Transform the box and cylinder so that the box is axis-aligned. + AlignedBox3 aabb(-box.extent, box.extent); + Vector3 diff = cylinder.axis.origin - box.center; + Cylinder3 transformedCylinder; + transformedCylinder.radius = cylinder.radius; + transformedCylinder.height = cylinder.height; + for (int i = 0; i < 3; ++i) + { + transformedCylinder.axis.origin[i] = Dot(box.axis[i], diff); + transformedCylinder.axis.direction[i] = Dot(box.axis[i], cylinder.axis.direction); + } + + TIQuery, Cylinder3> aabbCylinderQuery; + auto aabbCylinderResult = aabbCylinderQuery(aabb, transformedCylinder); + Result result; + result.intersect = aabbCylinderResult.intersect; + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Frustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Frustum3.h new file mode 100644 index 000000000000..6887725f644c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Frustum3.h @@ -0,0 +1,373 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +// The method of separating axes is used. The potential separating axes +// include the 3 box face normals, the 5 distinct frustum normals (near and +// far plane have the same normal), and cross products of normals, one from +// the box and one from the frustum. + +template +class TIQuery, Frustum3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox3 const& box, + Frustum3 const& frustum); +}; + + +template +typename TIQuery, Frustum3>::Result +TIQuery, Frustum3>::operator()( + OrientedBox3 const& box, Frustum3 const& frustum) +{ + Result result; + + // Convenience variables. + Vector3 const* axis = &box.axis[0]; + Vector3 const& extent = box.extent; + + Vector3 diff = box.center - frustum.origin; // C-E + + Real A[3]; // Dot(R,A[i]) + Real B[3]; // Dot(U,A[i]) + Real C[3]; // Dot(D,A[i]) + Real D[3]; // (Dot(R,C-E),Dot(U,C-E),Dot(D,C-E)) + Real NA[3]; // dmin*Dot(R,A[i]) + Real NB[3]; // dmin*Dot(U,A[i]) + Real NC[3]; // dmin*Dot(D,A[i]) + Real ND[3]; // dmin*(Dot(R,C-E),Dot(U,C-E),?) + Real RC[3]; // rmax*Dot(D,A[i]) + Real RD[3]; // rmax*(?,?,Dot(D,C-E)) + Real UC[3]; // umax*Dot(D,A[i]) + Real UD[3]; // umax*(?,?,Dot(D,C-E)) + Real NApRC[3]; // dmin*Dot(R,A[i]) + rmax*Dot(D,A[i]) + Real NAmRC[3]; // dmin*Dot(R,A[i]) - rmax*Dot(D,A[i]) + Real NBpUC[3]; // dmin*Dot(U,A[i]) + umax*Dot(D,A[i]) + Real NBmUC[3]; // dmin*Dot(U,A[i]) - umax*Dot(D,A[i]) + Real RBpUA[3]; // rmax*Dot(U,A[i]) + umax*Dot(R,A[i]) + Real RBmUA[3]; // rmax*Dot(U,A[i]) - umax*Dot(R,A[i]) + Real DdD, radius, p, fmin, fmax, MTwoUF, MTwoRF, tmp; + int i, j; + + // M = D + D[2] = Dot(diff, frustum.dVector); + for (i = 0; i < 3; ++i) + { + C[i] = Dot(axis[i], frustum.dVector); + } + radius = + extent[0] * std::abs(C[0]) + + extent[1] * std::abs(C[1]) + + extent[2] * std::abs(C[2]); + if (D[2] + radius < frustum.dMin + || D[2] - radius > frustum.dMax) + { + result.intersect = false; + return result; + } + + // M = n*R - r*D + for (i = 0; i < 3; ++i) + { + A[i] = Dot(axis[i], frustum.rVector); + RC[i] = frustum.rBound*C[i]; + NA[i] = frustum.dMin*A[i]; + NAmRC[i] = NA[i] - RC[i]; + } + D[0] = Dot(diff, frustum.rVector); + radius = + extent[0] * std::abs(NAmRC[0]) + + extent[1] * std::abs(NAmRC[1]) + + extent[2] * std::abs(NAmRC[2]); + ND[0] = frustum.dMin*D[0]; + RD[2] = frustum.rBound*D[2]; + DdD = ND[0] - RD[2]; + MTwoRF = frustum.GetMTwoRF(); + if (DdD + radius < MTwoRF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = -n*R - r*D + for (i = 0; i < 3; ++i) + { + NApRC[i] = NA[i] + RC[i]; + } + radius = + extent[0] * std::abs(NApRC[0]) + + extent[1] * std::abs(NApRC[1]) + + extent[2] * std::abs(NApRC[2]); + DdD = -(ND[0] + RD[2]); + if (DdD + radius < MTwoRF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = n*U - u*D + for (i = 0; i < 3; ++i) + { + B[i] = Dot(axis[i], frustum.uVector); + UC[i] = frustum.uBound*C[i]; + NB[i] = frustum.dMin*B[i]; + NBmUC[i] = NB[i] - UC[i]; + } + D[1] = Dot(diff, frustum.uVector); + radius = + extent[0] * std::abs(NBmUC[0]) + + extent[1] * std::abs(NBmUC[1]) + + extent[2] * std::abs(NBmUC[2]); + ND[1] = frustum.dMin*D[1]; + UD[2] = frustum.uBound*D[2]; + DdD = ND[1] - UD[2]; + MTwoUF = frustum.GetMTwoUF(); + if (DdD + radius < MTwoUF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = -n*U - u*D + for (i = 0; i < 3; ++i) + { + NBpUC[i] = NB[i] + UC[i]; + } + radius = + extent[0] * std::abs(NBpUC[0]) + + extent[1] * std::abs(NBpUC[1]) + + extent[2] * std::abs(NBpUC[2]); + DdD = -(ND[1] + UD[2]); + if (DdD + radius < MTwoUF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = A[i] + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(A[i]) + + frustum.uBound*std::abs(B[i]); + NC[i] = frustum.dMin*C[i]; + fmin = NC[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = NC[i] + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = A[i] * D[0] + B[i] * D[1] + C[i] * D[2]; + if (DdD + extent[i] < fmin || DdD - extent[i] > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(R,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.uBound*std::abs(C[i]); + fmin = -NB[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = -NB[i] + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = C[i] * D[1] - B[i] * D[2]; + radius = + extent[0] * std::abs(B[i] * C[0] - B[0] * C[i]) + + extent[1] * std::abs(B[i] * C[1] - B[1] * C[i]) + + extent[2] * std::abs(B[i] * C[2] - B[2] * C[i]); + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(C[i]); + fmin = NA[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = NA[i] + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = -C[i] * D[0] + A[i] * D[2]; + radius = + extent[0] * std::abs(A[i] * C[0] - A[0] * C[i]) + + extent[1] * std::abs(A[i] * C[1] - A[1] * C[i]) + + extent[2] * std::abs(A[i] * C[2] - A[2] * C[i]); + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D+r*R+u*U,A[i]) + for (i = 0; i < 3; ++i) + { + Real fRB = frustum.rBound*B[i]; + Real fUA = frustum.uBound*A[i]; + RBpUA[i] = fRB + fUA; + RBmUA[i] = fRB - fUA; + } + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBmUC[i]) + + frustum.uBound*std::abs(NAmRC[i]); + tmp = -frustum.dMin*RBmUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBmUC[i] - D[1] * NAmRC[i] - D[2] * RBmUA[i]; + radius = (Real)0; + for (j = 0; j < 3; j++) + { + radius += extent[j] * std::abs(A[j] * NBmUC[i] - + B[j] * NAmRC[i] - C[j] * RBmUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D+r*R-u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBpUC[i]) + + frustum.uBound*std::abs(NAmRC[i]); + tmp = -frustum.dMin*RBpUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBpUC[i] - D[1] * NAmRC[i] - D[2] * RBpUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::abs(A[j] * NBpUC[i] - + B[j] * NAmRC[i] - C[j] * RBpUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D-r*R+u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBmUC[i]) + + frustum.uBound*std::abs(NApRC[i]); + tmp = frustum.dMin*RBpUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBmUC[i] - D[1] * NApRC[i] + D[2] * RBpUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::abs(A[j] * NBmUC[i] - + B[j] * NApRC[i] + C[j] * RBpUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D-r*R-u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound*std::abs(NBpUC[i]) + + frustum.uBound*std::abs(NApRC[i]); + tmp = frustum.dMin*RBmUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax >(Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBpUC[i] - D[1] * NApRC[i] + D[2] * RBmUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::abs(A[j] * NBpUC[i] - + B[j] * NApRC[i] + C[j] * RBmUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3OrientedBox3.h new file mode 100644 index 000000000000..656d8cd3a46f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3OrientedBox3.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. The set of +// potential separating directions includes the 3 face normals of box0, the 3 +// face normals of box1, and 9 directions, each of which is the cross product +// of an edge of box0 and and an edge of box1. +// +// The separating axes involving cross products of edges has numerical +// robustness problems when the two edges are nearly parallel. The cross +// product of the edges is nearly the zero vector, so normalization of the +// cross product may produce unit-length directions that are not close to the +// true direction. Such a pair of edges occurs when a box0 face normal N0 and +// a box1 face normal N1 are nearly parallel. In this case, you may skip the +// edge-edge directions, which is equivalent to projecting the boxes onto the +// plane with normal N0 and applying a 2D separating axis test. The ability +// to do so involves choosing a small nonnegative epsilon . It is used to +// determine whether two face normals, one from each box, are nearly parallel: +// |Dot(N0,N1)| >= 1 - epsilon. If the input is negative, it is clamped to +// zero. +// +// The pair of integers 'separating', say, (i0,i1), identify the axis that +// reported separation; there may be more than one but only one is +// reported. If the separating axis is a face normal N[i0] of the aligned +// box0 in dimension i0, then (i0,-1) is returned. If the axis is a face +// normal box1.Axis[i1], then (-1,i1) is returned. If the axis is a cross +// product of edges, Cross(N[i0],box1.Axis[i1]), then (i0,i1) is returned. + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + // The 'epsilon' value must be nonnegative. + Result(Real inEpsilon = (Real)0); + + bool intersect; + Real epsilon; + int separating[2]; + }; + + Result operator()(OrientedBox3 const& box0, + OrientedBox3 const& box1); +}; + + +template +TIQuery, OrientedBox3>::Result:: +Result(Real inEpsilon) +{ + if (inEpsilon >= (Real)0) + { + epsilon = inEpsilon; + } + else + { + epsilon = (Real)0; + } +} + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + OrientedBox3 const& box0, OrientedBox3 const& box1) +{ + Result result; + + // Convenience variables. + Vector3 const& C0 = box0.center; + Vector3 const* A0 = &box0.axis[0]; + Vector3 const& E0 = box0.extent; + Vector3 const& C1 = box1.center; + Vector3 const* A1 = &box1.axis[0]; + Vector3 const& E1 = box1.extent; + + const Real cutoff = (Real)1 - result.epsilon; + bool existsParallelPair = false; + + // Compute difference of box centers. + Vector3 D = C1 - C0; + + Real dot01[3][3]; // dot01[i][j] = Dot(A0[i],A1[j]) = A1[j][i] + Real absDot01[3][3]; // |dot01[i][j]| + Real dotDA0[3]; // Dot(D, A0[i]) + Real r0, r1, r; // interval radii and distance between centers + Real r01; // r0 + r1 + + // Test for separation on the axis C0 + t*A0[0]. + for (int i = 0; i < 3; ++i) + { + dot01[0][i] = Dot(A0[0], A1[i]); + absDot01[0][i] = std::abs(dot01[0][i]); + if (absDot01[0][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[0] = Dot(D, A0[0]); + r = std::abs(dotDA0[0]); + r1 = E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1] + E1[2] * absDot01[0][2]; + r01 = E0[0] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]. + for (int i = 0; i < 3; ++i) + { + dot01[1][i] = Dot(A0[1], A1[i]); + absDot01[1][i] = std::abs(dot01[1][i]); + if (absDot01[1][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[1] = Dot(D, A0[1]); + r = std::abs(dotDA0[1]); + r1 = E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1] + E1[2] * absDot01[1][2]; + r01 = E0[1] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]. + for (int i = 0; i < 3; ++i) + { + dot01[2][i] = Dot(A0[2], A1[i]); + absDot01[2][i] = std::abs(dot01[2][i]); + if (absDot01[2][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[2] = Dot(D, A0[2]); + r = std::abs(dotDA0[2]); + r1 = E1[0] * absDot01[2][0] + E1[1] * absDot01[2][1] + E1[2] * absDot01[2][2]; + r01 = E0[2] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A1[0]. + r = std::abs(Dot(D, A1[0])); + r0 = E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0] + E0[2] * absDot01[2][0]; + r01 = r0 + E1[0]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A1[1]. + r = std::abs(Dot(D, A1[1])); + r0 = E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1] + E0[2] * absDot01[2][1]; + r01 = r0 + E1[1]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A1[2]. + r = std::abs(Dot(D, A1[2])); + r0 = E0[0] * absDot01[0][2] + E0[1] * absDot01[1][2] + E0[2] * absDot01[2][2]; + r01 = r0 + E1[2]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 2; + return result; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D. The edge-edge axes do not need to be tested. + if (existsParallelPair) + { + return true; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[0]. + r = std::abs(dotDA0[2] * dot01[1][0] - dotDA0[1] * dot01[2][0]); + r0 = E0[1] * absDot01[2][0] + E0[2] * absDot01[1][0]; + r1 = E1[1] * absDot01[0][2] + E1[2] * absDot01[0][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[1]. + r = std::abs(dotDA0[2] * dot01[1][1] - dotDA0[1] * dot01[2][1]); + r0 = E0[1] * absDot01[2][1] + E0[2] * absDot01[1][1]; + r1 = E1[0] * absDot01[0][2] + E1[2] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[2]. + r = std::abs(dotDA0[2] * dot01[1][2] - dotDA0[1] * dot01[2][2]); + r0 = E0[1] * absDot01[2][2] + E0[2] * absDot01[1][2]; + r1 = E1[0] * absDot01[0][1] + E1[1] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[0]. + r = std::abs(dotDA0[0] * dot01[2][0] - dotDA0[2] * dot01[0][0]); + r0 = E0[0] * absDot01[2][0] + E0[2] * absDot01[0][0]; + r1 = E1[1] * absDot01[1][2] + E1[2] * absDot01[1][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[1]. + r = std::abs(dotDA0[0] * dot01[2][1] - dotDA0[2] * dot01[0][1]); + r0 = E0[0] * absDot01[2][1] + E0[2] * absDot01[0][1]; + r1 = E1[0] * absDot01[1][2] + E1[2] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[2]. + r = std::abs(dotDA0[0] * dot01[2][2] - dotDA0[2] * dot01[0][2]); + r0 = E0[0] * absDot01[2][2] + E0[2] * absDot01[0][2]; + r1 = E1[0] * absDot01[1][1] + E1[1] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[0]. + r = std::abs(dotDA0[1] * dot01[0][0] - dotDA0[0] * dot01[1][0]); + r0 = E0[0] * absDot01[1][0] + E0[1] * absDot01[0][0]; + r1 = E1[1] * absDot01[2][2] + E1[2] * absDot01[2][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[1]. + r = std::abs(dotDA0[1] * dot01[0][1] - dotDA0[0] * dot01[1][1]); + r0 = E0[0] * absDot01[1][1] + E0[1] * absDot01[0][1]; + r1 = E1[0] * absDot01[2][2] + E1[2] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[2]. + r = std::abs(dotDA0[1] * dot01[0][2] - dotDA0[0] * dot01[1][2]); + r0 = E0[0] * absDot01[1][2] + E0[1] * absDot01[0][2]; + r1 = E1[0] * absDot01[2][1] + E1[1] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 2; + return result; + } + + result.intersect = true; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Sphere3.h new file mode 100644 index 000000000000..b30bb1d95419 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrOrientedBox3Sphere3.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/02) + +#pragma once + +#include +#include + +namespace gte +{ + template + class TIQuery, Sphere3> + : + public TIQuery, Sphere3> + { + public: + // The intersection query considers the box and sphere to be solids. + // For example, if the sphere is strictly inside the box (does not touch + // the box faces), the objects intersect. + struct Result + : + public TIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox3 const& box, Sphere3 const& sphere) + { + DCPQuery, OrientedBox3> pbQuery; + auto pbResult = pbQuery(sphere.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= sphere.radius * sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + : + public FIQuery, Sphere3> + { + public: + // Currently, only a dynamic query is supported. The static query must + // compute the intersection set of (solid) box and sphere. + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox3 const& box, Vector3 const& boxVelocity, + Sphere3 const& sphere, Vector3 const& sphereVelocity) + { + Result result; + result.intersectionType = 0; + result.contactTime = (Real)0; + result.contactPoint = { (Real)0, (Real)0, (Real)0 }; + + // Transform the sphere and box so that the box center becomes + // the origin and the box is axis aligned. Compute the velocity + // of the sphere relative to the box. + Vector3 temp = sphere.center - box.center; + Vector3 C + { + Dot(temp, box.axis[0]), + Dot(temp, box.axis[1]), + Dot(temp, box.axis[2]) + }; + + temp = sphereVelocity - boxVelocity; + Vector3 V + { + Dot(temp, box.axis[0]), + Dot(temp, box.axis[1]), + Dot(temp, box.axis[2]) + }; + + this->DoQuery(box.extent, C, sphere.radius, V, result); + + // Transform back to the original coordinate system. + if (result.intersectionType != 0) + { + auto& P = result.contactPoint; + P = box.center + P[0] * box.axis[0] + P[1] * box.axis[1] + P[2] * box.axis[2]; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Capsule3.h new file mode 100644 index 000000000000..9a1d554de29e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Capsule3.h @@ -0,0 +1,58 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, + Capsule3 const& capsule); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Plane3 const& plane, Capsule3 const& capsule) +{ + Result result; + + DCPQuery, Plane3> vpQuery; + Real sdistance0 = vpQuery(capsule.segment.p[0], plane).signedDistance; + Real sdistance1 = vpQuery(capsule.segment.p[1], plane).signedDistance; + if (sdistance0 * sdistance1 <= (Real)0) + { + // A capsule segment endpoint is on the plane or the two endpoints + // are on opposite sides of the plane. + result.intersect = true; + return result; + } + + // The endpoints on same side of plane, but the endpoint spheres might + // intersect the plane. + result.intersect = + std::abs(sdistance0) <= capsule.radius || + std::abs(sdistance1) <= capsule.radius; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Circle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Circle3.h new file mode 100644 index 000000000000..2a9022a46d6a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Circle3.h @@ -0,0 +1,166 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Circle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Circle3 const& circle); +}; + +template +class FIQuery, Circle3> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either 1 or 2 points + // or the entire circle. When points, 'numIntersections' and + // 'point' are valid. When a circle, 'circle' is set to the incoming + // circle. + bool isPoints; + int numIntersections; + Vector3 point[2]; + Circle3 circle; + }; + + Result operator()(Plane3 const& plane, Circle3 const& circle); +}; + + +template +typename TIQuery, Circle3>::Result +TIQuery, Circle3>::operator()( + Plane3 const& plane, Circle3 const& circle) +{ + Result result; + + // Construct the plane of the circle. + Plane3 cPlane(circle.normal, circle.center); + + // Compute the intersection of this plane with the input plane. + FIQuery, Plane3> ppQuery; + auto ppResult = ppQuery(plane, cPlane); + if (!ppResult.intersect) + { + // Planes are parallel and nonintersecting. + result.intersect = false; + return result; + } + + if (!ppResult.isLine) + { + // Planes are the same, the circle is the common intersection set. + result.intersect = true; + return result; + } + + // The planes intersect in a line. Locate one or two points that are on + // the circle and line. If the line is t*D+P, the circle center is C, + // and the circle radius is r, then + // r^2 = |t*D+P-C|^2 = |D|^2*t^2 + 2*Dot(D,P-C)*t + |P-C|^2 + // This is a quadratic equation of the form a2*t^2 + 2*a1*t + a0 = 0. + Vector3 diff = ppResult.line.origin - circle.center; + Real a2 = Dot(ppResult.line.direction, ppResult.line.direction); + Real a1 = Dot(diff, ppResult.line.direction); + Real a0 = Dot(diff, diff) - circle.radius*circle.radius; + + // Real-valued roots imply an intersection. + Real discr = a1*a1 - a0*a2; + result.intersect = (discr >= (Real)0); + return result; +} + + + +template +typename FIQuery, Circle3>::Result +FIQuery, Circle3>::operator()( + Plane3 const& plane, Circle3 const& circle) +{ + Result result; + + // Construct the plane of the circle. + Plane3 cPlane(circle.normal, circle.center); + + // Compute the intersection of this plane with the input plane. + FIQuery, Plane3> ppQuery; + auto ppResult = ppQuery(plane, cPlane); + if (!ppResult.intersect) + { + // Planes are parallel and nonintersecting. + result.intersect = false; + return result; + } + + if (!ppResult.isLine) + { + // Planes are the same, the circle is the common intersection set. + result.intersect = true; + result.isPoints = false; + result.circle = circle; + return result; + } + + // The planes intersect in a line. Locate one or two points that are on + // the circle and line. If the line is t*D+P, the circle center is C, + // and the circle radius is r, then + // r^2 = |t*D+P-C|^2 = |D|^2*t^2 + 2*Dot(D,P-C)*t + |P-C|^2 + // This is a quadratic equation of the form a2*t^2 + 2*a1*t + a0 = 0. + Vector3 diff = ppResult.line.origin - circle.center; + Real a2 = Dot(ppResult.line.direction, ppResult.line.direction); + Real a1 = Dot(diff, ppResult.line.direction); + Real a0 = Dot(diff, diff) - circle.radius*circle.radius; + + Real discr = a1*a1 - a0*a2; + if (discr < (Real)0) + { + // No real roots, the circle does not intersect the plane. + result.intersect = false; + return result; + } + + result.isPoints = true; + + Real inv = ((Real)1) / a2; + if (discr == (Real)0) + { + // One repeated root, the circle just touches the plane. + result.numIntersections = 1; + result.point[0] = + ppResult.line.origin - (a1*inv)*ppResult.line.direction; + return result; + } + + // Two distinct, real-valued roots, the circle intersects the plane in + // two points. + Real root = std::sqrt(discr); + result.numIntersections = 2; + result.point[0] = + ppResult.line.origin - ((a1 + root)*inv)*ppResult.line.direction; + result.point[1] = + ppResult.line.origin - ((a1 - root)*inv)*ppResult.line.direction; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Cylinder3.h new file mode 100644 index 000000000000..2f9da6637f76 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Cylinder3.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + }; + + // The cylinder must have finite height. + Result operator()(Plane3 const& plane, + Cylinder3 const& cylinder); +}; + +template +class FIQuery, Cylinder3> +{ +public: + struct Result + { + bool intersect; + + // The type of intersection. + // 0: none + // 1: single line (cylinder is tangent to plane), line[0] valid + // 2: two parallel lines (plane cuts cylinder in two lines) + // 3: circle (cylinder axis perpendicular to plane) + // 4: ellipse (cylinder axis neither parallel nor perpendicular) + int type; + Line3 line[2]; + Circle3 circle; + Ellipse3 ellipse; + }; + + // The cylinder must have infinite height. + Result operator()(Plane3 const& plane, + Cylinder3 const& cylinder); +}; + + +template +typename TIQuery, Cylinder3>::Result +TIQuery, Cylinder3>::operator()( + Plane3 const& plane, Cylinder3 const& cylinder) +{ + LogAssert(cylinder.height != std::numeric_limits::max(), + "Cylinder height must be finite for TIQuery."); + + Result result; + + // Compute extremes of signed distance Dot(N,X)-d for points on the + // cylinder. These are + // min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)| + // max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)| + DCPQuery, Plane3> vpQuery; + Real distance = vpQuery(cylinder.axis.origin, plane).distance; + Real absNdW = std::abs(Dot(plane.normal, cylinder.axis.direction)); + Real root = std::sqrt(std::max((Real)1 - absNdW*absNdW, (Real)0)); + Real term = cylinder.radius*root + ((Real)0.5)*cylinder.height*absNdW; + + // Intersection occurs if and only if 0 is in the interval [min,max]. + result.intersect = (distance <= term); + return result; +} + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Plane3 const& plane, Cylinder3 const& cylinder) +{ + LogAssert(cylinder.height == std::numeric_limits::max(), + "Cylinder height must be infinite for FIQuery."); + + Result result; + + DCPQuery, Plane3> vpQuery; + Real sdistance = vpQuery(cylinder.axis.origin, plane).signedDistance; + Vector3 center = cylinder.axis.origin - sdistance*plane.normal; + Real cosTheta = Dot(cylinder.axis.direction, plane.normal); + Real absCosTheta = std::abs(cosTheta); + + if (absCosTheta > (Real)0) + { + // The cylinder axis intersects the plane in a unique point. + result.intersect = true; + if (absCosTheta < (Real)1) + { + result.type = 4; + result.ellipse.normal = plane.normal; + result.ellipse.center = cylinder.axis.origin - + (sdistance / cosTheta)*cylinder.axis.direction; + result.ellipse.axis[0] = cylinder.axis.direction - + cosTheta*plane.normal; + Normalize(result.ellipse.axis[0]); + result.ellipse.axis[1] = UnitCross(plane.normal, + result.ellipse.axis[0]); + result.ellipse.extent[0] = cylinder.radius / absCosTheta; + result.ellipse.extent[1] = cylinder.radius; + } + else + { + result.type = 3; + result.circle.normal = plane.normal; + result.circle.center = center; + result.circle.radius = cylinder.radius; + } + } + else + { + // The cylinder is parallel to the plane. + Real distance = std::abs(sdistance); + if (distance < cylinder.radius) + { + result.intersect = true; + result.type = 2; + + Vector3 offset = Cross(cylinder.axis.direction, + plane.normal); + Real extent = std::sqrt(cylinder.radius*cylinder.radius - sdistance*sdistance); + + result.line[0].origin = center - extent*offset; + result.line[0].direction = cylinder.axis.direction; + result.line[1].origin = center + extent*offset; + result.line[1].direction = cylinder.axis.direction; + } + else if (distance == cylinder.radius) + { + result.intersect = true; + result.type = 1; + result.line[0].origin = center; + result.line[0].direction = cylinder.axis.direction; + } + else + { + result.intersect = false; + result.type = 0; + } + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Ellipsoid3.h new file mode 100644 index 000000000000..c99065a1a7fe --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Ellipsoid3.h @@ -0,0 +1,49 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, + Ellipsoid3 const& ellipsoid); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Plane3 const& plane, Ellipsoid3 const& ellipsoid) +{ + Result result; + Matrix3x3 MInverse; + ellipsoid.GetMInverse(MInverse); + Real discr = Dot(plane.normal, MInverse * plane.normal); + Real root = std::sqrt(std::max(discr, (Real)0)); + DCPQuery, Plane3> vpQuery; + Real distance = vpQuery(ellipsoid.center, plane).distance; + result.intersect = (distance <= root); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3OrientedBox3.h new file mode 100644 index 000000000000..67973eba030a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3OrientedBox3.h @@ -0,0 +1,51 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, OrientedBox3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, + OrientedBox3 const& box); +}; + + +template +typename TIQuery, OrientedBox3>::Result +TIQuery, OrientedBox3>::operator()( + Plane3 const& plane, OrientedBox3 const& box) +{ + Result result; + + Real radius = + std::abs(box.extent[0] * Dot(plane.normal, box.axis[0])) + + std::abs(box.extent[1] * Dot(plane.normal, box.axis[1])) + + std::abs(box.extent[2] * Dot(plane.normal, box.axis[2])); + + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(box.center, plane); + result.intersect = (ppResult.distance <= radius); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Plane3.h new file mode 100644 index 000000000000..08e5fff3441f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Plane3.h @@ -0,0 +1,155 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane0, Plane3 const& plane1); +}; + +template +class FIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either a line or the + // planes are the same. When a line, 'line' is valid. When a plane, + // 'plane' is set to one of the planes. + bool isLine; + Line3 line; + Plane3 plane; + }; + + Result operator()(Plane3 const& plane0, Plane3 const& plane1); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Plane3 const& plane0, Plane3 const& plane1) +{ + // If Cross(N0,N1) is zero, then either planes are parallel and separated + // or the same plane. In both cases, 'false' is returned. Otherwise, the + // planes intersect. To avoid subtle differences in reporting between + // Test() and Find(), the same parallel test is used. Mathematically, + // |Cross(N0,N1)|^2 = Dot(N0,N0)*Dot(N1,N1)-Dot(N0,N1)^2 + // = 1 - Dot(N0,N1)^2 + // The last equality is true since planes are required to have unit-length + // normal vectors. The test |Cross(N0,N1)| = 0 is the same as + // |Dot(N0,N1)| = 1. + + Result result; + Real dot = Dot(plane0.normal, plane1.normal); + if (std::abs(dot) < (Real)1) + { + result.intersect = true; + return result; + } + + // The planes are parallel. Check whether they are coplanar. + Real cDiff; + if (dot >= (Real)0) + { + // Normals are in same direction, need to look at c0-c1. + cDiff = plane0.constant - plane1.constant; + } + else + { + // Normals are in opposite directions, need to look at c0+c1. + cDiff = plane0.constant + plane1.constant; + } + + result.intersect = (std::abs(cDiff) == (Real)0); + return result; +} + + + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Plane3 const& plane0, Plane3 const& plane1) +{ + // If N0 and N1 are parallel, either the planes are parallel and separated + // or the same plane. In both cases, 'false' is returned. Otherwise, + // the intersection line is + // L(t) = t*Cross(N0,N1)/|Cross(N0,N1)| + c0*N0 + c1*N1 + // for some coefficients c0 and c1 and for t any real number (the line + // parameter). Taking dot products with the normals, + // d0 = Dot(N0,L) = c0*Dot(N0,N0) + c1*Dot(N0,N1) = c0 + c1*d + // d1 = Dot(N1,L) = c0*Dot(N0,N1) + c1*Dot(N1,N1) = c0*d + c1 + // where d = Dot(N0,N1). These are two equations in two unknowns. The + // solution is + // c0 = (d0 - d*d1)/det + // c1 = (d1 - d*d0)/det + // where det = 1 - d^2. + + Result result; + + Real dot = Dot(plane0.normal, plane1.normal); + if (std::abs(dot) >= (Real)1) + { + // The planes are parallel. Check if they are coplanar. + Real cDiff; + if (dot >= (Real)0) + { + // Normals are in same direction, need to look at c0-c1. + cDiff = plane0.constant - plane1.constant; + } + else + { + // Normals are in opposite directions, need to look at c0+c1. + cDiff = plane0.constant + plane1.constant; + } + + if (std::abs(cDiff) == (Real)0) + { + // The planes are coplanar. + result.intersect = true; + result.isLine = false; + result.plane = plane0; + return result; + } + + // The planes are parallel but distinct. + result.intersect = false; + return result; + } + + Real invDet = ((Real)1) / ((Real)1 - dot*dot); + Real c0 = (plane0.constant - dot*plane1.constant)*invDet; + Real c1 = (plane1.constant - dot*plane0.constant)*invDet; + result.intersect = true; + result.isLine = true; + result.line.origin = c0*plane0.normal + c1*plane1.normal; + result.line.direction = UnitCross(plane0.normal, plane1.normal); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Sphere3.h new file mode 100644 index 000000000000..129feb6eb0b7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Sphere3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either a point or a + // circle. When 'isCircle' is true, 'circle' is valid. When + // 'isCircle' is false, 'point' is valid. + bool isCircle; + Circle3 circle; + Vector3 point; + }; + + Result operator()(Plane3 const& plane, Sphere3 const& sphere); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Plane3 const& plane, Sphere3 const& sphere) +{ + Result result; + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(sphere.center, plane); + result.intersect = (ppResult.distance <= sphere.radius); + return result; +} + + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Plane3 const& plane, Sphere3 const& sphere) +{ + Result result; + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(sphere.center, plane); + if (ppResult.distance < sphere.radius) + { + result.intersect = true; + result.isCircle = true; + result.circle.center = sphere.center - ppResult.signedDistance * plane.normal; + result.circle.normal = plane.normal; + Real sum = sphere.radius + ppResult.distance; // > 0 + Real dif = sphere.radius - ppResult.distance; // > 0 + Real arg = sum * dif; // sqr(sphere.radius) - sqr(ppResult.distance) + result.circle.radius = std::sqrt(arg); + return result; + } + else if (ppResult.distance == sphere.radius) + { + result.intersect = true; + result.isCircle = false; + result.point = sphere.center - ppResult.signedDistance * plane.normal; + return result; + } + else + { + result.intersect = false; + return result; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Triangle3.h new file mode 100644 index 000000000000..3f528e27acca --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrPlane3Triangle3.h @@ -0,0 +1,289 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (plane and triangle intersect + // at a single point [vertex]), 2 (plane and triangle intersect in a + // segment), or 3 (triangle is in the plane). When the number is 2, + // the segment is either interior to the triangle or is an edge of the + // triangle, the distinction stored in 'isInterior'. + int numIntersections; + bool isInterior; + }; + + Result operator()(Plane3 const& plane, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (plane and triangle intersect + // at a single point [vertex]), 2 (plane and triangle intersect in a + // segment), or 3 (triangle is in the plane). When the number is 2, + // the segment is either interior to the triangle or is an edge of the + // triangle, the distinction stored in 'isInterior'. + int numIntersections; + bool isInterior; + Vector3 point[3]; + }; + + Result operator()(Plane3 const& plane, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Plane3 const& plane, Triangle3 const& triangle) +{ + Result result; + + // Determine on which side of the plane the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 triangle in the plane + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(plane.normal, triangle.v[i]) - plane.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = true; + return result; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + } + else + { + result.numIntersections = 2; + result.isInterior = true; + } + break; + } + } + return result; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = false; + return result; + } + + if (numZero == 3) + { + result.intersect = true; + result.numIntersections = 3; + } + else + { + result.intersect = false; + result.numIntersections = 0; + + } + return result; +} + + + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Plane3 const& plane, Triangle3 const& triangle) +{ + Result result; + + // Determine on which side of the plane the vertices lie. The table of + // possibilities is listed next with n = numNegative, p = numPositive, and + // z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 triangle in the plane + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(plane.normal, triangle.v[i]) - plane.constant; + if (s[i] >(Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = true; + Real sign = (Real)3 - numPositive * (Real)2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (sign * s[i0] >(Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + Real t1 = s[i1] / (s[i1] - s[i0]); + Real t2 = s[i2] / (s[i2] - s[i0]); + result.point[0] = triangle.v[i1] + t1 * + (triangle.v[i0] - triangle.v[i1]); + result.point[1] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + break; + } + } + return result; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + } + else + { + result.numIntersections = 2; + result.isInterior = true; + Real t = s[i1] / (s[i1] - s[i2]); + result.point[1] = triangle.v[i1] + t * + (triangle.v[i2] - triangle.v[i1]); + } + break; + } + } + return result; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = false; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] != (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i1]; + result.point[1] = triangle.v[i2]; + break; + } + } + return result; + } + + if (numZero == 3) + { + result.intersect = true; + result.numIntersections = 3; + result.point[0] = triangle.v[0]; + result.point[1] = triangle.v[1]; + result.point[2] = triangle.v[2]; + } + else + { + result.intersect = false; + result.numIntersections = 0; + + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2AlignedBox2.h new file mode 100644 index 000000000000..cab4ffef1eb6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2AlignedBox2.h @@ -0,0 +1,150 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, AlignedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Vector2 const& boxExtent, + Result& result); +}; + +template +class FIQuery, AlignedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Vector2 const& boxExtent, + Result& result); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + Ray2 const& ray, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector2 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + return result; +} + +template +void TIQuery, AlignedBox2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Vector2 const& boxExtent, Result& result) +{ + for (int i = 0; i < 2; ++i) + { + if (std::abs(rayOrigin[i]) > boxExtent[i] + && rayOrigin[i] * rayDirection[i] >= (Real)0) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox2>::DoQuery(rayOrigin, + rayDirection, boxExtent, result); +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + Ray2 const& ray, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector2 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, AlignedBox2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Vector2 const& boxExtent, Result& result) +{ + FIQuery, AlignedBox2>::DoQuery(rayOrigin, + rayDirection, boxExtent, result); + + if (result.intersect) + { + // The line containing the ray intersects the box; the t-interval is + // [t0,t1]. The ray intersects the box as long as [t0,t1] overlaps + // the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Arc2.h new file mode 100644 index 000000000000..03a499152541 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Arc2.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace gte +{ + +template +class TIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Arc2 const& arc); +}; + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Ray2 const& ray, Arc2 const& arc); +}; + + +template +typename TIQuery, Arc2>::Result +TIQuery, Arc2>::operator()( + Ray2 const& ray, Arc2 const& arc) +{ + Result result; + FIQuery, Arc2> raQuery; + auto raResult = raQuery(ray, arc); + result.intersect = raResult.intersect; + return result; +} + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Ray2 const& ray, Arc2 const& arc) +{ + Result result; + + FIQuery, Circle2> rcQuery; + Circle2 circle(arc.center, arc.radius); + auto rcResult = rcQuery(ray, circle); + if (rcResult.intersect) + { + // Test whether ray-circle intersections are on the arc. + result.numIntersections = 0; + for (int i = 0; i < rcResult.numIntersections; ++i) + { + if (arc.Contains(rcResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = rcResult.parameter[i]; + result.point[result.numIntersections++] + = rcResult.point[i]; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Circle2.h new file mode 100644 index 000000000000..5bac060c4087 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Circle2.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Circle2 const& circle); +}; + +template +class FIQuery, Circle2> + : + public FIQuery, Circle2> +{ +public: + struct Result + : + public FIQuery, Circle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, Circle2 const& circle); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Circle2 const& circle, + Result& result); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Ray2 const& ray, Circle2 const& circle) +{ + Result result; + FIQuery, Circle2> rcQuery; + result.intersect = rcQuery(ray, circle).intersect; + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Ray2 const& ray, Circle2 const& circle) +{ + Result result; + DoQuery(ray.origin, ray.direction, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Circle2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Circle2 const& circle, Result& result) +{ + FIQuery, Circle2>::DoQuery(rayOrigin, + rayDirection, circle, result); + + if (result.intersect) + { + // The line containing the ray intersects the disk; the t-interval is + // [t0,t1]. The ray intersects the disk as long as [t0,t1] overlaps + // the ray t-interval [0,+infinity). + std::array rayInterval = {{ (Real)0, std::numeric_limits::max() }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2OrientedBox2.h new file mode 100644 index 000000000000..a13b679b5432 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2OrientedBox2.h @@ -0,0 +1,106 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, OrientedBox2 const& box); +}; + +template +class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, OrientedBox2 const& box); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + Ray2 const& ray, OrientedBox2 const& box) +{ + // Transform the ray to the oriented-box coordinate system. + Vector2 diff = ray.origin - box.center; + Vector2 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 rayDirection = Vector2 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + Ray2 const& ray, OrientedBox2 const& box) +{ + // Transform the ray to the oriented-box coordinate system. + Vector2 diff = ray.origin - box.center; + Vector2 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 rayDirection = Vector2 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Ray2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Ray2.h new file mode 100644 index 000000000000..686ce518938b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Ray2.h @@ -0,0 +1,232 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (rays intersect in a + // single point), 2 (rays are collinear and intersect in a segment; + // ray directions are opposite of each other), or + // std::numeric_limits::max() (intersection is a ray; ray + // directions are the same). + int numIntersections; + }; + + Result operator()(Ray2 const& ray0, Ray2 const& ray1); +}; + +template +class FIQuery, Ray2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (rays intersect in a + // single point), 2 (rays are collinear and intersect in a segment; + // ray directions are opposite of each other), or + // std::numeric_limits::max() (intersection is a ray; ray + // directions are the same). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] = ray0.origin + ray0Parameter[0] * ray0.direction + // = ray1.origin + ray1Parameter[0] * ray1.direction + // If numIntersections is 2, the segment of intersection is formed by + // the ray origins, + // ray0Parameter[0] = ray1Parameter[0] = 0 + // point[0] = ray0.origin + // = ray1.origin + ray1Parameter[1] * ray1.direction + // point[1] = ray1.origin + // = ray0.origin + ray0Parameter[1] * ray0.direction + // where ray0Parameter[1] >= 0 and ray1Parameter[1] >= 0. + // If numIntersections is maxInt, let + // ray1.origin = ray0.origin + t * ray0.direction + // then + // ray0Parameter[] = { max(t,0), +maxReal } + // ray1Parameter[] = { -min(t,0), +maxReal } + // point[0] = ray0.origin + ray0Parameter[0] * ray0.direction + Real ray0Parameter[2], ray1Parameter[2]; + Vector2 point[2]; + }; + + Result operator()(Ray2 const& ray0, Ray2 const& ray1); +}; + + +template +typename TIQuery, Ray2>::Result +TIQuery, Ray2>::operator()( + Ray2 const& ray0, Ray2 const& ray1) +{ + Result result; + FIQuery, Line2> llQuery; + Line2 line0(ray0.origin, ray0.direction); + Line2 line1(ray1.origin, ray1.direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the rays. + if (llResult.line0Parameter[0] >= (Real)0 + && llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + if (Dot(ray0.direction, ray1.direction) > (Real)0) + { + // The rays are collinear and in the same direction, so they must + // overlap. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + } + else + { + // The rays are collinear but in opposite directions. Test + // whether they overlap. Ray0 has interval [0,+infinity) and + // ray1 has interval (-infinity,t] relative to ray0.direction. + Vector2 diff = ray1.origin - ray0.origin; + Real t = Dot(ray0.direction, diff); + if (t > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + } + else if (t < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // t == 0 + { + result.intersect = true; + result.numIntersections = 1; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + +template +typename FIQuery, Ray2>::Result +FIQuery, Ray2>::operator()( + Ray2 const& ray0, Ray2 const& ray1) +{ + Result result; + FIQuery, Line2> llQuery; + Line2 line0(ray0.origin, ray0.direction); + Line2 line1(ray1.origin, ray1.direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the rays. + if (llResult.line0Parameter[0] >= (Real)0 + && llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + result.ray0Parameter[0] = llResult.line0Parameter[0]; + result.ray1Parameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute t for which ray1.origin = ray0.origin + t*ray0.direction. + Real maxReal = std::numeric_limits::max(); + Vector2 diff = ray1.origin - ray0.origin; + Real t = Dot(ray0.direction, diff); + if (Dot(ray0.direction, ray1.direction) > (Real)0) + { + // The rays are collinear and in the same direction, so they must + // overlap. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + if (t >= (Real)0) + { + result.ray0Parameter[0] = t; + result.ray0Parameter[1] = maxReal; + result.ray1Parameter[0] = (Real)0; + result.ray1Parameter[1] = maxReal; + result.point[0] = ray1.origin; + } + else + { + result.ray0Parameter[0] = (Real)0; + result.ray0Parameter[1] = maxReal; + result.ray1Parameter[0] = -t; + result.ray1Parameter[1] = maxReal; + result.point[0] = ray0.origin; + } + } + else + { + // The rays are collinear but in opposite directions. Test + // whether they overlap. Ray0 has interval [0,+infinity) and + // ray1 has interval (-infinity,t1] relative to ray0.direction. + if (t >= (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + result.ray0Parameter[0] = (Real)0; + result.ray0Parameter[1] = t; + result.ray1Parameter[0] = (Real)0; + result.ray1Parameter[1] = t; + result.point[0] = ray0.origin; + result.point[1] = ray1.origin; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Segment2.h new file mode 100644 index 000000000000..7959c560ae1c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Segment2.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (ray and segment intersect in + // a single point), or 2 (ray and segment are collinear and intersect + // in a segment). + int numIntersections; + }; + + Result operator()(Ray2 const& ray, Segment2 const& segment); +}; + +template +class FIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (ray and segment intersect in + // a single point), or 2 (ray and segment are collinear and intersect + // in a segment). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] = ray.origin + rayParameter[0] * ray.direction + // = segment.center + segmentParameter[0] * segment.direction + // If numIntersections is 2, the endpoints of the segment of + // intersection are + // point[i] = ray.origin + rayParameter[i] * ray.direction + // = segment.center + segmentParameter[i] * segment.direction + // with rayParameter[0] <= rayParameter[1] and + // segmentParameter[0] <= segmentParameter[1]. + Real rayParameter[2], segmentParameter[2]; + Vector2 point[2]; + }; + + Result operator()(Ray2 const& ray, Segment2 const& segment); +}; + + +template +typename TIQuery, Segment2>::Result +TIQuery, Segment2>::operator()( + Ray2 const& ray, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + Line2 line0(ray.origin, ray.direction); + Line2 line1(segOrigin, segDirection); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray and segment. + if (llResult.line0Parameter[0] >= (Real)0 + && std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of the right-most point of the segment + // relative to the ray direction. + Vector2 diff = segOrigin - ray.origin; + Real t = Dot(ray.direction, diff) + segExtent; + if (t > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + } + else if (t < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // t == 0 + { + result.intersect = true; + result.numIntersections = 1; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + +template +typename FIQuery, Segment2>::Result +FIQuery, Segment2>::operator()( + Ray2 const& ray, Segment2 const& segment) +{ + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + Line2 line0(ray.origin, ray.direction); + Line2 line1(segOrigin, segDirection); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray and segment. + if (llResult.line0Parameter[0] >= (Real)0 + && std::abs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.rayParameter[0] = llResult.line0Parameter[0]; + result.segmentParameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute t for which segment.origin = ray.origin + t*ray.direction. + Vector2 diff = segOrigin - ray.origin; + Real t = Dot(ray.direction, diff); + + // Get the ray interval. + std::array interval0 = + { + (Real)0, std::numeric_limits::max() + }; + + // Compute the location of the segment endpoints relative to the ray. + std::array interval1 = {{ t - segExtent, t + segExtent }}; + + // Compute the intersection of [0,+infinity) and [tmin,tmax]. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.intersect) + { + result.intersect = true; + result.numIntersections = iiResult.numIntersections; + for (int i = 0; i < iiResult.numIntersections; ++i) + { + result.rayParameter[i] = iiResult.overlap[i]; + result.segmentParameter[i] = iiResult.overlap[i] - t; + result.point[i] = ray.origin + + result.rayParameter[i] * ray.direction; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Triangle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Triangle2.h new file mode 100644 index 000000000000..68761c8249d9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay2Triangle2.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace gte +{ + +template +class TIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Triangle2 const& triangle); +}; + +template +class FIQuery, Triangle2> + : + public FIQuery, Triangle2> +{ +public: + struct Result + : + public FIQuery, Triangle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, Triangle2 const& triangle); + +protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Triangle2 const& triangle, + Result& result); +}; + + +template +typename TIQuery, Triangle2>::Result +TIQuery, Triangle2>::operator()( + Ray2 const& ray, Triangle2 const& triangle) +{ + Result result; + FIQuery, Triangle2> rtQuery; + result.intersect = rtQuery(ray, triangle).intersect; + return result; +} + +template +typename FIQuery, Triangle2>::Result +FIQuery, Triangle2>::operator()( + Ray2 const& ray, Triangle2 const& triangle) +{ + Result result; + DoQuery(ray.origin, ray.direction, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Triangle2>::DoQuery( + Vector2 const& rayOrigin, Vector2 const& rayDirection, + Triangle2 const& triangle, Result& result) +{ + FIQuery, Triangle2>::DoQuery(rayOrigin, + rayDirection, triangle, result); + + if (result.intersect) + { + // The line containing the ray intersects the disk; the t-interval is + // [t0,t1]. The ray intersects the disk as long as [t0,t1] overlaps + // the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + result.parameter = iiQuery(result.parameter, rayInterval).overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3AlignedBox3.h new file mode 100644 index 000000000000..987603fe6243 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3AlignedBox3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, AlignedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector3 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, Vector3 const& rayDirection, + Vector3 const& boxExtent, Result& result) + { + for (int i = 0; i < 3; ++i) + { + if (std::abs(rayOrigin[i]) > boxExtent[i] && rayOrigin[i] * rayDirection[i] >= (Real)0) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox3>::DoQuery(rayOrigin, rayDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector3 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = ray.origin + result.lineParameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, Vector3 const& rayDirection, + Vector3 const& boxExtent, Result& result) + { + FIQuery, AlignedBox3>::DoQuery(rayOrigin, rayDirection, boxExtent, result); + if (result.intersect) + { + // The line containing the ray intersects the box; the + // t-interval is [t0,t1]. The ray intersects the box as long + // as [t0,t1] overlaps the ray t-interval (0,+infinity). + if (result.lineParameter[1] >= (Real)0) + { + if (result.lineParameter[0] < (Real)0) + { + result.lineParameter[0] = (Real)0; + } + } + else + { + result.intersect = false; + result.numPoints = 0; + } + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Capsule3.h new file mode 100644 index 000000000000..a03e37d11ad9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Capsule3.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Capsule3 const& capsule); +}; + +template +class FIQuery, Capsule3> + : + public FIQuery, Capsule3> +{ +public: + struct Result + : + public FIQuery, Capsule3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Capsule3 const& capsule); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Capsule3 const& capsule, + Result& result); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Ray3 const& ray, Capsule3 const& capsule) +{ + Result result; + DCPQuery, Segment3> rsQuery; + auto rsResult = rsQuery(ray, capsule.segment); + result.intersect = (rsResult.distance <= capsule.radius); + return result; +} + +template +typename FIQuery, Capsule3>::Result +FIQuery, Capsule3>::operator()( + Ray3 const& ray, Capsule3 const& capsule) +{ + Result result; + DoQuery(ray.origin, ray.direction, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Capsule3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Capsule3 const& capsule, Result& result) +{ + FIQuery, Capsule3>::DoQuery(rayOrigin, + rayDirection, capsule, result); + + if (result.intersect) + { + // The line containing the ray intersects the capsule; the t-interval + // is [t0,t1]. The ray intersects the capsule as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cone3.h new file mode 100644 index 000000000000..e64b0ff6272f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cone3.h @@ -0,0 +1,105 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the cone to be single sided and solid. + +namespace gte +{ + +template +class FIQuery, Cone3> + : + public FIQuery, Cone3> +{ +public: + struct Result + : + public FIQuery, Cone3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Cone3 const& cone); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Cone3 const& cone, + Result& result); +}; + + +template +typename FIQuery, Cone3>::Result +FIQuery, Cone3>::operator()(Ray3 const& ray, + Cone3 const& cone) +{ + Result result; + DoQuery(ray.origin, ray.direction, cone, result); + switch (result.type) + { + case 1: // point + result.point[0] = ray.origin + result.parameter[0] * ray.direction; + result.point[1] = result.point[0]; + break; + case 2: // segment + result.point[0] = ray.origin + result.parameter[0] * ray.direction; + result.point[1] = ray.origin + result.parameter[1] * ray.direction; + break; + case 3: // ray + result.point[0] = ray.origin + result.parameter[0] * ray.direction; + result.point[1] = ray.direction; + break; + default: // no intersection + break; + } + return result; +} + +template +void FIQuery, Cone3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Cone3 const& cone, Result& result) +{ + FIQuery, Cone3>::DoQuery(rayOrigin, + rayDirection, cone, result); + + if (result.intersect) + { + // The line containing the ray intersects the cone; the t-interval + // is [t0,t1]. The ray intersects the cone as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = {{ + (Real)0, std::numeric_limits::max() }}; + FIIntervalInterval iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.parameter = iiResult.overlap; + if (result.parameter[1] < std::numeric_limits::max()) + { + result.type = iiResult.numIntersections; + } + else + { + result.type = 3; + } + } + else + { + result.intersect = false; + result.type = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cylinder3.h new file mode 100644 index 000000000000..b44ff538052d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Cylinder3.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class FIQuery, Cylinder3> + : + public FIQuery, Cylinder3> +{ +public: + struct Result + : + public FIQuery, Cylinder3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Cylinder3 const& cylinder); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Cylinder3 const& cylinder, + Result& result); +}; + + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Ray3 const& ray, Cylinder3 const& cylinder) +{ + Result result; + DoQuery(ray.origin, ray.direction, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Cylinder3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Cylinder3 const& cylinder, Result& result) +{ + FIQuery, Cylinder3>::DoQuery(rayOrigin, + rayDirection, cylinder, result); + + if (result.intersect) + { + // The line containing the ray intersects the cylinder; the t-interval + // is [t0,t1]. The ray intersects the cylinder as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Ellipsoid3.h new file mode 100644 index 000000000000..30652203ce68 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Ellipsoid3.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, + Ellipsoid3 const& ellipsoid); +}; + +template +class FIQuery, Ellipsoid3> + : + public FIQuery, Ellipsoid3> +{ +public: + struct Result + : + public FIQuery, Ellipsoid3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, + Ellipsoid3 const& ellipsoid); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Ellipsoid3 const& ellipsoid, + Result& result); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Ray3 const& ray, Ellipsoid3 const& ellipsoid) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = ray.origin - ellipsoid.center; + Vector3 matDir = M*ray.direction; + Vector3 matDiff = M*diff; + Real a2 = Dot(ray.direction, matDir); + Real a1 = Dot(ray.direction, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + Real discr = a1*a1 - a0*a2; + if (discr >= (Real)0) + { + // Test whether ray origin is inside ellipsoid. + if (a0 <= (Real)0) + { + result.intersect = true; + } + else + { + // At this point, Q(0) = a0 > 0 and Q(t) has real roots. It is + // also the case that a2 > 0, since M is positive definite, + // implying that D^T*M*D > 0 for any nonzero vector D. Thus, + // an intersection occurs only when Q'(0) < 0. + result.intersect = (a1 < (Real)0); + } + } + else + { + // No intersection if Q(t) has no real roots. + result.intersect = false; + } + + return result; +} + +template +typename FIQuery, Ellipsoid3>::Result +FIQuery, Ellipsoid3>::operator()( + Ray3 const& ray, Ellipsoid3 const& ellipsoid) +{ + Result result; + DoQuery(ray.origin, ray.direction, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Ellipsoid3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Ellipsoid3 const& ellipsoid, Result& result) +{ + FIQuery, Ellipsoid3>::DoQuery(rayOrigin, + rayDirection, ellipsoid, result); + + if (result.intersect) + { + // The line containing the ray intersects the ellipsoid; the + // t-interval is [t0,t1]. The ray intersects the capsule as long as + // [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3OrientedBox3.h new file mode 100644 index 000000000000..f28f5bef9129 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3OrientedBox3.h @@ -0,0 +1,96 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector3 diff = ray.origin - box.center; + Vector3 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 rayDirection = Vector3 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]), + Dot(ray.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector3 diff = ray.origin - box.center; + Vector3 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 rayDirection = Vector3 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]), + Dot(ray.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = ray.origin + result.lineParameter[i] * ray.direction; + } + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Plane3.h new file mode 100644 index 000000000000..1e29e7c6b978 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Plane3.h @@ -0,0 +1,117 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Plane3 const& plane); +}; + +template +class FIQuery, Plane3> + : + public FIQuery, Plane3> +{ +public: + struct Result + : + public FIQuery, Plane3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Plane3 const& plane); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Plane3 const& plane, + Result& result); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Ray3 const& ray, Plane3 const& plane) +{ + Result result; + + // Compute the (signed) distance from the ray origin to the plane. + DCPQuery, Plane3> vpQuery; + auto vpResult = vpQuery(ray.origin, plane); + + Real DdN = Dot(ray.direction, plane.normal); + if (DdN > (Real)0) + { + // The ray is not parallel to the plane and is directed toward the + // +normal side of the plane. + result.intersect = (vpResult.signedDistance <= (Real)0); + } + else if (DdN < (Real)0) + { + // The ray is not parallel to the plane and is directed toward the + // -normal side of the plane. + result.intersect = (vpResult.signedDistance >= (Real)0); + } + else + { + // The ray and plane are parallel. + result.intersect = (vpResult.distance == (Real)0); + } + + return result; +} + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Ray3 const& ray, Plane3 const& plane) +{ + Result result; + DoQuery(ray.origin, ray.direction, plane, result); + if (result.intersect) + { + result.point = ray.origin + result.parameter * ray.direction; + } + return result; +} + +template +void FIQuery, Plane3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Plane3 const& plane, Result& result) +{ + FIQuery, Plane3>::DoQuery(rayOrigin, + rayDirection, plane, result); + if (result.intersect) + { + // The line intersects the plane in a point that might not be on the + // ray. + if (result.parameter < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Sphere3.h new file mode 100644 index 000000000000..1b1324d06a13 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Sphere3.h @@ -0,0 +1,130 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> + : + public FIQuery, Sphere3> +{ +public: + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Sphere3 const& sphere); + +protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Sphere3 const& sphere, + Result& result); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Ray3 const& ray, Sphere3 const& sphere) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 diff = ray.origin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + if (a0 <= (Real)0) + { + // P is inside the sphere. + result.intersect = true; + return result; + } + // else: P is outside the sphere + + Real a1 = Dot(ray.direction, diff); + if (a1 >= (Real)0) + { + result.intersect = false; + return result; + } + + // Intersection occurs when Q(t) has real roots. + Real discr = a1*a1 - a0; + result.intersect = (discr >= (Real)0); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Ray3 const& ray, Sphere3 const& sphere) +{ + Result result; + DoQuery(ray.origin, ray.direction, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; +} + +template +void FIQuery, Sphere3>::DoQuery( + Vector3 const& rayOrigin, Vector3 const& rayDirection, + Sphere3 const& sphere, Result& result) +{ + FIQuery, Sphere3>::DoQuery(rayOrigin, + rayDirection, sphere, result); + + if (result.intersect) + { + // The line containing the ray intersects the sphere; the t-interval + // is [t0,t1]. The ray intersects the sphere as long as [t0,t1] + // overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Triangle3.h new file mode 100644 index 000000000000..83a31cc80bf5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrRay3Triangle3.h @@ -0,0 +1,198 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + Result(); + + bool intersect; + Real parameter; + Real triangleBary[3]; + Vector3 point; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Ray3 const& ray, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = ray.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(ray.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(ray.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(ray.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether ray does. + Real QdN = -sign*Dot(diff, normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + result.intersect = true; + return result; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + +template +FIQuery, Triangle3>::Result::Result() + : + parameter((Real)0) +{ + triangleBary[0] = (Real)0; + triangleBary[1] = (Real)0; + triangleBary[2] = (Real)0; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Ray3 const& ray, Triangle3 const& triangle) +{ + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = ray.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(ray.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(ray.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(ray.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether ray does. + Real QdN = -sign*Dot(diff, normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + result.intersect = true; + Real inv = ((Real)1) / DdN; + result.parameter = QdN*inv; + result.triangleBary[1] = DdQxE2*inv; + result.triangleBary[2] = DdE1xQ*inv; + result.triangleBary[0] = (Real)1 - result.triangleBary[1] + - result.triangleBary[2]; + result.point = ray.origin + + result.parameter * ray.direction; + return result; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2AlignedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2AlignedBox2.h new file mode 100644 index 000000000000..0ffd55fa8d33 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2AlignedBox2.h @@ -0,0 +1,175 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + + +template +class TIQuery, AlignedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Vector2 const& boxExtent, Result& result); +}; + +template +class FIQuery, AlignedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // The base class parameter[] values are t-values for the + // segment parameterization (1-t)*p[0] + t*p[1], where t in [0,1]. + // The values in this class are s-values for the centered form + // C + s * D, where s in [-e,e] and e is the extent of the segment. + std::array cdeParameter; + }; + + Result operator()(Segment2 const& segment, + AlignedBox2 const& box); + +protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Vector2 const& boxExtent, Result& result); +}; + + +template +typename TIQuery, AlignedBox2>::Result +TIQuery, AlignedBox2>::operator()( + Segment2 const& segment, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box coordinate + // system. + Vector2 transformedP0 = segment.p[0] - boxCenter; + Vector2 transformedP1 = segment.p[1] - boxCenter; + Segment2 transformedSegment(transformedP0, transformedP1); + Vector2 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + return result; +} + +template +void TIQuery, AlignedBox2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Vector2 const& boxExtent, Result& result) +{ + for (int i = 0; i < 2; ++i) + { + Real lhs = std::abs(segOrigin[i]); + Real rhs = boxExtent[i] + segExtent * std::abs(segDirection[i]); + if (lhs > rhs) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox2>::DoQuery(segOrigin, + segDirection, boxExtent, result); +} + +template +typename FIQuery, AlignedBox2>::Result +FIQuery, AlignedBox2>::operator()( + Segment2 const& segment, AlignedBox2 const& box) +{ + // Get the centered form of the aligned box. The axes are implicitly + // Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box coordinate + // system. + Vector2 transformedP0 = segment.p[0] - boxCenter; + Vector2 transformedP1 = segment.p[1] - boxCenter; + Segment2 transformedSegment(transformedP0, transformedP1); + Vector2 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + // Compute the segment in the aligned-box coordinate system and then + // translate it back to the original coordinates using the box cener. + result.point[i] = boxCenter + (segOrigin + result.parameter[i] * segDirection); + result.cdeParameter[i] = result.parameter[i]; + + // Convert the parameters from the centered form to the endpoint form. + result.parameter[i] = (result.parameter[i] / segExtent + (Real)1) * (Real)0.5; + } + return result; +} + +template +void FIQuery, AlignedBox2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Vector2 const& boxExtent, Result& result) +{ + FIQuery, AlignedBox2>::DoQuery(segOrigin, + segDirection, boxExtent, result); + + if (result.intersect) + { + // The line containing the segment intersects the box; the t-interval + // is [t0,t1]. The segment intersects the box as long as [t0,t1] + // overlaps the segment t-interval [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Arc2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Arc2.h new file mode 100644 index 000000000000..fb8a0bc97398 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Arc2.h @@ -0,0 +1,89 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/06/22) + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace gte +{ + +template +class TIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, Arc2 const& arc); +}; + +template +class FIQuery, Arc2> +{ +public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Segment2 const& segment, Arc2 const& arc); +}; + + +template +typename TIQuery, Arc2>::Result +TIQuery, Arc2>::operator()( + Segment2 const& segment, Arc2 const& arc) +{ + Result result; + FIQuery, Arc2> saQuery; + auto saResult = saQuery(segment, arc); + result.intersect = saResult.intersect; + return result; +} + +template +typename FIQuery, Arc2>::Result +FIQuery, Arc2>::operator()( + Segment2 const& segment, Arc2 const& arc) +{ + Result result; + result.intersect = false; + result.numIntersections = 0; + + FIQuery, Circle2> scQuery; + Circle2 circle(arc.center, arc.radius); + auto scResult = scQuery(segment, circle); + if (scResult.intersect) + { + // Test whether line-circle intersections are on the arc. + for (int i = 0; i < scResult.numIntersections; ++i) + { + if (arc.Contains(scResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = scResult.parameter[i]; + result.point[result.numIntersections++] + = scResult.point[i]; + } + } + } + + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Circle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Circle2.h new file mode 100644 index 000000000000..d550caaaa3d1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Circle2.h @@ -0,0 +1,107 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace gte +{ + +template +class TIQuery, Circle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, + Circle2 const& circle); +}; + +template +class FIQuery, Circle2> + : + public FIQuery, Circle2> +{ +public: + struct Result + : + public FIQuery, Circle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + Circle2 const& circle); + +protected: + void DoQuery(Vector2 const& segyOrigin, + Vector2 const& segyDirection, Real segExtent, + Circle2 const& circle, Result& result); +}; + + +template +typename TIQuery, Circle2>::Result +TIQuery, Circle2>::operator()( + Segment2 const& segment, Circle2 const& circle) +{ + Result result; + FIQuery, Circle2> scQuery; + result.intersect = scQuery(segment, circle).intersect; + return result; +} + +template +typename FIQuery, Circle2>::Result +FIQuery, Circle2>::operator()( + Segment2 const& segment, Circle2 const& circle) +{ + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Circle2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Circle2 const& circle, Result& result) +{ + FIQuery, Circle2>::DoQuery(segOrigin, + segDirection, circle, result); + + if (result.intersect) + { + // The line containing the segment intersects the disk; the t-interval + // is [t0,t1]. The segment intersects the disk as long as [t0,t1] + // overlaps the segment t-interval [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2OrientedBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2OrientedBox2.h new file mode 100644 index 000000000000..d2ef12984d3e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2OrientedBox2.h @@ -0,0 +1,124 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the four edges of +// the box. + +namespace gte +{ + +template +class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> +{ +public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + OrientedBox2 const& box); +}; + +template +class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> +{ +public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // The base class parameter[] values are t-values for the + // segment parameterization (1-t)*p[0] + t*p[1], where t in [0,1]. + // The values in this class are s-values for the centered form + // C + s * D, where s in [-e,e] and e is the extent of the segment. + std::array cdeParameter; + }; + + Result operator()(Segment2 const& segment, + OrientedBox2 const& box); +}; + + +template +typename TIQuery, OrientedBox2>::Result +TIQuery, OrientedBox2>::operator()( + Segment2 const& segment, OrientedBox2 const& box) +{ + // Transform the segment to the oriented-box coordinate system. + Vector2 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector2 diff = tmpOrigin - box.center; + Vector2 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + return result; +} + +template +typename FIQuery, OrientedBox2>::Result +FIQuery, OrientedBox2>::operator()( + Segment2 const& segment, OrientedBox2 const& box) +{ + // Transform the segment to the oriented-box coordinate system. + Vector2 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector2 diff = tmpOrigin - box.center; + Vector2 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + // Compute the segment in the aligned-box coordinate system and then + // translate it back to the original coordinates using the box cener. + result.point[i] = box.center + (segOrigin + result.parameter[i] * segDirection); + result.cdeParameter[i] = result.parameter[i]; + + // Convert the parameters from the centered form to the endpoint form. + result.parameter[i] = (result.parameter[i] / segExtent + (Real)1) * (Real)0.5; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Segment2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Segment2.h new file mode 100644 index 000000000000..53aea0e8297e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Segment2.h @@ -0,0 +1,197 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2019/01/11) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (segments intersect in a + // single point), or 2 (segments are collinear and intersect in a + // segment). + int numIntersections; + }; + + Result operator()(Segment2 const& segment0, + Segment2 const& segment1); +}; + +template +class FIQuery, Segment2> +{ +public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (segments intersect in a + // a single point), or 2 (segments are collinear and intersect + // in a segment). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] + // = segment0.origin + segment0Parameter[0] * segment0.direction + // = segment1.origin + segment1Parameter[0] * segment1.direction + // If numIntersections is 2, the endpoints of the segment of + // intersection are + // point[i] + // = segment0.origin + segment0Parameter[i] * segment0.direction + // = segment1.origin + segment1Parameter[i] * segment1.direction + // with segment0Parameter[0] <= segment0Parameter[1] and + // segment1Parameter[0] <= segment1Parameter[1]. + Real segment0Parameter[2], segment1Parameter[2]; + Vector2 point[2]; + }; + + Result operator()(Segment2 const& segment0, + Segment2 const& segment1); +}; + + +template +typename TIQuery, Segment2>::Result +TIQuery, Segment2>::operator()( + Segment2 const& segment0, Segment2 const& segment1) +{ + Result result; + Vector2 seg0Origin, seg0Direction, seg1Origin, seg1Direction; + Real seg0Extent, seg1Extent; + segment0.GetCenteredForm(seg0Origin, seg0Direction, seg0Extent); + segment1.GetCenteredForm(seg1Origin, seg1Direction, seg1Extent); + + FIQuery, Line2> llQuery; + Line2 line0(seg0Origin, seg0Direction); + Line2 line1(seg1Origin, seg1Direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segments. + if (std::abs(llResult.line0Parameter[0]) <= seg0Extent + && std::abs(llResult.line1Parameter[0]) <= seg1Extent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of segment1 endpoints relative to segment0. + Vector2 diff = seg1Origin - seg0Origin; + Real t = Dot(seg0Direction, diff); + + // Get the parameter intervals of the segments relative to segment0. + std::array interval0 = {{ -seg0Extent, seg0Extent }}; + std::array interval1 = {{ t - seg1Extent, t + seg1Extent }}; + + // Compute the intersection of the intervals. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + +template +typename FIQuery, Segment2>::Result +FIQuery, Segment2>::operator()( + Segment2 const& segment0, Segment2 const& segment1) +{ + Result result; + Vector2 seg0Origin, seg0Direction, seg1Origin, seg1Direction; + Real seg0Extent, seg1Extent; + segment0.GetCenteredForm(seg0Origin, seg0Direction, seg0Extent); + segment1.GetCenteredForm(seg1Origin, seg1Direction, seg1Extent); + + FIQuery, Line2> llQuery; + Line2 line0(seg0Origin, seg0Direction); + Line2 line1(seg1Origin, seg1Direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segments. + if (std::abs(llResult.line0Parameter[0]) <= seg0Extent + && std::abs(llResult.line1Parameter[0]) <= seg1Extent) + { + result.intersect = true; + result.numIntersections = 1; + result.segment0Parameter[0] = llResult.line0Parameter[0]; + result.segment1Parameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of segment1 endpoints relative to segment0. + Vector2 diff = seg1Origin - seg0Origin; + Real t = Dot(seg0Direction, diff); + + // Get the parameter intervals of the segments relative to segment0. + std::array interval0 = {{ -seg0Extent, seg0Extent }}; + std::array interval1 = {{ t - seg1Extent, t + seg1Extent }}; + + // Compute the intersection of the intervals. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.intersect) + { + result.intersect = true; + result.numIntersections = iiResult.numIntersections; + for (int i = 0; i < iiResult.numIntersections; ++i) + { + result.segment0Parameter[i] = iiResult.overlap[i]; + result.segment1Parameter[i] = iiResult.overlap[i] - t; + result.point[i] = seg0Origin + + result.segment0Parameter[i] * seg0Direction; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Triangle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Triangle2.h new file mode 100644 index 000000000000..ab55bd991f17 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment2Triangle2.h @@ -0,0 +1,104 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace gte +{ + +template +class TIQuery, Triangle2> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, + Triangle2 const& triangle); +}; + +template +class FIQuery , Triangle2> + : + public FIQuery, Triangle2> +{ +public: + struct Result + : + public FIQuery, Triangle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, + Triangle2 const& triangle); + +protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Triangle2 const& triangle, Result& result); +}; + + +template +typename TIQuery, Triangle2>::Result +TIQuery, Triangle2>::operator()( + Segment2 const& segment, Triangle2 const& triangle) +{ + Result result; + FIQuery, Triangle2> stQuery; + result.intersect = stQuery(segment, triangle).intersect; + return result; +} + +template +typename FIQuery, Triangle2>::Result +FIQuery, Triangle2>::operator()( + Segment2 const& segment, Triangle2 const& triangle) +{ + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Triangle2>::DoQuery( + Vector2 const& segOrigin, Vector2 const& segDirection, + Real segExtent, Triangle2 const& triangle, Result& result) +{ + FIQuery, Triangle2>::DoQuery(segOrigin, + segDirection, triangle, result); + + if (result.intersect) + { + // The line containing the segment intersects the disk; the t-interval + // is [t0,t1]. The segment intersects the disk as long as [t0,t1] + // overlaps the segment t-interval [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + result.parameter = iiQuery(result.parameter, segInterval).overlap; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3AlignedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3AlignedBox3.h new file mode 100644 index 000000000000..31c7a8795c51 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3AlignedBox3.h @@ -0,0 +1,155 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/28) + +#pragma once + +#include +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, AlignedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector3 transformedP0 = segment.p[0] - boxCenter; + Vector3 transformedP1 = segment.p[1] - boxCenter; + Segment3 transformedSegment(transformedP0, transformedP1); + Vector3 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Vector3 const& boxExtent, Result& result) + { + for (int i = 0; i < 3; ++i) + { + if (std::abs(segOrigin[i]) > boxExtent[i] + segExtent * std::abs(segDirection[i])) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox3>::DoQuery(segOrigin, segDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector3 transformedP0 = segment.p[0] - boxCenter; + Vector3 transformedP1 = segment.p[1] - boxCenter; + Segment3 transformedSegment(transformedP0, transformedP1); + Vector3 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + + // The segment origin is in aligned-box coordinates. Transform it + // back to the original space. + segOrigin += boxCenter; + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = segOrigin + result.lineParameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Vector3 const& boxExtent, Result& result) + { + FIQuery, AlignedBox3>::DoQuery(segOrigin, segDirection, boxExtent, result); + if (result.intersect) + { + // The line containing the segment intersects the box; the + // t-interval is [t0,t1]. The segment intersects the box as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + FIQuery, std::array> iiQuery; + + std::array interval0 = + { + result.lineParameter[0], result.lineParameter[1] + }; + + std::array interval1 = + { + -segExtent, segExtent + }; + + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.numIntersections > 0) + { + result.numPoints = iiResult.numIntersections; + for (int i = 0; i < result.numPoints; ++i) + { + result.lineParameter[i] = iiResult.overlap[i]; + } + } + else + { + result.intersect = false; + result.numPoints = 0; + } + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Capsule3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Capsule3.h new file mode 100644 index 000000000000..9a586e0b5815 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Capsule3.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace gte +{ + +template +class TIQuery, Capsule3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Capsule3 const& capsule); +}; + +template +class FIQuery, Capsule3> + : + public FIQuery, Capsule3> +{ +public: + struct Result + : + public FIQuery, Capsule3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Capsule3 const& capsule); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Capsule3 const& capsule, Result& result); +}; + + +template +typename TIQuery, Capsule3>::Result +TIQuery, Capsule3>::operator()( + Segment3 const& segment, Capsule3 const& capsule) +{ + Result result; + DCPQuery, Segment3> ssQuery; + auto ssResult = ssQuery(segment, capsule.segment); + result.intersect = (ssResult.distance <= capsule.radius); + return result; +} + +template +typename FIQuery, Capsule3>::Result +FIQuery, Capsule3>::operator()( + Segment3 const& segment, Capsule3 const& capsule) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Capsule3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Capsule3 const& capsule, Result& result) +{ + FIQuery, Capsule3>::DoQuery(segOrigin, + segDirection, capsule, result); + + if (result.intersect) + { + // The line containing the segment intersects the capsule; the + // t-interval is [t0,t1]. The segment intersects the capsule as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cone3.h new file mode 100644 index 000000000000..51d59f636552 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cone3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The queries consider the cone to be single sided and solid. + +namespace gte +{ + +template +class FIQuery, Cone3> + : + public FIQuery, Cone3> +{ +public: + struct Result + : + public FIQuery, Cone3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Cone3 const& cone); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Cone3 const& cone, Result& result); +}; + + +template +typename FIQuery, Cone3>::Result +FIQuery, Cone3>::operator()( + Segment3 const& segment, Cone3 const& cone) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, cone, result); + switch (result.type) + { + case 1: // point + result.point[0] = segOrigin + result.parameter[0] * segDirection; + result.point[1] = result.point[0]; + break; + case 2: // segment + result.point[0] = segOrigin + result.parameter[0] * segDirection; + result.point[1] = segOrigin + result.parameter[1] * segDirection; + break; + default: // no intersection + break; + } + return result; +} + +template +void FIQuery, Cone3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Cone3 const& cone, Result& result) +{ + FIQuery, Cone3>::DoQuery(segOrigin, + segDirection, cone, result); + + if (result.intersect) + { + // The line containing the segment intersects the cone; the + // t-interval is [t0,t1]. The segment intersects the cone as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = {{ -segExtent, segExtent }}; + FIIntervalInterval iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.parameter = iiResult.overlap; + result.type = iiResult.numIntersections; + } + else + { + result.intersect = false; + result.type = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cylinder3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cylinder3.h new file mode 100644 index 000000000000..ad4a70db5d5e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Cylinder3.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace gte +{ + +template +class FIQuery, Cylinder3> + : + public FIQuery, Cylinder3> +{ +public: + struct Result + : + public FIQuery, Cylinder3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Cylinder3 const& cylinder); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Cylinder3 const& cylinder, Result& result); +}; + + +template +typename FIQuery, Cylinder3>::Result +FIQuery, Cylinder3>::operator()( + Segment3 const& segment, Cylinder3 const& cylinder) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Cylinder3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Cylinder3 const& cylinder, Result& result) +{ + FIQuery, Cylinder3>::DoQuery(segOrigin, + segDirection, cylinder, result); + + if (result.intersect) + { + // The line containing the segment intersects the cylinder; the + // t-interval is [t0,t1]. The segment intersects the cylinder as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Ellipsoid3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Ellipsoid3.h new file mode 100644 index 000000000000..e84be9995973 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Ellipsoid3.h @@ -0,0 +1,187 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace gte +{ + +template +class TIQuery, Ellipsoid3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Ellipsoid3 const& ellipsoid); +}; + +template +class FIQuery, Ellipsoid3> + : + public FIQuery, Ellipsoid3> +{ +public: + struct Result + : + public FIQuery, Ellipsoid3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Ellipsoid3 const& ellipsoid); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Ellipsoid3 const& ellipsoid, Result& result); +}; + + +template +typename TIQuery, Ellipsoid3>::Result +TIQuery, Ellipsoid3>::operator()( + Segment3 const& segment, Ellipsoid3 const& ellipsoid) +{ + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the ellipsoid equation to obtain + // a quadratic equation Q(t) = a2*t^2 + 2*a1*t + a0 = 0, where + // a2 = D^T*M*D, a1 = D^T*M*(P-K), and a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = segOrigin - ellipsoid.center; + Vector3 matDir = M*segDirection; + Vector3 matDiff = M*diff; + Real a2 = Dot(segDirection, matDir); + Real a1 = Dot(segDirection, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + Real discr = a1*a1 - a0*a2; + if (discr >= (Real)0) + { + // Test whether ray origin is inside ellipsoid. + if (a0 <= (Real)0) + { + result.intersect = true; + } + else + { + // At this point, Q(0) = a0 > 0 and Q(t) has real roots. It is + // also the case that a2 > 0, since M is positive definite, + // implying that D^T*M*D > 0 for any nonzero vector D. + Real q, qder; + if (a1 >= (Real)0) + { + // Roots are possible only on [-e,0], e is the segment extent. + // At least one root occurs if Q(-e) <= 0 or if Q(-e) > 0 and + // Q'(-e) < 0. + q = a0 + segExtent*(((Real)-2)*a1 + a2*segExtent); + if (q <= (Real)0) + { + result.intersect = true; + } + else + { + qder = a1 - a2*segExtent; + result.intersect = (qder < (Real)0); + } + } + else + { + // Roots are only possible on [0,e], e is the segment extent. + // At least one root occurs if Q(e) <= 0 or if Q(e) > 0 and + // Q'(e) > 0. + q = a0 + segExtent*(((Real)2)*a1 + a2*segExtent); + if (q <= (Real)0.0) + { + result.intersect = true; + } + else + { + qder = a1 + a2*segExtent; + result.intersect = (qder < (Real)0); + } + } + } + } + else + { + // No intersection if Q(t) has no real roots. + result.intersect = false; + } + + return result; +} + +template +typename FIQuery, Ellipsoid3>::Result +FIQuery, Ellipsoid3>::operator()( + Segment3 const& segment, Ellipsoid3 const& ellipsoid) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Ellipsoid3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Ellipsoid3 const& ellipsoid, Result& result) +{ + FIQuery, Ellipsoid3>::DoQuery(segOrigin, + segDirection, ellipsoid, result); + + if (result.intersect) + { + // The line containing the segment intersects the ellipsoid; the + // t-interval is [t0,t1]. The segment intersects the ellipsoid as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3OrientedBox3.h new file mode 100644 index 000000000000..53f99456bcfb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3OrientedBox3.h @@ -0,0 +1,113 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/11/28) + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. The +// find-intersection queries use parametric clipping against the six faces of +// the box. The find-intersection queries use Liang-Barsky clipping. The +// queries consider the box to be a solid. The algorithms are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace gte +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector3 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector3 diff = tmpOrigin - box.center; + Vector3 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]), + Dot(tmpDirection, box.axis[2]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector3 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector3 diff = tmpOrigin - box.center; + Vector3 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]), + Dot(tmpDirection, box.axis[2]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + // Compute the intersection point in the oriented-box + // coordinate system. + Vector3 y = segOrigin + result.lineParameter[i] * segDirection; + + // Transform the intersection point to the original coordinate + // system. + result.point[i] = box.center; + for (int j = 0; j < 3; ++j) + { + result.point[i] += y[j] * box.axis[j]; + } + } + + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Plane3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Plane3.h new file mode 100644 index 000000000000..cee46cdbcbc7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Plane3.h @@ -0,0 +1,120 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TIQuery, Plane3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Plane3 const& plane); +}; + +template +class FIQuery, Plane3> + : + public FIQuery, Plane3> +{ +public: + struct Result + : + public FIQuery, Plane3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Plane3 const& plane); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Plane3 const& plane, Result& result); +}; + + +template +typename TIQuery, Plane3>::Result +TIQuery, Plane3>::operator()( + Segment3 const& segment, Plane3 const& plane) +{ + Result result; + + // Compute the (signed) distance from the segment endpoints to the plane. + DCPQuery, Plane3> vpQuery; + Real sdistance0 = vpQuery(segment.p[0], plane).signedDistance; + if (sdistance0 == (Real)0) + { + // Endpoint p[0] is on the plane. + result.intersect = true; + return result; + } + + Real sdistance1 = vpQuery(segment.p[1], plane).signedDistance; + if (sdistance1 == (Real)0) + { + // Endpoint p[1] is on the plane. + result.intersect = true; + return result; + } + + // Test whether the segment transversely intersects the plane. + result.intersect = (sdistance0 * sdistance1 < (Real)0); + return result; +} + +template +typename FIQuery, Plane3>::Result +FIQuery, Plane3>::operator()( + Segment3 const& segment, Plane3 const& plane) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, plane, result); + if (result.intersect) + { + result.point = segOrigin + result.parameter * segDirection; + } + return result; +} + +template +void FIQuery, Plane3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Plane3 const& plane, Result& result) +{ + FIQuery, Plane3>::DoQuery(segOrigin, + segDirection, plane, result); + if (result.intersect) + { + // The line intersects the plane in a point that might not be on the + // segment. + if (std::abs(result.parameter) > segExtent) + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Sphere3.h new file mode 100644 index 000000000000..801f247e6633 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Sphere3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Sphere3 const& sphere); +}; + +template +class FIQuery, Sphere3> + : + public FIQuery, Sphere3> +{ +public: + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, + Sphere3 const& sphere); + +protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Sphere3 const& sphere, Result& result); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Segment3 const& segment, Sphere3 const& sphere) +{ + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to obtain a + // quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where a1 = D^T*(P-C), + // and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Vector3 diff = segOrigin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(segDirection, diff); + Real discr = a1*a1 - a0; + if (discr < (Real)0) + { + result.intersect = false; + return result; + } + + Real tmp0 = segExtent*segExtent + a0; + Real tmp1 = ((Real)2)*a1*segExtent; + Real qm = tmp0 - tmp1; + Real qp = tmp0 + tmp1; + if (qm*qp <= (Real)0) + { + result.intersect = true; + return result; + } + + result.intersect = (qm > (Real)0 && std::abs(a1) < segExtent); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Segment3 const& segment, Sphere3 const& sphere) +{ + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; +} + +template +void FIQuery, Sphere3>::DoQuery( + Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Sphere3 const& sphere, Result& result) +{ + FIQuery, Sphere3>::DoQuery(segOrigin, + segDirection, sphere, result); + + if (result.intersect) + { + // The line containing the segment intersects the sphere; the + // t-interval is [t0,t1]. The segment intersects the sphere as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Triangle3.h new file mode 100644 index 000000000000..714243fd0083 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSegment3Triangle3.h @@ -0,0 +1,210 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Triangle3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, + Triangle3 const& triangle); +}; + +template +class FIQuery, Triangle3> +{ +public: + struct Result + { + Result(); + + bool intersect; + Real parameter; + Real triangleBary[3]; + Vector3 point; + }; + + Result operator()(Segment3 const& segment, + Triangle3 const& triangle); +}; + + +template +typename TIQuery, Triangle3>::Result +TIQuery, Triangle3>::operator()( + Segment3 const& segment, Triangle3 const& triangle) +{ + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + // Compute the offset origin, edges, and normal. + Vector3 diff = segOrigin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = segment direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(segDirection, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Segment and triangle are parallel, call it a "no intersection" + // even if the segment does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(segDirection, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(segDirection, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether segment does. + Real QdN = -sign*Dot(diff, normal); + Real extDdN = segExtent*DdN; + if (-extDdN <= QdN && QdN <= extDdN) + { + // Segment intersects triangle. + result.intersect = true; + return result; + } + // else: |t| > extent, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + +template +FIQuery, Triangle3>::Result::Result() + : + parameter((Real)0) +{ + triangleBary[0] = (Real)0; + triangleBary[1] = (Real)0; + triangleBary[2] = (Real)0; +} + +template +typename FIQuery, Triangle3>::Result +FIQuery, Triangle3>::operator()( + Segment3 const& segment, Triangle3 const& triangle) +{ + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + // Compute the offset origin, edges, and normal. + Vector3 diff = segOrigin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = segment direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(segDirection, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Segment and triangle are parallel, call it a "no intersection" + // even if the segment does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign*DotCross(segDirection, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign*DotCross(segDirection, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether segment does. + Real QdN = -sign*Dot(diff, normal); + Real extDdN = segExtent*DdN; + if (-extDdN <= QdN && QdN <= extDdN) + { + // Segment intersects triangle. + result.intersect = true; + Real inv = ((Real)1) / DdN; + result.parameter = QdN*inv; + result.triangleBary[1] = DdQxE2*inv; + result.triangleBary[2] = DdE1xQ*inv; + result.triangleBary[0] = (Real)1 - result.triangleBary[1] + - result.triangleBary[2]; + result.point = segOrigin + + result.parameter * segDirection; + return result; + } + // else: |t| > extent, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Cone3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Cone3.h new file mode 100644 index 000000000000..a51839360e11 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Cone3.h @@ -0,0 +1,341 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2019/02/15) + +#pragma once + +#include +#include +#include +#include +#include + +// The test-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionSphereCone.pdf +// +// The find-intersection returns a single point in the set of intersection +// when that intersection is not empty. + +namespace gte +{ + template + class TIQuery, Cone3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere, Cone3 const& cone) + { + Result result; + if (cone.minHeight > (Real)0) + { + if (cone.maxHeight < std::numeric_limits::max()) + { + result.intersect = DoQueryConeFrustum(sphere, cone); + } + else + { + result.intersect = DoQueryInfiniteTruncatedCone(sphere, cone); + } + } + else + { + if (cone.maxHeight < std::numeric_limits::max()) + { + result.intersect = DoQueryFiniteCone(sphere, cone); + } + else + { + result.intersect = DoQueryInfiniteCone(sphere, cone); + } + } + return result; + } + + private: + bool DoQueryInfiniteCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < -sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + Real sqrLengthCmV = Dot(CmV, CmV); + return sqrLengthCmV <= sphere.radius * sphere.radius; + } + } + + return false; + } + + bool DoQueryInfiniteTruncatedCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < cone.minHeight - sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + Vector3 D = CmV - cone.minHeight * cone.ray.direction; + Real lengthAxD = Length(Cross(cone.ray.direction, D)); + Real hminTanAngle = cone.minHeight * cone.tanAngle; + if (lengthAxD <= hminTanAngle) + { + return true; + } + + Real AdD = AdCmV - cone.minHeight; + Real diff = lengthAxD - hminTanAngle; + Real sqrLengthCmK = AdD * AdD + diff * diff; + return sqrLengthCmK <= sphere.radius * sphere.radius; + } + } + + return false; + } + + bool DoQueryFiniteCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < -sphere.radius) + { + return false; + } + + if (AdCmV > cone.maxHeight + sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + if (AdCmV <= cone.maxHeight - rSinAngle) + { + return true; + } + else + { + Vector3 barD = CmV - cone.maxHeight * cone.ray.direction; + Real lengthAxBarD = Length(Cross(cone.ray.direction, barD)); + Real hmaxTanAngle = cone.maxHeight * cone.tanAngle; + if (lengthAxBarD <= hmaxTanAngle) + { + return true; + } + + Real AdBarD = AdCmV - cone.maxHeight; + Real diff = lengthAxBarD - hmaxTanAngle; + Real sqrLengthCmBarK = AdBarD * AdBarD + diff * diff; + return sqrLengthCmBarK <= sphere.radius * sphere.radius; + } + } + else + { + Real sqrLengthCmV = Dot(CmV, CmV); + return sqrLengthCmV <= sphere.radius * sphere.radius; + } + } + } + + return false; + } + + bool DoQueryConeFrustum(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < cone.minHeight - sphere.radius) + { + return false; + } + + if (AdCmV > cone.maxHeight + sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= cone.minHeight - rSinAngle) + { + if (AdCmV <= cone.maxHeight - rSinAngle) + { + return true; + } + else + { + Vector3 barD = CmV - cone.maxHeight * cone.ray.direction; + Real lengthAxBarD = Length(Cross(cone.ray.direction, barD)); + Real hmaxTanAngle = cone.maxHeight * cone.tanAngle; + if (lengthAxBarD <= hmaxTanAngle) + { + return true; + } + + Real AdBarD = AdCmV - cone.maxHeight; + Real diff = lengthAxBarD - hmaxTanAngle; + Real sqrLengthCmBarK = AdBarD * AdBarD + diff * diff; + return sqrLengthCmBarK <= sphere.radius * sphere.radius; + } + } + else + { + Vector3 D = CmV - cone.minHeight * cone.ray.direction; + Real lengthAxD = Length(Cross(cone.ray.direction, D)); + Real hminTanAngle = cone.minHeight * cone.tanAngle; + if (lengthAxD <= hminTanAngle) + { + return true; + } + + Real AdD = AdCmV - cone.minHeight; + Real diff = lengthAxD - hminTanAngle; + Real sqrLengthCmK = AdD * AdD + diff * diff; + return sqrLengthCmK <= sphere.radius * sphere.radius; + } + } + } + + return false; + } + }; + + template + class FIQuery, Cone3> + { + public: + struct Result + { + // If an intersection occurs, it is potentially an infinite set. + // If the cone vertex is inside the sphere, 'point' is set to the + // cone vertex. If the sphere center is inside the cone, 'point' + // is set to the sphere center. Otherwise, 'point' is set to the + // cone point that is closest to the cone vertex and inside the + // sphere. + bool intersect; + Vector3 point; + }; + + Result operator()(Sphere3 const& sphere, Cone3 const& cone) + { + Result result; + + // Test whether the cone vertex is inside the sphere. + Vector3 diff = sphere.center - cone.ray.origin; + Real rSqr = sphere.radius * sphere.radius; + Real lenSqr = Dot(diff, diff); + if (lenSqr <= rSqr) + { + // The cone vertex is inside the sphere, so the sphere and + // cone intersect. + result.intersect = true; + result.point = cone.ray.origin; + return result; + } + + // Test whether the sphere center is inside the cone. + Real dot = Dot(diff, cone.ray.direction); + Real dotSqr = dot * dot; + if (dotSqr >= lenSqr * cone.cosAngleSqr && dot > (Real)0) + { + // The sphere center is inside cone, so the sphere and cone + // intersect. + result.intersect = true; + result.point = sphere.center; + return result; + } + + // The sphere center is outside the cone. The problem now reduces + // to computing an intersection between the circle and the ray in + // the plane containing the cone vertex and spanned by the cone + // axis and vector from the cone vertex to the sphere center. + + // The ray is parameterized by t * D + V with t >= 0, |D| = 1 and + // dot(A,D) = cos(angle). Also, D = e * A + f * (C - V). + // Substituting the ray equation into the sphere equation yields + // R^2 = |t * D + V - C|^2, so the quadratic for intersections is + // t^2 - 2 * dot(D, C - V) * t + |C - V|^2 - R^2 = 0. An + // intersection occurs if and only if the discriminant is + // nonnegative. This test becomes + // dot(D, C - V)^2 >= dot(C - V, C - V) - R^2 + // Note that if the right-hand side is nonpositive, then the + // inequality is true (the sphere contains V). This is already + // ruled out in the first block of code in this function. + + Real uLen = std::sqrt(std::max(lenSqr - dotSqr, (Real)0)); + Real test = cone.cosAngle * dot + cone.sinAngle * uLen; + Real discr = test * test - lenSqr + rSqr; + + if (discr >= (Real)0 && test >= (Real)0) + { + // Compute the point of intersection closest to the cone + // vertex. + result.intersect = true; + Real t = test - std::sqrt(std::max(discr, (Real)0)); + Vector3 B = diff - dot * cone.ray.direction; + Real tmp = cone.sinAngle / uLen; + result.point = t * (cone.cosAngle * cone.ray.direction + tmp * B); + } + else + { + result.intersect = false; + } + + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Frustum3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Frustum3.h new file mode 100644 index 000000000000..6a55e82ea368 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Frustum3.h @@ -0,0 +1,44 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class TIQuery, Frustum3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere, + Frustum3 const& frustum); +}; + + +template +typename TIQuery, Frustum3>::Result +TIQuery, Frustum3>::operator()( + Sphere3 const& sphere, Frustum3 const& frustum) +{ + Result result; + DCPQuery, Frustum3> vfQuery; + Real distance = vfQuery(sphere.center, frustum).distance; + result.intersect = (distance <= sphere.radius); + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Sphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Sphere3.h new file mode 100644 index 000000000000..bbc07cbef243 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Sphere3.h @@ -0,0 +1,152 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The queries consider the spheres to be solids. + +namespace gte +{ + +template +class TIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere0, + Sphere3 const& sphere1); +}; + +template +class FIQuery, Sphere3> +{ +public: + struct Result + { + bool intersect; + + // The type of intersection. + // 0: spheres are disjoint and separated + // 1: spheres touch at point, each sphere outside the other + // 2: spheres intersect in a circle + // 3: sphere0 strictly contained in sphere1 + // 4: sphere0 contained in sphere1, share common point + // 5: sphere1 strictly contained in sphere0 + // 6: sphere1 contained in sphere0, share common point + int type; + Vector3 point; // types 1, 4, 6 + Circle3 circle; // type 2 + }; + + Result operator()(Sphere3 const& sphere0, + Sphere3 const& sphere1); +}; + + +template +typename TIQuery, Sphere3>::Result +TIQuery, Sphere3>::operator()( + Sphere3 const& sphere0, Sphere3 const& sphere1) +{ + Result result; + Vector3 diff = sphere1.center - sphere0.center; + Real rSum = sphere0.radius + sphere1.radius; + result.intersect = (Dot(diff, diff) <= rSum*rSum); + return result; +} + +template +typename FIQuery, Sphere3>::Result +FIQuery, Sphere3>::operator()( + Sphere3 const& sphere0, Sphere3 const& sphere1) +{ + Result result; + + // The plane of intersection must have C1-C0 as its normal direction. + Vector3 C1mC0 = sphere1.center - sphere0.center; + Real sqrLen = Dot(C1mC0, C1mC0); + Real r0 = sphere0.radius, r1 = sphere1.radius; + Real rSum = r0 + r1; + Real rSumSqr = rSum * rSum; + + if (sqrLen > rSumSqr) + { + // The spheres are disjoint/separated. + result.intersect = false; + result.type = 0; + return result; + } + + if (sqrLen == rSumSqr) + { + // The spheres are just touching with each sphere outside the other. + Normalize(C1mC0); + result.intersect = true; + result.type = 1; + result.point = sphere0.center + r0 * C1mC0; + return result; + } + + Real rDif = r0 - r1; + Real rDifSqr = rDif*rDif; + if (sqrLen < rDifSqr) + { + // One sphere is strictly contained in the other. Compute a point in + // the intersection set. + result.intersect = true; + result.type = (rDif <= (Real)0 ? 3 : 5); + result.point = ((Real)0.5)*(sphere0.center + sphere1.center); + return result; + } + if (sqrLen == rDifSqr) + { + // One sphere is contained in the other sphere but with a single point + // of contact. + Normalize(C1mC0); + result.intersect = true; + if (rDif <= (Real)0) + { + result.type = 4; + result.point = sphere1.center + r1 * C1mC0; + } + else + { + result.type = 6; + result.point = sphere0.center + r0 * C1mC0; + } + return result; + } + + // Compute t for which the circle of intersection has center + // K = C0 + t*(C1 - C0). + Real t = ((Real)0.5) * ((Real)1 + rDif * rSum / sqrLen); + + // Compute the center and radius of the circle of intersection. + result.circle.center = sphere0.center + t * C1mC0; + result.circle.radius = std::sqrt(std::max(r0*r0 - t*t*sqrLen, (Real)0)); + + // Compute the normal for the plane of the circle. + Normalize(C1mC0); + result.circle.normal = C1mC0; + + // The intersection is a circle. + result.intersect = true; + result.type = 2; + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Triangle3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Triangle3.h new file mode 100644 index 000000000000..944b154717b7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrSphere3Triangle3.h @@ -0,0 +1,591 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.21.0 (2019/01/21) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of triangle and sphere. + + template + class FIQuery, Triangle3> + { + public: + // The implementation for floating-point types. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = triangle point closest to sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector3 contactPoint; + }; + + template + typename std::enable_if::value, Result>::type + operator()(Sphere3 const& sphere, Vector3 const& sphereVelocity, + Triangle3 const& triangle, Vector3 const& triangleVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0, (Real)0 } }; + + // Test for initial overlap or contact. + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(sphere.center, triangle); + Real rsqr = sphere.radius * sphere.radius; + if (ptResult.sqrDistance <= rsqr) + { + result.intersectionType = (ptResult.sqrDistance < rsqr ? -1 : +1); + result.contactTime = (Real)0; + result.contactPoint = ptResult.closest; + return result; + } + + // To reach here, the sphere and triangle are initially separated. + // Compute the velocity of the sphere relative to the triangle. + Vector3 V = sphereVelocity - triangleVelocity; + Real sqrLenV = Dot(V, V); + if (sqrLenV == (Real)0) + { + // The sphere and triangle are separated and the sphere is not + // moving relative to the triangle, so there is no contact. + // The 'result' is already set to the correct state for this + // case. + return result; + } + + // Compute the triangle edge directions E[], the vector U normal + // to the plane of the triangle, and compute the normals to the + // edges in the plane of the triangle. TODO: For a nondeforming + // triangle (or mesh of triangles), these quantities can all be + // precomputed to reduce the computational cost of the query. Add + // another operator()-query that accepts the precomputed values. + // TODO: When the triangle is deformable, these quantities must be + // computed, either by the caller or here. Optimize the code to + // compute the quantities on-demand (i.e. only when they are + // needed, but cache them for later use). + Vector3 E[3] = + { + triangle.v[1] - triangle.v[0], + triangle.v[2] - triangle.v[1], + triangle.v[0] - triangle.v[2] + }; + Real sqrLenE[3] = { Dot(E[0], E[0]), Dot(E[1], E[1]), Dot(E[2], E[2]) }; + Vector3 U = UnitCross(E[0], E[1]); + Vector3 ExU[3] = + { + Cross(E[0], U), + Cross(E[1], U), + Cross(E[2], U) + }; + + // Compute the vectors from the triangle vertices to the sphere + // center. + Vector3 Delta[3] = + { + sphere.center - triangle.v[0], + sphere.center - triangle.v[1], + sphere.center - triangle.v[2] + }; + + // Determine where the sphere center is located relative to the + // planes of the triangle offset faces of the sphere-swept volume. + Real dotUDelta0 = Dot(U, Delta[0]); + if (dotUDelta0 >= sphere.radius) + { + // The sphere is on the positive side of Dot(U,X-C) = r. If + // the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV >= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set to + // the correct state for this case. + return result; + } + + Real tbar = (sphere.radius - dotUDelta0) / dotUV; + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + if (phi + psi * tbar > (Real)0) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + else if (dotUDelta0 <= -sphere.radius) + { + // The sphere is on the positive side of Dot(-U,X-C) = r. If + // the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV <= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set to + // the correct state for this case. + return result; + } + + Real tbar = (-sphere.radius - dotUDelta0) / dotUV; + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + if (phi + psi * tbar > (Real)0) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + // else: The ray-sphere-swept-volume contact point (if any) cannot + // be on a triangular face of the sphere-swept-volume. + + // The sphere is moving towards the slab between the two planes + // of the sphere-swept volume triangular faces. Determine whether + // the ray intersects the half cylinders or sphere wedges of the + // sphere-swept volume. + + // Test for contact with half cylinders of the sphere-swept + // volume. First, precompute some dot products required in the + // computations. TODO: Optimize the code to compute the quantities + // on-demand (i.e. only when they are needed, but cache them for + // later use). + Real del[3], delp[3], nu[3]; + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + del[i] = Dot(E[i], Delta[i]); + delp[im1] = Dot(E[im1], Delta[i]); + nu[i] = Dot(E[i], V); + } + + for (int i = 2, ip1 = 0; ip1 < 3; i = ip1++) + { + Vector3 hatV = V - E[i] * nu[i] / sqrLenE[i]; + Real sqrLenHatV = Dot(hatV, hatV); + if (sqrLenHatV > (Real)0) + { + Vector3 hatDelta = Delta[i] - E[i] * del[i] / sqrLenE[i]; + Real alpha = -Dot(hatV, hatDelta); + if (alpha >= (Real)0) + { + Real sqrLenHatDelta = Dot(hatDelta, hatDelta); + Real beta = alpha * alpha - sqrLenHatV * (sqrLenHatDelta - rsqr); + if (beta >= (Real)0) + { + Real tbar = (alpha - std::sqrt(beta)) / sqrLenHatV; + + Real mu = Dot(ExU[i], Delta[i]); + Real omega = Dot(ExU[i], hatV); + if (mu + omega * tbar >= (Real)0) + { + if (del[i] + nu[i] * tbar >= (Real)0) + { + if (delp[i] + nu[i] * tbar <= (Real)0) + { + // The constraints are satisfied, so + // tbar is the first time of contact. + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + } + } + } + } + } + + // Test for contact with sphere wedges of the sphere-swept + // volume. We know that |V|^2 > 0 because of a previous + // early-exit test. + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + Real alpha = -Dot(V, Delta[i]); + if (alpha >= (Real)0) + { + Real sqrLenDelta = Dot(Delta[i], Delta[i]); + Real beta = alpha * alpha - sqrLenV * (sqrLenDelta - rsqr); + if (beta >= (Real)0) + { + Real tbar = (alpha - std::sqrt(beta)) / sqrLenV; + if (delp[im1] + nu[im1] * tbar >= (Real)0) + { + if (del[i] + nu[i] * tbar <= (Real)0) + { + // The constraints are satisfied, so tbar + // is the first time of contact. + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + } + } + } + + // The ray and sphere-swept volume do not intersect, so the sphere + // and triangle do not come into contact. The 'result' is already + // set to the correct state for this case. + return result; + } + + + // The implementation for arbitrary-precision types. + typedef typename QuadraticField::Element QFElement; + + struct ExactResult + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = triangle point closest to sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + + // The exact representation of the contact time and point. To + // convert to a floating-point type, use + // FloatType contactTime = field.Convert(result.contactTime); + // Vector3 contactPoint + // { + // field.Convert(result.contactPoint[0]), + // field.Convert(result.contactPoint[1]), + // field.Convert(result.contactPoint[2]) + // }; + QuadraticField field; + QFElement contactTime; + Vector3 contactPoint; + }; + + template + typename std::enable_if::value, ExactResult>::type + operator()(Sphere3 const& sphere, Vector3 const& sphereVelocity, + Triangle3 const& triangle, Vector3 const& triangleVelocity) + { + // The default constructors for the members of 'result' set their + // own members to zero. + ExactResult result; + + // Test for initial overlap or contact. + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(sphere.center, triangle); + Real rsqr = sphere.radius * sphere.radius; + if (ptResult.sqrDistance <= rsqr) + { + // The values result.field, result.contactTime and + // result.contactPoint[] are all zero, so we need only set + // the result.contactPoint[].x values. + result.intersectionType = (ptResult.sqrDistance < rsqr ? -1 : +1); + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = ptResult.closest[j]; + } + return result; + } + + // To reach here, the sphere and triangle are initially separated. + // Compute the velocity of the sphere relative to the triangle. + Vector3 V = sphereVelocity - triangleVelocity; + Real sqrLenV = Dot(V, V); + if (sqrLenV == (Real)0) + { + // The sphere and triangle are separated and the sphere is not + // moving relative to the triangle, so there is no contact. + // The 'result' is already set to the correct state for this + // case. + return result; + } + + // Compute the triangle edge directions E[], the vector U normal + // to the plane of the triangle, and compute the normals to the + // edges in the plane of the triangle. TODO: For a nondeforming + // triangle (or mesh of triangles), these quantities can all be + // precomputed to reduce the computational cost of the query. Add + // another operator()-query that accepts the precomputed values. + // TODO: When the triangle is deformable, these quantities must be + // computed, either by the caller or here. Optimize the code to + // compute the quantities on-demand (i.e. only when they are + // needed, but cache them for later use). + Vector3 E[3] = + { + triangle.v[1] - triangle.v[0], + triangle.v[2] - triangle.v[1], + triangle.v[0] - triangle.v[2] + }; + Real sqrLenE[3] = { Dot(E[0], E[0]), Dot(E[1], E[1]), Dot(E[2], E[2]) }; + // Use an unnormalized U for the plane of the triangle. This + // allows us to use quadratic fields for the comparisons of the + // constraints. + Vector3 U = Cross(E[0], E[1]); + Real sqrLenU = Dot(U, U); + Vector3 ExU[3] = + { + Cross(E[0], U), + Cross(E[1], U), + Cross(E[2], U) + }; + + // Compute the vectors from the triangle vertices to the sphere + // center. + Vector3 Delta[3] = + { + sphere.center - triangle.v[0], + sphere.center - triangle.v[1], + sphere.center - triangle.v[2] + }; + + // Determine where the sphere center is located relative to the + // planes of the triangle offset faces of the sphere-swept volume. + QuadraticField ufield(sqrLenU); + QFElement element(Dot(U, Delta[0]), -sphere.radius); + if (ufield.GreaterThanOrEqualZero(element)) + { + // The sphere is on the positive side of Dot(U,X-C) = r|U|. + // If the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV >= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set + // to the correct state for this case. + return result; + } + + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + QFElement arg(psi * element.x - phi * dotUV, psi * element.y); + if (ufield.GreaterThanZero(arg)) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.field = ufield; + result.contactTime.x = -element.x / dotUV; + result.contactTime.y = -element.y / dotUV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + else + { + element.y = -element.y; + if (ufield.LessThanOrEqualZero(element)) + { + // The sphere is on the positive side of Dot(-U,X-C) = r|U|. + // If the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV <= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set + // to the correct state for this case. + return result; + } + + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + QFElement arg(phi * dotUV - psi * element.x, -psi * element.y); + if (ufield.GreaterThanZero(arg)) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.field = ufield; + result.contactTime.x = -element.x / dotUV; + result.contactTime.y = -element.y / dotUV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + // else: The ray-sphere-swept-volume contact point (if any) + // cannot be on a triangular face of the sphere-swept-volume. + } + + // The sphere is moving towards the slab between the two planes + // of the sphere-swept volume triangular faces. Determine whether + // the ray intersects the half cylinders or sphere wedges of the + // sphere-swept volume. + + // Test for contact with half cylinders of the sphere-swept + // volume. First, precompute some dot products required in the + // computations. TODO: Optimize the code to compute the quantities + // on-demand (i.e. only when they are needed, but cache them for + // later use). + Real del[3], delp[3], nu[3]; + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + del[i] = Dot(E[i], Delta[i]); + delp[im1] = Dot(E[im1], Delta[i]); + nu[i] = Dot(E[i], V); + } + + for (int i = 2, ip1 = 0; ip1 < 3; i = ip1++) + { + Vector3 hatV = V - E[i] * nu[i] / sqrLenE[i]; + Real sqrLenHatV = Dot(hatV, hatV); + if (sqrLenHatV > (Real)0) + { + Vector3 hatDelta = Delta[i] - E[i] * del[i] / sqrLenE[i]; + Real alpha = -Dot(hatV, hatDelta); + if (alpha >= (Real)0) + { + Real sqrLenHatDelta = Dot(hatDelta, hatDelta); + Real beta = alpha * alpha - sqrLenHatV * (sqrLenHatDelta - rsqr); + if (beta >= (Real)0) + { + QuadraticField bfield(beta); + Real mu = Dot(ExU[i], Delta[i]); + Real omega = Dot(ExU[i], hatV); + QFElement arg0(mu * sqrLenHatV + omega * alpha, -omega); + if (bfield.GreaterThanOrEqualZero(arg0)) + { + QFElement arg1(del[i] * sqrLenHatV + nu[i] * alpha, -nu[i]); + if (bfield.GreaterThanOrEqualZero(arg1)) + { + QFElement arg2(delp[i] * sqrLenHatV + nu[i] * alpha, -nu[i]); + if (bfield.LessThanOrEqualZero(arg2)) + { + result.intersectionType = 1; + result.field = bfield; + result.contactTime.x = alpha / sqrLenHatV; + result.contactTime.y = (Real)-1 / sqrLenHatV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + } + } + } + } + } + + // Test for contact with sphere wedges of the sphere-swept + // volume. We know that |V|^2 > 0 because of a previous + // early-exit test. + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + Real alpha = -Dot(V, Delta[i]); + if (alpha >= (Real)0) + { + Real sqrLenDelta = Dot(Delta[i], Delta[i]); + Real beta = alpha * alpha - sqrLenV * (sqrLenDelta - rsqr); + if (beta >= (Real)0) + { + QuadraticField bfield(beta); + QFElement arg0(delp[im1] * sqrLenV + nu[im1] * alpha, -nu[im1]); + if (bfield.GreaterThanOrEqualZero(arg0)) + { + QFElement arg1(del[i] * sqrLenV + nu[i] * alpha, -nu[i]); + if (bfield.LessThanOrEqualZero(arg1)) + { + result.intersectionType = 1; + result.field = bfield; + result.contactTime.x = alpha / sqrLenV; + result.contactTime.y = (Real)-1 / sqrLenV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + } + } + } + + // The ray and sphere-swept volume do not intersect, so the sphere + // and triangle do not come into contact. The 'result' is already + // set to the correct state for this case. + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrTriangle3OrientedBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrTriangle3OrientedBox3.h new file mode 100644 index 000000000000..e5db0debe5c7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIntrTriangle3OrientedBox3.h @@ -0,0 +1,207 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2019/02/13) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The test-intersection query is based on the document +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection query clips the triangle against the faces of +// the oriented box. + +namespace gte +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + Real min0, max0, min1, max1; + Vector3 D, edge[3]; + + // Test direction of triangle normal. + edge[0] = triangle.v[1] - triangle.v[0]; + edge[1] = triangle.v[2] - triangle.v[0]; + D = Cross(edge[0], edge[1]); + min0 = Dot(D, triangle.v[0]); + max0 = min0; + GetProjection(D, box, min1, max1); + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + + // Test direction of box faces. + for (int i = 0; i < 3; ++i) + { + D = box.axis[i]; + GetProjection(D, triangle, min0, max0); + Real DdC = Dot(D, box.center); + min1 = DdC - box.extent[i]; + max1 = DdC + box.extent[i]; + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + } + + // Test direction of triangle-box edge cross products. + edge[2] = edge[1] - edge[0]; + for (int i0 = 0; i0 < 3; ++i0) + { + for (int i1 = 0; i1 < 3; ++i1) + { + D = Cross(edge[i0], box.axis[i1]); + GetProjection(D, triangle, min0, max0); + GetProjection(D, box, min1, max1); + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + } + } + + result.intersect = true; + return result; + } + + private: + void GetProjection(Vector3 const& axis, Triangle3 const& triangle, Real& imin, Real& imax) + { + Real dot[3] = + { + Dot(axis, triangle.v[0]), + Dot(axis, triangle.v[1]), + Dot(axis, triangle.v[2]) + }; + + imin = dot[0]; + imax = imin; + + if (dot[1] < imin) + { + imin = dot[1]; + } + else if (dot[1] > imax) + { + imax = dot[1]; + } + + if (dot[2] < imin) + { + imin = dot[2]; + } + else if (dot[2] > imax) + { + imax = dot[2]; + } + } + + void GetProjection(Vector3 const& axis, OrientedBox3 const& box, Real& imin, Real& imax) + { + Real origin = Dot(axis, box.center); + Real maximumExtent = + std::abs(box.extent[0] * Dot(axis, box.axis[0])) + + std::abs(box.extent[1] * Dot(axis, box.axis[1])) + + std::abs(box.extent[2] * Dot(axis, box.axis[2])); + + imin = origin - maximumExtent; + imax = origin + maximumExtent; + } + }; + + template + class FIQuery, OrientedBox3> + { + public: + struct Result + { + std::vector> insidePolygon; + std::vector>> outsidePolygons; + }; + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + // Start with the triangle and clip it against each face of the + // box. The largest number of vertices for the polygon of + // intersection is 7. + result.insidePolygon.resize(3); + for (int i = 0; i < 3; ++i) + { + result.insidePolygon[i] = triangle.v[i]; + } + + typedef FIQuery>, Hyperplane<3, Real>> PPQuery; + + Plane3 plane; + PPQuery ppQuery; + typename PPQuery::Result ppResult; + for (int dir = -1; dir <= 1; dir += 2) + { + for (int side = 0; side < 3; ++side) + { + // Create a plane for the box face that points inside the box. + plane.normal = ((Real)dir) * box.axis[side]; + plane.constant = Dot(plane.normal, box.center) - box.extent[side]; + + ppResult = ppQuery(result.insidePolygon, plane); + switch (ppResult.configuration) + { + case PPQuery::Configuration::SPLIT: + result.insidePolygon = ppResult.positivePolygon; + result.outsidePolygons.push_back(ppResult.negativePolygon); + break; + case PPQuery::Configuration::POSITIVE_SIDE_VERTEX: + case PPQuery::Configuration::POSITIVE_SIDE_EDGE: + case PPQuery::Configuration::POSITIVE_SIDE_STRICT: + // The result.insidePolygon is already + // ppResult.positivePolygon, but to make it clear, + // assign it here. + result.insidePolygon = ppResult.positivePolygon; + break; + case PPQuery::Configuration::NEGATIVE_SIDE_VERTEX: + case PPQuery::Configuration::NEGATIVE_SIDE_EDGE: + case PPQuery::Configuration::NEGATIVE_SIDE_STRICT: + result.insidePolygon.clear(); + result.outsidePolygons.push_back(ppResult.negativePolygon); + return result; + case PPQuery::Configuration::CONTAINED: + // A triangle coplanar with a box face will be + // processed as if it is inside the box. + result.insidePolygon = ppResult.intersection; + break; + default: + result.insidePolygon.clear(); + result.outsidePolygons.clear(); + break; + } + } + } + + return result; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteInvSqrtEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteInvSqrtEstimate.h new file mode 100644 index 000000000000..54bd5ff7be7f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteInvSqrtEstimate.h @@ -0,0 +1,193 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to 1/sqrt(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|1/sqrt(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class InvSqrtEstimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = InvSqrtEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x > 0. Range reduction is used to generate a + // value y in [1,2], call Evaluate(y), and combine the output with the + // proper exponent to obtain the approximation. For example, + // float x; // x > 0 + // float result = InvSqrtEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); + inline static Real Evaluate(degree<8>, Real t); + + // Support for range reduction. + inline static void Reduce(Real x, Real& adj, Real& y, int& p); + inline static Real Combine(Real adj, Real y, int p); +}; + + +template +template +inline Real InvSqrtEstimate::Degree(Real x) +{ + Real t = x - (Real)1; // t in (0,1] + return Evaluate(degree(), t); +} + +template +template +inline Real InvSqrtEstimate::DegreeRR(Real x) +{ + Real adj, y; + int p; + Reduce(x, adj, y, p); + Real poly = Degree(y); + Real result = Combine(adj, poly, p); + return result; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG1_C1; + poly = (Real)GTE_C_INVSQRT_DEG1_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG2_C2; + poly = (Real)GTE_C_INVSQRT_DEG2_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG2_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG3_C3; + poly = (Real)GTE_C_INVSQRT_DEG3_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG3_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG3_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG4_C4; + poly = (Real)GTE_C_INVSQRT_DEG4_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG5_C5; + poly = (Real)GTE_C_INVSQRT_DEG5_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG6_C6; + poly = (Real)GTE_C_INVSQRT_DEG6_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG7_C7; + poly = (Real)GTE_C_INVSQRT_DEG7_C6 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C0 + poly * t; + return poly; +} + +template +inline Real InvSqrtEstimate::Evaluate(degree<8>, Real t) +{ + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG8_C8; + poly = (Real)GTE_C_INVSQRT_DEG8_C7 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C6 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C0 + poly * t; + return poly; +} + +template +inline void InvSqrtEstimate::Reduce(Real x, Real& adj, Real& y, int& p) +{ + y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2)*y; // y in [1,2) + --p; + adj = (1 & p)*(Real)GTE_C_INV_SQRT_2 + (1 & ~p)*(Real)1; + p = -(p >> 1); +} + +template +inline Real InvSqrtEstimate::Combine(Real adj, Real y, int p) +{ + return adj * std::ldexp(y, p); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIsPlanarGraph.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIsPlanarGraph.h new file mode 100644 index 000000000000..30aa2a576cbf --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteIsPlanarGraph.h @@ -0,0 +1,526 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// Test whether an undirected graph is planar. The input positions must be +// unique and the input edges must be unique, so the number of positions is +// at least 2 and the number of edges is at least one. The elements of the +// edges array must be indices in {0,..,positions.size()-1}. +// +// A sort-and-sweep algorithm is used to determine edge-edge intersections. +// If none of the intersections occur at edge-interior points, the graph is +// planar. See Game Physics (2nd edition), Section 6.2.2: Culling with +// Axis-Aligned Bounding Boxes for such an algorithm. The operator() +// returns 'true' when the graph is planar. If it returns 'false', the +// 'invalidIntersections' set contains pairs of edges that intersect at an +// edge-interior point (that by definition is not a graph vertex). Each set +// element is a pair of indices into the 'edges' array; the indices are +// ordered so that element[0] < element[1]. The Real type must be chosen +// to guarantee exact computation of edge-edge intersections. + +namespace gte +{ + +template +class IsPlanarGraph +{ +public: + IsPlanarGraph(); + + enum + { + IPG_IS_PLANAR_GRAPH = 0, + IPG_INVALID_INPUT_SIZES = 1, + IPG_DUPLICATED_POSITIONS = 2, + IPG_DUPLICATED_EDGES = 4, + IPG_DEGENERATE_EDGES = 8, + IPG_EDGES_WITH_INVALID_VERTICES = 16, + IPG_INVALID_INTERSECTIONS = 32 + }; + + // The function returns a combination of the IPG_* flags listed in the + // previous enumeration. A combined value of 0 indicates the input + // forms a planar graph. If the combined value is not zero, you may + // examine the flags for the failure conditions and use the Get* member + // accessors to obtain specific information about the failure. If the + // positions.size() < 2 or edges.size() == 0, the IPG_INVALID_INPUT_SIZES + // flag is set. + + int operator()(std::vector> const& positions, + std::vector> const& edges); + + // A pair of indices (v0,v1) into the positions array is stored as + // (min(v0,v1), max(v0,v1)). This supports sorted containers of edges. + struct OrderedEdge + { + OrderedEdge(int v0 = -1, int v1 = -1); + bool operator<(OrderedEdge const& edge) const; + std::array V; + }; + + inline std::vector> const& GetDuplicatedPositions() const; + inline std::vector> const& GetDuplicatedEdges() const; + inline std::vector const& GetDegenerateEdges() const; + inline std::vector const& GetEdgesWithInvalidVertices() const; + inline std::vector const& GetInvalidIntersections() const; + +private: + class Endpoint + { + public: + Real value; // endpoint value + int type; // '0' if interval min, '1' if interval max. + int index; // index of interval containing this endpoint + + // Comparison operator for sorting. + bool operator<(Endpoint const& endpoint) const; + }; + + int IsValidTopology(std::vector> const& positions, + std::vector> const& edges); + + void ComputeOverlappingRectangles(std::vector> const& positions, + std::vector> const& edges, + std::set& overlappingRectangles) const; + + bool InvalidSegmentIntersection( + std::array const& p0, std::array const& p1, + std::array const& q0, std::array const& q1) const; + + std::vector> mDuplicatedPositions; + std::vector> mDuplicatedEdges; + std::vector mDegenerateEdges; + std::vector mEdgesWithInvalidVertices; + std::vector mInvalidIntersections; + Real mZero, mOne; +}; + + +template +IsPlanarGraph::IsPlanarGraph() + : + mZero(0), + mOne(1) +{ +} + +template +int IsPlanarGraph::operator()( + std::vector> const& positions, + std::vector> const& edges) +{ + mDuplicatedPositions.clear(); + mDuplicatedEdges.clear(); + mDegenerateEdges.clear(); + mEdgesWithInvalidVertices.clear(); + mInvalidIntersections.clear(); + + int flags = IsValidTopology(positions, edges); + if (flags == IPG_INVALID_INPUT_SIZES) + { + return flags; + } + + std::set overlappingRectangles; + ComputeOverlappingRectangles(positions, edges, overlappingRectangles); + for (auto key : overlappingRectangles) + { + // Get the endpoints of the line segments for the edges whose bounding + // rectangles overlapped. Determine whether the line segments + // intersect. If they do, determine how they intersect. + std::array e0 = edges[key.V[0]]; + std::array e1 = edges[key.V[1]]; + std::array const& p0 = positions[e0[0]]; + std::array const& p1 = positions[e0[1]]; + std::array const& q0 = positions[e1[0]]; + std::array const& q1 = positions[e1[1]]; + if (InvalidSegmentIntersection(p0, p1, q0, q1)) + { + mInvalidIntersections.push_back(key); + } + } + + if (mInvalidIntersections.size() > 0) + { + flags |= IPG_INVALID_INTERSECTIONS; + } + + return flags; +} + +template +inline std::vector> const& +IsPlanarGraph::GetDuplicatedPositions() const +{ + return mDuplicatedPositions; +} + +template +inline std::vector> const& +IsPlanarGraph::GetDuplicatedEdges() const +{ + return mDuplicatedEdges; +} + +template +inline std::vector const& +IsPlanarGraph::GetDegenerateEdges() const +{ + return mDegenerateEdges; +} + +template +inline std::vector const& +IsPlanarGraph::GetEdgesWithInvalidVertices() const +{ + return mEdgesWithInvalidVertices; +} + +template +inline std::vector::OrderedEdge> const& +IsPlanarGraph::GetInvalidIntersections() const +{ + return mInvalidIntersections; +} + +template +int IsPlanarGraph::IsValidTopology(std::vector> const& positions, + std::vector> const& edges) +{ + int const numPositions = static_cast(positions.size()); + int const numEdges = static_cast(edges.size()); + if (numPositions < 2 || numEdges == 0) + { + // The graph must have at least one edge. + return IPG_INVALID_INPUT_SIZES; + } + + // The positions must be unique. + int flags = IPG_IS_PLANAR_GRAPH; + std::map, std::vector> uniquePositions; + for (int i = 0; i < numPositions; ++i) + { + std::array p = positions[i]; + auto iter = uniquePositions.find(p); + if (iter == uniquePositions.end()) + { + std::vector indices; + indices.push_back(i); + uniquePositions.insert(std::make_pair(p, indices)); + } + else + { + iter->second.push_back(i); + } + } + if (uniquePositions.size() < positions.size()) + { + // At least two positions are duplicated. + for (auto const& element : uniquePositions) + { + if (element.second.size() > 1) + { + mDuplicatedPositions.push_back(element.second); + } + } + flags |= IPG_DUPLICATED_POSITIONS; + } + + // The edges must be unique. + std::map> uniqueEdges; + for (int i = 0; i < numEdges; ++i) + { + OrderedEdge key(edges[i][0], edges[i][1]); + auto iter = uniqueEdges.find(key); + if (iter == uniqueEdges.end()) + { + std::vector indices; + indices.push_back(i); + uniqueEdges.insert(std::make_pair(key, indices)); + } + else + { + iter->second.push_back(i); + } + } + if (uniqueEdges.size() < edges.size()) + { + // At least two edges are duplicated, possibly even a pair of + // edges (v0,v1) and (v1,v0) which is not allowed because the + // graph is undirected. + for (auto const& element : uniqueEdges) + { + if (element.second.size() > 1) + { + mDuplicatedEdges.push_back(element.second); + } + } + flags |= IPG_DUPLICATED_EDGES; + } + + // The edges are represented as pairs of indices into the 'positions' + // array. The indices for a single edge must be different (no edges + // allowed from a vertex to itself) and all indices must be valid. + // At the same time, keep track of unique edges. + for (int i = 0; i < numEdges; ++i) + { + std::array e = edges[i]; + if (e[0] == e[1]) + { + // The edge is degenerate, originating and terminating at the + // same vertex. + mDegenerateEdges.push_back(i); + flags |= IPG_DEGENERATE_EDGES; + } + + if (e[0] < 0 || e[0] >= numPositions || e[1] < 0 || e[1] >= numPositions) + { + // The edge has an index that references a non-existant vertex. + mEdgesWithInvalidVertices.push_back(i); + flags |= IPG_EDGES_WITH_INVALID_VERTICES; + } + } + + return flags; +} + +template +void IsPlanarGraph::ComputeOverlappingRectangles(std::vector> const& positions, + std::vector> const& edges, std::set& overlappingRectangles) const +{ + // Compute axis-aligned bounding rectangles for the edges. + int const numEdges = static_cast(edges.size()); + std::vector> emin(numEdges); + std::vector> emax(numEdges); + for (int i = 0; i < numEdges; ++i) + { + std::array e = edges[i]; + std::array const& p0 = positions[e[0]]; + std::array const& p1 = positions[e[1]]; + + for (int j = 0; j < 2; ++j) + { + if (p0[j] < p1[j]) + { + emin[i][j] = p0[j]; + emax[i][j] = p1[j]; + } + else + { + emin[i][j] = p1[j]; + emax[i][j] = p0[j]; + } + } + } + + // Get the rectangle endpoints. + int const numEndpoints = 2 * numEdges; + std::vector xEndpoints(numEndpoints); + std::vector yEndpoints(numEndpoints); + for (int i = 0, j = 0; i < numEdges; ++i) + { + xEndpoints[j].type = 0; + xEndpoints[j].value = emin[i][0]; + xEndpoints[j].index = i; + yEndpoints[j].type = 0; + yEndpoints[j].value = emin[i][1]; + yEndpoints[j].index = i; + ++j; + + xEndpoints[j].type = 1; + xEndpoints[j].value = emax[i][0]; + xEndpoints[j].index = i; + yEndpoints[j].type = 1; + yEndpoints[j].value = emax[i][1]; + yEndpoints[j].index = i; + ++j; + } + + // Sort the rectangle endpoints. + std::sort(xEndpoints.begin(), xEndpoints.end()); + std::sort(yEndpoints.begin(), yEndpoints.end()); + + // Sweep through the endpoints to determine overlapping x-intervals. Use + // an active set of rectangles to reduce the complexity of the algorithm. + std::set active; + for (int i = 0; i < numEndpoints; ++i) + { + Endpoint const& endpoint = xEndpoints[i]; + int index = endpoint.index; + if (endpoint.type == 0) // an interval 'begin' value + { + // In the 1D problem, the current interval overlaps with all the + // active intervals. In 2D this we also need to check for + // y-overlap. + for (auto activeIndex : active) + { + // Rectangles activeIndex and index overlap in the + // x-dimension. Test for overlap in the y-dimension. + std::array const& r0min = emin[activeIndex]; + std::array const& r0max = emax[activeIndex]; + std::array const& r1min = emin[index]; + std::array const& r1max = emax[index]; + if (r0max[1] >= r1min[1] && r0min[1] <= r1max[1]) + { + if (activeIndex < index) + { + overlappingRectangles.insert(OrderedEdge(activeIndex, index)); + } + else + { + overlappingRectangles.insert(OrderedEdge(index, activeIndex)); + } + } + } + active.insert(index); + } + else // an interval 'end' value + { + active.erase(index); + } + } +} + +template +bool IsPlanarGraph::InvalidSegmentIntersection( + std::array const& p0, std::array const& p1, + std::array const& q0, std::array const& q1) const +{ + // We must solve the two linear equations + // p0 + t0 * (p1 - p0) = q0 + t1 * (q1 - q0) + // for the unknown variables t0 and t1. These may be written as + // t0 * (p1 - p0) - t1 * (q1 - q0) = q0 - p0 + // If denom = Dot(p1 - p0, Perp(q1 - q0)) is not zero, then + // t0 = Dot(q0 - p0, Perp(q1 - q0)) / denom = numer0 / denom + // t1 = Dot(q0 - p0, Perp(p1 - p0)) / denom = numer1 / denom + // For an invalid intersection, we need (t0,t1) with: + // ((0 < t0 < 1) and (0 <= t1 <= 1)) or ((0 <= t0 <= 1) and (0 < t1 < 1). + // If denom is zero, then + + std::array p1mp0, q1mq0, q0mp0; + for (int j = 0; j < 2; ++j) + { + p1mp0[j] = p1[j] - p0[j]; + q1mq0[j] = q1[j] - q0[j]; + q0mp0[j] = q0[j] - p0[j]; + } + + Real denom = p1mp0[0] * q1mq0[1] - p1mp0[1] * q1mq0[0]; + Real numer0 = q0mp0[0] * q1mq0[1] - q0mp0[1] * q1mq0[0]; + Real numer1 = q0mp0[0] * p1mp0[1] - q0mp0[1] * p1mp0[0]; + + if (denom != mZero) + { + // The lines of the segments are not parallel. + if (denom > mZero) + { + if (mZero <= numer0 && numer0 <= denom && mZero <= numer1 && numer1 <= denom) + { + // The segments intersect. + return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); + } + else + { + return false; + } + } + else // denom < mZero + { + if (mZero >= numer0 && numer0 >= denom && mZero >= numer1 && numer1 >= denom) + { + // The segments intersect. + return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); + } + else + { + return false; + } + } + } + else + { + // The lines of the segments are parallel. + if (numer0 != mZero || numer1 != mZero) + { + // The lines of the segments are separated. + return false; + } + else + { + // The segments lie on the same line. Compute the parameter + // intervals for the segments in terms of the t0-parameter and + // determine their overlap (if any). + std::array q1mp0; + for (int j = 0; j < 2; ++j) + { + q1mp0[j] = q1[j] - p0[j]; + } + Real sqrLenP1mP0 = p1mp0[0] * p1mp0[0] + p1mp0[1] * p1mp0[1]; + Real value0 = q0mp0[0] * p1mp0[0] + q0mp0[1] * p1mp0[1]; + Real value1 = q1mp0[0] * p1mp0[0] + q1mp0[1] * p1mp0[1]; + if ((value0 >= sqrLenP1mP0 && value1 >= sqrLenP1mP0) + || (value0 <= mZero && value1 <= mZero)) + { + // If the segments intersect, they must do so at endpoints of + // the segments. + return false; + } + else + { + // The segments overlap in a positive-length interval. + return true; + } + } + } +} + +template +bool IsPlanarGraph::Endpoint::operator<(Endpoint const& endpoint) const +{ + if (value < endpoint.value) + { + return true; + } + if (value > endpoint.value) + { + return false; + } + return type < endpoint.type; +} + +template +IsPlanarGraph::OrderedEdge::OrderedEdge(int v0, int v1) +{ + if (v0 < v1) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + } + else + { + // v1 is minimum + V[0] = v1; + V[1] = v0; + } +} + +template +bool IsPlanarGraph::OrderedEdge::operator<(OrderedEdge const& edge) const +{ + // Lexicographical ordering used by std::array. + return V < edge.V; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLCPSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLCPSolver.h new file mode 100644 index 000000000000..ada75319a9e7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLCPSolver.h @@ -0,0 +1,616 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.4.2 (2016/11/24) + +#pragma once + +#include +#include +#include + +// A class for solving the Linear Complementarity Problem (LCP) +// w = q + M * z, w^T * z = 0, w >= 0, z >= 0. The vectors q, w, and z are +// n-tuples and the matrix M is n-by-n. The inputs to Solve(...) are q and M. +// The outputs are w and z, which are valid when the returned bool is true but +// are invalid when the returned bool is false. +// +// The comments at the end of this file explain what the preprocessor symbol +// means regarding the LCP solver implementation. If the algorithm fails to +// converge within the specified maximum number of iterations, consider +// increasing the number and calling Solve(...) again. +//#define GTE_REPORT_FAILED_TO_CONVERGE + +namespace gte +{ + +// Support templates for number of dimensions known at compile time or known +// only at run time. +template +class LCPSolver {}; + + +template +class LCPSolverShared +{ +protected: + // Abstract base class construction. A virtual destructor is not provided + // because there are no required side effects when destroying objects from + // the derived classes. The member mMaxIterations is set by this call to + // the default value of n*n. + LCPSolverShared(int n); + +public: + // Theoretically, when there is a solution the algorithm must converge + // in a finite number of iterations. The number of iterations depends + // on the problem at hand, but we need to guard against an infinite loop + // by limiting the number. The implementation uses a maximum number of + // n*n (chosen arbitrarily). You can set the number yourself, perhaps + // when a call to Solve fails--increase the number of iterations and call + // Solve again. + inline void SetMaxIterations(int maxIterations); + inline int GetMaxIterations() const; + + // Access the actual number of iterations used in a call to Solve. + inline int GetNumIterations() const; + + enum Result + { + HAS_TRIVIAL_SOLUTION, + HAS_NONTRIVIAL_SOLUTION, + NO_SOLUTION, + FAILED_TO_CONVERGE, + INVALID_INPUT + }; + +protected: + // Bookkeeping of variables during the iterations of the solver. The name + // is either 'w' or 'z' and is used for human-readable debugging help. + // The 'index' is that for the original variables w[index] or z[index]. + // The 'complementary' index is the location of the complementary variable + // in mVarBasic[] or in mVarNonbasic[]. The 'tuple' is a pointer to + // &w[0] or &z[0], the choice based on name of 'w' or 'z', and is used to + // fill in the solution values (the variables are permuted during the + // pivoting algorithm). + struct Variable + { + char name; + int index; + int complementary; + Real* tuple; + }; + + // The augmented problem is w = q + M*z + z[n]*U = 0, where U is an + // n-tuple of 1-values. We manipulate the augmented matrix [M | U | p(t)] + // where p(t) is a column vector of polynomials of at most degree n. If + // p[r](t) is the polynomial for row r, then p[r](0) = q[r]. These are + // perturbations of q[r] designed so that the algorithm avoids degeneracies + // (a q-term becomes zero during the iterations). The basic variables are + // w[0] through w[n-1] and the nonbasic variables are z[0] through z[n]. + // The returned z consists only of z[0] through z[n-1]. + + // The derived classes ensure that the pointers point to the correct + // of elements for each array. The matrix M must be stored in row-major + // order. + bool Solve(Real const* q, Real const* M, Real* w, Real* z, Result* result); + + // Access mAugmented as a 2-dimensional array. + inline Real const& Augmented(int row, int col) const; + inline Real& Augmented(int row, int col); + + // Support for polynomials with n+1 coefficients and degree no larger + // than n. + void MakeZero(Real* poly); + void Copy(Real const* poly0, Real* poly1); + bool LessThan(Real const* poly0, Real const* poly1); + bool LessThanZero(Real const* poly); + void Multiply(Real const* poly, Real scalar, Real* product); + + int mDimension; + int mMaxIterations; + int mNumIterations; + + // These pointers are set by the derived-class constructors to arrays + // that have the correct number of elements. The arrays mVarBasic, + // mVarNonbasic, mQMin, mMinRatio, and mRatio each have n+1 elements. + // The mAugmented array has n rows and 2*(n+1) columns stored in + // row-major order in a 1-dimensional array. The array of pointers + // mPoly has n elements. + Variable* mVarBasic; + Variable* mVarNonbasic; + int mNumCols; + Real* mAugmented; + Real* mQMin; + Real* mMinRatio; + Real* mRatio; + Real** mPoly; +}; + + +template +class LCPSolver : public LCPSolverShared +{ +public: + // Construction. The member mMaxIterations is set by this call to the + // default value of n*n. + LCPSolver(); + + // If you want to know specifically why 'true' or 'false' was returned, + // pass the address of a Result variable as the last parameter. + bool Solve(std::array const& q, std::array, n> const& M, + std::array& w, std::array& z, + typename LCPSolverShared::Result* result = nullptr); + +private: + std::array::Variable, n + 1> mArrayVarBasic; + std::array::Variable, n + 1> mArrayVarNonbasic; + std::array mArrayAugmented; + std::array mArrayQMin; + std::array mArrayMinRatio; + std::array mArrayRatio; + std::array mArrayPoly; +}; + + +template +class LCPSolver : public LCPSolverShared +{ +public: + // Construction. The member mMaxIterations is set by this call to the + // default value of n*n. + LCPSolver(int n); + + // The input q must have n elements and the input M must be an n-by-n + // matrix stored in row-major order. The outputs w and z have n elements. + // If you want to know specifically why 'true' or 'false' was returned, + // pass the address of a Result variable as the last parameter. + bool Solve(std::vector const& q, std::vector const& M, + std::vector& w, std::vector& z, + typename LCPSolverShared::Result* result = nullptr); + +private: + std::vector::Variable> mVectorVarBasic; + std::vector::Variable> mVectorVarNonbasic; + std::vector mVectorAugmented; + std::vector mVectorQMin; + std::vector mVectorMinRatio; + std::vector mVectorRatio; + std::vector mVectorPoly; +}; + + +template +LCPSolverShared::LCPSolverShared(int n) + : + mNumIterations(0), + mVarBasic(nullptr), + mVarNonbasic(nullptr), + mNumCols(0), + mAugmented(nullptr), + mQMin(nullptr), + mMinRatio(nullptr), + mRatio(nullptr), + mPoly(nullptr) +{ + if (n > 0) + { + mDimension = n; + mMaxIterations = n * n; + } + else + { + mDimension = 0; + mMaxIterations = 0; + } +} + +template +inline void LCPSolverShared::SetMaxIterations(int maxIterations) +{ + mMaxIterations = (maxIterations > 0 ? maxIterations : mDimension * mDimension); +} + +template +inline int LCPSolverShared::GetMaxIterations() const +{ + return mMaxIterations; +} + +template +inline int LCPSolverShared::GetNumIterations() const +{ + return mNumIterations; +} + +template +bool LCPSolverShared::Solve(Real const* q, Real const* M, + Real* w, Real* z, Result* result) +{ + // Perturb the q[r] constants to be polynomials of degree r+1 represented + // as an array of n+1 coefficients. The coefficient with index r+1 is 1 + // and the coefficients with indices larger than r+1 are 0. + for (int r = 0; r < mDimension; ++r) + { + mPoly[r] = &Augmented(r, mDimension + 1); + MakeZero(mPoly[r]); + mPoly[r][0] = q[r]; + mPoly[r][r + 1] = (Real)1; + } + + // Determine whether there is the trivial solution w = z = 0. + Copy(mPoly[0], mQMin); + int basic = 0; + for (int r = 1; r < mDimension; ++r) + { + if (LessThan(mPoly[r], mQMin)) + { + Copy(mPoly[r], mQMin); + basic = r; + } + } + + if (!LessThanZero(mQMin)) + { + for (int r = 0; r < mDimension; ++r) + { + w[r] = q[r]; + z[r] = (Real)0; + } + + if (result) + { + *result = HAS_TRIVIAL_SOLUTION; + } + return true; + } + + // Initialize the remainder of the augmented matrix with M and U. + for (int r = 0; r < mDimension; ++r) + { + for (int c = 0; c < mDimension; ++c) + { + Augmented(r, c) = M[c + mDimension * r]; + } + Augmented(r, mDimension) = (Real)1; + } + + // Keep track of when the variables enter and exit the dictionary, + // including where complementary variables are relocated. + for (int i = 0; i <= mDimension; ++i) + { + mVarBasic[i].name = 'w'; + mVarBasic[i].index = i; + mVarBasic[i].complementary = i; + mVarBasic[i].tuple = w; + mVarNonbasic[i].name = 'z'; + mVarNonbasic[i].index = i; + mVarNonbasic[i].complementary = i; + mVarNonbasic[i].tuple = z; + } + + // The augmented variable z[n] is the initial driving variable for + // pivoting. The equation 'basic' is the one to solve for z[n] and + // pivoting with w[basic]. The last column of M remains all 1-values + // for this initial step, so no algebraic computations occur for M[r][n]. + int driving = mDimension; + for (int r = 0; r < mDimension; ++r) + { + if (r != basic) + { + for (int c = 0; c < mNumCols; ++c) + { + if (c != mDimension) + { + Augmented(r, c) -= Augmented(basic, c); + } + } + } + } + + for (int c = 0; c < mNumCols; ++c) + { + if (c != mDimension) + { + Augmented(basic, c) = -Augmented(basic, c); + } + } + + mNumIterations = 0; + for (int i = 0; i < mMaxIterations; ++i, ++mNumIterations) + { + // The basic variable of equation 'basic' exited the dictionary, so + // its complementary (nonbasic) variable must become the next driving + // variable in order for it to enter the dictionary. + int nextDriving = mVarBasic[basic].complementary; + mVarNonbasic[nextDriving].complementary = driving; + std::swap(mVarBasic[basic], mVarNonbasic[driving]); + if (mVarNonbasic[driving].index == mDimension) + { + // The algorithm has converged. + for (int r = 0; r < mDimension; ++r) + { + mVarBasic[r].tuple[mVarBasic[r].index] = mPoly[r][0]; + } + for (int c = 0; c <= mDimension; ++c) + { + int index = mVarNonbasic[c].index; + if (index < mDimension) + { + mVarNonbasic[c].tuple[index] = (Real)0; + } + } + if (result) + { + *result = HAS_NONTRIVIAL_SOLUTION; + } + return true; + } + + // Determine the 'basic' equation for which the ratio -q[r]/M(r,driving) + // is minimized among all equations r with M(r,driving) < 0. + driving = nextDriving; + basic = -1; + for (int r = 0; r < mDimension; ++r) + { + if (Augmented(r, driving) < (Real)0) + { + Real factor = (Real)-1 / Augmented(r, driving); + Multiply(mPoly[r], factor, mRatio); + if (basic == -1 || LessThan(mRatio, mMinRatio)) + { + Copy(mRatio, mMinRatio); + basic = r; + } + } + } + + if (basic == -1) + { + // The coefficients of z[driving] in all the equations are + // nonnegative, so the z[driving] variable cannot leave the + // dictionary. There is no solution to the LCP. + for (int r = 0; r < mDimension; ++r) + { + w[r] = (Real)0; + z[r] = (Real)0; + } + + if (result) + { + *result = NO_SOLUTION; + } + return false; + } + + // Solve the basic equation so that z[driving] enters the dictionary + // and w[basic] exits the dictionary. + Real invDenom = (Real)1 / Augmented(basic, driving); + for (int r = 0; r < mDimension; ++r) + { + if (r != basic && Augmented(r, driving) != (Real)0) + { + Real multiplier = Augmented(r, driving) * invDenom; + for (int c = 0; c < mNumCols; ++c) + { + if (c != driving) + { + Augmented(r, c) -= Augmented(basic, c) * multiplier; + } + else + { + Augmented(r, driving) = multiplier; + } + } + } + } + + for (int c = 0; c < mNumCols; ++c) + { + if (c != driving) + { + Augmented(basic, c) = -Augmented(basic, c) * invDenom; + } + else + { + Augmented(basic, driving) = invDenom; + } + } + } + + // Numerical round-off errors can cause the Lemke algorithm not to + // converge. In particular, the code above has a test + // if (mAugmented[r][driving] < (Real)0) { ... } + // to determine the 'basic' equation with which to pivot. It is + // possible that theoretically mAugmented[r][driving]is zero but + // rounding errors cause it to be slightly negative. If theoretically + // all mAugmented[r][driving] >= 0, there is no solution to the LCP. + // With the rounding errors, if the algorithm fails to converge within + // the specified number of iterations, NO_SOLUTION is returned, which + // is hopefully the correct result. It is also possible that the rounding + // errors lead to a NO_SOLUTION (returned from inside the loop) when in + // fact there is a solution. [When the LCP solver is used by intersection + // testing algorithms, the hope is that misclassifications occur only + // when the two objects are nearly in tangential contact.] + // + // To determine whether the rounding errors are the problem, you can + // execute the query using exact arithmetic with the following type + // used for 'Real' (replacing 'float' or 'double') of + // BSRational Rational. + // + // That said, if the algorithm fails to converge and you believe that + // the rounding errors are not causing this, please file a bug report + // and provide the input data to the solver. + +#if defined(GTE_REPORT_FAILED_TO_CONVERGE) + LogInformation("The algorithm failed to converge."); +#endif + + if (result) + { + *result = FAILED_TO_CONVERGE; + } + return false; +} + +template +inline Real const& LCPSolverShared::Augmented(int row, int col) const +{ + return mAugmented[col + mNumCols * row]; +} + +template +inline Real& LCPSolverShared::Augmented(int row, int col) +{ + return mAugmented[col + mNumCols * row]; +} + + +template +void LCPSolverShared::MakeZero(Real* poly) +{ + for (int i = 0; i <= mDimension; ++i) + { + poly[i] = (Real)0; + } +} + +template +void LCPSolverShared::Copy(Real const* poly0, Real* poly1) +{ + for (int i = 0; i <= mDimension; ++i) + { + poly1[i] = poly0[i]; + } +} + +template +bool LCPSolverShared::LessThan(Real const* poly0, Real const* poly1) +{ + for (int i = 0; i <= mDimension; ++i) + { + if (poly0[i] < poly1[i]) + { + return true; + } + + if (poly0[i] > poly1[i]) + { + return false; + } + } + + return false; +} + +template +bool LCPSolverShared::LessThanZero(Real const* poly) +{ + for (int i = 0; i <= mDimension; ++i) + { + if (poly[i] < (Real)0) + { + return true; + } + + if (poly[i] > (Real)0) + { + return false; + } + } + + return false; +} + +template +void LCPSolverShared::Multiply(Real const* poly, Real scalar, Real* product) +{ + for (int i = 0; i <= mDimension; ++i) + { + product[i] = poly[i] * scalar; + } +} + + +template +LCPSolver::LCPSolver() + : + LCPSolverShared(n) +{ + this->mVarBasic = mArrayVarBasic.data(); + this->mVarNonbasic = mArrayVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mArrayAugmented.data(); + this->mQMin = mArrayQMin.data(); + this->mMinRatio = mArrayMinRatio.data(); + this->mRatio = mArrayRatio.data(); + this->mPoly = mArrayPoly.data(); +} + +template +bool LCPSolver::Solve( + std::array const& q, std::array, n> const& M, + std::array& w, std::array& z, + typename LCPSolverShared::Result* result) +{ + return LCPSolverShared::Solve(q.data(), M.front().data(), w.data(), z.data(), result); +} + + +template +LCPSolver::LCPSolver(int n) + : + LCPSolverShared(n) +{ + if (n > 0) + { + mVectorVarBasic.resize(n + 1); + mVectorVarNonbasic.resize(n + 1); + mVectorAugmented.resize(2 * (n + 1) * n); + mVectorQMin.resize(n + 1); + mVectorMinRatio.resize(n + 1); + mVectorRatio.resize(n + 1); + mVectorPoly.resize(n); + + this->mVarBasic = mVectorVarBasic.data(); + this->mVarNonbasic = mVectorVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mVectorAugmented.data(); + this->mQMin = mVectorQMin.data(); + this->mMinRatio = mVectorMinRatio.data(); + this->mRatio = mVectorRatio.data(); + this->mPoly = mVectorPoly.data(); + } +} + +template +bool LCPSolver::Solve( + std::vector const& q, std::vector const& M, + std::vector& w, std::vector& z, + typename LCPSolverShared::Result* result) +{ + if (this->mDimension > static_cast(q.size()) + || this->mDimension * this->mDimension > static_cast(M.size())) + { + if (result) + { + *result = this->INVALID_INPUT; + } + return false; + } + + if (this->mDimension > static_cast(w.size())) + { + w.resize(this->mDimension); + } + + if (this->mDimension > static_cast(z.size())) + { + z.resize(this->mDimension); + } + + return LCPSolverShared::Solve(q.data(), M.data(), w.data(), z.data(), result); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLevenbergMarquardtMinimizer.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLevenbergMarquardtMinimizer.h new file mode 100644 index 000000000000..f308c4662fd6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLevenbergMarquardtMinimizer.h @@ -0,0 +1,292 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.14.0 (2018/07/16) + +#pragma once + +#include +#include + +// See GteGaussNewtonMinimizer.h for a formulation of the minimization +// problem and how Levenberg-Marquardt relates to Gauss-Newton. + +namespace gte +{ + template + class LevenbergMarquardtMinimizer + { + public: + // Convenient types for the domain vectors, the range vectors, the + // function F and the Jacobian J. + typedef GVector DVector; // NumPDimensions elements + typedef GVector RVector; // NumFDImensions elements + typedef GMatrix JMatrix; // NumFDimensions-by-NumPDimensions elements + typedef GMatrix JTJMatrix; // NumPDimensions-by-NumPDimensions elements + typedef GVector JTFVector; // NumPDimensions elements + typedef std::function FFunction; + typedef std::function JFunction; + typedef std::function JPlusFunction; + + // NOTE: The C++ compiler for Microsoft Visual Studio 12.0.21005.1 REL + // (MSVS 2013) appears to have a bug regarding passing std::function + // arguments. The unit-test code for the first LevenbergMarquardtMinimizer + // constructor was passed std::function objects of type FFunction and + // JFunction. The compiler claimed that the constructor call is + // ambiguous because the JFunction portion of the first constructor's + // signature matches the JPlusFunction portion of the second + // constructor's signature, which is clearly not correct. When using + // MSVS 2013, you need to explicitly typecast using + // LevenbergMarquardtMinimizer minimizer( + // numPDimensions, numFDimensions, + // (LevenbergMarquardtMinimizer::FFunction const&) myFFunction, + // (LevenbergMarquardtMinimizer::JFunction const&) myJFunction); + // The same typecasting is also required for the second constructor. + + // Create the minimizer that computes F(p) and J(p) directly. + LevenbergMarquardtMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JFunction const& inJFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJFunction(inJFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(true) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Create the minimizer that computes J^T(p)*J(p) and -J(p)*F(p). + LevenbergMarquardtMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JPlusFunction const& inJPlusFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJPlusFunction(inJPlusFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(false) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Disallow copy, assignment and move semantics. + LevenbergMarquardtMinimizer(LevenbergMarquardtMinimizer const&) = delete; + LevenbergMarquardtMinimizer& operator=(LevenbergMarquardtMinimizer const&) = delete; + LevenbergMarquardtMinimizer(LevenbergMarquardtMinimizer&&) = delete; + LevenbergMarquardtMinimizer& operator=(LevenbergMarquardtMinimizer&&) = delete; + + inline int GetNumPDimensions() const { return mNumPDimensions; } + inline int GetNumFDimensions() const { return mNumFDimensions; } + + // The lambda is positive, the multiplier is positive, and the initial + // guess for the p-parameter is p0. Typical choices are lambda = + // 0.001 and multiplier = 10. TODO: Explain lambda in more detail, + // Multiview Geometry mentions lambda = 0.001*average(diagonal(JTJ)), + // but let's just expose the factor in front of the average. + + struct Result + { + DVector minLocation; + Real minError; + Real minErrorDifference; + Real minUpdateLength; + size_t numIterations; + size_t numAdjustments; + bool converged; + }; + + Result operator()(DVector const& p0, size_t maxIterations, + Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments) + { + Result result; + result.minLocation = p0; + result.minError = std::numeric_limits::max(); + result.minErrorDifference = std::numeric_limits::max(); + result.minUpdateLength = (Real)0; + result.numIterations = 0; + result.numAdjustments = 0; + result.converged = false; + + // As a simple precaution, ensure that the lambda inputs are + // valid. If invalid, fall back to Gauss-Newton iteration. + if (lambdaFactor <= (Real)0 || lambdaAdjust <= (Real)0) + { + maxAdjustments = 1; + lambdaFactor = (Real)0; + lambdaAdjust = (Real)1; + } + + // As a simple precaution, ensure the tolerances are nonnegative. + updateLengthTolerance = std::max(updateLengthTolerance, (Real)0); + errorDifferenceTolerance = std::max(errorDifferenceTolerance, (Real)0); + + // Compute the initial error. + mFFunction(p0, mF); + result.minError = Dot(mF, mF); + + // Do the Levenberg-Marquart iterations. + auto pCurrent = p0; + for (result.numIterations = 1; result.numIterations <= maxIterations; ++result.numIterations) + { + std::pair status; + DVector pNext; + for (result.numAdjustments = 0; result.numAdjustments < maxAdjustments; ++result.numAdjustments) + { + status = DoIteration(pCurrent, lambdaFactor, updateLengthTolerance, + errorDifferenceTolerance, pNext, result); + if (status.first) + { + // Either the Cholesky decomposition failed or the + // iterates converged within tolerance. TODO: See the + // note in DoIteration about not failing on Cholesky + // decomposition. + return result; + } + + if (status.second) + { + // The error has been reduced but we have not yet + // converged within tolerance. + break; + } + + lambdaFactor *= lambdaAdjust; + } + + if (result.numAdjustments < maxAdjustments) + { + // The current value of lambda led us to an update that + // reduced the error, but the error is not yet small + // enough to conclude we converged. Reduce lambda for the + // next outer-loop iteration. + lambdaFactor /= lambdaAdjust; + } + else + { + // All lambdas tried during the inner-loop iteration did + // not lead to a reduced error. If we do nothing here, + // the next inner-loop iteration will continue to multiply + // lambda, risking eventual floating-point overflow. To + // avoid this, fall back to a Gauss-Newton iterate. + status = DoIteration(pCurrent, lambdaFactor, updateLengthTolerance, + errorDifferenceTolerance, pNext, result); + if (status.first) + { + // Either the Cholesky decomposition failed or the + // iterates converged within tolerance. TODO: See the + // note in DoIteration about not failing on Cholesky + // decomposition. + return result; + } + } + + pCurrent = pNext; + } + + return result; + } + + private: + void ComputeLinearSystemInputs(DVector const& pCurrent, Real lambda) + { + if (mUseJFunction) + { + mJFunction(pCurrent, mJ); + mJTJ = MultiplyATB(mJ, mJ); + mNegJTF = -(mF * mJ); + } + else + { + mJPlusFunction(pCurrent, mJTJ, mNegJTF); + } + + Real diagonalSum(0); + for (int i = 0; i < mNumPDimensions; ++i) + { + diagonalSum += mJTJ(i, i); + } + + Real diagonalAdjust = lambda * diagonalSum / static_cast(mNumPDimensions); + for (int i = 0; i < mNumPDimensions; ++i) + { + mJTJ(i, i) += diagonalAdjust; + } + } + + // The returned 'first' is true when the linear system cannot be + // solved (result.converged is false in this case) or when the + // error is reduced to within the tolerances specified by the caller + // (result.converged is true in this case). When the 'first' value + // is true, the 'second' value is true when the error is reduced or + // false when it is not. + std::pair DoIteration(DVector const& pCurrent, Real lambdaFactor, + Real updateLengthTolerance, Real errorDifferenceTolerance, DVector& pNext, + Result& result) + { + ComputeLinearSystemInputs(pCurrent, lambdaFactor); + if (!mDecomposer.Factor(mJTJ)) + { + // TODO: The matrix mJTJ is positive semi-definite, so the + // failure can occur when mJTJ has a zero eigenvalue in + // which case mJTJ is not invertible. Generate an iterate + // anyway, perhaps using gradient descent? + return std::make_pair(true, false); + } + mDecomposer.SolveLower(mJTJ, mNegJTF); + mDecomposer.SolveUpper(mJTJ, mNegJTF); + + pNext = pCurrent + mNegJTF; + mFFunction(pNext, mF); + Real error = Dot(mF, mF); + if (error < result.minError) + { + result.minErrorDifference = result.minError - error; + result.minUpdateLength = Length(mNegJTF); + result.minLocation = pNext; + result.minError = error; + if (result.minErrorDifference <= errorDifferenceTolerance + || result.minUpdateLength <= updateLengthTolerance) + { + result.converged = true; + return std::make_pair(true, true); + } + else + { + return std::make_pair(false, true); + } + } + else + { + return std::make_pair(false, false); + } + } + + int mNumPDimensions, mNumFDimensions; + FFunction mFFunction; + JFunction mJFunction; + JPlusFunction mJPlusFunction; + + // Storage for J^T(p)*J(p) and -J^T(p)*F(p) during the iterations. + RVector mF; + JMatrix mJ; + JTJMatrix mJTJ; + JTFVector mNegJTF; + + CholeskyDecomposition mDecomposer; + + bool mUseJFunction; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLine.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLine.h new file mode 100644 index 000000000000..b966126fec40 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLine.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The line is represented by P+t*D, where P is an origin point, D is a +// unit-length direction vector, and t is any real number. The user must +// ensure that D is unit length. + +namespace gte +{ + +template +class Line +{ +public: + // Construction and destruction. The default constructor sets the origin + // to (0,...,0) and the line direction to (1,0,...,0). + Line(); + Line(Vector const& inOrigin, Vector const& inDirection); + + // Public member access. The direction must be unit length. + Vector origin, direction; + +public: + // Comparisons to support sorted containers. + bool operator==(Line const& line) const; + bool operator!=(Line const& line) const; + bool operator< (Line const& line) const; + bool operator<=(Line const& line) const; + bool operator> (Line const& line) const; + bool operator>=(Line const& line) const; +}; + +// Template aliases for convenience. +template +using Line2 = Line<2, Real>; + +template +using Line3 = Line<3, Real>; + + +template +Line::Line() +{ + origin.MakeZero(); + direction.MakeUnit(0); +} + +template +Line::Line(Vector const& inOrigin, + Vector const& inDirection) + : + origin(inOrigin), + direction(inDirection) +{ +} + +template +bool Line::operator==(Line const& line) const +{ + return origin == line.origin && direction == line.direction; +} + +template +bool Line::operator!=(Line const& line) const +{ + return !operator==(line); +} + +template +bool Line::operator<(Line const& line) const +{ + if (origin < line.origin) + { + return true; + } + + if (origin > line.origin) + { + return false; + } + + return direction < line.direction; +} + +template +bool Line::operator<=(Line const& line) const +{ + return operator<(line) || operator==(line); +} + +template +bool Line::operator>(Line const& line) const +{ + return !operator<=(line); +} + +template +bool Line::operator>=(Line const& line) const +{ + return !operator<(line); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLinearSystem.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLinearSystem.h new file mode 100644 index 000000000000..ce204d82c7fa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLinearSystem.h @@ -0,0 +1,401 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Solve linear systems of equations where the matrix A is NxN. The return +// value of a function is 'true' when A is invertible. In this case the +// solution X and the solution is valid. If the return value is 'false', A +// is not invertible and X and Y are invalid, so do not use them. When a +// matrix is passed as Real*, the storage order is assumed to be the one +// consistent with your choice of GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR. +// +// The linear solvers that use the conjugate gradient algorithm are based +// on the discussion in "Matrix Computations, 2nd edition" by G. H. Golub +// and Charles F. Van Loan, The Johns Hopkins Press, Baltimore MD, Fourth +// Printing 1993. + +namespace gte +{ + +template +class LinearSystem +{ +public: + // Solve 2x2, 3x3, and 4x4 systems by inverting the matrix directly. This + // avoids the overhead of Gaussian elimination in small dimensions. + static bool Solve(Matrix2x2 const& A, Vector2 const& B, + Vector2& X); + + static bool Solve(Matrix3x3 const& A, Vector3 const& B, + Vector3& X); + + static bool Solve(Matrix4x4 const& A, Vector4 const& B, + Vector4& X); + + // Solve A*X = B, where B is Nx1 and the solution X is Nx1. + static bool Solve(int N, Real const* A, Real const* B, Real* X); + + // Solve A*X = B, where B is NxM and the solution X is NxM. + static bool Solve(int N, int M, Real const* A, Real const* B, Real* X); + + // Solve A*X = B, where A is tridiagonal. The function expects the + // subdiagonal, diagonal, and superdiagonal of A. The diagonal input + // must have N elements. The subdiagonal and superdiagonal inputs must + // have N-1 elements. + static bool SolveTridiagonal(int N, Real const* subdiagonal, + Real const* diagonal, Real const* superdiagonal, Real const* B, + Real* X); + + // Solve A*X = B, where A is tridiagonal. The function expects the + // subdiagonal, diagonal, and superdiagonal of A. Moreover, the + // subdiagonal elements are a constant, the diagonal elements are a + // constant, and the superdiagonal elements are a constant. + static bool SolveConstantTridiagonal(int N, Real subdiagonal, + Real diagonal, Real superdiagonal, Real const* B, Real* X); + + // Solve A*X = B using the conjugate gradient method, where A is + // symmetric. You must specify the maximum number of iterations and a + // tolerance for terminating the iterations. Reasonable choices for + // tolerance are 1e-06f for 'float' or 1e-08 for 'double'. + static unsigned int SolveSymmetricCG(int N, Real const* A, Real const* B, + Real* X, unsigned int maxIterations, Real tolerance); + + // Solve A*X = B using the conjugate gradient method, where A is sparse + // and symmetric. The nonzero entries of the symmetrix matrix A are + // stored in a map whose keys are pairs (i,j) and whose values are real + // numbers. The pair (i,j) is the location of the value in the array. + // Only one of (i,j) and (j,i) should be stored since A is symmetric. + // The column vector B is stored as an array of contiguous values. You + // must specify the maximum number of iterations and a tolerance for + // terminating the iterations. Reasonable choices for tolerance are + // 1e-06f for 'float' or 1e-08 for 'double'. + typedef std::map, Real> SparseMatrix; + static unsigned int SolveSymmetricCG(int N, SparseMatrix const& A, + Real const* B, Real* X, unsigned int maxIterations, Real tolerance); + +private: + // Support for the conjugate gradient method. + static Real Dot(int N, Real const* U, Real const* V); + static void Mul(int N, Real const* A, Real const* X, Real* P); + static void Mul(int N, SparseMatrix const& A, Real const* X, Real* P); + static void UpdateX(int N, Real* X, Real alpha, Real const* P); + static void UpdateR(int N, Real* R, Real alpha, Real const* W); + static void UpdateP(int N, Real* P, Real beta, Real const* R); +}; + + +template +bool LinearSystem::Solve(Matrix2x2 const& A, + Vector2 const& B, Vector2& X) +{ + bool invertible; + Matrix2x2 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector2::Zero(); + } + return invertible; +} + +template +bool LinearSystem::Solve(Matrix3x3 const& A, + Vector3 const& B, Vector3& X) +{ + bool invertible; + Matrix3x3 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector3::Zero(); + } + return invertible; +} + +template +bool LinearSystem::Solve(Matrix4x4 const& A, + Vector4 const& B, Vector4& X) +{ + bool invertible; + Matrix4x4 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector4::Zero(); + } + return invertible; +} + +template +bool LinearSystem::Solve(int N, Real const* A, Real const* B, Real* X) +{ + Real determinant; + return GaussianElimination()(N, A, nullptr, determinant, B, X, + nullptr, 0, nullptr); +} + +template +bool LinearSystem::Solve(int N, int M, Real const* A, Real const* B, + Real* X) +{ + Real determinant; + return GaussianElimination()(N, A, nullptr, determinant, nullptr, + nullptr, B, M, X); +} + +template +bool LinearSystem::SolveTridiagonal(int N, Real const* subdiagonal, + Real const* diagonal, Real const* superdiagonal, Real const* B, + Real* X) +{ + if (diagonal[0] == (Real)0) + { + return false; + } + + std::vector tmp(N - 1); + Real expr = diagonal[0]; + Real invExpr = ((Real)1) / expr; + X[0] = B[0] * invExpr; + + int i0, i1; + for (i0 = 0, i1 = 1; i1 < N; ++i0, ++i1) + { + tmp[i0] = superdiagonal[i0] * invExpr; + expr = diagonal[i1] - subdiagonal[i0] * tmp[i0]; + if (expr == (Real)0) + { + return false; + } + invExpr = ((Real)1) / expr; + X[i1] = (B[i1] - subdiagonal[i0] * X[i0]) * invExpr; + } + + for (i0 = N - 1, i1 = N - 2; i1 >= 0; --i0, --i1) + { + X[i1] -= tmp[i1] * X[i0]; + } + return true; +} + +template +bool LinearSystem::SolveConstantTridiagonal(int N, Real subdiagonal, + Real diagonal, Real superdiagonal, Real const* B, Real* X) +{ + if (diagonal == (Real)0) + { + return false; + } + + std::vector tmp(N - 1); + Real expr = diagonal; + Real invExpr = ((Real)1) / expr; + X[0] = B[0] * invExpr; + + int i0, i1; + for (i0 = 0, i1 = 1; i1 < N; ++i0, ++i1) + { + tmp[i0] = superdiagonal * invExpr; + expr = diagonal - subdiagonal * tmp[i0]; + if (expr == (Real)0) + { + return false; + } + invExpr = ((Real)1) / expr; + X[i1] = (B[i1] - subdiagonal * X[i0]) * invExpr; + } + + for (i0 = N - 1, i1 = N - 2; i1 >= 0; --i0, --i1) + { + X[i1] -= tmp[i1] * X[i0]; + } + return true; +} + +template +unsigned int LinearSystem::SolveSymmetricCG(int N, Real const* A, + Real const* B, Real* X, unsigned int maxIterations, Real tolerance) +{ + // The first iteration. + std::vector tmpR(N), tmpP(N), tmpW(N); + Real* R = &tmpR[0]; + Real* P = &tmpP[0]; + Real* W = &tmpW[0]; + size_t numBytes = N * sizeof(Real); + memset(X, 0, numBytes); + Memcpy(R, B, numBytes); + Real rho0 = Dot(N, R, R); + Memcpy(P, R, numBytes); + Mul(N, A, P, W); + Real alpha = rho0 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + Real rho1 = Dot(N, R, R); + + // The remaining iterations. + unsigned int iteration; + for (iteration = 1; iteration <= maxIterations; ++iteration) + { + Real root0 = std::sqrt(rho1); + Real norm = Dot(N, B, B); + Real root1 = std::sqrt(norm); + if (root0 <= tolerance*root1) + { + break; + } + + Real beta = rho1 / rho0; + UpdateP(N, P, beta, R); + Mul(N, A, P, W); + alpha = rho1 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + rho0 = rho1; + rho1 = Dot(N, R, R); + } + return iteration; +} + +template +unsigned int LinearSystem::SolveSymmetricCG(int N, + SparseMatrix const& A, Real const* B, Real* X, unsigned int maxIterations, + Real tolerance) +{ + // The first iteration. + std::vector tmpR(N), tmpP(N), tmpW(N); + Real* R = &tmpR[0]; + Real* P = &tmpP[0]; + Real* W = &tmpW[0]; + size_t numBytes = N * sizeof(Real); + memset(X, 0, numBytes); + Memcpy(R, B, numBytes); + Real rho0 = Dot(N, R, R); + Memcpy(P, R, numBytes); + Mul(N, A, P, W); + Real alpha = rho0 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + Real rho1 = Dot(N, R, R); + + // The remaining iterations. + unsigned int iteration; + for (iteration = 1; iteration <= maxIterations; ++iteration) + { + Real root0 = std::sqrt(rho1); + Real norm = Dot(N, B, B); + Real root1 = std::sqrt(norm); + if (root0 <= tolerance*root1) + { + break; + } + + Real beta = rho1 / rho0; + UpdateP(N, P, beta, R); + Mul(N, A, P, W); + alpha = rho1 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + rho0 = rho1; + rho1 = Dot(N, R, R); + } + return iteration; +} + +template +Real LinearSystem::Dot(int N, Real const* U, Real const* V) +{ + Real dot = (Real)0; + for (int i = 0; i < N; ++i) + { + dot += U[i] * V[i]; + } + return dot; +} + +template +void LinearSystem::Mul(int N, Real const* A, Real const* X, Real* P) +{ +#if defined(GTE_USE_ROW_MAJOR) + LexicoArray2 matA(N, N, const_cast(A)); +#else + LexicoArray2 matA(N, N, const_cast(A)); +#endif + + memset(P, 0, N * sizeof(Real)); + for (int row = 0; row < N; ++row) + { + for (int col = 0; col < N; ++col) + { + P[row] += matA(row, col) * X[col]; + } + } +} + +template +void LinearSystem::Mul(int N, SparseMatrix const& A, Real const* X, + Real* P) +{ + memset(P, 0, N * sizeof(Real)); + for (auto const& element : A) + { + int i = element.first[0]; + int j = element.first[1]; + Real value = element.second; + P[i] += value * X[j]; + if (i != j) + { + P[j] += value * X[i]; + } + } +} + +template +void LinearSystem::UpdateX(int N, Real* X, Real alpha, Real const* P) +{ + for (int i = 0; i < N; ++i) + { + X[i] += alpha*P[i]; + } +} + +template +void LinearSystem::UpdateR(int N, Real* R, Real alpha, Real const* W) +{ + for (int i = 0; i < N; ++i) + { + R[i] -= alpha*W[i]; + } +} + +template +void LinearSystem::UpdateP(int N, Real* P, Real beta, Real const* R) +{ + for (int i = 0; i < N; ++i) + { + P[i] = R[i] + beta*P[i]; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLog2Estimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLog2Estimate.h new file mode 100644 index 000000000000..45767f529211 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLog2Estimate.h @@ -0,0 +1,174 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to log2(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|log2(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class Log2Estimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = Log2Estimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x > 0. Range reduction is used to generate a + // value y in (0,1], call Degree(y), and add the exponent for the power + // of two in the binary scientific representation of x. For example, + // float x; // x > 0 + // float result = Log2Estimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); + inline static Real Evaluate(degree<8>, Real t); +}; + + +template +template +inline Real Log2Estimate::Degree(Real x) +{ + Real t = x - (Real)1; // t in (0,1] + return Evaluate(degree(), t); +} + +template +template +inline Real Log2Estimate::DegreeRR(Real x) +{ + int p; + Real y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2)*y; // y in [1,2) + --p; + Real poly = Degree(y); + Real result = poly + (Real)p; + return result; +} + +template +inline Real Log2Estimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG1_C1; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG2_C2; + poly = (Real)GTE_C_LOG2_DEG2_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG3_C3; + poly = (Real)GTE_C_LOG2_DEG3_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG3_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG4_C4; + poly = (Real)GTE_C_LOG2_DEG4_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG4_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG4_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG5_C5; + poly = (Real)GTE_C_LOG2_DEG5_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG6_C6; + poly = (Real)GTE_C_LOG2_DEG6_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG7_C7; + poly = (Real)GTE_C_LOG2_DEG7_C6 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C1 + poly * t; + poly = poly * t; + return poly; +} + +template +inline Real Log2Estimate::Evaluate(degree<8>, Real t) +{ + Real poly; + poly = (Real)GTE_C_LOG2_DEG8_C8; + poly = (Real)GTE_C_LOG2_DEG8_C7 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C6 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C1 + poly * t; + poly = poly * t; + return poly; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLogEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLogEstimate.h new file mode 100644 index 000000000000..562906421780 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteLogEstimate.h @@ -0,0 +1,55 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Minimax polynomial approximations to log2(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|log2(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. The natural logarithm is computed +// using log(x) = log2(x)/log2(e) = log2(x)*log(2). + +namespace gte +{ + +template +class LogEstimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = LogEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x > 0. Range reduction is used to generate a + // value y in (0,1], call Degree(y), and add the exponent for the power + // of two in the binary scientific representation of x. For example, + // float x; // x > 0 + // float result = LogEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); +}; + + +template +template +inline Real LogEstimate::Degree(Real x) +{ + return Log2Estimate::Degree(x) * (Real)GTE_C_INV_LN_2; +} + +template +template +inline Real LogEstimate::DegreeRR(Real x) +{ + return Log2Estimate::DegreeRR(x) * (Real)GTE_C_INV_LN_2; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMath.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMath.h new file mode 100644 index 000000000000..39379617ce80 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMath.h @@ -0,0 +1,654 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.17.1 (2019/01/17) + +#pragma once + +// This file extends the support to include convenient constants and +// functions. The shared constants for CPU, Intel SSE and GPU lead to +// correctly rounded approximations of the constants when using 'float' or +// 'double'. The file also includes a type trait, is_arbitrary_precision, +// to support selecting between floating-point arithmetic (float, double, +//long double) or arbitrary-precision arithmetic (BSNumber, BSRational) +// in an implementation using std::enable_if. There is also a type trait, +// has_division_operator, to support selecting between numeric types that +// have a division operator (BSRational) and those that do not have a +// division operator (BSNumber). + +#include +#include +#include +#include + +// Maximum number of iterations for bisection before a subinterval +// degenerates to a single point. TODO: Verify these. I used the formula: +// 3 + std::numeric_limits::digits - std::numeric_limits::min_exponent. +// IEEEBinary16: digits = 11, min_exponent = -13 +// float: digits = 27, min_exponent = -125 +// double: digits = 53, min_exponent = -1021 +// BSNumber and BSRational use std::numeric_limits::max(), +// but maybe these should be set to a large number or be user configurable. +// The MAX_BISECTIONS_GENERIC is an arbitrary choice for now and is used +// in template code where Real is the template parameter. +#define GTE_C_MAX_BISECTIONS_FLOAT16 27u +#define GTE_C_MAX_BISECTIONS_FLOAT32 155u +#define GTE_C_MAX_BISECTIONS_FLOAT64 1077u +#define GTE_C_MAX_BISECTIONS_BSNUMBER 0xFFFFFFFFu +#define GTE_C_MAX_BISECTIONS_BSRATIONAL 0xFFFFFFFFu +#define GTE_C_MAX_BISECTIONS_GENERIC 2048u + +// Constants involving pi. +#define GTE_C_PI 3.1415926535897931 +#define GTE_C_HALF_PI 1.5707963267948966 +#define GTE_C_QUARTER_PI 0.7853981633974483 +#define GTE_C_TWO_PI 6.2831853071795862 +#define GTE_C_INV_PI 0.3183098861837907 +#define GTE_C_INV_TWO_PI 0.1591549430918953 +#define GTE_C_INV_HALF_PI 0.6366197723675813 + +// Conversions between degrees and radians. +#define GTE_C_DEG_TO_RAD 0.0174532925199433 +#define GTE_C_RAD_TO_DEG 57.295779513082321 + +// Common constants. +#define GTE_C_SQRT_2 1.4142135623730951 +#define GTE_C_INV_SQRT_2 0.7071067811865475 +#define GTE_C_LN_2 0.6931471805599453 +#define GTE_C_INV_LN_2 1.4426950408889634 +#define GTE_C_LN_10 2.3025850929940459 +#define GTE_C_INV_LN_10 0.43429448190325176 + +// Constants for minimax polynomial approximations to sqrt(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +#define GTE_C_SQRT_DEG1_C0 +1.0 +#define GTE_C_SQRT_DEG1_C1 +4.1421356237309505e-01 +#define GTE_C_SQRT_DEG1_MAX_ERROR 1.7766952966368793e-2 + +#define GTE_C_SQRT_DEG2_C0 +1.0 +#define GTE_C_SQRT_DEG2_C1 +4.8563183076125260e-01 +#define GTE_C_SQRT_DEG2_C2 -7.1418268388157458e-02 +#define GTE_C_SQRT_DEG2_MAX_ERROR 1.1795695163108744e-3 + +#define GTE_C_SQRT_DEG3_C0 +1.0 +#define GTE_C_SQRT_DEG3_C1 +4.9750045320242231e-01 +#define GTE_C_SQRT_DEG3_C2 -1.0787308044477850e-01 +#define GTE_C_SQRT_DEG3_C3 +2.4586189615451115e-02 +#define GTE_C_SQRT_DEG3_MAX_ERROR 1.1309620116468910e-4 + +#define GTE_C_SQRT_DEG4_C0 +1.0 +#define GTE_C_SQRT_DEG4_C1 +4.9955939832918816e-01 +#define GTE_C_SQRT_DEG4_C2 -1.2024066151943025e-01 +#define GTE_C_SQRT_DEG4_C3 +4.5461507257698486e-02 +#define GTE_C_SQRT_DEG4_C4 -1.0566681694362146e-02 +#define GTE_C_SQRT_DEG4_MAX_ERROR 1.2741170151556180e-5 + +#define GTE_C_SQRT_DEG5_C0 +1.0 +#define GTE_C_SQRT_DEG5_C1 +4.9992197660031912e-01 +#define GTE_C_SQRT_DEG5_C2 -1.2378506719245053e-01 +#define GTE_C_SQRT_DEG5_C3 +5.6122776972699739e-02 +#define GTE_C_SQRT_DEG5_C4 -2.3128836281145482e-02 +#define GTE_C_SQRT_DEG5_C5 +5.0827122737047148e-03 +#define GTE_C_SQRT_DEG5_MAX_ERROR 1.5725568940708201e-6 + +#define GTE_C_SQRT_DEG6_C0 +1.0 +#define GTE_C_SQRT_DEG6_C1 +4.9998616695784914e-01 +#define GTE_C_SQRT_DEG6_C2 -1.2470733323278438e-01 +#define GTE_C_SQRT_DEG6_C3 +6.0388587356982271e-02 +#define GTE_C_SQRT_DEG6_C4 -3.1692053551807930e-02 +#define GTE_C_SQRT_DEG6_C5 +1.2856590305148075e-02 +#define GTE_C_SQRT_DEG6_C6 -2.6183954624343642e-03 +#define GTE_C_SQRT_DEG6_MAX_ERROR 2.0584155535630089e-7 + +#define GTE_C_SQRT_DEG7_C0 +1.0 +#define GTE_C_SQRT_DEG7_C1 +4.9999754817809228e-01 +#define GTE_C_SQRT_DEG7_C2 -1.2493243476353655e-01 +#define GTE_C_SQRT_DEG7_C3 +6.1859954146370910e-02 +#define GTE_C_SQRT_DEG7_C4 -3.6091595023208356e-02 +#define GTE_C_SQRT_DEG7_C5 +1.9483946523450868e-02 +#define GTE_C_SQRT_DEG7_C6 -7.5166134568007692e-03 +#define GTE_C_SQRT_DEG7_C7 +1.4127567687864939e-03 +#define GTE_C_SQRT_DEG7_MAX_ERROR 2.8072302919734948e-8 + +#define GTE_C_SQRT_DEG8_C0 +1.0 +#define GTE_C_SQRT_DEG8_C1 +4.9999956583056759e-01 +#define GTE_C_SQRT_DEG8_C2 -1.2498490369914350e-01 +#define GTE_C_SQRT_DEG8_C3 +6.2318494667579216e-02 +#define GTE_C_SQRT_DEG8_C4 -3.7982961896432244e-02 +#define GTE_C_SQRT_DEG8_C5 +2.3642612312869460e-02 +#define GTE_C_SQRT_DEG8_C6 -1.2529377587270574e-02 +#define GTE_C_SQRT_DEG8_C7 +4.5382426960713929e-03 +#define GTE_C_SQRT_DEG8_C8 -7.8810995273670414e-04 +#define GTE_C_SQRT_DEG8_MAX_ERROR 3.9460605685825989e-9 + +// Constants for minimax polynomial approximations to 1/sqrt(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +#define GTE_C_INVSQRT_DEG1_C0 +1.0 +#define GTE_C_INVSQRT_DEG1_C1 -2.9289321881345254e-01 +#define GTE_C_INVSQRT_DEG1_MAX_ERROR 3.7814314552701983e-2 + +#define GTE_C_INVSQRT_DEG2_C0 +1.0 +#define GTE_C_INVSQRT_DEG2_C1 -4.4539812104566801e-01 +#define GTE_C_INVSQRT_DEG2_C2 +1.5250490223221547e-01 +#define GTE_C_INVSQRT_DEG2_MAX_ERROR 4.1953446330581234e-3 + +#define GTE_C_INVSQRT_DEG3_C0 +1.0 +#define GTE_C_INVSQRT_DEG3_C1 -4.8703230993068791e-01 +#define GTE_C_INVSQRT_DEG3_C2 +2.8163710486669835e-01 +#define GTE_C_INVSQRT_DEG3_C3 -8.7498013749463421e-02 +#define GTE_C_INVSQRT_DEG3_MAX_ERROR 5.6307702007266786e-4 + +#define GTE_C_INVSQRT_DEG4_C0 +1.0 +#define GTE_C_INVSQRT_DEG4_C1 -4.9710061558048779e-01 +#define GTE_C_INVSQRT_DEG4_C2 +3.4266247597676802e-01 +#define GTE_C_INVSQRT_DEG4_C3 -1.9106356536293490e-01 +#define GTE_C_INVSQRT_DEG4_C4 +5.2608486153198797e-02 +#define GTE_C_INVSQRT_DEG4_MAX_ERROR 8.1513919987605266e-5 + +#define GTE_C_INVSQRT_DEG5_C0 +1.0 +#define GTE_C_INVSQRT_DEG5_C1 -4.9937760586004143e-01 +#define GTE_C_INVSQRT_DEG5_C2 +3.6508741295133973e-01 +#define GTE_C_INVSQRT_DEG5_C3 -2.5884890281853501e-01 +#define GTE_C_INVSQRT_DEG5_C4 +1.3275782221320753e-01 +#define GTE_C_INVSQRT_DEG5_C5 -3.2511945299404488e-02 +#define GTE_C_INVSQRT_DEG5_MAX_ERROR 1.2289367475583346e-5 + +#define GTE_C_INVSQRT_DEG6_C0 +1.0 +#define GTE_C_INVSQRT_DEG6_C1 -4.9987029229547453e-01 +#define GTE_C_INVSQRT_DEG6_C2 +3.7220923604495226e-01 +#define GTE_C_INVSQRT_DEG6_C3 -2.9193067713256937e-01 +#define GTE_C_INVSQRT_DEG6_C4 +1.9937605991094642e-01 +#define GTE_C_INVSQRT_DEG6_C5 -9.3135712130901993e-02 +#define GTE_C_INVSQRT_DEG6_C6 +2.0458166789566690e-02 +#define GTE_C_INVSQRT_DEG6_MAX_ERROR 1.9001451223750465e-6 + +#define GTE_C_INVSQRT_DEG7_C0 +1.0 +#define GTE_C_INVSQRT_DEG7_C1 -4.9997357250704977e-01 +#define GTE_C_INVSQRT_DEG7_C2 +3.7426216884998809e-01 +#define GTE_C_INVSQRT_DEG7_C3 -3.0539882498248971e-01 +#define GTE_C_INVSQRT_DEG7_C4 +2.3976005607005391e-01 +#define GTE_C_INVSQRT_DEG7_C5 -1.5410326351684489e-01 +#define GTE_C_INVSQRT_DEG7_C6 +6.5598809723041995e-02 +#define GTE_C_INVSQRT_DEG7_C7 -1.3038592450470787e-02 +#define GTE_C_INVSQRT_DEG7_MAX_ERROR 2.9887724993168940e-7 + +#define GTE_C_INVSQRT_DEG8_C0 +1.0 +#define GTE_C_INVSQRT_DEG8_C1 -4.9999471066120371e-01 +#define GTE_C_INVSQRT_DEG8_C2 +3.7481415745794067e-01 +#define GTE_C_INVSQRT_DEG8_C3 -3.1023804387422160e-01 +#define GTE_C_INVSQRT_DEG8_C4 +2.5977002682930106e-01 +#define GTE_C_INVSQRT_DEG8_C5 -1.9818790717727097e-01 +#define GTE_C_INVSQRT_DEG8_C6 +1.1882414252613671e-01 +#define GTE_C_INVSQRT_DEG8_C7 -4.6270038088550791e-02 +#define GTE_C_INVSQRT_DEG8_C8 +8.3891541755747312e-03 +#define GTE_C_INVSQRT_DEG8_MAX_ERROR 4.7596926146947771e-8 + +// Constants for minimax polynomial approximations to sin(x). +// The algorithm minimizes the maximum absolute error on [-pi/2,pi/2]. +#define GTE_C_SIN_DEG3_C0 +1.0 +#define GTE_C_SIN_DEG3_C1 -1.4727245910375519e-01 +#define GTE_C_SIN_DEG3_MAX_ERROR 1.3481903639145865e-2 + +#define GTE_C_SIN_DEG5_C0 +1.0 +#define GTE_C_SIN_DEG5_C1 -1.6600599923812209e-01 +#define GTE_C_SIN_DEG5_C2 +7.5924178409012000e-03 +#define GTE_C_SIN_DEG5_MAX_ERROR 1.4001209384639779e-4 + +#define GTE_C_SIN_DEG7_C0 +1.0 +#define GTE_C_SIN_DEG7_C1 -1.6665578084732124e-01 +#define GTE_C_SIN_DEG7_C2 +8.3109378830028557e-03 +#define GTE_C_SIN_DEG7_C3 -1.8447486103462252e-04 +#define GTE_C_SIN_DEG7_MAX_ERROR 1.0205878936686563e-6 + +#define GTE_C_SIN_DEG9_C0 +1.0 +#define GTE_C_SIN_DEG9_C1 -1.6666656235308897e-01 +#define GTE_C_SIN_DEG9_C2 +8.3329962509886002e-03 +#define GTE_C_SIN_DEG9_C3 -1.9805100675274190e-04 +#define GTE_C_SIN_DEG9_C4 +2.5967200279475300e-06 +#define GTE_C_SIN_DEG9_MAX_ERROR 5.2010746265374053e-9 + +#define GTE_C_SIN_DEG11_C0 +1.0 +#define GTE_C_SIN_DEG11_C1 -1.6666666601721269e-01 +#define GTE_C_SIN_DEG11_C2 +8.3333303183525942e-03 +#define GTE_C_SIN_DEG11_C3 -1.9840782426250314e-04 +#define GTE_C_SIN_DEG11_C4 +2.7521557770526783e-06 +#define GTE_C_SIN_DEG11_C5 -2.3828544692960918e-08 +#define GTE_C_SIN_DEG11_MAX_ERROR 1.9295870457014530e-11 + +// Constants for minimax polynomial approximations to cos(x). +// The algorithm minimizes the maximum absolute error on [-pi/2,pi/2]. +#define GTE_C_COS_DEG2_C0 +1.0 +#define GTE_C_COS_DEG2_C1 -4.0528473456935105e-01 +#define GTE_C_COS_DEG2_MAX_ERROR 5.4870946878404048e-2 + +#define GTE_C_COS_DEG4_C0 +1.0 +#define GTE_C_COS_DEG4_C1 -4.9607181958647262e-01 +#define GTE_C_COS_DEG4_C2 +3.6794619653489236e-02 +#define GTE_C_COS_DEG4_MAX_ERROR 9.1879932449712154e-4 + +#define GTE_C_COS_DEG6_C0 +1.0 +#define GTE_C_COS_DEG6_C1 -4.9992746217057404e-01 +#define GTE_C_COS_DEG6_C2 +4.1493920348353308e-02 +#define GTE_C_COS_DEG6_C3 -1.2712435011987822e-03 +#define GTE_C_COS_DEG6_MAX_ERROR 9.2028470133065365e-6 + +#define GTE_C_COS_DEG8_C0 +1.0 +#define GTE_C_COS_DEG8_C1 -4.9999925121358291e-01 +#define GTE_C_COS_DEG8_C2 +4.1663780117805693e-02 +#define GTE_C_COS_DEG8_C3 -1.3854239405310942e-03 +#define GTE_C_COS_DEG8_C4 +2.3154171575501259e-05 +#define GTE_C_COS_DEG8_MAX_ERROR 5.9804533020235695e-8 + +#define GTE_C_COS_DEG10_C0 +1.0 +#define GTE_C_COS_DEG10_C1 -4.9999999508695869e-01 +#define GTE_C_COS_DEG10_C2 +4.1666638865338612e-02 +#define GTE_C_COS_DEG10_C3 -1.3888377661039897e-03 +#define GTE_C_COS_DEG10_C4 +2.4760495088926859e-05 +#define GTE_C_COS_DEG10_C5 -2.6051615464872668e-07 +#define GTE_C_COS_DEG10_MAX_ERROR 2.7006769043325107e-10 + +// Constants for minimax polynomial approximations to tan(x). +// The algorithm minimizes the maximum absolute error on [-pi/4,pi/4]. +#define GTE_C_TAN_DEG3_C0 1.0 +#define GTE_C_TAN_DEG3_C1 4.4295926544736286e-01 +#define GTE_C_TAN_DEG3_MAX_ERROR 1.1661892256204731e-2 + +#define GTE_C_TAN_DEG5_C0 1.0 +#define GTE_C_TAN_DEG5_C1 3.1401320403542421e-01 +#define GTE_C_TAN_DEG5_C2 2.0903948109240345e-01 +#define GTE_C_TAN_DEG5_MAX_ERROR 5.8431854390143118e-4 + +#define GTE_C_TAN_DEG7_C0 1.0 +#define GTE_C_TAN_DEG7_C1 3.3607213284422555e-01 +#define GTE_C_TAN_DEG7_C2 1.1261037305184907e-01 +#define GTE_C_TAN_DEG7_C3 9.8352099470524479e-02 +#define GTE_C_TAN_DEG7_MAX_ERROR 3.5418688397723108e-5 + +#define GTE_C_TAN_DEG9_C0 1.0 +#define GTE_C_TAN_DEG9_C1 3.3299232843941784e-01 +#define GTE_C_TAN_DEG9_C2 1.3747843432474838e-01 +#define GTE_C_TAN_DEG9_C3 3.7696344813028304e-02 +#define GTE_C_TAN_DEG9_C4 4.6097377279281204e-02 +#define GTE_C_TAN_DEG9_MAX_ERROR 2.2988173242199927e-6 + +#define GTE_C_TAN_DEG11_C0 1.0 +#define GTE_C_TAN_DEG11_C1 3.3337224456224224e-01 +#define GTE_C_TAN_DEG11_C2 1.3264516053824593e-01 +#define GTE_C_TAN_DEG11_C3 5.8145237645931047e-02 +#define GTE_C_TAN_DEG11_C4 1.0732193237572574e-02 +#define GTE_C_TAN_DEG11_C5 2.1558456793513869e-02 +#define GTE_C_TAN_DEG11_MAX_ERROR 1.5426257940140409e-7 + +#define GTE_C_TAN_DEG13_C0 1.0 +#define GTE_C_TAN_DEG13_C1 3.3332916426394554e-01 +#define GTE_C_TAN_DEG13_C2 1.3343404625112498e-01 +#define GTE_C_TAN_DEG13_C3 5.3104565343119248e-02 +#define GTE_C_TAN_DEG13_C4 2.5355038312682154e-02 +#define GTE_C_TAN_DEG13_C5 1.8253255966556026e-03 +#define GTE_C_TAN_DEG13_C6 1.0069407176615641e-02 +#define GTE_C_TAN_DEG13_MAX_ERROR 1.0550264249037378e-8 + +// Constants for minimax polynomial approximations to acos(x), where the +// approximation is of the form acos(x) = sqrt(1 - x)*p(x) with p(x) a +// polynomial. The algorithm minimizes the maximum error +// |acos(x)/sqrt(1-x) - p(x)| on [0,1]. At the same time we get an +// approximation for asin(x) = pi/2 - acos(x). +#define GTE_C_ACOS_DEG1_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG1_C1 -1.5658276442180141e-01 +#define GTE_C_ACOS_DEG1_MAX_ERROR 1.1659002803738105e-2 + +#define GTE_C_ACOS_DEG2_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG2_C1 -2.0347053865798365e-01 +#define GTE_C_ACOS_DEG2_C2 +4.6887774236182234e-02 +#define GTE_C_ACOS_DEG2_MAX_ERROR 9.0311602490029258e-4 + +#define GTE_C_ACOS_DEG3_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG3_C1 -2.1253291899190285e-01 +#define GTE_C_ACOS_DEG3_C2 +7.4773789639484223e-02 +#define GTE_C_ACOS_DEG3_C3 -1.8823635069382449e-02 +#define GTE_C_ACOS_DEG3_MAX_ERROR 9.3066396954288172e-5 + +#define GTE_C_ACOS_DEG4_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG4_C1 -2.1422258835275865e-01 +#define GTE_C_ACOS_DEG4_C2 +8.4936675142844198e-02 +#define GTE_C_ACOS_DEG4_C3 -3.5991475120957794e-02 +#define GTE_C_ACOS_DEG4_C4 +8.6946239090712751e-03 +#define GTE_C_ACOS_DEG4_MAX_ERROR 1.0930595804481413e-5 + +#define GTE_C_ACOS_DEG5_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG5_C1 -2.1453292139805524e-01 +#define GTE_C_ACOS_DEG5_C2 +8.7973089282889383e-02 +#define GTE_C_ACOS_DEG5_C3 -4.5130266382166440e-02 +#define GTE_C_ACOS_DEG5_C4 +1.9467466687281387e-02 +#define GTE_C_ACOS_DEG5_C5 -4.3601326117634898e-03 +#define GTE_C_ACOS_DEG5_MAX_ERROR 1.3861070257241426-6 + +#define GTE_C_ACOS_DEG6_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG6_C1 -2.1458939285677325e-01 +#define GTE_C_ACOS_DEG6_C2 +8.8784960563641491e-02 +#define GTE_C_ACOS_DEG6_C3 -4.8887131453156485e-02 +#define GTE_C_ACOS_DEG6_C4 +2.7011519960012720e-02 +#define GTE_C_ACOS_DEG6_C5 -1.1210537323478320e-02 +#define GTE_C_ACOS_DEG6_C6 +2.3078166879102469e-03 +#define GTE_C_ACOS_DEG6_MAX_ERROR 1.8491291330427484e-7 + +#define GTE_C_ACOS_DEG7_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG7_C1 -2.1459960076929829e-01 +#define GTE_C_ACOS_DEG7_C2 +8.8986946573346160e-02 +#define GTE_C_ACOS_DEG7_C3 -5.0207843052845647e-02 +#define GTE_C_ACOS_DEG7_C4 +3.0961594977611639e-02 +#define GTE_C_ACOS_DEG7_C5 -1.7162031184398074e-02 +#define GTE_C_ACOS_DEG7_C6 +6.7072304676685235e-03 +#define GTE_C_ACOS_DEG7_C7 -1.2690614339589956e-03 +#define GTE_C_ACOS_DEG7_MAX_ERROR 2.5574620927948377e-8 + +#define GTE_C_ACOS_DEG8_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG8_C1 -2.1460143648688035e-01 +#define GTE_C_ACOS_DEG8_C2 +8.9034700107934128e-02 +#define GTE_C_ACOS_DEG8_C3 -5.0625279962389413e-02 +#define GTE_C_ACOS_DEG8_C4 +3.2683762943179318e-02 +#define GTE_C_ACOS_DEG8_C5 -2.0949278766238422e-02 +#define GTE_C_ACOS_DEG8_C6 +1.1272900916992512e-02 +#define GTE_C_ACOS_DEG8_C7 -4.1160981058965262e-03 +#define GTE_C_ACOS_DEG8_C8 +7.1796493341480527e-04 +#define GTE_C_ACOS_DEG8_MAX_ERROR 3.6340015129032732e-9 + +// Constants for minimax polynomial approximations to atan(x). +// The algorithm minimizes the maximum absolute error on [-1,1]. +#define GTE_C_ATAN_DEG3_C0 +1.0 +#define GTE_C_ATAN_DEG3_C1 -2.1460183660255172e-01 +#define GTE_C_ATAN_DEG3_MAX_ERROR 1.5970326392614240e-2 + +#define GTE_C_ATAN_DEG5_C0 +1.0 +#define GTE_C_ATAN_DEG5_C1 -3.0189478312144946e-01 +#define GTE_C_ATAN_DEG5_C2 +8.7292946518897740e-02 +#define GTE_C_ATAN_DEG5_MAX_ERROR 1.3509832247372636e-3 + +#define GTE_C_ATAN_DEG7_C0 +1.0 +#define GTE_C_ATAN_DEG7_C1 -3.2570157599356531e-01 +#define GTE_C_ATAN_DEG7_C2 +1.5342994884206673e-01 +#define GTE_C_ATAN_DEG7_C3 -4.2330209451053591e-02 +#define GTE_C_ATAN_DEG7_MAX_ERROR 1.5051227215514412e-4 + +#define GTE_C_ATAN_DEG9_C0 +1.0 +#define GTE_C_ATAN_DEG9_C1 -3.3157878236439586e-01 +#define GTE_C_ATAN_DEG9_C2 +1.8383034738018011e-01 +#define GTE_C_ATAN_DEG9_C3 -8.9253037587244677e-02 +#define GTE_C_ATAN_DEG9_C4 +2.2399635968909593e-02 +#define GTE_C_ATAN_DEG9_MAX_ERROR 1.8921598624582064e-5 + +#define GTE_C_ATAN_DEG11_C0 +1.0 +#define GTE_C_ATAN_DEG11_C1 -3.3294527685374087e-01 +#define GTE_C_ATAN_DEG11_C2 +1.9498657165383548e-01 +#define GTE_C_ATAN_DEG11_C3 -1.1921576270475498e-01 +#define GTE_C_ATAN_DEG11_C4 +5.5063351366968050e-02 +#define GTE_C_ATAN_DEG11_C5 -1.2490720064867844e-02 +#define GTE_C_ATAN_DEG11_MAX_ERROR 2.5477724974187765e-6 + +#define GTE_C_ATAN_DEG13_C0 +1.0 +#define GTE_C_ATAN_DEG13_C1 -3.3324998579202170e-01 +#define GTE_C_ATAN_DEG13_C2 +1.9856563505717162e-01 +#define GTE_C_ATAN_DEG13_C3 -1.3374657325451267e-01 +#define GTE_C_ATAN_DEG13_C4 +8.1675882859940430e-02 +#define GTE_C_ATAN_DEG13_C5 -3.5059680836411644e-02 +#define GTE_C_ATAN_DEG13_C6 +7.2128853633444123e-03 +#define GTE_C_ATAN_DEG13_MAX_ERROR 3.5859104691865484e-7 + +// Constants for minimax polynomial approximations to exp2(x) = 2^x. +// The algorithm minimizes the maximum absolute error on [0,1]. +#define GTE_C_EXP2_DEG1_C0 1.0 +#define GTE_C_EXP2_DEG1_C1 1.0 +#define GTE_C_EXP2_DEG1_MAX_ERROR 8.6071332055934313e-2 + +#define GTE_C_EXP2_DEG2_C0 1.0 +#define GTE_C_EXP2_DEG2_C1 6.5571332605741528e-01 +#define GTE_C_EXP2_DEG2_C2 3.4428667394258472e-01 +#define GTE_C_EXP2_DEG2_MAX_ERROR 3.8132476831060358e-3 + +#define GTE_C_EXP2_DEG3_C0 1.0 +#define GTE_C_EXP2_DEG3_C1 6.9589012084456225e-01 +#define GTE_C_EXP2_DEG3_C2 2.2486494900110188e-01 +#define GTE_C_EXP2_DEG3_C3 7.9244930154334980e-02 +#define GTE_C_EXP2_DEG3_MAX_ERROR 1.4694877755186408e-4 + +#define GTE_C_EXP2_DEG4_C0 1.0 +#define GTE_C_EXP2_DEG4_C1 6.9300392358459195e-01 +#define GTE_C_EXP2_DEG4_C2 2.4154981722455560e-01 +#define GTE_C_EXP2_DEG4_C3 5.1744260331489045e-02 +#define GTE_C_EXP2_DEG4_C4 1.3701998859367848e-02 +#define GTE_C_EXP2_DEG4_MAX_ERROR 4.7617792624521371e-6 + +#define GTE_C_EXP2_DEG5_C0 1.0 +#define GTE_C_EXP2_DEG5_C1 6.9315298010274962e-01 +#define GTE_C_EXP2_DEG5_C2 2.4014712313022102e-01 +#define GTE_C_EXP2_DEG5_C3 5.5855296413199085e-02 +#define GTE_C_EXP2_DEG5_C4 8.9477503096873079e-03 +#define GTE_C_EXP2_DEG5_C5 1.8968500441332026e-03 +#define GTE_C_EXP2_DEG5_MAX_ERROR 1.3162098333463490e-7 + +#define GTE_C_EXP2_DEG6_C0 1.0 +#define GTE_C_EXP2_DEG6_C1 6.9314698914837525e-01 +#define GTE_C_EXP2_DEG6_C2 2.4023013440952923e-01 +#define GTE_C_EXP2_DEG6_C3 5.5481276898206033e-02 +#define GTE_C_EXP2_DEG6_C4 9.6838443037086108e-03 +#define GTE_C_EXP2_DEG6_C5 1.2388324048515642e-03 +#define GTE_C_EXP2_DEG6_C6 2.1892283501756538e-04 +#define GTE_C_EXP2_DEG6_MAX_ERROR 3.1589168225654163e-9 + +#define GTE_C_EXP2_DEG7_C0 1.0 +#define GTE_C_EXP2_DEG7_C1 6.9314718588750690e-01 +#define GTE_C_EXP2_DEG7_C2 2.4022637363165700e-01 +#define GTE_C_EXP2_DEG7_C3 5.5505235570535660e-02 +#define GTE_C_EXP2_DEG7_C4 9.6136265387940512e-03 +#define GTE_C_EXP2_DEG7_C5 1.3429234504656051e-03 +#define GTE_C_EXP2_DEG7_C6 1.4299202757683815e-04 +#define GTE_C_EXP2_DEG7_C7 2.1662892777385423e-05 +#define GTE_C_EXP2_DEG7_MAX_ERROR 6.6864513925679603e-11 + +// Constants for minimax polynomial approximations to log2(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +// The polynomials all have constant term zero. +#define GTE_C_LOG2_DEG1_C1 +1.0 +#define GTE_C_LOG2_DEG1_MAX_ERROR 8.6071332055934202e-2 + +#define GTE_C_LOG2_DEG2_C1 +1.3465553856377803 +#define GTE_C_LOG2_DEG2_C2 -3.4655538563778032e-01 +#define GTE_C_LOG2_DEG2_MAX_ERROR 7.6362868906658110e-3 + +#define GTE_C_LOG2_DEG3_C1 +1.4228653756681227 +#define GTE_C_LOG2_DEG3_C2 -5.8208556916449616e-01 +#define GTE_C_LOG2_DEG3_C3 +1.5922019349637218e-01 +#define GTE_C_LOG2_DEG3_MAX_ERROR 8.7902902652883808e-4 + +#define GTE_C_LOG2_DEG4_C1 +1.4387257478171547 +#define GTE_C_LOG2_DEG4_C2 -6.7778401359918661e-01 +#define GTE_C_LOG2_DEG4_C3 +3.2118898377713379e-01 +#define GTE_C_LOG2_DEG4_C4 -8.2130717995088531e-02 +#define GTE_C_LOG2_DEG4_MAX_ERROR 1.1318551355360418e-4 + +#define GTE_C_LOG2_DEG5_C1 +1.4419170408633741 +#define GTE_C_LOG2_DEG5_C2 -7.0909645927612530e-01 +#define GTE_C_LOG2_DEG5_C3 +4.1560609399164150e-01 +#define GTE_C_LOG2_DEG5_C4 -1.9357573729558908e-01 +#define GTE_C_LOG2_DEG5_C5 +4.5149061716699634e-02 +#define GTE_C_LOG2_DEG5_MAX_ERROR 1.5521274478735858e-5 + +#define GTE_C_LOG2_DEG6_C1 +1.4425449435950917 +#define GTE_C_LOG2_DEG6_C2 -7.1814525675038965e-01 +#define GTE_C_LOG2_DEG6_C3 +4.5754919692564044e-01 +#define GTE_C_LOG2_DEG6_C4 -2.7790534462849337e-01 +#define GTE_C_LOG2_DEG6_C5 +1.2179791068763279e-01 +#define GTE_C_LOG2_DEG6_C6 -2.5841449829670182e-02 +#define GTE_C_LOG2_DEG6_MAX_ERROR 2.2162051216689793e-6 + +#define GTE_C_LOG2_DEG7_C1 +1.4426664401536078 +#define GTE_C_LOG2_DEG7_C2 -7.2055423726162360e-01 +#define GTE_C_LOG2_DEG7_C3 +4.7332419162501083e-01 +#define GTE_C_LOG2_DEG7_C4 -3.2514018752954144e-01 +#define GTE_C_LOG2_DEG7_C5 +1.9302965529095673e-01 +#define GTE_C_LOG2_DEG7_C6 -7.8534970641157997e-02 +#define GTE_C_LOG2_DEG7_C7 +1.5209108363023915e-02 +#define GTE_C_LOG2_DEG7_MAX_ERROR 3.2546531700261561e-7 + +#define GTE_C_LOG2_DEG8_C1 +1.4426896453621882 +#define GTE_C_LOG2_DEG8_C2 -7.2115893912535967e-01 +#define GTE_C_LOG2_DEG8_C3 +4.7861716616785088e-01 +#define GTE_C_LOG2_DEG8_C4 -3.4699935395019565e-01 +#define GTE_C_LOG2_DEG8_C5 +2.4114048765477492e-01 +#define GTE_C_LOG2_DEG8_C6 -1.3657398692885181e-01 +#define GTE_C_LOG2_DEG8_C7 +5.1421382871922106e-02 +#define GTE_C_LOG2_DEG8_C8 -9.1364020499895560e-03 +#define GTE_C_LOG2_DEG8_MAX_ERROR 4.8796219218050219e-8 + +// These functions are convenient for some applications. The classes +// BSNumber, BSRational and IEEEBinary16 have implementations that +// (for now) use typecasting to call the 'float' or 'double' versions. +namespace gte +{ + inline float atandivpi(float x) + { + return std::atan(x) * (float)GTE_C_INV_PI; + } + + inline float atan2divpi(float y, float x) + { + return std::atan2(y, x) * (float)GTE_C_INV_PI; + } + + inline float clamp(float x, float xmin, float xmax) + { + return (x <= xmin ? xmin : (x >= xmax ? xmax : x)); + } + + inline float cospi(float x) + { + return std::cos(x * (float)GTE_C_PI); + } + + inline float exp10(float x) + { + return std::exp(x * (float)GTE_C_LN_10); + } + + inline float invsqrt(float x) + { + return 1.0f / std::sqrt(x); + } + + inline int isign(float x) + { + return (x > 0.0f ? 1 : (x < 0.0f ? -1 : 0)); + } + + inline float saturate(float x) + { + return (x <= 0.0f ? 0.0f : (x >= 1.0f ? 1.0f : x)); + } + + inline float sign(float x) + { + return (x > 0.0f ? 1.0f : (x < 0.0f ? -1.0f : 0.0f)); + } + + inline float sinpi(float x) + { + return std::sin(x * (float)GTE_C_PI); + } + + inline float sqr(float x) + { + return x * x; + } + + + inline double atandivpi(double x) + { + return std::atan(x) * GTE_C_INV_PI; + } + + inline double atan2divpi(double y, double x) + { + return std::atan2(y, x) * GTE_C_INV_PI; + } + + inline double clamp(double x, double xmin, double xmax) + { + return (x <= xmin ? xmin : (x >= xmax ? xmax : x)); + } + + inline double cospi(double x) + { + return std::cos(x * GTE_C_PI); + } + + inline double exp10(double x) + { + return std::exp(x * GTE_C_LN_10); + } + + inline double invsqrt(double x) + { + return 1.0 / std::sqrt(x); + } + + inline double sign(double x) + { + return (x > 0.0 ? 1.0 : (x < 0.0 ? -1.0 : 0.0f)); + } + + inline int isign(double x) + { + return (x > 0.0 ? 1 : (x < 0.0 ? -1 : 0)); + } + + inline double saturate(double x) + { + return (x <= 0.0 ? 0.0 : (x >= 1.0 ? 1.0 : x)); + } + + inline double sinpi(double x) + { + return std::sin(x * GTE_C_PI); + } + + inline double sqr(double x) + { + return x * x; + } +} + +// Type traits to support std::enable_if conditional compilation for +// numerical computations. +namespace gte +{ + // The trait is_arbitrary_precision for type T of float, double or + // long double generates is_arbitrary_precision::value of false. The + // implementations for arbitrary-precision arithmetic are found in + // GteArbitraryPrecision.h. + template + struct is_arbitrary_precision_internal : std::false_type {}; + + template + struct is_arbitrary_precision : is_arbitrary_precision_internal>::type {}; + + // The trait has_division_operator for type T of float, double or + // long double generates has_division_operator::value of true. The + // implementations for arbitrary-precision arithmetic are found in + // GteArbitraryPrecision.h. + template + struct has_division_operator_internal : std::false_type {}; + + template + struct has_division_operator : has_division_operator_internal>::type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix.h new file mode 100644 index 000000000000..c28c4cf958da --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix.h @@ -0,0 +1,957 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class Matrix +{ +public: + // The table is initialized to zero. + Matrix(); + + // The table is fully initialized by the inputs. The 'values' must be + // specified in row-major order, regardless of the active storage scheme + // (GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR). + Matrix(std::array const& values); + + // At most NumRows*NumCols are copied from the initializer list, setting + // any remaining elements to zero. The 'values' must be specified in + // row-major order, regardless of the active storage scheme + // (GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR). Create the zero matrix using + // the syntax + // Matrix zero{(Real)0}; + // WARNING: The C++ 11 specification states that + // Matrix zero{}; + // will lead to a call of the default constructor, not the initializer + // constructor! + Matrix(std::initializer_list values); + + // For 0 <= r < NumRows and 0 <= c < NumCols, element (r,c) is 1 and all + // others are 0. If either of r or c is invalid, the zero matrix is + // created. This is a convenience for creating the standard Euclidean + // basis matrices; see also MakeUnit(int,int) and Unit(int,int). + Matrix(int r, int c); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access for which the storage representation is transparent. The + // matrix entry in row r and column c is A(r,c). The first operator() + // returns a const reference rather than a Real value. This supports + // writing via standard file operations that require a const pointer to + // data. + inline Real const& operator()(int r, int c) const; + inline Real& operator()(int r, int c); + + // Member access by rows or by columns. + void SetRow(int r, Vector const& vec); + void SetCol(int c, Vector const& vec); + Vector GetRow(int r) const; + Vector GetCol(int c) const; + + // Member access by 1-dimensional index. NOTE: These accessors are + // useful for the manipulation of matrix entries when it does not + // matter whether storage is row-major or column-major. Do not use + // constructs such as M[c+NumCols*r] or M[r+NumRows*c] that expose the + // storage convention. + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(Matrix const& mat) const; + inline bool operator!=(Matrix const& mat) const; + inline bool operator< (Matrix const& mat) const; + inline bool operator<=(Matrix const& mat) const; + inline bool operator> (Matrix const& mat) const; + inline bool operator>=(Matrix const& mat) const; + + // Special matrices. + void MakeZero(); // All components are 0. + void MakeUnit(int r, int c); // Component (r,c) is 1, all others zero. + void MakeIdentity(); // Diagonal entries 1, others 0, even when nonsquare + static Matrix Zero(); + static Matrix Unit(int r, int c); + static Matrix Identity(); + +protected: + class Table + { + public: + // Storage-order-independent element access as 2D array. + inline Real const& operator()(int r, int c) const; + inline Real& operator()(int r, int c); + + // Element access as 1D array. Use this internally only when + // the 2D storage order is not relevant. + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + +#if defined(GTE_USE_ROW_MAJOR) + std::array,NumRows> mStorage; +#else + std::array,NumCols> mStorage; +#endif + }; + + Table mTable; +}; + +// Unary operations. +template +Matrix +operator+(Matrix const& M); + +template +Matrix +operator-(Matrix const& M); + +// Linear-algebraic operations. +template +Matrix +operator+( + Matrix const& M0, + Matrix const& M1); + +template +Matrix +operator-( + Matrix const& M0, + Matrix const& M1); + +template +Matrix +operator*(Matrix const& M, Real scalar); + +template +Matrix +operator*(Real scalar, Matrix const& M); + +template +Matrix +operator/(Matrix const& M, Real scalar); + +template +Matrix& +operator+=( + Matrix& M0, + Matrix const& M1); + +template +Matrix& +operator-=( + Matrix& M0, + Matrix const& M1); + +template +Matrix& +operator*=(Matrix& M, Real scalar); + +template +Matrix& +operator/=(Matrix& M, Real scalar); + +// Geometric operations. +template +Real L1Norm(Matrix const& M); + +template +Real L2Norm(Matrix const& M); + +template +Real LInfinityNorm(Matrix const& M); + +template +Matrix Inverse(Matrix const& M, + bool* reportInvertibility = nullptr); + +template +Real Determinant(Matrix const& M); + +// M^T +template +Matrix +Transpose(Matrix const& M); + +// M*V +template +Vector +operator*( + Matrix const& M, + Vector const& V); + +// V^T*M +template +Vector +operator*( + Vector const& V, + Matrix const& M); + +// A*B +template +Matrix +operator*( + Matrix const& A, + Matrix const& B); + +template +Matrix +MultiplyAB( + Matrix const& A, + Matrix const& B); + +// A*B^T +template +Matrix +MultiplyABT( + Matrix const& A, + Matrix const& B); + +// A^T*B +template +Matrix +MultiplyATB( + Matrix const& A, + Matrix const& B); + +// A^T*B^T +template +Matrix +MultiplyATBT( + Matrix const& A, + Matrix const& B); + +// M*D, D is diagonal NumCols-by-NumCols +template +Matrix +MultiplyMD( + Matrix const& M, + Vector const& D); + +// D*M, D is diagonal NumRows-by-NumRows +template +Matrix +MultiplyDM( + Vector const& D, + Matrix const& M); + +// U*V^T, U is NumRows-by-1, V is Num-Cols-by-1, result is NumRows-by-NumCols. +template +Matrix +OuterProduct(Vector const& U, Vector const& V); + +// Initialization to a diagonal matrix whose diagonal entries are the +// components of D. +template +void MakeDiagonal(Vector const& D, Matrix& M); + +// Create an (N+1)-by-(N+1) matrix H by setting the upper N-by-N block to the +// input N-by-N matrix and all other entries to 0 except for the last row +// and last column entry which is set to 1. +template +Matrix HLift(Matrix const& M); + +// Extract the upper (N-1)-by-(N-1) block of the input N-by-N matrix. +template +Matrix HProject(Matrix const& M); + + +template +Matrix::Matrix() +{ + MakeZero(); +} + +template +Matrix::Matrix( + std::array const& values) +{ + for (int r = 0, i = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c, ++i) + { + mTable(r, c) = values[i]; + } + } +} + +template +Matrix::Matrix(std::initializer_list values) +{ + int const numValues = static_cast(values.size()); + auto iter = values.begin(); + int r, c, i; + for (r = 0, i = 0; r < NumRows; ++r) + { + for (c = 0; c < NumCols; ++c, ++i) + { + if (i < numValues) + { + mTable(r, c) = *iter++; + } + else + { + break; + } + } + + if (c < NumCols) + { + // Fill in the remaining columns of the current row with zeros. + for (/**/; c < NumCols; ++c) + { + mTable(r, c) = (Real)0; + } + ++r; + break; + } + } + + if (r < NumRows) + { + // Fill in the remain rows with zeros. + for (/**/; r < NumRows; ++r) + { + for (c = 0; c < NumCols; ++c) + { + mTable(r, c) = (Real)0; + } + } + } +} + +template +Matrix::Matrix(int r, int c) +{ + MakeUnit(r, c); +} + +template inline +Real const& Matrix::operator()(int r, int c) const +{ + return mTable(r, c); +} + +template inline +Real& Matrix::operator()(int r, int c) +{ + return mTable(r, c); +} + +template +void Matrix::SetRow(int r, + Vector const& vec) +{ + for (int c = 0; c < NumCols; ++c) + { + mTable(r, c) = vec[c]; + } +} + +template +void Matrix::SetCol(int c, + Vector const& vec) +{ + for (int r = 0; r < NumRows; ++r) + { + mTable(r, c) = vec[r]; + } +} + +template +Vector Matrix::GetRow(int r) const +{ + Vector vec; + for (int c = 0; c < NumCols; ++c) + { + vec[c] = mTable(r, c); + } + return vec; +} + +template +Vector Matrix::GetCol(int c) const +{ + Vector vec; + for (int r = 0; r < NumRows; ++r) + { + vec[r] = mTable(r, c); + } + return vec; +} + +template inline +Real const& Matrix::operator[](int i) const +{ + return mTable[i]; +} + +template inline +Real& Matrix::operator[](int i) +{ + return mTable[i]; +} + +template inline +bool Matrix::operator==(Matrix const& mat) const +{ + return mTable.mStorage == mat.mTable.mStorage; +} + +template inline +bool Matrix::operator!=(Matrix const& mat) const +{ + return mTable.mStorage != mat.mTable.mStorage; +} + +template inline +bool Matrix::operator<(Matrix const& mat) const +{ + return mTable.mStorage < mat.mTable.mStorage; +} + +template inline +bool Matrix::operator<=(Matrix const& mat) const +{ + return mTable.mStorage <= mat.mTable.mStorage; +} + +template inline +bool Matrix::operator>(Matrix const& mat) const +{ + return mTable.mStorage > mat.mTable.mStorage; +} + +template inline +bool Matrix::operator>=(Matrix const& mat) const +{ + return mTable.mStorage >= mat.mTable.mStorage; +} + +template +void Matrix::MakeZero() +{ + Real const zero = (Real)0; + for (int i = 0; i < NumRows * NumCols; ++i) + { + mTable[i] = zero; + } +} + +template +void Matrix::MakeUnit(int r, int c) +{ + MakeZero(); + if (0 <= r && r < NumRows && 0 <= c && c < NumCols) + { + mTable(r, c) = (Real)1; + } +} + +template +void Matrix::MakeIdentity() +{ + MakeZero(); + int const numDiagonal = (NumRows <= NumCols ? NumRows : NumCols); + for (int i = 0; i < numDiagonal; ++i) + { + mTable(i, i) = (Real)1; + } +} + +template +Matrix Matrix::Zero() +{ + Matrix M; + M.MakeZero(); + return M; +} + +template +Matrix Matrix::Unit(int r, + int c) +{ + Matrix M; + M.MakeUnit(r, c); + return M; +} + +template +Matrix Matrix::Identity() +{ + Matrix M; + M.MakeIdentity(); + return M; +} + + + +template +Matrix +operator+(Matrix const& M) +{ + return M; +} + +template +Matrix +operator-(Matrix const& M) +{ + Matrix result; + for (int i = 0; i < NumRows*NumCols; ++i) + { + result[i] = -M[i]; + } + return result; +} + +template +Matrix +operator+( + Matrix const& M0, + Matrix const& M1) +{ + Matrix result = M0; + return result += M1; +} + +template +Matrix +operator-( + Matrix const& M0, + Matrix const& M1) +{ + Matrix result = M0; + return result -= M1; +} + +template +Matrix +operator*(Matrix const& M, Real scalar) +{ + Matrix result = M; + return result *= scalar; +} + +template +Matrix +operator*(Real scalar, Matrix const& M) +{ + Matrix result = M; + return result *= scalar; +} + +template +Matrix +operator/(Matrix const& M, Real scalar) +{ + Matrix result = M; + return result /= scalar; +} + +template +Matrix& +operator+=( + Matrix& M0, + Matrix const& M1) +{ + for (int i = 0; i < NumRows*NumCols; ++i) + { + M0[i] += M1[i]; + } + return M0; +} + +template +Matrix& +operator-=( + Matrix& M0, + Matrix const& M1) +{ + for (int i = 0; i < NumRows*NumCols; ++i) + { + M0[i] -= M1[i]; + } + return M0; +} + +template +Matrix& +operator*=(Matrix& M, Real scalar) +{ + for (int i = 0; i < NumRows*NumCols; ++i) + { + M[i] *= scalar; + } + return M; +} + +template +Matrix& +operator/=(Matrix& M, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < NumRows*NumCols; ++i) + { + M[i] *= invScalar; + } + } + else + { + for (int i = 0; i < NumRows*NumCols; ++i) + { + M[i] = (Real)0; + } + } + return M; +} + +template +Real L1Norm(Matrix const& M) +{ + Real sum = std::abs(M[0]); + for (int i = 1; i < NumRows*NumCols; ++i) + { + sum += std::abs(M[i]); + } + return sum; +} + +template +Real L2Norm(Matrix const& M) +{ + Real sum = M[0] * M[0]; + for (int i = 1; i < NumRows*NumCols; ++i) + { + sum += M[i] * M[i]; + } + return std::sqrt(sum); +} + +template +Real LInfinityNorm(Matrix const& M) +{ + Real maxAbsElement = M[0]; + for (int i = 1; i < NumRows*NumCols; ++i) + { + Real absElement = std::abs(M[i]); + if (absElement > maxAbsElement) + { + maxAbsElement = absElement; + } + } + return maxAbsElement; +} + +template +Matrix Inverse(Matrix const& M, + bool* reportInvertibility) +{ + Matrix invM; + Real determinant; + bool invertible = GaussianElimination()(N, &M[0], &invM[0], + determinant, nullptr, nullptr, nullptr, 0, nullptr); + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return invM; +} + +template +Real Determinant(Matrix const& M) +{ + Real determinant; + GaussianElimination()(N, &M[0], nullptr, determinant, nullptr, + nullptr, nullptr, 0, nullptr); + return determinant; +} + +template +Matrix +Transpose(Matrix const& M) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(c, r) = M(r, c); + } + } + return result; +} + +template +Vector +operator*( + Matrix const& M, + Vector const& V) +{ + Vector result; + for (int r = 0; r < NumRows; ++r) + { + result[r] = (Real)0; + for (int c = 0; c < NumCols; ++c) + { + result[r] += M(r, c) * V[c]; + } + } + return result; +} + +template +Vector operator*(Vector const& V, + Matrix const& M) +{ + Vector result; + for (int c = 0; c < NumCols; ++c) + { + result[c] = (Real)0; + for (int r = 0; r < NumRows; ++r) + { + result[c] += V[r] * M(r, c); + } + } + return result; +} + +template +Matrix +operator*( + Matrix const& A, + Matrix const& B) +{ + return MultiplyAB(A, B); +} + +template +Matrix +MultiplyAB( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(r, i) * B(i, c); + } + } + } + return result; +} + +template +Matrix +MultiplyABT( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(r, i) * B(c, i); + } + } + } + return result; +} + +template +Matrix +MultiplyATB( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(i, r) * B(i, c); + } + } + } + return result; +} + +template +Matrix +MultiplyATBT( + Matrix const& A, + Matrix const& B) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(i, r) * B(c, i); + } + } + } + return result; +} + +template +Matrix +MultiplyMD( + Matrix const& M, + Vector const& D) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = M(r, c) * D[c]; + } + } + return result; +} + +template +Matrix +MultiplyDM( + Vector const& D, + Matrix const& M) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = D[r] * M(r, c); + } + } + return result; +} + +template +Matrix +OuterProduct(Vector const& U, Vector const& V) +{ + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = U[r] * V[c]; + } + } + return result; +} + +template +void MakeDiagonal(Vector const& D, Matrix& M) +{ + for (int i = 0; i < N*N; ++i) + { + M[i] = (Real)0; + } + + for (int i = 0; i < N; ++i) + { + M(i, i) = D[i]; + } +} + +template +Matrix HLift(Matrix const& M) +{ + Matrix result; + result.MakeIdentity(); + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < N; ++c) + { + result(r, c) = M(r, c); + } + } + return result; +} + +// Extract the upper (N-1)-by-(N-1) block of the input N-by-N matrix. +template +Matrix HProject(Matrix const& M) +{ + static_assert(N >= 2, "Invalid matrix dimension."); + Matrix result; + for (int r = 0; r < N - 1; ++r) + { + for (int c = 0; c < N - 1; ++c) + { + result(r, c) = M(r, c); + } + } + return result; +} + + +// Matrix::Table + +template inline +Real const& Matrix::Table::operator()(int r, int c) + const +{ +#if defined(GTE_USE_ROW_MAJOR) + return mStorage[r][c]; +#else + return mStorage[c][r]; +#endif +} + +template inline +Real& Matrix::Table::operator()(int r, int c) +{ +#if defined(GTE_USE_ROW_MAJOR) + return mStorage[r][c]; +#else + return mStorage[c][r]; +#endif +} + +template inline +Real const& Matrix::Table::operator[](int i) const +{ + Real const* elements = &mStorage[0][0]; + return elements[i]; +} + +template inline +Real& Matrix::Table::operator[](int i) +{ + Real* elements = &mStorage[0][0]; + return elements[i]; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix2x2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix2x2.h new file mode 100644 index 000000000000..0b66f0a6a41e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix2x2.h @@ -0,0 +1,132 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Matrix2x2 = Matrix<2, 2, Real>; + +// Create a rotation matrix from an angle (in radians). The matrix is +// [GTE_USE_MAT_VEC] +// R(t) = {{c,-s},{s,c}} +// [GTE_USE_VEC_MAT] +// R(t) = {{c,s},{-s,c}} +// where c = cos(t), s = sin(t), and the inner-brace pairs are rows of the +// matrix. +template +void MakeRotation(Real angle, Matrix2x2& rotation); + +// Get the angle (radians) from a rotation matrix. The caller is +// responsible for ensuring the matrix is a rotation. +template +Real GetRotationAngle(Matrix2x2 const& rotation); + +// Geometric operations. +template +Matrix2x2 Inverse(Matrix2x2 const& M, + bool* reportInvertibility = nullptr); + +template +Matrix2x2 Adjoint(Matrix2x2 const& M); + +template +Real Determinant(Matrix2x2 const& M); + +template +Real Trace(Matrix2x2 const& M); + + +template +void MakeRotation(Real angle, Matrix2x2& rotation) +{ + Real cs = std::cos(angle); + Real sn = std::sin(angle); +#if defined(GTE_USE_MAT_VEC) + rotation(0, 0) = cs; + rotation(0, 1) = -sn; + rotation(1, 0) = sn; + rotation(1, 1) = cs; +#else + rotation(0, 0) = cs; + rotation(0, 1) = sn; + rotation(1, 0) = -sn; + rotation(1, 1) = cs; +#endif +} + +template +Real GetRotationAngle(Matrix2x2 const& rotation) +{ +#if defined(GTE_USE_MAT_VEC) + return std::atan2(rotation(1, 0), rotation(0, 0)); +#else + return std::atan2(rotation(0, 1), rotation(0, 0)); +#endif +} + +template +Matrix2x2 Inverse(Matrix2x2 const& M, bool* reportInvertibility) +{ + Matrix2x2 inverse; + bool invertible; + Real det = M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0); + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix2x2 + { + M(1, 1)*invDet, -M(0, 1)*invDet, + -M(1, 0)*invDet, M(0, 0)*invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; +} + +template +Matrix2x2 Adjoint(Matrix2x2 const& M) +{ + return Matrix2x2 + { + M(1, 1), -M(0, 1), + -M(1, 0), M(0, 0) + }; +} + +template +Real Determinant(Matrix2x2 const& M) +{ + Real det = M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0); + return det; +} + +template +Real Trace(Matrix2x2 const& M) +{ + Real trace = M(0, 0) + M(1, 1); + return trace; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix3x3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix3x3.h new file mode 100644 index 000000000000..602a88e30ebb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix3x3.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Matrix3x3 = Matrix<3, 3, Real>; + +// Geometric operations. +template +Matrix3x3 Inverse(Matrix3x3 const& M, + bool* reportInvertibility = nullptr); + +template +Matrix3x3 Adjoint(Matrix3x3 const& M); + +template +Real Determinant(Matrix3x3 const& M); + +template +Real Trace(Matrix3x3 const& M); + + +template +Matrix3x3 Inverse(Matrix3x3 const& M, bool* reportInvertibility) +{ + Matrix3x3 inverse; + bool invertible; + Real c00 = M(1, 1)*M(2, 2) - M(1, 2)*M(2, 1); + Real c10 = M(1, 2)*M(2, 0) - M(1, 0)*M(2, 2); + Real c20 = M(1, 0)*M(2, 1) - M(1, 1)*M(2, 0); + Real det = M(0, 0)*c00 + M(0, 1)*c10 + M(0, 2)*c20; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix3x3 + { + c00*invDet, + (M(0, 2)*M(2, 1) - M(0, 1)*M(2, 2))*invDet, + (M(0, 1)*M(1, 2) - M(0, 2)*M(1, 1))*invDet, + c10*invDet, + (M(0, 0)*M(2, 2) - M(0, 2)*M(2, 0))*invDet, + (M(0, 2)*M(1, 0) - M(0, 0)*M(1, 2))*invDet, + c20*invDet, + (M(0, 1)*M(2, 0) - M(0, 0)*M(2, 1))*invDet, + (M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0))*invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; +} + +template +Matrix3x3 Adjoint(Matrix3x3 const& M) +{ + return Matrix3x3 + { + M(1, 1)*M(2, 2) - M(1, 2)*M(2, 1), + M(0, 2)*M(2, 1) - M(0, 1)*M(2, 2), + M(0, 1)*M(1, 2) - M(0, 2)*M(1, 1), + M(1, 2)*M(2, 0) - M(1, 0)*M(2, 2), + M(0, 0)*M(2, 2) - M(0, 2)*M(2, 0), + M(0, 2)*M(1, 0) - M(0, 0)*M(1, 2), + M(1, 0)*M(2, 1) - M(1, 1)*M(2, 0), + M(0, 1)*M(2, 0) - M(0, 0)*M(2, 1), + M(0, 0)*M(1, 1) - M(0, 1)*M(1, 0) + }; +} + +template +Real Determinant(Matrix3x3 const& M) +{ + Real c00 = M(1, 1)*M(2, 2) - M(1, 2)*M(2, 1); + Real c10 = M(1, 2)*M(2, 0) - M(1, 0)*M(2, 2); + Real c20 = M(1, 0)*M(2, 1) - M(1, 1)*M(2, 0); + Real det = M(0, 0)*c00 + M(0, 1)*c10 + M(0, 2)*c20; + return det; +} + +template +Real Trace(Matrix3x3 const& M) +{ + Real trace = M(0, 0) + M(1, 1) + M(2, 2); + return trace; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix4x4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix4x4.h new file mode 100644 index 000000000000..e49cc4e32fe9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMatrix4x4.h @@ -0,0 +1,349 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Matrix4x4 = Matrix<4, 4, Real>; + +// Geometric operations. +template +Matrix4x4 Inverse(Matrix4x4 const& M, + bool* reportInvertibility = nullptr); + +template +Matrix4x4 Adjoint(Matrix4x4 const& M); + +template +Real Determinant(Matrix4x4 const& M); + +template +Real Trace(Matrix4x4 const& M); + +// Special matrices. In the comments, the matrices are shown using the +// GTE_USE_MAT_VEC multiplication convention. + +// The projection plane is Dot(N,X-P) = 0 where N is a 3-by-1 unit-length +// normal vector and P is a 3-by-1 point on the plane. The projection is +// oblique to the plane, in the direction of the 3-by-1 vector D. Necessarily +// Dot(N,D) is not zero for this projection to make sense. Given a 3-by-1 +// point U, compute the intersection of the line U+t*D with the plane to +// obtain t = -Dot(N,U-P)/Dot(N,D); then +// +// projection(U) = P + [I - D*N^T/Dot(N,D)]*(U-P) +// +// A 4-by-4 homogeneous transformation representing the projection is +// +// +- -+ +// M = | D*N^T - Dot(N,D)*I -Dot(N,P)D | +// | 0^T -Dot(N,D) | +// +- -+ +// +// where M applies to [U^T 1]^T by M*[U^T 1]^T. The matrix is chosen so +// that M[3][3] > 0 whenever Dot(N,D) < 0; the projection is onto the +// "positive side" of the plane. +template +Matrix4x4 MakeObliqueProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& direction); + +// The perspective projection of a point onto a plane is +// +// +- -+ +// M = | Dot(N,E-P)*I - E*N^T -(Dot(N,E-P)*I - E*N^T)*E | +// | -N^t Dot(N,E) | +// +- -+ +// +// where E is the eye point, P is a point on the plane, and N is a +// unit-length plane normal. +template +Matrix4x4 MakePerspectiveProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& eye); + +// The reflection of a point through a plane is +// +- -+ +// M = | I-2*N*N^T 2*Dot(N,P)*N | +// | 0^T 1 | +// +- -+ +// +// where P is a point on the plane and N is a unit-length plane normal. + +template +Matrix4x4 MakeReflection(Vector4 const& origin, + Vector4 const& normal); + + +template +Matrix4x4 Inverse(Matrix4x4 const& M, bool* reportInvertibility) +{ + Matrix4x4 inverse; + bool invertible; + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + Real det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix4x4 + { + (+M(1, 1) * b5 - M(1, 2) * b4 + M(1, 3) * b3) * invDet, + (-M(0, 1) * b5 + M(0, 2) * b4 - M(0, 3) * b3) * invDet, + (+M(3, 1) * a5 - M(3, 2) * a4 + M(3, 3) * a3) * invDet, + (-M(2, 1) * a5 + M(2, 2) * a4 - M(2, 3) * a3) * invDet, + (-M(1, 0) * b5 + M(1, 2) * b2 - M(1, 3) * b1) * invDet, + (+M(0, 0) * b5 - M(0, 2) * b2 + M(0, 3) * b1) * invDet, + (-M(3, 0) * a5 + M(3, 2) * a2 - M(3, 3) * a1) * invDet, + (+M(2, 0) * a5 - M(2, 2) * a2 + M(2, 3) * a1) * invDet, + (+M(1, 0) * b4 - M(1, 1) * b2 + M(1, 3) * b0) * invDet, + (-M(0, 0) * b4 + M(0, 1) * b2 - M(0, 3) * b0) * invDet, + (+M(3, 0) * a4 - M(3, 1) * a2 + M(3, 3) * a0) * invDet, + (-M(2, 0) * a4 + M(2, 1) * a2 - M(2, 3) * a0) * invDet, + (-M(1, 0) * b3 + M(1, 1) * b1 - M(1, 2) * b0) * invDet, + (+M(0, 0) * b3 - M(0, 1) * b1 + M(0, 2) * b0) * invDet, + (-M(3, 0) * a3 + M(3, 1) * a1 - M(3, 2) * a0) * invDet, + (+M(2, 0) * a3 - M(2, 1) * a1 + M(2, 2) * a0) * invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; +} + +template +Matrix4x4 Adjoint(Matrix4x4 const& M) +{ + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + + return Matrix4x4 + { + +M(1, 1) * b5 - M(1, 2) * b4 + M(1, 3) * b3, + -M(0, 1) * b5 + M(0, 2) * b4 - M(0, 3) * b3, + +M(3, 1) * a5 - M(3, 2) * a4 + M(3, 3) * a3, + -M(2, 1) * a5 + M(2, 2) * a4 - M(2, 3) * a3, + -M(1, 0) * b5 + M(1, 2) * b2 - M(1, 3) * b1, + +M(0, 0) * b5 - M(0, 2) * b2 + M(0, 3) * b1, + -M(3, 0) * a5 + M(3, 2) * a2 - M(3, 3) * a1, + +M(2, 0) * a5 - M(2, 2) * a2 + M(2, 3) * a1, + +M(1, 0) * b4 - M(1, 1) * b2 + M(1, 3) * b0, + -M(0, 0) * b4 + M(0, 1) * b2 - M(0, 3) * b0, + +M(3, 0) * a4 - M(3, 1) * a2 + M(3, 3) * a0, + -M(2, 0) * a4 + M(2, 1) * a2 - M(2, 3) * a0, + -M(1, 0) * b3 + M(1, 1) * b1 - M(1, 2) * b0, + +M(0, 0) * b3 - M(0, 1) * b1 + M(0, 2) * b0, + -M(3, 0) * a3 + M(3, 1) * a1 - M(3, 2) * a0, + +M(2, 0) * a3 - M(2, 1) * a1 + M(2, 2) * a0 + }; +} + +template +Real Determinant(Matrix4x4 const& M) +{ + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + Real det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + return det; +} + +template +Real Trace(Matrix4x4 const& M) +{ + Real trace = M(0, 0) + M(1, 1) + M(2, 2) + M(3, 3); + return trace; +} + +template +Matrix4x4 MakeObliqueProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& direction) +{ + Matrix4x4 M; + + Real const zero = (Real)0; + Real dotND = Dot(normal, direction); + Real dotNO = Dot(origin, normal); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = direction[0] * normal[0] - dotND; + M(0, 1) = direction[0] * normal[1]; + M(0, 2) = direction[0] * normal[2]; + M(0, 3) = -dotNO * direction[0]; + M(1, 0) = direction[1] * normal[0]; + M(1, 1) = direction[1] * normal[1] - dotND; + M(1, 2) = direction[1] * normal[2]; + M(1, 3) = -dotNO * direction[1]; + M(2, 0) = direction[2] * normal[0]; + M(2, 1) = direction[2] * normal[1]; + M(2, 2) = direction[2] * normal[2] - dotND; + M(2, 3) = -dotNO * direction[2]; + M(3, 0) = zero; + M(3, 1) = zero; + M(3, 2) = zero; + M(3, 3) = -dotND; +#else + M(0, 0) = direction[0] * normal[0] - dotND; + M(1, 0) = direction[0] * normal[1]; + M(2, 0) = direction[0] * normal[2]; + M(3, 0) = -dotNO * direction[0]; + M(0, 1) = direction[1] * normal[0]; + M(1, 1) = direction[1] * normal[1] - dotND; + M(2, 1) = direction[1] * normal[2]; + M(3, 1) = -dotNO * direction[1]; + M(0, 2) = direction[2] * normal[0]; + M(1, 2) = direction[2] * normal[1]; + M(2, 2) = direction[2] * normal[2] - dotND; + M(3, 2) = -dotNO * direction[2]; + M(0, 2) = zero; + M(1, 3) = zero; + M(2, 3) = zero; + M(3, 3) = -dotND; +#endif + + return M; +} + +template +Matrix4x4 MakePerspectiveProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& eye) +{ + Matrix4x4 M; + + Real dotND = Dot(normal, eye - origin); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = dotND - eye[0] * normal[0]; + M(0, 1) = -eye[0] * normal[1]; + M(0, 2) = -eye[0] * normal[2]; + M(0, 3) = -(M(0, 0) * eye[0] + M(0, 1) * eye[1] + M(0, 2) * eye[2]); + M(1, 0) = -eye[1] * normal[0]; + M(1, 1) = dotND - eye[1] * normal[1]; + M(1, 2) = -eye[1] * normal[2]; + M(1, 3) = -(M(1, 0) * eye[0] + M(1, 1) * eye[1] + M(1, 2) * eye[2]); + M(2, 0) = -eye[2] * normal[0]; + M(2, 1) = -eye[2] * normal[1]; + M(2, 2) = dotND - eye[2] * normal[2]; + M(2, 3) = -(M(2, 0) * eye[0] + M(2, 1) * eye[1] + M(2, 2) * eye[2]); + M(3, 0) = -normal[0]; + M(3, 1) = -normal[1]; + M(3, 2) = -normal[2]; + M(3, 3) = Dot(eye, normal); +#else + M(0, 0) = dotND - eye[0] * normal[0]; + M(1, 0) = -eye[0] * normal[1]; + M(2, 0) = -eye[0] * normal[2]; + M(3, 0) = -(M(0, 0) * eye[0] + M(0, 1) * eye[1] + M(0, 2) * eye[2]); + M(0, 1) = -eye[1] * normal[0]; + M(1, 1) = dotND - eye[1] * normal[1]; + M(2, 1) = -eye[1] * normal[2]; + M(3, 1) = -(M(1, 0) * eye[0] + M(1, 1) * eye[1] + M(1, 2) * eye[2]); + M(0, 2) = -eye[2] * normal[0]; + M(1, 2) = -eye[2] * normal[1]; + M(2, 2) = dotND - eye[2] * normal[2]; + M(3, 2) = -(M(2, 0) * eye[0] + M(2, 1) * eye[1] + M(2, 2) * eye[2]); + M(0, 3) = -normal[0]; + M(1, 3) = -normal[1]; + M(2, 3) = -normal[2]; + M(3, 3) = Dot(eye, normal); +#endif + + return M; +} + +template +Matrix4x4 MakeReflection(Vector4 const& origin, + Vector4 const& normal) +{ + Matrix4x4 M; + + Real const zero = (Real)0, one = (Real)1, two = (Real)2; + Real twoDotNO = two * Dot(origin, normal); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = one - two * normal[0] * normal[0]; + M(0, 1) = -two * normal[0] * normal[1]; + M(0, 2) = -two * normal[0] * normal[2]; + M(0, 3) = twoDotNO * normal[0]; + M(1, 0) = M(0, 1); + M(1, 1) = one - two * normal[1] * normal[1]; + M(1, 2) = -two * normal[1] * normal[2]; + M(1, 3) = twoDotNO * normal[1]; + M(2, 0) = M(0, 2); + M(2, 1) = M(1, 2); + M(2, 2) = one - two * normal[2] * normal[2]; + M(2, 3) = twoDotNO * normal[2]; + M(3, 0) = zero; + M(3, 1) = zero; + M(3, 2) = zero; + M(3, 3) = one; +#else + M(0, 0) = one - two * normal[0] * normal[0]; + M(1, 0) = -two * normal[0] * normal[1]; + M(2, 0) = -two * normal[0] * normal[2]; + M(3, 0) = twoDotNO * normal[0]; + M(0, 1) = M(1, 0); + M(1, 1) = one - two * normal[1] * normal[1]; + M(2, 1) = -two * normal[1] * normal[2]; + M(3, 1) = twoDotNO * normal[1]; + M(0, 2) = M(2, 0); + M(1, 2) = M(2, 1); + M(2, 2) = one - two * normal[2] * normal[2]; + M(3, 2) = twoDotNO * normal[2]; + M(0, 3) = zero; + M(1, 3) = zero; + M(2, 3) = zero; + M(3, 3) = one; +#endif + + return M; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMesh.h new file mode 100644 index 000000000000..011147767df9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMesh.h @@ -0,0 +1,747 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// The Mesh class is designed to support triangulations of surfaces of a small +// number of topologies. See the documents +// http://www.geometrictools.com/MeshDifferentialGeometry.pdf +// http://www.geometrictools.com/MeshFactory.pdf +// for details. +// +// You must set the vertex attribute sources before calling Update(). +// +// The semantic "position" is required and its source must be an array +// of Real with at least 3 channels so that positions are computed as +// Vector3. +// +// The positions are assumed to be parameterized by texture coordinates +// (u,v); the position is thought of as a function P(u,v). If texture +// coordinates are provided, the semantic must be "tcoord". If texture +// coordinates are not provided, default texture coordinates are computed +// internally as described in the mesh factory document. +// +// The frame for the tangent space is optional. All vectors in the frame +// must have sources that are arrays of Real with at least 3 channels per +// attribute. If normal vectors are provided, the semantic must be +// "normal". +// +// Two options are supported for tangent vectors. The first option is +// that the tangents are surface derivatives dP/du and dP/dv, which are +// not necessarily unit length or orthogonal. The semantics must be +// "dpdu" and "dpdv". The second option is that the tangents are unit +// length and orthogonal, with the infrequent possibility that a vertex +// is degenerate in that dP/du and dP/dv are linearly dependent. The +// semantics must be "tangent" and "bitangent". +// +// For each provided vertex attribute, a derived class can initialize +// that attribute by overriding one of the Initialize*() functions whose +// stubs are defined in this class. + +namespace gte +{ + +enum class MeshTopology +{ + ARBITRARY, + RECTANGLE, + CYLINDER, + TORUS, + DISK, + SPHERE +}; + +class MeshDescription +{ +public: + // Constructor for MeshTopology::ARBITRARY. The members topology, + // numVertices, and numTriangles are set in the obvious manner. The + // members numRows and numCols are set to zero. The remaining members + // must be set explicitly by the client. + inline MeshDescription(uint32_t inNumVertices, uint32_t inNumTriangles); + + // Constructor for topologies other than MeshTopology::ARBITRARY. + // Compute the number of vertices and triangles for the mesh based on the + // requested number of rows and columns. If the number of rows or columns + // is invalid for the specified topology, they are modified to be valid, + // in which case inNumRows/numRows and inNumCols/numCols can differ. If + // the input topology is MeshTopology::ARBITRARY, then inNumRows and + // inNumCols are assigned to numVertices and numTriangles, respectively, + // and numRows and numCols are set to zero. The remaining members must be + // set explicitly by the client. + inline MeshDescription(MeshTopology inTopology, uint32_t inNumRows, uint32_t inNumCols); + + MeshTopology topology; + uint32_t numVertices; + uint32_t numTriangles; + std::vector vertexAttributes; + IndexAttribute indexAttribute; + bool wantDynamicTangentSpaceUpdate; // default: false + bool wantCCW; // default: true + + // For internal use only. + bool hasTangentSpaceVectors; + bool allowUpdateFrame; + uint32_t numRows, numCols; + uint32_t rMax, cMax, rIncrement; + + // After an attempt to construct a Mesh or Mesh-derived object, examine + // this value to determine whether the construction was successful. + bool constructed; +}; + +template +class Mesh +{ +public: + // Construction and destruction. This constructor is for ARBITRARY topology. + // The vertices and indices must already be assigned by the client. Derived + // classes use the protected constructor, but assignment of vertices and + // indices occurs in the derived-class constructors. + Mesh(MeshDescription const& description, std::vector const& validTopologies); + + virtual ~Mesh(); + + // No copying or assignment is allowed. + Mesh(Mesh const&) = delete; + Mesh& operator=(Mesh const&) = delete; + + // Member accessors. + inline MeshDescription const& GetDescription() const; + + // If the underlying geometric data varies dynamically, call this function + // to update whatever vertex attributes are specified by the vertex pool. + // A derived class + void Update(); + +protected: + // Access the vertex attributes. + inline Vector3& Position(uint32_t i); + inline Vector3& Normal(uint32_t i); + inline Vector3& Tangent(uint32_t i); + inline Vector3& Bitangent(uint32_t i); + inline Vector3& DPDU(uint32_t i); + inline Vector3& DPDV(uint32_t i); + inline Vector2& TCoord(uint32_t i); + + // Compute the indices for non-arbitrary topologies. This function is + // called by derived classes. + void ComputeIndices(); + + // The Update() function allows derived classes to use algorithms + // different from least-squares fitting to compute the normals (when + // no tangent-space information is requested) or to compute the frame + // (normals and tangent space). The UpdatePositions() is a stub; the + // base-class has no knowledge about how positions should be modified. + // A derived class, however, might choose to use dynamic updating + // and override UpdatePositions(). The base-class UpdateNormals() + // computes vertex normals as averages of area-weighted triangle + // normals (nonparametric approach). The base-class UpdateFrame() + // uses a least-squares algorithm for estimating the tangent space + // (parametric approach). + virtual void UpdatePositions() {} + virtual void UpdateNormals(); + virtual void UpdateFrame(); + + // Constructor inputs. + // The client requests this via the constructor; however, if it is + // requested and the vertex attributes do not contain entries for + // "tangent", "bitangent", "dpdu", or "dpdv", then this member is + // set to false. + MeshDescription mDescription; + + // Copied from mVertexAttributes when available. + Vector3* mPositions; + Vector3* mNormals; + Vector3* mTangents; + Vector3* mBitangents; + Vector3* mDPDUs; + Vector3* mDPDVs; + Vector2* mTCoords; + size_t mPositionStride; + size_t mNormalStride; + size_t mTangentStride; + size_t mBitangentStride; + size_t mDPDUStride; + size_t mDPDVStride; + size_t mTCoordStride; + + // When dynamic tangent-space updates are requested, the update algorithm + // requires texture coordinates (user-specified or non-local). It is + // possible to create a vertex-adjacent set (with indices into the + // vertex array) for each mesh vertex; however, instead we rely on a + // triangle iteration and incrementally store the information needed for + // the estimation of the tangent space. Each vertex has associated + // matrices D and U, but we need to store only U^T*U and D^T*U. See the + // PDF for details. + std::vector> mUTU; + std::vector> mDTU; +}; + + +inline MeshDescription::MeshDescription(uint32_t inNumVertices, uint32_t inNumTriangles) + : + topology(MeshTopology::ARBITRARY), + numVertices(inNumVertices), + numTriangles(inNumTriangles), + wantDynamicTangentSpaceUpdate(false), + wantCCW(true), + hasTangentSpaceVectors(false), + allowUpdateFrame(false), + numRows(0), + numCols(0), + rMax(0), + cMax(0), + rIncrement(0), + constructed(false) +{ + LogAssert(numVertices >= 3, "Invalid number of vertices."); + LogAssert(numTriangles >= 1, "Invalid number of triangles."); +} + +inline MeshDescription::MeshDescription(MeshTopology inTopology, uint32_t inNumRows, uint32_t inNumCols) + : + topology(inTopology), + wantDynamicTangentSpaceUpdate(false), + wantCCW(true), + hasTangentSpaceVectors(false), + allowUpdateFrame(false), + constructed(false) +{ + switch (topology) + { + case MeshTopology::ARBITRARY: + numVertices = inNumRows; + numTriangles = inNumCols; + numRows = 0; + numCols = 0; + rMax = 0; + cMax = 0; + rIncrement = 0; + break; + + case MeshTopology::RECTANGLE: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 2u); + rMax = numRows - 1; + cMax = numCols - 1; + rIncrement = numCols; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::CYLINDER: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::TORUS: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 3u); + rMax = numRows; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::DISK: + numRows = std::max(inNumRows, 1u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1) + 1; + numTriangles = 2 * rMax * cMax + numCols; + break; + + case MeshTopology::SPHERE: + numRows = std::max(inNumRows, 1u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1) + 2; + numTriangles = 2 * rMax * cMax + 2 * numCols; + break; + } +} + +template +Mesh::Mesh(MeshDescription const& description, std::vector const& validTopologies) + : + mDescription(description), + mPositions(nullptr), + mNormals(nullptr), + mTangents(nullptr), + mBitangents(nullptr), + mDPDUs(nullptr), + mDPDVs(nullptr), + mTCoords(nullptr), + mPositionStride(0), + mNormalStride(0), + mTangentStride(0), + mBitangentStride(0), + mDPDUStride(0), + mDPDVStride(0), + mTCoordStride(0) +{ + mDescription.constructed = false; + for (auto const& topology : validTopologies) + { + if (mDescription.topology == topology) + { + mDescription.constructed = true; + break; + } + } + + if (!mDescription.indexAttribute.source) + { + LogError("The mesh needs triangles/indices."); + mDescription.constructed = false; + return; + } + + // Set sources for the requested vertex attributes. + mDescription.hasTangentSpaceVectors = false; + mDescription.allowUpdateFrame = mDescription.wantDynamicTangentSpaceUpdate; + for (auto const& attribute : mDescription.vertexAttributes) + { + if (attribute.source != nullptr && attribute.stride > 0) + { + if (attribute.semantic == "position") + { + mPositions = reinterpret_cast*>(attribute.source); + mPositionStride = attribute.stride; + continue; + } + + if (attribute.semantic == "normal") + { + mNormals = reinterpret_cast*>(attribute.source); + mNormalStride = attribute.stride; + continue; + } + + if (attribute.semantic == "tangent") + { + mTangents = reinterpret_cast*>(attribute.source); + mTangentStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "bitangent") + { + mBitangents = reinterpret_cast*>(attribute.source); + mBitangentStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "dpdu") + { + mDPDUs = reinterpret_cast*>(attribute.source); + mDPDUStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "dpdv") + { + mDPDVs = reinterpret_cast*>(attribute.source); + mDPDVStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "tcoord") + { + mTCoords = reinterpret_cast*>(attribute.source); + mTCoordStride = attribute.stride; + continue; + } + } + } + + if (!mPositions) + { + LogError("The mesh needs positions."); + mPositions = nullptr; + mNormals = nullptr; + mTangents = nullptr; + mBitangents = nullptr; + mDPDUs = nullptr; + mDPDVs = nullptr; + mTCoords = nullptr; + mPositionStride = 0; + mNormalStride = 0; + mTangentStride = 0; + mBitangentStride = 0; + mDPDUStride = 0; + mDPDVStride = 0; + mTCoordStride = 0; + mDescription.constructed = false; + return; + } + + // The initial value of allowUpdateFrame is the client request about + // wanting dynamic tangent-space updates. If the vertex attributes do + // not include tangent-space vectors, then dynamic updates are not + // necessary. If tangent-space vectors are present, the update algorithm + // requires texture coordinates (mTCoords must be nonnull) or must compute + // local coordinates (mNormals must be nonnull). + if (mDescription.allowUpdateFrame) + { + if (!mDescription.hasTangentSpaceVectors) + { + mDescription.allowUpdateFrame = false; + } + + if (!mTCoords && !mNormals) + { + mDescription.allowUpdateFrame = false; + } + } + + if (mDescription.allowUpdateFrame) + { + mUTU.resize(mDescription.numVertices); + mDTU.resize(mDescription.numVertices); + } +} + +template +Mesh::~Mesh() +{ +} + +template +inline MeshDescription const& Mesh::GetDescription() const +{ + return mDescription; +} + +template +void Mesh::Update() +{ + if (!mDescription.constructed) + { + LogError("The Mesh object failed the construction."); + return; + } + + UpdatePositions(); + + if (mDescription.allowUpdateFrame) + { + UpdateFrame(); + } + else if (mNormals) + { + UpdateNormals(); + } + // else: The mesh has no frame data, so there is nothing to do. +} + +template +inline Vector3& Mesh::Position(uint32_t i) +{ + char* positions = reinterpret_cast(mPositions); + return *reinterpret_cast*>(positions + i * mPositionStride); +} + +template +inline Vector3& Mesh::Normal(uint32_t i) +{ + char* normals = reinterpret_cast(mNormals); + return *reinterpret_cast*>(normals + i * mNormalStride); +} + +template +inline Vector3& Mesh::Tangent(uint32_t i) +{ + char* tangents = reinterpret_cast(mTangents); + return *reinterpret_cast*>(tangents + i * mTangentStride); +} + +template +inline Vector3& Mesh::Bitangent(uint32_t i) +{ + char* bitangents = reinterpret_cast(mBitangents); + return *reinterpret_cast*>(bitangents + i * mBitangentStride); +} + +template +inline Vector3& Mesh::DPDU(uint32_t i) +{ + char* dpdus = reinterpret_cast(mDPDUs); + return *reinterpret_cast*>(dpdus + i * mDPDUStride); +} + +template +inline Vector3& Mesh::DPDV(uint32_t i) +{ + char* dpdvs = reinterpret_cast(mDPDVs); + return *reinterpret_cast*>(dpdvs + i * mDPDVStride); +} + +template +inline Vector2& Mesh::TCoord(uint32_t i) +{ + char* tcoords = reinterpret_cast(mTCoords); + return *reinterpret_cast*>(tcoords + i * mTCoordStride); +} + +template +void Mesh::ComputeIndices() +{ + uint32_t t = 0; + for (uint32_t r = 0, i = 0; r < mDescription.rMax; ++r) + { + uint32_t v0 = i, v1 = v0 + 1; + i += mDescription.rIncrement; + uint32_t v2 = i, v3 = v2 + 1; + for (uint32_t c = 0; c < mDescription.cMax; ++c, ++v0, ++v1, ++v2, ++v3) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + mDescription.indexAttribute.SetTriangle(t++, v1, v3, v2); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + mDescription.indexAttribute.SetTriangle(t++, v1, v2, v3); + } + } + } + + if (mDescription.topology == MeshTopology::DISK) + { + uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 1; + for (unsigned int c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + } + else if (mDescription.topology == MeshTopology::SPHERE) + { + uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 2; + for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + + v0 = (mDescription.numRows - 1) * mDescription.numCols; + v1 = v0 + 1; + v2 = mDescription.numVertices - 1; + for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + } +} + +template +void Mesh::UpdateNormals() +{ + // Compute normal vector as normalized weighted averages of triangle + // normal vectors. + + // Set the normals to zero to allow accumulation of triangle normals. + Vector3 zero{ (Real)0, (Real)0, (Real)0 }; + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Normal(i) = zero; + } + + // Accumulate the triangle normals. + for (uint32_t t = 0; t < mDescription.numTriangles; ++t) + { + // Get the positions for the triangle. + uint32_t v0, v1, v2; + mDescription.indexAttribute.GetTriangle(t, v0, v1, v2); + Vector3 P0 = Position(v0); + Vector3 P1 = Position(v1); + Vector3 P2 = Position(v2); + + // Get the edge vectors. + Vector3 E1 = P1 - P0; + Vector3 E2 = P2 - P0; + + // Compute a triangle normal show length is twice the area of the + // triangle. + Vector3 triangleNormal = Cross(E1, E2); + + // Accumulate the triangle normals. + Normal(v0) += triangleNormal; + Normal(v1) += triangleNormal; + Normal(v2) += triangleNormal; + } + + // Normalize the normals. + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Normalize(Normal(i), true); + } +} + +template +void Mesh::UpdateFrame() +{ + if (!mTCoords) + { + // We need to compute vertex normals first in order to compute + // local texture coordinates. The vertex normals are recomputed + // later based on estimated tangent vectors. + UpdateNormals(); + } + + // Use the least-squares algorithm to estimate the tangent-space vectors + // and, if requested, normal vectors. + Matrix<2, 2, Real> zero2x2; // initialized to zero + Matrix<3, 2, Real> zero3x2; // initialized to zero + std::fill(mUTU.begin(), mUTU.end(), zero2x2); + std::fill(mDTU.begin(), mDTU.end(), zero3x2); + for (uint32_t t = 0; t < mDescription.numTriangles; ++t) + { + // Get the positions and differences for the triangle. + uint32_t v0, v1, v2; + mDescription.indexAttribute.GetTriangle(t, v0, v1, v2); + Vector3 P0 = Position(v0); + Vector3 P1 = Position(v1); + Vector3 P2 = Position(v2); + Vector3 D10 = P1 - P0; + Vector3 D20 = P2 - P0; + Vector3 D21 = P2 - P1; + + if (mTCoords) + { + // Get the texture coordinates and differences for the triangle. + Vector2 C0 = TCoord(v0); + Vector2 C1 = TCoord(v1); + Vector2 C2 = TCoord(v2); + Vector2 U10 = C1 - C0; + Vector2 U20 = C2 - C0; + Vector2 U21 = C2 - C1; + + // Compute the outer products. + Matrix<2, 2, Real> outerU10 = OuterProduct(U10, U10); + Matrix<2, 2, Real> outerU20 = OuterProduct(U20, U20); + Matrix<2, 2, Real> outerU21 = OuterProduct(U21, U21); + Matrix<3, 2, Real> outerD10 = OuterProduct(D10, U10); + Matrix<3, 2, Real> outerD20 = OuterProduct(D20, U20); + Matrix<3, 2, Real> outerD21 = OuterProduct(D21, U21); + + // Keep a running sum of U^T*U and D^T*U. + mUTU[v0] += outerU10 + outerU20; + mUTU[v1] += outerU10 + outerU21; + mUTU[v2] += outerU20 + outerU21; + mDTU[v0] += outerD10 + outerD20; + mDTU[v1] += outerD10 + outerD21; + mDTU[v2] += outerD20 + outerD21; + } + else + { + // Compute local coordinates and differences for the triangle. + Vector3 basis[3]; + + basis[0] = Normal(v0); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U10{ Dot(basis[1], D10), Dot(basis[2], D10) }; + Vector2 U20{ Dot(basis[1], D20), Dot(basis[2], D20) }; + mUTU[v0] += OuterProduct(U10, U10) + OuterProduct(U20, U20); + mDTU[v0] += OuterProduct(D10, U10) + OuterProduct(D20, U20); + + basis[0] = Normal(v1); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U01{ Dot(basis[1], D10), Dot(basis[2], D10) }; + Vector2 U21{ Dot(basis[1], D21), Dot(basis[2], D21) }; + mUTU[v1] += OuterProduct(U01, U01) + OuterProduct(U21, U21); + mDTU[v1] += OuterProduct(D10, U01) + OuterProduct(D21, U21); + + basis[0] = Normal(v2); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U02{ Dot(basis[1], D20), Dot(basis[2], D20) }; + Vector2 U12{ Dot(basis[1], D21), Dot(basis[2], D21) }; + mUTU[v2] += OuterProduct(U02, U02) + OuterProduct(U12, U12); + mDTU[v2] += OuterProduct(D20, U02) + OuterProduct(D21, U12); + } + + } + + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Matrix<3, 2, Real> jacobian = mDTU[i] * Inverse(mUTU[i]); + + Vector3 basis[3]; + basis[0] = { jacobian(0, 0), jacobian(1, 0), jacobian(2, 0) }; + basis[1] = { jacobian(0, 1), jacobian(1, 1), jacobian(2, 1) }; + + if (mDPDUs) + { + DPDU(i) = basis[0]; + } + if (mDPDVs) + { + DPDV(i) = basis[1]; + } + + ComputeOrthogonalComplement(2, basis, true); + + if (mNormals) + { + Normal(i) = basis[2]; + } + if (mTangents) + { + Tangent(i) = basis[0]; + } + if (mBitangents) + { + Bitangent(i) = basis[1]; + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimalCycleBasis.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimalCycleBasis.h new file mode 100644 index 000000000000..a45a763c6d10 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimalCycleBasis.h @@ -0,0 +1,709 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Extract the minimal cycle basis for a planar graph. The input vertices and +// edges must form a graph for which edges intersect only at vertices; that is, +// no two edges must intersect at an interior point of one of the edges. The +// algorithm is described in +// http://www.geometrictools.com/Documentation/MinimalCycleBasis.pdf +// The graph might have filaments, which are polylines in the graph that are +// not shared by a cycle. These are also extracted by the implementation. +// Because the inputs to the constructor are vertices and edges of the graph, +// isolated vertices are ignored. +// +// The computations that determine which adjacent vertex to visit next during +// a filament or cycle traversal do not require division, so the exact +// arithmetic type BSNumber suffices for ComputeType when you +// want to ensure a correct output. (Floating-point rounding errors +// potentially can lead to an incorrect output.) + +namespace gte +{ + +template +class MinimalCycleBasis +{ +public: + struct Tree + { + std::vector cycle; + std::vector> children; + }; + + // The input positions and edges must form a planar graph for which edges + // intersect only at vertices; that is, no two edges must intersect at an + // interior point of one of the edges. + MinimalCycleBasis( + std::vector> const& positions, + std::vector> const& edges, + std::vector>& forest); + +private: + // No copy or assignment allowed. + MinimalCycleBasis(MinimalCycleBasis const&) = delete; + MinimalCycleBasis& operator=(MinimalCycleBasis const&) = delete; + + struct Vertex + { + Vertex(int inName, std::array const* inPosition); + bool operator< (Vertex const& vertex) const; + + // The index into the 'positions' input provided to the call to + // operator(). The index is used when reporting cycles to the + // caller of the constructor for MinimalCycleBasis. + int name; + + // Multiple vertices can share a position during processing of + // graph components. + std::array const* position; + + // The mVertexStorage member owns the Vertex objects and maintains + // the reference counts on those objects. The adjacent pointers + // are considered to be weak pointers; neither object ownership nor + // reference counting is required by 'adjacent'. + std::set adjacent; + + // Support for depth-first traversal of a graph. + int visited; + }; + + // The constructor uses GetComponents(...) and DepthFirstSearch(...) to + // get the connected components of the graph implied by the input 'edges'. + // Recursive processing uses only DepthFirstSearch(...) to collect + // vertices of the subgraphs of the original graph. + static void DepthFirstSearch(Vertex* vInitial, std::vector& component); + + // Support for traversing a simply connected component of the graph. + std::shared_ptr ExtractBasis(std::vector& component); + void RemoveFilaments(std::vector& component); + std::shared_ptr ExtractCycleFromComponent(std::vector& component); + std::shared_ptr ExtractCycleFromClosedWalk(std::vector& closedWalk); + std::vector ExtractCycle(std::vector& closedWalk); + + Vertex* GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const; + Vertex* GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const; + + // Storage for referenced vertices of the original graph and for new + // vertices added during graph traversal. + std::vector> mVertexStorage; +}; + + +template +MinimalCycleBasis::MinimalCycleBasis(std::vector> const& positions, + std::vector> const& edges, std::vector>& forest) +{ + forest.clear(); + if (positions.size() == 0 || edges.size() == 0) + { + // The graph is empty, so there are no filaments or cycles. + return; + } + + // Determine the unique positions referenced by the edges. + std::map> unique; + for (auto const& edge : edges) + { + for (int i = 0; i < 2; ++i) + { + int name = edge[i]; + if (unique.find(name) == unique.end()) + { + std::shared_ptr vertex = + std::make_shared(name, &positions[name]); + unique.insert(std::make_pair(name, vertex)); + + } + } + } + + // Assign responsibility for ownership of the Vertex objects. + std::vector vertices; + mVertexStorage.reserve(unique.size()); + vertices.reserve(unique.size()); + for (auto const& element : unique) + { + mVertexStorage.push_back(element.second); + vertices.push_back(element.second.get()); + } + + // Determine the adjacencies from the edge information. + for (auto const& edge : edges) + { + auto iter0 = unique.find(edge[0]); + auto iter1 = unique.find(edge[1]); + iter0->second->adjacent.insert(iter1->second.get()); + iter1->second->adjacent.insert(iter0->second.get()); + } + + // Get the connected components of the graph. The 'visited' flags are + // 0 (unvisited), 1 (discovered), 2 (finished). The Vertex constructor + // sets all 'visited' flags to 0. + std::vector> components; + for (auto vInitial : mVertexStorage) + { + if (vInitial->visited == 0) + { + components.push_back(std::vector()); + DepthFirstSearch(vInitial.get(), components.back()); + } + } + + // The depth-first search is used later for collecting vertices for + // subgraphs that are detached from the main graph, so the 'visited' + // flags must be reset to zero after component finding. + for (auto vertex : mVertexStorage) + { + vertex->visited = 0; + } + + // Get the primitives for the components. + for (auto& component : components) + { + forest.push_back(ExtractBasis(component)); + } +} + +template +void MinimalCycleBasis::DepthFirstSearch(Vertex* vInitial, std::vector& component) +{ + std::stack vStack; + vStack.push(vInitial); + while (vStack.size() > 0) + { + Vertex* vertex = vStack.top(); + vertex->visited = 1; + size_t i = 0; + for (auto adjacent : vertex->adjacent) + { + if (adjacent && adjacent->visited == 0) + { + vStack.push(adjacent); + break; + } + ++i; + } + + if (i == vertex->adjacent.size()) + { + vertex->visited = 2; + component.push_back(vertex); + vStack.pop(); + } + } +} + +template +std::shared_ptr::Tree> + MinimalCycleBasis::ExtractBasis(std::vector& component) +{ + // The root will not have its 'cycle' member set. The children are + // the cycle trees extracted from the component. + std::shared_ptr tree = std::make_shared(); + while (component.size() > 0) + { + RemoveFilaments(component); + if (component.size() > 0) + { + tree->children.push_back(ExtractCycleFromComponent(component)); + } + } + + if (tree->cycle.size() == 0 && tree->children.size() == 1) + { + // Replace the parent by the child to avoid having two empty + // cycles in parent/child. + std::shared_ptr child = tree->children.back(); + tree->cycle = std::move(child->cycle); + tree->children = std::move(child->children); + } + return tree; +} + +template +void MinimalCycleBasis::RemoveFilaments(std::vector& component) +{ + // Locate all filament endpoints, which are vertices, each having exactly + // one adjacent vertex. + std::vector endpoints; + for (auto vertex : component) + { + if (vertex->adjacent.size() == 1) + { + endpoints.push_back(vertex); + } + } + + if (endpoints.size() > 0) + { + // Remove the filaments from the component. If a filament has two + // endpoints, each having one adjacent vertex, the adjacency set of + // the final visited vertex become empty. We must test for that + // condition before starting a new filament removal. + for (auto vertex : endpoints) + { + if (vertex->adjacent.size() == 1) + { + // Traverse the filament and remove the vertices. + while (vertex->adjacent.size() == 1) + { + // Break the connection between the two vertices. + Vertex* adjacent = *vertex->adjacent.begin(); + adjacent->adjacent.erase(vertex); + vertex->adjacent.erase(adjacent); + + // Traverse to the adjacent vertex. + vertex = adjacent; + } + } + } + + // At this time the component is either empty (it was a union of + // polylines) or it has no filaments and at least one cycle. Remove + // the isolated vertices generated by filament extraction. + std::vector remaining; + remaining.reserve(component.size()); + for (auto vertex : component) + { + if (vertex->adjacent.size() > 0) + { + remaining.push_back(vertex); + } + } + component = std::move(remaining); + } +} + +template +std::shared_ptr::Tree> + MinimalCycleBasis::ExtractCycleFromComponent(std::vector& component) +{ + // Search for the left-most vertex of the component. If two or more + // vertices attain minimum x-value, select the one that has minimum + // y-value. + Vertex* minVertex = component[0]; + for (auto vertex : component) + { + if (*vertex->position < *minVertex->position) + { + minVertex = vertex; + } + } + + // Traverse the closed walk, duplicating the starting vertex as the + // last vertex. + std::vector closedWalk; + Vertex* vCurr = minVertex; + Vertex* vStart = vCurr; + closedWalk.push_back(vStart); + Vertex* vAdj = GetClockwiseMost(nullptr, vStart); + while (vAdj != vStart) + { + closedWalk.push_back(vAdj); + Vertex* vNext = GetCounterclockwiseMost(vCurr, vAdj); + vCurr = vAdj; + vAdj = vNext; + } + closedWalk.push_back(vStart); + + // Recursively process the closed walk to extract cycles. + std::shared_ptr tree = ExtractCycleFromClosedWalk(closedWalk); + + // The isolated vertices generated by cycle removal are also removed from + // the component. + std::vector remaining; + remaining.reserve(component.size()); + for (auto vertex : component) + { + if (vertex->adjacent.size() > 0) + { + remaining.push_back(vertex); + } + } + component = std::move(remaining); + + return tree; +} + +template +std::shared_ptr::Tree> + MinimalCycleBasis::ExtractCycleFromClosedWalk(std::vector& closedWalk) +{ + std::shared_ptr tree = std::make_shared(); + + std::map duplicates; + std::set detachments; + int numClosedWalk = static_cast(closedWalk.size()); + for (int i = 1; i < numClosedWalk - 1; ++i) + { + auto diter = duplicates.find(closedWalk[i]); + if (diter == duplicates.end()) + { + // We have not yet visited this vertex. + duplicates.insert(std::make_pair(closedWalk[i], i)); + continue; + } + + // The vertex has been visited previously. Collapse the closed walk + // by removing the subwalk sharing this vertex. Note that the vertex + // is pointed to by closedWalk[diter->second] and closedWalk[i]. + int iMin = diter->second, iMax = i; + detachments.insert(iMin); + for (int j = iMin + 1; j < iMax; ++j) + { + Vertex* vertex = closedWalk[j]; + duplicates.erase(vertex); + detachments.erase(j); + } + closedWalk.erase(closedWalk.begin() + iMin + 1, closedWalk.begin() + iMax + 1); + numClosedWalk = static_cast(closedWalk.size()); + i = iMin; + } + + if (numClosedWalk > 3) + { + // We do not know whether closedWalk[0] is a detachment point. To + // determine this, we must test for any edges strictly contained by + // the wedge formed by the edges and + // . However, we must execute this test + // even for the known detachment points. The ensuing logic is designed + // to handle this and reduce the amount of code, so we insert + // closedWalk[0] into the detachment set and will ignore it later if + // it actually is not. + detachments.insert(0); + + // Detach subgraphs from the vertices of the cycle. + for (auto i : detachments) + { + Vertex* original = closedWalk[i]; + Vertex* maxVertex = closedWalk[i + 1]; + Vertex* minVertex = (i > 0 ? closedWalk[i - 1] : closedWalk[numClosedWalk - 2]); + + std::array dMin, dMax; + for (int j = 0; j < 2; ++j) + { + dMin[j] = (*minVertex->position)[j] - (*original->position)[j]; + dMax[j] = (*maxVertex->position)[j] - (*original->position)[j]; + } + + bool isConvex = (dMax[0] * dMin[1] >= dMax[1] * dMin[0]); (void)isConvex; + + std::set inWedge; + std::set adjacent = original->adjacent; + for (auto vertex : adjacent) + { + if (vertex->name == minVertex->name || vertex->name == maxVertex->name) + { + continue; + } + + std::array dVer; + for (int j = 0; j < 2; ++j) + { + dVer[j] = (*vertex->position)[j] - (*original->position)[j]; + } + + bool containsVertex; + if (isConvex) + { + containsVertex = + dVer[0] * dMin[1] > dVer[1] * dMin[0] && + dVer[0] * dMax[1] < dVer[1] * dMax[0]; + } + else + { + containsVertex = + (dVer[0] * dMin[1] > dVer[1] * dMin[0]) || + (dVer[0] * dMax[1] < dVer[1] * dMax[0]); + } + if (containsVertex) + { + inWedge.insert(vertex); + } + } + + if (inWedge.size() > 0) + { + // The clone will manage the adjacents for 'original' that lie + // inside the wedge defined by the first and last edges of the + // subgraph rooted at 'original'. The sorting is in the + // clockwise direction. + std::shared_ptr clone = + std::make_shared(original->name, original->position); + mVertexStorage.push_back(clone); + + // Detach the edges inside the wedge. + for (auto vertex : inWedge) + { + original->adjacent.erase(vertex); + vertex->adjacent.erase(original); + clone->adjacent.insert(vertex); + vertex->adjacent.insert(clone.get()); + } + + // Get the subgraph (it is a single connected component). + std::vector component; + DepthFirstSearch(clone.get(), component); + + // Extract the cycles of the subgraph. + tree->children.push_back(ExtractBasis(component)); + } + // else the candidate was closedWalk[0] and it has no subgraph + // to detach. + } + + tree->cycle = std::move(ExtractCycle(closedWalk)); + } + else + { + // Detach the subgraph from vertex closedWalk[0]; the subgraph + // is attached via a filament. + Vertex* original = closedWalk[0]; + Vertex* adjacent = closedWalk[1]; + + std::shared_ptr clone = + std::make_shared(original->name, original->position); + mVertexStorage.push_back(clone); + + original->adjacent.erase(adjacent); + adjacent->adjacent.erase(original); + clone->adjacent.insert(adjacent); + adjacent->adjacent.insert(clone.get()); + + // Get the subgraph (it is a single connected component). + std::vector component; + DepthFirstSearch(clone.get(), component); + + // Extract the cycles of the subgraph. + tree->children.push_back(ExtractBasis(component)); + if (tree->cycle.size() == 0 && tree->children.size() == 1) + { + // Replace the parent by the child to avoid having two empty + // cycles in parent/child. + std::shared_ptr child = tree->children.back(); + tree->cycle = std::move(child->cycle); + tree->children = std::move(child->children); + } + } + + return tree; +} + +template +std::vector MinimalCycleBasis::ExtractCycle(std::vector& closedWalk) +{ + // TODO: This logic was designed not to remove filaments after the + // cycle deletion is complete. Modify this to allow filament removal. + + // The closed walk is a cycle. + int const numVertices = static_cast(closedWalk.size()); + std::vector cycle(numVertices); + for (int i = 0; i < numVertices; ++i) + { + cycle[i] = closedWalk[i]->name; + } + + // The clockwise-most edge is always removable. + Vertex* v0 = closedWalk[0]; + Vertex* v1 = closedWalk[1]; + Vertex* vBranch = (v0->adjacent.size() > 2 ? v0 : nullptr); + v0->adjacent.erase(v1); + v1->adjacent.erase(v0); + + // Remove edges while traversing counterclockwise. + while (v1 != vBranch && v1->adjacent.size() == 1) + { + Vertex* adj = *v1->adjacent.begin(); + v1->adjacent.erase(adj); + adj->adjacent.erase(v1); + v1 = adj; + } + + if (v1 != v0) + { + // If v1 had exactly 3 adjacent vertices, removal of the CCW edge + // that shared v1 leads to v1 having 2 adjacent vertices. When + // the CW removal occurs and we reach v1, the edge deletion will + // lead to v1 having 1 adjacent vertex, making it a filament + // endpoints. We must ensure we do not delete v1 in this case, + // allowing the recursive algorithm to handle the filament later. + vBranch = v1; + + // Remove edges while traversing clockwise. + while (v0 != vBranch && v0->adjacent.size() == 1) + { + v1 = *v0->adjacent.begin(); + v0->adjacent.erase(v1); + v1->adjacent.erase(v0); + v0 = v1; + } + } + // else the cycle is its own connected component. + + return cycle; +} + +template +typename MinimalCycleBasis::Vertex* + MinimalCycleBasis::GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const +{ + Vertex* vNext = nullptr; + bool vCurrConvex = false; + std::array dCurr, dNext; + if (vPrev) + { + dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0]; + dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1]; + } + else + { + dCurr[0] = static_cast(0); + dCurr[1] = static_cast(-1); + } + + for (auto vAdj : vCurr->adjacent) + { + // vAdj is a vertex adjacent to vCurr. No backtracking is allowed. + if (vAdj == vPrev) + { + continue; + } + + // Compute the potential direction to move in. + std::array dAdj; + dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0]; + dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1]; + + // Select the first candidate. + if (!vNext) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + continue; + } + + // Update if the next candidate is clockwise of the current + // clockwise-most vertex. + if (vCurrConvex) + { + if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0] + || dNext[0] * dAdj[1] < dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + else + { + if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0] + && dNext[0] * dAdj[1] < dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] < dNext[1] * dCurr[0]); + } + } + } + + return vNext; +} + +template +typename MinimalCycleBasis::Vertex* + MinimalCycleBasis::GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const +{ + Vertex* vNext = nullptr; + bool vCurrConvex = false; + std::array dCurr, dNext; + if (vPrev) + { + dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0]; + dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1]; + } + else + { + dCurr[0] = static_cast(0); + dCurr[1] = static_cast(-1); + } + + for (auto vAdj : vCurr->adjacent) + { + // vAdj is a vertex adjacent to vCurr. No backtracking is allowed. + if (vAdj == vPrev) + { + continue; + } + + // Compute the potential direction to move in. + std::array dAdj; + dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0]; + dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1]; + + // Select the first candidate. + if (!vNext) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + continue; + } + + // Select the next candidate if it is counterclockwise of the current + // counterclockwise-most vertex. + if (vCurrConvex) + { + if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0] + && dNext[0] * dAdj[1] > dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + else + { + if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0] + || dNext[0] * dAdj[1] > dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + } + + return vNext; +} + +template +MinimalCycleBasis::Vertex::Vertex(int inName, std::array const* inPosition) + : + name(inName), + position(inPosition), + visited(0) +{ +} + +template +bool MinimalCycleBasis::Vertex::operator< (Vertex const& vertex) const +{ + return name < vertex.name; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimize1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimize1.h new file mode 100644 index 000000000000..2530b820f221 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimize1.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// The interval [t0,t1] provided to GetMinimum(Real,Real,Real,Real&,Real&) +// is processed by examining subintervals. On each subinteral [a,b], the +// values f0 = F(a), f1 = F((a+b)/2), and f2 = F(b) are examined. If +// {f0,f1,f2} is monotonic, then [a,b] is subdivided and processed. The +// maximum depth of the recursion is limited by 'maxLevel'. If {f0,f1,f2} +// is not monotonic, then two cases arise. First, if f1 = min{f0,f1,f2}, +// then {f0,f1,f2} is said to "bracket a minimum" and GetBracketedMinimum(*) +// is called to locate the function minimum. The process uses a form of +// bisection called "parabolic interpolation" and the maximum number of +// bisection steps is 'maxBracket'. Second, if f1 = max{f0,f1,f2}, then +// {f0,f1,f2} brackets a maximum. The minimum search continues recursively +// as before on [a,(a+b)/2] and [(a+b)/2,b]. + +namespace gte +{ + +template +class Minimize1 +{ +public: + // Construction. + Minimize1(std::function const& F, int maxLevel, + int maxBracket); + + // Search for a minimum of 'function' on the interval [t0,t1] using an + // initial guess of 'tInitial'. The location of the minimum is 'tMin' and + // the value of the minimum is 'fMin'. + void GetMinimum(Real t0, Real t1, Real tInitial, Real& tMin, Real& fMin); + +private: + // This is called to start the search on [t0,tInitial] and [tInitial,t1]. + void GetMinimum(Real t0, Real f0, Real t1, Real f1, int level); + + // This is called recursively to search [a,(a+b)/2] and [(a+b)/2,b]. + void GetMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, Real f1, + int level); + + // This is called when {f0,f1,f2} brackets a minimum. + void GetBracketedMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, + Real f1, int level); + + std::function mFunction; + int mMaxLevel; + int mMaxBracket; + Real mTMin, mFMin; +}; + + +template +Minimize1::Minimize1(std::function const& F, int maxLevel, + int maxBracket) + : + mFunction(F), + mMaxLevel(maxLevel), + mMaxBracket(maxBracket) +{ +} + +template +void Minimize1::GetMinimum(Real t0, Real t1, Real tInitial, + Real& tMin, Real& fMin) +{ + LogAssert(t0 <= tInitial && tInitial <= t1, "Invalid initial t value."); + + mTMin = std::numeric_limits::max(); + mFMin = std::numeric_limits::max(); + + Real f0 = mFunction(t0); + if (f0 < mFMin) + { + mTMin = t0; + mFMin = f0; + } + + Real fInitial = mFunction(tInitial); + if (fInitial < mFMin) + { + mTMin = tInitial; + mFMin = fInitial; + } + + Real f1 = mFunction(t1); + if (f1 < mFMin) + { + mTMin = t1; + mFMin = f1; + } + + GetMinimum(t0, f0, tInitial, fInitial, t1, f1, mMaxLevel); + + tMin = mTMin; + fMin = mFMin; +} + +template +void Minimize1::GetMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, + Real f1, int level) +{ + if (level-- == 0) + { + return; + } + + if ((t1 - tm)*(f0 - fm) > (tm - t0)*(fm - f1)) + { + // The quadratic fit has positive second derivative at the midpoint. + if (f1 > f0) + { + if (fm >= f0) + { + // Increasing, repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else if (f1 < f0) + { + if (fm >= f1) + { + // Decreasing, repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else + { + // Constant, repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + else + { + // The quadratic fit has a nonpositive second derivative at the + // midpoint. + if (f1 > f0) + { + // Repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else if (f1 < f0) + { + // Repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } +} + +template +void Minimize1::GetMinimum(Real t0, Real f0, Real t1, Real f1, + int level) +{ + if (level-- == 0) + { + return; + } + + Real tm = ((Real)0.5)*(t0 + t1); + Real fm = mFunction(tm); + if (fm < mFMin) + { + mTMin = tm; + mFMin = fm; + } + + if (f0 - ((Real)2)*fm + f1 >(Real)0) + { + // The quadratic fit has positive second derivative at the midpoint. + if (f1 > f0) + { + if (fm >= f0) + { + // Increasing, repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else if (f1 < f0) + { + if (fm >= f1) + { + // Decreasing, repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else + { + // Constant, repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + else + { + // The quadratic fit has nonpositive second derivative at the + // midpoint. + if (f1 > f0) + { + // Repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else if (f1 < f0) + { + // Repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } +} + +template +void Minimize1::GetBracketedMinimum(Real t0, Real f0, Real tm, + Real fm, Real t1, Real f1, int level) +{ + for (int i = 0; i < mMaxBracket; ++i) + { + // Update minimum value. + if (fm < mFMin) + { + mTMin = tm; + mFMin = fm; + } + + // Test for convergence. TODO: Expose the epsilon and tolerance + // parameters to the caller. + Real const epsilon = (Real)1e-08; + Real const tolerance = (Real)1e-04; + if (std::abs(t1 - t0) <= ((Real)2)*tolerance*std::abs(tm) + epsilon) + { + break; + } + + // Compute vertex of interpolating parabola. + Real dt0 = t0 - tm; + Real dt1 = t1 - tm; + Real df0 = f0 - fm; + Real df1 = f1 - fm; + Real tmp0 = dt0*df1; + Real tmp1 = dt1*df0; + Real denom = tmp1 - tmp0; + if (std::abs(denom) < epsilon) + { + return; + } + + Real tv = tm + ((Real)0.5)*(dt1*tmp1 - dt0*tmp0) / denom; + LogAssert(t0 <= tv && tv <= t1, "Vertex not in interval."); + Real fv = mFunction(tv); + if (fv < mFMin) + { + mTMin = tv; + mFMin = fv; + } + + if (tv < tm) + { + if (fv < fm) + { + t1 = tm; + f1 = fm; + tm = tv; + fm = fv; + } + else + { + t0 = tv; + f0 = fv; + } + } + else if (tv > tm) + { + if (fv < fm) + { + t0 = tm; + f0 = fm; + tm = tv; + fm = fv; + } + else + { + t1 = tv; + f1 = fv; + } + } + else + { + // The vertex of parabola is already at middle sample point. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimizeN.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimizeN.h new file mode 100644 index 000000000000..a5b1f3187989 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimizeN.h @@ -0,0 +1,213 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The Cartesian-product domain provided to GetMinimum(*) has minimum values +// stored in t0[0..d-1] and maximum values stored in t1[0..d-1], where d is +// 'dimensions'. The domain is searched along lines through the current +// estimate of the minimum location. Each such line is searched for a minimum +// using a Minimize1 object. This is called "Powell's Direction Set +// Method". The parameters 'maxLevel' and 'maxBracket' are used by +// Minimize1, so read the documentation for that class (in its header +// file) to understand what these mean. The input 'maxIterations' is the +// number of iterations for the direction-set method. + +namespace gte +{ + +template +class MinimizeN +{ +public: + // Construction. + MinimizeN(int dimensions, std::function const& F, + int maxLevel, int maxBracket, int maxIterations); + + // Find the minimum on the Cartesian-product domain whose minimum values + // are stored in t0[0..d-1] and whose maximum values are stored in + // t1[0..d-1], where d is 'dimensions'. An initial guess is specified in + // tInitial[0..d-1]. The location of the minimum is tMin[0..d-1] and + // the value of the minimum is 'fMin'. + void GetMinimum(Real const* t0, Real const* t1, Real const* tInitial, + Real* tMin, Real& fMin); + +private: + // The current estimate of the minimum location is mTCurr[0..d-1]. The + // direction of the current line to search is mDCurr[0..d-1]. This line + // must be clipped against the Cartesian-product domain, a process + // implemented in this function. If the line is mTCurr+s*mDCurr, the + // clip result is the s-interval [ell0,ell1]. + void ComputeDomain(Real const* t0, Real const* t1, Real& ell0, + Real& ell1); + + int mDimensions; + std::function mFunction; + int mMaxIterations; + std::vector> mDirections; + int mDConjIndex; + int mDCurrIndex; + GVector mTCurr; + GVector mTSave; + Real mFCurr; + Minimize1 mMinimizer; +}; + + +template +MinimizeN::MinimizeN(int dimensions, + std::function const& F, int maxLevel, int maxBracket, + int maxIterations) + : + mDimensions(dimensions), + mFunction(F), + mMaxIterations(maxIterations), + mDirections(dimensions + 1), + mDConjIndex(dimensions), + mDCurrIndex(0), + mTCurr(dimensions), + mTSave(dimensions), + mMinimizer( + [this](Real t) +{ + return mFunction(&(mTCurr + t*mDirections[mDCurrIndex])[0]); +}, +maxLevel, maxBracket) +{ + for (auto& direction : mDirections) + { + direction.SetSize(dimensions); + } +} + +template +void MinimizeN::GetMinimum(Real const* t0, Real const* t1, + Real const* tInitial, Real* tMin, Real& fMin) +{ + // The initial guess. + size_t numBytes = mDimensions*sizeof(Real); + mFCurr = mFunction(tInitial); + Memcpy(&mTSave[0], tInitial, numBytes); + Memcpy(&mTCurr[0], tInitial, numBytes); + + // Initialize the direction set to the standard Euclidean basis. + for (int i = 0; i < mDimensions; ++i) + { + mDirections[i].MakeUnit(i); + } + + Real ell0, ell1, ellMin; + for (int iter = 0; iter < mMaxIterations; ++iter) + { + // Find minimum in each direction and update current location. + for (int i = 0; i < mDimensions; ++i) + { + mDCurrIndex = i; + ComputeDomain(t0, t1, ell0, ell1); + mMinimizer.GetMinimum(ell0, ell1, (Real)0, ellMin, mFCurr); + mTCurr += ellMin*mDirections[i]; + } + + // Estimate a unit-length conjugate direction. TODO: Expose + // epsilon to the caller. + mDirections[mDConjIndex] = mTCurr - mTSave; + Real length = Length(mDirections[mDConjIndex]); + Real const epsilon = (Real)1e-06; + if (length < epsilon) + { + // New position did not change significantly from old one. + // Should there be a better convergence criterion here? + break; + } + + mDirections[mDConjIndex] /= length; + + // Minimize in conjugate direction. + mDCurrIndex = mDConjIndex; + ComputeDomain(t0, t1, ell0, ell1); + mMinimizer.GetMinimum(ell0, ell1, (Real)0, ellMin, mFCurr); + mTCurr += ellMin*mDirections[mDCurrIndex]; + + // Cycle the directions and add conjugate direction to set. + mDConjIndex = 0; + for (int i = 0; i < mDimensions; ++i) + { + mDirections[i] = mDirections[i + 1]; + } + + // Set parameters for next pass. + mTSave = mTCurr; + } + + Memcpy(tMin, &mTCurr[0], numBytes); + fMin = mFCurr; +} + +template +void MinimizeN::ComputeDomain(Real const* t0, Real const* t1, + Real& ell0, Real& ell1) +{ + ell0 = -std::numeric_limits::max(); + ell1 = +std::numeric_limits::max(); + + for (int i = 0; i < mDimensions; ++i) + { + Real value = mDirections[mDCurrIndex][i]; + if (value != (Real)0) + { + Real b0 = t0[i] - mTCurr[i]; + Real b1 = t1[i] - mTCurr[i]; + Real inv = ((Real)1) / value; + if (value >(Real)0) + { + // The valid t-interval is [b0,b1]. + b0 *= inv; + if (b0 > ell0) + { + ell0 = b0; + } + b1 *= inv; + if (b1 < ell1) + { + ell1 = b1; + } + } + else + { + // The valid t-interval is [b1,b0]. + b0 *= inv; + if (b0 < ell1) + { + ell1 = b0; + } + b1 *= inv; + if (b1 > ell0) + { + ell0 = b1; + } + } + } + } + + // Correction if numerical errors lead to values nearly zero. + if (ell0 > (Real)0) + { + ell0 = (Real)0; + } + if (ell1 < (Real)0) + { + ell1 = (Real)0; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaBox2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaBox2.h new file mode 100644 index 000000000000..b4fde3629cf6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaBox2.h @@ -0,0 +1,719 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include + +// Compute a minimum-area oriented box containing the specified points. The +// algorithm uses the rotating calipers method. +// http://www-cgrl.cs.mcgill.ca/~godfried/research/calipers.html +// http://cgm.cs.mcgill.ca/~orm/rotcal.html +// The box is supported by the convex hull of the points, so the algorithm +// is really about computing the minimum-area box containing a convex polygon. +// The rotating calipers approach is O(n) in time for n polygon edges. +// +// A detailed description of the algorithm and implementation is found in +// http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf +// +// NOTE: This algorithm guarantees a correct output only when ComputeType is +// an exact arithmetic type that supports division. In GTEngine, one such +// type is BSRational (arbitrary precision). Another such type +// is BSRational> (fixed precision), where N is chosen large +// enough for your input data sets. If you choose ComputeType to be 'float' +// or 'double', the output is not guaranteed to be correct. +// +// See GeometricTools/GTEngine/Samples/Geometrics/MinimumAreaBox2 for an +// example of how to use the code. + +namespace gte +{ + +template +class MinimumAreaBox2 +{ +public: + // The class is a functor to support computing the minimum-area box of + // multiple data sets using the same class object. + MinimumAreaBox2(); + + // The points are arbitrary, so we must compute the convex hull from + // them in order to compute the minimum-area box. The input parameters + // are necessary for using ConvexHull2. NOTE: ConvexHull2 guarantees + // that the hull does not have three consecutive collinear points. + OrientedBox2 operator()(int numPoints, + Vector2 const* points, + bool useRotatingCalipers = + !std::is_floating_point::value); + + // The points already form a counterclockwise, nondegenerate convex + // polygon. If the points directly are the convex polygon, set + // numIndices to 0 and indices to nullptr. If the polygon vertices + // are a subset of the incoming points, that subset is identified by + // numIndices >= 3 and indices having numIndices elements. + OrientedBox2 operator()(int numPoints, + Vector2 const* points, int numIndices, int const* indices, + bool useRotatingCalipers = + !std::is_floating_point::value); + + // Member access. + inline int GetNumPoints() const; + inline Vector2 const* GetPoints() const; + inline std::vector const& GetHull() const; + inline std::array const& GetSupportIndices() const; + inline InputType GetArea() const; + +private: + // The box axes are U[i] and are usually not unit-length in order to allow + // exact arithmetic. The box is supported by mPoints[index[i]], where i + // is one of the enumerations above. The box axes are not necessarily unit + // length, but they have the same length. They need to be normalized for + // conversion back to InputType. + struct Box + { + Vector2 U[2]; + std::array index; // order: bottom, right, top, left + ComputeType sqrLenU0, area; + }; + + // The rotating calipers algorithm has a loop invariant that requires + // the convex polygon not to have collinear points. Any such points + // must be removed first. The code is also executed for the O(n^2) + // algorithm to reduce the number of process edges. + void RemoveCollinearPoints(std::vector>& vertices); + + // This is the slow O(n^2) search. + Box ComputeBoxForEdgeOrderNSqr( + std::vector> const& vertices); + + // The fast O(n) search. + Box ComputeBoxForEdgeOrderN( + std::vector> const& vertices); + + // Compute the smallest box for the polygon edge . + Box SmallestBox(int i0, int i1, + std::vector> const& vertices); + + // Compute (sin(angle))^2 for the polygon edges emanating from the support + // vertices of the box. The return value is 'true' if at least one angle + // is in [0,pi/2); otherwise, the return value is 'false' and the original + // polygon must be a rectangle. + bool ComputeAngles(std::vector> const& vertices, + Box const& box, std::array, 4>& A, + int& numA) const; + + // Sort the angles indirectly. The sorted indices are returned. This + // avoids swapping elements of A[], which can be expensive when + // ComputeType is an exact rational type. + std::array SortAngles( + std::array, 4> const& A, int numA) const; + + bool UpdateSupport(std::array, 4> const& A, + int numA, std::array const& sort, + std::vector> const& vertices, + std::vector& visited, Box& box); + + // Convert the ComputeType box to the InputType box. When the ComputeType + // is an exact rational type, the conversions are performed to avoid + // precision loss until necessary at the last step. + void ConvertTo(Box const& minBox, + std::vector> const& computePoints, + OrientedBox2& itMinBox); + + // The input points to be bound. + int mNumPoints; + Vector2 const* mPoints; + + // The indices into mPoints/mComputePoints for the convex hull vertices. + std::vector mHull; + + // The support indices for the minimum-area box. + std::array mSupportIndices; + + // The area of the minimum-area box. The ComputeType value is exact, + // so the only rounding errors occur in the conversion from ComputeType + // to InputType (default rounding mode is round-to-nearest-ties-to-even). + InputType mArea; + + // Convenient values that occur regularly in the code. When using + // rational ComputeType, we construct these numbers only once. + ComputeType mZero, mOne, mNegOne, mHalf; +}; + + +template +MinimumAreaBox2::MinimumAreaBox2() + : + mNumPoints(0), + mPoints(nullptr), + mArea((InputType)0), + mZero(0), + mOne(1), + mNegOne(-1), + mHalf((InputType)0.5) +{ + mSupportIndices = { 0, 0, 0, 0 }; +} + +template +OrientedBox2 MinimumAreaBox2::operator()( + int numPoints, Vector2 const* points, bool useRotatingCalipers) +{ + mNumPoints = numPoints; + mPoints = points; + mHull.clear(); + + // Get the convex hull of the points. + ConvexHull2 ch2; + ch2(mNumPoints, mPoints, (InputType)0); + int dimension = ch2.GetDimension(); + + OrientedBox2 minBox; + + if (dimension == 0) + { + // The points are all effectively the same (using fuzzy epsilon). + minBox.center = mPoints[0]; + minBox.axis[0] = Vector2::Unit(0); + minBox.axis[1] = Vector2::Unit(1); + minBox.extent[0] = (InputType)0; + minBox.extent[1] = (InputType)0; + mHull.resize(1); + mHull[0] = 0; + return minBox; + } + + if (dimension == 1) + { + // The points effectively lie on a line (using fuzzy epsilon). + // Determine the extreme t-values for the points represented as + // P = origin + t*direction. We know that 'origin' is an input + // vertex, so we can start both t-extremes at zero. + Line2 const& line = ch2.GetLine(); + InputType tmin = (InputType)0, tmax = (InputType)0; + int imin = 0, imax = 0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector2 diff = mPoints[i] - line.origin; + InputType t = Dot(diff, line.direction); + if (t > tmax) + { + tmax = t; + imax = i; + } + else if (t < tmin) + { + tmin = t; + imin = i; + } + } + + minBox.center = line.origin + + ((InputType)0.5)*(tmin + tmax) * line.direction; + minBox.extent[0] = ((InputType)0.5)*(tmax - tmin); + minBox.extent[1] = (InputType)0; + minBox.axis[0] = line.direction; + minBox.axis[1] = -Perp(line.direction); + mHull.resize(2); + mHull[0] = imin; + mHull[1] = imax; + return minBox; + } + + mHull = ch2.GetHull(); + Vector2 const* queryPoints = ch2.GetQuery().GetVertices(); + std::vector> computePoints(mHull.size()); + for (size_t i = 0; i < mHull.size(); ++i) + { + computePoints[i] = queryPoints[mHull[i]]; + } + + RemoveCollinearPoints(computePoints); + + Box box; + if (useRotatingCalipers) + { + box = ComputeBoxForEdgeOrderN(computePoints); + } + else + { + box = ComputeBoxForEdgeOrderNSqr(computePoints); + } + + ConvertTo(box, computePoints, minBox); + return minBox; +} + +template +OrientedBox2 MinimumAreaBox2::operator()( + int numPoints, Vector2 const* points, int numIndices, + int const* indices, bool useRotatingCalipers) +{ + mHull.clear(); + + OrientedBox2 minBox; + + if (numPoints < 3 || !points || (indices && numIndices < 3)) + { + minBox.center = Vector2::Zero(); + minBox.axis[0] = Vector2::Unit(0); + minBox.axis[1] = Vector2::Unit(1); + minBox.extent = Vector2::Zero(); + return minBox; + } + + if (indices) + { + mHull.resize(numIndices); + std::copy(indices, indices + numIndices, mHull.begin()); + } + else + { + numIndices = numPoints; + mHull.resize(numIndices); + for (int i = 0; i < numIndices; ++i) + { + mHull[i] = i; + } + } + + std::vector> computePoints(numIndices); + for (int i = 0; i < numIndices; ++i) + { + int h = mHull[i]; + computePoints[i][0] = (ComputeType)points[h][0]; + computePoints[i][1] = (ComputeType)points[h][1]; + } + + RemoveCollinearPoints(computePoints); + + Box box; + if (useRotatingCalipers) + { + box = ComputeBoxForEdgeOrderN(computePoints); + } + else + { + box = ComputeBoxForEdgeOrderNSqr(computePoints); + } + + ConvertTo(box, computePoints, minBox); + return minBox; +} + +template inline +int MinimumAreaBox2::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +Vector2 const* MinimumAreaBox2::GetPoints() + const +{ + return mPoints; +} + +template inline +std::vector const& +MinimumAreaBox2::GetHull() const +{ + return mHull; +} + +template inline +std::array const& +MinimumAreaBox2::GetSupportIndices() const +{ + return mSupportIndices; +} + +template inline +InputType MinimumAreaBox2::GetArea() const +{ + return mArea; +} + +template +void MinimumAreaBox2::RemoveCollinearPoints( + std::vector>& vertices) +{ + std::vector> tmpVertices = vertices; + + int const numVertices = static_cast(vertices.size()); + int numNoncollinear = 0; + Vector2 ePrev = tmpVertices[0] - tmpVertices.back(); + for (int i0 = 0, i1 = 1; i0 < numVertices; ++i0) + { + Vector2 eNext = tmpVertices[i1] - tmpVertices[i0]; + + ComputeType dp = DotPerp(ePrev, eNext); + if (dp != mZero) + { + vertices[numNoncollinear++] = tmpVertices[i0]; + } + + ePrev = eNext; + if (++i1 == numVertices) + { + i1 = 0; + } + } + + vertices.resize(numNoncollinear); +} + +template +typename MinimumAreaBox2::Box +MinimumAreaBox2::ComputeBoxForEdgeOrderNSqr( + std::vector> const& vertices) +{ + Box minBox; + minBox.area = mNegOne; + int const numIndices = static_cast(vertices.size()); + for (int i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++) + { + Box box = SmallestBox(i0, i1, vertices); + if (minBox.area == mNegOne || box.area < minBox.area) + { + minBox = box; + } + } + return minBox; +} + +template +typename MinimumAreaBox2::Box +MinimumAreaBox2::ComputeBoxForEdgeOrderN( + std::vector> const& vertices) +{ + // The inputs are assumed to be the vertices of a convex polygon that + // is counterclockwise ordered. The input points must not contain three + // consecutive collinear points. + + // When the bounding box corresponding to a polygon edge is computed, + // we mark the edge as visited. If the edge is encountered later, the + // algorithm terminates. + std::vector visited(vertices.size()); + std::fill(visited.begin(), visited.end(), false); + + // Start the minimum-area rectangle search with the edge from the last + // polygon vertex to the first. When updating the extremes, we want the + // bottom-most point on the left edge, the top-most point on the right + // edge, the left-most point on the top edge, and the right-most point + // on the bottom edge. The polygon edges starting at these points are + // then guaranteed not to coincide with a box edge except when an extreme + // point is shared by two box edges (at a corner). + Box minBox = SmallestBox((int)vertices.size() - 1, 0, vertices); + visited[minBox.index[0]] = true; + + // Execute the rotating calipers algorithm. + Box box = minBox; + for (size_t i = 0; i < vertices.size(); ++i) + { + std::array, 4> A; + int numA; + if (!ComputeAngles(vertices, box, A, numA)) + { + // The polygon is a rectangle, so the search is over. + break; + } + + // Indirectly sort the A-array. + std::array sort = SortAngles(A, numA); + + // Update the supporting indices (box.index[]) and the box axis + // directions (box.U[]). + if (!UpdateSupport(A, numA, sort, vertices, visited, box)) + { + // We have already processed the box polygon edge, so the search + // is over. + break; + } + + if (box.area < minBox.area) + { + minBox = box; + } + } + + return minBox; +} + +template +typename MinimumAreaBox2::Box +MinimumAreaBox2::SmallestBox(int i0, int i1, + std::vector> const& vertices) +{ + Box box; + box.U[0] = vertices[i1] - vertices[i0]; + box.U[1] = -Perp(box.U[0]); + box.index = { i1, i1, i1, i1 }; + box.sqrLenU0 = Dot(box.U[0], box.U[0]); + + Vector2 const& origin = vertices[i1]; + Vector2 support[4]; + for (int j = 0; j < 4; ++j) + { + support[j] = { mZero, mZero }; + } + + int i = 0; + for (auto const& vertex : vertices) + { + Vector2 diff = vertex - origin; + Vector2 v = { Dot(box.U[0], diff), Dot(box.U[1], diff) }; + + // The right-most vertex of the bottom edge is vertices[i1]. The + // assumption of no triple of collinear vertices guarantees that + // box.index[0] is i1, which is the initial value assigned at the + // beginning of this function. Therefore, there is no need to test + // for other vertices farther to the right than vertices[i1]. + + if (v[0] > support[1][0] || + (v[0] == support[1][0] && v[1] > support[1][1])) + { + // New right maximum OR same right maximum but closer to top. + box.index[1] = i; + support[1] = v; + } + + if (v[1] > support[2][1] || + (v[1] == support[2][1] && v[0] < support[2][0])) + { + // New top maximum OR same top maximum but closer to left. + box.index[2] = i; + support[2] = v; + } + + if (v[0] < support[3][0] || + (v[0] == support[3][0] && v[1] < support[3][1])) + { + // New left minimum OR same left minimum but closer to bottom. + box.index[3] = i; + support[3] = v; + } + + ++i; + } + + // The comment in the loop has the implication that support[0] = { 0, 0 }, + // so the scaled height (support[2][1] - support[0][1]) is simply + // support[2][1]. + ComputeType scaledWidth = support[1][0] - support[3][0]; + ComputeType scaledHeight = support[2][1]; + box.area = scaledWidth * scaledHeight / box.sqrLenU0; + return box; +} + +template +bool MinimumAreaBox2::ComputeAngles( + std::vector> const& vertices, Box const& box, + std::array, 4>& A, int& numA) const +{ + int const numVertices = static_cast(vertices.size()); + numA = 0; + for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++) + { + if (box.index[k0] != box.index[k1]) + { + // The box edges are ordered in k1 as U[0], U[1], -U[0], -U[1]. + Vector2 D = + ((k0 & 2) ? -box.U[k0 & 1] : box.U[k0 & 1]); + int j0 = box.index[k0], j1 = j0 + 1; + if (j1 == numVertices) + { + j1 = 0; + } + Vector2 E = vertices[j1] - vertices[j0]; + ComputeType dp = DotPerp(D, E); + ComputeType esqrlen = Dot(E, E); + ComputeType sinThetaSqr = (dp * dp) / esqrlen; + A[numA++] = std::make_pair(sinThetaSqr, k0); + } + } + return numA > 0; +} + +template +std::array MinimumAreaBox2::SortAngles( + std::array, 4> const& A, int numA) const +{ + std::array sort = {{ 0, 1, 2, 3 }}; + if (numA > 1) + { + if (numA == 2) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + } + else if (numA == 3) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + else // numA == 4 + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[2]].first > A[sort[3]].first) + { + std::swap(sort[2], sort[3]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[3]].first) + { + std::swap(sort[1], sort[3]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + } + return sort; +} + +template +bool MinimumAreaBox2::UpdateSupport( + std::array, 4> const& A, int numA, + std::array const& sort, + std::vector> const& vertices, + std::vector& visited, Box& box) +{ + // Replace the support vertices of those edges attaining minimum angle + // with the other endpoints of the edges. + int const numVertices = static_cast(vertices.size()); + auto const& amin = A[sort[0]]; + for (int k = 0; k < numA; ++k) + { + auto const& a = A[sort[k]]; + if (a.first == amin.first) + { + if (++box.index[a.second] == numVertices) + { + box.index[a.second] = 0; + } + } + else + { + break; + } + } + + int bottom = box.index[amin.second]; + if (visited[bottom]) + { + // We have already processed this polygon edge. + return false; + } + visited[bottom] = true; + + // Cycle the vertices so that the bottom support occurs first. + std::array nextIndex; + for (int k = 0; k < 4; ++k) + { + nextIndex[k] = box.index[(amin.second + k) % 4]; + } + box.index = nextIndex; + + // Compute the box axis directions. + int j1 = box.index[0], j0 = j1 - 1; + if (j0 < 0) + { + j0 = numVertices - 1; + } + box.U[0] = vertices[j1] - vertices[j0]; + box.U[1] = -Perp(box.U[0]); + box.sqrLenU0 = Dot(box.U[0], box.U[0]); + + // Compute the box area. + Vector2 diff[2] = + { + vertices[box.index[1]] - vertices[box.index[3]], + vertices[box.index[2]] - vertices[box.index[0]] + }; + box.area = Dot(box.U[0], diff[0]) * Dot(box.U[1], diff[1]) / box.sqrLenU0; + return true; +} + +template +void MinimumAreaBox2::ConvertTo(Box const& minBox, + std::vector> const& computePoints, + OrientedBox2& itMinBox) +{ + // The sum, difference, and center are all computed exactly. + Vector2 sum[2] = + { + computePoints[minBox.index[1]] + computePoints[minBox.index[3]], + computePoints[minBox.index[2]] + computePoints[minBox.index[0]] + }; + + Vector2 difference[2] = + { + computePoints[minBox.index[1]] - computePoints[minBox.index[3]], + computePoints[minBox.index[2]] - computePoints[minBox.index[0]] + }; + + Vector2 center = mHalf * ( + Dot(minBox.U[0], sum[0]) * minBox.U[0] + + Dot(minBox.U[1], sum[1]) * minBox.U[1]) / minBox.sqrLenU0; + + // Calculate the squared extent using ComputeType to avoid loss of + // precision before computing a squared root. + Vector2 sqrExtent; + for (int i = 0; i < 2; ++i) + { + sqrExtent[i] = mHalf * Dot(minBox.U[i], difference[i]); + sqrExtent[i] *= sqrExtent[i]; + sqrExtent[i] /= minBox.sqrLenU0; + } + + for (int i = 0; i < 2; ++i) + { + itMinBox.center[i] = (InputType)center[i]; + itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]); + + // Before converting to floating-point, factor out the maximum + // component using ComputeType to generate rational numbers in a + // range that avoids loss of precision during the conversion and + // normalization. + Vector2 const& axis = minBox.U[i]; + ComputeType cmax = std::max(std::abs(axis[0]), std::abs(axis[1])); + ComputeType invCMax = mOne / cmax; + for (int j = 0; j < 2; ++j) + { + itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax); + } + Normalize(itMinBox.axis[i]); + } + + mSupportIndices = minBox.index; + mArea = (InputType)minBox.area; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaCircle2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaCircle2.h new file mode 100644 index 000000000000..1df9fdf190f2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumAreaCircle2.h @@ -0,0 +1,445 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/28) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Compute the minimum area circle containing the input set of points. The +// algorithm randomly permutes the input points so that the construction +// occurs in 'expected' O(N) time. All internal minimal circle calculations +// store the squared radius in the radius member of Circle2. Only at the +// end is a sqrt computed. +// +// The most robust choice for ComputeType is BSRational for exact rational +// arithmetic. As long as this code is a correct implementation of the theory +// (which I hope it is), you will obtain the minimum-area circle containing +// the points. +// +// Instead, if you choose ComputeType to be float or double, floating-point +// rounding errors can cause the UpdateSupport{2,3} functions to fail. +// The failure is trapped in those functions and a simple bounding circle is +// computed using GetContainer in file GteContCircle2.h. This circle is +// generally not the minimum-area circle containing the points. The +// minimum-area algorithm is terminated immediately. The circle is returned +// as well as a bool value of 'true' when the circle is minimum area or +// 'false' when the failure is trapped. When 'false' is returned, you can +// try another call to the operator()(...) function. The random shuffle +// that occurs is highly likely to be different from the previous shuffle, +// and there is a chance that the algorithm can succeed just because of the +// different ordering of points. + +namespace gte +{ + template + class MinimumAreaCircle2 + { + public: + bool operator()(int numPoints, Vector2 const* points, Circle2& minimal) + { + if (numPoints >= 1 && points) + { + // Function array to avoid switch statement in the main loop. + std::function update[4]; + update[1] = [this](int i) { return UpdateSupport1(i); }; + update[2] = [this](int i) { return UpdateSupport2(i); }; + update[3] = [this](int i) { return UpdateSupport3(i); }; + + // Process only the unique points. + std::vector permuted(numPoints); + for (int i = 0; i < numPoints; ++i) + { + permuted[i] = i; + } + std::sort(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] < points[i1]; }); + auto end = std::unique(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] == points[i1]; }); + permuted.erase(end, permuted.end()); + numPoints = static_cast(permuted.size()); + + // Create a random permutation of the points. + std::shuffle(permuted.begin(), permuted.end(), mDRE); + + // Convert to the compute type, which is a simple copy when + // ComputeType is the same as InputType. + mComputePoints.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + for (int j = 0; j < 2; ++j) + { + mComputePoints[i][j] = points[permuted[i]][j]; + } + } + + // Start with the first point. + Circle2 ctMinimal = ExactCircle1(0); + mNumSupport = 1; + mSupport[0] = 0; + + // The loop restarts from the beginning of the point list each + // time the circle needs updating. Linus Kallberg (Computer + // Science at Malardalen University in Sweden) discovered that + // performance is better when the remaining points in the + // array are processed before restarting. The points + // processed before the point that caused the update are + // likely to be enclosed by the new circle (or near the circle + // boundary) because they were enclosed by the previous + // circle. The chances are better that points after the + // current one will cause growth of the bounding circle. + for (int i = 1 % numPoints, n = 0; i != n; i = (i + 1) % numPoints) + { + if (!SupportContains(i)) + { + if (!Contains(i, ctMinimal)) + { + auto result = update[mNumSupport](i); + if (result.second == true) + { + if (result.first.radius > ctMinimal.radius) + { + ctMinimal = result.first; + n = i; + } + } + else + { + // This case can happen when ComputeType is + // float or double. See the comments at the + // beginning of this file. + LogWarning("ComputeType is not exact and failure occurred. Returning non-minimal circle."); + GetContainer(numPoints, points, minimal); + mNumSupport = 0; + mSupport.fill(0); + return false; + } + } + } + } + + for (int j = 0; j < 2; ++j) + { + minimal.center[j] = static_cast(ctMinimal.center[j]); + } + minimal.radius = static_cast(ctMinimal.radius); + minimal.radius = std::sqrt(minimal.radius); + + for (int i = 0; i < mNumSupport; ++i) + { + mSupport[i] = permuted[mSupport[i]]; + } + return true; + } + else + { + LogError("Input must contain points."); + minimal.center = Vector2::Zero(); + minimal.radius = std::numeric_limits::max(); + return false; + } + } + + // Member access. + inline int GetNumSupport() const + { + return mNumSupport; + } + + inline std::array const& GetSupport() const + { + return mSupport; + } + + private: + // Test whether point P is inside circle C using squared distance and + // squared radius. + bool Contains(int i, Circle2 const& circle) const + { + // NOTE: In this algorithm, circle.radius is the *squared radius* + // until the function returns at which time a square root is + // applied. + Vector2 diff = mComputePoints[i] - circle.center; + return Dot(diff, diff) <= circle.radius; + } + + Circle2 ExactCircle1(int i0) const + { + Circle2 minimal; + minimal.center = mComputePoints[i0]; + minimal.radius = (ComputeType)0; + return minimal; + } + + Circle2 ExactCircle2(int i0, int i1) const + { + Vector2 const& P0 = mComputePoints[i0]; + Vector2 const& P1 = mComputePoints[i1]; + Vector2 diff = P1 - P0; + Circle2 minimal; + minimal.center = ((ComputeType)0.5)*(P0 + P1); + minimal.radius = ((ComputeType)0.25)*Dot(diff, diff); + return minimal; + } + + Circle2 ExactCircle3(int i0, int i1, int i2) const + { + // Compute the 2D circle containing P0, P1, and P2. The center in + // barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2, where + // x0 + x1 + x2 = 1. The center is equidistant from the three + // points, so |C - P0| = |C - P1| = |C - P2| = R, where R is the + // radius of the circle. From these conditions, + // C - P0 = x0*E0 + x1*E1 - E0 + // C - P1 = x0*E0 + x1*E1 - E1 + // C - P2 = x0*E0 + x1*E1 + // where E0 = P0 - P2 and E1 = P1 - P2, which leads to + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E0, x0*E0 + x1*E1) + |E0|^2 + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E1, x0*E0 + x1*E1) + |E1|^2 + // r^2 = |x0*E0 + x1*E1|^2 + // Subtracting the last equation from the first two and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) || x1 | | Dot(E1,E1) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0 and x1 and then + // evaluates the third equation in r^2 to obtain r. + + Vector2 const& P0 = mComputePoints[i0]; + Vector2 const& P1 = mComputePoints[i1]; + Vector2 const& P2 = mComputePoints[i2]; + + Vector2 E0 = P0 - P2; + Vector2 E1 = P1 - P2; + + Matrix2x2 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + + ComputeType const half = (ComputeType)0.5; + Vector2 B{ half * A(0, 0), half* A(1, 1) }; + + Circle2 minimal; + Vector2 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x2 = (ComputeType)1 - X[0] - X[1]; + minimal.center = X[0] * P0 + X[1] * P1 + x2 * P2; + Vector2 tmp = X[0] * E0 + X[1] * E1; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector2::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + + return minimal; + } + + typedef std::pair, bool> UpdateResult; + + UpdateResult UpdateSupport1(int i) + { + Circle2 minimal = ExactCircle2(mSupport[0], i); + mNumSupport = 2; + mSupport[1] = i; + return std::make_pair(minimal, true); + } + + UpdateResult UpdateSupport2(int i) + { + // Permutations of type 2, used for calling ExactCircle2(...). + int const numType2 = 2; + int const type2[numType2][2] = + { + { 0, /*2*/ 1 }, + { 1, /*2*/ 0 } + }; + + // Permutations of type 3, used for calling ExactCircle3(...). + int const numType3 = 1; // {0, 1, 2} + + Circle2 circle[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iCircle = 0, iMinRSqr = -1; + int k0, k1; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iCircle) + { + k0 = mSupport[type2[j][0]]; + circle[iCircle] = ExactCircle2(k0, i); + if (circle[iCircle].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + if (Contains(k1, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + // Permutations of type 3. + k0 = mSupport[0]; + k1 = mSupport[1]; + circle[iCircle] = ExactCircle3(k0, k1, i); + if (circle[iCircle].radius < minRSqr) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + + switch (iMinRSqr) + { + case 0: + mSupport[1] = i; + break; + case 1: + mSupport[0] = i; + break; + case 2: + mNumSupport = 3; + mSupport[2] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding circle for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Circle2(), false); + } + + return std::make_pair(circle[iMinRSqr], true); + } + + UpdateResult UpdateSupport3(int i) + { + // Permutations of type 2, used for calling ExactCircle2(...). + int const numType2 = 3; + int const type2[numType2][3] = + { + { 0, /*3*/ 1, 2 }, + { 1, /*3*/ 0, 2 }, + { 2, /*3*/ 0, 1 } + }; + + // Permutations of type 2, used for calling ExactCircle3(...). + int const numType3 = 3; + int const type3[numType3][3] = + { + { 0, 1, /*3*/ 2 }, + { 0, 2, /*3*/ 1 }, + { 1, 2, /*3*/ 0 } + }; + + Circle2 circle[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iCircle = 0, iMinRSqr = -1; + int k0, k1, k2; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iCircle) + { + k0 = mSupport[type2[j][0]]; + circle[iCircle] = ExactCircle2(k0, i); + if (circle[iCircle].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + if (Contains(k1, circle[iCircle]) && Contains(k2, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iCircle) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + circle[iCircle] = ExactCircle3(k0, k1, i); + if (circle[iCircle].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + if (Contains(k2, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mSupport[2] = i; + break; + case 4: + mSupport[1] = i; + break; + case 5: + mSupport[0] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding circle for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Circle2(), false); + } + + return std::make_pair(circle[iMinRSqr], true); + } + + // Indices of points that support the current minimum area circle. + bool SupportContains(int j) const + { + for (int i = 0; i < mNumSupport; ++i) + { + if (j == mSupport[i]) + { + return true; + } + } + return false; + } + + int mNumSupport; + std::array mSupport; + + // Random permutation of the unique input points to produce expected + // linear time for the algorithm. + std::default_random_engine mDRE; + std::vector> mComputePoints; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeBox3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeBox3.h new file mode 100644 index 000000000000..a217f28df8f9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeBox3.h @@ -0,0 +1,1252 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Compute a minimum-volume oriented box containing the specified points. The +// algorithm is really about computing the minimum-volume box containing the +// convex hull of the points, so we must compute the convex hull or you must +// pass an already built hull to the code. +// +// The minimum-volume oriented box has a face coincident with a hull face +// or has three mutually orthogonal edges coincident with three hull edges +// that (of course) are mutually orthogonal. +// J.O'Rourke, "Finding minimal enclosing boxes", +// Internat. J. Comput. Inform. Sci., 14:183-199, 1985. +// +// A detailed description of the algorithm and implementation is found in +// the documents +// http://www.geometrictools.com/Documentation/MinimumVolumeBox.pdf +// http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf +// +// NOTE: This algorithm guarantees a correct output only when ComputeType is +// an exact arithmetic type that supports division. In GTEngine, one such +// type is BSRational (arbitrary precision). Another such type +// is BSRational> (fixed precision), where N is chosen large +// enough for your input data sets. If you choose ComputeType to be 'float' +// or 'double', the output is not guaranteed to be correct. +// +// See GeometricTools/GTEngine/Samples/Geometrics/MinimumVolumeBox3 for an +// example of how to use the code. + +namespace gte +{ + +template +class MinimumVolumeBox3 +{ +public: + // The class is a functor to support computing the minimum-volume box of + // multiple data sets using the same class object. For multithreading + // in ProcessFaces, choose 'numThreads' subject to the constraints + // 1 <= numThreads <= std::thread::hardware_concurrency() + // To execute ProcessEdges in a thread separate from the main thrad, + // choose 'threadProcessEdges' to 'true'. + MinimumVolumeBox3(unsigned int numThreads = 1, bool threadProcessEdges = false); + + // The points are arbitrary, so we must compute the convex hull from + // them in order to compute the minimum-area box. The input parameters + // are necessary for using ConvexHull3. + OrientedBox3 operator()(int numPoints, Vector3 const* points, + bool useRotatingCalipers = !std::is_floating_point::value); + + // The points form a nondegenerate convex polyhedron. The indices input + // must be nonnull and specify the triangle faces. + OrientedBox3 operator()(int numPoints, Vector3 const* points, + int numIndices, int const* indices, + bool useRotatingCalipers = !std::is_floating_point::value); + + // Member access. + inline int GetNumPoints() const; + inline Vector3 const* GetPoints() const; + inline std::vector const& GetHull() const; + inline InputType GetVolume() const; + +private: + struct Box + { + Vector3 P, U[3]; + ComputeType sqrLenU[3], range[3][2], volume; + }; + + struct ExtrudeRectangle + { + Vector3 U[2]; + std::array index; + ComputeType sqrLenU[2], area; + }; + + // Compute the minimum-volume box relative to each hull face. + void ProcessFaces(ETManifoldMesh const& mesh, Box& minBox); + + // Compute the minimum-volume box for each triple of orthgonal hull edges. + void ProcessEdges(ETManifoldMesh const& mesh, Box& minBox); + + // Compute the minimum-volume box relative to a single hull face. + typedef ETManifoldMesh::Triangle Triangle; + void ProcessFace(std::shared_ptr const& supportTri, + std::vector> const& normal, + std::map, int> const& triNormalMap, + ETManifoldMesh::EMap const& emap, Box& localMinBox); + + // The rotating calipers algorithm has a loop invariant that requires + // the convex polygon not to have collinear points. Any such points + // must be removed first. The code is also executed for the O(n^2) + // algorithm to reduce the number of process edges. + void RemoveCollinearPoints(Vector3 const& N, std::vector& polyline); + + // This is the slow order O(n^2) search. + void ComputeBoxForFaceOrderNSqr(Vector3 const& N, std::vector const& polyline, Box& box); + + // This is the rotating calipers version, which is O(n). + void ComputeBoxForFaceOrderN(Vector3 const& N, std::vector const& polyline, Box& box); + + // Compute the smallest rectangle for the polyline edge . + ExtrudeRectangle SmallestRectangle(int i0, int i1, Vector3 const& N, std::vector const& polyline); + + // Compute (sin(angle))^2 for the polyline edges emanating from the + // support vertices of the rectangle. The return value is 'true' if at + // least one angle is in [0,pi/2); otherwise, the return value is 'false' + // and the original polyline must project to a rectangle. + bool ComputeAngles(Vector3 const& N, + std::vector const& polyline, ExtrudeRectangle const& rct, + std::array, 4>& A, int& numA) const; + + // Sort the angles indirectly. The sorted indices are returned. This + // avoids swapping elements of A[], which can be expensive when + // ComputeType is an exact rational type. + std::array SortAngles(std::array, 4> const& A, int numA) const; + + bool UpdateSupport(std::array, 4> const& A, int numA, + std::array const& sort, Vector3 const& N, std::vector const& polyline, + std::vector& visited, ExtrudeRectangle& rct); + + // Convert the extruded box to the minimum-volume box of InputType. When + // the ComputeType is an exact rational type, the conversions are + // performed to avoid precision loss until necessary at the last step. + void ConvertTo(Box const& minBox, OrientedBox3& itMinBox); + + // The code is multithreaded, both for convex hull computation and + // computing minimum-volume extruded boxes for the hull faces. The + // default value is 1, which implies a single-threaded computation (on + // the main thread). + unsigned int mNumThreads; + bool mThreadProcessEdges; + + // The input points to be bound. + int mNumPoints; + Vector3 const* mPoints; + + // The ComputeType conversions of the input points. Only points of the + // convex hull (vertices of a convex polyhedron) are converted for + // performance when ComputeType is rational. + Vector3 const* mComputePoints; + + // The indices into mPoints/mComputePoints for the convex hull vertices. + std::vector mHull; + + // The unique indices in mHull. This set allows us to compute only for + // the hull vertices and avoids redundant computations if the indices + // were to have repeated indices into mPoints/mComputePoints. This is + // a performance improvement for rational ComputeType. + std::set mUniqueIndices; + + // The caller can specify whether to use rotating calipers or the slower + // all-edge processing for computing an extruded bounding box. + bool mUseRotatingCalipers; + + // The volume of the minimum-volume box. The ComputeType value is exact, + // so the only rounding errors occur in the conversion from ComputeType + // to InputType (default rounding mode is round-to-nearest-ties-to-even). + InputType mVolume; + + // Convenient values that occur regularly in the code. When using + // rational ComputeType, we construct these numbers only once. + ComputeType mZero, mOne, mNegOne, mHalf; +}; + + +template +MinimumVolumeBox3::MinimumVolumeBox3(unsigned int numThreads, bool threadProcessEdges) + : + mNumThreads(numThreads), + mThreadProcessEdges(threadProcessEdges), + mNumPoints(0), + mPoints(nullptr), + mComputePoints(nullptr), + mUseRotatingCalipers(true), + mVolume((InputType)0), + mZero(0), + mOne(1), + mNegOne(-1), + mHalf((InputType)0.5) +{ +} + +template +OrientedBox3 MinimumVolumeBox3::operator()( + int numPoints, Vector3 const* points, bool useRotatingCalipers) +{ + mNumPoints = numPoints; + mPoints = points; + mUseRotatingCalipers = useRotatingCalipers; + mHull.clear(); + mUniqueIndices.clear(); + + // Get the convex hull of the points. + ConvexHull3 ch3; + ch3(mNumPoints, mPoints, (InputType)0); + int dimension = ch3.GetDimension(); + + OrientedBox3 itMinBox; + + if (dimension == 0) + { + // The points are all effectively the same (using fuzzy epsilon). + itMinBox.center = mPoints[0]; + itMinBox.axis[0] = Vector3::Unit(0); + itMinBox.axis[1] = Vector3::Unit(1); + itMinBox.axis[2] = Vector3::Unit(2); + itMinBox.extent[0] = (InputType)0; + itMinBox.extent[1] = (InputType)0; + itMinBox.extent[2] = (InputType)0; + mHull.resize(1); + mHull[0] = 0; + return itMinBox; + } + + if (dimension == 1) + { + // The points effectively lie on a line (using fuzzy epsilon). + // Determine the extreme t-values for the points represented as + // P = origin + t*direction. We know that 'origin' is an input + // vertex, so we can start both t-extremes at zero. + Line3 const& line = ch3.GetLine(); + InputType tmin = (InputType)0, tmax = (InputType)0; + int imin = 0, imax = 0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector3 diff = mPoints[i] - line.origin; + InputType t = Dot(diff, line.direction); + if (t > tmax) + { + tmax = t; + imax = i; + } + else if (t < tmin) + { + tmin = t; + imin = i; + } + } + + itMinBox.center = line.origin + ((InputType)0.5)*(tmin + tmax) * line.direction; + itMinBox.extent[0] = ((InputType)0.5)*(tmax - tmin); + itMinBox.extent[1] = (InputType)0; + itMinBox.extent[2] = (InputType)0; + itMinBox.axis[0] = line.direction; + ComputeOrthogonalComplement(1, &itMinBox.axis[0]); + mHull.resize(2); + mHull[0] = imin; + mHull[1] = imax; + return itMinBox; + } + + if (dimension == 2) + { + // The points effectively line on a plane (using fuzzy epsilon). + // Project the points onto the plane and compute the minimum-area + // bounding box of them. + Plane3 const& plane = ch3.GetPlane(); + + // Get a coordinate system relative to the plane of the points. + // Choose the origin to be any of the input points. + Vector3 origin = mPoints[0]; + Vector3 basis[3]; + basis[0] = plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the input points onto the plane. + std::vector> projection(mNumPoints); + for (int i = 0; i < mNumPoints; ++i) + { + Vector3 diff = mPoints[i] - origin; + projection[i][0] = Dot(basis[1], diff); + projection[i][1] = Dot(basis[2], diff); + } + + // Compute the minimum area box in 2D. + MinimumAreaBox2 mab2; + OrientedBox2 rectangle = mab2(mNumPoints, &projection[0]); + + // Lift the values into 3D. + itMinBox.center = origin + rectangle.center[0] * basis[1] + rectangle.center[1] * basis[2]; + itMinBox.axis[0] = rectangle.axis[0][0] * basis[1] + rectangle.axis[0][1] * basis[2]; + itMinBox.axis[1] = rectangle.axis[1][0] * basis[1] + rectangle.axis[1][1] * basis[2]; + itMinBox.axis[2] = basis[0]; + itMinBox.extent[0] = rectangle.extent[0]; + itMinBox.extent[1] = rectangle.extent[1]; + itMinBox.extent[2] = (InputType)0; + mHull = mab2.GetHull(); + return itMinBox; + } + + // Get the set of unique indices of the hull. This is used to project + // hull vertices onto lines. + ETManifoldMesh const& mesh = ch3.GetHullMesh(); + mHull.resize(3 * mesh.GetTriangles().size()); + int h = 0; + for (auto const& element : mesh.GetTriangles()) + { + for (int i = 0; i < 3; ++i, ++h) + { + int index = element.first.V[i]; + mHull[h] = index; + mUniqueIndices.insert(index); + } + } + + mComputePoints = ch3.GetQuery().GetVertices(); + + Box minBox, minBoxEdges; + minBox.volume = mNegOne; + minBoxEdges.volume = mNegOne; + + if (mThreadProcessEdges) + { + std::thread doEdges([this, &mesh, &minBoxEdges]() + { + ProcessEdges(mesh, minBoxEdges); + }); + ProcessFaces(mesh, minBox); + doEdges.join(); + } + else + { + ProcessEdges(mesh, minBoxEdges); + ProcessFaces(mesh, minBox); + } + + if (minBoxEdges.volume != mNegOne + && minBoxEdges.volume < minBox.volume) + { + minBox = minBoxEdges; + } + + ConvertTo(minBox, itMinBox); + mComputePoints = nullptr; + return itMinBox; +} + +template +OrientedBox3 MinimumVolumeBox3::operator()( + int numPoints, Vector3 const* points, int numIndices, + int const* indices, bool useRotatingCalipers) +{ + mNumPoints = numPoints; + mPoints = points; + mUseRotatingCalipers = useRotatingCalipers; + mUniqueIndices.clear(); + + // Build the mesh from the indices. The box construction uses the edge + // map of the mesh. + ETManifoldMesh mesh; + int numTriangles = numIndices / 3; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + mesh.Insert(v0, v1, v2); + } + + // Get the set of unique indices of the hull. This is used to project + // hull vertices onto lines. + mHull.resize(3 * mesh.GetTriangles().size()); + int h = 0; + for (auto const& element : mesh.GetTriangles()) + { + for (int i = 0; i < 3; ++i, ++h) + { + int index = element.first.V[i]; + mHull[h] = index; + mUniqueIndices.insert(index); + } + } + + // Create the ComputeType points to be used downstream. + std::vector> computePoints(mNumPoints); + for (auto i : mUniqueIndices) + { + for (int j = 0; j < 3; ++j) + { + computePoints[i][j] = (ComputeType)mPoints[i][j]; + } + } + + OrientedBox3 itMinBox; + mComputePoints = &computePoints[0]; + + Box minBox, minBoxEdges; + minBox.volume = mNegOne; + minBoxEdges.volume = mNegOne; + + if (mThreadProcessEdges) + { + std::thread doEdges([this, &mesh, &minBoxEdges]() + { + ProcessEdges(mesh, minBoxEdges); + }); + ProcessFaces(mesh, minBox); + doEdges.join(); + } + else + { + ProcessEdges(mesh, minBoxEdges); + ProcessFaces(mesh, minBox); + } + + if (minBoxEdges.volume != mNegOne && minBoxEdges.volume < minBox.volume) + { + minBox = minBoxEdges; + } + + ConvertTo(minBox, itMinBox); + mComputePoints = nullptr; + return itMinBox; +} + +template inline +int MinimumVolumeBox3::GetNumPoints() const +{ + return mNumPoints; +} + +template inline +Vector3 const* +MinimumVolumeBox3::GetPoints() const +{ + return mPoints; +} + +template inline +std::vector const& MinimumVolumeBox3::GetHull() + const +{ + return mHull; +} + +template inline +InputType MinimumVolumeBox3::GetVolume() const +{ + return mVolume; +} + +template +void MinimumVolumeBox3::ProcessFaces(ETManifoldMesh const& mesh, Box& minBox) +{ + // Get the mesh data structures. + auto const& tmap = mesh.GetTriangles(); + auto const& emap = mesh.GetEdges(); + + // Compute inner-pointing face normals for searching boxes supported by + // a face and an extreme vertex. The indirection in triNormalMap, using + // an integer index instead of the normal/sqrlength pair itself, avoids + // expensive copies when using exact arithmetic. + std::vector> normal(tmap.size()); + std::map, int> triNormalMap; + int index = 0; + for (auto const& element : tmap) + { + auto tri = element.second; + Vector3 const& v0 = mComputePoints[tri->V[0]]; + Vector3 const& v1 = mComputePoints[tri->V[1]]; + Vector3 const& v2 = mComputePoints[tri->V[2]]; + Vector3 edge1 = v1 - v0; + Vector3 edge2 = v2 - v0; + normal[index] = Cross(edge2, edge1); // inner-pointing normal + triNormalMap[tri] = index++; + } + + // Process the triangle faces. For each face, compute the polyline of + // edges that supports the bounding box with a face coincident to the + // triangle face. The projection of the polyline onto the plane of the + // triangle face is a convex polygon, so we can use the method of rotating + // calipers to compute its minimum-area box efficiently. + unsigned int numFaces = static_cast(tmap.size()); + if (mNumThreads > 1 && numFaces >= mNumThreads) + { + // Repackage the triangle pointers to support the partitioning of + // faces for multithreaded face processing. + std::vector> triangles; + triangles.reserve(numFaces); + for (auto const& element : tmap) + { + triangles.push_back(element.second); + } + + // Partition the data for multiple threads. + unsigned int numFacesPerThread = numFaces / mNumThreads; + std::vector imin(mNumThreads), imax(mNumThreads); + std::vector localMinBox(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + imin[t] = t * numFacesPerThread; + imax[t] = imin[t] + numFacesPerThread - 1; + localMinBox[t].volume = mNegOne; + } + imax[mNumThreads - 1] = numFaces - 1; + + // Execute the face processing in multiple threads. + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, t, &imin, &imax, &triangles, + &normal, &triNormalMap, &emap, &localMinBox]() + { + for (unsigned int i = imin[t]; i <= imax[t]; ++i) + { + auto const& supportTri = triangles[i]; + ProcessFace(supportTri, normal, triNormalMap, emap, localMinBox[t]); + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + + // Update the minimum-volume box candidate. + if (minBox.volume == mNegOne || localMinBox[t].volume < minBox.volume) + { + minBox = localMinBox[t]; + } + } + } + else + { + for (auto const& element : tmap) + { + auto const& supportTri = element.second; + ProcessFace(supportTri, normal, triNormalMap, emap, minBox); + } + } +} + +template +void MinimumVolumeBox3::ProcessEdges(ETManifoldMesh const& mesh, Box& minBox) +{ + // The minimum-volume box can also be supported by three mutually + // orthogonal edges of the convex hull. For each triple of orthogonal + // edges, compute the minimum-volume box for that coordinate frame by + // projecting the points onto the axes of the frame. Use a hull vertex + // as the origin. + int index = mesh.GetTriangles().begin()->first.V[0]; + Vector3 const& origin = mComputePoints[index]; + Vector3 U[3]; + std::array sqrLenU; + + auto const& emap = mesh.GetEdges(); + auto e2 = emap.begin(), end = emap.end(); + for (/**/; e2 != end; ++e2) + { + U[2] = mComputePoints[e2->first.V[1]] - mComputePoints[e2->first.V[0]]; + auto e1 = e2; + for (++e1; e1 != end; ++e1) + { + U[1] = mComputePoints[e1->first.V[1]] - mComputePoints[e1->first.V[0]]; + if (Dot(U[1], U[2]) != mZero) + { + continue; + } + sqrLenU[1] = Dot(U[1], U[1]); + + auto e0 = e1; + for (++e0; e0 != end; ++e0) + { + U[0] = mComputePoints[e0->first.V[1]] - mComputePoints[e0->first.V[0]]; + sqrLenU[0] = Dot(U[0], U[0]); + if (Dot(U[0], U[1]) != mZero || Dot(U[0], U[2]) != mZero) + { + continue; + } + + // The three edges are mutually orthogonal. To support exact + // rational arithmetic for volume computation, we replace U[2] + // by a parallel vector. + U[2] = Cross(U[0], U[1]); + sqrLenU[2] = sqrLenU[0] * sqrLenU[1]; + + // Project the vertices onto the lines containing the edges. + // Use vertex 0 as the origin. + std::array umin, umax; + for (int j = 0; j < 3; ++j) + { + umin[j] = mZero; + umax[j] = mZero; + } + + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - origin; + for (int j = 0; j < 3; ++j) + { + ComputeType dot = Dot(diff, U[j]); + if (dot < umin[j]) + { + umin[j] = dot; + } + else if (dot > umax[j]) + { + umax[j] = dot; + } + } + } + + ComputeType volume = (umax[0] - umin[0]) / sqrLenU[0]; + volume *= (umax[1] - umin[1]) / sqrLenU[1]; + volume *= (umax[2] - umin[2]); + + // Update current minimum-volume box (if necessary). + if (minBox.volume == mOne || volume < minBox.volume) + { + // The edge keys have unordered vertices, so it is + // possible that {U[0],U[1],U[2]} is a left-handed set. + // We need a right-handed set. + if (DotCross(U[0], U[1], U[2]) < mZero) + { + U[2] = -U[2]; + } + + minBox.P = origin; + for (int j = 0; j < 3; ++j) + { + minBox.U[j] = U[j]; + minBox.sqrLenU[j] = sqrLenU[j]; + for (int k = 0; k < 3; ++k) + { + minBox.range[k][0] = umin[k]; + minBox.range[k][1] = umax[k]; + } + } + minBox.volume = volume; + + } + } + } + } +} + +template +void MinimumVolumeBox3::ProcessFace(std::shared_ptr const& supportTri, + std::vector> const& normal, std::map, int> const& triNormalMap, + ETManifoldMesh::EMap const& emap, Box& localMinBox) +{ + // Get the supporting triangle information. + Vector3 const& supportNormal = normal[triNormalMap.find(supportTri)->second]; + + // Build the polyline of supporting edges. The pair (v,polyline[v]) + // represents an edge directed appropriately (see next set of + // comments). + std::vector polyline(mNumPoints); + int polylineStart = -1; + for (auto const& edgeElement : emap) + { + auto const& edge = *edgeElement.second; + auto const& tri0 = edge.T[0].lock(); + auto const& tri1 = edge.T[1].lock(); + auto const& normal0 = normal[triNormalMap.find(tri0)->second]; + auto const& normal1 = normal[triNormalMap.find(tri1)->second]; + ComputeType dot0 = Dot(supportNormal, normal0); + ComputeType dot1 = Dot(supportNormal, normal1); + + std::shared_ptr tri; + if (dot0 < mZero && dot1 >= mZero) + { + tri = tri0; + } + else if (dot1 < mZero && dot0 >= mZero) + { + tri = tri1; + } + + if (tri) + { + // The edge supports the bounding box. Insert the edge in the + // list using clockwise order relative to tri. This will lead + // to a polyline whose projection onto the plane of the hull + // face is a convex polygon that is counterclockwise oriented. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + if (tri->V[j1] == edge.V[0]) + { + if (tri->V[j0] == edge.V[1]) + { + polyline[edge.V[1]] = edge.V[0]; + } + else + { + polyline[edge.V[0]] = edge.V[1]; + } + polylineStart = edge.V[0]; + break; + } + } + } + } + + // Rearrange the edges to form a closed polyline. For M vertices, each + // ComputeBoxFor*() function starts with the edge from closedPolyline[M-1] + // to closedPolyline[0]. + std::vector closedPolyline(mNumPoints); + int numClosedPolyline = 0; + int v = polylineStart; + for (auto& cp : closedPolyline) + { + cp = v; + ++numClosedPolyline; + v = polyline[v]; + if (v == polylineStart) + { + break; + } + } + closedPolyline.resize(numClosedPolyline); + + // This avoids redundant face testing in the O(n^2) or O(n) algorithms + // and it simplifies the O(n) implementation. + RemoveCollinearPoints(supportNormal, closedPolyline); + + // Compute the box coincident to the hull triangle that has minimum + // area on the face coincident with the triangle. + Box faceBox; + if (mUseRotatingCalipers) + { + ComputeBoxForFaceOrderN(supportNormal, closedPolyline, faceBox); + } + else + { + ComputeBoxForFaceOrderNSqr(supportNormal, closedPolyline, faceBox); + } + + // Update the minimum-volume box candidate. + if (localMinBox.volume == mNegOne || faceBox.volume < localMinBox.volume) + { + localMinBox = faceBox; + } +} + +template +void MinimumVolumeBox3::RemoveCollinearPoints( + Vector3 const& N, std::vector& polyline) +{ + std::vector tmpPolyline = polyline; + + int const numPolyline = static_cast(polyline.size()); + int numNoncollinear = 0; + Vector3 ePrev = + mComputePoints[tmpPolyline[0]] - mComputePoints[tmpPolyline.back()]; + + for (int i0 = 0, i1 = 1; i0 < numPolyline; ++i0) + { + Vector3 eNext = + mComputePoints[tmpPolyline[i1]] - mComputePoints[tmpPolyline[i0]]; + + ComputeType tsp = DotCross(ePrev, eNext, N); + if (tsp != mZero) + { + polyline[numNoncollinear++] = tmpPolyline[i0]; + } + + ePrev = eNext; + if (++i1 == numPolyline) + { + i1 = 0; + } + } + + polyline.resize(numNoncollinear); +} + +template +void MinimumVolumeBox3::ComputeBoxForFaceOrderNSqr( + Vector3 const& N, std::vector const& polyline, + Box& box) +{ + // This code processes the polyline terminator associated with a convex + // hull face of inner-pointing normal N. The polyline is usually not + // contained by a plane, and projecting the polyline to a convex polygon + // in a plane perpendicular to N introduces floating-point rounding + // errors. The minimum-area box for the projected polyline is computed + // indirectly to support exact rational arithmetic. + + box.P = mComputePoints[polyline[0]]; + box.U[2] = N; + box.sqrLenU[2] = Dot(N, N); + box.range[1][0] = mZero; + box.volume = mNegOne; + int const numPolyline = static_cast(polyline.size()); + for (int i0 = numPolyline - 1, i1 = 0; i1 < numPolyline; i0 = i1++) + { + // Create a coordinate system for the plane perpendicular to the face + // normal and containing a polyline vertex. + Vector3 const& P = mComputePoints[polyline[i0]]; + Vector3 E = + mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]]; + + Vector3 U1 = Cross(N, E); + Vector3 U0 = Cross(U1, N); + + // Compute the smallest rectangle containing the projected polyline. + ComputeType min0 = mZero, max0 = mZero, max1 = mZero; + for (int j = 0; j < numPolyline; ++j) + { + Vector3 diff = mComputePoints[polyline[j]] - P; + ComputeType dot = Dot(U0, diff); + if (dot < min0) + { + min0 = dot; + } + else if (dot > max0) + { + max0 = dot; + } + + dot = Dot(U1, diff); + if (dot > max1) + { + max1 = dot; + } + } + + // The true area is Area(rectangle)*Length(N). After the smallest + // scaled-area rectangle is computed and returned, the box.volume is + // updated to be the actual squared volume of the box. + ComputeType sqrLenU1 = Dot(U1, U1); + ComputeType volume = (max0 - min0) * max1 / sqrLenU1; + if (box.volume == mNegOne || volume < box.volume) + { + box.P = P; + box.U[0] = U0; + box.U[1] = U1; + box.sqrLenU[0] = sqrLenU1 * box.sqrLenU[2]; + box.sqrLenU[1] = sqrLenU1; + box.range[0][0] = min0; + box.range[0][1] = max0; + box.range[1][1] = max1; + box.volume = volume; + } + } + + // Compute the range of points in the support-normal direction. + box.range[2][0] = mZero; + box.range[2][1] = mZero; + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - box.P; + ComputeType height = Dot(box.U[2], diff); + if (height < box.range[2][0]) + { + box.range[2][0] = height; + } + else if (height > box.range[2][1]) + { + box.range[2][1] = height; + } + } + + // Compute the actual volume. + box.volume *= (box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]; +} + +template +void MinimumVolumeBox3::ComputeBoxForFaceOrderN( + Vector3 const& N, std::vector const& polyline, Box& box) +{ + // This code processes the polyline terminator associated with a convex + // hull face of inner-pointing normal N. The polyline is usually not + // contained by a plane, and projecting the polyline to a convex polygon + // in a plane perpendicular to N introduces floating-point rounding + // errors. The minimum-area box for the projected polyline is computed + // indirectly to support exact rational arithmetic. + + // When the bounding box corresponding to a polyline edge is computed, + // we mark the edge as visited. If the edge is encountered later, the + // algorithm terminates. + std::vector visited(polyline.size()); + std::fill(visited.begin(), visited.end(), false); + + // Start the minimum-area rectangle search with the edge from the last + // polyline vertex to the first. When updating the extremes, we want the + // bottom-most point on the left edge, the top-most point on the right + // edge, the left-most point on the top edge, and the right-most point + // on the bottom edge. The polygon edges starting at these points are + // then guaranteed not to coincide with a box edge except when an extreme + // point is shared by two box edges (at a corner). + ExtrudeRectangle minRct = SmallestRectangle((int)polyline.size() - 1, 0, + N, polyline); + visited[minRct.index[0]] = true; + + // Execute the rotating calipers algorithm. + ExtrudeRectangle rct = minRct; + for (size_t i = 0; i < polyline.size(); ++i) + { + std::array, 4> A; + int numA; + if (!ComputeAngles(N, polyline, rct, A, numA)) + { + // The polyline projects to a rectangle, so the search is over. + break; + } + + // Indirectly sort the A-array. + std::array sort = SortAngles(A, numA); + + // Update the supporting indices (rct.index[]) and the rectangle axis + // directions (rct.U[]). + if (!UpdateSupport(A, numA, sort, N, polyline, visited, rct)) + { + // We have already processed the rectangle polygon edge, so the + // search is over. + break; + } + + if (rct.area < minRct.area) + { + minRct = rct; + } + } + + // Store relevant box information for computing volume and converting to + // an InputType bounding box. + box.P = mComputePoints[polyline[minRct.index[0]]]; + box.U[0] = minRct.U[0]; + box.U[1] = minRct.U[1]; + box.U[2] = N; + box.sqrLenU[0] = minRct.sqrLenU[0]; + box.sqrLenU[1] = minRct.sqrLenU[1]; + box.sqrLenU[2] = Dot(box.U[2], box.U[2]); + + // Compute the range of points in the plane perpendicular to the support + // normal. + box.range[0][0] = Dot(box.U[0], + mComputePoints[polyline[minRct.index[3]]] - box.P); + box.range[0][1] = Dot(box.U[0], + mComputePoints[polyline[minRct.index[1]]] - box.P); + box.range[1][0] = mZero; + box.range[1][1] = Dot(box.U[1], + mComputePoints[polyline[minRct.index[2]]] - box.P); + + // Compute the range of points in the support-normal direction. + box.range[2][0] = mZero; + box.range[2][1] = mZero; + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - box.P; + ComputeType height = Dot(box.U[2], diff); + if (height < box.range[2][0]) + { + box.range[2][0] = height; + } + else if (height > box.range[2][1]) + { + box.range[2][1] = height; + } + } + + // Compute the actual volume. + box.volume = + (box.range[0][1] - box.range[0][0]) * + ((box.range[1][1] - box.range[1][0]) / box.sqrLenU[1]) * + ((box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]); +} + +template +typename MinimumVolumeBox3::ExtrudeRectangle +MinimumVolumeBox3::SmallestRectangle(int i0, int i1, + Vector3 const& N, std::vector const& polyline) +{ + ExtrudeRectangle rct; + Vector3 E = + mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]]; + rct.U[1] = Cross(N, E); + rct.U[0] = Cross(rct.U[1], N); + rct.index = { i1, i1, i1, i1 }; + rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]); + rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]); + + Vector3 const& origin = mComputePoints[polyline[i1]]; + Vector2 support[4]; + for (int j = 0; j < 4; ++j) + { + support[j] = { mZero, mZero }; + } + + int i = 0; + for (auto p : polyline) + { + Vector3 diff = mComputePoints[p] - origin; + Vector2 v = { Dot(rct.U[0], diff), Dot(rct.U[1], diff) }; + + // The right-most vertex of the bottom edge is vertices[i1]. The + // assumption of no triple of collinear vertices guarantees that + // box.index[0] is i1, which is the initial value assigned at the + // beginning of this function. Therefore, there is no need to test + // for other vertices farther to the right than vertices[i1]. + + if (v[0] > support[1][0] || + (v[0] == support[1][0] && v[1] > support[1][1])) + { + // New right maximum OR same right maximum but closer to top. + rct.index[1] = i; + support[1] = v; + } + + if (v[1] > support[2][1] || + (v[1] == support[2][1] && v[0] < support[2][0])) + { + // New top maximum OR same top maximum but closer to left. + rct.index[2] = i; + support[2] = v; + } + + if (v[0] < support[3][0] || + (v[0] == support[3][0] && v[1] < support[3][1])) + { + // New left minimum OR same left minimum but closer to bottom. + rct.index[3] = i; + support[3] = v; + } + + ++i; + } + + // The comment in the loop has the implication that support[0] = { 0, 0 }, + // so the scaled height (support[2][1] - support[0][1]) is simply + // support[2][1]. + ComputeType scaledWidth = support[1][0] - support[3][0]; + ComputeType scaledHeight = support[2][1]; + rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1]; + return rct; +} + +template +bool MinimumVolumeBox3::ComputeAngles( + Vector3 const& N, std::vector const& polyline, + ExtrudeRectangle const& rct, + std::array, 4>& A, int& numA) const +{ + int const numPolyline = static_cast(polyline.size()); + numA = 0; + for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++) + { + if (rct.index[k0] != rct.index[k1]) + { + // The rct edges are ordered in k1 as U[0], U[1], -U[0], -U[1]. + int lookup = (k0 & 1); + Vector3 D = + ((k0 & 2) ? -rct.U[lookup] : rct.U[lookup]); + int j0 = rct.index[k0], j1 = j0 + 1; + if (j1 == numPolyline) + { + j1 = 0; + } + Vector3 E = + mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]]; + Vector3 Eperp = Cross(N, E); + E = Cross(Eperp, N); + Vector3 DxE = Cross(D, E); + ComputeType csqrlen = Dot(DxE, DxE); + ComputeType dsqrlen = rct.sqrLenU[lookup]; + ComputeType esqrlen = Dot(E, E); + ComputeType sinThetaSqr = csqrlen / (dsqrlen * esqrlen); + A[numA++] = std::make_pair(sinThetaSqr, k0); + } + } + return numA > 0; +} + +template +std::array MinimumVolumeBox3::SortAngles( + std::array, 4> const& A, int numA) const +{ + std::array sort = {{ 0, 1, 2, 3 }}; + if (numA > 1) + { + if (numA == 2) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + } + else if (numA == 3) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + else // numA == 4 + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[2]].first > A[sort[3]].first) + { + std::swap(sort[2], sort[3]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[3]].first) + { + std::swap(sort[1], sort[3]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + } + return sort; +} + +template +bool MinimumVolumeBox3::UpdateSupport( + std::array, 4> const& A, int numA, + std::array const& sort, Vector3 const& N, + std::vector const& polyline, std::vector& visited, + ExtrudeRectangle& rct) +{ + // Replace the support vertices of those edges attaining minimum angle + // with the other endpoints of the edges. + int const numPolyline = static_cast(polyline.size()); + auto const& amin = A[sort[0]]; + for (int k = 0; k < numA; ++k) + { + auto const& a = A[sort[k]]; + if (a.first == amin.first) + { + if (++rct.index[a.second] == numPolyline) + { + rct.index[a.second] = 0; + } + } + else + { + break; + } + } + + int bottom = rct.index[amin.second]; + if (visited[bottom]) + { + // We have already processed this polyline edge. + return false; + } + visited[bottom] = true; + + // Cycle the vertices so that the bottom support occurs first. + std::array nextIndex; + for (int k = 0; k < 4; ++k) + { + nextIndex[k] = rct.index[(amin.second + k) % 4]; + } + rct.index = nextIndex; + + // Compute the rectangle axis directions. + int j1 = rct.index[0], j0 = j1 - 1; + if (j1 < 0) + { + j1 = numPolyline - 1; + } + Vector3 E = + mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]]; + rct.U[1] = Cross(N, E); + rct.U[0] = Cross(rct.U[1], N); + rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]); + rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]); + + // Compute the rectangle area. + Vector3 diff[2] = + { + mComputePoints[polyline[rct.index[1]]] + - mComputePoints[polyline[rct.index[3]]], + mComputePoints[polyline[rct.index[2]]] + - mComputePoints[polyline[rct.index[0]]] + }; + ComputeType scaledWidth = Dot(rct.U[0], diff[0]); + ComputeType scaledHeight = Dot(rct.U[1], diff[1]); + rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1]; + return true; +} + +template +void MinimumVolumeBox3::ConvertTo(Box const& minBox, + OrientedBox3& itMinBox) +{ + Vector3 center = minBox.P; + for (int i = 0; i < 3; ++i) + { + ComputeType average = + mHalf * (minBox.range[i][0] + minBox.range[i][1]); + center += (average / minBox.sqrLenU[i]) * minBox.U[i]; + } + + // Calculate the squared extent using ComputeType to avoid loss of + // precision before computing a squared root. + Vector3 sqrExtent; + for (int i = 0; i < 3; ++i) + { + sqrExtent[i] = mHalf * (minBox.range[i][1] - minBox.range[i][0]); + sqrExtent[i] *= sqrExtent[i]; + sqrExtent[i] /= minBox.sqrLenU[i]; + } + + for (int i = 0; i < 3; ++i) + { + itMinBox.center[i] = (InputType)center[i]; + itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]); + + // Before converting to floating-point, factor out the maximum + // component using ComputeType to generate rational numbers in a + // range that avoids loss of precision during the conversion and + // normalization. + Vector3 const& axis = minBox.U[i]; + ComputeType cmax = std::max(std::abs(axis[0]), std::abs(axis[1])); + cmax = std::max(cmax, std::abs(axis[2])); + ComputeType invCMax = mOne / cmax; + for (int j = 0; j < 3; ++j) + { + itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax); + } + Normalize(itMinBox.axis[i]); + } + + mVolume = (InputType)minBox.volume; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeSphere3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeSphere3.h new file mode 100644 index 000000000000..c6fa29710934 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteMinimumVolumeSphere3.h @@ -0,0 +1,695 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/28) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Compute the minimum volume sphere containing the input set of points. The +// algorithm randomly permutes the input points so that the construction +// occurs in 'expected' O(N) time. All internal minimal sphere calculations +// store the squared radius in the radius member of Sphere3. Only at +// the end is a sqrt computed. +// +// The most robust choice for ComputeType is BSRational for exact rational +// arithmetic. As long as this code is a correct implementation of the theory +// (which I hope it is), you will obtain the minimum-volume sphere +// containing the points. +// +// Instead, if you choose ComputeType to be float or double, floating-point +// rounding errors can cause the UpdateSupport{2,3,4} functions to fail. +// The failure is trapped in those functions and a simple bounding sphere is +// computed using GetContainer in file GteContSphere3.h. This sphere is +// generally not the minimum-volume sphere containing the points. The +// minimum-volume algorithm is terminated immediately. The sphere is +// returned as well as a bool value of 'true' when the sphere is minimum +// volume or 'false' when the failure is trapped. When 'false' is returned, +// you can try another call to the operator()(...) function. The random +// shuffle that occurs is highly likely to be different from the previous +// shuffle, and there is a chance that the algorithm can succeed just because +// of the different ordering of points. + +namespace gte +{ + template + class MinimumVolumeSphere3 + { + public: + bool operator()(int numPoints, Vector3 const* points, Sphere3& minimal) + { + if (numPoints >= 1 && points) + { + // Function array to avoid switch statement in the main loop. + std::function update[5]; + update[1] = [this](int i) { return UpdateSupport1(i); }; + update[2] = [this](int i) { return UpdateSupport2(i); }; + update[3] = [this](int i) { return UpdateSupport3(i); }; + update[4] = [this](int i) { return UpdateSupport4(i); }; + + // Process only the unique points. + std::vector permuted(numPoints); + for (int i = 0; i < numPoints; ++i) + { + permuted[i] = i; + } + std::sort(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] < points[i1]; }); + auto end = std::unique(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] == points[i1]; }); + permuted.erase(end, permuted.end()); + numPoints = static_cast(permuted.size()); + + // Create a random permutation of the points. + std::shuffle(permuted.begin(), permuted.end(), mDRE); + + // Convert to the compute type, which is a simple copy when + // ComputeType is the same as InputType. + mComputePoints.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + for (int j = 0; j < 3; ++j) + { + mComputePoints[i][j] = points[permuted[i]][j]; + } + } + + // Start with the first point. + Sphere3 ctMinimal = ExactSphere1(0); + mNumSupport = 1; + mSupport[0] = 0; + + // The loop restarts from the beginning of the point list each + // time the sphere needs updating. Linus Kallberg (Computer + // Science at Malardalen University in Sweden) discovered that + // performance is/ better when the remaining points in the + // array are processed before restarting. The points + // processed before the point that caused the/ update are + // likely to be enclosed by the new sphere (or near the sphere + // boundary) because they were enclosed by the previous + // sphere. The chances are better that points after the + // current one will cause growth of the bounding sphere. + for (int i = 1 % numPoints, n = 0; i != n; i = (i + 1) % numPoints) + { + if (!SupportContains(i)) + { + if (!Contains(i, ctMinimal)) + { + auto result = update[mNumSupport](i); + if (result.second == true) + { + if (result.first.radius > ctMinimal.radius) + { + ctMinimal = result.first; + n = i; + } + } + else + { + // This case can happen when ComputeType is + // float or double. See the comments at the + // beginning of this file. + LogWarning("ComputeType is not exact and failure occurred. Returning non-minimal sphere."); + GetContainer(numPoints, points, minimal); + mNumSupport = 0; + mSupport.fill(0); + return false; + } + } + } + } + + for (int j = 0; j < 3; ++j) + { + minimal.center[j] = static_cast(ctMinimal.center[j]); + } + minimal.radius = static_cast(ctMinimal.radius); + minimal.radius = std::sqrt(minimal.radius); + + for (int i = 0; i < mNumSupport; ++i) + { + mSupport[i] = permuted[mSupport[i]]; + } + return true; + } + else + { + LogError("Input must contain points."); + minimal.center = Vector3::Zero(); + minimal.radius = std::numeric_limits::max(); + return false; + } + } + + // Member access. + inline int GetNumSupport() const + { + return mNumSupport; + } + + inline std::array const& GetSupport() const + { + return mSupport; + } + + private: + // Test whether point P is inside sphere S using squared distance and + // squared radius. + bool Contains(int i, Sphere3 const& sphere) const + { + // NOTE: In this algorithm, sphere.radius is the *squared radius* + // until the function returns at which time a square root is + // applied. + Vector3 diff = mComputePoints[i] - sphere.center; + return Dot(diff, diff) <= sphere.radius; + } + + Sphere3 ExactSphere1(int i0) const + { + Sphere3 minimal; + minimal.center = mComputePoints[i0]; + minimal.radius = (ComputeType)0; + return minimal; + } + + Sphere3 ExactSphere2(int i0, int i1) const + { + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Sphere3 minimal; + minimal.center = ((ComputeType)0.5)*(P0 + P1); + Vector3 diff = P1 - P0; + minimal.radius = ((ComputeType)0.25)*Dot(diff, diff); + return minimal; + } + + Sphere3 ExactSphere3(int i0, int i1, int i2) const + { + // Compute the 2D circle containing P0, P1, and P2. The center in + // barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2, where + // x0 + x1 + x2 = 1. The center is equidistant from the three + // points, so |C - P0| = |C - P1| = |C - P2| = R, where R is the + // radius of the circle. From these conditions, + // C - P0 = x0*E0 + x1*E1 - E0 + // C - P1 = x0*E0 + x1*E1 - E1 + // C - P2 = x0*E0 + x1*E1 + // where E0 = P0 - P2 and E1 = P1 - P2, which leads to + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E0, x0*E0 + x1*E1) + |E0|^2 + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E1, x0*E0 + x1*E1) + |E1|^2 + // r^2 = |x0*E0 + x1*E1|^2 + // Subtracting the last equation from the first two and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) || x1 | | Dot(E1,E1) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0 and x1 and then + // evaluates the third equation in r^2 to obtain r. + + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Vector3 const& P2 = mComputePoints[i2]; + + Vector3 E0 = P0 - P2; + Vector3 E1 = P1 - P2; + + Matrix2x2 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + + ComputeType const half = (ComputeType)0.5; + Vector2 B{ half*A(0, 0), half*A(1, 1) }; + + Sphere3 minimal; + Vector2 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x2 = (ComputeType)1 - X[0] - X[1]; + minimal.center = X[0] * P0 + X[1] * P1 + x2 * P2; + Vector3 tmp = X[0] * E0 + X[1] * E1; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector3::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + return minimal; + } + + Sphere3 ExactSphere4(int i0, int i1, int i2, int i3) const + { + // Compute the sphere containing P0, P1, P2, and P3. The center + // in barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2 + x3*P3, + // where x0 + x1 + x2 + x3 = 1. The center is equidistant from + // the three points, so |C - P0| = |C - P1| = |C - P2| = |C - P3| + // = R, where R is the radius of the sphere. From these + // conditions, + // C - P0 = x0*E0 + x1*E1 + x2*E2 - E0 + // C - P1 = x0*E0 + x1*E1 + x2*E2 - E1 + // C - P2 = x0*E0 + x1*E1 + x2*E2 - E2 + // C - P3 = x0*E0 + x1*E1 + x2*E2 + // where E0 = P0 - P3, E1 = P1 - P3, and E2 = P2 - P3, which + // leads to + // r^2 = |x0*E0+x1*E1+x2*E2|^2 - 2*Dot(E0,x0*E0+x1*E1+x2*E2) + |E0|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 - 2*Dot(E1,x0*E0+x1*E1+x2*E2) + |E1|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 - 2*Dot(E2,x0*E0+x1*E1+x2*E2) + |E2|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 + // Subtracting the last equation from the first three and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) Dot(E0,E2) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) Dot(E1,E2) || x1 | | Dot(E1,E1) | + // | Dot(E2,E0) Dot(E2,E1) Dot(E2,E2) || x2 | | Dot(E2,E2) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0, x1, and x2 and + // then evaluates the fourth equation in r^2 to obtain r. + + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Vector3 const& P2 = mComputePoints[i2]; + Vector3 const& P3 = mComputePoints[i3]; + + Vector3 E0 = P0 - P3; + Vector3 E1 = P1 - P3; + Vector3 E2 = P2 - P3; + + Matrix3x3 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(0, 2) = Dot(E0, E2); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + A(1, 2) = Dot(E1, E2); + A(2, 0) = A(0, 2); + A(2, 1) = A(1, 2); + A(2, 2) = Dot(E2, E2); + + ComputeType const half = (ComputeType)0.5; + Vector3 B{ half*A(0, 0), half*A(1, 1), half*A(2, 2) }; + + Sphere3 minimal; + Vector3 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x3 = (ComputeType)1 - X[0] - X[1] - X[2]; + minimal.center = X[0] * P0 + X[1] * P1 + X[2] * P2 + x3 * P3; + Vector3 tmp = X[0] * E0 + X[1] * E1 + X[2] * E2; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector3::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + return minimal; + } + + typedef std::pair, bool> UpdateResult; + + UpdateResult UpdateSupport1(int i) + { + Sphere3 minimal = ExactSphere2(mSupport[0], i); + mNumSupport = 2; + mSupport[1] = i; + return std::make_pair(minimal, true); + } + + UpdateResult UpdateSupport2(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 2; + int const type2[numType2][2] = + { + { 0, /*2*/ 1 }, + { 1, /*2*/ 0 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 1; // {0, 1, 2} + + Sphere3 sphere[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + if (Contains(k1, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + k0 = mSupport[0]; + k1 = mSupport[1]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + + switch (iMinRSqr) + { + case 0: + mSupport[1] = i; + break; + case 1: + mSupport[0] = i; + break; + case 2: + mNumSupport = 3; + mSupport[2] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-volume algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + UpdateResult UpdateSupport3(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 3; + int const type2[numType2][3] = + { + { 0, /*3*/ 1, 2 }, + { 1, /*3*/ 0, 2 }, + { 2, /*3*/ 0, 1 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 3; + int const type3[numType3][3] = + { + { 0, 1, /*3*/ 2 }, + { 0, 2, /*3*/ 1 }, + { 1, 2, /*3*/ 0 } + }; + + // Permutations of type 4, used for calling ExactSphere4(...). + int const numType4 = 1; // {0, 1, 2, 3} + + Sphere3 sphere[numType2 + numType3 + numType4]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1, k2; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + if (Contains(k1, sphere[iSphere]) && Contains(k2, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iSphere) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + if (Contains(k2, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 4. + k0 = mSupport[0]; + k1 = mSupport[1]; + k2 = mSupport[2]; + sphere[iSphere] = ExactSphere4(k0, k1, k2, i); + if (sphere[iSphere].radius < minRSqr) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mSupport[2] = i; + break; + case 4: + mSupport[1] = i; + break; + case 5: + mSupport[0] = i; + break; + case 6: + mNumSupport = 4; + mSupport[3] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + UpdateResult UpdateSupport4(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 4; + int const type2[numType2][4] = + { + { 0, /*4*/ 1, 2, 3 }, + { 1, /*4*/ 0, 2, 3 }, + { 2, /*4*/ 0, 1, 3 }, + { 3, /*4*/ 0, 1, 2 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 6; + int const type3[numType3][4] = + { + { 0, 1, /*4*/ 2, 3 }, + { 0, 2, /*4*/ 1, 3 }, + { 0, 3, /*4*/ 1, 2 }, + { 1, 2, /*4*/ 0, 3 }, + { 1, 3, /*4*/ 0, 2 }, + { 2, 3, /*4*/ 0, 1 } + }; + + // Permutations of type 4, used for calling ExactSphere4(...). + int const numType4 = 4; + int const type4[numType4][4] = + { + { 0, 1, 2, /*4*/ 3 }, + { 0, 1, 3, /*4*/ 2 }, + { 0, 2, 3, /*4*/ 1 }, + { 1, 2, 3, /*4*/ 0 } + }; + + Sphere3 sphere[numType2 + numType3 + numType4]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1, k2, k3; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + k3 = mSupport[type2[j][3]]; + if (Contains(k1, sphere[iSphere]) && Contains(k2, sphere[iSphere]) && Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iSphere) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + k3 = mSupport[type3[j][3]]; + if (Contains(k2, sphere[iSphere]) && Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 4. + for (int j = 0; j < numType4; ++j, ++iSphere) + { + k0 = mSupport[type4[j][0]]; + k1 = mSupport[type4[j][1]]; + k2 = mSupport[type4[j][2]]; + sphere[iSphere] = ExactSphere4(k0, k1, k2, i); + if (sphere[iSphere].radius < minRSqr) + { + k3 = mSupport[type4[j][3]]; + if (Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mNumSupport = 2; + mSupport[0] = mSupport[3]; + mSupport[1] = i; + break; + case 4: + mNumSupport = 3; + mSupport[2] = i; + break; + case 5: + mNumSupport = 3; + mSupport[1] = i; + break; + case 6: + mNumSupport = 3; + mSupport[1] = mSupport[3]; + mSupport[2] = i; + break; + case 7: + mNumSupport = 3; + mSupport[0] = i; + break; + case 8: + mNumSupport = 3; + mSupport[0] = mSupport[3]; + mSupport[2] = i; + break; + case 9: + mNumSupport = 3; + mSupport[0] = mSupport[3]; + mSupport[1] = i; + break; + case 10: + mSupport[3] = i; + break; + case 11: + mSupport[2] = i; + break; + case 12: + mSupport[1] = i; + break; + case 13: + mSupport[0] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + // Indices of points that support current minimum volume sphere. + bool SupportContains(int j) const + { + for (int i = 0; i < mNumSupport; ++i) + { + if (j == mSupport[i]) + { + return true; + } + } + return false; + } + + int mNumSupport; + std::array mSupport; + + // Random permutation of the unique input points to produce expected + // linear time for the algorithm. + std::default_random_engine mDRE; + std::vector> mComputePoints; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCircle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCircle.h new file mode 100644 index 000000000000..5e887ed3f345 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCircle.h @@ -0,0 +1,143 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.18.0 (2018/10/28) + +#pragma once + +#include + +// The algorithm for representing a circle as a NURBS curve or a sphere as a +// NURBS surface is described in +// http://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf +// The implementations are related to the documents as shown next. +// NURBSQuarterCircleDegree2 implements equation (9) +// NURBSQuarterCircleDegree4 implements equation (10) +// NURBSHalfCircleDegree3 implements equation (12) +// NURBSFullCircleDegree3 implements Section 2.3 + +namespace gte +{ + template + class NURBSQuarterCircleDegree2 : public NURBSCurve<2, Real> + { + public: + // Construction. The quarter circle is x^2 + y^2 = 1 for x >= 0 + // and y >= 0. The direction of traversal is counterclockwise as + // u increase from 0 to 1. + NURBSQuarterCircleDegree2() + : + NURBSCurve<2, Real>(BasisFunctionInput(3, 2), nullptr, nullptr) + { + Real const sqrt2 = std::sqrt((Real)2); + this->mWeights[0] = sqrt2; + this->mWeights[1] = (Real)1; + this->mWeights[2] = sqrt2; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)1 }; + this->mControls[2] = { (Real)0, (Real)1 }; + } + }; + + template + class NURBSQuarterCircleDegree4 : public NURBSCurve<2, Real> + { + public: + // Construction. The quarter circle is x^2 + y^2 = 1 for x >= 0 + // and y >= 0. The direction of traversal is counterclockwise as + // u increases from 0 to 1. + NURBSQuarterCircleDegree4() + : + NURBSCurve<2, Real>(BasisFunctionInput(5, 4), nullptr, nullptr) + { + Real const sqrt2 = std::sqrt((Real)2); + this->mWeights[0] = (Real)1; + this->mWeights[1] = (Real)1; + this->mWeights[2] = (Real)2 * sqrt2 / (Real)3; + this->mWeights[3] = (Real)1; + this->mWeights[4] = (Real)1; + + Real const x1 = (Real)1; + Real const y1 = (Real)0.5 / sqrt2; + Real const x2 = (Real)1 - sqrt2 / (Real)8; + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { x1, y1 }; + this->mControls[2] = { x2, x2 }; + this->mControls[3] = { y1, x1 }; + this->mControls[4] = { (Real)0, (Real)1 }; + } + }; + + template + class NURBSHalfCircleDegree3 : public NURBSCurve<2, Real> + { + public: + // Construction. The half circle is x^2 + y^2 = 1 for x >= 0. The + // direction of traversal is counterclockwise as u increases from + // 0 to 1. + NURBSHalfCircleDegree3() + : + NURBSCurve<2, Real>(BasisFunctionInput(4, 3), nullptr, nullptr) + { + Real const oneThird = (Real)1 / (Real)3; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)2 }; + this->mControls[2] = { (Real)-1, (Real)2 }; + this->mControls[3] = { (Real)-1, (Real)0 }; + } + }; + + template + class NURBSFullCircleDegree3 : public NURBSCurve<2, Real> + { + public: + // Construction. The full circle is x^2 + y^2 = 1. The direction of + // traversal is counterclockwise as u increases from 0 to 1. + NURBSFullCircleDegree3() + : + NURBSCurve<2, Real>(CreateBasisFunctionInput(), nullptr, nullptr) + { + Real const oneThird = (Real)1 / (Real)3; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[5] = oneThird; + this->mWeights[6] = (Real)1; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)2 }; + this->mControls[2] = { (Real)-1, (Real)2 }; + this->mControls[3] = { (Real)-1, (Real)0 }; + this->mControls[4] = { (Real)-1, (Real)-2 }; + this->mControls[5] = { (Real)1, (Real)-2 }; + this->mControls[6] = { (Real)1, (Real)0 }; + } + + private: + static BasisFunctionInput CreateBasisFunctionInput() + { + // We need knots (0,0,0,0,1/2,1/2,1/2,1,1,1,1). + BasisFunctionInput input; + input.numControls = 7; + input.degree = 3; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = 3; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0] = { (Real)0, 4 }; + input.uniqueKnots[1] = { (Real)0.5, 3 }; + input.uniqueKnots[2] = { (Real)1, 4 }; + return input; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCurve.h new file mode 100644 index 000000000000..d1fcda2439d3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSCurve.h @@ -0,0 +1,229 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/28) + +#pragma once + +#include +#include + +namespace gte +{ + template + class NURBSCurve : public ParametricCurve + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The domain is t in [t[d],t[n]], where t[d] and t[n] are + // knots with d the degree and n the number of control points. To + // validate construction, create an object as shown: + // NURBSCurve curve(parameters); + // if (!curve) { ; } + NURBSCurve(BasisFunctionInput const& input, + Vector const* controls, Real const* weights) + : + ParametricCurve((Real)0, (Real)1), + mBasisFunction(input) + { + if (!mBasisFunction) + { + // Errors were already generated during construction of the + // basis function. + return; + } + + // The mBasisFunction stores the domain but so does + // ParametricCurve. + this->mTime.front() = mBasisFunction.GetMinDomain(); + this->mTime.back() = mBasisFunction.GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + mControls.resize(input.numControls); + mWeights.resize(input.numControls); + if (controls) + { + std::copy(controls, controls + input.numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + if (weights) + { + std::copy(weights, weights + input.numControls, mWeights.begin()); + } + else + { + std::fill(mWeights.begin(), mWeights.end(), (Real)0); + } + this->mConstructed = true; + } + + // Member access. + inline BasisFunction const& GetBasisFunction() const + { + return mBasisFunction; + } + + inline int GetNumControls() const + { + return static_cast(mControls.size()); + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + void SetControl(int i, Vector const& control) + { + if (0 <= i && i < GetNumControls()) + { + mControls[i] = control; + } + } + + Vector const& GetControl(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mControls[i]; + } + else + { + // Invalid index, return something. + return mControls[0]; + } + } + + void SetWeight(int i, Real weight) + { + if (0 <= i && i < GetNumControls()) + { + mWeights[i] = weight; + } + } + + Real const& GetWeight(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mWeights[i]; + } + else + { + // Invalid index, return something. + return mWeights[0]; + } + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, maxOrder <= 3 is required. + // If you want only the position, pass in maxOrder of 0. If you + // want the position and first derivative, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position, first + // derivative, second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, Vector values[4]) const + { + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int imin, imax; + mBasisFunction.Evaluate(t, maxOrder, imin, imax); + + // Compute position. + Vector X; + Real w; + Compute(0, imin, imax, X, w); + Real invW = ((Real)1) / w; + values[0] = invW * X; + + if (maxOrder >= 1) + { + // Compute first derivative. + Vector XDer1; + Real wDer1; + Compute(1, imin, imax, XDer1, wDer1); + values[1] = invW * (XDer1 - wDer1 * values[0]); + + if (maxOrder >= 2) + { + // Compute second derivative. + Vector XDer2; + Real wDer2; + Compute(2, imin, imax, XDer2, wDer2); + values[2] = invW * (XDer2 - ((Real)2) * wDer1 * values[1] - + wDer2 * values[0]); + + if (maxOrder == 3) + { + // Compute third derivative. + Vector XDer3; + Real wDer3; + Compute(3, imin, imax, XDer3, wDer3); + values[3] = invW * (XDer3 - ((Real)3) * wDer1 * values[2] - + ((Real)3) * wDer2 * values[1] - wDer3 * values[0]); + } + else + { + values[3].MakeZero(); + } + } + } + } + + protected: + // Support for Evaluate(...). + void Compute(unsigned int order, int imin, int imax, Vector& X, Real& w) const + { + // The j-index introduces a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j = i always. + + int numControls = GetNumControls(); + X.MakeZero(); + w = (Real)0; + for (int i = imin; i <= imax; ++i) + { + int j = (i >= numControls ? i - numControls : i); + Real tmp = mBasisFunction.GetValue(order, i) * mWeights[j]; + X += tmp * mControls[j]; + w += tmp; + } + } + + BasisFunction mBasisFunction; + std::vector> mControls; + std::vector mWeights; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSphere.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSphere.h new file mode 100644 index 000000000000..c7f2a846a8c7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSphere.h @@ -0,0 +1,459 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.18.0 (2018/10/28) + +#pragma once + +#include +#include +#include + +// The algorithm for representing a circle as a NURBS curve or a sphere as a +// NURBS surface is described in +// http://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf +// The implementations are related to the documents as shown next. +// NURBSEighthSphereDegree4 implements Section 3.1.2 (triangular domain) +// NURBSHalfSphereDegree3 implements Section 3.2 (rectangular domain) +// NURBSFullSphereDegree3 implements Section 2.3 (rectangular domain) +// TODO: The class NURBSSurface currently assumes a rectangular domain. +// Once support is added for triangular domains, make that new class a +// base class of the sphere-representing NURBS. This will allow sharing +// of the NURBS basis functions and evaluation framework. + +namespace gte +{ + template + class NURBSEighthSphereDegree4 + { + public: + // Construction. The eigth sphere is x^2 + y^2 + z^2 = 1 for x >= 0, + // y >= 0 and z >= 0. + NURBSEighthSphereDegree4() + { + Real const sqrt2 = std::sqrt((Real)2); + Real const sqrt3 = std::sqrt((Real)3); + Real const a0 = (sqrt3 - (Real)1) / sqrt3; + Real const a1 = (sqrt3 + (Real)1) / ((Real)2 * sqrt3); + Real const a2 = (Real)1 - ((Real)5 - sqrt2) * ((Real)7 - sqrt3) / (Real)46; + Real const b0 = (Real)4 * sqrt3 * (sqrt3 - (Real)1); + Real const b1 = (Real)3 * sqrt2; + Real const b2 = (Real)4; + Real const b3 = sqrt2 * ((Real)3 + (Real)2 * sqrt2 - sqrt3) / sqrt3; + + mControls[0][0] = { (Real)0, (Real)0, (Real)1 }; // P004 + mControls[0][1] = { (Real)0, a0, (Real)1 }; // P013 + mControls[0][2] = { (Real)0, a1, a1 }; // P022 + mControls[0][3] = { (Real)0, (Real)1, a0 }; // P031 + mControls[0][4] = { (Real)0, (Real)1, (Real)0 }; // P040 + + mControls[1][0] = { a0, (Real)0, (Real)1 }; // P103 + mControls[1][1] = { a2, a2, (Real)1 }; // P112 + mControls[1][2] = { a2, (Real)1, a2 }; // P121 + mControls[1][3] = { a0, (Real)1, (Real)0 }; // P130 + mControls[1][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[2][0] = { a1, (Real)0, a1 }; // P202 + mControls[2][1] = { (Real)1, a2, a2 }; // P211 + mControls[2][2] = { a1, a1, (Real)0 }; // P220 + mControls[2][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[2][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[3][0] = { (Real)1, (Real)0, a0 }; // P301 + mControls[3][1] = { (Real)1, a0, (Real)0 }; // P310 + mControls[3][2] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[3][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[3][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[4][0] = { (Real)1, (Real)0, (Real)0 }; // P400 + mControls[4][1] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][2] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mWeights[0][0] = b0; // w004 + mWeights[0][1] = b1; // w013 + mWeights[0][2] = b2; // w022 + mWeights[0][3] = b1; // w031 + mWeights[0][4] = b0; // w040 + + mWeights[1][0] = b1; // w103 + mWeights[1][1] = b3; // w112 + mWeights[1][2] = b3; // w121 + mWeights[1][3] = b1; // w130 + mWeights[1][4] = (Real)0; // unused + + mWeights[2][0] = b2; // w202 + mWeights[2][1] = b3; // w211 + mWeights[2][2] = b2; // w220 + mWeights[2][3] = (Real)0; // unused + mWeights[2][4] = (Real)0; // unused + + mWeights[3][0] = b1; // w301 + mWeights[3][1] = b1; // w310 + mWeights[3][2] = (Real)0; // unused + mWeights[3][3] = (Real)0; // unused + mWeights[3][4] = (Real)0; // unused + + mWeights[4][0] = b0; // w400 + mWeights[4][1] = (Real)0; // unused + mWeights[4][2] = (Real)0; // unused + mWeights[4][3] = (Real)0; // unused + mWeights[4][4] = (Real)0; // unused + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv; second-order derivatives + // d2X/du2, d2X/dudv, d2X/dv2. + void Evaluate(Real u, Real v, unsigned int maxOrder, Vector<3, Real> values[6]) const + { + // TODO: Some of the polynomials are used in other polynomials. + // Optimize the code by eliminating the redundant computations. + + Real w = (Real)1 - u - v; + Real uu = u * u, uv = u * v, uw = u * w, vv = v * v, vw = v * w, ww = w * w; + + // Compute the order-0 polynomials. Only the elements to be used + // are filled in. The other terms are uninitialized but never + // used. + Real B[5][5]; + B[0][0] = ww * ww; + B[0][1] = (Real)4 * vw * ww; + B[0][2] = (Real)6 * vv * ww; + B[0][3] = (Real)4 * vv * vw; + B[0][4] = vv * vv; + B[1][0] = (Real)4 * uw * ww; + B[1][1] = (Real)12 * uv * ww; + B[1][2] = (Real)12 * uv * vw; + B[1][3] = (Real)4 * uv * vv; + B[2][0] = (Real)6 * uu * ww; + B[2][1] = (Real)12 * uu * vw; + B[2][2] = (Real)6 * uu * vv; + B[3][0] = (Real)4 * uu * uw; + B[3][1] = (Real)4 * uu * uv; + B[4][0] = uu * uu; + + // Compute the NURBS position. + Vector<3, Real> N{ (Real)0, (Real)0, (Real)0 }; + Real D(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * B[j1][j0]; + N += product * mControls[j1][j0]; + D += product; + } + } + values[0] = N / D; + + if (maxOrder >= 1) + { + // Compute the order-1 polynomials. Only the elements to be + // used are filled in. The other terms are uninitialized but + // never used. + Real WmU = w - u; + Real WmTwoU = WmU - u; + Real WmThreeU = WmTwoU - u; + Real TwoWmU = w + WmU; + Real ThreeWmU = w + TwoWmU; + Real WmV = w - v; + Real WmTwoV = WmV - v; + Real WmThreeV = WmTwoV - v; + Real TwoWmV = w + WmV; + Real ThreeWmV = w + TwoWmV; + Real Dsqr = D * D; + + Real Bu[5][5]; + Bu[0][0] = (Real)-4 * ww * w; + Bu[0][1] = (Real)-12 * v * ww; + Bu[0][2] = (Real)-12 * vv * w; + Bu[0][3] = (Real)-4 * v * vv; + Bu[0][4] = (Real)0; + Bu[1][0] = (Real)4 * ww * WmThreeU; + Bu[1][1] = (Real)12 * vw * WmTwoU; + Bu[1][2] = (Real)12 * vv * WmU; + Bu[1][3] = (Real)4 * vv; + Bu[2][0] = (Real)12 * uw * WmU; + Bu[2][1] = (Real)12 * uv * TwoWmU; + Bu[2][2] = (Real)12 * u * vv; + Bu[3][0] = (Real)4 * uu * ThreeWmU; + Bu[3][1] = (Real)12 * uu * v; + Bu[4][0] = (Real)4 * uu * u; + + Real Bv[5][5]; + Bv[0][0] = (Real)-4 * ww * w; + Bv[0][1] = (Real)4 * ww * WmThreeV; + Bv[0][2] = (Real)12 * vw * WmV; + Bv[0][3] = (Real)4 * vv * ThreeWmV; + Bv[0][4] = (Real)4 * vv * v; + Bv[1][0] = (Real)-12 * u * ww; + Bv[1][1] = (Real)12 * uw * WmTwoV; + Bv[1][2] = (Real)12 * uv * TwoWmV; + Bv[1][3] = (Real)12 * u * vv; + Bv[2][0] = (Real)-12 * uu * w; + Bv[2][1] = (Real)12 * uu * WmV; + Bv[2][2] = (Real)12 * uu * v; + Bv[3][0] = (Real)-4 * uu * u; + Bv[3][1] = (Real)4 * uu * u; + Bv[4][0] = (Real)0; + + Vector<3, Real> Nu{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nv{ (Real)0, (Real)0, (Real)0 }; + Real Du(0), Dv(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * Bu[j1][j0]; + Nu += product * mControls[j1][j0]; + Du += product; + product = mWeights[j1][j0] * Bv[j1][j0]; + Nv += product * mControls[j1][j0]; + Dv += product; + } + } + Vector<3, Real> numerDU = D * Nu - Du * N; + Vector<3, Real> numerDV = D * Nv - Dv * N; + values[1] = numerDU / Dsqr; + values[2] = numerDV / Dsqr; + + if (maxOrder >= 2) + { + // Compute the order-2 polynomials. Only the elements to + // be used are filled in. The other terms are + // uninitialized but never used. + Real Dcub = Dsqr * D; + + Real Buu[5][5]; + Buu[0][0] = (Real)12 * ww; + Buu[0][1] = (Real)24 * vw; + Buu[0][2] = (Real)12 * vv; + Buu[0][3] = (Real)0; + Buu[0][4] = (Real)0; + Buu[1][0] = (Real)-24 * w * WmU; + Buu[1][1] = (Real)-24 * v * TwoWmU; + Buu[1][2] = (Real)-24 * vv; + Buu[1][3] = (Real)0; + Buu[2][0] = (Real)12 * (ww - (Real)4 * uw + uu); + Buu[2][1] = (Real)24 * v * WmTwoU; + Buu[2][2] = (Real)12 * vv; + Buu[3][0] = (Real)24 * u * WmU; + Buu[3][1] = (Real)24 * uv; + Buu[4][0] = (Real)12 * uu; + + Real Buv[5][5]; + Buv[0][0] = (Real)12 * ww; + Buv[0][1] = (Real)-12 * w * WmTwoV; + Buv[0][2] = (Real)-12 * v * TwoWmV; + Buv[0][3] = (Real)-12 * vv; + Buv[0][4] = (Real)0; + Buv[1][0] = (Real)-12 * w * WmTwoU; + Buv[1][1] = (Real)12 * (ww + (Real)2 * (uv - uw - vw)); + Buv[1][2] = (Real)12 * v * ((Real)2 * WmU - v); + Buv[1][3] = (Real)12 * vv; + Buv[2][0] = (Real)-12 * u * TwoWmU; + Buv[2][1] = (Real)12 * u * ((Real)2 * WmV - u); + Buv[2][2] = (Real)24 * uv; + Buv[3][0] = (Real)-12 * uu; + Buv[3][1] = (Real)12 * uu; + Buv[4][0] = (Real)0; + + Real Bvv[5][5]; + Bvv[0][0] = (Real)12 * ww; + Bvv[0][1] = (Real)-24 * w * WmV; + Bvv[0][2] = (Real)12 * (ww - (Real)4 * vw + vv); + Bvv[0][3] = (Real)24 * v * WmV; + Bvv[0][4] = (Real)12 * vv; + Bvv[1][0] = (Real)24 * uw; + Bvv[1][1] = (Real)-24 * u * TwoWmV; + Bvv[1][2] = (Real)24 * u * WmTwoV; + Bvv[1][3] = (Real)24 * uv; + Bvv[2][0] = (Real)12 * uu; + Bvv[2][1] = (Real)-24 * uu; + Bvv[2][2] = (Real)12 * uu; + Bvv[3][0] = (Real)0; + Bvv[3][1] = (Real)0; + Bvv[4][0] = (Real)0; + + Vector<3, Real> Nuu{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nuv{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nvv{ (Real)0, (Real)0, (Real)0 }; + Real Duu(0), Duv(0), Dvv(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * Buu[j1][j0]; + Nuu += product * mControls[j1][j0]; + Duu += product; + product = mWeights[j1][j0] * Buv[j1][j0]; + Nuv += product * mControls[j1][j0]; + Duv += product; + product = mWeights[j1][j0] * Bvv[j1][j0]; + Nvv += product * mControls[j1][j0]; + Dvv += product; + } + } + Vector<3, Real> termDuu = D * (D * Nuu - Duu * N); + Vector<3, Real> termDuv = D * (D * Nuv - Duv * N - Du * Nv - Dv * Nu); + Vector<3, Real> termDvv = D * (D * Nvv - Dvv * N); + values[3] = (D * termDuu - (Real)2 * Du * numerDU) / Dcub; + values[4] = (D * termDuv + (Real)2 * Du * Dv * N) / Dcub; + values[5] = (D * termDvv - (Real)2 * Dv * numerDV) / Dcub; + } + } + } + + private: + // For simplicity of the implementation, 2-dimensional arrays + // of size 5-by-5 are used. Only array[r][c] is used where + // 0 <= r <= 4 and 0 <= c < 4 - r. + Vector3 mControls[5][5]; + Real mWeights[5][5]; + }; + + template + class NURBSHalfSphereDegree3 : public NURBSSurface<3, Real> + { + public: + NURBSHalfSphereDegree3() + : + NURBSSurface<3, Real>(BasisFunctionInput(4, 3), + BasisFunctionInput(4, 3), nullptr, nullptr) + { + // weight[j][i] is mWeights[i + 4 * j], 0 <= i < 4, 0 <= j < 4 + Real const oneThird = (Real)1 / (Real)3; + Real const oneNinth = (Real)1 / (Real)9; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[5] = oneNinth; + this->mWeights[6] = oneNinth; + this->mWeights[7] = oneThird; + this->mWeights[8] = oneThird; + this->mWeights[9] = oneNinth; + this->mWeights[10] = oneNinth; + this->mWeights[11] = oneThird; + this->mWeights[12] = (Real)1; + this->mWeights[13] = oneThird; + this->mWeights[14] = oneThird; + this->mWeights[15] = (Real)1; + + // control[j][i] is mControls[i + 4 * j], 0 <= i < 4, 0 <= j < 4 + this->mControls[0] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[1] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[2] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[3] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[4] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[5] = { (Real)2, (Real)4, (Real)1 }; + this->mControls[6] = { (Real)-2, (Real)4, (Real)1 }; + this->mControls[7] = { (Real)-2, (Real)0, (Real)1 }; + this->mControls[8] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[9] = { (Real)2, (Real)4, (Real)-1 }; + this->mControls[10] = { (Real)-2, (Real)4, (Real)-1 }; + this->mControls[11] = { (Real)-2, (Real)0, (Real)-1 }; + this->mControls[12] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[13] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[14] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[15] = { (Real)0, (Real)0, (Real)-1 }; + } + }; + + template + class NURBSFullSphereDegree3 : public NURBSSurface<3, Real> + { + public: + NURBSFullSphereDegree3() + : + NURBSSurface<3, Real>(BasisFunctionInput(4, 3), + CreateBasisFunctionInputV(), nullptr, nullptr) + { + // weight[j][i] is mWeights[i + 4 * j], 0 <= i < 4, 0 <= j < 7 + Real const oneThird = (Real)1 / (Real)3; + Real const oneNinth = (Real)1 / (Real)9; + this->mWeights[0] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[8] = oneThird; + this->mWeights[12] = (Real)1; + this->mWeights[16] = oneThird; + this->mWeights[20] = oneThird; + this->mWeights[24] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[5] = oneNinth; + this->mWeights[9] = oneNinth; + this->mWeights[13] = oneThird; + this->mWeights[17] = oneNinth; + this->mWeights[21] = oneNinth; + this->mWeights[25] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[6] = oneNinth; + this->mWeights[10] = oneNinth; + this->mWeights[14] = oneThird; + this->mWeights[18] = oneNinth; + this->mWeights[22] = oneNinth; + this->mWeights[26] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[7] = oneThird; + this->mWeights[11] = oneThird; + this->mWeights[15] = (Real)1; + this->mWeights[19] = oneThird; + this->mWeights[23] = oneThird; + this->mWeights[27] = (Real)1; + + // control[j][i] is mControls[i + 4 * j], 0 <= i < 4, 0 <= j < 7 + this->mControls[0] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[4] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[8] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[12] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[16] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[20] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[24] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[1] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[5] = { (Real)2, (Real)4, (Real)1 }; + this->mControls[9] = { (Real)-2, (Real)4, (Real)1 }; + this->mControls[13] = { (Real)-2, (Real)0, (Real)1 }; + this->mControls[17] = { (Real)-2, (Real)-4, (Real)1 }; + this->mControls[21] = { (Real)2, (Real)-4, (Real)1 }; + this->mControls[25] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[2] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[6] = { (Real)2, (Real)4, (Real)-1 }; + this->mControls[10] = { (Real)-2, (Real)4, (Real)-1 }; + this->mControls[14] = { (Real)-2, (Real)0, (Real)-1 }; + this->mControls[18] = { (Real)-2, (Real)-4, (Real)-1 }; + this->mControls[22] = { (Real)2, (Real)-4, (Real)-1 }; + this->mControls[26] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[3] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[7] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[11] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[15] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[19] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[23] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[27] = { (Real)0, (Real)0, (Real)-1 }; + } + + private: + static BasisFunctionInput CreateBasisFunctionInputV() + { + BasisFunctionInput input; + input.numControls = 7; + input.degree = 3; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = 3; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0] = { (Real)0, 4 }; + input.uniqueKnots[1] = { (Real)0.5, 3 }; + input.uniqueKnots[2] = { (Real)1, 4 }; + return input; + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSurface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSurface.h new file mode 100644 index 000000000000..f42bb131b53b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSSurface.h @@ -0,0 +1,253 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/28) + +#pragma once + +#include +#include +#include + +namespace gte +{ + template + class NURBSSurface : public ParametricSurface + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The 'controls' and 'weights' must be stored in + // row-major order, attribute[i0 + numControls0*i1]. As a 2D array, + // this corresponds to attribute2D[i1][i0]. To validate construction, + // create an object as shown: + // NURBSSurface surface(parameters); + // if (!surface) { ; } + NURBSSurface(BasisFunctionInput const& input0, + BasisFunctionInput const& input1, + Vector const* controls, Real const* weights) + : + ParametricSurface((Real)0, (Real)1, (Real)0, (Real)1, true) + { + BasisFunctionInput const* input[2] = { &input0, &input1 }; + for (int i = 0; i < 2; ++i) + { + mNumControls[i] = input[i]->numControls; + mBasisFunction[i].Create(*input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of + // the basis functions. + return; + } + } + + // The mBasisFunction stores the domain but so does + // ParametricSurface. + this->mUMin = mBasisFunction[0].GetMinDomain(); + this->mUMax = mBasisFunction[0].GetMaxDomain(); + this->mVMin = mBasisFunction[1].GetMinDomain(); + this->mVMax = mBasisFunction[1].GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1]; + mControls.resize(numControls); + mWeights.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + memset(mControls.data(), 0, mControls.size() * sizeof(mControls[0])); + } + if (weights) + { + std::copy(weights, weights + numControls, mWeights.begin()); + } + else + { + memset(mWeights.data(), 0, mWeights.size() * sizeof(mWeights[0])); + } + this->mConstructed = true; + } + + // Member access. The index 'dim' must be in {0,1}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + void SetControl(int i0, int i1, Vector const& control) + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + mControls[i0 + mNumControls[0] * i1] = control; + } + } + + Vector const& GetControl(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mControls[i0 + mNumControls[0] * i1]; + } + else + { + return mControls[0]; + } + } + + void SetWeight(int i0, int i1, Real weight) + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + mWeights[i0 + mNumControls[0] * i1] = weight; + } + } + + Real const& GetWeight(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mWeights[i0 + mNumControls[0] * i1]; + } + else + { + return mWeights[0]; + } + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv; second-order derivatives + // d2X/du2, d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int maxOrder, Vector values[6]) const + { + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 6; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + + // Compute position. + Vector X; + Real w; + Compute(0, 0, iumin, iumax, ivmin, ivmax, X, w); + Real invW = ((Real)1) / w; + values[0] = invW * X; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector XDerU; + Real wDerU; + Compute(1, 0, iumin, iumax, ivmin, ivmax, XDerU, wDerU); + values[1] = invW * (XDerU - wDerU * values[0]); + + Vector XDerV; + Real wDerV; + Compute(0, 1, iumin, iumax, ivmin, ivmax, XDerV, wDerV); + values[2] = invW * (XDerV - wDerV * values[0]); + + if (maxOrder >= 2) + { + // Compute second-order derivatives. + Vector XDerUU; + Real wDerUU; + Compute(2, 0, iumin, iumax, ivmin, ivmax, XDerUU, wDerUU); + values[3] = invW * (XDerUU - ((Real)2) * wDerU * values[1] - + wDerUU * values[0]); + + Vector XDerUV; + Real wDerUV; + Compute(1, 1, iumin, iumax, ivmin, ivmax, XDerUV, wDerUV); + values[4] = invW * (XDerUV - wDerU * values[2] - wDerV * values[1] + - wDerUV * values[0]); + + Vector XDerVV; + Real wDerVV; + Compute(0, 2, iumin, iumax, ivmin, ivmax, XDerVV, wDerVV); + values[5] = invW * (XDerVV - ((Real)2) * wDerV * values[2] - + wDerVV * values[0]); + } + } + } + + protected: + // Support for Evaluate(...). + void Compute(unsigned int uOrder, unsigned int vOrder, int iumin, + int iumax, int ivmin, int ivmax, Vector& X, Real& w) const + { + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + X.MakeZero(); + w = (Real)0; + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + int index = ju + numControls0 * jv; + Real tmp = tmpu * tmpv * mWeights[index]; + X += tmp * mControls[index]; + w += tmp; + } + } + } + + std::array, 2> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + std::vector mWeights; + }; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSVolume.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSVolume.h new file mode 100644 index 000000000000..94dfbf6e3897 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNURBSVolume.h @@ -0,0 +1,264 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/22) + +#pragma once + +#include +#include + +namespace gte +{ + template + class NURBSVolume + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The 'controls' and 'weights' must be stored in + // lexicographical order, + // attribute[i0 + numControls0 * (i1 + numControls1 * i2)] + // As a 3D array, this corresponds to attribute3D[i2][i1][i0]. + NURBSVolume(BasisFunctionInput const& input0, + BasisFunctionInput const& input1, + BasisFunctionInput const& input2, + Vector const* controls, Real const* weights) + : + mConstructed(false) + { + BasisFunctionInput const* input[3] = { &input0, &input1, &input2 }; + for (int i = 0; i < 3; ++i) + { + mNumControls[i] = input[i]->numControls; + mBasisFunction[i].Create(*input[i]); + if (!mBasisFunction[i]) + { + // Errors were already generated during construction of + // the basis functions. + return; + } + } + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1] * mNumControls[2]; + mControls.resize(numControls); + mWeights.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + memset(mControls.data(), 0, mControls.size() * sizeof(mControls[0])); + } + if (weights) + { + std::copy(weights, weights + numControls, mWeights.begin()); + } + else + { + memset(mWeights.data(), 0, mWeights.size() * sizeof(mWeights[0])); + } + mConstructed = true; + } + + // To validate construction, create an object as shown: + // NURBSVolume volume(parameters); + // if (!volume) { ; } + inline operator bool() const + { + return mConstructed; + } + + // Member access. The index 'dim' must be in {0,1,2}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline Real GetMinDomain(int dim) const + { + return mBasisFunction[dim].GetMinDomain(); + } + + inline Real GetMaxDomain(int dim) const + { + return mBasisFunction[dim].GetMaxDomain(); + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + // Evaluation of the volume. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv, dX/dw; second-order + // derivatives d2X/du2, d2X/dv2, d2X/dw2, d2X/dudv, d2X/dudw, + // d2X/dvdw. + void Evaluate(Real u, Real v, Real w, unsigned int maxOrder, Vector values[10]) const + { + if (!mConstructed) + { + // Errors were already generated during construction. + for (int i = 0; i < 10; ++i) + { + values[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax, iwmin, iwmax; + mBasisFunction[0].Evaluate(u, maxOrder, iumin, iumax); + mBasisFunction[1].Evaluate(v, maxOrder, ivmin, ivmax); + mBasisFunction[2].Evaluate(w, maxOrder, iwmin, iwmax); + + // Compute position. + Vector X; + Real h; + Compute(0, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, X, h); + Real invH = ((Real)1) / h; + values[0] = invH * X; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector XDerU; + Real hDerU; + Compute(1, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerU, hDerU); + values[1] = invH * (XDerU - hDerU * values[0]); + + Vector XDerV; + Real hDerV; + Compute(0, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerV, hDerV); + values[2] = invH * (XDerV - hDerV * values[0]); + + Vector XDerW; + Real hDerW; + Compute(0, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerW, hDerW); + values[3] = invH * (XDerW - hDerW * values[0]); + + if (maxOrder >= 2) + { + // Compute second-order derivatives. + Vector XDerUU; + Real hDerUU; + Compute(2, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerUU, hDerUU); + values[4] = invH * (XDerUU - ((Real)2) * hDerU * values[1] - + hDerUU * values[0]); + + Vector XDerVV; + Real hDerVV; + Compute(0, 2, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerVV, hDerVV); + values[5] = invH * (XDerVV - ((Real)2) * hDerV * values[2] - + hDerVV * values[0]); + + Vector XDerWW; + Real hDerWW; + Compute(0, 0, 2, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerWW, hDerWW); + values[6] = invH * (XDerWW - ((Real)2) * hDerW * values[3] - + hDerWW * values[0]); + + Vector XDerUV; + Real hDerUV; + Compute(1, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerUV, hDerUV); + values[7] = invH * (XDerUV - hDerU * values[2] + - hDerV * values[1] - hDerUV * values[0]); + + Vector XDerUW; + Real hDerUW; + Compute(1, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerUW, hDerUW); + values[8] = invH * (XDerUW - hDerU * values[3] + - hDerW * values[1] - hDerUW * values[0]); + + Vector XDerVW; + Real hDerVW; + Compute(0, 1, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, + XDerVW, hDerVW); + values[9] = invH * (XDerVW - hDerV * values[3] + - hDerW * values[2] - hDerVW * values[0]); + } + } + } + + private: + // Support for Evaluate(...). + void Compute(unsigned int uOrder, unsigned int vOrder, + unsigned int wOrder, int iumin, int iumax, int ivmin, int ivmax, + int iwmin, int iwmax, Vector& X, Real& h) const + { + // The j*-indices introduce a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j* = i* always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + int const numControls2 = mNumControls[2]; + X.MakeZero(); + h = (Real)0; + for (int iw = iwmin; iw <= iwmax; ++iw) + { + Real tmpw = mBasisFunction[2].GetValue(wOrder, iw); + int jw = (iw >= numControls2 ? iw - numControls2 : iw); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + Real tmpvw = tmpv * tmpw; + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + int index = ju + numControls0 * (jv + numControls1 * jw); + Real tmp = (tmpu * tmpvw) * mWeights[index]; + X += tmp * mControls[index]; + h += tmp; + } + } + } + } + + std::array, 3> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + std::vector mWeights; + bool mConstructed; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNaturalSplineCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNaturalSplineCurve.h new file mode 100644 index 000000000000..d6a09c169d2d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNaturalSplineCurve.h @@ -0,0 +1,404 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/02/17) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class NaturalSplineCurve : public ParametricCurve +{ +public: + // Construction and destruction. The object copies the input arrays. The + // number of points M must be at least 2. The first constructor is for a + // spline with second derivatives zero at the endpoints (isFree = true) + // or a spline that is closed (isFree = false). The second constructor is + // for clamped splines, where you specify the first derivatives at the + // endpoints. Usually, derivative0 = points[1] - points[0] at the first + // point and derivative1 = points[M-1] - points[M-2]. To validate + // construction, create an object as shown: + // NaturalSplineCurve curve(parameters); + // if (!curve) { ; } + virtual ~NaturalSplineCurve(); + NaturalSplineCurve(bool isFree, int numPoints, + Vector const* points, Real const* times); + NaturalSplineCurve(int numPoints, Vector const* points, + Real const* times, Vector const& derivative0, + Vector const& derivative1); + + // Member access. + inline int GetNumPoints() const; + inline Vector const* GetPoints() const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +protected: + // Support for construction. + void CreateFree(); + void CreateClosed(); + void CreateClamped(Vector const& derivative0, + Vector const& derivative1); + + // Determine the index i for which times[i] <= t < times[i+1]. + void GetKeyInfo(Real t, int& key, Real& dt) const; + + // Polynomial coefficients. mA are the points (constant coefficients of + // polynomials. mB are the degree 1 coefficients, mC are the degree 2 + // coefficients, and mD are the degree 3 coefficients. + std::vector> mA, mB, mC, mD; +}; + + +template +NaturalSplineCurve::~NaturalSplineCurve() +{ +} + +template +NaturalSplineCurve::NaturalSplineCurve(bool isFree, int numPoints, + Vector const* points, Real const* times) + : + ParametricCurve(numPoints - 1, times) +{ + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + mA.resize(numPoints); + std::copy(points, points + numPoints, mA.begin()); + + if (isFree) + { + CreateFree(); + } + else + { + CreateClosed(); + } + + this->mConstructed = true; +} + +template +NaturalSplineCurve::NaturalSplineCurve(int numPoints, + Vector const* points, Real const* times, + Vector const& derivative0, Vector const& derivative1) + : + ParametricCurve(numPoints - 1, times) +{ + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + mA.resize(numPoints); + std::copy(points, points + numPoints, mA.begin()); + + CreateClamped(derivative0, derivative1); + this->mConstructed = true; +} + +template inline +int NaturalSplineCurve::GetNumPoints() const +{ + return static_cast(mA.size()); +} + +template inline +Vector const* NaturalSplineCurve::GetPoints() const +{ + return &mA[0]; +} + +template +void NaturalSplineCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int key = 0; + Real dt = (Real)0; + GetKeyInfo(t, key, dt); + + // Compute position. + values[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key])); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = mB[key] + dt * (((Real)2) * mC[key] + + ((Real)3) * dt * mD[key]); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = ((Real)2) * mC[key] + ((Real)6) * dt * mD[key]; + if (maxOrder == 3) + { + values[3] = ((Real)6) * mD[key]; + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +void NaturalSplineCurve::CreateFree() +{ + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + std::vector d2t(numSegments); + for (int i = 1; i < numSegments; ++i) + { + d2t[i] = this->mTime[i + 1] - this->mTime[i - 1]; + } + + std::vector> alpha(numSegments); + for (int i = 1; i < numSegments; ++i) + { + Vector numer = ((Real)3)*(dt[i - 1] * mA[i + 1] - + d2t[i] * mA[i] + dt[i] * mA[i - 1]); + Real invDenom = ((Real)1) / (dt[i - 1] * dt[i]); + alpha[i] = invDenom * numer; + } + + std::vector ell(numSegments + 1); + std::vector mu(numSegments); + std::vector> z(numSegments + 1); + Real inv; + + ell[0] = (Real)1; + mu[0] = (Real)0; + z[0].MakeZero(); + for (int i = 1; i < numSegments; ++i) + { + ell[i] = ((Real)2) * d2t[i] - dt[i - 1] * mu[i - 1]; + inv = ((Real)1) / ell[i]; + mu[i] = inv * dt[i]; + z[i] = inv * (alpha[i] - dt[i - 1] * z[i - 1]); + } + ell[numSegments] = (Real)1; + z[numSegments].MakeZero(); + + mB.resize(numSegments); + mC.resize(numSegments + 1); + mD.resize(numSegments); + + Real oneThird = ((Real)1) / (Real)3; + mC[numSegments].MakeZero(); + for (int i = numSegments - 1; i >= 0; --i) + { + mC[i] = z[i] - mu[i] * mC[i + 1]; + inv = ((Real)1) / dt[i]; + mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird * dt[i] * (mC[i + 1] + + ((Real)2) * mC[i]); + mD[i] = oneThird * inv * (mC[i + 1] - mC[i]); + } +} + +template +void NaturalSplineCurve::CreateClosed() +{ + // TODO: A general linear system solver is used here. The matrix + // corresponding to this case is actually "cyclic banded", so a faster + // linear solver can be used. The current linear system code does not + // have such a solver. + + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + // Construct matrix of system. + GMatrix mat(numSegments + 1, numSegments + 1); + mat(0, 0) = (Real)1; + mat(0, numSegments) = (Real)-1; + for (int i = 1; i <= numSegments - 1; ++i) + { + mat(i, i - 1) = dt[i - 1]; + mat(i, i) = ((Real)2) * (dt[i - 1] + dt[i]); + mat(i, i + 1) = dt[i]; + } + mat(numSegments, numSegments - 1) = dt[numSegments - 1]; + mat(numSegments, 0) = ((Real)2) * (dt[numSegments - 1] + dt[0]); + mat(numSegments, 1) = dt[0]; + + // Construct right-hand side of system. + mC.resize(numSegments + 1); + mC[0].MakeZero(); + Real inv0, inv1; + for (int i = 1; i <= numSegments - 1; ++i) + { + inv0 = ((Real)1) / dt[i]; + inv1 = ((Real)1) / dt[i - 1]; + mC[i] = ((Real)3) * (inv0 * (mA[i + 1] - mA[i]) - + inv1*(mA[i] - mA[i - 1])); + } + inv0 = ((Real)1) / dt[0]; + inv1 = ((Real)1) / dt[numSegments - 1]; + mC[numSegments] = ((Real)3) * (inv0 * (mA[1] - mA[0]) - + inv1 * (mA[0] - mA[numSegments - 1])); + + // Solve the linear systems. + GMatrix invMat = Inverse(mat); + GVector input(numSegments + 1); + GVector output(numSegments + 1); + for (int j = 0; j < N; ++j) + { + for (int i = 0; i <= numSegments; ++i) + { + input[i] = mC[i][j]; + } + output = invMat * input; + for (int i = 0; i <= numSegments; ++i) + { + mC[i][j] = output[i]; + } + } + + Real oneThird = ((Real)1) / (Real)3; + mB.resize(numSegments); + mD.resize(numSegments); + for (int i = 0; i < numSegments; ++i) + { + inv0 = ((Real)1) / dt[i]; + mB[i] = inv0 * (mA[i + 1] - mA[i]) - oneThird * (mC[i + 1] + + ((Real)2) * mC[i]) * dt[i]; + mD[i] = oneThird * inv0 * (mC[i + 1] - mC[i]); + } +} + +template +void NaturalSplineCurve::CreateClamped( + Vector const& derivative0, Vector const& derivative1) +{ + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + std::vector d2t(numSegments); + for (int i = 1; i < numSegments; ++i) + { + d2t[i] = this->mTime[i + 1] - this->mTime[i - 1]; + } + + std::vector> alpha(numSegments + 1); + Real inv = ((Real)1) / dt[0]; + alpha[0] = ((Real)3) * (inv * (mA[1] - mA[0]) - derivative0); + inv = ((Real)1) / dt[numSegments - 1]; + alpha[numSegments] = ((Real)3)*(derivative1 - + inv * (mA[numSegments] - mA[numSegments - 1])); + for (int i = 1; i < numSegments; ++i) + { + Vector numer = ((Real)3)*(dt[i - 1] * mA[i + 1] - + d2t[i] * mA[i] + dt[i] * mA[i - 1]); + Real invDenom = ((Real)1) / (dt[i - 1] * dt[i]); + alpha[i] = invDenom*numer; + } + + std::vector ell(numSegments + 1); + std::vector mu(numSegments); + std::vector> z(numSegments + 1); + + ell[0] = ((Real)2) * dt[0]; + mu[0] = (Real)0.5; + inv = ((Real)1) / ell[0]; + z[0] = inv * alpha[0]; + + for (int i = 1; i < numSegments; ++i) + { + ell[i] = ((Real)2) * d2t[i] - dt[i - 1] * mu[i - 1]; + inv = ((Real)1) / ell[i]; + mu[i] = inv * dt[i]; + z[i] = inv * (alpha[i] - dt[i - 1] * z[i - 1]); + } + ell[numSegments] = dt[numSegments - 1] * + (((Real)2) - mu[numSegments - 1]); + inv = ((Real)1) / ell[numSegments]; + z[numSegments] = inv * (alpha[numSegments] - dt[numSegments - 1] * + z[numSegments - 1]); + + mB.resize(numSegments); + mC.resize(numSegments + 1); + mD.resize(numSegments); + + Real oneThird = ((Real)1) / (Real)3; + mC[numSegments] = z[numSegments]; + for (int i = numSegments - 1; i >= 0; --i) + { + mC[i] = z[i] - mu[i] * mC[i + 1]; + inv = ((Real)1) / dt[i]; + mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird*dt[i] * (mC[i + 1] + + ((Real)2) * mC[i]); + mD[i] = oneThird * inv * (mC[i + 1] - mC[i]); + } +} + +template +void NaturalSplineCurve::GetKeyInfo(Real t, int& key, Real& dt) const +{ + int numSegments = GetNumPoints() - 1; + if (t <= this->mTime[0]) + { + key = 0; + dt = (Real)0; + } + else if (t >= this->mTime[numSegments]) + { + key = numSegments - 1; + dt = this->mTime[numSegments] - this->mTime[numSegments - 1]; + } + else + { + for (int i = 0; i < numSegments; ++i) + { + if (t < this->mTime[i + 1]) + { + key = i; + dt = t - this->mTime[i]; + break; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNearestNeighborQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNearestNeighborQuery.h new file mode 100644 index 000000000000..5fd52b9bbf6a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteNearestNeighborQuery.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/26) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// Use a kd-tree for sorting used in a query for finding nearest neighbors +// of a point in a space of the specified dimension N. The split order is +// always 0,1,2,...,N-1. The number of sites at a leaf node is controlled +// by 'maxLeafSize' and the maximum level of the tree is controlled by +// 'maxLevels'. The points are of type Vector. The 'Site' is a +// structure of information that minimally implements the function +// 'Vector GetPosition () const'. The Site template parameter +// allows the query to be applied even when it has more local information +// than just point location. +template +class NearestNeighborQuery +{ +public: + // Construction. + NearestNeighborQuery(std::vector const& sites, int maxLeafSize, int maxLevel); + + // Member access. + inline int GetMaxLeafSize () const; + inline int GetMaxLevel () const; + + // Compute up to MaxNeighbors nearest neighbors within the specified + // radius of the point. The returned integer is the number of neighbors + // found, possibly zero. The neighbors array stores indices into the + // array passed to the constructor. + template + int FindNeighbors(Vector const& point, Real radius, + std::array& neighbors) const; + +private: + typedef std::pair, int> SortedPoint; + + struct Node + { + Real split; + int axis; + int numSites; + int siteOffset; + int left; + int right; + }; + + // Populate the node so that it contains the points split along the + // coordinate axes. + void Build(int numSites, int siteOffset, int nodeIndex, int level); + + int mMaxLeafSize; + int mMaxLevel; + std::vector mSortedPoints; + std::vector mNodes; +}; + + +template +NearestNeighborQuery::NearestNeighborQuery( + std::vector const& sites, int maxLeafSize, int maxLevel) + : + mMaxLeafSize(maxLeafSize), + mMaxLevel(maxLevel), + mSortedPoints(sites.size()) +{ + int const numSites = static_cast(sites.size()); + for (int i = 0; i < numSites; ++i) + { + mSortedPoints[i] = std::make_pair(sites[i].GetPosition(), i); + } + + mNodes.push_back(Node()); + Build(numSites, 0, 0, 0); +} + +template +inline int NearestNeighborQuery::GetMaxLeafSize() const +{ + return mMaxLeafSize; +} + +template +inline int NearestNeighborQuery::GetMaxLevel() const +{ + return mMaxLevel; +} + +template +template +int NearestNeighborQuery::FindNeighbors(Vector const& point, + Real radius, std::array& neighbors) const +{ + Real sqrRadius = radius * radius; + int numNeighbors = 0; + std::array localNeighbors; + std::array neighborSqrLength; + for (int i = 0; i <= MaxNeighbors; ++i) + { + localNeighbors[i] = -1; + neighborSqrLength[i] = std::numeric_limits::max(); + } + + // The kd-tree construction is recursive, simulated here by using a stack. + // The maximum depth is limited to 32, because the number of sites is + // limited to 2^{32} (the number of 32-bit integer indices). + std::array stack; + int top = 0; + stack[0] = 0; + + while (top >= 0) + { + Node node = mNodes[stack[top--]]; + + if (node.siteOffset != -1) + { + for (int i = 0, j = node.siteOffset; i < node.numSites; ++i, ++j) + { + Vector diff = mSortedPoints[j].first - point; + Real sqrLength = Dot(diff, diff); + if (sqrLength <= sqrRadius) + { + // Maintain the nearest neighbors. + int k; + for (k = 0; k < numNeighbors; ++k) + { + if (sqrLength <= neighborSqrLength[k]) + { + for (int n = numNeighbors; n > k; --n) + { + localNeighbors[n] = localNeighbors[n - 1]; + neighborSqrLength[n] = neighborSqrLength[n - 1]; + } + break; + } + } + if (k < MaxNeighbors) + { + localNeighbors[k] = mSortedPoints[j].second; + neighborSqrLength[k] = sqrLength; + } + if (numNeighbors < MaxNeighbors) + { + ++numNeighbors; + } + } + } + } + + if (node.left != -1 && point[node.axis] - radius <= node.split) + { + stack[++top] = node.left; + } + + if (node.right != -1 && point[node.axis] + radius >= node.split) + { + stack[++top] = node.right; + } + } + + for (int i = 0; i < numNeighbors; ++i) + { + neighbors[i] = localNeighbors[i]; + } + + return numNeighbors; +} + +template +void NearestNeighborQuery::Build(int numSites, int siteOffset, + int nodeIndex, int level) +{ + LogAssert(siteOffset != -1, "Invalid site offset."); + LogAssert(nodeIndex != -1, "Invalid node index."); + LogAssert(numSites > 0, "Empty point list."); + + Node& node = mNodes[nodeIndex]; + node.numSites = numSites; + + if (numSites > mMaxLeafSize && level <= mMaxLevel) + { + int halfNumSites = numSites / 2; + + // The point set is too large for a leaf node, so split it at the + // median. The O(m log m) sort is not needed; rather, we locate the + // median using an order statistic construction that is expected + // time O(m). + int const axis = level % N; + auto sorter = [axis](SortedPoint const& p0, SortedPoint const& p1) + { + return p0.first[axis] < p1.first[axis]; + }; + + auto begin = mSortedPoints.begin() + siteOffset; + auto mid = mSortedPoints.begin() + siteOffset + halfNumSites; + auto end = mSortedPoints.begin() + siteOffset + numSites; + std::nth_element(begin, mid, end, sorter); + + // Get the median position. + node.split = mSortedPoints[siteOffset + halfNumSites].first[axis]; + node.axis = axis; + node.siteOffset = -1; + + // Apply a divide-and-conquer step. + int left = (int)mNodes.size(), right = left + 1; + node.left = left; + node.right = right; + mNodes.push_back(Node()); + mNodes.push_back(Node()); + + int nextLevel = level + 1; + Build(halfNumSites, siteOffset, left, nextLevel); + Build(numSites - halfNumSites, siteOffset + halfNumSites, right, nextLevel); + } + else + { + // The number of points is small enough, so make this node a leaf. + node.split = std::numeric_limits::max(); + node.axis = -1; + node.siteOffset = siteOffset; + node.left = -1; + node.right = -1; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOBBTreeOfPoints.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOBBTreeOfPoints.h new file mode 100644 index 000000000000..fafd3900ca6e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOBBTreeOfPoints.h @@ -0,0 +1,359 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.10.1 (2017/09/16) + +#pragma once + +#include +#include + +// The depth of a node in a (nonempty) tree is the distance from the node to +// the root of the tree. The height is the maximum depth. A tree with a +// single node has height 0. The set of nodes of a tree with the same depth +// is refered to as a level of a tree (corresponding to that depth). A +// complete binary tree of height H has 2^{H+1}-1 nodes. The level +// corresponding to depth D has 2^D nodes, in which case the number of +// leaf nodes (depth H) is 2^H. + +namespace gte +{ + +template +class OBBTreeForPoints +{ +public: + struct Node + { + Node(); + OrientedBox3 box; + uint32_t depth; + uint32_t minIndex, maxIndex; + uint32_t leftChild, rightChild; + }; + + + // The 'points' array is a collection of vertices, each occupying a + // chunk of memory with 'stride' bytes. A vertex must start at the + // first byte of this chunk but does not necessarily fill it. The + // 'height' specifies the height of the tree and must be no larger + // than 31. If it is set to std::numeric_limits::max(), + // then the entire tree is built and the actual height is computed + // from 'numPoints'. + OBBTreeForPoints(uint32_t numPoints, char const* points, size_t stride, + uint32_t height = std::numeric_limits::max()); + + // Member access. + inline uint32_t GetNumPoints() const; + inline char const* GetPoints() const; + inline size_t GetStride() const; + inline std::vector const& GetTree() const; + inline uint32_t GetHeight() const; + inline std::vector const& GetPartition() const; + +private: + inline Vector3 GetPosition(uint32_t index) const; + void BuildTree(Node& node, uint32_t i0, uint32_t i1); + void ComputeOBB(uint32_t i0, uint32_t i1, OrientedBox3& box); + void SplitPoints(uint32_t i0, uint32_t i1, uint32_t& j0, uint32_t& j1, + Vector3 const& origin, Vector3 const& direction); + + struct ProjectionInfo + { + ProjectionInfo(); + bool operator< (ProjectionInfo const& info) const; + uint32_t pointIndex; + Real projection; + }; + + uint32_t mNumPoints; + char const* mPoints; + size_t mStride; + uint32_t mHeight; + std::vector mTree; + std::vector mPartition; +}; + +template +OBBTreeForPoints::OBBTreeForPoints(uint32_t numPoints, char const* points, size_t stride, uint32_t height) + : + mNumPoints(numPoints), + mPoints(points), + mStride(stride), + mHeight(height), + mPartition(numPoints) +{ + // The tree nodes are indexed by 32-bit unsigned integers, so + // the number of nodes can be at most 2^{32} - 1. This limits + // the height to 31. + + uint32_t numNodes; + if (mHeight == std::numeric_limits::max()) + { + uint32_t minPowerOfTwo = (uint32_t)RoundUpToPowerOfTwo(mNumPoints); + mHeight = Log2OfPowerOfTwo(minPowerOfTwo); + numNodes = 2 * mNumPoints - 1; + } + else + { + // The maximum level cannot exceed 30 because we are storing the + // indices into the node array as 32-bit unsigned integers. + if (mHeight < 32) + { + numNodes = (uint32_t)(1ULL << (mHeight + 1)) - 1; + } + else + { + // When the precondition is not met, return a tree of + // height 0 (a single node). + mHeight = 0; + numNodes = 1; + } + } + + // The tree is built recursively. A reference to a Node is passed + // to BuildTree and nodes are appended to a std::vector. Because the + // references are on the stack, we must guarantee no reallocations + // to avoid invalidating the references. TODO: This design can be + // modified to pass indices to the nodes so that reallocation is not + // a problem. + mTree.reserve(numNodes); + + // Build the tree recursively. The array mPartition stores the + // indices into the 'points' array so that at a node, the points + // represented by the node are those indexed by the range + // [node.minIndex, node.maxIndex]. + for (uint32_t i = 0; i < mNumPoints; ++i) + { + mPartition[i] = i; + } + mTree.push_back(Node()); + BuildTree(mTree.back(), 0, mNumPoints - 1); +} + +template +inline uint32_t OBBTreeForPoints::GetNumPoints() const +{ + return mNumPoints; +} + +template +inline char const* OBBTreeForPoints::GetPoints() const +{ + return mPoints; +} + +template +inline size_t OBBTreeForPoints::GetStride() const +{ + return mStride; +} + +template +inline std::vector::Node> const& OBBTreeForPoints::GetTree() const +{ + return mTree; +} + +template +inline uint32_t OBBTreeForPoints::GetHeight() const +{ + return mHeight; +} + +template +inline std::vector const& OBBTreeForPoints::GetPartition() const +{ + return mPartition; +} + +template +inline Vector3 OBBTreeForPoints::GetPosition(uint32_t index) const +{ + return *reinterpret_cast const*>(mPoints + index * mStride); +} + +template +void OBBTreeForPoints::BuildTree(Node& node, uint32_t i0, uint32_t i1) +{ + node.minIndex = i0; + node.maxIndex = i1; + + if (i0 == i1) + { + // We are at a leaf node. The left and right child indices were + // set to std::numeric_limits::max() during construction. + + // Create a degenerate box whose center is the point. + node.box.center = GetPosition(mPartition[i0]); + node.box.axis[0] = Vector3{ (Real)1, (Real)0, (Real)0 }; + node.box.axis[1] = Vector3{ (Real)0, (Real)1, (Real)0 }; + node.box.axis[2] = Vector3{ (Real)0, (Real)0, (Real)1 }; + node.box.extent = Vector3{ (Real)0, (Real)0, (Real)0 }; + } + else // i0 < i1 + { + // We are at an interior node. Compute an oriented bounding box. + ComputeOBB(i0, i1, node.box); + + if (node.depth == mHeight) + { + return; + } + + // Use the box axis corresponding to largest extent for the + // splitting axis. Partition the points into two subsets, one for + // the left child and one for the right child. The subsets have + // numbers of elements that differ by at most 1, so the tree is + // balanced. + Vector3 axis2 = node.box.axis[2]; + uint32_t j0, j1; + SplitPoints(i0, i1, j0, j1, node.box.center, axis2); + + node.leftChild = static_cast(mTree.size()); + node.rightChild = node.leftChild + 1; + mTree.push_back(Node()); + Node& leftTree = mTree.back(); + mTree.push_back(Node()); + Node& rightTree = mTree.back(); + leftTree.depth = node.depth + 1; + rightTree.depth = node.depth + 1; + BuildTree(leftTree, i0, j0); + BuildTree(rightTree, j1, i1); + } +} + +template +void OBBTreeForPoints::ComputeOBB(uint32_t i0, uint32_t i1, OrientedBox3& box) +{ + // Compute the mean of the points. + Vector3 zero{ (Real)0, (Real)0, (Real)0 }; + box.center = zero; + for (uint32_t i = i0; i <= i1; ++i) + { + box.center += GetPosition(mPartition[i]); + } + Real invSize = ((Real)1) / (Real)(i1 - i0 + 1); + box.center *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (uint32_t i = i0; i <= i1; ++i) + { + Vector3 diff = GetPosition(mPartition[i]) - box.center; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem and use the eigenvectors for the box axes. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, eval, evec); + for (int i = 0; i < 3; ++i) + { + box.axis[i] = evec[i]; + } + box.extent = eval; + + // Let C be the box center and let U0, U1, and U2 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. + // The following code computes min(y0), max(y0), min(y1), max(y1), + // min(y2), and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + Vector3 pmin = zero, pmax = zero; + for (uint32_t i = i0; i <= i1; ++i) + { + Vector3 diff = GetPosition(mPartition[i]) - box.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + Real const half(0.5); + for (int j = 0; j < 3; ++j) + { + box.center += (half * (pmin[j] + pmax[j])) * box.axis[j]; + box.extent[j] = half * (pmax[j] - pmin[j]); + } +} + +template +void OBBTreeForPoints::SplitPoints(uint32_t i0, uint32_t i1, uint32_t& j0, uint32_t& j1, + Vector3 const& origin, Vector3 const& direction) +{ + // Project the points onto the splitting axis. + uint32_t numProjections = i1 - i0 + 1; + std::vector info(numProjections); + uint32_t i, k; + for (i = i0, k = 0; i <= i1; ++i, ++k) + { + Vector3 diff = GetPosition(mPartition[i]) - origin; + info[k].pointIndex = mPartition[i]; + info[k].projection = Dot(direction, diff); + } + + // Partition the projections by the median. + uint32_t medianIndex = (numProjections - 1) / 2; + std::nth_element(info.begin(), info.begin() + medianIndex, info.end()); + + // Partition the points by the median. + for (k = 0, j0 = i0 - 1; k <= medianIndex; ++k) + { + mPartition[++j0] = info[k].pointIndex; + } + for (j1 = i1 + 1; k < numProjections; ++k) + { + mPartition[--j1] = info[k].pointIndex; + } +} + +template +OBBTreeForPoints::Node::Node() + : + depth(0), + minIndex(std::numeric_limits::max()), + maxIndex(std::numeric_limits::max()), + leftChild(std::numeric_limits::max()), + rightChild(std::numeric_limits::max()) +{ +} + +template +OBBTreeForPoints::ProjectionInfo::ProjectionInfo() + : + pointIndex(0), + projection((Real)0) +{ +} + +template +bool OBBTreeForPoints::ProjectionInfo::operator< (ProjectionInfo const& info) const +{ + return projection < info.projection; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeEuler.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeEuler.h new file mode 100644 index 000000000000..eb832ef3b2ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeEuler.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace gte +{ + +template +class OdeEuler : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeEuler(); + OdeEuler(Real tDelta, + std::function const& F); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); +}; + + +template +OdeEuler::~OdeEuler() +{ +} + +template +OdeEuler::OdeEuler(Real tDelta, + std::function const& F) + : + OdeSolver(tDelta, F) +{ +} + +template +void OdeEuler::Update(Real tIn, TVector const& xIn, + Real& tOut, TVector& xOut) +{ + TVector fVector = this->mFunction(tIn, xIn); + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeImplicitEuler.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeImplicitEuler.h new file mode 100644 index 000000000000..c6cb571819ea --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeImplicitEuler.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. The TMatrix parameter must be either Matrix +// or GMatrix accordingly. +// +// The function F(t,x) has input t, a scalar, and input x, an N-vector. +// The first derivative matrix with respect to x is DF(t,x), an +// N-by-N matrix. Entry DF(r,c) is the derivative of F[r] with +// respect to x[c]. + +namespace gte +{ + +template +class OdeImplicitEuler : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeImplicitEuler(); + OdeImplicitEuler(Real tDelta, + std::function const& F, + std::function const& DF); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); + +private: + std::function mDerivativeFunction; +}; + + +template +OdeImplicitEuler::~OdeImplicitEuler() +{ +} + +template +OdeImplicitEuler::OdeImplicitEuler(Real tDelta, + std::function const& F, + std::function const& DF) + : + OdeSolver(tDelta, F), + mDerivativeFunction(DF) +{ +} + +template +void OdeImplicitEuler::Update(Real tIn, + TVector const& xIn, Real& tOut, TVector& xOut) +{ + TVector fVector = this->mFunction(tIn, xIn); + TMatrix dfMatrix = mDerivativeFunction(tIn, xIn); + TMatrix dgMatrix = TMatrix::Identity() - this->mTDelta * dfMatrix; + TMatrix dgInverse = Inverse(dgMatrix); + fVector = dgInverse * fVector; + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeMidpoint.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeMidpoint.h new file mode 100644 index 000000000000..56357772c983 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeMidpoint.h @@ -0,0 +1,67 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace gte +{ + +template +class OdeMidpoint : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeMidpoint(); + OdeMidpoint(Real tDelta, + std::function const& F); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); +}; + + +template +OdeMidpoint::~OdeMidpoint() +{ +} + +template +OdeMidpoint::OdeMidpoint(Real tDelta, + std::function const& F) + : + OdeSolver(tDelta, F) +{ +} + +template +void OdeMidpoint::Update(Real tIn, TVector const& xIn, + Real& tOut, TVector& xOut) +{ + // Compute the first step. + Real halfTDelta = ((Real)0.5) * this->mTDelta; + TVector fVector = this->mFunction(tIn, xIn); + TVector xTemp = xIn + halfTDelta * fVector; + + // Compute the second step. + Real halfT = tIn + halfTDelta; + fVector = this->mFunction(halfT, xTemp); + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeRungeKutta4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeRungeKutta4.h new file mode 100644 index 000000000000..100ee5a047c1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeRungeKutta4.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace gte +{ + +template +class OdeRungeKutta4 : public OdeSolver +{ +public: + // Construction and destruction. + virtual ~OdeRungeKutta4(); + OdeRungeKutta4(Real tDelta, + std::function const& F); + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may allow + // xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut); +}; + + +template +OdeRungeKutta4::~OdeRungeKutta4() +{ +} + +template +OdeRungeKutta4::OdeRungeKutta4(Real tDelta, + std::function const& F) + : + OdeSolver(tDelta, F) +{ +} + +template +void OdeRungeKutta4::Update(Real tIn, TVector const& xIn, + Real& tOut, TVector& xOut) +{ + // Compute the first step. + Real halfTDelta = ((Real)0.5) * this->mTDelta; + TVector fTemp1 = this->mFunction(tIn, xIn); + TVector xTemp = xIn + halfTDelta * fTemp1; + + // Compute the second step. + Real halfT = tIn + halfTDelta; + TVector fTemp2 = this->mFunction(halfT, xTemp); + xTemp = xIn + halfTDelta * fTemp2; + + // Compute the third step. + TVector fTemp3 = this->mFunction(halfT, xTemp); + xTemp = xIn + this->mTDelta * fTemp3; + + // Compute the fourth step. + Real sixthTDelta = this->mTDelta / (Real)6; + tOut = tIn + this->mTDelta; + TVector fTemp4 = this->mFunction(tOut, xTemp); + xOut = xIn + sixthTDelta * ( + fTemp1 + ((Real)2)*(fTemp2 + fTemp3) + fTemp4); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeSolver.h new file mode 100644 index 000000000000..6d12cd42bf70 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOdeSolver.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The differential equation is dx/dt = F(t,x). The TVector template +// parameter allows you to create solvers with Vector when the +// dimension N is known at compile time or GVector when the dimension +// N is known at run time. Both classes have 'int GetSize() const' that +// allow OdeSolver-derived classes to query for the dimension. + +namespace gte +{ + +template +class OdeSolver +{ +public: + // Abstract base class. +public: + virtual ~OdeSolver(); +protected: + OdeSolver(Real tDelta, + std::function const& F); + +public: + // Member access. + inline void SetTDelta(Real tDelta); + inline Real GetTDelta() const; + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). The + // derived classes implement this so that it is possible for xIn and + // xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, + TVector& xOut) = 0; + +protected: + Real mTDelta; + std::function mFunction; +}; + + +template +OdeSolver::~OdeSolver() +{ +} + +template +OdeSolver::OdeSolver(Real tDelta, + std::function const& F) + : + mTDelta(tDelta), + mFunction(F) +{ +} + +template inline +void OdeSolver::SetTDelta(Real tDelta) +{ + mTDelta = tDelta; +} + +template inline +Real OdeSolver::GetTDelta() const +{ + return mTDelta; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOrientedBox.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOrientedBox.h new file mode 100644 index 000000000000..9ee2e8e83587 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteOrientedBox.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// A box has center C, axis directions U[i], and extents e[i]. The set +// {U[0],...,U[N-1]} is orthonormal, which means the vectors are +// unit-length and mutually perpendicular. The extents are nonnegative; +// zero is allowed, meaning the box is degenerate in the corresponding +// direction. A point X is represented in box coordinates by +// X = C + y[0]*U[0] + y[1]*U[1]. This point is inside or on the +// box whenever |y[i]| <= e[i] for all i. + +namespace gte +{ + +template +class OrientedBox +{ +public: + // Construction and destruction. The default constructor sets the center + // to (0,...,0), axis d to Vector::Unit(d), and extent d to +1. + OrientedBox(); + OrientedBox(Vector const& inCenter, + std::array, N> const& inAxis, + Vector const& inExtent); + + // Compute the vertices of the box. If index i has the bit pattern + // i = b[N-1]...b[0], then + // vertex[i] = center + sum_{d=0}^{N-1} sign[d] * extent[d] * axis[d] + // where sign[d] = 2*b[d] - 1. + void GetVertices(std::array, (1 << N)>& vertex) const; + + // Public member access. It is required that extent[i] >= 0. + Vector center; + std::array, N> axis; + Vector extent; + +public: + // Comparisons to support sorted containers. + bool operator==(OrientedBox const& box) const; + bool operator!=(OrientedBox const& box) const; + bool operator< (OrientedBox const& box) const; + bool operator<=(OrientedBox const& box) const; + bool operator> (OrientedBox const& box) const; + bool operator>=(OrientedBox const& box) const; +}; + +// Template aliases for convenience. +template +using OrientedBox2 = OrientedBox<2, Real>; + +template +using OrientedBox3 = OrientedBox<3, Real>; + + +template +OrientedBox::OrientedBox() +{ + center.MakeZero(); + for (int i = 0; i < N; ++i) + { + axis[i].MakeUnit(i); + extent[i] = (Real)1; + } +} + +template +OrientedBox::OrientedBox(Vector const& inCenter, + std::array, N> const& inAxis, + Vector const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) +{ +} + +template +void OrientedBox::GetVertices( + std::array, (1 << N)>& vertex) const +{ + unsigned int const dsup = static_cast(N); + std::array, N> product; + for (unsigned int d = 0; d < dsup; ++d) + { + product[d] = extent[d] * axis[d]; + } + + int const imax = (1 << N); + for (int i = 0; i < imax; ++i) + { + vertex[i] = center; + for (unsigned int d = 0, mask = 1; d < dsup; ++d, mask <<= 1) + { + Real sign = (i & mask ? (Real)1 : (Real)-1); + vertex[i] += sign * product[d]; + } + } +} + +template +bool OrientedBox::operator==(OrientedBox const& box) const +{ + return center == box.center && axis == box.axis && extent == box.extent; +} + +template +bool OrientedBox::operator!=(OrientedBox const& box) const +{ + return !operator==(box); +} + +template +bool OrientedBox::operator<(OrientedBox const& box) const +{ + if (center < box.center) + { + return true; + } + + if (center > box.center) + { + return false; + } + + if (axis < box.axis) + { + return true; + } + + if (axis > box.axis) + { + return false; + } + + return extent < box.extent; +} + +template +bool OrientedBox::operator<=(OrientedBox const& box) const +{ + return operator<(box) || operator==(box); +} + +template +bool OrientedBox::operator>(OrientedBox const& box) const +{ + return !operator<=(box); +} + +template +bool OrientedBox::operator>=(OrientedBox const& box) const +{ + return !operator<(box); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricCurve.h new file mode 100644 index 000000000000..e05f567eb6cd --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricCurve.h @@ -0,0 +1,355 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class ParametricCurve +{ +protected: + // Abstract base class for a parameterized curve X(t), where t is the + // parameter in [tmin,tmax] and X is an N-tuple position. The first + // constructor is for single-segment curves. The second constructor is + // for multiple-segment curves. The times must be strictly increasing. + ParametricCurve(Real tmin, Real tmax); + ParametricCurve(int numSegments, Real const* times); +public: + virtual ~ParametricCurve(); + + // To validate construction, create an object as shown: + // DerivedClassCurve curve(parameters); + // if (!curve) { ; } + inline operator bool() const; + + // Member access. + inline Real GetTMin() const; + inline Real GetTMax() const; + inline int GetNumSegments() const; + Real const* GetTimes() const; + + // This function applies only when the first constructor is used (two + // times rather than a sequence of three or more times). + void SetTimeInterval(Real tmin, Real tmax); + + // Parameters used in GetLength(...), GetTotalLength(), and GetTime(...). + void SetRombergOrder(int order); // default = 8 + void SetMaxBisections(unsigned int maxBisections); // default = 1024 + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const = 0; + + // Differential geometric quantities. + Vector GetPosition(Real t) const; + Vector GetTangent(Real t) const; + Real GetSpeed(Real t) const; + Real GetLength(Real t0, Real t1) const; + Real GetTotalLength() const; + + // Inverse mapping of s = Length(t) given by t = Length^{-1}(s). The + // inverse length function is generally a function that cannot be written + // in closed form, in which case it is not directly computable. Instead, + // we can specify s and estimate the root t for F(t) = Length(t) - s. The + // derivative is F'(t) = Speed(t) >= 0, so F(t) is nondecreasing. To be + // robust, we use bisection to locate the root, although it is possible to + // use a hybrid of Newton's method and bisection. + Real GetTime(Real length) const; + + // Compute a subset of curve points according to the specified attribute. + // The input 'numPoints' must be two or larger. + void SubdivideByTime(int numPoints, Vector* points) const; + void SubdivideByLength(int numPoints, Vector* points) const; + +protected: + enum + { + DEFAULT_ROMBERG_ORDER = 8, + DEFAULT_MAX_BISECTIONS = 1024 + }; + + std::vector mTime; + mutable std::vector mSegmentLength; + mutable std::vector mAccumulatedLength; + int mRombergOrder; + unsigned int mMaxBisections; + + bool mConstructed; +}; + + +template +ParametricCurve::ParametricCurve(Real tmin, Real tmax) + : + mTime(2), + mSegmentLength(1), + mAccumulatedLength(1), + mRombergOrder(DEFAULT_ROMBERG_ORDER), + mMaxBisections(DEFAULT_MAX_BISECTIONS), + mConstructed(false) +{ + mTime[0] = tmin; + mTime[1] = tmax; + mSegmentLength[0] = (Real)0; + mAccumulatedLength[0] = (Real)0; +} + +template +ParametricCurve::ParametricCurve(int numSegments, Real const* times) + : + mTime(numSegments + 1), + mSegmentLength(numSegments), + mAccumulatedLength(numSegments), + mRombergOrder(DEFAULT_ROMBERG_ORDER), + mMaxBisections(DEFAULT_MAX_BISECTIONS), + mConstructed(false) +{ + std::copy(times, times + numSegments + 1, mTime.begin()); + mSegmentLength[0] = (Real)0; + mAccumulatedLength[0] = (Real)0; +} + +template +ParametricCurve::~ParametricCurve() +{ +} + +template inline +ParametricCurve::operator bool() const +{ + return mConstructed; +} + +template inline +Real ParametricCurve::GetTMin() const +{ + return mTime.front(); +} + +template inline +Real ParametricCurve::GetTMax() const +{ + return mTime.back(); +} + +template inline +int ParametricCurve::GetNumSegments() const +{ + return static_cast(mSegmentLength.size()); +} + +template inline +Real const* ParametricCurve::GetTimes() const +{ + return &mTime[0]; +} + +template +void ParametricCurve::SetTimeInterval(Real tmin, Real tmax) +{ + if (mTime.size() == 2) + { + mTime[0] = tmin; + mTime[1] = tmax; + } +} + +template +void ParametricCurve::SetRombergOrder(int order) +{ + mRombergOrder = std::max(order, 1); +} + +template +void ParametricCurve::SetMaxBisections(unsigned int maxBisections) +{ + mMaxBisections = std::max(maxBisections, 1u); +} + +template +Vector ParametricCurve::GetPosition(Real t) const +{ + Vector values[4]; + Evaluate(t, 0, values); + return values[0]; +} + +template +Vector ParametricCurve::GetTangent(Real t) const +{ + Vector values[4]; + Evaluate(t, 1, values); + Normalize(values[1]); + return values[1]; +} + +template +Real ParametricCurve::GetSpeed(Real t) const +{ + Vector values[4]; + Evaluate(t, 1, values); + return Length(values[1]); +} + +template +Real ParametricCurve::GetLength(Real t0, Real t1) const +{ + std::function speed = [this](Real t) + { + return GetSpeed(t); + }; + + if (mSegmentLength[0] == (Real)0) + { + // Lazy initialization of lengths of segments. + int const numSegments = static_cast(mSegmentLength.size()); + Real accumulated = (Real)0; + for (int i = 0; i < numSegments; ++i) + { + mSegmentLength[i] = Integration::Romberg(mRombergOrder, + mTime[i], mTime[i + 1], speed); + accumulated += mSegmentLength[i]; + mAccumulatedLength[i] = accumulated; + } + } + + t0 = std::max(t0, GetTMin()); + t1 = std::min(t1, GetTMax()); + auto iter0 = std::lower_bound(mTime.begin(), mTime.end(), t0); + int index0 = static_cast(iter0 - mTime.begin()); + auto iter1 = std::lower_bound(mTime.begin(), mTime.end(), t1); + int index1 = static_cast(iter1 - mTime.begin()); + + Real length; + if (index0 < index1) + { + length = (Real)0; + if (t0 < *iter0) + { + length += Integration::Romberg(mRombergOrder, t0, + mTime[index0], speed); + } + + int isup; + if (t1 < *iter1) + { + length += Integration::Romberg(mRombergOrder, + mTime[index1 - 1], t1, speed); + isup = index1 - 1; + } + else + { + isup = index1; + } + for (int i = index0; i < isup; ++i) + { + length += mSegmentLength[i]; + } + } + else + { + length = Integration::Romberg(mRombergOrder, t0, t1, speed); + } + return length; +} + +template +Real ParametricCurve::GetTotalLength() const +{ + if (mAccumulatedLength.back() == (Real)0) + { + // Lazy evaluation of the accumulated length array. + return GetLength(mTime.front(), mTime.back()); + } + + return mAccumulatedLength.back(); +} + +template +Real ParametricCurve::GetTime(Real length) const +{ + if (length > (Real)0) + { + if (length < GetTotalLength()) + { + std::function F = [this, &length](Real t) + { + return Integration::Romberg(mRombergOrder, + mTime.front(), t, [this](Real z){ return GetSpeed(z); }) + - length; + }; + + // We know that F(tmin) < 0 and F(tmax) > 0, which allows us to + // use bisection. Rather than bisect the entire interval, let's + // narrow it down with a reasonable initial guess. + Real ratio = length / GetTotalLength(); + Real omratio = (Real)1 - ratio; + Real tmid = omratio * mTime.front() + ratio * mTime.back(); + Real fmid = F(tmid); + if (fmid > (Real)0) + { + RootsBisection::Find(F, mTime.front(), tmid, (Real)-1, + (Real)1, mMaxBisections, tmid); + } + else if (fmid < (Real)0) + { + RootsBisection::Find(F, tmid, mTime.back(), (Real)-1, + (Real)1, mMaxBisections, tmid); + } + return tmid; + } + else + { + return mTime.back(); + } + } + else + { + return mTime.front(); + } +} + +template +void ParametricCurve::SubdivideByTime(int numPoints, + Vector* points) const +{ + Real delta = (mTime.back() - mTime.front()) / (Real)(numPoints - 1); + for (int i = 0; i < numPoints; ++i) + { + Real t = mTime.front() + delta * i; + points[i] = GetPosition(t); + } +} + +template +void ParametricCurve::SubdivideByLength(int numPoints, + Vector* points) const +{ + Real delta = GetTotalLength() / (Real)(numPoints - 1); + for (int i = 0; i < numPoints; ++i) + { + Real length = delta * i; + Real t = GetTime(length); + points[i] = GetPosition(t); + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricSurface.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricSurface.h new file mode 100644 index 000000000000..18c18e39dce9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteParametricSurface.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +namespace gte +{ + +template +class ParametricSurface +{ +protected: + // Abstract base class for a parameterized surface X(u,v). The parametric + // domain is either rectangular or triangular. Valid (u,v) values for a + // rectangular domain satisfy + // umin <= u <= umax, vmin <= v <= vmax + // and valid (u,v) values for a triangular domain satisfy + // umin <= u <= umax, vmin <= v <= vmax, + // (vmax-vmin)*(u-umin)+(umax-umin)*(v-vmax) <= 0 + ParametricSurface(Real umin, Real umax, Real vmin, Real vmax, + bool rectangular); +public: + virtual ~ParametricSurface(); + + // To validate construction, create an object as shown: + // DerivedClassSurface surface(parameters); + // if (!surface) { ; } + inline operator bool() const; + + // Member access. + inline Real GetUMin() const; + inline Real GetUMax() const; + inline Real GetVMin() const; + inline Real GetVMax() const; + inline bool IsRectangular() const; + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv; second-order derivatives d2X/du2, d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int maxOrder, + Vector values[6]) const = 0; + + // Differential geometric quantities. + Vector GetPosition(Real u, Real v) const; + Vector GetUTangent(Real u, Real v) const; + Vector GetVTangent(Real u, Real v) const; + +protected: + Real mUMin, mUMax, mVMin, mVMax; + bool mRectangular; + bool mConstructed; +}; + + +template +ParametricSurface::ParametricSurface(Real umin, Real umax, + Real vmin, Real vmax, bool rectangular) + : + mUMin(umin), + mUMax(umax), + mVMin(vmin), + mVMax(vmax), + mRectangular(rectangular) +{ +} + +template +ParametricSurface::~ParametricSurface() +{ +} + +template +ParametricSurface::operator bool() const +{ + return mConstructed; +} + +template inline +Real ParametricSurface::GetUMin() const +{ + return mUMin; +} + +template inline +Real ParametricSurface::GetUMax() const +{ + return mUMax; +} + +template inline +Real ParametricSurface::GetVMin() const +{ + return mVMin; +} + +template inline +Real ParametricSurface::GetVMax() const +{ + return mVMax; +} + +template inline +bool ParametricSurface::IsRectangular() const +{ + return mRectangular; +} + +template inline +Vector ParametricSurface::GetPosition(Real u, Real v) const +{ + Vector values[6]; + Evaluate(u, v, 0, values); + return values[0]; +} + +template inline +Vector ParametricSurface::GetUTangent(Real u, Real v) const +{ + Vector values[6]; + Evaluate(u, v, 1, values); + Normalize(values[1]); + return values[1]; +} + +template inline +Vector ParametricSurface::GetVTangent(Real u, Real v) const +{ + Vector values[6]; + Evaluate(u, v, 1, values); + Normalize(values[2]); + return values[2]; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePlanarMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePlanarMesh.h new file mode 100644 index 000000000000..60525b22394e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePlanarMesh.h @@ -0,0 +1,500 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The planar mesh class is convenient for many applications involving +// searches for triangles containing a specified point. A couple of +// issues can show up in practice when the input data to the constructors +// is very large (number of triangles on the order of 10^5 or larger). +// +// The first constructor builds an ETManifoldMesh mesh that contains +// std::map objects. When such maps are large, the amount of time it +// takes to delete them is enormous. Although you can control the level +// of debug support in MSVS 2013 (see _ITERATOR_DEBUG_LEVEL), turning off +// checking might very well affect other classes for which you want +// iterator checking to be on. An alternative to reduce debugging time +// is to dynamically allocate the PlanarMesh object in the main thread but +// then launch another thread to delete the object and avoid stalling +// the main thread. For example, +// +// PlanarMesh* pmesh = +// new PlanarMesh(numV, vertices, numT, indices); +// ; +// std::thread deleter = [pmesh](){ delete pmesh; }; +// deleter.detach(); // Do not wait for the thread to finish. +// +// The second constructor has the mesh passed in, but mTriIndexMap is used +// in both constructors and can take time to delete. +// +// The input mesh should be consistently oriented, say, the triangles are +// counterclockwise ordered. The vertices should be consistent with this +// ordering. However, floating-point rounding errors in generating the +// vertices can cause apparent fold-over of the mesh; that is, theoretically +// the vertex geometry supports counterclockwise geometry but numerical +// errors cause an inconsistency. This can manifest in the mQuery.ToLine +// tests whereby cycles of triangles occur in the linear walk. When cycles +// occur, GetContainingTriangle(P,startTriangle) will iterate numTriangle +// times before reporting that the triangle cannot be found, which is a +// very slow process (in debug or release builds). The function +// GetContainingTriangle(P,startTriangle,visited) is provided to avoid the +// performance loss, trapping a cycle the first time and exiting, but +// again reporting that the triangle cannot be found. If you know that the +// query should be (theoretically) successful, use the second version of +// GetContainingTriangle. If it fails by returning -1, then perform an +// exhaustive search over the triangles. For example, +// +// int triangle = pmesh->GetContainingTriangle(P,startTriangle,visited); +// if (triangle >= 0) +// { +// ; +// } +// else +// { +// int numTriangles = pmesh->GetNumTriangles(); +// for (triangle = 0; triangle < numTriangles; ++triangle) +// { +// if (pmesh->Contains(triangle, P)) +// { +// ; +// break; +// } +// } +// if (triangle == numTriangles) +// { +// ; +// } +// } +// +// The PlanarMesh<*>::Contains function does not require the triangles to +// be ordered. + +namespace gte +{ + +template +class PlanarMesh +{ +public: + // Construction. The inputs must represent a manifold mesh of triangles + // in the plane. The index array must have 3*numTriangles elements, each + // triple of indices representing a triangle in the mesh. Each index is + // into the 'vertices' array. + PlanarMesh(int numVertices, Vector2 const* vertices, int numTriangles, int const* indices); + PlanarMesh(int numVertices, Vector2 const* vertices, ETManifoldMesh const& mesh); + + // Mesh information. + inline int GetNumVertices() const; + inline int GetNumTriangles() const; + inline Vector2 const* GetVertices() const; + inline int const* GetIndices() const; + inline int const* GetAdjacencies() const; + + // Containment queries. The function GetContainingTriangle works + // correctly when the planar mesh is a convex set. If the mesh is not + // convex, it is possible that the linear-walk search algorithm exits the + // mesh before finding a containing triangle. For example, a C-shaped + // mesh can contain a point in the top branch of the "C". A starting + // point in the bottom branch of the "C" will lead to the search exiting + // the bottom branch and having no path to walk to the top branch. If + // your mesh is not convex and you want a correct containment query, you + // will have to append "outside" triangles to your mesh to form a convex + // set. + int GetContainingTriangle(Vector2 const& P, int startTriangle = 0) const; + int GetContainingTriangle(Vector2 const& P, int startTriangle, std::set& visited) const; + bool GetVertices(int t, std::array, 3>& vertices) const; + bool GetIndices(int t, std::array& indices) const; + bool GetAdjacencies(int t, std::array& adjacencies) const; + bool GetBarycentrics(int t, Vector2 const& P, std::array& bary) const; + bool Contains(int triangle, Vector2 const& P) const; + +public: + void CreateVertices(int numVertices, Vector2 const* vertices); + + int mNumVertices; + Vector2 const* mVertices; + int mNumTriangles; + std::vector mIndices; + ETManifoldMesh mMesh; + std::map, int> mTriIndexMap; + std::vector mAdjacencies; + std::vector> mComputeVertices; + PrimalQuery2 mQuery; +}; + + +template +PlanarMesh::PlanarMesh(int numVertices, + Vector2 const* vertices, int numTriangles, int const* indices) + : + mNumVertices(0), + mVertices(nullptr), + mNumTriangles(0) +{ + if (numVertices < 3 || !vertices || numTriangles < 1 || !indices) + { + LogError("Invalid input."); + return; + } + + // Create a mesh in order to get adjacency information. + int const* current = indices; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *current++; + int v1 = *current++; + int v2 = *current++; + if (!mMesh.Insert(v0, v1, v2)) + { + // The 'mesh' object will assert on nonmanifold inputs. + return; + } + } + + // We have a valid mesh. + CreateVertices(numVertices, vertices); + + // Build the adjacency graph using the triangle ordering implied by the + // indices, not the mesh triangle map, to preserve the triangle ordering + // of the input indices. + mNumTriangles = numTriangles; + int const numIndices = 3 * numTriangles; + mIndices.resize(numIndices); + + std::copy(indices, indices + numIndices, mIndices.begin()); + for (int t = 0, vIndex = 0; t < numTriangles; ++t) + { + int v0 = indices[vIndex++]; + int v1 = indices[vIndex++]; + int v2 = indices[vIndex++]; + TriangleKey key(v0, v1, v2); + mTriIndexMap.insert(std::make_pair(key, t)); + } + + mAdjacencies.resize(numIndices); + auto const& tmap = mMesh.GetTriangles(); + for (int t = 0, base = 0; t < numTriangles; ++t, base += 3) + { + int v0 = indices[base]; + int v1 = indices[base + 1]; + int v2 = indices[base + 2]; + TriangleKey key(v0, v1, v2); + auto element = tmap.find(key); + for (int i = 0; i < 3; ++i) + { + auto adj = element->second->T[i].lock(); + if (adj) + { + key = TriangleKey(adj->V[0], adj->V[1], adj->V[2]); + mAdjacencies[base + i] = mTriIndexMap.find(key)->second; + } + else + { + mAdjacencies[base + i] = -1; + } + } + } +} + +template +PlanarMesh::PlanarMesh(int numVertices, + Vector2 const* vertices, ETManifoldMesh const& mesh) + : + mNumVertices(0), + mVertices(nullptr), + mNumTriangles(0) +{ + if (numVertices < 3 || !vertices || mesh.GetTriangles().size() < 1) + { + LogError("Invalid input."); + return; + } + + // We have a valid mesh. + CreateVertices(numVertices, vertices); + + // Build the adjacency graph using the triangle ordering implied by the + // mesh triangle map. + auto const& tmap = mesh.GetTriangles(); + mNumTriangles = static_cast(tmap.size()); + mIndices.resize(3 * mNumTriangles); + + int tIndex = 0, vIndex = 0; + for (auto const& element : tmap) + { + mTriIndexMap.insert(std::make_pair(element.first, tIndex++)); + for (int i = 0; i < 3; ++i, ++vIndex) + { + mIndices[vIndex] = element.second->V[i]; + } + } + + mAdjacencies.resize(3 * mNumTriangles); + vIndex = 0; + for (auto const& element : tmap) + { + for (int i = 0; i < 3; ++i, ++vIndex) + { + auto adj = element.second->T[i].lock(); + if (adj) + { + TriangleKey key(adj->V[0], adj->V[1], adj->V[2]); + mAdjacencies[vIndex] = mTriIndexMap.find(key)->second; + } + else + { + mAdjacencies[vIndex] = -1; + } + } + } +} + +template +inline int PlanarMesh:: +GetNumVertices() const +{ + return mNumVertices; +} + +template +inline int PlanarMesh::GetNumTriangles() const +{ + return mNumTriangles; +} + +template +inline Vector2 const* PlanarMesh::GetVertices() const +{ + return mVertices; +} + +template +inline int const* PlanarMesh::GetIndices() const +{ + return &mIndices[0]; +} + +template +inline int const* PlanarMesh::GetAdjacencies() const +{ + return &mAdjacencies[0]; +} + +template +int PlanarMesh::GetContainingTriangle( + Vector2 const& P, int startTriangle) const +{ + Vector2 test{ P[0], P[1] }; + + // Use triangle edges as binary separating lines. + int triangle = startTriangle; + for (int i = 0; i < mNumTriangles; ++i) + { + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1) + { + return -1; + } + continue; + } + + return triangle; + } + + return -1; +} + +template +int PlanarMesh::GetContainingTriangle( + Vector2 const& P, int startTriangle, std::set& visited) const +{ + Vector2 test{ P[0], P[1] }; + visited.clear(); + + // Use triangle edges as binary separating lines. + int triangle = startTriangle; + for (int i = 0; i < mNumTriangles; ++i) + { + visited.insert(triangle); + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + return triangle; + } + + return -1; +} + +template +bool PlanarMesh::GetVertices( + int t, std::array, 3>& vertices) const +{ + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + vertices[i] = mVertices[mIndices[vIndex]]; + } + return true; + } + return false; +} + +template +bool PlanarMesh::GetIndices( + int t, std::array& indices) const +{ + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + indices[i] = mIndices[vIndex]; + } + return true; + } + return false; +} + +template +bool PlanarMesh::GetAdjacencies( + int t, std::array& adjacencies) const +{ + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + adjacencies[i] = mAdjacencies[vIndex]; + } + return true; + } + return false; +} + +template +bool PlanarMesh::GetBarycentrics( + int t, Vector2 const& P, std::array& bary) const +{ + std::array indices; + if (GetIndices(t, indices)) + { + Vector2 rtP{ P[0], P[1] }; + std::array, 3> rtV; + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = mComputeVertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[3]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtBary)) + { + for (int i = 0; i < 3; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; +} + +template +bool PlanarMesh::Contains( + int triangle, Vector2 const& P) const +{ + Vector2 test{ P[0], P[1] }; + Vector2 v[3]; + v[0] = mComputeVertices[mIndices[3 * triangle + 0]]; + v[1] = mComputeVertices[mIndices[3 * triangle + 1]]; + v[2] = mComputeVertices[mIndices[3 * triangle + 2]]; + PointInPolygon2 pip(3, v); + return pip.Contains(test); +} + +template +void PlanarMesh::CreateVertices( + int numVertices, Vector2 const* vertices) +{ + mNumVertices = numVertices; + mVertices = vertices; + mComputeVertices.resize(mNumVertices); + for (int i = 0; i < mNumVertices; ++i) + { + for (int j = 0; j < 2; ++j) + { + mComputeVertices[i][j] = (ComputeType)mVertices[i][j]; + } + } + mQuery.Set(mNumVertices, &mComputeVertices[0]); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolygon2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolygon2.h new file mode 100644 index 000000000000..ce1d19f54813 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolygon2.h @@ -0,0 +1,308 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The Polygon2 object represents a simple polygon: No duplicate vertices, +// closed (each vertex is shared by exactly two edges), and no +// self-intersections at interior edge points. The 'vertexPool' array can +// contain more points than needed to define the polygon, which allows the +// vertex pool to have multiple polygons associated with it. Thus, the +// programmer must ensure that the vertex pool persists as long as any +// Polygon2 objects exist that depend on the pool. The number of polygon +// vertices is 'numIndices' and must be 3 or larger. The 'indices' array +// refers to the points in 'vertexPool' that are part of the polygon and must +// have 'numIndices' unique elements. The edges of the polygon are pairs of +// indices into 'vertexPool', +// edge[0] = (indices[0], indices[1]) +// : +// edge[numIndices-2] = (indices[numIndices-2], indices[numIndices-1]) +// edge[numIndices-1] = (indices[numIndices-1], indices[0]) +// The programmer should ensure the polygon is simple. The geometric +// queries are valid regardless of whether the polygon is oriented clockwise +// or counterclockwise. +// +// NOTE: Comparison operators are not provided. The semantics of equal +// polygons is complicated and (at the moment) not useful. The vertex pools +// can be different and indices do not match, but the vertices they reference +// can match. Even with a shared vertex pool, the indices can be "rotated", +// leading to the same polygon abstractly but the data structures do not +// match. + +namespace gte +{ + +template +class Polygon2 +{ +public: + // Construction. The constructor succeeds when 'numIndices' >= 3 and + // 'vertexPool' and 'indices' are not null; we cannot test whether you + // have a valid number of elements in the input arrays. A copy is made + // of 'indices', but the 'vertexPool' is not copied. If the constructor + // fails, the internal vertex pointer is set to null, the index array + // has no elements, and the orientation is set to clockwise. + Polygon2(Vector2 const* vertexPool, int numIndices, + int const* indices, bool counterClockwise); + + // To validate construction, create an object as shown: + // Polygon2 polygon(parameters); + // if (!polygon) { ; } + inline operator bool() const; + + // Member access. + inline Vector2 const* GetVertexPool() const; + inline std::set const& GetVertices() const; + inline std::vector const& GetIndices() const; + inline bool CounterClockwise() const; + + // Geometric queries. + Vector2 ComputeVertexAverage() const; + Real ComputePerimeterLength() const; + Real ComputeArea() const; + + // Simple polygons have no self-intersections at interior points + // of edges. The test is an exhaustive all-pairs intersection + // test for edges, which is inefficient for polygons with a large + // number of vertices. TODO: Provide an efficient algorithm that + // uses the algorithm of class RectangleManager.h. + bool IsSimple() const; + + // Convex polygons are simple polygons where the angles between + // consecutive edges are less than or equal to pi radians. + bool IsConvex() const; + +private: + // These calls have preconditions that mVertexPool is not null and + // mIndices.size() > 3. The heart of the algorithms are implemented + // here. + bool IsSimpleInternal() const; + bool IsConvexInternal() const; + + Vector2 const* mVertexPool; + std::set mVertices; + std::vector mIndices; + bool mCounterClockwise; +}; + + +template +Polygon2::Polygon2(Vector2 const* vertexPool, int numIndices, + int const* indices, bool counterClockwise) + : + mVertexPool(vertexPool), + mCounterClockwise(counterClockwise) +{ + if (numIndices >= 3 && vertexPool && indices) + { + for (int i = 0; i < numIndices; ++i) + { + mVertices.insert(indices[i]); + } + + if (numIndices == static_cast(mVertices.size())) + { + mIndices.resize(numIndices); + std::copy(indices, indices + numIndices, mIndices.begin()); + return; + } + + // At least one duplicated vertex was encountered, so the polygon + // is not simple. Fail the constructor call. + mVertices.clear(); + } + + // Invalid input to the Polygon2 constructor. + mVertexPool = nullptr; + mCounterClockwise = false; +} + +template inline +Polygon2::operator bool() const +{ + return mVertexPool != nullptr; +} + +template inline +Vector2 const* Polygon2::GetVertexPool() const +{ + return mVertexPool; +} + +template inline +std::set const& Polygon2::GetVertices() const +{ + return mVertices; +} + +template inline +std::vector const& Polygon2::GetIndices() const +{ + return mIndices; +} + +template inline +bool Polygon2::CounterClockwise() const +{ + return mCounterClockwise; +} + +template +Vector2 Polygon2::ComputeVertexAverage() const +{ + Vector2 average = Vector2::Zero(); + if (mVertexPool) + { + for (int index : mVertices) + { + average += mVertexPool[index]; + } + average /= static_cast(mVertices.size()); + } + return average; +} + +template +Real Polygon2::ComputePerimeterLength() const +{ + Real length = (Real)0; + if (mVertexPool) + { + Vector2 v0 = mVertexPool[mIndices.back()]; + for (int index : mIndices) + { + Vector2 v1 = mVertexPool[index]; + length += Length(v1 - v0); + v0 = v1; + } + } + return length; +} + +template +Real Polygon2::ComputeArea() const +{ + Real area = (Real)0; + if (mVertexPool) + { + int const numIndices = static_cast(mIndices.size()); + Vector2 v0 = mVertexPool[mIndices[numIndices - 2]]; + Vector2 v1 = mVertexPool[mIndices[numIndices - 1]]; + for (int index : mIndices) + { + Vector2 v2 = mVertexPool[index]; + area += v1[0] * (v2[1] - v0[1]); + v0 = v1; + v1 = v2; + } + area *= (Real)0.5; + } + return std::abs(area); +} + +template +bool Polygon2::IsSimple() const +{ + if (!mVertexPool) + { + return false; + } + + // For mVertexPool to be nonnull, the number of indices is guaranteed + // to be at least 3. + int const numIndices = static_cast(mIndices.size()); + if (numIndices == 3) + { + // The polygon is a triangle. + return true; + } + + return IsSimpleInternal(); +} + +template +bool Polygon2::IsConvex() const +{ + if (!mVertexPool) + { + return false; + } + + // For mVertexPool to be nonnull, the number of indices is guaranteed + // to be at least 3. + int const numIndices = static_cast(mIndices.size()); + if (numIndices == 3) + { + // The polygon is a triangle. + return true; + } + + return IsSimpleInternal() && IsConvexInternal(); +} + +template +bool Polygon2::IsSimpleInternal() const +{ + Segment2 seg0, seg1; + TIQuery, Segment2> query; + typename TIQuery, Segment2>::Result result; + + int const numIndices = static_cast(mIndices.size()); + for (int i0 = 0; i0 < numIndices; ++i0) + { + int i0p1 = (i0 + 1) % numIndices; + seg0.p[0] = mVertexPool[mIndices[i0]]; + seg0.p[1] = mVertexPool[mIndices[i0p1]]; + + int i1min = (i0 + 2) % numIndices; + int i1max = (i0 - 2 + numIndices) % numIndices; + for (int i1 = i1min; i1 <= i1max; ++i1) + { + int i1p1 = (i1 + 1) % numIndices; + seg1.p[0] = mVertexPool[mIndices[i1]]; + seg1.p[1] = mVertexPool[mIndices[i1p1]]; + + result = query(seg0, seg1); + if (result.intersect) + { + return false; + } + } + } + return true; +} + +template +bool Polygon2::IsConvexInternal() const +{ + Real sign = (mCounterClockwise ? (Real)1 : (Real)-1); + int const numIndices = static_cast(mIndices.size()); + for (int i = 0; i < numIndices; ++i) + { + int iPrev = (i + numIndices - 1) % numIndices; + int iNext = (i + 1) % numIndices; + Vector2 vPrev = mVertexPool[mIndices[iPrev]]; + Vector2 vCurr = mVertexPool[mIndices[i]]; + Vector2 vNext = mVertexPool[mIndices[iNext]]; + Vector2 edge0 = vCurr - vPrev; + Vector2 edge1 = vNext - vCurr; + Real test = sign * DotPerp(edge0, edge1); + if (test < (Real)0) + { + return false; + } + } + return true; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolyhedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolyhedron3.h new file mode 100644 index 000000000000..5c9a3f438499 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolyhedron3.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include + +// The Polyhedron3 object represents a simple polyhedron. The 'vertexPool' +// array can contain more points than needed to define the polyhedron, which +// allows the vertex pool to have multiple polyhedra associated with it. +// Thus, the programmer must ensure that the vertex pool persists as long as +// any Polyhedron3 objects exist that depend on the pool. The number of +// polyhedron indices is 'numIndices' and must be 6 or larger The 'indices' +// array refers to the points in 'vertexPool' that form the triangle faces, +// so 'numIndices' must be a multiple of 3. The number of vertices is +// the number of unique elements in 'indices' and is determined during +// construction. The programmer should ensure the polyhedron is simple. The +// geometric queries are valid regardless of whether the polyhedron triangles +// are oriented clockwise or counterclockwise. +// +// NOTE: Comparison operators are not provided. The semantics of equal +// polyhedra is complicated and (at the moment) not useful. The vertex pools +// can be different and indices do not match, but the vertices they reference +// can match. Even with a shared vertex pool, the indices can be permuted, +// leading to the same polyhedron abstractly but the data structures do not +// match. + +namespace gte +{ + +template +class Polyhedron3 +{ +public: + // Construction. The constructor succeeds when 'numIndices >= 12' (at + // least 4 triangles), and 'vertexPool' and 'indices' are not null; we + // cannot test whether you have a valid number of elements in the input + // arrays. A copy is made of 'indices', but the 'vertexPool' is not + // copied. If the constructor fails, the internal vertex pointer is set + // to null, the number of vertices is set to zero, the index array has no + // elements, and the triangle face orientation is set to clockwise. + Polyhedron3(std::shared_ptr>> const& vertexPool, + int numIndices, int const* indices, bool counterClockwise); + + // To validate construction, create an object as shown: + // Polyhedron3 polyhedron(parameters); + // if (!polyhedron) { ; } + inline operator bool() const; + + // Member access. + inline std::shared_ptr>> const& GetVertexPool() const; + inline std::vector> const& GetVertices() const; + inline std::set const& GetUniqueIndices() const; + inline std::vector const& GetIndices() const; + inline bool CounterClockwise() const; + + // Geometric queries. + Vector3 ComputeVertexAverage() const; + Real ComputeSurfaceArea() const; + Real ComputeVolume() const; + +private: + std::shared_ptr>> mVertexPool; + std::set mUniqueIndices; + std::vector mIndices; + bool mCounterClockwise; +}; + + +template +Polyhedron3::Polyhedron3(std::shared_ptr>> const& vertexPool, + int numIndices, int const* indices, bool counterClockwise) + : + mVertexPool(vertexPool), + mCounterClockwise(counterClockwise) +{ + if (vertexPool && indices && numIndices >= 12 && (numIndices % 3) == 0) + { + for (int i = 0; i < numIndices; ++i) + { + mUniqueIndices.insert(indices[i]); + } + + mIndices.resize(numIndices); + std::copy(indices, indices + numIndices, mIndices.begin()); + } + else + { + // Encountered an invalid input. + mVertexPool = nullptr; + mCounterClockwise = false; + } +} + +template inline +Polyhedron3::operator bool() const +{ + return mVertexPool != nullptr; +} + +template inline +std::shared_ptr>> const& Polyhedron3::GetVertexPool() const +{ + return mVertexPool; +} + +template inline +std::vector> const& Polyhedron3::GetVertices() const +{ + return *mVertexPool.get(); +} + +template inline +std::set const& Polyhedron3::GetUniqueIndices() const +{ + return mUniqueIndices; +} + +template inline +std::vector const& Polyhedron3::GetIndices() const +{ + return mIndices; +} + +template inline +bool Polyhedron3::CounterClockwise() const +{ + return mCounterClockwise; +} + +template +Vector3 Polyhedron3::ComputeVertexAverage() const +{ + Vector3 average = Vector3::Zero(); + if (mVertexPool) + { + auto vertexPool = GetVertices(); + for (int index : mUniqueIndices) + { + average += vertexPool[index]; + } + average /= static_cast(mUniqueIndices.size()); + } + return average; +} + +template +Real Polyhedron3::ComputeSurfaceArea() const +{ + Real surfaceArea = (Real)0; + if (mVertexPool) + { + auto vertexPool = GetVertices(); + int const numTriangles = static_cast(mIndices.size()) / 3; + int const* indices = mIndices.data(); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + Vector3 edge0 = vertexPool[v1] - vertexPool[v0]; + Vector3 edge1 = vertexPool[v2] - vertexPool[v0]; + Vector3 cross = Cross(edge0, edge1); + surfaceArea += Length(cross); + } + surfaceArea *= (Real)0.5; + } + return surfaceArea; +} + +template +Real Polyhedron3::ComputeVolume() const +{ + Real volume = (Real)0; + if (mVertexPool) + { + auto vertexPool = GetVertices(); + int const numTriangles = static_cast(mIndices.size()) / 3; + int const* indices = mIndices.data(); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + volume += DotCross(vertexPool[v0], vertexPool[v1], vertexPool[v2]); + } + volume /= (Real)6; + } + return std::abs(volume); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolynomial1.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolynomial1.h new file mode 100644 index 000000000000..1e4735d67a98 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePolynomial1.h @@ -0,0 +1,607 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/12/06) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + template + class Polynomial1 + { + public: + // Construction and destruction. The first constructor creates a + // polynomial of degree 0. The first and second constructors do not + // initialize their coefficients. In the third constructor, the + // degree is the number of initializers plus 1, but then adjusted to + // ensure coefficient[degree] is not zero. + Polynomial1() + : + mCoefficient(1) + { + // uninitialized + } + + Polynomial1(unsigned int degree) + : + mCoefficient(degree + 1) + { + // uninitialized + } + + Polynomial1(std::initializer_list values) + { + // C++ 11 will call the default constructor for + // Polynomial1 p{}, so it is guaranteed that + // values.size() > 0. + mCoefficient.resize(values.size()); + std::copy(values.begin(), values.end(), mCoefficient.begin()); + EliminateLeadingZeros(); + } + + // Support for partial construction, where the default constructor is + // used when the degree is not yet known. The coefficients are + // uninitialized. + void SetDegree(unsigned int degree) + { + mCoefficient.resize(degree + 1); + } + + // Set all coefficients to the specified value. + void SetCoefficients(Real value) + { + std::fill(mCoefficient.begin(), mCoefficient.end(), value); + } + + // Member access. + inline unsigned int GetDegree() const + { + // By design, mCoefficient.size() > 0. + return static_cast(mCoefficient.size() - 1); + } + + inline Real const& operator[](unsigned int i) const + { + return mCoefficient[i]; + } + + inline Real& operator[](unsigned int i) + { + return mCoefficient[i]; + } + + + // Comparisons. + inline bool operator==(Polynomial1 const& p) const + { + return mCoefficient == p.mCoefficient; + } + + inline bool operator!=(Polynomial1 const& p) const + { + return mCoefficient != p.mCoefficient; + } + + inline bool operator< (Polynomial1 const& p) const + { + return mCoefficient < p.mCoefficient; + } + + inline bool operator<=(Polynomial1 const& p) const + { + return mCoefficient <= p.mCoefficient; + } + + inline bool operator> (Polynomial1 const& p) const + { + return mCoefficient > p.mCoefficient; + } + + inline bool operator>=(Polynomial1 const& p) const + { + return mCoefficient >= p.mCoefficient; + } + + // Evaluate the polynomial. If the polynomial is invalid, the + // function returns zero. + Real operator()(Real t) const + { + int i = static_cast(mCoefficient.size()); + Real result = mCoefficient[--i]; + for (--i; i >= 0; --i) + { + result *= t; + result += mCoefficient[i]; + } + return result; + } + + // Compute the derivative of the polynomial. + Polynomial1 GetDerivative() const + { + unsigned int const degree = GetDegree(); + if (degree > 0) + { + Polynomial1 result(degree - 1); + for (unsigned int i0 = 0, i1 = 1; i0 < degree; ++i0, ++i1) + { + result.mCoefficient[i0] = mCoefficient[i1] * (Real)i1; + } + return result; + } + else + { + Polynomial1 result(0); + result[0] = (Real)0; + return result; + } + } + + // Inversion (invpoly[i] = poly[degree-i] for 0 <= i <= degree). + Polynomial1 GetInversion() const + { + unsigned int const degree = GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result.mCoefficient[i] = mCoefficient[degree - i]; + } + return result; + } + + // Tranlation. If 'this' is p(t}, return p(t-t0). + Polynomial1 GetTranslation(Real t0) const + { + Polynomial1 factor{ -t0, (Real)1 }; // f(t) = t - t0 + unsigned int const degree = GetDegree(); + Polynomial1 result{ mCoefficient[degree] }; + for (unsigned int i = 1, j = degree - 1; i <= degree; ++i, --j) + { + result = mCoefficient[j] + factor * result; + } + return result; + } + + // Eliminate any leading zeros in the polynomial, except in the case + // the degree is 0 and the coefficient is 0. The elimination is + // necessary when arithmetic operations cause a decrease in the degree + // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) = + // (2 + 3*x). The inputs both have degree 2, so the result is created + // with degree 2. After the addition we find that the degree is in + // fact 1 and resize the array of coefficients. This function is + // called internally by the arithmetic operators, but it is exposed in + // the public interface in case you need it for your own purposes. + void EliminateLeadingZeros() + { + size_t size = mCoefficient.size(); + if (size > 1) + { + Real const zero = (Real)0; + int leading; + for (leading = static_cast(size) - 1; leading > 0; --leading) + { + if (mCoefficient[leading] != zero) + { + break; + } + } + + mCoefficient.resize(++leading); + } + } + + // If 'this' is P(t) and the divisor is D(t) with + // degree(P) >= degree(D), then P(t) = Q(t)*D(t)+R(t) where Q(t) is + // the quotient with degree(Q) = degree(P) - degree(D) and R(t) is the + // remainder with degree(R) < degree(D). If this routine is called + // with degree(P) < degree(D), then Q = 0 and R = P are returned. + void Divide(Polynomial1 const& divisor, Polynomial1& quotient, Polynomial1& remainder) const + { + Real const zero = (Real)0; + int divisorDegree = static_cast(divisor.GetDegree()); + int quotientDegree = static_cast(GetDegree()) - divisorDegree; + if (quotientDegree >= 0) + { + quotient.SetDegree(quotientDegree); + + // Temporary storage for the remainder. + Polynomial1 tmp = *this; + + // Do the division using the Euclidean algorithm. + Real inv = ((Real)1) / divisor[divisorDegree]; + for (int i = quotientDegree; i >= 0; --i) + { + int j = divisorDegree + i; + quotient[i] = inv * tmp[j]; + for (j--; j >= i; j--) + { + tmp[j] -= quotient[i] * divisor[j - i]; + } + } + + // Calculate the correct degree for the remainder. + if (divisorDegree >= 1) + { + int remainderDegree = divisorDegree - 1; + while (remainderDegree > 0 && tmp[remainderDegree] == zero) + { + --remainderDegree; + } + + remainder.SetDegree(remainderDegree); + for (int i = 0; i <= remainderDegree; ++i) + { + remainder[i] = tmp[i]; + } + } + else + { + remainder.SetDegree(0); + remainder[0] = zero; + } + } + else + { + quotient.SetDegree(0); + quotient[0] = zero; + remainder = *this; + } + } + + // Scale the polynomial so the highest-degree term has coefficient 1. + void MakeMonic() + { + EliminateLeadingZeros(); + Real const one(1); + if (mCoefficient.back() != one) + { + unsigned int degree = GetDegree(); + Real invLeading = one / mCoefficient.back(); + mCoefficient.back() = one; + for (unsigned int i = 0; i < degree; ++i) + { + mCoefficient[i] *= invLeading; + } + } + } + + protected: + // The class is designed so that mCoefficient.size() >= 1. + std::vector mCoefficient; + }; + + // Compute the greatest common divisor of two polynomials. The returned + // polynomial has leading coefficient 1 (except when zero-valued + // polynomials are passed to the function. + template + Polynomial1 GreatestCommonDivisor(Polynomial1 const& p0, Polynomial1 const& p1) + { + // The numerator should be the polynomial of larger degree. + Polynomial1 a, b; + if (p0.GetDegree() >= p1.GetDegree()) + { + a = p0; + b = p1; + } + else + { + a = p1; + b = p0; + } + + Polynomial1 const zero{ (Real)0 }; + if (a == zero || b == zero) + { + return (a != zero ? a : zero); + } + + // Make the polynomials monic to keep the coefficients reasonable size + // when computing with floating-point Real. + a.MakeMonic(); + b.MakeMonic(); + + Polynomial1 q, r; + for (;;) + { + a.Divide(b, q, r); + if (r != zero) + { + // a = q * b + r, so gcd(a,b) = gcd(b, r) + a = b; + b = r; + b.MakeMonic(); + } + else + { + b.MakeMonic(); + break; + } + } + + return b; + } + + // Factor f = factor[0]*factor[1]^2*factor[2]^3*...*factor[n-1]^n + // according to the square-free factorization algorithm + // https://en.wikipedia.org/wiki/Square-free_polynomial + template + void SquareFreeFactorization(Polynomial1 const& f, std::vector>& factors) + { + // In the call to Divide(...), we know that the divisor exactly + // divides the numerator, so r = 0 after all such calls. + Polynomial1 fder = f.GetDerivative(); + Polynomial1 a, b, c, d, q, r; + + a = GreatestCommonDivisor(f, fder); + f.Divide(a, b, r); // b = f / a + fder.Divide(a, c, r); // c = fder / a + d = c - b.GetDerivative(); + + do + { + a = GreatestCommonDivisor(b, d); + factors.emplace_back(a); + b.Divide(a, q, r); // q = b / a + b = std::move(q); + d.Divide(a, c, r); // c = d / a + d = c - b.GetDerivative(); + } while (b.GetDegree() > 0); + } + + // Unary operations. + template + Polynomial1 operator+(Polynomial1 const& p) + { + return p; + } + + template + Polynomial1 operator-(Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = -p[i]; + } + return result; + } + + // Linear-algebraic operations. + template + Polynomial1 operator+(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + unsigned int i; + if (p0Degree >= p1Degree) + { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) + { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p0Degree; ++i) + { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else + { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) + { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p1Degree; ++i) + { + result[i] = p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } + } + + template + Polynomial1 operator-(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + unsigned int i; + if (p0Degree >= p1Degree) + { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) + { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p0Degree; ++i) + { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else + { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) + { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p1Degree; ++i) + { + result[i] = -p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } + } + + template + Polynomial1 operator*(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + Polynomial1 result(p0Degree + p1Degree); + result.SetCoefficients((Real)0); + for (unsigned int i0 = 0; i0 <= p0Degree; ++i0) + { + for (unsigned int i1 = 0; i1 <= p1Degree; ++i1) + { + result[i0 + i1] += p0[i0] * p1[i1]; + } + } + return result; + } + + template + Polynomial1 operator+(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] + scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator+(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] + scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator-(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] - scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator-(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = scalar - p[0]; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = -p[i]; + } + return result; + } + + template + Polynomial1 operator*(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = scalar * p[i]; + } + return result; + } + + template + Polynomial1 operator*(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = scalar * p[i]; + } + return result; + } + + template + Polynomial1 operator/(Polynomial1 const& p, Real scalar) + { + if (scalar == (Real)0) + { + LogWarning("Division by zero in operator/(p,scalar)."); + } + + unsigned int const degree = p.GetDegree(); + Real invScalar = ((Real)1) / scalar; + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = invScalar * p[i]; + } + return result; + } + + template + Polynomial1& operator+=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 + p1; + return p0; + } + + template + Polynomial1& operator-=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 - p1; + return p0; + } + + template + Polynomial1& operator*=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 * p1; + return p0; + } + + template + Polynomial1& operator+=(Polynomial1& p, Real scalar) + { + p[0] += scalar; + return p; + } + + template + Polynomial1& operator-=(Polynomial1& p, Real scalar) + { + p[0] -= scalar; + return p; + } + + template + Polynomial1& operator*=(Polynomial1& p, Real scalar) + { + p = p * scalar; + return p; + } + + template + Polynomial1 & operator/=(Polynomial1& p, Real scalar) + { + p = p / scalar; + return p; + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery2.h new file mode 100644 index 000000000000..d5bca32e7b0f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery2.h @@ -0,0 +1,437 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Queries about the relation of a point to various geometric objects. The +// choices for N when using UIntegerFP32 for either BSNumber of BSRational +// are determined in GeometricTools/GTEngine/Tools/PrecisionCalculator. These +// N-values are worst case scenarios. Your specific input data might require +// much smaller N, in which case you can modify PrecisionCalculator to use the +// BSPrecision(int32_t,int32_t,int32_t,bool) constructors. + +namespace gte +{ + +template +class PrimalQuery2 +{ +public: + // The caller is responsible for ensuring that the array is not empty + // before calling queries and that the indices passed to the queries are + // valid. The class does no range checking. + PrimalQuery2(); + PrimalQuery2(int numVertices, Vector2 const* vertices); + + // Member access. + inline void Set(int numVertices, Vector2 const* vertices); + inline int GetNumVertices() const; + inline Vector2 const* GetVertices() const; + + // In the following, point P refers to vertices[i] or 'test' and Vi refers + // to vertices[vi]. + + // For a line with origin V0 and direction , ToLine returns + // +1, P on right of line + // -1, P on left of line + // 0, P on the line + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + int ToLine(int i, int v0, int v1) const; + int ToLine(Vector2 const& test, int v0, int v1) const; + + // For a line with origin V0 and direction , ToLine returns + // +1, P on right of line + // -1, P on left of line + // 0, P on the line + // The 'order' parameter is + // -3, points not collinear, P on left of line + // -2, P strictly left of V0 on the line + // -1, P = V0 + // 0, P interior to line segment [V0,V1] + // +1, P = V1 + // +2, P strictly right of V0 on the line + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + // This is the same as the first-listed ToLine calls because the worst-case + // path has the same computational complexity. + int ToLine(int i, int v0, int v1, int& order) const; + int ToLine(Vector2 const& test, int v0, int v1, int& order) const; + + // For a triangle with counterclockwise vertices V0, V1, and V2, + // ToTriangle returns + // +1, P outside triangle + // -1, P inside triangle + // 0, P on triangle + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + // The query involves three calls to ToLine, so the numbers match those + // of ToLine. + int ToTriangle(int i, int v0, int v1, int v2) const; + int ToTriangle(Vector2 const& test, int v0, int v1, int v2) const; + + // For a triangle with counterclockwise vertices V0, V1, and V2, + // ToCircumcircle returns + // +1, P outside circumcircle of triangle + // -1, P inside circumcircle of triangle + // 0, P on circumcircle of triangle + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+------ + // float | BSNumber | 35 + // double | BSNumber | 263 + // float | BSRational | 12302 + // double | BSRational | 92827 + // The query involves three calls of ToLine, so the numbers match those + // of ToLine. + int ToCircumcircle(int i, int v0, int v1, int v2) const; + int ToCircumcircle(Vector2 const& test, int v0, int v1, int v2) const; + + // An extended classification of the relationship of a point to a line + // segment. For noncollinear points, the return value is + // ORDER_POSITIVE when is a counterclockwise triangle + // ORDER_NEGATIVE when is a clockwise triangle + // For collinear points, the line direction is Q1-Q0. The return value is + // ORDER_COLLINEAR_LEFT when the line ordering is + // ORDER_COLLINEAR_RIGHT when the line ordering is + // ORDER_COLLINEAR_CONTAIN when the line ordering is + enum OrderType + { + ORDER_Q0_EQUALS_Q1, + ORDER_P_EQUALS_Q0, + ORDER_P_EQUALS_Q1, + ORDER_POSITIVE, + ORDER_NEGATIVE, + ORDER_COLLINEAR_LEFT, + ORDER_COLLINEAR_RIGHT, + ORDER_COLLINEAR_CONTAIN + }; + + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 214 + // double | BSRational | 1587 + // This is the same as the first-listed ToLine calls because the worst-case + // path has the same computational complexity. + OrderType ToLineExtended(Vector2 const& P, Vector2 const& Q0, Vector2 const& Q1) const; + +private: + int mNumVertices; + Vector2 const* mVertices; +}; + + +template +PrimalQuery2::PrimalQuery2() + : + mNumVertices(0), + mVertices(nullptr) +{ +} + +template +PrimalQuery2::PrimalQuery2(int numVertices, + Vector2 const* vertices) + : + mNumVertices(numVertices), + mVertices(vertices) +{ +} + +template inline +void PrimalQuery2::Set(int numVertices, Vector2 const* vertices) +{ + mNumVertices = numVertices; + mVertices = vertices; +} + +template inline +int PrimalQuery2::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +Vector2 const* PrimalQuery2::GetVertices() const +{ + return mVertices; +} + +template +int PrimalQuery2::ToLine(int i, int v0, int v1) const +{ + return ToLine(mVertices[i], v0, v1); +} + +template +int PrimalQuery2::ToLine(Vector2 const& test, int v0, int v1) const +{ + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real x0y1 = x0*y1; + Real x1y0 = x1*y0; + Real det = x0y1 - x1y0; + Real const zero(0); + + return (det > zero ? +1 : (det < zero ? -1 : 0)); +} + +template +int PrimalQuery2::ToLine(int i, int v0, int v1, int& order) const +{ + return ToLine(mVertices[i], v0, v1, order); +} + +template +int PrimalQuery2::ToLine(Vector2 const& test, int v0, int v1, int& order) const +{ + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real x0y1 = x0*y1; + Real x1y0 = x1*y0; + Real det = x0y1 - x1y0; + Real const zero(0); + + if (det > zero) + { + order = +3; + return +1; + } + + if (det < zero) + { + order = -3; + return -1; + } + + Real x0x1 = x0*x1; + Real y0y1 = y0*y1; + Real dot = x0x1 + y0y1; + if (dot == zero) + { + order = -1; + } + else if (dot < zero) + { + order = -2; + } + else + { + Real x0x0 = x0*x0; + Real y0y0 = y0*y0; + Real sqrLength = x0x0 + y0y0; + if (dot == sqrLength) + { + order = +1; + } + else if (dot > sqrLength) + { + order = +2; + } + else + { + order = 0; + } + } + + return 0; +} + +template +int PrimalQuery2::ToTriangle(int i, int v0, int v1, int v2) const +{ + return ToTriangle(mVertices[i], v0, v1, v2); +} + +template +int PrimalQuery2::ToTriangle(Vector2 const& test, int v0, int v1, int v2) const +{ + int sign0 = ToLine(test, v1, v2); + if (sign0 > 0) + { + return +1; + } + + int sign1 = ToLine(test, v0, v2); + if (sign1 < 0) + { + return +1; + } + + int sign2 = ToLine(test, v0, v1); + if (sign2 > 0) + { + return +1; + } + + return ((sign0 && sign1 && sign2) ? -1 : 0); +} + +template +int PrimalQuery2::ToCircumcircle(int i, int v0, int v1, int v2) const +{ + return ToCircumcircle(mVertices[i], v0, v1, v2); +} + +template +int PrimalQuery2::ToCircumcircle(Vector2 const& test, int v0, int v1, int v2) const +{ + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + Vector2 const& vec2 = mVertices[v2]; + + Real x0 = vec0[0] - test[0]; + Real y0 = vec0[1] - test[1]; + Real s00 = vec0[0] + test[0]; + Real s01 = vec0[1] + test[1]; + Real t00 = s00*x0; + Real t01 = s01*y0; + Real z0 = t00 + t01; + + Real x1 = vec1[0] - test[0]; + Real y1 = vec1[1] - test[1]; + Real s10 = vec1[0] + test[0]; + Real s11 = vec1[1] + test[1]; + Real t10 = s10*x1; + Real t11 = s11*y1; + Real z1 = t10 + t11; + + Real x2 = vec2[0] - test[0]; + Real y2 = vec2[1] - test[1]; + Real s20 = vec2[0] + test[0]; + Real s21 = vec2[1] + test[1]; + Real t20 = s20*x2; + Real t21 = s21*y2; + Real z2 = t20 + t21; + + Real y0z1 = y0*z1; + Real y0z2 = y0*z2; + Real y1z0 = y1*z0; + Real y1z2 = y1*z2; + Real y2z0 = y2*z0; + Real y2z1 = y2*z1; + Real c0 = y1z2 - y2z1; + Real c1 = y2z0 - y0z2; + Real c2 = y0z1 - y1z0; + Real x0c0 = x0*c0; + Real x1c1 = x1*c1; + Real x2c2 = x2*c2; + Real term = x0c0 + x1c1; + Real det = term + x2c2; + Real const zero(0); + + return (det < zero ? 1 : (det > zero ? -1 : 0)); +} + +template +typename PrimalQuery2::OrderType PrimalQuery2::ToLineExtended( + Vector2 const& P, Vector2 const& Q0, Vector2 const& Q1) const +{ + Real const zero(0); + + Real x0 = Q1[0] - Q0[0]; + Real y0 = Q1[1] - Q0[1]; + if (x0 == zero && y0 == zero) + { + return ORDER_Q0_EQUALS_Q1; + } + + Real x1 = P[0] - Q0[0]; + Real y1 = P[1] - Q0[1]; + if (x1 == zero && y1 == zero) + { + return ORDER_P_EQUALS_Q0; + } + + Real x2 = P[0] - Q1[0]; + Real y2 = P[1] - Q1[1]; + if (x2 == zero && y2 == zero) + { + return ORDER_P_EQUALS_Q1; + } + + // The theoretical classification relies on computing exactly the sign of + // the determinant. Numerical roundoff errors can cause misclassification. + Real x0y1 = x0 * y1; + Real x1y0 = x1 * y0; + Real det = x0y1 - x1y0; + + if (det != zero) + { + if (det > zero) + { + // The points form a counterclockwise triangle . + return ORDER_POSITIVE; + } + else + { + // The points form a clockwise triangle . + return ORDER_NEGATIVE; + } + } + else + { + // The points are collinear; P is on the line through Q0 and Q1. + Real x0x1 = x0 * x1; + Real y0y1 = y0 * y1; + Real dot = x0x1 + y0y1; + if (dot < zero) + { + // The line ordering is . + return ORDER_COLLINEAR_LEFT; + } + + Real x0x0 = x0 * x0; + Real y0y0 = y0 * y0; + Real sqrLength = x0x0 + y0y0; + if (dot > sqrLength) + { + // The line ordering is . + return ORDER_COLLINEAR_RIGHT; + } + + // The line ordering is with P strictly between Q0 and Q1. + return ORDER_COLLINEAR_CONTAIN; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery3.h new file mode 100644 index 000000000000..7e44f23145d9 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GtePrimalQuery3.h @@ -0,0 +1,330 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Queries about the relation of a point to various geometric objects. The +// choices for N when using UIntegerFP32 for either BSNumber of BSRational +// are determined in GeometricTools/GTEngine/Tools/PrecisionCalculator. These +// N-values are worst case scenarios. Your specific input data might require +// much smaller N, in which case you can modify PrecisionCalculator to use the +// BSPrecision(int32_t,int32_t,int32_t,bool) constructors. + +namespace gte +{ + +template +class PrimalQuery3 +{ +public: + // The caller is responsible for ensuring that the array is not empty + // before calling queries and that the indices passed to the queries are + // valid. The class does no range checking. + PrimalQuery3(); + PrimalQuery3(int numVertices, Vector3 const* vertices); + + // Member access. + inline void Set(int numVertices, Vector3 const* vertices); + inline int GetNumVertices() const; + inline Vector3 const* GetVertices() const; + + // In the following, point P refers to vertices[i] or 'test' and Vi refers + // to vertices[vi]. + + // For a plane with origin V0 and normal N = Cross(V1-V0,V2-V0), ToPlane + // returns + // +1, P on positive side of plane (side to which N points) + // -1, P on negative side of plane (side to which -N points) + // 0, P on the plane + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+------ + // float | BSNumber | 27 + // double | BSNumber | 197 + // float | BSRational | 2882 + // double | BSRational | 21688 + int ToPlane(int i, int v0, int v1, int v2) const; + int ToPlane(Vector3 const& test, int v0, int v1, int v2) const; + + // For a tetrahedron with vertices ordered as described in the file + // GteTetrahedronKey.h, the function returns + // +1, P outside tetrahedron + // -1, P inside tetrahedron + // 0, P on tetrahedron + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 27 + // double | BSNumber | 197 + // float | BSRational | 2882 + // double | BSRational | 21688 + // The query involves four calls of ToPlane, so the numbers match those + // of ToPlane. + int ToTetrahedron(int i, int v0, int v1, int v2, int v3) const; + int ToTetrahedron(Vector3 const& test, int v0, int v1, int v2, int v3) const; + + // For a tetrahedron with vertices ordered as described in the file + // GteTetrahedronKey.h, the function returns + // +1, P outside circumsphere of tetrahedron + // -1, P inside circumsphere of tetrahedron + // 0, P on circumsphere of tetrahedron + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+-------- + // float | BSNumber | 44 + // float | BSRational | 329 + // double | BSNumber | 298037 + // double | BSRational | 2254442 + int ToCircumsphere(int i, int v0, int v1, int v2, int v3) const; + int ToCircumsphere(Vector3 const& test, int v0, int v1, int v2, int v3) const; + +private: + int mNumVertices; + Vector3 const* mVertices; +}; + + +template +PrimalQuery3::PrimalQuery3() + : + mNumVertices(0), + mVertices(nullptr) +{ +} + +template +PrimalQuery3::PrimalQuery3(int numVertices, + Vector3 const* vertices) + : + mNumVertices(numVertices), + mVertices(vertices) +{ +} + +template inline +void PrimalQuery3::Set(int numVertices, Vector3 const* vertices) +{ + mNumVertices = numVertices; + mVertices = vertices; +} + +template inline +int PrimalQuery3::GetNumVertices() const +{ + return mNumVertices; +} + +template inline +Vector3 const* PrimalQuery3::GetVertices() const +{ + return mVertices; +} + +template +int PrimalQuery3::ToPlane(int i, int v0, int v1, int v2) const +{ + return ToPlane(mVertices[i], v0, v1, v2); +} + +template +int PrimalQuery3::ToPlane(Vector3 const& test, int v0, int v1, + int v2) const +{ + Vector3 const& vec0 = mVertices[v0]; + Vector3 const& vec1 = mVertices[v1]; + Vector3 const& vec2 = mVertices[v2]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real z0 = test[2] - vec0[2]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real z1 = vec1[2] - vec0[2]; + Real x2 = vec2[0] - vec0[0]; + Real y2 = vec2[1] - vec0[1]; + Real z2 = vec2[2] - vec0[2]; + Real y1z2 = y1*z2; + Real y2z1 = y2*z1; + Real y2z0 = y2*z0; + Real y0z2 = y0*z2; + Real y0z1 = y0*z1; + Real y1z0 = y1*z0; + Real c0 = y1z2 - y2z1; + Real c1 = y2z0 - y0z2; + Real c2 = y0z1 - y1z0; + Real x0c0 = x0*c0; + Real x1c1 = x1*c1; + Real x2c2 = x2*c2; + Real term = x0c0 + x1c1; + Real det = term + x2c2; + Real const zero(0); + + return (det > zero ? +1 : (det < zero ? -1 : 0)); +} + +template +int PrimalQuery3::ToTetrahedron(int i, int v0, int v1, int v2, int v3) + const +{ + return ToTetrahedron(mVertices[i], v0, v1, v2, v3); +} + +template +int PrimalQuery3::ToTetrahedron(Vector3 const& test, int v0, + int v1, int v2, int v3) const +{ + int sign0 = ToPlane(test, v1, v2, v3); + if (sign0 > 0) + { + return +1; + } + + int sign1 = ToPlane(test, v0, v2, v3); + if (sign1 < 0) + { + return +1; + } + + int sign2 = ToPlane(test, v0, v1, v3); + if (sign2 > 0) + { + return +1; + } + + int sign3 = ToPlane(test, v0, v1, v2); + if (sign3 < 0) + { + return +1; + } + + return ((sign0 && sign1 && sign2 && sign3) ? -1 : 0); +} + +template +int PrimalQuery3::ToCircumsphere(int i, int v0, int v1, int v2, int v3) +const +{ + return ToCircumsphere(mVertices[i], v0, v1, v2, v3); +} + +template +int PrimalQuery3::ToCircumsphere(Vector3 const& test, int v0, + int v1, int v2, int v3) const +{ + Vector3 const& vec0 = mVertices[v0]; + Vector3 const& vec1 = mVertices[v1]; + Vector3 const& vec2 = mVertices[v2]; + Vector3 const& vec3 = mVertices[v3]; + + Real x0 = vec0[0] - test[0]; + Real y0 = vec0[1] - test[1]; + Real z0 = vec0[2] - test[2]; + Real s00 = vec0[0] + test[0]; + Real s01 = vec0[1] + test[1]; + Real s02 = vec0[2] + test[2]; + Real t00 = s00*x0; + Real t01 = s01*y0; + Real t02 = s02*z0; + Real t00pt01 = t00 + t01; + Real w0 = t00pt01 + t02; + + Real x1 = vec1[0] - test[0]; + Real y1 = vec1[1] - test[1]; + Real z1 = vec1[2] - test[2]; + Real s10 = vec1[0] + test[0]; + Real s11 = vec1[1] + test[1]; + Real s12 = vec1[2] + test[2]; + Real t10 = s10*x1; + Real t11 = s11*y1; + Real t12 = s12*z1; + Real t10pt11 = t10 + t11; + Real w1 = t10pt11 + t12; + + Real x2 = vec2[0] - test[0]; + Real y2 = vec2[1] - test[1]; + Real z2 = vec2[2] - test[2]; + Real s20 = vec2[0] + test[0]; + Real s21 = vec2[1] + test[1]; + Real s22 = vec2[2] + test[2]; + Real t20 = s20*x2; + Real t21 = s21*y2; + Real t22 = s22*z2; + Real t20pt21 = t20 + t21; + Real w2 = t20pt21 + t22; + + Real x3 = vec3[0] - test[0]; + Real y3 = vec3[1] - test[1]; + Real z3 = vec3[2] - test[2]; + Real s30 = vec3[0] + test[0]; + Real s31 = vec3[1] + test[1]; + Real s32 = vec3[2] + test[2]; + Real t30 = s30*x3; + Real t31 = s31*y3; + Real t32 = s32*z3; + Real t30pt31 = t30 + t31; + Real w3 = t30pt31 + t32; + + Real x0y1 = x0*y1; + Real x0y2 = x0*y2; + Real x0y3 = x0*y3; + Real x1y0 = x1*y0; + Real x1y2 = x1*y2; + Real x1y3 = x1*y3; + Real x2y0 = x2*y0; + Real x2y1 = x2*y1; + Real x2y3 = x2*y3; + Real x3y0 = x3*y0; + Real x3y1 = x3*y1; + Real x3y2 = x3*y2; + Real a0 = x0y1 - x1y0; + Real a1 = x0y2 - x2y0; + Real a2 = x0y3 - x3y0; + Real a3 = x1y2 - x2y1; + Real a4 = x1y3 - x3y1; + Real a5 = x2y3 - x3y2; + + Real z0w1 = z0*w1; + Real z0w2 = z0*w2; + Real z0w3 = z0*w3; + Real z1w0 = z1*w0; + Real z1w2 = z1*w2; + Real z1w3 = z1*w3; + Real z2w0 = z2*w0; + Real z2w1 = z2*w1; + Real z2w3 = z2*w3; + Real z3w0 = z3*w0; + Real z3w1 = z3*w1; + Real z3w2 = z3*w2; + Real b0 = z0w1 - z1w0; + Real b1 = z0w2 - z2w0; + Real b2 = z0w3 - z3w0; + Real b3 = z1w2 - z2w1; + Real b4 = z1w3 - z3w1; + Real b5 = z2w3 - z3w2; + Real a0b5 = a0*b5; + Real a1b4 = a1*b4; + Real a2b3 = a2*b3; + Real a3b2 = a3*b2; + Real a4b1 = a4*b1; + Real a5b0 = a5*b0; + Real term0 = a0b5 - a1b4; + Real term1 = term0 + a2b3; + Real term2 = term1 + a3b2; + Real term3 = term2 - a4b1; + Real det = term3 + a5b0; + Real const zero(0); + + return (det > zero ? 1 : (det < zero ? -1 : 0)); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteProjection.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteProjection.h new file mode 100644 index 000000000000..dcf078297840 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteProjection.h @@ -0,0 +1,71 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// Project an ellipse onto a line. The projection interval is [smin,smax] +// and corresponds to the line segment P+s*D, where smin <= s <= smax. +template +void Project(Ellipse2 const& ellipse, Line2 const& line, + Real& smin, Real& smax); + +// Project an ellipsoid onto a line. The projection interval is [smin,smax] +// and corresponds to the line segment P+s*D, where smin <= s <= smax. +template +void Project(Ellipsoid3 const& ellipsoid, + Line3 const& line, Real& smin, Real& smax); + + +template +void Project(Ellipse2 const& ellipse, Line2 const& line, + Real& smin, Real& smax) +{ + // Center of projection interval. + Real center = Dot(line.direction, ellipse.center - line.origin); + + // Radius of projection interval. + Real tmp[2] = + { + ellipse.extent[0] * Dot(line.direction, ellipse.axis[0]), + ellipse.extent[1] * Dot(line.direction, ellipse.axis[1]) + }; + Real rSqr = tmp[0] * tmp[0] + tmp[1] * tmp[1]; + Real radius = std::sqrt(rSqr); + + smin = center - radius; + smax = center + radius; +} + +template +void Project(Ellipsoid3 const& ellipsoid, Line3 const& line, + Real& smin, Real& smax) +{ + // Center of projection interval. + Real center = Dot(line.direction, ellipsoid.center - line.origin); + + // Radius of projection interval. + Real tmp[3] = + { + ellipsoid.extent[0] * Dot(line.direction, ellipsoid.axis[0]), + ellipsoid.extent[1] * Dot(line.direction, ellipsoid.axis[1]), + ellipsoid.extent[2] * Dot(line.direction, ellipsoid.axis[2]) + }; + Real rSqr = tmp[0] * tmp[0] + tmp[1] * tmp[1] + tmp[2] * tmp[2]; + Real radius = std::sqrt(rSqr); + + smin = center - radius; + smax = center + radius; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuadraticField.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuadraticField.h new file mode 100644 index 000000000000..e3a6c5023847 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuadraticField.h @@ -0,0 +1,368 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.21.0 (2019/01/21) + +#pragma once + +#include + +// A real quadratic field has elements of the form z = x + y * sqrt(d), where +// x, y and d are rational numbers and where d > 0. In abstract algebra, +// it is require that d not be the square of a rational number. When this +// is the case, the representation of z is unique. For GTEngine, the +// square-free constraint is not essential, because we need only the +// arithmetic properties of the field (addition, subtraction, multiplication +// and division). We also need comparisons between elements of the quadratic +// field, but these can be reformulated to use only operations for rational +// arithmetic. + +namespace gte +{ + // The type T can be rational (for arbitrary precision arithmetic) or + // floating-point (the quadratic field operations are valid, although + // with floating-point rounding errors). + template + class QuadraticField + { + public: + // The quadratic field descriptor that simply stores d. + QuadraticField() + : + d(0) + { + } + + QuadraticField(T const& inD) + : + d(inD) + { + } + + T d; + + // Elements of the quadratic field. The arithmetic and comparisons + // are managed by the quadratic field itself to avoid having multiple + // copies of d (one copy per Element). + class Element + { + public: + Element() + : + x(0), + y(0) + { + } + + Element(T const& inX, T const& inY) + : + x(inX), + y(inY) + { + } + + T x, y; + }; + + T Convert(Element const& e) const + { + return e.x + e.y * std::sqrt(d); + } + + Element Negate(Element const& e) const + { + return Element(-e.x, -e.y); + } + + Element Add(Element const& e0, Element const& e1) const + { + T x = e0.x + e1.x; + T y = e0.y + e1.y; + return Element(x, y); + } + + Element Add(Element const& e, T const& s) const + { + T x = e.x + s; + T y = e.y; + return Element(x, y); + } + + Element Add(T const& s, Element const& e) const + { + T x = s + e.x; + T y = e.y; + return Element(x, y); + } + + Element Sub(Element const& e0, Element const& e1) const + { + T x = e0.x - e1.x; + T y = e0.y - e1.y; + return Element(x, y); + } + + Element Sub(Element const& e, T const& s) const + { + T x = e.x - s; + T y = e.y; + return Element(x, y); + } + + Element Sub(T const& s, Element const& e) const + { + T x = s - e.x; + T y = -e.y; + return Element(x, y); + } + + Element Mul(Element const& e0, Element const& e1) const + { + T x = e0.x * e1.x + e0.y * e1.y * d; + T y = e0.x * e1.y + e0.y * e1.x; + return Element(x, y); + } + + Element Mul(Element const& e, T const& s) + { + return Element(e.x * s, e.y * s); + } + + Element Mul(T const& s, Element const& e) + { + return Element(s * e.x, s * e.y); + } + + // Expose division only when T has a division operator. + template + typename std::enable_if::value, Element>::type + Div(Element const& e0, Element const& e1) const + { + T z = e1.x * e1.x - e1.y * e1.y * d; + T x = (e0.x * e1.x - e0.y * e1.y * d) / z; + T y = (e0.y * e1.x - e0.x * e1.y) / z; + return Element(x, y); + } + + template + typename std::enable_if::value, Element>::type + Div(Element const& e, T const& s) + { + return Element(e.x / s, e.y / s); + } + + // Comparison functions. + bool EqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x == zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d == zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d == zero; + } + } + } + + bool NotEqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x != zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d != zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d != zero; + } + } + } + + bool LessThanZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x < zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d > zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d < zero; + } + } + } + + bool GreaterThanZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x > zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d < zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d > zero; + } + } + } + + bool LessThanOrEqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x <= zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d >= zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d <= zero; + } + } + } + + bool GreaterThanOrEqualZero(Element const& e) const + { + T const zero(0); + if (d == zero || e.y == zero) + { + return e.x >= zero; + } + else if (e.y > zero) + { + if (e.x >= zero) + { + return true; + } + else + { + return e.x * e.x - e.y * e.y * d <= zero; + } + } + else // e.y < zero + { + if (e.x <= zero) + { + return false; + } + else + { + return e.x * e.x - e.y * e.y * d >= zero; + } + } + } + + bool Equal(Element const& e0, Element const& e1) const + { + return EqualZero(Sub(e0, e1)); + } + + bool LessThan(Element const& e0, Element const& e1) const + { + return LessThanZero(Sub(e0, e1)); + } + + bool GreaterThan(Element const& e0, Element const& e1) const + { + return GreaterThanZero(Sub(e0, e1)); + } + + bool LessThanOrEqual(Element const& e0, Element const& e1) const + { + return LessThanOrEqualZero(Sub(e0, e1)); + } + + bool GreaterThanOrEqual(Element const& e0, Element const& e1) const + { + return GreaterThanOrEqualZero(Sub(e0, e1)); + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuarticRootsQR.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuarticRootsQR.h new file mode 100644 index 000000000000..f9fe78bebf4f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuarticRootsQR.h @@ -0,0 +1,305 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include "GteCubicRootsQR.h" + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). The algorithm is +// specialized for the companion matrix associated with a quartic polynomial. + +namespace gte +{ + +template +class QuarticRootsQR +{ +public: + typedef std::array, 4> Matrix; + + // Solve p(x) = c0 + c1 * x + c2 * x^2 + c3 * x^3 + x^4 = 0. + uint32_t operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, Real c3, + uint32_t& numRoots, std::array& roots); + + // Compute the real eigenvalues of the upper Hessenberg matrix A. The + // matrix is modified by in-place operations, so if you need to remember + // A, you must make your own copy before calling this function. + uint32_t operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots); + +private: + void DoIteration(std::array const& V, Matrix& A); + + template + std::array House(std::array const& X); + + template + void RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + template + void ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A); + + void GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots); +}; + + +template +uint32_t QuarticRootsQR::operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, Real c3, + uint32_t& numRoots, std::array& roots) +{ + // Create the companion matrix for the polynomial. The matrix is in upper + // Hessenberg form. + Matrix A; + A[0][0] = (Real)0; + A[0][1] = (Real)0; + A[0][2] = (Real)0; + A[0][3] = -c0; + A[1][0] = (Real)1; + A[1][1] = (Real)0; + A[1][2] = (Real)0; + A[1][3] = -c1; + A[2][0] = (Real)0; + A[2][1] = (Real)1; + A[2][2] = (Real)0; + A[2][3] = -c2; + A[3][0] = (Real)0; + A[3][1] = (Real)0; + A[3][2] = (Real)1; + A[3][3] = -c3; + + // Avoid the QR-cycle when c1 = c2 = 0 and avoid the slow convergence + // when c1 and c2 are nearly zero. + std::array V{ + (Real)1, + (Real)0.36602540378443865, + (Real)0.36602540378443865 }; + DoIteration(V, A); + + return operator()(maxIterations, A, numRoots, roots); +} + +template +uint32_t QuarticRootsQR::operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots) +{ + numRoots = 0; + std::fill(roots.begin(), roots.end(), (Real)0); + + for (uint32_t numIterations = 0; numIterations < maxIterations; ++numIterations) + { + // Apply a Francis QR iteration. + Real tr = A[2][2] + A[3][3]; + Real det = A[2][2] * A[3][3] - A[2][3] * A[3][2]; + std::array X{ + A[0][0] * A[0][0] + A[0][1] * A[1][0] - tr * A[0][0] + det, + A[1][0] * (A[0][0] + A[1][1] - tr), + A[1][0] * A[2][1] }; + std::array V = House<3>(X); + DoIteration(V, A); + + // Test for uncoupling of A. + Real tr12 = A[1][1] + A[2][2]; + if (tr12 + A[2][1] == tr12) + { + GetQuadraticRoots(0, 1, A, numRoots, roots); + GetQuadraticRoots(2, 3, A, numRoots, roots); + return numIterations; + } + + Real tr01 = A[0][0] + A[1][1]; + if (tr01 + A[1][0] == tr01) + { + numRoots = 1; + roots[0] = A[0][0]; + + // TODO: The cubic solver is not designed to process 3x3 submatrices + // of an NxN matrix, so the copy of a submatrix of A to B is a simple + // workaround for running the solver. Write general root-finding + // code that avoids such copying. + uint32_t subMaxIterations = maxIterations - numIterations; + typename CubicRootsQR::Matrix B; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + B[r][c] = A[r + 1][c + 1]; + } + } + + uint32_t numSubroots = 0; + std::array subroots; + uint32_t numSubiterations = CubicRootsQR()(subMaxIterations, B, + numSubroots, subroots); + for (uint32_t i = 0; i < numSubroots; ++i) + { + roots[numRoots++] = subroots[i]; + } + return numIterations + numSubiterations; + } + + Real tr23 = A[2][2] + A[3][3]; + if (tr23 + A[3][2] == tr23) + { + numRoots = 1; + roots[0] = A[3][3]; + + // TODO: The cubic solver is not designed to process 3x3 submatrices + // of an NxN matrix, so the copy of a submatrix of A to B is a simple + // workaround for running the solver. Write general root-finding + // code that avoids such copying. + uint32_t subMaxIterations = maxIterations - numIterations; + typename CubicRootsQR::Matrix B; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + B[r][c] = A[r][c]; + } + } + + uint32_t numSubroots = 0; + std::array subroots; + uint32_t numSubiterations = CubicRootsQR()(subMaxIterations, B, + numSubroots, subroots); + for (uint32_t i = 0; i < numSubroots; ++i) + { + roots[numRoots++] = subroots[i]; + } + return numIterations + numSubiterations; + } + } + return maxIterations; +} + +template +void QuarticRootsQR::DoIteration(std::array const& V, Matrix& A) +{ + Real multV = ((Real)-2) / (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]); + std::array MV{ multV * V[0], multV * V[1], multV * V[2] }; + RowHouse<3>(0, 2, 0, 3, V, MV, A); + ColHouse<3>(0, 3, 0, 2, V, MV, A); + + std::array X{ A[1][0], A[2][0], A[3][0] }; + std::array locV = House<3>(X); + multV = ((Real)-2) / (locV[0] * locV[0] + locV[1] * locV[1] + locV[2] * locV[2]); + MV = { multV * locV[0], multV * locV[1], multV * locV[2] }; + RowHouse<3>(1, 3, 0, 3, locV, MV, A); + ColHouse<3>(0, 3, 1, 3, locV, MV, A); + + std::array Y{ A[2][1], A[3][1] }; + std::array W = House<2>(Y); + Real multW = ((Real)-2) / (W[0] * W[0] + W[1] * W[1]); + std::array MW = { multW * W[0], multW * W[1] }; + RowHouse<2>(2, 3, 0, 3, W, MW, A); + ColHouse<2>(0, 3, 2, 3, W, MW, A); +} + +template +template +std::array QuarticRootsQR::House(std::array const & X) +{ + std::array V; + Real length = (Real)0; + for (int i = 0; i < N; ++i) + { + length += X[i] * X[i]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (X[0] >= (Real)0 ? (Real)1 : (Real)-1); + Real denom = X[0] + sign * length; + for (int i = 1; i < N; ++i) + { + V[i] = X[i] / denom; + } + } + V[0] = (Real)1; + return V; +} + +template +template +void QuarticRootsQR::RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements cmin through cmax are used + + for (int c = cmin; c <= cmax; ++c) + { + W[c] = (Real)0; + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + W[c] += V[k] * A[r][c]; + } + } + + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + for (int c = cmin; c <= cmax; ++c) + { + A[r][c] += MV[k] * W[c]; + } + } +} + +template +template +void QuarticRootsQR::ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) +{ + std::array W; // only elements rmin through rmax are used + + for (int r = rmin; r <= rmax; ++r) + { + W[r] = (Real)0; + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + W[r] += V[k] * A[r][c]; + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + A[r][c] += W[r] * MV[k]; + } + } +} + +template +void QuarticRootsQR::GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots) +{ + // Solve x^2 - t * x + d = 0, where t is the trace and d is the + // determinant of the 2x2 matrix defined by indices i0 and i1. The + // discriminant is D = (t/2)^2 - d. When D >= 0, the roots are real + // values t/2 - sqrt(D) and t/2 + sqrt(D). To avoid potential numerical + // issues with subtractive cancellation, the roots are computed as + // r0 = t/2 + sign(t/2)*sqrt(D), r1 = trace - r0. + Real trace = A[i0][i0] + A[i1][i1]; + Real halfTrace = trace * (Real)0.5; + Real determinant = A[i0][i0] * A[i1][i1] - A[i0][i1] * A[i1][i0]; + Real discriminant = halfTrace * halfTrace - determinant; + if (discriminant >= (Real)0) + { + Real sign = (trace >= (Real)0 ? (Real)1 : (Real)-1); + Real root = halfTrace + sign * std::sqrt(discriminant); + roots[numRoots++] = root; + roots[numRoots++] = trace - root; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuaternion.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuaternion.h new file mode 100644 index 000000000000..9a12033d3438 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteQuaternion.h @@ -0,0 +1,455 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/12/11) + +#pragma once + +#include +#include +#include + +// A quaternion is of the form +// q = x * i + y * j + z * k + w * 1 = x * i + y * j + z * k + w +// where w, x, y, and z are real numbers. The scalar and vector parts are +// Vector(q) = x * i + y * j + z * k +// Scalar(q) = w +// q = Vector(q) + Scalar(q) +// I assume that you are familiar with the arithmetic and algebraic properties +// of quaternions. See +// https://www.geometrictools.com/Documentation/Quaternions.pdf + +namespace gte +{ + template + class Quaternion + { + public: + // The quaternions are of the form q = x*i + y*j + z*k + w. In tuple + // form, q = (x,y,z,w). + + // Construction. The default constructor does not initialize the + // members. + Quaternion() = default; + + Quaternion(Real x, Real y, Real z, Real w) + { + mTuple[0] = x; + mTuple[1] = y; + mTuple[2] = z; + mTuple[3] = w; + } + + // Member access. + inline Real const& operator[](int i) const + { + return mTuple[i]; + } + + inline Real& operator[](int i) + { + return mTuple[i]; + } + + // Comparisons. + inline bool operator==(Quaternion const& q) const + { + return mTuple == q.mTuple; + } + + inline bool operator!=(Quaternion const& q) const + { + return mTuple != q.mTuple; + } + + inline bool operator<(Quaternion const& q) const + { + return mTuple < q.mTuple; + } + + inline bool operator<=(Quaternion const& q) const + { + return mTuple <= q.mTuple; + } + + inline bool operator>(Quaternion const& q) const + { + return mTuple > q.mTuple; + } + + inline bool operator>=(Quaternion const& q) const + { + return mTuple >= q.mTuple; + } + + // Special quaternions. + + // z = 0*i + 0*j + 0*k + 0 + static Quaternion Zero() + { + return Quaternion((Real)0, (Real)0, (Real)0, (Real)0); + } + + // i = 1*i + 0*j + 0*k + 0 + static Quaternion I() + { + return Quaternion((Real)1, (Real)0, (Real)0, (Real)0); + } + + // j = 0*i + 1*j + 0*k + 0 + static Quaternion J() + { + return Quaternion((Real)0, (Real)1, (Real)0, (Real)0); + } + + // k = 0*i + 0*j + 1*k + 0 + static Quaternion K() + { + return Quaternion((Real)0, (Real)0, (Real)1, (Real)0); + } + + // 1 = 0*i + 0*j + 0*k + 1 + static Quaternion Identity() + { + return Quaternion((Real)0, (Real)0, (Real)0, (Real)1); + } + + protected: + std::array mTuple; + }; + + // Unary operations. + template + Quaternion operator+(Quaternion const& q) + { + return q; + } + + template + Quaternion operator-(Quaternion const& q) + { + Quaternion result; + for (int i = 0; i < 4; ++i) + { + result[i] = -q[i]; + } + return result; + } + + // Linear algebraic operations. + template + Quaternion operator+(Quaternion const& q0, Quaternion const& q1) + { + Quaternion result = q0; + return result += q1; + } + + template + Quaternion operator-(Quaternion const& q0, Quaternion const& q1) + { + Quaternion result = q0; + return result -= q1; + } + + template + Quaternion operator*(Quaternion const& q, Real scalar) + { + Quaternion result = q; + return result *= scalar; + } + + template + Quaternion operator*(Real scalar, Quaternion const& q) + { + Quaternion result = q; + return result *= scalar; + } + + template + Quaternion operator/(Quaternion const& q, Real scalar) + { + Quaternion result = q; + return result /= scalar; + } + + template + Quaternion& operator+=(Quaternion& q0, Quaternion const& q1) + { + for (int i = 0; i < 4; ++i) + { + q0[i] += q1[i]; + } + return q0; + } + + template + Quaternion& operator-=(Quaternion& q0, Quaternion const& q1) + { + for (int i = 0; i < 4; ++i) + { + q0[i] -= q1[i]; + } + return q0; + } + + template + Quaternion& operator*=(Quaternion& q, Real scalar) + { + for (int i = 0; i < 4; ++i) + { + q[i] *= scalar; + } + return q; + } + + template + Quaternion& operator/=(Quaternion& q, Real scalar) + { + if (scalar != (Real)0) + { + for (int i = 0; i < 4; ++i) + { + q[i] /= scalar; + } + } + else + { + for (int i = 0; i < 4; ++i) + { + q[i] = (Real)0; + } + } + return q; + } + + // Geometric operations. + template + Real Dot(Quaternion const& q0, Quaternion const& q1) + { + Real dot = q0[0] * q1[0]; + for (int i = 1; i < 4; ++i) + { + dot += q0[i] * q1[i]; + } + return dot; + } + + template + Real Length(Quaternion const& q) + { + return std::sqrt(Dot(q, q)); + } + + template + Real Normalize(Quaternion& q) + { + Real length = std::sqrt(Dot(q, q)); + if (length > (Real)0) + { + q /= length; + } + else + { + for (int i = 0; i < 4; ++i) + { + q[i] = (Real)0; + } + } + return length; + } + + // Multiplication of quaternions. This operation is not generally + // commutative; that is, q0*q1 and q1*q0 are not usually the same value. + // (x0*i + y0*j + z0*k + w0)*(x1*i + y1*j + z1*k + w1) + // = + // i*(+x0*w1 + y0*z1 - z0*y1 + w0*x1) + + // j*(-x0*z1 + y0*w1 + z0*x1 + w0*y1) + + // k*(+x0*y1 - y0*x1 + z0*w1 + w0*z1) + + // 1*(-x0*x1 - y0*y1 - z0*z1 + w0*w1) + template + Quaternion operator*(Quaternion const& q0, Quaternion const& q1) + { + // (x0*i + y0*j + z0*k + w0)*(x1*i + y1*j + z1*k + w1) + // = + // i*(+x0*w1 + y0*z1 - z0*y1 + w0*x1) + + // j*(-x0*z1 + y0*w1 + z0*x1 + w0*y1) + + // k*(+x0*y1 - y0*x1 + z0*w1 + w0*z1) + + // 1*(-x0*x1 - y0*y1 - z0*z1 + w0*w1) + + return Quaternion + ( + +q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0], + -q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1], + +q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3] + q0[3] * q1[2], + -q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + q0[3] * q1[3] + ); + } + + // For a nonzero quaternion q = (x,y,z,w), inv(q) = (-x,-y,-z,w)/|q|^2, where + // |q| is the length of the quaternion. When q is zero, the function returns + // zero, which is considered to be an improbable case. + template + Quaternion Inverse(Quaternion const& q) + { + Real sqrLen = Dot(q, q); + if (sqrLen > (Real)0) + { + Quaternion inverse = Conjugate(q) / sqrLen; + return inverse; + } + else + { + return Quaternion::Zero(); + } + } + + // The conjugate of q = (x,y,z,w) is conj(q) = (-x,-y,-z,w). + template + Quaternion Conjugate(Quaternion const& q) + { + return Quaternion(-q[0], -q[1], -q[2], +q[3]); + } + + // Rotate a vector using quaternion multiplication. The input quaternion must + // be unit length. + template + Vector<4, Real> Rotate(Quaternion const& q, Vector<4, Real> const& v) + { + Quaternion input(v[0], v[1], v[2], (Real)0); + Quaternion output = q * input * Conjugate(q); + Vector<4, Real> u{ output[0], output[1], output[2], (Real)0 }; + return u; + } + + // The spherical linear interpolation (slerp) of unit-length quaternions + // q0 and q1 for t in [0,1] is + // slerp(t,q0,q1) = [sin(t*theta)*q0 + sin((1-t)*theta)*q1]/sin(theta) + // where theta is the angle between q0 and q1 [cos(theta) = Dot(q0,q1)]. + // This function is a parameterization of the great spherical arc between + // q0 and q1 on the unit hypersphere. Moreover, the parameterization is + // one of normalized arclength--a particle traveling along the arc through + // time t does so with constant speed. + // + // When using slerp in animations involving sequences of quaternions, it is + // typical that the quaternions are preprocessed so that consecutive ones + // form an acute angle A in [0,pi/2]. Other preprocessing can help with + // performance. See the function comments below. + // + // See GteSlerpEstimate.{h,inl} for various approximations, including + // SLERP::EstimateRPH that gives good performance and accurate results + // for preprocessed quaternions. + + // The angle between q0 and q1 is in [0,pi). There are no angle restrictions + // restrictions and nothing is precomputed. + template + Quaternion Slerp(Real t, Quaternion const& q0, Quaternion const& q1) + { + Real cosA = Dot(q0, q1); + Real sign; + if (cosA >= (Real)0) + { + sign = (Real)1; + } + else + { + cosA = -cosA; + sign = (Real)-1; + } + + Real f0, f1; + ChebyshevRatio::Get(t, cosA, f0, f1); + return q0 * f0 + q1 * (sign * f1); + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted'. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; // now Dot(q[i0], q[i]1) >= 0 + // } + // } + template + Quaternion SlerpR(Real t, Quaternion const& q0, Quaternion const& q1) + { + Real f0, f1; + ChebyshevRatio::Get(t, Dot(q0, q1), f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted' and the suffix P is for 'Preprocessed'. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Real cosA[n-1], omcosA[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cs = Dot(q[i0], q[i1]); + // if (cosA[i0] < 0) + // { + // q[i1] = -q[i1]; + // cs = -cs; + // } + // + // // for Quaterion::SlerpRP + // cosA[i0] = cs; + // + // // for SLERP::EstimateRP + // omcosA[i0] = 1 - cs; + // } + template + Quaternion SlerpRP(Real t, Quaternion const& q0, Quaternion const& q1, Real cosA) + { + Real f0, f1; + ChebyshevRatio::Get(t, cosA, f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 is A and must be in [0,pi/2]. The suffic R + // is for 'Restricted', the suffix P is for 'Preprocessed' and the suffix + // H is for 'Half' (the quaternion qh halfway between q0 and q1 is + // precomputed). Quaternion qh is slerp(1/2,q0,q1) = (q0+q1)/|q0+q1|, so + // the angle between q0 and qh is A/2 and the angle between qh and q1 is + // A/2. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // Quaternion qh[n-1]; // to be precomputed + // Real omcosAH[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; + // cosA = -cosA; + // } + // + // // for Quaternion::SlerpRPH and SLERP::EstimateRPH + // cosAH[i0] = sqrt((1+cosA)/2); + // qh[i0] = (q0 + q1) / (2 * cosAH[i0]); + // + // // for SLERP::EstimateRPH + // omcosAH[i0] = 1 - cosAH[i0]; + // } + template + Quaternion SlerpRPH(Real t, Quaternion const& q0, Quaternion const& q1, + Quaternion const& qh, Real cosAH) + { + Real f0, f1; + Real twoT = static_cast(2) * t; + if (twoT <= static_cast(1)) + { + ChebyshevRatio::Get(twoT, cosAH, f0, f1); + return q0 * f0 + qh * f1; + } + else + { + ChebyshevRatio::Get(twoT - static_cast(1), cosAH, f0, f1); + return qh * f0 + q1 * f1; + } + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRay.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRay.h new file mode 100644 index 000000000000..446b03d6006b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRay.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The ray is represented as P+t*D, where P is the ray origin, D is a +// unit-length direction vector, and t >= 0. The user must ensure that D is +// unit length. + +namespace gte +{ + +template +class Ray +{ +public: + // Construction and destruction. The default constructor sets the origin + // to (0,...,0) and the ray direction to (1,0,...,0). + Ray(); + Ray(Vector const& inOrigin, Vector const& inDirection); + + // Public member access. The direction must be unit length. + Vector origin, direction; + +public: + // Comparisons to support sorted containers. + bool operator==(Ray const& ray) const; + bool operator!=(Ray const& ray) const; + bool operator< (Ray const& ray) const; + bool operator<=(Ray const& ray) const; + bool operator> (Ray const& ray) const; + bool operator>=(Ray const& ray) const; +}; + +// Template aliases for convenience. +template +using Ray2 = Ray<2, Real>; + +template +using Ray3 = Ray<3, Real>; + + +template +Ray::Ray() +{ + origin.MakeZero(); + direction.MakeUnit(0); +} + +template +Ray::Ray(Vector const& inOrigin, + Vector const& inDirection) + : + origin(inOrigin), + direction(inDirection) +{ +} + +template +bool Ray::operator==(Ray const& ray) const +{ + return origin == ray.origin && direction == ray.direction; +} + +template +bool Ray::operator!=(Ray const& ray) const +{ + return !operator==(ray); +} + +template +bool Ray::operator<(Ray const& ray) const +{ + if (origin < ray.origin) + { + return true; + } + + if (origin > ray.origin) + { + return false; + } + + return direction < ray.direction; +} + +template +bool Ray::operator<=(Ray const& ray) const +{ + return operator<(ray) || operator==(ray); +} + +template +bool Ray::operator>(Ray const& ray) const +{ + return !operator<=(ray); +} + +template +bool Ray::operator>=(Ray const& ray) const +{ + return !operator<(ray); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangle.h new file mode 100644 index 000000000000..bb993bc49b2f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangle.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Points are R(s0,s1) = C + s0*A0 + s1*A1, where C is the center of the +// rectangle and A0 and A1 are unit-length and perpendicular axes. The +// parameters s0 and s1 are constrained by |s0| <= e0 and |s1| <= e1, +// where e0 > 0 and e1 > 0 are the extents of the rectangle. + +namespace gte +{ + +template +class Rectangle +{ +public: + // Construction and destruction. The default constructor sets the origin + // to (0,...,0), axis A0 to (1,0,...,0), axis A1 to (0,1,0,...0), and both + // extents to 1. + Rectangle(); + Rectangle(Vector const& inCenter, + std::array, 2> const& inAxis, + Vector<2, Real> const& inExtent); + + // Compute the vertices of the rectangle. If index i has the bit pattern + // i = b[1]b[0], then + // vertex[i] = center + sum_{d=0}^{1} sign[d] * extent[d] * axis[d] + // where sign[d] = 2*b[d] - 1. + void GetVertices(std::array, 4>& vertex) const; + + Vector center; + std::array, 2> axis; + Vector<2, Real> extent; + +public: + // Comparisons to support sorted containers. + bool operator==(Rectangle const& rectangle) const; + bool operator!=(Rectangle const& rectangle) const; + bool operator< (Rectangle const& rectangle) const; + bool operator<=(Rectangle const& rectangle) const; + bool operator> (Rectangle const& rectangle) const; + bool operator>=(Rectangle const& rectangle) const; +}; + +// Template alias for convenience. +template +using Rectangle3 = Rectangle<3, Real>; + + +template +Rectangle::Rectangle() +{ + center.MakeZero(); + for (int i = 0; i < 2; ++i) + { + axis[i].MakeUnit(i); + extent[i] = (Real)1; + } +} + +template +Rectangle::Rectangle(Vector const& inCenter, + std::array, 2> const& inAxis, + Vector<2, Real> const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) +{ +} + +template +void Rectangle::GetVertices( + std::array, 4>& vertex) const +{ + Vector product0 = extent[0] * axis[0]; + Vector product1 = extent[1] * axis[1]; + Vector sum = product0 + product1; + Vector dif = product0 - product1; + + vertex[0] = center - sum; + vertex[1] = center + dif; + vertex[2] = center - dif; + vertex[3] = center + sum; +} + +template +bool Rectangle::operator==(Rectangle const& rectangle) const +{ + if (center != rectangle.center) + { + return false; + } + + for (int i = 0; i < 2; ++i) + { + if (axis[i] != rectangle.axis[i]) + { + return false; + } + } + + for (int i = 0; i < 2; ++i) + { + if (extent[i] != rectangle.extent[i]) + { + return false; + } + } + + return true; +} + +template +bool Rectangle::operator!=(Rectangle const& rectangle) const +{ + return !operator==(rectangle); +} + +template +bool Rectangle::operator<(Rectangle const& rectangle) const +{ + if (center < rectangle.center) + { + return true; + } + + if (center > rectangle.center) + { + return false; + } + + if (axis < rectangle.axis) + { + return true; + } + + if (axis > rectangle.axis) + { + return false; + } + + return extent < rectangle.extent; +} + +template +bool Rectangle::operator<=(Rectangle const& rectangle) const +{ + return operator<(rectangle) || operator==(rectangle); +} + +template +bool Rectangle::operator>(Rectangle const& rectangle) const +{ + return !operator<=(rectangle); +} + +template +bool Rectangle::operator>=(Rectangle const& rectangle) const +{ + return !operator<(rectangle); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangleMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangleMesh.h new file mode 100644 index 000000000000..95a1ec63d4fa --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectangleMesh.h @@ -0,0 +1,170 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2016/08/29) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class RectangleMesh : public Mesh +{ +public: + // Create a mesh that tessellates a rectangle. + RectangleMesh(MeshDescription const& description, Rectangle<3, Real> const& rectangle); + + // Member access. + inline Rectangle<3, Real> const& GetRectangle() const; + +protected: + void InitializeTCoords(); + void InitializePositions(); + void InitializeNormals(); + void InitializeFrame(); + + Rectangle<3, Real> mRectangle; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +RectangleMesh::RectangleMesh(MeshDescription const& description, + Rectangle<3, Real> const& rectangle) + : + Mesh(description, { MeshTopology::RECTANGLE }), + mRectangle(rectangle) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + return; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + InitializePositions(); + if (this->mDescription.allowUpdateFrame) + { + InitializeFrame(); + } + else if (this->mNormals) + { + InitializeNormals(); + } +} + +template +inline Rectangle<3, Real> const& RectangleMesh::GetRectangle() const +{ + return mRectangle; +} + +template +void RectangleMesh::InitializeTCoords() +{ + Vector2 tcoord; + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)(this->mDescription.numCols - 1); + this->TCoord(i) = tcoord; + } + } +} + +template +void RectangleMesh::InitializePositions() +{ + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Real w0 = ((Real)2 * tcoord[0] - (Real)1) * mRectangle.extent[0]; + Real w1 = ((Real)2 * tcoord[1] - (Real)1) * mRectangle.extent[1]; + this->Position(i) = mRectangle.center + w0 * mRectangle.axis[0] + w1 * mRectangle.axis[1]; + } + } +} + +template +void RectangleMesh::InitializeNormals() +{ + Vector3 normal = UnitCross(mRectangle.axis[0], mRectangle.axis[1]); + for (uint32_t i = 0; i < this->mDescription.numVertices; ++i) + { + this->Normal(i) = normal; + } +} + +template +void RectangleMesh::InitializeFrame() +{ + Vector3 normal = UnitCross(mRectangle.axis[0], mRectangle.axis[1]); + Vector3 tangent{ (Real)1, (Real)0, (Real)0 }; + Vector3 bitangent{ (Real)0, (Real)1, (Real)0 }; // = Cross(normal,tangent) + for (uint32_t i = 0; i < this->mDescription.numVertices; ++i) + { + if (this->mNormals) + { + this->Normal(i) = normal; + } + + if (this->mTangents) + { + this->Tangent(i) = tangent; + } + + if (this->mBitangents) + { + this->Bitangent(i) = bitangent; + } + + if (this->mDPDUs) + { + this->DPDU(i) = tangent; + } + + if (this->mDPDVs) + { + this->DPDV(i) = bitangent; + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectanglePatchMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectanglePatchMesh.h new file mode 100644 index 000000000000..925b821f581a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRectanglePatchMesh.h @@ -0,0 +1,226 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2016/08/29) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class RectanglePatchMesh : public Mesh +{ +public: + // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified surface. + // It is required that surface->IsRectangular() return 'true'. + RectanglePatchMesh(MeshDescription const& description, + std::shared_ptr> const& surface); + + // Member access. + inline std::shared_ptr> const& GetSurface() const; + +protected: + void InitializeTCoords(); + void InitializePositions(); + void InitializeNormals(); + void InitializeFrame(); + virtual void UpdatePositions() override; + virtual void UpdateNormals() override; + virtual void UpdateFrame() override; + + std::shared_ptr> mSurface; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +RectanglePatchMesh::RectanglePatchMesh(MeshDescription const& description, + std::shared_ptr> const& surface) + : + Mesh(description, { MeshTopology::RECTANGLE }), + mSurface(surface) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + mSurface = nullptr; + return; + } + + if (!mSurface || !mSurface->IsRectangular()) + { + LogError("A nonnull rectangular surface is required."); + mSurface = nullptr; + this->mDescription.constructed = false; + return; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + InitializePositions(); + if (this->mDescription.allowUpdateFrame) + { + InitializeFrame(); + } + else if (this->mNormals) + { + InitializeNormals(); + } +} + +template +inline std::shared_ptr> const& +RectanglePatchMesh::GetSurface() const +{ + return mSurface; +} + +template +void RectanglePatchMesh::InitializeTCoords() +{ + Real uMin = mSurface->GetUMin(); + Real uDelta = (mSurface->GetUMax() - uMin) / static_cast(this->mDescription.numCols - 1); + Real vMin = mSurface->GetVMin(); + Real vDelta = (mSurface->GetVMax() - vMin) / static_cast(this->mDescription.numRows - 1); + Vector2 tcoord; + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = vMin + vDelta * (Real)r; + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = uMin + uDelta * (Real)c; + this->TCoord(i) = tcoord; + } + } +} + +template +void RectanglePatchMesh::InitializePositions() +{ + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + this->Position(i) = mSurface->GetPosition(tcoord[0], tcoord[1]); + } + } +} + +template +void RectanglePatchMesh::InitializeNormals() +{ + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Vector3 values[6]; + mSurface->Evaluate(tcoord[0], tcoord[1], 1, values); + Normalize(values[1], true); + Normalize(values[2], true); + this->Normal(i) = UnitCross(values[1], values[2], true); + } + } +} + +template +void RectanglePatchMesh::InitializeFrame() +{ + Vector3 normal, tangent, bitangent; + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (unsigned int c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Vector3 values[6]; + mSurface->Evaluate(tcoord[0], tcoord[1], 1, values); + Normalize(values[1], true); + Normalize(values[2], true); + + if (this->mDPDUs) + { + this->DPDU(i) = values[1]; + } + if (this->mDPDVs) + { + this->DPDV(i) = values[2]; + } + + ComputeOrthogonalComplement(2, &values[1], true); + + if (this->mNormals) + { + this->Normal(i) = values[3]; + } + if (this->mTangents) + { + this->Tangent(i) = values[1]; + } + if (this->mBitangents) + { + this->Bitangent(i) = values[2]; + } + } + } +} + +template +void RectanglePatchMesh::UpdatePositions() +{ + if (mSurface) + { + InitializePositions(); + } +} + +template +void RectanglePatchMesh::UpdateNormals() +{ + if (mSurface) + { + InitializeNormals(); + } +} + +template +void RectanglePatchMesh::UpdateFrame() +{ + if (mSurface) + { + InitializeFrame(); + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRevolutionMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRevolutionMesh.h new file mode 100644 index 000000000000..a53b2847ca4e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRevolutionMesh.h @@ -0,0 +1,347 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.4 (2018/10/05) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +template +class RevolutionMesh : public Mesh +{ +public: + // The axis of revolution is the z-axis. The curve of revolution is + // p(t) = (x(t),z(t)), where t in [tmin,tmax], x(t) > 0 for t in + // (tmin,tmax), x(tmin) >= 0, and x(tmax) >= 0. The values tmin and tmax + // are those for the curve object passed to the constructor. The curve + // must be non-self-intersecting, except possibly at its endpoints. The + // curve is closed when p(tmin) = p(tmax), in which case the surface of + // revolution has torus topology. The curve is open when p(tmin) != + // p(tmax). For an open curve, define x0 = x(tmin) and x1 = x(tmax). The + // surface has cylinder topology when x0 > 0 and x1 > 0, disk topology + // when exactly one of x0 or x1 is zero, or sphere topology when x0 and + // x1 are both zero. However, to simplify the design, the mesh is always + // built using cylinder topology. The row samples correspond to curve + // points and the column samples correspond to the points on the circles + // of revolution. + RevolutionMesh(MeshDescription const& description, + std::shared_ptr> const& curve, + bool sampleByArcLength = false); + + // Member access. + inline std::shared_ptr> const& GetCurve() const; + inline bool IsSampleByArcLength() const; + +private: + void CreateSampler(); + void InitializeTCoords(); + virtual void UpdatePositions() override; + void UpdateCylinderPositions(); + void UpdateTorusPositions(); + void UpdateDiskPositions(); + void UpdateSpherePositions(); + + std::shared_ptr> mCurve; + bool mSampleByArcLength; + std::vector mCosAngle, mSinAngle; + std::function mTSampler; + std::vector> mSamples; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +RevolutionMesh::RevolutionMesh(MeshDescription const& description, + std::shared_ptr> const& curve, bool sampleByArcLength) + : + Mesh(description, + { MeshTopology::CYLINDER, MeshTopology::TORUS, MeshTopology::DISK, MeshTopology::SPHERE }), + mCurve(curve), + mSampleByArcLength(sampleByArcLength) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + mCurve = nullptr; + return; + } + + if (!mCurve) + { + LogWarning("A nonnull revolution curve is required."); + this->mDescription.constructed = false; + return; + } + + // The four supported topologies all wrap around in the column direction. + mCosAngle.resize(this->mDescription.numCols + 1); + mSinAngle.resize(this->mDescription.numCols + 1); + Real invRadialSamples = (Real)1 / (Real)this->mDescription.numCols; + for (unsigned int c = 0; c < this->mDescription.numCols; ++c) + { + Real angle = c * invRadialSamples * (Real)GTE_C_TWO_PI; + mCosAngle[c] = std::cos(angle); + mSinAngle[c] = std::sin(angle); + } + mCosAngle[this->mDescription.numCols] = mCosAngle[0]; + mSinAngle[this->mDescription.numCols] = mSinAngle[0]; + + CreateSampler(); + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + UpdatePositions(); + if (this->mDescription.allowUpdateFrame) + { + this->UpdateFrame(); + } + else if (this->mNormals) + { + this->UpdateNormals(); + } +} + +template +inline std::shared_ptr> const& RevolutionMesh::GetCurve() const +{ + return mCurve; +} + +template +inline bool RevolutionMesh::IsSampleByArcLength() const +{ + return mSampleByArcLength; +} + +template +void RevolutionMesh::CreateSampler() +{ + if (this->mDescription.topology == MeshTopology::CYLINDER + || this->mDescription.topology == MeshTopology::TORUS) + { + mSamples.resize(this->mDescription.rMax + 1); + } + else if (this->mDescription.topology == MeshTopology::DISK) + { + mSamples.resize(this->mDescription.rMax + 2); + } + else if (this->mDescription.topology == MeshTopology::SPHERE) + { + mSamples.resize(this->mDescription.rMax + 3); + } + + Real invDenom = ((Real)1) / (Real)(mSamples.size() - 1); + if (mSampleByArcLength) + { + Real factor = mCurve->GetTotalLength() * invDenom; + mTSampler = [this, factor](unsigned int i) + { + return mCurve->GetTime(i * factor); + }; + } + else + { + Real factor = (mCurve->GetTMax() - mCurve->GetTMin()) * invDenom; + mTSampler = [this, factor](unsigned int i) + { + return mCurve->GetTMin() + i * factor; + }; + } +} + +template +void RevolutionMesh::InitializeTCoords() +{ + Vector2tcoord; + + switch (this->mDescription.topology) + { + case MeshTopology::CYLINDER: + { + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + break; + } + case MeshTopology::TORUS: + { + for (unsigned int r = 0, i = 0; r <= this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)this->mDescription.numRows; + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + break; + } + case MeshTopology::DISK: + { + Vector2 origin{ (Real)0.5, (Real)0.5 }; + unsigned int i = 0; + for (unsigned int r = 0; r < this->mDescription.numRows; ++r) + { + Real radius = (Real)(r + 1) / (Real)(2 * this->mDescription.numRows); + radius = std::min(radius, (Real)0.5); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + Real angle = (Real)GTE_C_TWO_PI * (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = { radius * std::cos(angle), radius * std::sin(angle) }; + } + } + this->TCoord(i) = origin; + break; + } + case MeshTopology::SPHERE: + { + unsigned int i = 0; + for (unsigned int r = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + this->TCoord(i++) = { (Real)0.5, (Real)0 }; + this->TCoord(i) = { (Real)0.5, (Real)1 }; + break; + } + default: + // Invalid topology is reported by the Mesh constructor, so there is + // no need to log a message here. + break; + } +} + +template +void RevolutionMesh::UpdatePositions() +{ + unsigned int const numSamples = static_cast(mSamples.size()); + for (unsigned int i = 0; i < numSamples; ++i) + { + Real t = mTSampler(i); + Vector2 position = mCurve->GetPosition(t); + mSamples[i][0] = position[0]; + mSamples[i][1] = (Real)0; + mSamples[i][2] = position[1]; + } + + switch (this->mDescription.topology) + { + case MeshTopology::CYLINDER: + UpdateCylinderPositions(); + break; + case MeshTopology::TORUS: + UpdateTorusPositions(); + break; + case MeshTopology::DISK: + UpdateDiskPositions(); + break; + case MeshTopology::SPHERE: + UpdateSpherePositions(); + break; + default: + break; + } +} + +template +void RevolutionMesh::UpdateCylinderPositions() +{ + for (unsigned int r = 0, i = 0; r <= this->mDescription.rMax; ++r) + { + Real radius = mSamples[r][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[r][2] }; + } + } +} + +template +void RevolutionMesh::UpdateTorusPositions() +{ + for (unsigned int r = 0, i = 0; r <= this->mDescription.rMax; ++r) + { + Real radius = mSamples[r][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[r][2] }; + } + } +} + +template +void RevolutionMesh::UpdateDiskPositions() +{ + for (unsigned int r = 0, rp1 = 1, i = 0; r <= this->mDescription.rMax; ++r, ++rp1) + { + Real radius = mSamples[rp1][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[rp1][2] }; + } + } + + this->Position(this->mDescription.numVertices - 1) = { (Real)0, (Real)0, mSamples.front()[2] }; +} + +template +void RevolutionMesh::UpdateSpherePositions() +{ + for (unsigned int r = 0, rp1 = 1, i = 0; r <= this->mDescription.rMax; ++r, ++rp1) + { + Real radius = mSamples[rp1][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[rp1][2] }; + } + } + + this->Position(this->mDescription.numVertices - 2) = { (Real)0, (Real)0, mSamples.front()[2] }; + this->Position(this->mDescription.numVertices - 1) = { (Real)0, (Real)0, mSamples.back()[2] }; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBisection.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBisection.h new file mode 100644 index 000000000000..c8114fb9c12a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBisection.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// Compute a root of a function F(t) on an interval [t0, t1]. The caller +// specifies the maximum number of iterations, in case you want limited +// accuracy for the root. However, the function is designed for native types +// (Real = float/double). If you specify a sufficiently large number of +// iterations, the root finder bisects until either F(t) is identically zero +// [a condition dependent on how you structure F(t) for evaluation] or the +// midpoint (t0 + t1)/2 rounds numerically to tmin or tmax. Of course, it +// is required that t0 < t1. The return value of Find is: +// 0: F(t0)*F(t1) > 0, we cannot determine a root +// 1: F(t0) = 0 or F(t1) = 0 +// 2..maxIterations: the number of bisections plus one +// maxIterations+1: the loop executed without a break (no convergence) + +namespace gte +{ + +template +class RootsBisection +{ +public: + // Use this function when F(t0) and F(t1) are not already known. + static unsigned int Find(std::function const& F, Real t0, + Real t1, unsigned int maxIterations, Real& root); + + // If f0 = F(t0) and f1 = F(t1) are already known, pass them to the + // bisector. This is useful when |f0| or |f1| is infinite, and you can + // pass sign(f0) or sign(f1) rather than then infinity because the + // bisector cares only about the signs of f. + static unsigned int Find(std::function const& F, Real t0, + Real t1, Real f0, Real f1, unsigned int maxIterations, Real& root); +}; + + +template +unsigned int RootsBisection::Find(std::function const& F, + Real t0, Real t1, unsigned int maxIterations, Real& root) +{ + if (t0 < t1) + { + // Test the endpoints to see whether F(t) is zero. + Real f0 = F(t0); + if (f0 == (Real)0) + { + root = t0; + return 1; + } + + Real f1 = F(t1); + if (f1 == (Real)0) + { + root = t1; + return 1; + } + + if (f0*f1 > (Real)0) + { + // It is not known whether the interval bounds a root. + return 0; + } + + unsigned int i; + for (i = 2; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (t0 + t1); + if (root == t0 || root == t1) + { + // The numbers t0 and t1 are consecutive floating-point + // numbers. + break; + } + + Real fm = F(root); + Real product = fm * f0; + if (product < (Real)0) + { + t1 = root; + f1 = fm; + } + else if (product > (Real)0) + { + t0 = root; + f0 = fm; + } + else + { + break; + } + } + return i; + } + else + { + return 0; + } +} + +template +unsigned int RootsBisection::Find(std::function const& F, + Real t0, Real t1, Real f0, Real f1, unsigned int maxIterations, + Real& root) +{ + if (t0 < t1) + { + // Test the endpoints to see whether F(t) is zero. + if (f0 == (Real)0) + { + root = t0; + return 1; + } + + if (f1 == (Real)0) + { + root = t1; + return 1; + } + + if (f0*f1 > (Real)0) + { + // It is not known whether the interval bounds a root. + return 0; + } + + unsigned int i; + for (i = 2; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (t0 + t1); + if (root == t0 || root == t1) + { + // The numbers t0 and t1 are consecutive floating-point + // numbers. + break; + } + + Real fm = F(root); + Real product = fm * f0; + if (product < (Real)0) + { + t1 = root; + f1 = fm; + } + else if (product >(Real)0) + { + t0 = root; + f0 = fm; + } + else + { + break; + } + } + return i; + } + else + { + return 0; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBrentsMethod.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBrentsMethod.h new file mode 100644 index 000000000000..ef81656b4b2b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsBrentsMethod.h @@ -0,0 +1,232 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// This is an implementation of Brent's Method for computing a root of a +// function on an interval [t0,t1] for which F(t0)*F(t1) < 0. The method +// uses inverse quadratic interpolation to generate a root estimate but +// falls back to inverse linear interpolation (secant method) if +// necessary. Moreover, based on previous iterates, the method will fall +// back to bisection when it appears the interpolated estimate is not of +// sufficient quality. +// +// maxIterations: +// The maximum number of iterations used to locate a root. This +// should be positive. +// negFTolerance, posFTolerance: +// The root estimate t is accepted when the function value F(t) +// satisfies negFTolerance <= F(t) <= posFTolerance. The values +// must satisfy: negFTolerance <= 0, posFTolerance >= 0. +// stepTTolerance: +// Brent's Method requires additional tests before an interpolated +// t-value is accepted as the next root estimate. One of these +// tests compares the difference of consecutive iterates and +// requires it to be larger than a user-specified t-tolerance (to +// ensure progress is made). This parameter is that tolerance +// and should be nonnegative. +// convTTolerance: +// The root search is allowed to terminate when the current +// subinterval [tsub0,tsub1] is sufficiently small, say, +// |tsub1 - tsub0| <= tolerance. This parameter is that tolerance +// and should be nonnegative. + +namespace gte +{ + +template +class RootsBrentsMethod +{ +public: + // It is necessary that F(t0)*F(t1) <= 0, in which case the function + // returns 'true' and the 'root' is valid; otherwise, the function returns + // 'false' and 'root' is invalid (do not use it). When F(t0)*F(t1) > 0, + // the interval may very well contain a root but we cannot know that. The + // function also returns 'false' if t0 >= t1. + + static bool Find(std::function const& F, Real t0, Real t1, + unsigned int maxIterations, Real negFTolerance, Real posFTolerance, + Real stepTTolerance, Real convTTolerance, Real& root); +}; + + +template +bool RootsBrentsMethod::Find(std::function const& F, + Real t0, Real t1, unsigned int maxIterations, Real negFTolerance, + Real posFTolerance, Real stepTTolerance, Real convTTolerance, Real& root) +{ + // Parameter validation. + if (t1 <= t0 + || maxIterations == 0 + || negFTolerance > (Real)0 + || posFTolerance < (Real)0 + || stepTTolerance < (Real)0 + || convTTolerance < (Real)0) + { + // The input is invalid. + return false; + } + + Real f0 = F(t0); + if (negFTolerance <= f0 && f0 <= posFTolerance) + { + // This endpoint is an approximate root that satisfies the function + // tolerance. + root = t0; + return true; + } + + Real f1 = F(t1); + if (negFTolerance <= f1 && f1 <= posFTolerance) + { + // This endpoint is an approximate root that satisfies the function + // tolerance. + root = t1; + return true; + } + + if (f0*f1 > (Real)0) + { + // The input interval must bound a root. + return false; + } + + if (std::abs(f0) < std::abs(f1)) + { + // Swap t0 and t1 so that |F(t1)| <= |F(t0)|. The number t1 is + // considered to be the best estimate of the root. + std::swap(t0, t1); + std::swap(f0, f1); + } + + // Initialize values for the root search. + Real t2 = t0, t3 = t0, f2 = f0; + bool prevBisected = true; + + // The root search. + for (unsigned int i = 0; i < maxIterations; ++i) + { + Real fDiff01 = f0 - f1, fDiff02 = f0 - f2, fDiff12 = f1 - f2; + Real invFDiff01 = ((Real)1) / fDiff01; + Real s; + if (fDiff02 != (Real)0 && fDiff12 != (Real)0) + { + // Use inverse quadratic interpolation. + Real infFDiff02 = ((Real)1) / fDiff02; + Real invFDiff12 = ((Real)1) / fDiff12; + s = + t0 * f1 * f2 * invFDiff01 * infFDiff02 - + t1 * f0 * f2 * invFDiff01 * invFDiff12 + + t2 * f0 * f1 * infFDiff02 * invFDiff12; + } + else + { + // Use inverse linear interpolation (secant method). + s = (t1 * f0 - t0 * f1) * invFDiff01; + } + + // Compute values need in the accept-or-reject tests. + Real tDiffSAvr = s - ((Real)0.75) * t0 - ((Real)0.25) * t1; + Real tDiffS1 = s - t1; + Real absTDiffS1 = std::abs(tDiffS1); + Real absTDiff12 = std::abs(t1 - t2); + Real absTDiff23 = std::abs(t2 - t3); + + bool currBisected = false; + if (tDiffSAvr * tDiffS1 > (Real)0) + { + // The value s is not between 0.75*t0 + 0.25*t1 and t1. NOTE: + // The algorithm sometimes has t0 < t1 but sometimes t1 < t0, so + // the between-ness test does not use simple comparisons. + currBisected = true; + } + else if (prevBisected) + { + // The first of Brent's tests to determine whether to accept the + // interpolated s-value. + currBisected = + (absTDiffS1 >= ((Real)0.5) * absTDiff12) || + (absTDiff12 <= stepTTolerance); + } + else + { + // The second of Brent's tests to determine whether to accept the + // interpolated s-value. + currBisected = + (absTDiffS1 >= ((Real)0.5) * absTDiff23) || + (absTDiff23 <= stepTTolerance); + } + + if (currBisected) + { + // One of the additional tests failed, so reject the interpolated + // s-value and use bisection instead. + s = ((Real)0.5) * (t0 + t1); + if (s == t0 || s == t1) + { + // The numbers t0 and t1 are consecutive floating-point + // numbers. + root = s; + return true; + } + prevBisected = true; + } + else + { + prevBisected = false; + } + + // Evaluate the function at the new estimate and test for convergence. + Real fs = F(s); + if (negFTolerance <= fs && fs <= posFTolerance) + { + root = s; + return true; + } + + // Update the subinterval to include the new estimate as an endpoint. + t3 = t2; + t2 = t1; + f2 = f1; + if (f0 * fs < (Real)0) + { + t1 = s; + f1 = fs; + } + else + { + t0 = s; + f0 = fs; + } + + // Allow the algorithm to terminate when the subinterval is + // sufficiently small. + if (std::abs(t1 - t0) <= convTTolerance) + { + root = t1; + return true; + } + + // A loop invariant is that t1 is the root estimate, F(t0)*F(t1) < 0, + // and |F(t1)| <= |F(t0)|. + if (std::abs(f0) < std::abs(f1)) + { + std::swap(t0, t1); + std::swap(f0, f1); + } + } + + // Failed to converge in the specified number of iterations. + return false; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsPolynomial.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsPolynomial.h new file mode 100644 index 000000000000..1fa708543e57 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRootsPolynomial.h @@ -0,0 +1,1158 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +// The Find functions return the number of roots, if any, and this number +// of elements of the outputs are valid. If the polynomial is identically +// zero, Find returns 1. +// +// Some root-bounding algorithms for real-valued roots are mentioned next for +// the polynomial p(t) = c[0] + c[1]*t + ... + c[d-1]*t^{d-1} + c[d]*t^d. +// +// 1. The roots must be contained by the interval [-M,M] where +// M = 1 + max{|c[0]|, ..., |c[d-1]|}/|c[d]| >= 1 +// is called the Cauchy bound. +// +// 2. You may search for roots in the interval [-1,1]. Define +// q(t) = t^d*p(1/t) = c[0]*t^d + c[1]*t^{d-1} + ... + c[d-1]*t + c[d] +// The roots of p(t) not in [-1,1] are the roots of q(t) in [-1,1]. +// +// 3. Between two consecutive roots of the derivative p'(t), say, r0 < r1, +// the function p(t) is strictly monotonic on the open interval (r0,r1). +// If additionally, p(r0) * p(r1) <= 0, then p(x) has a unique root on +// the closed interval [r0,r1]. Thus, one can compute the derivatives +// through order d for p(t), find roots for the derivative of order k+1, +// then use these to bound roots for the derivative of order k. +// +// 4. Sturm sequences of polynomials may be used to determine bounds on the +// roots. This is a more sophisticated approach to root bounding than item 3. +// Moreover, a Sturm sequence allows you to compute the number of real-valued +// roots on a specified interval. +// +// 5. For the low-degree Solve* functions, see +// http://www.geometrictools.com/Documentation/LowDegreePolynomialRoots.pdf + +namespace gte +{ + +template +class RootsPolynomial +{ +public: + // Low-degree root finders. These use exact rational arithmetic for + // theoretically correct root classification. The roots themselves are + // computed with mixed types (rational and floating-point arithmetic). + // The Rational type must support rational arithmetic (+, -, *, /); for + // example, BSRational suffices. The Rational class must + // have single-input constructors where the input is type Real. This + // ensures you can call the Solve* functions with floating-point inputs; + // they will be converted to Rational implicitly. The highest-order + // coefficients must be nonzero (p2 != 0 for quadratic, p3 != 0 for + // cubic, and p4 != 0 for quartic). + + template + static void SolveQuadratic(Rational const& p0, Rational const& p1, + Rational const& p2, std::map& rmMap); + + template + static void SolveCubic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, std::map& rmMap); + + template + static void SolveQuartic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, Rational const& p4, + std::map& rmMap); + + // Return only the number of real-valued roots and their multiplicities. + // info.size() is the number of real-valued roots and info[i] is the + // multiplicity of root corresponding to index i. + template + static void GetRootInfoQuadratic(Rational const& p0, Rational const& p1, + Rational const& p2, std::vector& info); + + template + static void GetRootInfoCubic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, std::vector& info); + + template + static void GetRootInfoQuartic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, Rational const& p4, + std::vector& info); + + + // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c' + // must have at least d+1 elements and the output array 'root' must have + // at least d elements. + + // Find the roots on (-infinity,+infinity). + static int Find(int degree, Real const* c, unsigned int maxIterations, + Real* roots); + + // If you know that p(tmin) * p(tmax) <= 0, then there must be at least + // one root in [tmin, tmax]. Compute it using bisection. + static bool Find(int degree, Real const* c, Real tmin, Real tmax, + unsigned int maxIterations, Real& root); + +private: + // Support for the Solve* functions. + template + static void SolveDepressedQuadratic(Rational const& c0, + std::map& rmMap); + + template + static void SolveDepressedCubic(Rational const& c0, Rational const& c1, + std::map& rmMap); + + template + static void SolveDepressedQuartic(Rational const& c0, Rational const& c1, + Rational const& c2, std::map& rmMap); + + template + static void SolveBiquadratic(Rational const& c0, Rational const& c2, + std::map& rmMap); + + // Support for the GetNumRoots* functions. + template + static void GetRootInfoDepressedQuadratic(Rational const& c0, + std::vector& info); + + template + static void GetRootInfoDepressedCubic(Rational const& c0, + Rational const& c1, std::vector& info); + + template + static void GetRootInfoDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::vector& info); + + template + static void GetRootInfoBiquadratic(Rational const& c0, + Rational const& c2, std::vector& info); + + // Support for the Find functions. + static int FindRecursive(int degree, Real const* c, Real tmin, Real tmax, + unsigned int maxIterations, Real* roots); + + static Real Evaluate(int degree, Real const* c, Real t); +}; + +// FOR INTERNAL USE ONLY. Do not define GTE_ROOTS_LOW_DEGREE_UNIT_TEST in +// your own code. +#if defined(GTE_ROOTS_LOW_DEGREE_UNIT_TEST) +extern void RootsLowDegreeBlock(int); +#define GTE_ROOTS_LOW_DEGREE_BLOCK(block) RootsLowDegreeBlock(block) +#else +#define GTE_ROOTS_LOW_DEGREE_BLOCK(block) +#endif + + +template +template +void RootsPolynomial::SolveQuadratic(Rational const& p0, + Rational const& p1, Rational const& p2, std::map& rmMap) +{ + Rational const rat2 = 2; + Rational q0 = p0 / p2; + Rational q1 = p1 / p2; + Rational q1half = q1 / rat2; + Rational c0 = q0 - q1half * q1half; + + std::map rmLocalMap; + SolveDepressedQuadratic(c0, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q1half; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } +} + +template +template +void RootsPolynomial::SolveCubic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + std::map& rmMap) +{ + Rational const rat2 = 2, rat3 = 3; + Rational q0 = p0 / p3; + Rational q1 = p1 / p3; + Rational q2 = p2 / p3; + Rational q2third = q2 / rat3; + Rational c0 = q0 - q2third * (q1 - rat2 * q2third * q2third); + Rational c1 = q1 - q2 * q2third; + + std::map rmLocalMap; + SolveDepressedCubic(c0, c1, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q2third; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } +} + +template +template +void RootsPolynomial::SolveQuartic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + Rational const& p4, std::map& rmMap) +{ + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat6 = 6; + Rational q0 = p0 / p4; + Rational q1 = p1 / p4; + Rational q2 = p2 / p4; + Rational q3 = p3 / p4; + Rational q3fourth = q3 / rat4; + Rational q3fourthSqr = q3fourth * q3fourth; + Rational c0 = q0 - q3fourth * (q1 - q3fourth * (q2 - q3fourthSqr * rat3)); + Rational c1 = q1 - rat2 * q3fourth * (q2 - rat4 * q3fourthSqr); + Rational c2 = q2 - rat6 * q3fourthSqr; + + std::map rmLocalMap; + SolveDepressedQuartic(c0, c1, c2, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q3fourth; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } +} + +template +template +void RootsPolynomial::GetRootInfoQuadratic(Rational const& p0, + Rational const& p1, Rational const& p2, std::vector& info) +{ + Rational const rat2 = 2; + Rational q0 = p0 / p2; + Rational q1 = p1 / p2; + Rational q1half = q1 / rat2; + Rational c0 = q0 - q1half * q1half; + + info.clear(); + info.reserve(2); + GetRootInfoDepressedQuadratic(c0, info); +} + +template +template +void RootsPolynomial::GetRootInfoCubic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + std::vector& info) +{ + Rational const rat2 = 2, rat3 = 3; + Rational q0 = p0 / p3; + Rational q1 = p1 / p3; + Rational q2 = p2 / p3; + Rational q2third = q2 / rat3; + Rational c0 = q0 - q2third * (q1 - rat2 * q2third * q2third); + Rational c1 = q1 - q2 * q2third; + + info.clear(); + info.reserve(3); + GetRootInfoDepressedCubic(c0, c1, info); +} + +template +template +void RootsPolynomial::GetRootInfoQuartic(Rational const& p0, + Rational const& p1, Rational const& p2, Rational const& p3, + Rational const& p4, std::vector& info) +{ + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat6 = 6; + Rational q0 = p0 / p4; + Rational q1 = p1 / p4; + Rational q2 = p2 / p4; + Rational q3 = p3 / p4; + Rational q3fourth = q3 / rat4; + Rational q3fourthSqr = q3fourth * q3fourth; + Rational c0 = q0 - q3fourth * (q1 - q3fourth * (q2 - q3fourthSqr * rat3)); + Rational c1 = q1 - rat2 * q3fourth * (q2 - rat4 * q3fourthSqr); + Rational c2 = q2 - rat6 * q3fourthSqr; + + info.clear(); + info.reserve(4); + GetRootInfoDepressedQuartic(c0, c1, c2, info); +} + +template +int RootsPolynomial::Find(int degree, Real const* c, + unsigned int maxIterations, Real* roots) +{ + if (degree >= 0 && c) + { + Real const zero = (Real)0; + while (degree >= 0 && c[degree] == zero) + { + --degree; + } + + if (degree > 0) + { + // Compute the Cauchy bound. + Real const one = (Real)1; + Real invLeading = one / c[degree]; + Real maxValue = zero; + for (int i = 0; i < degree; ++i) + { + Real value = std::abs(c[i] * invLeading); + if (value > maxValue) + { + maxValue = value; + } + } + Real bound = one + maxValue; + + return FindRecursive(degree, c, -bound, bound, maxIterations, + roots); + } + else if (degree == 0) + { + // The polynomial is a nonzero constant. + return 0; + } + else + { + // The polynomial is identically zero. + roots[0] = zero; + return 1; + } + } + else + { + // Invalid degree or c. + return 0; + } +} + +template +bool RootsPolynomial::Find(int degree, Real const* c, Real tmin, + Real tmax, unsigned int maxIterations, Real& root) +{ + Real const zero = (Real)0; + Real pmin = Evaluate(degree, c, tmin); + if (pmin == zero) + { + root = tmin; + return true; + } + Real pmax = Evaluate(degree, c, tmax); + if (pmax == zero) + { + root = tmax; + return true; + } + + if (pmin*pmax > zero) + { + // It is not known whether the interval bounds a root. + return false; + } + + if (tmin >= tmax) + { + // Invalid ordering of interval endpoitns. + return false; + } + + for (unsigned int i = 1; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (tmin + tmax); + + // This test is designed for 'float' or 'double' when tmin and tmax + // are consecutive floating-point numbers. + if (root == tmin || root == tmax) + { + break; + } + + Real p = Evaluate(degree, c, root); + Real product = p * pmin; + if (product < zero) + { + tmax = root; + pmax = p; + } + else if (product > zero) + { + tmin = root; + pmin = p; + } + else + { + break; + } + } + + return true; +} + +template +template +void RootsPolynomial::SolveDepressedQuadratic(Rational const& c0, + std::map& rmMap) +{ + Rational const zero = 0; + if (c0 < zero) + { + // Two simple roots. + Rational root1 = (Rational)std::sqrt(-(double)c0); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(0); + } + else if (c0 == zero) + { + // One double root. + rmMap.insert(std::make_pair(zero, 2)); + GTE_ROOTS_LOW_DEGREE_BLOCK(1); + } + else // c0 > 0 + { + // A complex-conjugate pair of roots. + // Complex z0 = -q1/2 - i*sqrt(c0); + // Complex z0conj = -q1/2 + i*sqrt(c0); + GTE_ROOTS_LOW_DEGREE_BLOCK(2); + } +} + +template +template +void RootsPolynomial::SolveDepressedCubic(Rational const& c0, + Rational const& c1, std::map& rmMap) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed quadratic. + Rational const zero = 0; + if (c0 == zero) + { + SolveDepressedQuadratic(c1, rmMap); + auto iter = rmMap.find(zero); + if (iter != rmMap.end()) + { + // The quadratic has a root of zero, so the multiplicity must be + // increased. + ++iter->second; + GTE_ROOTS_LOW_DEGREE_BLOCK(3); + } + else + { + // The quadratic does not have a root of zero. Insert the one + // for the cubic. + rmMap.insert(std::make_pair(zero, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(4); + } + return; + } + + // Handle the special case of c0 != 0 and c1 = 0. + double const oneThird = 1.0 / 3.0; + if (c1 == zero) + { + // One simple real root. + Rational root0; + if (c0 > zero) + { + root0 = (Rational)-std::pow((double)c0, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(5); + } + else + { + root0 = (Rational)std::pow(-(double)c0, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(6); + } + rmMap.insert(std::make_pair(root0, 1)); + + // One complex conjugate pair. + // Complex z0 = root0*(-1 - i*sqrt(3))/2; + // Complex z0conj = root0*(-1 + i*sqrt(3))/2; + return; + } + + // At this time, c0 != 0 and c1 != 0. + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat27 = 27, rat108 = 108; + Rational delta = -(rat4 * c1 * c1 * c1 + rat27 * c0 * c0); + if (delta > zero) + { + // Three simple roots. + Rational deltaDiv108 = delta / rat108; + Rational betaRe = -c0 / rat2; + Rational betaIm = std::sqrt(deltaDiv108); + Rational theta = std::atan2(betaIm, betaRe); + Rational thetaDiv3 = theta / rat3; + double angle = (double)thetaDiv3; + Rational cs = (Rational)std::cos(angle); + Rational sn = (Rational)std::sin(angle); + Rational rhoSqr = betaRe * betaRe + betaIm * betaIm; + Rational rhoPowThird = (Rational)std::pow((double)rhoSqr, 1.0 / 6.0); + Rational temp0 = rhoPowThird * cs; + Rational temp1 = rhoPowThird * sn * (Rational)std::sqrt(3.0); + Rational root0 = rat2 * temp0; + Rational root1 = -temp0 - temp1; + Rational root2 = -temp0 + temp1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(7); + } + else if (delta < zero) + { + // One simple root. + Rational deltaDiv108 = delta / rat108; + Rational temp0 = -c0 / rat2; + Rational temp1 = (Rational)std::sqrt(-(double)deltaDiv108); + Rational temp2 = temp0 - temp1; + Rational temp3 = temp0 + temp1; + if (temp2 >= zero) + { + temp2 = (Rational)std::pow((double)temp2, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(8); + } + else + { + temp2 = (Rational)-std::pow(-(double)temp2, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(9); + } + if (temp3 >= zero) + { + temp3 = (Rational)std::pow((double)temp3, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(10); + } + else + { + temp3 = (Rational)-std::pow(-(double)temp3, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(11); + } + Rational root0 = temp2 + temp3; + rmMap.insert(std::make_pair(root0, 1)); + + // One complex conjugate pair. + // Complex z0 = (-root0 - i*sqrt(3*root0*root0+4*c1))/2; + // Complex z0conj = (-root0 + i*sqrt(3*root0*root0+4*c1))/2; + } + else // delta = 0 + { + // One simple root and one double root. + Rational root0 = -rat3 * c0 / (rat2 * c1); + Rational root1 = -rat2 * root0; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(12); + } +} + +template +template +void RootsPolynomial::SolveDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::map& rmMap) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed cubic. + Rational const zero = 0; + if (c0 == zero) + { + SolveDepressedCubic(c1, c2, rmMap); + auto iter = rmMap.find(zero); + if (iter != rmMap.end()) + { + // The cubic has a root of zero, so the multiplicity must be + // increased. + ++iter->second; + GTE_ROOTS_LOW_DEGREE_BLOCK(13); + } + else + { + // The cubic does not have a root of zero. Insert the one + // for the quartic. + rmMap.insert(std::make_pair(zero, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(14); + } + return; + } + + // Handle the special case of c1 = 0, in which case the quartic is a + // biquadratic x^4 + c1*x^2 + c0 = (x^2 + c2/2)^2 + (c0 - c2^2/4). + if (c1 == zero) + { + SolveBiquadratic(c0, c2, rmMap); + return; + } + + // At this time, c0 != 0 and c1 != 0, which is a requirement for the + // general solver that must use a root of a special cubic polynomial. + Rational const rat2 = 2, rat4 = 4, rat8 = 8, rat12 = 12, rat16 = 16; + Rational const rat27 = 27, rat36 = 36; + Rational c0sqr = c0 * c0, c1sqr = c1 * c1, c2sqr = c2 * c2; + Rational delta = c1sqr * (-rat27 * c1sqr + rat4 * c2 * + (rat36 * c0 - c2sqr)) + rat16 * c0 * (c2sqr * (c2sqr - rat8 * c0) + + rat16 * c0sqr); + Rational a0 = rat12 * c0 + c2sqr; + Rational a1 = rat4 * c0 - c2sqr; + + if (delta > zero) + { + if (c2 < zero && a1 < zero) + { + // Four simple real roots. + std::map rmCubicMap; + SolveCubic(c1sqr - rat4 * c0 * c2, rat8 * c0, rat4 * c2, -rat8, + rmCubicMap); + Rational t = (Rational)rmCubicMap.rbegin()->first; + Rational alphaSqr = rat2 * t - c2; + Rational alpha = (Rational)std::sqrt((double)alphaSqr); + double sgnC1; + if (c1 > zero) + { + sgnC1 = 1.0; + GTE_ROOTS_LOW_DEGREE_BLOCK(15); + } + else + { + sgnC1 = -1.0; + GTE_ROOTS_LOW_DEGREE_BLOCK(16); + } + Rational arg = t * t - c0; + Rational beta = (Rational)(sgnC1 * std::sqrt(std::max((double)arg, 0.0))); + Rational D0 = alphaSqr - rat4 * (t + beta); + Rational sqrtD0 = (Rational)std::sqrt(std::max((double)D0, 0.0)); + Rational D1 = alphaSqr - rat4 * (t - beta); + Rational sqrtD1 = (Rational)std::sqrt(std::max((double)D1, 0.0)); + Rational root0 = (alpha - sqrtD0) / rat2; + Rational root1 = (alpha + sqrtD0) / rat2; + Rational root2 = (-alpha - sqrtD1) / rat2; + Rational root3 = (-alpha + sqrtD1) / rat2; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + rmMap.insert(std::make_pair(root3, 1)); + } + else // c2 >= 0 or a1 >= 0 + { + // Two complex-conjugate pairs. The values alpha, D0, and D1 are + // those of the if-block. + // Complex z0 = (alpha - i*sqrt(-D0))/2; + // Complex z0conj = (alpha + i*sqrt(-D0))/2; + // Complex z1 = (-alpha - i*sqrt(-D1))/2; + // Complex z1conj = (-alpha + i*sqrt(-D1))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(17); + } + } + else if (delta < zero) + { + // Two simple real roots, one complex-conjugate pair. + std::map rmCubicMap; + SolveCubic(c1sqr - rat4 * c0 * c2, rat8 * c0, rat4 * c2, -rat8, + rmCubicMap); + Rational t = (Rational)rmCubicMap.rbegin()->first; + Rational alphaSqr = rat2 * t - c2; + Rational alpha = (Rational)std::sqrt(std::max((double)alphaSqr, 0.0)); + double sgnC1; + if (c1 > zero) + { + sgnC1 = 1.0; // Leads to BLOCK(18) + } + else + { + sgnC1 = -1.0; // Leads to BLOCK(19) + } + Rational arg = t * t - c0; + Rational beta = (Rational)(sgnC1 * std::sqrt(std::max((double)arg, 0.0))); + Rational root0, root1; + if (sgnC1 > 0.0) + { + Rational D1 = alphaSqr - rat4 * (t - beta); + Rational sqrtD1 = (Rational)std::sqrt(std::max((double)D1, 0.0)); + root0 = (-alpha - sqrtD1) / rat2; + root1 = (-alpha + sqrtD1) / rat2; + + // One complex conjugate pair. + // Complex z0 = (alpha - i*sqrt(-D0))/2; + // Complex z0conj = (alpha + i*sqrt(-D0))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(18); + } + else + { + Rational D0 = alphaSqr - rat4 * (t + beta); + Rational sqrtD0 = (Rational)std::sqrt(std::max((double)D0, 0.0)); + root0 = (alpha - sqrtD0) / rat2; + root1 = (alpha + sqrtD0) / rat2; + + // One complex conjugate pair. + // Complex z0 = (-alpha - i*sqrt(-D1))/2; + // Complex z0conj = (-alpha + i*sqrt(-D1))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(19); + } + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + } + else // delta = 0 + { + if (a1 > zero || (c2 > zero && (a1 != zero || c1 != zero))) + { + // One double real root, one complex-conjugate pair. + Rational const rat9 = 9; + Rational root0 = -c1 * a0 / (rat9 * c1sqr - rat2 * c2 * a1); + rmMap.insert(std::make_pair(root0, 2)); + + // One complex conjugate pair. + // Complex z0 = -root0 - i*sqrt(c2 + root0^2); + // Complex z0conj = -root0 + i*sqrt(c2 + root0^2); + GTE_ROOTS_LOW_DEGREE_BLOCK(20); + } + else + { + Rational const rat3 = 3; + if (a0 != zero) + { + // One double real root, two simple real roots. + Rational const rat9 = 9; + Rational root0 = -c1 * a0 / (rat9 * c1sqr - rat2 * c2 * a1); + Rational alpha = rat2 * root0; + Rational beta = c2 + rat3 * root0 * root0; + Rational discr = alpha * alpha - rat4 * beta; + Rational temp1 = (Rational)std::sqrt((double)discr); + Rational root1 = (-alpha - temp1) / rat2; + Rational root2 = (-alpha + temp1) / rat2; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(21); + } + else + { + // One triple real root, one simple real root. + Rational root0 = -rat3 * c1 / (rat4 * c2); + Rational root1 = -rat3 * root0; + rmMap.insert(std::make_pair(root0, 3)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(22); + } + } + } +} + +template +template +void RootsPolynomial::SolveBiquadratic(Rational const& c0, + Rational const& c2, std::map& rmMap) +{ + // Solve 0 = x^4 + c2*x^2 + c0 = (x^2 + c2/2)^2 + a1, where + // a1 = c0 - c2^2/2. We know that c0 != 0 at the time of the function + // call, so x = 0 is not a root. The condition c1 = 0 implies the quartic + // Delta = 256*c0*a1^2. + + Rational const zero = 0, rat2 = 2, rat256 = 256; + Rational c2Half = c2 / rat2; + Rational a1 = c0 - c2Half * c2Half; + Rational delta = rat256 * c0 * a1 * a1; + if (delta > zero) + { + if (c2 < zero) + { + if (a1 < zero) + { + // Four simple roots. + Rational temp0 = (Rational)std::sqrt(-(double)a1); + Rational temp1 = -c2Half - temp0; + Rational temp2 = -c2Half + temp0; + Rational root1 = (Rational)std::sqrt((double)temp1); + Rational root0 = -root1; + Rational root2 = (Rational)std::sqrt((double)temp2); + Rational root3 = -root2; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + rmMap.insert(std::make_pair(root3, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(23); + } + else // a1 > 0 + { + // Two simple complex conjugate pairs. + // double thetaDiv2 = atan2(sqrt(a1), -c2/2) / 2.0; + // double cs = cos(thetaDiv2), sn = sin(thetaDiv2); + // double length = pow(c0, 0.25); + // Complex z0 = length*(cs + i*sn); + // Complex z0conj = length*(cs - i*sn); + // Complex z1 = length*(-cs + i*sn); + // Complex z1conj = length*(-cs - i*sn); + GTE_ROOTS_LOW_DEGREE_BLOCK(24); + } + } + else // c2 >= 0 + { + // Two simple complex conjugate pairs. + // Complex z0 = -i*sqrt(c2/2 - sqrt(-a1)); + // Complex z0conj = +i*sqrt(c2/2 - sqrt(-a1)); + // Complex z1 = -i*sqrt(c2/2 + sqrt(-a1)); + // Complex z1conj = +i*sqrt(c2/2 + sqrt(-a1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(25); + } + } + else if (delta < zero) + { + // Two simple real roots. + Rational temp0 = (Rational)std::sqrt(-(double)a1); + Rational temp1 = -c2Half + temp0; + Rational root1 = (Rational)std::sqrt((double)temp1); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + + // One complex conjugate pair. + // Complex z0 = -i*sqrt(c2/2 + sqrt(-a1)); + // Complex z0conj = +i*sqrt(c2/2 + sqrt(-a1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(26); + } + else // delta = 0 + { + if (c2 < zero) + { + // Two double real roots. + Rational root1 = (Rational)std::sqrt(-(double)c2Half); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 2)); + GTE_ROOTS_LOW_DEGREE_BLOCK(27); + } + else // c2 > 0 + { + // Two double complex conjugate pairs. + // Complex z0 = -i*sqrt(c2/2); // multiplicity 2 + // Complex z0conj = +i*sqrt(c2/2); // multiplicity 2 + GTE_ROOTS_LOW_DEGREE_BLOCK(28); + } + } +} + +template +template +void RootsPolynomial::GetRootInfoDepressedQuadratic(Rational const& c0, + std::vector& info) +{ + Rational const zero = 0; + if (c0 < zero) + { + // Two simple roots. + info.push_back(1); + info.push_back(1); + } + else if (c0 == zero) + { + // One double root. + info.push_back(2); // root is zero + } + else // c0 > 0 + { + // A complex-conjugate pair of roots. + } +} + +template +template +void RootsPolynomial::GetRootInfoDepressedCubic(Rational const& c0, + Rational const& c1, std::vector& info) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed quadratic. + Rational const zero = 0; + if (c0 == zero) + { + if (c1 == zero) + { + info.push_back(3); // triple root of zero + } + else + { + info.push_back(1); // simple root of zero + GetRootInfoDepressedQuadratic(c1, info); + } + return; + } + + Rational const rat4 = 4, rat27 = 27; + Rational delta = -(rat4 * c1 * c1 * c1 + rat27 * c0 * c0); + if (delta > zero) + { + // Three simple real roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else if (delta < zero) + { + // One simple real root. + info.push_back(1); + } + else // delta = 0 + { + // One simple real root and one double real root. + info.push_back(1); + info.push_back(2); + } +} + +template +template +void RootsPolynomial::GetRootInfoDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::vector& info) +{ + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed cubic. + Rational const zero = 0; + if (c0 == zero) + { + if (c1 == zero) + { + if (c2 == zero) + { + info.push_back(4); // quadruple root of zero + } + else + { + info.push_back(2); // double root of zero + GetRootInfoDepressedQuadratic(c2, info); + } + } + else + { + info.push_back(1); // simple root of zero + GetRootInfoDepressedCubic(c1, c2, info); + } + return; + } + + // Handle the special case of c1 = 0, in which case the quartic is a + // biquadratic x^4 + c1*x^2 + c0 = (x^2 + c2/2)^2 + (c0 - c2^2/4). + if (c1 == zero) + { + GetRootInfoBiquadratic(c0, c2, info); + return; + } + + // At this time, c0 != 0 and c1 != 0, which is a requirement for the + // general solver that must use a root of a special cubic polynomial. + Rational const rat4 = 4, rat8 = 8, rat12 = 12, rat16 = 16; + Rational const rat27 = 27, rat36 = 36; + Rational c0sqr = c0 * c0, c1sqr = c1 * c1, c2sqr = c2 * c2; + Rational delta = c1sqr * (-rat27 * c1sqr + rat4 * c2 * + (rat36 * c0 - c2sqr)) + rat16 * c0 * (c2sqr * (c2sqr - rat8 * c0) + + rat16 * c0sqr); + Rational a0 = rat12 * c0 + c2sqr; + Rational a1 = rat4 * c0 - c2sqr; + + if (delta > zero) + { + if (c2 < zero && a1 < zero) + { + // Four simple real roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else // c2 >= 0 or a1 >= 0 + { + // Two complex-conjugate pairs. + } + } + else if (delta < zero) + { + // Two simple real roots, one complex-conjugate pair. + info.push_back(1); + info.push_back(1); + } + else // delta = 0 + { + if (a1 > zero || (c2 > zero && (a1 != zero || c1 != zero))) + { + // One double real root, one complex-conjugate pair. + info.push_back(2); + } + else + { + if (a0 != zero) + { + // One double real root, two simple real roots. + info.push_back(2); + info.push_back(1); + info.push_back(1); + } + else + { + // One triple real root, one simple real root. + info.push_back(3); + info.push_back(1); + } + } + } +} + +template +template +void RootsPolynomial::GetRootInfoBiquadratic(Rational const& c0, + Rational const& c2, std::vector& info) +{ + // Solve 0 = x^4 + c2*x^2 + c0 = (x^2 + c2/2)^2 + a1, where + // a1 = c0 - c2^2/2. We know that c0 != 0 at the time of the function + // call, so x = 0 is not a root. The condition c1 = 0 implies the quartic + // Delta = 256*c0*a1^2. + + Rational const zero = 0, rat2 = 2, rat256 = 256; + Rational c2Half = c2 / rat2; + Rational a1 = c0 - c2Half * c2Half; + Rational delta = rat256 * c0 * a1 * a1; + if (delta > zero) + { + if (c2 < zero) + { + if (a1 < zero) + { + // Four simple roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else // a1 > 0 + { + // Two simple complex conjugate pairs. + } + } + else // c2 >= 0 + { + // Two simple complex conjugate pairs. + } + } + else if (delta < zero) + { + // Two simple real roots, one complex conjugate pair. + info.push_back(1); + info.push_back(1); + } + else // delta = 0 + { + if (c2 < zero) + { + // Two double real roots. + info.push_back(2); + info.push_back(2); + } + else // c2 > 0 + { + // Two double complex conjugate pairs. + } + } +} + +template +int RootsPolynomial::FindRecursive(int degree, Real const* c, + Real tmin, Real tmax, unsigned int maxIterations, Real* roots) +{ + // The base of the recursion. + Real const zero = (Real)0; + Real root = zero; + if (degree == 1) + { + int numRoots; + if (c[1] != zero) + { + root = -c[0] / c[1]; + numRoots = 1; + } + else if (c[0] == zero) + { + root = zero; + numRoots = 1; + } + else + { + numRoots = 0; + } + + if (numRoots > 0 && tmin <= root && root <= tmax) + { + roots[0] = root; + return 1; + } + return 0; + } + + // Find the roots of the derivative polynomial scaled by 1/degree. The + // scaling avoids the factorial growth in the coefficients; for example, + // without the scaling, the high-order term x^d becomes (d!)*x through + // multiple differentiations. With the scaling we instead get x. This + // leads to better numerical behavior of the root finder. + int derivDegree = degree - 1; + std::vector derivCoeff(derivDegree + 1); + std::vector derivRoots(derivDegree); + for (int i = 0; i <= derivDegree; ++i) + { + derivCoeff[i] = c[i + 1] * (Real)(i + 1) / (Real)degree; + } + int numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, + maxIterations, &derivRoots[0]); + + int numRoots = 0; + if (numDerivRoots > 0) + { + // Find root on [tmin,derivRoots[0]]. + if (Find(degree, c, tmin, derivRoots[0], maxIterations, root)) + { + roots[numRoots++] = root; + } + + // Find root on [derivRoots[i],derivRoots[i+1]]. + for (int i = 0; i <= numDerivRoots - 2; ++i) + { + if (Find(degree, c, derivRoots[i], derivRoots[i + 1], + maxIterations, root)) + { + roots[numRoots++] = root; + } + } + + // Find root on [derivRoots[numDerivRoots-1],tmax]. + if (Find(degree, c, derivRoots[numDerivRoots - 1], tmax, + maxIterations, root)) + { + roots[numRoots++] = root; + } + } + else + { + // The polynomial is monotone on [tmin,tmax], so has at most one root. + if (Find(degree, c, tmin, tmax, maxIterations, root)) + { + roots[numRoots++] = root; + } + } + return numRoots; +} + +template +Real RootsPolynomial::Evaluate(int degree, Real const* c, Real t) +{ + int i = degree; + Real result = c[i]; + while (--i >= 0) + { + result = t * result + c[i]; + } + return result; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRotation.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRotation.h new file mode 100644 index 000000000000..e3cb76c1238e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteRotation.h @@ -0,0 +1,892 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +// Conversions among various representations of rotations. The value of +// N must be 3 or 4. The latter case supports affine algebra when you use +// 4-tuple vectors (w-component is 1 for points and 0 for vector) and 4x4 +// matrices for affine transformations. Rotation axes must be unit length. +// The angles are in radians. The Euler angles are in world coordinates; +// we have not yet added support for body coordinates. + +template +class Rotation +{ +public: + // Create rotations from various representations. + Rotation(Matrix const& matrix); + Rotation(Quaternion const& quaternion); + Rotation(AxisAngle const& axisAngle); + Rotation(EulerAngles const& eulerAngles); + + // Convert one representation to another. + operator Matrix() const; + operator Quaternion() const; + operator AxisAngle() const; + EulerAngles const& operator()(int i0, int i1, int i2) const; + +private: + enum RepresentationType + { + IS_MATRIX, + IS_QUATERNION, + IS_AXIS_ANGLE, + IS_EULER_ANGLES + }; + + RepresentationType mType; + mutable Matrix mMatrix; + mutable Quaternion mQuaternion; + mutable AxisAngle mAxisAngle; + mutable EulerAngles mEulerAngles; + + // Convert a rotation matrix to a quaternion. + // + // x^2 = (+r00 - r11 - r22 + 1)/4 + // y^2 = (-r00 + r11 - r22 + 1)/4 + // z^2 = (-r00 - r11 + r22 + 1)/4 + // w^2 = (+r00 + r11 + r22 + 1)/4 + // x^2 + y^2 = (1 - r22)/2 + // z^2 + w^2 = (1 + r22)/2 + // y^2 - x^2 = (r11 - r00)/2 + // w^2 - z^2 = (r11 + r00)/2 + // x*y = (r01 + r10)/4 + // x*z = (r02 + r20)/4 + // y*z = (r12 + r21)/4 + // [GTE_USE_MAT_VEC] + // x*w = (r21 - r12)/4 + // y*w = (r02 - r20)/4 + // z*w = (r10 - r01)/4 + // [GTE_USE_VEC_MAT] + // x*w = (r12 - r21)/4 + // y*w = (r20 - r02)/4 + // z*w = (r01 - r10)/4 + // + // If Q is the 4x1 column vector (x,y,z,w), the previous equations give us + // +- -+ + // | x*x x*y x*z x*w | + // Q*Q^T = | y*x y*y y*z y*w | + // | z*x z*y z*z z*w | + // | w*x w*y w*z w*w | + // +- -+ + // The code extracts the row of maximum length, normalizing it to obtain + // the result q. + static void Convert(Matrix const& r, Quaternion& q); + + // Convert a quaterion q = x*i + y*j + z*k + w to a rotation matrix. + // [GTE_USE_MAT_VEC] + // +- -+ +- -+ + // R = | r00 r01 r02 | = | 1-2y^2-2z^2 2(xy-zw) 2(xz+yw) | + // | r10 r11 r12 | | 2(xy+zw) 1-2x^2-2z^2 2(yz-xw) | + // | r20 r21 r22 | | 2(xz-yw) 2(yz+xw) 1-2x^2-2y^2 | + // +- -+ +- -+ + // [GTE_USE_VEC_MAT] + // +- -+ +- -+ + // R = | r00 r01 r02 | = | 1-2y^2-2z^2 2(xy+zw) 2(xz-yw) | + // | r10 r11 r12 | | 2(xy-zw) 1-2x^2-2z^2 2(yz+xw) | + // | r20 r21 r22 | | 2(xz+yw) 2(yz-xw) 1-2x^2-2y^2 | + // +- -+ +- -+ + static void Convert(Quaternion const& q, Matrix& r); + + // Convert a rotation matrix to an axis-angle pair. Let (x0,x1,x2) be the + // axis let t be an angle of rotation. The rotation matrix is + // [GTE_USE_MAT_VEC] + // R = I + sin(t)*S + (1-cos(t))*S^2 + // or + // [GTE_USE_VEC_MAT] + // R = I - sin(t)*S + (1-cos(t))*S^2 + // where I is the identity and S = {{0,-x2,x1},{x2,0,-x0},{-x1,x0,0}} + // where the inner-brace triples are the rows of the matrix. If t > 0, + // R represents a counterclockwise rotation; see the comments for the + // constructor Matrix3x3(axis,angle). It may be shown that cos(t) = + // (trace(R)-1)/2 and R - Transpose(R) = 2*sin(t)*S. As long as sin(t) is + // not zero, we may solve for S in the second equation, which produces the + // axis direction U = (S21,S02,S10). When t = 0, the rotation is the + // identity, in which case any axis direction is valid; we choose (1,0,0). + // When t = pi, it must be that R - Transpose(R) = 0, which prevents us + // from extracting the axis. Instead, note that (R+I)/2 = I+S^2 = U*U^T, + // where U is a unit-length axis direction. + static void Convert(Matrix const& r, AxisAngle& a); + + // Convert an axis-angle pair to a rotation matrix. Assuming (x0,x1,x2) + // is for a right-handed world (x0 to right, x1 up, x2 out of plane of + // page), a positive angle corresponds to a counterclockwise rotation from + // the perspective of an observer looking at the origin of the plane of + // rotation and having view direction the negative of the axis direction. + // The coordinate-axis rotations are the following, where + // unit(0) = (1,0,0), unit(1) = (0,1,0), unit(2) = (0,0,1), + // [GTE_USE_MAT_VEC] + // R(unit(0),t) = {{ 1, 0, 0}, { 0, c,-s}, { 0, s, c}} + // R(unit(1),t) = {{ c, 0, s}, { 0, 1, 0}, {-s, 0, c}} + // R(unit(2),t) = {{ c,-s, 0}, { s, c, 0}, { 0, 0, 1}} + // or + // [GTE_USE_VEC_MAT] + // R(unit(0),t) = {{ 1, 0, 0}, { 0, c, s}, { 0,-s, c}} + // R(unit(1),t) = {{ c, 0,-s}, { 0, 1, 0}, { s, 0, c}} + // R(unit(2),t) = {{ c, s, 0}, {-s, c, 0}, { 0, 0, 1}} + // where c = cos(t), s = sin(t), and the inner-brace triples are rows of + // the matrix. The general matrix is + // [GTE_USE_MAT_VEC] + // +- -+ + // R = | (1-c)*x0^2 + c (1-c)*x0*x1 - s*x2 (1-c)*x0*x2 + s*x1 | + // | (1-c)*x0*x1 + s*x2 (1-c)*x1^2 + c (1-c)*x1*x2 - s*x0 | + // | (1-c)*x0*x2 - s*x1 (1-c)*x1*x2 + s*x0 (1-c)*x2^2 + c | + // +- -+ + // [GTE_USE_VEC_MAT] + // +- -+ + // R = | (1-c)*x0^2 + c (1-c)*x0*x1 + s*x2 (1-c)*x0*x2 - s*x1 | + // | (1-c)*x0*x1 - s*x2 (1-c)*x1^2 + c (1-c)*x1*x2 + s*x0 | + // | (1-c)*x0*x2 + s*x1 (1-c)*x1*x2 - s*x0 (1-c)*x2^2 + c | + // +- -+ + static void Convert(AxisAngle const& a, Matrix& r); + + // Convert a rotation matrix to Euler angles. Factorization into Euler + // angles is not necessarily unique. If the result is ER_NOT_UNIQUE_SUM, + // then the multiple solutions occur because angleN2+angleN0 is constant. + // If the result is ER_NOT_UNIQUE_DIF, then the multiple solutions occur + // because angleN2-angleN0 is constant. In either type of nonuniqueness, + // the function returns angleN0=0. + static void Convert(Matrix const& r, EulerAngles& e); + + // Convert Euler angles to a rotation matrix. The three integer inputs + // are in {0,1,2} and correspond to world directions unit(0) = (1,0,0), + // unit(1) = (0,1,0), or unit(2) = (0,0,1). The triples (N0,N1,N2) must + // be in the following set, + // {(0,1,2),(0,2,1),(1,0,2),(1,2,0),(2,0,1),(2,1,0), + // (0,1,0),(0,2,0),(1,0,1),(1,2,1),(2,0,2),(2,1,2)} + // The rotation matrix is + // [GTE_USE_MAT_VEC] + // R(unit(N2),angleN2)*R(unit(N1),angleN1)*R(unit(N0),angleN0) + // or + // [GTE_USE_VEC_MAT] + // R(unit(N0),angleN0)*R(unit(N1),angleN1)*R(unit(N2),angleN2) + // The conventions of constructor Matrix3(axis,angle) apply here as well. + // + // NOTE: The reversal of order is chosen so that a rotation matrix built + // with one multiplication convention is the transpose of the rotation + // matrix built with the other multiplication convention. Thus, + // [GTE_USE_MAT_VEC] + // Matrix3x3 R_mvconvention(N0,N1,N2,angleN0,angleN1,angleN2); + // Vector3 V(...); + // Vector3 U = R_mvconvention*V; // (u0,u1,u2) = R2*R1*R0*V + // [GTE_USE_VEC_MAT] + // Matrix3x3 R_vmconvention(N0,N1,N2,angleN0,angleN1,angleN2); + // Vector3 V(...); + // Vector3 U = R_mvconvention*V; // (u0,u1,u2) = V*R0*R1*R2 + // In either convention, you get the same 3-tuple U. + static void Convert(EulerAngles const& e, Matrix& r); + + // Convert a quaternion to an axis-angle pair, where + // q = sin(angle/2)*(axis[0]*i + axis[1]*j + axis[2]*k) + cos(angle/2) + static void Convert(Quaternion const& q, AxisAngle& a); + + // Convert an axis-angle pair to a quaternion, where + // q = sin(angle/2)*(axis[0]*i + axis[1]*j + axis[2]*k) + cos(angle/2) + static void Convert(AxisAngle const& a, Quaternion& q); + + // Convert a quaternion to Euler angles. The quaternion is converted to + // a matrix which is then converted to Euler angles. + static void Convert(Quaternion const& q, EulerAngles& e); + + // Convert Euler angles to a quaternion. The Euler angles are converted + // to a matrix which is then converted to a quaternion. + static void Convert(EulerAngles const& e, Quaternion& q); + + // Convert an axis-angle pair to Euler angles. The axis-angle pair is + // converted to a quaternion which is then converted to Euler angles. + static void Convert(AxisAngle const& a, EulerAngles& e); + + // Convert Euler angles to an axis-angle pair. The Euler angles are + // converted to a quaternion which is then converted to an axis-angle + // pair. + static void Convert(EulerAngles const& e, AxisAngle& a); +}; + + +template +Rotation::Rotation(Matrix const& matrix) + : + mType(IS_MATRIX), + mMatrix(matrix) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::Rotation(Quaternion const& quaternion) + : + mType(IS_QUATERNION), + mQuaternion(quaternion) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::Rotation(AxisAngle const& axisAngle) + : + mType(IS_AXIS_ANGLE), + mAxisAngle(axisAngle) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::Rotation(EulerAngles const& eulerAngles) + : + mType(IS_EULER_ANGLES), + mEulerAngles(eulerAngles) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); +} + +template +Rotation::operator Matrix() const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + break; + case IS_QUATERNION: + Convert(mQuaternion, mMatrix); + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mMatrix); + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mMatrix); + break; + } + + return mMatrix; +} + +template +Rotation::operator Quaternion() const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mQuaternion); + break; + case IS_QUATERNION: + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mQuaternion); + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mQuaternion); + break; + } + + return mQuaternion; +} + +template +Rotation::operator AxisAngle() const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mAxisAngle); + break; + case IS_QUATERNION: + Convert(mQuaternion, mAxisAngle); + break; + case IS_AXIS_ANGLE: + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mAxisAngle); + break; + } + + return mAxisAngle; +} + +template +EulerAngles const& Rotation::operator()(int i0, int i1, + int i2) const +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + mEulerAngles.axis[0] = i0; + mEulerAngles.axis[1] = i1; + mEulerAngles.axis[2] = i2; + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mEulerAngles); + break; + case IS_QUATERNION: + Convert(mQuaternion, mEulerAngles); + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mEulerAngles); + break; + case IS_EULER_ANGLES: + break; + } + + return mEulerAngles; +} + +template +void Rotation::Convert(Matrix const& r, + Quaternion& q) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Real r22 = r(2, 2); + if (r22 <= (Real)0) // x^2 + y^2 >= z^2 + w^2 + { + Real dif10 = r(1, 1) - r(0, 0); + Real omr22 = (Real)1 - r22; + if (dif10 <= (Real)0) // x^2 >= y^2 + { + Real fourXSqr = omr22 - dif10; + Real inv4x = ((Real)0.5) / std::sqrt(fourXSqr); + q[0] = fourXSqr*inv4x; + q[1] = (r(0, 1) + r(1, 0))*inv4x; + q[2] = (r(0, 2) + r(2, 0))*inv4x; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(2, 1) - r(1, 2))*inv4x; +#else + q[3] = (r(1, 2) - r(2, 1))*inv4x; +#endif + } + else // y^2 >= x^2 + { + Real fourYSqr = omr22 + dif10; + Real inv4y = ((Real)0.5) / std::sqrt(fourYSqr); + q[0] = (r(0, 1) + r(1, 0))*inv4y; + q[1] = fourYSqr*inv4y; + q[2] = (r(1, 2) + r(2, 1))*inv4y; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(0, 2) - r(2, 0))*inv4y; +#else + q[3] = (r(2, 0) - r(0, 2))*inv4y; +#endif + } + } + else // z^2 + w^2 >= x^2 + y^2 + { + Real sum10 = r(1, 1) + r(0, 0); + Real opr22 = (Real)1 + r22; + if (sum10 <= (Real)0) // z^2 >= w^2 + { + Real fourZSqr = opr22 - sum10; + Real inv4z = ((Real)0.5) / std::sqrt(fourZSqr); + q[0] = (r(0, 2) + r(2, 0))*inv4z; + q[1] = (r(1, 2) + r(2, 1))*inv4z; + q[2] = fourZSqr*inv4z; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(1, 0) - r(0, 1))*inv4z; +#else + q[3] = (r(0, 1) - r(1, 0))*inv4z; +#endif + } + else // w^2 >= z^2 + { + Real fourWSqr = opr22 + sum10; + Real inv4w = ((Real)0.5) / std::sqrt(fourWSqr); +#if defined(GTE_USE_MAT_VEC) + q[0] = (r(2, 1) - r(1, 2))*inv4w; + q[1] = (r(0, 2) - r(2, 0))*inv4w; + q[2] = (r(1, 0) - r(0, 1))*inv4w; +#else + q[0] = (r(1, 2) - r(2, 1))*inv4w; + q[1] = (r(2, 0) - r(0, 2))*inv4w; + q[2] = (r(0, 1) - r(1, 0))*inv4w; +#endif + q[3] = fourWSqr*inv4w; + } + } +} + +template +void Rotation::Convert(Quaternion const& q, Matrix& r) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + r.MakeIdentity(); + + Real twoX = ((Real)2)*q[0]; + Real twoY = ((Real)2)*q[1]; + Real twoZ = ((Real)2)*q[2]; + Real twoXX = twoX*q[0]; + Real twoXY = twoX*q[1]; + Real twoXZ = twoX*q[2]; + Real twoXW = twoX*q[3]; + Real twoYY = twoY*q[1]; + Real twoYZ = twoY*q[2]; + Real twoYW = twoY*q[3]; + Real twoZZ = twoZ*q[2]; + Real twoZW = twoZ*q[3]; + +#if defined(GTE_USE_MAT_VEC) + r(0, 0) = (Real)1 - twoYY - twoZZ; + r(0, 1) = twoXY - twoZW; + r(0, 2) = twoXZ + twoYW; + r(1, 0) = twoXY + twoZW; + r(1, 1) = (Real)1 - twoXX - twoZZ; + r(1, 2) = twoYZ - twoXW; + r(2, 0) = twoXZ - twoYW; + r(2, 1) = twoYZ + twoXW; + r(2, 2) = (Real)1 - twoXX - twoYY; +#else + r(0, 0) = (Real)1 - twoYY - twoZZ; + r(1, 0) = twoXY - twoZW; + r(2, 0) = twoXZ + twoYW; + r(0, 1) = twoXY + twoZW; + r(1, 1) = (Real)1 - twoXX - twoZZ; + r(2, 1) = twoYZ - twoXW; + r(0, 2) = twoXZ - twoYW; + r(1, 2) = twoYZ + twoXW; + r(2, 2) = (Real)1 - twoXX - twoYY; +#endif +} + +template +void Rotation::Convert(Matrix const& r, + AxisAngle& a) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Real trace = r(0, 0) + r(1, 1) + r(2, 2); + Real half = (Real)0.5; + Real cs = half*(trace - (Real)1); + cs = std::max(std::min(cs, (Real)1), (Real)-1); + a.angle = std::acos(cs); // The angle is in [0,pi]. + a.axis.MakeZero(); + + if (a.angle > (Real)0) + { + if (a.angle < (Real)GTE_C_PI) + { + // The angle is in (0,pi). +#if defined(GTE_USE_MAT_VEC) + a.axis[0] = r(2, 1) - r(1, 2); + a.axis[1] = r(0, 2) - r(2, 0); + a.axis[2] = r(1, 0) - r(0, 1); + Normalize(a.axis); +#else + a.axis[0] = r(1, 2) - r(2, 1); + a.axis[1] = r(2, 0) - r(0, 2); + a.axis[2] = r(0, 1) - r(1, 0); + Normalize(a.axis); +#endif + } + else + { + // The angle is pi, in which case R is symmetric and + // R+I = 2*(I+S^2) = 2*U*U^T, where U = (u0,u1,u2) is the + // unit-length direction of the rotation axis. Determine the + // largest diagonal entry of R+I and normalize the + // corresponding row to produce U. It does not matter the + // sign on u[d] for chosen diagonal d, because R(U,pi) = R(-U,pi). + Real one = (Real)1; + if (r(0, 0) >= r(1, 1)) + { + if (r(0, 0) >= r(2, 2)) + { + // r00 is maximum diagonal term + a.axis[0] = r(0, 0) + one; + a.axis[1] = half*(r(0, 1) + r(1, 0)); + a.axis[2] = half*(r(0, 2) + r(2, 0)); + } + else + { + // r22 is maximum diagonal term + a.axis[0] = half*(r(2, 0) + r(0, 2)); + a.axis[1] = half*(r(2, 1) + r(1, 2)); + a.axis[2] = r(2, 2) + one; + } + } + else + { + if (r(1, 1) >= r(2, 2)) + { + // r11 is maximum diagonal term + a.axis[0] = half*(r(1, 0) + r(0, 1)); + a.axis[1] = r(1, 1) + one; + a.axis[2] = half*(r(1, 2) + r(2, 1)); + } + else + { + // r22 is maximum diagonal term + a.axis[0] = half*(r(2, 0) + r(0, 2)); + a.axis[1] = half*(r(2, 1) + r(1, 2)); + a.axis[2] = r(2, 2) + one; + } + } + Normalize(a.axis); + } + } + else + { + // The angle is 0 and the matrix is the identity. Any axis will + // work, so choose the Unit(0) axis. + a.axis[0] = (Real)1; + } +} + +template +void Rotation::Convert(AxisAngle const& a, + Matrix& r) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + r.MakeIdentity(); + + Real cs = std::cos(a.angle); + Real sn = std::sin(a.angle); + Real oneMinusCos = ((Real)1) - cs; + Real x0sqr = a.axis[0] * a.axis[0]; + Real x1sqr = a.axis[1] * a.axis[1]; + Real x2sqr = a.axis[2] * a.axis[2]; + Real x0x1m = a.axis[0] * a.axis[1] * oneMinusCos; + Real x0x2m = a.axis[0] * a.axis[2] * oneMinusCos; + Real x1x2m = a.axis[1] * a.axis[2] * oneMinusCos; + Real x0Sin = a.axis[0] * sn; + Real x1Sin = a.axis[1] * sn; + Real x2Sin = a.axis[2] * sn; + +#if defined(GTE_USE_MAT_VEC) + r(0, 0) = x0sqr*oneMinusCos + cs; + r(0, 1) = x0x1m - x2Sin; + r(0, 2) = x0x2m + x1Sin; + r(1, 0) = x0x1m + x2Sin; + r(1, 1) = x1sqr*oneMinusCos + cs; + r(1, 2) = x1x2m - x0Sin; + r(2, 0) = x0x2m - x1Sin; + r(2, 1) = x1x2m + x0Sin; + r(2, 2) = x2sqr*oneMinusCos + cs; +#else + r(0, 0) = x0sqr*oneMinusCos + cs; + r(1, 0) = x0x1m - x2Sin; + r(2, 0) = x0x2m + x1Sin; + r(0, 1) = x0x1m + x2Sin; + r(1, 1) = x1sqr*oneMinusCos + cs; + r(2, 1) = x1x2m - x0Sin; + r(0, 2) = x0x2m - x1Sin; + r(1, 2) = x1x2m + x0Sin; + r(2, 2) = x2sqr*oneMinusCos + cs; +#endif +} + +template +void Rotation::Convert(Matrix const& r, + EulerAngles& e) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + if (0 <= e.axis[0] && e.axis[0] < 3 + && 0 <= e.axis[1] && e.axis[1] < 3 + && 0 <= e.axis[2] && e.axis[2] < 3 + && e.axis[1] != e.axis[0] + && e.axis[1] != e.axis[2]) + { + if (e.axis[0] != e.axis[2]) + { +#if defined(GTE_USE_MAT_VEC) + // Map (0,1,2), (1,2,0), and (2,0,1) to +1. + // Map (0,2,1), (2,1,0), and (1,0,2) to -1. + int parity = (((e.axis[2] | (e.axis[1] << 2)) >> e.axis[0]) & 1); + Real const sgn = (parity & 1 ? (Real)-1 : (Real)+1); + + if (r(e.axis[2], e.axis[0]) < (Real)1) + { + if (r(e.axis[2], e.axis[0]) > (Real)-1) + { + e.angle[2] = std::atan2(sgn*r(e.axis[1], e.axis[0]), + r(e.axis[0], e.axis[0])); + e.angle[1] = std::asin(-sgn*r(e.axis[2], e.axis[0])); + e.angle[0] = std::atan2(sgn*r(e.axis[2], e.axis[1]), + r(e.axis[2], e.axis[2])); + e.result = ER_UNIQUE; + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = sgn*(Real)GTE_C_HALF_PI; + e.angle[0] = std::atan2(-sgn*r(e.axis[1], e.axis[2]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = -sgn*(Real)GTE_C_HALF_PI; + e.angle[0] = std::atan2(-sgn*r(e.axis[1], e.axis[2]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#else + // Map (0,1,2), (1,2,0), and (2,0,1) to +1. + // Map (0,2,1), (2,1,0), and (1,0,2) to -1. + int parity = (((e.axis[0] | (e.axis[1] << 2)) >> e.axis[2]) & 1); + Real const sgn = (parity & 1 ? (Real)+1 : (Real)-1); + + if (r(e.axis[0], e.axis[2]) < (Real)1) + { + if (r(e.axis[0], e.axis[2]) > (Real)-1) + { + e.angle[0] = std::atan2(sgn*r(e.axis[1], e.axis[2]), + r(e.axis[2], e.axis[2])); + e.angle[1] = std::asin(-sgn*r(e.axis[0], e.axis[2])); + e.angle[2] = std::atan2(sgn*r(e.axis[0], e.axis[1]), + r(e.axis[0], e.axis[0])); + e.result = ER_UNIQUE; + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = sgn*(Real)GTE_C_HALF_PI; + e.angle[2] = std::atan2(-sgn*r(e.axis[1], e.axis[0]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = -sgn*(Real)GTE_C_HALF_PI; + e.angle[2] = std::atan2(-sgn*r(e.axis[1], e.axis[0]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#endif + } + else + { +#if defined(GTE_USE_MAT_VEC) + // Map (0,2,0), (1,0,1), and (2,1,2) to +1. + // Map (0,1,0), (1,2,1), and (2,0,2) to -1. + int b0 = 3 - e.axis[1] - e.axis[2]; + int parity = (((b0 | (e.axis[1] << 2)) >> e.axis[2]) & 1); + Real const sgn = (parity & 1 ? (Real)+1 : (Real)-1); + + if (r(e.axis[2], e.axis[2]) < (Real)1) + { + if (r(e.axis[2], e.axis[2]) > (Real)-1) + { + e.angle[2] = std::atan2(r(e.axis[1], e.axis[2]), + sgn*r(b0, e.axis[2])); + e.angle[1] = std::acos(r(e.axis[2], e.axis[2])); + e.angle[0] = std::atan2(r(e.axis[2], e.axis[1]), + -sgn*r(e.axis[2], b0)); + e.result = ER_UNIQUE; + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = (Real)GTE_C_PI; + e.angle[0] = std::atan2(sgn*r(e.axis[1], b0), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = (Real)0; + e.angle[0] = std::atan2(sgn*r(e.axis[1], b0), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#else + // Map (0,2,0), (1,0,1), and (2,1,2) to -1. + // Map (0,1,0), (1,2,1), and (2,0,2) to +1. + int b2 = 3 - e.axis[0] - e.axis[1]; + int parity = (((b2 | (e.axis[1] << 2)) >> e.axis[0]) & 1); + Real const sgn = (parity & 1 ? (Real)-1 : (Real)+1); + + if (r(e.axis[0], e.axis[0]) < (Real)1) + { + if (r(e.axis[0], e.axis[0]) > (Real)-1) + { + e.angle[0] = std::atan2(r(e.axis[1], e.axis[0]), + sgn*r(b2, e.axis[0])); + e.angle[1] = std::acos(r(e.axis[0], e.axis[0])); + e.angle[2] = std::atan2(r(e.axis[0], e.axis[1]), + -sgn*r(e.axis[0], b2)); + e.result = ER_UNIQUE; + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = (Real)GTE_C_PI; + e.angle[2] = std::atan2(sgn*r(e.axis[1], b2), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = (Real)0; + e.angle[2] = std::atan2(sgn*r(e.axis[1], b2), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#endif + } + } + else + { + // Invalid angles. + e.angle[0] = (Real)0; + e.angle[1] = (Real)0; + e.angle[2] = (Real)0; + e.result = ER_INVALID; + } +} + +template +void Rotation::Convert(EulerAngles const& e, + Matrix& r) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + if (0 <= e.axis[0] && e.axis[0] < 3 + && 0 <= e.axis[1] && e.axis[1] < 3 + && 0 <= e.axis[2] && e.axis[2] < 3 + && e.axis[1] != e.axis[0] + && e.axis[1] != e.axis[2]) + { + Matrix r0, r1, r2; + Convert(AxisAngle(Vector::Unit(e.axis[0]), + e.angle[0]), r0); + Convert(AxisAngle(Vector::Unit(e.axis[1]), + e.angle[1]), r1); + Convert(AxisAngle(Vector::Unit(e.axis[2]), + e.angle[2]), r2); +#if defined(GTE_USE_MAT_VEC) + r = r2*r1*r0; +#else + r = r0*r1*r2; +#endif + } + else + { + // Invalid angles. + r.MakeIdentity(); + } +} + +template +void Rotation::Convert(Quaternion const& q, + AxisAngle& a) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + a.axis.MakeZero(); + + Real axisSqrLen = q[0] * q[0] + q[1] * q[1] + q[2] * q[2]; + if (axisSqrLen > (Real)0) + { +#if defined(GTE_USE_MAT_VEC) + Real adjust = ((Real)1) / std::sqrt(axisSqrLen); +#else + Real adjust = ((Real)-1) / std::sqrt(axisSqrLen); +#endif + a.axis[0] = q[0] * adjust; + a.axis[1] = q[1] * adjust; + a.axis[2] = q[2] * adjust; + Real cs = std::max(std::min(q[3], (Real)1), (Real)-1); + a.angle = (Real)2 * std::acos(cs); + } + else + { + // The angle is 0 (modulo 2*pi). Any axis will work, so choose the + // Unit(0) axis. + a.axis[0] = (Real)1; + a.angle = (Real)0; + } +} + +template +void Rotation::Convert(AxisAngle const& a, + Quaternion& q) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + +#if defined(GTE_USE_MAT_VEC) + Real halfAngle = ((Real)0.5)*a.angle; +#else + Real halfAngle = ((Real)-0.5)*a.angle; +#endif + Real sn = std::sin(halfAngle); + q[0] = sn*a.axis[0]; + q[1] = sn*a.axis[1]; + q[2] = sn*a.axis[2]; + q[3] = std::cos(halfAngle); +} + +template +void Rotation::Convert(Quaternion const& q, + EulerAngles& e) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Matrix r; + Convert(q, r); + Convert(r, e); +} + +template +void Rotation::Convert(EulerAngles const& e, + Quaternion& q) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Matrix r; + Convert(e, r); + Convert(r, q); +} + +template +void Rotation::Convert(AxisAngle const& a, + EulerAngles& e) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Quaternion q; + Convert(a, q); + Convert(q, e); +} + +template +void Rotation::Convert(EulerAngles const& e, + AxisAngle& a) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Quaternion q; + Convert(e, q); + Convert(q, a); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSector2.h new file mode 100644 index 000000000000..c467ec7bef8d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSector2.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include + +namespace gte +{ + +// A solid sector is the intersection of a disk and a 2D cone. The disk +// has center C, radius R, and contains points X for which |X-C| <= R. The +// 2D cone has vertex C, unit-length axis direction D, angle A in (0,pi) +// measured from D, and contains points X for which Dot(D,(X-C)/|X-C|) >= cos(A). +// Sector points X satisfy both inequality constraints. + +template +class Sector2 +{ +public: + // Construction and destruction. The default constructor sets the vertex + // to (0,0), radius to 1, axis direction to (1,0), and angle to pi, + // all of which define a disk. + Sector2(); + Sector2(Vector2 const& inVertex, Real inRadius, + Vector2 const& inDirection, Real inAngle); + + // Set the angle and cos(angle) simultaneously. + void SetAngle(Real inAngle); + + // Test whether P is in the sector. + bool Contains(Vector2 const& p) const; + + // The cosine and sine of the angle are used in queries, so all o + // angle, cos(angle), and sin(angle) are stored. If you set 'angle' + // via the public members, you must set all to be consistent. You + // can also call SetAngle(...) to ensure consistency. + Vector2 vertex; + Real radius; + Vector2 direction; + Real angle, cosAngle, sinAngle; + +public: + // Comparisons to support sorted containers. + bool operator==(Sector2 const& sector) const; + bool operator!=(Sector2 const& sector) const; + bool operator< (Sector2 const& sector) const; + bool operator<=(Sector2 const& sector) const; + bool operator> (Sector2 const& sector) const; + bool operator>=(Sector2 const& sector) const; +}; + + +template +Sector2::Sector2() + : + vertex(Vector2::Zero()), + radius((Real)1), + direction(Vector2::Unit(0)), + angle((Real)GTE_C_PI), + cosAngle((Real)-1), + sinAngle((Real)0) +{ +} + +template +Sector2::Sector2(Vector2 const& inCenter, Real inRadius, + Vector2 const& inDirection, Real inAngle) + : + vertex(inCenter), + radius(inRadius), + direction(inDirection) +{ + SetAngle(inAngle); +} + +template +void Sector2::SetAngle(Real inAngle) +{ + angle = inAngle; + cosAngle = std::cos(angle); + sinAngle = std::sin(angle); +} + +template +bool Sector2::Contains(Vector2 const& p) const +{ + Vector2 diff = p - vertex; + Real length = Length(diff); + return length <= radius && Dot(direction, diff) >= length * cosAngle; +} + +template +bool Sector2::operator==(Sector2 const& sector) const +{ + return vertex == sector.vertex && radius == sector.radius + && direction == sector.direction && angle == sector.angle; +} + +template +bool Sector2::operator!=(Sector2 const& sector) const +{ + return !operator==(sector); +} + +template +bool Sector2::operator<(Sector2 const& sector) const +{ + if (vertex < sector.vertex) + { + return true; + } + + if (vertex > sector.vertex) + { + return false; + } + + if (radius < sector.radius) + { + return true; + } + + if (radius > sector.radius) + { + return false; + } + + if (direction < sector.direction) + { + return true; + } + + if (direction > sector.direction) + { + return false; + } + + return angle < sector.angle; +} + +template +bool Sector2::operator<=(Sector2 const& sector) const +{ + return operator<(sector) || operator==(sector); +} + +template +bool Sector2::operator>(Sector2 const& sector) const +{ + return !operator<=(sector); +} + +template +bool Sector2::operator>=(Sector2 const& sector) const +{ + return !operator<(sector); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSegment.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSegment.h new file mode 100644 index 000000000000..3fd213755d70 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSegment.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The segment is represented by (1-t)*P0 + t*P1, where P0 and P1 are the +// endpoints of the segment and 0 <= t <= 1. Some algorithms prefer a +// centered representation that is similar to how oriented bounding boxes are +// defined. This representation is C + s*D, where C = (P0 + P1)/2 is the +// center of the segment, D = (P1 - P0)/|P1 - P0| is a unit-length direction +// vector for the segment, and |t| <= e. The value e = |P1 - P0|/2 is the +// extent (or radius or half-length) of the segment. + +namespace gte +{ + +template +class Segment +{ +public: + // Construction and destruction. The default constructor sets p0 to + // (-1,0,...,0) and p1 to (1,0,...,0). NOTE: If you set p0 and p1; + // compute C, D, and e; and then recompute q0 = C-e*D and q1 = C+e*D, + // numerical round-off errors can lead to q0 not exactly equal to p0 + // and q1 not exactly equal to p1. + Segment(); + Segment(Vector const& p0, Vector const& p1); + Segment(std::array, 2> const& inP); + Segment(Vector const& center, Vector const& direction, + Real extent); + + // Manipulation via the centered form. + void SetCenteredForm(Vector const& center, + Vector const& direction, Real extent); + + void GetCenteredForm(Vector& center, Vector& direction, + Real& extent) const; + + // Public member access. + std::array, 2> p; + +public: + // Comparisons to support sorted containers. + bool operator==(Segment const& segment) const; + bool operator!=(Segment const& segment) const; + bool operator< (Segment const& segment) const; + bool operator<=(Segment const& segment) const; + bool operator> (Segment const& segment) const; + bool operator>=(Segment const& segment) const; +}; + +// Template aliases for convenience. +template +using Segment2 = Segment<2, Real>; + +template +using Segment3 = Segment<3, Real>; + + +template +Segment::Segment() +{ + p[1].MakeUnit(0); + p[0] = -p[1]; +} + +template +Segment::Segment(Vector const& p0, + Vector const& p1) +{ + p[0] = p0; + p[1] = p1; +} + +template +Segment::Segment(std::array, 2> const& inP) +{ + p = inP; +} + +template +Segment::Segment(Vector const& center, + Vector const& direction, Real extent) +{ + SetCenteredForm(center, direction, extent); +} + +template +void Segment::SetCenteredForm(Vector const& center, + Vector const& direction, Real extent) +{ + p[0] = center - extent * direction; + p[1] = center + extent * direction; +} + +template +void Segment::GetCenteredForm(Vector& center, + Vector& direction, Real& extent) const +{ + center = ((Real)0.5)*(p[0] + p[1]); + direction = p[1] - p[0]; + extent = ((Real)0.5)*Normalize(direction); +} + +template +bool Segment::operator==(Segment const& segment) const +{ + return p == segment.p; +} + +template +bool Segment::operator!=(Segment const& segment) const +{ + return !operator==(segment); +} + +template +bool Segment::operator<(Segment const& segment) const +{ + return p < segment.p; +} + +template +bool Segment::operator<=(Segment const& segment) const +{ + return operator<(segment) || operator==(segment); +} + +template +bool Segment::operator>(Segment const& segment) const +{ + return !operator<=(segment); +} + +template +bool Segment::operator>=(Segment const& segment) const +{ + return !operator<(segment); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints2.h new file mode 100644 index 000000000000..3235dfef7b9e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints2.h @@ -0,0 +1,221 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Separate two point sets, if possible, by computing a line for which the +// point sets lie on opposite sides. The algorithm computes the convex hull +// of the point sets, then uses the method of separating axes to determine if +// the two convex polygons are disjoint. The ComputeType is for the +// ConvexHull2 class. + +namespace gte +{ + +template +class SeparatePoints2 +{ +public: + // The return value is 'true' if and only if there is a separation. If + // 'true', the returned line is a separating line. The code assumes that + // each point set has at least 3 noncollinear points. + bool operator()(int numPoints0, Vector2 const* points0, + int numPoints1, Vector2 const* points1, + Line2& separatingLine) const; + +private: + int OnSameSide(Vector2 const& lineNormal, Real lineConstant, + int numEdges, int const* edges, Vector2 const* points) const; + + int WhichSide(Vector2 const& lineNormal, Real lineConstant, + int numEdges, int const* edges, Vector2 const* points) const; +}; + + +template +bool SeparatePoints2::operator()(int numPoints0, + Vector2 const* points0, int numPoints1, + Vector2 const* points1, Line2& separatingLine) const +{ + // Construct convex hull of point set 0. + ConvexHull2 ch0; + ch0(numPoints0, points0, (Real)0); + if (ch0.GetDimension() != 2) + { + return false; + } + + // Construct convex hull of point set 1. + ConvexHull2 ch1; + ch1(numPoints1, points1, (Real)0); + if (ch1.GetDimension() != 2) + { + return false; + } + + int numEdges0 = static_cast(ch0.GetHull().size()); + int const* edges0 = &ch0.GetHull()[0]; + int numEdges1 = static_cast(ch1.GetHull().size()); + int const* edges1 = &ch1.GetHull()[0]; + + // Test edges of hull 0 for possible separation of points. + int j0, j1, i0, i1, side0, side1; + Vector2 lineNormal; + Real lineConstant; + for (j1 = 0, j0 = numEdges0 - 1; j1 < numEdges0; j0 = j1++) + { + // Look up edge (assert: i0 != i1 ). + i0 = edges0[j0]; + i1 = edges0[j1]; + + // Compute potential separating line (assert: (xNor,yNor) != (0,0)). + separatingLine.origin = points0[i0]; + separatingLine.direction = points0[i1] - points0[i0]; + Normalize(separatingLine.direction); + lineNormal = Perp(separatingLine.direction); + lineConstant = Dot(lineNormal, separatingLine.origin); + + // Determine whether hull 1 is on same side of line. + side1 = OnSameSide(lineNormal, lineConstant, numEdges1, edges1, + points1); + + if (side1) + { + // Determine on which side of line hull 0 lies. + side0 = WhichSide(lineNormal, lineConstant, numEdges0, + edges0, points0); + + if (side0*side1 <= 0) // Line separates hulls. + { + return true; + } + } + } + + // Test edges of hull 1 for possible separation of points. + for (j1 = 0, j0 = numEdges1 - 1; j1 < numEdges1; j0 = j1++) + { + // Look up edge (assert: i0 != i1 ). + i0 = edges1[j0]; + i1 = edges1[j1]; + + // Compute perpendicular to edge (assert: (xNor,yNor) != (0,0)). + separatingLine.origin = points1[i0]; + separatingLine.direction = points1[i1] - points1[i0]; + Normalize(separatingLine.direction); + lineNormal = Perp(separatingLine.direction); + lineConstant = Dot(lineNormal, separatingLine.origin); + + // Determine whether hull 0 is on same side of line. + side0 = OnSameSide(lineNormal, lineConstant, numEdges0, edges0, + points0); + + if (side0) + { + // Determine on which side of line hull 1 lies. + side1 = WhichSide(lineNormal, lineConstant, numEdges1, + edges1, points1); + + if (side0*side1 <= 0) // Line separates hulls. + { + return true; + } + } + } + + return false; +} + +template +int SeparatePoints2::OnSameSide( + Vector2 const& lineNormal, Real lineConstant, int numEdges, + int const* edges, Vector2 const* points) const +{ + // Test whether all points on same side of line Dot(N,X) = c. + Real c0; + int posSide = 0, negSide = 0; + + for (int i1 = 0, i0 = numEdges - 1; i1 < numEdges; i0 = i1++) + { + c0 = Dot(lineNormal, points[edges[i0]]); + if (c0 > lineConstant) + { + ++posSide; + } + else if (c0 < lineConstant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Line splits point set. + return 0; + } + + c0 = Dot(lineNormal, points[edges[i1]]); + if (c0 > lineConstant) + { + ++posSide; + } + else if (c0 < lineConstant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Line splits point set. + return 0; + } + } + + return (posSide ? +1 : -1); +} + +template +int SeparatePoints2::WhichSide( + Vector2 const& lineNormal, Real lineConstant, int numEdges, + int const* edges, Vector2 const* points) const +{ + // Establish which side of line hull is on. + Real c0; + for (int i1 = 0, i0 = numEdges - 1; i1 < numEdges; i0 = i1++) + { + c0 = Dot(lineNormal, points[edges[i0]]); + if (c0 > lineConstant) + { + // Hull on positive side. + return +1; + } + if (c0 < lineConstant) + { + // Hull on negative side. + return -1; + } + + c0 = Dot(lineNormal, points[edges[i1]]); + if (c0 > lineConstant) + { + // Hull on positive side. + return +1; + } + if (c0 < lineConstant) + { + // Hull on negative side. + return -1; + } + } + + // Hull is effectively collinear. + return 0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints3.h new file mode 100644 index 000000000000..1d07e575434b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSeparatePoints3.h @@ -0,0 +1,244 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// Separate two point sets, if possible, by computing a plane for which the +// point sets lie on opposite sides. The algorithm computes the convex hull +// of the point sets, then uses the method of separating axes to determine if +// the two convex polyhedra are disjoint. The ComputeType is for the +// ConvexHull3 class. + +namespace gte +{ + +template +class SeparatePoints3 +{ +public: + // The return value is 'true' if and only if there is a separation. If + // 'true', the returned plane is a separating plane. The code assumes + // that each point set has at least 4 noncoplanar points. + bool operator()(int numPoints0, Vector3 const* points0, + int numPoints1, Vector3 const* points1, + Plane3& separatingPlane) const; + +private: + int OnSameSide(Plane3 const& plane, int numTriangles, + int const* indices, Vector3 const* points) const; + + int WhichSide(Plane3 const& plane, int numTriangles, + int const* indices, Vector3 const* points) const; +}; + + +template +bool SeparatePoints3::operator()(int numPoints0, + Vector3 const* points0, int numPoints1, + Vector3 const* points1, Plane3& separatingPlane) const +{ + // Construct convex hull of point set 0. + ConvexHull3 ch0; + ch0(numPoints0, points0, (Real)0); + if (ch0.GetDimension() != 3) + { + return false; + } + + // Construct convex hull of point set 1. + ConvexHull3 ch1; + ch1(numPoints1, points1, (Real)0); + if (ch1.GetDimension() != 3) + { + return false; + } + + auto const& hull0 = ch0.GetHullUnordered(); + auto const& hull1 = ch1.GetHullUnordered(); + int numTriangles0 = static_cast(hull0.size()); + int const* indices0 = reinterpret_cast(&hull0[0]); + int numTriangles1 = static_cast(hull1.size()); + int const* indices1 = reinterpret_cast(&hull1[0]); + + // Test faces of hull 0 for possible separation of points. + int i, i0, i1, i2, side0, side1; + Vector3 diff0, diff1; + for (i = 0; i < numTriangles0; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices0[3 * i]; + i1 = indices0[3 * i + 1]; + i2 = indices0[3 * i + 2]; + + // Compute potential separating plane (assert: normal != (0,0,0)). + separatingPlane = Plane3({ points0[i0], points0[i1], + points0[i2] }); + + // Determine whether hull 1 is on same side of plane. + side1 = OnSameSide(separatingPlane, numTriangles1, indices1, points1); + + if (side1) + { + // Determine on which side of plane hull 0 lies. + side0 = WhichSide(separatingPlane, numTriangles0, indices0, + points0); + if (side0*side1 <= 0) // Plane separates hulls. + { + return true; + } + } + } + + // Test faces of hull 1 for possible separation of points. + for (i = 0; i < numTriangles1; ++i) + { + // Look up edge (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices1[3 * i]; + i1 = indices1[3 * i + 1]; + i2 = indices1[3 * i + 2]; + + // Compute perpendicular to face (assert: normal != (0,0,0)). + separatingPlane = Plane3({ points1[i0], points1[i1], + points1[i2] }); + + // Determine whether hull 0 is on same side of plane. + side0 = OnSameSide(separatingPlane, numTriangles0, indices0, points0); + if (side0) + { + // Determine on which side of plane hull 1 lies. + side1 = WhichSide(separatingPlane, numTriangles1, indices1, + points1); + if (side0*side1 <= 0) // Plane separates hulls. + { + return true; + } + } + } + + // Build edge set for hull 0. + std::set> edgeSet0; + for (i = 0; i < numTriangles0; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices0[3 * i]; + i1 = indices0[3 * i + 1]; + i2 = indices0[3 * i + 2]; + edgeSet0.insert(std::make_pair(i0, i1)); + edgeSet0.insert(std::make_pair(i0, i2)); + edgeSet0.insert(std::make_pair(i1, i2)); + } + + // Build edge list for hull 1. + std::set> edgeSet1; + for (i = 0; i < numTriangles1; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices1[3 * i]; + i1 = indices1[3 * i + 1]; + i2 = indices1[3 * i + 2]; + edgeSet1.insert(std::make_pair(i0, i1)); + edgeSet1.insert(std::make_pair(i0, i2)); + edgeSet1.insert(std::make_pair(i1, i2)); + } + + // Test planes whose normals are cross products of two edges, one from + // each hull. + for (auto const& e0 : edgeSet0) + { + // Get edge. + diff0 = points0[e0.second] - points0[e0.first]; + + for (auto const& e1 : edgeSet1) + { + diff1 = points1[e1.second] - points1[e1.first]; + + // Compute potential separating plane. + separatingPlane.normal = UnitCross(diff0, diff1); + separatingPlane.constant = Dot(separatingPlane.normal, + points0[e0.first]); + + // Determine if hull 0 is on same side of plane. + side0 = OnSameSide(separatingPlane, numTriangles0, indices0, + points0); + side1 = OnSameSide(separatingPlane, numTriangles1, indices1, + points1); + + if (side0*side1 < 0) // Plane separates hulls. + { + return true; + } + } + } + + return false; +} + +template +int SeparatePoints3::OnSameSide(Plane3 const& plane, + int numTriangles, int const* indices, Vector3 const* points) const +{ + // test if all points on same side of plane (nx,ny,nz)*(x,y,z) = c + int posSide = 0, negSide = 0; + + for (int t = 0; t < numTriangles; ++t) + { + for (int i = 0; i < 3; ++i) + { + int v = indices[3 * t + i]; + Real c0 = Dot(plane.normal, points[v]); + if (c0 > plane.constant) + { + ++posSide; + } + else if (c0 < plane.constant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Plane splits point set. + return 0; + } + } + } + + return (posSide ? +1 : -1); +} + +template +int SeparatePoints3::WhichSide(Plane3 const& plane, + int numTriangles, int const* indices, Vector3 const* points) const +{ + // Establish which side of plane hull is on. + for (int t = 0; t < numTriangles; ++t) + { + for (int i = 0; i < 3; ++i) + { + int v = indices[3 * t + i]; + Real c0 = Dot(plane.normal, points[v]); + if (c0 > plane.constant) + { + // Positive side. + return +1; + } + if (c0 < plane.constant) + { + // Negative side. + return -1; + } + } + } + + // Hull is effectively collinear. + return 0; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSinEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSinEstimate.h new file mode 100644 index 000000000000..133e74ee6555 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSinEstimate.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to sin(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(pi/2) = sin(pi/2) = 1. It minimizes the quantity +// maximum{|sin(x) - p(x)| : x in [-pi/2,pi/2]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class SinEstimate +{ +public: + // The input constraint is x in [-pi/2,pi/2]. For example, + // float x; // in [-pi/2,pi/2] + // float result = SinEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2] for which sin(y) = sin(x). + // For example, + // float x; // x any real number + // float result = SinEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<9>, Real x); + inline static Real Evaluate(degree<11>, Real x); + + // Support for range reduction. + inline static Real Reduce(Real x); +}; + + +template +template +inline Real SinEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real SinEstimate::DegreeRR(Real x) +{ + return Degree(Reduce(x)); +} + +template +inline Real SinEstimate::Evaluate(degree<3>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG3_C1; + poly = (Real)GTE_C_SIN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<5>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG5_C2; + poly = (Real)GTE_C_SIN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<7>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG7_C3; + poly = (Real)GTE_C_SIN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<9>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG9_C4; + poly = (Real)GTE_C_SIN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Evaluate(degree<11>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG11_C5; + poly = (Real)GTE_C_SIN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real SinEstimate::Reduce(Real x) +{ + // Map x to y in [-pi,pi], x = 2*pi*quotient + remainder. + Real quotient = (Real)GTE_C_INV_TWO_PI * x; + if (x >= (Real)0) + { + quotient = (Real)((int)(quotient + (Real)0.5)); + } + else + { + quotient = (Real)((int)(quotient - (Real)0.5)); + } + Real y = x - (Real)GTE_C_TWO_PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(x). + if (y > (Real)GTE_C_HALF_PI) + { + y = (Real)GTE_C_PI - y; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y = (Real)-GTE_C_PI - y; + } + return y; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSingularValueDecomposition.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSingularValueDecomposition.h new file mode 100644 index 000000000000..726eba345137 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSingularValueDecomposition.h @@ -0,0 +1,1147 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The SingularValueDecomposition class is an implementation of Algorithm +// 8.3.2 (The SVD Algorithm) described in "Matrix Computations, 2nd +// edition" by G. H. Golub and Charles F. Van Loan, The Johns Hopkins +// Press, Baltimore MD, Fourth Printing 1993. Algorithm 5.4.2 (Householder +// Bidiagonalization) is used to reduce A to bidiagonal B. Algorithm 8.3.1 +// (Golub-Kahan SVD Step) is used for the iterative reduction from bidiagonal +// to diagonal. If A is the original matrix, S is the matrix whose diagonal +// entries are the singular values, and U and V are corresponding matrices, +// then theoretically U^T*A*V = S. Numerically, we have errors +// E = U^T*A*V - S. Algorithm 8.3.2 mentions that one expects |E| is +// approximately u*|A|, where |M| denotes the Frobenius norm of M and where +// u is the unit roundoff for the floating-point arithmetic: 2^{-23} for +// 'float', which is FLT_EPSILON = 1.192092896e-7f, and 2^{-52} for'double', +// which is DBL_EPSILON = 2.2204460492503131e-16. +// +// The condition |a(i,i+1)| <= epsilon*(|a(i,i) + a(i+1,i+1)|) used to +// determine when the reduction decouples to smaller problems is implemented +// as: sum = |a(i,i)| + |a(i+1,i+1)|; sum + |a(i,i+1)| == sum. The idea is +// that the superdiagonal term is small relative to its diagonal neighbors, +// and so it is effectively zero. The unit tests have shown that this +// interpretation of decoupling is effective. +// +// The condition |a(i,i)| <= epsilon*|B| used to determine when the +// reduction decouples (with a zero singular value) is implemented using +// the Frobenius norm of B and epsilon = multiplier*u, where for now the +// multiplier is hard-coded in Solve(...) as 8. +// +// The authors suggest that once you have the bidiagonal matrix, a practical +// implementation will store the diagonal and superdiagonal entries in linear +// arrays, ignoring the theoretically zero values not in the 2-band. This is +// good for cache coherence, and we have used the suggestion. The essential +// parts of the Householder u-vectors are stored in the lower-triangular +// portion of the matrix and the essential parts of the Householder v-vectors +// are stored in the upper-triangular portion of the matrix. To avoid having +// to recompute 2/Dot(u,u) and 2/Dot(v,v) when constructing orthogonal U and +// V, we store these quantities in additional memory during bidiagonalization. +// +// For matrices with randomly generated values in [0,1], the unit tests +// produce the following information for N-by-N matrices. +// +// N |A| |E| |E|/|A| iterations +// ------------------------------------------- +// 2 1.4831 4.1540e-16 2.8007e-16 1 +// 3 2.1065 3.5024e-16 1.6626e-16 4 +// 4 2.4979 7.4605e-16 2.9867e-16 6 +// 5 3.6591 1.8305e-15 5.0025e-16 9 +// 6 4.0572 2.0571e-15 5.0702e-16 10 +// 7 4.7745 2.9057e-15 6.0859e-16 12 +// 8 5.1964 2.7958e-15 5.3803e-16 13 +// 9 5.7599 3.3128e-15 5.7514e-16 16 +// 10 6.2700 3.7209e-15 5.9344e-16 16 +// 11 6.8220 5.0580e-15 7.4142e-16 18 +// 12 7.4540 5.2493e-15 7.0422e-16 21 +// 13 8.1225 5.6043e-15 6.8997e-16 24 +// 14 8.5883 5.8553e-15 6.8177e-16 26 +// 15 9.1337 6.9663e-15 7.6270e-16 27 +// 16 9.7884 9.1358e-15 9.3333e-16 29 +// 17 10.2407 8.2715e-15 8.0771e-16 34 +// 18 10.7147 8.9748e-15 8.3761e-16 33 +// 19 11.1887 1.0094e-14 9.0220e-16 32 +// 20 11.7739 9.7000e-15 8.2386e-16 35 +// 21 12.2822 1.1217e-14 9.1329e-16 36 +// 22 12.7649 1.1071e-14 8.6732e-16 37 +// 23 13.3366 1.1271e-14 8.4513e-16 41 +// 24 13.8505 1.2806e-14 9.2460e-16 43 +// 25 14.4332 1.3081e-14 9.0637e-16 43 +// 26 14.9301 1.4882e-14 9.9680e-16 46 +// 27 15.5214 1.5571e-14 1.0032e-15 48 +// 28 16.1029 1.7553e-14 1.0900e-15 49 +// 29 16.6407 1.6219e-14 9.7467e-16 53 +// 30 17.1891 1.8560e-14 1.0797e-15 55 +// 31 17.7773 1.8522e-14 1.0419e-15 56 +// +// The singularvalues and |E|/|A| values were compared to those generated by +// Mathematica Version 9.0; Wolfram Research, Inc., Champaign IL, 2012. +// The results were all comparable with singular values agreeing to a large +// number of decimal places. +// +// Timing on an Intel (R) Core (TM) i7-3930K CPU @ 3.20 GHZ (in seconds) +// for NxN matrices: +// +// N |E|/|A| iters bidiag QR U-and-V comperr +// ------------------------------------------------------- +// 512 3.8632e-15 848 0.341 0.016 1.844 2.203 +// 1024 5.6456e-15 1654 4.279 0.032 18.765 20.844 +// 2048 7.5499e-15 3250 40.421 0.141 186.607 213.216 +// +// where iters is the number of QR steps taken, bidiag is the computation +// of the Householder reflection vectors, U-and-V is the composition of +// Householder reflections and Givens rotations to obtain the orthogonal +// matrices of the decomposigion, and comperr is the computation E = +// U^T*A*V - S. + +namespace gte +{ + +template +class SingularValueDecomposition +{ +public: + // The solver processes MxN symmetric matrices, where M >= N > 1 + // ('numRows' is M and 'numCols' is N) and the matrix is stored in + // row-major order. The maximum number of iterations ('maxIterations') + // must be specified for the reduction of a bidiagonal matrix to a + // diagonal matrix. The goal is to compute MxM orthogonal U, NxN + // orthogonal V, and MxN matrix S for which U^T*A*V = S. The only + // nonzero entries of S are on the diagonal; the diagonal entries are + // the singular values of the original matrix. + SingularValueDecomposition(int numRows, int numCols, + unsigned int maxIterations); + + // A copy of the MxN input is made internally. The order of the singular + // values is specified by sortType: -1 (decreasing), 0 (no sorting), or +1 + // (increasing). When sorted, the columns of the orthogonal matrices + // are ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did not + // occur or 0 when N <= 1 or M < N was passed to the constructor. + unsigned int Solve(Real const* input, int sortType); + + // Get the singular values of the matrix passed to Solve(...). The input + // 'singularValues' must have N elements. + void GetSingularValues(Real* singularValues) const; + + // Accumulate the Householder reflections, the Givens rotations, and the + // diagonal fix-up matrix to compute the orthogonal matrices U and V for + // which U^T*A*V = S. The input uMatrix must be MxM and the input vMatrix + // must be NxN, both stored in row-major order. + void GetU(Real* uMatrix) const; + void GetV(Real* vMatrix) const; + + // Compute a single column of U or V. The reflections and rotations are + // applied incrementally. This is useful when you want only a small + // number of the singular values or vectors. + void GetUColumn(int index, Real* uColumn) const; + void GetVColumn(int index, Real* vColumn) const; + Real GetSingularValue(int index) const; + +private: + // Bidiagonalize using Householder reflections. On input, mMatrix is a + // copy of the input matrix and has one extra row. On output, the + // diagonal and superdiagonal contain the bidiagonalized results. The + // lower-triangular portion stores the essential parts of the Householder + // u vectors (the elements of u after the leading 1-valued component) and + // the upper-triangular portion stores the essential parts of the + // Householder v vectors. To avoid recomputing 2/Dot(u,u) and 2/Dot(v,v), + // these quantities are stored in mTwoInvUTU and mTwoInvVTV. + void Bidiagonalize(); + + // A helper for generating Givens rotation sine and cosine robustly. + void GetSinCos(Real u, Real v, Real& cs, Real& sn); + + // Test for (effectively) zero-valued diagonal entries (through all but + // the last). For each such entry, the B matrix decouples. Perform + // that decoupling. If there are no zero-valued entries, then the + // Golub-Kahan step must be performed. + bool DiagonalEntriesNonzero(int imin, int imax, Real threshold); + + // This is Algorithm 8.3.1 in "Matrix Computations, 2nd edition" by + // G. H. Golub and C. F. Van Loan. + void DoGolubKahanStep(int imin, int imax); + + // The diagonal entries are not guaranteed to be nonnegative during the + // construction. After convergence to a diagonal matrix S, test for + // negative entries and build a diagonal matrix that reverses the sign + // on the S-entry. + void EnsureNonnegativeDiagonal(); + + // Sort the singular values and compute the corresponding permutation of + // the indices of the array storing the singular values. The permutation + // is used for reordering the singular values and the corresponding + // columns of the orthogonal matrix in the calls to GetSingularValues(...) + // and GetOrthogonalMatrices(...). + void ComputePermutation(int sortType); + + // The number rows and columns of the matrices to be processed. + int mNumRows, mNumCols; + + // The maximum number of iterations for reducing the bidiagonal matrix + // to a diagonal matrix. + unsigned int mMaxIterations; + + // The internal copy of a matrix passed to the solver. See the comments + // about function Bidiagonalize() about what is stored in the matrix. + std::vector mMatrix; // MxN elements + + // After the initial bidiagonalization by Householder reflections, we no + // longer need the full mMatrix. Copy the diagonal and superdiagonal + // entries to linear arrays in order to be cache friendly. + std::vector mDiagonal; // N elements + std::vector mSuperdiagonal; // N-1 elements + + // The Givens rotations used to reduce the initial bidiagonal matrix to + // a diagonal matrix. A rotation is the identity with the following + // replacement entries: R(index0,index0) = cs, R(index0,index1) = sn, + // R(index1,index0) = -sn, and R(index1,index1) = cs. If N is the + // number of matrix columns and K is the maximum number of iterations, the + // maximum number of right or left Givens rotations is K*(N-1). The + // maximum amount of memory is allocated to store these. However, we also + // potentially need left rotations to decouple the matrix when a diagonal + // terms are zero. Worst case is a number of matrices quadratic in N, so + // for now we just use std::vector whose initial capacity is + // K*(N-1). + struct GivensRotation + { + GivensRotation(); + GivensRotation(int inIndex0, int inIndex1, Real inCs, Real inSn); + int index0, index1; + Real cs, sn; + }; + + std::vector mRGivens; + std::vector mLGivens; + + // The diagonal matrix that is used to convert S-entries to nonnegative. + std::vector mFixupDiagonal; // N elements + + // When sorting is requested, the permutation associated with the sort is + // stored in mPermutation. When sorting is not requested, mPermutation[0] + // is set to -1. mVisited is used for finding cycles in the permutation. + std::vector mPermutation; // N elements + mutable std::vector mVisited; // N elements + + // Temporary storage to compute Householder reflections and to support + // sorting of columns of the orthogonal matrices. + std::vector mTwoInvUTU; // N elements + std::vector mTwoInvVTV; // N-2 elements + mutable std::vector mUVector; // M elements + mutable std::vector mVVector; // N elements + mutable std::vector mWVector; // max(M,N) elements +}; + + +template +SingularValueDecomposition::SingularValueDecomposition(int numRows, + int numCols, unsigned int maxIterations) + : + mNumRows(0), + mNumCols(0), + mMaxIterations(0) +{ + if (numCols > 1 && numRows >= numCols && maxIterations > 0) + { + mNumRows = numRows; + mNumCols = numCols; + mMaxIterations = maxIterations; + mMatrix.resize(numRows * numCols); + mDiagonal.resize(numCols); + mSuperdiagonal.resize(numCols - 1); + mRGivens.reserve(maxIterations*(numCols - 1)); + mLGivens.reserve(maxIterations*(numCols - 1)); + mFixupDiagonal.resize(numCols); + mPermutation.resize(numCols); + mVisited.resize(numCols); + mTwoInvUTU.resize(numCols); + mTwoInvVTV.resize(numCols - 2); + mUVector.resize(numRows); + mVVector.resize(numCols); + mWVector.resize(numRows); + } +} + +template +unsigned int SingularValueDecomposition::Solve(Real const* input, + int sortType) +{ + if (mNumRows > 0) + { + int numElements = mNumRows * mNumCols; + std::copy(input, input + numElements, mMatrix.begin()); + Bidiagonalize(); + + // Compute 'threshold = multiplier*epsilon*|B|' as the threshold for + // diagonal entries effectively zero; that is, |d| <= |threshold| + // implies that d is (effectively) zero. TODO: Allow the caller to + // pass 'multiplier' to the constructor. + // + // We will use the L2-norm |B|, which is the length of the elements + // of B treated as an NM-tuple. The following code avoids overflow + // when accumulating the squares of the elements when those elements + // are large. + Real maxAbsComp = std::abs(input[0]); + for (int i = 1; i < numElements; ++i) + { + Real absComp = std::abs(input[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real norm = (Real)0; + if (maxAbsComp > (Real)0) + { + Real invMaxAbsComp = ((Real)1) / maxAbsComp; + for (int i = 0; i < numElements; ++i) + { + Real ratio = input[i] * invMaxAbsComp; + norm += ratio * ratio; + } + norm = maxAbsComp * std::sqrt(norm); + } + + Real const multiplier = (Real)8; // TODO: Expose to caller. + Real const epsilon = std::numeric_limits::epsilon(); + Real const threshold = multiplier * epsilon * norm; + + mRGivens.clear(); + mLGivens.clear(); + for (unsigned int j = 0; j < mMaxIterations; ++j) + { + int imin = -1, imax = -1; + for (int i = mNumCols - 2; i >= 0; --i) + { + // When a01 is much smaller than its diagonal neighbors, it is + // effectively zero. + Real a00 = mDiagonal[i]; + Real a01 = mSuperdiagonal[i]; + Real a11 = mDiagonal[i + 1]; + Real sum = std::abs(a00) + std::abs(a11); + if (sum + std::abs(a01) != sum) + { + if (imax == -1) + { + imax = i; + } + imin = i; + } + else + { + // The superdiagonal term is effectively zero compared to + // the neighboring diagonal terms. + if (imin >= 0) + { + break; + } + } + } + + if (imax == -1) + { + // The algorithm has converged. + EnsureNonnegativeDiagonal(); + ComputePermutation(sortType); + return j; + } + + // We need to test diagonal entries of B for zero. For each zero + // diagonal entry, zero the superdiagonal. + if (DiagonalEntriesNonzero(imin, imax, threshold)) + { + // Process the lower-right-most unreduced bidiagonal block. + DoGolubKahanStep(imin, imax); + } + } + return 0xFFFFFFFF; + } + else + { + return 0; + } +} + +template +void SingularValueDecomposition::GetSingularValues( + Real* singularValues) const +{ + if (singularValues && mNumCols > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + for (int i = 0; i < mNumCols; ++i) + { + int p = mPermutation[i]; + singularValues[i] = mDiagonal[p]; + } + } + else + { + // Sorting was not requested. + size_t numBytes = mNumCols*sizeof(Real); + Memcpy(singularValues, &mDiagonal[0], numBytes); + } + } +} + +template +void SingularValueDecomposition::GetU(Real* uMatrix) const +{ + if (!uMatrix || mNumCols == 0) + { + // Invalid input or the constructor failed. + return; + } + + // Start with the identity matrix. + std::fill(uMatrix, uMatrix + mNumRows*mNumRows, (Real)0); + for (int d = 0; d < mNumRows; ++d) + { + uMatrix[d + mNumRows*d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int r, c; + for (int i0 = mNumCols - 1, i1 = i0 + 1; i0 >= 0; --i0, --i1) + { + // Copy the u vector and 2/Dot(u,u) from the matrix. + Real twoinvudu = mTwoInvUTU[i0]; + Real const* column = &mMatrix[i0]; + mUVector[i0] = (Real)1; + for (r = i1; r < mNumRows; ++r) + { + mUVector[r] = column[mNumCols*r]; + } + + // Compute the w vector. + mWVector[i0] = twoinvudu; + for (r = i1; r < mNumRows; ++r) + { + mWVector[r] = (Real)0; + for (c = i1; c < mNumRows; ++c) + { + mWVector[r] += mUVector[c] * uMatrix[r + mNumRows*c]; + } + mWVector[r] *= twoinvudu; + } + + // Update the matrix, U <- U - u*w^T. + for (r = i0; r < mNumRows; ++r) + { + for (c = i0; c < mNumRows; ++c) + { + uMatrix[c + mNumRows*r] -= mUVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mLGivens) + { + int j0 = givens.index0; + int j1 = givens.index1; + for (r = 0; r < mNumRows; ++r, j0 += mNumRows, j1 += mNumRows) + { + Real& q0 = uMatrix[j0]; + Real& q1 = uMatrix[j1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (c = 0; c < mNumCols; ++c) + { + if (mVisited[c] == 0 && mPermutation[c] != c) + { + // The item starts a cycle with 2 or more elements. + int start = c, current = c, next; + for (r = 0; r < mNumRows; ++r) + { + mWVector[r] = uMatrix[c + mNumRows*r]; + } + while ((next = mPermutation[current]) != start) + { + mVisited[current] = 1; + for (r = 0; r < mNumRows; ++r) + { + uMatrix[current + mNumRows*r] = + uMatrix[next + mNumRows*r]; + } + current = next; + } + mVisited[current] = 1; + for (r = 0; r < mNumRows; ++r) + { + uMatrix[current + mNumRows*r] = mWVector[r]; + } + } + } + } +} + +template +void SingularValueDecomposition::GetV(Real* vMatrix) const +{ + if (!vMatrix || mNumCols == 0) + { + // Invalid input or the constructor failed. + return; + } + + // Start with the identity matrix. + std::fill(vMatrix, vMatrix + mNumCols*mNumCols, (Real)0); + for (int d = 0; d < mNumCols; ++d) + { + vMatrix[d + mNumCols*d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int i0 = mNumCols - 3; + int i1 = i0 + 1; + int i2 = i0 + 2; + int r, c; + for (/**/; i0 >= 0; --i0, --i1, --i2) + { + // Copy the v vector and 2/Dot(v,v) from the matrix. + Real twoinvvdv = mTwoInvVTV[i0]; + Real const* row = &mMatrix[mNumCols*i0]; + mVVector[i1] = (Real)1; + for (r = i2; r < mNumCols; ++r) + { + mVVector[r] = row[r]; + } + + // Compute the w vector. + mWVector[i1] = twoinvvdv; + for (r = i2; r < mNumCols; ++r) + { + mWVector[r] = (Real)0; + for (c = i2; c < mNumCols; ++c) + { + mWVector[r] += mVVector[c] * vMatrix[r + mNumCols*c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the matrix, V <- V - v*w^T. + for (r = i1; r < mNumCols; ++r) + { + for (c = i1; c < mNumCols; ++c) + { + vMatrix[c + mNumCols*r] -= mVVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mRGivens) + { + int j0 = givens.index0; + int j1 = givens.index1; + for (c = 0; c < mNumCols; ++c, j0 += mNumCols, j1 += mNumCols) + { + Real& q0 = vMatrix[j0]; + Real& q1 = vMatrix[j1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + // Fix-up the diagonal. + for (r = 0; r < mNumCols; ++r) + { + for (c = 0; c < mNumCols; ++c) + { + vMatrix[c + mNumCols*r] *= mFixupDiagonal[c]; + } + } + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (c = 0; c < mNumCols; ++c) + { + if (mVisited[c] == 0 && mPermutation[c] != c) + { + // The item starts a cycle with 2 or more elements. + int start = c, current = c, next; + for (r = 0; r < mNumCols; ++r) + { + mWVector[r] = vMatrix[c + mNumCols*r]; + } + while ((next = mPermutation[current]) != start) + { + mVisited[current] = 1; + for (r = 0; r < mNumCols; ++r) + { + vMatrix[current + mNumCols*r] = + vMatrix[next + mNumCols*r]; + } + current = next; + } + mVisited[current] = 1; + for (r = 0; r < mNumCols; ++r) + { + vMatrix[current + mNumCols*r] = mWVector[r]; + } + } + } + } +} + +template +void SingularValueDecomposition::GetUColumn(int index, + Real* uColumn) const +{ + if (0 <= index && index < mNumRows) + { + // y = H*x, then x and y are swapped for the next H + Real* x = uColumn; + Real* y = &mWVector[0]; + + // Start with the Euclidean basis vector. + memset(x, 0, mNumRows * sizeof(Real)); + if (index < mNumCols && mPermutation[0] >= 0) + { + // Sorting was requested. + x[mPermutation[index]] = (Real)1; + } + else + { + x[index] = (Real)1; + } + + // Apply the Givens rotations. + for (auto const& givens : gte::reverse(mLGivens)) + { + Real& xr0 = x[givens.index0]; + Real& xr1 = x[givens.index1]; + Real tmp0 = givens.cs * xr0 + givens.sn * xr1; + Real tmp1 = -givens.sn * xr0 + givens.cs * xr1; + xr0 = tmp0; + xr1 = tmp1; + } + + // Apply the Householder reflections. + for (int c = mNumCols - 1; c >= 0; --c) + { + // Get the Householder vector u. + int r; + for (r = 0; r < c; ++r) + { + y[r] = x[r]; + } + + // Compute s = Dot(x,u) * 2/u^T*u. + Real s = x[r]; // r = c, u[r] = 1 + for (int j = r + 1; j < mNumRows; ++j) + { + s += x[j] * mMatrix[c + mNumCols * j]; + } + s *= mTwoInvUTU[c]; + + // r = c, y[r] = x[r]*u[r] - s = x[r] - s because u[r] = 1 + y[r] = x[r] - s; + + // Compute the remaining components of y. + for (++r; r < mNumRows; ++r) + { + y[r] = x[r] - s * mMatrix[c + mNumCols * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != uColumn) + { + size_t numBytes = mNumRows*sizeof(Real); + Memcpy(uColumn, x, numBytes); + } + } +} + +template +void SingularValueDecomposition::GetVColumn(int index, + Real* vColumn) const +{ + if (0 <= index && index < mNumCols) + { + // y = H*x, then x and y are swapped for the next H + Real* x = vColumn; + Real* y = &mWVector[0]; + + // Start with the Euclidean basis vector. + memset(x, 0, mNumCols * sizeof(Real)); + if (mPermutation[0] >= 0) + { + // Sorting was requested. + int p = mPermutation[index]; + x[p] = mFixupDiagonal[p]; + } + else + { + x[index] = mFixupDiagonal[index]; + } + + // Apply the Givens rotations. + for (auto const& givens : gte::reverse(mRGivens)) + { + Real& xr0 = x[givens.index0]; + Real& xr1 = x[givens.index1]; + Real tmp0 = givens.cs * xr0 + givens.sn * xr1; + Real tmp1 = -givens.sn * xr0 + givens.cs * xr1; + xr0 = tmp0; + xr1 = tmp1; + } + + // Apply the Householder reflections. + for (int r = mNumCols - 3; r >= 0; --r) + { + // Get the Householder vector v. + int c; + for (c = 0; c < r + 1; ++c) + { + y[c] = x[c]; + } + + // Compute s = Dot(x,v) * 2/v^T*v. + Real s = x[c]; // c = r+1, v[c] = 1 + for (int j = c + 1; j < mNumCols; ++j) + { + s += x[j] * mMatrix[j + mNumCols * r]; + } + s *= mTwoInvVTV[r]; + + // c = r+1, y[c] = x[c]*v[c] - s = x[c] - s because v[c] = 1 + y[c] = x[c] - s; + + // Compute the remaining components of y. + for (++c; c < mNumCols; ++c) + { + y[c] = x[c] - s * mMatrix[c + mNumCols * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != vColumn) + { + size_t numBytes = mNumCols*sizeof(Real); + Memcpy(vColumn, x, numBytes); + } + } +} + +template +Real SingularValueDecomposition::GetSingularValue(int index) const +{ + if (0 <= index && index < mNumCols) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + return mDiagonal[mPermutation[index]]; + } + else + { + // Sorting was not requested. + return mDiagonal[index]; + } + } + else + { + return (Real)0; + } +} + +template +void SingularValueDecomposition::Bidiagonalize() +{ + int r, c; + for (int i = 0, ip1 = 1; i < mNumCols; ++i, ++ip1) + { + // Compute the U-Householder vector. + Real length = (Real)0; + for (r = i; r < mNumRows; ++r) + { + Real ur = mMatrix[i + mNumCols*r]; + mUVector[r] = ur; + length += ur * ur; + } + Real udu = (Real)1; + length = std::sqrt(length); + if (length >(Real)0) + { + Real& u1 = mUVector[i]; + Real sgn = (u1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (u1 + sgn * length); + u1 = (Real)1; + for (r = ip1; r < mNumRows; ++r) + { + Real& ur = mUVector[r]; + ur *= invDenom; + udu += ur * ur; + } + } + + // Compute the rank-1 offset u*w^T. + Real invudu = (Real)1 / udu; + Real twoinvudu = invudu * (Real)2; + for (c = i; c < mNumCols; ++c) + { + mWVector[c] = (Real)0; + for (r = i; r < mNumRows; ++r) + { + mWVector[c] += mMatrix[c + mNumCols*r] * mUVector[r]; + } + mWVector[c] *= twoinvudu; + } + + // Update the input matrix. + for (r = i; r < mNumRows; ++r) + { + for (c = i; c < mNumCols; ++c) + { + mMatrix[c + mNumCols*r] -= mUVector[r] * mWVector[c]; + } + } + + if (i < mNumCols - 2) + { + // Compute the V-Householder vectors. + length = (Real)0; + for (c = ip1; c < mNumCols; ++c) + { + Real vc = mMatrix[c + mNumCols*i]; + mVVector[c] = vc; + length += vc * vc; + } + Real vdv = (Real)1; + length = std::sqrt(length); + if (length >(Real)0) + { + Real& v1 = mVVector[ip1]; + Real sgn = (v1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (v1 + sgn * length); + v1 = (Real)1; + for (c = ip1 + 1; c < mNumCols; ++c) + { + Real& vc = mVVector[c]; + vc *= invDenom; + vdv += vc * vc; + } + } + + // Compute the rank-1 offset w*v^T. + Real invvdv = (Real)1 / vdv; + Real twoinvvdv = invvdv * (Real)2; + for (r = i; r < mNumRows; ++r) + { + mWVector[r] = (Real)0; + for (c = ip1; c < mNumCols; ++c) + { + mWVector[r] += mMatrix[c + mNumCols*r] * mVVector[c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the input matrix. + for (r = i; r < mNumRows; ++r) + { + for (c = ip1; c < mNumCols; ++c) + { + mMatrix[c + mNumCols*r] -= mWVector[r] * mVVector[c]; + } + } + + mTwoInvVTV[i] = twoinvvdv; + for (c = i + 2; c < mNumCols; ++c) + { + mMatrix[c + mNumCols*i] = mVVector[c]; + } + } + + mTwoInvUTU[i] = twoinvudu; + for (r = ip1; r < mNumRows; ++r) + { + mMatrix[i + mNumCols*r] = mUVector[r]; + } + } + + // Copy the diagonal and subdiagonal for cache coherence in the + // Golub-Kahan iterations. + int k, ksup = mNumCols - 1, index = 0, delta = mNumCols + 1; + for (k = 0; k < ksup; ++k, index += delta) + { + mDiagonal[k] = mMatrix[index]; + mSuperdiagonal[k] = mMatrix[index + 1]; + } + mDiagonal[k] = mMatrix[index]; +} + +template +void SingularValueDecomposition::GetSinCos(Real x, Real y, Real& cs, + Real& sn) +{ + // Solves sn*x + cs*y = 0 robustly. + Real tau; + if (y != (Real)0) + { + if (std::abs(y) > std::abs(x)) + { + tau = -x / y; + sn = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + cs = sn * tau; + } + else + { + tau = -y / x; + cs = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + sn = cs * tau; + } + } + else + { + cs = (Real)1; + sn = (Real)0; + } +} + +template +bool SingularValueDecomposition::DiagonalEntriesNonzero(int imin, + int imax, Real threshold) +{ + for (int i = imin; i <= imax; ++i) + { + if (std::abs(mDiagonal[i]) <= threshold) + { + // Use planar rotations to case the superdiagonal entry out of + // the matrix, thus producing a row of zeros. + Real x, z, cs, sn; + Real y = mSuperdiagonal[i]; + mSuperdiagonal[i] = (Real)0; + for (int j = i + 1; j <= imax + 1; ++j) + { + x = mDiagonal[j]; + GetSinCos(x, y, cs, sn); + mLGivens.push_back(GivensRotation(i, j, cs, sn)); + mDiagonal[j] = cs*x - sn*y; + if (j <= imax) + { + z = mSuperdiagonal[j]; + mSuperdiagonal[j] = cs*z; + y = sn*z; + } + } + return false; + } + } + return true; +} + +template +void SingularValueDecomposition::DoGolubKahanStep(int imin, int imax) +{ + // The implicit shift. Compute the eigenvalue u of the lower-right 2x2 + // block of A = B^T*B that is closer to b11. + Real f0 = (imax >= (Real)1 ? mSuperdiagonal[imax - 1] : (Real)0); + Real d1 = mDiagonal[imax]; + Real f1 = mSuperdiagonal[imax]; + Real d2 = mDiagonal[imax + 1]; + Real a00 = d1*d1 + f0*f0; + Real a01 = d1*f1; + Real a11 = d2*d2 + f1*f1; + Real dif = (a00 - a11) * (Real)0.5; + Real sgn = (dif >= (Real)0 ? (Real)1 : (Real)-1); + Real a01sqr = a01 * a01; + Real u = a11 - a01sqr / (dif + sgn * std::sqrt(dif*dif + a01sqr)); + Real x = mDiagonal[imin] * mDiagonal[imin] - u; + Real y = mDiagonal[imin] * mSuperdiagonal[imin]; + + Real a12, a21, a22, a23, cs, sn; + Real a02 = (Real)0; + int i0 = imin - 1, i1 = imin, i2 = imin + 1; + for (/**/; i1 <= imax; ++i0, ++i1, ++i2) + { + // Compute the Givens rotation G and save it for use in computing + // V in U^T*A*V = S. + GetSinCos(x, y, cs, sn); + mRGivens.push_back(GivensRotation(i1, i2, cs, sn)); + + // Update B0 = B*G. + if (i1 > imin) + { + mSuperdiagonal[i0] = cs*mSuperdiagonal[i0] - sn*a02; + } + + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + mDiagonal[i1] = cs*a11 - sn*a12; + mSuperdiagonal[i1] = sn*a11 + cs*a12; + mDiagonal[i2] = cs*a22; + a21 = -sn*a22; + + // Update the parameters for the next Givens rotations. + x = mDiagonal[i1]; + y = a21; + + // Compute the Givens rotation G and save it for use in computing + // U in U^T*A*V = S. + GetSinCos(x, y, cs, sn); + mLGivens.push_back(GivensRotation(i1, i2, cs, sn)); + + // Update B1 = G^T*B0. + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + mDiagonal[i1] = cs*a11 - sn*a21; + mSuperdiagonal[i1] = cs*a12 - sn*a22; + mDiagonal[i2] = sn*a12 + cs*a22; + + if (i1 < imax) + { + a23 = mSuperdiagonal[i2]; + a02 = -sn*a23; + mSuperdiagonal[i2] = cs*a23; + + // Update the parameters for the next Givens rotations. + x = mSuperdiagonal[i1]; + y = a02; + } + } +} + +template +void SingularValueDecomposition::EnsureNonnegativeDiagonal() +{ + for (int i = 0; i < mNumCols; ++i) + { + if (mDiagonal[i] >= (Real)0) + { + mFixupDiagonal[i] = (Real)1; + } + else + { + mDiagonal[i] = -mDiagonal[i]; + mFixupDiagonal[i] = (Real)-1; + } + } +} + +template +void SingularValueDecomposition::ComputePermutation(int sortType) +{ + if (sortType == 0) + { + // Set a flag for GetSingularValues() and GetOrthogonalMatrices() to + // know that sorted output was not requested. + mPermutation[0] = -1; + return; + } + + // Compute the permutation induced by sorting. Initially, we start with + // the identity permutation I = (0,1,...,N-1). + struct SortItem + { + Real singularValue; + int index; + }; + + std::vector items(mNumCols); + int i; + for (i = 0; i < mNumCols; ++i) + { + items[i].singularValue = mDiagonal[i]; + items[i].index = i; + } + + if (sortType > 0) + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.singularValue < item1.singularValue; + } + ); + } + else + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.singularValue > item1.singularValue; + } + ); + } + + i = 0; + for (auto const& item : items) + { + mPermutation[i++] = item.index; + } + + // GetOrthogonalMatrices() has nontrivial code for computing the + // orthogonal U and V from the reflections and rotations. To avoid + // complicating the code further when sorting is requested, U and V are + // computed as in the unsorted case. We then need to swap columns of + // U and V to be consistent with the sorting of the singular values. To + // minimize copying due to column swaps, we use permutation P. The + // minimum number of transpositions to obtain P from I is N minus the + // number of cycles of P. Each cycle is reordered with a minimum number + // of transpositions; that is, the singular items are cyclically swapped, + // leading to a minimum amount of copying. For example, if there is a + // cycle i0 -> i1 -> i2 -> i3, then the copying is + // save = singularitem[i0]; + // singularitem[i1] = singularitem[i2]; + // singularitem[i2] = singularitem[i3]; + // singularitem[i3] = save; +} + +template +SingularValueDecomposition::GivensRotation::GivensRotation() +{ + // No default initialization for fast creation of std::vector of objects + // of this type. +} + +template +SingularValueDecomposition::GivensRotation::GivensRotation(int inIndex0, + int inIndex1, Real inCs, Real inSn) + : + index0(inIndex0), + index1(inIndex1), + cs(inCs), + sn(inSn) +{ +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSlerpEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSlerpEstimate.h new file mode 100644 index 000000000000..f74470b5da18 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSlerpEstimate.h @@ -0,0 +1,156 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/12/10) + +#pragma once + +#include + +// The spherical linear interpolation (slerp) of unit-length quaternions +// q0 and q1 for t in [0,1] and theta in (0,pi) is +// slerp(t,q0,q1) = [sin((1-t)*theta)*q0 + sin(theta)*q1]/sin(theta) +// where theta is the angle between q0 and q1 [cos(theta) = Dot(q0,q1)]. +// This function is a parameterization of the great spherical arc between +// q0 and q1 on the unit hypersphere. Moreover, the parameterization is +// one of normalized arclength--a particle traveling along the arc through +// time t does so with constant speed. +// +// Read the comments in GteChebyshevRatio.h regarding estimates for the +// ratio sin(t*theta)/sin(theta). +// +// When using slerp in animations involving sequences of quaternions, it is +// typical that the quaternions are preprocessed so that consecutive ones +// form an acute angle A in [0,pi/2]. Other preprocessing can help with +// performance. See the function comments in the SLERP class. + +namespace gte +{ + template + class SLERP + { + public: + // The angle between q0 and q1 is in [0,pi). There are no angle + // restrictions and nothing is precomputed. + template + inline static Quaternion Estimate(Real t, Quaternion const& q0, Quaternion const& q1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real cs = Dot(q0, q1); + Real sign; + if (cs >= (Real)0) + { + sign = (Real)1; + } + else + { + cs = -cs; + sign = (Real)-1; + } + + Real f0, f1; + ChebyshevRatio::template + GetEstimate(t, (Real)1 - cs, f0, f1); + return q0 * f0 + q1 * (sign * f1); + } + + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted'. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; // now Dot(q[i0], q[i]1) >= 0 + // } + // } + template + inline static Quaternion EstimateR(Real t, Quaternion const& q0, Quaternion const& q1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + ChebyshevRatio::template + GetEstimate(t, (Real)1 - Dot(q0, q1), f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted' and the suffix P is for 'Preprocessed'. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Real cosA[n-1], omcosA[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cs = Dot(q[i0], q[i1]); + // if (cosA[i0] < 0) + // { + // q[i1] = -q[i1]; + // cs = -cs; + // } + // + // // for Quaterion::SlerpRP + // cosA[i0] = cs; + // + // // for SLERP::EstimateRP + // omcosA[i0] = 1 - cs; + // } + template + inline static Quaternion EstimateRP(Real t, Quaternion const& q0, Quaternion const& q1, + Real omcosA) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + ChebyshevRatio::template + GetEstimate(t, omcosA, f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 is A and must be in [0,pi/2]. Quaternion + // qh is slerp(1/2,q0,q1) = (q0+q1)/|q0+q1|, so the angle between q0 and + // qh is A/2 and the angle between qh and q1 is A/2. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Quaternion qh[n-1]; // to be precomputed + // Real omcosAH[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; + // cosA = -cosA; + // } + // cosAH = sqrt((1 + cosA)/2); + // qh[i0] = (q0 + q1) / (2 * cosAH[i0]); + // omcosAH[i0] = 1 - cosAH; + // } + template + inline static Quaternion EstimateRPH(Real t, Quaternion const& q0, Quaternion const& q1, + Quaternion const& qh, Real omcosAH) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + Real twoT = t * (Real)2; + if (twoT <= (Real)1) + { + ChebyshevRatio::template + GetEstimate(twoT, omcosAH, f0, f1); + return q0 * f0 + qh * f1; + } + else + { + ChebyshevRatio::template + GetEstimate(twoT - (Real)1, omcosAH, f0, f1); + return qh * f0 + q1 * f1; + } + } + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSqrtEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSqrtEstimate.h new file mode 100644 index 000000000000..e538913276f6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSqrtEstimate.h @@ -0,0 +1,193 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to sqrt(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|sqrt(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace gte +{ + +template +class SqrtEstimate +{ +public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = SqrtEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input constraint is x >= 0. Range reduction is used to generate a + // value y in [0,1], call Degree(y), and combine the output with the + // proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = SqrtEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<1>, Real t); + inline static Real Evaluate(degree<2>, Real t); + inline static Real Evaluate(degree<3>, Real t); + inline static Real Evaluate(degree<4>, Real t); + inline static Real Evaluate(degree<5>, Real t); + inline static Real Evaluate(degree<6>, Real t); + inline static Real Evaluate(degree<7>, Real t); + inline static Real Evaluate(degree<8>, Real t); + + // Support for range reduction. + inline static void Reduce(Real x, Real& adj, Real& y, int& p); + inline static Real Combine(Real adj, Real y, int p); +}; + + +template +template +inline Real SqrtEstimate::Degree(Real x) +{ + Real t = x - (Real)1; // t in [0,1] + return Evaluate(degree(), t); +} + +template +template +inline Real SqrtEstimate::DegreeRR(Real x) +{ + Real adj, y; + int p; + Reduce(x, adj, y, p); + Real poly = Degree(y); + Real result = Combine(adj, poly, p); + return result; +} + +template +inline Real SqrtEstimate::Evaluate(degree<1>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG1_C1; + poly = (Real)GTE_C_SQRT_DEG1_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<2>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG2_C2; + poly = (Real)GTE_C_SQRT_DEG2_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG2_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<3>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG3_C3; + poly = (Real)GTE_C_SQRT_DEG3_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG3_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG3_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<4>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG4_C4; + poly = (Real)GTE_C_SQRT_DEG4_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<5>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG5_C5; + poly = (Real)GTE_C_SQRT_DEG5_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<6>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG6_C6; + poly = (Real)GTE_C_SQRT_DEG6_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<7>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG7_C7; + poly = (Real)GTE_C_SQRT_DEG7_C6 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C0 + poly * t; + return poly; +} + +template +inline Real SqrtEstimate::Evaluate(degree<8>, Real t) +{ + Real poly; + poly = (Real)GTE_C_SQRT_DEG8_C8; + poly = (Real)GTE_C_SQRT_DEG8_C7 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C6 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C0 + poly * t; + return poly; +} + +template +inline void SqrtEstimate::Reduce(Real x, Real& adj, Real& y, int& p) +{ + y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2)*y; // y in [1,2) + --p; + adj = (1 & p)*(Real)GTE_C_SQRT_2 + (1 & ~p)*(Real)1; + p >>= 1; +} + +template +inline Real SqrtEstimate::Combine(Real adj, Real y, int p) +{ + return adj * std::ldexp(y, p); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver.h new file mode 100644 index 000000000000..5509b1803bef --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver.h @@ -0,0 +1,835 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The SymmetricEigensolver class is an implementation of Algorithm 8.2.3 +// (Symmetric QR Algorithm) described in "Matrix Computations, 2nd edition" +// by G. H. Golub and C. F. Van Loan, The Johns Hopkins University Press, +// Baltimore MD, Fourth Printing 1993. Algorithm 8.2.1 (Householder +// Tridiagonalization) is used to reduce matrix A to tridiagonal T. +// Algorithm 8.2.2 (Implicit Symmetric QR Step with Wilkinson Shift) is +// used for the iterative reduction from tridiagonal to diagonal. If A is +// the original matrix, D is the diagonal matrix of eigenvalues, and Q is +// the orthogonal matrix of eigenvectors, then theoretically Q^T*A*Q = D. +// Numerically, we have errors E = Q^T*A*Q - D. Algorithm 8.2.3 mentions +// that one expects |E| is approximately u*|A|, where |M| denotes the +// Frobenius norm of M and where u is the unit roundoff for the +// floating-point arithmetic: 2^{-23} for 'float', which is FLT_EPSILON +// = 1.192092896e-7f, and 2^{-52} for'double', which is DBL_EPSILON +// = 2.2204460492503131e-16. +// +// The condition |a(i,i+1)| <= epsilon*(|a(i,i) + a(i+1,i+1)|) used to +// determine when the reduction decouples to smaller problems is implemented +// as: sum = |a(i,i)| + |a(i+1,i+1)|; sum + |a(i,i+1)| == sum. The idea is +// that the superdiagonal term is small relative to its diagonal neighbors, +// and so it is effectively zero. The unit tests have shown that this +// interpretation of decoupling is effective. +// +// The authors suggest that once you have the tridiagonal matrix, a practical +// implementation will store the diagonal and superdiagonal entries in linear +// arrays, ignoring the theoretically zero values not in the 3-band. This is +// good for cache coherence. The authors also suggest storing the Householder +// vectors in the lower-triangular portion of the matrix to save memory. The +// implementation uses both suggestions. +// +// For matrices with randomly generated values in [0,1], the unit tests +// produce the following information for N-by-N matrices. +// +// N |A| |E| |E|/|A| iterations +// ------------------------------------------- +// 2 1.2332 5.5511e-17 4.5011e-17 1 +// 3 2.0024 1.1818e-15 5.9020e-16 5 +// 4 2.8708 9.9287e-16 3.4585e-16 7 +// 5 2.9040 2.5958e-15 8.9388e-16 9 +// 6 4.0427 2.2434e-15 5.5493e-16 12 +// 7 5.0276 3.2456e-15 6.4555e-16 15 +// 8 5.4468 6.5626e-15 1.2048e-15 15 +// 9 6.1510 4.0317e-15 6.5545e-16 17 +// 10 6.7523 4.9334e-15 7.3062e-16 21 +// 11 7.1322 7.1347e-15 1.0003e-15 22 +// 12 7.8324 5.6106e-15 7.1633e-16 24 +// 13 8.1073 5.1366e-15 6.3357e-16 25 +// 14 8.6257 8.3496e-15 9.6798e-16 29 +// 15 9.2603 6.9526e-15 7.5080e-16 31 +// 16 9.9853 6.5807e-15 6.5904e-16 32 +// 17 10.5388 8.0931e-15 7.6793e-16 35 +// 18 11.2377 1.1149e-14 9.9218e-16 38 +// 19 11.7105 1.0711e-14 9.1470e-16 42 +// 20 12.2227 1.7723e-14 1.4500e-15 42 +// 21 12.7411 1.2515e-14 9.8231e-16 47 +// 22 13.4462 1.2645e-14 9.4046e-16 50 +// 23 13.9541 1.2899e-14 9.2444e-16 47 +// 24 14.3298 1.6337e-14 1.1400e-15 53 +// 25 14.8050 1.4760e-14 9.9701e-16 54 +// 26 15.3914 1.7076e-14 1.1094e-15 57 +// 27 15.8430 1.9714e-14 1.2443e-15 60 +// 28 16.4771 1.7148e-14 1.0407e-15 60 +// 29 16.9909 1.7309e-14 1.0187e-15 60 +// 30 17.4456 2.1453e-14 1.2297e-15 64 +// 31 17.9909 2.2069e-14 1.2267e-15 68 +// +// The eigenvalues and |E|/|A| values were compared to those generated by +// Mathematica Version 9.0; Wolfram Research, Inc., Champaign IL, 2012. +// The results were all comparable with eigenvalues agreeing to a large +// number of decimal places. +// +// Timing on an Intel (R) Core (TM) i7-3930K CPU @ 3.20 GHZ (in seconds): +// +// N |E|/|A| iters tridiag QR evecs evec[N] comperr +// -------------------------------------------------------------- +// 512 4.4149e-15 1017 0.180 0.005 1.151 0.848 2.166 +// 1024 6.1691e-15 1990 1.775 0.031 11.894 12.759 21.179 +// 2048 8.5108e-15 3849 16.592 0.107 119.744 116.56 212.227 +// +// where iters is the number of QR steps taken, tridiag is the computation +// of the Householder reflection vectors, evecs is the composition of +// Householder reflections and Givens rotations to obtain the matrix of +// eigenvectors, evec[N] is N calls to get the eigenvectors separately, and +// comperr is the computation E = Q^T*A*Q - D. The construction of the full +// eigenvector matrix is, of course, quite expensive. If you need only a +// small number of eigenvectors, use function GetEigenvector(int,Real*). + +namespace gte +{ + +template +class SymmetricEigensolver +{ +public: + // The solver processes NxN symmetric matrices, where N > 1 ('size' is N) + // and the matrix is stored in row-major order. The maximum number of + // iterations ('maxIterations') must be specified for the reduction of a + // tridiagonal matrix to a diagonal matrix. The goal is to compute + // NxN orthogonal Q and NxN diagonal D for which Q^T*A*Q = D. + SymmetricEigensolver(int size, unsigned int maxIterations); + + // A copy of the NxN symmetric input is made internally. The order of + // the eigenvalues is specified by sortType: -1 (decreasing), 0 (no + // sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did + // not occur, or 0 when N <= 1 was passed to the constructor. + unsigned int Solve(Real const* input, int sortType); + + // Get the eigenvalues of the matrix passed to Solve(...). The input + // 'eigenvalues' must have N elements. + void GetEigenvalues(Real* eigenvalues) const; + + // Accumulate the Householder reflections and Givens rotations to produce + // the orthogonal matrix Q for which Q^T*A*Q = D. The input + // 'eigenvectors' must have N*N elements. The array is filled in as + // if the eigenvector matrix is stored in row-major order. The i-th + // eigenvector is + // (eigenvectors[i+size*0], ... eigenvectors[i+size*(size - 1)]) + // which is the i-th column of 'eigenvectors' as an NxN matrix stored + // in row-major order. + void GetEigenvectors(Real* eigenvectors) const; + + // The eigenvector matrix is a rotation (return +1) or a reflection + // (return 0). If the input 'size' to the constructor is 0 or the + // input 'eigenvectors' to GetEigenvectors is null, the returned value + // is -1. + int GetEigenvectorMatrixType() const; + + // Compute a single eigenvector, which amounts to computing column c + // of matrix Q. The reflections and rotations are applied incrementally. + // This is useful when you want only a small number of the eigenvectors. + void GetEigenvector(int c, Real* eigenvector) const; + Real GetEigenvalue(int c) const; + +private: + // Tridiagonalize using Householder reflections. On input, mMatrix is a + // copy of the input matrix. On output, the upper-triangular part of + // mMatrix including the diagonal stores the tridiagonalization. The + // lower-triangular part contains 2/Dot(v,v) that are used in computing + // eigenvectors and the part below the subdiagonal stores the essential + // parts of the Householder vectors v (the elements of v after the + // leading 1-valued component). + void Tridiagonalize(); + + // A helper for generating Givens rotation sine and cosine robustly. + void GetSinCos(Real u, Real v, Real& cs, Real& sn); + + // The QR step with implicit shift. Generally, the initial T is unreduced + // tridiagonal (all subdiagonal entries are nonzero). If a QR step causes + // a superdiagonal entry to become zero, the matrix decouples into a block + // diagonal matrix with two tridiagonal blocks. These blocks can be + // reduced independently of each other, which allows for parallelization + // of the algorithm. The inputs imin and imax identify the subblock of T + // to be processed. That block has upper-left element T(imin,imin) and + // lower-right element T(imax,imax). + void DoQRImplicitShift(int imin, int imax); + + // Sort the eigenvalues and compute the corresponding permutation of the + // indices of the array storing the eigenvalues. The permutation is used + // for reordering the eigenvalues and eigenvectors in the calls to + // GetEigenvalues(...) and GetEigenvectors(...). + void ComputePermutation(int sortType); + + // The number N of rows and columns of the matrices to be processed. + int mSize; + + // The maximum number of iterations for reducing the tridiagonal mtarix + // to a diagonal matrix. + unsigned int mMaxIterations; + + // The internal copy of a matrix passed to the solver. See the comments + // about function Tridiagonalize() about what is stored in the matrix. + std::vector mMatrix; // NxN elements + + // After the initial tridiagonalization by Householder reflections, we no + // longer need the full mMatrix. Copy the diagonal and superdiagonal + // entries to linear arrays in order to be cache friendly. + std::vector mDiagonal; // N elements + std::vector mSuperdiagonal; // N-1 elements + + // The Givens rotations used to reduce the initial tridiagonal matrix to + // a diagonal matrix. A rotation is the identity with the following + // replacement entries: R(index,index) = cs, R(index,index+1) = sn, + // R(index+1,index) = -sn, and R(index+1,index+1) = cs. If N is the + // matrix size and K is the maximum number of iterations, the maximum + // number of Givens rotations is K*(N-1). The maximum amount of memory + // is allocated to store these. + struct GivensRotation + { + GivensRotation(); + GivensRotation(int inIndex, Real inCs, Real inSn); + int index; + Real cs, sn; + }; + + std::vector mGivens; // K*(N-1) elements + + // When sorting is requested, the permutation associated with the sort is + // stored in mPermutation. When sorting is not requested, mPermutation[0] + // is set to -1. mVisited is used for finding cycles in the permutation. + // mEigenvectorMatrixType is +1 if GetEigenvectors returns a rotation + // matrix, 0 if GetEigenvectors returns a reflection matrix or -1 if an + // input to the constructor or to GetEigenvectors is invalid. + std::vector mPermutation; // N elements + mutable std::vector mVisited; // N elements + mutable int mEigenvectorMatrixType; + + // Temporary storage to compute Householder reflections and to support + // sorting of eigenvectors. + mutable std::vector mPVector; // N elements + mutable std::vector mVVector; // N elements + mutable std::vector mWVector; // N elements +}; + + +template +SymmetricEigensolver::SymmetricEigensolver(int size, + unsigned int maxIterations) + : + mSize(0), + mMaxIterations(0), + mEigenvectorMatrixType(-1) +{ + if (size > 1 && maxIterations > 0) + { + mSize = size; + mMaxIterations = maxIterations; + mMatrix.resize(size*size); + mDiagonal.resize(size); + mSuperdiagonal.resize(size - 1); + mGivens.reserve(maxIterations * (size - 1)); + mPermutation.resize(size); + mVisited.resize(size); + mPVector.resize(size); + mVVector.resize(size); + mWVector.resize(size); + } +} + +template +unsigned int SymmetricEigensolver::Solve(Real const* input, + int sortType) +{ + mEigenvectorMatrixType = -1; + + if (mSize > 0) + { + std::copy(input, input + mSize*mSize, mMatrix.begin()); + Tridiagonalize(); + + mGivens.clear(); + for (unsigned int j = 0; j < mMaxIterations; ++j) + { + int imin = -1, imax = -1; + for (int i = mSize - 2; i >= 0; --i) + { + // When a01 is much smaller than its diagonal neighbors, it is + // effectively zero. + Real a00 = mDiagonal[i]; + Real a01 = mSuperdiagonal[i]; + Real a11 = mDiagonal[i + 1]; + Real sum = std::abs(a00) + std::abs(a11); + if (sum + std::abs(a01) != sum) + { + if (imax == -1) + { + imax = i; + } + imin = i; + } + else + { + // The superdiagonal term is effectively zero compared to + // the neighboring diagonal terms. + if (imin >= 0) + { + break; + } + } + } + + if (imax == -1) + { + // The algorithm has converged. + ComputePermutation(sortType); + return j; + } + + // Process the lower-right-most unreduced tridiagonal block. + DoQRImplicitShift(imin, imax); + } + return 0xFFFFFFFF; + } + else + { + return 0; + } +} + +template +void SymmetricEigensolver::GetEigenvalues(Real* eigenvalues) const +{ + if (eigenvalues && mSize > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + for (int i = 0; i < mSize; ++i) + { + int p = mPermutation[i]; + eigenvalues[i] = mDiagonal[p]; + } + } + else + { + // Sorting was not requested. + size_t numBytes = mSize*sizeof(Real); + Memcpy(eigenvalues, &mDiagonal[0], numBytes); + } + } +} + +template +void SymmetricEigensolver::GetEigenvectors(Real* eigenvectors) const +{ + mEigenvectorMatrixType = -1; + + if (eigenvectors && mSize > 0) + { + // Start with the identity matrix. + std::fill(eigenvectors, eigenvectors + mSize*mSize, (Real)0); + for (int d = 0; d < mSize; ++d) + { + eigenvectors[d + mSize*d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int r, c; + for (int i = mSize - 3, rmin = i + 1; i >= 0; --i, --rmin) + { + // Copy the v vector and 2/Dot(v,v) from the matrix. + Real const* column = &mMatrix[i]; + Real twoinvvdv = column[mSize*(i + 1)]; + for (r = 0; r < i + 1; ++r) + { + mVVector[r] = (Real)0; + } + mVVector[r] = (Real)1; + for (++r; r < mSize; ++r) + { + mVVector[r] = column[mSize*r]; + } + + // Compute the w vector. + for (r = 0; r < mSize; ++r) + { + mWVector[r] = (Real)0; + for (c = rmin; c < mSize; ++c) + { + mWVector[r] += mVVector[c] * eigenvectors[r + mSize*c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the matrix, Q <- Q - v*w^T. + for (r = rmin; r < mSize; ++r) + { + for (c = 0; c < mSize; ++c) + { + eigenvectors[c + mSize*r] -= mVVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mGivens) + { + for (r = 0; r < mSize; ++r) + { + int j = givens.index + mSize*r; + Real& q0 = eigenvectors[j]; + Real& q1 = eigenvectors[j + 1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + // The number of Householder reflections is H = mSize - 2. If H is + // even, the product of Householder reflections is a rotation; + // otherwise, H is odd and the product is a reflection. The number + // of Givens rotations does not influence the type of the product of + // Householder reflections. + mEigenvectorMatrixType = 1 - (mSize & 1); + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (int i = 0; i < mSize; ++i) + { + if (mVisited[i] == 0 && mPermutation[i] != i) + { + // The item starts a cycle with 2 or more elements. + int start = i, current = i, j, next; + for (j = 0; j < mSize; ++j) + { + mPVector[j] = eigenvectors[i + mSize*j]; + } + while ((next = mPermutation[current]) != start) + { + mEigenvectorMatrixType = 1 - mEigenvectorMatrixType; + mVisited[current] = 1; + for (j = 0; j < mSize; ++j) + { + eigenvectors[current + mSize*j] = + eigenvectors[next + mSize*j]; + } + current = next; + } + mVisited[current] = 1; + for (j = 0; j < mSize; ++j) + { + eigenvectors[current + mSize*j] = mPVector[j]; + } + } + } + } + } +} + +template +int SymmetricEigensolver::GetEigenvectorMatrixType() const +{ + return mEigenvectorMatrixType; +} + +template +void SymmetricEigensolver::GetEigenvector(int c, Real* eigenvector) +const +{ + if (0 <= c && c < mSize) + { + // y = H*x, then x and y are swapped for the next H + Real* x = eigenvector; + Real* y = &mPVector[0]; + + // Start with the Euclidean basis vector. + memset(x, 0, mSize*sizeof(Real)); + if (mPermutation[0] >= 0) + { + // Sorting was requested. + x[mPermutation[c]] = (Real)1; + } + else + { + x[c] = (Real)1; + } + + // Apply the Givens rotations. + for (auto const& givens : gte::reverse(mGivens)) + { + Real& xr = x[givens.index]; + Real& xrp1 = x[givens.index + 1]; + Real tmp0 = givens.cs * xr + givens.sn * xrp1; + Real tmp1 = -givens.sn * xr + givens.cs * xrp1; + xr = tmp0; + xrp1 = tmp1; + } + + // Apply the Householder reflections. + for (int i = mSize - 3; i >= 0; --i) + { + // Get the Householder vector v. + Real const* column = &mMatrix[i]; + Real twoinvvdv = column[mSize*(i + 1)]; + int r; + for (r = 0; r < i + 1; ++r) + { + y[r] = x[r]; + } + + // Compute s = Dot(x,v) * 2/v^T*v. + Real s = x[r]; // r = i+1, v[i+1] = 1 + for (int j = r + 1; j < mSize; ++j) + { + s += x[j] * column[mSize*j]; + } + s *= twoinvvdv; + + y[r] = x[r] - s; // v[i+1] = 1 + + // Compute the remaining components of y. + for (++r; r < mSize; ++r) + { + y[r] = x[r] - s * column[mSize*r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != eigenvector) + { + size_t numBytes = mSize*sizeof(Real); + Memcpy(eigenvector, x, numBytes); + } + } +} + +template +Real SymmetricEigensolver::GetEigenvalue(int c) const +{ + if (mSize > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + return mDiagonal[mPermutation[c]]; + } + else + { + // Sorting was not requested. + return mDiagonal[c]; + } + } + else + { + return std::numeric_limits::max(); + } +} + +template +void SymmetricEigensolver::Tridiagonalize() +{ + int r, c; + for (int i = 0, ip1 = 1; i < mSize - 2; ++i, ++ip1) + { + // Compute the Householder vector. Read the initial vector from the + // row of the matrix. + Real length = (Real)0; + for (r = 0; r < ip1; ++r) + { + mVVector[r] = (Real)0; + } + for (r = ip1; r < mSize; ++r) + { + Real vr = mMatrix[r + mSize*i]; + mVVector[r] = vr; + length += vr * vr; + } + Real vdv = (Real)1; + length = std::sqrt(length); + if (length >(Real)0) + { + Real& v1 = mVVector[ip1]; + Real sgn = (v1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (v1 + sgn * length); + v1 = (Real)1; + for (r = ip1 + 1; r < mSize; ++r) + { + Real& vr = mVVector[r]; + vr *= invDenom; + vdv += vr * vr; + } + } + + // Compute the rank-1 offsets v*w^T and w*v^T. + Real invvdv = (Real)1 / vdv; + Real twoinvvdv = invvdv * (Real)2; + Real pdvtvdv = (Real)0; + for (r = i; r < mSize; ++r) + { + mPVector[r] = (Real)0; + for (c = i; c < r; ++c) + { + mPVector[r] += mMatrix[r + mSize*c] * mVVector[c]; + } + for (/**/; c < mSize; ++c) + { + mPVector[r] += mMatrix[c + mSize*r] * mVVector[c]; + } + mPVector[r] *= twoinvvdv; + pdvtvdv += mPVector[r] * mVVector[r]; + } + + pdvtvdv *= invvdv; + for (r = i; r < mSize; ++r) + { + mWVector[r] = mPVector[r] - pdvtvdv * mVVector[r]; + } + + // Update the input matrix. + for (r = i; r < mSize; ++r) + { + Real vr = mVVector[r]; + Real wr = mWVector[r]; + Real offset = vr * wr * (Real)2; + mMatrix[r + mSize*r] -= offset; + for (c = r + 1; c < mSize; ++c) + { + offset = vr * mWVector[c] + wr * mVVector[c]; + mMatrix[c + mSize*r] -= offset; + } + } + + // Copy the vector to column i of the matrix. The 0-valued components + // at indices 0 through i are not stored. The 1-valued component at + // index i+1 is also not stored; instead, the quantity 2/Dot(v,v) is + // stored for use in eigenvector construction. That construction must + // take into account the implied components that are not stored. + mMatrix[i + mSize*ip1] = twoinvvdv; + for (r = ip1 + 1; r < mSize; ++r) + { + mMatrix[i + mSize*r] = mVVector[r]; + } + } + + // Copy the diagonal and subdiagonal entries for cache coherence in + // the QR iterations. + int k, ksup = mSize - 1, index = 0, delta = mSize + 1; + for (k = 0; k < ksup; ++k, index += delta) + { + mDiagonal[k] = mMatrix[index]; + mSuperdiagonal[k] = mMatrix[index + 1]; + } + mDiagonal[k] = mMatrix[index]; +} + +template +void SymmetricEigensolver::GetSinCos(Real x, Real y, Real& cs, Real& sn) +{ + // Solves sn*x + cs*y = 0 robustly. + Real tau; + if (y != (Real)0) + { + if (std::abs(y) > std::abs(x)) + { + tau = -x / y; + sn = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + cs = sn * tau; + } + else + { + tau = -y / x; + cs = ((Real)1) / std::sqrt(((Real)1) + tau*tau); + sn = cs * tau; + } + } + else + { + cs = (Real)1; + sn = (Real)0; + } +} + +template +void SymmetricEigensolver::DoQRImplicitShift(int imin, int imax) +{ + // The implicit shift. Compute the eigenvalue u of the lower-right 2x2 + // block that is closer to a11. + Real a00 = mDiagonal[imax]; + Real a01 = mSuperdiagonal[imax]; + Real a11 = mDiagonal[imax + 1]; + Real dif = (a00 - a11) * (Real)0.5; + Real sgn = (dif >= (Real)0 ? (Real)1 : (Real)-1); + Real a01sqr = a01 * a01; + Real u = a11 - a01sqr / (dif + sgn * std::sqrt(dif*dif + a01sqr)); + Real x = mDiagonal[imin] - u; + Real y = mSuperdiagonal[imin]; + + Real a12, a22, a23, tmp11, tmp12, tmp21, tmp22, cs, sn; + Real a02 = (Real)0; + int i0 = imin - 1, i1 = imin, i2 = imin + 1; + for (/**/; i1 <= imax; ++i0, ++i1, ++i2) + { + // Compute the Givens rotation and save it for use in computing the + // eigenvectors. + GetSinCos(x, y, cs, sn); + mGivens.push_back(GivensRotation(i1, cs, sn)); + + // Update the tridiagonal matrix. This amounts to updating a 4x4 + // subblock, + // b00 b01 b02 b03 + // b01 b11 b12 b13 + // b02 b12 b22 b23 + // b03 b13 b23 b33 + // The four corners (b00, b03, b33) do not change values. The + // The interior block {{b11,b12},{b12,b22}} is updated on each pass. + // For the first pass, the b0c values are out of range, so only + // the values (b13, b23) change. For the last pass, the br3 values + // are out of range, so only the values (b01, b02) change. For + // passes between first and last, the values (b01, b02, b13, b23) + // change. + if (i1 > imin) + { + mSuperdiagonal[i0] = cs*mSuperdiagonal[i0] - sn*a02; + } + + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + tmp11 = cs*a11 - sn*a12; + tmp12 = cs*a12 - sn*a22; + tmp21 = sn*a11 + cs*a12; + tmp22 = sn*a12 + cs*a22; + mDiagonal[i1] = cs*tmp11 - sn*tmp12; + mSuperdiagonal[i1] = sn*tmp11 + cs*tmp12; + mDiagonal[i2] = sn*tmp21 + cs*tmp22; + + if (i1 < imax) + { + a23 = mSuperdiagonal[i2]; + a02 = -sn*a23; + mSuperdiagonal[i2] = cs*a23; + + // Update the parameters for the next Givens rotation. + x = mSuperdiagonal[i1]; + y = a02; + } + } +} + +template +void SymmetricEigensolver::ComputePermutation(int sortType) +{ + // The number of Householder reflections is H = mSize - 2. If H is + // even, the product of Householder reflections is a rotation; + // otherwise, H is odd and the product is a reflection. The number + // of Givens rotations does not influence the type of the product of + // Householder reflections. + mEigenvectorMatrixType = 1 - (mSize & 1); + + if (sortType == 0) + { + // Set a flag for GetEigenvalues() and GetEigenvectors() to know + // that sorted output was not requested. + mPermutation[0] = -1; + return; + } + + // Compute the permutation induced by sorting. Initially, we start with + // the identity permutation I = (0,1,...,N-1). + struct SortItem + { + Real eigenvalue; + int index; + }; + + std::vector items(mSize); + int i; + for (i = 0; i < mSize; ++i) + { + items[i].eigenvalue = mDiagonal[i]; + items[i].index = i; + } + + if (sortType > 0) + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.eigenvalue < item1.eigenvalue; + } + ); + } + else + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.eigenvalue > item1.eigenvalue; + } + ); + } + + i = 0; + for (auto const& item : items) + { + mPermutation[i++] = item.index; + } + + // GetEigenvectors() has nontrivial code for computing the orthogonal Q + // from the reflections and rotations. To avoid complicating the code + // further when sorting is requested, Q is computed as in the unsorted + // case. We then need to swap columns of Q to be consistent with the + // sorting of the eigenvalues. To minimize copying due to column swaps, + // we use permutation P. The minimum number of transpositions to obtain + // P from I is N minus the number of cycles of P. Each cycle is reordered + // with a minimum number of transpositions; that is, the eigenitems are + // cyclically swapped, leading to a minimum amount of copying. For + // example, if there is a cycle i0 -> i1 -> i2 -> i3, then the copying is + // save = eigenitem[i0]; + // eigenitem[i1] = eigenitem[i2]; + // eigenitem[i2] = eigenitem[i3]; + // eigenitem[i3] = save; +} + +template +SymmetricEigensolver::GivensRotation::GivensRotation() +{ + // No default initialization for fast creation of std::vector of objects + // of this type. +} + +template +SymmetricEigensolver::GivensRotation::GivensRotation(int inIndex, + Real inCs, Real inSn) + : + index(inIndex), + cs(inCs), + sn(inSn) +{ +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver2x2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver2x2.h new file mode 100644 index 000000000000..3398c8027906 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver2x2.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class SymmetricEigensolver2x2 +{ +public: + // The input matrix must be symmetric, so only the unique elements must + // be specified: a00, a01, and a11. + // + // The order of the eigenvalues is specified by sortType: -1 (decreasing), + // 0 (no sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly, and {evec[0], evec[1]} is guaranteed to be a + // right-handed orthonormal set. + + void operator()(Real a00, Real a01, Real a11, int sortType, + std::array& eval, std::array, 2>& evec) + const; +}; + + +template +void SymmetricEigensolver2x2::operator()(Real a00, Real a01, Real a11, + int sortType, std::array& eval, + std::array, 2>& evec) const +{ + // Normalize (c2,s2) robustly, avoiding floating-point overflow in the + // sqrt call. + Real const zero = (Real)0, one = (Real)1, half = (Real)0.5; + Real c2 = half * (a00 - a11), s2 = a01; + Real maxAbsComp = std::max(std::abs(c2), std::abs(s2)); + if (maxAbsComp > zero) + { + c2 /= maxAbsComp; // in [-1,1] + s2 /= maxAbsComp; // in [-1,1] + Real length = std::sqrt(c2 * c2 + s2 * s2); + c2 /= length; + s2 /= length; + if (c2 > zero) + { + c2 = -c2; + s2 = -s2; + } + } + else + { + c2 = -one; + s2 = zero; + } + + Real s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + Real c = half * s2 / s; + + Real diagonal[2]; + Real csqr = c * c, ssqr = s * s, mid = s2 * a01; + diagonal[0] = csqr * a00 + mid + ssqr * a11; + diagonal[1] = csqr * a11 - mid + ssqr * a00; + + if (sortType == 0 || sortType * diagonal[0] <= sortType * diagonal[1]) + { + eval[0] = diagonal[0]; + eval[1] = diagonal[1]; + evec[0][0] = c; + evec[0][1] = s; + evec[1][0] = -s; + evec[1][1] = c; + } + else + { + eval[0] = diagonal[1]; + eval[1] = diagonal[0]; + evec[0][0] = s; + evec[0][1] = -c; + evec[1][0] = c; + evec[1][1] = s; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver3x3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver3x3.h new file mode 100644 index 000000000000..55d9325b308c --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteSymmetricEigensolver3x3.h @@ -0,0 +1,777 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.5 (2018/10/09) + +#pragma once + +#include +#include +#include +#include + +// The document +// http://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf +// describes algorithms for solving the eigensystem associated with a 3x3 +// symmetric real-valued matrix. The iterative algorithm is implemented +// by class SymmmetricEigensolver3x3. The noniterative algorithm is +// implemented by class NISymmetricEigensolver3x3. The code does not use +// GTEngine objects. + +namespace gte +{ + +template +class SymmetricEigensolver3x3 +{ +public: + // The input matrix must be symmetric, so only the unique elements must + // be specified: a00, a01, a02, a11, a12, and a22. + // + // If 'aggressive' is 'true', the iterations occur until a superdiagonal + // entry is exactly zero. If 'aggressive' is 'false', the iterations + // occur until a superdiagonal entry is effectively zero compared to the + // sum of magnitudes of its diagonal neighbors. Generally, the + // nonaggressive convergence is acceptable. + // + // The order of the eigenvalues is specified by sortType: -1 (decreasing), + // 0 (no sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly, and {evec[0], evec[1], evec[2]} is guaranteed to + // be a right-handed orthonormal set. The return value is the number of + // iterations used by the algorithm. + + int operator()(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + bool aggressive, int sortType, std::array& eval, + std::array, 3>& evec) const; + +private: + // Update Q = Q*G in-place using G = {{c,0,-s},{s,0,c},{0,0,1}}. + void Update0(Real Q[3][3], Real c, Real s) const; + + // Update Q = Q*G in-place using G = {{0,1,0},{c,0,s},{-s,0,c}}. + void Update1(Real Q[3][3], Real c, Real s) const; + + // Update Q = Q*H in-place using H = {{c,s,0},{s,-c,0},{0,0,1}}. + void Update2(Real Q[3][3], Real c, Real s) const; + + // Update Q = Q*H in-place using H = {{1,0,0},{0,c,s},{0,s,-c}}. + void Update3(Real Q[3][3], Real c, Real s) const; + + // Normalize (u,v) robustly, avoiding floating-point overflow in the sqrt + // call. The normalized pair is (cs,sn) with cs <= 0. If (u,v) = (0,0), + // the function returns (cs,sn) = (-1,0). When used to generate a + // Householder reflection, it does not matter whether (cs,sn) or (-cs,-sn) + // is used. When generating a Givens reflection, cs = cos(2*theta) and + // sn = sin(2*theta). Having a negative cosine for the double-angle + // term ensures that the single-angle terms c = cos(theta) and + // s = sin(theta) satisfy |c| <= |s|. + void GetCosSin(Real u, Real v, Real& cs, Real& sn) const; + + // The convergence test. When 'aggressive' is 'true', the superdiagonal + // test is "bSuper == 0". When 'aggressive' is 'false', the superdiagonal + // test is "|bDiag0| + |bDiag1| + |bSuper| == |bDiag0| + |bDiag1|, which + // means bSuper is effectively zero compared to the sizes of the diagonal + // entries. + bool Converged(bool aggressive, Real bDiag0, Real bDiag1, + Real bSuper) const; + + // Support for sorting the eigenvalues and eigenvectors. The output + // (i0,i1,i2) is a permutation of (0,1,2) so that d[i0] <= d[i1] <= d[i2]. + // The 'bool' return indicates whether the permutation is odd. If it is + // not, the handedness of the Q matrix must be adjusted. + bool Sort(std::array const& d, int& i0, int& i1, int& i2) const; +}; + + +template +class NISymmetricEigensolver3x3 +{ +public: + // The input matrix must be symmetric, so only the unique elements must + // be specified: a00, a01, a02, a11, a12, and a22. The eigenvalues are + // sorted in ascending order: eval0 <= eval1 <= eval2. + + void operator()(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + std::array& eval, std::array, 3>& evec) const; + +private: + static std::array Multiply(Real s, std::array const& U); + static std::array Subtract(std::array const& U, std::array const& V); + static std::array Divide(std::array const& U, Real s); + static Real Dot(std::array const& U, std::array const& V); + static std::array Cross(std::array const& U, std::array const& V); + + void ComputeOrthogonalComplement(std::array const& W, + std::array& U, std::array& V) const; + + void ComputeEigenvector0(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + Real eval0, std::array& evec0) const; + + void ComputeEigenvector1(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + std::array const& evec0, Real eval1, std::array& evec1) const; +}; + + +template +int SymmetricEigensolver3x3::operator()(Real a00, Real a01, Real a02, + Real a11, Real a12, Real a22, bool aggressive, int sortType, + std::array& eval, std::array, 3>& evec) const +{ + // Compute the Householder reflection H and B = H*A*H, where b02 = 0. + Real const zero = (Real)0, one = (Real)1, half = (Real)0.5; + bool isRotation = false; + Real c, s; + GetCosSin(a12, -a02, c, s); + Real Q[3][3] = { { c, s, zero }, { s, -c, zero }, { zero, zero, one } }; + Real term0 = c * a00 + s * a01; + Real term1 = c * a01 + s * a11; + Real b00 = c * term0 + s * term1; + Real b01 = s * term0 - c * term1; + term0 = s * a00 - c * a01; + term1 = s * a01 - c * a11; + Real b11 = s * term0 - c * term1; + Real b12 = s * a02 - c * a12; + Real b22 = a22; + + // Givens reflections, B' = G^T*B*G, preserve tridiagonal matrices. + int const maxIteration = 2 * (1 + std::numeric_limits::digits - + std::numeric_limits::min_exponent); + int iteration; + Real c2, s2; + + if (std::abs(b12) <= std::abs(b01)) + { + Real saveB00, saveB01, saveB11; + for (iteration = 0; iteration < maxIteration; ++iteration) + { + // Compute the Givens reflection. + GetCosSin(half * (b00 - b11), b01, c2, s2); + s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + c = half * s2 / s; + + // Update Q by the Givens reflection. + Update0(Q, c, s); + isRotation = !isRotation; + + // Update B <- Q^T*B*Q, ensuring that b02 is zero and |b12| has + // strictly decreased. + saveB00 = b00; + saveB01 = b01; + saveB11 = b11; + term0 = c * saveB00 + s * saveB01; + term1 = c * saveB01 + s * saveB11; + b00 = c * term0 + s * term1; + b11 = b22; + term0 = c * saveB01 - s * saveB00; + term1 = c * saveB11 - s * saveB01; + b22 = c * term1 - s * term0; + b01 = s * b12; + b12 = c * b12; + + if (Converged(aggressive, b00, b11, b01)) + { + // Compute the Householder reflection. + GetCosSin(half * (b00 - b11), b01, c2, s2); + s = std::sqrt(half * (one - c2)); + c = half * s2 / s; // >= 1/sqrt(2) + + // Update Q by the Householder reflection. + Update2(Q, c, s); + isRotation = !isRotation; + + // Update D = Q^T*B*Q. + saveB00 = b00; + saveB01 = b01; + saveB11 = b11; + term0 = c * saveB00 + s * saveB01; + term1 = c * saveB01 + s * saveB11; + b00 = c * term0 + s * term1; + term0 = s * saveB00 - c * saveB01; + term1 = s * saveB01 - c * saveB11; + b11 = s * term0 - c * term1; + break; + } + } + } + else + { + Real saveB11, saveB12, saveB22; + for (iteration = 0; iteration < maxIteration; ++iteration) + { + // Compute the Givens reflection. + GetCosSin(half * (b22 - b11), b12, c2, s2); + s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + c = half * s2 / s; + + // Update Q by the Givens reflection. + Update1(Q, c, s); + isRotation = !isRotation; + + // Update B <- Q^T*B*Q, ensuring that b02 is zero and |b12| has + // strictly decreased. MODIFY... + saveB11 = b11; + saveB12 = b12; + saveB22 = b22; + term0 = c * saveB22 + s * saveB12; + term1 = c * saveB12 + s * saveB11; + b22 = c * term0 + s * term1; + b11 = b00; + term0 = c * saveB12 - s * saveB22; + term1 = c * saveB11 - s * saveB12; + b00 = c * term1 - s * term0; + b12 = s * b01; + b01 = c * b01; + + if (Converged(aggressive, b11, b22, b12)) + { + // Compute the Householder reflection. + GetCosSin(half * (b11 - b22), b12, c2, s2); + s = std::sqrt(half * (one - c2)); + c = half * s2 / s; // >= 1/sqrt(2) + + // Update Q by the Householder reflection. + Update3(Q, c, s); + isRotation = !isRotation; + + // Update D = Q^T*B*Q. + saveB11 = b11; + saveB12 = b12; + saveB22 = b22; + term0 = c * saveB11 + s * saveB12; + term1 = c * saveB12 + s * saveB22; + b11 = c * term0 + s * term1; + term0 = s * saveB11 - c * saveB12; + term1 = s * saveB12 - c * saveB22; + b22 = s * term0 - c * term1; + break; + } + } + } + + std::array diagonal = { b00, b11, b22 }; + int i0, i1, i2; + if (sortType >= 1) + { + // diagonal[i0] <= diagonal[i1] <= diagonal[i2] + bool isOdd = Sort(diagonal, i0, i1, i2); + if (!isOdd) + { + isRotation = !isRotation; + } + } + else if (sortType <= -1) + { + // diagonal[i0] >= diagonal[i1] >= diagonal[i2] + bool isOdd = Sort(diagonal, i0, i1, i2); + std::swap(i0, i2); // (i0,i1,i2)->(i2,i1,i0) is odd + if (isOdd) + { + isRotation = !isRotation; + } + } + else + { + i0 = 0; + i1 = 1; + i2 = 2; + } + + eval[0] = diagonal[i0]; + eval[1] = diagonal[i1]; + eval[2] = diagonal[i2]; + evec[0][0] = Q[0][i0]; + evec[0][1] = Q[1][i0]; + evec[0][2] = Q[2][i0]; + evec[1][0] = Q[0][i1]; + evec[1][1] = Q[1][i1]; + evec[1][2] = Q[2][i1]; + evec[2][0] = Q[0][i2]; + evec[2][1] = Q[1][i2]; + evec[2][2] = Q[2][i2]; + + // Ensure the columns of Q form a right-handed set. + if (!isRotation) + { + for (int j = 0; j < 3; ++j) + { + evec[2][j] = -evec[2][j]; + } + } + + return iteration; +} + +template +void SymmetricEigensolver3x3::Update0(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][0] + s * Q[r][1]; + Real tmp1 = Q[r][2]; + Real tmp2 = c * Q[r][1] - s * Q[r][0]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + Q[r][2] = tmp2; + } +} + +template +void SymmetricEigensolver3x3::Update1(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][1] - s * Q[r][2]; + Real tmp1 = Q[r][0]; + Real tmp2 = c * Q[r][2] + s * Q[r][1]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + Q[r][2] = tmp2; + } +} + +template +void SymmetricEigensolver3x3::Update2(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][0] + s * Q[r][1]; + Real tmp1 = s * Q[r][0] - c * Q[r][1]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + } +} + +template +void SymmetricEigensolver3x3::Update3(Real Q[3][3], Real c, Real s) +const +{ + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][1] + s * Q[r][2]; + Real tmp1 = s * Q[r][1] - c * Q[r][2]; + Q[r][1] = tmp0; + Q[r][2] = tmp1; + } +} + +template +void SymmetricEigensolver3x3::GetCosSin(Real u, Real v, Real& cs, + Real& sn) const +{ + Real maxAbsComp = std::max(std::abs(u), std::abs(v)); + if (maxAbsComp > (Real)0) + { + u /= maxAbsComp; // in [-1,1] + v /= maxAbsComp; // in [-1,1] + Real length = std::sqrt(u * u + v * v); + cs = u / length; + sn = v / length; + if (cs > (Real)0) + { + cs = -cs; + sn = -sn; + } + } + else + { + cs = (Real)-1; + sn = (Real)0; + } +} + +template +bool SymmetricEigensolver3x3::Converged(bool aggressive, Real bDiag0, + Real bDiag1, Real bSuper) const +{ + if (aggressive) + { + return bSuper == (Real)0; + } + else + { + Real sum = std::abs(bDiag0) + std::abs(bDiag1); + return sum + std::abs(bSuper) == sum; + } +} + +template +bool SymmetricEigensolver3x3::Sort(std::array const& d, + int& i0, int& i1, int& i2) const +{ + bool odd; + if (d[0] < d[1]) + { + if (d[2] < d[0]) + { + i0 = 2; i1 = 0; i2 = 1; odd = true; + } + else if (d[2] < d[1]) + { + i0 = 0; i1 = 2; i2 = 1; odd = false; + } + else + { + i0 = 0; i1 = 1; i2 = 2; odd = true; + } + } + else + { + if (d[2] < d[1]) + { + i0 = 2; i1 = 1; i2 = 0; odd = false; + } + else if (d[2] < d[0]) + { + i0 = 1; i1 = 2; i2 = 0; odd = true; + } + else + { + i0 = 1; i1 = 0; i2 = 2; odd = false; + } + } + return odd; +} + + +template +void NISymmetricEigensolver3x3::operator() (Real a00, Real a01, Real a02, + Real a11, Real a12, Real a22, std::array& eval, + std::array, 3>& evec) const +{ + // Precondition the matrix by factoring out the maximum absolute value + // of the components. This guards against floating-point overflow when + // computing the eigenvalues. + Real max0 = std::max(std::abs(a00), std::abs(a01)); + Real max1 = std::max(std::abs(a02), std::abs(a11)); + Real max2 = std::max(std::abs(a12), std::abs(a22)); + Real maxAbsElement = std::max(std::max(max0, max1), max2); + if (maxAbsElement == (Real)0) + { + // A is the zero matrix. + eval[0] = (Real)0; + eval[1] = (Real)0; + eval[2] = (Real)0; + evec[0] = { (Real)1, (Real)0, (Real)0 }; + evec[1] = { (Real)0, (Real)1, (Real)0 }; + evec[2] = { (Real)0, (Real)0, (Real)1 }; + return; + } + + Real invMaxAbsElement = (Real)1 / maxAbsElement; + a00 *= invMaxAbsElement; + a01 *= invMaxAbsElement; + a02 *= invMaxAbsElement; + a11 *= invMaxAbsElement; + a12 *= invMaxAbsElement; + a22 *= invMaxAbsElement; + + Real norm = a01 * a01 + a02 * a02 + a12 * a12; + if (norm > (Real)0) + { + // Compute the eigenvalues of A. + + // In the PDF mentioned previously, B = (A - q*I)/p, where q = tr(A)/3 + // with tr(A) the trace of A (sum of the diagonal entries of A) and where + // p = sqrt(tr((A - q*I)^2)/6). + Real q = (a00 + a11 + a22) / (Real)3; + + // The matrix A - q*I is represented by the following, where b00, b11 and + // b22 are computed after these comments, + // +- -+ + // | b00 a01 a02 | + // | a01 b11 a12 | + // | a02 a12 b22 | + // +- -+ + Real b00 = a00 - q; + Real b11 = a11 - q; + Real b22 = a22 - q; + + // The is the variable p mentioned in the PDF. + Real p = std::sqrt((b00 * b00 + b11 * b11 + b22 * b22 + norm * (Real)2) / (Real)6); + + // We need det(B) = det((A - q*I)/p) = det(A - q*I)/p^3. The value + // det(A - q*I) is computed using a cofactor expansion by the first + // row of A - q*I. The cofactors are c00, c01 and c02 and the + // determinant is b00*c00 - a01*c01 + a02*c02. The det(B) is then + // computed finally by the division with p^3. + Real c00 = b11 * b22 - a12 * a12; + Real c01 = a01 * b22 - a12 * a02; + Real c02 = a01 * a12 - b11 * a02; + Real det = (b00 * c00 - a01 * c01 + a02 * c02) / (p * p * p); + + // The halfDet value is cos(3*theta) mentioned in the PDF. The acos(z) + // function requires |z| <= 1, but will fail silently and return NaN + // if the input is larger than 1 in magnitude. To avoid this problem + // due to rounding errors, the halfDet/ value is clamped to [-1,1]. + Real halfDet = det * (Real)0.5; + halfDet = std::min(std::max(halfDet, (Real)-1), (Real)1); + + // The eigenvalues of B are ordered as beta0 <= beta1 <= beta2. The + // number of digits in twoThirdsPi is chosen so that, whether float or + // double, the floating-point number is the closest to theoretical 2*pi/3. + Real angle = std::acos(halfDet) / (Real)3; + Real const twoThirdsPi = (Real)2.09439510239319549; + Real beta2 = std::cos(angle) * (Real)2; + Real beta0 = std::cos(angle + twoThirdsPi) * (Real)2; + Real beta1 = -(beta0 + beta2); + + // The eigenvalues of A are ordered as alpha0 <= alpha1 <= alpha2. + eval[0] = q + p * beta0; + eval[1] = q + p * beta1; + eval[2] = q + p * beta2; + + // Compute the eigenvectors so that the set {evec[0], evec[1], evec[2]} + // is right handed and orthonormal. + if (halfDet >= (Real)0) + { + ComputeEigenvector0(a00, a01, a02, a11, a12, a22, eval[2], evec[2]); + ComputeEigenvector1(a00, a01, a02, a11, a12, a22, evec[2], eval[1], evec[1]); + evec[0] = Cross(evec[1], evec[2]); + } + else + { + ComputeEigenvector0(a00, a01, a02, a11, a12, a22, eval[0], evec[0]); + ComputeEigenvector1(a00, a01, a02, a11, a12, a22, evec[0], eval[1], evec[1]); + evec[2] = Cross(evec[0], evec[1]); + } + } + else + { + // The matrix is diagonal. + eval[0] = a00; + eval[1] = a11; + eval[2] = a22; + evec[0] = { (Real)1, (Real)0, (Real)0 }; + evec[1] = { (Real)0, (Real)1, (Real)0 }; + evec[2] = { (Real)0, (Real)0, (Real)1 }; + } + + // The preconditioning scaled the matrix A, which scales the eigenvalues. + // Revert the scaling. + eval[0] *= maxAbsElement; + eval[1] *= maxAbsElement; + eval[2] *= maxAbsElement; +} + +template +std::array NISymmetricEigensolver3x3::Multiply( + Real s, std::array const& U) +{ + std::array product = { s * U[0], s * U[1], s * U[2] }; + return product; +} + +template +std::array NISymmetricEigensolver3x3::Subtract( + std::array const& U, std::array const& V) +{ + std::array difference = { U[0] - V[0], U[1] - V[1], U[2] - V[2] }; + return difference; +} + +template +std::array NISymmetricEigensolver3x3::Divide( + std::array const& U, Real s) +{ + Real invS = (Real)1 / s; + std::array division = { U[0] * invS, U[1] * invS, U[2] * invS }; + return division; +} + +template +Real NISymmetricEigensolver3x3::Dot(std::array const& U, + std::array const& V) +{ + Real dot = U[0] * V[0] + U[1] * V[1] + U[2] * V[2]; + return dot; +} + +template +std::array NISymmetricEigensolver3x3::Cross(std::array const& U, + std::array const& V) +{ + std::array cross = + { + U[1] * V[2] - U[2] * V[1], + U[2] * V[0] - U[0] * V[2], + U[0] * V[1] - U[1] * V[0] + }; + return cross; +} + +template +void NISymmetricEigensolver3x3::ComputeOrthogonalComplement( + std::array const& W, std::array& U, std::array& V) const +{ + // Robustly compute a right-handed orthonormal set { U, V, W }. The + // vector W is guaranteed to be unit-length, in which case there is no + // need to worry about a division by zero when computing invLength. + Real invLength; + if (std::abs(W[0]) > std::abs(W[1])) + { + // The component of maximum absolute value is either W[0] or W[2]. + invLength = (Real)1 / std::sqrt(W[0] * W[0] + W[2] * W[2]); + U = { -W[2] * invLength, (Real)0, +W[0] * invLength }; + } + else + { + // The component of maximum absolute value is either W[1] or W[2]. + invLength = (Real)1 / std::sqrt(W[1] * W[1] + W[2] * W[2]); + U = { (Real)0, +W[2] * invLength, -W[1] * invLength }; + } + V = Cross(W, U); +} + +template +void NISymmetricEigensolver3x3::ComputeEigenvector0(Real a00, Real a01, + Real a02, Real a11, Real a12, Real a22, Real eval0, std::array& evec0) const +{ + // Compute a unit-length eigenvector for eigenvalue[i0]. The matrix is + // rank 2, so two of the rows are linearly independent. For a robust + // computation of the eigenvector, select the two rows whose cross product + // has largest length of all pairs of rows. + std::array row0 = { a00 - eval0, a01, a02 }; + std::array row1 = { a01, a11 - eval0, a12 }; + std::array row2 = { a02, a12, a22 - eval0 }; + std::array r0xr1 = Cross(row0, row1); + std::array r0xr2 = Cross(row0, row2); + std::array r1xr2 = Cross(row1, row2); + Real d0 = Dot(r0xr1, r0xr1); + Real d1 = Dot(r0xr2, r0xr2); + Real d2 = Dot(r1xr2, r1xr2); + + Real dmax = d0; + int imax = 0; + if (d1 > dmax) + { + dmax = d1; + imax = 1; + } + if (d2 > dmax) + { + imax = 2; + } + + if (imax == 0) + { + evec0 = Divide(r0xr1, std::sqrt(d0)); + } + else if (imax == 1) + { + evec0 = Divide(r0xr2, std::sqrt(d1)); + } + else + { + evec0 = Divide(r1xr2, std::sqrt(d2)); + } +} + +template +void NISymmetricEigensolver3x3::ComputeEigenvector1(Real a00, Real a01, + Real a02, Real a11, Real a12, Real a22, std::array const& evec0, + Real eval1, std::array& evec1) const +{ + // Robustly compute a right-handed orthonormal set { U, V, evec0 }. + std::array U, V; + ComputeOrthogonalComplement(evec0, U, V); + + // Let e be eval1 and let E be a corresponding eigenvector which is a + // solution to the linear system (A - e*I)*E = 0. The matrix (A - e*I) + // is 3x3, not invertible (so infinitely many solutions), and has rank 2 + // when eval1 and eval are different. It has rank 1 when eval1 and eval2 + // are equal. Numerically, it is difficult to compute robustly the rank + // of a matrix. Instead, the 3x3 linear system is reduced to a 2x2 system + // as follows. Define the 3x2 matrix J = [U V] whose columns are the U + // and V computed previously. Define the 2x1 vector X = J*E. The 2x2 + // system is 0 = M * X = (J^T * (A - e*I) * J) * X where J^T is the + // transpose of J and M = J^T * (A - e*I) * J is a 2x2 matrix. The system + // may be written as + // +- -++- -+ +- -+ + // | U^T*A*U - e U^T*A*V || x0 | = e * | x0 | + // | V^T*A*U V^T*A*V - e || x1 | | x1 | + // +- -++ -+ +- -+ + // where X has row entries x0 and x1. + + std::array AU = + { + a00 * U[0] + a01 * U[1] + a02 * U[2], + a01 * U[0] + a11 * U[1] + a12 * U[2], + a02 * U[0] + a12 * U[1] + a22 * U[2] + }; + + std::array AV = + { + a00 * V[0] + a01 * V[1] + a02 * V[2], + a01 * V[0] + a11 * V[1] + a12 * V[2], + a02 * V[0] + a12 * V[1] + a22 * V[2] + }; + + Real m00 = U[0] * AU[0] + U[1] * AU[1] + U[2] * AU[2] - eval1; + Real m01 = U[0] * AV[0] + U[1] * AV[1] + U[2] * AV[2]; + Real m11 = V[0] * AV[0] + V[1] * AV[1] + V[2] * AV[2] - eval1; + + // For robustness, choose the largest-length row of M to compute the + // eigenvector. The 2-tuple of coefficients of U and V in the + // assignments to eigenvector[1] lies on a circle, and U and V are + // unit length and perpendicular, so eigenvector[1] is unit length + // (within numerical tolerance). + Real absM00 = std::abs(m00); + Real absM01 = std::abs(m01); + Real absM11 = std::abs(m11); + Real maxAbsComp; + if (absM00 >= absM11) + { + maxAbsComp = std::max(absM00, absM01); + if (maxAbsComp > (Real)0) + { + if (absM00 >= absM01) + { + m01 /= m00; + m00 = (Real)1 / std::sqrt((Real)1 + m01 * m01); + m01 *= m00; + } + else + { + m00 /= m01; + m01 = (Real)1 / std::sqrt((Real)1 + m00 * m00); + m00 *= m01; + } + evec1 = Subtract(Multiply(m01, U), Multiply(m00, V)); + } + else + { + evec1 = U; + } + } + else + { + maxAbsComp = std::max(absM11, absM01); + if (maxAbsComp > (Real)0) + { + if (absM11 >= absM01) + { + m01 /= m11; + m11 = (Real)1 / std::sqrt((Real)1 + m01 * m01); + m01 *= m11; + } + else + { + m11 /= m01; + m01 = (Real)1 / std::sqrt((Real)1 + m11 * m11); + m11 *= m01; + } + evec1 = Subtract(Multiply(m11, U), Multiply(m01, V)); + } + else + { + evec1 = U; + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTCBSplineCurve.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTCBSplineCurve.h new file mode 100644 index 000000000000..73d3470dea59 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTCBSplineCurve.h @@ -0,0 +1,249 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +namespace gte +{ + +template +class TCBSplineCurve : public ParametricCurve +{ +public: + // Construction and destruction. The object copies the input arrays. + // The number of points must be at least 2. To validate construction, + // create an object as shown: + // TCBSplineCurve curve(parameters); + // if (!curve) { ; } + virtual ~TCBSplineCurve(); + TCBSplineCurve(int numPoints, Vector const* points, + Real const* times, Real const* tension, Real const* continuity, + Real const* bias); + + // Member access. + inline int GetNumPoints() const; + inline Vector const* GetPoints() const; + inline Real const* GetTensions() const; + inline Real const* GetContinuities() const; + inline Real const* GetBiases() const; + + // Evaluation of the curve. The function supports derivative calculation + // through order 3; that is, maxOrder <= 3 is required. If you want + // only the position, pass in maxOrder of 0. If you want the position and + // first derivative, pass in maxOrder of 1, and so on. The output + // 'values' are ordered as: position, first derivative, second derivative, + // third derivative. + virtual void Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const; + +protected: + // Support for construction. + void ComputePoly(int i0, int i1, int i2, int i3); + + // Determine the index i for which times[i] <= t < times[i+1]. + void GetKeyInfo(Real t, int& key, Real& dt) const; + + std::vector> mPoints; + std::vector mTension, mContinuity, mBias; + + // Polynomial coefficients. mA are the degree 0 coefficients, mB are + // the degree 1 coefficients, mC are the degree 2 coefficients, and mD + // are the degree 3 coefficients. + std::vector> mA, mB, mC, mD; +}; + + +template +TCBSplineCurve::~TCBSplineCurve() +{ +} + +template +TCBSplineCurve::TCBSplineCurve(int numPoints, + Vector const* points, Real const* times, Real const* tension, + Real const* continuity, Real const* bias) + : + ParametricCurve(numPoints - 1, times) +{ + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + mPoints.resize(numPoints); + mTension.resize(numPoints); + mContinuity.resize(numPoints); + mBias.resize(numPoints); + std::copy(points, points + numPoints, mPoints.begin()); + std::copy(tension, tension + numPoints, mTension.begin()); + std::copy(continuity, continuity + numPoints, mContinuity.begin()); + std::copy(bias, bias + numPoints, mBias.begin()); + + int numSegments = numPoints - 1; + mA.resize(numSegments); + mB.resize(numSegments); + mC.resize(numSegments); + mD.resize(numSegments); + + // For now, treat the first point as if it occurred twice. + ComputePoly(0, 0, 1, 2); + + for (int i = 1; i < numSegments - 1; ++i) + { + ComputePoly(i - 1, i, i + 1, i + 2); + } + + // For now, treat the last point as if it occurred twice. + ComputePoly(numSegments - 2, numSegments - 1, numSegments, numSegments); + + this->mConstructed = true; +} + +template inline +int TCBSplineCurve::GetNumPoints() const +{ + return static_cast(mPoints.size()); +} + +template inline +Vector const* TCBSplineCurve::GetPoints() const +{ + return &mPoints[0]; +} + +template inline +Real const* TCBSplineCurve::GetTensions() const +{ + return &mTension[0]; +} + +template inline +Real const* TCBSplineCurve::GetContinuities() const +{ + return &mContinuity[0]; +} + +template inline +Real const* TCBSplineCurve::GetBiases() const +{ + return &mBias[0]; +} + +template +void TCBSplineCurve::Evaluate(Real t, unsigned int maxOrder, + Vector values[4]) const +{ + if (!this->mConstructed) + { + // Errors were already generated during construction. + for (unsigned int order = 0; order < 4; ++order) + { + values[order].MakeZero(); + } + return; + } + + int key; + Real dt; + GetKeyInfo(t, key, dt); + dt /= (this->mTime[key + 1] - this->mTime[key]); + + // Compute position. + values[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key])); + if (maxOrder >= 1) + { + // Compute first derivative. + values[1] = mB[key] + dt * (mC[key] * ((Real)2) + mD[key] * + (((Real)3) * dt)); + if (maxOrder >= 2) + { + // Compute second derivative. + values[2] = mC[key] * ((Real)2) + mD[key] * (((Real)6) * dt); + if (maxOrder == 3) + { + values[3] = ((Real)6) * mD[key]; + } + else + { + values[3].MakeZero(); + } + } + } +} + +template +void TCBSplineCurve::ComputePoly(int i0, int i1, int i2, int i3) +{ + Vector diff = mPoints[i2] - mPoints[i1]; + Real dt = this->mTime[i2] - this->mTime[i1]; + + // Build multipliers at P1. + Real oneMinusT0 = (Real)1 - mTension[i1]; + Real oneMinusC0 = (Real)1 - mContinuity[i1]; + Real onePlusC0 = (Real)1 + mContinuity[i1]; + Real oneMinusB0 = (Real)1 - mBias[i1]; + Real onePlusB0 = (Real)1 + mBias[i1]; + Real adj0 = ((Real)2)*dt / (this->mTime[i2] - this->mTime[i0]); + Real out0 = ((Real)0.5)*adj0*oneMinusT0*onePlusC0*onePlusB0; + Real out1 = ((Real)0.5)*adj0*oneMinusT0*oneMinusC0*oneMinusB0; + + // Build outgoing tangent at P1. + Vector tOut = out1*diff + out0*(mPoints[i1] - mPoints[i0]); + + // Build multipliers at point P2. + Real oneMinusT1 = (Real)1 - mTension[i2]; + Real oneMinusC1 = (Real)1 - mContinuity[i2]; + Real onePlusC1 = (Real)1 + mContinuity[i2]; + Real oneMinusB1 = (Real)1 - mBias[i2]; + Real onePlusB1 = (Real)1 + mBias[i2]; + Real adj1 = ((Real)2)*dt / (this->mTime[i3] - this->mTime[i1]); + Real in0 = ((Real)0.5)*adj1*oneMinusT1*oneMinusC1*onePlusB1; + Real in1 = ((Real)0.5)*adj1*oneMinusT1*onePlusC1*oneMinusB1; + + // Build incoming tangent at P2. + Vector tIn = in1*(mPoints[i3] - mPoints[i2]) + in0*diff; + + mA[i1] = mPoints[i1]; + mB[i1] = tOut; + mC[i1] = ((Real)3)*diff - ((Real)2)*tOut - tIn; + mD[i1] = ((Real)-2)*diff + tOut + tIn; +} + +template +void TCBSplineCurve::GetKeyInfo(Real t, int& key, Real& dt) const +{ + int numSegments = static_cast(mA.size()); + if (t <= this->mTime[0]) + { + key = 0; + dt = (Real)0; + } + else if (t >= this->mTime[numSegments]) + { + key = numSegments - 1; + dt = this->mTime[numSegments] - this->mTime[numSegments - 1]; + } + else + { + for (int i = 0; i < numSegments; ++i) + { + if (t < this->mTime[i + 1]) + { + key = i; + dt = t - this->mTime[i]; + break; + } + } + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTIQuery.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTIQuery.h new file mode 100644 index 000000000000..c7afe55630a5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTIQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include + +namespace gte +{ + +// Test-intersection queries. + +template +class TIQuery +{ +public: + struct Result + { + // A TIQuery-base class B must define a B::Result struct with member + // 'bool intersect'. A TIQuery-derived class D must also derive a + // D::Result from B:Result but may have no members. The member + // 'intersect' is 'true' iff the primitives intersect. The operator() + // is non-const to allow TIQuery to store and modify private state + // that supports the query. + }; + Result operator()(Type0 const& primitive0, Type1 const& primitive1); +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.cpp new file mode 100644 index 000000000000..6118f12545ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.cpp @@ -0,0 +1,255 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include + +using namespace gte; + +TSManifoldMesh::~TSManifoldMesh() +{ +} + +TSManifoldMesh::TSManifoldMesh(TCreator tCreator, SCreator sCreator) + : + mTCreator(tCreator ? tCreator : CreateTriangle), + mSCreator(sCreator ? sCreator : CreateTetrahedron), + mAssertOnNonmanifoldInsertion(true) +{ +} + +TSManifoldMesh::TSManifoldMesh(TSManifoldMesh const& mesh) +{ + *this = mesh; +} + +TSManifoldMesh& TSManifoldMesh::operator=(TSManifoldMesh const& mesh) +{ + Clear(); + + mTCreator = mesh.mTCreator; + mSCreator = mesh.mSCreator; + mAssertOnNonmanifoldInsertion = mesh.mAssertOnNonmanifoldInsertion; + for (auto const& element : mesh.mSMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2], element.first.V[3]); + } + + return *this; +} + +TSManifoldMesh::TMap const& TSManifoldMesh::GetTriangles() const +{ + return mTMap; +} + +TSManifoldMesh::SMap const& TSManifoldMesh::GetTetrahedra() const +{ + return mSMap; +} + +std::shared_ptr TSManifoldMesh::CreateTriangle(int v0, int v1, int v2) +{ + return std::make_shared(v0, v1, v2); +} + +std::shared_ptr TSManifoldMesh::CreateTetrahedron(int v0, int v1, int v2, int v3) +{ + return std::make_shared(v0, v1, v2, v3); +} + +void TSManifoldMesh::AssertOnNonmanifoldInsertion(bool doAssert) +{ + mAssertOnNonmanifoldInsertion = doAssert; +} + +std::shared_ptr TSManifoldMesh::Insert(int v0, int v1, int v2, int v3) +{ + TetrahedronKey skey(v0, v1, v2, v3); + if (mSMap.find(skey) != mSMap.end()) + { + // The tetrahedron already exists. Return a null pointer as a signal + // to the caller that the insertion failed. + return nullptr; + } + + // Add the new tetrahedron. + std::shared_ptr tetra = mSCreator(v0, v1, v2, v3); + mSMap[skey] = tetra; + + // Add the faces to the mesh if they do not already exist. + for (int i = 0; i < 4; ++i) + { + auto opposite = TetrahedronKey::oppositeFace[i]; + TriangleKey tkey(tetra->V[opposite[0]], tetra->V[opposite[1]], tetra->V[opposite[2]]); + std::shared_ptr face; + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // This is the first time the face is encountered. + face = mTCreator(tetra->V[opposite[0]], tetra->V[opposite[1]], tetra->V[opposite[2]]); + mTMap[tkey] = face; + + // Update the face and tetrahedron. + face->T[0] = tetra; + tetra->T[i] = face; + } + else + { + // This is the second time the face is encountered. + face = titer->second; + if (!face) + { + LogError("Unexpected condition."); + return nullptr; + } + + // Update the face. + if (face->T[1].lock()) + { + if (mAssertOnNonmanifoldInsertion) + { + LogInformation("The mesh must be manifold."); + } + return nullptr; + } + face->T[1] = tetra; + + // Update the adjacent tetrahedra. + auto adjacent = face->T[0].lock(); + if (!adjacent) + { + LogError("Unexpected condition."); + return nullptr; + } + for (int j = 0; j < 4; ++j) + { + if (adjacent->T[j].lock() == face) + { + adjacent->S[j] = tetra; + break; + } + } + + // Update the tetrahedron. + tetra->T[i] = face; + tetra->S[i] = adjacent; + } + } + + return tetra; +} + +bool TSManifoldMesh::Remove(int v0, int v1, int v2, int v3) +{ + TetrahedronKey skey(v0, v1, v2, v3); + auto siter = mSMap.find(skey); + if (siter == mSMap.end()) + { + // The tetrahedron does not exist. + return false; + } + + // Get the tetrahedron. + std::shared_ptr tetra = siter->second; + + // Remove the faces and update adjacent tetrahedra if necessary. + for (int i = 0; i < 4; ++i) + { + // Inform the faces the tetrahedron is being deleted. + auto face = tetra->T[i].lock(); + if (!face) + { + // The triangle edge should be nonnull. + LogError("Unexpected condition."); + return false; + } + + if (face->T[0].lock() == tetra) + { + // One-tetrahedron faces always have pointer at index zero. + face->T[0] = face->T[1]; + face->T[1].reset(); + } + else if (face->T[1].lock() == tetra) + { + face->T[1].reset(); + } + else + { + LogError("Unexpected condition."); + return false; + } + + // Remove the face if you have the last reference to it. + if (!face->T[0].lock() && !face->T[1].lock()) + { + TriangleKey tkey(face->V[0], face->V[1], face->V[2]); + mTMap.erase(tkey); + } + + // Inform adjacent tetrahedra the tetrahedron is being deleted. + auto adjacent = tetra->S[i].lock(); + if (adjacent) + { + for (int j = 0; j < 4; ++j) + { + if (adjacent->S[j].lock() == tetra) + { + adjacent->S[j].reset(); + break; + } + } + } + } + + mSMap.erase(skey); + return true; +} + +void TSManifoldMesh::Clear() +{ + mTMap.clear(); + mSMap.clear(); +} + +bool TSManifoldMesh::IsClosed() const +{ + for (auto const& element : mSMap) + { + auto tri = element.second; + if (!tri->S[0].lock() || !tri->S[1].lock()) + { + return false; + } + } + return true; +} + +TSManifoldMesh::Triangle::~Triangle() +{ +} + +TSManifoldMesh::Triangle::Triangle(int v0, int v1, int v2) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; +} + +TSManifoldMesh::Tetrahedron::~Tetrahedron() +{ +} + +TSManifoldMesh::Tetrahedron::Tetrahedron(int v0, int v1, int v2, int v3) +{ + V[0] = v0; + V[1] = v1; + V[2] = v2; + V[3] = v3; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.h new file mode 100644 index 000000000000..01a2ad4a66e3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTSManifoldMesh.h @@ -0,0 +1,120 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP TSManifoldMesh +{ +public: + // Triangle data types. + class Triangle; + typedef std::shared_ptr (*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Tetrahedron data types. + class Tetrahedron; + typedef std::shared_ptr (*SCreator)(int, int, int, int); + typedef std::map, std::shared_ptr> SMap; + + // Triangle object. + class GTE_IMPEXP Triangle + { + public: + virtual ~Triangle(); + Triangle(int v0, int v1, int v2); + + // Vertices of the face. + int V[3]; + + // Tetrahedra sharing the face. + std::weak_ptr T[2]; + }; + + // Tetrahedron object. + class GTE_IMPEXP Tetrahedron + { + public: + virtual ~Tetrahedron(); + Tetrahedron(int v0, int v1, int v2, int v3); + + // Vertices, listed in an order so that each face vertices in + // counterclockwise order when viewed from outside the tetrahedron. + int V[4]; + + // Adjacent faces. T[i] points to the triangle face opposite V[i]. + // T[0] points to face (V[1],V[2],V[3]) + // T[1] points to face (V[0],V[3],V[2]) + // T[2] points to face (V[0],V[1],V[3]) + // T[3] points to face (V[0],V[2],V[1]) + std::weak_ptr T[4]; + + // Adjacent tetrahedra. S[i] points to the adjacent tetrahedron + // sharing face T[i]. + std::weak_ptr S[4]; + }; + + + // Construction and destruction. + virtual ~TSManifoldMesh(); + TSManifoldMesh(TCreator tCreator = nullptr, SCreator sCreator = nullptr); + + // Support for a deep copy of the mesh. The mTMap and mSMap objects have + // dynamically allocated memory for triangles and tetrahedra. A shallow + // copy of the pointers to this memory is problematic. Allowing sharing, + // say, via std::shared_ptr, is an option but not really the intent of + // copying the mesh graph. + TSManifoldMesh(TSManifoldMesh const& mesh); + TSManifoldMesh& operator=(TSManifoldMesh const& mesh); + + // Member access. + TMap const& GetTriangles() const; + SMap const& GetTetrahedra() const; + + // If the insertion of a tetrahedron fails because the mesh would become + // nonmanifold, the default behavior is to trigger a LogError message. + // You can disable this behavior in situations where you want the Logger + // system on but you want to continue gracefully without an assertion. + void AssertOnNonmanifoldInsertion(bool doAssert); + + // If is not in the mesh, a Tetrahedron object is created + // and returned; otherwise, is in the mesh and nullptr is + // returned. If the insertion leads to a nonmanifold mesh, the call + // fails with a nullptr returned. + std::shared_ptr Insert(int v0, int v1, int v2, int v3); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + bool Remove(int v0, int v1, int v2, int v3); + + // Destroy the triangles and tetrahedra to obtain an empty mesh. + virtual void Clear(); + + // A manifold mesh is closed if each face is shared twice. + bool IsClosed() const; + +protected: + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2); + TCreator mTCreator; + TMap mTMap; + + // The tetrahedron data and default tetrahedron creation. + static std::shared_ptr CreateTetrahedron(int v0, int v1, int v2, int v3); + SCreator mSCreator; + SMap mSMap; + bool mAssertOnNonmanifoldInsertion; // default: true +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTanEstimate.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTanEstimate.h new file mode 100644 index 000000000000..472d53ba8e5b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTanEstimate.h @@ -0,0 +1,187 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include + +// Minimax polynomial approximations to tan(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(pi/4) = tan(pi/4) = 1. It minimizes the quantity +// maximum{|tan(x) - p(x)| : x in [-pi/4,pi/4]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace gte +{ + +template +class TanEstimate +{ +public: + // The input constraint is x in [-pi/4,pi/4]. For example, + // float x; // in [-pi/4,pi/4] + // float result = TanEstimate::Degree<3>(x); + template + inline static Real Degree(Real x); + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2]. If |y| <= pi/4, then the + // polynomial is evaluated. If y in (pi/4,pi/2), set z = y - pi/4 + // and use the identity + // tan(y) = tan(z + pi/4) = [1 + tan(z)]/[1 - tan(z)] + // Be careful when evaluating at y nearly pi/2, because tan(y) + // becomes infinite. For example, + // float x; // x any real number + // float result = TanEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x); + +private: + // Metaprogramming and private implementation to allow specialization of + // a template member function. + template struct degree {}; + inline static Real Evaluate(degree<3>, Real x); + inline static Real Evaluate(degree<5>, Real x); + inline static Real Evaluate(degree<7>, Real x); + inline static Real Evaluate(degree<9>, Real x); + inline static Real Evaluate(degree<11>, Real x); + inline static Real Evaluate(degree<13>, Real x); + + // Support for range reduction. + inline static void Reduce(Real x, Real& y); +}; + + +template +template +inline Real TanEstimate::Degree(Real x) +{ + return Evaluate(degree(), x); +} + +template +template +inline Real TanEstimate::DegreeRR(Real x) +{ + Real y; + Reduce(x, y); + if (std::abs(y) <= (Real)GTE_C_QUARTER_PI) + { + return Degree(y); + } + else if (y > (Real)GTE_C_QUARTER_PI) + { + Real poly = Degree(y - (Real)GTE_C_QUARTER_PI); + return ((Real)1 + poly) / ((Real)1 - poly); + } + else + { + Real poly = Degree(y + (Real)GTE_C_QUARTER_PI); + return -((Real)1 - poly) / ((Real)1 + poly); + } +} + +template +inline Real TanEstimate::Evaluate(degree<3>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG3_C1; + poly = (Real)GTE_C_TAN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<5>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG5_C2; + poly = (Real)GTE_C_TAN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<7>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG7_C3; + poly = (Real)GTE_C_TAN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<9>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG9_C4; + poly = (Real)GTE_C_TAN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<11>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG11_C5; + poly = (Real)GTE_C_TAN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline Real TanEstimate::Evaluate(degree<13>, Real x) +{ + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG13_C6; + poly = (Real)GTE_C_TAN_DEG13_C5 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C4 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C0 + poly * xsqr; + poly = poly * x; + return poly; +} + +template +inline void TanEstimate::Reduce(Real x, Real& y) +{ + // Map x to y in [-pi,pi], x = pi*quotient + remainder. + y = std::fmod(x, (Real)GTE_C_PI); + + // Map y to [-pi/2,pi/2] with tan(y) = tan(x). + if (y > (Real)GTE_C_HALF_PI) + { + y -= (Real)GTE_C_PI; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y += (Real)GTE_C_PI; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedron3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedron3.h new file mode 100644 index 000000000000..ac1aab871ca3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedron3.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The tetrahedron is represented as an array of four vertices: V0, V1, V2, +// and V3. The vertices are ordered so that the triangular faces are +// counterclockwise-ordered triangles when viewed by an observer outside the +// tetrahedron: +// face 0 = +// face 1 = +// face 2 = +// face 3 = + +namespace gte +{ + +template +class Tetrahedron3 +{ +public: + // Construction and destruction. The default constructor sets the + // vertices to (0,0,0), (1,0,0), (0,1,0), and (0,0,1). + Tetrahedron3(); + Tetrahedron3(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3); + Tetrahedron3(Vector3 const inV[4]); + + // Get the vertex indices for the specified face. The input 'face' must + // be in {0,1,2,3}. + void GetFaceIndices(int face, int index[3]) const; + + // Construct the planes of the faces. The planes have outer pointing + // normal vectors. The plane indexing is the same as the face indexing + // mentioned previously. + void GetPlanes(Plane3 plane[4]) const; + + // Public member access. + Vector3 v[4]; + +public: + // Comparisons to support sorted containers. + bool operator==(Tetrahedron3 const& tetrahedron) const; + bool operator!=(Tetrahedron3 const& tetrahedron) const; + bool operator< (Tetrahedron3 const& tetrahedron) const; + bool operator<=(Tetrahedron3 const& tetrahedron) const; + bool operator> (Tetrahedron3 const& tetrahedron) const; + bool operator>=(Tetrahedron3 const& tetrahedron) const; +}; + + +template +Tetrahedron3::Tetrahedron3() +{ + v[0] = Vector3::Zero(); + v[1] = Vector3::Unit(0); + v[2] = Vector3::Unit(1); + v[3] = Vector3::Unit(2); +} + +template +Tetrahedron3::Tetrahedron3(Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3) +{ + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; +} + +template +Tetrahedron3::Tetrahedron3(Vector3 const inV[4]) +{ + for (int i = 0; i < 4; ++i) + { + v[i] = inV[i]; + } +} + +template +void Tetrahedron3::GetFaceIndices(int face, int index[3]) +const +{ + if (face == 0) + { + index[0] = 0; + index[1] = 2; + index[2] = 1; + } + else if (face == 1) + { + index[0] = 0; + index[1] = 1; + index[2] = 3; + } + else if (face == 2) + { + index[0] = 0; + index[1] = 3; + index[2] = 2; + } + else // face == 3 (no index validation is performed) + { + index[0] = 1; + index[1] = 2; + index[2] = 3; + } +} + +template +void Tetrahedron3::GetPlanes(Plane3 plane[4]) const +{ + Vector3 edge10 = v[1] - v[0]; + Vector3 edge20 = v[2] - v[0]; + Vector3 edge30 = v[3] - v[0]; + Vector3 edge21 = v[2] - v[1]; + Vector3 edge31 = v[3] - v[1]; + + plane[0].normal = UnitCross(edge20, edge10); // + plane[1].normal = UnitCross(edge10, edge30); // + plane[2].normal = UnitCross(edge30, edge20); // + plane[3].normal = UnitCross(edge21, edge31); // + + Real det = Dot(edge10, plane[3].normal); + if (det < (Real)0) + { + // The normals are inner pointing, reverse their directions. + for (int i = 0; i < 4; ++i) + { + plane[i].normal = -plane[i].normal; + } + } + + for (int i = 0; i < 4; ++i) + { + plane[i].constant = Dot(v[i], plane[i].normal); + } +} + +template +bool Tetrahedron3::operator==(Tetrahedron3 const& tetrahedron) const +{ + return v[0] == tetrahedron.v[0] + && v[1] == tetrahedron.v[1] + && v[2] == tetrahedron.v[2] + && v[3] == tetrahedron.v[3]; +} + +template +bool Tetrahedron3::operator!=(Tetrahedron3 const& tetrahedron) const +{ + return !operator==(tetrahedron); +} + +template +bool Tetrahedron3::operator<(Tetrahedron3 const& tetrahedron) const +{ + for (int i = 0; i < 4; ++i) + { + if (v[i] < tetrahedron.v[i]) + { + return true; + } + + if (v[i] > tetrahedron.v[i]) + { + return false; + } + } + + return false; +} + +template +bool Tetrahedron3::operator<=(Tetrahedron3 const& tetrahedron) const +{ + return operator<(tetrahedron) || operator==(tetrahedron); +} + +template +bool Tetrahedron3::operator>(Tetrahedron3 const& tetrahedron) const +{ + return !operator<=(tetrahedron); +} + +template +bool Tetrahedron3::operator>=(Tetrahedron3 const& tetrahedron) const +{ + return !operator<(tetrahedron); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.cpp new file mode 100644 index 000000000000..efb008836d56 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.cpp @@ -0,0 +1,145 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#include +#include +#include + +namespace +{ + void Permute(int u0, int u1, int u2, int V[4]) + { + // Once V[0] is determined, create a permutation (V[1],V[2],V[3]) so + // that (V[0],V[1],V[2],V[3]) is a positive permutation of + // (v0,v1,v2,v3). + if (u0 < u1) + { + if (u0 < u2) + { + // u0 is minimum + V[1] = u0; + V[2] = u1; + V[3] = u2; + } + else + { + // u2 is minimum + V[1] = u2; + V[2] = u0; + V[3] = u1; + } + } + else + { + if (u1 < u2) + { + // u1 is minimum + V[1] = u1; + V[2] = u2; + V[3] = u0; + } + else + { + // u2 is minimum + V[1] = u2; + V[2] = u0; + V[3] = u1; + } + } + } +} + +namespace gte +{ + // TetrahedronKey + + template<> std::array const TetrahedronKey::oppositeFace[4] = + { + {{ 1, 2, 3 }}, + {{ 0, 3, 2 }}, + {{ 0, 1, 3 }}, + {{ 0, 2, 1 }} + }; + + template<> + TetrahedronKey::TetrahedronKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + V[3] = -1; + } + + template<> + TetrahedronKey::TetrahedronKey(int v0, int v1, int v2, int v3) + { + int imin = 0; + V[0] = v0; + if (v1 < V[0]) + { + V[0] = v1; + imin = 1; + } + if (v2 < V[0]) + { + V[0] = v2; + imin = 2; + } + if (v3 < V[0]) + { + V[0] = v3; + imin = 3; + } + + if (imin == 0) + { + Permute(v1, v2, v3, V); + } + else if (imin == 1) + { + Permute(v0, v3, v2, V); + } + else if (imin == 2) + { + Permute(v0, v1, v3, V); + } + else // imin == 3 + { + Permute(v0, v2, v1, V); + } + } + + + // TetrahedronKey + + template<> std::array const TetrahedronKey::oppositeFace[4] = + { + {{ 1, 2, 3 }}, + {{ 0, 3, 2 }}, + {{ 0, 1, 3 }}, + {{ 0, 2, 1 }} + }; + + template<> + TetrahedronKey::TetrahedronKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + V[3] = -1; + } + + template<> + TetrahedronKey::TetrahedronKey(int v0, int v1, int v2, int v3) + { + V[0] = v0; + V[1] = v1; + V[2] = v2; + V[3] = v3; + std::sort(&V[0], &V[4]); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.h new file mode 100644 index 000000000000..c36db9c77375 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTetrahedronKey.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#pragma once + +#include +#include + +namespace gte +{ + template + class TetrahedronKey : public FeatureKey<4, Ordered> + { + public: + // An ordered tetrahedron has V[0] = min(v0,v1,v2,v3). Let {u1,u2,u3} + // be the set of inputs excluding the one assigned to V[0] and define + // V[1] = min(u1,u2,u3). Choose (V[1],V[2],V[3]) to be a permutation + // of (u1,u2,u3) so that the final storage is one of + // (v0,v1,v2,v3), (v0,v2,v3,v1), (v0,v3,v1,v2) + // (v1,v3,v2,v0), (v1,v2,v0,v3), (v1,v0,v3,v2) + // (v2,v3,v0,v1), (v2,v0,v1,v3), (v2,v1,v3,v0) + // (v3,v1,v0,v2), (v3,v0,v2,v1), (v3,v2,v1,v0) + // The idea is that if v0 corresponds to (1,0,0,0), v1 corresponds to + // (0,1,0,0), v2 corresponds to (0,0,1,0), and v3 corresponds to + // (0,0,0,1), the ordering (v0,v1,v2,v3) corresponds to the 4x4 + // identity matrix I; the rows are the specified 4-tuples. The + // permutation (V[0],V[1],V[2],V[3]) induces a permutation of the rows + // of the identity matrix to form a permutation matrix P with det(P) + // = 1 = det(I). + // + // An unordered tetrahedron stores a permutation of (v0,v1,v2,v3) so + // that V[0] < V[1] < V[2] < V[3]. + TetrahedronKey(); // creates key (-1,-1,-1,-1) + explicit TetrahedronKey(int v0, int v1, int v2, int v3); + + // Indexing for the vertices of the triangle opposite a vertex. The + // triangle opposite vertex j is + // + // and is listed in counterclockwise order when viewed from outside + // the tetrahedron. + static std::array const oppositeFace[4]; + }; + +#if !defined(__MSWINDOWS__) + // Apple LLVM 6.1.0 (clang-602.0.49) correctly requires these declarations + // to occur before the definition in GteTetrahedronKey.cpp. From the C++ + // specification: + // An explicit specialization of a static data member of a template is + // a definition if the declaration includes an initializer; otherwise, + // it is a declaration. + // If these declarations are exposed for MSVS 2013, the compiler generates + // error C2086, claiming that this is a definition (rather than a + // declaration) and the cpp file has a redefinition. + template<> std::array const TetrahedronKey::oppositeFace[4]; + template<> std::array const TetrahedronKey::oppositeFace[4]; +#endif +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTorus3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTorus3.h new file mode 100644 index 000000000000..a66146089319 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTorus3.h @@ -0,0 +1,254 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/05) + +#pragma once + +#include + +// A torus with origin (0,0,0), outer radius r0 and inner radius r1 (with +// (r0 >= r1) is defined implicitly as follows. The point P0 = (x,y,z) is on +// the torus. Its projection onto the xy-plane is P1 = (x,y,0). The circular +// cross section of the torus that contains the projection has radius r0 and +// center P2 = r0*(x,y,0)/sqrt(x^2+y^2). The points triangle is a +// right triangle with right angle at P1. The hypotenuse has length +// r1, leg has length z and leg has length |r0 - sqrt(x^2+y^2)|. +// The Pythagorean theorem says z^2 + |r0 - sqrt(x^2+y^2)|^2 = r1^2. This can +// be algebraically manipulated to +// (x^2 + y^2 + z^2 + r0^2 - r1^2)^2 - 4 * r0^2 * (x^2 + y^2) = 0 +// +// A parametric form is +// x = (r0 + r1 * cos(v)) * cos(u) +// y = (r0 + r1 * cos(v)) * sin(u) +// z = r1 * sin(v) +// for u in [0,2*pi) and v in [0,2*pi). +// +// Generally, let the torus center be C with plane of symmetry containing C +// and having directions D0 and D1. The axis of symmetry is the line +// containing C and having direction N (the plane normal). The radius from +// the center of the torus is r0 and the radius of the tube of the torus is +// r1. A point P may be written as P = C + x*D0 + y*D1 + z*N, where matrix +// [D0 D1 N] is orthonormal and has determinant 1. Thus, x = Dot(D0,P-C), +// y = Dot(D1,P-C) and z = Dot(N,P-C). The implicit form is +// [|P-C|^2 + r0^2 - r1^2]^2 - 4*r0^2*[|P-C|^2 - (Dot(N,P-C))^2] = 0 +// Observe that D0 and D1 are not present in the equation, which is to be +// expected by the symmetry. The parametric form is +// P(u,v) = C + (r0 + r1*cos(v))*(cos(u)*D0 + sin(u)*D1) + r1*sin(v)*N +// for u in [0,2*pi) and v in [0,2*pi). +// +// In the class Torus3, the members are 'center' C, 'direction0' D0, +// 'direction1' D1, 'normal' N, 'radius0' r0 and 'radius1' r1. + +namespace gte +{ + +template +class Torus3 +{ +public: + // Construction and destruction. The default constructor sets center to + // (0,0,0), direction0 to (1,0,0), direction1 to (0,1,0), normal to + // (0,0,1), radius0 to 2, and radius1 to 1. + Torus3(); + Torus3(Vector3 const& inCenter, Vector3 const& inDirection0, + Vector3 const& inDirection1, Vector3 const& inNormal, + Real inRadius0, Real inRadius1); + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. If + // you want only the position, pass in maxOrder of 0. If you want the + // position and first-order derivatives, pass in maxOrder of 1, and so on. + // The output 'values' are ordered as: position X; first-order derivatives + // dX/du, dX/dv; second-order derivatives d2X/du2, d2X/dudv, d2X/dv2. + void Evaluate(Real u, Real v, unsigned int maxOrder, Vector3 values[6]) const; + + // Reverse lookup of parameters from position. + void GetParameters(Vector3 const& X, Real& u, Real& v) const; + + Vector3 center, direction0, direction1, normal; + Real radius0, radius1; + +public: + // Comparisons to support sorted containers. + bool operator==(Torus3 const& torus) const; + bool operator!=(Torus3 const& torus) const; + bool operator< (Torus3 const& torus) const; + bool operator<=(Torus3 const& torus) const; + bool operator> (Torus3 const& torus) const; + bool operator>=(Torus3 const& torus) const; +}; + + +template +Torus3::Torus3() + : + center(Vector3::Zero()), + direction0(Vector3::Unit(0)), + direction1(Vector3::Unit(1)), + normal(Vector3::Unit(2)), + radius0((Real)2), + radius1((Real)1) +{ +} + +template +Torus3::Torus3(Vector3 const& inCenter, + Vector3 const& inDirection0, Vector3 const& inDirection1, + Vector3 const& inNormal, Real inRadius0, Real inRadius1) + : + center(inCenter), + direction0(inDirection0), + direction1(inDirection1), + normal(inNormal), + radius0(inRadius0), + radius1(inRadius1) +{ +} + +template +void Torus3::Evaluate(Real u, Real v, unsigned int maxOrder, + Vector3 values[6]) const +{ + // Compute position. + Real csu = std::cos(u), snu = std::sin(u), csv = std::cos(v), snv = std::sin(v); + Real r1csv = radius1 * csv; + Real r1snv = radius1 * snv; + Real r0pr1csv = radius0 + r1csv; + Vector3 combo0 = csu * direction0 + snu * direction1; + Vector3 r0pr1csvcombo0 = r0pr1csv * combo0; + Vector3 r1snvnormal = r1snv * normal; + values[0] = center + r0pr1csvcombo0 + r1snvnormal; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector3 combo1 = -snu * direction0 + csu * direction1; + values[1] = r0pr1csv * combo1; + values[2] = -r1snv * combo0 + r1csv * normal; + + if (maxOrder >= 2) + { + // Compute second-order derivatives. + values[3] = -r0pr1csvcombo0; + values[4] = -r1snv * combo1; + values[5] = -r1csv * combo0 - r1snvnormal; + + if (maxOrder >= 3) + { + // These orders are not supported. + for (int i = 0; i < 6; ++i) + { + values[i] = Vector3::Zero(); + } + } + } + } +} + +template +void Torus3::GetParameters(Vector3 const& X, Real& u, Real& v) const +{ + Vector3 delta = X - center; + Real dot0 = Dot(direction0, delta); // (r0 + r1*cos(v))*cos(u) + Real dot1 = Dot(direction1, delta); // (r0 + r1*cos(v))*sin(u) + Real dot2 = Dot(normal, delta); // r1*sin(v) + Real r1csv = std::sqrt(dot0 * dot0 + dot1 * dot1) - radius0; // r1*cos(v) + u = std::atan2(dot1, dot0); + v = std::atan2(dot2, r1csv); +} + +template +bool Torus3::operator==(Torus3 const& torus) const +{ + return center == torus.center + && direction0 == torus.direction0 + && direction1 == torus.direction1 + && normal == torus.normal + && radius0 == torus.radius0 + && radius1 == torus.radius1; +} + +template +bool Torus3::operator!=(Torus3 const& torus) const +{ + return !operator==(torus); +} + +template +bool Torus3::operator<(Torus3 const& torus) const +{ + if (center < torus.center) + { + return true; + } + + if (center > torus.center) + { + return false; + } + + if (direction0 < torus.direction0) + { + return true; + } + + if (direction0 > torus.direction0) + { + return false; + } + + if (direction1 < torus.direction1) + { + return true; + } + + if (direction1 > torus.direction1) + { + return false; + } + + if (normal < torus.normal) + { + return true; + } + + if (normal > torus.normal) + { + return false; + } + + if (radius0 < torus.radius0) + { + return true; + } + + if (radius0 > torus.radius0) + { + return false; + } + + return radius1 < torus.radius1; +} + +template +bool Torus3::operator<=(Torus3 const& torus) const +{ + return operator<(torus) || operator==(torus); +} + +template +bool Torus3::operator>(Torus3 const& torus) const +{ + return !operator<=(torus); +} + +template +bool Torus3::operator>=(Torus3 const& torus) const +{ + return !operator<(torus); +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangle.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangle.h new file mode 100644 index 000000000000..07dbc707ef29 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangle.h @@ -0,0 +1,111 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include + +// The triangle is represented as an array of three vertices. The dimension +// N must be 2 or larger. + +namespace gte +{ + +template +class Triangle +{ +public: + // Construction and destruction. The default constructor sets the + // vertices to (0,..,0), (1,0,...,0), and (0,1,0,...,0). + Triangle(); + Triangle(Vector const& v0, Vector const& v1, + Vector const& v2); + Triangle(std::array, 3> const& inV); + + // Public member access. + std::array, 3> v; + +public: + // Comparisons to support sorted containers. + bool operator==(Triangle const& triangle) const; + bool operator!=(Triangle const& triangle) const; + bool operator< (Triangle const& triangle) const; + bool operator<=(Triangle const& triangle) const; + bool operator> (Triangle const& triangle) const; + bool operator>=(Triangle const& triangle) const; +}; + +// Template aliases for convenience. +template +using Triangle2 = Triangle<2, Real>; + +template +using Triangle3 = Triangle<3, Real>; + + +template +Triangle::Triangle() +{ + v[0].MakeZero(); + v[1].MakeUnit(0); + v[2].MakeUnit(1); +} + +template +Triangle::Triangle(Vector const& v0, + Vector const& v1, Vector const& v2) +{ + v[0] = v0; + v[1] = v1; + v[2] = v2; +} + +template +Triangle::Triangle(std::array, 3> const& inV) + : + v(inV) +{ +} + +template +bool Triangle::operator==(Triangle const& triangle) const +{ + return v == triangle.v; +} + +template +bool Triangle::operator!=(Triangle const& triangle) const +{ + return !operator==(triangle); +} + +template +bool Triangle::operator<(Triangle const& triangle) const +{ + return v < triangle.v; +} + +template +bool Triangle::operator<=(Triangle const& triangle) const +{ + return operator<(triangle) || operator==(triangle); +} + +template +bool Triangle::operator>(Triangle const& triangle) const +{ + return !operator<=(triangle); +} + +template +bool Triangle::operator>=(Triangle const& triangle) const +{ + return !operator<(triangle); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangleKey.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangleKey.h new file mode 100644 index 000000000000..b651cdd93a3b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangleKey.h @@ -0,0 +1,134 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/05/22) + +#pragma once + +#include + +#include + +namespace gte +{ + template + class TriangleKey : public FeatureKey<3, Ordered> + { + public: + // An ordered triangle has V[0] = min(v0,v1,v2). Choose + // (V[0],V[1],V[2]) to be a permutation of (v0,v1,v2) so that the + // final storage is one of + // (v0,v1,v2), (v1,v2,v0), (v2,v0,v1) + // The idea is that if v0 corresponds to (1,0,0), v1 corresponds to + // (0,1,0), and v2 corresponds to (0,0,1), the ordering (v0,v1,v2) + // corresponds to the 3x3 identity matrix I; the rows are the + // specified 3-tuples. The permutation (V[0],V[1],V[2]) induces a + // permutation of the rows of the identity matrix to form a + // permutation matrix P with det(P) = 1 = det(I). + // + // An unordered triangle stores a permutation of (v0,v1,v2) so that + // V[0] < V[1] < V[2]. + TriangleKey(); // creates key (-1,-1,-1) + explicit TriangleKey(int v0, int v1, int v2); + }; + + template<> + inline + TriangleKey::TriangleKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + } + + template<> + inline + TriangleKey::TriangleKey(int v0, int v1, int v2) + { + if (v0 < v1) + { + if (v0 < v2) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + V[2] = v2; + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = v0; + V[2] = v1; + } + } + else + { + if (v1 < v2) + { + // v1 is minimum + V[0] = v1; + V[1] = v2; + V[2] = v0; + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = v0; + V[2] = v1; + } + } + } + + template<> + inline + TriangleKey::TriangleKey() + { + V[0] = -1; + V[1] = -1; + V[2] = -1; + } + + template<> + inline + TriangleKey::TriangleKey(int v0, int v1, int v2) + { + if (v0 < v1) + { + if (v0 < v2) + { + // v0 is minimum + V[0] = v0; + V[1] = std::min(v1, v2); + V[2] = std::max(v1, v2); + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = std::min(v0, v1); + V[2] = std::max(v0, v1); + } + } + else + { + if (v1 < v2) + { + // v1 is minimum + V[0] = v1; + V[1] = std::min(v2, v0); + V[2] = std::max(v2, v0); + } + else + { + // v2 is minimum + V[0] = v2; + V[1] = std::min(v0, v1); + V[2] = std::max(v0, v1); + } + } + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateCDT.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateCDT.h new file mode 100644 index 000000000000..1ed0f2904834 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateCDT.h @@ -0,0 +1,576 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include +#include + +// The triangulation is based on constrained Delaunay triangulations (CDT), +// which does not use divisions, so ComputeType may be chosen using BSNumber. +// The input constraints are relaxed compared to TriangulateEC; specifically, +// the inner polygons are allowed to share vertices with the outer polygons. +// The CDT produces a triangulation of the convex hull of the input, which +// includes triangles outside the top-level outer polygon and inside the +// inner polygons. Only the triangles relevant to the input are returned +// via the 'std::vector& triangles', but the other triangles are +// retained so that you can perform linear walks in search of points inside +// the original polygon (nested polygon, tree of nested polygons). This is +// useful, for example, when subsampling the polygon triangles for +// interpolation of function data specified at the vertices. A linear walk +// does not work for a mesh consisting only of the polygon triangles, but +// with the additional triangles, the walk can navigate through holes in +// the polygon to find the containing triangle of the specified point. + +namespace gte +{ + +template +class TriangulateCDT +{ +public: + // The class is a functor to support triangulating multiple polygons that + // share vertices in a collection of points. The interpretation of + // 'numPoints' and 'points' is described before each operator() function. + // Preconditions are numPoints >= 3 and points is a nonnull pointer to an + // array of at least numPoints elements. If the preconditions are + // satisfied, then operator() functions will return 'true'; otherwise, + // they return 'false'. + TriangulateCDT(int numPoints, Vector2 const* points); + TriangulateCDT(std::vector> const& points); + + // The triangles of the polygon triangulation. + inline std::vector> const& GetTriangles() const; + + // The triangles inside the convex hull of the points but outside the + // triangulation. + inline std::vector> const& GetOutsideTriangles() const; + + // The triangles of the convex hull of the inputs to the constructor. + inline std::vector> const& GetAllTriangles() const; + + // The classification of whether a triangle is part of the triangulation + // or outside the triangulation. These may be used in conjunction with + // the array returned by GetAllTriangles(). + inline std::vector const& GetIsInside() const; + inline bool IsInside(int triIndex) const; + inline bool IsOutside(int triIndex) const; + + // The outer polygons have counterclockwise ordered vertices. The inner + // polygons have clockwise ordered vertices. + typedef std::vector Polygon; + + // The input 'points' represents an array of vertices for a simple + // polygon. The vertices are points[0] through points[numPoints-1] and + // are listed in counterclockwise order. + bool operator()(); + + // The input 'points' represents an array of vertices that contains the + // vertices of a simple polygon. + bool operator()(Polygon const& polygon); + + // The input 'points' is a shared array of vertices that contains the + // vertices for two simple polygons, an outer polygon and an inner + // polygon. The inner polygon must be strictly inside the outer polygon. + bool operator()(Polygon const& outer, Polygon const& inner); + + // The input 'points' is a shared array of vertices that contains the + // vertices for multiple simple polygons, an outer polygon and one or more + // inner polygons. The inner polygons must be nonoverlapping and strictly + // inside the outer polygon. + bool operator()(Polygon const& outer, std::vector const& inners); + + // A tree of nested polygons. The root node corresponds to an outer + // polygon. The children of the root correspond to inner polygons, which + // are nonoverlapping polygons strictly contained in the outer polygon. + // Each inner polygon may itself contain an outer polygon, thus leading + // to a hierarchy of polygons. The outer polygons have vertices listed + // in counterclockwise order. The inner polygons have vertices listed in + // clockwise order. + class Tree + { + public: + Polygon polygon; + std::vector> child; + }; + + // The input 'positions' is a shared array of vertices that contains the + // vertices for multiple simple polygons in a tree of polygons. + bool operator()(std::shared_ptr const& tree); + +private: + // Triangulate the points referenced by an operator(...) query. The + // mAllTriangles and mIsInside are populated by this function, but the + // indices of mAllTriangles are relative to the packed 'points'. After + // the call, the indices need to be mapped back to the original set + // provided by the input arrays to operator(...). The mTriangles and + // mOutsideTriangles are generated after the call by examining + // mAllTriangles and mIsInside. + bool TriangulatePacked(int numPoints, Vector2 const* points, + std::shared_ptr const& tree, + std::map, int> const& offsets); + + int GetNumPointsAndOffsets(std::shared_ptr const& tree, + std::map, int>& offsets) const; + + void PackPoints(std::shared_ptr const& tree, + std::vector>& points); + + bool InsertEdges(std::shared_ptr const& tree); + + void LookupIndex(std::shared_ptr const& tree, int& v, + std::map, int> const& offsets) const; + + bool IsInside(std::shared_ptr const& tree, Vector2 const* points, + Vector2 const& test, + std::map, int> const& offsets) const; + + // The input polygon. + int mNumPoints; + Vector2 const* mPoints; + + // The output triangulation and those triangle inside the hull of the + // points but outside the triangulation. + std::vector> mTriangles; + std::vector> mOutsideTriangles; + std::vector> mAllTriangles; + std::vector mIsInside; + + ConstrainedDelaunay2 mConstrainedDelaunay; +}; + + +template +TriangulateCDT::TriangulateCDT(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) +{ + if (mNumPoints < 3 || !mPoints) + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template +TriangulateCDT::TriangulateCDT(std::vector> const& points) + : + mNumPoints(static_cast(points.size())), + mPoints(points.data()) +{ + if (mNumPoints < 3 || !mPoints) + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template inline +std::vector> const& TriangulateCDT::GetTriangles() const +{ + return mTriangles; +} + +template inline +std::vector> const& TriangulateCDT::GetOutsideTriangles() const +{ + return mOutsideTriangles; +} + +template inline +std::vector> const& TriangulateCDT::GetAllTriangles() const +{ + return mAllTriangles; +} + +template inline +std::vector const& TriangulateCDT::GetIsInside() const +{ + return mIsInside; +} + +template inline +bool TriangulateCDT::IsInside(int triIndex) const +{ + if (0 <= triIndex && triIndex < static_cast(mIsInside.size())) + { + return mIsInside[triIndex]; + } + else + { + return false; + } +} + +template inline +bool TriangulateCDT::IsOutside(int triIndex) const +{ + if (0 <= triIndex && triIndex < static_cast(mIsInside.size())) + { + return !mIsInside[triIndex]; + } + else + { + return false; + } +} + +template +bool TriangulateCDT::operator()() +{ + if (mPoints) + { + std::shared_ptr tree = std::make_shared(); + tree->polygon.resize(mNumPoints); + for (int i = 0; i < mNumPoints; ++i) + { + tree->polygon[i] = i; + } + + return operator()(tree); + } + return false; +} + +template +bool TriangulateCDT::operator()(Polygon const& polygon) +{ + if (mPoints) + { + std::shared_ptr tree = std::make_shared(); + tree->polygon = polygon; + + return operator()(tree); + } + return false; +} + +template +bool TriangulateCDT::operator()(Polygon const& outer, Polygon const& inner) +{ + if (mPoints) + { + std::shared_ptr otree = std::make_shared(); + otree->polygon = outer; + otree->child.resize(1); + + std::shared_ptr itree = std::make_shared(); + itree->polygon = inner; + otree->child[0] = itree; + + return operator()(otree); + } + return false; +} + +template +bool TriangulateCDT::operator()(Polygon const& outer, std::vector const& inners) +{ + if (mPoints) + { + std::shared_ptr otree = std::make_shared(); + otree->polygon = outer; + otree->child.resize(inners.size()); + + std::vector> itree(inners.size()); + for (size_t i = 0; i < inners.size(); ++i) + { + itree[i] = std::make_shared(); + itree[i]->polygon = inners[i]; + otree->child[i] = itree[i]; + } + + return operator()(otree); + } + return false; +} + +template +bool TriangulateCDT::operator()(std::shared_ptr const& tree) +{ + if (mPoints) + { + std::map, int> offsets; + int numPoints = GetNumPointsAndOffsets(tree, offsets); + std::vector> points(numPoints); + PackPoints(tree, points); + + if (TriangulatePacked(numPoints, &points[0], tree, offsets)) + { + int numTriangles = static_cast(mAllTriangles.size()); + for (int t = 0; t < numTriangles; ++t) + { + auto& tri = mAllTriangles[t]; + for (int j = 0; j < 3; ++j) + { + LookupIndex(tree, tri[j], offsets); + } + + if (mIsInside[t]) + { + mTriangles.push_back(tri); + } + else + { + mOutsideTriangles.push_back(tri); + } + } + return true; + } + } + + return false; +} + +template +bool TriangulateCDT::TriangulatePacked(int numPoints, + Vector2 const* points, std::shared_ptr const& tree, + std::map, int> const& offsets) +{ + mTriangles.clear(); + mOutsideTriangles.clear(); + mAllTriangles.clear(); + mIsInside.clear(); + + mConstrainedDelaunay(numPoints, points, static_cast(0)); + InsertEdges(tree); + + ComputeType half = static_cast(0.5); + ComputeType fourth = static_cast(0.25); + auto const& query = mConstrainedDelaunay.GetQuery(); + auto const* ctPoints = query.GetVertices(); + int numTriangles = mConstrainedDelaunay.GetNumTriangles(); + int const* indices = &mConstrainedDelaunay.GetIndices()[0]; + mIsInside.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + auto ctInside = fourth * ctPoints[v0] + half * ctPoints[v1] + fourth *ctPoints[v2]; + mIsInside[t] = IsInside(tree, ctPoints, ctInside, offsets); + mAllTriangles.push_back({ { v0, v1, v2 } }); + } + return true; +} + +template +int TriangulateCDT::GetNumPointsAndOffsets( + std::shared_ptr const& tree, std::map, int>& offsets) const +{ + int numPoints = 0; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + offsets.insert(std::make_pair(outer, numPoints)); + numPoints += static_cast(outer->polygon.size()); + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + offsets.insert(std::make_pair(inner, numPoints)); + numPoints += static_cast(inner->polygon.size()); + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + return numPoints; +} + +template +void TriangulateCDT::PackPoints(std::shared_ptr const& tree, + std::vector>& points) +{ + int numPoints = 0; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + points[numPoints++] = mPoints[outerIndices[i]]; + } + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + points[numPoints++] = mPoints[innerIndices[i]]; + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } +} + +template +bool TriangulateCDT::InsertEdges(std::shared_ptr const& tree) +{ + int numPoints = 0; + std::array edge; + std::vector ignoreOutEdge; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int numOuter = static_cast(outer->polygon.size()); + for (int i0 = numOuter - 1, i1 = 0; i1 < numOuter; i0 = i1++) + { + edge[0] = numPoints + i0; + edge[1] = numPoints + i1; + if (!mConstrainedDelaunay.Insert(edge, ignoreOutEdge)) + { + return false; + } + } + numPoints += numOuter; + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int numInner = static_cast(inner->polygon.size()); + for (int i0 = numInner - 1, i1 = 0; i1 < numInner; i0 = i1++) + { + edge[0] = numPoints + i0; + edge[1] = numPoints + i1; + if (!mConstrainedDelaunay.Insert(edge, ignoreOutEdge)) + { + return false; + } + } + numPoints += numInner; + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + return true; +} + +template +void TriangulateCDT::LookupIndex(std::shared_ptr const& tree, + int& v, std::map, int> const& offsets) const +{ + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + int offset = offsets.find(outer)->second; + if (v < offset + numOuterIndices) + { + v = outerIndices[v - offset]; + return; + } + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + offset = offsets.find(inner)->second; + if (v < offset + numInnerIndices) + { + v = innerIndices[v - offset]; + return; + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } +} + +template +bool TriangulateCDT::IsInside(std::shared_ptr const& tree, + Vector2 const* points, Vector2 const& test, + std::map, int> const& offsets) const +{ + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int offset = offsets.find(outer)->second; + PointInPolygon2 piOuter(numOuterIndices, points + offset); + if (piOuter.Contains(test)) + { + int numChildren = static_cast(outer->child.size()); + int c; + for (c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + offset = offsets.find(inner)->second; + PointInPolygon2 piInner(numInnerIndices, points + offset); + if (piInner.Contains(test)) + { + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + break; + } + } + if (c == numChildren) + { + return true; + } + } + } + return false; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateEC.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateEC.h new file mode 100644 index 000000000000..165239bfc7b2 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTriangulateEC.h @@ -0,0 +1,1319 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/02/17) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// The algorithm for processing nested polygons involves a division, so the +// ComputeType must be rational-based, say, BSRational. If you process only +// triangles that are simple, you may use BSNumber for the ComputeType. + +namespace gte +{ + +template +class TriangulateEC +{ +public: + // The class is a functor to support triangulating multiple polygons that + // share vertices in a collection of points. The interpretation of + // 'numPoints' and 'points' is described before each operator() function. + // Preconditions are numPoints >= 3 and points is a nonnull pointer to an + // array of at least numPoints elements. If the preconditions are + // satisfied, then operator() functions will return 'true'; otherwise, + // they return 'false'. + TriangulateEC(int numPoints, Vector2 const* points); + TriangulateEC(std::vector> const& points); + + // Access the triangulation after each operator() call. + inline std::vector> const& GetTriangles() const; + + // The outer polygons have counterclockwise ordered vertices. The inner + // polygons have clockwise ordered vertices. + typedef std::vector Polygon; + + // The input 'points' represents an array of vertices for a simple + // polygon. The vertices are points[0] through points[numPoints-1] and + // are listed in counterclockwise order. + bool operator()(); + + // The input 'points' represents an array of vertices that contains the + // vertices of a simple polygon. + bool operator()(Polygon const& polygon); + + // The input 'points' is a shared array of vertices that contains the + // vertices for two simple polygons, an outer polygon and an inner + // polygon. The inner polygon must be strictly inside the outer polygon. + bool operator()(Polygon const& outer, Polygon const& inner); + + // The input 'points' is a shared array of vertices that contains the + // vertices for multiple simple polygons, an outer polygon and one or more + // inner polygons. The inner polygons must be nonoverlapping and strictly + // inside the outer polygon. + bool operator()(Polygon const& outer, std::vector const& inners); + + // A tree of nested polygons. The root node corresponds to an outer + // polygon. The children of the root correspond to inner polygons, which + // are nonoverlapping polygons strictly contained in the outer polygon. + // Each inner polygon may itself contain an outer polygon, thus leading + // to a hierarchy of polygons. The outer polygons have vertices listed + // in counterclockwise order. The inner polygons have vertices listed in + // clockwise order. + class Tree + { + public: + Polygon polygon; + std::vector> child; + }; + + // The input 'positions' is a shared array of vertices that contains the + // vertices for multiple simple polygons in a tree of polygons. + bool operator()(std::shared_ptr const& tree); + +private: + // Create the vertex objects that store the various lists required by the + // ear-clipping algorithm. + void InitializeVertices(int numVertices, int const* indices); + + // Apply ear clipping to the input polygon. Polygons with holes are + // preprocessed to obtain an index array that is nearly a simple polygon. + // This outer polygon has a pair of coincident edges per inner polygon. + void DoEarClipping(int numVertices, int const* indices); + + // Given an outer polygon that contains an inner polygon, this function + // determines a pair of visible vertices and inserts two coincident edges + // to generate a nearly simple polygon. + bool CombinePolygons(int nextElement, Polygon const& outer, + Polygon const& inner, std::map& indexMap, + std::vector& combined); + + // Given an outer polygon that contains a set of nonoverlapping inner + // polygons, this function determines pairs of visible vertices and + // inserts coincident edges to generate a nearly simple polygon. It + // repeatedly calls CombinePolygons for each inner polygon of the outer + // polygon. + bool ProcessOuterAndInners(int& nextElement, Polygon const& outer, + std::vector const& inners, std::map& indexMap, + std::vector& combined); + + // The insertion of coincident edges to obtain a nearly simple polygon + // requires duplication of vertices in order that the ear-clipping + // algorithm work correctly. After the triangulation, the indices of + // the duplicated vertices are converted to the original indices. + void RemapIndices(std::map const& indexMap); + + // Two extra elements are needed in the position array per outer-inners + // polygon. This function computes the total number of extra elements + // needed for the input tree and it converts InputType vertices to + // ComputeType values. + int InitializeFromTree(std::shared_ptr const& tree); + + // The input polygon. + int mNumPoints; + Vector2 const* mPoints; + + // The output triangulation. + std::vector> mTriangles; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. The + // InputType points are convertex to ComputeType points on demand; the + // mIsConverted array keeps track of which input points have been + // converted. + std::vector> mComputePoints; + std::vector mIsConverted; + PrimalQuery2 mQuery; + + // Doubly linked lists for storing specially tagged vertices. + class Vertex + { + public: + Vertex(); + + int index; // index of vertex in position array + int vPrev, vNext; // vertex links for polygon + int sPrev, sNext; // convex/reflex vertex links (disjoint lists) + int ePrev, eNext; // ear links + bool isConvex, isEar; + }; + + inline Vertex& V(int i); + bool IsConvex(int i); + bool IsEar(int i); + void InsertAfterC(int i); // insert convex vertex + void InsertAfterR(int i); // insert reflex vertesx + void InsertEndE(int i); // insert ear at end of list + void InsertAfterE(int i); // insert ear after efirst + void InsertBeforeE(int i); // insert ear before efirst + void RemoveV(int i); // remove vertex + int RemoveE(int i); // remove ear at i + void RemoveR(int i); // remove reflex vertex + + // The doubly linked list. + std::vector mVertices; + int mCFirst, mCLast; // linear list of convex vertices + int mRFirst, mRLast; // linear list of reflex vertices + int mEFirst, mELast; // cyclical list of ears +}; + + +template +TriangulateEC::TriangulateEC(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) +{ + if (mNumPoints >= 3 && mPoints) + { + mComputePoints.resize(mNumPoints); + mIsConverted.resize(mNumPoints); + std::fill(mIsConverted.begin(), mIsConverted.end(), false); + mQuery.Set(mNumPoints, &mComputePoints[0]); + } + else + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template +TriangulateEC::TriangulateEC(std::vector> const& points) + : + mNumPoints(static_cast(points.size())), + mPoints(points.data()) +{ + if (mNumPoints >= 3 && mPoints) + { + mComputePoints.resize(mNumPoints); + mIsConverted.resize(mNumPoints); + std::fill(mIsConverted.begin(), mIsConverted.end(), false); + mQuery.Set(mNumPoints, &mComputePoints[0]); + } + else + { + LogError("Invalid input."); + mNumPoints = 0; + mPoints = nullptr; + // The operator() functions will triangulate only when mPoints is + // not null. The test mNumPoints >= 3 is redundant because of the + // logic in the constructor. + } +} + +template inline +std::vector> const& TriangulateEC::GetTriangles() const +{ + return mTriangles; +} + +template +bool TriangulateEC::operator()() +{ + mTriangles.clear(); + if (mPoints) + { + // Compute the points for the queries. + for (int i = 0; i < mNumPoints; ++i) + { + if (!mIsConverted[i]) + { + mIsConverted[i] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[i][j] = mPoints[i][j]; + } + } + } + + // Triangulate the unindexed polygon. + InitializeVertices(mNumPoints, nullptr); + DoEarClipping(mNumPoints, nullptr); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(Polygon const& polygon) +{ + mTriangles.clear(); + if (mPoints) + { + // Compute the points for the queries. + int const numIndices = static_cast(polygon.size()); + int const* indices = polygon.data(); + for (int i = 0; i < numIndices; ++i) + { + int index = indices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // Triangulate the indexed polygon. + InitializeVertices(numIndices, indices); + DoEarClipping(numIndices, indices); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(Polygon const& outer, Polygon const& inner) +{ + mTriangles.clear(); + if (mPoints) + { + // Two extra elements are needed to duplicate the endpoints of the + // edge introduced to combine outer and inner polygons. + int numPointsPlusExtras = mNumPoints + 2; + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + mIsConverted[mNumPoints] = false; + mIsConverted[mNumPoints + 1] = false; + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + // Convert any points that have not been encountered in other + // triangulation calls. + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // Combine the outer polygon and the inner polygon into a simple + // polygon by inserting two edges connecting mutually visible + // vertices, one from the outer polygon and one from the inner + // polygon. + int nextElement = mNumPoints; // The next available element. + std::map indexMap; + std::vector combined; + if (!CombinePolygons(nextElement, outer, inner, indexMap, combined)) + { + // An unexpected condition was encountered. + return false; + } + + // The combined polygon is now in the format of a simple polygon, + // albeit one with coincident edges. + int numVertices = static_cast(combined.size()); + int* const indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(Polygon const& outer, std::vector const& inners) +{ + mTriangles.clear(); + if (mPoints) + { + // Two extra elements per inner polygon are needed to duplicate the + // endpoints of the edges introduced to combine outer and inner + // polygons. + int numPointsPlusExtras = mNumPoints + 2 * (int)inners.size(); + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + for (int i = mNumPoints; i < numPointsPlusExtras; ++i) + { + mIsConverted[i] = false; + } + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + // Convert any points that have not been encountered in other + // triangulation calls. + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + for (auto const& inner : inners) + { + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + } + + // Combine the outer polygon and the inner polygons into a simple + // polygon by inserting two edges per inner polygon connecting + // mutually visible vertices. + int nextElement = mNumPoints; // The next available element. + std::map indexMap; + std::vector combined; + if (!ProcessOuterAndInners(nextElement, outer, inners, indexMap, combined)) + { + // An unexpected condition was encountered. + return false; + } + + // The combined polygon is now in the format of a simple polygon, albeit + // with coincident edges. + int numVertices = static_cast(combined.size()); + int* const indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } +} + +template +bool TriangulateEC::operator()(std::shared_ptr const& tree) +{ + mTriangles.clear(); + if (mPoints) + { + // Two extra elements per inner polygon are needed to duplicate the + // endpoints of the edges introduced to combine outer and inner + // polygons. + int numPointsPlusExtras = mNumPoints + InitializeFromTree(tree); + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + for (int i = mNumPoints; i < numPointsPlusExtras; ++i) + { + mIsConverted[i] = false; + } + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + int nextElement = mNumPoints; + std::map indexMap; + + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + + int numChildren = static_cast(outer->child.size()); + int numVertices; + int const* indices; + + if (numChildren == 0) + { + // The outer polygon is a simple polygon (no nested inner + // polygons). Triangulate the simple polygon. + numVertices = static_cast(outer->polygon.size()); + indices = outer->polygon.data(); + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + } + else + { + // Place the next level of outer polygon nodes on the queue for + // triangulation. + std::vector inners(numChildren); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + inners[c] = inner->polygon; + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + + // Combine the outer polygon and the inner polygons into a + // simple polygon by inserting two edges per inner polygon + // connecting mutually visible vertices. + std::vector combined; + ProcessOuterAndInners(nextElement, outer->polygon, inners, indexMap, combined); + + // The combined polygon is now in the format of a simple + // polygon, albeit with coincident edges. + numVertices = static_cast(combined.size()); + indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + } + } + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } +} + +template +void TriangulateEC::InitializeVertices(int numVertices, int const* indices) +{ + mVertices.clear(); + mVertices.resize(numVertices); + mCFirst = -1; + mCLast = -1; + mRFirst = -1; + mRLast = -1; + mEFirst = -1; + mELast = -1; + + // Create a circular list of the polygon vertices for dynamic removal of + // vertices. + int numVerticesM1 = numVertices - 1; + for (int i = 0; i <= numVerticesM1; ++i) + { + Vertex& vertex = V(i); + vertex.index = (indices ? indices[i] : i); + vertex.vPrev = (i > 0 ? i - 1 : numVerticesM1); + vertex.vNext = (i < numVerticesM1 ? i + 1 : 0); + } + + // Create a circular list of the polygon vertices for dynamic removal of + // vertices. Keep track of two linear sublists, one for the convex + // vertices and one for the reflex vertices. This is an O(N) process + // where N is the number of polygon vertices. + for (int i = 0; i <= numVerticesM1; ++i) + { + if (IsConvex(i)) + { + InsertAfterC(i); + } + else + { + InsertAfterR(i); + } + } +} + +template +void TriangulateEC::DoEarClipping(int numVertices, int const* indices) +{ + // If the polygon is convex, just create a triangle fan. + if (mRFirst == -1) + { + int numVerticesM1 = numVertices - 1; + if (indices) + { + for (int i = 1; i < numVerticesM1; ++i) + { + mTriangles.push_back({ { indices[0], indices[i], indices[i + 1] } }); + } + } + else + { + for (int i = 1; i < numVerticesM1; ++i) + { + mTriangles.push_back({ { 0, i, i + 1 } }); + } + } + return; + } + + // Identify the ears and build a circular list of them. Let V0, V1, and + // V2 be consecutive vertices forming a triangle T. The vertex V1 is an + // ear if no other vertices of the polygon lie inside T. Although it is + // enough to show that V1 is not an ear by finding at least one other + // vertex inside T, it is sufficient to search only the reflex vertices. + // This is an O(C*R) process, where C is the number of convex vertices and + // R is the number of reflex vertices with N = C+R. The order is O(N^2), + // for example when C = R = N/2. + for (int i = mCFirst; i != -1; i = V(i).sNext) + { + if (IsEar(i)) + { + InsertEndE(i); + } + } + V(mEFirst).ePrev = mELast; + V(mELast).eNext = mEFirst; + + // Remove the ears, one at a time. + bool bRemoveAnEar = true; + while (bRemoveAnEar) + { + // Add the triangle with the ear to the output list of triangles. + int iVPrev = V(mEFirst).vPrev; + int iVNext = V(mEFirst).vNext; + mTriangles.push_back({ { V(iVPrev).index, V(mEFirst).index, V(iVNext).index } }); + + // Remove the vertex corresponding to the ear. + RemoveV(mEFirst); + if (--numVertices == 3) + { + // Only one triangle remains, just remove the ear and copy it. + mEFirst = RemoveE(mEFirst); + iVPrev = V(mEFirst).vPrev; + iVNext = V(mEFirst).vNext; + mTriangles.push_back({ { V(iVPrev).index, V(mEFirst).index, V(iVNext).index } }); + bRemoveAnEar = false; + continue; + } + + // Removal of the ear can cause an adjacent vertex to become an ear + // or to stop being an ear. + Vertex& vPrev = V(iVPrev); + if (vPrev.isEar) + { + if (!IsEar(iVPrev)) + { + RemoveE(iVPrev); + } + } + else + { + bool wasReflex = !vPrev.isConvex; + if (IsConvex(iVPrev)) + { + if (wasReflex) + { + RemoveR(iVPrev); + } + + if (IsEar(iVPrev)) + { + InsertBeforeE(iVPrev); + } + } + } + + Vertex& vNext = V(iVNext); + if (vNext.isEar) + { + if (!IsEar(iVNext)) + { + RemoveE(iVNext); + } + } + else + { + bool wasReflex = !vNext.isConvex; + if (IsConvex(iVNext)) + { + if (wasReflex) + { + RemoveR(iVNext); + } + + if (IsEar(iVNext)) + { + InsertAfterE(iVNext); + } + } + } + + // Remove the ear. + mEFirst = RemoveE(mEFirst); + } +} + +template +bool TriangulateEC::CombinePolygons(int nextElement, + Polygon const& outer, Polygon const& inner, std::map& indexMap, + std::vector& combined) +{ + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + + // Locate the inner-polygon vertex of maximum x-value, call this vertex M. + ComputeType xmax = mComputePoints[innerIndices[0]][0]; + int xmaxIndex = 0; + for (int i = 1; i < numInnerIndices; ++i) + { + ComputeType x = mComputePoints[innerIndices[i]][0]; + if (x > xmax) + { + xmax = x; + xmaxIndex = i; + } + } + Vector2 M = mComputePoints[innerIndices[xmaxIndex]]; + + // Find the edge whose intersection Intr with the ray M+t*(1,0) minimizes + // the ray parameter t >= 0. + ComputeType const cmax = static_cast(std::numeric_limits::max()); + ComputeType const zero = static_cast(0); + Vector2 intr{ cmax, M[1] }; + int v0min = -1, v1min = -1, endMin = -1; + int i0, i1; + ComputeType s = cmax; + ComputeType t = cmax; + for (i0 = numOuterIndices - 1, i1 = 0; i1 < numOuterIndices; i0 = i1++) + { + // Consider only edges for which the first vertex is below (or on) the + // ray and the second vertex is above (or on) the ray. + Vector2 diff0 = mComputePoints[outerIndices[i0]] - M; + if (diff0[1] > zero) + { + continue; + } + Vector2 diff1 = mComputePoints[outerIndices[i1]] - M; + if (diff1[1] < zero) + { + continue; + } + + // At this time, diff0.y <= 0 and diff1.y >= 0. + int currentEndMin = -1; + if (diff0[1] < zero) + { + if (diff1[1] > zero) + { + // The intersection of the edge and ray occurs at an interior + // edge point. + s = diff0[1] / (diff0[1] - diff1[1]); + t = diff0[0] + s * (diff1[0] - diff0[0]); + } + else // diff1.y == 0 + { + // The vertex Outer[i1] is the intersection of the edge and + // the ray. + t = diff1[0]; + currentEndMin = i1; + } + } + else // diff0.y == 0 + { + if (diff1[1] > zero) + { + // The vertex Outer[i0] is the intersection of the edge and + // the ray; + t = diff0[0]; + currentEndMin = i0; + } + else // diff1.y == 0 + { + if (diff0[0] < diff1[0]) + { + t = diff0[0]; + currentEndMin = i0; + } + else + { + t = diff1[0]; + currentEndMin = i1; + } + } + } + + if (zero <= t && t < intr[0]) + { + intr[0] = t; + v0min = i0; + v1min = i1; + if (currentEndMin == -1) + { + // The current closest point is an edge-interior point. + endMin = -1; + } + else + { + // The current closest point is a vertex. + endMin = currentEndMin; + } + } + else if (t == intr[0]) + { + // The current closest point is a vertex shared by multiple edges; + // thus, endMin and currentMin refer to the same point. + if (endMin == -1 || currentEndMin == -1) + { + LogError("Unexpected condition."); + return false; + } + + // We need to select the edge closest to M. The previous closest + // edge is . The current candidate is + // . + Vector2 shared = mComputePoints[outerIndices[i1]]; + + // For the previous closest edge, endMin refers to a vertex of + // the edge. Get the index of the other vertex. + int other = (endMin == v0min ? v1min : v0min); + + // The new edge is closer if the other vertex of the old edge is + // left-of the new edge. + diff0 = mComputePoints[outerIndices[i0]] - shared; + diff1 = mComputePoints[outerIndices[other]] - shared; + ComputeType dotperp = DotPerp(diff0, diff1); + if (dotperp > zero) + { + // The new edge is closer to M. + v0min = i0; + v1min = i1; + endMin = currentEndMin; + } + } + } + + // The intersection intr[0] stored only the t-value of the ray. The + // actual point is (mx,my)+t*(1,0), so intr[0] must be adjusted. + intr[0] += M[0]; + + int maxCosIndex; + if (endMin == -1) + { + // If you reach this assert, there is a good chance that you have two + // inner polygons that share a vertex or an edge. + if (v0min < 0 || v1min < 0) + { + LogError("Is this an invalid nested polygon?"); + return false; + } + + // Select one of Outer[v0min] and Outer[v1min] that has an x-value + // larger than M.x, call this vertex P. The triangle must + // contain an outer-polygon vertex that is visible to M, which is + // possibly P itself. + Vector2 sTriangle[3]; // or + int pIndex; + if (mComputePoints[outerIndices[v0min]][0] > mComputePoints[outerIndices[v1min]][0]) + { + sTriangle[0] = mComputePoints[outerIndices[v0min]]; + sTriangle[1] = intr; + sTriangle[2] = M; + pIndex = v0min; + } + else + { + sTriangle[0] = mComputePoints[outerIndices[v1min]]; + sTriangle[1] = M; + sTriangle[2] = intr; + pIndex = v1min; + } + + // If any outer-polygon vertices other than P are inside the triangle + // , then at least one of these vertices must be a reflex + // vertex. It is sufficient to locate the reflex vertex R (if any) + // in that minimizes the angle between R-M and (1,0). The + // data member mQuery is used for the reflex query. + Vector2 diff = sTriangle[0] - M; + ComputeType maxSqrLen = Dot(diff, diff); + ComputeType maxCos = diff[0] * diff[0] / maxSqrLen; + PrimalQuery2 localQuery(3, sTriangle); + maxCosIndex = pIndex; + for (int i = 0; i < numOuterIndices; ++i) + { + if (i == pIndex) + { + continue; + } + + int curr = outerIndices[i]; + int prev = outerIndices[(i + numOuterIndices - 1) % numOuterIndices]; + int next = outerIndices[(i + 1) % numOuterIndices]; + if (mQuery.ToLine(curr, prev, next) <= 0 + && localQuery.ToTriangle(mComputePoints[curr], 0, 1, 2) <= 0) + { + // The vertex is reflex and inside the triangle. + diff = mComputePoints[curr] - M; + ComputeType sqrLen = Dot(diff, diff); + ComputeType cs = diff[0] * diff[0] / sqrLen; + if (cs > maxCos) + { + // The reflex vertex forms a smaller angle with the + // positive x-axis, so it becomes the new visible + // candidate. + maxSqrLen = sqrLen; + maxCos = cs; + maxCosIndex = i; + } + else if (cs == maxCos && sqrLen < maxSqrLen) + { + // The reflex vertex has angle equal to the current + // minimum but the length is smaller, so it becomes the + // new visible candidate. + maxSqrLen = sqrLen; + maxCosIndex = i; + } + } + } + } + else + { + maxCosIndex = endMin; + } + + // The visible vertices are Position[Inner[xmaxIndex]] and + // Position[Outer[maxCosIndex]]. Two coincident edges with these + // endpoints are inserted to connect the outer and inner polygons into a + // simple polygon. Each of the two Position[] values must be duplicated, + // because the original might be convex (or reflex) and the duplicate is + // reflex (or convex). The ear-clipping algorithm needs to distinguish + // between them. + combined.resize(numOuterIndices + numInnerIndices + 2); + int cIndex = 0; + for (int i = 0; i <= maxCosIndex; ++i, ++cIndex) + { + combined[cIndex] = outerIndices[i]; + } + + for (int i = 0; i < numInnerIndices; ++i, ++cIndex) + { + int j = (xmaxIndex + i) % numInnerIndices; + combined[cIndex] = innerIndices[j]; + } + + int innerIndex = innerIndices[xmaxIndex]; + mComputePoints[nextElement] = mComputePoints[innerIndex]; + combined[cIndex] = nextElement; + auto iter = indexMap.find(innerIndex); + if (iter != indexMap.end()) + { + innerIndex = iter->second; + } + indexMap[nextElement] = innerIndex; + ++cIndex; + ++nextElement; + + int outerIndex = outerIndices[maxCosIndex]; + mComputePoints[nextElement] = mComputePoints[outerIndex]; + combined[cIndex] = nextElement; + iter = indexMap.find(outerIndex); + if (iter != indexMap.end()) + { + outerIndex = iter->second; + } + indexMap[nextElement] = outerIndex; + ++cIndex; + ++nextElement; + + for (int i = maxCosIndex + 1; i < numOuterIndices; ++i, ++cIndex) + { + combined[cIndex] = outerIndices[i]; + } + return true; +} + +template +bool TriangulateEC::ProcessOuterAndInners(int& nextElement, + Polygon const& outer, std::vector const& inners, + std::map& indexMap, std::vector& combined) +{ + // Sort the inner polygons based on maximum x-values. + int numInners = static_cast(inners.size()); + std::vector> pairs(numInners); + for (int p = 0; p < numInners; ++p) + { + int numIndices = static_cast(inners[p].size()); + int const* indices = inners[p].data(); + ComputeType xmax = mComputePoints[indices[0]][0]; + for (int j = 1; j < numIndices; ++j) + { + ComputeType x = mComputePoints[indices[j]][0]; + if (x > xmax) + { + xmax = x; + } + } + pairs[p].first = xmax; + pairs[p].second = p; + } + std::sort(pairs.begin(), pairs.end()); + + // Merge the inner polygons with the outer polygon. + Polygon currentPolygon = outer; + for (int p = numInners - 1; p >= 0; --p) + { + Polygon const& polygon = inners[pairs[p].second]; + Polygon currentCombined; + if (!CombinePolygons(nextElement, currentPolygon, polygon, indexMap, currentCombined)) + { + return false; + } + currentPolygon = std::move(currentCombined); + nextElement += 2; + } + + for (auto index : currentPolygon) + { + combined.push_back(index); + } + return true; +} + +template +void TriangulateEC::RemapIndices(std::map const& indexMap) +{ + // The triangulation includes indices to the duplicated outer and inner + // vertices. These indices must be mapped back to the original ones. + for (auto& tri : mTriangles) + { + for (int i = 0; i < 3; ++i) + { + auto iter = indexMap.find(tri[i]); + if (iter != indexMap.end()) + { + tri[i] = iter->second; + } + } + } +} + +template +int TriangulateEC::InitializeFromTree(std::shared_ptr const& tree) +{ + // Use a breadth-first search to process the outer-inners pairs of the + // tree of nested polygons. + int numExtraPoints = 0; + + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + // The 'root' is an outer polygon. + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + + // Count number of extra points for this outer-inners pair. + int numChildren = static_cast(outer->child.size()); + numExtraPoints += 2 * numChildren; + + // Convert outer points from InputType to ComputeType. + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // The grandchildren of the outer polygon are also outer polygons. + // Insert them into the queue for processing. + for (int c = 0; c < numChildren; ++c) + { + // The 'child' is an inner polygon. + std::shared_ptr inner = outer->child[c]; + + // Convert inner points from InputType to ComputeType. + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + + return numExtraPoints; +} + +template +TriangulateEC::Vertex::Vertex() + : + index(-1), + vPrev(-1), + vNext(-1), + sPrev(-1), + sNext(-1), + ePrev(-1), + eNext(-1), + isConvex(false), + isEar(false) +{ +} + +template inline +typename TriangulateEC::Vertex& TriangulateEC::V(int i) +{ + return mVertices[i]; +} + +template +bool TriangulateEC::IsConvex(int i) +{ + Vertex& vertex = V(i); + int curr = vertex.index; + int prev = V(vertex.vPrev).index; + int next = V(vertex.vNext).index; + vertex.isConvex = (mQuery.ToLine(curr, prev, next) > 0); + return vertex.isConvex; +} + +template +bool TriangulateEC::IsEar(int i) +{ + Vertex& vertex = V(i); + + if (mRFirst == -1) + { + // The remaining polygon is convex. + vertex.isEar = true; + return true; + } + + // Search the reflex vertices and test if any are in the triangle + // . + int prev = V(vertex.vPrev).index; + int curr = vertex.index; + int next = V(vertex.vNext).index; + vertex.isEar = true; + for (int j = mRFirst; j != -1; j = V(j).sNext) + { + // Check if the test vertex is already one of the triangle vertices. + if (j == vertex.vPrev || j == i || j == vertex.vNext) + { + continue; + } + + // V[j] has been ruled out as one of the original vertices of the + // triangle . When triangulating polygons + // with holes, V[j] might be a duplicated vertex, in which case it + // does not affect the earness of V[curr]. + int test = V(j).index; + if (mComputePoints[test] == mComputePoints[prev] + || mComputePoints[test] == mComputePoints[curr] + || mComputePoints[test] == mComputePoints[next]) + { + continue; + } + + // Test if the vertex is inside or on the triangle. When it is, it + // causes V[curr] not to be an ear. + if (mQuery.ToTriangle(test, prev, curr, next) <= 0) + { + vertex.isEar = false; + break; + } + } + + return vertex.isEar; +} + +template +void TriangulateEC::InsertAfterC(int i) +{ + if (mCFirst == -1) + { + // Add the first convex vertex. + mCFirst = i; + } + else + { + V(mCLast).sNext = i; + V(i).sPrev = mCLast; + } + mCLast = i; +} + +template +void TriangulateEC::InsertAfterR(int i) +{ + if (mRFirst == -1) + { + // Add the first reflex vertex. + mRFirst = i; + } + else + { + V(mRLast).sNext = i; + V(i).sPrev = mRLast; + } + mRLast = i; +} + +template +void TriangulateEC::InsertEndE(int i) +{ + if (mEFirst == -1) + { + // Add the first ear. + mEFirst = i; + mELast = i; + } + V(mELast).eNext = i; + V(i).ePrev = mELast; + mELast = i; +} + +template +void TriangulateEC::InsertAfterE(int i) +{ + Vertex& first = V(mEFirst); + int currENext = first.eNext; + Vertex& vertex = V(i); + vertex.ePrev = mEFirst; + vertex.eNext = currENext; + first.eNext = i; + V(currENext).ePrev = i; +} + +template +void TriangulateEC::InsertBeforeE(int i) +{ + Vertex& first = V(mEFirst); + int currEPrev = first.ePrev; + Vertex& vertex = V(i); + vertex.ePrev = currEPrev; + vertex.eNext = mEFirst; + first.ePrev = i; + V(currEPrev).eNext = i; +} + +template +void TriangulateEC::RemoveV(int i) +{ + int currVPrev = V(i).vPrev; + int currVNext = V(i).vNext; + V(currVPrev).vNext = currVNext; + V(currVNext).vPrev = currVPrev; +} + +template +int TriangulateEC::RemoveE(int i) +{ + int currEPrev = V(i).ePrev; + int currENext = V(i).eNext; + V(currEPrev).eNext = currENext; + V(currENext).ePrev = currEPrev; + return currENext; +} + +template +void TriangulateEC::RemoveR(int i) +{ + LogAssert(mRFirst != -1 && mRLast != -1, "Reflex vertices must exist."); + + if (i == mRFirst) + { + mRFirst = V(i).sNext; + if (mRFirst != -1) + { + V(mRFirst).sPrev = -1; + } + V(i).sNext = -1; + } + else if (i == mRLast) + { + mRLast = V(i).sPrev; + if (mRLast != -1) + { + V(mRLast).sNext = -1; + } + V(i).sPrev = -1; + } + else + { + int currSPrev = V(i).sPrev; + int currSNext = V(i).sNext; + V(currSPrev).sNext = currSNext; + V(currSNext).sPrev = currSPrev; + V(i).sNext = -1; + V(i).sPrev = -1; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTubeMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTubeMesh.h new file mode 100644 index 000000000000..0ef0f17d6133 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteTubeMesh.h @@ -0,0 +1,259 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.6 (2018/10/05) + +#pragma once + +#include +#include +#include +#include + +namespace gte +{ + +template +class TubeMesh : public Mesh +{ +public: + // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified medial + // curve and radial function. The mesh has torus topology when 'closed' + // is true and has cylinder topology when 'closed' is false. The client + // is responsible for setting the topology correctly in the 'description' + // input. The rows correspond to medial samples and the columns + // correspond to radial samples. The medial curve is sampled according + // to its natural t-parameter when 'sampleByArcLength' is false; + // otherwise, it is sampled uniformly in arclength. + TubeMesh(MeshDescription const& description, + std::shared_ptr> const& medial, + std::function const& radial, bool closed, + bool sampleByArcLength, Vector3 upVector); + + // Member access. + inline std::shared_ptr> const& GetMedial() const; + inline std::function const& GetRadial() const; + inline bool IsClosed() const; + inline bool IsSampleByArcLength() const; + inline Vector3 const& GetUpVector() const; + +private: + void InitializeTCoords(); + virtual void UpdatePositions() override; + + std::shared_ptr> mMedial; + std::function mRadial; + bool mClosed, mSampleByArcLength; + Vector3 mUpVector; + std::vector mCosAngle, mSinAngle; + std::function mTSampler; + std::function, 4>(Real)> mFSampler; + std::unique_ptr> mFrenet; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; +}; + + +template +TubeMesh::TubeMesh(MeshDescription const& description, + std::shared_ptr> const& medial, + std::function const& radial, bool closed, bool sampleByArcLength, + Vector3 upVector) + : + Mesh(description, { MeshTopology::CYLINDER }), // TODO: Allow TORUS and remove the 'closed' input + mMedial(medial), + mRadial(radial), + mClosed(closed), + mSampleByArcLength(sampleByArcLength), + mUpVector(upVector) +{ + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh constructor. + mMedial = nullptr; + return; + } + + if (!mMedial) + { + LogWarning("A nonnull medial curve is required."); + this->mDescription.constructed = false; + return; + } + + mCosAngle.resize(this->mDescription.numCols); + mSinAngle.resize(this->mDescription.numCols); + Real invRadialSamples = (Real)1 / (Real)(this->mDescription.numCols - 1); + for (unsigned int i = 0; i < this->mDescription.numCols - 1; ++i) + { + Real angle = i * invRadialSamples * (Real)GTE_C_TWO_PI; + mCosAngle[i] = std::cos(angle); + mSinAngle[i] = std::sin(angle); + } + mCosAngle[this->mDescription.numCols - 1] = mCosAngle[0]; + mSinAngle[this->mDescription.numCols - 1] = mSinAngle[0]; + + Real invDenom; + if (mClosed) + { + invDenom = ((Real)1) / (Real)this->mDescription.numRows; + } + else + { + invDenom = ((Real)1) / (Real)(this->mDescription.numRows - 1); + } + + Real factor; + if (mSampleByArcLength) + { + factor = mMedial->GetTotalLength() * invDenom; + mTSampler = [this, factor](unsigned int row) + { + return mMedial->GetTime(row * factor); + }; + } + else + { + factor = (mMedial->GetTMax() - mMedial->GetTMin()) * invDenom; + mTSampler = [this, factor](unsigned int row) + { + return mMedial->GetTMin() + row * factor; + }; + } + + if (mUpVector != Vector3::Zero()) + { + mFSampler = [this](Real t) + { + std::array, 4> frame; + frame[0] = mMedial->GetPosition(t); + frame[1] = mMedial->GetTangent(t); + frame[3] = UnitCross(frame[1], mUpVector); + frame[2] = UnitCross(frame[3], frame[1]); + return frame; + }; + } + else + { + mFrenet = std::make_unique>(mMedial); + mFSampler = [this](Real t) + { + std::array, 4> frame; + (*mFrenet)(t, frame[0], frame[1], frame[2], frame[3]); + return frame; + }; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + UpdatePositions(); + if (this->mDescription.allowUpdateFrame) + { + this->UpdateFrame(); + } + else if (this->mNormals) + { + this->UpdateNormals(); + } +} + +template +inline std::shared_ptr> const& TubeMesh::GetMedial() const +{ + return mMedial; +} + +template +inline std::function const& TubeMesh::GetRadial() const +{ + return mRadial; +} + +template +inline bool TubeMesh::IsClosed() const +{ + return mClosed; +} + +template +inline bool TubeMesh::IsSampleByArcLength() const +{ + return mSampleByArcLength; +} + +template +inline Vector3 const& TubeMesh::GetUpVector() const +{ + return mUpVector; +} + +template +void TubeMesh::InitializeTCoords() +{ + Vector2tcoord; + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)this->mDescription.rMax; + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } +} + +template +void TubeMesh::UpdatePositions() +{ + uint32_t row, col, v, save; + for (row = 0, v = 0; row < this->mDescription.numRows; ++row, ++v) + { + Real t = mTSampler(row); + Real radius = mRadial(t); + // frame = (position, tangent, normal, binormal) + std::array, 4> frame = mFSampler(t); + for (col = 0, save = v; col < this->mDescription.numCols; ++col, ++v) + { + this->Position(v) = frame[0] + radius * (mCosAngle[col] * frame[2] + + mSinAngle[col] * frame[3]); + } + this->Position(v) = this->Position(save); + } + + if (mClosed) + { + for (col = 0; col < this->mDescription.numCols; ++col) + { + uint32_t i0 = col; + uint32_t i1 = col + this->mDescription.numCols * (this->mDescription.numRows - 1); + this->Position(i1) = this->Position(i0); + } + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerALU32.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerALU32.h new file mode 100644 index 000000000000..7e6909a80dae --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerALU32.h @@ -0,0 +1,562 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#pragma once + +#include +#include + +// Support for unsigned integer arithmetic in BSNumber and BSRational. The +// Curiously Recurring Template Paradigm is used to allow the UInteger +// types to share code without introducing virtual functions. + +namespace gte +{ + +template +class UIntegerALU32 +{ +public: + // Comparisons. These are not generic. They rely on their being caled + // when the two BSNumber arguments to BSNumber::operatorX() are of the + // form 1.u*2^p and 1.v*2^p. The comparisons apply to 1.u and 1.v as + // unsigned integers with their leading 1-bits aligned. + bool operator==(UInteger const& number) const; + bool operator!=(UInteger const& number) const; + bool operator< (UInteger const& number) const; + bool operator<=(UInteger const& number) const; + bool operator> (UInteger const& number) const; + bool operator>=(UInteger const& number) const; + + // Arithmetic operations. These are performed in-place; that is, the + // result is stored in 'this' object. The goal is to reduce the number of + // object copies, much like the goal is for std::move. The Sub function + // requires the inputs to satisfy n0 > n1. + void Add(UInteger const& n0, UInteger const& n1); + void Sub(UInteger const& n0, UInteger const& n1); + void Mul(UInteger const& n0, UInteger const& n1); + + // The shift is performed in-place; that is, the result is stored in + // 'this' object. + void ShiftLeft(UInteger const& number, int32_t shift); + + // The 'number' is even and positive. It is shifted right to become an + // odd number and the return value is the amount shifted. The operation + // is performed in-place; that is, the result is stored in 'this' object. + int32_t ShiftRightToOdd(UInteger const& number); + + // Get a block of numRequested bits starting with the leading 1-bit of the + // nonzero number. The returned number has the prefix stored in the + // high-order bits. Additional bits are copied and used by the caller for + // rounding. This function supports conversions from 'float' and 'double'. + // The input 'numRequested' is smaller than 64. + uint64_t GetPrefix(int32_t numRequested) const; +}; + + +template +bool UIntegerALU32::operator==(UInteger const& number) const +{ + UInteger const& self = *(UInteger const*)this; + int32_t numBits = self.GetNumBits(); + if (numBits != number.GetNumBits()) + { + return false; + } + + if (numBits > 0) + { + auto const& bits = self.GetBits(); + auto const& nBits = number.GetBits(); + int32_t const last = self.GetSize() - 1; + for (int32_t i = last; i >= 0; --i) + { + if (bits[i] != nBits[i]) + { + return false; + } + } + } + return true; +} + +template +bool UIntegerALU32::operator!=(UInteger const& number) const +{ + return !operator==(number); +} + +template +bool UIntegerALU32::operator< (UInteger const& number) const +{ + UInteger const& self = *(UInteger const*)this; + int32_t nNumBits = number.GetNumBits(); + auto const& nBits = number.GetBits(); + + int32_t numBits = self.GetNumBits(); + if (numBits > 0 && nNumBits > 0) + { + // The numbers must be compared as if they are left-aligned with + // each other. We got here because we had self = 1.u * 2^p and + // number = 1.v * 2^p. Although they have the same exponent, it is + // possible that 'self < number' but 'numBits(1u) > numBits(1v)'. + // Compare the bits one 32-bit block at a time. + auto const& bits = self.GetBits(); + int bitIndex0 = numBits - 1; + int bitIndex1 = nNumBits - 1; + int block0 = bitIndex0 / 32; + int block1 = bitIndex1 / 32; + int numBlockBits0 = 1 + (bitIndex0 % 32); + int numBlockBits1 = 1 + (bitIndex1 % 32); + uint64_t n0shift = bits[block0]; + uint64_t n1shift = nBits[block1]; + while (block0 >= 0 && block1 >= 0) + { + // Shift the bits in the leading blocks to the high-order bit. + uint32_t value0 = (uint32_t)((n0shift << (32 - numBlockBits0)) & 0x00000000FFFFFFFFull); + uint32_t value1 = (uint32_t)((n1shift << (32 - numBlockBits1)) & 0x00000000FFFFFFFFull); + + // Shift bits in the next block (if any) to fill the current + // block. + if (--block0 >= 0) + { + n0shift = bits[block0]; + value0 |= (uint32_t)((n0shift >> numBlockBits0) & 0x00000000FFFFFFFFull); + } + if (--block1 >= 0) + { + n1shift = nBits[block1]; + value1 |= (uint32_t)((n1shift >> numBlockBits1) & 0x00000000FFFFFFFFull); + } + if (value0 < value1) + { + return true; + } + if (value0 > value1) + { + return false; + } + } + return block0 < block1; + } + else + { + // One or both numbers are negative. The only time 'less than' is + // 'true' is when 'number' is positive. + return (nNumBits > 0); + } +} + +template +bool UIntegerALU32::operator<=(UInteger const& number) const +{ + return operator<(number) || operator==(number); +} + +template +bool UIntegerALU32::operator> (UInteger const& number) const +{ + return !operator<=(number); +} + +template +bool UIntegerALU32::operator>=(UInteger const& number) const +{ + return !operator<(number); +} + +template +void UIntegerALU32::Add(UInteger const& n0, UInteger const& n1) +{ + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + int32_t n1NumBits = n1.GetNumBits(); + + // Add the numbers considered as positive integers. Set the last block to + // zero in case no carry-out occurs. + int numBits = std::max(n0NumBits, n1NumBits) + 1; + self.SetNumBits(numBits); + self.SetBack(0); + + // Get the input array sizes. + int32_t numElements0 = n0.GetSize(); + int32_t numElements1 = n1.GetSize(); + + // Order the inputs so that the first has the most blocks. + auto const& u0 = + (numElements0 >= numElements1 ? n0.GetBits() : n1.GetBits()); + auto const& u1 = + (numElements0 >= numElements1 ? n1.GetBits() : n0.GetBits()); + auto numElements = std::minmax(numElements0, numElements1); + + // Add the u1-blocks to u0-blocks. + auto& bits = self.GetBits(); + uint64_t carry = 0, sum; + int32_t i; + for (i = 0; i < numElements.first; ++i) + { + sum = u0[i] + (u1[i] + carry); + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // We have no more u1-blocks. Propagate the carry-out, if there is one, or + // copy the remaining blocks if there is not. + if (carry > 0) + { + for (/**/; i < numElements.second; ++i) + { + sum = u0[i] + carry; + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + if (carry > 0) + { + bits[i] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + } + else + { + for (/**/; i < numElements.second; ++i) + { + bits[i] = u0[i]; + } + } + + // Reduce the number of bits if there was not a carry-out. + uint32_t firstBitIndex = (numBits - 1) % 32; + uint32_t mask = (1 << firstBitIndex); + if ((mask & self.GetBack()) == 0) + { + self.SetNumBits(--numBits); + } +} + +template +void UIntegerALU32::Sub(UInteger const& n0, UInteger const& n1) +{ + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + auto const& n0Bits = n0.GetBits(); + auto const& n1Bits = n1.GetBits(); + + // Subtract the numbers considered as positive integers. We know that + // n0 > n1, so create a number n2 that has the same number of bits as + // n0 and use two's-complement to generate -n2, and then add n0 and -n2. + // The result is nonnegative, so we do not need to apply two's complement + // to a negative result to extract the sign and absolute value. + + // Get the input array sizes. We know numElements0 >= numElements1. + int32_t numElements0 = n0.GetSize(); + int32_t numElements1 = n1.GetSize(); + + // Create the two's-complement number n2. We know n2.GetNumElements() is + // the same as numElements0. + UInteger n2(n0NumBits); + auto& n2Bits = n2.GetBits(); + int32_t i; + for (i = 0; i < numElements1; ++i) + { + n2Bits[i] = ~n1Bits[i]; + } + for (/**/; i < numElements0; ++i) + { + n2Bits[i] = ~0u; + } + + // Now add 1 to the bit-negated result to obtain -n1. + uint64_t carry = 1, sum; + for (i = 0; i < numElements0; ++i) + { + sum = n2Bits[i] + carry; + n2Bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // Add the numbers as positive integers. Set the last block to zero in + // case no carry-out occurs. + self.SetNumBits(n0NumBits + 1); + self.SetBack(0); + + // Add the n0-blocks to n2-blocks. + auto& bits = self.GetBits(); + for (i = 0, carry = 0; i < numElements0; ++i) + { + sum = n2Bits[i] + (n0Bits[i] + carry); + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // Strip off the bits introduced by two's-complement. + int32_t block; + for (block = numElements0 - 1; block >= 0; --block) + { + if (bits[block] > 0) + { + break; + } + } + + // Added this condition to allow n0 == n1 and satisfy the static analyzer + if (block < 0) + { + self = 0; + } + else + { + self.SetNumBits(32 * block + GetLeadingBit(bits[block]) + 1); + } +} + +template +void UIntegerALU32::Mul(UInteger const& n0, UInteger const& n1) +{ + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + int32_t n1NumBits = n1.GetNumBits(); + auto const& n0Bits = n0.GetBits(); + auto const& n1Bits = n1.GetBits(); + + // The number of bits is at most this, possibly one bit smaller. + int numBits = n0NumBits + n1NumBits; + self.SetNumBits(numBits); + auto& bits = self.GetBits(); + + // Product of a single-block number with a multiple-block number. + UInteger product(numBits); + auto& pBits = product.GetBits(); + + // Get the array sizes. + int32_t const numElements0 = n0.GetSize(); + int32_t const numElements1 = n1.GetSize(); + int32_t const numElements = self.GetSize(); + + // Compute the product v = u0*u1. + int32_t i0, i1, i2; + uint64_t term, sum; + + // The case i0 == 0 is handled separately to initialize the accumulator + // with u0[0]*v. This avoids having to fill the bytes of 'bits' with + // zeros outside the double loop, something that can be a performance + // issue when 'numBits' is large. + uint64_t block0 = n0Bits[0]; + uint64_t carry = 0; + for (i1 = 0; i1 < numElements1; ++i1) + { + term = block0 * n1Bits[i1] + carry; + bits[i1] = (uint32_t)(term & 0x00000000FFFFFFFFull); + carry = (term >> 32); + } + if (i1 < numElements) + { + bits[i1] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + + for (i0 = 1; i0 < numElements0; ++i0) + { + // Compute the product p = u0[i0]*u1. + block0 = n0Bits[i0]; + carry = 0; + for (i1 = 0, i2 = i0; i1 < numElements1; ++i1, ++i2) + { + term = block0 * n1Bits[i1] + carry; + pBits[i2] = (uint32_t)(term & 0x00000000FFFFFFFFull); + carry = (term >> 32); + } + if (i2 < numElements) + { + pBits[i2] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + + // Add p to the accumulator v. + carry = 0; + for (i1 = 0, i2 = i0; i1 < numElements1; ++i1, ++i2) + { + sum = pBits[i2] + (bits[i2] + carry); + bits[i2] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + if (i2 < numElements) + { + sum = pBits[i2] + carry; + bits[i2] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + } + } + + // Reduce the number of bits if there was not a carry-out. + uint32_t firstBitIndex = (numBits - 1) % 32; + uint32_t mask = (1 << firstBitIndex); + if ((mask & self.GetBack()) == 0) + { + self.SetNumBits(--numBits); + } +} + +template +void UIntegerALU32::ShiftLeft(UInteger const& number, int32_t shift) +{ + UInteger& self = *(UInteger*)this; + int32_t nNumBits = number.GetNumBits(); + auto const& nBits = number.GetBits(); + + // Shift the 'number' considered as an odd positive integer. + self.SetNumBits(nNumBits + shift); + + // Set the low-order bits to zero. + auto& bits = self.GetBits(); + int32_t const shiftBlock = shift / 32; + for (int32_t i = 0; i < shiftBlock; ++i) + { + bits[i] = 0; + } + + // Get the location of the low-order 1-bit within the result. + int32_t const numInElements = number.GetSize(); + int32_t const lshift = shift % 32; + int32_t i, j; + if (lshift > 0) + { + // The trailing 1-bits for source and target are at different + // relative indices. Each shifted source block straddles a boundary + // between two target blocks, so we must extract the subblocks and + // copy accordingly. + int32_t const rshift = 32 - lshift; + uint32_t prev = 0, curr; + for (i = shiftBlock, j = 0; j < numInElements; ++i, ++j) + { + curr = nBits[j]; + bits[i] = (curr << lshift) | (prev >> rshift); + prev = curr; + } + if (i < self.GetSize()) + { + // The leading 1-bit of the source is at a relative index such + // that when you add the shift amount, that bit occurs in a new + // block. + bits[i] = (prev >> rshift); + } + } + else + { + // The trailing 1-bits for source and target are at the same relative + // index. The shift reduces to a block copy. + for (i = shiftBlock, j = 0; j < numInElements; ++i, ++j) + { + bits[i] = nBits[j]; + } + } +} + +template +int32_t UIntegerALU32::ShiftRightToOdd(UInteger const& number) +{ + UInteger& self = *(UInteger*)this; + auto const& nBits = number.GetBits(); + + // Get the leading 1-bit. + int32_t const numElements = number.GetSize(); + int32_t const numM1 = numElements - 1; + int32_t firstBitIndex = 32 * numM1 + GetLeadingBit(nBits[numM1]); + + // Get the trailing 1-bit. + int32_t lastBitIndex = -1; + for (int32_t block = 0; block < numElements; ++block) + { + uint32_t value = nBits[block]; + if (value > 0) + { + lastBitIndex = 32 * block + GetTrailingBit(value); + break; + } + } + + // The right-shifted result. + self.SetNumBits(firstBitIndex - lastBitIndex + 1); + auto& bits = self.GetBits(); + int32_t const numBlocks = self.GetSize(); + + // Get the location of the low-order 1-bit within the result. + int32_t const shiftBlock = lastBitIndex / 32; + int32_t rshift = lastBitIndex % 32; + if (rshift > 0) + { + int32_t const lshift = 32 - rshift; + int32_t i, j = shiftBlock; + uint32_t curr = nBits[j++]; + for (i = 0; j < numElements; ++i, ++j) + { + uint32_t next = nBits[j]; + bits[i] = (curr >> rshift) | (next << lshift); + curr = next; + } + if (i < numBlocks) + { + bits[i] = (curr >> rshift); + } + } + else + { + for (int32_t i = 0, j = shiftBlock; i < numBlocks; ++i, ++j) + { + bits[i] = nBits[j]; + } + } + + return rshift + 32 * shiftBlock; +} + +template +uint64_t UIntegerALU32::GetPrefix(int32_t numRequested) const +{ + UInteger const& self = *(UInteger const*)this; + auto const& bits = self.GetBits(); + + // Copy to 'prefix' the leading 32-bit block that is nonzero. + int32_t bitIndex = self.GetNumBits() - 1; + int32_t blockIndex = bitIndex / 32; + uint64_t prefix = bits[blockIndex]; + + // Get the number of bits in the block starting with the leading 1-bit. + int32_t firstBitIndex = bitIndex % 32; + int32_t numBlockBits = firstBitIndex + 1; + + // Shift the leading 1-bit to bit-63 of prefix. We have consumed + // numBlockBits, which might not be the entire budget. + int32_t targetIndex = 63; + prefix <<= targetIndex - firstBitIndex; + numRequested -= numBlockBits; + targetIndex -= numBlockBits; + + if (numRequested > 0 && --blockIndex >= 0) + { + // More bits are available. Copy and shift the entire 32-bit next + // block and OR it into the 'prefix'. For 'float', we will have + // consumed the entire budget. For 'double', we might have to get + // bits from a third block. + uint64_t nextBlock = bits[blockIndex]; + nextBlock <<= targetIndex - 31; // Shift amount is positive. + prefix |= nextBlock; + numRequested -= 32; + targetIndex -= 32; + + if (numRequested > 0 && --blockIndex >= 0) + { + // We know that targetIndex > 0; only 'double' allows us to get + // here, so numRequested is at most 53. We also know that + // targetIndex < 32 because we started with 63 and subtracted + // at least 32 from it. Thus, the shift amount is positive. + nextBlock = bits[blockIndex]; + nextBlock >>= 31 - targetIndex; + prefix |= nextBlock; + } + } + + return prefix; +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.cpp new file mode 100644 index 000000000000..342ad43e1a76 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.cpp @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#include +#include +#include +#include +using namespace gte; + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) +std::atomic UIntegerAP32::msMaxSize; +#endif + + +UIntegerAP32::UIntegerAP32() + : + mNumBits(0) +{ +} + +UIntegerAP32::UIntegerAP32(UIntegerAP32 const& number) +{ + *this = number; +} + +UIntegerAP32::UIntegerAP32(uint32_t number) +{ + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + mNumBits = first - last + 1; + mBits.resize(1); + mBits[0] = (number >> last); + } + else + { + mNumBits = 0; + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +UIntegerAP32::UIntegerAP32(uint64_t number) +{ + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + number >>= last; + mNumBits = first - last + 1; + mBits.resize(1 + (mNumBits - 1) / 32); + mBits[0] = (uint32_t)(number & 0x00000000FFFFFFFFull); + if (mBits.size() > 1) + { + mBits[1] = (uint32_t)((number >> 32) & 0x00000000FFFFFFFFull); + } + } + else + { + mNumBits = 0; + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +UIntegerAP32::UIntegerAP32(int numBits) + : + mNumBits(numBits), + mBits(1 + (numBits - 1) / 32) +{ +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +UIntegerAP32& UIntegerAP32::operator=(UIntegerAP32 const& number) +{ + mNumBits = number.mNumBits; + mBits = number.mBits; + return *this; +} + +UIntegerAP32::UIntegerAP32(UIntegerAP32&& number) +{ + *this = std::move(number); +} + +UIntegerAP32& UIntegerAP32::operator=(UIntegerAP32&& number) +{ + mNumBits = number.mNumBits; + mBits = std::move(number.mBits); + number.mNumBits = 0; + return *this; +} + +void UIntegerAP32::SetNumBits(uint32_t numBits) +{ + mNumBits = numBits; + if (mNumBits > 0) + { + mBits.resize(1 + (numBits - 1) / 32); + } + else + { + mBits.clear(); + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(msMaxSize, mBits.size()); +#endif +} + +bool UIntegerAP32::Write(std::ofstream& output) const +{ + if (output.write((char const*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + std::size_t size = mBits.size(); + if (output.write((char const*)&size, sizeof(size)).bad()) + { + return false; + } + + return output.write((char const*)&mBits[0], size*sizeof(mBits[0])).good(); +} + +bool UIntegerAP32::Read(std::ifstream& input) +{ + if (input.read((char*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + std::size_t size; + if (input.read((char*)&size, sizeof(size)).bad()) + { + return false; + } + + mBits.resize(size); + return input.read((char*)&mBits[0], size*sizeof(mBits[0])).good(); +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.h new file mode 100644 index 000000000000..8a65e0225c6d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerAP32.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +// Class UIntegerAP32 is designed to support arbitrary precision arithmetic +// using BSNumber and BSRational. It is not a general-purpose class for +// arithmetic of unsigned integers. + +// Uncomment this to collect statistics on how large the UIntegerAP32 storage +// becomes when using it for the UIntegerType of BSNumber. After a sequence +// of BSNumber operations, look at UIntegerAP32::msMaxSize in the debugger +// watch window. If the number is not too large, you might be safe in +// replacing UIntegerAP32 by UIntegerFP32, where N is the value of +// UIntegerAP32::msMaxSize. This leads to much faster code because you no +// you no longer have dynamic memory allocations and deallocations that occur +// regularly with std::vector during BSNumber operations. A safer +// choice is to argue mathematically that the maximum size is bounded by N. +// This requires an analysis of how many bits of precision you need for the +// types of computation you perform. See class BSPrecision for code that +// allows you to compute maximum N. +// +//#define GTE_COLLECT_UINTEGERAP32_STATISTICS + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) +#include +#endif + +namespace gte +{ + +class UIntegerAP32 : public UIntegerALU32 +{ +public: + // Construction. + UIntegerAP32(); + UIntegerAP32(UIntegerAP32 const& number); + UIntegerAP32(uint32_t number); + UIntegerAP32(uint64_t number); + UIntegerAP32(int numBits); + + // Assignment. + UIntegerAP32& operator=(UIntegerAP32 const& number); + + // Support for std::move. + UIntegerAP32(UIntegerAP32&& number); + UIntegerAP32& operator=(UIntegerAP32&& number); + + // Member access. + void SetNumBits(uint32_t numBits); + inline int32_t GetNumBits() const; + inline std::vector const& GetBits() const; + inline std::vector& GetBits(); + inline void SetBack(uint32_t value); + inline uint32_t GetBack() const; + inline int32_t GetSize() const; + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const; + bool Read(std::ifstream& input); + +private: + int32_t mNumBits; + std::vector mBits; + + friend class UnitTestBSNumber; + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + static std::atomic msMaxSize; +public: + static void SetMaxSizeToZero() { msMaxSize = 0; } + static size_t GetMaxSize() { return msMaxSize; } +#endif +}; + + +inline int32_t UIntegerAP32::GetNumBits() const +{ + return mNumBits; +} + +inline std::vector const& UIntegerAP32::GetBits() const +{ + return mBits; +} + +inline std::vector& UIntegerAP32::GetBits() +{ + return mBits; +} + +inline void UIntegerAP32::SetBack(uint32_t value) +{ + mBits.back() = value; +} + +inline uint32_t UIntegerAP32::GetBack() const +{ + return mBits.back(); +} + +inline int32_t UIntegerAP32::GetSize() const +{ + return static_cast(mBits.size()); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h new file mode 100644 index 000000000000..6743d8d218f0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h @@ -0,0 +1,301 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2017/07/04) + +#pragma once + +#include +#include +#include +#include + +// Class UIntegerFP32 is designed to support fixed precision arithmetic +// using BSNumber and BSRational. It is not a general-purpose class for +// arithmetic of unsigned integers. The template parameter N is the +// number of 32-bit words required to store the precision for the desired +// computations (maximum number of bits is 32*N). + +// Uncomment this to trap when an attempt is made to create storage with +// more than N uint32_t items. +// +//#define GTE_ASSERT_ON_UINTEGERFP32_OUT_OF_RANGE + +// Uncomment this to collect statistics on how large the UIntegerFP32 storage +// becomes when using it for the UIntegerType of BSNumber. After a sequence +// of BSNumber operations, look at UIntegerFP32::msMaxSize in the debugger +// watch window. If the number is not too large, you might be safe in +// replacing the template parameter N by a smaller number. See class +// BSPrecision for code that allows you to compute maximum N. +// +// NOTE: Because UIntegerFP32::msMaxSize is a static member of a template +// class, if you expose this define, you must also declare in your code +// 'std::atomic gte::UIntegerFP32::msMaxSize;' +//#define GTE_COLLECT_UINTEGERFP32_STATISTICS + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) +#include +#endif + +namespace gte +{ + +template +class UIntegerFP32 : public UIntegerALU32> +{ +public: + // Construction. + UIntegerFP32(); + UIntegerFP32(UIntegerFP32 const& number); + UIntegerFP32(uint32_t number); + UIntegerFP32(uint64_t number); + UIntegerFP32(int numBits); + + // Assignment. Only mSize elements are copied. + UIntegerFP32& operator=(UIntegerFP32 const& number); + + // Support for std::move. The interface is required by BSNumber, but the + // std::move of std::array is a copy (no pointer stealing). Moreover, + // a std::array object in this class typically uses smaller than N + // elements, the actual size stored in mSize, so we do not want to move + // everything. Therefore, the move operator only copies the bits BUT + // BUT 'number' is modified as if you have stolen the data (mNumBits and + // mSize set to zero). + UIntegerFP32(UIntegerFP32&& number); + UIntegerFP32& operator=(UIntegerFP32&& number); + + // Member access. + void SetNumBits(uint32_t numBits); + inline int32_t GetNumBits() const; + inline std::array const& GetBits() const; + inline std::array& GetBits(); + inline void SetBack(uint32_t value); + inline uint32_t GetBack() const; + inline int32_t GetSize() const; + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ofstream& output) const; + bool Read(std::ifstream& input); + +private: + int32_t mNumBits, mSize; + std::array mBits; + + friend class UnitTestBSNumber; + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + static std::atomic msMaxSize; +public: + static void SetMaxSizeToZero() { msMaxSize = 0; } + static int32_t GetMaxSize() { return msMaxSize; } +#endif +}; + + +template +UIntegerFP32::UIntegerFP32() + : + mNumBits(0), + mSize(0) +{ + static_assert(N >= 1, "Invalid size N."); +} + +template +UIntegerFP32::UIntegerFP32(UIntegerFP32 const& number) +{ + static_assert(N >= 1, "Invalid size N."); + + *this = number; +} + +template +UIntegerFP32::UIntegerFP32(uint32_t number) +{ + static_assert(N >= 1, "Invalid size N."); + + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + mNumBits = first - last + 1; + mSize = 1; + mBits[0] = (number >> last); + } + else + { + mNumBits = 0; + mSize = 0; + } + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template +UIntegerFP32::UIntegerFP32(uint64_t number) +{ + static_assert(N >= 2, "N not large enough to store 64-bit integers."); + + if (number > 0) + { + int32_t first = GetLeadingBit(number); + int32_t last = GetTrailingBit(number); + number >>= last; + mNumBits = first - last + 1; + mSize = 1 + (mNumBits - 1) / 32; + mBits[0] = (uint32_t)(number & 0x00000000FFFFFFFFull); + if (mSize > 1) + { + mBits[1] = (uint32_t)((number >> 32) & 0x00000000FFFFFFFFull); + } + } + else + { + mNumBits = 0; + mSize = 0; + } + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template +UIntegerFP32::UIntegerFP32(int numBits) + : + mNumBits(numBits), + mSize(1 + (numBits - 1) / 32) +{ + static_assert(N >= 1, "Invalid size N."); + +#if defined(GTE_ASSERT_ON_UINTEGERFP32_OUT_OF_RANGE) + LogAssert(mSize <= N, "N not large enough to store number of bits."); +#endif + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template +UIntegerFP32& UIntegerFP32::operator=(UIntegerFP32 const& number) +{ + static_assert(N >= 1, "Invalid size N."); + + mNumBits = number.mNumBits; + mSize = number.mSize; + std::copy(number.mBits.begin(), number.mBits.begin() + mSize, + mBits.begin()); + return *this; +} + +template +UIntegerFP32::UIntegerFP32(UIntegerFP32&& number) +{ + *this = std::move(number); +} + +template +UIntegerFP32& UIntegerFP32::operator=(UIntegerFP32&& number) +{ + mNumBits = number.mNumBits; + mSize = number.mSize; + std::copy(number.mBits.begin(), number.mBits.begin() + mSize, + mBits.begin()); + number.mNumBits = 0; + number.mSize = 0; + return *this; +} + +template +void UIntegerFP32::SetNumBits(uint32_t numBits) +{ + mNumBits = numBits; + mSize = 1 + (numBits - 1) / 32; + +#if defined(GTE_ASSERT_ON_UINTEGERFP32_OUT_OF_RANGE) + LogAssert(mSize <= N, "N not large enough to store number of bits."); +#endif + +#if defined(GTE_COLLECT_UINTEGERFP32_STATISTICS) + AtomicMax(msMaxSize, mSize); +#endif +} + +template inline +int32_t UIntegerFP32::GetNumBits() const +{ + return mNumBits; +} + +template inline +std::array const& UIntegerFP32::GetBits() const +{ + return mBits; +} + +template inline +std::array& UIntegerFP32::GetBits() +{ + return mBits; +} + +template inline +void UIntegerFP32::SetBack(uint32_t value) +{ + mBits[mSize - 1] = value; +} + +template inline +uint32_t UIntegerFP32::GetBack() const +{ + return mBits[mSize - 1]; +} + +template inline +int32_t UIntegerFP32::GetSize() const +{ + return mSize; +} + +template +bool UIntegerFP32::Write(std::ofstream& output) const +{ + if (output.write((char const*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + if (output.write((char const*)&mSize, sizeof(mSize)).bad()) + { + return false; + } + + return output.write((char const*)&mBits[0], + mSize*sizeof(mBits[0])).good(); +} + +template +bool UIntegerFP32::Read(std::ifstream& input) +{ + if (input.read((char*)&mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + if (input.read((char*)&mSize, sizeof(mSize)).bad()) + { + return false; + } + + return input.read((char*)&mBits[0], mSize*sizeof(mBits[0])).good(); +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUniqueVerticesTriangles.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUniqueVerticesTriangles.h new file mode 100644 index 000000000000..e4340dad9e21 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUniqueVerticesTriangles.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include +#include + +// The VertexType must have an operator< member because it is used as the +// key in a std::map. For example, if VertexType is +// Vector3, the comparison operator already exist. Another example is +// that you have a vertex type used for vertex coloring, say, +// struct VertexType +// { +// Vector3 position; +// Vector4 color; +// bool operator< (VertexType const& v) const +// { +// return position < v.position; +// } +// } +// The comparision will guarantee unique vertex positions, although if you +// have two VertexType objects with the same position but different colors, +// there is no guarantee which color will occur in the final result. + +namespace gte +{ + +template +class UniqueVerticesTriangles +{ +public: + // Triangle soup. The input vertex array consists of triples of vertices, + // each triple representing a triangle. The array 'inVertices' must have + // a multiple of 3 elements. An array 'outVertices' of unique vertices + // and an array 'outIndices' of 'inVertices.size()/3' unique index triples + // are computed. The indices are relative to the array of unique vertices + // and each index triple represents a triangle. + UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector& outVertices, + std::vector& outIndices); + + // Indexed triangles. The input vertex array consists of all vertices + // referenced by the input index array. The array 'inIndices' must have a + // multiple of 3 elements. An array 'outVertices' of unique vertices and + // an array 'outIndices' of 'indices.size()/3' unique index triples are + // computed. The indices are relative to the array of unique vertices and + // each index triple represents a triangle. + UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector const& inIndices, + std::vector& outVertices, + std::vector& outIndices); + + // The input vertices have indices 0 <= i < VInNum. The output vertices + // have indices 0 <= j < VOutNum. The construction leads to a mapping of + // input indices i to output indices j. Duplicate vertices have different + // input indices but the same output index. The following function gives + // you access to the mapping. If the input index is invalid (i < 0 or + // i >= VINum), the return value is -1. + inline int GetOutputIndexFor(int index) const; + +private: + void ConstructUniqueVertices(std::vector const& inVertices, + std::vector& outVertices); + + int mNumInVertices, mNumOutVertices; + std::vector mInToOutMapping; +}; + + +template +UniqueVerticesTriangles::UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector& outVertices, std::vector& outIndices) +{ + ConstructUniqueVertices(inVertices, outVertices); + + // The input index array is implicitly {<0,1,2>,<3,4,5>,...,} + // where n is the number of vertices. The output index array is the same + // as the mapping array. + outIndices.resize(inVertices.size()); + std::copy(mInToOutMapping.begin(), mInToOutMapping.end(), + outIndices.begin()); +} + +template +UniqueVerticesTriangles::UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector const& inIndices, std::vector& outVertices, + std::vector& outIndices) +{ + ConstructUniqueVertices(inVertices, outVertices); + + // The input index array needs it indices mapped to the unique vertex + // indices. + outIndices.resize(inIndices.size()); + for (size_t i = 0; i < inIndices.size(); ++i) + { + outIndices[i] = mInToOutMapping[inIndices[i]]; + } +} + +template inline +int UniqueVerticesTriangles::GetOutputIndexFor(int index) const +{ + return mInToOutMapping[index]; +} + +template +void UniqueVerticesTriangles::ConstructUniqueVertices( + std::vector const& inVertices, + std::vector& outVertices) +{ + // Construct the unique vertices. + mNumInVertices = (int)inVertices.size(); + mInToOutMapping.resize(mNumInVertices); + std::map table; + mNumOutVertices = 0; + for (int i = 0; i < mNumInVertices; ++i) + { + auto const iter = table.find(inVertices[i]); + if (iter != table.end()) + { + // Vertex i is a duplicate of one inserted earlier into the + // table. Map vertex i to the first-found copy. + mInToOutMapping[i] = iter->second; + } + else + { + // Vertex i is the first occurrence of such a point. + table.insert(std::make_pair(inVertices[i], mNumOutVertices)); + mInToOutMapping[i] = mNumOutVertices; + ++mNumOutVertices; + } + } + + // Pack the unique vertices into an array in the correct order. + outVertices.resize(mNumOutVertices); + for (auto const& element : table) + { + outVertices[element.second] = element.first; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUnsymmetricEigenvalues.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUnsymmetricEigenvalues.h new file mode 100644 index 000000000000..cc4b7dec9114 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteUnsymmetricEigenvalues.h @@ -0,0 +1,403 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). + +namespace gte +{ + +template +class UnsymmetricEigenvalues +{ +public: + // The solver processes NxN matrices (not necessarily symmetric), where + // N >= 3 ('size' is N) and the matrix is stored in row-major order. The + // maximum number of iterations ('maxIterations') must be specified for + // reducing an upper Hessenberg matrix to an upper quasi-triangular + // matrix (upper triangular matrix of blocks where the diagonal blocks + // are 1x1 or 2x2). The goal is to compute the real-valued eigenvalues. + UnsymmetricEigenvalues(int32_t size, uint32_t maxIterations); + + // A copy of the NxN input is made internally. The order of the + // eigenvalues is specified by sortType: -1 (decreasing), 0 (no + // sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did + // not occur, or 0 when N <= 1 was passed to the constructor. + uint32_t Solve(Real const* input, int32_t sortType); + + // Get the real-valued eigenvalues of the matrix passed to Solve(...). + // The input 'eigenvalues' must have at least N elements. + void GetEigenvalues(uint32_t& numEigenvalues, Real* eigenvalues) const; + +private: + // 2D accessors to elements of mMatrix[]. + inline Real const& A(int r, int c) const; + inline Real& A(int r, int c); + + // Compute the Householder vector for (X[rmin],...,x[rmax]). The + // input vector is stored in mX in the index range [rmin,rmax]. The + // output vector V is stored in mV in the index range [rmin,rmax]. The + // scaled vector is S = (-2/Dot(V,V))*V and is stored in mScaledV in + // the index range [rmin,rmax]. + void House(int rmin, int rmax); + + // Support for replacing matrix A by P^T*A*P, where P is a Householder + // reflection computed using House(...). + void RowHouse(int rmin, int rmax, int cmin, int cmax); + void ColHouse(int rmin, int rmax, int cmin, int cmax); + + void ReduceToUpperHessenberg(); + void FrancisQRStep(int rmin, int rmax); + bool GetBlock(std::array& block); + + // The number N of rows and columns of the matrices to be processed. + int32_t mSize, mSizeM1; + + // The maximum number of iterations for reducing the tridiagonal mtarix + // to a diagonal matrix. + uint32_t mMaxIterations; + + // The internal copy of a matrix passed to the solver. + std::vector mMatrix; // NxN elements + + // Temporary storage to compute Householder reflections. + std::vector mX, mV, mScaledV, mW; // N elements + + // Flags about the zeroness of the subdiagonal entries. This is used + // to detect uncoupled submatrices and apply the QR algorithm to the + // corresponding subproblems. The storage is padded on both ends with + // zeros to avoid additional code logic when packing the eigenvalues + // for access by the caller. + std::vector mFlagStorage; + int* mSubdiagonalFlag; + + int mNumEigenvalues; + std::vector mEigenvalues; +}; + + +template +UnsymmetricEigenvalues::UnsymmetricEigenvalues(int32_t size, uint32_t maxIterations) + : + mSize(0), + mSizeM1(0), + mMaxIterations(0), + mNumEigenvalues(0) +{ + if (size >= 3 && maxIterations > 0) + { + mSize = size; + mSizeM1 = size - 1; + mMaxIterations = maxIterations; + mMatrix.resize(size * size); + mX.resize(size); + mV.resize(size); + mScaledV.resize(size); + mW.resize(size); + mFlagStorage.resize(size + 1); + std::fill(mFlagStorage.begin(), mFlagStorage.end(), 0); + mSubdiagonalFlag = &mFlagStorage[1]; + mEigenvalues.resize(mSize); + } +} + +template +uint32_t UnsymmetricEigenvalues::Solve(Real const* input, int32_t sortType) +{ + if (mSize > 0) + { + std::copy(input, input + mSize * mSize, mMatrix.begin()); + ReduceToUpperHessenberg(); + + std::array block; + bool found = GetBlock(block); + uint32_t numIterations; + for (numIterations = 0; numIterations < mMaxIterations; ++numIterations) + { + if (found) + { + // Solve the current subproblem. + FrancisQRStep(block[0], block[1] + 1); + + // Find another subproblem (if any). + found = GetBlock(block); + } + else + { + break; + } + } + + // The matrix is fully uncoupled, upper Hessenberg with 1x1 or + // 2x2 diagonal blocks. Golub and Van Loan call this "upper + // quasi-triangular". + mNumEigenvalues = 0; + std::fill(mEigenvalues.begin(), mEigenvalues.end(), (Real)0); + for (int i = 0; i < mSizeM1; ++i) + { + if (mSubdiagonalFlag[i] == 0) + { + if (mSubdiagonalFlag[i - 1] == 0) + { + // We have a 1x1 block with a real eigenvalue. + mEigenvalues[mNumEigenvalues++] = A(i, i); + } + } + else + { + if (mSubdiagonalFlag[i - 1] == 0 && mSubdiagonalFlag[i + 1] == 0) + { + // We have a 2x2 block that might have real eigenvalues. + Real a00 = A(i, i); + Real a01 = A(i, i + 1); + Real a10 = A(i + 1, i); + Real a11 = A(i + 1, i + 1); + Real tr = a00 + a11; + Real det = a00 * a11 - a01 * a10; + Real halfTr = tr * (Real)0.5; + Real discr = halfTr * halfTr - det; + if (discr >= (Real)0) + { + Real rootDiscr = std::sqrt(discr); + mEigenvalues[mNumEigenvalues++] = halfTr - rootDiscr; + mEigenvalues[mNumEigenvalues++] = halfTr + rootDiscr; + } + } + // else: + // The QR iteration failed to converge at this block. It + // must also be the case that numIterations == mMaxIterations. + // TODO: The caller will be aware of this when testing the + // returned numIterations. Is there a remedy for such a + // case? (This happened with root finding using the + // companion matrix of a polynomial.) + } + } + + if (sortType != 0 && mNumEigenvalues > 1) + { + if (sortType > 0) + { + std::sort(mEigenvalues.begin(), mEigenvalues.begin() + mNumEigenvalues, + std::less()); + } + else + { + std::sort(mEigenvalues.begin(), mEigenvalues.begin() + mNumEigenvalues, + std::greater()); + } + } + + return numIterations; + } + return 0; +} + +template +void UnsymmetricEigenvalues::GetEigenvalues(uint32_t& numEigenvalues, Real* eigenvalues) const +{ + if (mSize > 0) + { + numEigenvalues = mNumEigenvalues; + Memcpy(eigenvalues, mEigenvalues.data(), numEigenvalues * sizeof(Real)); + } + else + { + numEigenvalues = 0; + } +} + +template +inline Real const& UnsymmetricEigenvalues::A(int r, int c) const +{ + return mMatrix[c + r * mSize]; +} + +template +inline Real& UnsymmetricEigenvalues::A(int r, int c) +{ + return mMatrix[c + r * mSize]; +} + +template +void UnsymmetricEigenvalues::House(int rmin, int rmax) +{ + Real length = (Real)0; + for (int r = rmin; r <= rmax; ++r) + { + length += mX[r] * mX[r]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (mX[rmin] >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (mX[rmin] + sign * length); + for (int r = rmin + 1; r <= rmax; ++r) + { + mV[r] = mX[r] * invDenom; + } + } + mV[rmin] = (Real)1; + + Real dot = (Real)1; + for (int r = rmin + 1; r <= rmax; ++r) + { + dot += mV[r] * mV[r]; + } + Real scale = ((Real)-2) / dot; + for (int r = rmin; r <= rmax; ++r) + { + mScaledV[r] = scale * mV[r]; + } +} + +template +void UnsymmetricEigenvalues::RowHouse(int rmin, int rmax, int cmin, int cmax) +{ + for (int c = cmin; c <= cmax; ++c) + { + mW[c] = (Real)0; + for (int r = rmin; r <= rmax; ++r) + { + mW[c] += mScaledV[r] * A(r, c); + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin; c <= cmax; ++c) + { + A(r, c) += mV[r] * mW[c]; + } + } +} + +template +void UnsymmetricEigenvalues::ColHouse(int rmin, int rmax, int cmin, int cmax) +{ + for (int r = rmin; r <= rmax; ++r) + { + mW[r] = (Real)0; + for (int c = cmin; c <= cmax; ++c) + { + mW[r] += mScaledV[c] * A(r, c); + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin; c <= cmax; ++c) + { + A(r, c) += mW[r] * mV[c]; + } + } +} + +template +void UnsymmetricEigenvalues::ReduceToUpperHessenberg() +{ + for (int c = 0, cp1 = 1; c <= mSize - 3; ++c, ++cp1) + { + for (int r = cp1; r <= mSizeM1; ++r) + { + mX[r] = A(r, c); + } + + House(cp1, mSizeM1); + RowHouse(cp1, mSizeM1, c, mSizeM1); + ColHouse(0, mSizeM1, cp1, mSizeM1); + } +} + +template +void UnsymmetricEigenvalues::FrancisQRStep(int rmin, int rmax) +{ + // Apply the double implicit shift step. + int const i0 = rmax - 1, i1 = rmax; + Real a00 = A(i0, i0); + Real a01 = A(i0, i1); + Real a10 = A(i1, i0); + Real a11 = A(i1, i1); + Real tr = a00 + a11; + Real det = a00 * a11 - a01 * a10; + + int const j0 = rmin, j1 = j0 + 1, j2 = j1 + 1; + Real b00 = A(j0, j0); + Real b01 = A(j0, j1); + Real b10 = A(j1, j0); + Real b11 = A(j1, j1); + Real b21 = A(j2, j1); + mX[rmin] = b00 * (b00 - tr) + b01 * b10 + det; + mX[rmin + 1] = b10 * (b00 + b11 - tr); + mX[rmin + 2] = b10 * b21; + + House(rmin, rmin + 2); + RowHouse(rmin, rmin + 2, rmin, rmax); + ColHouse(rmin, std::min(rmax, rmin + 3), rmin, rmin + 2); + + // Apply Householder reflections to restore the matrix to upper + // Hessenberg form. + for (int c = 0, cp1 = 1; c <= mSize - 3; ++c, ++cp1) + { + int kmax = std::min(cp1 + 2, mSizeM1); + for (int r = cp1; r <= kmax; ++r) + { + mX[r] = A(r, c); + } + + House(cp1, kmax); + RowHouse(cp1, kmax, c, mSizeM1); + ColHouse(0, mSizeM1, cp1, kmax); + } +} + +template +bool UnsymmetricEigenvalues::GetBlock(std::array& block) +{ + for (int i = 0; i < mSizeM1; ++i) + { + Real a00 = A(i, i); + Real a11 = A(i + 1, i + 1); + Real a21 = A(i + 1, i); + Real sum0 = a00 + a11; + Real sum1 = sum0 + a21; + mSubdiagonalFlag[i] = (sum1 != sum0 ? 1 : 0); + } + + for (int i = 0; i < mSizeM1; ++i) + { + if (mSubdiagonalFlag[i] == 1) + { + block = {{ i, -1 }}; + while (i < mSizeM1 && mSubdiagonalFlag[i] == 1) + { + block[1] = i++; + } + if (block[1] != block[0]) + { + return true; + } + } + } + return false; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.cpp new file mode 100644 index 000000000000..74d271c52ea5 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.cpp @@ -0,0 +1,228 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + +VEManifoldMesh::~VEManifoldMesh() +{ +} + +VEManifoldMesh::VEManifoldMesh(VCreator vCreator, ECreator eCreator) + : + mVCreator(vCreator ? vCreator : CreateVertex), + mECreator(eCreator ? eCreator : CreateEdge), + mAssertOnNonmanifoldInsertion(true) +{ +} + +VEManifoldMesh::VMap const& VEManifoldMesh::GetVertices() const +{ + return mVMap; +} + +VEManifoldMesh::EMap const& VEManifoldMesh::GetEdges() const +{ + return mEMap; +} + +std::shared_ptr VEManifoldMesh::CreateVertex(int v) +{ + return std::make_shared(v); +} + +std::shared_ptr VEManifoldMesh::CreateEdge(int v0, int v1) +{ + return std::make_shared(v0, v1); +} + +void VEManifoldMesh::AssertOnNonmanifoldInsertion(bool doAssert) +{ + mAssertOnNonmanifoldInsertion = doAssert; +} + +std::shared_ptr VEManifoldMesh::Insert(int v0, int v1) +{ + std::pair ekey(v0, v1); + if (mEMap.find(ekey) != mEMap.end()) + { + // The edge already exists. Return a null pointer as a signal to + // the caller that the insertion failed. + return nullptr; + } + + // Add the new edge. + std::shared_ptr edge = mECreator(v0, v1); + mEMap[ekey] = edge; + + // Add the vertices if they do not already exist. + for (int i = 0; i < 2; ++i) + { + int v = edge->V[i]; + std::shared_ptr vertex; + auto viter = mVMap.find(v); + if (viter == mVMap.end()) + { + // This is the first time the vertex is encountered. + vertex = mVCreator(v); + mVMap[v] = vertex; + + // Update the vertex. + vertex->E[0] = edge; + } + else + { + // This is the second time the vertex is encountered. + vertex = viter->second; + if (!vertex) + { + LogError("Unexpected condition."); + return nullptr; + } + + // Update the vertex. + if (vertex->E[1].lock()) + { + if (mAssertOnNonmanifoldInsertion) + { + LogInformation("The mesh must be manifold."); + } + return nullptr; + } + vertex->E[1] = edge; + + // Update the adjacent edge. + auto adjacent = vertex->E[0].lock(); + if (!adjacent) + { + LogError("Unexpected condition."); + return nullptr; + } + for (int j = 0; j < 2; ++j) + { + if (adjacent->V[j] == v) + { + adjacent->E[j] = edge; + break; + } + } + + // Update the edge. + edge->E[i] = adjacent; + } + } + + return edge; +} + +bool VEManifoldMesh::Remove(int v0, int v1) +{ + std::pair ekey(v0, v1); + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // The edge does not exist. + return false; + } + + // Get the edge. + std::shared_ptr edge = eiter->second; + + // Remove the vertices if necessary (when they are not shared). + for (int i = 0; i < 2; ++i) + { + // Inform the vertices the edge is being deleted. + auto viter = mVMap.find(edge->V[i]); + if (viter == mVMap.end()) + { + // The edge vertices should be in the map. + LogError("Unexpected condition."); + return false; + } + + std::shared_ptr vertex = viter->second; + if (!vertex) + { + // Any in the map must have a dynamically allocated + // Vertex object. + LogError("Unexpected condition."); + return false; + } + if (vertex->E[0].lock() == edge) + { + // One-edge vertices always have pointer at index zero. + vertex->E[0] = vertex->E[1]; + vertex->E[1].reset(); + } + else if (vertex->E[1].lock() == edge) + { + vertex->E[1].reset(); + } + else + { + LogError("Unexpected condition."); + return false; + } + + // Remove the vertex if you have the last reference to it. + if (!vertex->E[0].lock() && !vertex->E[1].lock()) + { + mVMap.erase(vertex->V); + } + + // Inform adjacent edges the edge is being deleted. + auto adjacent = edge->E[i].lock(); + if (adjacent) + { + for (int j = 0; j < 2; ++j) + { + if (adjacent->E[j].lock() == edge) + { + adjacent->E[j].reset(); + break; + } + } + } + } + + mEMap.erase(ekey); + return true; +} + +bool VEManifoldMesh::IsClosed() const +{ + for (auto const& element : mVMap) + { + auto vertex = element.second; + if (!vertex->E[0].lock() || !vertex->E[1].lock()) + { + return false; + } + } + return true; +} + +VEManifoldMesh::Vertex::~Vertex() +{ +} + +VEManifoldMesh::Vertex::Vertex(int v) +{ + V = v; +} + +VEManifoldMesh::Edge::~Edge() +{ +} + +VEManifoldMesh::Edge::Edge(int v0, int v1) +{ + V[0] = v0; + V[1] = v1; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.h new file mode 100644 index 000000000000..7e969660feb0 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVEManifoldMesh.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include +#include + +namespace gte +{ + +class GTE_IMPEXP VEManifoldMesh +{ +public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr (*VCreator)(int); + typedef std::map> VMap; + + // Edge data types. + class Edge; + typedef std::shared_ptr (*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Vertex object. + class GTE_IMPEXP Vertex + { + public: + virtual ~Vertex(); + Vertex(int v); + + // The unique vertex index. + int V; + + // The edges (if any) sharing the vertex. + std::weak_ptr E[2]; + }; + + // Edge object. + class GTE_IMPEXP Edge + { + public: + virtual ~Edge(); + Edge(int v0, int v1); + + // Vertices, listed as a directed edge . + int V[2]; + + // Adjacent edges. E[i] points to edge sharing V[i]. + std::weak_ptr E[2]; + }; + + + // Construction and destruction. + virtual ~VEManifoldMesh(); + VEManifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr); + + // Member access. + VMap const& GetVertices() const; + EMap const& GetEdges() const; + + // If the insertion of an edge fails because the mesh would become + // nonmanifold, the default behavior is to trigger a LogError message. + // You can disable this behavior in situations where you want the Logger + // system on but you want to continue gracefully without an assertion. + void AssertOnNonmanifoldInsertion(bool doAssert); + + // If is not in the mesh, an Edge object is created and returned; + // otherwise, is in the mesh and nullptr is returned. If the + // insertion leads to a nonmanifold mesh, the call fails with a nullptr + // returned. + std::shared_ptr Insert(int v0, int v1); + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + bool Remove(int v0, int v1); + + // A manifold mesh is closed if each vertex is shared twice. + bool IsClosed() const; + +protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int v0); + VCreator mVCreator; + VMap mVMap; + + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1); + ECreator mECreator; + EMap mEMap; + bool mAssertOnNonmanifoldInsertion; // default: true +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.cpp new file mode 100644 index 000000000000..29285c18d4e1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.cpp @@ -0,0 +1,175 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#include +#include +#include +using namespace gte; + +VETManifoldMesh::~VETManifoldMesh() +{ +} + +VETManifoldMesh::VETManifoldMesh(VCreator vCreator, ECreator eCreator, TCreator tCreator) + : + ETManifoldMesh(eCreator, tCreator), + mVCreator(vCreator ? vCreator : CreateVertex) +{ +} + +VETManifoldMesh::VETManifoldMesh(VETManifoldMesh const& mesh) +{ + *this = mesh; +} + +VETManifoldMesh& VETManifoldMesh::operator=(VETManifoldMesh const& mesh) +{ + Clear(); + mVCreator = mesh.mVCreator; + ETManifoldMesh::operator=(mesh); + return *this; +} + +VETManifoldMesh::VMap const& VETManifoldMesh::GetVertices() const +{ + return mVMap; +} + +std::shared_ptr VETManifoldMesh::Insert(int v0, int v1, int v2) +{ + std::shared_ptr tri = ETManifoldMesh::Insert(v0, v1, v2); + if (!tri) + { + return nullptr; + } + + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + std::shared_ptr vertex; + if (vItem == mVMap.end()) + { + vertex = mVCreator(vIndex); + mVMap[vIndex] = vertex; + } + else + { + vertex = vItem->second; + } + + vertex->TAdjacent.insert(tri); + + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (edge) + { + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.insert(edge->V[1]); + vertex->EAdjacent.insert(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.insert(edge->V[0]); + vertex->EAdjacent.insert(edge); + } + } + else + { + LogError("Malformed mesh: Triangle edges must not be null."); + return nullptr; + } + } + } + + return tri; +} + +bool VETManifoldMesh::Remove(int v0, int v1, int v2) +{ + auto tItem = mTMap.find(TriangleKey(v0, v1, v2)); + if (tItem == mTMap.end()) + { + return false; + } + + std::shared_ptr tri = tItem->second; + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + if (vItem != mVMap.end()) + { + std::shared_ptr vertex = vItem->second; + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (edge) + { + if (edge->T[0].lock() && !edge->T[1].lock()) + { + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.erase(edge->V[1]); + vertex->EAdjacent.erase(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.erase(edge->V[0]); + vertex->EAdjacent.erase(edge); + } + } + } + else + { + LogError("Malformed mesh: Triangle edges must not be null."); + return false; + } + } + + vertex->TAdjacent.erase(tri); + + if (vertex->TAdjacent.size() == 0) + { + LogAssert(vertex->VAdjacent.size() == 0 && vertex->EAdjacent.size() == 0, + "Malformed mesh: Inconsistent vertex adjacency information."); + + mVMap.erase(vItem); + } + } + else + { + LogError("Malformed mesh: Vertex must exist in the mesh."); + return false; + } + } + + return ETManifoldMesh::Remove(v0, v1, v2); +} + +void VETManifoldMesh::Clear() +{ + mVMap.clear(); + ETManifoldMesh::Clear(); +} + +std::shared_ptr VETManifoldMesh::CreateVertex(int vIndex) +{ + return std::make_shared(vIndex); +} + +VETManifoldMesh::Vertex::~Vertex() +{ +} + +VETManifoldMesh::Vertex::Vertex(int vIndex) + : + V(vIndex) +{ +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.h new file mode 100644 index 000000000000..9a997ceb582b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETManifoldMesh.h @@ -0,0 +1,79 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.0 (2016/06/19) + +#pragma once + +#include +#include + +// The VETManifoldMesh class represents an edge-triangle manifold mesh +// but additionally stores vertex adjacency information. + +namespace gte +{ + +class GTE_IMPEXP VETManifoldMesh : public ETManifoldMesh +{ +public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr (*VCreator)(int); + typedef std::map> VMap; + + // Vertex object. + class GTE_IMPEXP Vertex + { + public: + virtual ~Vertex(); + Vertex(int vIndex); + + // The index into the vertex pool of the mesh. + int V; + + // Adjacent objects. + std::set VAdjacent; + std::set> EAdjacent; + std::set> TAdjacent; + }; + + + // Construction and destruction. + virtual ~VETManifoldMesh(); + VETManifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mVMap, mEMap, and mTMap + // objects have dynamically allocated memory for vertices, edges, and + // triangles. A shallow copy of the pointers to this memory is + // problematic. Allowing sharing, say, via std::shared_ptr, is an + // option but not really the intent of copying the mesh graph. + VETManifoldMesh(VETManifoldMesh const& mesh); + VETManifoldMesh& operator=(VETManifoldMesh const& mesh); + + // Member access. + VMap const& GetVertices() const; + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is returned. + // If the insertion leads to a nonmanifold mesh, the call fails with a + // nullptr returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) override; + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) override; + + // Destroy the vertices, edges, and triangles to obtain an empty mesh. + virtual void Clear() override; + +protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int vIndex); + VCreator mVCreator; + VMap mVMap; +}; + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.cpp new file mode 100644 index 000000000000..9be4b4fc91c3 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.cpp @@ -0,0 +1,182 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/08) + +#include +#include +#include +using namespace gte; + +VETNonmanifoldMesh::~VETNonmanifoldMesh() +{ +} + +VETNonmanifoldMesh::VETNonmanifoldMesh(VCreator vCreator, ECreator eCreator, TCreator tCreator) + : + ETNonmanifoldMesh(eCreator, tCreator), + mVCreator(vCreator ? vCreator : CreateVertex) +{ +} + +VETNonmanifoldMesh::VETNonmanifoldMesh(VETNonmanifoldMesh const& mesh) +{ + *this = mesh; +} + +VETNonmanifoldMesh& VETNonmanifoldMesh::operator=(VETNonmanifoldMesh const& mesh) +{ + Clear(); + mVCreator = mesh.mVCreator; + ETNonmanifoldMesh::operator=(mesh); + return *this; +} + +VETNonmanifoldMesh::VMap const& VETNonmanifoldMesh::GetVertices() const +{ + return mVMap; +} + +std::shared_ptr VETNonmanifoldMesh::Insert(int v0, int v1, int v2) +{ + std::shared_ptr tri = ETNonmanifoldMesh::Insert(v0, v1, v2); + if (!tri) + { + return nullptr; + } + + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + std::shared_ptr vertex; + if (vItem == mVMap.end()) + { + vertex = mVCreator(vIndex); + mVMap[vIndex] = vertex; + } + else + { + vertex = vItem->second; + } + + vertex->TAdjacent.insert(tri); + + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return nullptr; + } + + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.insert(edge->V[1]); + vertex->EAdjacent.insert(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.insert(edge->V[0]); + vertex->EAdjacent.insert(edge); + } + } + } + + return tri; +} + +bool VETNonmanifoldMesh::Remove(int v0, int v1, int v2) +{ + auto tItem = mTMap.find(TriangleKey(v0, v1, v2)); + if (tItem == mTMap.end()) + { + return false; + } + + std::shared_ptr tri = tItem->second; + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + if (vItem == mVMap.end()) + { + LogError("Unexpected condition."); + return false; + } + + std::shared_ptr vertex = vItem->second; + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + if (!edge) + { + LogError("Unexpected condition."); + return false; + } + + // If the edge will be removed by ETNonmanifoldMesh::Remove, + // remove the vertex references to it. + if (edge->T.size() == 1) + { + for (auto const& adjw : edge->T) + { + auto adj = adjw.lock(); + if (!adj) + { + LogError("Unexpected condition."); + return false; + } + + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.erase(edge->V[1]); + vertex->EAdjacent.erase(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.erase(edge->V[0]); + vertex->EAdjacent.erase(edge); + } + } + } + } + + vertex->TAdjacent.erase(tri); + + // If the vertex is no longer shared by any triangle, remove it. + if (vertex->TAdjacent.size() == 0) + { + LogAssert(vertex->VAdjacent.size() == 0 && vertex->EAdjacent.size() == 0, + "Malformed mesh: Inconsistent vertex adjacency information."); + + mVMap.erase(vItem); + } + } + + return ETNonmanifoldMesh::Remove(v0, v1, v2); +} + +void VETNonmanifoldMesh::Clear() +{ + mVMap.clear(); + ETNonmanifoldMesh::Clear(); +} + +std::shared_ptr VETNonmanifoldMesh::CreateVertex(int vIndex) +{ + return std::make_shared(vIndex); +} + +VETNonmanifoldMesh::Vertex::~Vertex() +{ +} + +VETNonmanifoldMesh::Vertex::Vertex(int vIndex) + : + V(vIndex) +{ +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.h new file mode 100644 index 000000000000..7174cbf729ab --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVETNonmanifoldMesh.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2018 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.20.0 (2019/01/09) + +#pragma once + +#include +#include + +// The VETNonmanifoldMesh class represents an edge-triangle nonmanifold mesh +// but additionally stores vertex adjacency information. + +namespace gte +{ + class GTE_IMPEXP VETNonmanifoldMesh : public ETNonmanifoldMesh + { + public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr(*VCreator)(int); + typedef std::map> VMap; + + // Vertex object. + class GTE_IMPEXP Vertex + { + public: + virtual ~Vertex(); + Vertex(int vIndex); + + // The index into the vertex pool of the mesh. + int V; + + bool operator<(Vertex const& other) const + { + return V < other.V; + } + + // Adjacent objects. + std::set VAdjacent; + std::set, SharedPtrLT> EAdjacent; + std::set, SharedPtrLT> TAdjacent; + }; + + + // Construction and destruction. + virtual ~VETNonmanifoldMesh(); + VETNonmanifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr, TCreator tCreator = nullptr); + + // Support for a deep copy of the mesh. The mVMap, mEMap, and mTMap + // objects have dynamically allocated memory for vertices, edges, and + // triangles. A shallow copy of the pointers to this memory is + // problematic. Allowing sharing, say, via std::shared_ptr, is an + // option but not really the intent of copying the mesh graph. + VETNonmanifoldMesh(VETNonmanifoldMesh const& mesh); + VETNonmanifoldMesh& operator=(VETNonmanifoldMesh const& mesh); + + // Member access. + VMap const& GetVertices() const; + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) override; + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) override; + + // Destroy the vertices, edges, and triangles to obtain an empty mesh. + virtual void Clear() override; + + protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int vIndex); + VCreator mVCreator; + VMap mVMap; + }; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector.h new file mode 100644 index 000000000000..022039ddafa6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector.h @@ -0,0 +1,692 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.3 (2018/10/05) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class Vector +{ +public: + // The tuple is uninitialized. + Vector(); + + // The tuple is fully initialized by the inputs. + Vector(std::array const& values); + + // At most N elements are copied from the initializer list, setting any + // remaining elements to zero. Create the zero vector using the syntax + // Vector zero{(Real)0}; + // WARNING: The C++ 11 specification states that + // Vector zero{}; + // will lead to a call of the default constructor, not the initializer + // constructor! + Vector(std::initializer_list values); + + // For 0 <= d < N, element d is 1 and all others are 0. If d is invalid, + // the zero vector is created. This is a convenience for creating the + // standard Euclidean basis vectors; see also MakeUnit(int) and Unit(int). + Vector(int d); + + // The copy constructor, destructor, and assignment operator are generated + // by the compiler. + + // Member access. The first operator[] returns a const reference rather + // than a Real value. This supports writing via standard file operations + // that require a const pointer to data. + inline int GetSize() const; // returns N + inline Real const& operator[](int i) const; + inline Real& operator[](int i); + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(Vector const& vec) const; + inline bool operator!=(Vector const& vec) const; + inline bool operator< (Vector const& vec) const; + inline bool operator<=(Vector const& vec) const; + inline bool operator> (Vector const& vec) const; + inline bool operator>=(Vector const& vec) const; + + // Special vectors. + void MakeZero(); // All components are 0. + void MakeUnit(int d); // Component d is 1, all others are zero. + static Vector Zero(); + static Vector Unit(int d); + +protected: + // This data structure takes advantage of the built-in operator[], + // range checking, and visualizers in MSVS. + std::array mTuple; +}; + +// Unary operations. +template +Vector operator+(Vector const& v); + +template +Vector operator-(Vector const& v); + +// Linear-algebraic operations. +template +Vector +operator+(Vector const& v0, Vector const& v1); + +template +Vector +operator-(Vector const& v0, Vector const& v1); + +template +Vector operator*(Vector const& v, Real scalar); + +template +Vector operator*(Real scalar, Vector const& v); + +template +Vector operator/(Vector const& v, Real scalar); + +template +Vector& operator+=(Vector& v0, Vector const& v1); + +template +Vector& operator-=(Vector& v0, Vector const& v1); + +template +Vector& operator*=(Vector& v, Real scalar); + +template +Vector& operator/=(Vector& v, Real scalar); + +// Componentwise algebraic operations. +template +Vector +operator*(Vector const& v0, Vector const& v1); + +template +Vector +operator/(Vector const& v0, Vector const& v1); + +template +Vector& operator*=(Vector& v0, Vector const& v1); + +template +Vector& operator/=(Vector& v0, Vector const& v1); + +// Geometric operations. The functions with 'robust' set to 'false' use the +// standard algorithm for normalizing a vector by computing the length as a +// square root of the squared length and dividing by it. The results can be +// infinite (or NaN) if the length is zero. When 'robust' is set to 'true', +// the algorithm is designed to avoid floating-point overflow and sets the +// normalized vector to zero when the length is zero. +template +Real Dot(Vector const& v0, Vector const& v1); + +template +Real Length(Vector const& v, bool robust = false); + +template +Real Normalize(Vector& v, bool robust = false); + +// Gram-Schmidt orthonormalization to generate orthonormal vectors from the +// linearly independent inputs. The function returns the smallest length of +// the unnormalized vectors computed during the process. If this value is +// nearly zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, 1 <= numElements <= N and v[0] +// through v[numElements-1] must be initialized. On output, the vectors +// v[0] through v[numElements-1] form an orthonormal set. +template +Real Orthonormalize(int numElements, Vector* v, bool robust = false); + +// Construct a single vector orthogonal to the nonzero input vector. If the +// maximum absolute component occurs at index i, then the orthogonal vector +// U has u[i] = v[i+1], u[i+1] = -v[i], and all other components zero. The +// index addition i+1 is computed modulo N. +template +Vector GetOrthogonal(Vector const& v, bool unitLength); + +// Compute the axis-aligned bounding box of the vectors. The return value is +// 'true' iff the inputs are valid, in which case vmin and vmax have valid +// values. +template +bool ComputeExtremes(int numVectors, Vector const* v, + Vector& vmin, Vector& vmax); + +// Lift n-tuple v to homogeneous (n+1)-tuple (v,last). +template +Vector HLift(Vector const& v, Real last); + +// Project homogeneous n-tuple v = (u,v[n-1]) to (n-1)-tuple u. +template +Vector HProject(Vector const& v); + +// Lift n-tuple v = (w0,w1) to (n+1)-tuple u = (w0,u[inject],w1). By +// inference, w0 is a (inject)-tuple [nonexistent when inject=0] and w1 is a +// (n-inject)-tuple [nonexistent when inject=n]. +template +Vector Lift(Vector const& v, int inject, Real value); + +// Project n-tuple v = (w0,v[reject],w1) to (n-1)-tuple u = (w0,w1). By +// inference, w0 is a (reject)-tuple [nonexistent when reject=0] and w1 is a +// (n-1-reject)-tuple [nonexistent when reject=n-1]. +template +Vector Project(Vector const& v, int reject); + + +template +Vector::Vector() +{ + // Uninitialized. +} + +template +Vector::Vector(std::array const& values) + : + mTuple(values) +{ +} +template +Vector::Vector(std::initializer_list values) +{ + int const numValues = static_cast(values.size()); + if (N == numValues) + { + std::copy(values.begin(), values.end(), mTuple.begin()); + } + else if (N > numValues) + { + std::copy(values.begin(), values.end(), mTuple.begin()); + std::fill(mTuple.begin() + numValues, mTuple.end(), (Real)0); + } + else // N < numValues + { + std::copy(values.begin(), values.begin() + N, mTuple.begin()); + } +} + +template +Vector::Vector(int d) +{ + MakeUnit(d); +} + +template inline +int Vector::GetSize() const +{ + return N; +} + +template inline +Real const& Vector::operator[](int i) const +{ + return mTuple[i]; +} + +template inline +Real& Vector::operator[](int i) +{ + return mTuple[i]; +} + +template inline +bool Vector::operator==(Vector const& vec) const +{ + return mTuple == vec.mTuple; +} + +template inline +bool Vector::operator!=(Vector const& vec) const +{ + return mTuple != vec.mTuple; +} + +template inline +bool Vector::operator<(Vector const& vec) const +{ + return mTuple < vec.mTuple; +} + +template inline +bool Vector::operator<=(Vector const& vec) const +{ + return mTuple <= vec.mTuple; +} + +template inline +bool Vector::operator>(Vector const& vec) const +{ + return mTuple > vec.mTuple; +} + +template inline +bool Vector::operator>=(Vector const& vec) const +{ + return mTuple >= vec.mTuple; +} + +template +void Vector::MakeZero() +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); +} + +template +void Vector::MakeUnit(int d) +{ + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + if (0 <= d && d < N) + { + mTuple[d] = (Real)1; + } +} + +template +Vector Vector::Zero() +{ + Vector v; + v.MakeZero(); + return v; +} + +template +Vector Vector::Unit(int d) +{ + Vector v; + v.MakeUnit(d); + return v; +} + + + +template +Vector operator+(Vector const& v) +{ + return v; +} + +template +Vector operator-(Vector const& v) +{ + Vector result; + for (int i = 0; i < N; ++i) + { + result[i] = -v[i]; + } + return result; +} + +template +Vector operator+(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result += v1; +} + +template +Vector operator-(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result -= v1; +} + +template +Vector operator*(Vector const& v, Real scalar) +{ + Vector result = v; + return result *= scalar; +} + +template +Vector operator*(Real scalar, Vector const& v) +{ + Vector result = v; + return result *= scalar; +} + +template +Vector operator/(Vector const& v, Real scalar) +{ + Vector result = v; + return result /= scalar; +} + +template +Vector& operator+=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] += v1[i]; + } + return v0; +} + +template +Vector& operator-=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] -= v1[i]; + } + return v0; +} + +template +Vector& operator*=(Vector& v, Real scalar) +{ + for (int i = 0; i < N; ++i) + { + v[i] *= scalar; + } + return v; +} + +template +Vector& operator/=(Vector& v, Real scalar) +{ + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < N; ++i) + { + v[i] *= invScalar; + } + } + else + { + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return v; +} + +template +Vector operator*(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result *= v1; +} + +template +Vector operator/(Vector const& v0, + Vector const& v1) +{ + Vector result = v0; + return result /= v1; +} + +template +Vector& operator*=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] *= v1[i]; + } + return v0; +} + +template +Vector& operator/=(Vector& v0, Vector const& v1) +{ + for (int i = 0; i < N; ++i) + { + v0[i] /= v1[i]; + } + return v0; +} + +template +Real Dot(Vector const& v0, Vector const& v1) +{ + Real dot = v0[0] * v1[0]; + for (int i = 1; i < N; ++i) + { + dot += v0[i] * v1[i]; + } + return dot; +} + +template +Real Length(Vector const& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < N; ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + Vector scaled = v / maxAbsComp; + length = maxAbsComp * std::sqrt(Dot(scaled, scaled)); + } + else + { + length = (Real)0; + } + return length; + } + else + { + return std::sqrt(Dot(v, v)); + } +} + +template +Real Normalize(Vector& v, bool robust) +{ + if (robust) + { + Real maxAbsComp = std::abs(v[0]); + for (int i = 1; i < N; ++i) + { + Real absComp = std::abs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + v /= maxAbsComp; + length = std::sqrt(Dot(v, v)); + v /= length; + length *= maxAbsComp; + } + else + { + length = (Real)0; + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return length; + } + else + { + Real length = std::sqrt(Dot(v, v)); + if (length > (Real)0) + { + v /= length; + } + else + { + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return length; + } +} + +template +Real Orthonormalize(int numInputs, Vector* v, bool robust) +{ + if (v && 1 <= numInputs && numInputs <= N) + { + Real minLength = Normalize(v[0], robust); + for (int i = 1; i < numInputs; ++i) + { + for (int j = 0; j < i; ++j) + { + Real dot = Dot(v[i], v[j]); + v[i] -= v[j] * dot; + } + Real length = Normalize(v[i], robust); + if (length < minLength) + { + minLength = length; + } + } + return minLength; + } + + return (Real)0; +} + +template +Vector GetOrthogonal(Vector const& v, bool unitLength) +{ + Real cmax = std::abs(v[0]); + int imax = 0; + for (int i = 1; i < N; ++i) + { + Real c = std::abs(v[i]); + if (c > cmax) + { + cmax = c; + imax = i; + } + } + + Vector result; + result.MakeZero(); + int inext = imax + 1; + if (inext == N) + { + inext = 0; + } + result[imax] = v[inext]; + result[inext] = -v[imax]; + if (unitLength) + { + Real sqrDistance = result[imax] * result[imax] + result[inext] * result[inext]; + Real invLength = ((Real)1) / std::sqrt(sqrDistance); + result[imax] *= invLength; + result[inext] *= invLength; + } + return result; +} + +template +bool ComputeExtremes(int numVectors, Vector const* v, + Vector& vmin, Vector& vmax) +{ + if (v && numVectors > 0) + { + vmin = v[0]; + vmax = vmin; + for (int j = 1; j < numVectors; ++j) + { + Vector const& vec = v[j]; + for (int i = 0; i < N; ++i) + { + if (vec[i] < vmin[i]) + { + vmin[i] = vec[i]; + } + else if (vec[i] > vmax[i]) + { + vmax[i] = vec[i]; + } + } + } + return true; + } + + return false; +} + +template +Vector HLift(Vector const& v, Real last) +{ + Vector result; + for (int i = 0; i < N; ++i) + { + result[i] = v[i]; + } + result[N] = last; + return result; +} + +template +Vector HProject(Vector const& v) +{ + static_assert(N >= 2, "Invalid dimension."); + Vector result; + for (int i = 0; i < N - 1; ++i) + { + result[i] = v[i]; + } + return result; +} + +template +Vector Lift(Vector const& v, int inject, Real value) +{ + Vector result; + int i; + for (i = 0; i < inject; ++i) + { + result[i] = v[i]; + } + result[i] = value; + int j = i; + for (++j; i < N; ++i, ++j) + { + result[j] = v[i]; + } + return result; +} + +template +Vector Project(Vector const& v, int reject) +{ + static_assert(N >= 2, "Invalid dimension."); + Vector result; + for (int i = 0, j = 0; i < N - 1; ++i, ++j) + { + if (j == reject) + { + ++j; + } + result[i] = v[j]; + } + return result; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector2.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector2.h new file mode 100644 index 000000000000..407142aaf39e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector2.h @@ -0,0 +1,276 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/04) + +#pragma once + +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Vector2 = Vector<2, Real>; + +// Compute the perpendicular using the formal determinant, +// perp = det{{e0,e1},{x0,x1}} = (x1,-x0) +// where e0 = (1,0), e1 = (0,1), and v = (x0,x1). +template +Vector2 Perp(Vector2 const& v); + +// Compute the normalized perpendicular. +template +Vector2 UnitPerp(Vector2 const& v, bool robust = false); + +// Compute Dot((x0,x1),Perp(y0,y1)) = x0*y1 - x1*y0, where v0 = (x0,x1) +// and v1 = (y0,y1). +template +Real DotPerp(Vector2 const& v0, Vector2 const& v1); + +// Compute a right-handed orthonormal basis for the orthogonal complement +// of the input vectors. The function returns the smallest length of the +// unnormalized vectors computed during the process. If this value is nearly +// zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, numInputs must be 1 and v[0] +// must be initialized. On output, the vectors v[0] and v[1] form an +// orthonormal set. +template +Real ComputeOrthogonalComplement(int numInputs, Vector2* v, bool robust = false); + +// Compute the barycentric coordinates of the point P with respect to the +// triangle , P = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1. +// The return value is 'true' iff {V0,V1,V2} is a linearly independent set. +// Numerically, this is measured by |det[V0 V1 V2]| <= epsilon. The values +// bary[] are valid only when the return value is 'true' but set to zero when +// the return value is 'false'. +template +bool ComputeBarycentrics(Vector2 const& p, Vector2 const& v0, + Vector2 const& v1, Vector2 const& v2, Real bary[3], + Real epsilon = (Real)0); + +// Get intrinsic information about the input array of vectors. The return +// value is 'true' iff the inputs are valid (numVectors > 0, v is not null, +// and epsilon >= 0), in which case the class members are valid. +template +class IntrinsicsVector2 +{ +public: + // The constructor sets the class members based on the input set. + IntrinsicsVector2(int numVectors, Vector2 const* v, Real inEpsilon); + + // A nonnegative tolerance that is used to determine the intrinsic + // dimension of the set. + Real epsilon; + + // The intrinsic dimension of the input set, computed based on the + // nonnegative tolerance mEpsilon. + int dimension; + + // Axis-aligned bounding box of the input set. The maximum range is + // the larger of max[0]-min[0] and max[1]-min[1]. + Real min[2], max[2]; + Real maxRange; + + // Coordinate system. The origin is valid for any dimension d. The + // unit-length direction vector is valid only for 0 <= i < d. The + // extreme index is relative to the array of input points, and is also + // valid only for 0 <= i < d. If d = 0, all points are effectively + // the same, but the use of an epsilon may lead to an extreme index + // that is not zero. If d = 1, all points effectively lie on a line + // segment. If d = 2, the points are not collinear. + Vector2 origin; + Vector2 direction[2]; + + // The indices that define the maximum dimensional extents. The + // values extreme[0] and extreme[1] are the indices for the points + // that define the largest extent in one of the coordinate axis + // directions. If the dimension is 2, then extreme[2] is the index + // for the point that generates the largest extent in the direction + // perpendicular to the line through the points corresponding to + // extreme[0] and extreme[1]. The triangle formed by the points + // V[extreme[0]], V[extreme[1]], and V[extreme[2]] is clockwise or + // counterclockwise, the condition stored in extremeCCW. + int extreme[3]; + bool extremeCCW; +}; + + +template +Vector2 Perp(Vector2 const& v) +{ + return Vector2{ v[1], -v[0] }; +} + +template +Vector2 UnitPerp(Vector2 const& v, bool robust) +{ + Vector2 unitPerp{ v[1], -v[0] }; + Normalize(unitPerp, robust); + return unitPerp; +} + +template +Real DotPerp(Vector2 const& v0, Vector2 const& v1) +{ + return Dot(v0, Perp(v1)); +} + +template +Real ComputeOrthogonalComplement(int numInputs, Vector2* v, bool robust) +{ + if (numInputs == 1) + { + v[1] = -Perp(v[0]); + return Orthonormalize<2, Real>(2, v, robust); + } + + return (Real)0; +} + +template +bool ComputeBarycentrics(Vector2 const& p, Vector2 const& v0, + Vector2 const& v1, Vector2 const& v2, Real bary[3], + Real epsilon) +{ + // Compute the vectors relative to V2 of the triangle. + Vector2 diff[3] = { v0 - v2, v1 - v2, p - v2 }; + + Real det = DotPerp(diff[0], diff[1]); + if (det < -epsilon || det > epsilon) + { + Real invDet = ((Real)1) / det; + bary[0] = DotPerp(diff[2], diff[1])*invDet; + bary[1] = DotPerp(diff[0], diff[2])*invDet; + bary[2] = (Real)1 - bary[0] - bary[1]; + return true; + } + + for (int i = 0; i < 3; ++i) + { + bary[i] = (Real)0; + } + return false; +} + +template +IntrinsicsVector2::IntrinsicsVector2(int numVectors, + Vector2 const* v, Real inEpsilon) + : + epsilon(inEpsilon), + dimension(0), + maxRange((Real)0), + origin({ (Real)0, (Real)0 }), + extremeCCW(false) +{ + min[0] = (Real)0; + min[1] = (Real)0; + direction[0] = { (Real)0, (Real)0 }; + direction[1] = { (Real)0, (Real)0 }; + extreme[0] = 0; + extreme[1] = 0; + extreme[2] = 0; + + if (numVectors > 0 && v && epsilon >= (Real)0) + { + // Compute the axis-aligned bounding box for the input vectors. Keep + // track of the indices into 'vectors' for the current min and max. + int j, indexMin[2], indexMax[2]; + for (j = 0; j < 2; ++j) + { + min[j] = v[0][j]; + max[j] = min[j]; + indexMin[j] = 0; + indexMax[j] = 0; + } + + int i; + for (i = 1; i < numVectors; ++i) + { + for (j = 0; j < 2; ++j) + { + if (v[i][j] < min[j]) + { + min[j] = v[i][j]; + indexMin[j] = i; + } + else if (v[i][j] > max[j]) + { + max[j] = v[i][j]; + indexMax[j] = i; + } + } + } + + // Determine the maximum range for the bounding box. + maxRange = max[0] - min[0]; + extreme[0] = indexMin[0]; + extreme[1] = indexMax[0]; + Real range = max[1] - min[1]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[1]; + extreme[1] = indexMax[1]; + } + + // The origin is either the vector of minimum x0-value or vector of + // minimum x1-value. + origin = v[extreme[0]]; + + // Test whether the vector set is (nearly) a vector. + if (maxRange <= epsilon) + { + dimension = 0; + for (j = 0; j < 2; ++j) + { + extreme[j + 1] = extreme[0]; + } + return; + } + + // Test whether the vector set is (nearly) a line segment. We need + // direction[1] to span the orthogonal complement of direction[0]. + direction[0] = v[extreme[1]] - origin; + Normalize(direction[0], false); + direction[1] = -Perp(direction[0]); + + // Compute the maximum distance of the points from the line + // origin+t*direction[0]. + Real maxDistance = (Real)0; + Real maxSign = (Real)0; + extreme[2] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector2 diff = v[i] - origin; + Real distance = Dot(direction[1], diff); + Real sign = (distance >(Real)0 ? (Real)1 : + (distance < (Real)0 ? (Real)-1 : (Real)0)); + distance = std::abs(distance); + if (distance > maxDistance) + { + maxDistance = distance; + maxSign = sign; + extreme[2] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the line origin+t*direction[0]. + dimension = 1; + extreme[2] = extreme[1]; + return; + } + + dimension = 2; + extremeCCW = (maxSign > (Real)0); + return; + } +} + + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector3.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector3.h new file mode 100644 index 000000000000..614c60e8fc2d --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector3.h @@ -0,0 +1,382 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/04) + +#pragma once + +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Vector3 = Vector<3, Real>; + +// Cross, UnitCross, and DotCross have a template parameter N that should +// be 3 or 4. The latter case supports affine vectors in 4D (last component +// w = 0) when you want to use 4-tuples and 4x4 matrices for affine algebra. + +// Compute the cross product using the formal determinant: +// cross = det{{e0,e1,e2},{x0,x1,x2},{y0,y1,y2}} +// = (x1*y2-x2*y1, x2*y0-x0*y2, x0*y1-x1*y0) +// where e0 = (1,0,0), e1 = (0,1,0), e2 = (0,0,1), v0 = (x0,x1,x2), and +// v1 = (y0,y1,y2). +template +Vector Cross(Vector const& v0, Vector const& v1); + +// Compute the normalized cross product. +template +Vector UnitCross(Vector const& v0, Vector const& v1, + bool robust = false); + +// Compute Dot((x0,x1,x2),Cross((y0,y1,y2),(z0,z1,z2)), the triple scalar +// product of three vectors, where v0 = (x0,x1,x2), v1 = (y0,y1,y2), and +// v2 is (z0,z1,z2). +template +Real DotCross(Vector const& v0, Vector const& v1, + Vector const& v2); + +// Compute a right-handed orthonormal basis for the orthogonal complement +// of the input vectors. The function returns the smallest length of the +// unnormalized vectors computed during the process. If this value is nearly +// zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, numInputs must be 1 or 2 and +// v[0] through v[numInputs-1] must be initialized. On output, the +// vectors v[0] through v[2] form an orthonormal set. +template +Real ComputeOrthogonalComplement(int numInputs, Vector3* v, bool robust = false); + +// Compute the barycentric coordinates of the point P with respect to the +// tetrahedron , P = b0*V0 + b1*V1 + b2*V2 + b3*V3, where +// b0 + b1 + b2 + b3 = 1. The return value is 'true' iff {V0,V1,V2,V3} is +// a linearly independent set. Numerically, this is measured by +// |det[V0 V1 V2 V3]| <= epsilon. The values bary[] are valid only when the +// return value is 'true' but set to zero when the return value is 'false'. +template +bool ComputeBarycentrics(Vector3 const& p, Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3, + Real bary[4], Real epsilon = (Real)0); + +// Get intrinsic information about the input array of vectors. The return +// value is 'true' iff the inputs are valid (numVectors > 0, v is not null, +// and epsilon >= 0), in which case the class members are valid. +template +class IntrinsicsVector3 +{ +public: + // The constructor sets the class members based on the input set. + IntrinsicsVector3(int numVectors, Vector3 const* v, Real inEpsilon); + + // A nonnegative tolerance that is used to determine the intrinsic + // dimension of the set. + Real epsilon; + + // The intrinsic dimension of the input set, computed based on the + // nonnegative tolerance mEpsilon. + int dimension; + + // Axis-aligned bounding box of the input set. The maximum range is + // the larger of max[0]-min[0], max[1]-min[1], and max[2]-min[2]. + Real min[3], max[3]; + Real maxRange; + + // Coordinate system. The origin is valid for any dimension d. The + // unit-length direction vector is valid only for 0 <= i < d. The + // extreme index is relative to the array of input points, and is also + // valid only for 0 <= i < d. If d = 0, all points are effectively + // the same, but the use of an epsilon may lead to an extreme index + // that is not zero. If d = 1, all points effectively lie on a line + // segment. If d = 2, all points effectively line on a plane. If + // d = 3, the points are not coplanar. + Vector3 origin; + Vector3 direction[3]; + + // The indices that define the maximum dimensional extents. The + // values extreme[0] and extreme[1] are the indices for the points + // that define the largest extent in one of the coordinate axis + // directions. If the dimension is 2, then extreme[2] is the index + // for the point that generates the largest extent in the direction + // perpendicular to the line through the points corresponding to + // extreme[0] and extreme[1]. Furthermore, if the dimension is 3, + // then extreme[3] is the index for the point that generates the + // largest extent in the direction perpendicular to the triangle + // defined by the other extreme points. The tetrahedron formed by the + // points V[extreme[0]], V[extreme[1]], V[extreme[2]], and + // V[extreme[3]] is clockwise or counterclockwise, the condition + // stored in extremeCCW. + int extreme[4]; + bool extremeCCW; +}; + + +template +Vector Cross(Vector const& v0, Vector const& v1) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Vector result; + result.MakeZero(); + result[0] = v0[1] * v1[2] - v0[2] * v1[1]; + result[1] = v0[2] * v1[0] - v0[0] * v1[2]; + result[2] = v0[0] * v1[1] - v0[1] * v1[0]; + return result; +} + +template +Vector UnitCross(Vector const& v0, Vector const& v1, bool robust) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Vector unitCross = Cross(v0, v1); + Normalize(unitCross, robust); + return unitCross; +} + +template +Real DotCross(Vector const& v0, Vector const& v1, + Vector const& v2) +{ + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + return Dot(v0, Cross(v1, v2)); +} + +template +Real ComputeOrthogonalComplement(int numInputs, Vector3* v, bool robust) +{ + if (numInputs == 1) + { + if (std::abs(v[0][0]) > std::abs(v[0][1])) + { + v[1] = { -v[0][2], (Real)0, +v[0][0] }; + } + else + { + v[1] = { (Real)0, +v[0][2], -v[0][1] }; + } + numInputs = 2; + } + + if (numInputs == 2) + { + v[2] = Cross(v[0], v[1]); + return Orthonormalize<3, Real>(3, v, robust); + } + + return (Real)0; +} + +template +bool ComputeBarycentrics(Vector3 const& p, Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3, + Real bary[4], Real epsilon) +{ + // Compute the vectors relative to V3 of the tetrahedron. + Vector3 diff[4] = { v0 - v3, v1 - v3, v2 - v3, p - v3 }; + + Real det = DotCross(diff[0], diff[1], diff[2]); + if (det < -epsilon || det > epsilon) + { + Real invDet = ((Real)1) / det; + bary[0] = DotCross(diff[3], diff[1], diff[2]) * invDet; + bary[1] = DotCross(diff[3], diff[2], diff[0]) * invDet; + bary[2] = DotCross(diff[3], diff[0], diff[1]) * invDet; + bary[3] = (Real)1 - bary[0] - bary[1] - bary[2]; + return true; + } + + for (int i = 0; i < 4; ++i) + { + bary[i] = (Real)0; + } + return false; +} + +template +IntrinsicsVector3::IntrinsicsVector3(int numVectors, + Vector3 const* v, Real inEpsilon) + : + epsilon(inEpsilon), + dimension(0), + maxRange((Real)0), + origin({ (Real)0, (Real)0, (Real)0 }), + extremeCCW(false) +{ + min[0] = (Real)0; + min[1] = (Real)0; + min[2] = (Real)0; + direction[0] = { (Real)0, (Real)0, (Real)0 }; + direction[1] = { (Real)0, (Real)0, (Real)0 }; + direction[2] = { (Real)0, (Real)0, (Real)0 }; + extreme[0] = 0; + extreme[1] = 0; + extreme[2] = 0; + extreme[3] = 0; + + if (numVectors > 0 && v && epsilon >= (Real)0) + { + // Compute the axis-aligned bounding box for the input vectors. Keep + // track of the indices into 'vectors' for the current min and max. + int j, indexMin[3], indexMax[3]; + for (j = 0; j < 3; ++j) + { + min[j] = v[0][j]; + max[j] = min[j]; + indexMin[j] = 0; + indexMax[j] = 0; + } + + int i; + for (i = 1; i < numVectors; ++i) + { + for (j = 0; j < 3; ++j) + { + if (v[i][j] < min[j]) + { + min[j] = v[i][j]; + indexMin[j] = i; + } + else if (v[i][j] > max[j]) + { + max[j] = v[i][j]; + indexMax[j] = i; + } + } + } + + // Determine the maximum range for the bounding box. + maxRange = max[0] - min[0]; + extreme[0] = indexMin[0]; + extreme[1] = indexMax[0]; + Real range = max[1] - min[1]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[1]; + extreme[1] = indexMax[1]; + } + range = max[2] - min[2]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[2]; + extreme[1] = indexMax[2]; + } + + // The origin is either the vector of minimum x0-value, vector of + // minimum x1-value, or vector of minimum x2-value. + origin = v[extreme[0]]; + + // Test whether the vector set is (nearly) a vector. + if (maxRange <= epsilon) + { + dimension = 0; + for (j = 0; j < 3; ++j) + { + extreme[j + 1] = extreme[0]; + } + return; + } + + // Test whether the vector set is (nearly) a line segment. We need + // {direction[2],direction[3]} to span the orthogonal complement of + // direction[0]. + direction[0] = v[extreme[1]] - origin; + Normalize(direction[0], false); + if (std::abs(direction[0][0]) > std::abs(direction[0][1])) + { + direction[1][0] = -direction[0][2]; + direction[1][1] = (Real)0; + direction[1][2] = +direction[0][0]; + } + else + { + direction[1][0] = (Real)0; + direction[1][1] = +direction[0][2]; + direction[1][2] = -direction[0][1]; + } + Normalize(direction[1], false); + direction[2] = Cross(direction[0], direction[1]); + + // Compute the maximum distance of the points from the line + // origin+t*direction[0]. + Real maxDistance = (Real)0; + Real distance, dot; + extreme[2] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector3 diff = v[i] - origin; + dot = Dot(direction[0], diff); + Vector3 proj = diff - dot * direction[0]; + distance = Length(proj, false); + if (distance > maxDistance) + { + maxDistance = distance; + extreme[2] = i; + } + } + + if (maxDistance <= epsilon*maxRange) + { + // The points are (nearly) on the line origin+t*direction[0]. + dimension = 1; + extreme[2] = extreme[1]; + extreme[3] = extreme[1]; + return; + } + + // Test whether the vector set is (nearly) a planar polygon. The + // point v[extreme[2]] is farthest from the line: origin + + // t*direction[0]. The vector v[extreme[2]]-origin is not + // necessarily perpendicular to direction[0], so project out the + // direction[0] component so that the result is perpendicular to + // direction[0]. + direction[1] = v[extreme[2]] - origin; + dot = Dot(direction[0], direction[1]); + direction[1] -= dot * direction[0]; + Normalize(direction[1], false); + + // We need direction[2] to span the orthogonal complement of + // {direction[0],direction[1]}. + direction[2] = Cross(direction[0], direction[1]); + + // Compute the maximum distance of the points from the plane + // origin+t0*direction[0]+t1*direction[1]. + maxDistance = (Real)0; + Real maxSign = (Real)0; + extreme[3] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector3 diff = v[i] - origin; + distance = Dot(direction[2], diff); + Real sign = (distance >(Real)0 ? (Real)1 : + (distance < (Real)0 ? (Real)-1 : (Real)0)); + distance = std::abs(distance); + if (distance > maxDistance) + { + maxDistance = distance; + maxSign = sign; + extreme[3] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the plane origin+t0*direction[0] + // +t1*direction[1]. + dimension = 2; + extreme[3] = extreme[2]; + return; + } + + dimension = 3; + extremeCCW = (maxSign > (Real)0); + return; + } +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector4.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector4.h new file mode 100644 index 000000000000..f44c8f2b8468 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVector4.h @@ -0,0 +1,186 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.2 (2018/10/04) + +#pragma once + +#include + +namespace gte +{ + +// Template alias for convenience. +template +using Vector4 = Vector<4, Real>; + +// In GteVector3.h, the Vector3 Cross, UnitCross, and DotCross have a template +// parameter N that should be 3 or 4. The latter case supports affine vectors +// in 4D (last component w = 0) when you want to use 4-tuples and 4x4 matrices +// for affine algebra. Thus, you may use those template functions for Vector4. + +// Compute the hypercross product using the formal determinant: +// hcross = det{{e0,e1,e2,e3},{x0,x1,x2,x3},{y0,y1,y2,y3},{z0,z1,z2,z3}} +// where e0 = (1,0,0,0), e1 = (0,1,0,0), e2 = (0,0,1,0), e3 = (0,0,0,1), +// v0 = (x0,x1,x2,x3), v1 = (y0,y1,y2,y3), and v2 = (z0,z1,z2,z3). +template +Vector4 HyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2); + +// Compute the normalized hypercross product. +template +Vector4 UnitHyperCross(Vector4 const& v0, + Vector4 const& v1, Vector4 const& v2, bool robust = false); + +// Compute Dot(HyperCross((x0,x1,x2,x3),(y0,y1,y2,y3),(z0,z1,z2,z3)), +// (w0,w1,w2,w3)), where v0 = (x0,x1,x2,x3), v1 = (y0,y1,y2,y3), +// v2 = (z0,z1,z2,z3), and v3 = (w0,w1,w2,w3). +template +Real DotHyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2, Vector4 const& v3); + +// Compute a right-handed orthonormal basis for the orthogonal complement +// of the input vectors. The function returns the smallest length of the +// unnormalized vectors computed during the process. If this value is nearly +// zero, it is possible that the inputs are linearly dependent (within +// numerical round-off errors). On input, numInputs must be 1, 2, or 3 and +// v[0] through v[numInputs-1] must be initialized. On output, the vectors +// v[0] through v[3] form an orthonormal set. +template +Real ComputeOrthogonalComplement(int numInputs, Vector4* v, + bool robust = false); + + +template +Vector4 HyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2) +{ + Real m01 = v0[0] * v1[1] - v0[1] * v1[0]; // x0*y1 - y0*x1 + Real m02 = v0[0] * v1[2] - v0[2] * v1[0]; // x0*z1 - z0*x1 + Real m03 = v0[0] * v1[3] - v0[3] * v1[0]; // x0*w1 - w0*x1 + Real m12 = v0[1] * v1[2] - v0[2] * v1[1]; // y0*z1 - z0*y1 + Real m13 = v0[1] * v1[3] - v0[3] * v1[1]; // y0*w1 - w0*y1 + Real m23 = v0[2] * v1[3] - v0[3] * v1[2]; // z0*w1 - w0*z1 + return Vector4 + { + +m23*v2[1] - m13*v2[2] + m12*v2[3], // +m23*y2 - m13*z2 + m12*w2 + -m23*v2[0] + m03*v2[2] - m02*v2[3], // -m23*x2 + m03*z2 - m02*w2 + +m13*v2[0] - m03*v2[1] + m01*v2[3], // +m13*x2 - m03*y2 + m01*w2 + -m12*v2[0] + m02*v2[1] - m01*v2[2] // -m12*x2 + m02*y2 - m01*z2 + }; +} + +template +Vector4 UnitHyperCross(Vector4 const& v0, + Vector4 const& v1, Vector4 const& v2, bool robust) +{ + Vector4 unitHyperCross = HyperCross(v0, v1, v2); + Normalize(unitHyperCross, robust); + return unitHyperCross; +} + +template +Real DotHyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2, Vector4 const& v3) +{ + return Dot(HyperCross(v0, v1, v2), v3); +} + +template +Real ComputeOrthogonalComplement(int numInputs, Vector4* v, bool robust) +{ + if (numInputs == 1) + { + int maxIndex = 0; + Real maxAbsValue = std::abs(v[0][0]); + for (int i = 1; i < 4; ++i) + { + Real absValue = std::abs(v[0][i]); + if (absValue > maxAbsValue) + { + maxIndex = i; + maxAbsValue = absValue; + } + } + + if (maxIndex < 2) + { + v[1][0] = -v[0][1]; + v[1][1] = +v[0][0]; + v[1][2] = (Real)0; + v[1][3] = (Real)0; + } + else if (maxIndex == 3) + { + // Generally, you can skip this clause and swap the last two + // components. However, by swapping 2 and 3 in this case, we + // allow the function to work properly when the inputs are 3D + // vectors represented as 4D affine vectors (w = 0). + v[1][0] = (Real)0; + v[1][1] = +v[0][2]; + v[1][2] = -v[0][1]; + v[1][3] = (Real)0; + } + else + { + v[1][0] = (Real)0; + v[1][1] = (Real)0; + v[1][2] = -v[0][3]; + v[1][3] = +v[0][2]; + } + + numInputs = 2; + } + + if (numInputs == 2) + { + Real det[6] = + { + v[0][0] * v[1][1] - v[1][0] * v[0][1], + v[0][0] * v[1][2] - v[1][0] * v[0][2], + v[0][0] * v[1][3] - v[1][0] * v[0][3], + v[0][1] * v[1][2] - v[1][1] * v[0][2], + v[0][1] * v[1][3] - v[1][1] * v[0][3], + v[0][2] * v[1][3] - v[1][2] * v[0][3] + }; + + int maxIndex = 0; + Real maxAbsValue = std::abs(det[0]); + for (int i = 1; i < 6; ++i) + { + Real absValue = std::abs(det[i]); + if (absValue > maxAbsValue) + { + maxIndex = i; + maxAbsValue = absValue; + } + } + + if (maxIndex == 0) + { + v[2] = { -det[4], +det[2], (Real)0, -det[0] }; + } + else if (maxIndex <= 2) + { + v[2] = { +det[5], (Real)0, -det[2], +det[1] }; + } + else + { + v[2] = { (Real)0, -det[5], +det[4], -det[3] }; + } + + numInputs = 3; + } + + if (numInputs == 3) + { + v[3] = HyperCross(v[0], v[1], v[2]); + return Orthonormalize<4, Real>(4, v, robust); + } + + return (Real)0; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexAttribute.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexAttribute.h new file mode 100644 index 000000000000..b6e61b2eb201 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexAttribute.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.3.0 (2016/08/29) + +#pragma once + +#include +#include + +namespace gte +{ + +struct VertexAttribute +{ + inline VertexAttribute(); + inline VertexAttribute(std::string const& inSemantic, void* inSource, size_t inStride); + + // The 'semantic' string allows you to query for a specific vertex + // attribute and use the 'source' and 'stride' to access the data + // of the attribute. For example, you might use the semantics + // "position" (px,py,pz), "normal" (nx,ny,nz), "tcoord" (texture + // coordinates (u,v)), "dpdu" (derivative of position with respect + // to u), or "dpdv" (derivative of position with respect to v) for + // mesh vertices. + // + // The source pointer must be 4-byte aligned. The stride must be + // positive and a multiple of 4. The pointer alignment constraint is + // guaranteed on 32-bit and 64-bit architectures. The stride constraint + // is reasonable given that (usually) geometric attributes are usually + // arrays of 'float' or 'double'. + + std::string semantic; + void* source; + size_t stride; +}; + +inline VertexAttribute::VertexAttribute() + : + semantic(""), + source(nullptr), + stride(0) +{ +} + +inline VertexAttribute::VertexAttribute(std::string const& inSemantic, void* inSource, size_t inStride) + : + semantic(inSemantic), + source(inSource), + stride(inStride) +{ +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexCollapseMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexCollapseMesh.h new file mode 100644 index 000000000000..19211f8c4f8a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Private/ThirdParty/GTEngine/Mathematics/GteVertexCollapseMesh.h @@ -0,0 +1,546 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2019 +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// File Version: 3.0.1 (2018/10/04) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace gte +{ + +template +class VertexCollapseMesh +{ +public: + // Construction. + VertexCollapseMesh(int numPositions, Vector3 const* positions, + int numIndices, int const* indices); + + // Decimate the mesh using vertex collapses + struct Record + { + // The index of the interior vertex that is removed from the mesh. + // The triangles adjacent to the vertex are 'removed' from the mesh. + // The polygon boundary of the adjacent triangles is triangulated and + // the new triangles are 'inserted' into the mesh. + int vertex; + std::vector> removed; + std::vector> inserted; + }; + + // Return 'true' when a vertex collapse occurs. Once the function returns + // 'false', no more vertex collapses are allowed so you may then stop + // calling the function. The implementation has several consistency tests + // that should not fail with a theoretically correct implementation. If a + // test fails, the function returns 'false' and the record.vertex is set to + // the invalid integer 0x80000000. When the Logger system is enabled, the + // failed tests are reported to any Logger listeners. + bool DoCollapse(Record& record); + + // Access the current state of the mesh, whether the original built in the + // constructor or a decimated mesh during DoCollapse calls. + inline ETManifoldMesh const& GetMesh() const; + +private: + struct VCVertex : public VETManifoldMesh::Vertex + { + VCVertex(int v); + static std::shared_ptr Create(int v); + + // The weight depends on the area of the triangles sharing the vertex + // and the lengths of the projections of the adjacent vertices onto + // the vertex normal line. A side effect of the call is that the + // vertex normal is computed and stored. + Real ComputeWeight(Vector3 const* positions); + + Vector3 normal; + bool isBoundary; + }; + + // The functions TriangulateLink and Collapsed return one of the + // enumerates described next. + // + // VCM_NO_MORE_ALLOWED: + // Either the mesh has no more interior vertices or a collapse + // will lead to a mesh fold-over or to a nonmanifold mesh. The + // returned value 'v' is invalid (0x80000000) and 'removed' and + // 'inserted' are empty. + // + // VCM_ALLOWED: + // An interior vertex v has been removed. This is allowed using the + // following algorithm. The vertex normal is the weighted average + // of non-unit-length normals of triangles sharing v. The weights + // are the triangle areas. The adjacent vertices are projected onto + // a plane containing v and having normal equal to the vertex normal. + // If the projection is a simple polygon in the plane, the collapse + // is allowed. The triangles sharing v are 'removed', the polygon is + // triangulated, and the new triangles are 'inserted' into the mesh. + // + // VCM_DEFERRED: + // If the projection polygon described in the previous case is not + // simple (at least one pair of edges overlaps at some edge-interior + // point), the collapse would produce a fold-over in the mesh. We + // do not collapse in this case. It is possible that such a vertex + // occurs in a later collapse as its neighbors are adjusted by + // collapses. When this case occurs, v is valid (even though the + // collapse was not allowed) but 'removed' and 'inserted' are empty. + // + // VCM_UNEXPECTED_ERROR: + // The code has several tests for conditions that are not expected + // to occur for a theoretically correct implementation. If you + // receive this error, file a bug report and provide a data set + // that caused the error. + enum + { + VCM_NO_MORE_ALLOWED, + VCM_ALLOWED, + VCM_DEFERRED, + VCM_UNEXPECTED_ERROR + }; + + int TriangulateLink(std::shared_ptr const& vertex, std::vector>& removed, + std::vector>& inserted, std::vector& linkVertices) const; + + int Collapsed(std::vector> const& removed, + std::vector> const& inserted, std::vector const& linkVertices); + + int mNumPositions; + Vector3 const* mPositions; + VETManifoldMesh mMesh; + + MinHeap mMinHeap; + std::map::Record*> mHeapRecords; +}; + + +template +VertexCollapseMesh::VertexCollapseMesh(int numPositions, + Vector3 const* positions, int numIndices, int const* indices) + : + mNumPositions(numPositions), + mPositions(positions), + mMesh(VCVertex::Create) +{ + if (numPositions <= 0 || !positions || numIndices < 3 || !indices) + { + mNumPositions = 0; + mPositions = nullptr; + return; + } + + // Build the manifold mesh from the inputs. + int numTriangles = numIndices / 3; + int const* current = indices; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *current++; + int v1 = *current++; + int v2 = *current++; + mMesh.Insert(v0, v1, v2); + } + + // Locate the vertices (if any) on the mesh boundary. + auto const& vmap = mMesh.GetVertices(); + for (auto const& eelement : mMesh.GetEdges()) + { + auto edge = eelement.second; + if (!edge->T[1].lock()) + { + for (int i = 0; i < 2; ++i) + { + auto velement = vmap.find(edge->V[i]); + auto vertex = std::static_pointer_cast(velement->second); + vertex->isBoundary = true; + } + } + } + + // Build the priority queue of weights for the interior vertices. + mMinHeap.Reset((int)vmap.size()); + for (auto const& velement : vmap) + { + auto vertex = std::static_pointer_cast(velement.second); + + Real weight; + if (vertex->isBoundary) + { + weight = std::numeric_limits::max(); + } + else + { + weight = vertex->ComputeWeight(mPositions); + } + + auto record = mMinHeap.Insert(velement.first, weight); + mHeapRecords.insert(std::make_pair(velement.first, record)); + } +} + +template +bool VertexCollapseMesh::DoCollapse(Record& record) +{ + record.vertex = 0x80000000; + record.removed.clear(); + record.inserted.clear(); + + if (mNumPositions == 0) + { + // The constructor failed, so there is nothing to collapse. + return false; + } + + while (mMinHeap.GetNumElements() > 0) + { + int v = -1; + Real weight = std::numeric_limits::max(); + mMinHeap.GetMinimum(v, weight); + if (weight == std::numeric_limits::max()) + { + // There are no more interior vertices to collapse. + return false; + } + + auto const& vmap = mMesh.GetVertices(); + auto velement = vmap.find(v); + if (velement == vmap.end()) + { + LogError("Unexpected condition."); + return false; + } + + auto vertex = std::static_pointer_cast(velement->second); + std::vector> removed, inserted; + std::vector linkVertices; + int result = TriangulateLink(vertex, removed, inserted, linkVertices); + if (result == VCM_UNEXPECTED_ERROR) + { + return false; + } + + if (result == VCM_ALLOWED) + { + result = Collapsed(removed, inserted, linkVertices); + if (result == VCM_UNEXPECTED_ERROR) + { + return false; + } + + if (result == VCM_ALLOWED) + { + // Remove the vertex and associated weight. + mMinHeap.Remove(v, weight); + mHeapRecords.erase(v); + + // Update the weights of the link vertices. + for (auto vlink : linkVertices) + { + velement = vmap.find(vlink); + if (velement == vmap.end()) + { + LogError("Unexpected condition."); + return false; + } + + vertex = std::static_pointer_cast(velement->second); + if (!vertex->isBoundary) + { + auto iter = mHeapRecords.find(vlink); + if (iter == mHeapRecords.end()) + { + LogError("Unexpected condition."); + return false; + } + + weight = vertex->ComputeWeight(mPositions); + mMinHeap.Update(iter->second, weight); + } + } + + record.vertex = v; + record.removed = std::move(removed); + record.inserted = std::move(inserted); + return true; + } + // else: result == VCM_DEFERRED + } + + // To get here, result must be VCM_DEFERRED. The vertex collapse + // would cause mesh fold-over. Temporarily set the edge weight to + // infinity. After removal of other triangles, the vertex weight + // will be updated to a finite value and the vertex possibly can be + // removed at that time. + auto iter = mHeapRecords.find(v); + if (iter == mHeapRecords.end()) + { + LogError("Unexpected condition."); + return false; + } + mMinHeap.Update(iter->second, std::numeric_limits::max()); + } + + // We do not expect to reach this line of code, even for a closed mesh. + // However, the compiler does not know this, yet requires a return value. + return false; +} + +template inline +ETManifoldMesh const& VertexCollapseMesh::GetMesh() const +{ + return mMesh; +} + +template +int VertexCollapseMesh::TriangulateLink(std::shared_ptr const& vertex, + std::vector>& removed, std::vector>& inserted, + std::vector& linkVertices) const +{ + // Create the (CCW) polygon boundary of the link of the vertex. The + // incoming vertex is interior, so the number of triangles sharing the + // vertex is equal to the number of vertices of the polygon. A + // precondition of the function call is that the vertex normal has already + // been computed. + + // Get the edges of the link that are opposite the incoming vertex. + int const numVertices = static_cast(vertex->TAdjacent.size()); + removed.resize(numVertices); + int j = 0; + std::map edgeMap; + for (auto tri : vertex->TAdjacent) + { + for (int i = 0; i < 3; ++i) + { + if (tri->V[i] == vertex->V) + { + edgeMap.insert(std::make_pair(tri->V[(i + 1) % 3], tri->V[(i + 2) % 3])); + break; + } + } + removed[j++] = TriangleKey(tri->V[0], tri->V[1], tri->V[2]); + } + if (edgeMap.size() != vertex->TAdjacent.size()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + // Connect the edges into a polygon. + linkVertices.resize(numVertices); + auto iter = edgeMap.begin(); + for (int i = 0; i < numVertices; ++i) + { + linkVertices[i] = iter->first; + iter = edgeMap.find(iter->second); + if (iter == edgeMap.end()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + } + if (iter->first != linkVertices[0]) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + // Project the polygon onto the plane containing the incoming vertex and + // having the vertex normal. The projected polygon is computed so that + // the incoming vertex is projected to (0,0). + Vector3 center = mPositions[vertex->V]; + Vector3 basis[3]; + basis[0] = vertex->normal; + ComputeOrthogonalComplement(1, basis); + std::vector> projected(numVertices); + std::vector indices(numVertices); + for (int i = 0; i < numVertices; ++i) + { + Vector3 diff = mPositions[linkVertices[i]] - center; + projected[i][0] = Dot(basis[1], diff); + projected[i][1] = Dot(basis[2], diff); + indices[i] = i; + } + + // The polygon must be simple in order to triangulate it. + Polygon2 polygon(projected.data(), numVertices, indices.data(), true); + if (polygon.IsSimple()) + { + TriangulateEC triangulator(numVertices, projected.data()); + triangulator(); + auto const& triangles = triangulator.GetTriangles(); + if (triangles.size() == 0) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + int const numTriangles = static_cast(triangles.size()); + inserted.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + inserted[t] = TriangleKey( + linkVertices[triangles[t][0]], + linkVertices[triangles[t][1]], + linkVertices[triangles[t][2]]); + } + return VCM_ALLOWED; + } + else + { + return VCM_DEFERRED; + } +} + +template +int VertexCollapseMesh::Collapsed(std::vector> const& removed, + std::vector> const& inserted, std::vector const& linkVertices) +{ + // The triangles that were disconnected from the link edges are guaranteed + // to allow manifold reconnection to 'inserted' triangles. On the + // insertion, each diagonal of the link becomes a mesh edge and shares two + // (link) triangles. It is possible that the mesh already contains the + // (diagonal) edge, which will lead to a nonmanifold connection, which we + // cannot allow. The following code traps this condition and restores the + // mesh to its state before the 'Remove(...)' call. + bool isCollapsible = true; + auto const& emap = mMesh.GetEdges(); + std::set> edges; + for (auto const& tri : inserted) + { + for (int k0 = 2, k1 = 0; k1 < 3; k0 = k1++) + { + EdgeKey edge(tri.V[k0], tri.V[k1]); + if (edges.find(edge) == edges.end()) + { + edges.insert(edge); + } + else + { + // The edge has been visited twice, so it is a diagonal of + // the link. + + auto eelement = emap.find(edge); + if (eelement != emap.end()) + { + if (eelement->second->T[1].lock()) + { + // The edge will not allow a manifold connection. + isCollapsible = false; + break; + } + } + + edges.erase(edge); + } + }; + + if (!isCollapsible) + { + return VCM_DEFERRED; + } + } + + // Remove the old triangle neighborhood, which will lead to the vertex + // itself being removed from the mesh. + for (auto tri : removed) + { + mMesh.Remove(tri.V[0], tri.V[1], tri.V[2]); + } + + // Insert the new triangulation. + for (auto const& tri : inserted) + { + mMesh.Insert(tri.V[0], tri.V[1], tri.V[2]); + } + + // If the Remove(...) calls remove a boundary vertex that is in the link + // vertices, the Insert(...) calls will insert the boundary vertex again. + // We must re-tag those boundary vertices. + auto const& vmap = mMesh.GetVertices(); + size_t const numVertices = linkVertices.size(); + for (size_t i0 = numVertices - 1, i1 = 0; i1 < numVertices; i0 = i1++) + { + EdgeKey ekey(linkVertices[i0], linkVertices[i1]); + auto eelement = emap.find(ekey); + if (eelement == emap.end()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + auto edge = eelement->second; + if (!edge) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + if (edge->T[0].lock() && !edge->T[1].lock()) + { + for (int k = 0; k < 2; ++k) + { + auto velement = vmap.find(edge->V[k]); + if (velement == vmap.end()) + { + LogError("Unexpected condition."); + return VCM_UNEXPECTED_ERROR; + } + + auto vertex = std::static_pointer_cast(velement->second); + vertex->isBoundary = true; + } + } + } + + return VCM_ALLOWED; +} + +template +VertexCollapseMesh::VCVertex::VCVertex(int v) + : + VETManifoldMesh::Vertex(v), + normal(Vector3::Zero()), + isBoundary(false) +{ +} + +template +std::shared_ptr VertexCollapseMesh::VCVertex::Create(int v) +{ + return std::make_shared(v); +} + +template +Real VertexCollapseMesh::VCVertex::ComputeWeight(Vector3 const* positions) +{ + Real weight = (Real)0; + + normal = { (Real)0, (Real)0, (Real)0 }; + for (auto const& tri : TAdjacent) + { + Vector3 E0 = positions[tri->V[1]] - positions[tri->V[0]]; + Vector3 E1 = positions[tri->V[2]] - positions[tri->V[0]]; + Vector3 N = Cross(E0, E1); + normal += N; + weight += Length(N); + } + Normalize(normal); + + for (int index : VAdjacent) + { + Vector3 diff = positions[index] - positions[V]; + weight += std::abs(Dot(normal, diff)); + } + + return weight; +} + +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/Arrangement2d.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/Arrangement2d.h new file mode 100644 index 000000000000..7b322570fc26 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/Arrangement2d.h @@ -0,0 +1,515 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +// Port of geometry3Sharp Arrangement2d + +#pragma once + +#include "BoxTypes.h" +#include "Curve/DynamicGraph2.h" +#include "Intersection/IntrSegment2Segment2.h" +#include "Polygon2.h" +#include "Spatial/PointHashGrid2.h" +#include "Util/GridIndexing2.h" + +#include "CoreMinimal.h" + +/** + * Arrangement2d constructs a planar arrangement of a set of 2D line segments. + * When a segment is inserted, existing edges are split, and the inserted + * segment becomes multiple graph edges. So, the resulting FDynamicGraph2d should + * not have any edges that intersect. + * + * Calculations are performed in double-precision, so there is no guarantee + * of correctness. + * + * + * [TODO] multi-level segment has to accelerate find_intersecting_edges() + * [TODO] maybe smarter handling + * + */ +struct FArrangement2d +{ + // graph of arrangement + FDynamicGraph2d Graph; + + // PointHash for vertices of graph + TPointHashGrid2d PointHash; + + // points within this tolerance are merged + double VertexSnapTol = 0.00001; + + FArrangement2d(const FAxisAlignedBox2d& BoundsHint) + : PointHash(BoundsHint.MaxDim() / 64, -1) + { + } + + FArrangement2d(double PointHashCellSize) + : PointHash(PointHashCellSize, -1) + { + } + + /** + * Attempts to triangulates the arrangement with a constrained delaunay triangulation + * NOTE: Not robust; generates invalid triangulations and fails to insert edges sometimes, even if the arrangement has no self-intersections + * But should always do *something* that at least covers the point set, if the point set is not degenerate + * TODO: Make a robust triangulation algo + * + * Triangles: Output triangles (as indices into Graph vertices) + * SkippedEdges: Output indices of edges that the algorithm failed to insert + * BoundaryEdgeGroupID: ID of edges corresponding to a boundary; if we have a closed loop of these boundary edges on output triangulation, will discard triangles outside this + * return: false if triangulation algo knows it failed (NOTE: triangulation may still be invalid when function returns true, as function is not robust) + */ + bool GEOMETRYALGORITHMS_API AttemptTriangulate(TArray& Triangles, TArray& SkippedEdges, int32 BoundaryEdgeGroupID); + + /** + * Check if current Graph has self-intersections; not optimized, only for debugging + */ + bool HasSelfIntersections() + { + for (const FDynamicGraph::FEdge& e : Graph.Edges()) + { + TArray Hits; + int HitCount = find_intersecting_edges(Graph.GetVertex(e.A), Graph.GetVertex(e.B), Hits, 0.0); + for (const FIntersection& Intersect : Hits) + { + FDynamicGraph::FEdge o = Graph.GetEdge(Intersect.EID); + if (o.A != e.A && o.A != e.B && o.B != e.A && o.B != e.B) + { + return true; + } + } + } + return false; + } + + /** + * Subdivide edge at a given position + */ + FIndex2i SplitEdgeAtPoint(int EdgeID, FVector2d Point) + { + FDynamicGraph2d::FEdgeSplitInfo splitInfo; + EMeshResult result = Graph.SplitEdge(EdgeID, splitInfo); + ensureMsgf(result == EMeshResult::Ok, TEXT("SplitEdgeAtPoint: edge split failed?")); + Graph.SetVertex(splitInfo.VNew, Point); + PointHash.InsertPointUnsafe(splitInfo.VNew, Point); + return FIndex2i(splitInfo.VNew, splitInfo.ENewBN); + } + + /** + * Check if vertex exists in region + */ + bool HasVertexNear(FVector2d Point, double SearchRadius) + { + return find_nearest_vertex(Point, SearchRadius) > -1; + } + + /** + * Insert isolated point P into the arrangement + */ + int Insert(const FVector2d& Pt) + { + return insert_point(Pt, VertexSnapTol); + } + + + /** + * insert segment [A,B] into the arrangement + */ + void Insert(const FVector2d& A, const FVector2d& B, int GID = -1) + { + insert_segment(A, B, GID, VertexSnapTol); + } + + /** + * insert segment into the arrangement + */ + void Insert(const FSegment2d& Segment, int GID = -1) + { + insert_segment(Segment.StartPoint(), Segment.EndPoint(), GID, VertexSnapTol); + } + + ///** + // * sequentially insert segments of polyline + // */ + //void Insert(PolyLine2d pline, int GID = -1) + //{ + // int N = pline.VertexCount - 1; + // for (int i = 0; i < N; ++i) { + // FVector2d A = pline[i]; + // FVector2d B = pline[i + 1]; + // insert_segment(A, B, GID); + // } + //} + + ///** + // * sequentially insert segments of polygon + // */ + void Insert(const FPolygon2d& Poly, int GID = -1) + { + int N = Poly.VertexCount(); + for (int i = 0; i < N; ++i) + { + insert_segment(Poly[i], Poly[(i + 1) % N], GID, VertexSnapTol); + } + } + + /* + * Graph improvement + */ + + /** + * connect open boundary vertices within DistThresh, by inserting new segments + */ + void ConnectOpenBoundaries(double DistThresh) + { + int max_vid = Graph.MaxVertexID(); + for (int VID = 0; VID < max_vid; ++VID) + { + if (Graph.IsBoundaryVertex(VID) == false) + { + continue; + } + + FVector2d v = Graph.GetVertex(VID); + int snap_with = find_nearest_boundary_vertex(v, DistThresh, VID); + if (snap_with != -1) + { + FVector2d v2 = Graph.GetVertex(snap_with); + Insert(v, v2); + } + } + } + +protected: + struct FSegmentPoint + { + double T; + int VID; + }; + + /** + * insert pt P into the arrangement, splitting existing edges as necessary + */ + int insert_point(const FVector2d &P, double Tol = 0) + { + int PIdx = find_existing_vertex(P); + if (PIdx > -1) + { + return -1; + } + + // TODO: currently this tries to add the vertex on the closest edge below tolerance; we should instead insert at *every* edge below tolerance! ... but that is more inconvenient to write + FVector2d x = FVector2d::Zero(), y = FVector2d::Zero(); + double ClosestDistSq = Tol*Tol; + int FoundEdgeToSplit = -1; + for (int EID = 0, ExistingEdgeMax = Graph.MaxEdgeID(); EID < ExistingEdgeMax; EID++) + { + if (!Graph.IsEdge(EID)) + { + continue; + } + + Graph.GetEdgeV(EID, x, y); + FSegment2d Seg(x, y); + double DistSq = Seg.DistanceSquared(P); + if (DistSq < ClosestDistSq) + { + ClosestDistSq = DistSq; + FoundEdgeToSplit = EID; + } + } + if (FoundEdgeToSplit > -1) + { + FDynamicGraph2d::FEdgeSplitInfo splitInfo; + EMeshResult result = Graph.SplitEdge(FoundEdgeToSplit, splitInfo); + ensureMsgf(result == EMeshResult::Ok, TEXT("insert_into_segment: edge split failed?")); + Graph.SetVertex(splitInfo.VNew, P); + PointHash.InsertPointUnsafe(splitInfo.VNew, P); + return splitInfo.VNew; + } + + int VID = Graph.AppendVertex(P); + PointHash.InsertPointUnsafe(VID, P); + return VID; + } + + /** + * insert edge [A,B] into the arrangement, splitting existing edges as necessary + */ + bool insert_segment(FVector2d A, FVector2d B, int GID = -1, double Tol = 0) + { + // handle degenerate edges + int a_idx = find_existing_vertex(A); + int b_idx = find_existing_vertex(B); + if (a_idx == b_idx && a_idx >= 0) + { + return false; + } + // snap input vertices + if (a_idx >= 0) + { + A = Graph.GetVertex(a_idx); + } + if (b_idx >= 0) + { + B = Graph.GetVertex(b_idx); + } + + // handle tiny-segment case + double SegLenSq = A.DistanceSquared(B); + if (SegLenSq <= VertexSnapTol*VertexSnapTol) + { + // seg is too short and was already on an existing vertex; just consider that vertex to be the inserted segment + if (a_idx >= 0 || b_idx >= 0) + { + return false; + } + // seg is too short and wasn't on an existing vertex; add it as an isolated vertex + return insert_point(A, Tol) != -1; + } + + // ok find all intersections + TArray Hits; + find_intersecting_edges(A, B, Hits, Tol); + + // we are going to construct a list of values along segment AB + TArray points; + FSegment2d segAB = FSegment2d(A, B); + + find_intersecting_floating_vertices(segAB, a_idx, b_idx, points, Tol); + + // insert intersections into existing segments + for (int i = 0, N = Hits.Num(); i < N; ++i) + { + FIntersection Intr = Hits[i]; + int EID = Intr.EID; + double t0 = Intr.Intr.Parameter0, t1 = Intr.Intr.Parameter1; + + // insert first point at t0 + int new_eid = -1; + if (Intr.Intr.Type == EIntersectionType::Point || Intr.Intr.Type == EIntersectionType::Segment) + { + FIndex2i new_info = split_segment_at_t(EID, t0, VertexSnapTol); + new_eid = new_info.B; + FVector2d v = Graph.GetVertex(new_info.A); + points.Add(FSegmentPoint{segAB.Project(v), new_info.A}); + } + + // if intersection was on-segment, then we have a second point at t1 + if (Intr.Intr.Type == EIntersectionType::Segment) + { + if (new_eid == -1) + { + // did not actually split edge for t0, so we can still use EID + FIndex2i new_info = split_segment_at_t(EID, t1, VertexSnapTol); + FVector2d v = Graph.GetVertex(new_info.A); + points.Add(FSegmentPoint{segAB.Project(v), new_info.A}); + } + else + { + // find t1 was in EID, rebuild in new_eid + FSegment2d new_seg = Graph.GetEdgeSegment(new_eid); + FVector2d p1 = Intr.Intr.GetSegment1().PointAt(t1); + double new_t1 = new_seg.Project(p1); + check(new_t1 <= FMath::Abs(new_seg.Extent)); + + FIndex2i new_info = split_segment_at_t(new_eid, new_t1, VertexSnapTol); + FVector2d v = Graph.GetVertex(new_info.A); + points.Add(FSegmentPoint{segAB.Project(v), new_info.A}); + } + } + } + + // find or create start and end points + if (a_idx == -1) + { + a_idx = find_existing_vertex(A); + } + if (a_idx == -1) + { + a_idx = Graph.AppendVertex(A); + PointHash.InsertPointUnsafe(a_idx, A); + } + if (b_idx == -1) + { + b_idx = find_existing_vertex(B); + } + if (b_idx == -1) + { + b_idx = Graph.AppendVertex(B); + PointHash.InsertPointUnsafe(b_idx, B); + } + + // add start/end to points list. These may be duplicates but we will sort that out after + points.Add(FSegmentPoint{-segAB.Extent, a_idx}); + points.Add(FSegmentPoint{segAB.Extent, b_idx}); + // sort by T + points.Sort([](const FSegmentPoint& pa, const FSegmentPoint& pb) { return pa.T < pb.T; }); + + // connect sequential points, as long as they aren't the same point, + // and the segment doesn't already exist + for (int k = 0; k < points.Num() - 1; ++k) + { + int v0 = points[k].VID; + int v1 = points[k + 1].VID; + if (v0 == v1) + { + continue; + } + + // sanity check + ensureMsgf(FMath::Abs(points[k].T - points[k + 1].T) >= std::numeric_limits::epsilon(), TEXT("insert_segment: different points have same T??")); + + if (Graph.FindEdge(v0, v1) == FDynamicGraph2d::InvalidID) + { + Graph.AppendEdge(v0, v1, GID); + } + } + + return true; + } + + /** + * insert new point into segment EID at parameter value T + * If T is within Tol of endpoint of segment, we use that instead. + */ + FIndex2i split_segment_at_t(int EID, double T, double Tol) + { + FVector2d V1, V2; + FIndex2i ev = Graph.GetEdgeV(EID); + FSegment2d seg = FSegment2d(Graph.GetVertex(ev.A), Graph.GetVertex(ev.B)); + + int use_vid = -1; + int new_eid = -1; + if (T < -(seg.Extent - Tol)) + { + use_vid = ev.A; + } + else if (T > (seg.Extent - Tol)) + { + use_vid = ev.B; + } + else + { + FVector2d Pt = seg.PointAt(T); + // ensure(find_existing_vertex(Pt) == -1); // TODO: handle this case!? + FDynamicGraph2d::FEdgeSplitInfo splitInfo; + EMeshResult result = Graph.SplitEdge(EID, splitInfo); + ensureMsgf(result == EMeshResult::Ok, TEXT("insert_into_segment: edge split failed?")); + use_vid = splitInfo.VNew; + new_eid = splitInfo.ENewBN; + Graph.SetVertex(use_vid, Pt); + PointHash.InsertPointUnsafe(splitInfo.VNew, Pt); + } + return FIndex2i(use_vid, new_eid); + } + + /** + * find existing vertex at point, if it exists + */ + int find_existing_vertex(FVector2d Pt) + { + return find_nearest_vertex(Pt, VertexSnapTol); + } + /** + * find closest vertex, within SearchRadius + */ + int find_nearest_vertex(FVector2d Pt, double SearchRadius, int IgnoreVID = -1) + { + auto FuncDistSq = [&](int B) { return Pt.DistanceSquared(Graph.GetVertex(B)); }; + auto FuncIgnore = [&](int VID) { return VID == IgnoreVID; }; + TPair found = (IgnoreVID == -1) ? PointHash.FindNearestInRadius(Pt, SearchRadius, FuncDistSq) + : PointHash.FindNearestInRadius(Pt, SearchRadius, FuncDistSq, FuncIgnore); + if (found.Key == PointHash.InvalidValue()) + { + return -1; + } + return found.Key; + } + + /** + * find nearest boundary vertex, within SearchRadius + */ + int find_nearest_boundary_vertex(FVector2d Pt, double SearchRadius, int IgnoreVID = -1) + { + auto FuncDistSq = [&](int B) { return Pt.DistanceSquared(Graph.GetVertex(B)); }; + auto FuncIgnore = [&](int VID) { return Graph.IsBoundaryVertex(VID) == false || VID == IgnoreVID; }; + TPair found = + PointHash.FindNearestInRadius(Pt, SearchRadius, FuncDistSq, FuncIgnore); + if (found.Key == PointHash.InvalidValue()) + { + return -1; + } + return found.Key; + } + + struct FIntersection + { + int EID; + int SideX; + int SideY; + FIntrSegment2Segment2d Intr; + }; + + /** + * find set of edges in graph that intersect with edge [A,B] + */ + bool find_intersecting_edges(FVector2d A, FVector2d B, TArray& Hits, double Tol = 0) + { + int num_hits = 0; + FVector2d x = FVector2d::Zero(), y = FVector2d::Zero(); + FVector2d EPerp = (B - A).Perp(); + EPerp.Normalize(); + for (int EID : Graph.EdgeIndices()) + { + Graph.GetEdgeV(EID, x, y); + // inlined version of WhichSide with pre-normalized EPerp, to ensure Tolerance is consistent for different edge lengths + double SignX = EPerp.Dot(x - A); + double SignY = EPerp.Dot(y - A); + int SideX = (SignX > Tol ? +1 : (SignX < -Tol ? -1 : 0)); + int SideY = (SignY > Tol ? +1 : (SignY < -Tol ? -1 : 0)); + if (SideX == SideY && SideX != 0) + { + continue; // both pts on same side + } + + FIntrSegment2Segment2d Intr(FSegment2d(x, y), FSegment2d(A, B)); + Intr.SetIntervalThreshold(Tol); + // set a loose DotThreshold as well so almost-parallel segments are treated as parallel; + // otherwise we're more likely to hit later problems when an edge intersects near-overlapping edges at almost the same point + // (TODO: detect + handle that case!) + Intr.SetDotThreshold(1e-4); + if (Intr.Find()) + { + Hits.Add(FIntersection{EID, SideX, SideY, Intr}); + num_hits++; + } + } + + return (num_hits > 0); + } + + bool find_intersecting_floating_vertices(const FSegment2d &SegAB, int32 AID, int32 BID, TArray& Hits, double Tol = 0) + { + int num_hits = 0; + + for (int VID : Graph.VertexIndices()) + { + if (Graph.GetVtxEdgeCount(VID) > 0 || VID == AID || VID == BID) // if it's an existing edge or on the currently added edge, it's not floating so skip it + { + continue; + } + + FVector2d V = Graph.GetVertex(VID); + double T; + double DSQ = SegAB.DistanceSquared(V, T); + if (DSQ < Tol*Tol) + { + Hits.Add(FSegmentPoint{ T, VID }); + num_hits++; + } + } + + return num_hits > 0; + } +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/GeometryAlgorithmsModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/GeometryAlgorithmsModule.h new file mode 100644 index 000000000000..21bd24fa5f4f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/GeometryAlgorithms/Public/GeometryAlgorithmsModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FGeometryAlgorithmsModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/MeshConversion.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/MeshConversion.Build.cs new file mode 100644 index 000000000000..3b8573a2163a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/MeshConversion.Build.cs @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MeshConversion : ModuleRules +{ + public MeshConversion(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "MeshDescription", + "GeometricObjects", + "DynamicMesh" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine" + } + ); + } +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp new file mode 100644 index 000000000000..4701ddcd07ee --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/DynamicMeshToMeshDescription.cpp @@ -0,0 +1,246 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DynamicMeshToMeshDescription.h" +#include "MeshAttributes.h" +#include "DynamicMeshAttributeSet.h" +#include "DynamicMeshOverlay.h" +#include "MeshDescriptionBuilder.h" +#include "MeshNormals.h" + + + +void FDynamicMeshToMeshDescription::Update(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + check(MeshIn->IsCompactV()); + check(MeshIn->VertexCount() == MeshOut.Vertices().Num()); + + // update positions + for (int VertID : MeshIn->VertexIndicesItr()) + { + Builder.SetPosition(FVertexID(VertID), MeshIn->GetVertex(VertID)); + } + + // can't trust these yet... + //const FDynamicMeshUVOverlay* UVOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryUV() : nullptr; + //const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; + + Builder.RecalculateInstanceNormals(); +} + + + + + + + + +void FDynamicMeshToMeshDescription::Convert(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + if (MeshIn->HasAttributes()) + { + Convert_SharedInstances(MeshIn, MeshOut); + } + else + { + Convert_NoAttributes(MeshIn, MeshOut); + } +} + + +void FDynamicMeshToMeshDescription::Convert_NoAttributes(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + MeshOut.Empty(); + + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + bool bCopyGroupToPolyGroup = false; + if (bSetPolyGroups && MeshIn->HasTriangleGroups()) + { + Builder.EnablePolyGroups(); + bCopyGroupToPolyGroup = true; + } + + // create vertices + TArray MapV; + MapV.SetNum(MeshIn->MaxVertexID()); + for (int VertID : MeshIn->VertexIndicesItr()) + { + MapV[VertID] = Builder.AppendVertex(MeshIn->GetVertex(VertID)); + } + + FMeshNormals VertexNormals(MeshIn); + VertexNormals.ComputeVertexNormals(); + + FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); + + // create new instances when seen + TMap InstanceList; + for (int TriID : MeshIn->TriangleIndicesItr()) + { + FIndex3i Triangle = MeshIn->GetTriangle(TriID); + FIndex3i UVTriangle(-1, -1, -1); + FIndex3i NormalTriangle = Triangle; + FVertexInstanceID InstanceTri[3]; + for (int j = 0; j < 3; ++j) + { + FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); + if (InstanceList.Contains(InstanceElem) == false) + { + FVertexInstanceID NewInstanceID = Builder.AppendInstance(MapV[Triangle[j]]); + InstanceList.Add(InstanceElem, NewInstanceID); + Builder.SetInstance(NewInstanceID, FVector2f::Zero(), VertexNormals[Triangle[j]]); + } + InstanceTri[j] = InstanceList[InstanceElem]; + } + + FPolygonID NewPolygonID = Builder.AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); + if (bCopyGroupToPolyGroup) + { + Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); + } + } + + Builder.RecalculateInstanceNormals(); +} + + + + + + + +void FDynamicMeshToMeshDescription::Convert_SharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + const FDynamicMeshUVOverlay* UVOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryUV() : nullptr; + const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; + + MeshOut.Empty(); + + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + bool bCopyGroupToPolyGroup = false; + if (bSetPolyGroups && MeshIn->HasTriangleGroups()) + { + Builder.EnablePolyGroups(); + bCopyGroupToPolyGroup = true; + } + + // create vertices + TArray MapV; MapV.SetNum(MeshIn->MaxVertexID()); + for (int VertID : MeshIn->VertexIndicesItr()) + { + MapV[VertID] = Builder.AppendVertex(MeshIn->GetVertex(VertID)); + } + + + FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); + + + // create new instances when seen + TMap InstanceList; + for (int TriID : MeshIn->TriangleIndicesItr()) + { + // @todo support additional overlays (requires IndexNi...) + FIndex3i Triangle = MeshIn->GetTriangle(TriID); + FIndex3i UVTriangle = (UVOverlay != nullptr) ? UVOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FIndex3i NormalTriangle = (NormalOverlay != nullptr) ? NormalOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FVertexInstanceID InstanceTri[3]; + for (int j = 0; j < 3; ++j) + { + FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); + if (InstanceList.Contains(InstanceElem) == false) + { + FVertexInstanceID NewInstanceID = Builder.AppendInstance(MapV[Triangle[j]]); + InstanceList.Add(InstanceElem, NewInstanceID); + Builder.SetInstance(NewInstanceID, + (UVTriangle[j] == -1 || UVOverlay == nullptr) ? FVector2f::Zero() : UVOverlay->GetElement(UVTriangle[j]), + (NormalTriangle[j] == -1 || NormalOverlay == nullptr) ? FVector3f::UnitY() : NormalOverlay->GetElement(NormalTriangle[j])); + } + InstanceTri[j] = InstanceList[InstanceElem]; + } + + FPolygonID NewPolygonID = Builder.AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); + + if (bCopyGroupToPolyGroup) + { + Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); + } + } +} + + + + + +void FDynamicMeshToMeshDescription::Convert_NoSharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut) +{ + const FDynamicMeshUVOverlay* UVOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryUV() : nullptr; + const FDynamicMeshNormalOverlay* NormalOverlay = MeshIn->HasAttributes() ? MeshIn->Attributes()->PrimaryNormals() : nullptr; + + MeshOut.Empty(); + + FMeshDescriptionBuilder Builder; + Builder.SetMeshDescription(&MeshOut); + + bool bCopyGroupToPolyGroup = false; + if (bSetPolyGroups && MeshIn->HasTriangleGroups()) + { + Builder.EnablePolyGroups(); + bCopyGroupToPolyGroup = true; + } + + TArray MapV; MapV.SetNum(MeshIn->MaxVertexID()); + for (int VertID : MeshIn->VertexIndicesItr()) + { + MapV[VertID] = Builder.AppendVertex(MeshIn->GetVertex(VertID)); + } + + FPolygonGroupID AllGroupID = Builder.AppendPolygonGroup(); + + FVertexID TriVertices[3]; + FVector2D TriUVs[3]; + FVector TriNormals[3]; + for (int TriID : MeshIn->TriangleIndicesItr()) + { + FIndex3i Triangle = MeshIn->GetTriangle(TriID); + + FVector2D* UseUVs = nullptr; + if (UVOverlay != nullptr) + { + FIndex3i UVTriangle = UVOverlay->GetTriangle(TriID); + TriUVs[0] = UVOverlay->GetElement(UVTriangle[0]); + TriUVs[1] = UVOverlay->GetElement(UVTriangle[1]); + TriUVs[2] = UVOverlay->GetElement(UVTriangle[2]); + UseUVs = TriUVs; + } + + FVector* UseNormals = nullptr; + if (NormalOverlay != nullptr) + { + FIndex3i NormalTriangle = NormalOverlay->GetTriangle(TriID); + TriNormals[0] = NormalOverlay->GetElement(NormalTriangle[0]); + TriNormals[1] = NormalOverlay->GetElement(NormalTriangle[1]); + TriNormals[2] = NormalOverlay->GetElement(NormalTriangle[2]); + UseNormals = TriNormals; + } + + TriVertices[0] = MapV[Triangle[0]]; + TriVertices[1] = MapV[Triangle[1]]; + TriVertices[2] = MapV[Triangle[2]]; + + FPolygonID NewPolygonID = Builder.AppendTriangle(TriVertices, AllGroupID, UseUVs, UseNormals); + + if (bCopyGroupToPolyGroup) + { + Builder.SetPolyGroupID(NewPolygonID, MeshIn->GetTriangleGroup(TriID)); + } + } + + // set to hard edges + //PolyMeshIn->SetAllEdgesHardness(true); +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshConversionModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshConversionModule.cpp new file mode 100644 index 000000000000..5400834b1d3e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshConversionModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshConversionModule.h" + +#define LOCTEXT_NAMESPACE "FMeshConversionModule" + +void FMeshConversionModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FMeshConversionModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMeshConversionModule, MeshConversion) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionBuilder.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionBuilder.cpp new file mode 100644 index 000000000000..51ab3d061f2f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionBuilder.cpp @@ -0,0 +1,379 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshDescriptionBuilder.h" +#include "MeshAttributes.h" +#include "VectorTypes.h" +#include "BoxTypes.h" + +#include "DynamicMesh3.h" +#include "DynamicMeshAttributeSet.h" + +namespace ExtendedMeshAttribute +{ + const FName PolyTriGroups("PolyTriGroups"); +} + + + +void FMeshDescriptionBuilder::SetMeshDescription(FMeshDescription* Description) +{ + this->MeshDescription = Description; + this->VertexPositions = + MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + this->InstanceUVs = + MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + this->InstanceNormals = + MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + this->InstanceColors = + MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); +} + + +void FMeshDescriptionBuilder::EnablePolyGroups() +{ + PolyGroups = + MeshDescription->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); + if (PolyGroups.IsValid() == false) + { + MeshDescription->PolygonAttributes().RegisterAttribute( + ExtendedMeshAttribute::PolyTriGroups, 1, 0, EMeshAttributeFlags::AutoGenerated); + PolyGroups = + MeshDescription->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); + check(PolyGroups.IsValid()); + } +} + + +FVertexID FMeshDescriptionBuilder::AppendVertex(const FVector& Position) +{ + FVertexID VertexID = MeshDescription->CreateVertex(); + VertexPositions.Set(VertexID, Position); + return VertexID; +} + + +FPolygonGroupID FMeshDescriptionBuilder::AppendPolygonGroup() +{ + return MeshDescription->CreatePolygonGroup(); +} + + + +FPolygonID FMeshDescriptionBuilder::AppendTriangle(const FVertexID& Vertex0, const FVertexID& Vertex1, const FVertexID& Vertex2, const FPolygonGroupID& PolygonGroup) +{ + TempBuffer.SetNum(3, false); + TempBuffer[0] = Vertex0; + TempBuffer[1] = Vertex1; + TempBuffer[2] = Vertex2; + return AppendPolygon(TempBuffer, PolygonGroup); +} + + +FVertexInstanceID FMeshDescriptionBuilder::AppendInstance(const FVertexID& VertexID) +{ + return MeshDescription->CreateVertexInstance(VertexID); +} + + + +void FMeshDescriptionBuilder::SetPosition(const FVertexID& VertexID, const FVector& NewPosition) +{ + VertexPositions.Set(VertexID, 0, NewPosition); +} + +FVector FMeshDescriptionBuilder::GetPosition(const FVertexID& VertexID) +{ + return VertexPositions.Get(VertexID, 0); +} + +FVector FMeshDescriptionBuilder::GetPosition(const FVertexInstanceID& InstanceID) +{ + return VertexPositions.Get(MeshDescription->GetVertexInstance(InstanceID).VertexID, 0); +} + + +void FMeshDescriptionBuilder::SetInstance(const FVertexInstanceID& InstanceID, const FVector2D& InstanceUV, const FVector& InstanceNormal) +{ + if (InstanceUVs.IsValid()) + { + InstanceUVs.Set(InstanceID, InstanceUV); + } + if (InstanceNormals.IsValid()) + { + InstanceNormals.Set(InstanceID, InstanceNormal); + } +} + + +void FMeshDescriptionBuilder::SetInstanceNormal(const FVertexInstanceID& InstanceID, const FVector& Normal) +{ + if (InstanceNormals.IsValid()) + { + InstanceNormals.Set(InstanceID, Normal); + } +} + + +void FMeshDescriptionBuilder::SetInstanceColor(const FVertexInstanceID& InstanceID, const FVector4& Color) +{ + if (InstanceColors.IsValid()) + { + InstanceColors.Set(InstanceID, Color); + } +} + + +FPolygonID FMeshDescriptionBuilder::AppendTriangle(const FVertexID* Triangle, const FPolygonGroupID& PolygonGroup, + const FVector2D * VertexUVs, const FVector* VertexNormals) +{ + TempBuffer.SetNum(3, false); + TempBuffer[0] = Triangle[0]; + TempBuffer[1] = Triangle[1]; + TempBuffer[2] = Triangle[2]; + + const TArray * UseUVs = nullptr; + if (VertexUVs != nullptr) + { + UVBuffer.SetNum(3, false); + UVBuffer[0] = VertexUVs[0]; + UVBuffer[1] = VertexUVs[1]; + UVBuffer[2] = VertexUVs[2]; + UseUVs = &UVBuffer; + } + + const TArray * UseNormals = nullptr; + if (VertexNormals != nullptr) + { + NormalBuffer.SetNum(3, false); + NormalBuffer[0] = VertexNormals[0]; + NormalBuffer[1] = VertexNormals[1]; + NormalBuffer[2] = VertexNormals[2]; + UseNormals = &NormalBuffer; + } + + return AppendPolygon(TempBuffer, PolygonGroup, UseUVs, UseNormals); +} + + +FPolygonID FMeshDescriptionBuilder::AppendPolygon(const TArray& Vertices, const FPolygonGroupID& PolygonGroup, + const TArray * VertexUVs, const TArray* VertexNormals) +{ + int NumVertices = Vertices.Num(); + TArray Polygon; + Polygon.Reserve(NumVertices); + for (int j = 0; j < NumVertices; ++j) + { + FVertexInstanceID VertexInstance = MeshDescription->CreateVertexInstance(Vertices[j]); + Polygon.Add(VertexInstance); + + if (VertexUVs != nullptr) + { + InstanceUVs.Set(VertexInstance, (*VertexUVs)[j]); + } + if (VertexNormals != nullptr) + { + InstanceNormals.Set(VertexInstance, (*VertexNormals)[j]); + } + + } + + const FPolygonID NewPolygonID = MeshDescription->CreatePolygon(PolygonGroup, Polygon); + + // compute triangulation + FMeshPolygon& NewPolygon = MeshDescription->GetPolygon(NewPolygonID); + MeshDescription->ComputePolygonTriangulation(NewPolygonID, NewPolygon.Triangles); + + return NewPolygonID; +} + + + + +FPolygonID FMeshDescriptionBuilder::AppendTriangle(const FVertexInstanceID& Instance0, const FVertexInstanceID& Instance1, const FVertexInstanceID& Instance2, const FPolygonGroupID& PolygonGroup) +{ + TArray Polygon; + Polygon.Add(Instance0); + Polygon.Add(Instance1); + Polygon.Add(Instance2); + + const FPolygonID NewPolygonID = MeshDescription->CreatePolygon(PolygonGroup, Polygon); + + // compute triangulation + FMeshPolygon& NewPolygon = MeshDescription->GetPolygon(NewPolygonID); + MeshDescription->ComputePolygonTriangulation(NewPolygonID, NewPolygon.Triangles); + + return NewPolygonID; +} + + + + + +void FMeshDescriptionBuilder::SetPolyGroupID(const FPolygonID& PolygonID, int GroupID) +{ + PolyGroups.Set(PolygonID, 0, GroupID); +} + + + + + +void FMeshDescriptionBuilder::AppendMesh(const FDynamicMesh3* Mesh, bool bSetPolyGroups) +{ + // create vertices + TArray MapV; + MapV.SetNum(Mesh->MaxVertexID()); + for (int VertID : Mesh->VertexIndicesItr()) + { + FVector3d v = Mesh->GetVertex(VertID); + MapV[VertID] = AppendVertex(v); + } + + bool bDoTransferPolyGroups = false; + if (bSetPolyGroups) + { + EnablePolyGroups(); + bDoTransferPolyGroups = Mesh->HasTriangleGroups(); + } + + FPolygonGroupID AllGroupID = AppendPolygonGroup(); + + bool bHasVertexColors = Mesh->HasVertexColors(); + bool bHasVertexNormals = Mesh->HasVertexNormals(); + const FDynamicMeshUVOverlay* UVOverlay = Mesh->HasAttributes() ? Mesh->Attributes()->PrimaryUV() : nullptr; + const FDynamicMeshNormalOverlay* NormalOverlay = Mesh->HasAttributes() ? Mesh->Attributes()->PrimaryNormals() : nullptr; + //FDynamicMeshUVOverlay* UVOverlay = nullptr; + //FDynamicMeshNormalOverlay* NormalOverlay = nullptr; + + + // create new instances when seen + TMap InstanceList; + for (int TriID : Mesh->TriangleIndicesItr()) + { + // @todo support additional overlays (requires IndexNi...) + FIndex3i Triangle = Mesh->GetTriangle(TriID); + FIndex3i UVTriangle = (UVOverlay != nullptr) ? UVOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FIndex3i NormalTriangle = (NormalOverlay != nullptr) ? NormalOverlay->GetTriangle(TriID) : FIndex3i(-1, -1, -1); + FVertexInstanceID InstanceTri[3]; + for (int j = 0; j < 3; ++j) + { + FIndex3i InstanceElem(Triangle[j], UVTriangle[j], NormalTriangle[j]); + if (InstanceList.Contains(InstanceElem) == false) + { + FVertexInstanceID NewInstanceID = AppendInstance(MapV[Triangle[j]]); + InstanceList.Add(InstanceElem, NewInstanceID); + SetInstance(NewInstanceID, + (UVTriangle[j] == -1 || UVOverlay == nullptr) ? FVector2f::Zero() : UVOverlay->GetElement(UVTriangle[j]), + (NormalTriangle[j] == -1 || NormalOverlay == nullptr) ? FVector3f::UnitY() : NormalOverlay->GetElement(NormalTriangle[j])); + } + InstanceTri[j] = InstanceList[InstanceElem]; + } + + FPolygonID NewPolyID = AppendTriangle(InstanceTri[0], InstanceTri[1], InstanceTri[2], AllGroupID); + if (bDoTransferPolyGroups) + { + SetPolyGroupID(NewPolyID, Mesh->GetTriangleGroup(TriID)); + } + + if (bHasVertexColors) + { + for (int j = 0; j < 3; ++j) + { + FVector3f Color = Mesh->GetVertexColor(Triangle[j]); + FVector4 Color4(Color.X, Color.Y, Color.Z, 1.0); + SetInstanceColor(InstanceTri[j], Color4); + } + } + if (NormalOverlay == nullptr && bHasVertexNormals) + { + SetInstanceNormal(InstanceTri[0], Mesh->GetVertexNormal(Triangle[0])); + SetInstanceNormal(InstanceTri[1], Mesh->GetVertexNormal(Triangle[1])); + SetInstanceNormal(InstanceTri[2], Mesh->GetVertexNormal(Triangle[2])); + } + } +} + + + + + +void FMeshDescriptionBuilder::Translate(const FVector& Translation) +{ + for (FVertexID VertexID : MeshDescription->Vertices().GetElementIDs()) + { + FVector Position = VertexPositions.Get(VertexID); + Position += Translation; + VertexPositions.Set(VertexID, Position); + } +} + + + + +void FMeshDescriptionBuilder::SetAllEdgesHardness(bool bHard) +{ + TEdgeAttributesRef EdgeHardness = + MeshDescription->EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); + for (FEdgeID EdgeID : MeshDescription->Edges().GetElementIDs()) + { + EdgeHardness.Set(EdgeID, 0, bHard); + } +} + + + +void FMeshDescriptionBuilder::RecalculateInstanceNormals() +{ + for (int k = 0; k < InstanceNormals.GetNumElements(); ++k) + { + InstanceNormals.Set(FVertexInstanceID(k), 0, FVector::ZeroVector); + } + + const FPolygonArray& Polygons = MeshDescription->Polygons(); + for (const FPolygonID PolygonID : Polygons.GetElementIDs()) + { + const TArray& Triangles = MeshDescription->GetPolygonTriangles(PolygonID); + for (FMeshTriangle Triangle : Triangles) + { + FVector3d A = GetPosition(Triangle.VertexInstanceID0); + FVector3d B = GetPosition(Triangle.VertexInstanceID1); + FVector3d C = GetPosition(Triangle.VertexInstanceID2); + double Area = 1.0; + FVector FaceNormal = VectorUtil::FastNormalArea(A, B, C, Area); + if (Area > FMathf::ZeroTolerance) + { + FaceNormal *= Area; + InstanceNormals.Set(Triangle.VertexInstanceID0, + InstanceNormals.Get(Triangle.VertexInstanceID0, 0) + FaceNormal); + InstanceNormals.Set(Triangle.VertexInstanceID1, + InstanceNormals.Get(Triangle.VertexInstanceID1, 0) + FaceNormal); + InstanceNormals.Set(Triangle.VertexInstanceID2, + InstanceNormals.Get(Triangle.VertexInstanceID2, 0) + FaceNormal); + } + } + } + + + for (int k = 0; k < InstanceNormals.GetNumElements(); ++k) + { + FVector SumNormal = InstanceNormals.Get(FVertexInstanceID(k), 0); + SumNormal.Normalize(); + if ( SumNormal.Size() < 0.99 ) + { + SumNormal = FVector(1, 0, 0); + } + InstanceNormals.Set(FVertexInstanceID(k), 0, SumNormal); + } +} + + + +FBox FMeshDescriptionBuilder::ComputeBoundingBox() const +{ + FAxisAlignedBox3f bounds = FAxisAlignedBox3f::Empty(); + for ( FVertexID VertexID : MeshDescription->Vertices().GetElementIDs() ) + { + bounds.Contain(VertexPositions.Get(VertexID)); + } + return bounds; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionToDynamicMesh.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionToDynamicMesh.cpp new file mode 100644 index 000000000000..fd209deb390f --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Private/MeshDescriptionToDynamicMesh.cpp @@ -0,0 +1,343 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshDescriptionToDynamicMesh.h" +#include "MeshAttributes.h" +#include "DynamicMeshAttributeSet.h" +#include "DynamicMeshOverlay.h" +#include "MeshDescriptionBuilder.h" + + +struct FVertexUV +{ + int vid; + float x; + float y; + bool operator==(const FVertexUV & o) const + { + return vid == o.vid && x == o.x && y == o.y; + } +}; +FORCEINLINE uint32 GetTypeHash(const FVertexUV& Vector) +{ + // ugh copied from FVector clearly should not be using CRC for hash!! + return FCrc::MemCrc32(&Vector, sizeof(Vector)); +} + + +class FUVWelder +{ +public: + TMap UniqueVertexUVs; + FDynamicMeshUVOverlay* UVOverlay; + + FUVWelder(FDynamicMeshUVOverlay* UVOverlayIn) + { + check(UVOverlayIn); + UVOverlay = UVOverlayIn; + } + + int FindOrAddUnique(const FVector2D & UV, int VertexID) + { + FVertexUV VertUV = { VertexID, UV.X, UV.Y }; + + int NewIndex = -1; + if (UniqueVertexUVs.Contains(VertUV)) + { + NewIndex = UniqueVertexUVs[VertUV]; + } + else + { + NewIndex = UVOverlay->AppendElement(UV, VertexID); + UniqueVertexUVs.Add(VertUV, NewIndex); + } + return NewIndex; + } +}; + + + + +struct FVertexNormal +{ + int vid; + float x; + float y; + float z; + bool operator==(const FVertexNormal & o) const + { + return vid == o.vid && x == o.x && y == o.y && z == o.z; + } +}; +FORCEINLINE uint32 GetTypeHash(const FVertexNormal& Vector) +{ + // ugh copied from FVector clearly should not be using CRC for hash!! + return FCrc::MemCrc32(&Vector, sizeof(Vector)); +} + + +class FNormalWelder +{ +public: + TMap UniqueVertexNormals; + FDynamicMeshNormalOverlay* NormalOverlay; + + FNormalWelder(FDynamicMeshNormalOverlay* NormalOverlayIn) + { + check(NormalOverlayIn); + NormalOverlay = NormalOverlayIn; + } + + int FindOrAddUnique(const FVector & Normal, int VertexID) + { + FVertexNormal VertNormal = { VertexID, Normal.X, Normal.Y, Normal.Z }; + + int NewIndex = -1; + if (UniqueVertexNormals.Contains(VertNormal)) + { + NewIndex = UniqueVertexNormals[VertNormal]; + } + else + { + NewIndex = NormalOverlay->AppendElement(Normal, VertexID); + UniqueVertexNormals.Add(VertNormal, NewIndex); + } + return NewIndex; + } +}; + + + + + + + + + + + + + +void FMeshDescriptionToDynamicMesh::Convert(const FMeshDescription* MeshIn, FDynamicMesh3& MeshOut) +{ + // look up vertex positions + const FVertexArray& VertexIDs = MeshIn->Vertices(); + TVertexAttributesConstRef VertexPositions = + MeshIn->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + + // copy vertex positions + for (const FVertexID VertexID : VertexIDs.GetElementIDs()) + { + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + check(NewVertIdx == VertexID.GetValue()); + } + + // look up vertex-instance UVs and normals + // @todo: does the MeshDescription always have UVs and Normals? + TVertexInstanceAttributesConstRef InstanceUVs = + MeshIn->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + TVertexInstanceAttributesConstRef InstanceNormals = + MeshIn->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + + // enable attributes on output mesh + MeshOut.EnableAttributes(); + FDynamicMeshUVOverlay* UVOverlay = MeshOut.Attributes()->PrimaryUV(); + FDynamicMeshNormalOverlay* NormalOverlay = MeshOut.Attributes()->PrimaryNormals(); + + TPolygonAttributesConstRef PolyGroups = + MeshIn->PolygonAttributes().GetAttributesRef(ExtendedMeshAttribute::PolyTriGroups); + + // base triangle groups will track polygons + if (bEnableOutputGroups) + { + MeshOut.EnableTriangleGroups(0); + } + + int NumVertices = MeshIn->Vertices().Num(); + int NumPolygons = MeshIn->Polygons().Num(); + int NumVtxInstances = MeshIn->VertexInstances().Num(); + if (bPrintDebugMessages) + { + UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: MeshDescription verts %d polys %d instances %d"), NumVertices, NumPolygons, NumVtxInstances); + } + + // reserve space in MeshOut? + + // used to merge coincident elements so that we get actual topology + FUVWelder UVWelder(UVOverlay); + FNormalWelder NormalWelder(NormalOverlay); + + const FPolygonArray& Polygons = MeshIn->Polygons(); + for (const FPolygonID PolygonID : Polygons.GetElementIDs()) + { + FPolygonGroupID PolygonGroupID = MeshIn->GetPolygonPolygonGroup(PolygonID); + + const TArray& Triangles = MeshIn->GetPolygonTriangles(PolygonID); + int NumTriangles = Triangles.Num(); + for ( int TriIdx = 0; TriIdx < NumTriangles; ++TriIdx) + { + const FMeshTriangle& Triangle = Triangles[TriIdx]; + + FVertexInstanceID InstanceTri[3]; + InstanceTri[0] = Triangle.VertexInstanceID0; + InstanceTri[1] = Triangle.VertexInstanceID1; + InstanceTri[2] = Triangle.VertexInstanceID2; + + int GroupID = 0; + if (GroupMode == EPrimaryGroupMode::SetToPolyGroup) + { + if (PolyGroups.IsValid()) + { + GroupID = PolyGroups.Get(PolygonID, 0); + } + } + else if (GroupMode == EPrimaryGroupMode::SetToPolygonID) + { + GroupID = PolygonID.GetValue(); + } + else if (GroupMode == EPrimaryGroupMode::SetToPolygonGroupID) + { + GroupID = PolygonGroupID.GetValue(); + } + + + // append triangle + int VertexID0 = MeshIn->GetVertexInstance(Triangle.VertexInstanceID0).VertexID.GetValue(); + int VertexID1 = MeshIn->GetVertexInstance(Triangle.VertexInstanceID1).VertexID.GetValue(); + int VertexID2 = MeshIn->GetVertexInstance(Triangle.VertexInstanceID2).VertexID.GetValue(); + int NewTriangleID = MeshOut.AppendTriangle(VertexID0, VertexID1, VertexID2, GroupID); + + // if append failed due to non-manifold, duplicate verts + if (NewTriangleID == FDynamicMesh3::NonManifoldID) + { + int e0 = MeshOut.FindEdge(VertexID0, VertexID1); + int e1 = MeshOut.FindEdge(VertexID1, VertexID2); + int e2 = MeshOut.FindEdge(VertexID2, VertexID0); + + // determine which verts need to be duplicated + bool bDuplicate[3] = { false, false, false }; + if (e0 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e0) == false) + { + bDuplicate[0] = true; + bDuplicate[1] = true; + } + if (e1 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e1) == false) + { + bDuplicate[1] = true; + bDuplicate[2] = true; + } + if (e2 != FDynamicMesh3::InvalidID && MeshOut.IsBoundaryEdge(e2) == false) + { + bDuplicate[2] = true; + bDuplicate[0] = true; + } + if (bDuplicate[0]) + { + FVertexID VertexID = MeshIn->GetVertexInstance(Triangle.VertexInstanceID0).VertexID; + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + VertexID0 = NewVertIdx; + } + if (bDuplicate[1]) + { + FVertexID VertexID = MeshIn->GetVertexInstance(Triangle.VertexInstanceID1).VertexID; + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + VertexID1 = NewVertIdx; + } + if (bDuplicate[2]) + { + FVertexID VertexID = MeshIn->GetVertexInstance(Triangle.VertexInstanceID2).VertexID; + const FVector Position = VertexPositions.Get(VertexID); + int NewVertIdx = MeshOut.AppendVertex(Position); + VertexID2 = NewVertIdx; + } + + NewTriangleID = MeshOut.AppendTriangle(VertexID0, VertexID1, VertexID2, GroupID); + checkSlow(NewTriangleID != FDynamicMesh3::NonManifoldID); + } + + FIndex3i Tri(VertexID0, VertexID1, VertexID2); + + if (bCalculateMaps) + { + TriToPolyTriMap.Insert(FIndex2i(PolygonID.GetValue(), TriIdx), NewTriangleID); + } + + if (UVOverlay != nullptr) + { + FIndex3i TriUV; + for (int j = 0; j < 3; ++j) + { + FVector2D UV = InstanceUVs.Get(InstanceTri[j]); + TriUV[j] = UVWelder.FindOrAddUnique(UV, Tri[j]); + } + UVOverlay->SetTriangle(NewTriangleID, TriUV); + } + + if (NormalOverlay != nullptr) + { + FIndex3i TriNormals; + for (int j = 0; j < 3; ++j) + { + FVector Normal = InstanceNormals.Get(InstanceTri[j]); + TriNormals[j] = NormalWelder.FindOrAddUnique(Normal, Tri[j]); + } + NormalOverlay->SetTriangle(NewTriangleID, TriNormals); + } + + } + } + + if (bPrintDebugMessages) + { + int NumUVs = (UVOverlay != nullptr) ? UVOverlay->MaxElementID() : 0; + int NumNormals = (NormalOverlay != nullptr) ? NormalOverlay->MaxElementID() : 0; + UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: FDynamicMesh verts %d triangles %d uvs %d normals %d"), MeshOut.MaxVertexID(), MeshOut.MaxTriangleID(), NumUVs, NumNormals); + } + +} + + + + + +void FMeshDescriptionToDynamicMesh::CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, FMeshTangentsf& TangentsOut) +{ + check(bCalculateMaps == true); + check(TriToPolyTriMap.Num() == TargetMesh->TriangleCount()); + + TVertexInstanceAttributesConstRef InstanceNormals = + SourceMesh->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + TVertexInstanceAttributesConstRef InstanceTangents = + SourceMesh->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); + TVertexInstanceAttributesConstRef InstanceSigns = + SourceMesh->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); + check(InstanceNormals.IsValid()); + check(InstanceTangents.IsValid()); + check(InstanceSigns.IsValid()); + + TangentsOut.SetMesh(TargetMesh); + TangentsOut.InitializePerTriangleTangents(false); + + for (int TriID : TargetMesh->TriangleIndicesItr()) + { + FIndex2i PolyTriIdx = TriToPolyTriMap[TriID]; + const TArray& Triangles = SourceMesh->GetPolygonTriangles( FPolygonID(PolyTriIdx.A) ); + const FMeshTriangle& Triangle = Triangles[PolyTriIdx.B]; + FVertexInstanceID InstanceTri[3]; + InstanceTri[0] = Triangle.VertexInstanceID0; + InstanceTri[1] = Triangle.VertexInstanceID1; + InstanceTri[2] = Triangle.VertexInstanceID2; + for (int j = 0; j < 3; ++j) + { + FVector Normal = InstanceNormals.Get(InstanceTri[j], 0); + FVector Tangent = InstanceTangents.Get(InstanceTri[j], 0); + float BinormalSign = InstanceSigns.Get(InstanceTri[j], 0); + FVector3f Bitangent = VectorUtil::Binormal((FVector3f)Normal, (FVector3f)Tangent, (float)BinormalSign); + Tangent.Normalize(); Bitangent.Normalize(); + TangentsOut.SetPerTriangleTangent(TriID, j, Tangent, Bitangent); + } + } + +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/DynamicMeshToMeshDescription.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/DynamicMeshToMeshDescription.h new file mode 100644 index 000000000000..d5527a81ad64 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/DynamicMeshToMeshDescription.h @@ -0,0 +1,56 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "MeshDescription.h" + +/** + * Convert FDynamicMesh3 to FMeshDescription + * + */ +class MESHCONVERSION_API FDynamicMeshToMeshDescription +{ +public: + /** If true, will print some possibly-helpful debugging spew to output log */ + bool bPrintDebugMessages = false; + + /** Should DynamicMesh triangle groups be transfered to MeshDescription via custom PolyTriGroups attribute */ + bool bSetPolyGroups = true; + + /** + * Default conversion of DynamicMesh to MeshDescription. Calls functions below depending on mesh state + */ + void Convert(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + + /** + * Update existing MeshDescription based on DynamicMesh. Assumes mesh topology has not changed. + * Copies positions, recalculates MeshDescription normals. + */ + void Update(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + + // + // Internal functions that you can also call directly + // + + /** + * Ignore any Attributes on input Mesh, calculate per-vertex normals and have MeshDescription compute tangents. + * One VertexInstance per input vertex is generated + */ + void Convert_NoAttributes(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + /** + * Convert while minimizing VertexInstance count, IE new VertexInstances are only created + * if a unique UV or Normal is required. + */ + void Convert_SharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); + + /** + * Convert with no shared VertexInstances. A new VertexInstance is created for + * each triangle vertex (ie corner). However vertex positions are shared. + */ + void Convert_NoSharedInstances(const FDynamicMesh3* MeshIn, FMeshDescription& MeshOut); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshConversionModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshConversionModule.h new file mode 100644 index 000000000000..5f7d52d66178 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshConversionModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMeshConversionModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionBuilder.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionBuilder.h new file mode 100644 index 000000000000..4f4e8a704772 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionBuilder.h @@ -0,0 +1,117 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "MeshDescription.h" + +class FDynamicMesh3; + + +/** + * These attributes are used to store custom modeling tools data on a MeshDescription + */ +namespace ExtendedMeshAttribute +{ + extern MESHCONVERSION_API const FName PolyTriGroups; +} + + +/** + * Utility class to construct MeshDescription instances + */ +class MESHCONVERSION_API FMeshDescriptionBuilder +{ +public: + void SetMeshDescription(FMeshDescription* Description); + + /** Append vertex and return new vertex ID */ + FVertexID AppendVertex(const FVector& Position); + + /** Return position of vertex */ + FVector GetPosition(const FVertexID& VertexID); + + /** Return position of vertex parent of instance */ + FVector GetPosition(const FVertexInstanceID& InstanceID); + + /** Set the position of a vertex */ + void SetPosition(const FVertexID& VertexID, const FVector& NewPosition); + + + + /** Append new vertex instance and return ID */ + FVertexInstanceID AppendInstance(const FVertexID& VertexID); + + /** Set the UV and Normal of a vertex instance*/ + void SetInstance(const FVertexInstanceID& InstanceID, const FVector2D& InstanceUV, const FVector& InstanceNormal); + + /** Set the Normal of a vertex instance*/ + void SetInstanceNormal(const FVertexInstanceID& InstanceID, const FVector& Normal); + + /** Set the Color of a vertex instance*/ + void SetInstanceColor(const FVertexInstanceID& InstanceID, const FVector4& Color); + + /** Enable per-triangle integer attribute named PolyTriGroups */ + void EnablePolyGroups(); + + /** Create a new polygon group and return it's ID */ + FPolygonGroupID AppendPolygonGroup(); + + /** Set the PolyTriGroups attribute value to a specific GroupID for a Polygon */ + void SetPolyGroupID(const FPolygonID& PolygonID, int GroupID); + + + + /** Append a triangle to the mesh with the given PolygonGroup ID */ + FPolygonID AppendTriangle(const FVertexID& Vertex0, const FVertexID& Vertex1, const FVertexID& Vertex2, const FPolygonGroupID& PolygonGroup); + + /** Append a triangle to the mesh with the given PolygonGroup ID, and optionally with triangle-vertex UVs and Normals */ + FPolygonID AppendTriangle(const FVertexID* Triangle, const FPolygonGroupID& PolygonGroup, + const FVector2D* VertexUVs = nullptr, const FVector* VertexNormals = nullptr); + + /** + * Append an arbitrary polygon to the mesh with the given PolygonGroup ID, and optionally with polygon-vertex UVs and Normals + * Unique Vertex instances will be created for each polygon-vertex. + */ + FPolygonID AppendPolygon(const TArray& Vertices, const FPolygonGroupID& PolygonGroup, + const TArray* VertexUVs = nullptr, const TArray* VertexNormals = nullptr); + + /** + * Append a triangle to the mesh using the given vertex instances and PolygonGroup ID + */ + FPolygonID AppendTriangle(const FVertexInstanceID& Instance0, const FVertexInstanceID& Instance1, const FVertexInstanceID& Instance2, const FPolygonGroupID& PolygonGroup); + + + /** + * Append an entire mesh to this mesh. This will create a minimal set of shared vertex instances. + * @param bSetPolyGroups if true, we transfer the input mesh triangle groups to the PolyTriGroups attribute + */ + void AppendMesh(const FDynamicMesh3* Mesh, bool bSetPolyGroups); + + + + /** Set MeshAttribute::Edge::IsHard to true for all edges */ + void SetAllEdgesHardness(bool bHard); + + /** Translate the MeshDescription vertex positions */ + void Translate(const FVector& Translation); + + /** Calculate normals on the MeshDescription vertex instances, using area-weighted face average */ + void RecalculateInstanceNormals(); + + /** Return the current bounding box of the mesh */ + FBox ComputeBoundingBox() const; + +protected: + FMeshDescription* MeshDescription; + + TVertexAttributesRef VertexPositions; + TVertexInstanceAttributesRef InstanceUVs; + TVertexInstanceAttributesRef InstanceNormals; + TVertexInstanceAttributesRef InstanceColors; + TArray TempBuffer; + TArray UVBuffer; + TArray NormalBuffer; + + TPolygonAttributesRef PolyGroups; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionToDynamicMesh.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionToDynamicMesh.h new file mode 100644 index 000000000000..6b501aca37c7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshConversion/Public/MeshDescriptionToDynamicMesh.h @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "MeshTangents.h" +#include "MeshDescription.h" + +/** + * Convert FMeshDescription to FDynamicMesh3 + * + * @todo handle missing UV/normals on MD? + * @todo be able to ignore UV/Normals + * @todo handle additional UV layers on MD + * @todo option to disable UV/Normal welding + */ +class MESHCONVERSION_API FMeshDescriptionToDynamicMesh +{ +public: + /** If true, will print some possibly-helpful debugging spew to output log */ + bool bPrintDebugMessages = false; + + /** Should we initialize triangle groups on output mesh */ + bool bEnableOutputGroups = true; + + /** Should we calculate conversion index maps */ + bool bCalculateMaps = true; + + /** map from triangle ID to (polygon,triangle) pair */ + TArray TriToPolyTriMap; + + + /** + * Various modes can be used to create output triangle groups + */ + enum class EPrimaryGroupMode + { + SetToZero, + SetToPolygonID, + SetToPolygonGroupID, + SetToPolyGroup + }; + /** + * Which mode to use to create groups on output mesh. Ignored if bEnableOutputGroups = false. + */ + EPrimaryGroupMode GroupMode = EPrimaryGroupMode::SetToPolyGroup; + + + /** + * Default conversion of MeshDescription to DynamicMesh + */ + void Convert(const FMeshDescription* MeshIn, FDynamicMesh3& MeshOut); + + /** + * Copy tangents from MeshDescription to a FMeshTangents instance. + * @warning Convert() must have been used to create the TargetMesh before calling this function + */ + void CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, FMeshTangentsf& TangentsOut); +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/MeshSolverUtilities.Build.cs b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/MeshSolverUtilities.Build.cs new file mode 100644 index 000000000000..a9b684b3bbe1 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/MeshSolverUtilities.Build.cs @@ -0,0 +1,34 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MeshSolverUtilities : ModuleRules +{ + public MeshSolverUtilities(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + //PCHUsage = ModuleRules.PCHUsageMode.NoSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "Eigen" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "GeometricObjects", + "DynamicMesh" + //"GeometricObjects", + //"DynamicMesh" + //"Slate", + //"SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + } +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/ConstrainedPoissonSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/ConstrainedPoissonSolver.h new file mode 100644 index 000000000000..4c9c6b73b38e --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/ConstrainedPoissonSolver.h @@ -0,0 +1,348 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "FSOAPositions.h" +#include "FSparseMatrixD.h" + +#include "MatrixSolver.h" + + + + + +#include "ProfilingDebugging/ScopedTimers.h" +#include "Async/ParallelFor.h" + + + +/** +* Least Squares constrained poisson solver. +* +* Where L is a Laplacian type matrix +* {lambda_i, cvec_i} are k weights (lambda) and positional constraints. +* +* Solves in the lease squares sense the equation +* L p_vec = d_vec +* lambda_i * (p_vec)_i = lambda_i (c_vec)_i for some constraints. +* +* this amounts to +* ( Transpose(L) * L + (0 0 ) ) p_vec = source_vec + ( 0 ) +* ( (0 lambda^2) ) ( lambda^2 c_vec ) +* +* where source_vec = Transpose(L)*L p_vec +* +* C.F. http://sites.fas.harvard.edu/~cs277/papers/deformation_survey.pdf section V, subsection C +* +* @todo consider reworking to hold TUniquePtr<> to the various objects. +*/ + +class FConstrainedSolver +{ +public: + + typedef typename FSparseMatrixD::Scalar ScalarType; + typedef typename FSOAPositions::VectorType VectorType; + + + + struct FConstraintPosition + { + FConstraintPosition(const FVector3d& P, bool b) : Position(P), bPostFix(b) {} + + FVector3d Position; + bool bPostFix; + }; + + + FConstrainedSolver(TUniquePtr& SymmatrixMatrixOperator, const EMatrixSolverType MatrixSolverType) + : ConstraintPositions(SymmatrixMatrixOperator->cols()) + { + SymmetricMatrixPtr.Reset(SymmatrixMatrixOperator.Release()); + + bMatrixSolverDirty = true; + MatrixSolver = ContructMatrixSolver(MatrixSolverType); + } + + + // Updates the diagonal weights matrix. + void SetConstraintWeights(const TMap& WeightMap) + { + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + ClearWeights(); + + std::vector MatrixTripelList; + MatrixTripelList.reserve(WeightMap.Num()); + + for (const auto& WeightPair : WeightMap) + { + const int32 i = WeightPair.Key; // row id + double Weight = WeightPair.Value; + + checkSlow(i < SymmetricMatrixPtr->cols()); + + // the soft constrained system uses the square of the weight. + Weight *= Weight; + + //const int32 i = ToIndex[VertId]; + MatrixTripelList.push_back(MatrixTripletT(i, i, Weight)); + + } + + // Construct matrix with weights on the diagonal for the constrained verts ( and zero everywhere else) + WeightsSqrdMatrix.setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + WeightsSqrdMatrix.makeCompressed(); + + // The solver matrix will have to be updated and re-factored + UpdateSolverWithContraints(); + } + + // Updates the positional source term + void SetContraintPositions(const TMap& PositionMap) + { + ClearConstraintPositions(); + + for (const auto& PositionPair : PositionMap) + { + const int32 i = PositionPair.Key; // row id + const FVector3d& Pos = PositionPair.Value.Position; + + checkSlow(i < SymmetricMatrixPtr->cols()); + + + // constrained source vector + ConstraintPositions.XVector[i] = Pos.X; + ConstraintPositions.YVector[i] = Pos.Y; + ConstraintPositions.ZVector[i] = Pos.Z; + + } + } + + ScalarType GetWeightSqrd(int32 VtxIndex) const + { + return WeightsSqrdMatrix.coeff(VtxIndex, VtxIndex); + } + + /** + * Access to the underlying solver + */ + IMatrixSolverBase* GetMatrixSolverBase() { return MatrixSolver.Get(); } + + const IMatrixSolverBase* GetMatrixSolverBase() const { return MatrixSolver.Get(); } + + /** + * Access to the underlying solver in iterative form - will return null if the solver is not iterative. + */ + IIterativeMatrixSolverBase* GetMatrixSolverIterativeBase() + { + IIterativeMatrixSolverBase* IterativeBasePtr = NULL; + if (MatrixSolver->bIsIterative()) + { + IterativeBasePtr = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + } + return IterativeBasePtr; + } + const IIterativeMatrixSolverBase* GetMatrixSolverIterativeBase() const + { + const IIterativeMatrixSolverBase* IterativeBasePtr = NULL; + if (MatrixSolver->bIsIterative()) + { + IterativeBasePtr = (const IIterativeMatrixSolverBase*)MatrixSolver.Get(); + } + return IterativeBasePtr; + } + + /** + * @param SourceVector - Required that each component vector has size equal to the the number of columns in the laplacian + * @param SolutionVector - the resulting solution to the least squares problem + */ + bool Solve(const FSOAPositions& SourceVector, FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + // Set up the source vector + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = SourceVector.Array(Dir) + WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + MatrixSolver->Solve(RHSVector, SolutionVector); + + bool bSuccess = MatrixSolver->bSucceeded(); + + return bSuccess; + } + + + /** + * Special case when the source vector is identically zero. + * NB: this is used for region smoothing. + */ + bool Solve(FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + MatrixSolver->Solve(RHSVector, SolutionVector); + + bool bSuccess = MatrixSolver->bSucceeded(); + return bSuccess; + } + + /* + * For use with iterative solvers. + * + * Reverts to Solve(SolutionVector); + * if the matrix solver type is direct. + */ + bool SolveWithGuess(const FSOAPositions& Guess, FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + + if (MatrixSolver->bIsIterative()) + { + const IIterativeMatrixSolverBase* IterativeSolver = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + IterativeSolver->SolveWithGuess(Guess, RHSVector, SolutionVector); + + bool bSuccess = IterativeSolver->bSucceeded(); + return bSuccess; + } + else + { + return Solve(SolutionVector); + } + } + + /** + * For use with iterative solvers. + * + * Reverts to Solve(SourceVector, SolutionVector); + * if the matrix solver type is direct + */ + bool SolveWithGuess(const FSOAPositions& Guess, const FSOAPositions& SourceVector, FSOAPositions& SolutionVector) const + { + checkSlow(bMatrixSolverDirty == false); + + if (MatrixSolver->bIsIterative()) + { + const IIterativeMatrixSolverBase* IterativeSolver = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + + FScopedDurationTimeLogger Timmer(TEXT("Post-setup solve time")); + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + // Set up the source vector + FSOAPositions RHSVector(SymmetricMatrix.cols()); + for (int32 Dir = 0; Dir < 3; ++Dir) + { + RHSVector.Array(Dir) = SourceVector.Array(Dir) + WeightsSqrdMatrix * ConstraintPositions.Array(Dir); + } + + IterativeSolver->SolveWithGuess(Guess, RHSVector, SolutionVector); + + bool bSuccess = IterativeSolver->bSucceeded(); + + return bSuccess; + } + else + { + return Solve(SourceVector, SolutionVector); + } + } + + + const FSparseMatrixD& Biharmonic() const { return *SymmetricMatrixPtr; } + + +protected: + + // Zero the diagonal matrix that holds constraints + void ClearConstraints() + { + ClearConstraintPositions(); + ClearWeights(); + } + + void ClearConstraintPositions() + { + const FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + const int32 NumColumns = SymmetricMatrix.cols(); + + ConstraintPositions.SetZero(NumColumns); + + } + void ClearWeights() + { + WeightsSqrdMatrix.setZero(); + + WeightsSqrdMatrix.resize(SymmetricMatrixPtr->rows(), SymmetricMatrixPtr->cols()); + + // The constraints are part of the matrix + bMatrixSolverDirty = true; + } + + // Note: If constraints haven't been set, or if constraints have been cleared, this must be called prior to solve. + void UpdateSolverWithContraints() + { + FScopedDurationTimeLogger Timmer(TEXT("Matrix setup time")); + + // The constrained verts are part of the matrix + + FSparseMatrixD& SymmetricMatrix = *SymmetricMatrixPtr; + + + LHSMatrix = SymmetricMatrix + WeightsSqrdMatrix; + + LHSMatrix.makeCompressed(); + // This matrix by construction is symmetric + const bool bIsSymmetric = true; + + + MatrixSolver->Reset(); + + MatrixSolver->SetUp(LHSMatrix, bIsSymmetric); + + bMatrixSolverDirty = false; + } + +private: + + + // Positional Constraint Arrays + FSOAPositions ConstraintPositions; + + TUniquePtr SymmetricMatrixPtr; // Transpose(Laplacian) * Laplacain + FSparseMatrixD WeightsSqrdMatrix; // Weights Squared Diagonal Matrix. + + FSparseMatrixD LHSMatrix; + + bool bMatrixSolverDirty; + TUniquePtr MatrixSolver; + + +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSOAPositions.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSOAPositions.h new file mode 100644 index 000000000000..7f0742cd21a6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSOAPositions.h @@ -0,0 +1,79 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + +#include "FSparseMatrixD.h" + +/** +* A struct of arrays representation used to hold vertex positions +* in three vectors that can interface with the eigen library +*/ +class FSOAPositions +{ +public: + typedef typename FSparseMatrixD::Scalar ScalarType; + typedef Eigen::Matrix VectorType; + + + FSOAPositions(int32 Size) + : XVector(Size) + , YVector(Size) + , ZVector(Size) + {} + + FSOAPositions() + {} + + VectorType& Array(int32 i) + { + return (i == 0) ? XVector : (i == 1) ? YVector : ZVector; + } + const VectorType& Array(int32 i) const + { + return (i == 0) ? XVector : (i == 1) ? YVector : ZVector; + } + + VectorType XVector; + VectorType YVector; + VectorType ZVector; + + void SetZero(int32 NumElements) + { + XVector.setZero(NumElements); + YVector.setZero(NumElements); + ZVector.setZero(NumElements); + } + + // Test that all the arrays have the same given size. + bool bHasSize(int32 Size) const + { + return (XVector.rows() == Size && YVector.rows() == Size && ZVector.rows() == Size); + } + + int32 Num() const + { + int32 Size = XVector.rows(); + if (!bHasSize(Size)) + { + Size = -1; + } + return Size; + } + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSparseMatrixD.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSparseMatrixD.h new file mode 100644 index 000000000000..aed12d855b17 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/FSparseMatrixD.h @@ -0,0 +1,33 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +// According to http://eigen.tuxfamily.org/index.php?title=Main_Page +// SimplicialCholesky, AMD ordering, and constrained_cg are disabled. + +#ifndef EIGEN_MPL2_ONLY +#define EIGEN_MPL2_ONLY +#endif + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + + +// NB: The LU solver likes ColMajor but the CG sovler likes RowMajor +// Also, to change everything to float / double just change the scalar type here + +typedef Eigen::SparseMatrix FSparseMatrixD; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.cpp new file mode 100644 index 000000000000..36e518781318 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.cpp @@ -0,0 +1,695 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LaplacianMeshSmoother.h" + +#include "FSOAPositions.h" + +#include "LaplacianOperators.h" +#include "MatrixSolver.h" +#include "MeshSmoothingUtilities.h" + +#include "ProfilingDebugging/ScopedTimers.h" +#include "Async/ParallelFor.h" + + +DEFINE_LOG_CATEGORY_STATIC(LogMeshSmoother, Log, All); + + +double ComputeDistSqrd(const FSOAPositions& VecA, const FSOAPositions& VecB) +{ + const int32 NumA = VecA.Num(); + const int32 NumB = VecB.Num(); + checkSlow(NumA == NumB); + +#if 0 + // the eigne way? + double Tmp = (VecA.Array(0) - VecB.Array(0)).dot(VecA.Array(0) - VecB.Array(0)) + + (VecA.Array(1) - VecB.Array(1)).dot(VecA.Array(1) - VecB.Array(1)) + + (VecA.Array(2) - VecB.Array(2)).dot(VecA.Array(2) - VecB.Array(2)); + +#endif + + // doing it by hand so we can break when the error is large + double DistSqrd = 0.; + { + const auto& AX = VecA.Array(0); + const auto& AY = VecA.Array(1); + const auto& AZ = VecA.Array(2); + + const auto& BX = VecB.Array(0); + const auto& BY = VecB.Array(1); + const auto& BZ = VecB.Array(2); + + + for (int32 i = 0; i < NumA; ++i) + { + double TmpX = AX(i)-BX(i); + double TmpY = AY(i)-BY(i); + double TmpZ = AZ(i)-BZ(i); + + TmpX *= TmpX; + TmpY *= TmpY; + TmpZ *= TmpZ; + double TmpT = TmpX + TmpY + TmpZ; + DistSqrd += TmpT; + } + } + return DistSqrd; +} + + + +FConstrainedMeshOperator::FConstrainedMeshOperator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme, const EMatrixSolverType MatrixSolverType) + : VertexCount(DynamicMesh.VertexCount()) +{ + + Laplacian = ConstructLaplacian(Scheme, DynamicMesh, VtxLinearization, &EdgeVerts); + FSparseMatrixD& LMatrix = *(Laplacian); + + TUniquePtr LTL(new FSparseMatrixD(Laplacian->rows(), Laplacian->cols())); + FSparseMatrixD& LTLMatrix = *(LTL); + + bool bIsLaplacianSymmetric = (Scheme == ELaplacianWeightScheme::Valence || Scheme == ELaplacianWeightScheme::Uniform); + + if (bIsLaplacianSymmetric) + { + // Laplacian is symmetric, i.e. equal to its transpose + LTLMatrix = LMatrix * LMatrix; + + ConstrainedSolver.Reset(new FConstrainedSolver(LTL, MatrixSolverType)); + } + else + { + // the laplacian + LTLMatrix = LMatrix.transpose() * LMatrix; + ConstrainedSolver.Reset(new FConstrainedSolver(LTL, MatrixSolverType)); + } + +} + +void FConstrainedMeshOperator::AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Pos, const bool bPostFix) +{ + + bConstraintPositionsDirty = true; + bConstraintWeightsDirty = true; + int32 Index = VtxLinearization.ToIndex()[VtxId]; + + ConstraintPositionMap.Add(TTuple(Index, FConstraintPosition(Pos, bPostFix))); + ConstraintWeightMap.Add(TTuple(Index, Weight)); + +} + + +bool FConstrainedMeshOperator::UpdateConstraintPosition(const int32 VtxId, const FVector3d& Pos, const bool bPostFix) +{ + bConstraintPositionsDirty = true; + int32 Index = VtxLinearization.ToIndex()[VtxId]; + // Add should over-write any existing value for this key + ConstraintPositionMap.Add(TTuple(Index, FConstraintPosition(Pos, bPostFix))); + + return ConstraintWeightMap.Contains(VtxId); +} + +bool FConstrainedMeshOperator::UpdateConstraintWeight(const int32 VtxId, const double Weight) +{ + + bConstraintWeightsDirty = true; + int32 Index = VtxLinearization.ToIndex()[VtxId]; + // Add should over-write any existing value for this key + ConstraintWeightMap.Add(TTuple(Index, Weight)); + + return ConstraintPositionMap.Contains(VtxId); +} + + +bool FConstrainedMeshOperator::IsConstrained(const int32 VtxId) const +{ + if (VtxId > VtxLinearization.ToIndex().Num()) return false; + + int32 Index = VtxLinearization.ToIndex()[VtxId]; + + return ConstraintWeightMap.Contains(Index); +} + +void FConstrainedMeshOperator::UpdateSolverConstraints() +{ + if (bConstraintWeightsDirty) + { + ConstrainedSolver->SetConstraintWeights(ConstraintWeightMap); + bConstraintWeightsDirty = false; + } + + if (bConstraintPositionsDirty) + { + ConstrainedSolver->SetContraintPositions(ConstraintPositionMap); + bConstraintPositionsDirty = false; + } +} + +bool FConstrainedMeshOperator::Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const +{ + // Number of positions + + const int32 Num = PositionalVector.XVector.rows(); + + // early out if the x,y,z arrays in the PositionalVector have different lengths + if (!PositionalVector.bHasSize(Num)) + { + return false; + } + + // + const auto& IndexToVtxId = VtxLinearization.ToId(); + const int32 MaxVtxId = IndexToVtxId.Num(); // NB: this is really max_used + 1 in the mesh. See FDynamicMesh3::MaxVertexID() + + + + LinearArray.Empty(MaxVtxId); + LinearArray.AddUninitialized(MaxVtxId); + + for (int32 i = 0; i < Num; ++i) + { + const int32 VtxId = IndexToVtxId[i]; + + LinearArray[VtxId] = FVector3d(PositionalVector.XVector.coeff(i), PositionalVector.YVector.coeff(i), PositionalVector.ZVector.coeff(i)); + } + + return true; +} + +void FConstrainedMeshOperator::ExtractVertexPositions(const FDynamicMesh3& DynamicMesh, FSOAPositions& VertexPositions) const +{ + VertexPositions.SetZero(VertexCount); + + + const TArray& ToIndex = VtxLinearization.ToIndex(); + + + for (int32 VtxId : DynamicMesh.VertexIndicesItr()) + { + const int32 i = ToIndex[VtxId]; + + checkSlow(i != FDynamicMesh3::InvalidID); + + const FVector3d& Vertex = DynamicMesh.GetVertex(VtxId); + // coeffRef - coeff access without range checking + VertexPositions.XVector.coeffRef(i) = Vertex.X; + VertexPositions.YVector.coeffRef(i) = Vertex.Y; + VertexPositions.ZVector.coeffRef(i) = Vertex.Z; + } +} + +void FConstrainedMeshOperator::UpdateWithPostFixConstraints(FSOAPositions& PositionVector) const +{ + for (const auto& ConstraintPosition : ConstraintPositionMap) + { + const int32 Index = ConstraintPosition.Key; + const FConstraintPosition& Constraint = ConstraintPosition.Value; + + // we only care about post-fix constraints + + if (Constraint.bPostFix) + { + const FVector3d& Pos = Constraint.Position; + PositionVector.XVector[Index] = Pos.X; + PositionVector.YVector[Index] = Pos.Y; + PositionVector.ZVector[Index] = Pos.Z; + } + } +} + + + +FConstrainedMeshDeformer::FConstrainedMeshDeformer(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme LaplacianType) + : FConstrainedMeshOperator(DynamicMesh, LaplacianType, EMatrixSolverType::LU) + , LaplacianVectors(FConstrainedMeshOperator::VertexCount) +{ + + // The current vertex positions + + // Note: the OriginalVertexPositions are being stored as member data + // for use if the solver is iterative. + // FSOAPositions OriginalVertexPositions; + ExtractVertexPositions(DynamicMesh, OriginalVertexPositions); + + + // The biharmonic part of the constrained solver + // Biharmonic := Laplacian^{T} * Laplacian + + const auto& Biharmonic = ConstrainedSolver->Biharmonic(); + + // Compute the Laplacian Vectors + // := Biharmonic * VertexPostion + // In the case of the cotangent laplacian this can be identified as the mean curvature * normal. + for (int32 i = 0; i < 3; ++i) + { + LaplacianVectors.Array(i) = Biharmonic * OriginalVertexPositions.Array(i); + } +} + +bool FConstrainedMeshDeformer::Deform(TArray& PositionBuffer) +{ + + // Update constraints. This only trigger solver rebuild if the weights were updated. + UpdateSolverConstraints(); + + // Allocate space for the result as a struct of arrays + FSOAPositions SolutionVector(VertexCount); + + // Solve the linear system + // NB: the original positions will only be used if the underlying solver type is iterative + bool bSuccess = ConstrainedSolver->SolveWithGuess(OriginalVertexPositions, LaplacianVectors, SolutionVector); + + // Move any vertices to match bPostFix constraints + + UpdateWithPostFixConstraints(SolutionVector); + + // Copy the result into the array of stucts form. + // NB: this re-indexes so the results can be looked up using VtxId + + Linearize(SolutionVector, PositionBuffer); + + // the matrix solve state + return bSuccess; + +} + + +bool FBiHarmonicMeshSmoother::ComputeSmoothedMeshPositions(TArray& UpdatedPositions) +{ + + UpdateSolverConstraints(); + + // Solves the constrained system and updates the mesh + + FSOAPositions SolutionVector(VertexCount); + + bool bSuccess = ConstrainedSolver->Solve(SolutionVector); + + + // Move any vertices to match bPostFix constraints + + UpdateWithPostFixConstraints(SolutionVector); + + // Move vertices to solution positions + + Linearize(SolutionVector, UpdatedPositions); + + return bSuccess; +} + +bool FCGBiHarmonicMeshSmoother::ComputeSmoothedMeshPositions(TArray& UpdatedPositions) +{ + + UpdateSolverConstraints(); + + // Solves the constrained system and updates the mesh + + // Solves the constrained system + + FSOAPositions SolutionVector(VertexCount); + + bool bSuccess = ConstrainedSolver->Solve(SolutionVector); + + + // Move any vertices to match bPostFix constraints + + UpdateWithPostFixConstraints(SolutionVector); + + // Move vertices to solution positions + + Linearize(SolutionVector, UpdatedPositions); + + return bSuccess; +} + + + +FDiffusionIntegrator::FDiffusionIntegrator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) +{ + Id = 0; + bIsSymmetric = false; + MinDiagonalValue = 0; + + VertexCount = DynamicMesh.VertexCount(); + + // Allocate the buffers. + Tmp[0].SetZero(VertexCount); + Tmp[1].SetZero(VertexCount); + + +} + +void FDiffusionIntegrator::Initialize(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) +{ + // Construct the laplacian, and extract the mapping for vertices (VtxLinearization) + DiffusionOperator = ConstructDiffusionOperator(Scheme, DynamicMesh, bIsSymmetric, VtxLinearization, &EdgeVerts); + + const auto& ToVertId = VtxLinearization.ToId(); + + // Extract current positions. + for (int32 i = 0; i < VertexCount; ++i) + { + int32 VtxId = ToVertId[i]; + + const FVector3d Pos = DynamicMesh.GetVertex(VtxId); + Tmp[0].XVector[i] = Pos.X; + Tmp[0].YVector[i] = Pos.Y; + Tmp[0].ZVector[i] = Pos.Z; + } + + const FSparseMatrixD& M = *DiffusionOperator; + + // Find the min diagonal entry (all should be negative). + int32 Rank = M.rows(); + MinDiagonalValue = FSparseMatrixD::Scalar(0); + for (int32 i = 0; i < Rank; ++i) + { + auto Diag = M.coeff(i, i); + MinDiagonalValue = FMath::Min(Diag, MinDiagonalValue); + } + + +#if 0 + // testing - how to print the matrix to debug output + + std::stringstream ss; + ss << Eigen::MatrixXd(M) << std::endl; + + FString Foo = ss.str().c_str(); + FPlatformMisc::LowLevelOutputDebugStringf(*Foo); +#endif + +} + + + +void FDiffusionIntegrator::Integrate_ForwardEuler(int32 NumSteps, double Alpha, double) +{ + Alpha = FMath::Clamp(Alpha, 0., 1.); + + const FSparseMatrixD& M = *DiffusionOperator; + FSparseMatrixD::Scalar TimeStep = -Alpha / MinDiagonalValue; + Id = 0; + for (int32 s = 0; s < NumSteps; ++s) + { + + int32 SrcBuffer = Id; + Id = 1 - Id; + Tmp[Id].XVector = Tmp[SrcBuffer].XVector + TimeStep * M * Tmp[SrcBuffer].XVector; + Tmp[Id].YVector = Tmp[SrcBuffer].YVector + TimeStep * M * Tmp[SrcBuffer].YVector; + Tmp[Id].ZVector = Tmp[SrcBuffer].ZVector + TimeStep * M * Tmp[SrcBuffer].ZVector; + + } + +} + + + +void FDiffusionIntegrator::Integrate_BackwardEuler(const EMatrixSolverType MatrixSolverType, int32 NumSteps, double Alpha, double Intensity) +{ + + //typedef typename TMatrixSolverTrait::MatrixSolverType MatrixSolverType; + + // We solve + // p^{n+1} - dt * L[p^{n+1}] = p^{n} + // + // i.e. + // [I - dt * L ] p^{n+1} = p^{n} + // + // NB: in the case of the cotangent laplacian this would be better if we broke the L int + // L = (A^{-1}) H where A is the "area matrix" (think "mass matrix"), then this would + // become + // [A - dt * H] p^{n+1} = Ap^{n} + // + // A - dt * H would be symmetric + // + + + const FSparseMatrixD& L = *DiffusionOperator; + // Identity matrix + FSparseMatrixD Ident(L.rows(), L.cols()); + Ident.setIdentity(); + + FSparseMatrixD::Scalar TimeStep = Alpha * FMath::Min(Intensity, 1.e6); + + FSparseMatrixD M = Ident -TimeStep * L; + + + M.makeCompressed(); + + TUniquePtr MatrixSolver = ContructMatrixSolver(MatrixSolverType); + + MatrixSolver->SetUp(M, bIsSymmetric); + + if (MatrixSolver->bIsIterative()) + { + IIterativeMatrixSolverBase* IterativeSolver = (IIterativeMatrixSolverBase*)MatrixSolver.Get(); + + bool bForceSingleThreaded = false; + Id = 0; + for (int32 s = 0; s < NumSteps; ++s) + { + + int32 SrcBuffer = Id; + Id = 1 - Id; + + // Old solution is the guess. + IterativeSolver->SolveWithGuess(Tmp[SrcBuffer], Tmp[SrcBuffer], Tmp[Id]); + + } + } + else + { + bool bForceSingleThreaded = false; + Id = 0; + for (int32 s = 0; s < NumSteps; ++s) + { + + int32 SrcBuffer = Id; + Id = 1 - Id; + + MatrixSolver->Solve(Tmp[SrcBuffer], Tmp[Id]); + } + } + + +} + +void FDiffusionIntegrator::GetPositions(TArray& PositionArray) const +{ + Linearize(Tmp[Id], PositionArray); +} + +bool FDiffusionIntegrator::Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const +{ + // Number of positions + + const int32 Num = PositionalVector.XVector.rows(); + + // early out if the x,y,z arrays in the PositionalVector have different lengths + if (!PositionalVector.bHasSize(Num)) + { + return false; + } + + // + const auto& IndexToVtxId = VtxLinearization.ToId(); + const int32 MaxVtxId = IndexToVtxId.Num(); // NB: this is really max_used + 1 in the mesh. See FDynamicMesh3::MaxVertexID() + + + + LinearArray.Empty(MaxVtxId); + LinearArray.AddUninitialized(MaxVtxId); + + for (int32 i = 0; i < Num; ++i) + { + const int32 VtxId = IndexToVtxId[i]; + + LinearArray[VtxId] = FVector3d(PositionalVector.XVector.coeff(i), PositionalVector.YVector.coeff(i), PositionalVector.ZVector.coeff(i)); + } + + return true; +} + + +TUniquePtr FLaplacianDiffusionMeshSmoother::ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& DynamicMesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx ) +{ + bIsOperatorSymmetric = bIsSymmetricLaplacian(Scheme); + + + // Construct the laplacian, and extract the mapping for vertices (VtxLinearization) + TUniquePtr Laplacian = ConstructLaplacian(Scheme, DynamicMesh, VtxLinearization, &EdgeVerts); + Laplacian->makeCompressed(); + return Laplacian; +}; + +TUniquePtr FBiHarmonicDiffusionMeshSmoother::ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& DynamicMesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx ) +{ + bIsOperatorSymmetric = true; + + // Construct the laplacian, and extract the mapping for vertices (VtxLinearization) + TUniquePtr Laplacian = ConstructLaplacian(Scheme, DynamicMesh, VtxLinearization, &EdgeVerts); + const FSparseMatrixD& L = *Laplacian; + TUniquePtr MatrixOperator(new FSparseMatrixD()); + + bool bIsLaplacianSymmetric = bIsSymmetricLaplacian(Scheme); + + if (bIsLaplacianSymmetric) + { + + *MatrixOperator = -1. * L * L; + } + else + { + *MatrixOperator = -1. * L.transpose() * L; + } + + MatrixOperator->makeCompressed(); + return MatrixOperator; +}; + + +void MeshSmoothingOperators::ComputeSmoothing_BiHarmonic(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Intensity, const int32 NumIterations, TArray& PositionArray) +{ + + + + // This is equivalent to taking a single backward Euler time step of bi-harmonic diffusion + // where L is the Laplacian (Del^2) , and L^T L is an approximation of the Del^4. + // + // dp/dt = - k*k L^T L[p] + // with + // weight = 1 / (k * Sqrt[dt] ) + // + // p^{n+1} + dt * k * k L^TL [p^{n+1}] = p^{n} + // + // re-write as + // L^TL[p^{n+1}] + weight * weight p^{n+1} = weight * weight p^{n} + +#ifndef EIGEN_MPL2_ONLY + const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LTL; +#else + // const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LU; + // const EMatrixSolverType MatrixSolverType = EMatrixSolverType::PCG; + + // The Symmetric Laplacians are SPD, and so are the LtL Operators + const EMatrixSolverType MatrixSolverType = (bIsSymmetricLaplacian(WeightScheme))? EMatrixSolverType::PCG : EMatrixSolverType::LU; + +#endif + + + FString DebugLogString = FString::Printf(TEXT("Biharmonic Smoothing of mesh with %d verts "), OriginalMesh.VertexCount()) + LaplacianSchemeName(WeightScheme) + MatrixSolverName(MatrixSolverType); + + FScopedDurationTimeLogger Timmer(DebugLogString); + + FBiHarmonicDiffusionMeshSmoother BiHarmonicDiffusionSmoother(OriginalMesh, WeightScheme); + + BiHarmonicDiffusionSmoother.Integrate_BackwardEuler(MatrixSolverType, NumIterations, Speed, Intensity); + + BiHarmonicDiffusionSmoother.GetPositions(PositionArray); + + +} + +void MeshSmoothingOperators::ComputeSmoothing_ImplicitBiHarmonicPCG( const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Weight, const int32 MaxIterations, TArray& PositionArray) +{ + + // This is equivalent to taking a single backward Euler time step of bi-harmonic diffusion + // where L is the Laplacian (Del^2) , and L^T L is an approximation of the Del^4. + // + // dp/dt = - k*k L^T L[p] + // with + // weight = 1 / (k * Sqrt[dt] ) + // + // p^{n+1} + dt * k * k L^TL [p^{n+1}] = p^{n} + // + // re-write as + // L^TL[p^{n+1}] + weight * weight p^{n+1} = weight * weight p^{n} + + FString DebugLogString = FString::Printf(TEXT("PCG Biharmonic Smoothing of mesh with %d verts "), OriginalMesh.VertexCount()) + LaplacianSchemeName(WeightScheme); + + FScopedDurationTimeLogger Timmer(DebugLogString); + + if (MaxIterations < 1) return; + + FCGBiHarmonicMeshSmoother Smoother(OriginalMesh, WeightScheme); + + // Treat all vertices as constraints with the same weight + const bool bPostFix = false; + + for (int32 VertId : OriginalMesh.VertexIndicesItr()) + { + FVector3d Pos = OriginalMesh.GetVertex(VertId); + + Smoother.AddConstraint(VertId, Weight, Pos, bPostFix); + } + + Smoother.SetMaxIterations(MaxIterations); + Smoother.SetTolerance(1.e-4); + + bool bSuccess = Smoother.ComputeSmoothedMeshPositions(PositionArray); + +} + +void MeshSmoothingOperators::ComputeSmoothing_Diffusion( const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, bool bForwardEuler, + const double Speed, const double Intensity, const int32 IterationCount, TArray& PositionArray) +{ + +#ifndef EIGEN_MPL2_ONLY + const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LTL; +#else + const EMatrixSolverType MatrixSolverType = EMatrixSolverType::LU; + //const EMatrixSolverType MatrixSolverType = EMatrixSolverType::PCG; + //const EMatrixSolverType MatrixSolverType = EMatrixSolverType::BICGSTAB; +#endif + + FString DebugLogString = FString::Printf(TEXT("Diffusion Smoothing of mesh with %d verts"), OriginalMesh.VertexCount()); + if (!bForwardEuler) + { + DebugLogString += MatrixSolverName(MatrixSolverType); + } + + FScopedDurationTimeLogger Timmer(DebugLogString); + + if (IterationCount < 1) return; + + FLaplacianDiffusionMeshSmoother Smoother(OriginalMesh, WeightScheme); + + if (bForwardEuler) + { + Smoother.Integrate_ForwardEuler(IterationCount, Speed, Intensity); + } + else + { + Smoother.Integrate_BackwardEuler(MatrixSolverType, IterationCount, Speed, Intensity); + } + + Smoother.GetPositions(PositionArray); +}; + + +TUniquePtr MeshDeformingOperators::ConstructConstrainedMeshDeformer(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh) +{ + TUniquePtr Deformer(new FConstrainedMeshDeformer(DynamicMesh, WeightScheme)); + + return Deformer; +} + + +TUniquePtr MeshDeformingOperators::ConstructConstrainedMeshSmoother(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh) +{ + TUniquePtr Deformer(new FBiHarmonicMeshSmoother(DynamicMesh, WeightScheme)); + + return Deformer; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.h new file mode 100644 index 000000000000..627be22a695a --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianMeshSmoother.h @@ -0,0 +1,249 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" +#include "ConstrainedPoissonSolver.h" +#include "FSOAPositions.h" +#include "MatrixSolver.h" +#include "MeshSmoothingUtilities.h" +#include "MeshElementLinearizations.h" + + + +class FConstrainedMeshOperator : public MeshDeformingOperators::IConstrainedMeshOperator +{ +public: + typedef FConstrainedSolver::FConstraintPosition FConstraintPosition; + + + FConstrainedMeshOperator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme, const EMatrixSolverType MatrixSolverType); + virtual ~FConstrainedMeshOperator() override {} + + // Add constraint associated with given vertex id. + void AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Pos, const bool bPostFix) override; + + + bool UpdateConstraintPosition(const int32 VtxId, const FVector3d& Position, const bool bPostFix) override; + + // The underlying solver will have to refactor the matrix if this is done + bool UpdateConstraintWeight(const int32 VtxId, const double Weight) override; + + // Clear all constraints associated with this smoother + void ClearConstraints() override { FConstrainedMeshOperator::ClearConstraintPositions(); FConstrainedMeshOperator::ClearConstraintWeights(); } + + void ClearConstraintWeights() override { ConstraintWeightMap.Empty(); bConstraintWeightsDirty = true; } + + void ClearConstraintPositions() override { ConstraintPositionMap.Empty(); bConstraintPositionsDirty = true; } + + + // Test if for constraint associated with given vertex id. + bool IsConstrained(const int32 VtxId) const override; + + + + virtual bool Deform(TArray& PositionBuffer) override { return false; } + +protected: + + // Sync constraints with internal solver. + void UpdateSolverConstraints(); + + + void ExtractVertexPositions(const FDynamicMesh3& DynamicMesh, FSOAPositions& Positions) const ; + + //void UpdateMeshWithConstraints(); + // Respect any bPostFix constraints by moving those vertices to position defined by said constraint. + void UpdateWithPostFixConstraints(FSOAPositions& PositionalVector) const; + + + // converts the positionalvector to a TArray where the offset in the array is implicitly the + // VtxId in the mesh, and not ness the matrix row id + // NB: the resulting array is treated as sparse and may have un-initialized elements. + bool Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const; + + +protected: + + // The Key (int32) here is the vertex index not vertex ID + // making it the same as the matrix row + TMap ConstraintPositionMap; + TMap ConstraintWeightMap; + + bool bConstraintPositionsDirty = true; + bool bConstraintWeightsDirty = true; + + // Cache the vertex count. + int32 VertexCount; + + // Used to map between VtxId and vertex Index in linear vector.. + FVertexLinearization VtxLinearization; + + // I don't know if we want to keep this after the constructor + TUniquePtr Laplacian; + TArray EdgeVerts; + + // Actual solver that manages the various linear algebra bits. + TUniquePtr ConstrainedSolver; +}; + + +class FConstrainedMeshDeformer : public FConstrainedMeshOperator +{ +public: + FConstrainedMeshDeformer(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme LaplacianType); + ~FConstrainedMeshDeformer() override {} + + bool Deform(TArray& PositionBuffer) override; + +private: + + FSOAPositions LaplacianVectors; + FSOAPositions OriginalVertexPositions; +}; + + +class FBiHarmonicMeshSmoother : public FConstrainedMeshOperator +{ +public: + typedef FConstrainedMeshOperator MyBaseType; + + FBiHarmonicMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) : + MyBaseType(DynamicMesh, Scheme, EMatrixSolverType::LU) + {} + + bool Deform(TArray& UpdatedPositions) override + { + return ComputeSmoothedMeshPositions(UpdatedPositions); + } + + // (Direct) Solve the constrained system and populate the UpdatedPositions with the result + bool ComputeSmoothedMeshPositions(TArray& UpdatedPositions); + +}; + + +// NB: This conjugate gradient solver could be updated to use solveWithGuess() method on the iterative solver +class FCGBiHarmonicMeshSmoother : public FConstrainedMeshOperator +{ +public: + typedef FConstrainedMeshOperator MyBaseType; + + FCGBiHarmonicMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) : + MyBaseType(DynamicMesh, Scheme, EMatrixSolverType::BICGSTAB) + {} + + bool Deform(TArray& UpdatedPositions) override + { + return ComputeSmoothedMeshPositions(UpdatedPositions); + } + + void SetMaxIterations(int32 MaxIterations) + { + IIterativeMatrixSolverBase* Solver = ConstrainedSolver->GetMatrixSolverIterativeBase(); + if (Solver) + { + Solver->SetIterations(MaxIterations); + } + } + + void SetTolerance(double Tol) + { + IIterativeMatrixSolverBase* Solver = ConstrainedSolver->GetMatrixSolverIterativeBase(); + if (Solver) + { + Solver->SetTolerance(Tol); + } + } + + // (Iterative) Solve the constrained system and populate the UpdatedPositions with the result + bool ComputeSmoothedMeshPositions(TArray& UpdatedPositions); + + +}; + + +class FDiffusionIntegrator +{ +public: + + FDiffusionIntegrator(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme); + virtual ~FDiffusionIntegrator() {} + + void Integrate_ForwardEuler(int32 NumSteps, double Speed, double); + + // Note: + void Integrate_BackwardEuler(const EMatrixSolverType MatrixSolverType, int32 NumSteps, double Speed, double Intensity); + + void GetPositions(TArray& PositionArray) const; + +protected: + + // The derived class has to implement this. + virtual TUniquePtr ConstructDiffusionOperator(const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& Mesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx) = 0 ; + + void Initialize(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme); + +protected: + bool Linearize(const FSOAPositions& PositionalVector, TArray& LinearArray) const; + + // Cache the vertex count. + int32 VertexCount; + + // Used to map vertex ID to entry in linear vector.. + FVertexLinearization VtxLinearization; + + // I don't know if we want to keep this after the constructor + bool bIsSymmetric; + TUniquePtr DiffusionOperator; + TArray EdgeVerts; + FSparseMatrixD::Scalar MinDiagonalValue; + + FSOAPositions Tmp[2]; + int32 Id; // double buffer id +}; + + +class FLaplacianDiffusionMeshSmoother : public FDiffusionIntegrator +{ +public: + + FLaplacianDiffusionMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) + : FDiffusionIntegrator(DynamicMesh, Scheme) + { + Initialize(DynamicMesh, Scheme); + } + +protected: + TUniquePtr ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& Mesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx) override ; + + +}; + +class FBiHarmonicDiffusionMeshSmoother : public FDiffusionIntegrator +{ +public: + + FBiHarmonicDiffusionMeshSmoother(const FDynamicMesh3& DynamicMesh, const ELaplacianWeightScheme Scheme) + : FDiffusionIntegrator(DynamicMesh, Scheme) + { + Initialize(DynamicMesh, Scheme); + } + +protected: + TUniquePtr ConstructDiffusionOperator( const ELaplacianWeightScheme Scheme, + const FDynamicMesh3& Mesh, + bool& bIsOperatorSymmetric, + FVertexLinearization& Linearization, + TArray* EdgeVtx) override; +}; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.cpp new file mode 100644 index 000000000000..324efb955f04 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.cpp @@ -0,0 +1,1164 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "LaplacianOperators.h" + +#include // double version of sqrt +#include // used by eigen to initialize sparse matrix + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include // for Matrix4d in testing +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + + +#define LAPLACIAN_SKIP_BOUNDARY 0 + + + +FString LaplacianSchemeName(const ELaplacianWeightScheme Scheme) +{ + FString LaplacianName; + switch (Scheme) + { + case ELaplacianWeightScheme::ClampedCotangent: + LaplacianName = FString(TEXT("Clamped Cotangent Laplacian")); + break; + case ELaplacianWeightScheme::Cotangent: + LaplacianName = FString(TEXT("Cotangent Laplacian")); + break; + case ELaplacianWeightScheme::Umbrella: + LaplacianName = FString(TEXT("Umbrella Laplacian")); + break; + case ELaplacianWeightScheme::MeanValue: + LaplacianName = FString(TEXT("MeanValue Laplacian")); + break; + case ELaplacianWeightScheme::Uniform: + LaplacianName = FString(TEXT("Uniform Laplacian")); + break; + case ELaplacianWeightScheme::Valence: + LaplacianName = FString(TEXT("Valence Laplacian")); + break; + default: + check(0 && "Unknown Laplacian Weight Scheme Enum"); + } + + return LaplacianName; +} + + +// Utility to compute the number of elements in the sparse laplacian matrix +int32 ComputeNumMatrixElements(const FDynamicMesh3& DynamicMesh, const TArray& ToVtxId) +{ + const int32 NumVerts = ToVtxId.Num(); + TArray OneRingSize; + { + OneRingSize.SetNumUninitialized(NumVerts); + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToVtxId[i]; + OneRingSize[i] = DynamicMesh.GetVtxEdgeCount(VertId); + } + } + + // Compute the total number of entries in the sparse matrix + int32 NumMatrixEntries = 0; + { + for (int32 i = 0; i < NumVerts; ++i) + { + NumMatrixEntries += 1 + OneRingSize[i]; // myself plus my neighbors + } + + } + + return NumMatrixEntries; +} + +TUniquePtr ConstructUniformLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + // Sync the mapping between the mesh vertex ids and their offsets in a nomimal linear array. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + + if (DynamicMesh.IsBoundaryVertex(VertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(VertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + ScalarT CenterWeight = ScalarT(0); // equal and opposite the sum of the neighbor weights + for (int NeighborVertId : DynamicMesh.VtxVerticesItr(VertId)) + { + const int32 j = ToIndex[NeighborVertId]; + + + ScalarT NeighborWeight = ScalarT(1); + CenterWeight += NeighborWeight; + + // add the neighbor + MatrixTripelList.push_back(MatrixTripletT(i, j, NeighborWeight)); + + } + // add the center + MatrixTripelList.push_back(MatrixTripletT(i, i, -CenterWeight)); + + } + + // SparseMatrix doesn't have (SparseMatrix&& ) constructor + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + +TUniquePtr ConstructUmbrellaLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + // Sync the mapping between the mesh vertex ids and their offsets in a nomimal linear array. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + // Cache valency of each vertex. + // Number of non-zero elements in the i'th row = 1 + OneRingSize(i) + TArray OneRingSize; + { + OneRingSize.SetNumUninitialized(NumVerts); + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + OneRingSize[i] = DynamicMesh.GetVtxEdgeCount(VertId); + } + } + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + const int32 Valence = OneRingSize[i]; + double InvValence = (Valence != 0) ? 1. / double(Valence) : 0.; + + if (DynamicMesh.IsBoundaryVertex(VertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(VertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + for (int NeighborVertId : DynamicMesh.VtxVerticesItr(VertId)) + { + const int32 j = ToIndex[NeighborVertId]; + + // add the neighbor + MatrixTripelList.push_back(MatrixTripletT(i, j, InvValence)); + + } + // add the center + MatrixTripelList.push_back(MatrixTripletT(i, i, -ScalarT(1))); + + } + + // SparseMatrix doesn't have (SparseMatrix&& ) constructor + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + +TUniquePtr ConstructValenceWeightedLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + // Sync the mapping between the mesh vertex ids and their offsets in a nomimal linear array. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + // Cache valency of each vertex. + // Number of non-zero elements in the i'th row = 1 + OneRingSize(i) + TArray OneRingSize; + { + OneRingSize.SetNumUninitialized(NumVerts); + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + OneRingSize[i] = DynamicMesh.GetVtxEdgeCount(VertId); + } + } + + + // Compute the total number of entries in the sparse matrix + int32 NumMatrixEntries = 0; + { + for (int32 i = 0; i < NumVerts; ++i) + { + NumMatrixEntries += 1 + OneRingSize[i]; // myself plus my neighbors + } + + } + std::vector MatrixTripelList; + MatrixTripelList.reserve(NumMatrixEntries); + + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 VertId = ToMeshV[i]; + const int32 IOneRingSize = OneRingSize[i]; + + if (DynamicMesh.IsBoundaryVertex(VertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(VertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + ScalarT CenterWeight = ScalarT(0); // equal and opposite the sum of the neighbor weights + for (int NeighborVertId : DynamicMesh.VtxVerticesItr(VertId)) + { + const int32 j = ToIndex[NeighborVertId]; + const int32 JOneRingSize = OneRingSize[j]; + + + ScalarT NeighborWeight = ScalarT(1) / std::sqrt(IOneRingSize + JOneRingSize); + CenterWeight += NeighborWeight; + + // add the neighbor + MatrixTripelList.push_back(MatrixTripletT(i, j, NeighborWeight)); + + } + // add the center + MatrixTripelList.push_back(MatrixTripletT(i, i, -CenterWeight)); + + } + + // SparseMatrix doesn't have (SparseMatrix&& ) constructor + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + + +/** +* The per-triangle data used in constructing the cotangent weighted laplacian. +* +*/ +class CotanTriangleData +{ +public: + + typedef FVector3d TriangleVertices[3]; + + CotanTriangleData() = default; + + CotanTriangleData(const CotanTriangleData& Other) + { + + Cotangent[0] = Other.Cotangent[0]; + Cotangent[1] = Other.Cotangent[1]; + Cotangent[2] = Other.Cotangent[2]; + + VoronoiArea[0] = Other.VoronoiArea[0]; + VoronoiArea[1] = Other.VoronoiArea[1]; + VoronoiArea[2] = Other.VoronoiArea[2]; + + OppositeEdge[0] = Other.OppositeEdge[0]; + OppositeEdge[1] = Other.OppositeEdge[1]; + OppositeEdge[2] = Other.OppositeEdge[2]; + + } + + + CotanTriangleData(const FDynamicMesh3& DynamicMesh, int32 TriId) + { + Initialize(DynamicMesh, TriId); + } + + void Initialize(const FDynamicMesh3& DynamicMesh, int32 SrcTriId) + { + TriId = SrcTriId; + + // edges: ab, bc, ca + FIndex3i EdgeIds = DynamicMesh.GetTriEdges(TriId); + + FVector3d VertA, VertB, VertC; + DynamicMesh.GetTriVertices(TriId, VertA, VertB, VertC); + + const FVector3d EdgeAB(VertB - VertA); + const FVector3d EdgeAC(VertC - VertA); + const FVector3d EdgeBC(VertC - VertB); + + + OppositeEdge[0] = EdgeIds[1]; // EdgeBC is opposite vert A + OppositeEdge[1] = EdgeIds[2]; // EdgeAC is opposite vert B + OppositeEdge[2] = EdgeIds[0]; // EdgeAB is opposite vert C + + + // NB: didn't use VectorUtil::Area() so we can re-use the Edges. + // also this formulation of area is always positive. + + const double TwiceArea = EdgeAB.Cross(EdgeAC).Length(); + + // NB: Area = 1/2 || EdgeA X EdgeB || where EdgeA and EdgeB are any two edges in the triangle. + + // Compute the Voronoi areas + // + // From Discrete Differential-Geometry Operators for Triangulated 2-Manifolds (Meyer, Desbrun, Schroder, Barr) + // http://www.geometry.caltech.edu/pubs/DMSB_III.pdf + // Given triangle P,Q, R the voronoi area at P is given by + // Area = (1/8) * ( |PR|**2 Cot 2. * SmallTriangleArea) + { + + + + // Compute the cotangent of the angle between V1 and V2 + // as the ratio V1.Dot.V2 / || V1 Cross V2 || + + // Cotangent[i] is cos(theta)/sin(theta) at the i'th vertex. + + Cotangent[0] = EdgeAB.Dot(EdgeAC) / TwiceArea; + Cotangent[1] = -EdgeAB.Dot(EdgeBC) / TwiceArea; + Cotangent[2] = EdgeAC.Dot(EdgeBC) / TwiceArea; + + if (bIsObtuse()) + { + const double Area = 0.5 * TwiceArea; + + // Voronoi inappropriate case. Instead use Area(T)/2 at obtuse corner + // and Area(T) / 4 at the other corners. + + VoronoiArea[0] = 0.25 * Area; + VoronoiArea[1] = 0.25 * Area; + VoronoiArea[2] = 0.25 * Area; + + for (int i = 0; i < 3; ++i) + { + if (Cotangent[i] < 0.) + { + VoronoiArea[i] = 0.5 * Area; + } + } + } + else + { + // If T is non-obtuse. + + const double EdgeABSqLength = EdgeAB.SquaredLength(); + const double EdgeACSqLength = EdgeAC.SquaredLength(); + const double EdgeBCSqLength = EdgeBC.SquaredLength(); + + VoronoiArea[0] = EdgeABSqLength * Cotangent[1] + EdgeACSqLength * Cotangent[2]; + VoronoiArea[1] = EdgeABSqLength * Cotangent[0] + EdgeBCSqLength * Cotangent[2]; + VoronoiArea[2] = EdgeACSqLength * Cotangent[0] + EdgeBCSqLength * Cotangent[1]; + + const double Inv8 = .125; // 1/8 + + VoronoiArea[0] *= Inv8; + VoronoiArea[1] *= Inv8; + VoronoiArea[2] *= Inv8; + } + } + else + { + // default small triangle - equilateral + double CotOf60 = 1. / FMath::Sqrt(3.f); + Cotangent[0] = CotOf60; + Cotangent[1] = CotOf60; + Cotangent[2] = CotOf60; + + VoronoiArea[0] = SmallTriangleArea / 3.; + VoronoiArea[1] = SmallTriangleArea / 3.; + VoronoiArea[2] = SmallTriangleArea / 3.; + } + } + + int32 GetLocalEdgeIdx(const int32 DynamicsMeshEdgeId) const + { + int32 Result = -1; + if (DynamicsMeshEdgeId == OppositeEdge[0]) + { + Result = 0; + } + else if (DynamicsMeshEdgeId == OppositeEdge[1]) + { + Result = 1; + } + else if (DynamicsMeshEdgeId == OppositeEdge[2]) + { + Result = 2; + } + return Result; + } + + /** helper to return the cotangent of the angle opposite the given edge + * + * @param DynamicsMeshEdgeId is the id used by FDynamicMesh3 for this edge. + * @param bValid will false on return if the requested edge is not part of this triangle + * @return Cotangent of the opposite angle. + */ + double GetOpposingCotangent(const int32 DynamicsMeshEdgeId, bool& bValid) const + { + + double OpposingCotangent = -1.; + bValid = false; + int32 LocalEdgeIdx = GetLocalEdgeIdx(DynamicsMeshEdgeId); + if (LocalEdgeIdx > -1) + { + OpposingCotangent = Cotangent[LocalEdgeIdx]; + bValid = true; + } + + return OpposingCotangent; + } + + double GetOpposingCotangent(const int32 DynamicsMeshEdgeId) const + { + bool bValid; + double OpposingCotangent = GetOpposingCotangent(DynamicsMeshEdgeId, bValid); + checkSlow(bValid); + return OpposingCotangent; + } + + bool bIsObtuse() const + { + return (Cotangent[0] < 0.f || Cotangent[1] < 0.f || Cotangent[2] < 0.f); + } + + + // The "floor" for triangle area. + // NB: the cotan laplacian has terms ~ 1/TriArea + // and the deformation matrix has terms ~ 1/TriArea**2 + + static constexpr double SmallTriangleArea = 1.e-4; + + // testing + int32 TriId = -1; + + /** Total byte count: 6 double + 3 int32 = 60 bytes. */ + + // Cotangent[i] is cos(theta)/sin(theta) at the i'th vertex. + + double Cotangent[3] = { 0. }; + + // VoronoiArea[i] is the voronoi area about the i'th vertex in this triangle. + + double VoronoiArea[3] = { 0. }; + + // OppositeEdge[i] = Corresponding DynamicsMesh3::EdgeId for the edge that is opposite + // the i'th vertex in this triangle + + int32 OppositeEdge[3] = { -1 }; + +}; + + +/** +* The per-triangle data used in constructing the mean-value weighted laplacian. +* +*/ +class MeanValueTriangleData +{ + +public: + + MeanValueTriangleData(const MeanValueTriangleData& Other) + : TriId(Other.TriId) + , TriVtxIds(Other.TriVtxIds) + , TriEdgeIds(Other.TriEdgeIds) + , bDegenerate(Other.bDegenerate) + { + EdgeLength[0] = Other.EdgeLength[0]; + EdgeLength[1] = Other.EdgeLength[1]; + EdgeLength[2] = Other.EdgeLength[2]; + + TanHalfAngle[0] = Other.TanHalfAngle[0]; + TanHalfAngle[1] = Other.TanHalfAngle[1]; + TanHalfAngle[2] = Other.TanHalfAngle[2]; + } + + void Initialize(const FDynamicMesh3& DynamicMesh, int32 SrcTriId) + { + TriId = SrcTriId; + + // VertAId, VertBId, VertCId + TriVtxIds = DynamicMesh.GetTriangle(TriId); + TriEdgeIds = DynamicMesh.GetTriEdges(TriId); + + FVector3d VertA, VertB, VertC; + DynamicMesh.GetTriVertices(TriId, VertA, VertB, VertC); + + const FVector3d EdgeAB(VertB - VertA); + const FVector3d EdgeAC(VertC - VertA); + const FVector3d EdgeBC(VertC - VertB); + + EdgeLength[0] = EdgeAB.Length(); + EdgeLength[1] = EdgeAC.Length(); + EdgeLength[2] = EdgeBC.Length(); + + constexpr double SmallEdge = 1e-4; + + bDegenerate = (EdgeLength[0] < SmallEdge || EdgeLength[1] < SmallEdge || EdgeLength[2] < SmallEdge); + + // Compute tan(angle/2) = Sqrt[ (1-cos) / (1 + cos)] + + const double ABdotAC = EdgeAB.Dot(EdgeAC); + const double BCdotBA = -EdgeBC.Dot(EdgeAB); + const double CAdotCB = EdgeAC.Dot(EdgeBC); + + + const double RegularizingConst = 1.e-6; // keeps us from dividing by zero when making tan[180/2] = sin[90]/cos[90] = inf + TanHalfAngle[0] = (EdgeLength[0] * EdgeLength[1] - ABdotAC) / (EdgeLength[0] * EdgeLength[1] + ABdotAC + RegularizingConst); + TanHalfAngle[1] = (EdgeLength[0] * EdgeLength[2] - BCdotBA) / (EdgeLength[0] * EdgeLength[2] + BCdotBA + RegularizingConst); + TanHalfAngle[2] = (EdgeLength[1] * EdgeLength[2] - CAdotCB) / (EdgeLength[1] * EdgeLength[2] + CAdotCB + RegularizingConst); + + // The ABS is just a precaution.. mathematically these should all be positive, but very small angles may result in negative values. + + TanHalfAngle[0] = std::sqrt(FMath::Abs(TanHalfAngle[0])); // at vertA + TanHalfAngle[1] = std::sqrt(FMath::Abs(TanHalfAngle[1])); // at vertB + TanHalfAngle[2] = std::sqrt(FMath::Abs(TanHalfAngle[2])); // at vertC +#if 0 + // testing + double totalAngle = 2. * (std::atan(TanHalfAngle[0]) + std::atan(TanHalfAngle[1]) + std::atan(TanHalfAngle[2])); + + double angleError = M_PI - totalAngle; +#endif + } + + // return Tan(angle / 2) for the corner indicated by this vert id. + double GetTanHalfAngle(int32 VtxId) const + { + int32 Offset = 0; + + while (VtxId != TriVtxIds[Offset]) + { + Offset++; + checkSlow(Offset < 3); + } + + return TanHalfAngle[Offset]; + } + + // return the length of the indicated edge + double GetEdgeLenght(int32 EdgeId) const + { + int32 Offset = 0; + + while (EdgeId != TriEdgeIds[Offset]) + { + Offset++; + checkSlow(Offset < 3); + } + + return EdgeLength[Offset]; + } + + + int32 TriId = -1; + FIndex3i TriVtxIds; + FIndex3i TriEdgeIds; + + bool bDegenerate = true; + + double EdgeLength[3] = { 0. }; + double TanHalfAngle[3] = { 0. }; + + +}; + +/** +* Return and array in triangle order that holds the per-triangle derived data needed +*/ +template +TArray ConstructTriangleDataArray(const FDynamicMesh3& DynamicMesh, const FTriangleLinearization& TriangleLinearization) +{ + TArray< TriangleDataType > TriangleDataArray; + + const int32 NumTris = TriangleLinearization.NumTris(); + TriangleDataArray.SetNumUninitialized(NumTris); + + const auto& ToTriIdx = TriangleLinearization.ToIndex(); + + for (int32 i = 0; i < NumTris; ++i) + { + // Current triangle + + const int32 TriIdx = ToTriIdx[i]; + + // Compute all the geometric data needed for this triangle. + + TriangleDataArray[i].Initialize(DynamicMesh, TriIdx); + } + + return TriangleDataArray; +} + + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, FSparseMatrixD& AreaMatrix, TArray* BoundaryVerts) +{ + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + + // Create a mapping between the ordering of the vertex indices in the Dynamic Mesh + // and the actual storage. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + + // Create the mapping of triangles + + FTriangleLinearization TriangleMap(DynamicMesh); + + const TArray& ToMeshTri = TriangleMap.ToId(); + const TArray& ToTriIdx = TriangleMap.ToIndex(); + const int32 NumTris = TriangleMap.NumTris(); + + + // Clear space for the areas + std::vector DiagonalTriplets; + DiagonalTriplets.reserve(NumVerts); + + FSparseMatrixD Diagonals; + Diagonals.reserve(NumVerts); + + + // Create an array that holds all the geometric information we need for each triangle. + + TArray CotangentTriangleDataArray = ConstructTriangleDataArray(DynamicMesh, TriangleMap); + + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + // store the id of the boundary verts for later use. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 IVertId = ToMeshV[i]; // I - the row + + + if (DynamicMesh.IsBoundaryVertex(IVertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(IVertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + DiagonalTriplets.push_back(MatrixTripletT(i, i, 1.)); + continue; +#endif + } + + + // Compute the Voronoi area for this vertex. + double WeightArea = 0.; + for (int32 TriId : DynamicMesh.VtxTrianglesItr(IVertId)) + { + const int32 TriIdx = ToTriIdx[TriId]; + const CotanTriangleData& TriData = CotangentTriangleDataArray[TriIdx]; + + + // The three VertIds for this triangle. + const FIndex3i TriVertIds = DynamicMesh.GetTriangle(TriId); + + // Which of the corners is IVertId? + int32 Offset = 0; + while (TriVertIds[Offset] != IVertId) + { + Offset++; + checkSlow(Offset < 3); + } + + WeightArea += TriData.VoronoiArea[Offset]; + } + + + double WeightII = 0.; // accumulate to equal and opposite the sum of the neighbor weights + + // for each connecting edge + + for (int32 EdgeId : DynamicMesh.VtxEdgesItr(IVertId)) + { + // [v0, v1, t0, t1]: NB: both t0 & t1 exist since IVert isn't a boundary vert. + FIndex4i Edge = DynamicMesh.GetEdge(EdgeId); + + + // the other vert in the edge - identifies the matrix column + const int32 JVertId = (Edge[0] == IVertId) ? Edge[1] : Edge[0]; // J - the column + + checkSlow(JVertId != IVertId); + + // Get the cotangents for this edge. + + const int32 Tri0Idx = ToTriIdx[Edge[2]]; + const CotanTriangleData& Tri0Data = CotangentTriangleDataArray[Tri0Idx]; + const double CotanAlpha = Tri0Data.GetOpposingCotangent(EdgeId); + + + // The second triangle will be invalid if this is an edge! + + const double CotanBeta = (Edge[3] != FDynamicMesh3::InvalidID) ? CotangentTriangleDataArray[ToTriIdx[Edge[3]]].GetOpposingCotangent(EdgeId) : 0.; + + double WeightIJ = 0.5 * (CotanAlpha + CotanBeta); + WeightII += WeightIJ; + + const int32 j = ToIndex[JVertId]; + + MatrixTripelList.push_back(MatrixTripletT(i, j, WeightIJ)); + + + } + + MatrixTripelList.push_back(MatrixTripletT(i, i, -WeightII)); + + DiagonalTriplets.push_back(MatrixTripletT(i, i, WeightArea)); + + + } + + Diagonals.setFromTriplets(DiagonalTriplets.begin(), DiagonalTriplets.end()); + AreaMatrix.swap(Diagonals); + AreaMatrix.makeCompressed(); + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + LaplacianMatrix->makeCompressed(); + + + return LaplacianMatrix; + +} + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, const bool bClampWeights, TArray* BoundaryVerts) +{ + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + + // Create a mapping between the ordering of the vertex indices in the Dynamic Mesh + // and the actual storage. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + + // Map the triangles. + + FTriangleLinearization TriangleMap(DynamicMesh); + + const TArray& ToMeshTri = TriangleMap.ToId(); + const TArray& ToTriIdx = TriangleMap.ToIndex(); + const int32 NumTris = TriangleMap.NumTris(); + + // Create an array that holds all the geometric information we need for each triangle. + + TArray CotangentTriangleDataArray = ConstructTriangleDataArray(DynamicMesh, TriangleMap); + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + // skipping the boundary verts for later use. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 IVertId = ToMeshV[i]; // I - the row + + + if (DynamicMesh.IsBoundaryVertex(IVertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(IVertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + // Compute the Voronoi area for this vertex. + double WeightArea = 0.; + for (int32 TriId : DynamicMesh.VtxTrianglesItr(IVertId)) + { + const int32 TriIdx = ToTriIdx[TriId]; + const CotanTriangleData& TriData = CotangentTriangleDataArray[TriIdx]; + + + // The three VertIds for this triangle. + const FIndex3i TriVertIds = DynamicMesh.GetTriangle(TriId); + + // Which of the corners is IVertId? + int32 Offset = 0; + while (TriVertIds[Offset] != IVertId) + { + Offset++; + checkSlow(Offset < 3); + } + + WeightArea += TriData.VoronoiArea[Offset]; + } + + + double WeightII = 0.; // accumulate to equal and opposite the sum of the neighbor weights + + // for each connecting edge + + for (int32 EdgeId : DynamicMesh.VtxEdgesItr(IVertId)) + { + // [v0, v1, t0, t1]: NB: both t0 & t1 exist since IVert isn't a boundary vert. + FIndex4i Edge = DynamicMesh.GetEdge(EdgeId); + + + // the other vert in the edge - identifies the matrix column + const int32 JVertId = (Edge[0] == IVertId) ? Edge[1] : Edge[0]; // J - the column + + checkSlow(JVertId != IVertId); + + // Get the cotangents for this edge. + + const int32 Tri0Idx = ToTriIdx[Edge[2]]; + const CotanTriangleData& Tri0Data = CotangentTriangleDataArray[Tri0Idx]; + const double CotanAlpha = Tri0Data.GetOpposingCotangent(EdgeId); + + // The second triangle will be invalid if this is an edge! + + const double CotanBeta = (Edge[3] != FDynamicMesh3::InvalidID) ? CotangentTriangleDataArray[ToTriIdx[Edge[3]]].GetOpposingCotangent(EdgeId) : 0.; + + double WeightIJ = 0.5 * (CotanAlpha + CotanBeta); + + // clamp the weight + if (bClampWeights) + { + WeightIJ = FMath::Clamp(WeightIJ, -1.e5 * WeightArea, 1.e5 * WeightArea); + } + + WeightII += WeightIJ; + + const int32 j = ToIndex[JVertId]; + + MatrixTripelList.push_back(MatrixTripletT(i, j, WeightIJ / WeightArea)); + + } + + MatrixTripelList.push_back(MatrixTripletT(i, i, -WeightII / WeightArea)); + + } + +#if 0 + // used in degubing to insepct the matrix for testing + std::vector Tmp = MatrixTripelList; + + auto SortFunctor = [](const MatrixTripletT& A, const MatrixTripletT& B)->bool + { + return (A.value() < B.value()); + }; + + std::sort(Tmp.begin(), Tmp.end(), SortFunctor); + + auto ReverseSortFunctor = [](const MatrixTripletT& A, const MatrixTripletT& B)->bool + { + return (A.value() > B.value()); + }; + + std::sort(Tmp.begin(), Tmp.end(), ReverseSortFunctor); +#endif + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + + + + + + + +TUniquePtr ConstructMeanValueWeightLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + if (BoundaryVerts) + { + // empty + BoundaryVerts->Empty(); + } + + typedef FSparseMatrixD::Scalar ScalarT; + typedef Eigen::Triplet MatrixTripletT; + + + // Create a mapping between the ordering of the vertex indices in the Dynamic Mesh + // and the actual storage. + VertexMap.Reset(DynamicMesh); + + const TArray& ToMeshV = VertexMap.ToId(); + const TArray& ToIndex = VertexMap.ToIndex(); + const int32 NumVerts = VertexMap.NumVerts(); + + + // Map the triangles. + + FTriangleLinearization TriangleMap(DynamicMesh); + + const TArray& ToMeshTri = TriangleMap.ToId(); + const TArray& ToTriIdx = TriangleMap.ToIndex(); + const int32 NumTris = TriangleMap.NumTris(); + + + // Create an array that holds all the geometric information we need for each triangle. + + TArray TriangleDataArray = ConstructTriangleDataArray(DynamicMesh, TriangleMap); + + + // Eigen constructs a sparse matrix from a linearized array of matrix entries. + + std::vector MatrixTripelList; + { + int32 NumMatrixEntries = ComputeNumMatrixElements(DynamicMesh, ToMeshV); + MatrixTripelList.reserve(NumMatrixEntries); + } + + + // Construct Laplacian Matrix: loop over verts constructing the corresponding matrix row. + // skipping the boundary verts for later use. + + for (int32 i = 0; i < NumVerts; ++i) + { + const int32 IVertId = ToMeshV[i]; // I - the row + + + if (DynamicMesh.IsBoundaryVertex(IVertId)) + { + if (BoundaryVerts) + { + BoundaryVerts->Add(IVertId); + } +#if LAPLACIAN_SKIP_BOUNDARY == 1 + MatrixTripelList.push_back(MatrixTripletT(i, i, 0.)); + continue; +#endif + } + + + + double WeightII = 0.; // accumulate to equal and opposite the sum of the neighbor weights + + // for each connecting edge + + for (int32 EdgeId : DynamicMesh.VtxEdgesItr(IVertId)) + { + // [v0, v1, t0, t1]: NB: both t0 & t1 exist since IVert isn't a boundary vert. + FIndex4i Edge = DynamicMesh.GetEdge(EdgeId); + + // the other vert in the edge - identifies the matrix column + const int32 JVertId = (Edge[0] == IVertId) ? Edge[1] : Edge[0]; // J - the column + + // Get the cotangents for this edge. + + const int32 Tri0Idx = ToTriIdx[Edge[2]]; + const auto& Tri0Data = TriangleDataArray[Tri0Idx]; + double TanHalfAngleSum = Tri0Data.GetTanHalfAngle(IVertId); + double EdgeLength = FMath::Max(1.e-5, Tri0Data.GetEdgeLenght(EdgeId)); // Clamp the length + + // The second triangle will be invalid if this is an edge! + + TanHalfAngleSum += (Edge[3] != FDynamicMesh3::InvalidID) ? TriangleDataArray[ToTriIdx[Edge[3]]].GetTanHalfAngle(IVertId) : 0.; + + double WeightIJ = TanHalfAngleSum / EdgeLength; + WeightII += WeightIJ; + + const int32 j = ToIndex[JVertId]; + + MatrixTripelList.push_back(MatrixTripletT(i, j, WeightIJ)); + } + + MatrixTripelList.push_back(MatrixTripletT(i, i, -WeightII)); + + + } + + TUniquePtr LaplacianMatrix(new FSparseMatrixD(NumVerts, NumVerts)); + LaplacianMatrix->setFromTriplets(MatrixTripelList.begin(), MatrixTripelList.end()); + LaplacianMatrix->makeCompressed(); + + return LaplacianMatrix; + +} + +TUniquePtr ConstructLaplacian(const ELaplacianWeightScheme Scheme, const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts) +{ + + switch (Scheme) + { + default: + case ELaplacianWeightScheme::Uniform: + return ConstructUniformLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + case ELaplacianWeightScheme::Umbrella: + return ConstructUmbrellaLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + case ELaplacianWeightScheme::Valence: + return ConstructValenceWeightedLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + case ELaplacianWeightScheme::Cotangent: + { + bool bClampWeights = false; + return ConstructCotangentLaplacian(DynamicMesh, VertexMap, bClampWeights, BoundaryVerts); + break; + } + case ELaplacianWeightScheme::ClampedCotangent: + { + bool bClampWeights = true; + return ConstructCotangentLaplacian(DynamicMesh, VertexMap, bClampWeights, BoundaryVerts); + break; + } + case ELaplacianWeightScheme::MeanValue: + return ConstructMeanValueWeightLaplacian(DynamicMesh, VertexMap, BoundaryVerts); + break; + } +} + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.h new file mode 100644 index 000000000000..1d9b6ba1fb66 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/LaplacianOperators.h @@ -0,0 +1,156 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" + +#include "MeshElementLinearizations.h" + +#include "FSparseMatrixD.h" + +#include "MeshSmoothingUtilities.h" // for ELaplacianWeightScheme + + +/** +* Construct a sparse matrix representation of a uniform weighted Laplacian. +* The uniform weighted Laplacian is defined solely in terms of the connectivity +* of the mesh. Note, by construction this should be a symmetric matrix. +* +* Row i represents the Laplacian at vert_i, the non-zero entries correspond to +* the incident one-ring vertices vert_j. +* +* L_{ij} = 1 if vert_j is in the one-ring of vert_i +* L_{ii} = -Sum{ L_{ij}, j != i} +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ +TUniquePtr ConstructUniformLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation of an umbrella weighted Laplacian. +* This Laplacian is defined solely in terms of the connectivity +* of the mesh. Note, there is no expectation that the resulting matrix will be symmetric. +* +* Row i represents the Laplacian at vert_i, the non-zero entries correspond to +* the incident one-ring vertices vert_j. +* +* L_{ij} = 1 / valence(of i) if vert_j is in the one-ring of vert_i +* L_{ii} = -Sum{ L_{ij}, j != i} = -1 +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ +TUniquePtr ConstructUmbrellaLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation of a valence-weighted Laplacian. +* The valence weighted Laplacian is defined solely in terms of the connectivity +* of the mesh. Note, by construction this should be a symmetric matrix. +* +* Row i represents the Laplacian at vert_i, the non-zero entries correspond to +* the incident one-ring vertices vert_j. +* +* L_{ij} = 1/\sqrt(valence(i) + valence(j)) if vert_j is in the one-ring of vert_i +* L_{ii} = -Sum{ L_{ij}, j != i} +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ +TUniquePtr ConstructValenceWeightedLaplacian(const FDynamicMesh3& DynamicMesh3, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation using a cotangent-weighted Laplacian. +* NB: there is no reason to expect this to be a symmetric matrix. +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param bClampWeights Indicates if the off-diagonal weights should be clamped on construction. +* in practice this is desirable when creating the biharmonic operator, but not the mean curvature flow operator +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, const bool bClampWeights = false, TArray* BoundaryVerts = NULL); + +/** +* Construct a sparse matrix representation using a cotangent-weighted Laplacian. +* but returns the result in two symmetric parts. +* +* (AreaMatrix^{-1}) * L_hat = Cotangent weighted Laplacian. +* +* @return L_hat = Laplacian Without Area weighting. - this will be symmetric +* +* @param On return - diagonal matrix holding the voronoi areas for each vertex. Area 1 is assigned to any edge vertex +* +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ + +TUniquePtr ConstructCotangentLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, FSparseMatrixD& AreaMatrix, TArray* BoundaryVerts = NULL); + + + +/** +* Construct a sparse matrix representation using a meanvalue-weighted Laplacian. +* NB: there is no reason to expect this to be a symmetric matrix. +* +* @param DynamicMesh3 The triangle mesh +* @param VertexMap Additional arrays used to map between vertexID and offset in a linear array (i.e. the row) +* @param bClampWeights Indicates if the off-diagonal weights should be clamped on construction. +* in practice this is desirable when creating the biharmonic operator, but not the mean curvature flow operator +* @param BoundaryVerts Optionally capture the verts that are on mesh edges. +*/ + +TUniquePtr ConstructMeanValueWeightLaplacian(const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + +TUniquePtr ConstructLaplacian(const ELaplacianWeightScheme Scheme, const FDynamicMesh3& DynamicMesh, FVertexLinearization& VertexMap, TArray* BoundaryVerts = NULL); + + +/** +* Utility to map the enum names for debuging etc. +*/ +FString LaplacianSchemeName(const ELaplacianWeightScheme Scheme); + + +/** +* Only certian laplacian operators are symmetric... +*/ +static bool bIsSymmetricLaplacian(const ELaplacianWeightScheme Scheme) +{ + bool bSymmetric = false; + switch (Scheme) + { + case ELaplacianWeightScheme::ClampedCotangent: + bSymmetric = false; + break; + case ELaplacianWeightScheme::Cotangent: + bSymmetric = false; + break; + case ELaplacianWeightScheme::Umbrella: + bSymmetric = false; + break; + case ELaplacianWeightScheme::MeanValue: + bSymmetric = false; + break; + case ELaplacianWeightScheme::Uniform: + bSymmetric = true; + break; + case ELaplacianWeightScheme::Valence: + bSymmetric = true; + break; + default: + check(0); + } + return bSymmetric; +} + + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.cpp new file mode 100644 index 000000000000..a9091e8b1c30 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.cpp @@ -0,0 +1,36 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MatrixSolver.h" + +IMatrixSolverBase::~IMatrixSolverBase() {}; +IIterativeMatrixSolverBase::~IIterativeMatrixSolverBase() {}; + +TUniquePtr ContructMatrixSolver(const EMatrixSolverType& MatrixSolverType) +{ + TUniquePtr ResultPtr; + + switch (MatrixSolverType) + { + default: + + case EMatrixSolverType::LU: + ResultPtr.Reset(new FLUMatrixSolver()); + break; + case EMatrixSolverType::QR: + ResultPtr.Reset(new FQRMatrixSolver()); + break; + case EMatrixSolverType::PCG: + ResultPtr.Reset(new FPCGMatrixSolver()); + break; + case EMatrixSolverType::BICGSTAB: + ResultPtr.Reset(new FBiCGMatrixSolver()); + break; +#ifndef EIGEN_MPL2_ONLY + case EMatrixSolverType::LDLT: + ResultPtr.Reset(new FLDLTMatrixSolver()); + break; +#endif + } + + return ResultPtr; +} diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.h new file mode 100644 index 000000000000..eba5ad5c0efb --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MatrixSolver.h @@ -0,0 +1,304 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "FSparseMatrixD.h" +#include "Async/ParallelFor.h" + +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(push) +#pragma warning(disable : 6011) +#pragma warning(disable : 6387) +#pragma warning(disable : 6313) +#pragma warning(disable : 6294) +#endif +THIRD_PARTY_INCLUDES_START +#include +#include +#include +#include +#include +#include // BiCGSTAB & IncompleteLUT +#ifndef EIGEN_MPL2_ONLY +#include +#endif +#include +THIRD_PARTY_INCLUDES_END +#if defined(_MSC_VER) && USING_CODE_ANALYSIS +#pragma warning(pop) +#endif + +#include "FSOAPositions.h" + + +#include "ProfilingDebugging/ScopedTimers.h" + + +enum class EMatrixSolverType +{ + LU /* SparseLU*/, + QR /* SparseQR */, + BICGSTAB /* Iterative Bi-conjugate gradient */, + PCG /* Pre-conditioned conjugate gradient - requires symmetric positive def.*/, +#ifndef EIGEN_MPL2_ONLY + LDLT /**Not included due to MPL2 */ +#endif +}; + +static FString MatrixSolverName(const EMatrixSolverType SolverType) +{ + FString String; + switch (SolverType) + { + case EMatrixSolverType::LU: + String = FString(TEXT(" Direct LU ")); + break; + case EMatrixSolverType::QR: + String = FString(TEXT(" Direct QR ")); + break; + case EMatrixSolverType::BICGSTAB: + String = FString(TEXT(" Iterative BiConjugate Gradient ")); + break; + case EMatrixSolverType::PCG: + String = FString(TEXT(" Iterative Preconditioned Conjugate Gradient ")); + break; +#ifndef EIGEN_MPL2_ONLY + case EMatrixSolverType::PCG: + String = FString(TEXT(" Direct Cholesky ")); + break; +#endif + default: + check(0); + } + + return String; +} + +class FMatrixSolverSettings +{ +public: + EMatrixSolverType MatrixSolverType; + + // Used by iterative solvers + int32 MaxIterations = 600; + double Tolerance = 1e-4; +}; + + + +// Pure Virtual Base Class used to wrap Eigen Solvers. +class IMatrixSolverBase +{ +public: + typedef typename FSparseMatrixD::Scalar ScalarType; + typedef typename FSOAPositions::VectorType VectorType; + + IMatrixSolverBase() {} + virtual ~IMatrixSolverBase() = 0; + virtual bool bIsIterative() const = 0; + virtual void Solve(const VectorType& BVector, VectorType& SolVector) const = 0; + virtual void Solve(const FSOAPositions& BVectors, FSOAPositions& SolVectors) const = 0; + + virtual void SetUp(const FSparseMatrixD& Matrix, bool bIsSymmetric) = 0; + //virtual void SetIterations(int32 MaxIterations) = 0; + virtual void Reset() = 0; + virtual bool bSucceeded() const = 0; +}; + +// Additional methods particular to the iterative solvers +class IIterativeMatrixSolverBase : public IMatrixSolverBase +{ +public: + IIterativeMatrixSolverBase() {} + + virtual ~IIterativeMatrixSolverBase() = 0; + virtual void SetIterations(int32 MaxIterations) = 0; + virtual void SetTolerance(double Tol) = 0; + virtual void SolveWithGuess(const VectorType& GuessVector, const VectorType& BVector, VectorType& SolVector) const = 0; + virtual void SolveWithGuess(const FSOAPositions& GuessVector, const FSOAPositions& BVector, FSOAPositions& SolVector) const = 0; + +}; + +// Matrix Solver Factory +TUniquePtr ContructMatrixSolver(const EMatrixSolverType& MatrixSolverType); + +template +class TMatrixSolver : public IMatrixSolverBase +{ +public: + typedef DirectSolverType SolverType; + TMatrixSolver() {} + ~TMatrixSolver() override {}; + + bool bIsIterative() const override { return false; } + + void Solve(const VectorType& BVector, VectorType& SolVector) const override + { + checkSlow(bSetup); + SolVector = MatrixSolver.solve(BVector); + } + + void Solve(const FSOAPositions& BVectors, FSOAPositions& SolVectors) const override + { + const bool bForceSingleThreaded = false; + ParallelFor(3, [&](int Dir) + { SolVectors.Array(Dir) = MatrixSolver.solve(BVectors.Array(Dir)); }, bForceSingleThreaded); + } + + void SetUp(const FSparseMatrixD& SparseMatrix, bool bIsSymmetric) override + { + SetSymmetry(bIsSymmetric); + SetUp(SparseMatrix); + } + + void SetSymmetry(bool bIsSymmetric); + + //void SetIterations(int32 MaxIterations) override {} + + void Reset() override + { + //MatrixSolver = FMatrixSolver(); + bSetup = false; + } + + bool bSucceeded() const override + { + return (MatrixSolver.info() == Eigen::ComputationInfo::Success); + } + + +private: + + void SetUp(const FSparseMatrixD& SparseMatrix) + { + // The analyzePattern could be done just once if + // the sparsity pattern of the matrix is fixed. + // But testing indicates that takes little time compared + // with factorizing: e.g. dim 14508 matrix. Analyze 0.145s, Factorize 0.935s + + MatrixSolver.analyzePattern(SparseMatrix); + MatrixSolver.factorize(SparseMatrix); + + bSetup = true; + } +private: + + bool bSetup = false; + DirectSolverType MatrixSolver; +}; + + +/** +* Define wrappers for Direct Matrix Solver Types +*/ +// Timing Tests: +// 7k Tri 3.6k Verts. 10 sets of 3 backsolves 0.17s +// 29k tri 14.5k Verts 1.3s +// 100k tri 51.5k Verts 5.39s +// 127k tri 63.4k Verts 3.0s +typedef TMatrixSolver>> FLUMatrixSolver; +typedef TMatrixSolver>> FQRMatrixSolver; + +#ifndef EIGEN_MPL2_ONLY +// Not included due to MPL2. But in general much faster than standard LU + +// Timing info: +// 29k tris 14.5k verts 10 sets of 3 backsolves - 0.42s Analyze 0.03s Factorize 0.28s +// 45k tris 23k verts with 10 sets of 3 backsolves - 0.8s Analyze 0.08s Factorize 0.54s +// 49k verts with 10 sets of 3 backsolves - 3.35s Analyze 0.17s Factorize 2.67s +// 101k tris 50k verts with 10 sets of 3 backsolves - 1.34s Analyze 0.12s Factorize 0.84s +// 126k tris 63k verts with 10 sets of 3 backsolves - 0.8s Analyze 0.17s Factorize 0.22s +// 205k tris 102k verts with 10 sets of 3 backsolves - 3.5s Analyze 0.41s Factorize 2.2s +typedef TMatrixSolver< Eigen::SimplicialLDLT > FLDLTMatrixSolver +template<> +void TMatrixSolver::SetSymmetry(bool bIsSymmetric) { check(bIsSymmetric); }; + +#endif // EIGEN_MPL2_ONLY + +template<> +inline void TMatrixSolver::SetSymmetry(bool bIsSymmetric) { MatrixSolver.isSymmetric(bIsSymmetric); }; + +template<> +inline void TMatrixSolver::SetSymmetry(bool bIsSymmetric) {}; + + +template +class TIterativeMatrixSolver : public IIterativeMatrixSolverBase +{ +public: + typedef IterativeMatrixSolverType SolverType; + + TIterativeMatrixSolver() { MatrixSolver.setMaxIterations(1000); MatrixSolver.setTolerance(1e-4); } + ~TIterativeMatrixSolver() override {}; + + bool bIsIterative() const override { return true; } + + void Solve(const VectorType& BVector, VectorType& SolVector) const override + { + checkSlow(bSetup); + SolVector = MatrixSolver.solve(BVector); + } + void Solve(const FSOAPositions& BVectors, FSOAPositions& SolVectors) const override + { + const bool bForceSingleThreaded = false; + ParallelFor(3, [&](int Dir) + { SolVectors.Array(Dir) = MatrixSolver.solve(BVectors.Array(Dir)); }, bForceSingleThreaded); + } + + void SolveWithGuess(const VectorType& GuessVector, const VectorType& BVector, VectorType& SolVector) const override + { + + checkSlow(bSetup); + SolVector = MatrixSolver.solveWithGuess(BVector, GuessVector); + } + + void SolveWithGuess(const FSOAPositions& GuessVectors, const FSOAPositions& BVectors, FSOAPositions& SolVectors) const override + { + const bool bForceSingleThreaded = false; + ParallelFor(3, [&](int Dir) + { SolVectors.Array(Dir) = MatrixSolver.solveWithGuess(BVectors.Array(Dir), GuessVectors.Array(Dir)); }, bForceSingleThreaded); + } + void SetUp(const FSparseMatrixD& SparseMatrix, bool bIsSymmetric) override + { + SetUp(SparseMatrix); + } + + void SetIterations(int32 MaxIterations) override { MatrixSolver.setMaxIterations(MaxIterations); } + void SetTolerance(double Tol) override { MatrixSolver.setTolerance(Tol); } + + void Reset() override + { + //MatrixSolver = FMatrixSolver(); + bSetup = false; + } + + bool bSucceeded() const override + { + return (MatrixSolver.info() == Eigen::ComputationInfo::Success); + } + +private: + void SetUp(const FSparseMatrixD& SparseMatrix) + { + MatrixSolver.analyzePattern(SparseMatrix); + MatrixSolver.factorize(SparseMatrix); + + bSetup = true; + + int32 n = Eigen::nbThreads(); + } + +private: + bool bSetup = false; + + IterativeMatrixSolverType MatrixSolver; +}; + +/** +* Define wrappers for Iterative Matrix Solver Types +*/ +//typedef Eigen::IncompleteCholesky< double, Eigen::Lower | Eigen::Upper> FICPreConditioner; +typedef TIterativeMatrixSolver> FPCGMatrixSolver; +typedef TIterativeMatrixSolver >> FBiCGMatrixSolver; + diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshElementLinearizations.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshElementLinearizations.h new file mode 100644 index 000000000000..91edb43c8ad7 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshElementLinearizations.h @@ -0,0 +1,113 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" + + +/** +* Used linearize the VtxIds in a mesh as a single array and allow mapping from array offset to mesh VtxId. +* Generally, the array offset will correspond to a matrix row when forming a Laplacian +*/ +class FMeshElementLinearization +{ +public: + + FMeshElementLinearization() = default; + + // Lookup ToVtxId(Index) = VtxId; + const TArray& ToId() const { return ToIdMap; } + + // Lookup ToIndex(VtxId) = Index; may return FDynamicMesh3::InvalidID + const TArray& ToIndex() const { return ToIndexMap; } + + int32 NumIds() const { return ToIdMap.Num(); } + + // Following the FDynamicMesh3 convention this is really MaxId + 1 + int32 MaxId() const { return ToIndexMap.Num(); } + + void Empty() { ToIdMap.Empty(); ToIndexMap.Empty(); } + + void Populate(const int32 MaxId, const int32 Count, FRefCountVector::IndexEnumerable ElementItr) + { + ToIndexMap.SetNumUninitialized(MaxId); + ToIdMap.SetNumUninitialized(Count); + + for (int32 i = 0; i < MaxId; ++i) + { + ToIndexMap[i] = FDynamicMesh3::InvalidID; + } + + // create the mapping + { + int32 N = 0; + for (int32 Id : ElementItr) + { + ToIdMap[N] = Id; + ToIndexMap[Id] = N; + N++; + } + } + } + +protected: + TArray ToIdMap; + TArray ToIndexMap; + +private: + FMeshElementLinearization(const FMeshElementLinearization&); +}; + + +/** +* Used linearize the VtxIds in a mesh as a single array and allow mapping from array offset to mesh VtxId. +* Generally, the array offset will correspond to a matrix row when forming a Laplacian +*/ +class FVertexLinearization : public FMeshElementLinearization +{ +public: + FVertexLinearization() = default; + FVertexLinearization(const FDynamicMesh3& DynamicMesh) + { + Reset(DynamicMesh); + } + + void Reset(const FDynamicMesh3& DynamicMesh) + { + Empty(); + FMeshElementLinearization::Populate(DynamicMesh.MaxVertexID(), DynamicMesh.VertexCount(), DynamicMesh.VertexIndicesItr()); + } + + int32 NumVerts() const { return FMeshElementLinearization::NumIds(); } + +private: + FVertexLinearization(const FVertexLinearization&); +}; + +/** +* Used linearize the TriIds in a mesh as a single array and allow mapping from array offset to mesh TriId. +* +*/ +class FTriangleLinearization : public FMeshElementLinearization +{ +public: + FTriangleLinearization() = default; + + FTriangleLinearization(const FDynamicMesh3& DynamicMesh) + { + Reset(DynamicMesh); + } + + void Reset(const FDynamicMesh3& DynamicMesh) + { + Empty(); + FMeshElementLinearization::Populate(DynamicMesh.MaxTriangleID(), DynamicMesh.TriangleCount(), DynamicMesh.TriangleIndicesItr()); + } + + int32 NumTris() const { return FMeshElementLinearization::NumIds(); } + + +private: + FTriangleLinearization(const FTriangleLinearization&); +}; diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshSolverUtilitiesModule.cpp b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshSolverUtilitiesModule.cpp new file mode 100644 index 000000000000..32b7fb258ab6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Private/MeshSolverUtilitiesModule.cpp @@ -0,0 +1,19 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeshSolverUtilitiesModule.h" + +#define LOCTEXT_NAMESPACE "FMeshSolverUtilitiesModule" + +void FMeshSolverUtilitiesModule::StartupModule() +{ +} + +void FMeshSolverUtilitiesModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMeshSolverUtilitiesModule, MeshSolverUtilities) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSmoothingUtilities.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSmoothingUtilities.h new file mode 100644 index 000000000000..f7c80bb81b9b --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSmoothingUtilities.h @@ -0,0 +1,162 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DynamicMesh3.h" + + + +enum class ELaplacianWeightScheme +{ + Uniform, + Umbrella, + Valence, + MeanValue, + Cotangent, + ClampedCotangent +}; + +namespace MeshSmoothingOperators +{ + /** + * + * Note: for discussion of implicit / explicit integration of diffusion and biharmonic equations + * see "Implicit Fairing of Irregular Meshes using Diffusion and Curvature Flow" - M Desbrun 99. + * although the following suggests an additional source term could be included in the implicit solve for better accuracy. + * or "Generalized Surface Flows for Mesh Processing" Eckstein et al. 2007 + */ + + /** + * This is equivalent to taking a single backward Euler time step of bi-harmonic diffusion + * where L is the Laplacian (Del^2) , and L^T L is an approximation of the Del^4. + * + * dp/dt = - k*k L^T L[p] + * with + * weight = 1 / (k * Sqrt[dt] ) + * + * p^{n+1} + dt * k * k L^TL [p^{n+1}] = p^{n} + * + * re-write as + * L^TL[p^{n+1}] + weight * weight p^{n+1} = weight * weight p^{n} + + * + * The result is returned in the PositionArray + */ + void MESHSOLVERUTILITIES_API ComputeSmoothing_BiHarmonic(const ELaplacianWeightScheme WeightingScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Weight, const int32 NumIterations, TArray& PositionArray); + + void MESHSOLVERUTILITIES_API ComputeSmoothing_ImplicitBiHarmonicPCG(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, + const double Speed, const double Weight, const int32 MaxIterations, TArray& PositionArray); + + /** + * This is equivalent to forward or backward Euler time steps of the diffusion equation + * + * dp/dt = L[p] + * + * p^{n+1} = p^{n} + dt L[p^{n}] + * + * with dt = Speed / Max(|w_ii|) + * + * here w_ii are the diagonal values of L + */ + void MESHSOLVERUTILITIES_API ComputeSmoothing_Diffusion(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& OriginalMesh, bool bForwardEuler, + const double Speed, double Weight, const int32 NumIterations, TArray& PositionArray); + +} + +namespace MeshDeformingOperators +{ + class MESHSOLVERUTILITIES_API IConstrainedMeshOperator + { + public: + + virtual ~IConstrainedMeshOperator() {}; + + // Add or update a constraint associated with VtxId + virtual void AddConstraint(const int32 VtxId, const double Weight, const FVector3d& Position, const bool bPostFix) = 0; + + // Update or create a constraint position associated with @param VtxId + // @return true if a constraint weight is associated with @param VtxId + virtual bool UpdateConstraintPosition(const int32 VtxId, const FVector3d& Position, const bool bPostFix) = 0; + + // Update or create a constraint weight associated with @param VtxId + // @return true if a constraint position is associated with @param VtxId + virtual bool UpdateConstraintWeight(const int32 VtxId, const double Weight) = 0; + + // Clear all constraints (Positions and Weights) + virtual void ClearConstraints() = 0; + + // Clear all Constraint Weights + virtual void ClearConstraintWeights() = 0; + + // Clear all Constraint Positions + virtual void ClearConstraintPositions() = 0; + + + // Test if a non-zero weighted constraint is associated with VtxId + virtual bool IsConstrained(const int32 VtxId) const = 0; + + // Returns the vertex locations of the deformed mesh. + // Note the array may have empty elements as the index matches the Mesh based VtxId. PositionBuffer[VtxId] = Pos. + virtual bool Deform(TArray& PositionBuffer) = 0; + }; + + /** + * Solves the linear system for p_vec + * + * ( Transpose(L) * L + (0 0 ) ) p_vec = source_vec + ( 0 ) + * ( (0 lambda^2) ) ( lambda^2 c_vec ) + * + * where: L := laplacian for the mesh, + * source_vec := Transpose(L)*L mesh_vertex_positions + * lambda := weights + * c_vec := constrained positions + * + * Expected Use: + * + * // Create Deformation Solver from Mesh + * TUniquePtr MeshDeformer = ConstructConstrainedMeshDeformer(ELaplacianWeightScheme::ClampedCotangent, DynamicMesh); + * + * // Add constraints. + * for.. + * { + * int32 VtxId = ..; double Weight = ..; FVector3d TargetPos = ..; bool bPostFix = ...; + * MeshDeformer->AddConstraint(VtxId, Weight, TargetPos, bPostFix); + * } + * + * // Solve for new mesh vertex locations + * TArray PositionBuffer; + * MeshDeformer->Deform(PositionBuffer); + * + * // Update Mesh? for (int32 VtxId : DynamicMesh.VertexIndices()) DynamicMesh.SetVertex(VtxId, PositionBuffer[VtxId]); + * ... + * + * // Update constraint positions. + * for .. + * { + * int32 VtxId = ..; FVector3d TargetPos = ..; bool bPostFix = ...; + * MeshDeformer->UpdateConstraintPosition(VtxId, TargetPos, bPostFix); + * } + * + * // Solve for new vertex locations. + * MeshDeformer->Deform(PositionBuffer); + * // Update Mesh? + */ + TUniquePtr MESHSOLVERUTILITIES_API ConstructConstrainedMeshDeformer(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh); + + /** + * Solves the linear system for p_vec + * + * ( Transpose(L) * L + (0 0 ) ) p_vec = ( 0 ) + * ( (0 lambda^2) ) ( lambda^2 c_vec ) + * + * where: L := laplacian for the mesh, + * lambda := weights + * c_vec := constrained positions + * + * Expected Use: same as the ConstrainedMeshDeformer above. + * + */ + TUniquePtr MESHSOLVERUTILITIES_API ConstructConstrainedMeshSmoother(const ELaplacianWeightScheme WeightScheme, const FDynamicMesh3& DynamicMesh); +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSolverUtilitiesModule.h b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSolverUtilitiesModule.h new file mode 100644 index 000000000000..1c7e5dfa4f94 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/Source/MeshSolverUtilities/Public/MeshSolverUtilitiesModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMeshSolverUtilitiesModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/WildMagic.tps b/Engine/Plugins/Experimental/GeometryProcessing/WildMagic.tps new file mode 100644 index 000000000000..197752188e99 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/WildMagic.tps @@ -0,0 +1,13 @@ + + + WildMagic 5.17 + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/WildMagic_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/geometry3Sharp.tps b/Engine/Plugins/Experimental/GeometryProcessing/geometry3Sharp.tps new file mode 100644 index 000000000000..d01869f33319 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/geometry3Sharp.tps @@ -0,0 +1,13 @@ + + + geometry3Sharp + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://github.com/gradientspace/geometry3Sharp/blob/master/LICENSE + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/geometry3Sharp_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/GeometryProcessing/geometry3cpp.tps b/Engine/Plugins/Experimental/GeometryProcessing/geometry3cpp.tps new file mode 100644 index 000000000000..18e59c0597c6 --- /dev/null +++ b/Engine/Plugins/Experimental/GeometryProcessing/geometry3cpp.tps @@ -0,0 +1,13 @@ + + + geometry3cpp + /Engine/Plugins/Experimental/GeometryProcessing + 2D/3D geometry processing and geometric modeling. + https://github.com/gradientspace/geometry3cpp/blob/master/LICENSE + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/geometry3cpp_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h index 0bc045923fe6..f871c8b33f15 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshConvertUtils.h @@ -39,9 +39,10 @@ namespace ProxyLOD * * @param InMesh Mesh with a mixture of quads and triangles. * @param OutMesh Triangle Mesh. + * @param bClockWise how to order the verts in a triangle */ template - void MixedPolyMeshToAOSMesh(const FMixedPolyMesh& InMesh, TAOSMesh& OutMesh); + void MixedPolyMeshToAOSMesh(const FMixedPolyMesh& InMesh, TAOSMesh& OutMesh, const bool bClockWise = true); /** @@ -103,8 +104,6 @@ namespace ProxyLOD #define PROXYLOD_CLOCKWISE_TRIANGLES 1 #endif - - static FVector ComputeNormal(const FVector(&Tri)[3]) { @@ -119,7 +118,7 @@ static FVector ComputeNormal(const FVector(&Tri)[3]) // Convert MixedPolyMesh to AOS Mesh. This requires splitting quads to produce triangles. template -void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSMesh& DstAOSMesh) +void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSMesh& DstAOSMesh, bool bClockWise) { // Splitting a quad doesn't introduce any new verts. @@ -160,7 +159,7 @@ void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSM // NB: The Quads are ordered in clockwise fashion. ProxyLOD::Parallel_For(ProxyLOD::FUIntRange(0, NumQuads), - [&MixedPolyMesh, &DstAOSMesh](const ProxyLOD::FUIntRange& Range) + [&MixedPolyMesh, &DstAOSMesh, bClockWise](const ProxyLOD::FUIntRange& Range) { uint32* Indices = DstAOSMesh.Indexes; for (uint32 q = Range.begin(), Q = Range.end(); q < Q; ++q) @@ -168,33 +167,36 @@ void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSM const uint32 Offset = q * 6; const openvdb::Vec4I& Quad = MixedPolyMesh.Quads[q]; // add as two triangles -#if (PROXYLOD_CLOCKWISE_TRIANGLES == 1) - // first triangle - Indices[Offset] = Quad[0]; - Indices[Offset + 1] = Quad[1]; - Indices[Offset + 2] = Quad[2]; - // second triangle - Indices[Offset + 3] = Quad[2]; - Indices[Offset + 4] = Quad[3]; - Indices[Offset + 5] = Quad[0]; -#else - // first triangle - Indices[Offset] = Quad[0]; - Indices[Offset + 1] = Quad[3]; - Indices[Offset + 2] = Quad[2]; + if (bClockWise) + { + // first triangle + Indices[Offset] = Quad[0]; + Indices[Offset + 1] = Quad[1]; + Indices[Offset + 2] = Quad[2]; + // second triangle + Indices[Offset + 3] = Quad[2]; + Indices[Offset + 4] = Quad[3]; + Indices[Offset + 5] = Quad[0]; + } + else + { + // first triangle + Indices[Offset] = Quad[0]; + Indices[Offset + 1] = Quad[3]; + Indices[Offset + 2] = Quad[2]; - // second triangle - Indices[Offset + 3] = Quad[2]; - Indices[Offset + 4] = Quad[1]; - Indices[Offset + 5] = Quad[0]; -#endif + // second triangle + Indices[Offset + 3] = Quad[2]; + Indices[Offset + 4] = Quad[1]; + Indices[Offset + 5] = Quad[0]; + } } }); // add the MixedPolyMesh triangles. ProxyLOD::Parallel_For(ProxyLOD::FUIntRange(0, MixedPolyMesh.Triangles.size()), - [&MixedPolyMesh, &DstAOSMesh, NumQuads](const ProxyLOD::FUIntRange& Range) + [&MixedPolyMesh, &DstAOSMesh, NumQuads, bClockWise](const ProxyLOD::FUIntRange& Range) { uint32* Indices = DstAOSMesh.Indexes; for (uint32 t = Range.begin(), EndT = Range.end(); t < EndT; ++t) @@ -202,15 +204,18 @@ void ProxyLOD::MixedPolyMeshToAOSMesh(const FMixedPolyMesh& MixedPolyMesh, TAOSM const uint32 Offset = NumQuads * 6 + t * 3; const openvdb::Vec3I& Tri = MixedPolyMesh.Triangles[t]; // add the triangle -#if (PROXYLOD_CLOCKWISE_TRIANGLES == 1) - Indices[Offset] = Tri[0]; - Indices[Offset + 1] = Tri[1]; - Indices[Offset + 2] = Tri[2]; -#else - Indices[Offset] = Tri[2]; - Indices[Offset + 1] = Tri[1]; - Indices[Offset + 2] = Tri[0]; -#endif + if (bClockWise) + { + Indices[Offset] = Tri[0]; + Indices[Offset + 1] = Tri[1]; + Indices[Offset + 2] = Tri[2]; + } + else + { + Indices[Offset] = Tri[2]; + Indices[Offset + 1] = Tri[1]; + Indices[Offset + 2] = Tri[0]; + } } }); } diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp index f23aa8de05a9..b487385b7beb 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.cpp @@ -11,8 +11,31 @@ THIRD_PARTY_INCLUDES_END #include "ProxyLODThreadedWrappers.h" #include "ProxyLODMeshUtilities.h" - bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& TextureAtlasDesc, const bool VertexColorParts) +{ + // desired parameters for ISO-Chart method + + // MaxChartNum = 0 will allow any number of charts to be generated. + + const size_t MaxChartNumber = 0; + + // Let the polys in the partitions stretch some.. 1.f will let it stretch freely + + const float MaxStretch = 0.125f; // Question. Does this override the pIMT? + + // Note: I tried constructing this from the normals, but that resulted in some large planar regions being really compressed in the + // UV chart. + const bool bComputeIMTFromVertexNormal = false; + + // No Op + auto NoOpCallBack = [](float percent)->HRESULT {return S_OK; }; + + return GenerateUVs(InOutMesh, TextureAtlasDesc, VertexColorParts, MaxStretch, MaxChartNumber, bComputeIMTFromVertexNormal, NoOpCallBack); +} + +bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& TextureAtlasDesc, const bool VertexColorParts, + const float MaxStretch, const size_t MaxChartNumber, const bool bComputeIMTFromVertexNormal, + std::function StatusCallBack, float* MaxStretchOut, size_t* NumChartsOut) { std::vector DirextXAdjacency; @@ -22,11 +45,6 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& if (!bValidMesh) return false; - // No Op - auto NoOpCallBack = [](float percent)->HRESULT {return S_OK; }; - - // Gather data for UVAtlas Create - // Data from the existing mesh const DirectX::XMFLOAT3* Pos = (DirectX::XMFLOAT3*) (InOutMesh.Points.GetData()); @@ -40,16 +58,6 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& const uint32* adjacency = DirextXAdjacency.data(); - // desired parameters for ISO-Chart method - - // MaxChartNum = 0 will allow any number of charts to be generated. - - const size_t MaxChartNumber = 0; - - // Let the polys in the partitions stretch some.. 1.f will let it stretch freely - - const float MaxStretch = 0.125f; // Question. Does this override the pIMT? - // Size of the texture atlas @@ -66,21 +74,36 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& std::vector facePartitioning; // Capture stats about the result. - float maxStretchOut = 0.f; - size_t numChartsOut = 0; + float maxStretchUsed = 0.f; + size_t numChartsUsed = 0; - // per-triangle IMT from per-vertex data(normal). Note: I tried constructing this from - // the normals, but that resulted in some large planar regions being really compressed in the - // UV chart. + float * pIMTArray = new float[NumFaces * 3]; - for (int32 f = 0; f < NumFaces; ++f) + if (!bComputeIMTFromVertexNormal) { - int32 offset = 3 * f; + for (int32 f = 0; f < NumFaces; ++f) { - pIMTArray[offset ] = 1.f; - pIMTArray[offset + 1] = 0.f; - pIMTArray[offset + 2] = 1.f; + int32 offset = 3 * f; + { + pIMTArray[offset] = 1.f; + pIMTArray[offset + 1] = 0.f; + pIMTArray[offset + 2] = 1.f; + } + } + } + else + { + // per-triangle IMT from per-vertex data(normal). + + const TArray& Normals = InOutMesh.Normal; + const float* PerVertSignal = (float*)Normals.GetData(); + size_t SignalStride = 3 * sizeof(float); + HRESULT IMTResult = UVAtlasComputeIMTFromPerVertexSignal(Pos, NumVerts, indices, DXGI_FORMAT_R32_UINT, NumFaces, PerVertSignal, 3, SignalStride, StatusCallBack, pIMTArray); + + if (FAILED(IMTResult)) + { + return false; } } @@ -89,10 +112,18 @@ bool ProxyLOD::GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& MaxChartNumber, MaxStretch, width, height, gutter, adjacency, NULL /*false adj*/, pIMTArray /*IMTArray*/, - NoOpCallBack, DirectX::UVATLAS_DEFAULT_CALLBACK_FREQUENCY, + StatusCallBack, DirectX::UVATLAS_DEFAULT_CALLBACK_FREQUENCY, DirectX::UVATLAS_DEFAULT, vb, ib, - &facePartitioning, &vertexRemapArray, &maxStretchOut, &numChartsOut); + &facePartitioning, &vertexRemapArray, &maxStretchUsed, &numChartsUsed); + if (MaxStretchOut) + { + *MaxStretchOut = maxStretchUsed; + } + if (NumChartsOut) + { + *NumChartsOut = numChartsUsed; + } if (FAILED(hr)) return false; if (pIMTArray) delete[] pIMTArray; diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h index 688b22ece5e8..caffefb299f0 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshParameterization.h @@ -5,7 +5,7 @@ #include "ProxyLODMeshTypes.h" // FVertexDataMesh #include "ProxyLODGrid2d.h" // FTextureAtlasDesc -#include +#include // Note: should convert std::function to TFunction /** * In mesh segmentation (charts and atlases) and parameterization (generating UVs ) we rely @@ -33,6 +33,34 @@ namespace ProxyLOD */ bool GenerateUVs( FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& InTextureAtlasDesc, const bool bVertexColorParts = false); + + + /** + * Lower-level entry point: + * Method that generates new UVs on the FVertexDataMesh according to the parameters specified in the FTextureAtlasDesc + * The underlying code uses Isometeric approach (Iso-Charts) in UV generation. + * + * As a debugging option, the updated InOutMesh can have vertex colors that distinguish the various UV charts. + * + * NB: The mesh vertex count may change as vertices are split on UV seams. + * + * @param InOutMesh Mesh that will be updated by this function, adding UVs + * @param InTextureAtlasDesc Description of the texel resolution of the desired texture atlas. + * @param bVertColorParts Option to add vertex colors according the the chart in the texture atlas, used for debugging. + * @param MaxStretch The maximum amount of stretch between (0 - none and 1 - any) + * @param MaxChartNumber Maximum number of charts required for the atlas. If this is 0, will be parameterized solely on stretch. + * Note, not a hard limit - isochart will stop when a valid charting is found that is greater or equal to this number. + * @param MaxStretchOut Actual max stretch used in computing uvs + * @param NumChartsOut Number of charts actually generated + * + * @return 'true' if the UV generation succeeded, 'false' if it failed. + */ + bool GenerateUVs(FVertexDataMesh& InOutMesh, const FTextureAtlasDesc& TextureAtlasDesc, const bool VertexColorParts, + const float MaxStretch, const size_t MaxChartNumber, + const bool bComputeIMTFromVertexNormal, + std::function StatusCallBack, + float* MaxStretchOut = NULL, size_t* NumChartsOut = NULL); + /** * Generate adjacency data needed for the mesh, additionally this may alter the mesh in attempting to * remove mesh degeneracy problems. This method is primarily called within GenerateUVs diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp index b8046c66d080..3b4fa80cfa52 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.cpp @@ -6,7 +6,7 @@ // --- FMeshDescriptionAdapter ---- FMeshDescriptionAdapter::FMeshDescriptionAdapter(const FMeshDescription& InRawMesh, const openvdb::math::Transform& InTransform) : - RawMesh(&InRawMesh), Transform(&InTransform) + RawMesh(&InRawMesh), Transform(InTransform) { InitializeCacheData(); } @@ -25,6 +25,21 @@ void FMeshDescriptionAdapter::InitializeCacheData() { TriangleCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + + IndexBuffer.Reserve(TriangleCount * 3); + + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.Add(Tri.VertexInstanceID0); + IndexBuffer.Add(Tri.VertexInstanceID1); + IndexBuffer.Add(Tri.VertexInstanceID2); + } + } + + } size_t FMeshDescriptionAdapter::polygonCount() const @@ -40,10 +55,13 @@ size_t FMeshDescriptionAdapter::pointCount() const void FMeshDescriptionAdapter::getIndexSpacePoint(size_t FaceNumber, size_t CornerNumber, openvdb::Vec3d& pos) const { // Get the vertex position in local space. - const FVertexInstanceID VertexInstanceID = FVertexInstanceID(FaceNumber * 3 + CornerNumber); + const FVertexInstanceID VertexInstanceID = IndexBuffer[FaceNumber * 3 + CornerNumber]; // float3 position - FVector Position = VertexPositions[RawMesh->GetVertexInstanceVertex(VertexInstanceID)]; - pos = Transform->worldToIndex(openvdb::Vec3d(Position.X, Position.Y, Position.Z)); + const FVertexID VertexID = RawMesh->GetVertexInstanceVertex(VertexInstanceID); + + + FVector Position = VertexPositions[VertexID]; + pos = Transform.worldToIndex(openvdb::Vec3d(Position.X, Position.Y, Position.Z)); }; @@ -63,11 +81,30 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const TArrayRawMesh; PointCount += size_t(RawMesh->Vertices().Num()); + // Sum up all the polys in this mesh. + int32 MeshPolyCount = 0; for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) { - PolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); + MeshPolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + // Construct local index buffer for this mesh + IndexBufferArray.push_back(std::vector()); + std::vector& IndexBuffer = IndexBufferArray[MeshIdx]; + IndexBuffer.reserve(MeshPolyCount * 3); + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.push_back(Tri.VertexInstanceID0); + IndexBuffer.push_back(Tri.VertexInstanceID1); + IndexBuffer.push_back(Tri.VertexInstanceID2); + } + } + + + PolyCount += MeshPolyCount; PolyOffsetArray.push_back(PolyCount); RawMeshArray.push_back(RawMesh); RawMeshArrayData.push_back(FMeshDescriptionAttributesGetter(RawMesh)); @@ -93,10 +130,29 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const TArrayRawMesh; PointCount += size_t(RawMesh->Vertices().Num()); + + // Sum up all the polys in this mesh. + int32 MeshPolyCount = 0; for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) { - PolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); + MeshPolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + PolyCount += MeshPolyCount; + // Construct local index buffer for this mesh + IndexBufferArray.push_back(std::vector()); + std::vector& IndexBuffer = IndexBufferArray[MeshIdx]; + IndexBuffer.reserve(MeshPolyCount * 3); + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.push_back(Tri.VertexInstanceID0); + IndexBuffer.push_back(Tri.VertexInstanceID1); + IndexBuffer.push_back(Tri.VertexInstanceID2); + } + } + PolyOffsetArray.push_back(PolyCount); RawMeshArray.push_back(RawMesh); @@ -121,11 +177,30 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const TArrayRawMesh; PointCount += size_t(RawMesh->Vertices().Num()); + + // Sum up all the polys in this mesh. + int32 MeshPolyCount = 0; for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) { - PolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); + MeshPolyCount += RawMesh->GetPolygonTriangles(PolygonID).Num(); } + // Construct local index buffer for this mesh + IndexBufferArray.push_back(std::vector()); + std::vector& IndexBuffer = IndexBufferArray[MeshIdx]; + IndexBuffer.reserve(MeshPolyCount * 3); + for (const FPolygonID& PolygonID : RawMesh->Polygons().GetElementIDs()) + { + const auto& Triangles = RawMesh->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& Tri : Triangles) + { + IndexBuffer.push_back(Tri.VertexInstanceID0); + IndexBuffer.push_back(Tri.VertexInstanceID1); + IndexBuffer.push_back(Tri.VertexInstanceID2); + } + } + + PolyCount += MeshPolyCount; PolyOffsetArray.push_back(PolyCount); RawMeshArray.push_back(RawMesh); RawMeshArrayData.push_back(FMeshDescriptionAttributesGetter(RawMesh)); @@ -147,6 +222,13 @@ FMeshDescriptionArrayAdapter::FMeshDescriptionArrayAdapter(const FMeshDescriptio { RawMeshArrayData.push_back(FMeshDescriptionAttributesGetter(RawMesh)); } + + IndexBufferArray.reserve(other.IndexBufferArray.size()); + for (const auto& IndexBuffer : other.IndexBufferArray) + { + IndexBufferArray.push_back(IndexBuffer); + } + } FMeshDescriptionArrayAdapter::~FMeshDescriptionArrayAdapter() @@ -166,8 +248,9 @@ void FMeshDescriptionArrayAdapter::getWorldSpacePoint(size_t FaceNumber, size_t const FMeshDescription& RawMesh = GetRawMesh(FaceNumber, MeshIdx, LocalFaceNumber, &AttributesGetter); check(AttributesGetter); + const auto& IndexBuffer = IndexBufferArray[MeshIdx]; // Get the vertex position in local space. - const FVertexInstanceID VertexInstanceID = FVertexInstanceID(3 * LocalFaceNumber + int32(CornerNumber)); + const FVertexInstanceID VertexInstanceID = IndexBuffer[3 * LocalFaceNumber + int32(CornerNumber)]; // float3 position FVector Position = AttributesGetter->VertexPositions[RawMesh.GetVertexInstanceVertex(VertexInstanceID)]; pos = openvdb::Vec3d(Position.X, Position.Y, Position.Z); diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h index bbf8b2c5544b..262110f28e86 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODMeshTypes.h @@ -219,7 +219,7 @@ public: */ const openvdb::math::Transform& GetTransform() const { - return *Transform; + return Transform; } private: @@ -227,14 +227,20 @@ private: // Pointer to the raw mesh we are wrapping const FMeshDescription* RawMesh; + + ////////////////////////////////////////////////////////////////////////// //Cache data void InitializeCacheData(); uint32 TriangleCount; TVertexAttributesConstRef VertexPositions; + // Local version of the index array. The FMeshDescription doesn't really have one. + TArray IndexBuffer; + + // Transform used to convert the mesh space into the index space used by voxelization - const openvdb::math::Transform* Transform; + const openvdb::math::Transform Transform; }; namespace ProxyLOD @@ -421,6 +427,9 @@ protected: std::vector MergeDataArray; + // Need to build local index buffers for each mesh because the FMeshDescription doesn't natively have the construct. + std::vector> IndexBufferArray; + }; /** diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODParameterization.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODParameterization.cpp new file mode 100644 index 000000000000..d27b0cd7e64e --- /dev/null +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODParameterization.cpp @@ -0,0 +1,92 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ProxyLODParameterization.h" + +#include "ProxyLODMeshConvertUtils.h" +#include "ProxyLODMeshUtilities.h" +#include "ProxyLODMeshParameterization.h" + + + + +class FProxyLODParameterizationImpl : public IProxyLODParameterization +{ +public: + FProxyLODParameterizationImpl() {} + + virtual ~FProxyLODParameterizationImpl() {}; + + + virtual bool ParameterizeMeshDescription(FMeshDescription& MeshDescription, int32 Width, int32 Height, float GutterSpace, float Stretch, int32 ChartNum, bool bUseNormals, bool bRecomputeTangentSpace, bool bPrintDebugMessages) const override + { + const float MaxStretch = Stretch; + const size_t MaxChartNumber = ChartNum; + const bool bUseNormalsInUV = bUseNormals; + + // No Op + auto NoOpCallBack = [](float percent)->HRESULT {return S_OK; }; + + bool bComputeNormals = bRecomputeTangentSpace; + bool bSplitHardAngles = false; + bool bComputeTangentSpace = bRecomputeTangentSpace; + + float HardAngleDegrees = 80.f; + FVertexDataMesh VertexDataMesh; + ProxyLOD::ConvertMesh(MeshDescription, VertexDataMesh); + + if (bSplitHardAngles) + { + ProxyLOD::SplitHardAngles(HardAngleDegrees, VertexDataMesh); + } + + if (bComputeNormals) + { + ProxyLOD::ComputeVertexNormals(VertexDataMesh, ProxyLOD::ENormalComputationMethod::AngleWeighted); + } + + + FIntPoint UVSize(Height, Width); + ProxyLOD::FTextureAtlasDesc TextureAtlasDesc(UVSize, GutterSpace); + + float MaxStretchUsed = 0.f; + size_t NumChartsMade = 0; + bool bUVGenerationSucess = ProxyLOD::GenerateUVs(VertexDataMesh, TextureAtlasDesc, false, MaxStretch, MaxChartNumber, bUseNormalsInUV, NoOpCallBack, &MaxStretchUsed, &NumChartsMade); + + if (bUVGenerationSucess) + { + if (bPrintDebugMessages) + { + UE_LOG(LogTemp, Warning, TEXT("UV Generation: generated %d charts, using max stretch %f "), NumChartsMade, MaxStretchUsed); + } + + if (bComputeTangentSpace) + { + ProxyLOD::ComputeTangentSpace(VertexDataMesh, false); + } + // Reset the result mesh description + MeshDescription.Empty(); + UStaticMesh::RegisterMeshAttributes(MeshDescription); + + // convert to fill the mesh description + ProxyLOD::ConvertMesh(VertexDataMesh, MeshDescription); + } + + + return bUVGenerationSucess; + }; +}; + + + +TUniquePtr IProxyLODParameterization::CreateTool() +{ + TUniquePtr Tool = MakeUnique(); + + if (Tool == nullptr) + { + return nullptr; + } + + return Tool; + +} diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp index 3898a1d81199..8d089b88c731 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp @@ -15,6 +15,7 @@ THIRD_PARTY_INCLUDES_START #include // for Spatial Query #include // for MeshToVolume #include // for VolumeToMesh +#include // for CSG operations #pragma warning(pop) THIRD_PARTY_INCLUDES_END @@ -149,3 +150,345 @@ TUniquePtr IProxyLODVolume::CreateSDFVolumeFromMeshArray(const return Volume; } + + +class FPolygonSoup +{ +public: + FPolygonSoup(const TArray& AdapterArray, double VoxelSize); + + // Total number of polygons + size_t polygonCount() const { return NumPolys; } + + // Total number of points (vertex locations) + size_t pointCount() const { return NumVerts; } + + // Vertex count for polygon n: currently FMeshDescription is just triangles. + size_t vertexCount(size_t n) const { return 3; } + + // Return position pos in local grid index space for polygon n and vertex v + void getIndexSpacePoint(size_t FaceNumber, size_t CornerNumber, openvdb::Vec3d& pos) const + { + int32 AdapterIdx = PolyIdxToAdapter[FaceNumber]; + int32 Offset = AdapterToOffset[AdapterIdx]; + + Adapters[AdapterIdx].getIndexSpacePoint(FaceNumber - Offset, CornerNumber, pos); + } + + const OpenVDBTransform& Transform() const { return *Xform; } + +private: + + FPolygonSoup(); + + OpenVDBTransform::Ptr Xform; + + TArray Adapters; + TArray AdapterToOffset; + TArray PolyIdxToAdapter; + size_t NumPolys; + size_t NumVerts; + +}; + +FPolygonSoup::FPolygonSoup(const TArray& AdapterArray, double VoxelSize) +{ + + Xform = OpenVDBTransform::createLinearTransform(VoxelSize); + + int32 NumMeshes = AdapterArray.Num(); + Adapters.Reserve(NumMeshes); + + NumPolys = 0; + NumVerts = 0; + for (auto& Adapter : AdapterArray) + { + Adapters.Add(Adapter); + NumPolys += Adapter.polygonCount(); + NumVerts += Adapter.pointCount(); + } + + AdapterToOffset.AddZeroed(NumMeshes + 1); + for (int32 i = 1; i < NumMeshes + 1; ++i) + { + AdapterToOffset[i] = AdapterToOffset[i-1] + AdapterArray[i - 1].polygonCount(); + } + + PolyIdxToAdapter.Reserve(NumPolys); + for (int32 i = 0; i < NumMeshes; ++i) + { + int32 np = AdapterArray[i].polygonCount(); + for (int32 j = 0; j < np; ++j) + { + PolyIdxToAdapter.Add(i); + } + } +} + + + + + +class FVoxelBasedCSGImpl : public IVoxelBasedCSG +{ +public: + FVoxelBasedCSGImpl() + { + openvdb::initialize(); + + double VoxelSize = 0.1; + XForm = OpenVDBTransform::createLinearTransform(VoxelSize); + } + + FVoxelBasedCSGImpl(double VoxelSize) + { + openvdb::initialize(); + + XForm = OpenVDBTransform::createLinearTransform(VoxelSize); + } + + virtual ~FVoxelBasedCSGImpl() + { + XForm.reset(); + } + + double GetVoxelSize() const override + { + return XForm->voxelSize()[0]; + } + + void SetVoxelSize(double VoxelSize) override + { + XForm = OpenVDBTransform::createLinearTransform(VoxelSize); + } + + // Note this will become very slow if the isosurface is more than 3 voxel widths from 0. + // the tool that calls this should clamp the isosurface. + FVector ComputeUnion(const TArray& PlacedMeshArray, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface ) const override + { + + + // convert IsoSurface units to voxels + + const double VoxelSize = GetVoxelSize(); + + const double IsoSurfaceInVoxels = IsoSurface / VoxelSize; + + const double ExteriorVoxelWidth = FMath::Max( 2., IsoSurfaceInVoxels + 1.); + const double InteriorVoxelWidth = -FMath::Min(-2., IsoSurfaceInVoxels - 1); + + + const int32 NumMeshes = PlacedMeshArray.Num(); + if (NumMeshes == 0) + { + return FVector(0.f, 0.f, 0.f); + } + + // Find the average translation of all the meshes. + + FVector AverageTranslation(0.f, 0.f, 0.f); + for (int32 i = 0; i < NumMeshes; ++i) + { + AverageTranslation += PlacedMeshArray[i].Transform.GetTranslation(); + } + AverageTranslation /= (float)NumMeshes; + + + // Target grid to hold the union in SDF form. + //openvdb::FloatGrid::Ptr SDFUnionVolume = openvdb::FloatGrid::create(ExteriorVoxelWidth /*background value */); + //SDFUnionVolume->setTransform(XForm); + + // Fill the SDFUnionVolume + FMatrix LocalToVoxel = FMatrix::Identity; + LocalToVoxel.M[0][0] = VoxelSize; + LocalToVoxel.M[1][1] = VoxelSize; + LocalToVoxel.M[2][2] = VoxelSize; + + TArray Adapters; + for (int32 i = 0, I = PlacedMeshArray.Num(); i < I; ++i) + { + const FPlacedMesh& PlacedMesh = PlacedMeshArray[i]; + const FMeshDescription* MeshPtr = PlacedMesh.Mesh; + if (MeshPtr) + { + + // Get the transform relative to the average + FTransform MeshTransform = PlacedMesh.Transform; + MeshTransform.AddToTranslation(-AverageTranslation); + FMatrix TransformMatrix = MeshTransform.ToMatrixWithScale().Inverse(); + + + TransformMatrix = LocalToVoxel * TransformMatrix; + float* data = &TransformMatrix.M[0][0]; + openvdb::math::Mat4 VDBMatFloat(data); + + openvdb::Mat4R VDBMatDouble(VDBMatFloat); + OpenVDBTransform::Ptr LocalXForm = OpenVDBTransform::createLinearTransform(VDBMatDouble); + + // Create a wrapper with OpenVDB semantics. + + FMeshDescriptionAdapter MeshAdapter(*MeshPtr, *LocalXForm); + + Adapters.Add(MeshAdapter); + + } + } + + FPolygonSoup PolySoup(Adapters, VoxelSize); + openvdb::FloatGrid::Ptr SDFUnionVolume = openvdb::tools::meshToVolume(PolySoup, PolySoup.Transform(), ExteriorVoxelWidth, InteriorVoxelWidth); + + + // Convert the SDFUnionVolume to a mesh + + ConvertSDFToMesh(SDFUnionVolume, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + + FVector ComputeDifference(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override + { + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA; + openvdb::FloatGrid::Ptr SDFVolumeB; + + FVector AverageTranslation = GenerateVolumes(IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB); + + // create the difference - result stored in volume A + openvdb::tools::csgDifference(*SDFVolumeA, *SDFVolumeB); + + // convert the result + ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + + FVector ComputeIntersection(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override + { + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA; + openvdb::FloatGrid::Ptr SDFVolumeB; + + FVector AverageTranslation = GenerateVolumes(IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB); + + + // create the difference - result stored in volume A + openvdb::tools::csgIntersection(*SDFVolumeA, *SDFVolumeB); + + // convert the result + ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + + FVector ComputeUnion(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override + { + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA; + openvdb::FloatGrid::Ptr SDFVolumeB; + + FVector AverageTranslation = GenerateVolumes(IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB); + + + // create the difference - result stored in volume A + openvdb::tools::csgUnion(*SDFVolumeA, *SDFVolumeB); + + // convert the result + ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh); + + return AverageTranslation; + } + +private: + + void ConvertSDFToMesh(openvdb::FloatGrid::Ptr SDFVolume, double Adaptivity, double IsoSurface, FMeshDescription& ResultMesh) const + { + // Convert the SDFUnionVolume to a mesh + FAOSMesh AOSMeshedVolume; + ProxyLOD::ExtractIsosurfaceWithNormals(SDFVolume, IsoSurface, Adaptivity, AOSMeshedVolume); // QUestion should IsoSurface be worldspace? + + + // Evidently this conversion code makes certain assumptions about the FMeshDescription. that were introduced when it was converted from FRawMesh... + + ProxyLOD::ConvertMesh(AOSMeshedVolume, ResultMesh); + } + + FVector GenerateVolumes(const double IsoSurface, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, openvdb::FloatGrid::Ptr& VolumeA, openvdb::FloatGrid::Ptr& VolumeB) const + { + const FMeshDescription& MeshA = *PlacedMeshA.Mesh; + const FMeshDescription& MeshB = *PlacedMeshB.Mesh; + + const double VoxelSize = GetVoxelSize(); + + const double IsoSurfaceInVoxels = IsoSurface / VoxelSize; + + + const double ExteriorVoxelWidth = FMath::Max(2., IsoSurfaceInVoxels + 1.); + const double InteriorVoxelWidth = -FMath::Min(-2., IsoSurfaceInVoxels - 1); + + // The average translation of the two meshes. + FVector AverageTranslation = PlacedMeshA.Transform.GetTranslation() + PlacedMeshB.Transform.GetTranslation(); + AverageTranslation *= 0.5f; + + // Fill the SDFUnionVolume + FMatrix LocalToVoxel = FMatrix::Identity; + LocalToVoxel.M[0][0] = VoxelSize; + LocalToVoxel.M[1][1] = VoxelSize; + LocalToVoxel.M[2][2] = VoxelSize; + + auto TransformGenerator = [&LocalToVoxel, &AverageTranslation](const FPlacedMesh& PlacedMesh)->openvdb::Mat4R + { + FTransform XForm = PlacedMesh.Transform; + XForm.AddToTranslation(-AverageTranslation); + FMatrix TransformMatrix = XForm.ToMatrixWithScale().Inverse(); + + TransformMatrix = LocalToVoxel * TransformMatrix; + float* data = &TransformMatrix.M[0][0]; + openvdb::math::Mat4 VDBMatFloat(data); + return openvdb::Mat4R(VDBMatFloat); + }; + + openvdb::Mat4R XFormA = TransformGenerator(PlacedMeshA); + OpenVDBTransform::Ptr VDBXFormA = OpenVDBTransform::createLinearTransform(XFormA); + + openvdb::Mat4R XFormB = TransformGenerator(PlacedMeshB); + OpenVDBTransform::Ptr VDBXFormB = OpenVDBTransform::createLinearTransform(XFormB); + + // Create adapters that understand the openVDB semantics. + FMeshDescriptionAdapter AdapterA(MeshA, *VDBXFormA); + FMeshDescriptionAdapter AdapterB(MeshB, *VDBXFormB); + + // target transform + OpenVDBTransform::Ptr TargetXForm = OpenVDBTransform::createLinearTransform(VoxelSize); + + // make SDFs + openvdb::FloatGrid::Ptr SDFVolumeA = openvdb::tools::meshToVolume(AdapterA, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth); + openvdb::FloatGrid::Ptr SDFVolumeB = openvdb::tools::meshToVolume(AdapterB, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth); + + VolumeA = SDFVolumeA; + VolumeB = SDFVolumeB; + + return AverageTranslation; + + } + + OpenVDBTransform::Ptr XForm; +}; + +TUniquePtr IVoxelBasedCSG::CreateCSGTool(float VoxelSize) +{ + TUniquePtr CSGTool = MakeUnique(); + + if (CSGTool == nullptr) + { + return nullptr; + } + else + { + CSGTool->SetVoxelSize(VoxelSize); + } + + return CSGTool; +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODParameterization.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODParameterization.h new file mode 100644 index 000000000000..86caf2fe5763 --- /dev/null +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODParameterization.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +struct FMeshDescription; + + +class PROXYLODMESHREDUCTION_API IProxyLODParameterization +{ +public: + static TUniquePtr CreateTool(); + virtual ~IProxyLODParameterization(){} + virtual bool ParameterizeMeshDescription(FMeshDescription& MeshDescription, int32 Width, int32 Height, + float GutterSpace, float Stretch, int32 ChartNum, + bool bUseNormals, bool bRecomputeTangentSpace, bool bPrintDebugMessages) const = 0; +}; diff --git a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h index 27f9fa5f2127..03b4acf73952 100644 --- a/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h +++ b/Engine/Plugins/Experimental/ProxyLODPlugin/Source/ProxyLOD/Public/ProxyLODVolume.h @@ -56,3 +56,48 @@ public: */ virtual float QueryDistance(const FVector& Point) const = 0; }; + + + + +class PROXYLODMESHREDUCTION_API IVoxelBasedCSG +{ +public : + static TUniquePtr CreateCSGTool(float VoxelSize); + + virtual ~IVoxelBasedCSG() {} + + class FPlacedMesh + { + public: + FPlacedMesh() + : Mesh(nullptr) + {} + + FPlacedMesh(const FPlacedMesh& other) + : Mesh(other.Mesh) + , Transform(other.Transform) + {} + + FPlacedMesh& operator=(const FPlacedMesh& other) + { + Mesh = other.Mesh; + Transform = other.Transform; + return *this; + } + + const FMeshDescription* Mesh; + FTransform Transform; + }; + + virtual double GetVoxelSize() const = 0; + virtual void SetVoxelSize(double VolexSize) = 0; + + // Will destroy UVs and other attributes Returns the average location of the input meshes + virtual FVector ComputeUnion(const TArray& MeshArray, FMeshDescription& ResultMesh, double Adaptivity = 0.1, double IsoSurfcae = 0.) const = 0; + + // We could make this keep the UVs and other attributes from the AMesh.. + virtual FVector ComputeDifference(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const = 0; + virtual FVector ComputeIntersection(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const = 0; + virtual FVector ComputeUnion(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const = 0; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp index 778cfa350743..ef0367ce1ada 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.cpp @@ -1687,6 +1687,7 @@ void UPythonGeneratedClass::PostInitInstance(UObject* InObj) } } + void UPythonGeneratedClass::ReleasePythonResources() { PyType.Reset(); @@ -1696,6 +1697,12 @@ void UPythonGeneratedClass::ReleasePythonResources() PyMetaData = FPyWrapperObjectMetaData(); } +bool UPythonGeneratedClass::IsFunctionImplementedInScript(FName InFunctionName) const +{ + UFunction* Function = FindFunctionByName(InFunctionName); + return Function && Function->GetOuter() && Function->GetOuter()->IsA(UPythonGeneratedClass::StaticClass()); +} + UPythonGeneratedClass* UPythonGeneratedClass::GenerateClass(PyTypeObject* InPyType) { // Get the correct super class from the parent type in Python diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h index c40fb1eb9d4a..338ede363bf3 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/Source/PythonScriptPlugin/Private/PyWrapperObject.h @@ -196,6 +196,8 @@ public: //~ IPythonResourceOwner interface virtual void ReleasePythonResources() override; + virtual bool IsFunctionImplementedInScript(FName InFunctionName) const override; + /** Generate an Unreal class from the given Python type */ static UPythonGeneratedClass* GenerateClass(PyTypeObject* InPyType); diff --git a/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp b/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp index 5e0cb37a2943..4313d0bdec29 100644 --- a/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp +++ b/Engine/Plugins/Experimental/RemoteSession/Source/RemoteSession/Private/MessageHandler/RecordingMessageHandler.cpp @@ -134,7 +134,7 @@ FVector2D FRecordingMessageHandler::ConvertFromNormalizedScreenLocation(const FV FWidgetPath WidgetPath(GameWindow.ToSharedRef(), JustWindow); if (WidgetPath.ExtendPathTo(FWidgetMatcher(ViewportWidget.ToSharedRef()), EVisibility::Visible)) { - FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(ViewportWidget.ToSharedRef()).Get(FArrangedWidget::NullWidget); + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(ViewportWidget.ToSharedRef()).Get(FArrangedWidget::GetNullWidget()); FVector2D WindowClientOffset = ArrangedWidget.Geometry.GetAbsolutePosition(); FVector2D WindowClientSize = ArrangedWidget.Geometry.GetAbsoluteSize(); diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/SampleToolsEditorMode.uplugin b/Engine/Plugins/Experimental/SampleToolsEditorMode/SampleToolsEditorMode.uplugin new file mode 100644 index 000000000000..b8783284a86f --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/SampleToolsEditorMode.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.1", + "FriendlyName": "Sample Tools Mode", + "Description": "Adds Sample Tools Editor Mode Tab", + "Category": "Other", + "CreatedBy": "Epic Games, Inc.", + "CreatedByURL": "http://epicgames.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "SampleToolsEditorMode", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [] +} \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.cpp new file mode 100644 index 000000000000..de44fb0cf9cd --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.cpp @@ -0,0 +1,114 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CreateActorSampleTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "CollisionQueryParams.h" +#include "Engine/World.h" + +// localization namespace +#define LOCTEXT_NAMESPACE "UCreateActorSampleTool" + +/* + * ToolBuilder + */ + + +bool UCreateActorSampleToolBuilder::CanBuildTool(const FToolBuilderState & SceneState) const +{ + return (this->AssetAPI != nullptr); +} + +UInteractiveTool* UCreateActorSampleToolBuilder::BuildTool(const FToolBuilderState & SceneState) const +{ + UCreateActorSampleTool* NewTool = NewObject(SceneState.ToolManager); + NewTool->SetWorld(SceneState.World); + NewTool->SetAssetAPI(AssetAPI); + return NewTool; +} + + + +/* + * Tool + */ + + +UCreateActorSampleToolProperties::UCreateActorSampleToolProperties() +{ + PlaceOnObjects = true; + GroundHeight = 0.0f; +} + + +UCreateActorSampleTool::UCreateActorSampleTool() +{ +} + + +void UCreateActorSampleTool::SetWorld(UWorld* World) +{ + this->TargetWorld = World; +} + +void UCreateActorSampleTool::SetAssetAPI(IToolsContextAssetAPI* AssetAPIIn) +{ + this->AssetAPI = AssetAPIIn; +} + + +void UCreateActorSampleTool::Setup() +{ + USingleClickTool::Setup(); + + Properties = NewObject(this); + AddToolPropertySource(Properties); +} + + + + +void UCreateActorSampleTool::OnClicked(const FInputDeviceRay& ClickPos) +{ + // we will create actor at this position + FVector NewActorPos = FVector::ZeroVector; + + // cast ray into world to find hit position + FVector RayStart = ClickPos.WorldRay.Origin; + FVector RayEnd = ClickPos.WorldRay.PointAt(999999); + FCollisionObjectQueryParams QueryParams(FCollisionObjectQueryParams::AllObjects); + FHitResult Result; + bool bHitWorld = TargetWorld->LineTraceSingleByObjectType(Result, RayStart, RayEnd, QueryParams); + if (Properties->PlaceOnObjects && bHitWorld) + { + NewActorPos = Result.ImpactPoint; + } + else + { + // hit the ground plane + FPlane GroundPlane(FVector(0,0,Properties->GroundHeight) , FVector(0, 0, 1)); + NewActorPos = FMath::RayPlaneIntersection(ClickPos.WorldRay.Origin, ClickPos.WorldRay.Direction, GroundPlane); + } + + // create new actor + FRotator Rotation(0.0f, 0.0f, 0.0f); + FActorSpawnParameters SpawnInfo; + AActor* NewActor = TargetWorld->SpawnActor(FVector::ZeroVector, Rotation, SpawnInfo); + + // create root component + USceneComponent* RootComponent = NewObject(NewActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); + RootComponent->Mobility = EComponentMobility::Movable; + RootComponent->bVisualizeComponent = true; + RootComponent->SetWorldTransform(FTransform(NewActorPos)); + + NewActor->SetRootComponent(RootComponent); + NewActor->AddInstanceComponent(RootComponent); + RootComponent->RegisterComponent(); + +} + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.h new file mode 100644 index 000000000000..d06ac0f88c8e --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/CreateActorSampleTool.h @@ -0,0 +1,89 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "InteractiveToolBuilder.h" +#include "BaseTools/SingleClickTool.h" +#include "CreateActorSampleTool.generated.h" + + +/** + * Builder for UCreateActorSampleTool + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UCreateActorSampleToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + IToolsContextAssetAPI* AssetAPI; + + UCreateActorSampleToolBuilder() + { + AssetAPI = nullptr; + } + + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + + +/** + * Settings UObject for UCreateActorSampleTool. This UClass inherits from UInteractiveToolPropertySet, + * which provides an OnModified delegate that the Tool will listen to for changes in property values. + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UCreateActorSampleToolProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UCreateActorSampleToolProperties(); + + /** Place actors on existing objects */ + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Place On Objects")) + bool PlaceOnObjects; + + /** Height of ground plane */ + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Ground Height", UIMin = "-1000.0", UIMax = "1000.0", ClampMin = "-1000000", ClampMax = "1000000.0")) + float GroundHeight; +}; + + + + +/** + * UCreateActorSampleTool is an example Tool that drops an empty Actor at each position the user + * clicks left mouse button. The Actors are placed at the first ray intersection in the scene, + * or on a ground plane if no scene objects are hit. All the action is in the ::OnClicked handler. + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UCreateActorSampleTool : public USingleClickTool +{ + GENERATED_BODY() + +public: + UCreateActorSampleTool(); + + virtual void SetWorld(UWorld* World); + virtual void SetAssetAPI(IToolsContextAssetAPI* AssetAPI); + + virtual void Setup() override; + + virtual void OnClicked(const FInputDeviceRay& ClickPos); + + +protected: + UPROPERTY() + UCreateActorSampleToolProperties* Properties; + + +protected: + /** target World we will raycast into and create Actor in */ + UWorld* TargetWorld; + + /** Access to the ToolContext's Asset Creation API. This is not currently used, but can be used to (eg) add Components, etc*/ + IToolsContextAssetAPI* AssetAPI; +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.cpp new file mode 100644 index 000000000000..cb34f2aca202 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.cpp @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "DrawCurveOnMeshSampleTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "SceneManagement.h" // FPrimitiveDrawInterface + +// localization namespace +#define LOCTEXT_NAMESPACE "UDrawCurveOnMeshSampleTool" + +/* + * ToolBuilder + */ + + +UMeshSurfacePointTool* UDrawCurveOnMeshSampleToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const +{ + UDrawCurveOnMeshSampleTool* NewTool = NewObject(SceneState.ToolManager); + return NewTool; +} + + + +/* + * Tool + */ + +UDrawCurveOnMeshSampleToolProperties::UDrawCurveOnMeshSampleToolProperties() +{ + Thickness = 4.0f; + DepthBias = 0.0f; + MinSpacing = 1.0; + NormalOffset = 0.25; + Color = FLinearColor(255, 0, 0); + bScreenSpace = true; +} + + +UDrawCurveOnMeshSampleTool::UDrawCurveOnMeshSampleTool() +{ +} + + +void UDrawCurveOnMeshSampleTool::Setup() +{ + UMeshSurfacePointTool::Setup(); + + // add settings object + Settings = NewObject(this, TEXT("Settings")); + AddToolPropertySource(Settings); +} + + + +void UDrawCurveOnMeshSampleTool::Render(IToolsContextRenderAPI* RenderAPI) +{ + FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface(); + + int NumPts = Positions.Num(); + for (int i = 0; i < NumPts - 1; ++i) + { + FVector A = Positions[i] + Settings->NormalOffset*Normals[i]; + FVector B = Positions[i+1] + Settings->NormalOffset*Normals[i+1]; + PDI->DrawLine(A, B, Settings->Color, 0, Settings->Thickness, Settings->DepthBias, Settings->bScreenSpace); + } +} + + + + +void UDrawCurveOnMeshSampleTool::OnBeginDrag(const FRay& Ray) +{ + Positions.Reset(); + Normals.Reset(); + + FHitResult OutHit; + if (HitTest(Ray, OutHit)) + { + Positions.Add(OutHit.ImpactPoint); + Normals.Add(OutHit.ImpactNormal); + } + +} + +void UDrawCurveOnMeshSampleTool::OnUpdateDrag(const FRay& Ray) +{ + FHitResult OutHit; + if (HitTest(Ray, OutHit)) + { + if ( FVector::Dist(OutHit.ImpactPoint, Positions[Positions.Num()-1]) > Settings->MinSpacing) + { + Positions.Add(OutHit.ImpactPoint); + Normals.Add(OutHit.ImpactNormal); + } + } +} + +void UDrawCurveOnMeshSampleTool::OnEndDrag(const FRay& Ray) +{ + +} + + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.h new file mode 100644 index 000000000000..a5dfd58723bb --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/DrawCurveOnMeshSampleTool.h @@ -0,0 +1,91 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "InteractiveToolBuilder.h" +#include "BaseTools/MeshSurfacePointTool.h" +#include "DrawCurveOnMeshSampleTool.generated.h" + + +/** + * UMeshSurfacePointToolBuilder override for UDrawCurveOnMeshSampleTool + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UDrawCurveOnMeshSampleToolBuilder : public UMeshSurfacePointToolBuilder +{ + GENERATED_BODY() + +public: + virtual UMeshSurfacePointTool* CreateNewTool(const FToolBuilderState& SceneState) const override; +}; + + +/** + * Settings UObject for UDrawCurveOnMeshSampleTool. This UClass inherits from UInteractiveToolPropertySet, + * which provides an OnModified delegate that the Tool will listen to for changes in property values. + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UDrawCurveOnMeshSampleToolProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() +public: + UDrawCurveOnMeshSampleToolProperties(); + + UPROPERTY(EditAnywhere, Category = Options) + FLinearColor Color; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Thickness", UIMin = "0.25", UIMax = "10.0", ClampMin = "0.01", ClampMax = "1000.0")) + float Thickness; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Min Spacing", UIMin = "0.01", UIMax = "10.0")) + float MinSpacing; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Offset", UIMin = "0.0", UIMax = "10.0", ClampMin = "-1000.0", ClampMax = "1000.0")) + float NormalOffset; + + UPROPERTY(EditAnywhere, Category = Options, meta = (DisplayName = "Depth Bias", UIMin = "-10.0", UIMax = "10.0")) + float DepthBias; + + UPROPERTY(EditAnywhere, Category = Options) + bool bScreenSpace; +}; + + + +/** + * UDrawCurveOnMeshSampleTool is a sample Tool that allows the user to draw curves on the surface of + * a selected Mesh Component. The various rendering properties of the polycurve are exposed and can be tweaked. + * Nothing is done with the curve, it is just drawn by ::Render() and discarded when the Tool exits. + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UDrawCurveOnMeshSampleTool : public UMeshSurfacePointTool +{ + GENERATED_BODY() + +public: + UDrawCurveOnMeshSampleTool(); + + // UInteractiveTool API + + virtual void Setup() override; + + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + + // UMeshSurfacePointTool API + virtual void OnBeginDrag(const FRay& Ray) override; + virtual void OnUpdateDrag(const FRay& Ray) override; + virtual void OnEndDrag(const FRay& Ray) override; + +protected: + UPROPERTY() + UDrawCurveOnMeshSampleToolProperties* Settings; + + UPROPERTY() + TArray Positions; + + UPROPERTY() + TArray Normals; + +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.cpp new file mode 100644 index 000000000000..491f78a288f8 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.cpp @@ -0,0 +1,167 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MeasureDistanceSampleTool.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" +#include "BaseBehaviors/ClickDragBehavior.h" + +// for raycast into World +#include "CollisionQueryParams.h" +#include "Engine/World.h" + +#include "SceneManagement.h" + +// localization namespace +#define LOCTEXT_NAMESPACE "UMeasureDistanceSampleTool" + +/* + * ToolBuilder + */ + + +bool UMeasureDistanceSampleToolBuilder::CanBuildTool(const FToolBuilderState & SceneState) const +{ + return true; +} + +UInteractiveTool* UMeasureDistanceSampleToolBuilder::BuildTool(const FToolBuilderState & SceneState) const +{ + UMeasureDistanceSampleTool* NewTool = NewObject(SceneState.ToolManager); + NewTool->SetWorld(SceneState.World); + return NewTool; +} + + + +/* + * Tool + */ + +UMeasureDistanceProperties::UMeasureDistanceProperties() +{ + // initialize the points and distance to reasonable values + StartPoint = FVector(0,0,0); + EndPoint = FVector(0,0,100); + Distance = 100; +} + + +UMeasureDistanceSampleTool::UMeasureDistanceSampleTool() +{ +} + + +void UMeasureDistanceSampleTool::SetWorld(UWorld* World) +{ + check(World); + this->TargetWorld = World; +} + + + + +void UMeasureDistanceSampleTool::Setup() +{ + UInteractiveTool::Setup(); + + // Add default mouse input behavior + UClickDragInputBehavior* MouseBehavior = NewObject(); + // We will use the shift key to indicate that we should move the second point. + // This call tells the Behavior to call our OnUpdateModifierState() function on mouse-down and mouse-move + MouseBehavior->Modifiers.RegisterModifier(MoveSecondPointModifierID, FInputDeviceState::IsShiftKeyDown); + MouseBehavior->Initialize(this); + AddInputBehavior(MouseBehavior); + + // Create the property set and register it with the Tool + Properties = NewObject(this, "Measurement"); + AddToolPropertySource(Properties); + + bSecondPointModifierDown = false; + bMoveSecondPoint = false; +} + + +void UMeasureDistanceSampleTool::OnUpdateModifierState(int ModifierID, bool bIsOn) +{ + // keep track of the "second point" modifier (shift key for mouse input) + if (ModifierID == MoveSecondPointModifierID) + { + bSecondPointModifierDown = bIsOn; + } +} + + + +bool UMeasureDistanceSampleTool::CanBeginClickDragSequence(const FInputDeviceRay& PressPos) +{ + // we only start drag if press-down is on top of something we can raycast + FVector Temp; + return FindRayHit(PressPos.WorldRay, Temp); +} + +void UMeasureDistanceSampleTool::OnClickPress(const FInputDeviceRay& PressPos) +{ + // determine whether we are moving first or second point for the drag sequence + bMoveSecondPoint = bSecondPointModifierDown; + UpdatePosition(PressPos.WorldRay); +} + +void UMeasureDistanceSampleTool::OnClickDrag(const FInputDeviceRay& DragPos) +{ + UpdatePosition(DragPos.WorldRay); +} + + + +bool UMeasureDistanceSampleTool::FindRayHit(const FRay& WorldRay, FVector& HitPos) +{ + // trace a ray into the World + FCollisionObjectQueryParams QueryParams(FCollisionObjectQueryParams::AllObjects); + FHitResult Result; + bool bHitWorld = TargetWorld->LineTraceSingleByObjectType(Result, WorldRay.Origin, WorldRay.PointAt(999999), QueryParams); + if (bHitWorld) + { + HitPos = Result.ImpactPoint; + return true; + } + return false; +} + + +void UMeasureDistanceSampleTool::UpdatePosition(const FRay& WorldRay) +{ + if (FindRayHit(WorldRay, (bMoveSecondPoint) ? Properties->EndPoint : Properties->StartPoint)) + { + UpdateDistance(); + } +} + + +void UMeasureDistanceSampleTool::UpdateDistance() +{ + Properties->Distance = FVector::Distance(Properties->StartPoint, Properties->EndPoint); +} + + +void UMeasureDistanceSampleTool::OnPropertyModified(UObject* PropertySet, UProperty* Property) +{ + // if the user updated any of the property fields, update the distance + UpdateDistance(); +} + + + +void UMeasureDistanceSampleTool::Render(IToolsContextRenderAPI* RenderAPI) +{ + FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface(); + // draw a thin line that shows through objects + PDI->DrawLine(Properties->StartPoint, Properties->EndPoint, + FColor(240, 16, 16), SDPG_Foreground, 2.0f, 0.0f, true); + // draw a thicker line that is depth-tested + PDI->DrawLine(Properties->StartPoint, Properties->EndPoint, + FColor(240, 16, 16), SDPG_World, 4.0f, 0.0f, true); +} + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.h new file mode 100644 index 000000000000..311b49d1ff73 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleTools/MeasureDistanceSampleTool.h @@ -0,0 +1,102 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveToolBuilder.h" +#include "BaseTools/ClickDragTool.h" +#include "MeasureDistanceSampleTool.generated.h" + + +/** + * Builder for UMeasureDistanceSampleTool + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UMeasureDistanceSampleToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + +/** + * Property set for the UMeasureDistanceSampleTool + */ +UCLASS(Transient) +class SAMPLETOOLSEDITORMODE_API UMeasureDistanceProperties : public UInteractiveToolPropertySet +{ + GENERATED_BODY() + +public: + UMeasureDistanceProperties(); + + /** First point of measurement */ + UPROPERTY(EditAnywhere, Category = Options) + FVector StartPoint; + + /** Second point of measurement */ + UPROPERTY(EditAnywhere, Category = Options) + FVector EndPoint; + + /** Current distance measurement */ + UPROPERTY(EditAnywhere, Category = Options) + float Distance; +}; + + + +/** + * UMeasureDistanceSampleTool is an example Tool that allows the user to measure the + * distance between two points. The first point is set by click-dragging the mouse, and + * the second point is set by shift-click-dragging the mouse. + */ +UCLASS() +class SAMPLETOOLSEDITORMODE_API UMeasureDistanceSampleTool : public UInteractiveTool, public IClickDragBehaviorTarget +{ + GENERATED_BODY() + +public: + UMeasureDistanceSampleTool(); + + virtual void SetWorld(UWorld* World); + + // UInteractiveTool overrides + + virtual void Setup() override; + virtual void Render(IToolsContextRenderAPI* RenderAPI) override; + virtual void OnPropertyModified(UObject* PropertySet, UProperty* Property) override; + + // IClickDragBehaviorTarget implementation + + virtual bool CanBeginClickDragSequence(const FInputDeviceRay& PressPos) override; + virtual void OnClickPress(const FInputDeviceRay& PressPos) override; + virtual void OnClickDrag(const FInputDeviceRay& DragPos) override; + // these are not used in this Tool + virtual void OnClickRelease(const FInputDeviceRay& ReleasePos) override {} + virtual void OnTerminateDragSequence() override {} + + // IModifierToggleBehaviorTarget implementation (inherited via IClickDragBehaviorTarget) + + virtual void OnUpdateModifierState(int ModifierID, bool bIsOn) override; + + +protected: + /** Properties of the tool are stored here */ + UPROPERTY() + UMeasureDistanceProperties* Properties; + + +protected: + UWorld* TargetWorld = nullptr; // target World we will raycast into + + static const int MoveSecondPointModifierID = 1; // identifier we associate with the shift key + bool bSecondPointModifierDown = false; // flag we use to keep track of modifier state + bool bMoveSecondPoint = false; // flag we use to keep track of which point we are moving during a press-drag + + bool FindRayHit(const FRay& WorldRay, FVector& HitPos); // raycasts into World + void UpdatePosition(const FRay& WorldRay); // updates first or second point based on raycast + void UpdateDistance(); // updates distance +}; \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorMode.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorMode.cpp new file mode 100644 index 000000000000..4acbcc7c7ca1 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorMode.cpp @@ -0,0 +1,236 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SampleToolsEditorMode.h" +#include "SampleToolsEditorModeToolkit.h" +#include "Toolkits/ToolkitManager.h" +#include "EditorViewportClient.h" +#include "EditorModeManager.h" + + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// AddYourTool Step 1 - include the header file for your Tool here +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +#include "BaseTools/SingleClickTool.h" +#include "BaseTools/MeshSurfacePointTool.h" +#include "SampleTools/CreateActorSampleTool.h" +#include "SampleTools/DrawCurveOnMeshSampleTool.h" +#include "SampleTools/MeasureDistanceSampleTool.h" + +// step 2: register a ToolBuilder in FSampleToolsEditorMode::Enter() +// step 3: add a button in FSampleToolsEditorModeToolkit::Init() + + +#define LOCTEXT_NAMESPACE "FSampleToolsEditorMode" + +const FEditorModeID FSampleToolsEditorMode::EM_SampleToolsEditorModeId = TEXT("EM_SampleToolsEditorMode"); + + +FSampleToolsEditorMode::FSampleToolsEditorMode() +{ + ToolsContext = nullptr; +} + + +FSampleToolsEditorMode::~FSampleToolsEditorMode() +{ + // this should have happend already in ::Exit() + if (ToolsContext != nullptr) + { + ToolsContext->ShutdownContext(); + ToolsContext = nullptr; + } +} + + +void FSampleToolsEditorMode::ActorSelectionChangeNotify() +{ + // @todo support selection change +} + + + +void FSampleToolsEditorMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) +{ + FEdMode::Tick(ViewportClient, DeltaTime); + + // give ToolsContext a chance to tick + if (ToolsContext != nullptr) + { + ToolsContext->Tick(ViewportClient, DeltaTime); + } +} + + + + +void FSampleToolsEditorMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + FEdMode::Render(View, Viewport, PDI); + + // give ToolsContext a chance to render + if (ToolsContext != nullptr) + { + ToolsContext->Render(View, Viewport, PDI); + } +} + + + + + +// +// Input device event tracking. We forward input events to the ToolsContext adapter for handling. +// + +bool FSampleToolsEditorMode::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + bool bHandled = FEdMode::InputKey(ViewportClient, Viewport, Key, Event); + bHandled |= ToolsContext->InputKey(ViewportClient, Viewport, Key, Event); + return bHandled; +} + +bool FSampleToolsEditorMode::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) +{ + // mouse axes: EKeys::MouseX, EKeys::MouseY, EKeys::MouseWheelAxis + return FEdMode::InputAxis(InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime); +} + + +bool FSampleToolsEditorMode::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + bool bHandled = FEdMode::StartTracking(InViewportClient, InViewport); + bHandled |= ToolsContext->StartTracking(InViewportClient, InViewport); + return bHandled; +} + +bool FSampleToolsEditorMode::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) +{ + bool bHandled = ToolsContext->CapturedMouseMove(InViewportClient, InViewport, InMouseX, InMouseY); + return bHandled; +} + +bool FSampleToolsEditorMode::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + bool bHandled = ToolsContext->EndTracking(InViewportClient, InViewport); + return bHandled; +} + + + +bool FSampleToolsEditorMode::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ + bool bHandled = ToolsContext->MouseEnter(ViewportClient, Viewport, x, y); + return bHandled; +} + +bool FSampleToolsEditorMode::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ + bool bHandled = ToolsContext->MouseMove(ViewportClient, Viewport, x, y); + return bHandled; +} + +bool FSampleToolsEditorMode::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ + bool bHandled = ToolsContext->MouseLeave(ViewportClient, Viewport); + return bHandled; +} + + + +bool FSampleToolsEditorMode::ReceivedFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ + return FEdMode::ReceivedFocus(ViewportClient, Viewport); +} + + +bool FSampleToolsEditorMode::LostFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ + return FEdMode::LostFocus(ViewportClient, Viewport); +} + + + + + + +void FSampleToolsEditorMode::Enter() +{ + FEdMode::Enter(); + + if (!Toolkit.IsValid() && UsesToolkits()) + { + Toolkit = MakeShareable(new FSampleToolsEditorModeToolkit); + Toolkit->Init(Owner->GetToolkitHost()); + } + + // initialize the adapter that attaches the ToolsContext to this FEdMode + ToolsContext = NewObject(GetTransientPackage(), TEXT("ToolsContext"), RF_Transient); + ToolsContext->InitializeContextFromEdMode(this); + + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // AddYourTool Step 2 - register a ToolBuilder for your Tool here. + // The string name you pass to the ToolManager is used to select/activate your ToolBuilder later. + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + + auto CreateActorSampleToolBuilder = NewObject(); + CreateActorSampleToolBuilder->AssetAPI = ToolsContext->GetAssetAPI(); + ToolsContext->ToolManager->RegisterToolType(TEXT("CreateActorSampleTool"), CreateActorSampleToolBuilder); + + auto DrawCurveOnMeshSampleToolBuilder = NewObject(); + ToolsContext->ToolManager->RegisterToolType(TEXT("DrawCurveOnMeshSampleTool"), DrawCurveOnMeshSampleToolBuilder); + + auto MeasureDistanceSampleToolBuilder = NewObject(); + ToolsContext->ToolManager->RegisterToolType(TEXT("MeasureDistanceSampleTool"), MeasureDistanceSampleToolBuilder); + + auto SurfacePointToolBuilder = NewObject(); + ToolsContext->ToolManager->RegisterToolType(TEXT("SurfacePointTool"), SurfacePointToolBuilder); + + // active tool type is not relevant here, we just set to default + ToolsContext->ToolManager->SelectActiveToolType(EToolSide::Left, TEXT("SurfacePointTool")); +} + + + + +void FSampleToolsEditorMode::Exit() +{ + // shutdown and clean up the ToolsContext + ToolsContext->ShutdownContext(); + ToolsContext = nullptr; + + if (Toolkit.IsValid()) + { + FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef()); + Toolkit.Reset(); + } + + // Call base Exit method to ensure proper cleanup + FEdMode::Exit(); +} + +bool FSampleToolsEditorMode::UsesToolkits() const +{ + return true; +} + + +void FSampleToolsEditorMode::AddReferencedObjects(FReferenceCollector& Collector) +{ + Collector.AddReferencedObject(ToolsContext); +} + + + + + + + + + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeModule.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeModule.cpp new file mode 100644 index 000000000000..8403aa98d5c7 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeModule.cpp @@ -0,0 +1,23 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SampleToolsEditorModeModule.h" +#include "SampleToolsEditorMode.h" + +#define LOCTEXT_NAMESPACE "FSampleToolsEditorModeModule" + +void FSampleToolsEditorModeModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + FEditorModeRegistry::Get().RegisterMode(FSampleToolsEditorMode::EM_SampleToolsEditorModeId, LOCTEXT("SampleToolsEditorModeName", "SampleToolsEditorMode"), FSlateIcon(), true); +} + +void FSampleToolsEditorModeModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + FEditorModeRegistry::Get().UnregisterMode(FSampleToolsEditorMode::EM_SampleToolsEditorModeId); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FSampleToolsEditorModeModule, SampleToolsEditorMode) \ No newline at end of file diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeToolkit.cpp b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeToolkit.cpp new file mode 100644 index 000000000000..356f0027292b --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Private/SampleToolsEditorModeToolkit.cpp @@ -0,0 +1,222 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SampleToolsEditorModeToolkit.h" +#include "SampleToolsEditorMode.h" +#include "Engine/Selection.h" + +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#include "IDetailsView.h" + +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" + + + +#include "EditorModeManager.h" + +#define LOCTEXT_NAMESPACE "FSampleToolsEditorModeToolkit" + +FSampleToolsEditorModeToolkit::FSampleToolsEditorModeToolkit() +{ +} + +void FSampleToolsEditorModeToolkit::Init(const TSharedPtr& InitToolkitHost) +{ + + FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs( + /*bUpdateFromSelection=*/ false, + /*bLockable=*/ false, + /*bAllowSearch=*/ false, + FDetailsViewArgs::HideNameArea, + /*bHideSelectionTip=*/ true, + /*InNotifyHook=*/ nullptr, + /*InSearchInitialKeyFocus=*/ false, + /*InViewIdentifier=*/ NAME_None); + DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; + DetailsViewArgs.bShowOptions = false; + DetailsViewArgs.bAllowMultipleTopLevelObjects = true; + + DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); + + + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // AddYourTool Step 3 - add a button to initialize your Tool + ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + + + SAssignNew(ToolkitWidget, SBorder) + .HAlign(HAlign_Center) + .Padding(25) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(50) + [ + SNew(STextBlock) + .AutoWrapText(true) + .Text(LOCTEXT("HeaderLabel", "Sample Tools")) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("CreateActorSampleToolLabel", "Create Actor on Click")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("CreateActorSampleTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("CreateActorSampleTool")); }) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("MeasureDistanceSampleToolLabel", "Measure Distance")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("MeasureDistanceSampleTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("MeasureDistanceSampleTool")); }) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("DrawCurveOnMeshSampleToolLabel", "Draw Curve On Mesh")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("DrawCurveOnMeshSampleTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("DrawCurveOnMeshSampleTool")); }) + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton).Text(LOCTEXT("SurfacePointToolLabel", "Surface Point Tool")) + .OnClicked_Lambda([this]() { return this->StartTool(TEXT("SurfacePointTool")); }) + .IsEnabled_Lambda([this]() { return this->CanStartTool(TEXT("SurfacePointTool")); }) + ] + + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton).Text(LOCTEXT("AcceptToolButtonLabel", "Accept")) + .OnClicked_Lambda([this]() { return this->EndTool(EToolShutdownType::Accept); }) + .IsEnabled_Lambda([this]() { return this->CanAcceptActiveTool(); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton).Text(LOCTEXT("CancelToolButtonLabel", "Cancel")) + .OnClicked_Lambda([this]() { return this->EndTool(EToolShutdownType::Cancel); }) + .IsEnabled_Lambda([this]() { return this->CanCancelActiveTool(); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton).Text(LOCTEXT("CompletedToolButtonLabel", "Complete")) + .OnClicked_Lambda([this]() { return this->EndTool(EToolShutdownType::Completed); }) + .IsEnabled_Lambda([this]() { return this->CanCompleteActiveTool(); }) + ] + ] + + + SVerticalBox::Slot() + .HAlign(HAlign_Center) + .Padding(2) + .AutoHeight() + .MaxHeight(500.0f) + [ + DetailsView->AsShared() + ] + + ]; + + FModeToolkit::Init(InitToolkitHost); +} + + +bool FSampleToolsEditorModeToolkit::CanStartTool(const FString& ToolTypeIdentifier) +{ + UInteractiveToolManager* Manager = GetToolsEditorMode()->GetToolManager(); + + return (Manager->HasActiveTool(EToolSide::Left) == false) && + (Manager->CanActivateTool(EToolSide::Left, ToolTypeIdentifier) == true); +} + +bool FSampleToolsEditorModeToolkit::CanAcceptActiveTool() +{ + return GetToolsEditorMode()->GetToolManager()->CanAcceptActiveTool(EToolSide::Left); +} + +bool FSampleToolsEditorModeToolkit::CanCancelActiveTool() +{ + return GetToolsEditorMode()->GetToolManager()->CanCancelActiveTool(EToolSide::Left); +} + +bool FSampleToolsEditorModeToolkit::CanCompleteActiveTool() +{ + return GetToolsEditorMode()->GetToolManager()->HasActiveTool(EToolSide::Left) && CanCancelActiveTool() == false; +} + + +FReply FSampleToolsEditorModeToolkit::StartTool(const FString& ToolTypeIdentifier) +{ + if (GetToolsEditorMode()->GetToolManager()->SelectActiveToolType(EToolSide::Left, ToolTypeIdentifier) == false) + { + UE_LOG(LogTemp, Warning, TEXT("ToolManager: Unknown Tool Type %s"), *ToolTypeIdentifier); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("ToolManager: Starting Tool Type %s"), *ToolTypeIdentifier); + GetToolsEditorMode()->GetToolManager()->ActivateTool(EToolSide::Left); + + // Update properties panel + UInteractiveTool* CurTool = GetToolsEditorMode()->GetToolManager()->GetActiveTool(EToolSide::Left); + DetailsView->SetObjects(CurTool->GetToolProperties()); + } + return FReply::Handled(); +} + +FReply FSampleToolsEditorModeToolkit::EndTool(EToolShutdownType ShutdownType) +{ + UE_LOG(LogTemp, Warning, TEXT("ENDING TOOL")); + + GetToolsEditorMode()->GetToolManager()->DeactivateTool(EToolSide::Left, ShutdownType); + + DetailsView->SetObject(nullptr); + + return FReply::Handled(); +} + + +FName FSampleToolsEditorModeToolkit::GetToolkitFName() const +{ + return FName("SampleToolsEditorMode"); +} + +FText FSampleToolsEditorModeToolkit::GetBaseToolkitName() const +{ + return NSLOCTEXT("SampleToolsEditorModeToolkit", "DisplayName", "SampleToolsEditorMode Tool"); +} + +class FEdMode* FSampleToolsEditorModeToolkit::GetEditorMode() const +{ + return GLevelEditorModeTools().GetActiveMode(FSampleToolsEditorMode::EM_SampleToolsEditorModeId); +} + +FSampleToolsEditorMode* FSampleToolsEditorModeToolkit::GetToolsEditorMode() const +{ + return (FSampleToolsEditorMode*)GetEditorMode(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorMode.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorMode.h new file mode 100644 index 000000000000..5a7af362ac1d --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorMode.h @@ -0,0 +1,157 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdMode.h" + +#include "InputState.h" +#include "InteractiveToolManager.h" +#include "EdModeInteractiveToolsContext.h" + + +class FEditorComponentSourceFactory; +class FEditorToolAssetAPI; + + +/** + * This class provides an example of how to connect an FEdMode to the + * InteractiveTools framework. The various FEdMode input event handlers + * forward events to a UEdModeInteractiveToolsContext instance, which + * has all the logic for interacting with the InputRouter, ToolManager, etc. + * The functions here are basically just forwarders. + */ +class FSampleToolsEditorMode : public FEdMode +{ +public: + const static FEditorModeID EM_SampleToolsEditorModeId; +public: + FSampleToolsEditorMode(); + virtual ~FSampleToolsEditorMode(); + + //////////////// + // FEdMode interface + //////////////// + + virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override; + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; + virtual void ActorSelectionChangeNotify() override; + virtual bool UsesToolkits() const override; + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + // these disable the standard gizmo, which is probably want we want in + // these tools as we can't hit-test the standard gizmo... + virtual bool AllowWidgetMove() override { return false; } + virtual bool ShouldDrawWidget() const override { return false; } + virtual bool UsesTransformWidget() const override { return false; } + + + /* + * focus events + */ + + // called when we "start" this editor mode (ie switch to this tab) + virtual void Enter() override; + + // called when we "end" this editor mode (ie switch to another tab) + virtual void Exit() override; + + // called when viewport window is focused + virtual bool ReceivedFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) override; + + // called when viewport window loses focus (ie when some other window is focused) + // *not* called when editor is backgrounded, but is called when editor is minimized + virtual bool LostFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) override; + + + /* + * Mouse position events - These are called when no mouse button is down + */ + + // called when mouse moves over viewport window + virtual bool MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override; + + // called when mouse leaves viewport window + virtual bool MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) override; + + // called on any mouse-move event. *not* called during tracking/capturing, eg if any button is down + virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override; + + + /* + * Input Button/Axis Events & Mouse Capture + * + * event sequence for left mouse down/capture/up is: + * - InputKey( EKeys::LeftMouseButton, IE_PRESSED ) + * - StartTracking() + * - CapturedMouseMove() and InputAxis() (repeated for each mouse-move) + * - Inputkey( EKeys::LeftMouseButton, IE_RELEASED ) + * - EndTracking() + * + * for doubleclick, we get one of the above sequence, then a second + * where instead of the first IE_PRESSED we get a IE_DoubleClick + * + * for mouse wheel, we get following sequence + * - InputKey ( EKeys::MouseScrollUp / Down, IE_PRESSED ) + * - InputAxis() for wheel move + * - InputKey ( EKeys::MouseScrollUp / Down, IE_RELEASED ) + * it appears that there will only ever be *one* InputAxis() between the pressed/released sequenece + * + * Note that this wheel event sequence can happen *during* a + * middle-mouse tracking sequence on a wheel-button (nice!) + */ + + + // This is not just called for keyboard keys, it is also called for mouse down/up events! + // Eg for left-press we get Key = EKeys::LeftMouseButton and Event = IE_Pressed + // Return value indicates "handled'. If we return true for mouse press events then + // the StartTracking/CapturedMouseMove/EndTracking sequence is *not* called (but we + // also don't get MouseMove(), so the mouse-movement events appear to be lost?) + virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + + + // Called for 1D Axis movements - EKeys::MouseX, EKeys::MouseY, and EKeys::MouseWheelAxis + // Called even if we return true from InputKey which otherwise blocks tracking. + // Return value indicates "handled" but has no effect on CapturedMouseMove events (ie we still get them) + virtual bool InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime); + + + // called on mouse-down. return value indicates whether this was "handled" but + // does not mean we get exclusive capture events! + virtual bool StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; + + // called during mouse-down mouse-move. Always called, return value is used to indicate whether + // we "handled" this mouse move (see FEditorModeTools::CapturedMouseMove) but does not pre-empt + // other editor modes from seeing this move event... + virtual bool CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) override; + + // always called on mouse-up + virtual bool EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; + + + ////////////////// + // End of FEdMode interface + ////////////////// + + +public: + + /** + * @return active ToolManager + */ + virtual UInteractiveToolManager* GetToolManager() const + { + return ToolsContext->ToolManager; + } + +protected: + + /** + * This is the ToolsContext adapter that attaches to the FEdMode. + * We forward FEdMode event handler functions to this implementation. + * This UObject is explicitly kept alive by adding it to the Collector in ::AddReferencedObjects() + */ + UEdModeInteractiveToolsContext* ToolsContext; + +}; diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeModule.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeModule.h new file mode 100644 index 000000000000..8018f2a976f6 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FSampleToolsEditorModeModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeToolkit.h b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeToolkit.h new file mode 100644 index 000000000000..aaa422310a80 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/Public/SampleToolsEditorModeToolkit.h @@ -0,0 +1,46 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Toolkits/BaseToolkit.h" +#include "InteractiveTool.h" +#include "SampleToolsEditorMode.h" + +class IDetailsView; + +/** + * This FModeToolkit just creates a basic UI panel that allows various InteractiveTools to + * be initialized, and a DetailsView used to show properties of the active Tool. + */ +class FSampleToolsEditorModeToolkit : public FModeToolkit +{ +public: + FSampleToolsEditorModeToolkit(); + + // FModeToolkit interface + virtual void Init(const TSharedPtr& InitToolkitHost) override; + + // IToolkit interface + virtual FName GetToolkitFName() const override; + virtual FText GetBaseToolkitName() const override; + virtual class FEdMode* GetEditorMode() const override; + virtual TSharedPtr GetInlineContent() const override { return ToolkitWidget; } + + virtual FSampleToolsEditorMode* GetToolsEditorMode() const; + +private: + + TSharedPtr ToolkitWidget; + TSharedPtr DetailsView; + + // these functions just forward calls to the ToolsContext / ToolManager + + bool CanStartTool(const FString& ToolTypeIdentifier); + bool CanAcceptActiveTool(); + bool CanCancelActiveTool(); + bool CanCompleteActiveTool(); + + FReply StartTool(const FString& ToolTypeIdentifier); + FReply EndTool(EToolShutdownType ShutdownType); +}; diff --git a/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/SampleToolsEditorMode.Build.cs b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/SampleToolsEditorMode.Build.cs new file mode 100644 index 000000000000..775928f751d1 --- /dev/null +++ b/Engine/Plugins/Experimental/SampleToolsEditorMode/Source/SampleToolsEditorMode.Build.cs @@ -0,0 +1,60 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class SampleToolsEditorMode : ModuleRules +{ + public SampleToolsEditorMode(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + //"ContentBrowser" + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "UnrealEd", + "ContentBrowser", + "LevelEditor", + "InteractiveToolsFramework", + "EditorInteractiveToolsFramework" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp index 33df3791ba64..01fb905a89e5 100644 --- a/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp +++ b/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalMeshReductionPlugin.cpp @@ -1772,7 +1772,34 @@ void FQuadricSkeletalMeshReduction::ReduceSkeletalMesh(USkeletalMesh& SkeletalMe UE_LOG(LogSkeletalMeshReduction, Warning, TEXT("Building LOD %d - Invalid Base LOD entered. Using Base LOD 0 instead"), LODIndex); } } - + + //Store the sections flags + struct FSectionData + { + uint16 MaterialIndex; + bool bCastShadow; + bool bRecomputeTangent; + }; + + TMap BackupSectionIndexToSectionData; + //Store the section data. Store the source LOD model in case we add a LOD, or the target LOD model in case the LOD already exist + FSkeletalMeshLODModel& BackupSectionLODModel = bLODModelAdded ? SkeletalMeshResource.LODModels[Settings.BaseLOD] : SkeletalMeshResource.LODModels[LODIndex]; + BackupSectionIndexToSectionData.Reserve(BackupSectionLODModel.Sections.Num()); + + for (int32 SectionIndex = 0; SectionIndex < BackupSectionLODModel.Sections.Num(); ++SectionIndex) + { + //Skip disable and not generated section + int32 MaxGeneratedLODIndex = BackupSectionLODModel.Sections[SectionIndex].GenerateUpToLodIndex; + if (BackupSectionLODModel.Sections[SectionIndex].bDisabled || (MaxGeneratedLODIndex != -1 && MaxGeneratedLODIndex < LODIndex)) + { + continue; + } + FSectionData& SectionData = BackupSectionIndexToSectionData.FindOrAdd(SectionIndex); + SectionData.MaterialIndex = BackupSectionLODModel.Sections[SectionIndex].MaterialIndex; + SectionData.bCastShadow = BackupSectionLODModel.Sections[SectionIndex].bCastShadow; + SectionData.bRecomputeTangent = BackupSectionLODModel.Sections[SectionIndex].bRecomputeTangent; + } + auto FillClothingData = [&SkeletalMeshResource, &LODIndex, bLODModelAdded](int32 &EnableSectionNumber, TArray &SectionStatus) { EnableSectionNumber = 0; @@ -1961,6 +1988,31 @@ void FQuadricSkeletalMeshReduction::ReduceSkeletalMesh(USkeletalMesh& SkeletalMe // Flag this LOD as having been simplified. SkeletalMesh.GetLODInfo(LODIndex)->bHasBeenSimplified = true; SkeletalMesh.bHasBeenSimplified = true; + //Restore section data + FSkeletalMeshLODModel& ImportedModelLOD = SkeletalMesh.GetImportedModel()->LODModels[LODIndex]; + TArray SectionMatched; + SectionMatched.AddZeroed(ImportedModelLOD.Sections.Num()); + for (auto Kvp : BackupSectionIndexToSectionData) + { + const int32 SourceSectionIndex = Kvp.Key; + + const FSectionData& SectionData = Kvp.Value; + int32 MaxSectionIndex = FMath::Min(ImportedModelLOD.Sections.Num(), SourceSectionIndex+1); + for (int32 SectionIndex = 0; SectionIndex < MaxSectionIndex; ++SectionIndex) + { + if (SectionMatched[SectionIndex]) + { + continue; + } + if (SectionData.MaterialIndex == ImportedModelLOD.Sections[SectionIndex].MaterialIndex) + { + ImportedModelLOD.Sections[SectionIndex].bCastShadow = SectionData.bCastShadow; + ImportedModelLOD.Sections[SectionIndex].bRecomputeTangent = SectionData.bRecomputeTangent; + SectionMatched[SectionIndex] = true; + break; + } + } + } } else { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h index a542ae5110ba..2b2e7a3760bf 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptDerivedData.h @@ -54,7 +54,7 @@ public: // This is a version string that mimics the old versioning scheme. If you // want to bump this version, generate a new guid using VS->Tools->Create GUID and // return it here. Ex. - return TEXT("FB23DB34150247469E8B70EBE84A30C9"); + return TEXT("B575C44C549E4AE5A86049B662D2EFFA"); } virtual FString GetPluginSpecificCacheKeySuffix() const override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp index 78c6f9f74b5f..d562c5645f3c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraGeneratedCodeView.cpp @@ -476,14 +476,14 @@ void SNiagaraGeneratedCodeView::UpdateUI() { GeneratedCode[i].HorizontalScrollBar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); } if (!GeneratedCode[i].VerticalScrollBar.IsValid()) { GeneratedCode[i].VerticalScrollBar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); } if (!GeneratedCode[i].Container.IsValid()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp index 4c8edb2c977b..8c420c3e2a62 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp @@ -285,19 +285,19 @@ void SNiagaraSpreadsheetView::Construct(const FArguments& InArgs, TSharedRef >) .IsEnabled(this, &SNiagaraSpreadsheetView::IsPausedAtRightTimeOnRightHandle) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h index 9f3e7cf3eb19..84a9fc655dc3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Public/NiagaraShared.h @@ -513,7 +513,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnNiagaraScriptCompilationComplete); /** * FNiagaraShaderScript represents a Niagara script to the shader compilation process */ -class FNiagaraShaderScript +class NIAGARASHADER_VTABLE FNiagaraShaderScript { public: diff --git a/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp b/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp index baaab589efac..88ef94a4fba2 100644 --- a/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp +++ b/Engine/Plugins/Importers/USDImporter/Source/ThirdParty/USD/include/boost/config/compiler/visualc.hpp @@ -291,8 +291,14 @@ // last known and checked version is 19.00.23026 (VC++ 2015 RTM): #if (_MSC_VER > 1900) # if defined(BOOST_ASSERT_CONFIG) -# error "Unknown compiler version - please run the configure tests and report the results" -# else -# pragma message("Unknown compiler version - please run the configure tests and report the results") +# error "Boost.Config is older than your current compiler version." +# elif !defined(BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE) + // Disabled this warning, as newer versions of boost already disable the message + // + // Newer versions of boost have comment says: + // + // Disabled as of March 2018 - the pace of VS releases is hard to keep up with + // and in any case, we have relatively few defect macros defined now. + // BOOST_PRAGMA_MESSAGE("Info: Boost.Config is older than your compiler version - probably nothing bad will happen - but you may wish to look for an updated Boost version. Define BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE to suppress this message.") # endif #endif diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs index 2e0d4fe072d5..dc4bbbc69b1f 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/ImgMedia.Build.cs @@ -32,6 +32,7 @@ namespace UnrealBuildTool.Rules new string[] { "ImgMedia/Private", "ImgMedia/Private/Assets", + "ImgMedia/Private/GlobalCache", "ImgMedia/Private/Loader", "ImgMedia/Private/Player", "ImgMedia/Private/Readers", diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.cpp new file mode 100644 index 000000000000..6f8172295195 --- /dev/null +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.cpp @@ -0,0 +1,223 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ImgMediaGlobalCache.h" +#include "IImgMediaReader.h" +#include "ImgMediaPrivate.h" +#include "ImgMediaSettings.h" + + +FImgMediaGlobalCache::FImgMediaGlobalCache() + : LeastRecent(nullptr) + , MostRecent(nullptr) + , CurrentSize(0) + , MaxSize(0) +{ +} + +FImgMediaGlobalCache::~FImgMediaGlobalCache() +{ + Shutdown(); +} + +void FImgMediaGlobalCache::Initialize() +{ + auto Settings = GetDefault(); + MaxSize = Settings->GlobalCacheSizeGB * 1024 * 1024 * 1024; +} + +void FImgMediaGlobalCache::Shutdown() +{ + FScopeLock Lock(&CriticalSection); + Empty(); +} + +void FImgMediaGlobalCache::AddFrame(const FName& Sequence, int32 Index, const TSharedPtr& Frame) +{ + FScopeLock Lock(&CriticalSection); + + // Make sure we have enough space in the cache to add this new frame. + SIZE_T FrameSize = Frame->Info.UncompressedSize; + if (FrameSize <= MaxSize) + { + // Empty cache until we have enough space. + while (CurrentSize + FrameSize > MaxSize) + { + FName* RemoveSequencePtr = MapLeastRecentToSequence.Find(LeastRecent); + FName RemoveSequence = RemoveSequencePtr != nullptr ? *RemoveSequencePtr : FName(); + Remove(RemoveSequence, *LeastRecent); + } + + // Create new entry. + FImgMediaGlobalCacheEntry* NewEntry = new FImgMediaGlobalCacheEntry(Index, Frame); + MapFrameToEntry.Emplace(TPair(Sequence, Index), NewEntry); + + MarkAsRecent(Sequence, *NewEntry); + + CurrentSize += FrameSize; + } + else + { + UE_LOG(LogImgMedia, Warning, TEXT("Global cache size %d is smaller than frame size %d."), MaxSize, FrameSize); + } +} + +TSharedPtr* FImgMediaGlobalCache::FindAndTouch(const FName& Sequence, int32 Index) +{ + FScopeLock Lock(&CriticalSection); + + TSharedPtr* Frame = nullptr; + + FImgMediaGlobalCacheEntry** Entry = MapFrameToEntry.Find(TPair(Sequence, Index)); + + if ((Entry != nullptr) && (*Entry != nullptr)) + { + Frame = &((*Entry)->Frame); + + // Mark this as the most recent. + Unlink(Sequence, **Entry); + MarkAsRecent(Sequence, **Entry); + } + + return Frame; +} + +void FImgMediaGlobalCache::GetIndices(const FName& Sequence, TArray& OutIndices) const +{ + FScopeLock Lock(&CriticalSection); + + // Get most recent entry in this sequence. + FImgMediaGlobalCacheEntry* const* CurrentPtr = MapSequenceToMostRecentEntry.Find(Sequence); + const FImgMediaGlobalCacheEntry* Current = CurrentPtr != nullptr ? *CurrentPtr : nullptr; + + // Loop over all entries in the sequence. + while (Current != nullptr) + { + OutIndices.Add(Current->Index); + Current = Current->LessRecentSequence; + } +} + +void FImgMediaGlobalCache::Remove(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry) +{ + // Remove from cache. + Unlink(Sequence, Entry); + + // Update current cache size. + SIZE_T FrameSize = Entry.Frame->Info.UncompressedSize; + CurrentSize -= FrameSize; + + // Delete entry. + MapFrameToEntry.Remove(TPair(Sequence, Entry.Index)); + delete &Entry; +} + +void FImgMediaGlobalCache::MarkAsRecent(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry) +{ + // Mark most recent. + Entry.LessRecent = MostRecent; + if (MostRecent != nullptr) + { + MostRecent->MoreRecent = &Entry; + } + MostRecent = &Entry; + + // Mark most recent in sequence. + FImgMediaGlobalCacheEntry** SequenceMostRecentPtr = MapSequenceToMostRecentEntry.Find(Sequence); + FImgMediaGlobalCacheEntry* SequenceMostRecent = (SequenceMostRecentPtr != nullptr) ? (*SequenceMostRecentPtr) : nullptr; + Entry.LessRecentSequence = SequenceMostRecent; + if (SequenceMostRecent != nullptr) + { + SequenceMostRecent->MoreRecentSequence = &Entry; + } + else + { + // If we did not have a most recent one, then this is the first in the sequence. + MapLeastRecentToSequence.Emplace(&Entry, Sequence); + } + MapSequenceToMostRecentEntry.Emplace(Sequence, &Entry); + + // If LeastRecent is null, then set it now. + if (LeastRecent == nullptr) + { + LeastRecent = &Entry; + } +} + +void FImgMediaGlobalCache::Unlink(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry) +{ + // Remove from link. + if (Entry.LessRecent != nullptr) + { + Entry.LessRecent->MoreRecent = Entry.MoreRecent; + } + else if (LeastRecent == &Entry) + { + LeastRecent = Entry.MoreRecent; + } + + if (Entry.MoreRecent != nullptr) + { + Entry.MoreRecent->LessRecent = Entry.LessRecent; + } + else if (MostRecent == &Entry) + { + MostRecent = Entry.LessRecent; + } + + Entry.LessRecent = nullptr; + Entry.MoreRecent = nullptr; + + // Remove from sequence link. + if (Entry.LessRecentSequence != nullptr) + { + Entry.LessRecentSequence->MoreRecentSequence = Entry.MoreRecentSequence; + } + else + { + MapLeastRecentToSequence.Remove(&Entry); + if (Entry.MoreRecentSequence != nullptr) + { + MapLeastRecentToSequence.Emplace(Entry.MoreRecentSequence, Sequence); + } + } + + if (Entry.MoreRecentSequence != nullptr) + { + Entry.MoreRecentSequence->LessRecentSequence = Entry.LessRecentSequence; + } + else + { + // Update most recent in sequence. + FImgMediaGlobalCacheEntry** MostRecentSequence = MapSequenceToMostRecentEntry.Find(Sequence); + if (MostRecentSequence != nullptr) + { + if (*MostRecentSequence == &Entry) + { + MapSequenceToMostRecentEntry.Emplace(Sequence, Entry.LessRecentSequence); + } + } + } + + Entry.LessRecent = nullptr; + Entry.LessRecentSequence = nullptr; + Entry.MoreRecent = nullptr; + Entry.MoreRecentSequence = nullptr; +} + +void FImgMediaGlobalCache::Empty() +{ + while (LeastRecent != nullptr) + { + FImgMediaGlobalCacheEntry *Entry = LeastRecent; + LeastRecent = Entry->MoreRecent; + + delete Entry; + } + + MostRecent = nullptr; + CurrentSize = 0; + + MapSequenceToMostRecentEntry.Empty(); + MapLeastRecentToSequence.Empty(); + MapFrameToEntry.Empty(); +} diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.h new file mode 100644 index 000000000000..25077c1b121a --- /dev/null +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/GlobalCache/ImgMediaGlobalCache.h @@ -0,0 +1,145 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Containers/Map.h" +#include "Templates/SharedPointer.h" + +struct FImgMediaFrame; + +/** + * A global cache for all ImgMedia players. + * + * Uses Least Recently Used (LRU). + */ +class FImgMediaGlobalCache +{ +public: + /** + * Constructor. + */ + FImgMediaGlobalCache(); + + /** + * Desctructor. + */ + ~FImgMediaGlobalCache(); + + /** + * Initialize the cache. + */ + void Initialize(); + + /** + * Shut down the cache. + */ + void Shutdown(); + + /** + * Adds a frame to the cache. + * + * @param Sequence Indentifying name of this sequence. + * @param Index Index of frame to add. + * @param Frame Actual frame to add. + */ + void AddFrame(const FName& Sequence, int32 Index, const TSharedPtr& Frame); + + /** + * Find the entry with the specified sequence and index and mark it as the most recently used. + * + * @param Sequence Indentifying name of this sequence. + * @param Index Index of frame to touch. + */ + TSharedPtr* FindAndTouch(const FName& Sequence, int32 Index); + + /** + * Find the indices of all cached entries of a particular sequence. + * + * @param Sequence Indentifying name of this sequence. + * @param OutIndices Will contain the collection of indices. + */ + void GetIndices(const FName& Sequence, TArray& OutIndices) const; + +private: + + /** An entry in the cache. */ + struct FImgMediaGlobalCacheEntry + { + /** Frame index. */ + int32 Index; + /** Actual frame. */ + TSharedPtr Frame; + + /** Previous entry in the cache. */ + FImgMediaGlobalCacheEntry* LessRecent; + /** Previous entry in the cache that is part of the same sequence. */ + FImgMediaGlobalCacheEntry* LessRecentSequence; + /** Next entry in the cache. */ + FImgMediaGlobalCacheEntry* MoreRecent; + /** Next entry in the cache that is part of the same sequence. */ + FImgMediaGlobalCacheEntry* MoreRecentSequence; + + /** Constructor. */ + FImgMediaGlobalCacheEntry(int32 InIndex, const TSharedPtr& InFrame) + : Index(InIndex) + , Frame(InFrame) + , LessRecent(nullptr) + , LessRecentSequence(nullptr) + , MoreRecent(nullptr) + , MoreRecentSequence(nullptr) + { + } + }; + + /** Entry that was used first. */ + FImgMediaGlobalCacheEntry* LeastRecent; + /** Entry that was used last. */ + FImgMediaGlobalCacheEntry* MostRecent; + + /** Maps a sequence name to the most recent cache entry of that sequence. */ + TMap MapSequenceToMostRecentEntry; + /** Maps a cache entry that is the least recent entry to the name of its sequence. */ + TMap MapLeastRecentToSequence; + /** Maps a sequence name and frame index to an entry in the cache. */ + TMap, FImgMediaGlobalCacheEntry*> MapFrameToEntry; + + /** Current size of the cache in bytes. */ + SIZE_T CurrentSize; + /** Maximum size of the cache in bytes. */ + SIZE_T MaxSize; + + /** Critical section for synchronizing access to Frames. */ + mutable FCriticalSection CriticalSection; + + /** + * Removes an entry from the cache and deletes the entry. + * + * @param Sequence Indentifying name of this sequence. + * @param Entry Entry to remove. + */ + void Remove(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry); + + /** + * Inserts an entry into the cache as the most recent entry. + * Assumes the entry is not already in the cache. + * + * @param Sequence Indentifying name of this sequence. + * @param Entry Entry to mark as recent. + */ + void MarkAsRecent(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry); + + /** + * Removes an entry from the cache but does not delete the entry. + * + * @param Sequence Indentifying name of this sequence. + * @param Entry Entry to remove. + */ + void Unlink(const FName& Sequence, FImgMediaGlobalCacheEntry& Entry); + + /** + * Empties the cache. + */ + void Empty(); +}; + diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp index 3b9b779f7903..d23e7d336080 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/ImgMediaModule.cpp @@ -7,6 +7,7 @@ #include "Misc/QueuedThreadPool.h" #include "Modules/ModuleManager.h" +#include "ImgMediaGlobalCache.h" #include "ImgMediaPlayer.h" #include "ImgMediaScheduler.h" #include "IImgMediaModule.h" @@ -103,8 +104,12 @@ public: { InitScheduler(); } + if (!GlobalCache.IsValid()) + { + InitGlobalCache(); + } - return MakeShared(EventSink, Scheduler.ToSharedRef()); + return MakeShared(EventSink, Scheduler.ToSharedRef(), GlobalCache.ToSharedRef()); } public: @@ -119,6 +124,7 @@ public: virtual void ShutdownModule() override { Scheduler.Reset(); + GlobalCache.Reset(); #if USE_IMGMEDIA_DEALLOC_POOL ImgMediaThreadPool.Reset(); @@ -141,7 +147,15 @@ private: } } + void InitGlobalCache() + { + // Initialize global cache. + GlobalCache = MakeShared(); + GlobalCache->Initialize(); + } + TSharedPtr Scheduler; + TSharedPtr GlobalCache; }; diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp index b6dfb099fab5..b115bbbe6ab3 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.cpp @@ -1,6 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "ImgMediaLoader.h" +#include "ImgMediaGlobalCache.h" #include "ImgMediaPrivate.h" #include "Algo/Reverse.h" @@ -35,17 +36,20 @@ DECLARE_CYCLE_STAT(TEXT("ImgMedia Loader Release Cache"), STAT_ImgMedia_LoaderRe /* FImgMediaLoader structors *****************************************************************************/ -FImgMediaLoader::FImgMediaLoader(const TSharedRef& InScheduler) +FImgMediaLoader::FImgMediaLoader(const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache) : Frames(1) , ImageWrapperModule(FModuleManager::LoadModuleChecked("ImageWrapper")) , Initialized(false) , NumLoadAhead(0) , NumLoadBehind(0) , Scheduler(InScheduler) + , GlobalCache(InGlobalCache) , SequenceDim(FIntPoint::ZeroValue) , SequenceDuration(FTimespan::Zero()) , SequenceFrameRate(0, 0) , LastRequestedFrame(INDEX_NONE) + , UseGlobalCache(false) { UE_LOG(LogImgMedia, Verbose, TEXT("Loader %p: Created"), this); } @@ -95,7 +99,14 @@ void FImgMediaLoader::GetCompletedTimeRanges(TRangeSet& OutRangeSet) FScopeLock Lock(&CriticalSection); TArray CompletedFrames; - Frames.GetKeys(CompletedFrames); + if (UseGlobalCache) + { + GlobalCache->GetIndices(SequenceName, CompletedFrames); + } + else + { + Frames.GetKeys(CompletedFrames); + } FrameNumbersToTimeRanges(CompletedFrames, OutRangeSet); } @@ -111,7 +122,16 @@ TSharedPtr FImgMediaLoader::GetFram FScopeLock ScopeLock(&CriticalSection); - const TSharedPtr* Frame = Frames.FindAndTouch(FrameIndex); + const TSharedPtr* Frame; + if (UseGlobalCache) + { + Frame = GlobalCache->FindAndTouch(SequenceName, FrameIndex); + } + else + { + Frame = Frames.FindAndTouch(FrameIndex); + } + if (Frame == nullptr) { @@ -307,6 +327,7 @@ void FImgMediaLoader::LoadSequence(const FString& SequencePath, const FFrameRate // initialize loader auto Settings = GetDefault(); + UseGlobalCache = Settings->UseGlobalCache; const FPlatformMemoryStats Stats = FPlatformMemory::GetStats(); const SIZE_T DesiredCacheSize = Settings->CacheSizeGB * 1024 * 1024 * 1024; @@ -320,6 +341,9 @@ void FImgMediaLoader::LoadSequence(const FString& SequencePath, const FFrameRate NumLoadAhead = NumFramesToLoad - NumLoadBehind; Frames.Empty(NumFramesToLoad); + + SequenceName = FName(*SequencePath); + Update(0, 0.0f, Loop); // update info @@ -453,7 +477,17 @@ void FImgMediaLoader::Update(int32 PlayHeadFrame, float PlayRate, bool Loop) for (int32 FrameNumber : FramesToLoad) { - if ((Frames.FindAndTouch(FrameNumber) == nullptr) && !QueuedFrameNumbers.Contains(FrameNumber)) + bool NeedFrame = false; + if (UseGlobalCache) + { + NeedFrame = GlobalCache->FindAndTouch(SequenceName, FrameNumber) == nullptr; + } + else + { + NeedFrame = Frames.FindAndTouch(FrameNumber) == nullptr; + } + + if ((NeedFrame) && !QueuedFrameNumbers.Contains(FrameNumber)) { PendingFrameNumbers.Add(FrameNumber); } @@ -476,7 +510,14 @@ void FImgMediaLoader::NotifyWorkComplete(FImgMediaLoaderWork& CompletedWork, int if (Frame.IsValid()) { UE_LOG(LogImgMedia, VeryVerbose, TEXT("Loader %p: Loaded frame %i"), this, FrameNumber); - Frames.Add(FrameNumber, Frame); + if (UseGlobalCache) + { + GlobalCache->AddFrame(SequenceName, FrameNumber, Frame); + } + else + { + Frames.Add(FrameNumber, Frame); + } } } diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h index f9aedcdd744b..7d44280c04b2 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Loader/ImgMediaLoader.h @@ -8,6 +8,7 @@ #include "Misc/FrameRate.h" #include "Templates/SharedPointer.h" +class FImgMediaGlobalCache; class FImgMediaLoaderWork; class FImgMediaScheduler; class FImgMediaTextureSample; @@ -31,7 +32,8 @@ public: * * @param InScheduler The scheduler for image loading. */ - FImgMediaLoader(const TSharedRef& InScheduler); + FImgMediaLoader(const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache); /** Virtual destructor. */ virtual ~FImgMediaLoader(); @@ -270,6 +272,9 @@ private: /** The scheduler for image loading. */ TSharedPtr Scheduler; + /** The scheduler for image loading. */ + TSharedPtr GlobalCache; + /** Width and height of the image sequence (in pixels) .*/ FIntPoint SequenceDim; @@ -279,6 +284,9 @@ private: /** Frame rate of the currently loaded sequence. */ FFrameRate SequenceFrameRate; + /** Identifying name of sequence files. */ + FName SequenceName; + private: /** Index of the previously requested frame. */ @@ -292,4 +300,7 @@ private: /** Object pool for reusable work items. */ TArray WorkPool; + + /** True if we are using the global cache, false to use the local cache. */ + bool UseGlobalCache; }; diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp index 826b244991c2..28bd6eb5eb35 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.cpp @@ -32,7 +32,8 @@ const FTimespan HackDeltaTimeOffset(1); /* FImgMediaPlayer structors *****************************************************************************/ -FImgMediaPlayer::FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler) +FImgMediaPlayer::FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache) : CurrentDuration(FTimespan::Zero()) , CurrentRate(0.0f) , CurrentState(EMediaState::Closed) @@ -44,6 +45,7 @@ FImgMediaPlayer::FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef< , Scheduler(InScheduler) , SelectedVideoTrack(INDEX_NONE) , ShouldLoop(false) + , GlobalCache(InGlobalCache) { } @@ -179,7 +181,7 @@ bool FImgMediaPlayer::Open(const FString& Url, const IMediaOptions* Options) } // initialize image loader on a separate thread - Loader = MakeShared(Scheduler.ToSharedRef()); + Loader = MakeShared(Scheduler.ToSharedRef(), GlobalCache.ToSharedRef()); Scheduler->RegisterLoader(Loader.ToSharedRef()); const FString SequencePath = Url.RightChop(6); diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h index 318119eb3415..d24d848735d1 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMedia/Private/Player/ImgMediaPlayer.h @@ -15,6 +15,7 @@ class FImgMediaScheduler; class IImgMediaReader; class IMediaEventSink; class IMediaTextureSample; +class FImgMediaGlobalCache; /** @@ -36,7 +37,8 @@ public: * @param InEventSink The object that receives media events from this player. * @param InScheduler The image loading scheduler to use. */ - FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler); + FImgMediaPlayer(IMediaEventSink& InEventSink, const TSharedRef& InScheduler, + const TSharedRef& InGlobalCache); /** Virtual destructor. */ virtual ~FImgMediaPlayer(); @@ -153,4 +155,7 @@ private: /** Should the video loop to the beginning at completion */ bool ShouldLoop; + + /** The global cache to use. */ + TSharedPtr GlobalCache; }; diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp index 7e7abcc82594..8859d0342479 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Private/ImgMediaSettings.cpp @@ -12,6 +12,8 @@ UImgMediaSettings::UImgMediaSettings() , CacheSizeGB(1.0f) , CacheThreads(8) , CacheThreadStackSizeKB(128) + , GlobalCacheSizeGB(1.0f) + , UseGlobalCache(true) , ExrDecoderThreads(0) , DefaultProxy(TEXT("proxy")) , UseDefaultProxy(false) diff --git a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h index 110905299eb3..2524b343d57a 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h +++ b/Engine/Plugins/Media/ImgMedia/Source/ImgMediaFactory/Public/ImgMediaSettings.h @@ -44,6 +44,14 @@ public: UPROPERTY(config, EditAnywhere, Category=Caching, meta=(ClampMin=128), AdvancedDisplay) int32 CacheThreadStackSizeKB; + /** Maximum size of the global look-ahead cache (in GB; default = 1 GB). */ + UPROPERTY(config, EditAnywhere, Category = Caching, meta = (ClampMin = 0)) + float GlobalCacheSizeGB; + + /** Whether to use the global cache or not. */ + UPROPERTY(config, EditAnywhere, Category = Caching) + bool UseGlobalCache; + public: /** Number of worker threads to use when decoding EXR images (0 = auto). */ diff --git a/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp b/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp index cf10df17f311..8290cd9aff7a 100644 --- a/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp +++ b/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp @@ -5,6 +5,7 @@ #include "Containers/UnrealString.h" #include "Modules/ModuleManager.h" +PRAGMA_DEFAULT_VISIBILITY_START THIRD_PARTY_INCLUDES_START #include "ImathBox.h" #include "ImfHeader.h" @@ -12,6 +13,7 @@ THIRD_PARTY_INCLUDES_START #include "ImfCompressionAttribute.h" #include "ImfStandardAttributes.h" THIRD_PARTY_INCLUDES_END +PRAGMA_DEFAULT_VISIBILITY_END /* FOpenExr diff --git a/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl b/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl index 6ced19d1b188..6d198b0b6308 100644 --- a/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl +++ b/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl @@ -22,7 +22,8 @@ ItemType SMediaPermutationsSelector::GetSelectedItem() co template void SMediaPermutationsSelector::Construct(const FArguments& InArgs) { - SWidget::Construct(InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, InArgs._FlowDirectionPreference, InArgs.MetaData); + SWidget::Construct(InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, + InArgs._FlowDirectionPreference, InArgs._AccessibleParams, InArgs.MetaData); PermutationsSource = InArgs._PermutationsSource; SelectedPermutationIndex = INDEX_NONE; diff --git a/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp b/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp index 4a95a8090c18..4f0aaf131c99 100644 --- a/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp +++ b/Engine/Plugins/MovieScene/ActorSequence/Source/ActorSequenceEditor/Private/ActorSequenceComponentCustomization.cpp @@ -100,7 +100,7 @@ void FActorSequenceComponentCustomization::CustomizeDetails(IDetailLayoutBuilder bool bIsExternalTabAlreadyOpened = false; - if (HostTabManager.IsValid() && HostTabManager->CanSpawnTab(SequenceTabId)) + if (HostTabManager.IsValid() && HostTabManager->HasTabSpawner(SequenceTabId)) { WeakTabManager = HostTabManager; @@ -151,7 +151,7 @@ void FActorSequenceComponentCustomization::CustomizeDetails(IDetailLayoutBuilder FReply FActorSequenceComponentCustomization::InvokeSequencer() { TSharedPtr TabManager = WeakTabManager.Pin(); - if (TabManager.IsValid() && TabManager->CanSpawnTab(SequenceTabId)) + if (TabManager.IsValid() && TabManager->HasTabSpawner(SequenceTabId)) { TSharedRef Tab = TabManager->InvokeTab(SequenceTabId); diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h index 4bbbe9e3e8a8..3f4c896edcea 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Public/Party/PartyTypes.h @@ -273,8 +273,8 @@ public: UPROPERTY() bool bOnlyLeaderFriendsCanJoin = true; - bool operator==(const FPartyPrivacySettings& Other) const; - bool operator!=(const FPartyPrivacySettings& Other) const { return !operator==(Other); } + bool PARTY_API operator==(const FPartyPrivacySettings& Other) const; + bool PARTY_API operator!=(const FPartyPrivacySettings& Other) const { return !operator==(Other); } FPartyPrivacySettings() {} }; diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h index 824cb7fa0bfe..c1af1c941ac3 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystemModule.h @@ -144,7 +144,7 @@ public: * * @return true if the instance exists, false otherwise */ - bool DoesInstanceExist(const FName InSubsystemName) const; + ONLINESUBSYSTEM_API bool DoesInstanceExist(const FName InSubsystemName) const; /** * Determine if a subsystem is loaded by the OSS module @@ -182,7 +182,7 @@ public: * **NOTE** This is intended for editor use only, attempting to use this at the wrong time can result * in unexpected crashes/behavior */ - void ReloadDefaultSubsystem(); + void ONLINESUBSYSTEM_API ReloadDefaultSubsystem(); // IModuleInterface diff --git a/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h b/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h index 3871ef313f86..20c9e448dc8c 100644 --- a/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h +++ b/Engine/Plugins/Runtime/AudioCapture/Source/AudioCapture/Public/AudioCapture.h @@ -79,7 +79,7 @@ namespace Audio { public: FAudioCaptureSynth(); - virtual ~FAudioCaptureSynth(); + AUDIOCAPTURE_API virtual ~FAudioCaptureSynth(); // Gets the default capture device info bool GetDefaultCaptureDeviceInfo(FCaptureDeviceInfo& OutInfo); @@ -204,4 +204,4 @@ public: UFUNCTION(BlueprintCallable, Category = "Audio Capture") static class UAudioCapture* CreateAudioCapture(); -}; \ No newline at end of file +}; diff --git a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp index 43ae9823bf0c..8a4d9f6e6c2b 100644 --- a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp +++ b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMesh.cpp @@ -427,7 +427,7 @@ static void InvertRemapTable( TSparseArray& InvertedRemapTable, const TSp } -class FCompactChange : public FChange +class FCompactChange : public FSwapChange { public: @@ -459,7 +459,7 @@ struct FUncompactChangeInput }; -class FUncompactChange : public FChange +class FUncompactChange : public FSwapChange { public: @@ -2621,7 +2621,8 @@ void UEditableMesh::SetSubdivisionCount( const int32 NewSubdivisionCount ) void UEditableMesh::MoveVertices( const TArray& VerticesToMove ) { - EM_ENTER( TEXT( "MoveVertices: %s" ), *LogHelpers::ArrayToString( VerticesToMove ) ); + //EM_ENTER( TEXT( "MoveVertices: %s" ), *LogHelpers::ArrayToString( VerticesToMove ) ); + EM_ENTER(TEXT("MoveVertices: [redacted]")); static TSet< FPolygonID > VertexConnectedPolygons; VertexConnectedPolygons.Reset(); @@ -4415,7 +4416,8 @@ void UEditableMesh::DeletePolygonGroups( const TArray& PolygonG void UEditableMesh::SetVerticesAttributes( const TArray& AttributesForVertices ) { - EM_ENTER( TEXT( "SetVerticesAttributes: %s" ), *LogHelpers::ArrayToString( AttributesForVertices ) ); + //EM_ENTER( TEXT( "SetVerticesAttributes: %s" ), *LogHelpers::ArrayToString( AttributesForVertices ) ); + EM_ENTER(TEXT("SetVerticesAttributes: [redacted]")); FSetVerticesAttributesChangeInput RevertInput; diff --git a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h index 2cc55ff86c27..87a29c889992 100644 --- a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h +++ b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableMeshChanges.h @@ -22,7 +22,7 @@ struct FDeleteOrphanVerticesChangeInput }; -class FDeleteOrphanVerticesChange : public FChange +class FDeleteOrphanVerticesChange : public FSwapChange { public: @@ -67,7 +67,7 @@ struct FDeleteVertexInstancesChangeInput }; -class FDeleteVertexInstancesChange : public FChange +class FDeleteVertexInstancesChange : public FSwapChange { public: @@ -111,7 +111,7 @@ struct FDeleteEdgesChangeInput }; -class FDeleteEdgesChange : public FChange +class FDeleteEdgesChange : public FSwapChange { public: @@ -153,7 +153,7 @@ struct FCreateVerticesChangeInput }; -class FCreateVerticesChange : public FChange +class FCreateVerticesChange : public FSwapChange { public: @@ -195,7 +195,7 @@ struct FCreateVertexInstancesChangeInput }; -class FCreateVertexInstancesChange : public FChange +class FCreateVertexInstancesChange : public FSwapChange { public: @@ -237,7 +237,7 @@ struct FCreateEdgesChangeInput }; -class FCreateEdgesChange : public FChange +class FCreateEdgesChange : public FSwapChange { public: @@ -279,7 +279,7 @@ struct FCreatePolygonsChangeInput }; -class FCreatePolygonsChange : public FChange +class FCreatePolygonsChange : public FSwapChange { public: @@ -337,7 +337,7 @@ struct FDeletePolygonsChangeInput }; -class FDeletePolygonsChange : public FChange +class FDeletePolygonsChange : public FSwapChange { public: @@ -376,7 +376,7 @@ struct FFlipPolygonsChangeInput } }; -class FFlipPolygonsChange : public FChange +class FFlipPolygonsChange : public FSwapChange { public: FFlipPolygonsChange( const FFlipPolygonsChangeInput& InitInput ) @@ -400,7 +400,7 @@ struct FSetVerticesAttributesChangeInput }; -class FSetVerticesAttributesChange : public FChange +class FSetVerticesAttributesChange : public FSwapChange { public: @@ -435,7 +435,7 @@ struct FSetVertexInstancesAttributesChangeInput }; -class FSetVertexInstancesAttributesChange : public FChange +class FSetVertexInstancesAttributesChange : public FSwapChange { public: @@ -477,7 +477,7 @@ struct FSetEdgesAttributesChangeInput }; -class FSetEdgesAttributesChange : public FChange +class FSetEdgesAttributesChange : public FSwapChange { public: @@ -519,7 +519,7 @@ struct FSetPolygonsVertexAttributesChangeInput }; -class FSetPolygonsVertexAttributesChange : public FChange +class FSetPolygonsVertexAttributesChange : public FSwapChange { public: @@ -561,7 +561,7 @@ struct FChangePolygonsVertexInstancesChangeInput }; -class FChangePolygonsVertexInstancesChange : public FChange +class FChangePolygonsVertexInstancesChange : public FSwapChange { public: @@ -603,7 +603,7 @@ struct FSetEdgesVerticesChangeInput }; -class FSetEdgesVerticesChange : public FChange +class FSetEdgesVerticesChange : public FSwapChange { public: @@ -654,7 +654,7 @@ struct FInsertPolygonPerimeterVerticesChangeInput }; -class FInsertPolygonPerimeterVerticesChange : public FChange +class FInsertPolygonPerimeterVerticesChange : public FSwapChange { public: @@ -709,7 +709,7 @@ struct FRemovePolygonPerimeterVerticesChangeInput }; -class FRemovePolygonPerimeterVerticesChange : public FChange +class FRemovePolygonPerimeterVerticesChange : public FSwapChange { public: @@ -755,7 +755,7 @@ struct FStartOrEndModificationChangeInput }; -class FStartOrEndModificationChange : public FChange +class FStartOrEndModificationChange : public FSwapChange { public: @@ -792,7 +792,7 @@ struct FSetSubdivisionCountChangeInput }; -class FSetSubdivisionCountChange : public FChange +class FSetSubdivisionCountChange : public FSwapChange { public: @@ -829,7 +829,7 @@ struct FCreatePolygonGroupsChangeInput }; -class FCreatePolygonGroupsChange : public FChange +class FCreatePolygonGroupsChange : public FSwapChange { public: @@ -870,7 +870,7 @@ struct FDeletePolygonGroupsChangeInput }; -class FDeletePolygonGroupsChange : public FChange +class FDeletePolygonGroupsChange : public FSwapChange { public: @@ -915,7 +915,7 @@ struct FAssignPolygonsToPolygonGroupChangeInput }; -class FAssignPolygonsToPolygonGroupChange : public FChange +class FAssignPolygonsToPolygonGroupChange : public FSwapChange { public: diff --git a/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin b/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin index 0da68e6c1146..79bd9a97fcc6 100644 --- a/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin +++ b/Engine/Plugins/Runtime/GoogleVR/GoogleVRController/GoogleVRController.uplugin @@ -20,7 +20,7 @@ "Name": "GoogleVRController", "Type": "Runtime", "LoadingPhase": "PostConfigInit", - "WhitelistPlatforms": [ "Android", "Mac", "Win32", "Win64", "Linux" ] + "WhitelistPlatforms": [ "Android", "Mac", "Win32", "Win64" ] } ], "Plugins": [ diff --git a/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin b/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin index 3e6294f0a9ac..0e45e3a8eaee 100644 --- a/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin +++ b/Engine/Plugins/Runtime/GoogleVR/GoogleVRHMD/GoogleVRHMD.uplugin @@ -20,7 +20,7 @@ "Name": "GoogleVRHMD", "Type": "Runtime", "LoadingPhase": "PostConfigInit", - "WhitelistPlatforms": [ "Win64", "Win32", "Mac", "Linux", "Android" ] + "WhitelistPlatforms": [ "Win64", "Win32", "Mac", "Android" ] } ] } diff --git a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h index 269594bf99c9..64f52d0b34b1 100644 --- a/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h +++ b/Engine/Plugins/Runtime/ReplicationGraph/Source/Public/ReplicationGraphTypes.h @@ -31,7 +31,7 @@ DECLARE_LOG_CATEGORY_EXTERN( LogReplicationGraph, Log, All ); #define repCheck(x) check(x) #define repCheckf(expr, format, ...) checkf(expr, format, ##__VA_ARGS__ ) #define RG_QUICK_SCOPE_CYCLE_COUNTER(x) QUICK_SCOPE_CYCLE_COUNTER(x) - extern int32 CVar_RepGraph_Verify; + REPLICATIONGRAPH_API extern int32 CVar_RepGraph_Verify; #else #define REPGRAPH_DETAILS 0 #define DO_REPGRAPH_DETAILS(X) 0 @@ -1680,4 +1680,4 @@ struct FActorConnectionPair }; // Generic/global pair that can be set by debug commands etc for extra logging/debugging functionality -extern FActorConnectionPair DebugActorConnectionPair; \ No newline at end of file +extern FActorConnectionPair DebugActorConnectionPair; diff --git a/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp b/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp index 67a504a38179..e71f0eced14b 100644 --- a/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp +++ b/Engine/Plugins/Tests/RuntimeTests/Source/RuntimeTests/Private/Slate/RichTextMarkupProcessingTest.cpp @@ -2,7 +2,6 @@ #include "CoreMinimal.h" #include "Misc/AutomationTest.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/ITextDecorator.h" #include "Framework/Text/RichTextMarkupProcessing.h" diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp index 21c20255f6fd..3bdc076585a6 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_SkeletalMesh.cpp @@ -620,9 +620,9 @@ TSharedPtr FAssetTypeActions_SkeletalMesh::GetThumbnailOverlay(const FA return SNew(SBorder) .BorderImage(FEditorStyle::GetNoBrush()) .Visibility(this, &FAssetTypeActions_SkeletalMesh::GetThumbnailSkinningOverlayVisibility, AssetData) - .Padding(FMargin(0.0f, 3.0f, 3.0f, 0.0f)) + .Padding(FMargin(0.0f, 0.0f, 3.0f, 3.0f)) .HAlign(HAlign_Right) - .VAlign(VAlign_Top) + .VAlign(VAlign_Bottom) [ SNew(SImage) .ToolTipText(LOCTEXT("FAssetTypeActions_SkeletalMesh_NeedSkinning_ToolTip", "Asset geometry was imported, the skinning need to be validate")) @@ -662,7 +662,7 @@ void FAssetTypeActions_SkeletalMesh::GetLODMenu(class FMenuBuilder& MenuBuilder, int32 LODMax = SkeletalMesh->GetLODNum(); for(int32 LOD = 1; LOD <= LODMax; ++LOD) { - const FText Description = FText::Format( LOCTEXT("LODLevel", "LOD {0}"), FText::AsNumber( LOD ) ); + const FText Description = (LOD == LODMax) ? FText::Format(LOCTEXT("AddLODLevel", "Add LOD {0}"), FText::AsNumber(LOD)) : FText::Format( LOCTEXT("LODLevel", "Reimport LOD {0}"), FText::AsNumber( LOD ) ); const FText ToolTip = ( LOD == LODMax ) ? LOCTEXT("NewImportTip", "Import new LOD") : LOCTEXT("ReimportTip", "Reimport over existing LOD"); MenuBuilder.AddMenuEntry( Description, ToolTip, FSlateIcon(), diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp index 8be05042a0af..09874c4068b6 100644 --- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp +++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_StaticMesh.cpp @@ -111,7 +111,7 @@ void FAssetTypeActions_StaticMesh::GetImportLODMenu(class FMenuBuilder& MenuBuil FText ToolTip = NSLOCTEXT("AssetTypeActions_StaticMesh", "ReimportTip", "Reimport over existing LOD"); if(LOD == First->GetNumLODs()) { - Description = FText::Format( NSLOCTEXT("AssetTypeActions_StaticMesh", "LOD (number)", "LOD {0}"), LODText ); + Description = FText::Format( NSLOCTEXT("AssetTypeActions_StaticMesh", "LOD (number)", "Add LOD {0}"), LODText ); ToolTip = NSLOCTEXT("AssetTypeActions_StaticMesh", "NewImportTip", "Import new LOD"); } diff --git a/Engine/Source/Developer/Localization/Public/LocTextHelper.h b/Engine/Source/Developer/Localization/Public/LocTextHelper.h index 127112116087..463f0bd6b480 100644 --- a/Engine/Source/Developer/Localization/Public/LocTextHelper.h +++ b/Engine/Source/Developer/Localization/Public/LocTextHelper.h @@ -129,6 +129,10 @@ public: */ FString GetConflictReport() const; + /** Publicly movable */ + FLocTextConflicts(FLocTextConflicts&&) = default; + FLocTextConflicts& operator=(FLocTextConflicts&&) = default; + private: FLocTextConflicts(const FLocTextConflicts&) = delete; FLocTextConflicts& operator=(const FLocTextConflicts&) = delete; diff --git a/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp b/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp index adc9917d970a..81179b9455a3 100644 --- a/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp +++ b/Engine/Source/Developer/LogVisualizer/Private/SVisualLoggerView.cpp @@ -79,7 +79,7 @@ void SVisualLoggerView::Construct(const FArguments& InArgs, const TSharedRef ZoomScrollBar = SNew(SScrollBar) .Orientation(EOrientation::Orient_Horizontal) - .Thickness(FVector2D(2.0f, 2.0f)); + .Thickness(FVector2D(6.0f, 6.0f)); ZoomScrollBar->SetState(0.0f, 1.0f); FLogVisualizer::Get().GetTimeSliderController()->SetExternalScrollbar(ZoomScrollBar); @@ -90,7 +90,7 @@ void SVisualLoggerView::Construct(const FArguments& InArgs, const TSharedRef ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(2.0f, 2.0f)); + .Thickness(FVector2D(6.0f, 6.0f)); ULogVisualizerSettings* Settings = ULogVisualizerSettings::StaticClass()->GetDefaultObject(); diff --git a/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp b/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp index e7003d3a72ff..48f351075d3b 100644 --- a/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp +++ b/Engine/Source/Developer/MaterialBaking/Private/MaterialOptionsCustomization.cpp @@ -200,14 +200,14 @@ void AddTextureSizeClamping(TSharedPtr TextureSizeProperty) const FString MaxTextureResolutionString = FString::FromInt(GetMax2DTextureDimension()); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); const FString MinTextureResolutionString("1"); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); } diff --git a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs index 096579d699b9..c44a978a78dc 100644 --- a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs +++ b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs @@ -28,6 +28,11 @@ namespace UnrealBuildTool.Rules AddEngineThirdPartyPrivateStaticDependencies(Target, "ForsythTriOptimizer"); AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTessLib"); AddEngineThirdPartyPrivateStaticDependencies(Target, "QuadricMeshReduction"); - } + + if (Target.IsInPlatformGroup(UnrealPlatformGroup.Unix)) + { + PublicAdditionalLibraries.Add("stdc++"); // can be fixed, see UE-70769 + } + } } } diff --git a/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp b/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp index 5a370a9778e6..85fc7b1c8f5c 100644 --- a/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp +++ b/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp @@ -199,7 +199,7 @@ bool FStaticMeshBuilder::Build(FStaticMeshRenderData& StaticMeshRenderData, USta } //If the reduce did not output the same number of section use the base LOD sectionInfoMap - bool bIsOldMappingInvalid = OldSectionInfoMapCount != UniqueMaterialIndex.Num(); + bool bIsOldMappingInvalid = OldSectionInfoMapCount != MeshDescriptions[LodIndex].PolygonGroups().Num(); bool bValidBaseSectionInfoMap = BeforeBuildSectionInfoMap.GetSectionNumber(BaseReduceLodIndex) > 0; //All used material represent a different section diff --git a/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp b/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp index 5de69ebc534e..cab4f89ac7f4 100644 --- a/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp +++ b/Engine/Source/Developer/MeshMergeUtilities/Private/MeshMergeHelpers.cpp @@ -170,11 +170,21 @@ void FMeshMergeHelpers::ExpandInstances(const UInstancedStaticMeshComponent* InI { FMeshDescription CombinedRawMesh; + bool bFirstMesh = true; for(const FInstancedStaticMeshInstanceData& InstanceData : InInstancedStaticMeshComponent->PerInstanceSMData) { FMeshDescription InstanceRawMesh = InOutRawMesh; FMeshMergeHelpers::TransformRawMeshVertexData(FTransform(InstanceData.Transform), InstanceRawMesh); - FMeshMergeHelpers::AppendRawMesh(CombinedRawMesh, InstanceRawMesh); + + if(bFirstMesh) + { + CombinedRawMesh = InstanceRawMesh; + bFirstMesh = false; + } + else + { + FMeshMergeHelpers::AppendRawMesh(CombinedRawMesh, InstanceRawMesh); + } } InOutRawMesh = CombinedRawMesh; @@ -1176,7 +1186,7 @@ void FMeshMergeHelpers::AppendRawMesh(FMeshDescription& InTarget, const FMeshDes InTarget.ReserveNewVertices(InSource.Vertices().Num()); InTarget.ReserveNewVertexInstances(InSource.VertexInstances().Num()); InTarget.ReserveNewEdges(InSource.Edges().Num()); - InTarget.ReserveNewPolygons(InSource.Vertices().Num()); + InTarget.ReserveNewPolygons(InSource.Polygons().Num()); //Append PolygonGroup for (const FPolygonGroupID& SourcePolygonGroupID : InSource.PolygonGroups().GetElementIDs()) @@ -1189,12 +1199,14 @@ void FMeshMergeHelpers::AppendRawMesh(FMeshDescription& InTarget, const FMeshDes bool bUnique = true; do { + bUnique = true; for (const FPolygonGroupID PolygonGroupID : InTarget.PolygonGroups().GetElementIDs()) { if (TargetPolygonGroupImportedMaterialSlotNames[PolygonGroupID] == CurrentTestName) { CurrentTestName = FName(*(BaseName.ToString() + FString::FromInt(UniqueID++))); bUnique = false; + break; } } } while (!bUnique); diff --git a/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp b/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp index c3e686f45194..df730d922edb 100644 --- a/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp +++ b/Engine/Source/Developer/MeshUtilities/Private/MeshUtilities.cpp @@ -266,7 +266,7 @@ static void SkinnedMeshToRawMeshes(USkinnedMeshComponent* InSkinnedMeshComponent const FVector TangentX = InComponentToWorld.TransformVector(SkinnedVertex.TangentX.ToFVector()); const FVector TangentZ = InComponentToWorld.TransformVector(SkinnedVertex.TangentZ.ToFVector()); const FVector4 UnpackedTangentZ = SkinnedVertex.TangentZ.ToFVector4(); - const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * UnpackedTangentZ.W; + const FVector TangentY = (TangentZ ^ TangentX).GetSafeNormal() * UnpackedTangentZ.W; RawMesh.WedgeTangentX.Add(TangentX); RawMesh.WedgeTangentY.Add(TangentY); @@ -747,9 +747,9 @@ void FMeshUtilities::BuildSkeletalModelFromChunks(FSkeletalMeshLODModel& LODMode FSoftSkinVertex NewVertex; NewVertex.Position = SoftVertex.Position; - NewVertex.TangentX = SoftVertex.TangentX.ToFVector(); - NewVertex.TangentY = SoftVertex.TangentY.ToFVector(); - NewVertex.TangentZ = SoftVertex.TangentZ.ToFVector(); + NewVertex.TangentX = SoftVertex.TangentX; + NewVertex.TangentY = SoftVertex.TangentY; + NewVertex.TangentZ = SoftVertex.TangentZ; FMemory::Memcpy(NewVertex.UVs, SoftVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS); NewVertex.Color = SoftVertex.Color; for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; ++i) diff --git a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp index e91f128d7047..3a6a9f44054b 100644 --- a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp +++ b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.cpp @@ -24,17 +24,17 @@ namespace SkeletalMeshTools } } - if(!NormalsEqual(V1.TangentX.ToFVector(), V2.TangentX.ToFVector(), OverlappingThresholds)) + if(!NormalsEqual(V1.TangentX, V2.TangentX, OverlappingThresholds)) { return false; } - if(!NormalsEqual(V1.TangentY.ToFVector(), V2.TangentY.ToFVector(), OverlappingThresholds)) + if(!NormalsEqual(V1.TangentY, V2.TangentY, OverlappingThresholds)) { return false; } - if(!NormalsEqual(V1.TangentZ.ToFVector(), V2.TangentZ.ToFVector(), OverlappingThresholds)) + if(!NormalsEqual(V1.TangentZ, V2.TangentZ, OverlappingThresholds)) { return false; } diff --git a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h index 6ed0fbca7254..7cda66c6461c 100644 --- a/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h +++ b/Engine/Source/Developer/MeshUtilities/Private/SkeletalMeshTools.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "Containers/IndirectArray.h" -#include "PackedNormal.h" #include "GPUSkinPublicDefs.h" #include "Components.h" #include "BoneIndices.h" @@ -26,7 +25,7 @@ struct FSkeletalMeshVertIndexAndZ struct FSoftSkinBuildVertex { FVector Position; - FPackedNormal TangentX, // Tangent, U-direction + FVector TangentX, // Tangent, U-direction TangentY, // Binormal, V-direction TangentZ; // Normal FVector2D UVs[MAX_TEXCOORDS]; // UVs diff --git a/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp b/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp index f48a3a0e3d8c..a21157ff06ee 100644 --- a/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp +++ b/Engine/Source/Developer/OutputLog/Private/SOutputLog.cpp @@ -1,7 +1,6 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SOutputLog.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Misc/ConfigCacheIni.h" diff --git a/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp b/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp index 283eeb2b1ea4..72f222e465d7 100644 --- a/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp +++ b/Engine/Source/Developer/Profiler/Private/Widgets/SProfilerGraphPanel.cpp @@ -82,7 +82,7 @@ void SProfilerGraphPanel::Construct(const FArguments& InArgs) .Orientation(Orient_Horizontal) .AlwaysShowScrollbar(true) .Visibility(EVisibility::Visible) - .Thickness(FVector2D(8.0f, 8.0f)) + .Thickness(FVector2D(12.0f, 12.0f)) .OnUserScrolled(this, &SProfilerGraphPanel::HorizontalScrollBar_OnUserScrolled) ] ] @@ -94,7 +94,7 @@ void SProfilerGraphPanel::Construct(const FArguments& InArgs) .Orientation(Orient_Vertical) .AlwaysShowScrollbar(true) .Visibility(EVisibility::Visible) - .Thickness(FVector2D(8.0f, 8.0f)) + .Thickness(FVector2D(12.0f, 12.0f)) .OnUserScrolled(this, &SProfilerGraphPanel::VerticalScrollBar_OnUserScrolled) ] ] diff --git a/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp b/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp index 0d9dacbdf363..12f7b12b02c7 100644 --- a/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp +++ b/Engine/Source/Developer/ShaderFormatOpenGL/Private/OpenGLShaderCompiler.cpp @@ -29,12 +29,9 @@ #include #include "Windows/HideWindowsPlatformTypes.h" #elif PLATFORM_LINUX - #define GL_GLEXT_PROTOTYPES 1 #include #include #include "SDL.h" - GLAPI GLuint APIENTRY glCreateShader (GLenum type); - GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); typedef SDL_Window* SDL_HWindow; typedef SDL_GLContext SDL_HGLContext; struct FPlatformOpenGLContext @@ -281,6 +278,27 @@ static void PlatformReleaseOpenGL(void* ContextPtr, void* PrevContextPtr) wglMakeCurrent((HDC)ContextPtr, (HGLRC)PrevContextPtr); } #elif PLATFORM_LINUX +/** List all OpenGL entry points needed for shader compilation. */ +#define ENUM_GL_ENTRYPOINTS(EnumMacro) \ + EnumMacro(PFNGLCOMPILESHADERPROC,glCompileShader) \ + EnumMacro(PFNGLCREATESHADERPROC,glCreateShader) \ + EnumMacro(PFNGLDELETESHADERPROC,glDeleteShader) \ + EnumMacro(PFNGLGETSHADERIVPROC,glGetShaderiv) \ + EnumMacro(PFNGLGETSHADERINFOLOGPROC,glGetShaderInfoLog) \ + EnumMacro(PFNGLSHADERSOURCEPROC,glShaderSource) \ + EnumMacro(PFNGLDELETEBUFFERSPROC,glDeleteBuffers) + +/** Define all GL functions. */ +// We need to make pointer names different from GL functions otherwise we may end up getting +// addresses of those symbols when looking for extensions. +namespace GLFuncPointers +{ + #define DEFINE_GL_ENTRYPOINTS(Type,Func) static Type Func = NULL; + ENUM_GL_ENTRYPOINTS(DEFINE_GL_ENTRYPOINTS); +}; + +using namespace GLFuncPointers; + static void _PlatformCreateDummyGLWindow(FPlatformOpenGLContext *OutContext) { static bool bInitializedWindowClass = false; @@ -332,6 +350,19 @@ static void PlatformInitOpenGL(void*& ContextPtr, void*& PrevContextPtr, int InM UE_LOG(LogOpenGLShaderCompiler, Fatal, TEXT("Unable to dynamically load libGL: %s"), ANSI_TO_TCHAR(SDL_GetError())); } + if (glCreateShader == nullptr) + { + // Initialize all entry points. + #define GET_GL_ENTRYPOINTS(Type,Func) GLFuncPointers::Func = reinterpret_cast(SDL_GL_GetProcAddress(#Func)); + ENUM_GL_ENTRYPOINTS(GET_GL_ENTRYPOINTS); + + // Check that all of the entry points have been initialized. + bool bFoundAllEntryPoints = true; + #define CHECK_GL_ENTRYPOINTS(Type,Func) if (Func == nullptr) { bFoundAllEntryPoints = false; UE_LOG(LogOpenGLShaderCompiler, Warning, TEXT("Failed to find entry point for %s"), TEXT(#Func)); } + ENUM_GL_ENTRYPOINTS(CHECK_GL_ENTRYPOINTS); + checkf(bFoundAllEntryPoints, TEXT("Failed to find all OpenGL entry points.")); + } + if (SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, InMajorVersion)) { UE_LOG(LogOpenGLShaderCompiler, Fatal, TEXT("Failed to set GL major version: %s"), ANSI_TO_TCHAR(SDL_GetError())); diff --git a/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp b/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp index 7f4a322a980d..f8ed0f18043e 100644 --- a/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp +++ b/Engine/Source/Developer/SlateFileDialogs/Private/SlateFileDlgWindow.cpp @@ -755,7 +755,7 @@ void SSlateFileOpenDlg::Construct(const FArguments& InArgs) .Padding(FMargin(0.0f)) [ SNew(STextBlock) - .Text(LOCTEXT("FilterLabel", "Filter:")) + .Text(bSaveFile ? LOCTEXT("SaveTypeLabel", "Save as type:") : LOCTEXT("FilterLabel", "Filter:")) .Font(StyleSet->GetFontStyle("SlateFileDialogs.Dialog")) .Justification(ETextJustify::Left) ] @@ -1304,7 +1304,10 @@ void SSlateFileOpenDlg::OnItemDoubleClicked(TSharedPtr Item) { if (Item->bIsDirectory) { - SetDefaultFile(FString("")); + if (!bSaveFile) + { + SetDefaultFile(FString("")); + } CurrentPath = CurrentPath + Item->Label + TEXT("/"); bNeedsBuilding = true; @@ -1392,6 +1395,19 @@ void SSlateFileOpenDlg::ParseTextField(TArray &FilenameArray, FString F } else { + FString Extension; + + // get current filter extension + if (!bDirectoriesOnly && GetFilterExtension(Extension)) + { + // append extension to filename if user left it off + if (!SaveFilename.EndsWith(Extension, ESearchCase::CaseSensitive) && + !IsWildcardExtension(Extension)) + { + Files = Files + Extension; + } + } + FilenameArray.Add(Files); } } @@ -1433,20 +1449,7 @@ void SSlateFileOpenDlg::OnFileNameCommitted(const FText& InText, ETextCommit::Ty // update edit box unless user choose to escape out if (InCommitType != ETextCommit::OnCleared) { - FString Extension; SaveFilename = InText.ToString(); - - // get current filter extension - if (!bDirectoriesOnly && GetFilterExtension(Extension)) - { - // append extension to filename if user left it off - if (!SaveFilename.EndsWith(Extension, ESearchCase::CaseSensitive) && - !IsWildcardExtension(Extension)) - { - SaveFilename = SaveFilename + Extension; - } - } - ListView->ClearSelection(); SetDefaultFile(SaveFilename); @@ -1540,6 +1543,12 @@ bool SSlateFileOpenDlg::GetFilterExtension(FString &OutString) return false; } + // We have attempted to get the filter extension before parsing them + if (FilterNameArray.Num() == 0) + { + ParseFilters(); + } + // make a copy of filter string that we can modify TCHAR Temp[MAX_FILTER_LENGTH] = {0}; FCString::Strcpy(Temp, ARRAY_COUNT(Temp), *(*FilterNameArray[FilterIndex].Get())); @@ -1645,7 +1654,10 @@ FReply SSlateFileOpenDlg::OnGoForwardClick() { if ((HistoryIndex+1) < History.Num()) { - SetDefaultFile(FString("")); + if (!bSaveFile) + { + SetDefaultFile(FString("")); + } HistoryIndex++; CurrentPath = History[HistoryIndex]; @@ -1662,7 +1674,10 @@ FReply SSlateFileOpenDlg::OnGoBackClick() { if (HistoryIndex > 0) { - SetDefaultFile(FString("")); + if (!bSaveFile) + { + SetDefaultFile(FString("")); + } HistoryIndex--; CurrentPath = History[HistoryIndex]; diff --git a/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h b/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h index e6f61985dfda..943227d7ec35 100644 --- a/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h +++ b/Engine/Source/Developer/SourceControl/Public/ISourceControlProvider.h @@ -71,7 +71,7 @@ DECLARE_MULTICAST_DELEGATE(FSourceControlStateChanged); /** * Interface to talking with source control providers. */ -class ISourceControlProvider : public IModularFeature +class SOURCECONTROL_VTABLE ISourceControlProvider : public IModularFeature { public: /** diff --git a/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h b/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h index fc4f3eb09193..2ee5c363c422 100644 --- a/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h +++ b/Engine/Source/Developer/TargetPlatform/Public/Common/TargetPlatformBase.h @@ -9,7 +9,7 @@ /** * Base class for target platforms. */ -class FTargetPlatformBase +class TARGETPLATFORM_VTABLE FTargetPlatformBase : public ITargetPlatform { public: diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp index 16fb80b9d051..4326930a950b 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimGraphCommands.cpp @@ -7,6 +7,7 @@ void FAnimGraphCommands::RegisterCommands() { UI_COMMAND(TogglePoseWatch, "Toggle Pose Watch", "Toggle Pose Watching on this node", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles automatically hiding nodes which are unrelated to the selected nodes.", EUserInterfaceActionType::ToggleButton, FInputChord() ); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp b/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp index c03922b421b5..2b784cef8930 100644 --- a/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/PoseDriverDetails.cpp @@ -360,6 +360,7 @@ TSharedRef< SWidget > SPDD_TargetRow::GenerateWidgetForColumn(const FName& Colum +SWidgetSwitcher::Slot() [ SNew(SVectorInputBox) + .AllowSpin(true) .X(this, &SPDD_TargetRow::GetTranslation, BoneIndex, EAxis::X) .OnXChanged(this, &SPDD_TargetRow::SetTranslation, BoneIndex, EAxis::X) .Y(this, &SPDD_TargetRow::GetTranslation, BoneIndex, EAxis::Y) diff --git a/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h b/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h index 51a841e0389b..a1db5e9ee6f3 100644 --- a/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h +++ b/Engine/Source/Editor/AnimGraph/Public/AnimGraphCommands.h @@ -21,4 +21,7 @@ public: // Toggle pose watching for a given node TSharedPtr TogglePoseWatch; + + // Toggle hiding nodes which are not related to the selected nodes + TSharedPtr< FUICommandInfo > ToggleHideUnrelatedNodes; }; diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp index 35024b0a2bee..be32b067ca32 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.cpp @@ -68,6 +68,13 @@ #include "EditorFontGlyphs.h" #include "AnimationBlueprintInterfaceEditorMode.h" +// Hide related nodes feature +#include "Preferences/AnimationBlueprintEditorOptions.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "EdGraphNode_Comment.h" +#include "AnimStateNodeBase.h" +#include "AnimStateEntryNode.h" + #define LOCTEXT_NAMESPACE "AnimationBlueprintEditor" const FName AnimationBlueprintEditorAppName(TEXT("AnimationBlueprintEditorApp")); @@ -166,6 +173,8 @@ FAnimationBlueprintEditor::~FAnimationBlueprintEditor() FReimportManager::Instance()->OnPostReimport().RemoveAll(this); // NOTE: Any tabs that we still have hanging out when destroyed will be cleaned up by FBaseToolkit's destructor + + SaveEditorSettings(); } UAnimBlueprint* FAnimationBlueprintEditor::GetAnimBlueprint() const @@ -209,6 +218,8 @@ void FAnimationBlueprintEditor::InitAnimationBlueprintEditor(const EToolkitMode: Toolbar = MakeShareable(new FBlueprintEditorToolbar(SharedThis(this))); } + LoadEditorSettings(); + GetToolkitCommands()->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef()); FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked("Persona"); @@ -347,6 +358,39 @@ void FAnimationBlueprintEditor::ExtendToolbar() } )); } + + struct Local + { + static void FillToolbar(FToolBarBuilder& ToolbarBuilder, const TSharedRef< FUICommandList > ToolkitCommands, FAnimationBlueprintEditor* BlueprintEditor) + { + ToolbarBuilder.BeginSection("Graph"); + { + ToolbarBuilder.AddToolBarButton( + FAnimGraphCommands::Get().ToggleHideUnrelatedNodes, + NAME_None, + TAttribute(), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") + ); + ToolbarBuilder.AddComboButton( + FUIAction(), + FOnGetContent::CreateSP(BlueprintEditor, &FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu), + LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"), + LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"), + TAttribute(), + true + ); + } + ToolbarBuilder.EndSection(); + } + }; + + ToolbarExtender->AddToolBarExtension( + "Asset", + EExtensionHook::After, + GetToolkitCommands(), + FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, GetToolkitCommands(), this ) + ); } UBlueprint* FAnimationBlueprintEditor::GetBlueprintObj() const @@ -386,6 +430,11 @@ void FAnimationBlueprintEditor::OnGraphEditorFocused(const TSharedRefOnPinDefaultValueChanged.Add(FOnPinDefaultValueChanged::FDelegate::CreateSP(this, &FAnimationBlueprintEditor::HandlePinDefaultValueChanged)); } + + if (bHideUnrelatedNodes && GetSelectedNodes().Num() <= 0) + { + ResetAllNodesUnrelatedStates(); + } } void FAnimationBlueprintEditor::OnGraphEditorBackgrounded(const TSharedRef& InGraphEditor) @@ -405,6 +454,12 @@ void FAnimationBlueprintEditor::CreateDefaultCommands() if (GetBlueprintObj()) { FBlueprintEditor::CreateDefaultCommands(); + + ToolkitCommands->MapAction( + FAnimGraphCommands::Get().ToggleHideUnrelatedNodes, + FExecuteAction::CreateSP(this, &FBlueprintEditor::ToggleHideUnrelatedNodes), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsToggleHideUnrelatedNodesChecked)); } else { @@ -1392,6 +1447,11 @@ void FAnimationBlueprintEditor::HandleOpenNewAsset(UObject* InNewAsset) FAssetEditorManager::Get().OpenEditorForAsset(InNewAsset); } +void FAnimationBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) +{ + Collector.AddReferencedObject( EditorOptions ); +} + FAnimNode_Base* FAnimationBlueprintEditor::FindAnimNode(UAnimGraphNode_Base* AnimGraphNode) const { FAnimNode_Base* AnimNode = nullptr; @@ -1440,6 +1500,29 @@ void FAnimationBlueprintEditor::OnSelectedNodesChangedImpl(const TSet(*It); + UAnimStateNodeBase* AnimGraphNodeBase = Cast(*It); + UAnimStateEntryNode* AnimStateEntryNode = Cast(*It); + if (!SeqNode && !AnimGraphNodeBase && !AnimStateEntryNode) + { + bSelectRegularNode = true; + break; + } + } + + if (bHideUnrelatedNodes && !bLockNodeFadeState) + { + ResetAllNodesUnrelatedStates(); + + if ( bSelectRegularNode ) + { + HideUnrelatedNodes(); + } + } } void FAnimationBlueprintEditor::OnPostCompile() @@ -1667,5 +1750,24 @@ void FAnimationBlueprintEditor::HandleViewportCreated(const TSharedRef(); + + if (EditorOptions->bHideUnrelatedNodes) + { + ToggleHideUnrelatedNodes(); + } +} + +void FAnimationBlueprintEditor::SaveEditorSettings() +{ + if ( EditorOptions ) + { + EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; + EditorOptions->SaveConfig(); + } +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h index 77422cb28cb0..f917a7e7c59c 100644 --- a/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h +++ b/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationBlueprintEditor.h @@ -11,6 +11,7 @@ #include "IAnimationBlueprintEditor.h" #include "Containers/ArrayView.h" +class UAnimationBlueprintEditorOptions; class IPersonaToolkit; class IPersonaViewport; class ISkeletonTree; @@ -124,6 +125,10 @@ public: /** Get the object to be displayed in the asset properties */ UObject* HandleGetObject(); + + //~ Begin FGCObject Interface + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + //~ End FGCObject Interface /** Handle opening a new asset from the asset browser */ void HandleOpenNewAsset(UObject* InNewAsset); @@ -269,6 +274,16 @@ private: /** Handle the viewport being created */ void HandleViewportCreated(const TSharedRef& InPersonaViewport); + /** + * Load editor settings from disk (docking state, window pos/size, option state, etc). + */ + virtual void LoadEditorSettings(); + + /** + * Saves editor settings to disk (docking state, window pos/size, option state, etc). + */ + virtual void SaveEditorSettings(); + /** The extender to pass to the level editor to extend it's window menu */ TSharedPtr MenuExtender; @@ -292,4 +307,7 @@ private: /** The last pin type we added to a graph's inputs */ FEdGraphPinType LastGraphPinType; + + /** Configuration class used to store editor settings across sessions. */ + UAnimationBlueprintEditorOptions* EditorOptions; }; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h index f297d914f248..e6f7aaf2e216 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/EdGraphSchema_K2_Actions.h @@ -335,7 +335,7 @@ struct FEdGraphSchemaAction_K2AddCallOnActor : public FEdGraphSchemaAction_K2New /** Action to add a 'comment' node to the graph */ USTRUCT() -struct FEdGraphSchemaAction_K2AddComment : public FEdGraphSchemaAction +struct BLUEPRINTGRAPH_VTABLE FEdGraphSchemaAction_K2AddComment : public FEdGraphSchemaAction { GENERATED_USTRUCT_BODY() diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h index 282a0ad6499f..724d576c4b65 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_EditablePinBase.h @@ -83,7 +83,7 @@ public: }; UCLASS(abstract, MinimalAPI) -class UK2Node_EditablePinBase : public UK2Node +class BLUEPRINTGRAPH_VTABLE UK2Node_EditablePinBase : public UK2Node { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h index 2cb50832d525..d3b04f478ce1 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_Event.h @@ -18,7 +18,7 @@ class FNodeHandlingFunctor; class UEdGraph; UCLASS(MinimalAPI) -class UK2Node_Event : public UK2Node_EditablePinBase, public IK2Node_EventNodeInterface +class BLUEPRINTGRAPH_VTABLE UK2Node_Event : public UK2Node_EditablePinBase, public IK2Node_EventNodeInterface { GENERATED_UCLASS_BODY() BLUEPRINTGRAPH_API static const FName DelegateOutputName; diff --git a/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h b/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h index ef4f82a134aa..1664d22c77ec 100644 --- a/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h +++ b/Engine/Source/Editor/Blutility/Classes/EditorUtilityWidgetBlueprint.h @@ -31,7 +31,7 @@ public: TSharedRef CreateUtilityWidget(); /** Recreate the tab's content on recompile */ - void RegenerateCreatedTab(UBlueprint* RecompiledBlueprint); + void RegenerateCreatedTab(); void UpdateRespawnListIfNeeded(TSharedRef TabBeingClosed); @@ -60,6 +60,7 @@ private: TWeakPtr CreatedTab; + UPROPERTY() UEditorUtilityWidget* CreatedUMGWidget; }; diff --git a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp index cbdf3ffed657..c3b1fff6fd11 100644 --- a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp +++ b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.cpp @@ -18,6 +18,7 @@ #include "Widgets/Docking/SDockTab.h" #include "Framework/Docking/TabManager.h" #include "IBlutilityModule.h" +#include "SBlueprintDiff.h" #define LOCTEXT_NAMESPACE "AssetTypeActions" @@ -88,6 +89,26 @@ uint32 FAssetTypeActions_EditorUtilityWidgetBlueprint::GetCategories() return BlutilityModule->GetAssetCategory(); } +void FAssetTypeActions_EditorUtilityWidgetBlueprint::PerformAssetDiff(UObject* Asset1, UObject* Asset2, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const +{ + UBlueprint* OldBlueprint = CastChecked(Asset1); + UBlueprint* NewBlueprint = CastChecked(Asset2); + + // sometimes we're comparing different revisions of one single asset (other + // times we're comparing two completely separate assets altogether) + bool bIsSingleAsset = (NewBlueprint->GetName() == OldBlueprint->GetName()); + + FText WindowTitle = LOCTEXT("NamelessWidgetBlueprintDiff", "Editor Utility Widget Blueprint Diff"); + // if we're diffing one asset against itself + if (bIsSingleAsset) + { + // identify the assumed single asset in the window's title + WindowTitle = FText::Format(LOCTEXT("WidgetBlueprintDiff", "{0} - Editor Utility Widget Blueprint Diff"), FText::FromString(NewBlueprint->GetName())); + } + + SBlueprintDiff::CreateDiffWindow(WindowTitle, OldBlueprint, NewBlueprint, OldRevision, NewRevision); +} + void FAssetTypeActions_EditorUtilityWidgetBlueprint::ExecuteRun(FWeakBlueprintPointerArray InObjects) { for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt) @@ -109,7 +130,7 @@ void FAssetTypeActions_EditorUtilityWidgetBlueprint::ExecuteRun(FWeakBlueprintPo FText DisplayName = FText::FromString(Blueprint->GetName()); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); - if (!LevelEditorTabManager->CanSpawnTab(RegistrationName)) + if (!LevelEditorTabManager->HasTabSpawner(RegistrationName)) { IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr("Blutility"); UEditorUtilityWidgetBlueprint* WidgetBlueprint = Cast(Blueprint); diff --git a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h index df683057d084..394db085ddb2 100644 --- a/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h +++ b/Engine/Source/Editor/Blutility/Private/AssetTypeActions_EditorUtilityWidgetBlueprint.h @@ -20,6 +20,7 @@ public: virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; virtual uint32 GetCategories() override; + virtual void PerformAssetDiff(UObject* Asset1, UObject* Asset2, const struct FRevisionInfo& OldRevision, const struct FRevisionInfo& NewRevision) const override; virtual bool CanLocalize() const override { return false; } // End of IAssetTypeActions interface diff --git a/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp b/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp index b835f126be2e..f0e447326f3d 100644 --- a/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp +++ b/Engine/Source/Editor/Blutility/Private/BlutilityModule.cpp @@ -117,7 +117,7 @@ public: FName RegistrationName = FName(*(Blueprint->GetPathName() + LOCTEXT("ActiveTabSuffix", "_ActiveTab").ToString())); Blueprint->SetRegistrationName(RegistrationName); FText DisplayName = FText::FromString(Blueprint->GetName()); - if (LevelEditorTabManager && !LevelEditorTabManager->CanSpawnTab(RegistrationName)) + if (LevelEditorTabManager && !LevelEditorTabManager->HasTabSpawner(RegistrationName)) { LevelEditorTabManager->RegisterTabSpawner(RegistrationName, FOnSpawnTab::CreateUObject(Blueprint, &UEditorUtilityWidgetBlueprint::SpawnEditorUITab)) .SetDisplayName(DisplayName) diff --git a/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp b/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp index 6102215261a8..0a0c5246dd1a 100644 --- a/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp +++ b/Engine/Source/Editor/Blutility/Private/EditorUtilitySubsystem.cpp @@ -4,6 +4,7 @@ #include "EditorUtilityCommon.h" #include "Interfaces/IMainFrameModule.h" #include "Engine/Blueprint.h" +#include "ScopedTransaction.h" UEditorUtilitySubsystem::UEditorUtilitySubsystem() : UEditorSubsystem() diff --git a/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp b/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp index 982714915899..b9c36abb6026 100644 --- a/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp +++ b/Engine/Source/Editor/Blutility/Private/EditorUtilityWidgetBlueprint.cpp @@ -56,33 +56,36 @@ TSharedRef UEditorUtilityWidgetBlueprint::SpawnEditorUITab(const FSpaw SpawnedTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateUObject(this, &UEditorUtilityWidgetBlueprint::UpdateRespawnListIfNeeded)); CreatedTab = SpawnedTab; - OnCompiled().AddUObject(this, &UEditorUtilityWidgetBlueprint::RegenerateCreatedTab); + GEditor->OnBlueprintReinstanced().AddUObject(this, &UEditorUtilityWidgetBlueprint::RegenerateCreatedTab); return SpawnedTab; } TSharedRef UEditorUtilityWidgetBlueprint::CreateUtilityWidget() { + TSharedRef TabWidget = SNullWidget::NullWidget; + UClass* BlueprintClass = GeneratedClass; TSubclassOf WidgetClass = BlueprintClass; UWorld* World = GEditor->GetEditorWorldContext().World(); - check(World); - CreatedUMGWidget = CreateWidget(World, WidgetClass); - TSharedRef TabWidget = SNullWidget::NullWidget; + if (!CreatedUMGWidget && World) + { + CreatedUMGWidget = CreateWidget(World, WidgetClass); + } + if (CreatedUMGWidget) { - TSharedRef CreatedSlateWidget = CreatedUMGWidget->TakeWidget(); TabWidget = SNew(SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Fill) [ - CreatedSlateWidget + CreatedUMGWidget->TakeWidget() ]; } return TabWidget; } -void UEditorUtilityWidgetBlueprint::RegenerateCreatedTab(UBlueprint* RecompiledBlueprint) +void UEditorUtilityWidgetBlueprint::RegenerateCreatedTab() { if (CreatedTab.IsValid()) { diff --git a/Engine/Source/Editor/Cascade/Private/Cascade.cpp b/Engine/Source/Editor/Cascade/Private/Cascade.cpp index 4eccb4f7d002..a902aacee6eb 100644 --- a/Engine/Source/Editor/Cascade/Private/Cascade.cpp +++ b/Engine/Source/Editor/Cascade/Private/Cascade.cpp @@ -72,6 +72,8 @@ static const FName Cascade_CurveEditorTab("Cascade_CurveEditor"); DEFINE_LOG_CATEGORY(LogCascade); +FRandomStream FCascade::RandomStream(FPlatformTime::Cycles()); + FCascade::FCascade() : ParticleSystem(nullptr) , ParticleSystemComponent(nullptr) @@ -1673,14 +1675,6 @@ void FCascade::Tick(float DeltaTime) } } - if (CurveEditor.IsValid()) - { - if (CurveEditor->GetNeedsRedraw()) - { - CurveEditor->DrawViewport(); - } - } - if (bCurrentlySoloing != bIsSoloing) { bIsSoloing = bCurrentlySoloing; @@ -4761,7 +4755,7 @@ void FCascade::OnSetRandomSeed() ParticleSystem->PreEditChange(NULL); ParticleSystemComponent->PreEditChange(NULL); - int32 RandomSeed = FMath::RoundToInt(RAND_MAX * FMath::SRand()); + int32 RandomSeed = FMath::RoundToInt(RAND_MAX * RandomStream.FRand()); if (SelectedModule->SetRandomSeedEntry(0, RandomSeed) == false) { UE_LOG(LogCascade, Warning, TEXT("Failed to set random seed entry on module %s"), *(SelectedModule->GetClass()->GetName())); @@ -4903,7 +4897,7 @@ bool FCascade::ConvertModuleToSeeded(UParticleSystem* ParticleSystem, UParticleE if (RandSeedInfo != NULL) { RandSeedInfo->bResetSeedOnEmitterLooping = true; - RandSeedInfo->RandomSeeds.Add(FMath::TruncToInt(FMath::Rand() * UINT_MAX)); + RandSeedInfo->RandomSeeds.Add(FMath::TruncToInt(RandomStream.FRand() * UINT_MAX)); } } diff --git a/Engine/Source/Editor/Cascade/Private/Cascade.h b/Engine/Source/Editor/Cascade/Private/Cascade.h index 1fe2c8b045ec..14717aa91413 100644 --- a/Engine/Source/Editor/Cascade/Private/Cascade.h +++ b/Engine/Source/Editor/Cascade/Private/Cascade.h @@ -16,6 +16,7 @@ #include "ICascade.h" #include "IDistCurveEditor.h" #include "Particles/ParticleEmitter.h" +#include "Math/RandomStream.h" class FFXSystemInterface; class IDetailsView; @@ -497,4 +498,7 @@ private: /** Maps particle emitters to rendered thumbnails for the emitter UI */ FParticleEmitterThumbnailMap EmitterToThumbnailMap; + + /** Random stream used to seed particle modules */ + static FRandomStream RandomStream; }; diff --git a/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp b/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp index 6449e66f9c24..431899a14942 100644 --- a/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp +++ b/Engine/Source/Editor/Cascade/Private/SCascadeEmitterCanvas.cpp @@ -77,6 +77,7 @@ void SCascadeEmitterCanvas::Construct(const FArguments& InArgs) void SCascadeEmitterCanvas::RefreshViewport() { Viewport->Invalidate(); + Viewport->InvalidateDisplay(); } bool SCascadeEmitterCanvas::IsVisible() const diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerFilter.cpp b/Engine/Source/Editor/ClassViewer/Private/ClassViewerFilter.cpp new file mode 100644 index 000000000000..b1610add649e --- /dev/null +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerFilter.cpp @@ -0,0 +1,556 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ClassViewerFilter.h" + +#include "UnloadedBlueprintData.h" +#include "Engine/Brush.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Misc/ConfigCacheIni.h" +#include "Misc/PackageName.h" +#include "Misc/Paths.h" +#include "Misc/TextFilterExpressionEvaluator.h" + +EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (InClass->IsChildOf(*CurClassIt)) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (InClass->IsChildOf(*CurClassIt)) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!InClass->IsChildOf(*CurClassIt)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!InClass->IsChildOf(*CurClassIt)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!(*CurClassIt)->IsA(InClass)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (!(*CurClassIt)->IsA(UBlueprintGeneratedClass::StaticClass())) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (!Object->IsA(InClass)) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (!Object->IsA(UBlueprintGeneratedClass::StaticClass())) + { + // Since it doesn't match one, it fails. + return EFilterReturn::Failed; + } + } + + // It matches all of them, so it passes. + return EFilterReturn::Passed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (Object->IsA(InClass)) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const UObject* Object = *CurClassIt; + if (Object->IsA(UBlueprintGeneratedClass::StaticClass())) + { + return EFilterReturn::Passed; + } + } + + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) +{ + check(InClass); + + if (InSet.Num()) + { + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + if (InClass == *CurClassIt) + { + return EFilterReturn::Passed; + } + } + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) +{ + check(InClass.IsValid()); + + if (InSet.Num()) + { + for (auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt) + { + const TSharedPtr UnloadedBlueprintData = StaticCastSharedPtr(InClass); + if (*UnloadedBlueprintData->GetClassViewerNode().Pin()->GetClassName() == (*CurClassIt)->GetName()) + { + return EFilterReturn::Passed; + } + } + return EFilterReturn::Failed; + } + + // Since there are none on this list, return that there is no items. + return EFilterReturn::NoItems; +} + +/** Checks if a particular class is a brush. +* @param InClass The Class to check. +* @return Returns true if the class is a brush. +*/ +static bool IsBrush(const UClass* InClass) +{ + return InClass->IsChildOf(ABrush::StaticClass()); +} + +static bool IsBrush(const TSharedRef& InBlueprintData) +{ + return InBlueprintData->IsChildOf(ABrush::StaticClass()); +} + +/** Checks if a particular class is placeable. +* @param InClass The Class to check. +* @return Returns true if the class is placeable. +*/ +static bool IsPlaceable(const UClass* InClass) +{ + return !InClass->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) + && InClass->IsChildOf(AActor::StaticClass()); +} + +static bool IsPlaceable(const TSharedRef& InBlueprintData) +{ + return !InBlueprintData->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) + && InBlueprintData->IsChildOf(AActor::StaticClass()); +} + +/** Util class to checks if a particular class can be made into a Blueprint, ignores deprecation + * + * @param InClass The class to verify can be made into a Blueprint + * @return true if the class can be made into a Blueprint + */ +static bool CanCreateBlueprintOfClass(UClass* InClass) +{ + // Temporarily remove the deprecated flag so we can check if it is valid for + bool bIsClassDeprecated = InClass->HasAnyClassFlags(CLASS_Deprecated); + InClass->ClassFlags &= ~CLASS_Deprecated; + + bool bCanCreateBlueprintOfClass = FKismetEditorUtilities::CanCreateBlueprintOfClass(InClass); + + // Reassign the deprecated flag if it was previously assigned + if (bIsClassDeprecated) + { + InClass->ClassFlags |= CLASS_Deprecated; + } + + return bCanCreateBlueprintOfClass; +} + +/** Checks if a node is a blueprint base or not. +* @param InNode The node to check if it is a blueprint base. +* @return true if the class is a blueprint base. +*/ +static bool CheckIfBlueprintBase(const TSharedRef& InBlueprintData) +{ + if (InBlueprintData->IsNormalBlueprintType()) + { + bool bAllowDerivedBlueprints = false; + GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni); + + return bAllowDerivedBlueprints; + } + return false; +} + +/** Checks if the TestString passes the filter. +* @param InTestString The string to test against the filter. +* @param InTextFilter Compiled text filter to apply. +* +* @return true if it passes the filter. +*/ +static bool PassesTextFilter(const FString& InTestString, const TSharedRef& InTextFilter) +{ + class FClassFilterContext : public ITextFilterExpressionContext + { + public: + explicit FClassFilterContext(const FString& InStr) + : StrPtr(&InStr) + { + } + + virtual bool TestBasicStringExpression(const FTextFilterString& InValue, const ETextFilterTextComparisonMode InTextComparisonMode) const override + { + return TextFilterUtils::TestBasicStringExpression(*StrPtr, InValue, InTextComparisonMode); + } + + virtual bool TestComplexExpression(const FName& InKey, const FTextFilterString& InValue, const ETextFilterComparisonOperation InComparisonOperation, const ETextFilterTextComparisonMode InTextComparisonMode) const override + { + return false; + } + + private: + const FString* StrPtr; + }; + + return InTextFilter->TestTextFilter(FClassFilterContext(InTestString)); +} + +FClassViewerFilter::FClassViewerFilter(const FClassViewerInitializationOptions& InInitOptions) : + TextFilter(MakeShared(ETextFilterExpressionEvaluatorMode::BasicString)), + FilterFunctions(MakeShared()) +{ +} + +bool FClassViewerFilter::IsNodeAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef& InNode) +{ + if (InNode->Class.IsValid()) + { + return IsClassAllowed(InInitOptions, InNode->Class.Get(), FilterFunctions); + } + else if (InInitOptions.bShowUnloadedBlueprints && InNode->UnloadedBlueprintData.IsValid()) + { + return IsUnloadedClassAllowed(InInitOptions, InNode->UnloadedBlueprintData.ToSharedRef(), FilterFunctions); + } + + return false; +} + +bool FClassViewerFilter::IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef InFilterFuncs) +{ + if (InInitOptions.bIsActorsOnly && !InClass->IsChildOf(AActor::StaticClass())) + { + return false; + } + + const bool bPassesBlueprintBaseFilter = !InInitOptions.bIsBlueprintBaseOnly || CanCreateBlueprintOfClass(const_cast(InClass)); + const bool bPassesEditorClassFilter = !InInitOptions.bEditorClassesOnly || IsEditorOnlyObject(InClass); + + // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. + static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); + static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); + FString GeneratedClassPathString = InClass->GetPathName(); + + bool bPassesDeveloperFilter = true; + EClassViewerDeveloperType AllowedDeveloperType = GetDefault()->DeveloperFolderType; + if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) + { + bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); + } + else if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) + { + if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) + { + bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); + } + } + + // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? + // INI path: /Script/ClassViewer.ClassViewerProjectSettings + bool bPassesInternalFilter = true; + if (!GetDefault()->DisplayInternalClasses) + { + for (int i = 0; i < InternalPaths.Num(); ++i) + { + if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) + { + bPassesInternalFilter = false; + break; + } + } + + if (bPassesInternalFilter) + { + for (int i = 0; i < InternalClasses.Num(); ++i) + { + if (InClass->IsChildOf(InternalClasses[i])) + { + bPassesInternalFilter = false; + break; + } + } + } + } + + bool bPassesPlaceableFilter = true; + if (InInitOptions.bIsPlaceableOnly) + { + bPassesPlaceableFilter = IsPlaceable(InClass) && + (InInitOptions.Mode == EClassViewerMode::ClassPicker || !IsBrush(InClass)); + } + + bool bPassesCustomFilter = true; + if (InInitOptions.ClassFilter.IsValid()) + { + bPassesCustomFilter = InInitOptions.ClassFilter->IsClassAllowed(InInitOptions, InClass, FilterFunctions); + } + + const bool bPassesTextFilter = PassesTextFilter(InClass->GetName(), TextFilter); + + bool bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter + && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassFilter + && bPassesCustomFilter && bPassesTextFilter; + + return bPassesFilter; +} + +bool FClassViewerFilter::IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef InUnloadedClassData, TSharedRef InFilterFuncs) +{ + if (InInitOptions.bIsActorsOnly && !InUnloadedClassData->IsChildOf(AActor::StaticClass())) + { + return false; + } + + const bool bPassesBlueprintBaseFilter = !InInitOptions.bIsBlueprintBaseOnly || CheckIfBlueprintBase(InUnloadedClassData); + + // unloaded blueprints cannot be editor-only + const bool bPassesEditorClassFilter = !InInitOptions.bEditorClassesOnly; + + // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. + static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); + static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); + FString GeneratedClassPathString = InUnloadedClassData->GetClassPath().ToString(); + + bool bPassesDeveloperFilter = true; + + EClassViewerDeveloperType AllowedDeveloperType = GetDefault()->DeveloperFolderType; + if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) + { + bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); + } + else if (AllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) + { + if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) + { + bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); + } + } + + // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? + // INI path: /Script/ClassViewer.ClassViewerProjectSettings + bool bPassesInternalFilter = true; + if (!GetDefault()->DisplayInternalClasses) + { + for (int i = 0; i < InternalPaths.Num(); ++i) + { + if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) + { + bPassesInternalFilter = false; + break; + } + } + } + + bool bPassesPlaceableFilter = true; + if (InInitOptions.bIsPlaceableOnly) + { + bPassesPlaceableFilter = IsPlaceable(InUnloadedClassData) && + (InInitOptions.Mode == EClassViewerMode::ClassPicker || !IsBrush(InUnloadedClassData)); + } + + bool bPassesCustomFilter = true; + if (InInitOptions.ClassFilter.IsValid()) + { + bPassesCustomFilter = InInitOptions.ClassFilter->IsUnloadedClassAllowed(InInitOptions, InUnloadedClassData, FilterFunctions); + } + + const bool bPassesTextFilter = PassesTextFilter(*InUnloadedClassData->GetClassName().Get(), TextFilter); + + bool bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter + && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassFilter + && bPassesCustomFilter && bPassesTextFilter; + + return bPassesFilter; +} diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp b/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp index 9e72b0f5614c..75a866ea4b32 100644 --- a/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerModule.cpp @@ -16,6 +16,7 @@ #include "Widgets/Docking/SDockTab.h" #include "ClassViewerProjectSettings.h" #include "ISettingsModule.h" +#include "ClassViewerFilter.h" #define LOCTEXT_NAMESPACE "ClassViewer" @@ -95,5 +96,14 @@ TSharedRef FClassViewerModule::CreateClassViewer(const FClassViewerInit .OnClassPickedDelegate(OnClassPickedDelegate); } +TSharedRef FClassViewerModule::CreateClassFilter(const FClassViewerInitializationOptions& InitOptions) +{ + return TSharedRef(new FClassViewerFilter(InitOptions)); +} + +TSharedRef FClassViewerModule::CreateFilterFuncs() +{ + return TSharedRef(new FClassViewerFilterFuncs()); +} #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp index 0f5ba4fd9ef5..417406b8f700 100644 --- a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.cpp @@ -8,12 +8,35 @@ #include "ClassViewerFilter.h" #include "PropertyHandle.h" +FClassViewerNode::FClassViewerNode(UClass* InClass) +{ + Class = InClass; + ClassName = MakeShareable(new FString(Class->GetName())); + ClassDisplayName = MakeShareable(new FString(Class->GetDisplayNameText().ToString())); + ClassPath = FName(*Class->GetPathName()); + + if (Class->GetSuperClass()) + { + ParentClassPath = FName(*Class->GetSuperClass()->GetPathName()); + } + + if (Class->ClassGeneratedBy && Class->ClassGeneratedBy->IsA(UBlueprint::StaticClass())) + { + Blueprint = Cast(Class->ClassGeneratedBy); + } + else + { + Blueprint = nullptr; + } + + bPassesFilter = false; +} + FClassViewerNode::FClassViewerNode(const FString& InClassName, const FString& InClassDisplayName) { ClassName = MakeShareable(new FString(InClassName)); ClassDisplayName = MakeShareable(new FString(InClassDisplayName)); bPassesFilter = false; - bIsBPNormalType = false; Class = nullptr; Blueprint = nullptr; @@ -34,7 +57,6 @@ FClassViewerNode::FClassViewerNode( const FClassViewerNode& InCopyObject) ParentClassPath = InCopyObject.ParentClassPath; ClassName = InCopyObject.ClassName; BlueprintAssetPath = InCopyObject.BlueprintAssetPath; - bIsBPNormalType = InCopyObject.bIsBPNormalType; // We do not want to copy the child list, do not add it. It should be the only item missing. } diff --git a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h index 3e5749c2363b..9ced0c9311f9 100644 --- a/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h +++ b/Engine/Source/Editor/ClassViewer/Private/ClassViewerNode.h @@ -13,6 +13,8 @@ class UBlueprint; class FClassViewerNode { public: + FClassViewerNode(UClass* Class); + /** * Creates a node for the widget's tree. * @@ -101,9 +103,6 @@ public: /** true if the class passed the filter. */ bool bPassesFilter; - /** true if the class is a "normal type", this is used to identify unloaded blueprints as blueprint bases. */ - bool bIsBPNormalType; - /** Pointer to the parent to this object. */ TWeakPtr< FClassViewerNode > ParentNode; diff --git a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp index 0868709cafe0..8938a7fd471d 100644 --- a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.cpp @@ -81,287 +81,6 @@ DEFINE_LOG_CATEGORY_STATIC(LogEditorClassViewer, Log, All); -////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////// - -EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(InClass->IsChildOf(*CurClassIt)) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(InClass->IsChildOf(*CurClassIt)) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!InClass->IsChildOf(*CurClassIt)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAllInChildOfClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!InClass->IsChildOf(*CurClassIt)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!(*CurClassIt)->IsA(InClass)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ObjectsSetIsAClass(TSet< const UObject* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(!(*CurClassIt)->IsA(UBlueprintGeneratedClass::StaticClass())) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(!Object->IsA(InClass)) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatchesAll_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(!Object->IsA(UBlueprintGeneratedClass::StaticClass())) - { - // Since it doesn't match one, it fails. - return EFilterReturn::Failed; - } - } - - // It matches all of them, so it passes. - return EFilterReturn::Passed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(Object->IsA(InClass)) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfMatches_ClassesSetIsAClass(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - // If a class is a child of any classes on this list, it will be allowed onto the list, unless it also appears on a disallowed list. - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const UObject* Object = *CurClassIt; - if(Object->IsA(UBlueprintGeneratedClass::StaticClass())) - { - return EFilterReturn::Passed; - } - } - - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const UClass* InClass) -{ - check(InClass); - - if(InSet.Num()) - { - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - if(InClass == *CurClassIt) - { - return EFilterReturn::Passed; - } - } - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -EFilterReturn::Type FClassViewerFilterFuncs::IfInClassesSet(TSet< const UClass* >& InSet, const TSharedPtr< const IUnloadedBlueprintData > InClass) -{ - check(InClass.IsValid()); - - if(InSet.Num()) - { - for( auto CurClassIt = InSet.CreateConstIterator(); CurClassIt; ++CurClassIt ) - { - const TSharedPtr UnloadedBlueprintData = StaticCastSharedPtr(InClass); - if(*UnloadedBlueprintData->GetClassViewerNode().Pin()->GetClassName() == (*CurClassIt)->GetName()) - { - return EFilterReturn::Passed; - } - } - return EFilterReturn::Failed; - } - - // Since there are none on this list, return that there is no items. - return EFilterReturn::NoItems; -} - -////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// // A hack to make IMPLEMENT_COMPARE_CONSTREF with a templated type @@ -430,9 +149,9 @@ public: private: /** Recursive function to build a tree, will not filter. * @param InOutRootNode The node that this function will add the children of to the tree. - * @param PackageNameToAssetDataMap The asset registry map of blueprint package names to blueprint data + * @param InOutClassPathToNode Map that will be populated with the class path to the corresponding class node. */ - void AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap ); + void AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, TMap>& InOutClassPathToNode ); /** Called when hot reload has finished */ void OnHotReload( bool bWasTriggeredAutomatically ); @@ -500,7 +219,6 @@ namespace ClassViewer static bool bPopulateClassHierarchy; // Pre-declare these functions. - static bool CheckIfBlueprintBase( TSharedPtr< FClassViewerNode> InNode ); static UBlueprint* GetBlueprint( UClass* InClass ); static void UpdateClassInNode(FName InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint ); @@ -526,24 +244,6 @@ namespace ClassViewer return bCanCreateBlueprintOfClass; } - /** Checks if a particular class is a brush. - * @param InClass The Class to check. - * @return Returns true if the class is a brush. - */ - static bool IsBrush(const UClass* InClass) - { - return InClass->IsChildOf( ABrush::StaticClass() ); - } - - /** Checks if a particular class is placeable. - * @param InClass The Class to check. - * @return Returns true if the class is placeable. - */ - static bool IsPlaceable(const UClass* InClass) - { - return !InClass->HasAnyClassFlags(CLASS_Abstract | CLASS_NotPlaceable) && InClass->IsChildOf(); - } - /** Checks if a particular class is abstract. * @param InClass The Class to check. * @return Returns true if the class is abstract. @@ -553,67 +253,6 @@ namespace ClassViewer return InClass->HasAnyClassFlags(CLASS_Abstract); } - /** Checks if the class is allowed under the init options of the class viewer currently building it's tree/list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. - * @param InClass The class to test against. - */ - static bool IsClassAllowed( const FClassViewerInitializationOptions& InInitOptions, const TWeakObjectPtr InClass ) - { - if(InInitOptions.ClassFilter.IsValid()) - { - return InInitOptions.ClassFilter->IsClassAllowed(InInitOptions, InClass.Get(), MakeShared()); - } - - return true; - } - - /** Checks if the unloaded class is allowed under the init options of the class viewer currently building it's tree/list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. - * @param InNode The node to pull the unloaded class from. - */ - bool IsClassAllowed_UnloadedBlueprint(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode > InNode ) - { - if(InInitOptions.ClassFilter.IsValid() && InNode->UnloadedBlueprintData.IsValid()) - { - return InInitOptions.ClassFilter->IsUnloadedClassAllowed(InInitOptions, InNode->UnloadedBlueprintData.ToSharedRef(), MakeShared()); - } - - return true; - } - - /** Checks if the TestString passes the filter. - * @param InTestString The string to test against the filter. - * @param InTextFilter Compiled text filter to apply. - * - * @return true if it passes the filter. - */ - static bool PassesFilter( const FString& InTestString, const FTextFilterExpressionEvaluator& InTextFilter ) - { - class FClassFilterContext : public ITextFilterExpressionContext - { - public: - explicit FClassFilterContext(const FString& InStr) - : StrPtr(&InStr) - { - } - - virtual bool TestBasicStringExpression(const FTextFilterString& InValue, const ETextFilterTextComparisonMode InTextComparisonMode) const override - { - return TextFilterUtils::TestBasicStringExpression(*StrPtr, InValue, InTextComparisonMode); - } - - virtual bool TestComplexExpression(const FName& InKey, const FTextFilterString& InValue, const ETextFilterComparisonOperation InComparisonOperation, const ETextFilterTextComparisonMode InTextComparisonMode) const override - { - return false; - } - - private: - const FString* StrPtr; - }; - - return InTextFilter.TestTextFilter(FClassFilterContext(InTestString)); - } - /** Will create the instance of FClassHierarchy and populate the class hierarchy tree. */ static void ConstructClassHierarchy() { @@ -660,319 +299,131 @@ namespace ClassViewer } /** Recursive function to build a tree, filtering out nodes based on the InitOptions and filter search terms. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node that this function will add the children of to the tree. * @param InRootClassIndex The index of the root node. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyActors Filter option to remove non-actor classes. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * * @return Returns true if the child passed the filter. */ - static bool AddChildren_Tree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, - const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyActors, - bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType, bool bInInternalClasses, - const TArray& InternalClasses, const TArray& InternalPaths) + static bool AddChildren_Tree(TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, + const TSharedPtr& InClassFilter, const FClassViewerInitializationOptions& InInitOptions) { - if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) + InOutRootNode->bPassesFilter = InClassFilter->IsNodeAllowed(InInitOptions, InOutRootNode.ToSharedRef()); + if (!InOutRootNode->bPassesFilter) { - InOutRootNode->bPassesFilter = false; return false; } - bool bChildrenPassesFilter(false); - bool bReturnPassesFilter(false); - - bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); - bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); - bool bPassesPlaceableFilter = false; - - // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. - bool bPassesDeveloperFilter = true; - static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); - static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); - FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); - - if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) - { - bPassesDeveloperFilter = !GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); - } - else if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) - { - if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) - bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); - } - - // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? - // INI path: /Script/ClassViewer.ClassViewerProjectSettings - bool bPassesInternalFilter = true; - if (!bInInternalClasses && InternalPaths.Num() > 0) - { - for (int i = 0; i < InternalPaths.Num(); i++) - { - if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) - { - bPassesInternalFilter = false; - break; - } - } - } - if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter && InOriginalRootNode->Class.IsValid()) - { - for (int i = 0; i < InternalClasses.Num(); i++) - { - if (InOriginalRootNode->Class->IsChildOf(InternalClasses[i])) - { - bPassesInternalFilter = false; - break; - } - } - } - - // When in picker mode, brushes are valid "placeable" actors - if (bInOnlyPlaceables && InInitOptions.Mode == EClassViewerMode::ClassPicker && IsBrush(InOriginalRootNode->Class.Get()) && IsPlaceable(InOriginalRootNode->Class.Get())) - { - bPassesPlaceableFilter = true; - } - else - { - bPassesPlaceableFilter = !bInOnlyPlaceables || InOriginalRootNode->IsClassPlaceable(); - } - - // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. - if(bIsUnloadedBlueprint) - { - if(bInShowUnloadedBlueprints) - { - bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - } - else - { - bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - + bool bReturnPassesFilter = false; TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList(); for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++) { TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *ChildList[ChildIdx].Get() ) ); - bReturnPassesFilter |= bChildrenPassesFilter = AddChildren_Tree(InInitOptions, NewNode, ChildList[ChildIdx], InTextFilter, false, /* bInOnlyActors - false so that anything below Actor is added */ - bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); - if(bChildrenPassesFilter) + bool bChildrenPassesFilter = AddChildren_Tree(NewNode, ChildList[ChildIdx], InClassFilter, InInitOptions); + bReturnPassesFilter |= bChildrenPassesFilter; + + if (bChildrenPassesFilter) { InOutRootNode->AddChild(NewNode); } - } return bReturnPassesFilter; } /** Builds the class tree. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node to root the tree to. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyActors Filter option to root the tree in the "Actor" class. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. + * * @return A fully built tree. */ - static void GetClassTree(const FClassViewerInitializationOptions& InInitOptions, TSharedPtr< FClassViewerNode >& InOutRootNode, - const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, - bool bInShowUnloadedBlueprints, EClassViewerDeveloperType InAllowedDeveloperType = EClassViewerDeveloperType::CVDT_All, bool bInInternalClasses = true, - const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) + static void GetClassTree(TSharedPtr< FClassViewerNode >& InOutRootNode, const TSharedPtr& InClassFilter, + const FClassViewerInitializationOptions& InInitOptions) { - const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); // Duplicate the node, it will have no children. InOutRootNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot)); - if(bInOnlyActors) + if (InInitOptions.bIsActorsOnly) { - for(int32 ClassIdx = 0; ClassIdx < ObjectClassRoot->GetChildrenList().Num(); ClassIdx++) + for (int32 ClassIdx = 0; ClassIdx < ObjectClassRoot->GetChildrenList().Num(); ClassIdx++) { TSharedPtr ChildNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot->GetChildrenList()[ClassIdx].Get())); - if (AddChildren_Tree(InInitOptions, ChildNode, ObjectClassRoot->GetChildrenList()[ClassIdx], InTextFilter, true, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths)) + if (AddChildren_Tree(ChildNode, ObjectClassRoot->GetChildrenList()[ClassIdx], InClassFilter, InInitOptions)) + { InOutRootNode->AddChild(ChildNode); + } } } else { - AddChildren_Tree(InInitOptions, InOutRootNode, ObjectClassRoot, InTextFilter, false, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_Tree(InOutRootNode, ObjectClassRoot, InClassFilter, InInitOptions); } } /** Recursive function to build the list, filtering out nodes based on the InitOptions and filter search terms. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutRootNode The node that this function will add the children of to the tree. * @param InRootClassIndex The index of the root node. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyActors Filter option to remove non-actor classes. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * * @return Returns true if the child passed the filter. */ - static void AddChildren_List(const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, - const TSharedPtr< FClassViewerNode >& InOriginalRootNode, const FTextFilterExpressionEvaluator& InTextFilter, - bool bInOnlyActors, bool bInOnlyPlaceables, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, - EClassViewerDeveloperType InAllowedDeveloperType, bool bInInternalClasses, - const TArray& InternalClasses, const TArray& InternalPaths) + static void AddChildren_List(TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr< FClassViewerNode >& InOriginalRootNode, + const TSharedPtr< FClassViewerFilter >& InClassFilter, const FClassViewerInitializationOptions& InInitOptions) { - if (bInOnlyActors && *InOriginalRootNode->GetClassName().Get() != FString(TEXT("Actor"))) + if (InClassFilter->IsNodeAllowed(InInitOptions, InOriginalRootNode.ToSharedRef())) { - return; - } + TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(*InOriginalRootNode.Get())); + NewNode->bPassesFilter = true; + NewNode->PropertyHandle = InOriginalRootNode->PropertyHandle; - bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); - bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); - bool bPassesPlaceableFilter = false; - const bool bPassesEditorClassTest = !InInitOptions.bEditorClassesOnly || InOriginalRootNode->IsEditorOnlyClass(); - - // Determine if we allow any developer folder classes, if so determine if this class is in one of the allowed developer folders. - bool bPassesDeveloperFilter = true; - static const FString DeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameDevelopersDir()); - static const FString UserDeveloperPathWithSlash = FPackageName::FilenameToLongPackageName(FPaths::GameUserDeveloperDir()); - FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); - if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_None) - { - bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(DeveloperPathWithSlash); - } - else if (InAllowedDeveloperType == EClassViewerDeveloperType::CVDT_CurrentUser) - { - if (GeneratedClassPathString.StartsWith(DeveloperPathWithSlash)) - bPassesDeveloperFilter = GeneratedClassPathString.StartsWith(UserDeveloperPathWithSlash); - } - - // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? - // INI path: /Script/ClassViewer.ClassViewerProjectSettings - bool bPassesInternalFilter = true; - if (!bInInternalClasses && InternalPaths.Num() > 0) - { - for (int i = 0; i < InternalPaths.Num(); i++) - { - if (GeneratedClassPathString.StartsWith(InternalPaths[i].Path)) - { - bPassesInternalFilter = false; - break; - } - } - } - if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter) - { - for (int i = 0; i < InternalClasses.Num(); i++) - { - if (InOriginalRootNode->Class->IsChildOf(InternalClasses[i])) - { - bPassesInternalFilter = false; - break; - } - } - } - - // When in picker mode, brushes are valid "placeable" actors - if( bInOnlyPlaceables && InInitOptions.Mode == EClassViewerMode::ClassPicker && IsBrush(InOriginalRootNode->Class.Get()) && IsPlaceable(InOriginalRootNode->Class.Get())) - { - bPassesPlaceableFilter = true; - } - else - { - bPassesPlaceableFilter = !bInOnlyPlaceables || InOriginalRootNode->IsClassPlaceable(); - } - - TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *InOriginalRootNode.Get() ) ); - - // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. - if(bIsUnloadedBlueprint) - { - if(bInShowUnloadedBlueprints) - { - NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassTest - && IsClassAllowed_UnloadedBlueprint(InInitOptions, InOriginalRootNode) - && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - } - else - { - NewNode->bPassesFilter = bPassesPlaceableFilter && bPassesBlueprintBaseFilter && bPassesDeveloperFilter && bPassesInternalFilter && bPassesEditorClassTest - && IsClassAllowed(InInitOptions, InOriginalRootNode->Class) - && PassesFilter(*InOriginalRootNode->GetClassName().Get(), InTextFilter); - } - - if(NewNode->bPassesFilter) - { InOutNodeList.Add(NewNode); } - NewNode->PropertyHandle = InInitOptions.PropertyHandle; - - TArray< TSharedPtr< FClassViewerNode > >& ChildList = InOriginalRootNode->GetChildrenList(); - for(int32 ChildIdx = 0; ChildIdx < ChildList.Num(); ChildIdx++) + for(const TSharedPtr& ChildNode : InOriginalRootNode->GetChildrenList()) { - AddChildren_List(InInitOptions, InOutNodeList, ChildList[ChildIdx], InTextFilter, false, /* bInOnlyActors - false so that anything below Actor is added */ - bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); + FClassViewerInitializationOptions TempOptions = InInitOptions; + + // set bOnlyActors to false so that anything below Actor is added + TempOptions.bIsActorsOnly = false; + AddChildren_List(InOutNodeList, ChildNode, InClassFilter, InInitOptions); } } /** Builds the class list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * @param InOutNodeList The list to add all the nodes to. - * @param InTextFilter Compiled text filter to apply. - * @param bInOnlyPlaceables Filter option to remove non-placeable classes. - * @param bInOnlyActors Filter option to root the tree in the "Actor" class. - * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. - * @param InAllowedDeveloperType Filter option for dealing with developer folders. - * @param bInInternalClasses Filter option for showing internal classes. - * @param InternalClasses The classes that have been marked as Internal Only. - * @param InternalPaths The paths that have been marked Internal Only. + * @param InClassFilter The class filter to use to filter nodes. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. * * @return A fully built list. */ - static void GetClassList(const FClassViewerInitializationOptions& InInitOptions, TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, - const FTextFilterExpressionEvaluator& InTextFilter, bool bInOnlyPlaceables, bool bInOnlyActors, bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, - EClassViewerDeveloperType InAllowedDeveloperType = EClassViewerDeveloperType::CVDT_All, bool bInInternalClasses = true, - const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) + static void GetClassList(TArray< TSharedPtr< FClassViewerNode > >& InOutNodeList, const TSharedPtr& InClassFilter, + const FClassViewerInitializationOptions& InInitOptions) { const TSharedPtr< FClassViewerNode > ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); // If the option to see the object root class is set, add it to the list, proceed normally from there so the actor's only filter continues to work. - if(InInitOptions.bShowObjectRootClass) + if (InInitOptions.bShowObjectRootClass) { - TSharedPtr< FClassViewerNode > NewNode = MakeShareable( new FClassViewerNode( *ObjectClassRoot.Get() ) ); - NewNode->bPassesFilter = IsClassAllowed(InInitOptions, ObjectClassRoot->Class) && PassesFilter(*ObjectClassRoot->GetClassName().Get(), InTextFilter); - - if(NewNode->bPassesFilter) + if (InClassFilter->IsNodeAllowed(InInitOptions, ObjectClassRoot.ToSharedRef())) { + TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(*ObjectClassRoot.Get())); + NewNode->bPassesFilter = true; + NewNode->PropertyHandle = InInitOptions.PropertyHandle; + InOutNodeList.Add(NewNode); } - - NewNode->PropertyHandle = InInitOptions.PropertyHandle; } - TArray< TSharedPtr< FClassViewerNode > >& ChildList = ObjectClassRoot->GetChildrenList(); - for(int32 ObjectChildIndex = 0; ObjectChildIndex < ChildList.Num(); ObjectChildIndex++) + for(const TSharedPtr& ChildNode : ObjectClassRoot->GetChildrenList()) { - AddChildren_List(InInitOptions, InOutNodeList, ChildList[ObjectChildIndex], InTextFilter, bInOnlyActors, bInOnlyPlaceables, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, InAllowedDeveloperType, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_List(InOutNodeList, ChildNode, InClassFilter, InInitOptions); } } @@ -1012,29 +463,6 @@ namespace ClassViewer } } - /** Checks if a node is a blueprint base or not. - * @param InNode The node to check if it is a blueprint base. - * - * @return true if the class is a blueprint. - */ - static bool CheckIfBlueprintBase( TSharedPtr< FClassViewerNode> InNode ) - { - // If there is no class, it may be an unloaded blueprint. - if(UClass* Class = InNode->Class.Get()) - { - return CanCreateBlueprintOfClass_IgnoreDeprecation(Class); - } - else if(InNode->bIsBPNormalType) - { - bool bAllowDerivedBlueprints = false; - GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni); - - return bAllowDerivedBlueprints; - } - - return false; - } - /** * Creates a blueprint from a class. * @@ -1616,27 +1044,12 @@ FClassHierarchy::~FClassHierarchy() FModuleManager::Get().OnModulesChanged().RemoveAll(this); } -static TSharedPtr< FClassViewerNode > CreateNodeForClass(UClass* Class, const TMultiMap& BlueprintPackageToAssetDataMap) -{ - // Create the new node so it can be passed to AddChildren, fill it in with if it is placeable, abstract, and/or a brush. - TSharedPtr< FClassViewerNode > NewNode = MakeShareable(new FClassViewerNode(Class->GetName(), Class->GetDisplayNameText().ToString())); - NewNode->Blueprint = ClassViewer::Helpers::GetBlueprint(Class); - NewNode->Class = Class; - NewNode->ClassPath = FName(*Class->GetPathName()); - if (Class->GetSuperClass()) - { - NewNode->ParentClassPath = FName(*Class->GetSuperClass()->GetPathName()); - } - - return NewNode; -} - void FClassHierarchy::OnHotReload( bool bWasTriggeredAutomatically ) { ClassViewer::Helpers::RequestPopulateClassHierarchy(); } -void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap ) +void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOutRootNode, TMap>& InOutClassPathToNode ) { UClass* RootClass = UObject::StaticClass(); @@ -1674,13 +1087,15 @@ void FClassHierarchy::AddChildren_NoFilter( TSharedPtr< FClassViewerNode >& InOu TSharedPtr& ParentEntry = Nodes.FindOrAdd(CurrentClass->GetSuperClass()); if ( !ParentEntry.IsValid() ) { - ParentEntry = CreateNodeForClass(CurrentClass->GetSuperClass(), BlueprintPackageToAssetDataMap); + ParentEntry = MakeShareable(new FClassViewerNode(CurrentClass->GetSuperClass())); + InOutClassPathToNode.Add(ParentEntry->ClassPath, ParentEntry); } TSharedPtr& MyEntry = Nodes.FindOrAdd(CurrentClass); if ( !MyEntry.IsValid() ) { - MyEntry = CreateNodeForClass(CurrentClass, BlueprintPackageToAssetDataMap); + MyEntry = MakeShareable(new FClassViewerNode(CurrentClass)); + InOutClassPathToNode.Add(MyEntry->ClassPath, MyEntry); } if ( !Visited.Contains(CurrentClass) ) @@ -1929,12 +1344,13 @@ void FClassHierarchy::LoadUnloadedTagData(TSharedPtr& InOutCla InOutClassViewerNode->ParentClassPath = FName(*FPackageName::ExportTextPathToObjectPath(ParentClassPathString)); } - InOutClassViewerNode->bIsBPNormalType = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); - // It is an unloaded blueprint, so we need to create the structure that will hold the data. TSharedPtr UnloadedBlueprintData = MakeShareable( new FUnloadedBlueprintData(InOutClassViewerNode) ); InOutClassViewerNode->UnloadedBlueprintData = UnloadedBlueprintData; + const bool bNormalBlueprintType = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); + InOutClassViewerNode->UnloadedBlueprintData->SetNormalBlueprintType( bNormalBlueprintType ); + // Get the class flags. const uint32 ClassFlags = InAssetData.GetTagValueRef(FBlueprintTags::ClassFlags); InOutClassViewerNode->UnloadedBlueprintData->SetClassFlags(ClassFlags); @@ -1983,21 +1399,21 @@ void FClassHierarchy::PopulateClassHierarchy() Filter.bRecursiveClasses = true; AssetRegistryModule.Get().GetAssets(Filter, BlueprintList); - TMultiMap BlueprintPackageToAssetDataMap; + TMap> ClassPathToNode; for ( int32 AssetIdx = 0; AssetIdx < BlueprintList.Num(); ++AssetIdx ) { TSharedPtr< FClassViewerNode > NewNode; LoadUnloadedTagData(NewNode, BlueprintList[AssetIdx]); RootLevelClasses.Add(NewNode); + check(!NewNode->GetChildrenList().Num()); + ClassPathToNode.Add(NewNode->ClassPath, NewNode); + // Find the blueprint if it's loaded. FindClass(NewNode); - - - BlueprintPackageToAssetDataMap.Add(BlueprintList[AssetIdx].PackageName, BlueprintList[AssetIdx]); } - AddChildren_NoFilter(ObjectClassRoot, BlueprintPackageToAssetDataMap); + AddChildren_NoFilter(ObjectClassRoot, ClassPathToNode); RootLevelClasses.Add(ObjectClassRoot); @@ -2009,18 +1425,15 @@ void FClassHierarchy::PopulateClassHierarchy() // Resolve the parent's class name locally and use it to find the parent's class. FString ParentClassPath = RootLevelClasses[CurrentNodeIdx]->ParentClassPath.ToString(); const UClass* ParentClass = FindObject(nullptr, *ParentClassPath); + TSharedPtr< FClassViewerNode > ParentNode; - for (int32 SearchNodeIdx = 0; SearchNodeIdx < RootLevelClasses.Num(); ++SearchNodeIdx) + if (TSharedPtr< FClassViewerNode >* ParentNodePtr = ClassPathToNode.Find(RootLevelClasses[CurrentNodeIdx]->ParentClassPath)) { - TSharedPtr< FClassViewerNode > ParentNode = FindParent(RootLevelClasses[SearchNodeIdx], RootLevelClasses[CurrentNodeIdx]->ParentClassPath, ParentClass); - if(ParentNode.IsValid()) - { - // AddUniqueChild makes sure that when a node was generated one by EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected - ParentNode->AddUniqueChild(RootLevelClasses[CurrentNodeIdx]); - RootLevelClasses.RemoveAtSwap(CurrentNodeIdx); - --CurrentNodeIdx; - break; - } + // AddUniqueChild makes sure that when a node was generated one by EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected + ParentNode = *ParentNodePtr; + ParentNode->AddUniqueChild(RootLevelClasses[CurrentNodeIdx]); + RootLevelClasses.RemoveAtSwap(CurrentNodeIdx); + --CurrentNodeIdx; } } @@ -2039,8 +1452,6 @@ void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitial bNeedsRefresh = true; NumClasses = 0; - bCanShowInternalClasses = true; - // Listen for when view settings are changed UClassViewerSettings::OnSettingChanged().AddSP(this, &SClassViewer::HandleSettingChanged); @@ -2048,22 +1459,15 @@ void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitial OnClassPicked = InArgs._OnClassPickedDelegate; - TextFilterPtr = MakeShareable(new FTextFilterExpressionEvaluator(ETextFilterExpressionEvaluatorMode::BasicString)); - bSaveExpansionStates = true; bPendingSetExpansionStates = false; + ClassFilter = MakeShareable(new FClassViewerFilter(InitOptions)); + bEnableClassDynamicLoading = InInitOptions.bEnableClassDynamicLoading; EVisibility HeaderVisibility = (this->InitOptions.Mode == EClassViewerMode::ClassBrowsing)? EVisibility::Visible : EVisibility::Collapsed; - // Set these values to the user specified settings. - bIsActorsOnly = InitOptions.bIsActorsOnly | InitOptions.bIsPlaceableOnly; - bIsPlaceableOnly = InitOptions.bIsPlaceableOnly; - bIsBlueprintBaseOnly = InitOptions.bIsBlueprintBaseOnly; - bShowUnloadedBlueprints = InitOptions.bShowUnloadedBlueprints; - bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false; - // If set to default, decide what display mode to use. if( InitOptions.DisplayMode == EClassViewerDisplayMode::DefaultView ) { @@ -2167,6 +1571,8 @@ void SClassViewer::Construct(const FArguments& InArgs, const FClassViewerInitial ); TSharedRef > > ClassTreeView = ClassTree.ToSharedRef(); TSharedRef > > ClassListView = ClassList.ToSharedRef(); + + bool bHasTitle = InitOptions.ViewerTitleString.IsEmpty() == false; // Holds the bulk of the class viewer's sub-widgets, to be added to the widget after construction TSharedPtr< SWidget > ClassViewerContent; @@ -2616,7 +2022,6 @@ EClassViewerDeveloperType SClassViewer::GetCurrentDeveloperViewType() const { return EClassViewerDeveloperType::CVDT_All; } - return GetDefault()->DeveloperFolderType; } @@ -2631,7 +2036,6 @@ void SClassViewer::GetInternalOnlyClasses(TArray& Classes) { return; } - Classes = GetDefault()->InternalOnlyClasses; } @@ -2644,7 +2048,6 @@ void SClassViewer::GetInternalOnlyPaths(TArray& Paths) Paths = GetDefault()->InternalOnlyPaths; } - FText SClassViewer::GetClassCountText() const { @@ -2743,8 +2146,8 @@ void SClassViewer::FindInContentBrowser() void SClassViewer::OnFilterTextChanged( const FText& InFilterText ) { // Update the compiled filter and report any syntax error information back to the user - TextFilterPtr->SetFilterText(InFilterText); - SearchBox->SetError(TextFilterPtr->GetFilterErrorText()); + ClassFilter->TextFilter->SetFilterText(InFilterText); + SearchBox->SetError(ClassFilter->TextFilter->GetFilterErrorText()); // Repopulate the list to show only what has not been filtered out. Refresh(); @@ -2790,12 +2193,12 @@ bool SClassViewer::Menu_CanExecute() const void SClassViewer::MenuActorsOnly_Execute() { - bIsActorsOnly = !bIsActorsOnly; + InitOptions.bIsActorsOnly = !InitOptions.bIsActorsOnly; // "Placeable Only" cannot be true when "Actors Only" is false. - if(!bIsActorsOnly) + if (!InitOptions.bIsActorsOnly) { - bIsPlaceableOnly = false; + InitOptions.bIsPlaceableOnly = false; } Refresh(); @@ -2803,17 +2206,17 @@ void SClassViewer::MenuActorsOnly_Execute() bool SClassViewer::MenuActorsOnly_IsChecked() const { - return bIsActorsOnly; + return InitOptions.bIsActorsOnly; } void SClassViewer::MenuPlaceableOnly_Execute() { - bIsPlaceableOnly = !bIsPlaceableOnly; + InitOptions.bIsPlaceableOnly = !InitOptions.bIsPlaceableOnly; // "Actors Only" must be true when "Placeable Only" is true. - if(bIsPlaceableOnly) + if (InitOptions.bIsPlaceableOnly) { - bIsActorsOnly = true; + InitOptions.bIsActorsOnly = true; } Refresh(); @@ -2821,19 +2224,19 @@ void SClassViewer::MenuPlaceableOnly_Execute() bool SClassViewer::MenuPlaceableOnly_IsChecked() const { - return bIsPlaceableOnly; + return InitOptions.bIsPlaceableOnly; } void SClassViewer::MenuBlueprintBasesOnly_Execute() { - bIsBlueprintBaseOnly = !bIsBlueprintBaseOnly; + InitOptions.bIsBlueprintBaseOnly = !InitOptions.bIsBlueprintBaseOnly; Refresh(); } bool SClassViewer::MenuBlueprintBasesOnly_IsChecked() const { - return bIsBlueprintBaseOnly; + return InitOptions.bIsBlueprintBaseOnly; } TSharedRef SClassViewer::FillFilterEntries() @@ -2975,15 +2378,11 @@ void SClassViewer::Populate() // Empty the tree out so it can be redone. RootTreeItems.Empty(); - bool ShowingInternalClasses = IsShowingInternalClasses(); - TArray InternalClassNames; - TArray InternalClasses; - TArray InternalPaths; // If we aren't showing the internal classes, then we need to know what classes to consider Internal Only, so let's gather them up from the settings object. - if (!ShowingInternalClasses) + if (!IsShowingInternalClasses()) { - GetInternalOnlyPaths(InternalPaths); + GetInternalOnlyPaths(ClassFilter->InternalPaths); GetInternalOnlyClasses(InternalClassNames); // Take the package names for the internal only classes and convert them into their UClass @@ -2994,11 +2393,13 @@ void SClassViewer::Populate() if (ClassNode.IsValid()) { - InternalClasses.Add(ClassNode->Class.Get()); + ClassFilter->InternalClasses.Add(ClassNode->Class.Get()); } } } + + // Based on if the list or tree is visible we create what will be displayed differently. if(InitOptions.DisplayMode == EClassViewerDisplayMode::TreeView) { @@ -3006,11 +2407,10 @@ void SClassViewer::Populate() TSharedPtr RootNode; // Get the class tree, passing in certain filter options. - ClassViewer::Helpers::GetClassTree(InitOptions, RootNode, *TextFilterPtr, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), - bShowUnloadedBlueprints, GetCurrentDeveloperViewType(), ShowingInternalClasses, InternalClasses, InternalPaths); + ClassViewer::Helpers::GetClassTree(RootNode, ClassFilter, InitOptions); // Check if we will restore expansion states, we will not if there is filtering happening. - const bool bRestoreExpansionState = TextFilterPtr->GetFilterType() == ETextFilterExpressionType::Empty; + const bool bRestoreExpansionState = ClassFilter->TextFilter->GetFilterType() == ETextFilterExpressionType::Empty; if(InitOptions.bShowObjectRootClass) { @@ -3022,7 +2422,7 @@ void SClassViewer::Populate() } // Expand any items that pass the filter. - if(TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty) + if(ClassFilter->TextFilter->GetFilterType() != ETextFilterExpressionType::Empty) { ExpandFilteredInNodes(RootNode); } @@ -3039,7 +2439,7 @@ void SClassViewer::Populate() } // Expand any items that pass the filter. - if(TextFilterPtr->GetFilterType() != ETextFilterExpressionType::Empty) + if(ClassFilter->TextFilter->GetFilterType() != ETextFilterExpressionType::Empty) { ExpandFilteredInNodes(RootNode->GetChildrenList()[ChildIndex]); } @@ -3064,8 +2464,7 @@ void SClassViewer::Populate() else { // Get the class list, passing in certain filter options. - ClassViewer::Helpers::GetClassList(InitOptions, RootTreeItems, *TextFilterPtr, MenuPlaceableOnly_IsChecked(), MenuActorsOnly_IsChecked(), MenuBlueprintBasesOnly_IsChecked(), - bShowUnloadedBlueprints, GetCurrentDeveloperViewType(), ShowingInternalClasses, InternalClasses, InternalPaths); + ClassViewer::Helpers::GetClassList(RootTreeItems, ClassFilter, InitOptions); // Sort the list alphabetically. struct FCompareFClassViewerNode @@ -3180,7 +2579,7 @@ void SClassViewer::Tick( const FGeometry& AllottedGeometry, const double InCurre bool SClassViewer::IsClassAllowed(const UClass* InClass) const { - return ClassViewer::Helpers::IsClassAllowed(InitOptions, InClass); + return ClassFilter->IsClassAllowed(InitOptions, InClass, ClassFilter->FilterFunctions); } void SClassViewer::HandleSettingChanged(FName PropertyName) @@ -3202,7 +2601,7 @@ void SClassViewer::ToggleShowInternalClasses() bool SClassViewer::IsToggleShowInternalClassesAllowed() const { - return bCanShowInternalClasses; + return InitOptions.bAllowViewOptions; } bool SClassViewer::IsShowingInternalClasses() const diff --git a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h index 32c06619848e..48b8c9262872 100644 --- a/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h +++ b/Engine/Source/Editor/ClassViewer/Private/SClassViewer.h @@ -21,8 +21,7 @@ class FMenuBuilder; class FTextFilterExpressionEvaluator; class UBlueprint; class SComboButton; -class FClassViewerNode; -class FTextFilterExpressionEvaluator; +class FClassViewerFilter; ////////////////////////////////////////////////////////////////////////// // SClassViewer @@ -226,12 +225,12 @@ private: /** Init options, cached */ FClassViewerInitializationOptions InitOptions; + /** Filter to use to determine what classes are valid. */ + TSharedPtr ClassFilter; + /** The items to be displayed in the tree. */ TArray< TSharedPtr< FClassViewerNode > > RootTreeItems; - /** Compiled filter search terms. */ - TSharedPtr TextFilterPtr; - /** Holds the Slate Tree widget which holds the classes for the Class Viewer. */ TSharedPtr >> ClassTree; @@ -241,18 +240,6 @@ private: /** The Class Search Box, used for filtering the classes visible. */ TSharedPtr SearchBox; - /** true to filter for Actors only. */ - bool bIsActorsOnly; - - /** true to filter for Placeable classes only. */ - bool bIsPlaceableOnly; - - /** true to filter for Blueprint Base classes only. */ - bool bIsBlueprintBaseOnly; - - /** true to filter for unloaded Blueprint classes. */ - bool bShowUnloadedBlueprints; - /** true to allow class dynamic loading. */ bool bEnableClassDynamicLoading; @@ -283,9 +270,6 @@ private: /** True if we need to set the tree expansion states according to our local copy next tick */ bool bPendingSetExpansionStates; - /** Indicates if the 'Show Internal Classes' option should be enabled or disabled */ - bool bCanShowInternalClasses; - /** The button that displays view options */ TSharedPtr ViewOptionsComboButton; diff --git a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp index 99942f726052..21b10344f580 100644 --- a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp +++ b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.cpp @@ -116,6 +116,25 @@ const UClass* FUnloadedBlueprintData::GetNativeParent() const return nullptr; } +TSharedPtr FUnloadedBlueprintData::GetClassName() const +{ + if (ClassViewerNode.IsValid()) + { + return ClassViewerNode.Pin()->GetClassName(); + } + + return TSharedPtr(); +} + +FName FUnloadedBlueprintData::GetClassPath() const +{ + if (ClassViewerNode.IsValid()) + { + return ClassViewerNode.Pin()->ClassPath; + } + + return FName(); +} const TWeakPtr& FUnloadedBlueprintData::GetClassViewerNode() const { diff --git a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h index f103335bbd11..241bbde47efc 100644 --- a/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h +++ b/Engine/Source/Editor/ClassViewer/Private/UnloadedBlueprintData.h @@ -12,9 +12,9 @@ public: virtual ~FUnloadedBlueprintData() {} - virtual bool HasAnyClassFlags( uint32 InFlagsToCheck ) const override; + virtual bool HasAnyClassFlags(uint32 InFlagsToCheck) const override; - virtual bool HasAllClassFlags( uint32 InFlagsToCheck ) const override; + virtual bool HasAllClassFlags(uint32 InFlagsToCheck) const override; virtual void SetClassFlags(uint32 InFlags) override; @@ -24,6 +24,14 @@ public: virtual bool IsA(const UClass* InClass) const override; + virtual void SetNormalBlueprintType(bool bInNormalBPType) override { bNormalBlueprintType = bInNormalBPType; } + + virtual bool IsNormalBlueprintType() const override { return bNormalBlueprintType; } + + virtual TSharedPtr GetClassName() const override; + + virtual FName GetClassPath() const override; + virtual const UClass* GetClassWithin() const override; virtual const UClass* GetNativeParent() const override; @@ -38,6 +46,9 @@ private: /** Flags for the class. */ uint32 ClassFlags = CLASS_None; + /** Is this a normal blueprint type? */ + bool bNormalBlueprintType; + /** The implemented interfaces for this class. */ TArray ImplementedInterfaces; diff --git a/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h b/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h index 45dda6d94f3b..f890e1fde641 100644 --- a/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h +++ b/Engine/Source/Editor/ClassViewer/Public/ClassViewerFilter.h @@ -2,8 +2,12 @@ #pragma once #include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "Settings/ClassViewerSettings.h" +class FClassViewerNode; class FClassViewerInitializationOptions; +class FTextFilterExpressionEvaluator; /** Interface class for creating filters for the Class Viewer. */ class IClassViewerFilter @@ -30,6 +34,24 @@ public: virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const class IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs) = 0; }; +/** Filter class that performs many common checks. */ +class FClassViewerFilter : public IClassViewerFilter +{ +public: + FClassViewerFilter(const FClassViewerInitializationOptions& InInitOptions); + + virtual bool IsNodeAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef& Node); + + virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs ) override; + virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const class IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< class FClassViewerFilterFuncs > InFilterFuncs) override; + + TArray InternalClasses; + TArray InternalPaths; + + TSharedRef TextFilter; + TSharedRef FilterFunctions; +}; + namespace EFilterReturn { enum Type{ Failed = 0, Passed, NoItems }; @@ -164,6 +186,7 @@ public: class IUnloadedBlueprintData { public: + /** * Used to safely check whether the passed in flag is set. * @@ -227,4 +250,24 @@ public: * @return The child-most Native class in the hierarchy. */ virtual const UClass* GetNativeParent() const = 0; + + /** + * Set whether or not this blueprint is a normal blueprint. + */ + virtual void SetNormalBlueprintType(bool bInNormalBPType) = 0; + + /** + * Get whether or not this blueprint is a normal blueprint. + */ + virtual bool IsNormalBlueprintType() const = 0; + + /** + * Get the generated class name of this blueprint. + */ + virtual TSharedPtr GetClassName() const = 0; + + /** + * Get the class path of this blueprint. + */ + virtual FName GetClassPath() const = 0; }; diff --git a/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h b/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h index 0ee1d7c615f1..e80650a89f4d 100644 --- a/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h +++ b/Engine/Source/Editor/ClassViewer/Public/ClassViewerModule.h @@ -7,6 +7,7 @@ class IClassViewerFilter; class IPropertyHandle; +class FClassViewerFilterFuncs; /** Delegate used with the Class Viewer in 'class picking' mode. You'll bind a delegate when the class viewer widget is created, which will be fired off when a class is selected in the list */ @@ -163,4 +164,10 @@ public: virtual TSharedRef CreateClassViewer(const FClassViewerInitializationOptions& InitOptions, const FOnClassPicked& OnClassPickedDelegate ); + /** + * Create a new class filter from the given initialization options. + */ + virtual TSharedRef CreateClassFilter(const FClassViewerInitializationOptions& InitOptions); + + virtual TSharedRef CreateFilterFuncs(); }; diff --git a/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp b/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp index 7d6229c6d79b..5a0c544e07dd 100644 --- a/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp +++ b/Engine/Source/Editor/ClothPainter/Private/ClothPaintingModule.cpp @@ -175,7 +175,7 @@ TSharedPtr FClothPaintingModule::GetActiveClothTab(TWeakPtrCanSpawnTab(FClothPaintTabSummoner::TabName)) + if(TabManager->HasTabSpawner(FClothPaintTabSummoner::TabName)) { TSharedPtr Tab = TabManager->FindExistingLiveTab(FTabId(FClothPaintTabSummoner::TabName)); diff --git a/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp b/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp index 91dbf6dcab7a..8c9c08bafacb 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/AssetViewWidgets.cpp @@ -46,7 +46,9 @@ FReply FAssetViewModeUtils::OnViewModeKeyDown( const TSet< TSharedPtr >& SelectedItems, const FKeyEvent& InKeyEvent ) { // All asset views use Ctrl-C to copy references to assets - if ( InKeyEvent.IsControlDown() && InKeyEvent.GetCharacter() == 'C' ) + if ( InKeyEvent.IsControlDown() && InKeyEvent.GetCharacter() == 'C' + && !InKeyEvent.IsShiftDown() && !InKeyEvent.IsAltDown() + ) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); diff --git a/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp b/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp index d78db41d16a0..e7603ad64f78 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/PathContextMenu.cpp @@ -107,7 +107,7 @@ TSharedRef FPathContextMenu::MakePathViewContextMenuExtender(const TA { if (MenuExtenderDelegates[i].IsBound()) { - Extenders.Add(MenuExtenderDelegates[i].Execute( SelectedPaths )); + Extenders.Add(MenuExtenderDelegates[i].Execute( InSelectedPaths )); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); diff --git a/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp b/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp index 3fa0dc700857..08aaa6e916f3 100644 --- a/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp +++ b/Engine/Source/Editor/CurveEditor/Private/Tree/SCurveEditorTree.cpp @@ -23,7 +23,8 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs; using SetKeyFuncs = DefaultKeyFuncs; - static void AddReferencedObjects(FReferenceCollector&, TArray&, TSet&) {} + template + static void AddReferencedObjects(FReferenceCollector&, TArray&, TSet&, TMap< const U*, FCurveEditorTreeItemID >&) {} static bool IsPtrValid(NullableType InPtr) { diff --git a/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp b/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp index ea3881fe604d..180541368ab3 100644 --- a/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp +++ b/Engine/Source/Editor/CurveTableEditor/Private/CurveTableEditor.cpp @@ -274,11 +274,11 @@ TSharedRef FCurveTableEditor::SpawnTab_CurveTable( const FSpawnTabArgs TSharedRef HorizontalScrollBar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef VerticalScrollBar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef RowNamesHeaderRow = SNew(SHeaderRow) .Visibility(this, &FCurveTableEditor::GetGridViewControlsVisibility); diff --git a/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs b/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs index efa66c48f35a..1214cf965950 100644 --- a/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs +++ b/Engine/Source/Editor/DataTableEditor/DataTableEditor.Build.cs @@ -12,6 +12,7 @@ public class DataTableEditor : ModuleRules new string[] { "Core", "CoreUObject", + "ApplicationCore", "Engine", "InputCore", "Slate", diff --git a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp index 0771140be496..972c3c5f77c4 100644 --- a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp +++ b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp @@ -7,11 +7,17 @@ #include "EditorStyleSet.h" #include "Fonts/FontMeasure.h" #include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/GenericCommands.h" #include "Framework/Layout/Overscroll.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Framework/Notifications/NotificationManager.h" +#include "HAL/PlatformApplicationMisc.h" #include "IDocumentation.h" +#include "Misc/FeedbackContext.h" #include "Misc/FileHelper.h" #include "Modules/ModuleManager.h" #include "Policies/PrettyJsonPrintPolicy.h" +#include "ScopedTransaction.h" #include "SDataTableListViewRowName.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" @@ -25,8 +31,13 @@ #include "Widgets/Input/SButton.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SHyperlink.h" +#include "Widgets/Notifications/SNotificationList.h" #include "SourceCodeNavigation.h" #include "PropertyEditorModule.h" +#include "UObject/StructOnScope.h" + + + #define LOCTEXT_NAMESPACE "DataTableEditor" @@ -262,7 +273,10 @@ void FDataTableEditor::InitDataTableEditor( const EToolkitMode::Type Mode, const SpawnToolkitTab( DataTableTabId, TabInitializationPayload, EToolkitTabSpot::Details ); }*/ - // NOTE: Could fill in asset editor commands here! + // asset editor commands here + ToolkitCommands->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateSP(this, &FDataTableEditor::CopySelectedRow)); + ToolkitCommands->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateSP(this, &FDataTableEditor::PasteOnSelectedRow)); + ToolkitCommands->MapAction(FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &FDataTableEditor::DuplicateSelectedRow)); } FName FDataTableEditor::GetToolkitFName() const @@ -475,6 +489,65 @@ void FDataTableEditor::OnRowSelectionChanged(FDataTableEditorRowListViewDataPtr } } +void FDataTableEditor::CopySelectedRow() +{ + UDataTable* TablePtr = Cast(GetEditingObject()); + uint8* RowPtr = TablePtr ? TablePtr->GetRowMap().FindRef(HighlightedRowName) : nullptr; + + if (!RowPtr || !TablePtr->RowStruct) + return; + + FString ClipboardValue; + TablePtr->RowStruct->ExportText(ClipboardValue, RowPtr, RowPtr, TablePtr, PPF_Copy, nullptr); + + FPlatformApplicationMisc::ClipboardCopy(*ClipboardValue); +} + +void FDataTableEditor::PasteOnSelectedRow() +{ + UDataTable* TablePtr = Cast(GetEditingObject()); + uint8* RowPtr = TablePtr ? TablePtr->GetRowMap().FindRef(HighlightedRowName) : nullptr; + + if (!RowPtr || !TablePtr->RowStruct) + return; + + const FScopedTransaction Transaction(LOCTEXT("PasteDataTableRow", "Paste Data Table Row")); + TablePtr->Modify(); + + FString ClipboardValue; + FPlatformApplicationMisc::ClipboardPaste(ClipboardValue); + + FDataTableEditorUtils::BroadcastPreChange(TablePtr, FDataTableEditorUtils::EDataTableChangeInfo::RowData); + + const TCHAR* Result = TablePtr->RowStruct->ImportText(*ClipboardValue, RowPtr, TablePtr, PPF_Copy, GWarn, GetPathNameSafe(TablePtr->RowStruct)); + + FDataTableEditorUtils::BroadcastPostChange(TablePtr, FDataTableEditorUtils::EDataTableChangeInfo::RowData); + + if (Result == nullptr) + { + FNotificationInfo Info(LOCTEXT("FailedPaste", "Failed to paste row")); + FSlateNotificationManager::Get().AddNotification(Info); + } +} + +void FDataTableEditor::DuplicateSelectedRow() +{ + UDataTable* TablePtr = Cast(GetEditingObject()); + FName NewName = HighlightedRowName; + + if (NewName == NAME_None || TablePtr == nullptr) + return; + + const TArray ExistingNames = TablePtr->GetRowNames(); + while (ExistingNames.Contains(NewName)) + { + NewName.SetNumber(NewName.GetNumber() + 1); + } + + FDataTableEditorUtils::DuplicateRow(TablePtr, HighlightedRowName, NewName); + FDataTableEditorUtils::SelectRow(TablePtr, NewName); +} + FText FDataTableEditor::GetFilterText() const { return ActiveFilterText; @@ -733,11 +806,11 @@ TSharedRef FDataTableEditor::CreateContentBox() { TSharedRef HorizontalScrollBar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef VerticalScrollBar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(8.0f, 8.0f)); + .Thickness(FVector2D(12.0f, 12.0f)); TSharedRef RowNamesHeaderRow = SNew(SHeaderRow); RowNamesHeaderRow->AddColumn( diff --git a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h index 1e6f27556a84..fcdd4c3b1d68 100644 --- a/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h +++ b/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.h @@ -137,6 +137,10 @@ protected: void OnRowSelectionChanged(FDataTableEditorRowListViewDataPtr InNewSelection, ESelectInfo::Type InSelectInfo); + void CopySelectedRow(); + void PasteOnSelectedRow(); + void DuplicateSelectedRow(); + /** Helper function for creating and registering the tab containing the data table data */ virtual void CreateAndRegisterDataTableTab(const TSharedRef& InTabManager); diff --git a/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp b/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp index 13824c3b4e54..a042aba080fa 100644 --- a/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp +++ b/Engine/Source/Editor/DataTableEditor/Private/SDataTableListViewRowName.cpp @@ -36,9 +36,11 @@ void SDataTableListViewRowName::Construct(const FArguments& InArgs, const TShare FReply SDataTableListViewRowName::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { - if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && RowDataPtr.IsValid() && FEditorDelegates::OnOpenReferenceViewer.IsBound()) + if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && RowDataPtr.IsValid() && FEditorDelegates::OnOpenReferenceViewer.IsBound() && DataTableEditor.IsValid()) { - TSharedRef MenuWidget = FDataTableRowUtils::MakeRowActionsMenu(FExecuteAction::CreateSP(this, &SDataTableListViewRowName::OnSearchForReferences)); + FDataTableEditorUtils::SelectRow(DataTableEditor.Pin()->GetDataTable(), RowDataPtr->RowId); + + TSharedRef MenuWidget = FDataTableRowUtils::MakeRowActionsMenu(DataTableEditor.Pin(), FExecuteAction::CreateSP(this, &SDataTableListViewRowName::OnSearchForReferences)); FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath(); diff --git a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp index cfcda5923ddb..3043e22cdb6c 100644 --- a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp +++ b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.cpp @@ -6,17 +6,23 @@ #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Textures/SlateIcon.h" #include "Widgets/SWidget.h" +#include "Framework/Commands/GenericCommands.h" +#include "IDataTableEditor.h" #define LOCTEXT_NAMESPACE "FDataTableRowUtils" const FText FDataTableRowUtils::SearchForReferencesActionName = LOCTEXT("FDataTableRowUtils_SearchForReferences", "Find Row References"); const FText FDataTableRowUtils::SearchForReferencesActionTooltip = LOCTEXT("FDataTableRowUtils_SearchForReferencesTooltip", "Find assets that reference this Row"); -TSharedRef FDataTableRowUtils::MakeRowActionsMenu(FExecuteAction SearchForReferencesAction) +TSharedRef FDataTableRowUtils::MakeRowActionsMenu(TSharedPtr Editor, FExecuteAction SearchForReferencesAction) { if (SearchForReferencesAction.IsBound()) { - FMenuBuilder MenuBuilder(true, nullptr); + FMenuBuilder MenuBuilder(true, Editor->GetToolkitCommands()); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Paste); + MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate); + MenuBuilder.AddMenuSeparator(); MenuBuilder.AddMenuEntry(SearchForReferencesActionName, SearchForReferencesActionTooltip, FSlateIcon(), FUIAction(SearchForReferencesAction)); return MenuBuilder.MakeWidget(); diff --git a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h index f01ddfb78f3f..140e578d454d 100644 --- a/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h +++ b/Engine/Source/Editor/DataTableEditor/Public/DataTableRowUtlis.h @@ -12,7 +12,7 @@ class FDetailWidgetRow; class DATATABLEEDITOR_API FDataTableRowUtils { public: - static TSharedRef MakeRowActionsMenu(FExecuteAction SearchForReferencesAction); + static TSharedRef MakeRowActionsMenu(TSharedPtr Editor, FExecuteAction SearchForReferencesAction); static void AddSearchForReferencesContextMenu(FDetailWidgetRow& RowNameDetailWidget, FExecuteAction SearchForReferencesAction); private: diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp index 565cf8719f10..7d0ba35a4f1c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ActorDetails.cpp @@ -318,15 +318,10 @@ TSharedRef FActorDetails::OnGetConvertContent() return SNew(SBox) - .WidthOverride(280) + .WidthOverride(280) + .MaxDesiredHeight(500) [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - .MaxHeight(500) - [ - ClassPicker - ] + ClassPicker ]; } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp index 079a9d8239f0..e359124ad73e 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/BodyInstanceCustomization.cpp @@ -824,6 +824,8 @@ void FBodyInstanceCustomization::OnCollisionProfileComboOpening() void FBodyInstanceCustomization::MarkAllBodiesDefaultCollision(bool bUseDefaultCollision) { + CollisionResponsesHandle->NotifyPreChange(); + if(PrimComponents.Num() && UseDefaultCollisionHandle.IsValid()) //If we have prim components we might be coming from bp editor which needs to propagate all instances { for(UPrimitiveComponent* PrimComp : PrimComponents) @@ -850,6 +852,8 @@ void FBodyInstanceCustomization::MarkAllBodiesDefaultCollision(bool bUseDefaultC } } } + + CollisionResponsesHandle->NotifyPostChange(); } void FBodyInstanceCustomization::OnCollisionProfileChanged( TSharedPtr NewSelection, ESelectInfo::Type SelectInfo, IDetailGroup* CollisionGroup ) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp index 074495a04322..6c3143840210 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ComponentMaterialCategory.cpp @@ -15,7 +15,7 @@ #include "Editor.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" -#include "PropertyCustomizationHelpers.h" +#include "MaterialList.h" #include "IPropertyUtilities.h" #include "LandscapeProxy.h" diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp index e396ae633265..5d56079103d5 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.cpp @@ -341,12 +341,18 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .bColorAxisLabels( true ) .AllowResponsiveLayout( true ) .IsEnabled( this, &FComponentTransformDetails::GetIsEnabled ) + .OnXChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Location, EAxisList::X, false ) + .OnYChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Location, EAxisList::Y, false ) + .OnZChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Location, EAxisList::Z, false ) .OnXCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Location, EAxisList::X, true ) .OnYCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Location, EAxisList::Y, true ) .OnZCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Location, EAxisList::Z, true ) .Font( FontInfo ) .TypeInterface( TypeInterface ) - .AllowSpin(false) + .AllowSpin( SelectedObjects.Num() == 1 ) + .SpinDelta( 1 ) + .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginLocationSlider ) + .OnEndSliderMovement( this, &FComponentTransformDetails::OnEndLocationSlider ) ] +SHorizontalBox::Slot() .AutoWidth() @@ -409,7 +415,7 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .AllowResponsiveLayout( true ) .bColorAxisLabels( true ) .IsEnabled( this, &FComponentTransformDetails::GetIsEnabled ) - .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginRotatonSlider ) + .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginRotationSlider ) .OnEndSliderMovement( this, &FComponentTransformDetails::OnEndRotationSlider ) .OnRollChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Rotation, EAxisList::X, false ) .OnPitchChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Rotation, EAxisList::Y, false ) @@ -417,7 +423,7 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .OnRollCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Rotation, EAxisList::X, true ) .OnPitchCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Rotation, EAxisList::Y, true ) .OnYawCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Rotation, EAxisList::Z, true ) - .TypeInterface(TypeInterface) + .TypeInterface( TypeInterface ) .Font( FontInfo ) ] +SHorizontalBox::Slot() @@ -474,6 +480,9 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .bColorAxisLabels( true ) .AllowResponsiveLayout( true ) .IsEnabled( this, &FComponentTransformDetails::GetIsEnabled ) + .OnXChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Scale, EAxisList::X, false ) + .OnYChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Scale, EAxisList::Y, false ) + .OnZChanged( this, &FComponentTransformDetails::OnSetTransformAxis, ETextCommit::Default, ETransformField::Scale, EAxisList::Z, false ) .OnXCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Scale, EAxisList::X, true ) .OnYCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Scale, EAxisList::Y, true ) .OnZCommitted( this, &FComponentTransformDetails::OnSetTransformAxis, ETransformField::Scale, EAxisList::Z, true ) @@ -481,7 +490,10 @@ void FComponentTransformDetails::GenerateChildContent( IDetailChildrenBuilder& C .ContextMenuExtenderY( this, &FComponentTransformDetails::ExtendYScaleContextMenu ) .ContextMenuExtenderZ( this, &FComponentTransformDetails::ExtendZScaleContextMenu ) .Font( FontInfo ) - .AllowSpin(false) + .AllowSpin( SelectedObjects.Num() == 1 ) + .SpinDelta( 0.0025f ) + .OnBeginSliderMovement( this, &FComponentTransformDetails::OnBeginScaleSlider ) + .OnEndSliderMovement( this, &FComponentTransformDetails::OnEndScaleSlider ) ] +SHorizontalBox::Slot() .AutoWidth() @@ -856,14 +868,14 @@ void FComponentTransformDetails::OnYScaleMirrored() { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Mouse); FScopedTransaction Transaction(LOCTEXT("MirrorActorScaleY", "Mirror actor scale Y")); - OnSetTransform(ETransformField::Scale, EAxisList::X, FVector(1.0f), true, true); + OnSetTransform(ETransformField::Scale, EAxisList::Y, FVector(1.0f), true, true); } void FComponentTransformDetails::OnZScaleMirrored() { FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Mouse); FScopedTransaction Transaction(LOCTEXT("MirrorActorScaleZ", "Mirror actor scale Z")); - OnSetTransform(ETransformField::Scale, EAxisList::X, FVector(1.0f), true, true); + OnSetTransform(ETransformField::Scale, EAxisList::Z, FVector(1.0f), true, true); } void FComponentTransformDetails::CacheTransform() @@ -1060,7 +1072,7 @@ void FComponentTransformDetails::OnSetTransform(ETransformField::Type TransformF // Set the incoming value if (bMirror) { - NewComponentValue = -OldComponentValue; + NewComponentValue = GetAxisFilteredVector(Axis, -OldComponentValue, OldComponentValue); } else { @@ -1300,40 +1312,37 @@ void FComponentTransformDetails::OnSetTransformAxis(float NewValue, ETextCommit: OnSetTransform(TransformField, Axis, NewVector, false, bCommitted); } -void FComponentTransformDetails::OnBeginRotatonSlider() +void FComponentTransformDetails::BeginSliderTransaction(FText ActorTransaction, FText ComponentTransaction) const { - bEditingRotationInUI = true; - bool bBeganTransaction = false; - for( int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex ) + for (TWeakObjectPtr ObjectPtr : SelectedObjects) { - TWeakObjectPtr ObjectPtr = SelectedObjects[ObjectIndex]; - if( ObjectPtr.IsValid() ) + if (ObjectPtr.IsValid()) { UObject* Object = ObjectPtr.Get(); - // Start a new transation when a rotator slider begins to change - // We'll end it when the slider is release + // Start a new transaction when a slider begins to change + // We'll end it when the slider is released // NOTE: One transaction per change, not per actor - if(!bBeganTransaction) + if (!bBeganTransaction) { - if(Object->IsA()) + if (Object->IsA()) { - GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set Rotation" ) ); + GEditor->BeginTransaction(ActorTransaction); } else { - GEditor->BeginTransaction( LOCTEXT( "OnSetRotation_ComponentDirect", "Modify Component(s)") ); + GEditor->BeginTransaction(ComponentTransaction); } bBeganTransaction = true; } - USceneComponent* SceneComponent = GetSceneComponentFromDetailsObject( Object ); - if( SceneComponent ) + USceneComponent* SceneComponent = GetSceneComponentFromDetailsObject(Object); + if (SceneComponent) { - FScopedSwitchWorldForObject WorldSwitcher( Object ); - + FScopedSwitchWorldForObject WorldSwitcher(Object); + if (SceneComponent->HasAnyFlags(RF_DefaultSubObject)) { // Default subobjects must be included in any undo/redo operations @@ -1342,18 +1351,41 @@ void FComponentTransformDetails::OnBeginRotatonSlider() // Call modify but not PreEdit, we don't do the proper "Edit" until it's committed SceneComponent->Modify(); + } + } + } + + // Just in case we couldn't start a new transaction for some reason + if (!bBeganTransaction) + { + GEditor->BeginTransaction(ActorTransaction); + } +} + +void FComponentTransformDetails::OnBeginRotationSlider() +{ + FText ActorTransaction = LOCTEXT("OnSetRotation", "Set Rotation"); + FText ComponentTransaction = LOCTEXT("OnSetRotation_ComponentDirect", "Modify Component(s)"); + BeginSliderTransaction(ActorTransaction, ComponentTransaction); + + bEditingRotationInUI = true; + + for (TWeakObjectPtr ObjectPtr : SelectedObjects) + { + if (ObjectPtr.IsValid()) + { + UObject* Object = ObjectPtr.Get(); + + USceneComponent* SceneComponent = GetSceneComponentFromDetailsObject(Object); + if (SceneComponent) + { + FScopedSwitchWorldForObject WorldSwitcher(Object); // Add/update cached rotation value prior to slider interaction ObjectToRelativeRotationMap.FindOrAdd(SceneComponent) = SceneComponent->RelativeRotation; } } } - - // Just in case we couldn't start a new transaction for some reason - if(!bBeganTransaction) - { - GEditor->BeginTransaction( LOCTEXT( "OnSetRotation", "Set Rotation" ) ); - } } void FComponentTransformDetails::OnEndRotationSlider(float NewValue) @@ -1363,4 +1395,28 @@ void FComponentTransformDetails::OnEndRotationSlider(float NewValue) GEditor->EndTransaction(); } +void FComponentTransformDetails::OnBeginLocationSlider() +{ + FText ActorTransaction = LOCTEXT("OnSetLocation", "Set Location"); + FText ComponentTransaction = LOCTEXT("OnSetLocation_ComponentDirect", "Modify Component Location"); + BeginSliderTransaction(ActorTransaction, ComponentTransaction); +} + +void FComponentTransformDetails::OnEndLocationSlider(float NewValue) +{ + GEditor->EndTransaction(); +} + +void FComponentTransformDetails::OnBeginScaleSlider() +{ + FText ActorTransaction = LOCTEXT("OnSetScale", "Set Scale"); + FText ComponentTransaction = LOCTEXT("OnSetScale_ComponentDirect", "Modify Component Scale"); + BeginSliderTransaction(ActorTransaction, ComponentTransaction); +} + +void FComponentTransformDetails::OnEndScaleSlider(float NewValue) +{ + GEditor->EndTransaction(); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h index 435a9fb5f7ac..c1973baac399 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/ComponentTransformDetails.h @@ -81,16 +81,32 @@ private: */ void OnSetTransformAxis(float NewValue, ETextCommit::Type CommitInfo, ETransformField::Type TransformField, EAxisList::Type Axis, bool bCommitted); - /** - * Called when the one of the axis sliders for object rotation begins to change for the first time - */ - void OnBeginRotatonSlider(); - /** - * Called when the one of the axis sliders for object rotation is released + /** + * Helper to begin a new transaction for a slider interaction. + * @param ActorTransaction The name to give the transaction when changing an actor transform. + * @param ComponentTransaction The name to give the transaction when directly editing a component transform. */ + void BeginSliderTransaction(FText ActorTransaction, FText ComponentTransaction) const; + + /** Called when the one of the axis sliders for object rotation begins to change for the first time */ + void OnBeginRotationSlider(); + + /** Called when the one of the axis sliders for object rotation is released */ void OnEndRotationSlider(float NewValue); + /** Called when one of the axis sliders for object location begins to change */ + void OnBeginLocationSlider(); + + /** Called when one of the axis sliders for object location is released */ + void OnEndLocationSlider(float NewValue); + + /** Called when one of the axis sliders for object scale begins to change */ + void OnBeginScaleSlider(); + + /** Called when one of the axis sliders for object scale is released */ + void OnEndScaleSlider(float NewValue); + /** @return Icon to use in the preserve scale ratio check box */ const FSlateBrush* GetPreserveScaleRatioImage() const; @@ -216,7 +232,7 @@ private: /** @return The visibility of the "Reset to Default" button for the scale component */ EVisibility GetScaleResetVisibility() const; - /** Cache a single unit to display all location comonents in */ + /** Cache a single unit to display all location components in */ void CacheCommonLocationUnits(); private: @@ -254,7 +270,7 @@ private: TOptional Y; TOptional Z; }; - + FSelectedActorInfo SelectedActorInfo; /** Copy of selected actor references in the details view */ TArray< TWeakObjectPtr > SelectedObjects; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp index de136bf9f320..07df9309d08b 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/CurveStructCustomization.cpp @@ -108,7 +108,7 @@ void FCurveStructCustomization::CustomizeHeader( TSharedRef InS } else { - CurveWidget->SetCurveOwner(this); + CurveWidget->SetCurveOwner(this, InStructPropertyHandle->IsEditable()); } } else @@ -276,7 +276,7 @@ void FCurveStructCustomization::OnExternalCurveChanged(TSharedRefSetCurveOwner(this); + CurveWidget->SetCurveOwner(this, CurvePropertyHandle->IsEditable()); } CurvePropertyHandle->NotifyPostChange(); diff --git a/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp index fbd207403285..2c147a77e797 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/FbxImportUIDetails.cpp @@ -593,6 +593,18 @@ void FFbxImportUIDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder } } } + + TSharedPtr ReorderMaterialToFbxOrderProp = nullptr; + if (ImportUI->MeshTypeToImport == FBXIT_StaticMesh) + { + ReorderMaterialToFbxOrderProp = StaticMeshDataProp->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, bReorderMaterialToFbxOrder)); + } + else if (ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh) + { + ReorderMaterialToFbxOrderProp = SkeletalMeshDataProp->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxSkeletalMeshImportData, bReorderMaterialToFbxOrder)); + } + //Add the advance reorder option at the end + MaterialCategory.AddProperty(ReorderMaterialToFbxOrderProp); } //Information category diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp index bf35226faf34..088e3798603c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.cpp @@ -23,6 +23,8 @@ void FMarginStructCustomization::CustomizeHeader( TSharedRefGetProperty()->GetMetaData( TEXT( "UVSpace" ) ) ); bIsMarginUsingUVSpace = UVSpaceString.Len() > 0 && UVSpaceString == TEXT( "true" ); + NumericInterface = MakeShareable(new TDefaultNumericTypeInterface); + uint32 NumChildren; StructPropertyHandle->GetNumChildren( NumChildren ); @@ -102,6 +104,7 @@ TSharedRef FMarginStructCustomization::MakeChildPropertyWidget( int32 P .MaxValue( bIsMarginUsingUVSpace ? 1.0f : TNumericLimits::Max() ) .MinSliderValue( bIsMarginUsingUVSpace ? 0.0f : TNumericLimits::Lowest() ) .MaxSliderValue( bIsMarginUsingUVSpace ? 1.0f : TNumericLimits::Max() ) + .TypeInterface(NumericInterface) .Label() [ SNew( STextBlock ) @@ -141,10 +144,11 @@ void FMarginStructCustomization::OnMarginTextCommitted( const FText& InText, ETe LeftString.TrimStartAndEndInline(); - if( LeftString.IsNumeric() ) + float Value = 0.f; + TOptional NumericValue = NumericInterface->FromString(LeftString, Value); + if (NumericValue.IsSet()) { - float Value; - TTypeFromString::FromString( Value, *LeftString ); + Value = NumericValue.GetValue(); PropertyValues.Add( bIsMarginUsingUVSpace ? FMath::Clamp( Value, 0.0f, 1.0f ) : FMath::Max( Value, 0.0f ) ); } else diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h index a60601f0e25b..f26422bb547d 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/MarginCustomization.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Widgets/SWidget.h" +#include "Widgets/Input/NumericTypeInterface.h" #include "UnrealClient.h" #include "IPropertyTypeCustomization.h" #include "PropertyHandle.h" @@ -103,5 +104,8 @@ private: /** Margin text editable text box */ TSharedPtr MarginEditableTextBox; + + /** Used to evaluate margin strings as mathematical expressions **/ + TSharedPtr< INumericTypeInterface > NumericInterface; }; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp index 3019d75bece3..99d4c92d88a0 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MaterialProxySettingsCustomizations.cpp @@ -138,16 +138,16 @@ void FMaterialProxySettingsCustomizations::AddTextureSizeClamping(TSharedPtrGetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); TextureSizeProperty->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMax"), *MaxTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMax"), *MaxTextureResolutionString); const FString MinTextureResolutionString("1"); - PropertyX->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyX->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("ClampMin"), *MinTextureResolutionString); - PropertyY->GetProperty()->SetMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyX->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("ClampMin"), *MinTextureResolutionString); + PropertyY->SetInstanceMetaData(TEXT("UIMin"), *MinTextureResolutionString); } EVisibility FMaterialProxySettingsCustomizations::AreManualOverrideTextureSizesEnabled() const diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp index a26c9df9df8c..d5f17941f994 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp @@ -29,6 +29,7 @@ TSharedRef FMathStructProxyCustomization::MakeNumericProxyWidget(TShare return SNew( SNumericEntryBox ) + .IsEnabled( this, &FMathStructProxyCustomization::IsValueEnabled, WeakHandlePtr ) .Value( this, &FMathStructProxyCustomization::OnGetValue, WeakHandlePtr, ProxyValue ) .Font( IDetailLayoutBuilder::GetDetailFont() ) .UndeterminedString( NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values") ) diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp index 30f6bc4e84d5..33c8a700f754 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MoviePlayerSettingsDetails.cpp @@ -16,6 +16,7 @@ #include "Widgets/Input/SFilePathPicker.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" +#include "HAL/FileManager.h" #define LOCTEXT_NAMESPACE "MoviePlayerSettingsDetails" @@ -77,6 +78,16 @@ void FMoviePlayerSettingsDetails::GenerateArrayElementWidget(TSharedRef Property ) { + // Ignore if value is unchanged. + FString OldValue; + if (Property->GetValueAsFormattedString(OldValue) == FPropertyAccess::Success) + { + if (PickedPath == OldValue) + { + return; + } + } + FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_OPEN, FPaths::GetPath(PickedPath)); // sanitize the location of the chosen movies to the content/movies directory @@ -102,35 +113,75 @@ void FMoviePlayerSettingsDetails::HandleFilePathPickerPathPicked( const FString& } else if (!PickedPath.IsEmpty()) { - // ask the user if they want to import this movie - FSuppressableWarningDialog::FSetupInfo Info( - LOCTEXT("ExternalMovieImportWarning", "This movie needs to be copied into your project, would you like to copy the file now?"), - LOCTEXT("ExternalMovieImportTitle", "Copy Movie"), - TEXT("ImportMovieIntoProject") ); - Info.ConfirmText = LOCTEXT("ExternalMovieImport_Confirm", "Copy"); - Info.CancelText = LOCTEXT("ExternalMovieImport_Cancel", "Don't Copy"); - - FSuppressableWarningDialog ImportWarningDialog( Info ); - - if(ImportWarningDialog.ShowModal() != FSuppressableWarningDialog::EResult::Cancel) + // If the path of PickedPath is empty then we are dealing with a movie already in the correct directory. + if (FPaths::GetPath(PickedPath).IsEmpty()) { - const FString FileName = FPaths::GetCleanFilename(PickedPath); - const FString DestPath = MoviesBaseDir / FileName; + FString FullPickedPath = FPaths::Combine(MoviesBaseDir, PickedPath); - FText FailReason; - - if (SourceControlHelpers::CopyFileUnderSourceControl(DestPath, PickedPath, LOCTEXT("MovieFileDescription", "movie"), FailReason)) + // Look for this movie. + TArray ExistingMovieFiles; + IFileManager::Get().FindFiles(ExistingMovieFiles, *MoviesBaseDir); + bool bHasValidMovie = ExistingMovieFiles.ContainsByPredicate( + [&PickedPath](const FString& ExistingMovie) { - // trim the path so we just have a partial path with no extension (the movie player expects this) - Property->SetValue(FPaths::GetBaseFilename(DestPath.RightChop(MoviesBaseDir.Len()))); + return ExistingMovie.Contains(PickedPath); + }); + + // Did we find a movie? + if (bHasValidMovie) + { + // Yes. + Property->SetValue(FPaths::GetBaseFilename(PickedPath)); } else { - FNotificationInfo FailureInfo(FailReason); - FailureInfo.ExpireDuration = 3.0f; - FSlateNotificationManager::Get().AddNotification(FailureInfo); + // Nope. Bring up a dialog box informing the user. + FSuppressableWarningDialog::FSetupInfo Info( + LOCTEXT("ExternalMovieImportNotExistWarning", "This movie does not exist."), + LOCTEXT("ExternalMovieImportNotExistTitle", "Does not exist"), + TEXT("ImportMovieIntoProjectNotExist")); + Info.ConfirmText = LOCTEXT("ExternalMovieImportNotExist_Confirm", "OK"); + Info.CancelText = LOCTEXT("ExternalMovieImportNotExist_Cancel", "Cancel"); + + FSuppressableWarningDialog ImportWarningDialog(Info); + if (ImportWarningDialog.ShowModal() == FSuppressableWarningDialog::EResult::Cancel) + { + Property->SetValue(FPaths::GetBaseFilename(PickedPath)); + } } - } + } + else + { + // ask the user if they want to import this movie + FSuppressableWarningDialog::FSetupInfo Info( + LOCTEXT("ExternalMovieImportWarning", "This movie needs to be copied into your project, would you like to copy the file now?"), + LOCTEXT("ExternalMovieImportTitle", "Copy Movie"), + TEXT("ImportMovieIntoProject")); + Info.ConfirmText = LOCTEXT("ExternalMovieImport_Confirm", "Copy"); + Info.CancelText = LOCTEXT("ExternalMovieImport_Cancel", "Don't Copy"); + + FSuppressableWarningDialog ImportWarningDialog(Info); + + if (ImportWarningDialog.ShowModal() != FSuppressableWarningDialog::EResult::Cancel) + { + const FString FileName = FPaths::GetCleanFilename(PickedPath); + const FString DestPath = MoviesBaseDir / FileName; + + FText FailReason; + + if (SourceControlHelpers::CopyFileUnderSourceControl(DestPath, PickedPath, LOCTEXT("MovieFileDescription", "movie"), FailReason)) + { + // trim the path so we just have a partial path with no extension (the movie player expects this) + Property->SetValue(FPaths::GetBaseFilename(DestPath.RightChop(MoviesBaseDir.Len()))); + } + else + { + FNotificationInfo FailureInfo(FailReason); + FailureInfo.ExpireDuration = 3.0f; + FSlateNotificationManager::Get().AddNotification(FailureInfo); + } + } + } } else { diff --git a/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp index 5297aa99ea45..3decdd61edbd 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/ObjectDetails.cpp @@ -46,9 +46,9 @@ void FObjectDetails::AddExperimentalWarningCategory(IDetailLayoutBuilder& Detail const FText CategoryDisplayName = LOCTEXT("WarningCategoryDisplayName", "Warning"); FString ClassUsed = DetailBuilder.GetTopLevelProperty().ToString(); const FText WarningText = bBaseClassIsExperimental ? FText::Format( LOCTEXT("ExperimentalClassWarning", "Uses experimental class: {0}") , FText::FromString(ClassUsed) ) - : FText::Format( LOCTEXT("EarlyAccessClassWarning", "Uses early access class {0}"), FText::FromString(*ClassUsed) ); + : FText::Format( LOCTEXT("EarlyAccessClassWarning", "Uses beta class {0}"), FText::FromString(*ClassUsed) ); const FText SearchString = WarningText; - const FText Tooltip = bBaseClassIsExperimental ? LOCTEXT("ExperimentalClassTooltip", "Here be dragons! Uses one or more unsupported 'experimental' classes") : LOCTEXT("EarlyAccessClassTooltip", "Uses one or more 'early access' classes"); + const FText Tooltip = bBaseClassIsExperimental ? LOCTEXT("ExperimentalClassTooltip", "Here be dragons! Uses one or more unsupported 'experimental' classes") : LOCTEXT("EarlyAccessClassTooltip", "Uses one or more 'beta' classes"); const FString ExcerptName = bBaseClassIsExperimental ? TEXT("ObjectUsesExperimentalClass") : TEXT("ObjectUsesEarlyAccessClass"); const FSlateBrush* WarningIcon = FEditorStyle::GetBrush(bBaseClassIsExperimental ? "PropertyEditor.ExperimentalClass" : "PropertyEditor.EarlyAccessClass"); @@ -110,7 +110,11 @@ void FObjectDetails::AddCallInEditorMethods(IDetailLayoutBuilder& DetailBuilder) } } - CallInEditorFunctions.Add(*FunctionIter); + const FName FunctionName = TestFunction->GetFName(); + if (!CallInEditorFunctions.FindByPredicate([&FunctionName](const UFunction* Func) { return Func->GetFName() == FunctionName; })) + { + CallInEditorFunctions.Add(*FunctionIter); + } } } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp index d198678fd47b..df17bca968d4 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/RenderPassesCustomization.cpp @@ -100,10 +100,15 @@ public: } else { - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([=]{ - Property->Value.RemoveAt(PropertyIndex); - Update(); - })); + TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([this, PropertyIndex] + { + if (PropertyIndex < Property->Value.Num()) + { + Property->Value.RemoveAt(PropertyIndex); + Update(); + } + })); EnabledPassesContainer->AddSlot() .AutoHeight() @@ -133,6 +138,8 @@ public: ComboEntries.Sort([](const TSharedPtr& A, const TSharedPtr& B){ return A->Text.CompareToCaseIgnored(B->Text) < 0; }); + + ComboBox->ClearSelection(); ComboBox->SetVisibility(ComboEntries.Num() == 0 ? EVisibility::Collapsed : EVisibility::Visible); } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp index 0e69c3bad7a3..0e468646a528 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalMeshReductionSettingsDetails.cpp @@ -15,6 +15,8 @@ #include "Widgets/Text/STextBlock.h" #include "SkeletalRenderPublic.h" +#include "Rendering/SkeletalMeshModel.h" + #define LOCTEXT_NAMESPACE "SkeletalMeshReductionSettingsDetails" TSharedRef FSkeletalMeshReductionSettingsDetails::MakeInstance() @@ -54,6 +56,14 @@ void FSkeletalMeshReductionSettingsDetails::CustomizeChildren(TSharedRef Objects; + StructPropertyHandle->GetOuterObjects(Objects); + if (Objects.Num() > 0) + { + SkeletalMesh = Cast(Objects[0]); + } + ReductionMethodPropertyHandle = StructPropertyHandle->GetChildHandle(CustomizedProperties[0]); NumTrianglesPercentagePropertyHandle = StructPropertyHandle->GetChildHandle(CustomizedProperties[1]); MaxDeviationPercentagePropertyHandle = StructPropertyHandle->GetChildHandle(CustomizedProperties[2]); @@ -73,11 +83,13 @@ void FSkeletalMeshReductionSettingsDetails::CustomizeChildren(TSharedRef& BaseLODPropertyHandle) + auto BaseLODCustomization = [LODIndex, &StructBuilder, &SkeletalMesh](TSharedPtr& BaseLODPropertyHandle) { - // Only able to do this for LOD2 and above, so only show the property if this is the case - if (LODIndex > 1) + // Only able to do this for LOD1 and above, so only show the property if this is the case + if (LODIndex > 0) { + const FSkeletalMeshModel* ImportedModel = SkeletalMesh != nullptr ? SkeletalMesh->GetImportedModel() : nullptr; + bool AllowInline = ImportedModel != nullptr && ImportedModel->LODModels.IsValidIndex(LODIndex) && !ImportedModel->LODModels[LODIndex].RawSkeletalMeshBulkData.IsEmpty(); // Add and retrieve the default widgets IDetailPropertyRow& Row = StructBuilder.AddProperty(BaseLODPropertyHandle->AsShared()); @@ -99,7 +111,7 @@ void FSkeletalMeshReductionSettingsDetails::CustomizeChildren(TSharedRef) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0) - .MaxValue(FMath::Max(LODIndex - 1, 0)) + .MaxValue(FMath::Max(LODIndex - (AllowInline ? 0 : 1), 0)) .Value_Lambda([BaseLODPropertyHandle]() -> int32 { int32 Value = INDEX_NONE; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp index 928491c88a10..a7316f541565 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SlateBrushCustomization.cpp @@ -981,6 +981,8 @@ class SSlateBrushStaticPreview : public SCompoundWidget { ResourceObjectProperty = InResourceObjectProperty; + UpdateBrush(); + ChildSlot [ SNew(SHorizontalBox) @@ -1014,19 +1016,7 @@ class SSlateBrushStaticPreview : public SCompoundWidget void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { - TArray RawData; - - if (ResourceObjectProperty.IsValid() && ResourceObjectProperty->GetProperty()) - { - ResourceObjectProperty->AccessRawData(RawData); - - // RawData will be empty when creating a new Data Table, an idiosyncrasy - // of the Data Table Editor... - if (RawData.Num() > 0) - { - TemporaryBrush = *static_cast(RawData[0]); - } - } + UpdateBrush(); } private: @@ -1061,6 +1051,22 @@ private: return TemporaryBrush.DrawAs == ESlateBrushDrawType::Image ? EVisibility::Visible : EVisibility::Collapsed; } + void UpdateBrush() + { + if (ResourceObjectProperty.IsValid() && ResourceObjectProperty->GetProperty()) + { + TArray RawData; + ResourceObjectProperty->AccessRawData(RawData); + + // RawData will be empty when creating a new Data Table, an idiosyncrasy + // of the Data Table Editor... + if (RawData.Num() > 0) + { + TemporaryBrush = *static_cast(RawData[0]); + } + } + } + private: /** @@ -1154,43 +1160,13 @@ class SBrushResourceObjectBox : public SCompoundWidget ] ] ]; + + UpdateResourceError(); } void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { - UObject* Resource = nullptr; - - if( ResourceObjectProperty->GetValue(Resource) == FPropertyAccess::Success && Resource && Resource->IsA() ) - { - UMaterialInterface* MaterialInterface = Cast( Resource ); - UMaterial* BaseMaterial = MaterialInterface->GetBaseMaterial(); - if( BaseMaterial && !BaseMaterial->IsUIMaterial() ) - { - ResourceError->SetVisibility( EVisibility::Visible ); - - // Special engine materials cannot change domain. This typically occurs when - // the user creates or assigns a material instance with no parent material. - // In this case, we warn the user rather than offer to change the domain. - if (BaseMaterial->bUsedAsSpecialEngineMaterial) - { - ChangeDomainLink->SetVisibility( EVisibility::Collapsed ); - IsEngineMaterialError->SetVisibility( EVisibility::Visible ); - } - else - { - ChangeDomainLink->SetVisibility( EVisibility::Visible ); - IsEngineMaterialError->SetVisibility( EVisibility::Collapsed ); - } - } - else - { - ResourceError->SetVisibility( EVisibility::Collapsed ); - } - } - else if( ResourceError->GetVisibility() != EVisibility::Collapsed ) - { - ResourceError->SetVisibility( EVisibility::Collapsed ); - } + UpdateResourceError(); } private: @@ -1258,6 +1234,43 @@ private: } } + void UpdateResourceError() + { + UObject* Resource = nullptr; + + if( ResourceObjectProperty->GetValue(Resource) == FPropertyAccess::Success && Resource && Resource->IsA() ) + { + UMaterialInterface* MaterialInterface = Cast( Resource ); + UMaterial* BaseMaterial = MaterialInterface->GetBaseMaterial(); + if( BaseMaterial && !BaseMaterial->IsUIMaterial() ) + { + ResourceError->SetVisibility( EVisibility::Visible ); + + // Special engine materials cannot change domain. This typically occurs when + // the user creates or assigns a material instance with no parent material. + // In this case, we warn the user rather than offer to change the domain. + if (BaseMaterial->bUsedAsSpecialEngineMaterial) + { + ChangeDomainLink->SetVisibility( EVisibility::Collapsed ); + IsEngineMaterialError->SetVisibility( EVisibility::Visible ); + } + else + { + ChangeDomainLink->SetVisibility( EVisibility::Visible ); + IsEngineMaterialError->SetVisibility( EVisibility::Collapsed ); + } + } + else + { + ResourceError->SetVisibility( EVisibility::Collapsed ); + } + } + else if( ResourceError->GetVisibility() != EVisibility::Collapsed ) + { + ResourceError->SetVisibility( EVisibility::Collapsed ); + } + } + private: TSharedPtr ResourceObjectProperty; TSharedPtr ImageSizeProperty; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp index 2638ca0d1b7f..489a7c420726 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SplineComponentDetails.cpp @@ -280,6 +280,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetPositionY) .Z(this, &FSplinePointDetails::GetPositionZ) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetPosition, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetPosition, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetPosition, 2) @@ -306,6 +307,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetArriveTangentY) .Z(this, &FSplinePointDetails::GetArriveTangentZ) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetArriveTangent, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetArriveTangent, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetArriveTangent, 2) @@ -332,6 +334,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetLeaveTangentY) .Z(this, &FSplinePointDetails::GetLeaveTangentZ) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetLeaveTangent, 2) @@ -358,6 +361,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .Y(this, &FSplinePointDetails::GetRotationPitch) .Z(this, &FSplinePointDetails::GetRotationYaw) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FSplinePointDetails::OnSetRotation, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetRotation, 1) .OnZCommitted(this, &FSplinePointDetails::OnSetRotation, 2) @@ -383,6 +387,7 @@ void FSplinePointDetails::GenerateChildContent(IDetailChildrenBuilder& ChildrenB .X(this, &FSplinePointDetails::GetScaleX) .Y(this, &FSplinePointDetails::GetScaleY) .Z(this, &FSplinePointDetails::GetScaleZ) + .AllowSpin(false) .AllowResponsiveLayout(true) .OnXCommitted(this, &FSplinePointDetails::OnSetScale, 0) .OnYCommitted(this, &FSplinePointDetails::OnSetScale, 1) diff --git a/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp b/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp index 1fec6d02a2b9..222e35e3e1b7 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp +++ b/Engine/Source/Editor/DistCurveEditor/Private/CurveEditorViewportClient.cpp @@ -49,7 +49,6 @@ FCurveEditorViewportClient::FCurveEditorViewportClient(TWeakPtrDraw(); - } - - if (ViewportClient.IsValid()) - { - ViewportClient->SetNeedsRedraw(false); - } -} - void SCurveEditorViewport::RefreshViewport() { Viewport->Invalidate(); + Viewport->InvalidateDisplay(); } void SCurveEditorViewport::SetVerticalScrollBarPosition(float Position) diff --git a/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h b/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h index fb73825da465..e5ec3c728bac 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h +++ b/Engine/Source/Editor/DistCurveEditor/Private/SCurveEditorViewport.h @@ -35,9 +35,6 @@ public: /** SCompoundWidget interface */ void Construct(const FArguments& InArgs); - /** Draws the viewport */ - void DrawViewport(); - /** Refreshes the viewport */ void RefreshViewport(); diff --git a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp index dd751a7481c4..5e9204f82f65 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp +++ b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.cpp @@ -63,21 +63,7 @@ void SDistributionCurveEditor::Construct(const FArguments& InArgs) void SDistributionCurveEditor::RefreshViewport() { Viewport->GetViewport()->Invalidate(); -} - -bool SDistributionCurveEditor::GetNeedsRedraw() -{ - if (Viewport->GetViewportClient().IsValid()) - { - return Viewport->GetViewportClient()->GetNeedsRedraw(); - } - - return false; -} - -void SDistributionCurveEditor::DrawViewport() -{ - Viewport->DrawViewport(); + Viewport->GetViewport()->InvalidateDisplay(); } void SDistributionCurveEditor::CurveChanged() diff --git a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h index 1380312e6268..e253c35f47e6 100644 --- a/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h +++ b/Engine/Source/Editor/DistCurveEditor/Private/SDistributionCurveEditor.h @@ -61,8 +61,6 @@ public: void Construct(const FArguments& InArgs); /** IDistributionCurveEditor interface */ - virtual void DrawViewport() override; - virtual bool GetNeedsRedraw() override; virtual void RefreshViewport() override; virtual void CurveChanged() override; virtual void SetCurveVisible(const UObject* InCurve, bool bShow) override; diff --git a/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h b/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h index baf5b5ccb480..6e89d764ed05 100644 --- a/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h +++ b/Engine/Source/Editor/DistCurveEditor/Public/IDistCurveEditor.h @@ -38,13 +38,7 @@ public: class DISTCURVEEDITOR_API IDistributionCurveEditor : public SCompoundWidget { public: - /** Draws the viewport */ - virtual void DrawViewport() = 0; - - /** Returns whether draw is needed */ - virtual bool GetNeedsRedraw() = 0; - - /** Refreshes the viewport by invalidating */ + /** Refreshes the viewport */ virtual void RefreshViewport() = 0; /** Clears selected keys and updates the viewport */ diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/EditorInteractiveToolsFramework.Build.cs b/Engine/Source/Editor/EditorInteractiveToolsFramework/EditorInteractiveToolsFramework.Build.cs new file mode 100644 index 000000000000..735e90172283 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/EditorInteractiveToolsFramework.Build.cs @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class EditorInteractiveToolsFramework : ModuleRules +{ + public EditorInteractiveToolsFramework(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + //"ContentBrowser" + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "InputCore", + "UnrealEd", + "ContentBrowser", + "LevelEditor", + "ApplicationCore", + "EditorStyle", + "InteractiveToolsFramework" + + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp new file mode 100644 index 000000000000..0e2d7bfb646d --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EdModeInteractiveToolsContext.cpp @@ -0,0 +1,788 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "EdModeInteractiveToolsContext.h" +#include "Editor.h" +#include "EditorViewportClient.h" +#include "EditorModeManager.h" +#include "LevelEditorViewport.h" // for GCurrentLevelEditingViewportClient +#include "Engine/Selection.h" +#include "Misc/ITransaction.h" +#include "ScopedTransaction.h" +#include "Materials/Material.h" +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" + +#include "EditorToolAssetAPI.h" +#include "EditorComponentSourceFactory.h" + +//#include "PhysicsEngine/BodySetup.h" +//#include "Interfaces/Interface_CollisionDataProvider.h" + +//#define ENABLE_DEBUG_PRINTING + + + +class FEdModeToolsContextQueriesImpl : public IToolsContextQueriesAPI +{ +public: + UEdModeInteractiveToolsContext* ToolsContext; + FEdMode* EditorMode; + + FViewCameraState CachedViewState; + + FEdModeToolsContextQueriesImpl(UEdModeInteractiveToolsContext* Context, FEdMode* EditorModeIn) + { + ToolsContext = Context; + EditorMode = EditorModeIn; + } + + void CacheCurrentViewState(FEditorViewportClient* ViewportClient) + { + FViewportCameraTransform ViewTransform = ViewportClient->GetViewTransform(); + CachedViewState.Position = ViewTransform.GetLocation(); + CachedViewState.Orientation = ViewTransform.GetRotation().Quaternion(); + CachedViewState.bIsOrthographic = ViewportClient->IsOrtho(); + CachedViewState.bIsVR = false; + } + + virtual void GetCurrentSelectionState(FToolBuilderState& StateOut) const override + { + StateOut.ToolManager = ToolsContext->ToolManager; + StateOut.GizmoManager = ToolsContext->GizmoManager; + StateOut.World = EditorMode->GetWorld(); + StateOut.SelectedActors = EditorMode->GetModeManager()->GetSelectedActors(); + StateOut.SelectedComponents = EditorMode->GetModeManager()->GetSelectedComponents(); + StateOut.SourceBuilder = ToolsContext->GetComponentSourceFactory(); + } + + + virtual void GetCurrentViewState(FViewCameraState& StateOut) const override + { + StateOut = CachedViewState; + } + + + virtual bool ExecuteSceneSnapQuery(const FSceneSnapQueryRequest& Request, TArray& Results) const override + { + if (Request.RequestType != ESceneSnapQueryType::Position) + { + return false; // not supported yet + } + + int FoundResultCount = 0; + + // + // Run a snap query by casting ray into the world. + // If a hit is found, we look up what triangle was hit, and then test its vertices and edges + // + + // cast ray into world + FVector RayStart = CachedViewState.Position; + FVector RayDirection = Request.Position - RayStart; RayDirection.Normalize(); + FVector RayEnd = RayStart + 9999999 * RayDirection; + FCollisionObjectQueryParams ObjectQueryParams(FCollisionObjectQueryParams::AllObjects); + FCollisionQueryParams QueryParams = FCollisionQueryParams::DefaultQueryParam; + QueryParams.bTraceComplex = true; + QueryParams.bReturnFaceIndex = true; + FHitResult HitResult; + bool bHitWorld = EditorMode->GetWorld()->LineTraceSingleByObjectType(HitResult, RayStart, RayEnd, ObjectQueryParams, QueryParams); + if (bHitWorld && HitResult.FaceIndex >= 0) + { + float VisualAngle = OpeningAngleDeg(Request.Position, HitResult.ImpactPoint, RayStart); + //UE_LOG(LogTemp, Warning, TEXT("[HIT] visualangle %f faceindex %d"), VisualAngle, HitResult.FaceIndex); + if (VisualAngle < Request.VisualAngleThresholdDegrees) + { + UPrimitiveComponent* Component = HitResult.Component.Get(); + if (Cast(Component) != nullptr) + { + // HitResult.FaceIndex is apparently an index into the TriMeshCollisionData, not sure how + // to directly access it. Calling GetPhysicsTriMeshData is expensive! + //UBodySetup* BodySetup = Cast(Component)->GetBodySetup(); + //UObject* CDPObj = BodySetup->GetOuter(); + //IInterface_CollisionDataProvider* CDP = Cast(CDPObj); + //FTriMeshCollisionData TriMesh; + //CDP->GetPhysicsTriMeshData(&TriMesh, true); + //FTriIndices Triangle = TriMesh.Indices[HitResult.FaceIndex]; + //FVector Positions[3] = { TriMesh.Vertices[Triangle.v0], TriMesh.Vertices[Triangle.v1], TriMesh.Vertices[Triangle.v2] }; + + // physics collision data is created from StaticMesh RenderData + // so use HitResult.FaceIndex to extract triangle from the LOD0 mesh + // (note: this may be incorrect if there are multiple sections...in that case I think we have to + // first find section whose accumulated index range would contain .FaceIndexX) + UStaticMesh* StaticMesh = Cast(Component)->GetStaticMesh(); + FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[0]; + FIndexArrayView Indices = LOD.IndexBuffer.GetArrayView(); + int32 TriIdx = 3 * HitResult.FaceIndex; + FVector Positions[3]; + Positions[0] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx]); + Positions[1] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx+1]); + Positions[2] = LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(Indices[TriIdx+2]); + + // transform to world space + FTransform ComponentTransform = Component->GetComponentTransform(); + Positions[0] = ComponentTransform.TransformPosition(Positions[0]); + Positions[1] = ComponentTransform.TransformPosition(Positions[1]); + Positions[2] = ComponentTransform.TransformPosition(Positions[2]); + + FSceneSnapQueryResult SnapResult; + SnapResult.TriVertices[0] = Positions[0]; + SnapResult.TriVertices[1] = Positions[1]; + SnapResult.TriVertices[2] = Positions[2]; + + // try snapping to vertices + float SmallestAngle = Request.VisualAngleThresholdDegrees; + if ( (Request.TargetTypes & ESceneSnapQueryTargetType::MeshVertex) != ESceneSnapQueryTargetType::None) + { + for (int j = 0; j < 3; ++j) + { + VisualAngle = OpeningAngleDeg(Request.Position, Positions[j], RayStart); + if (VisualAngle < SmallestAngle) + { + SmallestAngle = VisualAngle; + SnapResult.Position = Positions[j]; + SnapResult.TargetType = ESceneSnapQueryTargetType::MeshVertex; + SnapResult.TriSnapIndex = j; + } + } + } + + // try snapping to nearest points on edges + if ( ((Request.TargetTypes & ESceneSnapQueryTargetType::MeshEdge) != ESceneSnapQueryTargetType::None) && + (SnapResult.TargetType != ESceneSnapQueryTargetType::MeshVertex) ) + { + for (int j = 0; j < 3; ++j) + { + FVector EdgeNearestPt = NearestSegmentPt(Positions[j], Positions[(j+1)%3], Request.Position); + VisualAngle = OpeningAngleDeg(Request.Position, EdgeNearestPt, RayStart); + if (VisualAngle < SmallestAngle ) + { + SmallestAngle = VisualAngle; + SnapResult.Position = EdgeNearestPt; + SnapResult.TargetType = ESceneSnapQueryTargetType::MeshEdge; + SnapResult.TriSnapIndex = j; + } + } + } + + // if we found a valid snap, return it + if (SmallestAngle < Request.VisualAngleThresholdDegrees) + { + SnapResult.TargetActor = HitResult.Actor.Get(); + SnapResult.TargetComponent = HitResult.Component.Get(); + Results.Add(SnapResult); + FoundResultCount++; + } + } + } + + } + + return (FoundResultCount > 0); + } + + + //@ todo this are mirrored from GeometryProcessing, which is still experimental...replace w/ direct calls once GP component is standardized + static float OpeningAngleDeg(FVector A, FVector B, const FVector& P) + { + A -= P; + A.Normalize(); + B -= P; + B.Normalize(); + float Dot = FMath::Clamp(FVector::DotProduct(A,B), -1.0f, 1.0f); + return acos(Dot) * (180.0f / 3.141592653589f); + } + static FVector NearestSegmentPt(FVector A, FVector B, const FVector& P) + { + FVector Direction = (B - A); + float Length = Direction.Size(); + Direction /= Length; + float t = FVector::DotProduct( (P - A), Direction); + if (t >= Length) + { + return B; + } + if (t <= 0) + { + return A; + } + return A + t * Direction; + } + + + + virtual UMaterialInterface* GetStandardMaterial(EStandardToolContextMaterials MaterialType) const + { + if (MaterialType == EStandardToolContextMaterials::VertexColorMaterial) + { + return ToolsContext->StandardVertexColorMaterial; + } + check(false); + return nullptr; + } + +}; + + + + +class FEdModeToolsContextTransactionImpl : public IToolsContextTransactionsAPI +{ +public: + UEdModeInteractiveToolsContext* ToolsContext; + FEdMode* EditorMode; + + FEdModeToolsContextTransactionImpl(UEdModeInteractiveToolsContext* Context, FEdMode* EditorModeIn) + { + ToolsContext = Context; + EditorMode = EditorModeIn; + } + + + virtual void PostMessage(const TCHAR* Message, EToolMessageLevel Level) override + { + UE_LOG(LogTemp, Warning, TEXT("[ToolsContext] %s"), Message); + } + + + virtual void PostInvalidation() override + { + ToolsContext->PostInvalidation(); + } + + virtual void BeginUndoTransaction(const FText& Description) override + { + GEditor->BeginTransaction(Description); + } + + virtual void EndUndoTransaction() override + { + GEditor->EndTransaction(); + } + + virtual void AppendChange(UObject* TargetObject, TUniquePtr Change, const FText& Description) override + { + FScopedTransaction Transaction(Description); + check(GUndo != nullptr); + GUndo->StoreUndo(TargetObject, MoveTemp(Change)); + // end transaction + } + + + virtual bool RequestSelectionChange(const FSelectedOjectsChangeList& SelectionChange) override + { + checkf(SelectionChange.Components.Num() == 0, TEXT("FEdModeToolsContextTransactionImpl::RequestSelectionChange - Component selection not supported yet")); + + if (SelectionChange.ModificationType == ESelectedObjectsModificationType::Clear) + { + GEditor->SelectNone(true, true, false); + return true; + } + + if (SelectionChange.ModificationType == ESelectedObjectsModificationType::Replace ) + { + GEditor->SelectNone(false, true, false); + } + + bool bAdd = (SelectionChange.ModificationType != ESelectedObjectsModificationType::Remove); + int NumActors = SelectionChange.Actors.Num(); + for (int k = 0; k < NumActors; ++k) + { + GEditor->SelectActor(SelectionChange.Actors[k], bAdd, false, true, false); + } + + GEditor->NoteSelectionChange(true); + return true; + } + +}; + + + + + +UEdModeInteractiveToolsContext::UEdModeInteractiveToolsContext() +{ + EditorMode = nullptr; + QueriesAPI = nullptr; + TransactionAPI = nullptr; + AssetAPI = nullptr; + SourceFactory = nullptr; +} + + + +void UEdModeInteractiveToolsContext::Initialize(IToolsContextQueriesAPI* QueriesAPIIn, IToolsContextTransactionsAPI* TransactionsAPIIn) +{ + UInteractiveToolsContext::Initialize(QueriesAPIIn, TransactionsAPIIn); + + BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddLambda([this](bool bSimulating) + { + TerminateActiveToolsOnPIEStart(); + }); + PreSaveWorldDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([this](uint32 SaveFlags, UWorld* World) + { + TerminateActiveToolsOnSaveWorld(); + }); + + bInvalidationPending = false; +} + +void UEdModeInteractiveToolsContext::Shutdown() +{ + FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle); + FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldDelegateHandle); + + UInteractiveToolsContext::Shutdown(); +} + + + + +void UEdModeInteractiveToolsContext::InitializeContextFromEdMode(FEdMode* EditorModeIn) +{ + this->EditorMode = EditorModeIn; + + this->TransactionAPI = new FEdModeToolsContextTransactionImpl(this, EditorModeIn); + this->QueriesAPI = new FEdModeToolsContextQueriesImpl(this, EditorModeIn); + this->AssetAPI = new FEditorToolAssetAPI(); + this->SourceFactory = new FEditorComponentSourceFactory(); + + Initialize(QueriesAPI, TransactionAPI); + + // enable auto invalidation in Editor, because invalidating for all hover and capture events is unpleasant + this->InputRouter->bAutoInvalidateOnHover = true; + this->InputRouter->bAutoInvalidateOnCapture = true; + + + // set up standard materials + StandardVertexColorMaterial = LoadObject(nullptr, TEXT("/Game/Materials/VertexColor")); +} + + +void UEdModeInteractiveToolsContext::ShutdownContext() +{ + Shutdown(); + + if (QueriesAPI != nullptr) + { + delete QueriesAPI; + QueriesAPI = nullptr; + } + + if (TransactionAPI != nullptr) + { + delete TransactionAPI; + TransactionAPI = nullptr; + } + + if (AssetAPI != nullptr) + { + delete AssetAPI; + AssetAPI = nullptr; + } + + if (SourceFactory != nullptr) + { + delete SourceFactory; + SourceFactory = nullptr; + } + + this->EditorMode = nullptr; +} + + + + +void UEdModeInteractiveToolsContext::TerminateActiveToolsOnPIEStart() +{ + if (ToolManager->HasActiveTool(EToolSide::Left)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Left) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Left, ShutdownType); + } + if (ToolManager->HasActiveTool(EToolSide::Right)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Right) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Right, ShutdownType); + } +} +void UEdModeInteractiveToolsContext::TerminateActiveToolsOnSaveWorld() +{ + if (ToolManager->HasActiveTool(EToolSide::Left)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Left) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Left, ShutdownType); + } + if (ToolManager->HasActiveTool(EToolSide::Right)) + { + EToolShutdownType ShutdownType = ToolManager->CanAcceptActiveTool(EToolSide::Right) ? + EToolShutdownType::Accept : EToolShutdownType::Cancel; + ToolManager->DeactivateTool(EToolSide::Right, ShutdownType); + } +} + + + + + +void UEdModeInteractiveToolsContext::PostInvalidation() +{ + bInvalidationPending = true; +} + + +void UEdModeInteractiveToolsContext::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) +{ + ToolManager->Tick(DeltaTime); + GizmoManager->Tick(DeltaTime); + + if (bInvalidationPending) + { + ViewportClient->Invalidate(); + bInvalidationPending = false; + } + + // save this view + // Check against GCurrentLevelEditingViewportClient is temporary and should be removed in future. + // Current issue is that this ::Tick() is called *per viewport*, so once for each view in a 4-up view. + if (ViewportClient == GCurrentLevelEditingViewportClient) + { + ((FEdModeToolsContextQueriesImpl*)this->QueriesAPI)->CacheCurrentViewState(ViewportClient); + } +} + + + +class TempRenderContext : public IToolsContextRenderAPI +{ +public: + FPrimitiveDrawInterface* PDI; + + virtual FPrimitiveDrawInterface* GetPrimitiveDrawInterface() override + { + return PDI; + } +}; + + +void UEdModeInteractiveToolsContext::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + TempRenderContext RenderContext; + RenderContext.PDI = PDI; + ToolManager->Render(&RenderContext); + GizmoManager->Render(&RenderContext); +} + + + + +bool UEdModeInteractiveToolsContext::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ +#ifdef ENABLE_DEBUG_PRINTING + if (Event == IE_Pressed) { UE_LOG(LogTemp, Warning, TEXT("PRESSED EVENT")); } + else if (Event == IE_Released) { UE_LOG(LogTemp, Warning, TEXT("RELEASED EVENT")); } + else if (Event == IE_Repeat) { UE_LOG(LogTemp, Warning, TEXT("REPEAT EVENT")); } + else if (Event == IE_Axis) { UE_LOG(LogTemp, Warning, TEXT("AXIS EVENT")); } + else if (Event == IE_DoubleClick) { UE_LOG(LogTemp, Warning, TEXT("DOUBLECLICK EVENT")); } +#endif + + bool bHandled = false; + + + // escape key cancels current tool + if (Key == EKeys::Escape && Event == IE_Released ) + { + if (ToolManager->HasAnyActiveTool()) + { + if (ToolManager->HasActiveTool(EToolSide::Mouse)) + { + ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Cancel); + } + return true; + } + } + + // enter key accepts current tool, or ends tool if it does not have accept state + if (Key == EKeys::Enter && Event == IE_Released && ToolManager->HasAnyActiveTool()) + { + if (ToolManager->HasActiveTool(EToolSide::Mouse)) + { + if (ToolManager->GetActiveTool(EToolSide::Mouse)->HasAccept()) + { + if (ToolManager->CanAcceptActiveTool(EToolSide::Mouse)) + { + ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Accept); + return true; + } + } + else + { + ToolManager->DeactivateTool(EToolSide::Mouse, EToolShutdownType::Completed); + return true; + } + } + } + + // if alt is down we do not process mouse event + if (ViewportClient->IsAltPressed()) + { + return false; + } + + if (Event == IE_Pressed || Event == IE_Released) + { + if (Key.IsMouseButton()) + { + bool bIsLeftMouse = (Key == EKeys::LeftMouseButton); + bool bIsMiddleMouse = (Key == EKeys::MiddleMouseButton); + bool bIsRightMouse = (Key == EKeys::RightMouseButton); + + if (bIsLeftMouse || bIsMiddleMouse || bIsRightMouse) + { + + // early-out here if we are going to do camera manipulation + if (ViewportClient->IsAltPressed()) + { + return bHandled; + } + + FInputDeviceState InputState = CurrentMouseState; + InputState.InputDevice = EInputDevices::Mouse; + InputState.SetModifierKeyStates( + ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), + ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); + + if (bIsLeftMouse) + { + InputState.Mouse.Left.SetStates( + (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); + CurrentMouseState.Mouse.Left.bDown = (Event == IE_Pressed); + } + else if (bIsMiddleMouse) + { + InputState.Mouse.Middle.SetStates( + (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); + CurrentMouseState.Mouse.Middle.bDown = (Event == IE_Pressed); + } + else + { + InputState.Mouse.Right.SetStates( + (Event == IE_Pressed), (Event == IE_Pressed), (Event == IE_Released)); + CurrentMouseState.Mouse.Right.bDown = (Event == IE_Pressed); + } + + InputRouter->PostInputEvent(InputState); + + if (InputRouter->HasActiveMouseCapture()) + { + // what is this about? MeshPaintMode has it... + ViewportClient->bLockFlightCamera = true; + bHandled = true; // indicate that we handled this event, + // which will disable camera movement/etc ? + } + else + { + //ViewportClient->bLockFlightCamera = false; + } + + } + } + else if (Key.IsGamepadKey()) + { + // not supported yet + } + else if (Key.IsTouch()) + { + // not supported yet + } + else if (Key.IsFloatAxis() || Key.IsVectorAxis()) + { + // not supported yet + } + else // is this definitely a keyboard key? + { + FInputDeviceState InputState; + InputState.InputDevice = EInputDevices::Keyboard; + InputState.SetModifierKeyStates( + ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), + ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); + InputState.Keyboard.ActiveKey.Button = Key; + bool bPressed = (Event == IE_Pressed); + InputState.Keyboard.ActiveKey.SetStates(bPressed, bPressed, !bPressed); + InputRouter->PostInputEvent(InputState); + } + + } + + return bHandled; +} + + + + +bool UEdModeInteractiveToolsContext::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ +#ifdef ENABLE_DEBUG_PRINTING + UE_LOG(LogTemp, Warning, TEXT("MOUSE ENTER")); +#endif + + CurrentMouseState.Mouse.Position2D = FVector2D(x, y); + CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y); + + return false; +} + + +bool UEdModeInteractiveToolsContext::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) +{ +#ifdef ENABLE_DEBUG_PRINTING + //UE_LOG(LogTemp, Warning, TEXT("MOUSE MOVE")); +#endif + + CurrentMouseState.Mouse.Position2D = FVector2D(x, y); + CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(ViewportClient, Viewport, x, y); + FInputDeviceState InputState = CurrentMouseState; + InputState.InputDevice = EInputDevices::Mouse; + + InputState.SetModifierKeyStates( + ViewportClient->IsShiftPressed(), ViewportClient->IsAltPressed(), + ViewportClient->IsCtrlPressed(), ViewportClient->IsCmdPressed()); + + if (InputRouter->HasActiveMouseCapture()) + { + // This state occurs if InputBehavior did not release capture on mouse release. + // UMultiClickSequenceInputBehavior does this, eg for multi-click draw-polygon sequences. + // It's not ideal though and maybe would be better done via multiple captures + hover...? + InputRouter->PostInputEvent(InputState); + } + else + { + InputRouter->PostHoverInputEvent(InputState); + } + + return false; +} + + +bool UEdModeInteractiveToolsContext::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) +{ +#ifdef ENABLE_DEBUG_PRINTING + UE_LOG(LogTemp, Warning, TEXT("MOUSE LEAVE")); +#endif + + return false; +} + + + +bool UEdModeInteractiveToolsContext::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + return false; +} + +bool UEdModeInteractiveToolsContext::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) +{ +#ifdef ENABLE_DEBUG_PRINTING + //UE_LOG(LogTemp, Warning, TEXT("CAPTURED MOUSE MOVE")); +#endif + + // if alt is down we will not allow client to see this event + if (InViewportClient->IsAltPressed()) + { + return false; + } + + FVector2D OldPosition = CurrentMouseState.Mouse.Position2D; + CurrentMouseState.Mouse.Position2D = FVector2D(InMouseX, InMouseY); + CurrentMouseState.Mouse.WorldRay = GetRayFromMousePos(InViewportClient, InViewport, InMouseX, InMouseY); + + if (InputRouter->HasActiveMouseCapture()) + { + FInputDeviceState InputState = CurrentMouseState; + InputState.InputDevice = EInputDevices::Mouse; + InputState.SetModifierKeyStates( + InViewportClient->IsShiftPressed(), InViewportClient->IsAltPressed(), + InViewportClient->IsCtrlPressed(), InViewportClient->IsCmdPressed()); + InputState.Mouse.Delta2D = CurrentMouseState.Mouse.Position2D - OldPosition; + InputRouter->PostInputEvent(InputState); + return true; + } + + return false; +} + + +bool UEdModeInteractiveToolsContext::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ +#ifdef ENABLE_DEBUG_PRINTING + UE_LOG(LogTemp, Warning, TEXT("END TRACKING")); +#endif + + return true; +} + + + + + + +FRay UEdModeInteractiveToolsContext::GetRayFromMousePos(FEditorViewportClient* ViewportClient, FViewport* Viewport, int MouseX, int MouseY) +{ + FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( + ViewportClient->Viewport, + ViewportClient->GetScene(), + ViewportClient->EngineShowFlags) + .SetRealtimeUpdate(ViewportClient->IsRealtime())); + FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily); + FViewportCursorLocation MouseViewportRay(View, (FEditorViewportClient*)Viewport->GetClient(), MouseX, MouseY); + + return FRay(MouseViewportRay.GetOrigin(), MouseViewportRay.GetDirection(), true); +} + + + + + + +bool UEdModeInteractiveToolsContext::CanStartTool(const FString& ToolTypeIdentifier) const +{ + return (ToolManager->HasActiveTool(EToolSide::Mouse) == false) && + (ToolManager->CanActivateTool(EToolSide::Mouse, ToolTypeIdentifier) == true); +} +bool UEdModeInteractiveToolsContext::ActiveToolHasAccept() const +{ + return ToolManager->HasActiveTool(EToolSide::Mouse) && + ToolManager->GetActiveTool(EToolSide::Mouse)->HasAccept(); +} +bool UEdModeInteractiveToolsContext::CanAcceptActiveTool() const +{ + return ToolManager->CanAcceptActiveTool(EToolSide::Mouse); +} +bool UEdModeInteractiveToolsContext::CanCancelActiveTool() const +{ + return ToolManager->CanCancelActiveTool(EToolSide::Mouse); +} + +bool UEdModeInteractiveToolsContext::CanCompleteActiveTool() const +{ + return ToolManager->HasActiveTool(EToolSide::Mouse) && CanCancelActiveTool() == false; +} +void UEdModeInteractiveToolsContext::StartTool(const FString& ToolTypeIdentifier) +{ + if (ToolManager->SelectActiveToolType(EToolSide::Mouse, ToolTypeIdentifier) == false) + { + UE_LOG(LogTemp, Warning, TEXT("ToolManager: Unknown Tool Type %s"), *ToolTypeIdentifier); + } + else + { + ToolManager->ActivateTool(EToolSide::Mouse); + } +} +void UEdModeInteractiveToolsContext::EndTool(EToolShutdownType ShutdownType) +{ + ToolManager->DeactivateTool(EToolSide::Mouse, ShutdownType); +} \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorComponentSourceFactory.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorComponentSourceFactory.cpp new file mode 100644 index 000000000000..5799eb513e56 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorComponentSourceFactory.cpp @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorComponentSourceFactory.h" + +#include "Engine/StaticMesh.h" +#include "Components/ActorComponent.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/BodySetup.h" + + +TUniquePtr FEditorComponentSourceFactory::MakeMeshDescriptionSource(UActorComponent* Component) +{ + UStaticMeshComponent* StaticMeshComp = Cast(Component); + if (StaticMeshComp != nullptr) + { + auto NewSource = new FStaticMeshComponentMeshDescriptionSource(StaticMeshComp, 0); + return TUniquePtr(NewSource); + } + return nullptr; +} + + + + + +FStaticMeshComponentMeshDescriptionSource::FStaticMeshComponentMeshDescriptionSource( + UStaticMeshComponent* ComponentIn, int LODIndex) +{ + this->Component = ComponentIn; + this->LODIndex = LODIndex; +} + + +AActor* FStaticMeshComponentMeshDescriptionSource::GetOwnerActor() const +{ + return Component->GetOwner(); +} + +UActorComponent* FStaticMeshComponentMeshDescriptionSource::GetOwnerComponent() const +{ + return Component; +} + + +void FStaticMeshComponentMeshDescriptionSource::SetOwnerVisibility(bool bVisible) const +{ + Component->SetVisibility(bVisible); +} + +FMeshDescription* FStaticMeshComponentMeshDescriptionSource::GetMeshDescription() const +{ + return Component->GetStaticMesh()->GetMeshDescription(0); +} + +UMaterialInterface* FStaticMeshComponentMeshDescriptionSource::GetMaterial(int32 MaterialIndex) const +{ + return Component->GetMaterial(MaterialIndex); +} + +FTransform FStaticMeshComponentMeshDescriptionSource::GetWorldTransform() const +{ + //return Component->GetOwner()->GetActorTransform(); + return Component->GetComponentTransform(); +} + + + +bool FStaticMeshComponentMeshDescriptionSource::IsReadOnly() const +{ + return false; +} + + +void FStaticMeshComponentMeshDescriptionSource::CommitInPlaceModification(const TFunction& ModifyFunction) +{ + //bool bSaved = Component->Modify(); + //check(bSaved); + UStaticMesh* StaticMesh = Component->GetStaticMesh(); + + // make sure transactional flag is on + StaticMesh->SetFlags(RF_Transactional); + + bool bSavedToTransactionBuffer = StaticMesh->Modify(); + check(bSavedToTransactionBuffer); + FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex); + + ModifyFunction(MeshDescription); + + StaticMesh->CommitMeshDescription(LODIndex); + StaticMesh->PostEditChange(); + + // this rebuilds physics, but it doesn't undo! + Component->RecreatePhysicsState(); + + //Component->PostEditChange(); +} + + +bool FStaticMeshComponentMeshDescriptionSource::HitTest(const FRay& WorldRay, FHitResult& OutHit) const +{ + FVector End = WorldRay.PointAt(HALF_WORLD_MAX); + if (Component->LineTraceComponent(OutHit, WorldRay.Origin, End, FCollisionQueryParams(SCENE_QUERY_STAT(HitTest), true))) + { + return true; + } + + return false; +} \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorInteractiveToolsFrameworkModule.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorInteractiveToolsFrameworkModule.cpp new file mode 100644 index 000000000000..c2e4272e8335 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorInteractiveToolsFrameworkModule.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorInteractiveToolsFrameworkModule.h" + +#define LOCTEXT_NAMESPACE "FEditorInteractiveToolsFrameworkModule" + +void FEditorInteractiveToolsFrameworkModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FEditorInteractiveToolsFrameworkModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FEditorInteractiveToolsFrameworkModule, EditorInteractiveToolsFramework) \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorToolAssetAPI.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorToolAssetAPI.cpp new file mode 100644 index 000000000000..02e3e6e276ed --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/EditorToolAssetAPI.cpp @@ -0,0 +1,93 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditorToolAssetAPI.h" +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" + + +#include "AssetRegistryModule.h" +#include "FileHelpers.h" +#include "PackageTools.h" +#include "ObjectTools.h" + +// for content-browser things +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" + + +FString FEditorToolAssetAPI::GetActiveAssetFolderPath() +{ + check(false); // [RMS] this only works if we have an asset selected! + + IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked("ContentBrowser").Get(); + + //ContentBrowser.CreatePathPicker() + + TArray SelectedAssets; + ContentBrowser.GetSelectedAssets(SelectedAssets); + return SelectedAssets[0].PackagePath.ToString(); +} + + + +FString FEditorToolAssetAPI::MakePackageName(const FString& AssetName, const FString& FolderPath) +{ + return FolderPath + TEXT("/") + AssetName; +} + + +FString FEditorToolAssetAPI::MakeUniqueAssetName(const FString& AssetName, const FString& FolderPath) +{ + FString NewPackageName = MakePackageName(AssetName, FolderPath); + if (FPackageName::DoesPackageExist(NewPackageName) == false) + { + return AssetName; + } + + int Counter = 1; + FString UniqueName = AssetName; + while ( FPackageName::DoesPackageExist(NewPackageName) ) + { + UniqueName = AssetName + FString::Printf(TEXT("_%d"), Counter++); + NewPackageName = MakePackageName(UniqueName, FolderPath); + } + return UniqueName; +} + + +UPackage* FEditorToolAssetAPI::CreateNewPackage(const FString& AssetName, const FString& FolderPath) +{ + FString NewPackageName = MakePackageName(AssetName, FolderPath); + NewPackageName = UPackageTools::SanitizePackageName(NewPackageName); + UPackage* AssetPackage = CreatePackage(nullptr, *NewPackageName); + return AssetPackage; +} + + + +void FEditorToolAssetAPI::InteractiveSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) +{ + Asset->MarkPackageDirty(); + + FAssetRegistryModule::AssetCreated(Asset); + + TArray PackagesToSave; + PackagesToSave.Add(AssetPackage); + bool bCheckDirty = true; + bool bPromptToSave = true; + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); +} + + +void FEditorToolAssetAPI::AutoSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) +{ + Asset->MarkPackageDirty(); + + FAssetRegistryModule::AssetCreated(Asset); + + TArray PackagesToSave; + PackagesToSave.Add(AssetPackage); + bool bCheckDirty = true; + bool bPromptToSave = false; + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); +} \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/StandardToolModeCommands.cpp b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/StandardToolModeCommands.cpp new file mode 100644 index 000000000000..29a3978a0e4c --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Private/StandardToolModeCommands.cpp @@ -0,0 +1,38 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "StandardToolModeCommands.h" +#include "EditorStyleSet.h" + +#define LOCTEXT_NAMESPACE "StandardToolModeCommands" + + +FStandardToolModeCommands::FStandardToolModeCommands() : + TCommands( + "StandardToolCommands", // Context name for fast lookup + NSLOCTEXT("Contexts", "StandardToolCommands", "Standard Tool Commands"), // Localized context name for displaying + NAME_None, // Parent + FEditorStyle::GetStyleSetName() // Icon Style Set + ) +{} + + +void FStandardToolModeCommands::RegisterCommands() +{ + TSharedPtr IncreaseBrushSize; + UI_COMMAND(IncreaseBrushSize, "Increase Brush Size", "Increases the size of the brush", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket)); + Commands.Add(EStandardToolModeCommands::IncreaseBrushSize, IncreaseBrushSize); + + TSharedPtr DecreaseBrushSize; + UI_COMMAND(DecreaseBrushSize, "Decrease Brush Size", "Decreases the size of the brush", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket)); + Commands.Add(EStandardToolModeCommands::DecreaseBrushSize, DecreaseBrushSize); +} + + +TSharedPtr FStandardToolModeCommands::FindStandardCommand(EStandardToolModeCommands Command) const +{ + const TSharedPtr* Found = Commands.Find(Command); + checkf(Found != nullptr, TEXT("FStandardToolModeCommands::FindStandardCommand: standard command %d was not found!"), Command); + return (Found != nullptr) ? *Found : nullptr; +} + + diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EdModeInteractiveToolsContext.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EdModeInteractiveToolsContext.h new file mode 100644 index 000000000000..82e97b36f626 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EdModeInteractiveToolsContext.h @@ -0,0 +1,99 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EdMode.h" +#include "InteractiveToolsContext.h" +#include "EdModeInteractiveToolsContext.generated.h" + +/** + * EdModeInteractiveToolsContext is an extension/adapter of an InteractiveToolsContext which + * allows it to be easily embedded inside an FEdMode. A set of functions are provided which can be + * called from the FEdMode functions of the same name. These will handle the data type + * conversions and forwarding calls necessary to operate the ToolsContext + */ +UCLASS(Transient) +class EDITORINTERACTIVETOOLSFRAMEWORK_API UEdModeInteractiveToolsContext : public UInteractiveToolsContext +{ + GENERATED_BODY() + +public: + UEdModeInteractiveToolsContext(); + + + virtual void InitializeContextFromEdMode(FEdMode* EditorMode); + virtual void ShutdownContext(); + + // default behavior is to accept active tool + virtual void TerminateActiveToolsOnPIEStart(); + + // default behavior is to accept active tool + virtual void TerminateActiveToolsOnSaveWorld(); + + IToolsContextQueriesAPI* GetQueriesAPI() const { return QueriesAPI; } + IToolsContextTransactionsAPI* GetTransactionAPI() const { return TransactionAPI; } + IToolsContextAssetAPI* GetAssetAPI() const { return AssetAPI; } + IComponentSourceFactory* GetComponentSourceFactory() const { return SourceFactory; } + + virtual void PostInvalidation(); + + + // call these from your FEdMode functions of the same name + + virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime); + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI); + + virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event); + + virtual bool MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y); + virtual bool MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport); + virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y); + + virtual bool StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport); + virtual bool CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY); + virtual bool EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport); + + + // + // Utility functions useful for hooking up to UICommand/etc + // + + virtual bool CanStartTool(const FString& ToolTypeIdentifier) const; + virtual bool ActiveToolHasAccept() const; + virtual bool CanAcceptActiveTool() const; + virtual bool CanCancelActiveTool() const; + virtual bool CanCompleteActiveTool() const; + virtual void StartTool(const FString& ToolTypeIdentifier); + virtual void EndTool(EToolShutdownType ShutdownType); + + +protected: + // we hide these + virtual void Initialize(IToolsContextQueriesAPI* QueriesAPI, IToolsContextTransactionsAPI* TransactionsAPI) override; + virtual void Shutdown() override; + + +public: + UPROPERTY() + UMaterialInterface* StandardVertexColorMaterial; + +protected: + FEdMode* EditorMode; + + FDelegateHandle BeginPIEDelegateHandle; + FDelegateHandle PreSaveWorldDelegateHandle; + + IToolsContextQueriesAPI* QueriesAPI; + IToolsContextTransactionsAPI* TransactionAPI; + IToolsContextAssetAPI* AssetAPI; + IComponentSourceFactory* SourceFactory; + + bool bInvalidationPending; + + /** Input event instance used to keep track of various button states, etc, that we cannot directly query on-demand */ + FInputDeviceState CurrentMouseState; + + static FRay GetRayFromMousePos(FEditorViewportClient* ViewportClient, FViewport* Viewport, int MouseX, int MouseY); + +}; diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorComponentSourceFactory.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorComponentSourceFactory.h new file mode 100644 index 000000000000..06951f9d2094 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorComponentSourceFactory.h @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ComponentSourceInterfaces.h" + + + + +/** + * Component Source Factory for use in the Editor (ie inside FEdMode, etc) + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FEditorComponentSourceFactory : public IComponentSourceFactory +{ +public: + virtual ~FEditorComponentSourceFactory() {} + + virtual TUniquePtr MakeMeshDescriptionSource(UActorComponent* Component); +}; + + + + + +class UStaticMeshComponent; + +/** + * This MeshDescriptionSource provides a specific LOD from a StaticMeshComponent + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FStaticMeshComponentMeshDescriptionSource : public IMeshDescriptionSource +{ +public: + UStaticMeshComponent* Component; + int LODIndex; + + FStaticMeshComponentMeshDescriptionSource( + UStaticMeshComponent* ComponentIn, + int LODIndex = 0); + virtual ~FStaticMeshComponentMeshDescriptionSource() {} + + virtual AActor* GetOwnerActor() const override; + virtual UActorComponent* GetOwnerComponent() const override; + virtual FMeshDescription* GetMeshDescription() const override; + virtual UMaterialInterface* GetMaterial(int32 MaterialIndex) const override; + virtual FTransform GetWorldTransform() const override; + virtual bool HitTest(const FRay& WorldRay, FHitResult& OutHit) const override; + + virtual bool IsReadOnly() const override; + + virtual void SetOwnerVisibility(bool bVisible) const override; + virtual void CommitInPlaceModification(const TFunction& ModifyFunction) override; +}; + diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorInteractiveToolsFrameworkModule.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorInteractiveToolsFrameworkModule.h new file mode 100644 index 000000000000..6f6428eea95e --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorInteractiveToolsFrameworkModule.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FEditorInteractiveToolsFrameworkModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorToolAssetAPI.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorToolAssetAPI.h new file mode 100644 index 000000000000..3dc61a51893b --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/EditorToolAssetAPI.h @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ToolContextInterfaces.h" + +/** + * Implementation of ToolsContext Asset management API that is suitable for use + * inside the Editor (eg inside an FEdMode) + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FEditorToolAssetAPI : public IToolsContextAssetAPI +{ +public: + virtual FString GetActiveAssetFolderPath() override; + + virtual FString MakePackageName(const FString& AssetName, const FString& FolderPath) override; + + virtual FString MakeUniqueAssetName(const FString& AssetName, const FString& FolderPath) override; + + // FolderPath should neither start with, nor end with, path separators + // Generated path will be /Game/FolderPath/AssetName + virtual UPackage* CreateNewPackage(const FString& AssetName, const FString& FolderPath) override; + + virtual void InteractiveSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) override; + + virtual void AutoSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) override; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/InteractiveToolsCommands.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/InteractiveToolsCommands.h new file mode 100644 index 000000000000..5ca48d70770e --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/InteractiveToolsCommands.h @@ -0,0 +1,302 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" +#include "InteractiveTool.h" +#include "StandardToolModeCommands.h" + + + +/** + * TInteractiveToolCommands is a base class that handles connecting up Tool Actions + * (ie the FInteractiveToolAction provided by a UInteractiveTool) to the UnrealEditor + * Command system, which allows for remappable hotkeys, etc + * + * Usage is as follows: + * - in your EdMode Module, create a subclass-instance of this, say FMyToolCommands and call FMyToolCommands::Register() in your ::StartupModule() function + * - subclass must implement ::GetToolDefaultObjectList(), here you just add GetMutableDefault to the input list for all your Tools + * - subclass must implement ::GetNamespaceString(), likely you can just return TEXT(LOCTEXT_NAMESPACE) + * - add a member TSharedPtr UICommandList; to your EdMode impl + * - when you start a new Tool, call FMyToolCommands::Get().BindCommandsForCurrentTool(UICommandList, NewTool) + * - when you end a Tool, call FMyToolCommands::Get().UnbindActiveCommands() + * - in your EdModeImpl::InputKey override, call UICommandList->ProcessCommandBindings() + */ +template +class TInteractiveToolCommands : public TCommands +{ + +public: + /** + * Initialize commands. Collects possible Actions from the available UInteractiveTools + * (provided by GetToolDefaultObjectList) and then registers a FUICommandInfo for each. + * This needs to be called in ::StartupModule for your EdMode Module + */ + virtual void RegisterCommands() override; + + /** + * Bind any of the registered UICommands to the given Tool, if they are compatible. + */ + virtual void BindCommandsForCurrentTool(TSharedPtr UICommandList, UInteractiveTool* Tool) const; + + /** + * Unbind all of the currently-registered commands + */ + virtual void UnbindActiveCommands(TSharedPtr UICommandList) const; + + + // + // Interface that subclasses need to implement + // + + + /** + * RegisterCommands() needs actual UInteractiveTool instances for all the Tools that want to + * provide Actions which will be connected to hotkeys. We can do this based on the CDO's for + * each tool, returned by GetMutableDefault(). The Tool CDOs are + * not owned by a ToolManager and we will only call .GetActionSet() on them. + */ + virtual void GetToolDefaultObjectList(TArray& ToolCDOs) = 0; + + /** + * The Namespace used to identify this Command set + */ + virtual const TCHAR * GetNamespaceString() = 0; + + +protected: + + /** Forwarding constructor */ + TInteractiveToolCommands(const FName InContextName, const FText& InContextDesc, const FName InContextParent, const FName InStyleSetName) + : TCommands(InContextName, InContextDesc, InContextParent, InStyleSetName) + { + } + + /** + * Query FStandardToolModeCommands to find an existing command/hotkey for this standard tool action + */ + virtual TSharedPtr FindStandardCommand(EStandardToolActions ToolActionID); + + /** + * List of pairs of known Tool Actions and their associated UICommands. + * This list is used at bind-time to connect up the already-registered UICommands to the active Tool + */ + struct FToolActionCommand + { + FInteractiveToolAction ToolAction; + TSharedPtr UICommand; + }; + TArray ActionCommands; + + + // + // Support for sharing common commands between Tools where the commands + // are not shared across Tool Modes (and hence not in FStandardToolModeCommands) + // + + /** List FToolActionCommands for standard Tool Actions */ + TMap SharedActionCommands; + + /** + * Find or Create a UICommand for a standard Tool Action, that will be shared across Tools + */ + bool FindOrCreateSharedCommand(EStandardToolActions ActionID, FToolActionCommand& FoundOut); + + /** + * Create a suitable FInteractiveToolAction for the standard Tool Action, ie with suitable + * command name and description strings and hotkeys. This info will be used to create the shared UICommand. + */ + virtual bool IntializeStandardToolAction(EStandardToolActions ActionID, FInteractiveToolAction& ToolActionOut); + + /** + * Utility function that registeres a Tool Aciton as a UICommand + */ + void RegisterUIToolCommand(const FInteractiveToolAction& ToolAction, TSharedPtr& UICommandInfo); +}; + + + + + + +template +void TInteractiveToolCommands::RegisterCommands() +{ + // make sure standard commands are registered + FStandardToolModeCommands::Register(); + + // get list of all tools used by command set + TArray AllToolsCDOs; + GetToolDefaultObjectList(AllToolsCDOs); + + // collect all the actions in these tools + TArray ToolActions; + for (UInteractiveTool* Tool : AllToolsCDOs) + { + Tool->GetActionSet()->CollectActions(ToolActions); + } + + // register all the commands in all the actions + int NumActions = ToolActions.Num(); + ActionCommands.SetNum(NumActions); + for (int k = 0; k < NumActions; ++k) + { + const FInteractiveToolAction& ToolAction = ToolActions[k]; + ActionCommands[k].ToolAction = ToolAction; + + bool bRegistered = false; + if ((int32)ToolAction.ActionID < (int32)EStandardToolActions::BaseClientDefinedActionID) + { + TSharedPtr FoundStandard = FindStandardCommand((EStandardToolActions)ToolAction.ActionID); + if (FoundStandard.IsValid()) + { + ActionCommands[k].UICommand = FoundStandard; + bRegistered = true; + } + else + { + FToolActionCommand StandardActionCommand; + bool bFoundRemap = FindOrCreateSharedCommand((EStandardToolActions)ToolAction.ActionID, StandardActionCommand); + if (bFoundRemap) + { + ActionCommands[k].UICommand = StandardActionCommand.UICommand; + bRegistered = true; + } + } + } + + if (bRegistered == false) + { + RegisterUIToolCommand(ActionCommands[k].ToolAction, ActionCommands[k].UICommand); + } + } +} + + +template +void TInteractiveToolCommands::BindCommandsForCurrentTool(TSharedPtr UICommandList, UInteractiveTool* Tool) const +{ + UClass* ToolClassType = Tool->GetClass(); + + int NumActionCommands = ActionCommands.Num(); + for (int32 k = 0; k < NumActionCommands; ++k) + { + const FToolActionCommand& ActionCommand = ActionCommands[k]; + if (ActionCommand.ToolAction.ClassType == ToolClassType) + { + UICommandList->MapAction( + ActionCommand.UICommand, + FExecuteAction::CreateUObject(Tool, &UInteractiveTool::ExecuteAction, ActionCommand.ToolAction.ActionID)); + //FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + } + } +} + + +template +void TInteractiveToolCommands::UnbindActiveCommands(TSharedPtr UICommandList) const +{ + // @todo would be more efficient if we kept track of which commands were mapped. + // However currently this function must be const because TCommands::Get() returns a const + for (const FToolActionCommand& ActionCommand : ActionCommands) + { + if (UICommandList->IsActionMapped(ActionCommand.UICommand)) + { + UICommandList->UnmapAction(ActionCommand.UICommand); + } + } +} + + + + + + +// +// Support for re-using known commands from FStandardToolModeCommands +// + +template +TSharedPtr TInteractiveToolCommands::FindStandardCommand(EStandardToolActions ToolActionID) +{ + const FStandardToolModeCommands& StdCommands = FStandardToolModeCommands::Get(); + switch (ToolActionID) + { + case EStandardToolActions::IncreaseBrushSize: + return StdCommands.FindStandardCommand(EStandardToolModeCommands::IncreaseBrushSize); + case EStandardToolActions::DecreaseBrushSize: + return StdCommands.FindStandardCommand(EStandardToolModeCommands::DecreaseBrushSize); + default: + return nullptr; + } +} + + + + + + +// +// Support for sharing UI commands between multiple Tools, where the +// shared command is not at the shared-between-modes level +// + +template +bool TInteractiveToolCommands::FindOrCreateSharedCommand(EStandardToolActions ActionID, FToolActionCommand& FoundOut) +{ + if (SharedActionCommands.Contains(ActionID)) + { + FoundOut = SharedActionCommands[ActionID]; + return true; + } + + FInteractiveToolAction NewAction; + bool bIsKnownAction = IntializeStandardToolAction(ActionID, NewAction); + + if (bIsKnownAction) + { + check(NewAction.ActionID == (int32)ActionID); + FToolActionCommand NewActionCommand; + NewActionCommand.ToolAction = NewAction; + RegisterUIToolCommand(NewActionCommand.ToolAction, NewActionCommand.UICommand); + FoundOut = SharedActionCommands.Add(ActionID, NewActionCommand); + return true; + } + return false; +} + + +template +bool TInteractiveToolCommands::IntializeStandardToolAction(EStandardToolActions ActionID, FInteractiveToolAction& ToolActionOut) +{ + // base class does not have any standard Tool actions. Subclasses can override + // this function, populating ToolActionOut and returning true, if the ActionID should be shared between multiple Tools + + return false; +} + + + + +template +void TInteractiveToolCommands::RegisterUIToolCommand(const FInteractiveToolAction& ToolAction, TSharedPtr& UICommandInfo) +{ + FString TooltipString = ToolAction.ActionName + FString("_ToolTip"); + FString DotString = FString(".") + ToolAction.ActionName; + + UI_COMMAND_Function(this, + UICommandInfo, + GetNamespaceString(), + *ToolAction.ActionName, + *TooltipString, + TCHAR_TO_ANSI(*DotString), + *ToolAction.ShortName, *ToolAction.Description, + EUserInterfaceActionType::Button, + FInputChord(ToolAction.DefaultModifiers, ToolAction.DefaultKey)); +} + + + + diff --git a/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/StandardToolModeCommands.h b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/StandardToolModeCommands.h new file mode 100644 index 000000000000..d129f3a39bb0 --- /dev/null +++ b/Engine/Source/Editor/EditorInteractiveToolsFramework/Public/StandardToolModeCommands.h @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandInfo.h" + + +/** + * Integer identifiers for "Standard" Tool commands, that many EditorModes / AssetEditors / etc may share. + * This allows a single hotkey binding to be used across many contexts. + */ +enum class EStandardToolModeCommands +{ + IncreaseBrushSize = 100, + DecreaseBrushSize = 101 +}; + + + +/** + * FStandardToolModeCommands provides standard commands for Tools. This allows + * for "sharing" common hotkey bindings between multiple EditorModes + * + * The set of standard commands is defined by EStandardToolModeCommands. + * You must call FStandardToolModeCommands::Register() on module startup to register these commands + * (note that they may already be registered by another module, but you should call this to be safe). + * + * Then, when you want to Bind a standard command in your FUICommandList, + * call FStandardToolModeCommands::Get().FindStandardCommand() to get the registered UICommandInfo. + * + */ +class EDITORINTERACTIVETOOLSFRAMEWORK_API FStandardToolModeCommands : public TCommands +{ +public: + FStandardToolModeCommands(); + + /** + * Registers the set of standard commands. Call on module startup. + */ + virtual void RegisterCommands() override; + + /** + * Look up the UICommandInfo for a standard command + */ + TSharedPtr FindStandardCommand(EStandardToolModeCommands Command) const; + +protected: + TMap> Commands; + +}; + + diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp index c13d382639ff..562a21e35031 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp @@ -179,6 +179,12 @@ void FSlateEditorStyle::FStyle::Initialize() SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); SetupGeneralStyles(); + SetupLevelGeneralStyles(); + SetupWorldBrowserStyles(); + SetupSequencerStyles(); + SetupViewportStyles(); + SetupNotificationBarStyles(); + SetupMenuBarStyles(); SetupGeneralIcons(); SetupWindowStyles(); SetupPropertyEditorStyles(); @@ -1378,64 +1384,7 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() Set( "LayerBrowserButton.LabelFont", DEFAULT_FONT( "Regular", 8 ) ); } - // Levels General - { - Set( "Level.VisibleIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_visible_16px", Icon16x16 ) ); - Set( "Level.VisibleHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_visible_hi_16px", Icon16x16 ) ); - Set( "Level.NotVisibleIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_invisible_16px", Icon16x16 ) ); - Set( "Level.NotVisibleHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_invisible_hi_16px", Icon16x16 ) ); - Set( "Level.LightingScenarioIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LightingScenario_16px", Icon16x16 ) ); - Set( "Level.LightingScenarioNotIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LightingScenarioNot_16px", Icon16x16 ) ); - Set( "Level.LockedIcon16x", new IMAGE_BRUSH( "Icons/icon_locked_16px", Icon16x16 ) ); - Set( "Level.LockedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_locked_highlight_16px", Icon16x16 ) ); - Set( "Level.UnlockedIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_unlocked_16px", Icon16x16 ) ); - Set( "Level.UnlockedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_unlocked_hi_16px", Icon16x16 ) ); - Set( "Level.ReadOnlyLockedIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LockedReadOnly_16px", Icon16x16 ) ); - Set( "Level.ReadOnlyLockedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_LockedReadOnly_hi_16px", Icon16x16 ) ); - Set( "Level.SaveIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Save_16px", Icon16x16 ) ); - Set( "Level.SaveHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Save_hi_16px", Icon16x16 ) ); - Set( "Level.SaveModifiedIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveModified_16px", Icon16x16 ) ); - Set( "Level.SaveModifiedHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveModified_hi_16px", Icon16x16 ) ); - Set( "Level.SaveDisabledIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveDisabled_16px", Icon16x16 ) ); - Set( "Level.SaveDisabledHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_SaveDisabled_hi_16px", Icon16x16 ) ); - Set( "Level.ScriptIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Blueprint_16px", Icon16x16 ) ); - Set( "Level.ScriptHighlightIcon16x", new IMAGE_BRUSH( "Icons/icon_levels_Blueprint_hi_16px", Icon16x16 ) ); - Set( "Level.EmptyIcon16x", new IMAGE_BRUSH( "Icons/Empty_16x", Icon16x16 ) ); - Set( "Level.ColorIcon40x", new IMAGE_BRUSH( "Icons/icon_levels_back_16px", Icon16x16 ) ); - } - - // World Browser - { - Set( "WorldBrowser.AddLayer", new IMAGE_BRUSH( "Icons/icon_levels_addlayer_16x", Icon16x16 ) ); - Set( "WorldBrowser.SimulationViewPositon", new IMAGE_BRUSH( "Icons/icon_levels_simulationviewpos_16x", Icon16x16 ) ); - Set( "WorldBrowser.MouseLocation", new IMAGE_BRUSH( "Icons/icon_levels_mouselocation_16x", Icon16x16 ) ); - Set( "WorldBrowser.MarqueeRectSize", new IMAGE_BRUSH( "Icons/icon_levels_marqueerectsize_16x", Icon16x16 ) ); - Set( "WorldBrowser.WorldSize", new IMAGE_BRUSH( "Icons/icon_levels_worldsize_16x", Icon16x16 ) ); - Set( "WorldBrowser.WorldOrigin", new IMAGE_BRUSH( "Icons/icon_levels_worldorigin_16x", Icon16x16 ) ); - Set( "WorldBrowser.DirectionXPositive", new IMAGE_BRUSH( "Icons/icon_PanRight", Icon16x16 ) ); - Set( "WorldBrowser.DirectionXNegative", new IMAGE_BRUSH( "Icons/icon_PanLeft", Icon16x16 ) ); - Set( "WorldBrowser.DirectionYPositive", new IMAGE_BRUSH( "Icons/icon_PanUp", Icon16x16 ) ); - Set( "WorldBrowser.DirectionYNegative", new IMAGE_BRUSH( "Icons/icon_PanDown", Icon16x16 ) ); - Set( "WorldBrowser.LevelStreamingAlwaysLoaded", new FSlateNoResource() ); - Set( "WorldBrowser.LevelStreamingBlueprint", new IMAGE_BRUSH( "Icons/icon_levels_blueprinttype_7x16", Icon7x16 ) ); - Set( "WorldBrowser.LevelsMenuBrush", new IMAGE_BRUSH( "Icons/icon_levels_levelsmenu_40x", Icon25x25 ) ); - Set( "WorldBrowser.HierarchyButtonBrush", new IMAGE_BRUSH( "Icons/icon_levels_hierarchybutton_16x", Icon16x16 ) ); - Set( "WorldBrowser.DetailsButtonBrush", new IMAGE_BRUSH( "Icons/icon_levels_detailsbutton_40x", Icon16x16 ) ); - Set( "WorldBrowser.CompositionButtonBrush", new IMAGE_BRUSH( "Icons/icon_levels_compositionbutton_16x", Icon16x16 ) ); - - Set( "WorldBrowser.FolderClosed", new IMAGE_BRUSH( "Icons/FolderClosed", Icon16x16 ) ); - Set( "WorldBrowser.FolderOpen", new IMAGE_BRUSH( "Icons/FolderOpen", Icon16x16 ) ); - Set( "WorldBrowser.NewFolderIcon", new IMAGE_BRUSH( "Icons/icon_AddFolder_16x", Icon16x16 ) ); - - Set( "WorldBrowser.StatusBarText", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "BoldCondensed", 12 ) ) - .SetColorAndOpacity( FLinearColor(0.9, 0.9f, 0.9f, 0.5f) ) - .SetShadowOffset( FVector2D::ZeroVector ) - ); - - Set( "WorldBrowser.LabelFont", DEFAULT_FONT( "Regular", 9 ) ); - Set( "WorldBrowser.LabelFontBold", DEFAULT_FONT( "Bold", 10 ) ); - } + // Scene Outliner { @@ -1779,294 +1728,6 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() Set( "BuildAndSubmit.SmallFont", DEFAULT_FONT( "Regular", 7 ) ); } - // Sequencer - if (IncludeEditorSpecificStyles()) - { - Set( "Sequencer.IconKeyAuto", new IMAGE_BRUSH( "Sequencer/IconKeyAuto", Icon12x12 ) ); - Set( "Sequencer.IconKeyBreak", new IMAGE_BRUSH( "Sequencer/IconKeyBreak", Icon12x12 ) ); - Set( "Sequencer.IconKeyConstant", new IMAGE_BRUSH( "Sequencer/IconKeyConstant", Icon12x12 ) ); - Set( "Sequencer.IconKeyLinear", new IMAGE_BRUSH( "Sequencer/IconKeyLinear", Icon12x12 ) ); - Set( "Sequencer.IconKeyUser", new IMAGE_BRUSH( "Sequencer/IconKeyUser", Icon12x12 ) ); - - Set( "Sequencer.KeyCircle", new IMAGE_BRUSH( "Sequencer/KeyCircle", Icon12x12 ) ); - Set( "Sequencer.KeyDiamond", new IMAGE_BRUSH( "Sequencer/KeyDiamond", Icon12x12 ) ); - Set( "Sequencer.KeyDiamondBorder", new IMAGE_BRUSH( "Sequencer/KeyDiamondBorder", Icon12x12 ) ); - Set( "Sequencer.KeySquare", new IMAGE_BRUSH( "Sequencer/KeySquare", Icon12x12 ) ); - Set( "Sequencer.KeyTriangle", new IMAGE_BRUSH( "Sequencer/KeyTriangle", Icon12x12 ) ); - Set( "Sequencer.KeyLeft", new IMAGE_BRUSH( "Sequencer/KeyLeft", Icon12x12 ) ); - Set( "Sequencer.KeyRight", new IMAGE_BRUSH( "Sequencer/KeyRight", Icon12x12 ) ); - Set( "Sequencer.PartialKey", new IMAGE_BRUSH( "Sequencer/PartialKey", FVector2D(11.f, 11.f) ) ); - Set( "Sequencer.Star", new IMAGE_BRUSH( "Sequencer/Star", Icon12x12 ) ); - Set( "Sequencer.Empty", new IMAGE_BRUSH( "Sequencer/Empty", Icon12x12 ) ); - Set( "Sequencer.TangentHandle", new IMAGE_BRUSH( "Sequencer/TangentHandle", FVector2D(7, 7) ) ); - Set( "Sequencer.GenericDivider", new IMAGE_BRUSH( "Sequencer/GenericDivider", FVector2D(2.f, 2.f), FLinearColor::White, ESlateBrushTileType::Vertical ) ); - - Set("Sequencer.Timeline.ScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown", FMargin(6.f / 13.f, 5 / 12.f, 6 / 13.f, 8 / 12.f))); - Set("Sequencer.Timeline.ScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); - Set( "Sequencer.Timeline.ScrubFill", new BOX_BRUSH( "Sequencer/ScrubFill", FMargin( 2.f/4.f, 0.f ) ) ); - Set( "Sequencer.Timeline.FrameBlockScrubHandleDown", new BOX_BRUSH( "Sequencer/ScrubHandleDown", FMargin( 6.f/13.f, 5/12.f, 6/13.f, 8/12.f ) ) ); - Set( "Sequencer.Timeline.FrameBlockScrubHandleUp", new BOX_BRUSH( "Sequencer/ScrubHandleUp", FMargin( 6.f/13.f, 8/12.f, 6/13.f, 5/12.f ) ) ); - Set( "Sequencer.Timeline.VanillaScrubHandleDown", new BOX_BRUSH( "Sequencer/ScrubHandleDown_Clamped", FMargin( 6.f/13.f, 3.f/12.f, 6.f/13.f, 7.f/12.f ) ) ); - Set( "Sequencer.Timeline.VanillaScrubHandleUp", new BOX_BRUSH( "Sequencer/ScrubHandleUp_Clamped", FMargin( 6.f/13.f, 8/12.f, 6/13.f, 5/12.f ) ) ); - Set( "Sequencer.Timeline.ScrubHandleWhole", new BOX_BRUSH( "Sequencer/ScrubHandleWhole", FMargin( 6.f/13.f, 10/24.f, 6/13.f, 10/24.f ) ) ); - Set( "Sequencer.Timeline.RangeHandleLeft", new BOX_BRUSH( "Sequencer/GenericGripLeft", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Timeline.RangeHandleRight", new BOX_BRUSH( "Sequencer/GenericGripRight", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Timeline.RangeHandle", new BOX_BRUSH( "Sequencer/GenericSectionBackground", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Timeline.NotifyAlignmentMarker", new IMAGE_BRUSH( "Sequencer/NotifyAlignmentMarker", FVector2D(10,19) ) ); - Set( "Sequencer.Timeline.PlayRange_Top_L", new BOX_BRUSH( "Sequencer/PlayRange_Top_L", FMargin(1.f, 0.5f, 0.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_Top_R", new BOX_BRUSH( "Sequencer/PlayRange_Top_R", FMargin(0.f, 0.5f, 1.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_L", new BOX_BRUSH( "Sequencer/PlayRange_L", FMargin(1.f, 0.5f, 0.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_R", new BOX_BRUSH( "Sequencer/PlayRange_R", FMargin(0.f, 0.5f, 1.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_Bottom_L", new BOX_BRUSH( "Sequencer/PlayRange_Bottom_L", FMargin(1.f, 0.5f, 0.f, 0.5f) ) ); - Set( "Sequencer.Timeline.PlayRange_Bottom_R", new BOX_BRUSH( "Sequencer/PlayRange_Bottom_R", FMargin(0.f, 0.5f, 1.f, 0.5f) ) ); - - Set( "Sequencer.Timeline.SubSequenceRangeHashL", new BORDER_BRUSH( "Sequencer/SubSequenceRangeHashL", FMargin(1.f, 0.f, 0.f, 0.f) ) ); - Set( "Sequencer.Timeline.SubSequenceRangeHashR", new BORDER_BRUSH( "Sequencer/SubSequenceRangeHashR", FMargin(1.f, 0.f, 0.f, 0.f) ) ); - Set( "Sequencer.Timeline.EaseInOut", new IMAGE_BRUSH( "Sequencer/EaseInOut", FVector2D(128, 128) ) ); - Set( "Sequencer.InterpLine", new BOX_BRUSH( "Sequencer/InterpLine", FMargin(5.f/7.f, 0.f, 0.f, 0.f) ) ); - - Set( "Sequencer.Transport.JumpToPreviousKey", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24 ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Previous_Frame", Icon24x24 ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24 ) )); - Set( "Sequencer.Transport.JumpToNextKey", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24 ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Next_Frame_24x", Icon24x24 ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24 ) ) ); - Set( "Sequencer.Transport.SetPlayStart", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16,24) ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_In_16x24", FVector2D(16,24) ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16,24) ) ) ); - Set( "Sequencer.Transport.SetPlayEnd", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16,24) ) ) - .SetPressed( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_Out_16x24", FVector2D(16,24) ) ) - .SetHovered( IMAGE_BRUSH( "/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16,24) ) ) ); - - Set( "Sequencer.Transport.CloseButton", FButtonStyle() - .SetNormal ( IMAGE_BRUSH( "/Docking/CloseApp_Normal", Icon16x16 ) ) - .SetPressed( IMAGE_BRUSH( "/Docking/CloseApp_Pressed", Icon16x16 ) ) - .SetHovered( IMAGE_BRUSH( "/Docking/CloseApp_Hovered", Icon16x16 ))); - - Set( "Sequencer.NotificationImage_AddedPlayMovieSceneEvent", new IMAGE_BRUSH( "Old/Checkbox_checked", Icon16x16 ) ); - - Set( "Sequencer.Save", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon48x48) ); - Set( "Sequencer.Save.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon24x24) ); - Set( "Sequencer.SaveAsterisk", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon48x48) ); - Set( "Sequencer.SaveAsterisk.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon24x24) ); - Set( "Sequencer.SaveAs", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon48x48 ) ); - Set( "Sequencer.SaveAs.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon24x24 ) ); - Set( "Sequencer.DiscardChanges", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon48x48 ) ); - Set( "Sequencer.DiscardChanges.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon24x24 ) ); - Set( "Sequencer.RestoreAnimatedState", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon48x48 ) ); - Set( "Sequencer.RestoreAnimatedState.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon24x24 ) ); - Set( "Sequencer.GenericGripLeft", new BOX_BRUSH( "Sequencer/GenericGripLeft", FMargin(5.f/16.f) ) ); - Set( "Sequencer.GenericGripRight", new BOX_BRUSH( "Sequencer/GenericGripRight", FMargin(5.f/16.f) ) ); - Set( "Sequencer.SectionArea.Background", new FSlateColorBrush( FColor::White ) ); - - Set( "Sequencer.Section.Background", new BORDER_BRUSH( TEXT("Sequencer/SectionBackground"), FMargin(4.f/16.f) ) ); - Set( "Sequencer.Section.BackgroundTint", new BOX_BRUSH( TEXT("Sequencer/SectionBackgroundTint"), FMargin(4/16.f) ) ); - Set( "Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH( TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.Section.SelectedTrackTint", new BOX_BRUSH( TEXT("Sequencer/SelectedTrackTint"), FMargin(0.f, 0.5f) ) ); - Set( "Sequencer.Section.SelectionBorder", new BORDER_BRUSH( TEXT("Sequencer/SectionHighlight"), FMargin(7.f/16.f) ) ); - Set( "Sequencer.Section.LockedBorder", new BORDER_BRUSH( TEXT("Sequencer/SectionLocked"), FMargin(7.f/16.f) ) ); - Set( "Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH( TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.Section.FilmBorder", new IMAGE_BRUSH( TEXT("Sequencer/SectionFilmBorder"), FVector2D(10, 7), FLinearColor::White, ESlateBrushTileType::Horizontal ) ); - Set( "Sequencer.Section.GripLeft", new BOX_BRUSH( "Sequencer/SectionGripLeft", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Section.GripRight", new BOX_BRUSH( "Sequencer/SectionGripRight", FMargin(5.f/16.f) ) ); - Set( "Sequencer.Section.EasingHandle", new IMAGE_BRUSH( "Sequencer/EasingHandle", FVector2D(10.f,10.f) ) ); - - Set( "Sequencer.Section.PreRoll", new BORDER_BRUSH( TEXT("Sequencer/PreRoll"), FMargin(0.f, .5f, 0.f, .5f) ) ); - - Set( "Sequencer.Section.PinCusion", new IMAGE_BRUSH( TEXT("Sequencer/PinCusion"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.Section.OverlapBorder", new BORDER_BRUSH( TEXT("Sequencer/OverlapBorder"), FMargin(1.f/4.f, 0.f) ) ); - Set( "Sequencer.Section.StripeOverlay", new BOX_BRUSH( "Sequencer/SectionStripeOverlay", FMargin(0.f, .5f) ) ); - Set( "Sequencer.Section.BackgroundText", DEFAULT_FONT( "Bold", 24 ) ); - Set( "Sequencer.Section.EmptySpace", new BOX_BRUSH( TEXT("Sequencer/EmptySpace"), FMargin(0.f, 7.f/14.f) ) ); - - Set( "Sequencer.AnimationOutliner.ColorStrip", FButtonStyle() - .SetNormal(FSlateNoResource()) - .SetHovered(FSlateNoResource()) - .SetPressed(FSlateNoResource()) - .SetNormalPadding(FMargin(0,0,0,0)) - .SetPressedPadding(FMargin(0,0,0,0)) - ); - - Set( "Sequencer.AnimationOutliner.TopLevelBorder_Expanded", new BOX_BRUSH( "Sequencer/TopLevelNodeBorder_Expanded", FMargin(4.0f/16.0f) ) ); - Set( "Sequencer.AnimationOutliner.TopLevelBorder_Collapsed", new BOX_BRUSH( "Sequencer/TopLevelNodeBorder_Collapsed", FMargin(4.0f/16.0f) ) ); - Set( "Sequencer.AnimationOutliner.DefaultBorder", new FSlateColorBrush( FLinearColor::White ) ); - Set( "Sequencer.AnimationOutliner.TransparentBorder", new FSlateColorBrush( FLinearColor::Transparent ) ); - Set( "Sequencer.AnimationOutliner.BoldFont", DEFAULT_FONT( "Bold", 11 ) ); - Set( "Sequencer.AnimationOutliner.RegularFont", DEFAULT_FONT( "Regular", 9 ) ); - Set( "Sequencer.ShotFilter", new IMAGE_BRUSH( "Sequencer/FilteredArea", FVector2D(74,74), FLinearColor::White, ESlateBrushTileType::Both ) ); - Set( "Sequencer.KeyMark", new IMAGE_BRUSH("Sequencer/KeyMark", FVector2D(3,21), FLinearColor::White, ESlateBrushTileType::NoTile ) ); - Set( "Sequencer.SetAutoKey", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon48x48 ) ); - Set( "Sequencer.SetAutoKey.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon24x24 ) ); - Set( "Sequencer.SetAutoTrack", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon48x48 ) ); - Set( "Sequencer.SetAutoTrack.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon24x24 ) ); - Set( "Sequencer.SetAutoChangeAll", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon48x48 ) ); - Set( "Sequencer.SetAutoChangeAll.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon24x24 ) ); - Set( "Sequencer.SetAutoChangeNone", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon48x48)); - Set( "Sequencer.SetAutoChangeNone.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon24x24 ) ); - Set( "Sequencer.AllowAllEdits", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon48x48 ) ); - Set( "Sequencer.AllowAllEdits.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon24x24 ) ); - Set( "Sequencer.AllowSequencerEditsOnly", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon48x48 ) ); - Set( "Sequencer.AllowSequencerEditsOnly.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon24x24 ) ); - Set( "Sequencer.AllowLevelEditsOnly", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon48x48 ) ); - Set( "Sequencer.AllowLevelEditsOnly.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon24x24 ) ); - Set( "Sequencer.SetKeyAll", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon48x48 ) ); - Set( "Sequencer.SetKeyAll.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon24x24 ) ); - Set( "Sequencer.SetKeyGroup", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon48x48)); - Set( "Sequencer.SetKeyGroup.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon24x24)); - Set( "Sequencer.SetKeyChanged", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon48x48 ) ); - Set( "Sequencer.SetKeyChanged.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon24x24 ) ); - Set( "Sequencer.ToggleIsSnapEnabled", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon48x48 ) ); - Set( "Sequencer.ToggleIsSnapEnabled.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon24x24 ) ); - Set( "Sequencer.ToggleShowCurveEditor", new IMAGE_BRUSH("GenericCurveEditor/Icons/GenericCurveEditor_48x", Icon48x48) ); - Set( "Sequencer.ToggleShowCurveEditor.Small", new IMAGE_BRUSH("GenericCurveEditor/Icons/GenericCurveEditor_48x", Icon24x24) ); - Set( "Sequencer.ToggleAutoScroll", new IMAGE_BRUSH( "Icons/icon_Sequencer_ToggleAutoScroll_40x", Icon48x48 ) ); - Set( "Sequencer.ToggleAutoScroll.Small", new IMAGE_BRUSH( "Icons/icon_Sequencer_ToggleAutoScroll_16x", Icon16x16 ) ); - Set( "Sequencer.MoveTool.Small", new IMAGE_BRUSH( "Icons/SequencerIcons/icon_Sequencer_Move_24x", Icon16x16 ) ); - Set( "Sequencer.MarqueeTool.Small", new IMAGE_BRUSH( "Icons/SequencerIcons/icon_Sequencer_Marquee_24x", Icon16x16 ) ); - Set( "Sequencer.RenderMovie.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Create_Movie_24x", Icon24x24 ) ); - Set( "Sequencer.CreateCamera.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Create_Camera_24x", Icon24x24 ) ); - Set( "Sequencer.FindInContentBrowser.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Find_In_Content_Browser_24x", Icon24x24 ) ); - Set( "Sequencer.LockCamera", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16 ) ); - Set( "Sequencer.UnlockCamera", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16, FLinearColor(1.f, 1.f, 1.f, 0.5f) ) ); - Set( "Sequencer.Thumbnail.SectionHandle", new IMAGE_BRUSH( "Old/White", Icon16x16, FLinearColor::Black ) ); - Set( "Sequencer.TrackHoverHighlight_Top", new IMAGE_BRUSH( TEXT("Sequencer/TrackHoverHighlight_Top"), FVector2D(4, 4) ) ); - Set( "Sequencer.TrackHoverHighlight_Bottom", new IMAGE_BRUSH( TEXT("Sequencer/TrackHoverHighlight_Bottom"), FVector2D(4, 4) ) ); - Set( "Sequencer.SpawnableIconOverlay", new IMAGE_BRUSH( TEXT("Sequencer/SpawnableIconOverlay"), FVector2D(13, 13) ) ); - Set( "Sequencer.MultipleIconOverlay", new IMAGE_BRUSH(TEXT("Sequencer/MultipleIconOverlay"), FVector2D(13, 13) ) ); - Set( "Sequencer.LockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Locked_16x", Icon16x16) ); - Set( "Sequencer.UnlockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Unlocked_16x", Icon16x16) ); - - Set( "Sequencer.GeneralOptions", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon48x48 ) ); - Set( "Sequencer.GeneralOptions.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon24x24 ) ); - Set( "Sequencer.PlaybackOptions", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon48x48 ) ); - Set( "Sequencer.PlaybackOptions.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon24x24 ) ); - Set( "Sequencer.SelectEditOptions", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon48x48 ) ); - Set( "Sequencer.SelectEditOptions.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon24x24 ) ); - Set( "Sequencer.Time", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon48x48 ) ); - Set( "Sequencer.Time.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon24x24 ) ); - Set( "Sequencer.Value", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon48x48 ) ); - Set( "Sequencer.Value.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon24x24 ) ); - - Set( "Sequencer.OverlayPanel.Background", new BOX_BRUSH( "Sequencer/OverlayPanelBackground", FMargin(26.f/54.f) ) ); - - Set( "Sequencer.TrackArea.LaneColor", FLinearColor(0.3f, 0.3f, 0.3f, 0.3f) ); - - Set( "Sequencer.Tracks.Media", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Media_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Audio", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Audio_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Event", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Event_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Fade", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Fade_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.CameraCut", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Camera_Cut_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.CinematicShot", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Shot_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Slomo", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Play_Rate_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.Sub", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Sub_Track_16x", Icon16x16)); - Set( "Sequencer.Tracks.LevelVisibility", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Level_Visibility_Track_16x", Icon16x16)); - - Set( "Sequencer.CursorDecorator_MarqueeAdd", new IMAGE_BRUSH( "Sequencer/CursorDecorator_MarqueeAdd", Icon16x16)); - Set( "Sequencer.CursorDecorator_MarqueeSubtract", new IMAGE_BRUSH( "Sequencer/CursorDecorator_MarqueeSubtract", Icon16x16)); - - Set( "Sequencer.BreadcrumbText", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "Bold", 11 ) ) - .SetColorAndOpacity( FLinearColor( 1.0f, 1.0f, 1.0f ) ) - .SetHighlightColor( FLinearColor( 1.0f, 1.0f, 1.0f ) ) - .SetShadowOffset( FVector2D( 1,1 ) ) - .SetShadowColorAndOpacity( FLinearColor(0,0,0,0.9f) ) ); - Set( "Sequencer.BreadcrumbIcon", new IMAGE_BRUSH( "Common/SmallArrowRight", Icon10x10 ) ); - - const FButtonStyle DetailsKeyButton = FButtonStyle(NoBorder) - .SetNormal( IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11,11) ) ) - .SetHovered( IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11,11), SelectionColor ) ) - .SetPressed( IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11,11), SelectionColor_Pressed ) ) - .SetNormalPadding(FMargin(0, 1)) - .SetPressedPadding(FMargin(0, 2, 0, 0)); - Set( "Sequencer.AddKey.Details", DetailsKeyButton ); - - const FSplitterStyle OutlinerSplitterStyle = FSplitterStyle() - .SetHandleNormalBrush( FSlateNoResource() ) - .SetHandleHighlightBrush( FSlateNoResource() ); - Set( "Sequencer.AnimationOutliner.Splitter", OutlinerSplitterStyle ); - - Set( "Sequencer.HyperlinkSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) - .SetTextPadding(FMargin(0)) - .SetBackgroundBrush(BORDER_BRUSH("Old/HyperlinkDotted", FMargin(0,0,0,3/16.0f), FSlateColor::UseSubduedForeground())) - .SetHoveredBackgroundBrush(FSlateNoResource()) - .SetInactiveFillBrush(FSlateNoResource()) - .SetActiveFillBrush(FSlateNoResource()) - .SetForegroundColor(FSlateColor::UseSubduedForeground()) - .SetArrowsImage(FSlateNoResource()) - ); - - Set("Sequencer.PlayTimeSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) - .SetTextPadding(FMargin(0)) - .SetBackgroundBrush(FSlateNoResource()) - .SetHoveredBackgroundBrush(FSlateNoResource()) - .SetInactiveFillBrush(FSlateNoResource()) - .SetActiveFillBrush(FSlateNoResource()) - .SetForegroundColor(SelectionColor_Pressed) - .SetArrowsImage(FSlateNoResource()) - ); - - Set( "Sequencer.HyperlinkTextBox", FEditableTextBoxStyle() - .SetFont( DEFAULT_FONT( "Regular", 9 ) ) - .SetBackgroundImageNormal( FSlateNoResource() ) - .SetBackgroundImageHovered( FSlateNoResource() ) - .SetBackgroundImageFocused( FSlateNoResource() ) - .SetBackgroundImageReadOnly( FSlateNoResource() ) - .SetBackgroundColor( FLinearColor::Transparent ) - .SetForegroundColor( FSlateColor::UseSubduedForeground() ) - ); - Set( "Sequencer.FixedFont", DEFAULT_FONT( "Mono", 9 ) ); - - Set( "Sequencer.RecordSelectedActors", new IMAGE_BRUSH( "SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16 ) ); - - FComboButtonStyle SequencerSectionComboButton = FComboButtonStyle() - .SetButtonStyle( - FButtonStyle() - .SetNormal(FSlateNoResource()) - .SetHovered(FSlateNoResource()) - .SetPressed(FSlateNoResource()) - .SetNormalPadding(FMargin(0,0,0,0)) - .SetPressedPadding(FMargin(0,1,0,0)) - ) - .SetDownArrowImage(IMAGE_BRUSH("Common/ComboArrow", Icon8x8)); - Set( "Sequencer.SectionComboButton", SequencerSectionComboButton ); - - Set("Sequencer.CreateEventBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_AddFunction_16px", Icon16x16)); - Set("Sequencer.CreateQuickBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_Node_16x", Icon16x16)); - Set("Sequencer.ClearEventBinding", new IMAGE_BRUSH("Icons/Edit/icon_Edit_Delete_40x", Icon16x16)); - Set("Sequencer.MultipleEvents", new IMAGE_BRUSH("Sequencer/MultipleEvents", Icon16x16)); - Set("Sequencer.UnboundEvent", new IMAGE_BRUSH("Sequencer/UnboundEvent", Icon16x16)); - - // Sequencer Blending Iconography - Set( "EMovieSceneBlendType::Absolute", new IMAGE_BRUSH( "Sequencer/EMovieSceneBlendType_Absolute", FVector2D(32, 16) ) ); - Set( "EMovieSceneBlendType::Relative", new IMAGE_BRUSH( "Sequencer/EMovieSceneBlendType_Relative", FVector2D(32, 16) ) ); - Set( "EMovieSceneBlendType::Additive", new IMAGE_BRUSH( "Sequencer/EMovieSceneBlendType_Additive", FVector2D(32, 16) ) ); - } - - - // Sequence recorder standalone UI - if (IncludeEditorSpecificStyles()) - { - Set( "SequenceRecorder.TabIcon", new IMAGE_BRUSH( "SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16 ) ); - Set( "SequenceRecorder.Common.RecordAll.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_RecordAll_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.RecordAll", new IMAGE_BRUSH( "SequenceRecorder/icon_RecordAll_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.StopAll.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_StopAll_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.StopAll", new IMAGE_BRUSH( "SequenceRecorder/icon_StopAll_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.AddRecording.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_AddRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.AddRecording", new IMAGE_BRUSH( "SequenceRecorder/icon_AddRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.AddCurrentPlayerRecording.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.AddCurrentPlayerRecording", new IMAGE_BRUSH( "SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.RemoveRecording.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.RemoveRecording", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.RemoveAllRecordings.Small", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon20x20 ) ); - Set( "SequenceRecorder.Common.RemoveAllRecordings", new IMAGE_BRUSH( "SequenceRecorder/icon_RemoveRecording_40x", Icon40x40 ) ); - Set( "SequenceRecorder.Common.RecordingActive", new IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ); - Set( "SequenceRecorder.Common.RecordingInactive", new IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ); - } - // Foliage Edit Mode if (IncludeEditorSpecificStyles()) { @@ -2372,6 +2033,22 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() /* ... and add new style */ Set( "ToolBar.CheckBox", ToolBarCheckBoxStyle ); + /* Read-only checkbox that appears next to a toolbar item */ + /* Set images for various SCheckBox states associated with read-only toolbar check box items... */ + const FCheckBoxStyle ToolBarCheckStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUndeterminedHoveredImage(FSlateNoResource()) + .SetUndeterminedPressedImage(FSlateNoResource()); + + /* ...and add the new style */ + Set("Toolbar.Check", ToolBarCheckStyle); + // This radio button is actually just a check box with different images /* Create style for "ToolBar.RadioButton" widget ... */ const FCheckBoxStyle ToolbarRadioButtonCheckBoxStyle = FCheckBoxStyle() @@ -2439,108 +2116,7 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() ); } - // MenuBar - { - Set( "Menu.Background", new BOX_BRUSH( "Old/Menu_Background", FMargin(8.0f/64.0f) ) ); - Set( "Menu.Icon", new IMAGE_BRUSH( "Icons/icon_tab_toolbar_16px", Icon16x16 ) ); - Set( "Menu.Expand", new IMAGE_BRUSH( "Icons/toolbar_expand_16x", Icon16x16) ); - Set( "Menu.SubMenuIndicator", new IMAGE_BRUSH( "Common/SubmenuArrow", Icon8x8 ) ); - Set( "Menu.SToolBarComboButtonBlock.Padding", FMargin(4.0f)); - Set( "Menu.SToolBarButtonBlock.Padding", FMargin(4.0f)); - Set( "Menu.SToolBarCheckComboButtonBlock.Padding", FMargin(4.0f)); - Set( "Menu.SToolBarButtonBlock.CheckBox.Padding", FMargin(0.0f) ); - Set( "Menu.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground ); - - Set( "Menu.Block.IndentedPadding", FMargin( 18.0f, 2.0f, 4.0f, 4.0f ) ); - Set( "Menu.Block.Padding", FMargin( 2.0f, 2.0f, 4.0f, 4.0f ) ); - - Set( "Menu.Separator", new BOX_BRUSH( "Old/Button", 4.0f/32.0f ) ); - Set( "Menu.Separator.Padding", FMargin( 0.5f ) ); - - Set( "Menu.Label", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "Menu.Label.Padding", FMargin(0.0f, 0.0f, 0.0f, 0.0f) ); - Set( "Menu.Label.ContentPadding", FMargin(10.0f, 2.0f) ); - Set( "Menu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "Menu.Keybinding", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 8 ) ) ); - - Set( "Menu.Heading", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "Regular", 8 ) ) - .SetColorAndOpacity( FLinearColor( 0.4f, 0.4, 0.4f, 1.0f ) ) ); - - /* Set images for various SCheckBox states associated with menu check box items... */ - const FCheckBoxStyle BasicMenuCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined", Icon14x14 ) ) - .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14 ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ); - - /* ...and add the new style */ - Set( "Menu.CheckBox", BasicMenuCheckBoxStyle ); - - /* Read-only checkbox that appears next to a menu item */ - /* Set images for various SCheckBox states associated with read-only menu check box items... */ - const FCheckBoxStyle BasicMenuCheckStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUndeterminedHoveredImage( FSlateNoResource() ) - .SetUndeterminedPressedImage( FSlateNoResource() ); - - /* ...and add the new style */ - Set( "Menu.Check", BasicMenuCheckStyle ); - - /* This radio button is actually just a check box with different images */ - /* Set images for various Menu radio button (SCheckBox) states... */ - const FCheckBoxStyle BasicMenuRadioButtonStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16, SelectionColor ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed ) ); - - /* ...and set new style */ - Set( "Menu.RadioButton", BasicMenuRadioButtonStyle ); - - /* Create style for "Menu.ToggleButton" widget ... */ - const FCheckBoxStyle MenuToggleButtonCheckBoxStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( FSlateNoResource() ) - .SetUncheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetCheckedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetCheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ); - /* ... and add new style */ - Set( "Menu.ToggleButton", MenuToggleButtonCheckBoxStyle ); - - Set( "Menu.Button", FButtonStyle( NoBorder ) - .SetNormal ( FSlateNoResource() ) - .SetPressed( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetHovered( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetNormalPadding( FMargin(0,1) ) - .SetPressedPadding( FMargin(0,2,0,0) ) - ); - - Set( "Menu.Button.Checked", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "Menu.Button.Checked_Hovered", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "Menu.Button.Checked_Pressed", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ); - - /* The style of a menu bar button when it has a sub menu open */ - Set( "Menu.Button.SubMenuOpen", new BORDER_BRUSH( "Common/Selection", FMargin(4.f/16.f), FLinearColor(0.10f, 0.10f, 0.10f) ) ); - } + // ViewportLayoutToolbar { @@ -2568,239 +2144,7 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() Set( "ViewportLayoutToolbar.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground ); } - // NotificationBar - { - Set( "NotificationBar.Background", new FSlateNoResource() ); - Set( "NotificationBar.Icon", new FSlateNoResource() ); - Set( "NotificationBar.Expand", new IMAGE_BRUSH( "Icons/toolbar_expand_16x", Icon16x16) ); - Set( "NotificationBar.SubMenuIndicator", new IMAGE_BRUSH( "Common/SubmenuArrow", Icon8x8 ) ); - - Set( "NotificationBar.Block.IndentedPadding", FMargin( 0 ) ); - Set( "NotificationBar.Block.Padding", FMargin( 0 ) ); - - Set( "NotificationBar.Separator", new BOX_BRUSH( "Old/Button", 4.0f/32.0f ) ); - Set( "NotificationBar.Separator.Padding", FMargin( 0.5f ) ); - - Set( "NotificationBar.Label", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "NotificationBar.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle) .SetFont( DEFAULT_FONT( "Regular", 9 ) ) ); - Set( "NotificationBar.Keybinding", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 8 ) ) ); - - Set( "NotificationBar.Heading", FTextBlockStyle(NormalText) - .SetFont( DEFAULT_FONT( "Regular", 8 ) ) - .SetColorAndOpacity( FLinearColor( 0.4f, 0.4, 0.4f, 1.0f ) ) ); - - const FCheckBoxStyle NotificationBarCheckBoxCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined", Icon14x14 ) ) - .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14 ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ); - Set( "NotificationBar.CheckBox", NotificationBarCheckBoxCheckBoxStyle ); - - // Read-only checkbox that appears next to a menu item - const FCheckBoxStyle NotificationBarCheckCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedPressedImage( FSlateNoResource() ) - .SetUncheckedHoveredImage( FSlateNoResource() ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetUndeterminedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUndeterminedPressedImage( FSlateNoResource() ) - .SetUndeterminedHoveredImage( FSlateNoResource() ); - Set( "NotificationBar.Check", NotificationBarCheckCheckBoxStyle ); - - // This radio button is actually just a check box with different images - const FCheckBoxStyle NotificationBarRadioButtonCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16, SelectionColor ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16, SelectionColor_Pressed ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/RadioButton_Selected_16x", Icon16x16 ) ); - Set( "NotificationBar.RadioButton", NotificationBarRadioButtonCheckBoxStyle ); - - const FCheckBoxStyle NotificationBarToggleButtonCheckBoxStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( FSlateNoResource() ) - .SetUncheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetCheckedImage( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "NotificationBar.ToggleButton", NotificationBarToggleButtonCheckBoxStyle ); - - Set( "NotificationBar.Button", FButtonStyle( NoBorder ) - .SetNormal ( FSlateNoResource() ) - .SetPressed( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ) - .SetHovered( BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ) - .SetNormalPadding( FMargin(0,1) ) - .SetPressedPadding( FMargin(0,2,0,0) ) - ); - - Set( "NotificationBar.Button.Checked", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "NotificationBar.Button.Checked_Hovered", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor_Pressed ) ); - Set( "NotificationBar.Button.Checked_Pressed", new BOX_BRUSH( "Common/RoundedSelection_16x", 4.0f/16.0f, SelectionColor ) ); - - Set( "NotificationBar.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f) ); - Set( "NotificationBar.SToolBarButtonBlock.Button.Padding", FMargin(0.0f) ); - Set( "NotificationBar.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground ); - } - - // Viewport ToolbarBar - { - Set( "ViewportMenu.Background", new BOX_BRUSH( "Old/Menu_Background", FMargin(8.0f/64.0f), FLinearColor::Transparent ) ); - Set( "ViewportMenu.Icon", new IMAGE_BRUSH( "Icons/icon_tab_toolbar_16px", Icon16x16 ) ); - Set( "ViewportMenu.Expand", new IMAGE_BRUSH( "Icons/toolbar_expand_16x", Icon8x8) ); - Set( "ViewportMenu.SubMenuIndicator", new IMAGE_BRUSH( "Common/SubmenuArrow", Icon8x8) ); - Set( "ViewportMenu.SToolBarComboButtonBlock.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarButtonBlock.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarButtonBlock.Button.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarCheckComboButtonBlock.Padding", FMargin(0)); - Set( "ViewportMenu.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f) ); - Set( "ViewportMenu.SToolBarComboButtonBlock.ComboButton.Color", FLinearColor(0.f,0.f,0.f,0.75f) ); - - Set( "ViewportMenu.Separator", new BOX_BRUSH( "Old/Button", 8.0f/32.0f, FLinearColor::Transparent ) ); - Set( "ViewportMenu.Separator.Padding", FMargin( 100.0f ) ); - - Set( "ViewportMenu.Label", FTextBlockStyle(NormalText) - .SetFont(DEFAULT_FONT("Bold", 9)) - .SetColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f))); - Set( "ViewportMenu.Label.Padding", FMargin(0.0f, 0.0f, 3.0f, 0.0f) ); - Set( "ViewportMenu.Label.ContentPadding", FMargin(5.0f, 2.0f) ); - Set( "ViewportMenu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle) .SetFont( DEFAULT_FONT( "Regular", 9) ) ); - Set( "ViewportMenu.Keybinding", FTextBlockStyle(NormalText) .SetFont( DEFAULT_FONT( "Regular", 8) ) ); - - Set( "ViewportMenu.Block.IndentedPadding", FMargin( 0 ) ); - Set( "ViewportMenu.Block.Padding", FMargin( 0 ) ); - - Set( "ViewportMenu.Heading.Font", DEFAULT_FONT( "Regular", 8 ) ); - Set( "ViewportMenu.Heading.ColorAndOpacity", FLinearColor( 0.4f, 0.4, 0.4f, 1.0f ) ); - - const FCheckBoxStyle ViewportMenuCheckBoxCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Hovered", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheckBox_Checked", Icon14x14 ) ); - Set( "ViewportMenu.CheckBox", ViewportMenuCheckBoxCheckBoxStyle ); - - // Read-only checkbox that appears next to a menu item - const FCheckBoxStyle ViewportMenuCheckCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Icons/Empty_14x", Icon14x14 ) ) - .SetUncheckedPressedImage( FSlateNoResource() ) - .SetUncheckedHoveredImage( FSlateNoResource() ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/SmallCheck", Icon14x14 ) ); - Set( "ViewportMenu.Check", ViewportMenuCheckCheckBoxStyle ); - - const FString SmallRoundedButton (TEXT("Common/SmallRoundedButton")); - const FString SmallRoundedButtonStart (TEXT("Common/SmallRoundedButtonLeft")); - const FString SmallRoundedButtonMiddle (TEXT("Common/SmallRoundedButtonCentre")); - const FString SmallRoundedButtonEnd (TEXT("Common/SmallRoundedButtonRight")); - - const FLinearColor NormalColor(1,1,1,0.75f); - const FLinearColor PressedColor(1,1,1,1.f); - - const FCheckBoxStyle ViewportMenuRadioButtonCheckBoxStyle = FCheckBoxStyle() - .SetUncheckedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_Off", Icon14x14 ) ) - .SetUncheckedPressedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_Off", Icon14x14, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) - .SetUncheckedHoveredImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_Off", Icon14x14 ) ) - .SetCheckedHoveredImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_On", Icon14x14 ) ) - .SetCheckedPressedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_On_Pressed", Icon14x14 ) ) - .SetCheckedImage( IMAGE_BRUSH( "Common/MenuItemRadioButton_On", Icon14x14 ) ); - Set( "ViewportMenu.RadioButton", ViewportMenuRadioButtonCheckBoxStyle ); - - /* Create style for "ViewportMenu.ToggleButton" ... */ - const FCheckBoxStyle ViewportMenuToggleButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButton, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton", ViewportMenuToggleButtonStyle ); - - /* Create style for "ViewportMenu.ToggleButton.Start" ... */ - const FCheckBoxStyle ViewportMenuToggleStartButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButtonStart, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton.Start", ViewportMenuToggleStartButtonStyle ); - - /* Create style for "ViewportMenu.ToggleButton.Middle" ... */ - const FCheckBoxStyle ViewportMenuToggleMiddleButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButtonMiddle, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton.Middle", ViewportMenuToggleMiddleButtonStyle ); - - /* Create style for "ViewportMenu.ToggleButton.End" ... */ - const FCheckBoxStyle ViewportMenuToggleEndButtonStyle = FCheckBoxStyle() - .SetCheckBoxType( ESlateCheckBoxType::ToggleButton ) - .SetUncheckedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), NormalColor ) ) - .SetUncheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), PressedColor ) ) - .SetUncheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), PressedColor ) ) - .SetCheckedHoveredImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedPressedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), SelectionColor_Pressed ) ) - .SetCheckedImage( BOX_BRUSH( *SmallRoundedButtonEnd, FMargin(7.f/16.f), SelectionColor_Pressed ) ); - /* ... and add new style */ - Set( "ViewportMenu.ToggleButton.End", ViewportMenuToggleEndButtonStyle ); - - const FMargin NormalPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); - const FMargin PressedPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); - - const FButtonStyle ViewportMenuButton = FButtonStyle(Button) - .SetNormal ( BOX_BRUSH( *SmallRoundedButton, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButton, 7.0f/16.0f, PressedColor ) ) - .SetHovered(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, PressedColor)) - .SetPressedPadding(PressedPadding) - .SetNormalPadding(NormalPadding); - - Set( "ViewportMenu.Button", ViewportMenuButton ); - - Set( "ViewportMenu.Button.Start", FButtonStyle(ViewportMenuButton) - .SetNormal ( BOX_BRUSH( *SmallRoundedButtonStart, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButtonStart, 7.0f/16.0f, PressedColor ) ) - .SetHovered( BOX_BRUSH( *SmallRoundedButtonStart, 7.0f/16.0f, PressedColor ) ) - ); - - Set( "ViewportMenu.Button.Middle", FButtonStyle(ViewportMenuButton) - .SetNormal ( BOX_BRUSH( *SmallRoundedButtonMiddle, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButtonMiddle, 7.0f/16.0f, PressedColor ) ) - .SetHovered( BOX_BRUSH( *SmallRoundedButtonMiddle, 7.0f/16.0f, PressedColor ) ) - ); - - Set( "ViewportMenu.Button.End", FButtonStyle(ViewportMenuButton) - .SetNormal ( BOX_BRUSH( *SmallRoundedButtonEnd, 7.0f/16.0f, NormalColor)) - .SetPressed( BOX_BRUSH( *SmallRoundedButtonEnd, 7.0f/16.0f, PressedColor ) ) - .SetHovered( BOX_BRUSH( *SmallRoundedButtonEnd, 7.0f/16.0f, PressedColor ) ) - ); - } - - // Viewport actor preview's pin/unpin buttons - { - Set( "ViewportActorPreview.Pinned", new IMAGE_BRUSH( "Common/PushPin_Down", Icon16x16 ) ); - Set( "ViewportActorPreview.Unpinned", new IMAGE_BRUSH( "Common/PushPin_Up", Icon16x16 ) ); - } + // Standard Dialog Settings { @@ -2901,6 +2245,731 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() #endif // WITH_EDITOR || (IS_PROGRAM && WITH_UNREAL_DEVELOPER_TOOLS) } +void FSlateEditorStyle::FStyle::SetupLevelGeneralStyles() +{ +// Levels General + { + Set("Level.VisibleIcon16x", new IMAGE_BRUSH("Icons/icon_levels_visible_16px", Icon16x16)); + Set("Level.VisibleHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_visible_hi_16px", Icon16x16)); + Set("Level.NotVisibleIcon16x", new IMAGE_BRUSH("Icons/icon_levels_invisible_16px", Icon16x16)); + Set("Level.NotVisibleHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_invisible_hi_16px", Icon16x16)); + Set("Level.LightingScenarioIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LightingScenario_16px", Icon16x16)); + Set("Level.LightingScenarioNotIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LightingScenarioNot_16px", Icon16x16)); + Set("Level.LockedIcon16x", new IMAGE_BRUSH("Icons/icon_locked_16px", Icon16x16)); + Set("Level.LockedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_locked_highlight_16px", Icon16x16)); + Set("Level.UnlockedIcon16x", new IMAGE_BRUSH("Icons/icon_levels_unlocked_16px", Icon16x16)); + Set("Level.UnlockedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_unlocked_hi_16px", Icon16x16)); + Set("Level.ReadOnlyLockedIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LockedReadOnly_16px", Icon16x16)); + Set("Level.ReadOnlyLockedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_LockedReadOnly_hi_16px", Icon16x16)); + Set("Level.SaveIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Save_16px", Icon16x16)); + Set("Level.SaveHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Save_hi_16px", Icon16x16)); + Set("Level.SaveModifiedIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveModified_16px", Icon16x16)); + Set("Level.SaveModifiedHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveModified_hi_16px", Icon16x16)); + Set("Level.SaveDisabledIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveDisabled_16px", Icon16x16)); + Set("Level.SaveDisabledHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_SaveDisabled_hi_16px", Icon16x16)); + Set("Level.ScriptIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Blueprint_16px", Icon16x16)); + Set("Level.ScriptHighlightIcon16x", new IMAGE_BRUSH("Icons/icon_levels_Blueprint_hi_16px", Icon16x16)); + Set("Level.EmptyIcon16x", new IMAGE_BRUSH("Icons/Empty_16x", Icon16x16)); + Set("Level.ColorIcon40x", new IMAGE_BRUSH("Icons/icon_levels_back_16px", Icon16x16)); + } +} + +void FSlateEditorStyle::FStyle::SetupWorldBrowserStyles() +{ + + // World Browser + { + Set("WorldBrowser.AddLayer", new IMAGE_BRUSH("Icons/icon_levels_addlayer_16x", Icon16x16)); + Set("WorldBrowser.SimulationViewPositon", new IMAGE_BRUSH("Icons/icon_levels_simulationviewpos_16x", Icon16x16)); + Set("WorldBrowser.MouseLocation", new IMAGE_BRUSH("Icons/icon_levels_mouselocation_16x", Icon16x16)); + Set("WorldBrowser.MarqueeRectSize", new IMAGE_BRUSH("Icons/icon_levels_marqueerectsize_16x", Icon16x16)); + Set("WorldBrowser.WorldSize", new IMAGE_BRUSH("Icons/icon_levels_worldsize_16x", Icon16x16)); + Set("WorldBrowser.WorldOrigin", new IMAGE_BRUSH("Icons/icon_levels_worldorigin_16x", Icon16x16)); + Set("WorldBrowser.DirectionXPositive", new IMAGE_BRUSH("Icons/icon_PanRight", Icon16x16)); + Set("WorldBrowser.DirectionXNegative", new IMAGE_BRUSH("Icons/icon_PanLeft", Icon16x16)); + Set("WorldBrowser.DirectionYPositive", new IMAGE_BRUSH("Icons/icon_PanUp", Icon16x16)); + Set("WorldBrowser.DirectionYNegative", new IMAGE_BRUSH("Icons/icon_PanDown", Icon16x16)); + Set("WorldBrowser.LevelStreamingAlwaysLoaded", new FSlateNoResource()); + Set("WorldBrowser.LevelStreamingBlueprint", new IMAGE_BRUSH("Icons/icon_levels_blueprinttype_7x16", Icon7x16)); + Set("WorldBrowser.LevelsMenuBrush", new IMAGE_BRUSH("Icons/icon_levels_levelsmenu_40x", Icon25x25)); + Set("WorldBrowser.HierarchyButtonBrush", new IMAGE_BRUSH("Icons/icon_levels_hierarchybutton_16x", Icon16x16)); + Set("WorldBrowser.DetailsButtonBrush", new IMAGE_BRUSH("Icons/icon_levels_detailsbutton_40x", Icon16x16)); + Set("WorldBrowser.CompositionButtonBrush", new IMAGE_BRUSH("Icons/icon_levels_compositionbutton_16x", Icon16x16)); + + Set("WorldBrowser.FolderClosed", new IMAGE_BRUSH("Icons/FolderClosed", Icon16x16)); + Set("WorldBrowser.FolderOpen", new IMAGE_BRUSH("Icons/FolderOpen", Icon16x16)); + Set("WorldBrowser.NewFolderIcon", new IMAGE_BRUSH("Icons/icon_AddFolder_16x", Icon16x16)); + + Set("WorldBrowser.StatusBarText", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("BoldCondensed", 12)) + .SetColorAndOpacity(FLinearColor(0.9, 0.9f, 0.9f, 0.5f)) + .SetShadowOffset(FVector2D::ZeroVector) + ); + + Set("WorldBrowser.LabelFont", DEFAULT_FONT("Regular", 9)); + Set("WorldBrowser.LabelFontBold", DEFAULT_FONT("Bold", 10)); + } +} + +void FSlateEditorStyle::FStyle::SetupSequencerStyles() +{ + // Sequencer + if (IncludeEditorSpecificStyles()) + { + Set("Sequencer.IconKeyAuto", new IMAGE_BRUSH("Sequencer/IconKeyAuto", Icon12x12)); + Set("Sequencer.IconKeyBreak", new IMAGE_BRUSH("Sequencer/IconKeyBreak", Icon12x12)); + Set("Sequencer.IconKeyConstant", new IMAGE_BRUSH("Sequencer/IconKeyConstant", Icon12x12)); + Set("Sequencer.IconKeyLinear", new IMAGE_BRUSH("Sequencer/IconKeyLinear", Icon12x12)); + Set("Sequencer.IconKeyUser", new IMAGE_BRUSH("Sequencer/IconKeyUser", Icon12x12)); + + Set("Sequencer.KeyCircle", new IMAGE_BRUSH("Sequencer/KeyCircle", Icon12x12)); + Set("Sequencer.KeyDiamond", new IMAGE_BRUSH("Sequencer/KeyDiamond", Icon12x12)); + Set("Sequencer.KeyDiamondBorder", new IMAGE_BRUSH("Sequencer/KeyDiamondBorder", Icon12x12)); + Set("Sequencer.KeySquare", new IMAGE_BRUSH("Sequencer/KeySquare", Icon12x12)); + Set("Sequencer.KeyTriangle", new IMAGE_BRUSH("Sequencer/KeyTriangle", Icon12x12)); + Set("Sequencer.KeyLeft", new IMAGE_BRUSH("Sequencer/KeyLeft", Icon12x12)); + Set("Sequencer.KeyRight", new IMAGE_BRUSH("Sequencer/KeyRight", Icon12x12)); + Set("Sequencer.PartialKey", new IMAGE_BRUSH("Sequencer/PartialKey", FVector2D(11.f, 11.f))); + Set("Sequencer.Star", new IMAGE_BRUSH("Sequencer/Star", Icon12x12)); + Set("Sequencer.Empty", new IMAGE_BRUSH("Sequencer/Empty", Icon12x12)); + Set("Sequencer.TangentHandle", new IMAGE_BRUSH("Sequencer/TangentHandle", FVector2D(7, 7))); + Set("Sequencer.GenericDivider", new IMAGE_BRUSH("Sequencer/GenericDivider", FVector2D(2.f, 2.f), FLinearColor::White, ESlateBrushTileType::Vertical)); + + Set("Sequencer.Timeline.ScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown", FMargin(6.f / 13.f, 5 / 12.f, 6 / 13.f, 8 / 12.f))); + Set("Sequencer.Timeline.ScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); + Set("Sequencer.Timeline.ScrubFill", new BOX_BRUSH("Sequencer/ScrubFill", FMargin(2.f / 4.f, 0.f))); + Set("Sequencer.Timeline.FrameBlockScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown", FMargin(6.f / 13.f, 5 / 12.f, 6 / 13.f, 8 / 12.f))); + Set("Sequencer.Timeline.FrameBlockScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); + Set("Sequencer.Timeline.VanillaScrubHandleDown", new BOX_BRUSH("Sequencer/ScrubHandleDown_Clamped", FMargin(6.f / 13.f, 3.f / 12.f, 6.f / 13.f, 7.f / 12.f))); + Set("Sequencer.Timeline.VanillaScrubHandleUp", new BOX_BRUSH("Sequencer/ScrubHandleUp_Clamped", FMargin(6.f / 13.f, 8 / 12.f, 6 / 13.f, 5 / 12.f))); + Set("Sequencer.Timeline.ScrubHandleWhole", new BOX_BRUSH("Sequencer/ScrubHandleWhole", FMargin(6.f / 13.f, 10 / 24.f, 6 / 13.f, 10 / 24.f))); + Set("Sequencer.Timeline.RangeHandleLeft", new BOX_BRUSH("Sequencer/GenericGripLeft", FMargin(5.f / 16.f))); + Set("Sequencer.Timeline.RangeHandleRight", new BOX_BRUSH("Sequencer/GenericGripRight", FMargin(5.f / 16.f))); + Set("Sequencer.Timeline.RangeHandle", new BOX_BRUSH("Sequencer/GenericSectionBackground", FMargin(5.f / 16.f))); + Set("Sequencer.Timeline.NotifyAlignmentMarker", new IMAGE_BRUSH("Sequencer/NotifyAlignmentMarker", FVector2D(10, 19))); + Set("Sequencer.Timeline.PlayRange_Top_L", new BOX_BRUSH("Sequencer/PlayRange_Top_L", FMargin(1.f, 0.5f, 0.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_Top_R", new BOX_BRUSH("Sequencer/PlayRange_Top_R", FMargin(0.f, 0.5f, 1.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_L", new BOX_BRUSH("Sequencer/PlayRange_L", FMargin(1.f, 0.5f, 0.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_R", new BOX_BRUSH("Sequencer/PlayRange_R", FMargin(0.f, 0.5f, 1.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_Bottom_L", new BOX_BRUSH("Sequencer/PlayRange_Bottom_L", FMargin(1.f, 0.5f, 0.f, 0.5f))); + Set("Sequencer.Timeline.PlayRange_Bottom_R", new BOX_BRUSH("Sequencer/PlayRange_Bottom_R", FMargin(0.f, 0.5f, 1.f, 0.5f))); + + Set("Sequencer.Timeline.SubSequenceRangeHashL", new BORDER_BRUSH("Sequencer/SubSequenceRangeHashL", FMargin(1.f, 0.f, 0.f, 0.f))); + Set("Sequencer.Timeline.SubSequenceRangeHashR", new BORDER_BRUSH("Sequencer/SubSequenceRangeHashR", FMargin(1.f, 0.f, 0.f, 0.f))); + Set("Sequencer.Timeline.EaseInOut", new IMAGE_BRUSH("Sequencer/EaseInOut", FVector2D(128, 128))); + Set("Sequencer.InterpLine", new BOX_BRUSH("Sequencer/InterpLine", FMargin(5.f / 7.f, 0.f, 0.f, 0.f))); + + Set("Sequencer.Transport.JumpToPreviousKey", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24)) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Previous_Frame", Icon24x24)) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Previous_Frame_OFF", Icon24x24))); + Set("Sequencer.Transport.JumpToNextKey", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24)) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Next_Frame_24x", Icon24x24)) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Next_Frame_24x_OFF", Icon24x24))); + Set("Sequencer.Transport.SetPlayStart", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16, 24))) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_In_16x24", FVector2D(16, 24))) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_In_16x24_OFF", FVector2D(16, 24)))); + Set("Sequencer.Transport.SetPlayEnd", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16, 24))) + .SetPressed(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_Out_16x24", FVector2D(16, 24))) + .SetHovered(IMAGE_BRUSH("/Sequencer/Transport_Bar/Bracket_Out_16x24_OFF", FVector2D(16, 24)))); + + Set("Sequencer.Transport.CloseButton", FButtonStyle() + .SetNormal(IMAGE_BRUSH("/Docking/CloseApp_Normal", Icon16x16)) + .SetPressed(IMAGE_BRUSH("/Docking/CloseApp_Pressed", Icon16x16)) + .SetHovered(IMAGE_BRUSH("/Docking/CloseApp_Hovered", Icon16x16))); + + Set("Sequencer.NotificationImage_AddedPlayMovieSceneEvent", new IMAGE_BRUSH("Old/Checkbox_checked", Icon16x16)); + + Set("Sequencer.Save", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon48x48)); + Set("Sequencer.Save.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Save_48x", Icon24x24)); + Set("Sequencer.SaveAsterisk", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon48x48)); + Set("Sequencer.SaveAsterisk.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SaveAsterisk_48x", Icon24x24)); + Set("Sequencer.SaveAs", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon48x48 )); + Set("Sequencer.SaveAs.Small", new IMAGE_BRUSH( "Sequencer/Main_Icons/Icon_Sequencer_SaveAs_48x", Icon24x24 )); + Set("Sequencer.DiscardChanges", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon48x48)); + Set("Sequencer.DiscardChanges.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Revert_24x", Icon24x24)); + Set("Sequencer.RestoreAnimatedState", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon48x48)); + Set("Sequencer.RestoreAnimatedState.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_RestoreAnimatedState_24x", Icon24x24)); + Set("Sequencer.GenericGripLeft", new BOX_BRUSH("Sequencer/GenericGripLeft", FMargin(5.f / 16.f))); + Set("Sequencer.GenericGripRight", new BOX_BRUSH("Sequencer/GenericGripRight", FMargin(5.f / 16.f))); + Set("Sequencer.SectionArea.Background", new FSlateColorBrush(FColor::White)); + + Set("Sequencer.Section.Background", new BORDER_BRUSH(TEXT("Sequencer/SectionBackground"), FMargin(4.f / 16.f))); + Set("Sequencer.Section.BackgroundTint", new BOX_BRUSH(TEXT("Sequencer/SectionBackgroundTint"), FMargin(4 / 16.f))); + Set("Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH(TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.Section.SelectedTrackTint", new BOX_BRUSH(TEXT("Sequencer/SelectedTrackTint"), FMargin(0.f, 0.5f))); + Set("Sequencer.Section.SelectionBorder", new BORDER_BRUSH(TEXT("Sequencer/SectionHighlight"), FMargin(7.f / 16.f))); + Set("Sequencer.Section.LockedBorder", new BORDER_BRUSH(TEXT("Sequencer/SectionLocked"), FMargin(7.f / 16.f))); + Set("Sequencer.Section.SelectedSectionOverlay", new IMAGE_BRUSH(TEXT("Sequencer/SelectedSectionOverlay"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.Section.FilmBorder", new IMAGE_BRUSH(TEXT("Sequencer/SectionFilmBorder"), FVector2D(10, 7), FLinearColor::White, ESlateBrushTileType::Horizontal)); + Set("Sequencer.Section.GripLeft", new BOX_BRUSH("Sequencer/SectionGripLeft", FMargin(5.f / 16.f))); + Set("Sequencer.Section.GripRight", new BOX_BRUSH("Sequencer/SectionGripRight", FMargin(5.f / 16.f))); + Set("Sequencer.Section.EasingHandle", new IMAGE_BRUSH("Sequencer/EasingHandle", FVector2D(10.f, 10.f))); + + Set("Sequencer.Section.PreRoll", new BORDER_BRUSH(TEXT("Sequencer/PreRoll"), FMargin(0.f, .5f, 0.f, .5f))); + + Set("Sequencer.Section.PinCusion", new IMAGE_BRUSH(TEXT("Sequencer/PinCusion"), Icon16x16, FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.Section.OverlapBorder", new BORDER_BRUSH(TEXT("Sequencer/OverlapBorder"), FMargin(1.f / 4.f, 0.f))); + Set("Sequencer.Section.StripeO.verlay", new BOX_BRUSH("Sequencer/SectionStripeOverlay", FMargin(0.f, .5f))); + Set("Sequencer.Section.BackgroundText", DEFAULT_FONT("Bold", 24)); + Set("Sequencer.Section.EmptySpace", new BOX_BRUSH(TEXT("Sequencer/EmptySpace"), FMargin(0.f, 7.f / 14.f))); + + Set("Sequencer.AnimationOutliner.ColorStrip", FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 0)) + .SetPressedPadding(FMargin(0, 0, 0, 0)) + ); + + Set("Sequencer.AnimationOutliner.TopLevelBorder_Expanded", new BOX_BRUSH("Sequencer/TopLevelNodeBorder_Expanded", FMargin(4.0f / 16.0f))); + Set("Sequencer.AnimationOutliner.TopLevelBorder_Collapsed", new BOX_BRUSH("Sequencer/TopLevelNodeBorder_Collapsed", FMargin(4.0f / 16.0f))); + Set("Sequencer.AnimationOutliner.DefaultBorder", new FSlateColorBrush(FLinearColor::White)); + Set("Sequencer.AnimationOutliner.TransparentBorder", new FSlateColorBrush(FLinearColor::Transparent)); + Set("Sequencer.AnimationOutliner.BoldFont", DEFAULT_FONT("Bold", 11)); + Set("Sequencer.AnimationOutliner.RegularFont", DEFAULT_FONT("Regular", 9)); + Set("Sequencer.ShotFilter", new IMAGE_BRUSH("Sequencer/FilteredArea", FVector2D(74, 74), FLinearColor::White, ESlateBrushTileType::Both)); + Set("Sequencer.KeyMark", new IMAGE_BRUSH("Sequencer/KeyMark", FVector2D(3, 21), FLinearColor::White, ESlateBrushTileType::NoTile)); + Set("Sequencer.SetAutoKey", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon48x48)); + Set("Sequencer.SetAutoKey.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_24x", Icon24x24)); + Set("Sequencer.SetAutoTrack", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon48x48)); + Set("Sequencer.SetAutoTrack.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Track_24x", Icon24x24)); + Set("Sequencer.SetAutoChangeAll", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon48x48)); + Set("Sequencer.SetAutoChangeAll.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Auto_Key_All_24x", Icon24x24)); + Set("Sequencer.SetAutoChangeNone", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon48x48)); + Set("Sequencer.SetAutoChangeNone.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Disable_Auto_Key_24x", Icon24x24)); + Set("Sequencer.AllowAllEdits", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon48x48)); + Set("Sequencer.AllowAllEdits.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_All_Edits_24x", Icon24x24)); + Set("Sequencer.AllowSequencerEditsOnly", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon48x48)); + Set("Sequencer.AllowSequencerEditsOnly.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Sequencer_Edits_Only_24x", Icon24x24)); + Set("Sequencer.AllowLevelEditsOnly", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon48x48)); + Set("Sequencer.AllowLevelEditsOnly.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Allow_Level_Edits_Only_24x", Icon24x24)); + Set("Sequencer.SetKeyAll", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon48x48)); + Set("Sequencer.SetKeyAll.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_All_24x", Icon24x24)); + Set("Sequencer.SetKeyGroup", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon48x48)); + Set("Sequencer.SetKeyGroup.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Group_24x", Icon24x24)); + Set("Sequencer.SetKeyChanged", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon48x48)); + Set("Sequencer.SetKeyChanged.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Key_Part_24x", Icon24x24)); + Set("Sequencer.ToggleIsSnapEnabled", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon48x48)); + Set("Sequencer.ToggleIsSnapEnabled.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Snap_24x", Icon24x24)); + Set("Sequencer.ToggleShowCurveEditor", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_CurveEditor_24x", Icon48x48)); + Set("Sequencer.ToggleShowCurveEditor.Small", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_CurveEditor_24x", Icon24x24)); + Set("Sequencer.ToggleAutoScroll", new IMAGE_BRUSH("Icons/icon_Sequencer_ToggleAutoScroll_40x", Icon48x48)); + Set("Sequencer.ToggleAutoScroll.Small", new IMAGE_BRUSH("Icons/icon_Sequencer_ToggleAutoScroll_16x", Icon16x16)); + Set("Sequencer.MoveTool.Small", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_Move_24x", Icon16x16)); + Set("Sequencer.MarqueeTool.Small", new IMAGE_BRUSH("Icons/SequencerIcons/icon_Sequencer_Marquee_24x", Icon16x16)); + Set("Sequencer.RenderMovie.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Create_Movie_24x", Icon24x24)); + Set("Sequencer.CreateCamera.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Create_Camera_24x", Icon24x24)); + Set("Sequencer.FindInContentBrowser.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Find_In_Content_Browser_24x", Icon24x24)); + Set("Sequencer.LockCamera", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16)); + Set("Sequencer.UnlockCamera", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Look_Thru_24x", Icon16x16, FLinearColor(1.f, 1.f, 1.f, 0.5f))); + Set("Sequencer.Thumbnail.SectionHandle", new IMAGE_BRUSH("Old/White", Icon16x16, FLinearColor::Black)); + Set("Sequencer.TrackHoverHighlight_Top", new IMAGE_BRUSH(TEXT("Sequencer/TrackHoverHighlight_Top"), FVector2D(4, 4))); + Set("Sequencer.TrackHoverHighlight_Bottom", new IMAGE_BRUSH(TEXT("Sequencer/TrackHoverHighlight_Bottom"), FVector2D(4, 4))); + Set("Sequencer.SpawnableIconOverlay", new IMAGE_BRUSH(TEXT("Sequencer/SpawnableIconOverlay"), FVector2D(13, 13))); + Set("Sequencer.MultipleIconOverlay", new IMAGE_BRUSH(TEXT("Sequencer/MultipleIconOverlay"), FVector2D(13, 13))); + Set("Sequencer.LockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Locked_16x", Icon16x16)); + Set("Sequencer.UnlockSequence", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Unlocked_16x", Icon16x16)); + + Set("Sequencer.GeneralOptions", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon48x48)); + Set("Sequencer.GeneralOptions.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_General_Options_24x", Icon24x24)); + Set("Sequencer.PlaybackOptions", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon48x48)); + Set("Sequencer.PlaybackOptions.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Playback_Options_24x", Icon24x24)); + Set("Sequencer.SelectEditOptions", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon48x48)); + Set("Sequencer.SelectEditOptions.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_SelectEdit_Options_24x", Icon24x24)); + Set("Sequencer.Time", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon48x48)); + Set("Sequencer.Time.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Time_24x", Icon24x24)); + Set("Sequencer.Value", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon48x48)); + Set("Sequencer.Value.Small", new IMAGE_BRUSH("Sequencer/Main_Icons/Icon_Sequencer_Value_24x", Icon24x24)); + + Set("Sequencer.OverlayPanel.Background", new BOX_BRUSH("Sequencer/OverlayPanelBackground", FMargin(26.f / 54.f))); + + Set("Sequencer.TrackArea.LaneColor", FLinearColor(0.3f, 0.3f, 0.3f, 0.3f)); + + Set("Sequencer.Tracks.Media", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Media_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Audio", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Audio_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Event", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Event_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Fade", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Fade_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.CameraCut", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Camera_Cut_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.CinematicShot", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Shot_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Slomo", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Play_Rate_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.Sub", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Sub_Track_16x", Icon16x16)); + Set("Sequencer.Tracks.LevelVisibility", new IMAGE_BRUSH("Sequencer/Dropdown_Icons/Icon_Level_Visibility_Track_16x", Icon16x16)); + + Set("Sequencer.CursorDecorator_MarqueeAdd", new IMAGE_BRUSH("Sequencer/CursorDecorator_MarqueeAdd", Icon16x16)); + Set("Sequencer.CursorDecorator_MarqueeSubtract", new IMAGE_BRUSH("Sequencer/CursorDecorator_MarqueeSubtract", Icon16x16)); + + Set("Sequencer.BreadcrumbText", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Bold", 11)) + .SetColorAndOpacity(FLinearColor(1.0f, 1.0f, 1.0f)) + .SetHighlightColor(FLinearColor(1.0f, 1.0f, 1.0f)) + .SetShadowOffset(FVector2D(1, 1)) + .SetShadowColorAndOpacity(FLinearColor(0, 0, 0, 0.9f))); + Set("Sequencer.BreadcrumbIcon", new IMAGE_BRUSH("Common/SmallArrowRight", Icon10x10)); + + const FButtonStyle NoBorder = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + + const FButtonStyle DetailsKeyButton = FButtonStyle(NoBorder) + .SetNormal(IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11, 11))) + .SetHovered(IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11, 11), SelectionColor)) + .SetPressed(IMAGE_BRUSH("Sequencer/AddKey_Details", FVector2D(11, 11), SelectionColor_Pressed)) + .SetNormalPadding(FMargin(0, 1)) + .SetPressedPadding(FMargin(0, 2, 0, 0)); + Set("Sequencer.AddKey.Details", DetailsKeyButton); + + const FSplitterStyle OutlinerSplitterStyle = FSplitterStyle() + .SetHandleNormalBrush(FSlateNoResource()) + .SetHandleHighlightBrush(FSlateNoResource()); + Set("Sequencer.AnimationOutliner.Splitter", OutlinerSplitterStyle); + + Set("Sequencer.HyperlinkSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) + .SetTextPadding(FMargin(0)) + .SetBackgroundBrush(BORDER_BRUSH("Old/HyperlinkDotted", FMargin(0, 0, 0, 3 / 16.0f), FSlateColor::UseSubduedForeground())) + .SetHoveredBackgroundBrush(FSlateNoResource()) + .SetInactiveFillBrush(FSlateNoResource()) + .SetActiveFillBrush(FSlateNoResource()) + .SetForegroundColor(FSlateColor::UseSubduedForeground()) + .SetArrowsImage(FSlateNoResource()) + ); + + Set("Sequencer.PlayTimeSpinBox", FSpinBoxStyle(GetWidgetStyle("SpinBox")) + .SetTextPadding(FMargin(0)) + .SetBackgroundBrush(FSlateNoResource()) + .SetHoveredBackgroundBrush(FSlateNoResource()) + .SetInactiveFillBrush(FSlateNoResource()) + .SetActiveFillBrush(FSlateNoResource()) + .SetForegroundColor(SelectionColor_Pressed) + .SetArrowsImage(FSlateNoResource()) + ); + + Set("Sequencer.HyperlinkTextBox", FEditableTextBoxStyle() + .SetFont(DEFAULT_FONT("Regular", 9)) + .SetBackgroundImageNormal(FSlateNoResource()) + .SetBackgroundImageHovered(FSlateNoResource()) + .SetBackgroundImageFocused(FSlateNoResource()) + .SetBackgroundImageReadOnly(FSlateNoResource()) + .SetBackgroundColor(FLinearColor::Transparent) + .SetForegroundColor(FSlateColor::UseSubduedForeground()) + ); + Set("Sequencer.FixedFont", DEFAULT_FONT("Mono", 9)); + + Set("Sequencer.RecordSelectedActors", new IMAGE_BRUSH("SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16)); + + FComboButtonStyle SequencerSectionComboButton = FComboButtonStyle() + .SetButtonStyle( + FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 0)) + .SetPressedPadding(FMargin(0, 1, 0, 0)) + ) + .SetDownArrowImage(IMAGE_BRUSH("Common/ComboArrow", Icon8x8)); + Set("Sequencer.SectionComboButton", SequencerSectionComboButton); + + Set("Sequencer.CreateEventBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_AddFunction_16px", Icon16x16)); + Set("Sequencer.CreateQuickBinding", new IMAGE_BRUSH("Icons/icon_Blueprint_Node_16x", Icon16x16)); + Set("Sequencer.ClearEventBinding", new IMAGE_BRUSH("Icons/Edit/icon_Edit_Delete_40x", Icon16x16)); + Set("Sequencer.MultipleEvents", new IMAGE_BRUSH("Sequencer/MultipleEvents", Icon16x16)); + Set("Sequencer.UnboundEvent", new IMAGE_BRUSH("Sequencer/UnboundEvent", Icon16x16)); + + // Sequencer Blending Iconography + Set("EMovieSceneBlendType::Absolute", new IMAGE_BRUSH("Sequencer/EMovieSceneBlendType_Absolute", FVector2D(32, 16))); + Set("EMovieSceneBlendType::Relative", new IMAGE_BRUSH("Sequencer/EMovieSceneBlendType_Relative", FVector2D(32, 16))); + Set("EMovieSceneBlendType::Additive", new IMAGE_BRUSH("Sequencer/EMovieSceneBlendType_Additive", FVector2D(32, 16))); + } + + + // Sequence recorder standalone UI + if (IncludeEditorSpecificStyles()) + { + Set("SequenceRecorder.TabIcon", new IMAGE_BRUSH("SequenceRecorder/icon_tab_SequenceRecorder_16x", Icon16x16)); + Set("SequenceRecorder.Common.RecordAll.Small", new IMAGE_BRUSH("SequenceRecorder/icon_RecordAll_40x", Icon20x20)); + Set("SequenceRecorder.Common.RecordAll", new IMAGE_BRUSH("SequenceRecorder/icon_RecordAll_40x", Icon40x40)); + Set("SequenceRecorder.Common.StopAll.Small", new IMAGE_BRUSH("SequenceRecorder/icon_StopAll_40x", Icon20x20)); + Set("SequenceRecorder.Common.StopAll", new IMAGE_BRUSH("SequenceRecorder/icon_StopAll_40x", Icon40x40)); + Set("SequenceRecorder.Common.AddRecording.Small", new IMAGE_BRUSH("SequenceRecorder/icon_AddRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.AddRecording", new IMAGE_BRUSH("SequenceRecorder/icon_AddRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.AddCurrentPlayerRecording.Small", new IMAGE_BRUSH("SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.AddCurrentPlayerRecording", new IMAGE_BRUSH("SequenceRecorder/icon_AddCurrentPlayerRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.RemoveRecording.Small", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.RemoveRecording", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.RemoveAllRecordings.Small", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon20x20)); + Set("SequenceRecorder.Common.RemoveAllRecordings", new IMAGE_BRUSH("SequenceRecorder/icon_RemoveRecording_40x", Icon40x40)); + Set("SequenceRecorder.Common.RecordingActive", new IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)); + Set("SequenceRecorder.Common.RecordingInactive", new IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)); + } + +} + +void FSlateEditorStyle::FStyle::SetupViewportStyles() +{ +// Viewport ToolbarBar + { + Set("ViewportMenu.Background", new BOX_BRUSH("Old/Menu_Background", FMargin(8.0f / 64.0f), FLinearColor::Transparent)); + Set("ViewportMenu.Icon", new IMAGE_BRUSH("Icons/icon_tab_toolbar_16px", Icon16x16)); + Set("ViewportMenu.Expand", new IMAGE_BRUSH("Icons/toolbar_expand_16x", Icon8x8)); + Set("ViewportMenu.SubMenuIndicator", new IMAGE_BRUSH("Common/SubmenuArrow", Icon8x8)); + Set("ViewportMenu.SToolBarComboButtonBlock.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarButtonBlock.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarButtonBlock.Button.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarCheckComboButtonBlock.Padding", FMargin(0)); + Set("ViewportMenu.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f)); + Set("ViewportMenu.SToolBarComboButtonBlock.ComboButton.Color", FLinearColor(0.f, 0.f, 0.f, 0.75f)); + + Set("ViewportMenu.Separator", new BOX_BRUSH("Old/Button", 8.0f / 32.0f, FLinearColor::Transparent)); + Set("ViewportMenu.Separator.Padding", FMargin(100.0f)); + + Set("ViewportMenu.Label", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Bold", 9)) + .SetColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f))); + Set("ViewportMenu.Label.Padding", FMargin(0.0f, 0.0f, 3.0f, 0.0f)); + Set("ViewportMenu.Label.ContentPadding", FMargin(5.0f, 2.0f)); + Set("ViewportMenu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle).SetFont(DEFAULT_FONT("Regular", 9))); + Set("ViewportMenu.Keybinding", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 8))); + + Set("ViewportMenu.Block.IndentedPadding", FMargin(0)); + Set("ViewportMenu.Block.Padding", FMargin(0)); + + Set("ViewportMenu.Heading.Font", DEFAULT_FONT("Regular", 8)); + Set("ViewportMenu.Heading.ColorAndOpacity", FLinearColor(0.4f, 0.4, 0.4f, 1.0f)); + + const FCheckBoxStyle ViewportMenuCheckBoxCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)); + Set("ViewportMenu.CheckBox", ViewportMenuCheckBoxCheckBoxStyle); + + // Read-only checkbox that appears next to a menu item + const FCheckBoxStyle ViewportMenuCheckCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(FSlateNoResource()) + .SetUncheckedHoveredImage(FSlateNoResource()) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)); + Set("ViewportMenu.Check", ViewportMenuCheckCheckBoxStyle); + + const FString SmallRoundedButton(TEXT("Common/SmallRoundedButton")); + const FString SmallRoundedButtonStart(TEXT("Common/SmallRoundedButtonLeft")); + const FString SmallRoundedButtonMiddle(TEXT("Common/SmallRoundedButtonCentre")); +const FString SmallRoundedButtonEnd(TEXT("Common/SmallRoundedButtonRight")); + +const FLinearColor NormalColor(1, 1, 1, 0.75f); +const FLinearColor PressedColor(1, 1, 1, 1.f); + +const FCheckBoxStyle ViewportMenuRadioButtonCheckBoxStyle = FCheckBoxStyle() +.SetUncheckedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_Off", Icon14x14)) +.SetUncheckedPressedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_Off", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) +.SetUncheckedHoveredImage(IMAGE_BRUSH("Common/MenuItemRadioButton_Off", Icon14x14)) +.SetCheckedHoveredImage(IMAGE_BRUSH("Common/MenuItemRadioButton_On", Icon14x14)) +.SetCheckedPressedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_On_Pressed", Icon14x14)) +.SetCheckedImage(IMAGE_BRUSH("Common/MenuItemRadioButton_On", Icon14x14)); +Set("ViewportMenu.RadioButton", ViewportMenuRadioButtonCheckBoxStyle); + +/* Create style for "ViewportMenu.ToggleButton" ... */ +const FCheckBoxStyle ViewportMenuToggleButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButton, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton", ViewportMenuToggleButtonStyle); + +/* Create style for "ViewportMenu.ToggleButton.Start" ... */ +const FCheckBoxStyle ViewportMenuToggleStartButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButtonStart, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton.Start", ViewportMenuToggleStartButtonStyle); + +/* Create style for "ViewportMenu.ToggleButton.Middle" ... */ +const FCheckBoxStyle ViewportMenuToggleMiddleButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButtonMiddle, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton.Middle", ViewportMenuToggleMiddleButtonStyle); + +/* Create style for "ViewportMenu.ToggleButton.End" ... */ +const FCheckBoxStyle ViewportMenuToggleEndButtonStyle = FCheckBoxStyle() +.SetCheckBoxType(ESlateCheckBoxType::ToggleButton) +.SetUncheckedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), NormalColor)) +.SetUncheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), PressedColor)) +.SetUncheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), PressedColor)) +.SetCheckedHoveredImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedPressedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), SelectionColor_Pressed)) +.SetCheckedImage(BOX_BRUSH(*SmallRoundedButtonEnd, FMargin(7.f / 16.f), SelectionColor_Pressed)); +/* ... and add new style */ +Set("ViewportMenu.ToggleButton.End", ViewportMenuToggleEndButtonStyle); + +const FMargin NormalPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); +const FMargin PressedPadding = FMargin(4.0f, 4.0f, 4.0f, 4.0f); + +const FButtonStyle ViewportMenuButton = FButtonStyle(Button) +.SetNormal(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, NormalColor)) +.SetPressed(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, PressedColor)) +.SetHovered(BOX_BRUSH(*SmallRoundedButton, 7.0f / 16.0f, PressedColor)) +.SetPressedPadding(PressedPadding) +.SetNormalPadding(NormalPadding); + +Set("ViewportMenu.Button", ViewportMenuButton); + +Set("ViewportMenu.Button.Start", FButtonStyle(ViewportMenuButton) + .SetNormal(BOX_BRUSH(*SmallRoundedButtonStart, 7.0f / 16.0f, NormalColor)) + .SetPressed(BOX_BRUSH(*SmallRoundedButtonStart, 7.0f / 16.0f, PressedColor)) + .SetHovered(BOX_BRUSH(*SmallRoundedButtonStart, 7.0f / 16.0f, PressedColor)) +); + +Set("ViewportMenu.Button.Middle", FButtonStyle(ViewportMenuButton) + .SetNormal(BOX_BRUSH(*SmallRoundedButtonMiddle, 7.0f / 16.0f, NormalColor)) + .SetPressed(BOX_BRUSH(*SmallRoundedButtonMiddle, 7.0f / 16.0f, PressedColor)) + .SetHovered(BOX_BRUSH(*SmallRoundedButtonMiddle, 7.0f / 16.0f, PressedColor)) +); + +Set("ViewportMenu.Button.End", FButtonStyle(ViewportMenuButton) + .SetNormal(BOX_BRUSH(*SmallRoundedButtonEnd, 7.0f / 16.0f, NormalColor)) + .SetPressed(BOX_BRUSH(*SmallRoundedButtonEnd, 7.0f / 16.0f, PressedColor)) + .SetHovered(BOX_BRUSH(*SmallRoundedButtonEnd, 7.0f / 16.0f, PressedColor)) +); + } + + // Viewport actor preview's pin/unpin buttons + { + Set("ViewportActorPreview.Pinned", new IMAGE_BRUSH("Common/PushPin_Down", Icon16x16)); + Set("ViewportActorPreview.Unpinned", new IMAGE_BRUSH("Common/PushPin_Up", Icon16x16)); + } +} + +void FSlateEditorStyle::FStyle::SetupNotificationBarStyles() +{ +// NotificationBar + { + Set("NotificationBar.Background", new FSlateNoResource()); + Set("NotificationBar.Icon", new FSlateNoResource()); + Set("NotificationBar.Expand", new IMAGE_BRUSH("Icons/toolbar_expand_16x", Icon16x16)); + Set("NotificationBar.SubMenuIndicator", new IMAGE_BRUSH("Common/SubmenuArrow", Icon8x8)); + + Set("NotificationBar.Block.IndentedPadding", FMargin(0)); + Set("NotificationBar.Block.Padding", FMargin(0)); + + Set("NotificationBar.Separator", new BOX_BRUSH("Old/Button", 4.0f / 32.0f)); + Set("NotificationBar.Separator.Padding", FMargin(0.5f)); + + Set("NotificationBar.Label", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 9))); + Set("NotificationBar.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle).SetFont(DEFAULT_FONT("Regular", 9))); + Set("NotificationBar.Keybinding", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 8))); + + Set("NotificationBar.Heading", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Regular", 8)) + .SetColorAndOpacity(FLinearColor(0.4f, 0.4, 0.4f, 1.0f))); + + const FCheckBoxStyle NotificationBarCheckBoxCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined", Icon14x14)) + .SetUndeterminedHoveredImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14)) + .SetUndeterminedPressedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))); + Set("NotificationBar.CheckBox", NotificationBarCheckBoxCheckBoxStyle); + + // Read-only checkbox that appears next to a menu item + const FCheckBoxStyle NotificationBarCheckCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(FSlateNoResource()) + .SetUncheckedHoveredImage(FSlateNoResource()) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUndeterminedPressedImage(FSlateNoResource()) + .SetUndeterminedHoveredImage(FSlateNoResource()); + Set("NotificationBar.Check", NotificationBarCheckCheckBoxStyle); + + // This radio button is actually just a check box with different images + const FCheckBoxStyle NotificationBarRadioButtonCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16, SelectionColor)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16, SelectionColor_Pressed)) + .SetCheckedImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16)); + Set("NotificationBar.RadioButton", NotificationBarRadioButtonCheckBoxStyle); + + const FCheckBoxStyle NotificationBarToggleButtonCheckBoxStyle = FCheckBoxStyle() + .SetCheckBoxType(ESlateCheckBoxType::ToggleButton) + .SetUncheckedImage(FSlateNoResource()) + .SetUncheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetUncheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetCheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetCheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetCheckedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("NotificationBar.ToggleButton", NotificationBarToggleButtonCheckBoxStyle); + + const FButtonStyle NoBorder = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + + Set("NotificationBar.Button", FButtonStyle(NoBorder) + .SetNormal(FSlateNoResource()) + .SetPressed(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetHovered(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetNormalPadding(FMargin(0, 1)) + .SetPressedPadding(FMargin(0, 2, 0, 0)) + ); + + Set("NotificationBar.Button.Checked", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("NotificationBar.Button.Checked_Hovered", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("NotificationBar.Button.Checked_Pressed", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)); + + Set("NotificationBar.SToolBarButtonBlock.CheckBox.Padding", FMargin(4.0f)); + Set("NotificationBar.SToolBarButtonBlock.Button.Padding", FMargin(0.0f)); + Set("NotificationBar.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground); + } +} + +void FSlateEditorStyle::FStyle::SetupMenuBarStyles() +{ + // MenuBar + { + Set("Menu.Background", new BOX_BRUSH("Old/Menu_Background", FMargin(8.0f / 64.0f))); + Set("Menu.Icon", new IMAGE_BRUSH("Icons/icon_tab_toolbar_16px", Icon16x16)); + Set("Menu.Expand", new IMAGE_BRUSH("Icons/toolbar_expand_16x", Icon16x16)); + Set("Menu.SubMenuIndicator", new IMAGE_BRUSH("Common/SubmenuArrow", Icon8x8)); + Set("Menu.SToolBarComboButtonBlock.Padding", FMargin(4.0f)); + Set("Menu.SToolBarButtonBlock.Padding", FMargin(4.0f)); + Set("Menu.SToolBarCheckComboButtonBlock.Padding", FMargin(4.0f)); + Set("Menu.SToolBarButtonBlock.CheckBox.Padding", FMargin(0.0f)); + Set("Menu.SToolBarComboButtonBlock.ComboButton.Color", DefaultForeground); + + Set("Menu.Block.IndentedPadding", FMargin(18.0f, 2.0f, 4.0f, 4.0f)); + Set("Menu.Block.Padding", FMargin(2.0f, 2.0f, 4.0f, 4.0f)); + + Set("Menu.Separator", new BOX_BRUSH("Old/Button", 4.0f / 32.0f)); + Set("Menu.Separator.Padding", FMargin(0.5f)); + + Set("Menu.Label", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 9))); + Set("Menu.Label.Padding", FMargin(0.0f, 0.0f, 0.0f, 0.0f)); + Set("Menu.Label.ContentPadding", FMargin(10.0f, 2.0f)); + Set("Menu.EditableText", FEditableTextBoxStyle(NormalEditableTextBoxStyle).SetFont(DEFAULT_FONT("Regular", 9))); + Set("Menu.Keybinding", FTextBlockStyle(NormalText).SetFont(DEFAULT_FONT("Regular", 8))); + + Set("Menu.Heading", FTextBlockStyle(NormalText) + .SetFont(DEFAULT_FONT("Regular", 8)) + .SetColorAndOpacity(FLinearColor(0.4f, 0.4, 0.4f, 1.0f))); + + /* Set images for various SCheckBox states associated with menu check box items... */ + const FCheckBoxStyle BasicMenuCheckBoxStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/SmallCheckBox", Icon14x14)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Checked_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))) + .SetUndeterminedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined", Icon14x14)) + .SetUndeterminedHoveredImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14)) + .SetUndeterminedPressedImage(IMAGE_BRUSH("Common/CheckBox_Undetermined_Hovered", Icon14x14, FLinearColor(0.5f, 0.5f, 0.5f))); + + /* ...and add the new style */ + Set("Menu.CheckBox", BasicMenuCheckBoxStyle); + + /* Read-only checkbox that appears next to a menu item */ + /* Set images for various SCheckBox states associated with read-only menu check box items... */ + const FCheckBoxStyle BasicMenuCheckStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/SmallCheckBox_Hovered", Icon14x14)) + .SetCheckedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/SmallCheck", Icon14x14)) + .SetUndeterminedImage(IMAGE_BRUSH("Icons/Empty_14x", Icon14x14)) + .SetUndeterminedHoveredImage(FSlateNoResource()) + .SetUndeterminedPressedImage(FSlateNoResource()); + + /* ...and add the new style */ + Set("Menu.Check", BasicMenuCheckStyle); + + /* This radio button is actually just a check box with different images */ + /* Set images for various Menu radio button (SCheckBox) states... */ + const FCheckBoxStyle BasicMenuRadioButtonStyle = FCheckBoxStyle() + .SetUncheckedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUncheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetCheckedImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16)) + .SetCheckedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Selected_16x", Icon16x16, SelectionColor)) + .SetCheckedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed)) + .SetUndeterminedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16)) + .SetUndeterminedHoveredImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor)) + .SetUndeterminedPressedImage(IMAGE_BRUSH("Common/RadioButton_Unselected_16x", Icon16x16, SelectionColor_Pressed)); + + /* ...and set new style */ + Set("Menu.RadioButton", BasicMenuRadioButtonStyle); + + /* Create style for "Menu.ToggleButton" widget ... */ + const FCheckBoxStyle MenuToggleButtonCheckBoxStyle = FCheckBoxStyle() + .SetCheckBoxType(ESlateCheckBoxType::ToggleButton) + .SetUncheckedImage(FSlateNoResource()) + .SetUncheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetUncheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetCheckedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetCheckedHoveredImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetCheckedPressedImage(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)); + /* ... and add new style */ + Set("Menu.ToggleButton", MenuToggleButtonCheckBoxStyle); + + const FButtonStyle NoBorder = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(FSlateNoResource()) + .SetPressed(FSlateNoResource()) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + + Set("Menu.Button", FButtonStyle(NoBorder) + .SetNormal(FSlateNoResource()) + .SetPressed(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)) + .SetHovered(BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)) + .SetNormalPadding(FMargin(0, 1)) + .SetPressedPadding(FMargin(0, 2, 0, 0)) + ); + + Set("Menu.Button.Checked", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("Menu.Button.Checked_Hovered", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor_Pressed)); + Set("Menu.Button.Checked_Pressed", new BOX_BRUSH("Common/RoundedSelection_16x", 4.0f / 16.0f, SelectionColor)); + + /* The style of a menu bar button when it has a sub menu open */ + Set("Menu.Button.SubMenuOpen", new BORDER_BRUSH("Common/Selection", FMargin(4.f / 16.f), FLinearColor(0.10f, 0.10f, 0.10f))); + } +} + void FSlateEditorStyle::FStyle::SetupGeneralIcons() { Set("Plus", new IMAGE_BRUSH("Icons/PlusSymbol_12x", Icon12x12)); @@ -3640,6 +3709,7 @@ void FSlateEditorStyle::FStyle::SetupPropertyEditorStyles() Set( "DetailsView.CategoryMiddle", new IMAGE_BRUSH( "PropertyView/DetailCategoryMiddle", FVector2D( 16, 16 ) ) ); Set( "DetailsView.CategoryMiddle_Hovered", new IMAGE_BRUSH( "PropertyView/DetailCategoryMiddle_Hovered", FVector2D( 16, 16 ) ) ); Set( "DetailsView.CategoryMiddle_Highlighted", new BOX_BRUSH( "Common/TextBox_Special_Active", FMargin(8.0f/32.0f) ) ); + Set( "DetailsView.CategoryMiddle_Active", new BOX_BRUSH( "Common/TextBox_Special_Active", FMargin(8.0f/32.0f), SelectionColor_Pressed ) ); Set( "DetailsView.PropertyIsFavorite", new IMAGE_BRUSH("PropertyView/Favorites_Enabled", Icon12x12)); Set( "DetailsView.PropertyIsNotFavorite", new IMAGE_BRUSH("PropertyView/Favorites_Disabled", Icon12x12)); @@ -4497,6 +4567,9 @@ void FSlateEditorStyle::FStyle::SetupGraphEditorStyles() Set( "GraphEditor.DistributeNodesHorizontally", new IMAGE_BRUSH( "Icons/GraphEditor/icon_DistributeNodesHorizontally_20px", Icon20x20 ) ); Set( "GraphEditor.DistributeNodesVertically", new IMAGE_BRUSH( "Icons/GraphEditor/icon_DistributeNodesVertically_20px", Icon20x20 ) ); + + Set( "GraphEditor.ToggleHideUnrelatedNodes", new IMAGE_BRUSH( "Icons/icon_HideUnrelatedNodes_40x", Icon40x40 ) ); + Set( "GraphEditor.ToggleHideUnrelatedNodes.Small", new IMAGE_BRUSH( "Icons/icon_HideUnrelatedNodes_40x", Icon20x20 ) ); // Graph editor widgets { @@ -7868,6 +7941,17 @@ void FSlateEditorStyle::FStyle::SetupUMGEditorStyles() .SetOddRowBackgroundBrush(BOX_BRUSH("PropertyView/DetailCategoryMiddle", FMargin(4 / 16.0f, 8.0f / 16.0f, 4 / 16.0f, 4 / 16.0f))) ); + // Style of the favorite toggle + const FCheckBoxStyle UMGEditorFavoriteToggleStyle = FCheckBoxStyle() + .SetCheckBoxType(ESlateCheckBoxType::CheckBox) + .SetUncheckedImage(IMAGE_BRUSH("Icons/EmptyStar_16x", Icon10x10, FLinearColor(0.8f, 0.8f, 0.8f, 1.f))) + .SetUncheckedHoveredImage(IMAGE_BRUSH("Icons/EmptyStar_16x", Icon10x10, FLinearColor(2.5f, 2.5f, 2.5f, 1.f))) + .SetUncheckedPressedImage(IMAGE_BRUSH("Icons/EmptyStar_16x", Icon10x10, FLinearColor(0.8f, 0.8f, 0.8f, 1.f))) + .SetCheckedImage(IMAGE_BRUSH("Icons/Star_16x", Icon10x10, FLinearColor(0.2f, 0.2f, 0.2f, 1.f))) + .SetCheckedHoveredImage(IMAGE_BRUSH("Icons/Star_16x", Icon10x10, FLinearColor(0.4f, 0.4f, 0.4f, 1.f))) + .SetCheckedPressedImage(IMAGE_BRUSH("Icons/Star_16x", Icon10x10, FLinearColor(0.2f, 0.2f, 0.2f, 1.f))); + Set("UMGEditor.Palette.FavoriteToggleStyle", UMGEditorFavoriteToggleStyle); + Set("HorizontalAlignment_Left", new IMAGE_BRUSH("Icons/UMG/Alignment/Horizontal_Left", Icon20x20)); Set("HorizontalAlignment_Center", new IMAGE_BRUSH("Icons/UMG/Alignment/Horizontal_Center", Icon20x20)); Set("HorizontalAlignment_Right", new IMAGE_BRUSH("Icons/UMG/Alignment/Horizontal_Right", Icon20x20)); diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h index 80bec0136467..b1014cfe8f82 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.h @@ -70,6 +70,12 @@ public: void Initialize(); void SetupGeneralStyles(); + void SetupLevelGeneralStyles(); + void SetupWorldBrowserStyles(); + void SetupSequencerStyles(); + void SetupViewportStyles(); + void SetupNotificationBarStyles(); + void SetupMenuBarStyles(); void SetupGeneralIcons(); void SetupWindowStyles(); void SetupProjectBadgeStyle(); diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp index d34fd023c620..f6bbceb2e0df 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.cpp @@ -233,7 +233,6 @@ FEdModeFoliage::FEdModeFoliage() FName OpacityParamName("OpacityAmount"); BrushMID->GetScalarParameterValue(OpacityParamName, DefaultBrushOpacity); - FFoliageEditCommands::Register(); UICommandList = MakeShareable(new FUICommandList); BindCommands(); } @@ -244,12 +243,32 @@ void FEdModeFoliage::BindCommands() UICommandList->MapAction( Commands.IncreaseBrushSize, - FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, 50.f), + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, 1.f), FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); UICommandList->MapAction( Commands.DecreaseBrushSize, - FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, -50.f), + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustBrushRadius, -1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.IncreasePaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustPaintDensity, 1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.DecreasePaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustPaintDensity, -1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.IncreaseUnpaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustUnpaintDensity, 1.f), + FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); + + UICommandList->MapAction( + Commands.DecreaseUnpaintDensity, + FExecuteAction::CreateRaw(this, &FEdModeFoliage::AdjustUnpaintDensity, -1.f), FCanExecuteAction::CreateRaw(this, &FEdModeFoliage::CurrentToolUsesBrush)); UICommandList->MapAction( @@ -1983,23 +2002,44 @@ void FEdModeFoliage::SelectInvalidInstances(const UFoliageType* Settings) } } -void FEdModeFoliage::AdjustBrushRadius(float Adjustment) +void FEdModeFoliage::AdjustBrushRadius(float Multiplier) { if (UISettings.IsInAnySingleInstantiationMode()) { return; } + const float PercentageChange = 0.05f; const float CurrentBrushRadius = UISettings.GetRadius(); - if (Adjustment > 0.f) + float NewValue = CurrentBrushRadius * (1 + PercentageChange * Multiplier); + UISettings.SetRadius(FMath::Clamp(NewValue, 0.1f, 8192.0f)); +} + +void FEdModeFoliage::AdjustPaintDensity(float Multiplier) +{ + if (UISettings.IsInAnySingleInstantiationMode()) { - UISettings.SetRadius(FMath::Min(CurrentBrushRadius + Adjustment, 8192.f)); + return; } - else if (Adjustment < 0.f) + + const float AdjustmentAmount = 0.02f; + const float CurrentDensity = UISettings.GetPaintDensity(); + + UISettings.SetPaintDensity(FMath::Clamp(CurrentDensity + AdjustmentAmount * Multiplier, 0.0f, 1.0f)); +} + +void FEdModeFoliage::AdjustUnpaintDensity(float Multiplier) +{ + if (UISettings.IsInAnySingleInstantiationMode()) { - UISettings.SetRadius(FMath::Max(CurrentBrushRadius + Adjustment, 0.f)); + return; } + + const float AdjustmentAmount = 0.02f; + const float CurrentDensity = UISettings.GetUnpaintDensity(); + + UISettings.SetUnpaintDensity(FMath::Clamp(CurrentDensity + AdjustmentAmount * Multiplier, 0.0f, 1.0f)); } void FEdModeFoliage::ReapplyInstancesForBrush(UWorld* InWorld, const UFoliageType* Settings, const FSphere& BrushSphere, float Pressure) diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h index 6cd07215789c..aeb5d804df2b 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEdMode.h @@ -462,8 +462,14 @@ public: /** Find and select instances that don't have valid base or 'off-ground' */ void SelectInvalidInstances(const UFoliageType* Settings); - /** Adjusts the radius of the foliage brush by the specified amount */ - void AdjustBrushRadius(float Adjustment); + /** Adjusts the radius of the foliage brush, using the given multiplier to adjust speed */ + void AdjustBrushRadius(float Multiplier); + + /** Adjusts the painting density of the foliage brush, using the given multiplier to adjust speed */ + void AdjustPaintDensity(float Multiplier); + + /** Adjusts the unpainting (erasing) density of the foliage brush, using the given multiplier to adjust speed */ + void AdjustUnpaintDensity(float Multiplier); /** Add desired instances. Uses foliage settings to determine location/scale/rotation and whether instances should be ignored */ static void AddInstances(UWorld* InWorld, const TArray& DesiredInstances, const FFoliagePaintingGeometryFilter& OverrideGeometryFilter, bool InRebuildFoliageTree = true); diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp index 9f9370f1e790..7914e3e8ea17 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.cpp @@ -8,8 +8,14 @@ void FFoliageEditCommands::RegisterCommands() { - UI_COMMAND(DecreaseBrushSize, "Decrease Brush Size", "Decreases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket)); - UI_COMMAND(IncreaseBrushSize, "Increase Brush Size", "Increases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket)); + UI_COMMAND( DecreaseBrushSize, "Decrease Brush Size", "Decreases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket) ); + UI_COMMAND( IncreaseBrushSize, "Increase Brush Size", "Increases the size of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket) ); + + UI_COMMAND( DecreasePaintDensity, "Decrease Brush Density", "Decreases the density of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::LeftBracket) ); + UI_COMMAND( IncreasePaintDensity, "Increase Brush Density", "Increases the density of the foliage brush", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::RightBracket) ); + + UI_COMMAND( DecreaseUnpaintDensity, "Decrease Erase Density", "Decreases the density of the foliage eraser", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::LeftBracket)); + UI_COMMAND( IncreaseUnpaintDensity, "Increase Erase Density", "Increases the density of the foliage eraser", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::RightBracket)); UI_COMMAND( SetPaint, "Paint", "Paint", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( SetReapplySettings, "Reapply", "Reapply settings to instances", EUserInterfaceActionType::ToggleButton, FInputChord() ); diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h index 5e9f6db7414b..21cdeddaac37 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditActions.h @@ -31,6 +31,12 @@ public: TSharedPtr< FUICommandInfo > IncreaseBrushSize; TSharedPtr< FUICommandInfo > DecreaseBrushSize; + TSharedPtr< FUICommandInfo > IncreasePaintDensity; + TSharedPtr< FUICommandInfo > DecreasePaintDensity; + + TSharedPtr< FUICommandInfo > IncreaseUnpaintDensity; + TSharedPtr< FUICommandInfo > DecreaseUnpaintDensity; + /** Commands for the tools toolbar. */ TSharedPtr< FUICommandInfo > SetPaint; TSharedPtr< FUICommandInfo > SetReapplySettings; diff --git a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp index b3a92a634f0a..8abff00f4a6a 100644 --- a/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp +++ b/Engine/Source/Editor/FoliageEdit/Private/FoliageEditModule.cpp @@ -19,6 +19,7 @@ const FName FoliageEditAppIdentifier = FName(TEXT("FoliageEdApp")); #include "FoliageType_Actor.h" #include "InstancedFoliageActor.h" #include "FoliageEdMode.h" +#include "FoliageEditActions.h" #include "PropertyEditorModule.h" #include "FoliageTypeDetails.h" #include "ProceduralFoliageComponent.h" @@ -52,6 +53,8 @@ public: true, 400 ); + FFoliageEditCommands::Register(); + // Register the details customizer FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomClassLayout("FoliageType", FOnGetDetailCustomizationInstance::CreateStatic(&FFoliageTypeDetails::MakeInstance)); @@ -76,7 +79,7 @@ public: SubscribeEvents(); #endif - + // Register thumbnail renderer UThumbnailManager::Get().RegisterCustomRenderer(UFoliageType_InstancedStaticMesh::StaticClass(), UFoliageType_ISMThumbnailRenderer::StaticClass()); UThumbnailManager::Get().RegisterCustomRenderer(UFoliageType_Actor::StaticClass(), UFoliageType_ActorThumbnailRenderer::StaticClass()); @@ -87,6 +90,8 @@ public: */ virtual void ShutdownModule() override { + FFoliageEditCommands::Unregister(); + FEditorModeRegistry::Get().UnregisterMode(FBuiltinEditorModes::EM_Foliage); if (!UObjectInitialized()) diff --git a/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp b/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp index f3ab48bd3b75..27eefdfa2933 100644 --- a/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp +++ b/Engine/Source/Editor/GameProjectGeneration/Private/GameProjectUtils.cpp @@ -824,46 +824,46 @@ void GameProjectUtils::CheckForOutOfDateGameProjectFile() if ( ProjectStatus.bRequiresUpdate ) { bRequiresUpdate = true; - } + } } // Get the current project descriptor - const FProjectDescriptor* Project = IProjectManager::Get().GetCurrentProject(); + const FProjectDescriptor* Project = IProjectManager::Get().GetCurrentProject(); // Check if there are any installed plugins that need to be added as a reference - TArray NewPluginReferences = Project->Plugins; - for(TSharedRef& Plugin: IPluginManager::Get().GetEnabledPlugins()) - { - if(Plugin->GetDescriptor().bInstalled && Project->FindPluginReferenceIndex(Plugin->GetName()) == INDEX_NONE) - { - FPluginReferenceDescriptor PluginReference(Plugin->GetName(), true); - NewPluginReferences.Add(PluginReference); + TArray NewPluginReferences = Project->Plugins; + for(TSharedRef& Plugin: IPluginManager::Get().GetEnabledPlugins()) + { + if(Plugin->GetDescriptor().bInstalled && Project->FindPluginReferenceIndex(Plugin->GetName()) == INDEX_NONE) + { + FPluginReferenceDescriptor PluginReference(Plugin->GetName(), true); + NewPluginReferences.Add(PluginReference); bRequiresUpdate = true; - } - } + } + } - // Check if there are any referenced plugins that do not have a matching supported plugins list - for(FPluginReferenceDescriptor& Reference: NewPluginReferences) + // Check if there are any referenced plugins that do not have a matching supported plugins list + for(FPluginReferenceDescriptor& Reference: NewPluginReferences) + { + if(Reference.bEnabled) + { + TSharedPtr Plugin = IPluginManager::Get().FindPlugin(Reference.Name); + if(Plugin.IsValid()) { - if(Reference.bEnabled) + const FPluginDescriptor& Descriptor = Plugin->GetDescriptor(); + if(Reference.MarketplaceURL != Descriptor.MarketplaceURL) { - TSharedPtr Plugin = IPluginManager::Get().FindPlugin(Reference.Name); - if(Plugin.IsValid()) - { - const FPluginDescriptor& Descriptor = Plugin->GetDescriptor(); - if(Reference.MarketplaceURL != Descriptor.MarketplaceURL) - { - Reference.MarketplaceURL = Descriptor.MarketplaceURL; + Reference.MarketplaceURL = Descriptor.MarketplaceURL; bRequiresUpdate = true; - } - if(Reference.SupportedTargetPlatforms != Descriptor.SupportedTargetPlatforms) - { - Reference.SupportedTargetPlatforms = Descriptor.SupportedTargetPlatforms; + } + if(Reference.SupportedTargetPlatforms != Descriptor.SupportedTargetPlatforms) + { + Reference.SupportedTargetPlatforms = Descriptor.SupportedTargetPlatforms; bRequiresUpdate = true; - } - } } } + } + } // If we have updates pending, show the prompt if (bRequiresUpdate) @@ -887,10 +887,10 @@ void GameProjectUtils::CheckForOutOfDateGameProjectFile() Info.ButtonDetails.Add(FNotificationButtonInfo(UpdateProjectCancelText, FText(), FSimpleDelegate::CreateStatic(&GameProjectUtils::OnUpdateProjectCancel))); if (UpdateGameProjectNotification.IsValid()) - { + { UpdateGameProjectNotification.Pin()->ExpireAndFadeout(); UpdateGameProjectNotification.Reset(); - } + } UpdateGameProjectNotification = FSlateNotificationManager::Get().AddNotification(Info); diff --git a/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp b/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp index 6090f326a36d..9f75cf8673eb 100644 --- a/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/BlueprintConnectionDrawingPolicy.cpp @@ -563,6 +563,11 @@ void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin } } + if ((OutputPin && OutputPin->GetOwningNode()->IsNodeUnrelated()) || (InputPin && InputPin->GetOwningNode()->IsNodeUnrelated())) + { + bWireIsOnDisabledNodeAndNotPassthru = true; + } + if (bWireIsOnDisabledNodeAndNotPassthru) { Params.WireColor *= 0.5f; @@ -574,6 +579,7 @@ void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin { ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor); } + } void FKismetConnectionDrawingPolicy::SetIncompatiblePinDrawState(const TSharedPtr& StartPin, const TSet< TSharedRef >& VisiblePins) diff --git a/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp b/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp index d2cbffdaa550..8d24cca814ae 100644 --- a/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinKey.cpp @@ -7,25 +7,27 @@ void SGraphPinKey::Construct(const FArguments& InArgs, UEdGraphPin* InGraphPinObj) { - - - TArray KeyList; - EKeys::GetAllKeys(KeyList); - SelectedKey = FKey(*InGraphPinObj->GetDefaultAsString()); + InGraphPinObj->AutogeneratedDefaultValue = "None"; + if (SelectedKey.GetFName() == NAME_None) + { + InGraphPinObj->GetSchema()->ResetPinToAutogeneratedDefaultValue(InGraphPinObj, false); + SelectedKey = FKey(*InGraphPinObj->GetDefaultAsString()); + } + if (InGraphPinObj->Direction == EEdGraphPinDirection::EGPD_Input) { // None is a valid key if (SelectedKey.GetFName() == NAME_None) { SelectedKey = EKeys::Invalid; - InGraphPinObj->AutogeneratedDefaultValue = "None"; - InGraphPinObj->GetSchema()->ResetPinToAutogeneratedDefaultValue(InGraphPinObj, false); } else if (!SelectedKey.IsValid()) { // Ensure first valid key is always set by default + TArray KeyList; + EKeys::GetAllKeys(KeyList); SelectedKey = KeyList[0]; InGraphPinObj->GetSchema()->TrySetDefaultValue(*InGraphPinObj, SelectedKey.ToString()); } diff --git a/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp b/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp index 2fb78403b691..25c97586c498 100644 --- a/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/MaterialGraphConnectionDrawingPolicy.cpp @@ -125,7 +125,7 @@ void FMaterialGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* Ou Params.StartDirection = EGPD_Input; } } - else if (!OutputNode->IsNodeEnabled() || OutputNode->IsDisplayAsDisabledForced()) + else if (!OutputNode->IsNodeEnabled() || OutputNode->IsDisplayAsDisabledForced() || OutputNode->IsNodeUnrelated()) { Params.WireColor = MaterialGraphSchema->InactivePinColor; } @@ -145,7 +145,7 @@ void FMaterialGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* Ou Params.EndDirection = EGPD_Output; } } - else if (!InputNode->IsNodeEnabled() || InputNode->IsDisplayAsDisabledForced()) + else if (!InputNode->IsNodeEnabled() || InputNode->IsDisplayAsDisabledForced() || InputNode->IsNodeUnrelated()) { Params.WireColor = MaterialGraphSchema->InactivePinColor; } diff --git a/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp b/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp index 6e6a484f7bc6..36ee51671fec 100644 --- a/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp @@ -453,6 +453,7 @@ TSharedRef SGraphNodeMaterialBase::CreatePreviewWidget() TSharedPtr ViewportWidget = SNew( SViewport ) + .RenderDirectlyToWindow(true) .EnableGammaCorrection(false); PreviewViewport = MakeShareable(new FPreviewViewport(MaterialNode)); diff --git a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp index 80f1959d616b..5b36d7d7bbc2 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp @@ -387,7 +387,7 @@ FSlateColor SCommentBubble::GetBubbleColor() const { FLinearColor ReturnColor = ColorAndOpacity.Get().GetSpecifiedColor(); - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnColor.A *= 0.6f; } diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp index 0706181b4a6c..e62d43784820 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphNode.cpp @@ -563,7 +563,7 @@ FSlateColor SGraphNode::GetNodeTitleColor() const { FLinearColor ReturnTitleColor = GraphNode->IsDeprecated() ? FLinearColor::Red : GetNodeObj()->GetNodeTitleColor(); - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnTitleColor *= FLinearColor(0.5f, 0.5f, 0.5f, 0.4f); } @@ -577,7 +577,7 @@ FSlateColor SGraphNode::GetNodeTitleColor() const FSlateColor SGraphNode::GetNodeBodyColor() const { FLinearColor ReturnBodyColor = GraphNode->GetNodeBodyTintColor(); - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnBodyColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } @@ -592,7 +592,7 @@ const FSlateBrush * SGraphNode::GetNodeBodyBrush() const FSlateColor SGraphNode::GetNodeTitleIconColor() const { FLinearColor ReturnIconColor = IconColor; - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnIconColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f); } @@ -602,7 +602,7 @@ FSlateColor SGraphNode::GetNodeTitleIconColor() const FLinearColor SGraphNode::GetNodeTitleTextColor() const { FLinearColor ReturnTextColor = FLinearColor::White; - if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced()) + if(!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || GraphNode->IsNodeUnrelated()) { ReturnTextColor *= FLinearColor(1.0f, 1.0f, 1.0f, 0.3f); } diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp index c14fa376a1d5..77a34a07ec24 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphNodeDocumentation.cpp @@ -215,8 +215,8 @@ TSharedPtr SGraphNodeDocumentation::CreateDocumentationPage() +SOverlay::Slot() [ SNew(SSimpleGradient) - .StartColor(GraphNodeDocumentationDefs::PageGradientStartColor) - .EndColor(GraphNodeDocumentationDefs::PageGradientEndColor) + .StartColor(this, &SGraphNodeDocumentation::GetPageGradientStartColor) + .EndColor(this, &SGraphNodeDocumentation::GetPageGradientEndColor) ] +SOverlay::Slot() [ @@ -355,6 +355,18 @@ void SGraphNodeDocumentation::Tick( const FGeometry& AllottedGeometry, const dou } } +FLinearColor SGraphNodeDocumentation::GetPageGradientStartColor() const +{ + FLinearColor Color = GraphNodeDocumentationDefs::PageGradientStartColor; + return Color; +} + +FLinearColor SGraphNodeDocumentation::GetPageGradientEndColor() const +{ + FLinearColor Color = GraphNodeDocumentationDefs::PageGradientEndColor; + return Color; +} + ///////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp index 562a9bf76c51..36760d423cf8 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp @@ -172,6 +172,9 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo if (bNodeIsVisible) { const bool bSelected = SelectionToVisualize->Contains( StaticCastSharedRef(CurWidget.Widget)->GetObjectBeingDisplayed() ); + + UEdGraphNode* NodeObj = Cast(ChildNode->GetObjectBeingDisplayed()); + float Alpha = 1.0f; // Handle Node renaming once the node is visible if( bSelected && ChildNode->IsRenamePending() ) @@ -192,7 +195,9 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo OutDrawElements, ShadowLayerId, CurWidget.Geometry.ToInflatedPaintGeometry(NodeShadowSize), - ShadowBrush + ShadowBrush, + ESlateDrawEffect::None, + FLinearColor(1.0f, 1.0f, 1.0f, Alpha) ); } @@ -216,8 +221,6 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo int32 CurWidgetsMaxLayerId; { - UEdGraphNode* NodeObj = Cast(ChildNode->GetObjectBeingDisplayed()); - /** When diffing nodes, nodes that are different between revisions are opaque, nodes that have not changed are faded */ FGraphDiffControl::FNodeMatch NodeMatch = FGraphDiffControl::FindNodeMatch(GraphObjToDiff, NodeObj, NodeMatches); if (NodeMatch.IsValid()) @@ -229,7 +232,10 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo /* When dragging off a pin, we want to duck the alpha of some nodes */ TSharedPtr< SGraphPin > OnlyStartPin = (1 == PreviewConnectorFromPins.Num()) ? PreviewConnectorFromPins[0].FindInGraphPanel(*this) : TSharedPtr< SGraphPin >(); const bool bNodeIsNotUsableInCurrentContext = Schema->FadeNodeWhenDraggingOffPin(NodeObj, OnlyStartPin.IsValid() ? OnlyStartPin.Get()->GetPinObj() : nullptr); - const FWidgetStyle& NodeStyleToUse = (bNodeIsDifferent && !bNodeIsNotUsableInCurrentContext)? InWidgetStyle : FadedStyle; + + const FWidgetStyle& NodeStyle = (bNodeIsDifferent && !bNodeIsNotUsableInCurrentContext)? InWidgetStyle : FadedStyle; + FWidgetStyle NodeStyleToUse = NodeStyle; + NodeStyleToUse.BlendColorAndOpacityTint(FLinearColor(1.0f, 1.0f, 1.0f, Alpha)); // Draw the node.O CurWidgetsMaxLayerId = CurWidget.Widget->Paint(NewArgs, CurWidget.Geometry, MyCullingRect, OutDrawElements, ChildLayerId, NodeStyleToUse, !DisplayAsReadOnly.Get() && ShouldBeEnabled( bParentEnabled ) ); @@ -261,7 +267,9 @@ int32 SGraphPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeo OutDrawElements, CurWidgetsMaxLayerId, BouncedGeometry, - OverlayBrush + OverlayBrush, + ESlateDrawEffect::None, + FLinearColor(1.0f, 1.0f, 1.0f, Alpha) ); } diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp index 3c4489b01767..1fe2e4b1cbef 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphPin.cpp @@ -978,7 +978,7 @@ FSlateColor SGraphPin::GetPinColor() const } if (const UEdGraphSchema* Schema = GraphPinObj->GetSchema()) { - if (!GetPinObj()->GetOwningNode()->IsNodeEnabled() || GetPinObj()->GetOwningNode()->IsDisplayAsDisabledForced() || !IsEditingEnabled()) + if (!GetPinObj()->GetOwningNode()->IsNodeEnabled() || GetPinObj()->GetOwningNode()->IsDisplayAsDisabledForced() || !IsEditingEnabled() || GetPinObj()->GetOwningNode()->IsNodeUnrelated()) { return Schema->GetPinTypeColor(GraphPinObj->PinType) * FLinearColor(1.0f, 1.0f, 1.0f, 0.5f); } @@ -1001,13 +1001,13 @@ FSlateColor SGraphPin::GetPinTextColor() const // If there is no schema there is no owning node (or basically this is a deleted node) if (UEdGraphNode* GraphNode = GraphPinObj->GetOwningNodeUnchecked()) { - const bool bDisabled = (!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !IsEditingEnabled()); + const bool bDisabled = (!GraphNode->IsNodeEnabled() || GraphNode->IsDisplayAsDisabledForced() || !IsEditingEnabled() || GraphNode->IsNodeUnrelated()); if (GraphPinObj->bOrphanedPin) { FLinearColor PinColor = FLinearColor::Red; if (bDisabled) { - PinColor.A = 0.5f; + PinColor.A = .25f; } return PinColor; } diff --git a/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h b/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h index 6c1037011e96..dbcd18bffb08 100644 --- a/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h +++ b/Engine/Source/Editor/GraphEditor/Public/SGraphNodeDocumentation.h @@ -52,6 +52,10 @@ protected: float GetDocumentationWrapWidth() const; /** Returns the current child widgets visibility for hit testing */ EVisibility GetWidgetVisibility() const; + /** Color of the page gradient start */ + FLinearColor GetPageGradientStartColor() const; + /** Color of the page gradient end */ + FLinearColor GetPageGradientEndColor() const; private: diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp index 99735eb63045..eca06dc1c8f3 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp @@ -107,9 +107,10 @@ void FBlueprintDetails::AddEventsCategory(IDetailLayoutBuilder& DetailBuilder, U UProperty* Property = *PropertyIt; FName PropertyName = ComponentProperty->GetFName(); - + static const FName HideInDetailPanelName("HideInDetailPanel"); // Check for multicast delegates that we can safely assign - if ( !Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintAssignable) ) + if ( !Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintAssignable) && + !Property->HasMetaData(HideInDetailPanelName) ) { FName EventName = Property->GetFName(); FText EventText = Property->GetDisplayNameText(); @@ -4915,8 +4916,6 @@ bool FBlueprintGraphActionDetails::IsConstFunctionVisible() const UK2Node_EditablePinBase * FunctionEntryNode = FunctionEntryNodePtr.Get(); if(FunctionEntryNode) { - UBlueprint* Blueprint = FunctionEntryNode->GetBlueprint(); - bSupportedType = FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } @@ -5271,21 +5270,16 @@ TSharedRef FBlueprintInterfaceLayout::OnGetAddInterfaceMenuContent() TArray Blueprints; Blueprints.Add(Blueprint); TSharedRef ClassPicker = FBlueprintEditorUtils::ConstructBlueprintInterfaceClassPicker(Blueprints, FOnClassPicked::CreateSP(this, &FBlueprintInterfaceLayout::OnClassPicked)); - return - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + // Achieving fixed width by nesting items within a fixed width box. + return SNew(SBox) + .WidthOverride(350.0f) [ - // Achieving fixed width by nesting items within a fixed width box. - SNew(SBox) - .WidthOverride(350.0f) + SNew(SVerticalBox) + +SVerticalBox::Slot() + .MaxHeight(400.0f) + .AutoHeight() [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .MaxHeight(400.0f) - .AutoHeight() - [ - ClassPicker - ] + ClassPicker ] ]; } @@ -5328,21 +5322,16 @@ TSharedRef FBlueprintGlobalOptionsDetails::GetParentClassMenuContent() Blueprints.Add(GetBlueprintObj()); TSharedRef ClassPicker = FBlueprintEditorUtils::ConstructBlueprintParentClassPicker(Blueprints, FOnClassPicked::CreateSP(this, &FBlueprintGlobalOptionsDetails::OnClassPicked)); - return - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + // Achieving fixed width by nesting items within a fixed width box. + return SNew(SBox) + .WidthOverride(350.0f) [ - // Achieving fixed width by nesting items within a fixed width box. - SNew(SBox) - .WidthOverride(350.0f) + SNew(SVerticalBox) + +SVerticalBox::Slot() + .MaxHeight(400.0f) + .AutoHeight() [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .MaxHeight(400.0f) - .AutoHeight() - [ - ClassPicker - ] + ClassPicker ] ]; } diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp index 262f65c76614..b48e8a23734c 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp @@ -154,6 +154,11 @@ #include "Widgets/Notifications/SNotificationList.h" #include "NativeCodeGenerationTool.h" +// Focusing related nodes feature +#include "Preferences/BlueprintEditorOptions.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Input/SNumericEntryBox.h" + #define LOCTEXT_NAMESPACE "BlueprintEditor" ///////////////////////////////////////////////////// @@ -1483,7 +1488,8 @@ void FBlueprintEditor::OnChangeBreadCrumbGraph(UEdGraph* InGraph) } FBlueprintEditor::FBlueprintEditor() - : bSaveIntermediateBuildProducts(false) + : EditorOptions(nullptr) + , bSaveIntermediateBuildProducts(false) , bPendingDeferredClose(false) , bRequestedSavingOpenDocumentState(false) , bBlueprintModifiedOnOpen (false) @@ -1491,6 +1497,9 @@ FBlueprintEditor::FBlueprintEditor() , bIsActionMenuContextSensitive(true) , CurrentUISelection(NAME_None) , bEditorMarkedAsClosed(false) + , bHideUnrelatedNodes(false) + , bLockNodeFadeState(false) + , bSelectRegularNode(false) , HasOpenActionMenu(nullptr) , InstructionsFadeCountdown(0.f) { @@ -1505,7 +1514,7 @@ FBlueprintEditor::FBlueprintEditor() AnalyticsStats.NodePasteCreateCount = 0; UEditorEngine* Editor = (UEditorEngine*)GEngine; - if (Editor != NULL) + if (Editor != nullptr) { Editor->RegisterForUndo(this); } @@ -1758,6 +1767,11 @@ void FBlueprintEditor::InitBlueprintEditor( // TRUE if a single Blueprint is being opened and is marked as newly created bool bNewlyCreated = InBlueprints.Num() == 1 && InBlueprints[0]->bIsNewlyCreated; + EditorOptions = nullptr; + + // Load editor settings from disk. + LoadEditorSettings(); + TArray< UObject* > Objects; for (UBlueprint* Blueprint : InBlueprints) { @@ -1785,6 +1799,42 @@ void FBlueprintEditor::InitBlueprintEditor( InitalizeExtenders(); + struct Local + { + static void FillToolbar(FToolBarBuilder& ToolbarBuilder, const TSharedRef< FUICommandList > ToolkitCommands, FBlueprintEditor* BlueprintEditor) + { + ToolbarBuilder.BeginSection("Graph"); + { + ToolbarBuilder.AddToolBarButton( + FBlueprintEditorCommands::Get().ToggleHideUnrelatedNodes, + NAME_None, + TAttribute(), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") + ); + ToolbarBuilder.AddComboButton( + FUIAction(), + FOnGetContent::CreateSP(BlueprintEditor, &FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu), + LOCTEXT("HideUnrelatedNodesOptions", "Focus Related Nodes Options"), + LOCTEXT("HideUnrelatedNodesOptionsMenu", "Focus Related Nodes options menu"), + TAttribute(), + true + ); + } + ToolbarBuilder.EndSection(); + } + }; + + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension( + "Asset", + EExtensionHook::After, + GetToolkitCommands(), + FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar, GetToolkitCommands(), this ) + ); + + AddToolbarExtender(ToolbarExtender); + RegenerateMenusAndToolbars(); RegisterApplicationModes(InBlueprints, bShouldOpenInDefaultsMode, bNewlyCreated); @@ -2259,6 +2309,8 @@ FBlueprintEditor::~FBlueprintEditor() FEngineAnalytics::GetProvider().RecordEvent( FString( "Editor.Usage.BPDisallowedPinConnection" ), BPEditorPinConnectAttribs ); } } + + SaveEditorSettings(); } void FBlueprintEditor::FocusInspectorOnGraphSelection(const FGraphPanelSelectionSet& NewSelection, bool bForceRefresh) @@ -2667,6 +2719,13 @@ void FBlueprintEditor::CreateDefaultCommands() FGraphEditorCommands::Get().ClearAllQuickJumps, FExecuteAction::CreateSP(this, &FBlueprintEditor::ClearAllGraphEditorQuickJumps) ); + + ToolkitCommands->MapAction( + FBlueprintEditorCommands::Get().ToggleHideUnrelatedNodes, + FExecuteAction::CreateSP(this, &FBlueprintEditor::ToggleHideUnrelatedNodes), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsToggleHideUnrelatedNodesChecked) + ); } void FBlueprintEditor::OpenNativeCodeGenerationTool() @@ -3043,6 +3102,11 @@ void FBlueprintEditor::OnGraphEditorFocused(const TSharedRef& InGr } } + if (bHideUnrelatedNodes && SelectedNodes.Num() <= 0) + { + ResetAllNodesUnrelatedStates(); + } + // If the bookmarks view is active, check whether or not we're restricting the view to the current graph. If we are, update the tree to reflect the focused graph context. if (BookmarksWidget.IsValid() && GetDefault()->bShowBookmarksForCurrentDocumentOnlyInTab) @@ -3169,6 +3233,28 @@ void FBlueprintEditor::OnSelectedNodesChangedImpl(const FGraphPanelSelectionSet& } Inspector->ShowDetailsForObjects(NewSelection.Array()); + + + bSelectRegularNode = false; + for (FGraphPanelSelectionSet::TConstIterator It(NewSelection); It; ++It) + { + UEdGraphNode_Comment* SeqNode = Cast(*It); + if (!SeqNode) + { + bSelectRegularNode = true; + break; + } + } + + if (bHideUnrelatedNodes && !bLockNodeFadeState) + { + ResetAllNodesUnrelatedStates(); + + if ( bSelectRegularNode ) + { + HideUnrelatedNodes(); + } + } } void FBlueprintEditor::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIsJustBeingCompiled ) @@ -3594,6 +3680,8 @@ void FBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector ) } } + Collector.AddReferencedObject(EditorOptions); + UserDefinedStructures.Remove(TWeakObjectPtr()); // Remove NULLs for (const TWeakObjectPtr& ObjectPtr : UserDefinedStructures) { @@ -6268,6 +6356,301 @@ ECheckBoxState FBlueprintEditor::CheckEnabledStateForSelectedNodes(ENodeEnabledS return Result; } +void FBlueprintEditor::UpdateNodesUnrelatedStatesAfterGraphChange() +{ + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) + { + ResetAllNodesUnrelatedStates(); + + HideUnrelatedNodes(); + } +} + +void FBlueprintEditor::ResetAllNodesUnrelatedStates() +{ + TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); + + if (FocusedGraphEd.IsValid()) + { + FocusedGraphEd->ResetAllNodesUnrelatedStates(); + } +} + +void FBlueprintEditor::CollectExecDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UEdGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectExecDownstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } +} + +void FBlueprintEditor::CollectExecUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UEdGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectExecUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } +} + +void FBlueprintEditor::CollectPureDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory != K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UK2Node* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + if (LinkedNode->IsNodePure()) + { + CollectPureDownstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } + } +} + +void FBlueprintEditor::CollectPureUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes) +{ + const UEdGraphSchema_K2* K2Schema = GetDefault(); + + TArray AllPins = CurrentNode->GetAllPins(); + + for (auto& Pin : AllPins) + { + if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != K2Schema->PC_Exec) + { + for (auto& Link : Pin->LinkedTo) + { + UK2Node* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + if (LinkedNode->IsNodePure()) + { + CollectPureUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } + } +} + +void FBlueprintEditor::HideUnrelatedNodes() +{ + TArray NodesToShow; + + TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); + if (FocusedGraphEd.IsValid()) + { + FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); + + TArray ImpureNodes = SelectedNodes.Array().FilterByPredicate([](UObject* Node){ + UK2Node* K2Node = Cast(Node); + if (K2Node) + { + return !(K2Node->IsNodePure()); + } + return false; + }); + + TArray PureNodes = SelectedNodes.Array().FilterByPredicate([](UObject* Node){ + UK2Node* K2Node = Cast(Node); + if (K2Node) + { + return K2Node->IsNodePure(); + } + // Treat a node which can't cast to an UK2Node as a pure node (like a document node or a commment node) + // Make sure all selected nodes are handled + return true; + }); + + for (auto Node : ImpureNodes) + { + UEdGraphNode* SelectedNode = Cast(Node); + + if (SelectedNode) + { + NodesToShow.Add(SelectedNode); + CollectExecDownstreamNodes( SelectedNode, NodesToShow ); + CollectExecUpstreamNodes( SelectedNode, NodesToShow ); + CollectPureDownstreamNodes( SelectedNode, NodesToShow ); + CollectPureUpstreamNodes( SelectedNode, NodesToShow ); + } + } + + for (auto Node : PureNodes) + { + UEdGraphNode* SelectedNode = Cast(Node); + + if (SelectedNode) + { + NodesToShow.Add(SelectedNode); + CollectPureDownstreamNodes( SelectedNode, NodesToShow ); + CollectPureUpstreamNodes( SelectedNode, NodesToShow ); + } + } + + TArray AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes; + + TArray CommentNodes; + TArray RelatedNodes; + + for (auto& Node : AllNodes) + { + if (NodesToShow.Contains(Cast(Node))) + { + Node->SetNodeUnrelated(false); + RelatedNodes.Add(Node); + } + else + { + if (UEdGraphNode_Comment* CommentNode = Cast(Node)) + { + CommentNodes.Add(Node); + } + else + { + Node->SetNodeUnrelated(true); + } + } + } + + if (FocusedGraphEd.IsValid()) + { + FocusedGraphEd->FocusCommentNodes(CommentNodes, RelatedNodes); + } + } +} + +void FBlueprintEditor::ToggleHideUnrelatedNodes() +{ + bHideUnrelatedNodes = !bHideUnrelatedNodes; + + ResetAllNodesUnrelatedStates(); + + if (bHideUnrelatedNodes && bSelectRegularNode) + { + HideUnrelatedNodes(); + } + else + { + bLockNodeFadeState = false; + } +} + +bool FBlueprintEditor::IsToggleHideUnrelatedNodesChecked() const +{ + return bHideUnrelatedNodes == true; +} + +TSharedRef FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu() +{ + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, GetToolkitCommands() ); + + TSharedRef OptionsHeading = SNew(SBox) + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("FocusRelatedOptions", "Focus Related Options")) + .TextStyle(FEditorStyle::Get(), "Menu.Heading") + ] + ]; + + TSharedRef LockNodeStateCheckBox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged(this, &FBlueprintEditor::OnLockNodeStateCheckStateChanged) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes.")) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("LockNodeState", "Lock Node State")) + ] + ] + ]; + + MenuBuilder.AddWidget(OptionsHeading, FText::GetEmpty(), true); + + MenuBuilder.AddMenuEntry(FUIAction(), LockNodeStateCheckBox); + + return MenuBuilder.MakeWidget(); +} + +void FBlueprintEditor::LoadEditorSettings() +{ + EditorOptions = NewObject(); + + if (EditorOptions->bHideUnrelatedNodes) + { + ToggleHideUnrelatedNodes(); + } +} + +void FBlueprintEditor::SaveEditorSettings() +{ + if ( EditorOptions ) + { + EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; + EditorOptions->SaveConfig(); + } +} + +void FBlueprintEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState) +{ + bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false; +} + void FBlueprintEditor::ToggleSaveIntermediateBuildProducts() { bSaveIntermediateBuildProducts = !bSaveIntermediateBuildProducts; @@ -8163,7 +8546,7 @@ bool FBlueprintEditor::IsFocusedGraphEditable() const void FBlueprintEditor::TryInvokingDetailsTab(bool bFlash) { - if ( TabManager->CanSpawnTab(FBlueprintEditorTabs::DetailsID) ) + if ( TabManager->HasTabSpawner(FBlueprintEditorTabs::DetailsID) ) { TSharedPtr BlueprintTab = FGlobalTabmanager::Get()->GetMajorTabForTabManager(TabManager.ToSharedRef()); diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp index ca736aea7f0c..a51c557c2f9c 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.cpp @@ -74,6 +74,9 @@ void FBlueprintEditorCommands::RegisterCommands() // SCC commands UI_COMMAND( BeginBlueprintMerge, "Merge", "Shows the Blueprint merge panel and toolbar, allowing the user to resolve conflicted blueprints", EUserInterfaceActionType::Button, FInputChord() ); + + // Hide unrelated nodes + UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles automatically hiding nodes which are unrelated to the selected nodes.", EUserInterfaceActionType::ToggleButton, FInputChord() ); } PRAGMA_ENABLE_OPTIMIZATION diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h index 631d9a215eb2..7b7b90130ed1 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditorCommands.h @@ -75,6 +75,9 @@ public: // SSC commands TSharedPtr< FUICommandInfo > BeginBlueprintMerge; + + // Toggle focusing nodes which are related to the selected nodes + TSharedPtr< FUICommandInfo > ToggleHideUnrelatedNodes; }; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp index 8794b64c7e39..6a4b18ef5210 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp @@ -2588,7 +2588,7 @@ void FFindInBlueprintSearchManager::EnableGlobalFindResults(bool bEnable) for (int32 TabIdx = 0; TabIdx < ARRAY_COUNT(GlobalFindResultsTabIDs); TabIdx++) { const FName TabID = GlobalFindResultsTabIDs[TabIdx]; - if (!GlobalTabManager->CanSpawnTab(TabID)) + if (!GlobalTabManager->HasTabSpawner(TabID)) { const FText DisplayName = FText::Format(LOCTEXT("GlobalFindResultsDisplayName", "Find in Blueprints {0}"), FText::AsNumber(TabIdx + 1)); @@ -2623,7 +2623,7 @@ void FFindInBlueprintSearchManager::EnableGlobalFindResults(bool bEnable) for (int32 TabIdx = 0; TabIdx < ARRAY_COUNT(GlobalFindResultsTabIDs); TabIdx++) { const FName TabID = GlobalFindResultsTabIDs[TabIdx]; - if (GlobalTabManager->CanSpawnTab(TabID)) + if (GlobalTabManager->HasTabSpawner(TabID)) { GlobalTabManager->UnregisterNomadTabSpawner(TabID); } @@ -2644,7 +2644,7 @@ void FFindInBlueprintSearchManager::CloseOrphanedGlobalFindResultsTabs(TSharedPt for (int32 TabIdx = 0; TabIdx < ARRAY_COUNT(GlobalFindResultsTabIDs); TabIdx++) { const FName TabID = GlobalFindResultsTabIDs[TabIdx]; - if (!FGlobalTabmanager::Get()->CanSpawnTab(TabID)) + if (!FGlobalTabmanager::Get()->HasTabSpawner(TabID)) { TSharedPtr OrphanedTab = TabManager->FindExistingLiveTab(FTabId(TabID)); if (OrphanedTab.IsValid()) diff --git a/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp b/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp index 11a4f4803739..edb8b716af8c 100644 --- a/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp +++ b/Engine/Source/Editor/Kismet/Private/ImaginaryBlueprintData.cpp @@ -35,7 +35,10 @@ FText FSearchableValueInfo::GetDisplayText(const TMap& InLookupTab AsyncTask(ENamedThreads::GameThread, [TableId, &Promise]() { FName ResolvedTableId = TableId; - IStringTableEngineBridge::FullyLoadStringTableAsset(ResolvedTableId); // Trigger the asset load + if (IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) + { + IStringTableEngineBridge::FullyLoadStringTableAsset(ResolvedTableId); // Trigger the asset load + } Promise.SetValue(true); // Signal completion }); diff --git a/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp b/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp index 7daed67b02ec..306edf1d82fb 100644 --- a/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp +++ b/Engine/Source/Editor/Kismet/Private/SBlueprintEditorToolbar.cpp @@ -88,7 +88,7 @@ void FKismet2Menu::FillFileMenuBlueprintSection( FMenuBuilder& MenuBuilder, FBlu LOCTEXT("DeveloperMenu", "Developer"), LOCTEXT("DeveloperMenu_ToolTip", "Open the developer menu"), FNewMenuDelegate::CreateStatic( &FKismet2Menu::FillDeveloperMenu ), - true); + false); } MenuBuilder.EndSection(); } diff --git a/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h b/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h index 6d0429e7e92e..0ce460f2637b 100644 --- a/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h +++ b/Engine/Source/Editor/Kismet/Public/BlueprintEditor.h @@ -43,6 +43,7 @@ class UEdGraph; class UEdGraphNode; class UUserDefinedEnum; class UUserDefinedStruct; +class UBlueprintEditorOptions; struct Rect; /* Enums to use when grouping the blueprint members in the list panel. The order here will determine the order in the list */ @@ -1083,6 +1084,19 @@ private: /** Returns the appropriate check box state representing whether or not the selected nodes are enabled */ ECheckBoxState GetEnabledCheckBoxStateForSelectedNodes(); + /** Configuration class used to store editor settings across sessions. */ + UBlueprintEditorOptions* EditorOptions; + + /** + * Load editor settings from disk (docking state, window pos/size, option state, etc). + */ + virtual void LoadEditorSettings(); + + /** + * Saves editor settings to disk (docking state, window pos/size, option state, etc). + */ + virtual void SaveEditorSettings(); + /** Attempt to match the given enabled state for currently-selected nodes */ ECheckBoxState CheckEnabledStateForSelectedNodes(ENodeEnabledState CheckState); @@ -1091,6 +1105,9 @@ private: public://@TODO TSharedPtr DocumentManager; + + /** Update all nodes' unrelated states when the graph has changed */ + void UpdateNodesUnrelatedStatesAfterGraphChange(); protected: @@ -1192,6 +1209,35 @@ protected: /** The preview actor representing the current preview */ mutable TWeakObjectPtr PreviewActorPtr; + /** If true, fade out nodes which are unrelated to the selected nodes automatically. */ + bool bHideUnrelatedNodes; + + /** Lock the current fade state of each node */ + bool bLockNodeFadeState; + + /** If a regular node (not a comment node) has been selected */ + bool bSelectRegularNode; + + /** Focus nodes which are related to the selected nodes */ + void ResetAllNodesUnrelatedStates(); + void CollectExecUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectExecDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectPureDownstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectPureUpstreamNodes(UEdGraphNode* CurrentNode, TArray& CollectedNodes); + void HideUnrelatedNodes(); + +public: + /** Make nodes which are unrelated to the selected nodes fade out */ + void ToggleHideUnrelatedNodes(); + bool IsToggleHideUnrelatedNodesChecked() const; + + /** Make a drop down menu to control the opacity of unrelated nodes */ + TSharedRef MakeHideUnrelatedNodesOptionsMenu(); + TOptional HandleUnrelatedNodesOpacityBoxValue() const; + void HandleUnrelatedNodesOpacityBoxChanged(float NewOpacity); + void OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState); + + public: //@TODO: To be moved/merged TSharedPtr OpenDocument(const UObject* DocumentID, FDocumentTracker::EOpenDocumentCause Cause); diff --git a/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp b/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp index e8555f7fcde3..aa5832e5f016 100644 --- a/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp +++ b/Engine/Source/Editor/KismetWidgets/Private/SPinTypeSelector.cpp @@ -444,7 +444,6 @@ void SPinTypeSelector::Construct(const FArguments& InArgs, FGetPinTypeTree GetPi .ColorAndOpacity( this, &SPinTypeSelector::GetSecondaryTypeIconColor ) ] +SHorizontalBox::Slot() - .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp index 3840b7984628..767e6d6ce94f 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.cpp @@ -99,7 +99,7 @@ IMPLEMENT_HIT_PROXY(HNewLandscapeGrabHandleProxy, HHitProxy) ENGINE_API extern bool GDisableAutomaticTextureMaterialUpdateDependencies; -void ALandscape::SplitHeightmap(ULandscapeComponent* Comp, ALandscapeProxy* TargetProxy, FMaterialUpdateContext* InOutUpdateContext, TArray* InOutRecreateRenderStateContext, bool InReregisterComponent) +void ALandscape::SplitHeightmap(ULandscapeComponent* Comp, ALandscapeProxy* TargetProxy,FMaterialUpdateContext* InOutUpdateContext, TArray* InOutRecreateRenderStateContext, bool InReregisterComponent) { ULandscapeInfo* Info = Comp->GetLandscapeInfo(); @@ -3493,7 +3493,7 @@ void FEdModeLandscape::ForceRealTimeViewports(const bool bEnable, const bool bSt void FEdModeLandscape::ReimportData(const FLandscapeTargetListInfo& TargetInfo) { - const FString& SourceFilePath = TargetInfo.ReimportFilePath(); + const FString& SourceFilePath = TargetInfo.GetReimportFilePath(); if (SourceFilePath.Len()) { FScopedSetLandscapeEditingLayer Scope(GetLandscape(), GetCurrentLayerGuid(), [&] { RequestLayersContentUpdateForceAll(); }); @@ -4114,7 +4114,6 @@ ALandscape* FEdModeLandscape::ChangeComponentSetting(int32 NumComponentsX, int32 NewLandscape->PositiveZBoundsExtension = OldLandscape->PositiveZBoundsExtension; NewLandscape->DefaultPhysMaterial = OldLandscape->DefaultPhysMaterial; NewLandscape->StreamingDistanceMultiplier = OldLandscape->StreamingDistanceMultiplier; - NewLandscape->LandscapeHoleMaterial = OldLandscape->LandscapeHoleMaterial; NewLandscape->StaticLightingResolution = OldLandscape->StaticLightingResolution; NewLandscape->bCastStaticShadow = OldLandscape->bCastStaticShadow; NewLandscape->bCastShadowAsTwoSided = OldLandscape->bCastShadowAsTwoSided; diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h index 39ba6f7c60cc..d6956b6bec8d 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdMode.h @@ -152,7 +152,7 @@ struct FLandscapeTargetListInfo FName GetLayerName() const; - FString& ReimportFilePath() const + FString GetReimportFilePath() const { if (TargetType == ELandscapeToolTargetType::Weightmap) { @@ -162,7 +162,39 @@ struct FLandscapeTargetListInfo } else //if (TargetType == ELandscapeToolTargetType::Heightmap) { - return LandscapeInfo->GetLandscapeProxy()->ReimportHeightmapFilePath; + if (LandscapeInfo.IsValid()) + { + ALandscapeProxy* LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); + + if (LandscapeProxy) + { + return LandscapeProxy->ReimportHeightmapFilePath; + } + } + + return FString(TEXT("")); + } + } + + void SetReimportFilePath(const FString& InNewPath) + { + if (TargetType == ELandscapeToolTargetType::Weightmap) + { + FLandscapeEditorLayerSettings* EditorLayerSettings = GetEditorLayerSettings(); + check(EditorLayerSettings); + EditorLayerSettings->ReimportLayerFilePath = InNewPath; + } + else //if (TargetType == ELandscapeToolTargetType::Heightmap) + { + if (LandscapeInfo.IsValid()) + { + ALandscapeProxy* LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); + + if (LandscapeProxy) + { + LandscapeProxy->ReimportHeightmapFilePath = InNewPath; + } + } } } }; diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp index 09893563cd0b..b7797b059198 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEdModeComponentTools.cpp @@ -313,11 +313,8 @@ public: virtual bool BeginTool(FEditorViewportClient* ViewportClient, const FLandscapeToolTarget& InTarget, const FVector& InHitLocation) override { ALandscapeProxy* Proxy = InTarget.LandscapeInfo->GetLandscapeProxy(); - UMaterialInterface* HoleMaterial = Proxy->GetLandscapeHoleMaterial(); - if (!HoleMaterial) - { - HoleMaterial = Proxy->GetLandscapeMaterial(); - } + UMaterialInterface* HoleMaterial = Proxy->GetLandscapeMaterial(); + if (!HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType()) { FMessageDialog::Open(EAppMsgType::Ok, diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp index 0e65cbbc3ab8..d4615a0fac39 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_NewLandscape.cpp @@ -226,6 +226,10 @@ void FLandscapeEditorDetailCustomization_NewLandscape::CustomizeDetails(IDetailL .OnXCommitted_Static(&SetPropertyValue, PropertyHandle_Location_X) .OnYCommitted_Static(&SetPropertyValue, PropertyHandle_Location_Y) .OnZCommitted_Static(&SetPropertyValue, PropertyHandle_Location_Z) + .OnXChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Location_X->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnYChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Location_Y->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnZChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Location_Z->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .AllowSpin(true) ]; TSharedRef PropertyHandle_Rotation = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, NewLandscape_Rotation)); @@ -251,6 +255,7 @@ void FLandscapeEditorDetailCustomization_NewLandscape::CustomizeDetails(IDetailL .Yaw_Static(&GetOptionalPropertyValue, PropertyHandle_Rotation_Yaw) .OnYawCommitted_Static(&SetPropertyValue, PropertyHandle_Rotation_Yaw) // not allowed to roll or pitch landscape .OnYawChanged_Lambda([=](float NewValue){ ensure(PropertyHandle_Rotation_Yaw->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .AllowSpin(true) ]; TSharedRef PropertyHandle_Scale = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, NewLandscape_Scale)); @@ -276,6 +281,10 @@ void FLandscapeEditorDetailCustomization_NewLandscape::CustomizeDetails(IDetailL .OnXCommitted_Static(&SetScale, PropertyHandle_Scale_X) .OnYCommitted_Static(&SetScale, PropertyHandle_Scale_Y) .OnZCommitted_Static(&SetScale, PropertyHandle_Scale_Z) + .OnXChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Scale_X->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnYChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Scale_Y->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .OnZChanged_Lambda([=](float NewValue) { ensure(PropertyHandle_Scale_Z->SetValue(NewValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Success); }) + .AllowSpin(true) ]; TSharedRef PropertyHandle_QuadsPerSection = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULandscapeEditorObject, NewLandscape_QuadsPerSection)); diff --git a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp index e0bda2ba6932..3138c207a802 100644 --- a/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp +++ b/Engine/Source/Editor/LandscapeEditor/Private/LandscapeEditorDetailCustomization_TargetLayers.cpp @@ -138,12 +138,9 @@ bool FLandscapeEditorDetailCustomization_TargetLayers::ShouldShowVisibilityTip() if (LandscapeEdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Visibility) { ALandscapeProxy* Proxy = LandscapeEdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy(); - UMaterialInterface* HoleMaterial = Proxy->GetLandscapeHoleMaterial(); - if (!HoleMaterial) - { - HoleMaterial = Proxy->GetLandscapeMaterial(); - } - if (!HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType()) + UMaterialInterface* HoleMaterial = Proxy->GetLandscapeMaterial(); + + if (HoleMaterial && !HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType()) { return true; } @@ -916,7 +913,7 @@ TSharedPtr FLandscapeEditorCustomNodeBuilder_TargetLayers::OnTargetLaye MenuBuilder.AddMenuEntry(LOCTEXT("LayerContextMenu.Import", "Import from file"), FText(), FSlateIcon(), ImportAction); // Reimport - const FString& ReimportPath = Target->ReimportFilePath(); + const FString& ReimportPath = Target->GetReimportFilePath(); if (!ReimportPath.IsEmpty()) { @@ -1014,7 +1011,7 @@ void FLandscapeEditorCustomNodeBuilder_TargetLayers::OnExportLayer(const TShared LandscapeInfo->ExportLayer(LayerInfoObj, SaveFilename); } - Target->ReimportFilePath() = SaveFilename; + Target->SetReimportFilePath(SaveFilename); } } } @@ -1069,7 +1066,7 @@ void FLandscapeEditorCustomNodeBuilder_TargetLayers::OnImportLayer(const TShared // Actually do the Import LandscapeEdMode->ImportData(*Target, OpenFilename); - Target->ReimportFilePath() = OpenFilename; + Target->SetReimportFilePath(OpenFilename); } } } diff --git a/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp b/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp index fc9f6689268c..ab1f64265575 100644 --- a/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/DlgDeltaTransform.cpp @@ -72,6 +72,7 @@ public: .Z( this, &SDlgDeltaTransform::GetDeltaZ ) .bColorAxisLabels( true ) .AllowResponsiveLayout( true ) + .AllowSpin( false ) .OnXCommitted( this, &SDlgDeltaTransform::OnSetDelta, 0 ) .OnYCommitted( this, &SDlgDeltaTransform::OnSetDelta, 1 ) .OnZCommitted( this, &SDlgDeltaTransform::OnSetDelta, 2 ) diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp index 0b31a06d5cd0..f029d95fc3ea 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorGenericDetails.cpp @@ -22,8 +22,7 @@ #include "SurfaceIterators.h" #include "ScopedTransaction.h" #include "FunctionalTest.h" - -#include "PropertyCustomizationHelpers.h" +#include "MaterialList.h" #define LOCTEXT_NAMESPACE "FLevelEditorGenericDetails" diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp index 7350be6ec70c..be530ea25e82 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp @@ -2206,11 +2206,13 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } }; + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + TSharedPtr Extender = FExtender::Combine(LevelEditorModule.GetAllLevelEditorToolbarBlueprintsMenuExtenders()); const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, InCommandList ); + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, InCommandList, Extender); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("BlueprintClass", "Blueprint Class")); + MenuBuilder.BeginSection("BlueprintClass", LOCTEXT("BlueprintClass", "Blueprint Class")); { // Create a blank BP MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().CreateBlankBlueprintClass); @@ -2230,7 +2232,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } MenuBuilder.EndSection(); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("LevelScriptBlueprints", "Level Blueprints")); + MenuBuilder.BeginSection("LevelScriptBlueprints", LOCTEXT("LevelScriptBlueprints", "Level Blueprints")); { MenuBuilder.AddMenuEntry( FLevelEditorCommands::Get().OpenLevelBlueprint ); @@ -2247,7 +2249,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } MenuBuilder.EndSection(); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("ProjectSettingsClasses", "Project Settings")); + MenuBuilder.BeginSection("ProjectSettingsClasses", LOCTEXT("ProjectSettingsClasses", "Project Settings")); { // If source control is enabled, queue up a query to the status of the config file so it is (hopefully) ready before we get to the sub-menu if(ISourceControlModule::Get().IsEnabled()) @@ -2261,7 +2263,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateOpenBlueprintMenuContent( TSh } MenuBuilder.EndSection(); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldSettingsClasses", "World Override")); + MenuBuilder.BeginSection("WorldSettingsClasses", LOCTEXT("WorldSettingsClasses", "World Override")); { LevelEditorActionHelpers::CreateGameModeSubMenu(MenuBuilder, InCommandList, InLevelEditor, false); } diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp index 46e227ceac0c..48451478a8d7 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.cpp @@ -632,6 +632,10 @@ TSharedRef SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Arg ]; } + else if (TabIdentifier == FEditorModeTools::EditorModeToolbarTabName) + { + return GLevelEditorModeTools().MakeModeToolbarTab(); + } else if( TabIdentifier == TEXT("LevelEditorSelectionDetails") || TabIdentifier == TEXT("LevelEditorSelectionDetails2") || TabIdentifier == TEXT("LevelEditorSelectionDetails3") || TabIdentifier == TEXT("LevelEditorSelectionDetails4") ) { TSharedRef DetailsPanel = SummonDetailsPanel( TabIdentifier ); @@ -671,6 +675,7 @@ TSharedRef SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Arg else if( TabIdentifier == TEXT("LevelEditorSceneOutliner") ) { SceneOutliner::FInitializationOptions InitOptions; + InitOptions.bShowTransient = true; InitOptions.Mode = ESceneOutlinerMode::ActorBrowsing; { TWeakPtr WeakLevelEditor = SharedThis(this); @@ -826,6 +831,11 @@ TSharedRef SLevelEditor::SpawnLevelEditorTab( const FSpawnTabArgs& Arg return SNew(SDockTab); } +bool SLevelEditor::CanSpawnEditorModeToolbarTab(const FSpawnTabArgs& Args) const +{ + return GLevelEditorModeTools().ShouldShowModeToolbar(); +} + TSharedRef SLevelEditor::InvokeTab( FName TabID ) { TSharedPtr LevelEditorTabManager = GetTabManager(); @@ -1034,7 +1044,7 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef { const FSlateIcon ToolbarIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Toolbar"); - LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBar", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBar"), FString()) ) + LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBar", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBar"), FString())) .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBar", "Toolbar")) .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBarTooltipText", "Open the Toolbar tab, which provides access to the most common / important actions.")) .SetGroup( MenuStructure.GetLevelEditorCategory() ) @@ -1070,12 +1080,22 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef .SetIcon( DetailsIcon ); } - const FSlateIcon ToolsIcon( FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Modes" ); - LevelEditorTabManager->RegisterTabSpawner( "LevelEditorToolBox", FOnSpawnTab::CreateSP( this, &SLevelEditor::SpawnLevelEditorTab, FName( "LevelEditorToolBox" ), FString() ) ) - .SetDisplayName( NSLOCTEXT( "LevelEditorTabs", "LevelEditorToolBox", "Modes" ) ) - .SetTooltipText( NSLOCTEXT( "LevelEditorTabs", "LevelEditorToolBoxTooltipText", "Open the Modes tab, which specifies all the available editing modes." ) ) - .SetGroup( MenuStructure.GetLevelEditorCategory() ) - .SetIcon( ToolsIcon ); + { + + const FSlateIcon ToolsIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Modes"); + LevelEditorTabManager->RegisterTabSpawner("LevelEditorToolBox", FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FName("LevelEditorToolBox"), FString())) + .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBox", "Modes Panel")) + .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorToolBoxTooltipText", "Open the Modes tab, which specifies all the available editing modes.")) + .SetGroup(MenuStructure.GetLevelEditorModesCategory()) + .SetIcon(ToolsIcon); + + FCanSpawnTab CanSpawnTabDelegate = FCanSpawnTab::CreateSP(this, &SLevelEditor::CanSpawnEditorModeToolbarTab); + LevelEditorTabManager->RegisterTabSpawner(FEditorModeTools::EditorModeToolbarTabName, FOnSpawnTab::CreateSP(this, &SLevelEditor::SpawnLevelEditorTab, FEditorModeTools::EditorModeToolbarTabName, FString()), CanSpawnTabDelegate) + .SetDisplayName(NSLOCTEXT("LevelEditorTabs", "LevelEditorModesTab", "Active Mode Tools")) + .SetTooltipText(NSLOCTEXT("LevelEditorTabs", "LevelEditorModesTabTooltipText", "Open the modes toolbar which has the active mode's tools")) + .SetGroup(MenuStructure.GetLevelEditorModesCategory()) + .SetIcon(ToolsIcon); + } { const FSlateIcon OutlinerIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Outliner"); @@ -1203,6 +1223,12 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef ->AddTab("LevelEditorToolBar", ETabState::OpenedTab) ) ->Split + ( + FTabManager::NewStack() + ->SetHideTabWell(true) + ->AddTab(FEditorModeTools::EditorModeToolbarTabName, ETabState::ClosedTab) + ) + ->Split ( FTabManager::NewStack() ->SetHideTabWell(true) @@ -1244,6 +1270,10 @@ TSharedRef SLevelEditor::RestoreContentArea( const TSharedRef )); FLayoutExtender LayoutExtender; + + // Make a layout extension for the mode toolbar. This avoids bumping level editor layout version to add a mandatory tab + LayoutExtender.ExtendLayout(FName("LevelEditorToolBar"), ELayoutExtensionPosition::Below, FTabManager::FTab(FEditorModeTools::EditorModeToolbarTabName, ETabState::ClosedTab)); + LevelEditorModule.OnRegisterLayoutExtensions().Broadcast(LayoutExtender); Layout->ProcessExtensions(LayoutExtender); diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h index 2eb78385606b..40e5743f7fad 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelEditor.h @@ -138,6 +138,8 @@ public: private: TSharedRef SpawnLevelEditorTab(const FSpawnTabArgs& Args, FName TabIdentifier, FString InitializationPayload); + bool CanSpawnEditorModeToolbarTab(const FSpawnTabArgs& Args) const; + //TSharedRef SpawnLevelEditorModeTab(const FSpawnTabArgs& Args, FEdMode* EditorMode); TSharedRef SummonDetailsPanel( FName Identifier ); diff --git a/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp b/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp index 621239d43010..01e2008d465e 100644 --- a/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/SLevelViewport.cpp @@ -295,12 +295,12 @@ void SLevelViewport::ConstructViewportOverlayContent() .AutoHeight() .Padding(2.0f, 1.0f, 2.0f, 1.0f) [ - SNew(SHorizontalBox) + SNew(SVerticalBox) .Visibility(this, &SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility) // Current level label - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 1.0f, 2.0f, 1.0f) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(6.0f, 1.0f, 2.0f, 1.0f) [ SNew(STextBlock) .Text(this, &SLevelViewport::GetSelectedActorsCurrentLevelText, true) @@ -308,9 +308,9 @@ void SLevelViewport::ConstructViewportOverlayContent() .ShadowOffset(FVector2D(1, 1)) ] // Current level - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(4.0f, 1.0f, 2.0f, 1.0f) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(6.0f, 1.0f, 2.0f, 1.0f) [ SNew(STextBlock) .Text(this, &SLevelViewport::GetSelectedActorsCurrentLevelText, false) @@ -333,7 +333,7 @@ void SLevelViewport::ConstructViewportOverlayContent() .VAlign(VAlign_Center) .ButtonStyle(FEditorStyle::Get(), "EditorViewportToolBar.MenuButton") .OnClicked(this, &SLevelViewport::OnMenuClicked) - .Visibility(this, &SLevelViewport::GetCurrentLevelTextVisibility) + .Visibility(this, &SLevelViewport::GetCurrentLevelButtonVisibility) [ SNew(SHorizontalBox) .Visibility(this, &SLevelViewport::GetCurrentLevelTextVisibility) @@ -3566,7 +3566,20 @@ EVisibility SLevelViewport::GetCurrentLevelTextVisibility() const { ContentVisibility = EVisibility::SelfHitTestInvisible; } - return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && !IsPlayInEditorViewportActive() ? ContentVisibility : EVisibility::Collapsed; + return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) + && !IsPlayInEditorViewportActive() + && GetWorld() && GetWorld()->GetCurrentLevel()->OwningWorld->GetLevels().Num() > 1 + ? ContentVisibility : EVisibility::Collapsed; +} + +EVisibility SLevelViewport::GetCurrentLevelButtonVisibility() const +{ + EVisibility TextVisibility = GetCurrentLevelTextVisibility(); + if (TextVisibility == EVisibility::SelfHitTestInvisible) + { + TextVisibility = EVisibility::Visible; + } + return TextVisibility; } EVisibility SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility() const @@ -3576,7 +3589,11 @@ EVisibility SLevelViewport::GetSelectedActorsCurrentLevelTextVisibility() const { ContentVisibility = EVisibility::SelfHitTestInvisible; } - return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) && (GEditor->GetSelectedActorCount() > 0) && !IsPlayInEditorViewportActive() ? ContentVisibility : EVisibility::Collapsed; + return (&GetLevelViewportClient() == GCurrentLevelEditingViewportClient) + && (GEditor->GetSelectedActorCount() > 0) + && !IsPlayInEditorViewportActive() + && GetWorld() && GetWorld()->GetCurrentLevel()->OwningWorld->GetLevels().Num() > 1 + ? ContentVisibility : EVisibility::Collapsed; } FText SLevelViewport::GetSelectedActorsCurrentLevelText(bool bDrawOnlyLabel) const diff --git a/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h b/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h index ff3d5b48eeb6..9693d03d9d8c 100644 --- a/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h +++ b/Engine/Source/Editor/LevelEditor/Public/LevelEditor.h @@ -231,6 +231,7 @@ public: virtual TArray& GetAllLevelEditorToolbarSourceControlMenuExtenders() { return LevelEditorToolbarSourceControlMenuExtenders; } virtual TArray& GetAllLevelEditorToolbarCreateMenuExtenders() { return LevelEditorToolbarCreateMenuExtenders; } virtual TArray& GetAllLevelEditorToolbarPlayMenuExtenders() { return LevelEditorToolbarPlayMenuExtenders; } + virtual TArray>& GetAllLevelEditorToolbarBlueprintsMenuExtenders() { return LevelEditorToolbarBlueprintsMenuExtenders; } virtual TArray>& GetAllLevelEditorToolbarCinematicsMenuExtenders() {return LevelEditorToolbarCinematicsMenuExtenders;} virtual TArray& GetAllLevelEditorLevelMenuExtenders() { return LevelEditorLevelMenuExtenders; } @@ -384,6 +385,7 @@ private: TArray LevelEditorToolbarCreateMenuExtenders; TArray LevelEditorToolbarPlayMenuExtenders; TArray> LevelEditorToolbarCinematicsMenuExtenders; + TArray> LevelEditorToolbarBlueprintsMenuExtenders; TArray LevelEditorLevelMenuExtenders; /* Pointer to the current level Editor instance */ diff --git a/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h b/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h index 1d269fd2d96c..deb9bd6c555a 100644 --- a/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h +++ b/Engine/Source/Editor/LevelEditor/Public/SLevelViewport.h @@ -295,6 +295,9 @@ public: /** @return The visibility of the current level text display */ virtual EVisibility GetCurrentLevelTextVisibility() const; + /** @return The visibility of the current level button display */ + virtual EVisibility GetCurrentLevelButtonVisibility() const; + /** @return The visibility of the current level text display */ virtual EVisibility GetSelectedActorsCurrentLevelTextVisibility() const; diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp index f0fb9c365295..97de51a76349 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.cpp @@ -11,6 +11,8 @@ #include "Widgets/Layout/SSeparator.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SButton.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SCheckBox.h" #include "EditorStyleSet.h" #include "EdGraph/EdGraph.h" #include "MaterialGraph/MaterialGraph.h" @@ -121,6 +123,8 @@ #include "Materials/MaterialExpressionMaterialLayerOutput.h" #include "MaterialStats.h" +#include "Materials/MaterialExpression.h" + #include "SMaterialParametersOverviewWidget.h" #include "IPropertyRowGenerator.h" @@ -724,23 +728,27 @@ void FMaterialEditor::InitMaterialEditor( const EToolkitMode::Type Mode, const T FMaterialEditor::FMaterialEditor() : bMaterialDirty(false) , bStatsFromPreviewMaterial(false) - , Material(NULL) - , OriginalMaterial(NULL) - , ExpressionPreviewMaterial(NULL) - , EmptyMaterial(NULL) - , PreviewExpression(NULL) - , MaterialFunction(NULL) - , OriginalMaterialObject(NULL) - , EditorOptions(NULL) - , ScopedTransaction(NULL) + , Material(nullptr) + , OriginalMaterial(nullptr) + , ExpressionPreviewMaterial(nullptr) + , EmptyMaterial(nullptr) + , PreviewExpression(nullptr) + , MaterialFunction(nullptr) + , OriginalMaterialObject(nullptr) + , EditorOptions(nullptr) + , ScopedTransaction(nullptr) , bAlwaysRefreshAllPreviews(false) , bHideUnusedConnectors(false) , bLivePreview(true) , bIsRealtime(false) , bShowBuiltinStats(false) + , bHideUnrelatedNodes(false) + , bLockNodeFadeState(false) + , bFocusWholeChain(false) + , bSelectRegularNode(false) , MenuExtensibilityManager(new FExtensibilityManager) , ToolBarExtensibilityManager(new FExtensibilityManager) - , MaterialEditorInstance(NULL) + , MaterialEditorInstance(nullptr) { } @@ -1127,6 +1135,21 @@ void FMaterialEditor::FillToolbar(FToolBarBuilder& ToolbarBuilder) ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleLivePreview); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleRealtimeExpressions); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews); + ToolbarBuilder.AddToolBarButton( + FMaterialEditorCommands::Get().ToggleHideUnrelatedNodes, + NAME_None, + TAttribute(), + TAttribute(), + FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") + ); + ToolbarBuilder.AddComboButton( + FUIAction(), + FOnGetContent::CreateSP(this, &FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu), + LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"), + LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"), + TAttribute(), + true + ); } ToolbarBuilder.EndSection(); @@ -1511,6 +1534,11 @@ void FMaterialEditor::LoadEditorSettings() PreviewViewport->OnToggleRealtime(); } } + + if (EditorOptions->bHideUnrelatedNodes) + { + ToggleHideUnrelatedNodes(); + } // Primitive type int32 PrimType; @@ -1532,7 +1560,8 @@ void FMaterialEditor::SaveEditorSettings() EditorOptions->bHideUnusedConnectors = !IsOnShowConnectorsChecked(); EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews(); EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked(); - EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); + EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); + EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; EditorOptions->SaveConfig(); } @@ -2237,6 +2266,12 @@ void FMaterialEditor::BindCommands() FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked)); + ToolkitCommands->MapAction( + Commands.ToggleHideUnrelatedNodes, + FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleHideUnrelatedNodes), + FCanExecuteAction(), + FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleHideUnrelatedNodesChecked)); + ToolkitCommands->MapAction( Commands.ToggleRealtimeExpressions, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleRealTimeExpressions), @@ -2426,6 +2461,203 @@ bool FMaterialEditor::IsOnAlwaysRefreshAllPreviews() const return bAlwaysRefreshAllPreviews == true; } +void FMaterialEditor::ToggleHideUnrelatedNodes() +{ + bHideUnrelatedNodes = !bHideUnrelatedNodes; + + GraphEditor->ResetAllNodesUnrelatedStates(); + + if (bHideUnrelatedNodes && bSelectRegularNode) + { + HideUnrelatedNodes(); + } + else + { + bLockNodeFadeState = false; + bFocusWholeChain = false; + } +} + +bool FMaterialEditor::IsToggleHideUnrelatedNodesChecked() const +{ + return bHideUnrelatedNodes == true; +} + +void FMaterialEditor::CollectDownstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes) +{ + TArray OutputPins; + CurrentNode->GetOutputPins(OutputPins); + + for (auto& OutputPin : OutputPins) + { + for (auto& Link : OutputPin->LinkedTo) + { + UMaterialGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectDownstreamNodes( LinkedNode, CollectedNodes ); + + if (bFocusWholeChain) + { + CollectUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } + } +} + +void FMaterialEditor::CollectUpstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes) +{ + TArray InputPins; + CurrentNode->GetInputPins(InputPins); + + for (auto& InputPin : InputPins) + { + for (auto& Link : InputPin->LinkedTo) + { + UMaterialGraphNode* LinkedNode = Cast(Link->GetOwningNode()); + if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) + { + CollectedNodes.Add(LinkedNode); + CollectUpstreamNodes( LinkedNode, CollectedNodes ); + } + } + } +} + +void FMaterialEditor::HideUnrelatedNodes() +{ + TArray NodesToShow; + + const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); + + for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) + { + UMaterialGraphNode* SelectedNode = Cast(*NodeIt); + + if (SelectedNode) + { + NodesToShow.Add(SelectedNode); + CollectDownstreamNodes( SelectedNode, NodesToShow ); + CollectUpstreamNodes( SelectedNode, NodesToShow ); + } + } + + TArray AllNodes = GraphEditor->GetCurrentGraph()->Nodes; + + TArray CommentNodes; + TArray RelatedNodes; + + for (auto& Node : AllNodes) + { + // Always draw the root graph node which can't cast to UMaterialGraphNode + if (UMaterialGraphNode* GraphNode = Cast(Node)) + { + if (NodesToShow.Contains(GraphNode)) + { + Node->SetNodeUnrelated(false); + RelatedNodes.Add(Node); + } + else + { + Node->SetNodeUnrelated(true); + } + } + else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) + { + CommentNodes.Add(Node); + } + } + + GraphEditor->FocusCommentNodes(CommentNodes, RelatedNodes); +} + +TSharedRef FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu() +{ + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, GetToolkitCommands() ); + + TSharedRef OptionsHeading = SNew(SBox) + .Padding(2.0f) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("HideUnrelatedOptions", "Hide Unrelated Options")) + .TextStyle(FEditorStyle::Get(), "Menu.Heading") + ] + ]; + + TSharedRef LockNodeStateCheckBox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged(this, &FMaterialEditor::OnLockNodeStateCheckStateChanged) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes.")) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("LockNodeState", "Lock Node State")) + ] + ] + ]; + + TSharedRef FocusWholeChainCheckBox = SNew(SBox) + [ + SNew(SCheckBox) + .IsChecked(bFocusWholeChain ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged(this, &FMaterialEditor::OnFocusWholeChainCheckStateChanged) + .Style(FEditorStyle::Get(), "Menu.CheckBox") + .ToolTipText(LOCTEXT("FocusWholeChainCheckBoxToolTip", "Focus all nodes in the chain.")) + .Content() + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .Padding(2.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("FocusWholeChain", "Focus Whole Chain")) + ] + ] + ]; + + MenuBuilder.AddWidget(OptionsHeading, FText::GetEmpty(), true); + + MenuBuilder.AddMenuEntry(FUIAction(), LockNodeStateCheckBox); + + MenuBuilder.AddMenuEntry(FUIAction(), FocusWholeChainCheckBox); + + return MenuBuilder.MakeWidget(); +} + +void FMaterialEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState) +{ + bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false; +} + +void FMaterialEditor::OnFocusWholeChainCheckStateChanged(ECheckBoxState NewCheckedState) +{ + bFocusWholeChain = (NewCheckedState == ECheckBoxState::Checked) ? true : false; + + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) + { + GraphEditor->ResetAllNodesUnrelatedStates(); + + HideUnrelatedNodes(); + } +} + + void FMaterialEditor::OnUseCurrentTexture() { // Set the currently selected texture in the generic browser @@ -3944,6 +4176,13 @@ void FMaterialEditor::UpdateMaterialAfterGraphChange() RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); + + if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) + { + GraphEditor->ResetAllNodesUnrelatedStates(); + + HideUnrelatedNodes(); + } } int32 FMaterialEditor::GetNumberOfSelectedNodes() const @@ -3951,7 +4190,7 @@ int32 FMaterialEditor::GetNumberOfSelectedNodes() const return GraphEditor->GetSelectedNodes().Num(); } -FMaterialRenderProxy* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) +FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) { bool bNewlyCreated; return GetExpressionPreview(InExpression, bNewlyCreated); @@ -4688,6 +4927,7 @@ void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSele EditObject = MaterialFunction; } + bSelectRegularNode = false; if( NewSelection.Num() == 0 ) { SelectedObjects.Add(EditObject); @@ -4698,6 +4938,7 @@ void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSele { if (UMaterialGraphNode* GraphNode = Cast(*SetIt)) { + bSelectRegularNode = true; SelectedObjects.Add(GraphNode->MaterialExpression); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(*SetIt)) @@ -4713,6 +4954,16 @@ void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSele GetDetailView()->SetObjects( SelectedObjects, true ); FocusDetailsPanel(); + + if (bHideUnrelatedNodes && !bLockNodeFadeState) + { + GraphEditor->ResetAllNodesUnrelatedStates(); + + if (bSelectRegularNode) + { + HideUnrelatedNodes(); + } + } } void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node) diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h index 54d7db7701c5..264fe5d52cff 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditor.h @@ -34,6 +34,7 @@ class UFactory; class UMaterialEditorOptions; class UMaterialExpressionComment; class UMaterialInstance; +class UMaterialGraphNode; struct FGraphAppearanceInfo; /** @@ -44,6 +45,7 @@ class FMatExpressionPreview : public FMaterial, public FMaterialRenderProxy public: FMatExpressionPreview() : FMaterial() + , UnrelatedNodesOpacity(1.0f) { // Register this FMaterial derivative with AddEditorLoadedMaterialResource since it does not have a corresponding UMaterialInterface FMaterial::AddEditorLoadedMaterialResource(this); @@ -52,6 +54,7 @@ public: FMatExpressionPreview(UMaterialExpression* InExpression) : FMaterial() + , UnrelatedNodesOpacity(1.0f) , Expression(InExpression) { FMaterial::AddEditorLoadedMaterialResource(this); @@ -150,7 +153,7 @@ public: virtual bool IsSpecialEngineMaterial() const override { return false; } virtual bool IsWireframe() const override { return false; } virtual bool IsMasked() const override { return false; } - virtual enum EBlendMode GetBlendMode() const override { return BLEND_Opaque; } + virtual enum EBlendMode GetBlendMode() const override { return BLEND_Translucent; } virtual FMaterialShadingModelField GetShadingModels() const override { return MSM_Unlit; } virtual float GetOpacityMaskClipValue() const override { return 0.5f; } virtual bool GetCastDynamicShadowAsMasked() const override { return false; } @@ -172,6 +175,8 @@ public: return Ar << V.Expression; } + float UnrelatedNodesOpacity; + private: TWeakObjectPtr Expression; TArray ReferencedTextures; @@ -363,7 +368,7 @@ public: virtual bool CanPasteNodes() const override; virtual void PasteNodesHere(const FVector2D& Location) override; virtual int32 GetNumberOfSelectedNodes() const override; - virtual FMaterialRenderProxy* GetExpressionPreview(UMaterialExpression* InExpression) override; + virtual FMatExpressionPreview* GetExpressionPreview(UMaterialExpression* InExpression) override; virtual void DeleteNodes(const TArray& NodesToDelete) override; @@ -585,6 +590,20 @@ private: bool IsOnAlwaysRefreshAllPreviews() const; /** Command for the stats button */ + /** Make nodes which are unrelated to the selected nodes fade out */ + void ToggleHideUnrelatedNodes(); + bool IsToggleHideUnrelatedNodesChecked() const; + void CollectDownstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes); + void CollectUpstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes); + void HideUnrelatedNodes(); + + /** Make a drop down menu to control the opacity of unrelated nodes */ + TSharedRef MakeHideUnrelatedNodesOptionsMenu(); + TOptional HandleUnrelatedNodesOpacityBoxValue() const; + void HandleUnrelatedNodesOpacityBoxChanged(float NewOpacity); + void OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState); + void OnFocusWholeChainCheckStateChanged(ECheckBoxState NewCheckedState); + void ToggleReleaseStats(); bool IsToggleReleaseStatsChecked() const; void ToggleBuiltinStats(); @@ -787,6 +806,18 @@ private: /** If true, show stats for an empty material. Helps artists to judge the cost of their changes to the graph. */ bool bShowBuiltinStats; + /** If true, fade out nodes which are unrelated to the selected nodes automatically. */ + bool bHideUnrelatedNodes; + + /** Lock the current fade state of each node */ + bool bLockNodeFadeState; + + /** Focus all nodes in the same output chain */ + bool bFocusWholeChain; + + /** If a regular node (not a comment node, not the output node) has been selected */ + bool bSelectRegularNode; + /** Command list for this editor */ TSharedPtr GraphEditorCommands; diff --git a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp index e39192fbd7cb..89c56933f375 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/MaterialEditorActions.cpp @@ -30,7 +30,8 @@ void FMaterialEditorCommands::RegisterCommands() UI_COMMAND( ToggleRealtimeExpressions, "Live Nodes", "Toggles real time update of the graph canvas.", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( AlwaysRefreshAllPreviews, "Live Update", "All nodes are previewed live.", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ToggleMaterialStats, "Stats", "Toggles displaying of the material's stats.", EUserInterfaceActionType::ToggleButton, FInputChord()); - UI_COMMAND(TogglePlatformStats, "Platform Stats", "Toggles the window that shows material stats and compilation errors for multiple platforms.", EUserInterfaceActionType::ToggleButton, FInputChord() ); + UI_COMMAND( TogglePlatformStats, "Platform Stats", "Toggles the window that shows material stats and compilation errors for multiple platforms.", EUserInterfaceActionType::ToggleButton, FInputChord() ); + UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles hiding nodes which are unrelated to the selected nodes automatically.", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( NewComment, "New Comment", "Creates a new comment node.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( MatertialPasteHere, "Paste Here", "Pastes copied items at this location.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( UseCurrentTexture, "Use Current Texture", "Uses the current texture selected in the content browser.", EUserInterfaceActionType::Button, FInputChord() ); diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp index 9d633e3f7503..7639eb906745 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorStatsWidget.cpp @@ -570,11 +570,11 @@ void SMaterialEditorStatsWidget::Construct(const FArguments& InArgs) const auto VerticalScrollbar = SNew(SScrollBar) .Orientation(Orient_Vertical) - .Thickness(FVector2D(7.0f, 7.0f)); + .Thickness(FVector2D(11.0f, 11.0f)); const auto HorizontalScrollbar = SNew(SScrollBar) .Orientation(Orient_Horizontal) - .Thickness(FVector2D(7.0f, 7.0f)); + .Thickness(FVector2D(11.0f, 11.0f)); this->ChildSlot [ diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp index 98ec308d3c43..08258d2da334 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.cpp @@ -29,6 +29,7 @@ #include "AdvancedPreviewScene.h" #include "AssetViewerSettings.h" #include "Engine/PostProcessVolume.h" +#include "Widgets/Input/SCheckBox.h" #define LOCTEXT_NAMESPACE "MaterialEditor" @@ -890,7 +891,7 @@ void SMaterialEditorUIPreviewZoomer::SetPreviewMaterial(UMaterialInterface* InPr void SMaterialEditorUIPreviewViewport::Construct( const FArguments& InArgs, UMaterialInterface* PreviewMaterial ) { - + bRealtime = false; ChildSlot [ SNew( SVerticalBox ) @@ -905,6 +906,21 @@ void SMaterialEditorUIPreviewViewport::Construct( const FArguments& InArgs, UMat SNew( SHorizontalBox ) + SHorizontalBox::Slot() .VAlign(VAlign_Center) + .Padding( 9.f ) + .AutoWidth() + [ + SNew( SCheckBox ) + .CheckBoxContentUsesAutoWidth( true ) + .OnCheckStateChanged(this, &SMaterialEditorUIPreviewViewport::OnRealtimeChanged) + .IsChecked(this, &SMaterialEditorUIPreviewViewport::IsRealtimeChecked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("Realtime", "Realtime")) + ] + ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) .Padding( 3.f ) .AutoWidth() [ @@ -1005,4 +1021,32 @@ void SMaterialEditorUIPreviewViewport::OnPreviewYCommitted( int32 NewValue, ETex OnPreviewYChanged( NewValue ); } +void SMaterialEditorUIPreviewViewport::OnRealtimeChanged(ECheckBoxState State) +{ + if (State == ECheckBoxState::Checked) + { + bRealtime = true; + ActiveTimerHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SMaterialEditorUIPreviewViewport::EnsureTick)); + } + else + { + bRealtime = false; + if (ActiveTimerHandle.IsValid()) + { + UnRegisterActiveTimer(ActiveTimerHandle.Pin().ToSharedRef()); + } + } +} + +ECheckBoxState SMaterialEditorUIPreviewViewport::IsRealtimeChecked() const +{ + return bRealtime ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +EActiveTimerReturnType SMaterialEditorUIPreviewViewport::EnsureTick(double InCurrentTime, float InDeltaTime) +{ + // Keep the timer going if we're realtime + return bRealtime ? EActiveTimerReturnType::Continue : EActiveTimerReturnType::Stop; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h index e0f3ab94b1ab..c9aa1e9f32ff 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialEditorViewport.h @@ -148,7 +148,13 @@ private: void OnPreviewYCommitted( int32 NewValue, ETextCommit::Type ); TOptional OnGetPreviewXValue() const { return PreviewSize.X; } TOptional OnGetPreviewYValue() const { return PreviewSize.Y; } + void OnRealtimeChanged(ECheckBoxState State); + ECheckBoxState IsRealtimeChecked() const; + EActiveTimerReturnType EnsureTick(double InCurrentTime, float InDeltaTime); + private: FIntPoint PreviewSize; TSharedPtr PreviewZoomer; + bool bRealtime; + TWeakPtr ActiveTimerHandle; }; diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp index 4cb2a2b0d223..1af08c2b2678 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.cpp @@ -376,6 +376,7 @@ void SMaterialParametersOverviewTree::Construct(const FArguments& InArgs) .OnGenerateRow(this, &SMaterialParametersOverviewTree::OnGenerateRowMaterialLayersFunctionsTreeView) .OnGetChildren(this, &SMaterialParametersOverviewTree::OnGetChildrenMaterialLayersFunctionsTreeView) .OnExpansionChanged(this, &SMaterialParametersOverviewTree::OnExpansionChanged) + .ExternalScrollbar(InArgs._InScrollbar) ); } @@ -621,7 +622,6 @@ void SMaterialParametersOverviewPanel::Refresh() [ SNew(SVerticalBox) +SVerticalBox::Slot() - .AutoHeight() [ SNew(SBorder) .BorderImage(this, &SMaterialParametersOverviewPanel::GetBackgroundImage) @@ -646,22 +646,36 @@ void SMaterialParametersOverviewPanel::Refresh() ] + SVerticalBox::Slot() .Padding(FMargin(3.0f, 2.0f, 3.0f, 3.0f)) - .AutoHeight() [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop")) [ - SNew(SWidgetSwitcher) - .WidgetIndex(this, &SMaterialParametersOverviewPanel::GetPanelIndex) - +SWidgetSwitcher::Slot() + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Fill) [ - SNew(STextBlock) - .Text(LOCTEXT("AddParams", "Add parameters to see them here.")) + SNew(SWidgetSwitcher) + .WidgetIndex(this, &SMaterialParametersOverviewPanel::GetPanelIndex) + +SWidgetSwitcher::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("AddParams", "Add parameters to see them here.")) + ] + +SWidgetSwitcher::Slot() + [ + NestedTree.ToSharedRef() + ] ] - +SWidgetSwitcher::Slot() + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() [ - NestedTree.ToSharedRef() - ] + SNew(SBox) + .WidthOverride(16.0f) + [ + ExternalScrollbar.ToSharedRef() + ] + ] ] ] ] @@ -713,13 +727,15 @@ void SMaterialParametersOverviewPanel::Refresh() void SMaterialParametersOverviewPanel::Construct(const FArguments& InArgs) { + ExternalScrollbar = SNew(SScrollBar); + NestedTree = SNew(SMaterialParametersOverviewTree) .InMaterialEditorInstance(InArgs._InMaterialEditorInstance) - .InOwner(SharedThis(this)); + .InOwner(SharedThis(this)) + .InScrollbar(ExternalScrollbar); MaterialEditorInstance = InArgs._InMaterialEditorInstance; FEditorSupportDelegates::UpdateUI.AddSP(this, &SMaterialParametersOverviewPanel::Refresh); - } void SMaterialParametersOverviewPanel::UpdateEditorInstance(UMaterialEditorPreviewParameters* InMaterialEditorInstance) diff --git a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h index 7e61665119e7..7e8f6a9ea350 100644 --- a/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h +++ b/Engine/Source/Editor/MaterialEditor/Private/SMaterialParametersOverviewWidget.h @@ -88,6 +88,8 @@ private: /** The tree contained in this item */ TSharedPtr NestedTree; + + TSharedPtr ExternalScrollbar; }; // ********* SMaterialParametersOverviewTree ******* @@ -100,10 +102,12 @@ public: SLATE_BEGIN_ARGS(SMaterialParametersOverviewTree) : _InMaterialEditorInstance(nullptr) , _InOwner(nullptr) + , _InScrollbar() {} SLATE_ARGUMENT(UMaterialEditorPreviewParameters*, InMaterialEditorInstance) SLATE_ARGUMENT(TSharedPtr, InOwner) + SLATE_ARGUMENT(TSharedPtr, InScrollbar) SLATE_END_ARGS() /** Constructs this widget with InArgs */ diff --git a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h index cfc7f6c361be..477a46881584 100644 --- a/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h +++ b/Engine/Source/Editor/MaterialEditor/Public/MaterialEditorActions.h @@ -86,6 +86,9 @@ public: /** Toggles live updating of the preview material. */ TSharedPtr< FUICommandInfo > ToggleLivePreview; + + /** Fade nodes which are not connected to the selected nodes. */ + TSharedPtr< FUICommandInfo > ToggleHideUnrelatedNodes; /** Toggles real time expression nodes */ TSharedPtr< FUICommandInfo > ToggleRealtimeExpressions; diff --git a/Engine/Source/Editor/Matinee/Private/Matinee.cpp b/Engine/Source/Editor/Matinee/Private/Matinee.cpp index 618424f3d553..1643ee68e53b 100644 --- a/Engine/Source/Editor/Matinee/Private/Matinee.cpp +++ b/Engine/Source/Editor/Matinee/Private/Matinee.cpp @@ -438,7 +438,6 @@ void FMatinee::BuildCurveEditor() CurveEd->SetEndMarker(true, IData->InterpLength); CurveEd->SetPositionMarker(true, 0.f, PosMarkerColor); CurveEd->SetRegionMarker(true, IData->EdSectionStart, IData->EdSectionEnd, RegionFillColor); - CurveEd->DrawViewport(); }; /** Should NOT open an InterpEd unless InInterp has a valid MatineeData attached! */ @@ -2639,7 +2638,6 @@ void FMatinee::NotifyPreChange( UProperty* PropertyAboutToChange ) void FMatinee::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged ) { CurveEd->CurveChanged(); - CurveEd->DrawViewport(); // Dirty the track window viewports InvalidateTrackWindowViewports(); diff --git a/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp b/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp index 32f6e06a0173..eb09bbf0ec6f 100644 --- a/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/IMeshPainter.cpp @@ -17,18 +17,26 @@ #include "VREditorInteractor.h" #include "EditorWorldExtension.h" -#include "Framework/Commands/UICommandList.h" #include "Components/MeshComponent.h" #include "Components/PrimitiveComponent.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/UICommandList.h" -IMeshPainter::IMeshPainter() - : CurrentViewportInteractor(nullptr), bArePainting(false), TimeSinceStartedPainting(0.0f), Time(0.0f), WidgetLineThickness(1.0f), VertexPointColor(FLinearColor::White), HoverVertexPointColor(0.3f, 1.0f, 0.3f), PaintTransaction(nullptr) +IMeshPainter::IMeshPainter() : + CurrentViewportInteractor(nullptr), + bArePainting(false), + TimeSinceStartedPainting(0.0f), + Time(0.0f), + WidgetLineThickness(1.0f), + VertexPointColor(FLinearColor::White), + HoverVertexPointColor(0.3f, 1.0f, 0.3f), + PaintTransaction(nullptr) { - FMeshPainterCommands::Register(); } IMeshPainter::~IMeshPainter() { + UICommandList.Reset(); } void IMeshPainter::RenderInteractors(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI, bool bRenderVertices, ESceneDepthPriorityGroup DepthGroup/* = SDPG_World*/) @@ -66,24 +74,53 @@ void IMeshPainter::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) Time += DeltaTime; } +void IMeshPainter::ChangeBrushRadius(float Multiplier) +{ + const float ChangePercentage = 0.05f; + const float OldValue = GetBrushSettings()->GetBrushRadius(); + + float NewValue = OldValue * (1 + ChangePercentage * Multiplier); + GetBrushSettings()->SetBrushRadius(NewValue); +} + +void IMeshPainter::ChangeBrushStrength(float Multiplier) +{ + const float ChangeAmount = 0.02f; + const float OldValue = GetBrushSettings()->GetBrushStrength(); + + float NewValue = OldValue + ChangeAmount * Multiplier; + GetBrushSettings()->SetBrushStrength(NewValue); +} + +void IMeshPainter::ChangeBrushFalloff(float Multiplier) +{ + const float ChangeAmount = 0.02f; + const float OldValue = GetBrushSettings()->GetBrushFalloff(); + + float NewValue = OldValue + ChangeAmount * Multiplier; + GetBrushSettings()->SetBrushFalloff(NewValue); +} + void IMeshPainter::RegisterCommands(TSharedRef CommandList) { const FMeshPainterCommands& Commands = FMeshPainterCommands::Get(); - // Lambda used to alter the paint brush size - auto BrushLambda = [this](float Multiplier) - { - const float BrushChangeValue = 0.05f; - float BrushRadius = GetBrushSettings()->GetBrushRadius(); - BrushRadius *= (1.f + (BrushChangeValue * Multiplier)); - GetBrushSettings()->SetBrushRadius(BrushRadius); - }; - CommandList->MapAction(Commands.IncreaseBrushSize, FExecuteAction::CreateLambda(BrushLambda, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); - CommandList->MapAction(Commands.DecreaseBrushSize, FExecuteAction::CreateLambda(BrushLambda, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.IncreaseBrushRadius, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushRadius, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.DecreaseBrushRadius, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushRadius, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + + CommandList->MapAction(Commands.IncreaseBrushStrength, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushStrength, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.DecreaseBrushStrength, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushStrength, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + + CommandList->MapAction(Commands.IncreaseBrushFalloff, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushFalloff, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + CommandList->MapAction(Commands.DecreaseBrushFalloff, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushFalloff, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled); + + UICommandList = CommandList; } void IMeshPainter::UnregisterCommands(TSharedRef CommandList) { + UICommandList.Reset(); + const FMeshPainterCommands& Commands = FMeshPainterCommands::Get(); for (const TSharedPtr Action : Commands.Commands) { @@ -142,26 +179,11 @@ bool IMeshPainter::InputKey(FEditorViewportClient* InViewportClient, FViewport* { bool bHandled = false; - // Lambda used to alter the paint brush size - auto BrushLambda = [this](float Multiplier) + if (UICommandList.IsValid()) { - const float BrushChangeValue = 0.05f; - float BrushRadius = GetBrushSettings()->GetBrushRadius(); - BrushRadius *= (1.f + (BrushChangeValue * Multiplier)); - GetBrushSettings()->SetBrushRadius(BrushRadius); - }; - - if (InKey == EKeys::LeftBracket) - { - BrushLambda(-1.0f); - bHandled = true; + bHandled = UICommandList->ProcessCommandBindings(InKey, FSlateApplication::Get().GetModifierKeys(), InEvent == IE_Repeat); } - else if (InKey == EKeys::RightBracket) - { - BrushLambda(1.0f); - bHandled = true; - } - + return bHandled; } diff --git a/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp b/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp index ecdb89894ba0..31c2ba8dc884 100644 --- a/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/MeshPaintModule.cpp @@ -6,6 +6,7 @@ #include "EditorStyleSet.h" #include "EditorModeRegistry.h" #include "EditorModes.h" +#include "MeshPainterCommands.h" #include "MeshPaintAdapterFactory.h" #include "MeshPaintStaticMeshAdapter.h" #include "MeshPaintSplineMeshAdapter.h" @@ -38,6 +39,8 @@ public: */ virtual void StartupModule() override { + FMeshPainterCommands::Register(); + RegisterGeometryAdapterFactory(MakeShareable(new FMeshPaintGeometryAdapterForSplineMeshesFactory)); RegisterGeometryAdapterFactory(MakeShareable(new FMeshPaintGeometryAdapterForStaticMeshesFactory)); RegisterGeometryAdapterFactory(MakeShareable(new FMeshPaintGeometryAdapterForSkeletalMeshesFactory)); @@ -60,6 +63,8 @@ public: virtual void ShutdownModule() override { + FMeshPainterCommands::Unregister(); + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.UnregisterCustomClassLayout("VertexColorImportOptions"); diff --git a/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp b/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp index e859aa90b415..04de96d20c1b 100644 --- a/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/MeshPaintSettings.cpp @@ -16,7 +16,13 @@ UPaintBrushSettings::UPaintBrushSettings(const FObjectInitializer& ObjectInitial BrushRadiusMin = 0.01f, BrushRadiusMax = 250000.0f; GConfig->GetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushRadius"), BrushRadius, GEditorPerProjectIni); - BrushRadius = (float)FMath::Clamp(BrushRadius, BrushRadiusMin, BrushRadiusMax); + BrushRadius = FMath::Clamp(BrushRadius, BrushRadiusMin, BrushRadiusMax); + + GConfig->GetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushStrength"), BrushStrength, GEditorPerProjectIni); + BrushStrength = FMath::Clamp(BrushRadius, 0.f, 1.f); + + GConfig->GetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushFalloff"), BrushFalloffAmount, GEditorPerProjectIni); + BrushFalloffAmount = FMath::Clamp(BrushFalloffAmount, 0.f, 1.f); } UPaintBrushSettings::~UPaintBrushSettings() @@ -29,10 +35,32 @@ void UPaintBrushSettings::SetBrushRadius(float InRadius) GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushRadius"), BrushRadius, GEditorPerProjectIni); } +void UPaintBrushSettings::SetBrushStrength(float InStrength) +{ + BrushStrength = FMath::Clamp(InStrength, 0.f, 1.f); + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushStrength"), BrushStrength, GEditorPerProjectIni); +} + +void UPaintBrushSettings::SetBrushFalloff(float InFalloff) +{ + BrushFalloffAmount = FMath::Clamp(InFalloff, 0.f, 1.f); + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushFalloff"), BrushFalloffAmount, GEditorPerProjectIni); +} + void UPaintBrushSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPaintBrushSettings, BrushRadius) && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushRadius"), BrushRadius, GEditorPerProjectIni); } + + if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPaintBrushSettings, BrushStrength) && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushStrength"), BrushStrength, GEditorPerProjectIni); + } + + if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPaintBrushSettings, BrushFalloffAmount) && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + GConfig->SetFloat(TEXT("MeshPaintEdit"), TEXT("DefaultBrushFalloff"), BrushFalloffAmount, GEditorPerProjectIni); + } } \ No newline at end of file diff --git a/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp b/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp index ccb63a443c91..c2c5d7e990ae 100644 --- a/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp +++ b/Engine/Source/Editor/MeshPaint/Private/MeshPainterCommands.cpp @@ -4,13 +4,31 @@ #define LOCTEXT_NAMESPACE "MeshPainterCommands" +FMeshPainterCommands::FMeshPainterCommands() + : TCommands( + "MeshPainter", + NSLOCTEXT("Contexts", "MeshPainter", "Mesh Painter"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{ +} + void FMeshPainterCommands::RegisterCommands() { - UI_COMMAND(IncreaseBrushSize, "IncreaseBrush", "Increases brush size", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket, EModifierKey::Control)); - Commands.Add(IncreaseBrushSize); + UI_COMMAND(IncreaseBrushRadius, "Increase Brush Radius", "Press this key to increase brush radius by a percentage of its current size.", EUserInterfaceActionType::Button, FInputChord(EKeys::RightBracket)); + Commands.Add(IncreaseBrushRadius); + UI_COMMAND(DecreaseBrushRadius, "Decrease Brush Size", "Press this key to decrease brush radius by a percentage of its current size.", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket)); + Commands.Add(DecreaseBrushRadius); - UI_COMMAND(DecreaseBrushSize, "DecreaseBrush", "Decreases brush size", EUserInterfaceActionType::Button, FInputChord(EKeys::LeftBracket, EModifierKey::Control)); - Commands.Add(DecreaseBrushSize); + UI_COMMAND(IncreaseBrushStrength, "Increase Brush Strength", "Press this key to increase brush strength by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::RightBracket)); + Commands.Add(IncreaseBrushStrength); + UI_COMMAND(DecreaseBrushStrength, "Decrease Brush Strength", "Press this key to decrease brush strength by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::LeftBracket)); + Commands.Add(DecreaseBrushStrength); + + UI_COMMAND(IncreaseBrushFalloff, "Increase Brush Falloff", "Press this key to increase brush falloff by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::RightBracket))); + Commands.Add(IncreaseBrushFalloff); + UI_COMMAND(DecreaseBrushFalloff, "Decrease Brush Falloff", "Press this key to decrease brush falloff by a fixed increment.", EUserInterfaceActionType::Button, FInputChord(FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::LeftBracket))); + Commands.Add(DecreaseBrushFalloff); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h b/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h index 1cb2cb1f1ff7..5a28ba387ede 100644 --- a/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h +++ b/Engine/Source/Editor/MeshPaint/Public/IMeshPainter.h @@ -112,6 +112,9 @@ protected: FLinearColor WidgetLineColor; FLinearColor VertexPointColor; FLinearColor HoverVertexPointColor; + + TSharedPtr UICommandList; + protected: /** Begins and keeps track of an Editor transaction with the given Description */ void BeginTransaction(const FText Description); @@ -119,4 +122,15 @@ protected: void EndTransaction(); /** Painting transaction instance which is currently active */ FScopedTransaction* PaintTransaction; + +private: + + /** Change the brush radius by a percentage, multiplied by the given value. */ + void ChangeBrushRadius(float Multiplier); + + /** Change the brush radius by one increment, multiplied by the given value */ + void ChangeBrushStrength(float Multiplier); + + /** Change the brush radius by one increment, multiplied by the given value */ + void ChangeBrushFalloff(float Multiplier); }; \ No newline at end of file diff --git a/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h b/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h index c8df2f631b6d..3b831010fccc 100644 --- a/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h +++ b/Engine/Source/Editor/MeshPaint/Public/MeshPaintSettings.h @@ -41,6 +41,13 @@ public: float GetBrushRadius() const { return BrushRadius; } void SetBrushRadius(float InRadius); + + float GetBrushStrength() const { return BrushStrength; } + void SetBrushStrength(float InStrength); + + float GetBrushFalloff() const { return BrushFalloffAmount; } + void SetBrushFalloff(float InFalloff); + protected: /** Radius of the Brush used for Painting */ UPROPERTY(EditAnywhere, Category = Brush, meta = (DisplayName = "Radius", UIMin = "0.01", UIMax = "2048.0", ClampMin = "0.01", ClampMax = "250000.0")) diff --git a/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h b/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h index abf749095f9c..8e73df8b3be2 100644 --- a/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h +++ b/Engine/Source/Editor/MeshPaint/Public/MeshPainterCommands.h @@ -9,9 +9,8 @@ /** Base set of mesh painter commands */ class MESHPAINT_API FMeshPainterCommands : public TCommands { - public: - FMeshPainterCommands() : TCommands("MeshPainter", NSLOCTEXT("Contexts", "MeshPainter", "Mesh Painter"), NAME_None, FEditorStyle::GetStyleSetName()) {} + FMeshPainterCommands(); /** * Initialize commands @@ -19,7 +18,14 @@ public: virtual void RegisterCommands() override; public: - TSharedPtr IncreaseBrushSize; - TSharedPtr DecreaseBrushSize; + TSharedPtr IncreaseBrushRadius; + TSharedPtr DecreaseBrushRadius; + + TSharedPtr IncreaseBrushStrength; + TSharedPtr DecreaseBrushStrength; + + TSharedPtr IncreaseBrushFalloff; + TSharedPtr DecreaseBrushFalloff; + TArray> Commands; }; \ No newline at end of file diff --git a/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h b/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h index dbc0d2643899..156f859980e5 100644 --- a/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h +++ b/Engine/Source/Editor/MeshPaintMode/Private/PaintModeSettings.h @@ -167,20 +167,20 @@ enum class EPaintMode : uint8 }; /** Paint mode settings class derives from base mesh painting settings */ -UCLASS() +UCLASS(Config=EditorPerProjectUserSettings) class UPaintModeSettings : public UMeshPaintSettings { GENERATED_UCLASS_BODY() public: static UPaintModeSettings* Get(); - + UPROPERTY() EPaintMode PaintMode; - UPROPERTY(EditAnywhere, Category = VertexPainting, meta=(ShowOnlyInnerProperties)) + UPROPERTY(EditAnywhere, Config, Category = VertexPainting, meta=(ShowOnlyInnerProperties)) FVertexPaintSettings VertexPaintSettings; - UPROPERTY(EditAnywhere, Category = TexturePainting, meta=(ShowOnlyInnerProperties)) + UPROPERTY(EditAnywhere, Config, Category = TexturePainting, meta=(ShowOnlyInnerProperties)) FTexturePaintSettings TexturePaintSettings; }; diff --git a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp index 1cd6b74d3aff..70686790e5a9 100644 --- a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp +++ b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.cpp @@ -104,7 +104,7 @@ void SPaintModeWidget::CreateDetailsView() /*bAllowSearch=*/ false, FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true, - /*InNotifyHook=*/ nullptr, + /*InNotifyHook=*/ this, /*InSearchInitialKeyFocus=*/ false, /*InViewIdentifier=*/ NAME_None); DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; @@ -245,4 +245,15 @@ EVisibility SPaintModeWidget::IsTexturePaintModeVisible() const return (MeshPaintSettings->PaintMode == EPaintMode::Textures) ? EVisibility::Visible : EVisibility::Collapsed; } +void SPaintModeWidget::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged) +{ + if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + for (UObject* Settings : SettingsObjects) + { + Settings->SaveConfig(); + } + } +} + #undef LOCTEXT_NAMESPACE // "PaintModePainter" diff --git a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h index 1b8672cf1984..4a4955b181a5 100644 --- a/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h +++ b/Engine/Source/Editor/MeshPaintMode/Private/SPaintModeWidget.h @@ -4,13 +4,14 @@ #include "Widgets/SCompoundWidget.h" #include "Widgets/DeclarativeSyntaxSupport.h" +#include "Misc/NotifyHook.h" class FPaintModePainter; class UPaintModeSettings; class IDetailsView; /** Widget representing the state / functionality and settings for PaintModePainter*/ -class SPaintModeWidget : public SCompoundWidget +class SPaintModeWidget : public SCompoundWidget, public FNotifyHook { public: SLATE_BEGIN_ARGS(SPaintModeWidget) {} @@ -44,4 +45,7 @@ protected: FPaintModePainter* MeshPainter; /** Paint settings instance */ UPaintModeSettings* PaintModeSettings; + +private: + virtual void NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged) override; }; diff --git a/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp b/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp index 27ef44809a60..8350add6e6cc 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaEditorModeManager.cpp @@ -6,9 +6,9 @@ bool FPersonaEditorModeManager::GetCameraTarget(FSphere& OutTarget) const { // Note: assumes all of our modes are IPersonaEditMode! - for(int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex) + for(int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex) { - TSharedPtr EditMode = StaticCastSharedPtr(Modes[ModeIndex]); + TSharedPtr EditMode = StaticCastSharedPtr(ActiveModes[ModeIndex]); FSphere Target; if (EditMode->GetCameraTarget(Target)) @@ -24,9 +24,9 @@ bool FPersonaEditorModeManager::GetCameraTarget(FSphere& OutTarget) const void FPersonaEditorModeManager::GetOnScreenDebugInfo(TArray& OutDebugText) const { // Note: assumes all of our modes are IPersonaEditMode! - for (int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex) + for (int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex) { - TSharedPtr EditMode = StaticCastSharedPtr(Modes[ModeIndex]); + TSharedPtr EditMode = StaticCastSharedPtr(ActiveModes[ModeIndex]); EditMode->GetOnScreenDebugInfo(OutDebugText); } } diff --git a/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp b/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp index affb29221dff..06ce57b89539 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp @@ -32,6 +32,7 @@ #include "EditorDirectories.h" #include "UnrealEdGlobals.h" #include "IDetailsView.h" +#include "MaterialList.h" #include "PropertyCustomizationHelpers.h" #include "DesktopPlatformModule.h" #include "Interfaces/IMainFrameModule.h" @@ -2084,11 +2085,47 @@ FReply FPersonaMeshDetails::OnReimportLodClicked(EReimportButtonType InReimportT } FString SourceFilenameBackup(""); + + //If we alter the reduction setting and the user cancel the import we must set them back + bool bRestoreReductionOnfail = false; + FSkeletalMeshOptimizationSettings ReductionSettingsBackup; + FSkeletalMeshLODInfo* LODInfo = SkelMesh->GetLODInfo(InLODIndex); if(InReimportType == EReimportButtonType::ReimportWithNewFile) { // Back up current source filename and empty it so the importer asks for a new one. - SourceFilenameBackup = SkelMesh->GetLODInfo(InLODIndex)->SourceImportFilename; - SkelMesh->GetLODInfo(InLODIndex)->SourceImportFilename.Empty(); + SourceFilenameBackup = LODInfo->SourceImportFilename; + LODInfo->SourceImportFilename.Empty(); + + //Avoid changing the settings if the skeletal mesh is using a LODSettings asset valid for this LOD + bool bUseLODSettingAsset = SkelMesh->LODSettings != nullptr && SkelMesh->LODSettings->GetNumberOfSettings() > InLODIndex; + //Make the reduction settings change according to the context + if (!bUseLODSettingAsset && SkelMesh->IsReductionActive(InLODIndex) && LODInfo->bHasBeenSimplified && SkelMesh->GetImportedModel()->LODModels[InLODIndex].RawSkeletalMeshBulkData.IsEmpty()) + { + FSkeletalMeshOptimizationSettings& ReductionSettings = LODInfo->ReductionSettings; + //Backup the reduction settings + ReductionSettingsBackup = ReductionSettings; + //In case we have a vert/tri percent we just put the percent to 100% and avoid reduction + //If we have a maximum criterion we change the BaseLOD to reduce the imported fbx instead of other LOD + switch (ReductionSettings.TerminationCriterion) + { + case SkeletalMeshTerminationCriterion::SMTC_NumOfTriangles: + ReductionSettings.NumOfTrianglesPercentage = 1.0f; + break; + case SkeletalMeshTerminationCriterion::SMTC_NumOfVerts: + ReductionSettings.NumOfVertPercentage = 1.0f; + break; + case SkeletalMeshTerminationCriterion::SMTC_TriangleOrVert: + ReductionSettings.NumOfTrianglesPercentage = 1.0f; + ReductionSettings.NumOfVertPercentage = 1.0f; + break; + case SkeletalMeshTerminationCriterion::SMTC_AbsNumOfTriangles: + case SkeletalMeshTerminationCriterion::SMTC_AbsNumOfVerts: + case SkeletalMeshTerminationCriterion::SMTC_AbsTriangleOrVert: + ReductionSettings.BaseLOD = InLODIndex; + break; + } + bRestoreReductionOnfail = true; + } } bool bImportSucceeded = FbxMeshUtils::ImportMeshLODDialog(SkelMesh, InLODIndex); @@ -2096,7 +2133,16 @@ FReply FPersonaMeshDetails::OnReimportLodClicked(EReimportButtonType InReimportT if(InReimportType == EReimportButtonType::ReimportWithNewFile && !bImportSucceeded) { // Copy old source file back, as this one failed - SkelMesh->GetLODInfo(InLODIndex)->SourceImportFilename = SourceFilenameBackup; + LODInfo->SourceImportFilename = SourceFilenameBackup; + if (bRestoreReductionOnfail) + { + LODInfo->ReductionSettings = ReductionSettingsBackup; + } + } + else if(InReimportType == EReimportButtonType::ReimportWithNewFile) + { + //Refresh the layout so the BaseLOD min max get recompute + MeshDetailLayout->ForceRefreshDetails(); } return FReply::Handled(); diff --git a/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp b/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp index b3cacfdf8e5e..65625990f260 100644 --- a/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp +++ b/Engine/Source/Editor/PlacementMode/Private/SPlacementModeTools.cpp @@ -321,7 +321,7 @@ void SPlacementModeTools::Construct( const FArguments& InArgs ) } TSharedRef ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(5, 5)); + .Thickness(FVector2D(9.0f, 9.0f)); ChildSlot [ diff --git a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp index 90e123676691..157dd1c64cfb 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/DetailPropertyRow.cpp @@ -13,6 +13,7 @@ #include "ObjectPropertyNode.h" #include "DetailWidgetRow.h" #include "CategoryPropertyNode.h" +#include "Widgets/Layout/SSpacer.h" const float FDetailWidgetRow::DefaultValueMinWidth = 125.0f; const float FDetailWidgetRow::DefaultValueMaxWidth = 125.0f; @@ -261,6 +262,11 @@ void FDetailPropertyRow::OnGenerateChildren( FDetailNodeList& OutChildren ) const bool bIgnoreAdvancedDropdown = true; MyCategory->GetGeneratedChildren(OutChildren, bIgnoreVisibility, bIgnoreAdvancedDropdown); } + else + { + // Fall back to the default if we can't find the category implementation + GenerateChildrenForPropertyNode(PropertyNode, OutChildren); + } } else if (PropertyNode->AsCategoryNode() || PropertyNode->GetProperty() || ExternalObjectLayout.IsValid()) { @@ -690,15 +696,30 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt .IsEnabled( IsEnabledAttrib ); TSharedPtr ResetButton = nullptr; - if (!PropertyHandle->HasMetaData(TEXT("NoResetToDefault")) && !PropertyHandle->IsResetToDefaultCustomized()) + TSharedPtr ResetWidget = nullptr; + if (!PropertyHandle->HasMetaData(TEXT("NoResetToDefault"))) { - SAssignNew(ResetButton, SResetToDefaultPropertyEditor, PropertyEditor->GetPropertyHandle()) - .IsEnabled(IsEnabledAttrib) - .CustomResetToDefault(CustomResetToDefault); + if (PropertyHandle->IsResetToDefaultCustomized()) + { + // FIXME: Workaround for JIRA UE-73210. + // We had an oscillating SPropertyValueWidget width while dragging a UMG widget in the designer. + // The way drag&drop is implemented (SDesignerView::ProcessDropAndAddWidget), a new UCanvasPanelSlot gets + // recreated every frame, so the details panel gets refreshed every frame. Since new property rows are created + // before old ones are destroyed in the details panel, the HasCustomResetToDefault flag on the property node + // toggles from frame to frame, so we alternate between having a ResetToDefaultPropertyEditor and not having one. + // By having a spacer fill the blank, the property row layout doesn't change while dragging, but we still see + // a flashing yellow reset arrow (when visible). + const FSlateBrush* DiffersFromDefaultBrush = FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"); + ResetWidget = SNew(SSpacer).Size(DiffersFromDefaultBrush != nullptr ? DiffersFromDefaultBrush->ImageSize : FVector2D(8.0f, 8.0f)); + } + else + { + SAssignNew(ResetButton, SResetToDefaultPropertyEditor, PropertyEditor->GetPropertyHandle()) + .IsEnabled(IsEnabledAttrib) + .CustomResetToDefault(CustomResetToDefault); + ResetWidget = ResetButton; + } }; - - - TSharedRef ResetWidget = ResetButton.IsValid() ? ResetButton.ToSharedRef() : SNullWidget::NullWidget; TSharedPtr PropertyValue; @@ -718,7 +739,7 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt [ SAssignNew( PropertyValue, SPropertyValueWidget, PropertyEditor, GetPropertyUtilities() ) .ShowPropertyButtons( false ) // We handle this ourselves - .OptionalResetWidget(ResetWidget) + .OptionalResetWidget(ResetButton.IsValid() ? ResetButton.ToSharedRef() : SNullWidget::NullWidget) ]; MinWidth = PropertyValue->GetMinDesiredWidth(); MaxWidth = PropertyValue->GetMaxDesiredWidth(); @@ -757,7 +778,7 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt } if ((!PropertyValue.IsValid() || (PropertyValue.IsValid() && !PropertyValue->CreatedResetButton())) - && ResetButton.IsValid()) + && ResetWidget.IsValid()) { ValueWidget->AddSlot() .Padding(4.0f, 0.0f) @@ -765,7 +786,7 @@ void FDetailPropertyRow::MakeValueWidget( FDetailWidgetRow& Row, const TSharedPt .VAlign(VAlign_Center) .HAlign(HAlign_Left) [ - ResetWidget + ResetWidget.ToSharedRef() ]; } } diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp new file mode 100644 index 000000000000..b241813e1c8c --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.cpp @@ -0,0 +1,245 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditConditionContext.h" +#include "EditConditionParser.h" + +#include "PropertyNode.h" +#include "PropertyEditorHelpers.h" + +DEFINE_LOG_CATEGORY_STATIC(LogEditCondition, Log, All); + +FEditConditionContext::FEditConditionContext(FPropertyNode& InPropertyNode) +{ + PropertyNode = InPropertyNode.AsShared(); + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + check(ComplexParentNode); + + const UProperty* Property = InPropertyNode.GetProperty(); + check(Property); +} + +FComplexPropertyNode* FEditConditionContext::FindComplexParent() const +{ + if (!PropertyNode.IsValid()) + { + return nullptr; + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + FPropertyNode* SearchStart = PinnedNode.Get(); + if (PropertyEditorHelpers::IsStaticArray(*SearchStart)) + { + //in the case of conditional static arrays, we have to go up one more level to get the proper parent struct. + SearchStart = SearchStart->GetParentNode(); + } + + return SearchStart->FindComplexParent(); +} + +const UBoolProperty* FEditConditionContext::GetSingleBoolProperty(const TSharedPtr& Expression) const +{ + if (!PropertyNode.IsValid()) + { + return nullptr; + } + + const UProperty* Property = PropertyNode.Pin()->GetProperty(); + + const UBoolProperty* BoolProperty = nullptr; + for (const FCompiledToken& Token : Expression->Tokens) + { + if (const EditConditionParserTokens::FPropertyToken* PropertyToken = Token.Node.Cast()) + { + if (BoolProperty != nullptr) + { + // second property token + return nullptr; + } + + const UProperty* Field = FindField(Property->GetOwnerStruct(), *PropertyToken->PropertyName); + BoolProperty = Cast(Field); + + // not a bool + if (BoolProperty == nullptr) + { + return nullptr; + } + } + } + + return BoolProperty; +} + +template +T* FindTypedField(const TWeakPtr& PropertyNode, const FString& PropertyName) +{ + if (PropertyNode.IsValid()) + { + TSharedPtr PinnedNode = PropertyNode.Pin(); + const UProperty* Property = PinnedNode->GetProperty(); + + UProperty* Field = FindField(Property->GetOwnerStruct(), *PropertyName); + if (Field == nullptr) + { + UE_LOG(LogEditCondition, Error, TEXT("EditCondition parsing failed: Field name %s was not found in struct %s."), *PropertyName, *Property->GetOwnerStruct()->GetName()); + return nullptr; + } + + return Cast(Field); + } + + return nullptr; +} + +TOptional FEditConditionContext::GetBoolValue(const FString& PropertyName) const +{ + const UBoolProperty* BoolProperty = FindTypedField(PropertyNode, PropertyName); + if (BoolProperty == nullptr) + { + return TOptional(); + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + TOptional Result; + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) + { + uint8* BasePtr = ComplexParentNode->GetMemoryOfInstance(Index); + uint8* ValuePtr = BoolProperty->ContainerPtrToValuePtr(BasePtr); + + bool bValue = BoolProperty->GetPropertyValue(ValuePtr); + if (!Result.IsSet()) + { + Result = bValue; + } + else if (Result.GetValue() != bValue) + { + // all values aren't the same... + return TOptional(); + } + } + + return Result; +} + +TOptional FEditConditionContext::GetNumericValue(const FString& PropertyName) const +{ + const UNumericProperty* NumericProperty = FindTypedField(PropertyNode, PropertyName); + if (NumericProperty == nullptr) + { + return TOptional(); + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + TOptional Result; + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) + { + uint8* BasePtr = ComplexParentNode->GetMemoryOfInstance(Index); + uint8* ValuePtr = NumericProperty->ContainerPtrToValuePtr(BasePtr); + + double Value = 0; + + if (NumericProperty->IsInteger()) + { + Value = (double) NumericProperty->GetSignedIntPropertyValue(ValuePtr); + } + else if (NumericProperty->IsFloatingPoint()) + { + Value = NumericProperty->GetFloatingPointPropertyValue(ValuePtr); + } + + if (!Result.IsSet()) + { + Result = Value; + } + else if (!FMath::IsNearlyEqual(Result.GetValue(), Value)) + { + // all values aren't the same... + return TOptional(); + } + } + + return Result; +} + +TOptional FEditConditionContext::GetEnumValue(const FString& PropertyName) const +{ + const UProperty* Property = FindTypedField(PropertyNode, PropertyName); + if (Property == nullptr) + { + return TOptional(); + } + + const UEnum* EnumType = nullptr; + const UNumericProperty* NumericProperty = nullptr; + if (const UEnumProperty* EnumProperty = Cast(Property)) + { + NumericProperty = EnumProperty->GetUnderlyingProperty(); + EnumType = EnumProperty->GetEnum(); + } + else if (const UByteProperty* ByteProperty = Cast(Property)) + { + NumericProperty = ByteProperty; + EnumType = ByteProperty->GetIntPropertyEnum(); + } + else + { + return TOptional(); + } + + TSharedPtr PinnedNode = PropertyNode.Pin(); + + TOptional Result; + + FComplexPropertyNode* ComplexParentNode = FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) + { + uint8* BasePtr = ComplexParentNode->GetMemoryOfInstance(Index); + uint8* ValuePtr = Property->ContainerPtrToValuePtr(BasePtr); + + int64 Value = NumericProperty->GetSignedIntPropertyValue(ValuePtr); + if (!Result.IsSet()) + { + Result = Value; + } + else if (Result.GetValue() != Value) + { + // all values aren't the same... + return TOptional(); + } + } + + if (Result.IsSet()) + { + return EnumType->GetNameStringByValue(Result.GetValue()); + } + + return TOptional(); +} + +TOptional FEditConditionContext::GetTypeName(const FString& PropertyName) const +{ + const UProperty* Property = FindTypedField(PropertyNode, PropertyName); + if (Property == nullptr) + { + return TOptional(); + } + + if (const UEnumProperty* EnumProperty = Cast(Property)) + { + return EnumProperty->GetEnum()->GetName(); + } + else if (const UByteProperty* ByteProperty = Cast(Property)) + { + return ByteProperty->GetIntPropertyEnum()->GetName(); + } + + return Property->GetCPPType(); +} \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.h b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.h new file mode 100644 index 000000000000..10f95aeaaf92 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionContext.h @@ -0,0 +1,42 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class IEditConditionContext +{ +public: + virtual TOptional GetBoolValue(const FString& PropertyName) const = 0; + virtual TOptional GetNumericValue(const FString& PropertyName) const = 0; + virtual TOptional GetEnumValue(const FString& PropertyName) const = 0; + virtual TOptional GetTypeName(const FString& PropertyName) const = 0; +}; + +class UProperty; +class FPropertyNode; +class FComplexPropertyNode; +class FEditConditionExpression; + +class FEditConditionContext : public IEditConditionContext +{ +public: + FEditConditionContext(FPropertyNode& InPropertyNode); + virtual ~FEditConditionContext() {} + + virtual TOptional GetBoolValue(const FString& PropertyName) const override; + virtual TOptional GetNumericValue(const FString& PropertyName) const override; + virtual TOptional GetEnumValue(const FString& PropertyName) const override; + virtual TOptional GetTypeName(const FString& PropertyName) const override; + + /** + * Fetch the single boolean property referenced. + * Returns nullptr if more than one property is referenced. + */ + const UBoolProperty* GetSingleBoolProperty(const TSharedPtr& Expression) const; + +private: + TWeakPtr PropertyNode; + + FComplexPropertyNode* FindComplexParent() const; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.cpp new file mode 100644 index 000000000000..320ba695584b --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.cpp @@ -0,0 +1,675 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditConditionParser.h" +#include "EditConditionContext.h" + +#include "Math/BasicMathExpressionEvaluator.h" +#include "Misc/ExpressionParser.h" + +#define LOCTEXT_NAMESPACE "EditConditionParser" + +namespace EditConditionParserTokens +{ + const TCHAR* const FEqual::Moniker = TEXT("=="); + const TCHAR* const FNotEqual::Moniker = TEXT("!="); + const TCHAR* const FGreater::Moniker = TEXT(">"); + const TCHAR* const FGreaterEqual::Moniker = TEXT(">="); + const TCHAR* const FLess::Moniker = TEXT("<"); + const TCHAR* const FLessEqual::Moniker = TEXT("<="); + const TCHAR* const FNot::Moniker = TEXT("!"); + const TCHAR* const FAnd::Moniker = TEXT("&&"); + const TCHAR* const FOr::Moniker = TEXT("||"); + const TCHAR* const FAdd::Moniker = TEXT("+"); + const TCHAR* const FSubtract::Moniker = TEXT("-"); + const TCHAR* const FMultiply::Moniker = TEXT("*"); + const TCHAR* const FDivide::Moniker = TEXT("/"); +} + +static const TCHAR PropertyBreakingChars[] = { '|', '=', '&', '>', '<', '!', '+', '-', '*', '/', ' ', '\t' }; + +static TOptional ConsumeBool(FExpressionTokenConsumer& Consumer) +{ + int TrueChars = 0; + int FalseChars = 0; + TOptional StringToken = Consumer.GetStream().ParseToken([&TrueChars, &FalseChars](TCHAR InC) + { + if (TEXT("true")[TrueChars] == InC) + { + ++TrueChars; + if (TrueChars == 4) + { + return EParseState::StopAfter; + } + else + { + return EParseState::Continue; + } + } + else if (TEXT("false")[FalseChars] == InC) + { + ++FalseChars; + if (FalseChars == 5) + { + return EParseState::StopAfter; + } + else + { + return EParseState::Continue; + } + } + return EParseState::Cancel; + }); + + if (StringToken.IsSet()) + { + bool bIsTrue = TrueChars == 4; + bool bIsFalse = FalseChars == 5; + ensure(bIsTrue || bIsFalse); + + Consumer.Add(StringToken.GetValue(), bIsTrue); + } + + return TOptional(); +} + +static TOptional ConsumePropertyName(FExpressionTokenConsumer& Consumer) +{ + FString PropertyName; + bool bShouldBeEnum = false; + + TOptional StringToken = Consumer.GetStream().ParseToken([&PropertyName, &bShouldBeEnum](TCHAR InC) + { + for (const TCHAR BreakingChar : PropertyBreakingChars) + { + if (InC == BreakingChar) + { + return EParseState::StopBefore; + } + } + + if (InC == ':') + { + bShouldBeEnum = true; + } + + PropertyName.AppendChar(InC); + + return EParseState::Continue; + }); + + if (StringToken.IsSet()) + { + if (bShouldBeEnum) + { + int32 DoubleColonIndex = PropertyName.Find("::"); + if (DoubleColonIndex == INDEX_NONE) + { + return FExpressionError(FText::Format(LOCTEXT("PropertyContainsSingleColon", "EditCondition contains single colon in property name \"{0}\", expected double colons."), FText::FromString(PropertyName))); + } + + if (DoubleColonIndex == 0) + { + return FExpressionError(FText::Format(LOCTEXT("PropertyDoubleColonAtStart", "EditCondition contained double colon at start of property name \"{0}\", expected enum type."), FText::FromString(PropertyName))); + } + + FString EnumType = PropertyName.Left(DoubleColonIndex); + FString EnumValue = PropertyName.RightChop(DoubleColonIndex + 2); + + if (EnumValue.Len() == 0) + { + return FExpressionError(FText::Format(LOCTEXT("PropertyDoubleColonAtEnd", "EditCondition contained double colon at end of property name \"{0}\", expected enum value."), FText::FromString(PropertyName))); + } + + Consumer.Add(StringToken.GetValue(), EditConditionParserTokens::FEnumToken(MoveTemp(EnumType), MoveTemp(EnumValue))); + } + else + { + Consumer.Add(StringToken.GetValue(), EditConditionParserTokens::FPropertyToken(MoveTemp(PropertyName))); + } + } + + return TOptional(); +} + +template +TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) +{ + return TOptional(); +} + +template<> +TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) +{ + return Context.GetBoolValue(PropertyName); +} + +template<> +TOptional GetValueInternal(const IEditConditionContext& Context, const FString& PropertyName) +{ + return Context.GetNumericValue(PropertyName); +} + +template +struct TOperand +{ + TOperand(T InValue) : Value(InValue), Property(nullptr), Context(nullptr) {} + TOperand(const EditConditionParserTokens::FPropertyToken& InProperty, const IEditConditionContext& InContext) : + Property(&InProperty), Context(&InContext) {} + + bool IsProperty() const { return Property != nullptr; } + TOptional GetValue() const + { + if (IsProperty()) + { + return GetValueInternal(*Context, Property->PropertyName); + } + + return TOptional(Value); + } + + const FString& GetName() const { check(IsProperty()); return Property->PropertyName; } + +private: + T Value; + const EditConditionParserTokens::FPropertyToken* Property; + const IEditConditionContext* Context; +}; + +static FExpressionResult ApplyNot(TOperand A) +{ + TOptional Value = A.GetValue(); + if (Value.IsSet()) + { + return MakeValue(!Value.GetValue()); + } + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.GetName()))); +} + +template +FExpressionResult ApplyBinary(TOperand A, TOperand B, Function Apply) +{ + TOptional ValueA = A.GetValue(); + if (!ValueA.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.GetName()))); + } + + TOptional ValueB = B.GetValue(); + if (!ValueB.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(B.GetName()))); + } + + return MakeValue(Apply(ValueA.GetValue(), ValueB.GetValue())); +} + +static FExpressionResult ApplyPropertiesEqual(const EditConditionParserTokens::FPropertyToken& A, const EditConditionParserTokens::FPropertyToken& B, const IEditConditionContext& Context, bool bNegate) +{ + TOptional TypeNameA = Context.GetTypeName(A.PropertyName); + TOptional TypeNameB = Context.GetTypeName(B.PropertyName); + if (!TypeNameA.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(A.PropertyName))); + } + + if (!TypeNameB.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(B.PropertyName))); + } + + if (TypeNameA.GetValue() != TypeNameB.GetValue()) + { + return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(A.PropertyName), FText::FromString(B.PropertyName))); + } + + TOptional bEqual; + + TOptional BoolA = Context.GetBoolValue(A.PropertyName); + TOptional BoolB = Context.GetBoolValue(B.PropertyName); + if (BoolA.IsSet() && BoolB.IsSet()) + { + bEqual = BoolA.GetValue() == BoolB.GetValue(); + } + + TOptional DoubleA = Context.GetNumericValue(A.PropertyName); + TOptional DoubleB = Context.GetNumericValue(B.PropertyName); + if (DoubleA.IsSet() && DoubleB.IsSet()) + { + bEqual = DoubleA.GetValue() == DoubleB.GetValue(); + } + + TOptional EnumA = Context.GetEnumValue(A.PropertyName); + TOptional EnumB = Context.GetEnumValue(B.PropertyName); + if (EnumA.IsSet() && EnumB.IsSet()) + { + bEqual = EnumA.GetValue() == EnumB.GetValue(); + } + + if (bEqual.IsSet()) + { + return MakeValue(bNegate ? !bEqual.GetValue() : bEqual.GetValue()); + } + + return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(A.PropertyName), FText::FromString(B.PropertyName))); +} + +static void CreateBooleanOperators(TOperatorJumpTable& OperatorJumpTable) +{ + using namespace EditConditionParserTokens; + + OperatorJumpTable.MapPreUnary([](bool A) { return !A; }); + OperatorJumpTable.MapPreUnary([](const FPropertyToken& A, const IEditConditionContext* Context) + { + return ApplyNot(TOperand(A, *Context)); + }); + + // AND + { + auto ApplyAnd = [](bool First, bool Second) { return First && Second; }; + + OperatorJumpTable.MapBinary(ApplyAnd); + OperatorJumpTable.MapBinary([ApplyAnd](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyAnd); + }); + OperatorJumpTable.MapBinary([ApplyAnd](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyAnd); + }); + OperatorJumpTable.MapBinary([ApplyAnd](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyAnd); + }); + } + + // OR + { + auto ApplyOr = [](bool First, bool Second) { return First || Second; }; + + OperatorJumpTable.MapBinary(ApplyOr); + OperatorJumpTable.MapBinary([ApplyOr](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyOr); + }); + OperatorJumpTable.MapBinary([ApplyOr](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyOr); + }); + OperatorJumpTable.MapBinary([ApplyOr](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyOr); + }); + } + + // EQUALS + { + auto ApplyEqual = [](bool First, bool Second) { return First == Second; }; + + OperatorJumpTable.MapBinary(ApplyEqual); + OperatorJumpTable.MapBinary([ApplyEqual](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyEqual); + }); + OperatorJumpTable.MapBinary([ApplyEqual](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyEqual); + }); + } + + // NOT-EQUALS + { + auto ApplyNotEqual = [](bool First, bool Second) { return First != Second; }; + + OperatorJumpTable.MapBinary(ApplyNotEqual); + OperatorJumpTable.MapBinary([ApplyNotEqual](const FPropertyToken& A, bool B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyNotEqual); + }); + OperatorJumpTable.MapBinary([ApplyNotEqual](bool A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyNotEqual); + }); + } +} + +template +void CreateNumberOperators(TOperatorJumpTable& OperatorJumpTable) +{ + using namespace EditConditionParserTokens; + + // EQUAL + { + auto ApplyEqual = [](T First, T Second) { return First == Second; }; + + OperatorJumpTable.MapBinary(ApplyEqual); + OperatorJumpTable.MapBinary([ApplyEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyEqual); + }); + OperatorJumpTable.MapBinary([ApplyEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyEqual); + }); + } + + // NOT-EQUAL + { + auto ApplyNotEqual = [](T First, T Second) { return First != Second; }; + + OperatorJumpTable.MapBinary(ApplyNotEqual); + OperatorJumpTable.MapBinary([ApplyNotEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyNotEqual); + }); + OperatorJumpTable.MapBinary([ApplyNotEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyNotEqual); + }); + } + + // GREATER + { + auto ApplyGreater = [](T First, T Second) { return First > Second; }; + + OperatorJumpTable.MapBinary(ApplyGreater); + OperatorJumpTable.MapBinary([ApplyGreater](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyGreater); + }); + OperatorJumpTable.MapBinary([ApplyGreater](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyGreater); + }); + OperatorJumpTable.MapBinary([ApplyGreater](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyGreater); + }); + } + + // GREATER-EQUAL + { + auto ApplyGreaterEqual = [](T First, T Second) { return First >= Second; }; + + OperatorJumpTable.MapBinary(ApplyGreaterEqual); + OperatorJumpTable.MapBinary([ApplyGreaterEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyGreaterEqual); + }); + OperatorJumpTable.MapBinary([ApplyGreaterEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyGreaterEqual); + }); + OperatorJumpTable.MapBinary([ApplyGreaterEqual](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyGreaterEqual); + }); + } + + // LESS + { + auto ApplyLess = [](T First, T Second) { return First < Second; }; + + OperatorJumpTable.MapBinary(ApplyLess); + OperatorJumpTable.MapBinary([ApplyLess](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyLess); + }); + OperatorJumpTable.MapBinary([ApplyLess](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyLess); + }); + OperatorJumpTable.MapBinary([ApplyLess](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyLess); + }); + } + + // LESS-EQUAL + { + auto ApplyLessEqual = [](T First, T Second) { return First <= Second; }; + + OperatorJumpTable.MapBinary(ApplyLessEqual); + OperatorJumpTable.MapBinary([ApplyLessEqual](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyLessEqual); + }); + OperatorJumpTable.MapBinary([ApplyLessEqual](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyLessEqual); + }); + OperatorJumpTable.MapBinary([ApplyLessEqual](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyLessEqual); + }); + } + + // ADD + { + auto ApplyAdd = [](T First, T Second) { return First + Second; }; + + OperatorJumpTable.MapBinary(ApplyAdd); + OperatorJumpTable.MapBinary([ApplyAdd](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyAdd); + }); + OperatorJumpTable.MapBinary([ApplyAdd](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyAdd); + }); + OperatorJumpTable.MapBinary([ApplyAdd](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyAdd); + }); + } + + // SUBTRACT + { + auto ApplySubtract = [](T First, T Second) { return First - Second; }; + + OperatorJumpTable.MapBinary(ApplySubtract); + OperatorJumpTable.MapBinary([ApplySubtract](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplySubtract); + }); + OperatorJumpTable.MapBinary([ApplySubtract](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplySubtract); + }); + OperatorJumpTable.MapBinary([ApplySubtract](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplySubtract); + }); + } + + // MULTIPLY + { + auto ApplyMultiply = [](T First, T Second) { return First * Second; }; + + OperatorJumpTable.MapBinary(ApplyMultiply); + OperatorJumpTable.MapBinary([ApplyMultiply](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyMultiply); + }); + OperatorJumpTable.MapBinary([ApplyMultiply](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyMultiply); + }); + OperatorJumpTable.MapBinary([ApplyMultiply](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyMultiply); + }); + } + + // DIVIDE + { + auto ApplyDivide = [](T First, T Second) { return First / Second; }; + + OperatorJumpTable.MapBinary(ApplyDivide); + OperatorJumpTable.MapBinary([ApplyDivide](const FPropertyToken& A, T B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B), ApplyDivide); + }); + OperatorJumpTable.MapBinary([ApplyDivide](T A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A), TOperand(B, *Context), ApplyDivide); + }); + OperatorJumpTable.MapBinary([ApplyDivide](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return ApplyBinary(TOperand(A, *Context), TOperand(B, *Context), ApplyDivide); + }); + } +} + +static FExpressionResult EnumPropertyEquals(const EditConditionParserTokens::FEnumToken& Enum, const EditConditionParserTokens::FPropertyToken& Property, const IEditConditionContext& Context, bool bNegate) +{ + TOptional TypeName = Context.GetTypeName(Property.PropertyName); + + if (TypeName.GetValue() != Enum.Type) + { + return MakeError(FText::Format(LOCTEXT("OperandTypeMismatch", "EditCondition attempted to compare operands of different types: \"{0}\" and \"{1}\"."), FText::FromString(Property.PropertyName), FText::FromString(Enum.Type + TEXT("::") + Enum.Value))); + } + + TOptional ValueProp = Context.GetEnumValue(Property.PropertyName); + + if (!ValueProp.IsSet()) + { + return MakeError(FText::Format(LOCTEXT("InvalidOperand", "EditCondition attempted to use an invalid operand \"{0}\"."), FText::FromString(Property.PropertyName))); + } + + bool bEqual = ValueProp.GetValue() == Enum.Value; + return MakeValue(bNegate ? !bEqual : bEqual); +} + +static void CreateEnumOperators(TOperatorJumpTable& OperatorJumpTable) +{ + using namespace EditConditionParserTokens; + + // EQUALS + { + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FEnumToken& B, const IEditConditionContext* Context) + { + return A.Type == B.Type && A.Value == B.Value; + }); + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) + { + return EnumPropertyEquals(B, A, *Context, false); + }); + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FPropertyToken& B, const IEditConditionContext* Context) + { + return EnumPropertyEquals(A, B, *Context, false); + }); + } + + // NOT-EQUALS + { + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FEnumToken& B, const IEditConditionContext* Context) + { + return A.Type != B.Type || A.Value != B.Value; + }); + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FEnumToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return EnumPropertyEquals(B, A, *Context, true); + }); + OperatorJumpTable.MapBinary([](const FEnumToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return EnumPropertyEquals(A, B, *Context, true); + }); + } +} + +FEditConditionParser::FEditConditionParser() +{ + using namespace EditConditionParserTokens; + + TokenDefinitions.IgnoreWhitespace(); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeSymbol); + TokenDefinitions.DefineToken(&ExpressionParser::ConsumeNumber); + TokenDefinitions.DefineToken(&ConsumeBool); + TokenDefinitions.DefineToken(&ConsumePropertyName); + + ExpressionGrammar.DefineBinaryOperator(4); + ExpressionGrammar.DefineBinaryOperator(4); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(3); + ExpressionGrammar.DefineBinaryOperator(2); + ExpressionGrammar.DefineBinaryOperator(2); + ExpressionGrammar.DefineBinaryOperator(1); + ExpressionGrammar.DefineBinaryOperator(1); + ExpressionGrammar.DefinePreUnaryOperator(); + + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return ApplyPropertiesEqual(A, B, *Context, false); + }); + + OperatorJumpTable.MapBinary([](const FPropertyToken& A, const FPropertyToken& B, const IEditConditionContext* Context) -> FExpressionResult + { + return ApplyPropertiesEqual(A, B, *Context, true); + }); + + CreateBooleanOperators(OperatorJumpTable); + CreateNumberOperators(OperatorJumpTable); + CreateEnumOperators(OperatorJumpTable); +} + +TOptional FEditConditionParser::Evaluate(const FEditConditionExpression& Expression, const IEditConditionContext& Context) const +{ + using namespace EditConditionParserTokens; + + FExpressionResult Result = ExpressionParser::Evaluate(Expression.Tokens, OperatorJumpTable, &Context); + if (Result.IsValid()) + { + const bool* BoolResult = Result.GetValue().Cast(); + if (BoolResult != nullptr) + { + return *BoolResult; + } + + const FPropertyToken* PropertyResult = Result.GetValue().Cast(); + if (PropertyResult != nullptr) + { + TOptional PropertyValue = Context.GetBoolValue(PropertyResult->PropertyName); + if (PropertyValue.IsSet()) + { + return PropertyValue.GetValue(); + } + } + } + + return TOptional(); +} + +TSharedPtr FEditConditionParser::Parse(const FString& ExpressionString) const +{ + using namespace ExpressionParser; + + LexResultType LexResult = ExpressionParser::Lex(*ExpressionString, TokenDefinitions); + if (LexResult.IsValid()) + { + CompileResultType CompileResult = ExpressionParser::Compile(LexResult.StealValue(), ExpressionGrammar); + if (CompileResult.IsValid()) + { + return TSharedPtr(new FEditConditionExpression(CompileResult.StealValue())); + } + } + + return TSharedPtr(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.h b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.h new file mode 100644 index 000000000000..6e8003bb76b1 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParser.h @@ -0,0 +1,109 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Misc/ExpressionParserTypes.h" +#include "UObject/WeakObjectPtr.h" +#include "UObject/WeakObjectPtrTemplates.h" + +namespace EditConditionParserTokens +{ + struct FPropertyToken + { + FPropertyToken(FString&& InProperty) : + PropertyName(InProperty) {} + + FPropertyToken(const FPropertyToken& Other) : + PropertyName(Other.PropertyName) {} + + FPropertyToken& operator=(const FPropertyToken& Other) + { + PropertyName = Other.PropertyName; + return *this; + } + + FString PropertyName; + }; + + struct FEnumToken + { + FEnumToken(FString&& InType, FString&& InValue) : + Type(InType), Value(InValue) {} + + FEnumToken(const FEnumToken& Other) : + Type(Other.Type), Value(Other.Value) {} + + FEnumToken& operator=(const FEnumToken& Other) + { + Type = Other.Type; + Value = Other.Value; + return *this; + } + + FString Type; + FString Value; + }; +} + +#define DEFINE_EDIT_CONDITION_NODE(TYPE, ...) \ + namespace EditConditionParserTokens { struct TYPE { static const TCHAR* const Moniker; }; } \ + DEFINE_EXPRESSION_NODE_TYPE(EditConditionParserTokens::TYPE, __VA_ARGS__) + + DEFINE_EDIT_CONDITION_NODE(FEqual, 0x3AF0EE1B, 0xC3F847C7, 0xA2B95102, 0x4EFC202D) + DEFINE_EDIT_CONDITION_NODE(FNotEqual, 0x5CDF3FA4, 0x35614D88, 0x8F94017C, 0xF9229625) + DEFINE_EDIT_CONDITION_NODE(FLessEqual, 0x0D097F12, 0x2268447B, 0xA9C6769E, 0x5FE086EA) + DEFINE_EDIT_CONDITION_NODE(FLess, 0xD3B1D8E9, 0x552249C7, 0x92646CF8, 0x6D065F45) + DEFINE_EDIT_CONDITION_NODE(FGreaterEqual, 0x5BC680B9, 0x148448FB, 0xA37A0EBB, 0x34C4BCA8) + DEFINE_EDIT_CONDITION_NODE(FGreater, 0xBD99934C, 0x5E7F4BE9, 0x99A1BD6E, 0x582F8FC9) + DEFINE_EDIT_CONDITION_NODE(FNot, 0xE0FB4BB7, 0x66A646C2, 0xB2360362, 0x0E1FAE33) + DEFINE_EDIT_CONDITION_NODE(FAnd, 0x71F839BC, 0xD75F6B47, 0x9C68017B, 0x33FAB080) + DEFINE_EDIT_CONDITION_NODE(FOr, 0x9C317A4C, 0xCE50304D, 0x8BF6D471, 0x9D791746) + DEFINE_EDIT_CONDITION_NODE(FAdd, 0xF4086317, 0x80428041, 0x903149EB, 0xC5A60ACF) + DEFINE_EDIT_CONDITION_NODE(FSubtract, 0xE80D53E9, 0x32F1E145, 0x8535B767, 0x0634DE9E) + DEFINE_EDIT_CONDITION_NODE(FMultiply, 0x0D4D37DB, 0x53C7EA41, 0x8297C200, 0xF5B28D27) + DEFINE_EDIT_CONDITION_NODE(FDivide, 0x48009789, 0x2EE84F40, 0x83467A99, 0x92617168) + DEFINE_EDIT_CONDITION_NODE(FSubExpressionStart, 0x78CE8411, 0x34076241, 0xA2BD0548, 0x54458A3A) + DEFINE_EDIT_CONDITION_NODE(FSubExpressionEnd, 0x5CB25466, 0x780CAE4D, 0x8A88E000, 0xD3695885) + + DEFINE_EXPRESSION_NODE_TYPE(bool, 0xCACBC715, 0x505A6B4A, 0x8808809F, 0x897AA5F6) + DEFINE_EXPRESSION_NODE_TYPE(EditConditionParserTokens::FPropertyToken, 0x9A3FAF6F, 0xB2E45E4D, 0xA80A70C6, 0x47A89BD7) + DEFINE_EXPRESSION_NODE_TYPE(EditConditionParserTokens::FEnumToken, 0xC9A35C24, 0x21FC904B, 0x9F1B2B6A, 0xDF6F4BC4) + +#undef DEFINE_EDIT_CONDITION_NODE + +class IEditConditionContext; + +class FEditConditionExpression +{ +public: + FEditConditionExpression(TArray&& InTokens) : + Tokens(MoveTemp(InTokens)) + {} + + TArray Tokens; +}; + +class FEditConditionParser +{ +public: + FEditConditionParser(); + + FEditConditionParser(const FEditConditionParser&) = delete; + FEditConditionParser(FEditConditionParser&&) = default; + FEditConditionParser& operator=(const FEditConditionParser&) = delete; + FEditConditionParser& operator=(FEditConditionParser&&) = default; + + TSharedPtr Parse(const FString& ExpressionString) const; + + /** + * Evaluate the given expression within the given context. + * @returns The result of the evaluated expression if valid, invalid TOptional if the evaluation failed or produced a non-bool result. + */ + TOptional Evaluate(const FEditConditionExpression& Expression, const IEditConditionContext& Context) const; + +private: + FTokenDefinitions TokenDefinitions; + FExpressionGrammar ExpressionGrammar; + TOperatorJumpTable OperatorJumpTable; +}; diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp new file mode 100644 index 000000000000..8ae3d1f56019 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.cpp @@ -0,0 +1,407 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "EditConditionParserTests.h" +#include "EditConditionParser.h" +#include "EditConditionContext.h" +#include "ObjectPropertyNode.h" +#include "Misc/AutomationTest.h" + +UEditConditionTestObject::UEditConditionTestObject(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_DEV_AUTOMATION_TESTS +struct FTestEditConditionContext : IEditConditionContext +{ + TMap BoolValues; + TMap DoubleValues; + TMap EnumValues; + FString EnumTypeName; + + FTestEditConditionContext(){} + virtual ~FTestEditConditionContext() {} + + virtual TOptional GetBoolValue(const FString& PropertyName) const override + { + TOptional Result; + if (const bool* Value = BoolValues.Find(PropertyName)) + { + Result = *Value; + } + return Result; + } + + virtual TOptional GetNumericValue(const FString& PropertyName) const override + { + TOptional Result; + if (const double* Value = DoubleValues.Find(PropertyName)) + { + Result = *Value; + } + return Result; + } + + virtual TOptional GetEnumValue(const FString& PropertyName) const override + { + TOptional Result; + if (const FString* Value = EnumValues.Find(PropertyName)) + { + Result = *Value; + } + return Result; + } + + virtual TOptional GetTypeName(const FString& PropertyName) const override + { + TOptional Result; + + if (BoolValues.Find(PropertyName) != nullptr) + { + Result = TEXT("bool"); + } + else if (DoubleValues.Find(PropertyName) != nullptr) + { + Result = TEXT("double"); + } + else if (EnumValues.Find(PropertyName) != nullptr) + { + Result = EnumTypeName; + } + + return Result; + } + + void SetupBool(const FString& PropertyName, bool Value) + { + BoolValues.Add(PropertyName, Value); + } + + void SetupDouble(const FString& PropertyName, double Value) + { + DoubleValues.Add(PropertyName, Value); + } + + void SetupEnum(const FString& PropertyName, const FString& Value) + { + EnumValues.Add(PropertyName, Value); + } + + void SetupEnumType(const FString& EnumType) + { + EnumTypeName = EnumType; + } +}; + +static bool CanParse(const FEditConditionParser& Parser, const FString& Expression, int32 ExpectedTokens, int32 ExpectedProperties) +{ + TSharedPtr Parsed = Parser.Parse(Expression); + + if (!Parsed.IsValid()) + { + ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); + return false; + } + + int PropertyCount = 0; + + for (const auto& Token : Parsed->Tokens) + { + const EditConditionParserTokens::FPropertyToken* PropertyToken = Token.Node.Cast(); + if (PropertyToken != nullptr) + { + ++PropertyCount; + } + } + + return Parsed->Tokens.Num() == ExpectedTokens && + PropertyCount == ExpectedProperties; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_Parse, "EditConditionParser.Parse", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_Parse::RunTest(const FString& Parameters) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanParse(Parser, TEXT("BoolProperty"), 1, 1); + bResult &= CanParse(Parser, TEXT("!BoolProperty"), 2, 1); + bResult &= CanParse(Parser, TEXT("BoolProperty == true"), 3, 1); + bResult &= CanParse(Parser, TEXT("BoolProperty == false"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty == 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty != 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty > 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty < 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty <= 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("IntProperty >= 0"), 3, 1); + bResult &= CanParse(Parser, TEXT("Foo > Bar"), 3, 2); + bResult &= CanParse(Parser, TEXT("Foo && Bar"), 3, 2); + bResult &= CanParse(Parser, TEXT("Foo || Bar"), 3, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar + 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar - 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar * 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Foo == Bar / 5"), 5, 2); + bResult &= CanParse(Parser, TEXT("Enum == EType::Value"), 3, 1); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value"), 3, 1); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value && BoolProperty"), 5, 2); + bResult &= CanParse(Parser, TEXT("Enum == EType::Value || BoolProperty == false"), 7, 2); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value || BoolProperty == bFoo"), 7, 3); + bResult &= CanParse(Parser, TEXT("Enum == EType::Value && Foo != 5"), 7, 2); + bResult &= CanParse(Parser, TEXT("Enum != EType::Value && Foo == Bar"), 7, 3); + + return bResult; +} + +static bool CanEvaluate(const FEditConditionParser& Parser, const IEditConditionContext& Context, const FString& Expression, bool Expected) +{ + TSharedPtr Parsed = Parser.Parse(Expression); + if (!Parsed.IsValid()) + { + ensureMsgf(false, TEXT("Failed to parse expression: %s"), *Expression); + return false; + } + + TOptional Result = Parser.Evaluate(*Parsed.Get(), Context); + if (!Result.IsSet()) + { + ensureMsgf(false, TEXT("Expression failed to evaluate: %s"), *Expression); + return false; + } + + if (Result.GetValue() != Expected) + { + ensureMsgf(false, TEXT("Expression evaluated to unexpected value."), *Expression); + return false; + } + + return true; +} + +static bool RunBoolTests(const IEditConditionContext& Context) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanEvaluate(Parser, Context, TEXT("true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("!true"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("!false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("!BoolProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == false"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty == BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != BoolProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != true"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty != false"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("true && true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("false && true"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("false && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("true && true && true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true && true && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty && BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty && false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("false && BoolProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("true || true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true || false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false || true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false || false"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("true || true || true"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("true || true || false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty || BoolProperty"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("BoolProperty || false"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("false || BoolProperty"), true); + + return bResult; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateBool, "EditConditionParser.EvaluateBool", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateBool::RunTest(const FString& Parameters) +{ + FTestEditConditionContext TestContext; + TestContext.SetupBool(TEXT("BoolProperty"), true); + + return RunBoolTests(TestContext); +} + +static bool RunNumericTests(const IEditConditionContext& Context) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanEvaluate(Parser, Context, TEXT("5 == 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("5.0 == 5.0"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 5.0"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == DoubleProperty"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 5.0"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 6.0"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != 6"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty != DoubleProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 4.5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > 6"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty > DoubleProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 4.5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < 6"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty < DoubleProperty"), false); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 4.5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= 6"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty >= DoubleProperty"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 4.5"), false); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 5"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= 6"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty <= DoubleProperty"), true); + + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 2 + 3"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 6 - 1"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 2.5 * 2"), true); + bResult &= CanEvaluate(Parser, Context, TEXT("DoubleProperty == 10 / 2"), true); + + return bResult; +} + + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateDouble, "EditConditionParser.EvaluateDouble", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateDouble::RunTest(const FString& Parameters) +{ + FTestEditConditionContext TestContext; + TestContext.SetupDouble(TEXT("DoubleProperty"), 5.0); + + return RunNumericTests(TestContext); +} + +static bool RunEnumTests(const IEditConditionContext& Context, const FString& EnumName, const FString& PropertyName) +{ + FEditConditionParser Parser; + bool bResult = true; + + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + EnumName + TEXT("::First"), true); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + EnumName + TEXT("::Second"), false); + + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First != ") + EnumName + TEXT("::First"), false); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First != ") + EnumName + TEXT("::Second"), true); + + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + PropertyName, true); + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + PropertyName, false); + + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + EnumName + TEXT("::First"), true); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::First == ") + PropertyName, true); + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" == ") + EnumName + TEXT("::Second"), false); + + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + EnumName + TEXT("::Second"), true); + bResult &= CanEvaluate(Parser, Context, PropertyName + TEXT(" != ") + EnumName + TEXT("::First"), false); + bResult &= CanEvaluate(Parser, Context, EnumName + TEXT("::Second != ") + PropertyName, true); + + return bResult; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateEnum, "EditConditionParser.EvaluateEnum", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateEnum::RunTest(const FString& Parameters) +{ + FTestEditConditionContext TestContext; + + const FString EnumType = TEXT("EditConditionTestEnum"); + TestContext.SetupEnumType(EnumType); + + const FString PropertyName = TEXT("EnumProperty"); + TestContext.SetupEnum(PropertyName, TEXT("First")); + + return RunEnumTests(TestContext, EnumType, PropertyName); +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(FEditConditionParser_EvaluateUObject, "EditConditionParser.EvaluateUObject", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +bool FEditConditionParser_EvaluateUObject::RunTest(const FString& Parameters) +{ + UEditConditionTestObject* TestObject = NewObject(); + TestObject->AddToRoot(); + + TSharedPtr ObjectNode(new FObjectPropertyNode); + ObjectNode->AddObject(TestObject); + + FPropertyNodeInitParams InitParams; + ObjectNode->InitNode(InitParams); + + TOptional bResult; + bool bAllResults = true; + + // enum comparison + { + static const FName EnumPropertyName = TEXT("EnumProperty"); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(EnumPropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->EnumProperty = EditConditionTestEnum::First; + TestObject->ByteEnumProperty = EditConditionByteEnum::First; + + static const FString EnumType = TEXT("EditConditionTestEnum"); + bAllResults &= RunEnumTests(Context, EnumType, EnumPropertyName.ToString()); + + static const FString ByteEnumType = TEXT("EditConditionByteEnum"); + static const FString ByteEnumPropertyName = TEXT("ByteEnumProperty"); + bAllResults &= RunEnumTests(Context, ByteEnumType, ByteEnumPropertyName); + } + + // bool comparison + { + static const FName BoolPropertyName(TEXT("BoolProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(BoolPropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->BoolProperty = true; + + bAllResults &= RunBoolTests(Context); + } + + // double comparison + { + static const FName DoublePropertyName(TEXT("DoubleProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(DoublePropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->DoubleProperty = 5.0; + + bAllResults &= RunNumericTests(Context); + } + + // integer comparison + { + static const FName IntegerPropertyName(TEXT("IntegerProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(IntegerPropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestObject->IntegerProperty = 5; + + bAllResults &= RunNumericTests(Context); + } + + { + static const FName DoublePropertyName(TEXT("DoubleProperty")); + TSharedPtr PropertyNode = ObjectNode->FindChildPropertyNode(DoublePropertyName, true); + FEditConditionContext Context(*PropertyNode.Get()); + + TestEqual(TEXT("Boolean Type Name"), Context.GetTypeName(TEXT("BoolProperty")).GetValue(), TEXT("bool")); + TestEqual(TEXT("Enum Type Name"), Context.GetTypeName(TEXT("EnumProperty")).GetValue(), TEXT("EditConditionTestEnum")); + TestEqual(TEXT("Byte Enum Type Name"), Context.GetTypeName(TEXT("ByteEnumProperty")).GetValue(), TEXT("EditConditionByteEnum")); + TestEqual(TEXT("Double Type Name"), Context.GetTypeName(TEXT("DoubleProperty")).GetValue(), TEXT("double")); + } + + TestObject->RemoveFromRoot(); + + return bAllResults; +} + +#endif // WITH_DEV_AUTOMATION_TESTS \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.h b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.h new file mode 100644 index 000000000000..a40321af564a --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/EditConditionParserTests.h @@ -0,0 +1,41 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "UObject/Object.h" +#include "EditConditionParserTests.generated.h" + +UENUM() +enum class EditConditionTestEnum +{ + First = 15, + Second = 31 +}; + +UENUM() +enum EditConditionByteEnum +{ + First = 15, + Second = 31 +}; + +UCLASS(transient) +class UEditConditionTestObject : public UObject +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditAnywhere, Category=Test) + bool BoolProperty; + + UPROPERTY(EditAnywhere, Category=Test) + EditConditionTestEnum EnumProperty; + + UPROPERTY(EditAnywhere, Category=Test) + TEnumAsByte ByteEnumProperty; + + UPROPERTY(EditAnywhere, Category=Test) + double DoubleProperty; + + UPROPERTY(EditAnywhere, Category=Test) + int32 IntegerProperty; +}; diff --git a/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h b/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h index 682c2b632b95..33f052c95e01 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h +++ b/Engine/Source/Editor/PropertyEditor/Private/IDetailsViewPrivate.h @@ -8,6 +8,7 @@ #include "PropertyNode.h" #include "IDetailsView.h" +class FEditConditionParser; class FNotifyHook; class IDetailPropertyExtensionHandler; class IDetailRootObjectCustomization; @@ -79,8 +80,6 @@ public: */ virtual bool IsPropertyReadOnly( const struct FPropertyAndParent& PropertyAndParent ) const = 0; - virtual TSharedPtr GetExtensionHandler() = 0; - /** * @return The thumbnail pool that should be used for thumbnails being rendered in this view */ @@ -130,4 +129,6 @@ public: * Restores the expansion state of property nodes for the selected object set */ virtual void RestoreExpandedItems(TSharedRef StartNode) = 0; + + virtual TSharedPtr GetEditConditionParser() const = 0; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/MaterialList.cpp b/Engine/Source/Editor/PropertyEditor/Private/MaterialList.cpp new file mode 100644 index 000000000000..b076faf6ae21 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Private/MaterialList.cpp @@ -0,0 +1,640 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "MaterialList.h" +#include "DetailLayoutBuilder.h" +#include "Editor.h" +#include "IDetailChildrenBuilder.h" +#include "PropertyCustomizationHelpers.h" +#include "PropertyHandle.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "PropertyCustomizationHelpers" + +/** + * Builds up a list of unique materials while creating some information about the materials + */ +class FMaterialListBuilder : public IMaterialListBuilder +{ + friend class FMaterialList; +public: + + /** + * Adds a new material to the list + * + * @param SlotIndex The slot (usually mesh element index) where the material is located on the component + * @param Material The material being used + * @param bCanBeReplced Whether or not the material can be replaced by a user + */ + virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) override + { + int32 NumMaterials = MaterialSlots.Num(); + + FMaterialListItem MaterialItem( Material, SlotIndex, bCanBeReplaced ); + if( !UniqueMaterials.Contains( MaterialItem ) ) + { + MaterialSlots.Add( MaterialItem ); + UniqueMaterials.Add( MaterialItem ); + } + + // Did we actually add material? If we did then we need to increment the number of materials in the element + if( MaterialSlots.Num() > NumMaterials ) + { + // Resize the array to support the slot if needed + if( !MaterialCount.IsValidIndex(SlotIndex) ) + { + int32 NumToAdd = (SlotIndex - MaterialCount.Num()) + 1; + if( NumToAdd > 0 ) + { + MaterialCount.AddZeroed( NumToAdd ); + } + } + + ++MaterialCount[SlotIndex]; + } + } + + /** Empties the list */ + void Empty() + { + UniqueMaterials.Empty(); + MaterialSlots.Reset(); + MaterialCount.Reset(); + } + + /** Sorts the list by slot index */ + void Sort() + { + struct FSortByIndex + { + bool operator()( const FMaterialListItem& A, const FMaterialListItem& B ) const + { + return A.SlotIndex < B.SlotIndex; + } + }; + + MaterialSlots.Sort( FSortByIndex() ); + } + + /** @return The number of materials in the list */ + uint32 GetNumMaterials() const { return MaterialSlots.Num(); } + + /** @return The number of materials in the list at a given slot */ + uint32 GetNumMaterialsInSlot( uint32 Index ) const { return MaterialCount[Index]; } +private: + /** All unique materials */ + TSet UniqueMaterials; + /** All material items in the list */ + TArray MaterialSlots; + /** Material counts for each slot. The slot is the index and the value at that index is the count */ + TArray MaterialCount; +}; + +/** + * A view of a single item in an FMaterialList + */ +class FMaterialItemView : public TSharedFromThis +{ +public: + /** + * Creates a new instance of this class + * + * @param Material The material to view + * @param InOnMaterialChanged Delegate for when the material changes + */ + static TSharedRef Create( + const FMaterialListItem& Material, + FOnMaterialChanged InOnMaterialChanged, + FOnGenerateWidgetsForMaterial InOnGenerateNameWidgetsForMaterial, + FOnGenerateWidgetsForMaterial InOnGenerateWidgetsForMaterial, + FOnResetMaterialToDefaultClicked InOnResetToDefaultClicked, + int32 InMultipleMaterialCount, + bool bShowUsedTextures, + bool bDisplayCompactSize) + { + return MakeShareable( new FMaterialItemView( Material, InOnMaterialChanged, InOnGenerateNameWidgetsForMaterial, InOnGenerateWidgetsForMaterial, InOnResetToDefaultClicked, InMultipleMaterialCount, bShowUsedTextures, bDisplayCompactSize) ); + } + + TSharedRef CreateNameContent() + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ElementIndex"), MaterialItem.SlotIndex); + + return + SNew(SVerticalBox) + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + [ + SNew( STextBlock ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .Text( FText::Format(LOCTEXT("ElementIndex", "Element {ElementIndex}"), Arguments ) ) + ] + +SVerticalBox::Slot() + .Padding(0.0f,4.0f) + .AutoHeight() + [ + OnGenerateCustomNameWidgets.IsBound() ? OnGenerateCustomNameWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) + ]; + } + + TSharedRef CreateValueContent( const TSharedPtr& ThumbnailPool ) + { + FIntPoint ThumbnailSize(64, 64); + + FResetToDefaultOverride ResetToDefaultOverride = FResetToDefaultOverride::Create( + FIsResetToDefaultVisible::CreateSP(this, &FMaterialItemView::GetReplaceVisibility), + FResetToDefaultHandler::CreateSP(this, &FMaterialItemView::OnResetToBaseClicked) + ); + + return + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + .Padding( 0.0f ) + .VAlign(VAlign_Center) + .HAlign(HAlign_Fill) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SObjectPropertyEntryBox ) + .ObjectPath(this, &FMaterialItemView::OnGetObjectPath) + .AllowedClass(UMaterialInterface::StaticClass()) + .OnObjectChanged(this, &FMaterialItemView::OnSetObject) + .ThumbnailPool(ThumbnailPool) + .DisplayCompactSize(bDisplayCompactSize) + .CustomResetToDefault(ResetToDefaultOverride) + .CustomContentSlot() + [ + SNew( SBox ) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(0.0f, 0.0f, 3.0f, 0.0f) + .AutoWidth() + [ + // Add a menu for displaying all textures + SNew( SComboButton ) + .OnGetMenuContent( this, &FMaterialItemView::OnGetTexturesMenuForMaterial ) + .VAlign(VAlign_Center) + .ContentPadding(2) + .IsEnabled( this, &FMaterialItemView::IsTexturesMenuEnabled ) + .Visibility( bShowUsedTextures ? EVisibility::Visible : EVisibility::Hidden ) + .ButtonContent() + [ + SNew( STextBlock ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + .ToolTipText( LOCTEXT("ViewTexturesToolTip", "View the textures used by this material" ) ) + .Text( LOCTEXT("ViewTextures","Textures") ) + ] + ] + +SHorizontalBox::Slot() + .Padding(3.0f, 0.0f) + .FillWidth(1.0f) + [ + OnGenerateCustomMaterialWidgets.IsBound() && bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute(MaterialItem.Material.Get(), MaterialItem.SlotIndex) : StaticCastSharedRef(SNullWidget::NullWidget) + ] + ] + ] + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + .Padding(2) + .VAlign( VAlign_Center ) + [ + OnGenerateCustomMaterialWidgets.IsBound() && !bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) + ] + ]; + } + +private: + + FMaterialItemView( const FMaterialListItem& InMaterial, + FOnMaterialChanged& InOnMaterialChanged, + FOnGenerateWidgetsForMaterial& InOnGenerateNameWidgets, + FOnGenerateWidgetsForMaterial& InOnGenerateMaterialWidgets, + FOnResetMaterialToDefaultClicked& InOnResetToDefaultClicked, + int32 InMultipleMaterialCount, + bool bInShowUsedTextures, + bool bInDisplayCompactSize) + + : MaterialItem( InMaterial ) + , OnMaterialChanged( InOnMaterialChanged ) + , OnGenerateCustomNameWidgets( InOnGenerateNameWidgets ) + , OnGenerateCustomMaterialWidgets( InOnGenerateMaterialWidgets ) + , OnResetToDefaultClicked( InOnResetToDefaultClicked ) + , MultipleMaterialCount( InMultipleMaterialCount ) + , bShowUsedTextures( bInShowUsedTextures ) + , bDisplayCompactSize(bInDisplayCompactSize) + { + + } + + void ReplaceMaterial( UMaterialInterface* NewMaterial, bool bReplaceAll = false ) + { + UMaterialInterface* PrevMaterial = NULL; + if( MaterialItem.Material.IsValid() ) + { + PrevMaterial = MaterialItem.Material.Get(); + } + + if( NewMaterial != PrevMaterial ) + { + // Replace the material + OnMaterialChanged.ExecuteIfBound( NewMaterial, PrevMaterial, MaterialItem.SlotIndex, bReplaceAll ); + } + } + + void OnSetObject( const FAssetData& AssetData ) + { + const bool bReplaceAll = false; + + UMaterialInterface* NewMaterial = Cast(AssetData.GetAsset()); + ReplaceMaterial( NewMaterial, bReplaceAll ); + } + + FString OnGetObjectPath() const + { + return MaterialItem.Material->GetPathName(); + } + + /** + * @return Whether or not the textures menu is enabled + */ + bool IsTexturesMenuEnabled() const + { + return MaterialItem.Material.Get() != NULL; + } + + TSharedRef OnGetTexturesMenuForMaterial() + { + FMenuBuilder MenuBuilder( true, NULL ); + + if( MaterialItem.Material.IsValid() ) + { + UMaterialInterface* Material = MaterialItem.Material.Get(); + + TArray< UTexture* > Textures; + Material->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, ERHIFeatureLevel::Num, true); + + // Add a menu item for each texture. Clicking on the texture will display it in the content browser + // UObject for delegate compatibility + for( UObject* Texture : Textures ) + { + FUIAction Action( FExecuteAction::CreateSP( this, &FMaterialItemView::GoToAssetInContentBrowser, MakeWeakObjectPtr(Texture) ) ); + + MenuBuilder.AddMenuEntry( FText::FromString( Texture->GetName() ), LOCTEXT( "BrowseTexture_ToolTip", "Find this texture in the content browser" ), FSlateIcon(), Action ); + } + } + + return MenuBuilder.MakeWidget(); + } + + /** + * Finds the asset in the content browser + */ + void GoToAssetInContentBrowser( TWeakObjectPtr Object ) + { + if( Object.IsValid() ) + { + TArray< UObject* > Objects; + Objects.Add( Object.Get() ); + GEditor->SyncBrowserToObjects( Objects ); + } + } + + /** + * Called to get the visibility of the replace button + */ + bool GetReplaceVisibility(TSharedPtr PropertyHandle) const + { + // Only show the replace button if the current material can be replaced + if (OnMaterialChanged.IsBound() && MaterialItem.bCanBeReplaced) + { + return true; + } + + return false; + } + + /** + * Called when reset to base is clicked + */ + void OnResetToBaseClicked(TSharedPtr PropertyHandle) + { + // Only allow reset to base if the current material can be replaced + if( MaterialItem.Material.IsValid() && MaterialItem.bCanBeReplaced ) + { + bool bReplaceAll = false; + ReplaceMaterial( NULL, bReplaceAll ); + OnResetToDefaultClicked.ExecuteIfBound( MaterialItem.Material.Get(), MaterialItem.SlotIndex ); + } + } + +private: + FMaterialListItem MaterialItem; + FOnMaterialChanged OnMaterialChanged; + FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; + FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; + FOnResetMaterialToDefaultClicked OnResetToDefaultClicked; + int32 MultipleMaterialCount; + bool bShowUsedTextures; + bool bDisplayCompactSize; +}; + + +FMaterialList::FMaterialList(IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& InMaterialListDelegates, bool bInAllowCollapse, bool bInShowUsedTextures, bool bInDisplayCompactSize) + : MaterialListDelegates( InMaterialListDelegates ) + , DetailLayoutBuilder( InDetailLayoutBuilder ) + , MaterialListBuilder( new FMaterialListBuilder ) + , bAllowCollpase(bInAllowCollapse) + , bShowUsedTextures(bInShowUsedTextures) + , bDisplayCompactSize(bInDisplayCompactSize) +{ +} + +void FMaterialList::OnDisplayMaterialsForElement( int32 SlotIndex ) +{ + // We now want to display all the materials in the element + ExpandedSlots.Add( SlotIndex ); + + MaterialListBuilder->Empty(); + MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); + + OnRebuildChildren.ExecuteIfBound(); +} + +void FMaterialList::OnHideMaterialsForElement( int32 SlotIndex ) +{ + // No longer want to expand the element + ExpandedSlots.Remove( SlotIndex ); + + // regenerate the materials + MaterialListBuilder->Empty(); + MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); + + OnRebuildChildren.ExecuteIfBound(); +} + + +void FMaterialList::Tick( float DeltaTime ) +{ + // Check each material to see if its still valid. This allows the material list to stay up to date when materials are changed out from under us + if( MaterialListDelegates.OnGetMaterials.IsBound() ) + { + // Whether or not to refresh the material list + bool bRefreshMaterialList = false; + + // Get the current list of materials from the user + MaterialListBuilder->Empty(); + MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); + + if( MaterialListBuilder->GetNumMaterials() != DisplayedMaterials.Num() ) + { + // The array sizes differ so we need to refresh the list + bRefreshMaterialList = true; + } + else + { + // Compare the new list against the currently displayed list + for( int32 MaterialIndex = 0; MaterialIndex < MaterialListBuilder->MaterialSlots.Num(); ++MaterialIndex ) + { + const FMaterialListItem& Item = MaterialListBuilder->MaterialSlots[MaterialIndex]; + + // The displayed materials is out of date if there isn't a 1:1 mapping between the material sets + if( !DisplayedMaterials.IsValidIndex( MaterialIndex ) || DisplayedMaterials[ MaterialIndex ] != Item ) + { + bRefreshMaterialList = true; + break; + } + } + } + + if (!bRefreshMaterialList && MaterialListDelegates.OnMaterialListDirty.IsBound()) + { + bRefreshMaterialList = MaterialListDelegates.OnMaterialListDirty.Execute(); + } + + if( bRefreshMaterialList ) + { + OnRebuildChildren.ExecuteIfBound(); + } + } +} + +void FMaterialList::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) +{ + NodeRow.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialList), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialList))); + NodeRow.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialList))); + + if (bAllowCollpase) + { + NodeRow.NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("MaterialHeaderTitle", "Materials")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + } +} + +void FMaterialList::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) +{ + ViewedMaterials.Empty(); + DisplayedMaterials.Empty(); + if( MaterialListBuilder->GetNumMaterials() > 0 ) + { + DisplayedMaterials = MaterialListBuilder->MaterialSlots; + + MaterialListBuilder->Sort(); + TArray& MaterialSlots = MaterialListBuilder->MaterialSlots; + + int32 CurrentSlot = INDEX_NONE; + bool bDisplayAllMaterialsInSlot = true; + for( auto It = MaterialSlots.CreateConstIterator(); It; ++It ) + { + const FMaterialListItem& Material = *It; + + if( CurrentSlot != Material.SlotIndex ) + { + // We've encountered a new slot. Make a widget to display that + CurrentSlot = Material.SlotIndex; + + uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); + + // If an element is expanded we want to display all its materials + bool bWantToDisplayAllMaterials = NumMaterials > 1 && ExpandedSlots.Contains(CurrentSlot); + + // If we are currently displaying an expanded set of materials for an element add a link to collapse all of them + if( bWantToDisplayAllMaterials ) + { + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT( "HideAllMaterialSearchString", "Hide All Materials") ); + + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ElementSlot"), CurrentSlot); + ChildRow + .ValueContent() + .MaxDesiredWidth(0.0f)// No Max Width + [ + SNew( SBox ) + .HAlign( HAlign_Center ) + [ + SNew( SHyperlink ) + .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) + .Text( FText::Format(LOCTEXT("HideAllMaterialLinkText", "Hide All Materials on Element {ElementSlot}"), Arguments ) ) + .OnNavigate( this, &FMaterialList::OnHideMaterialsForElement, CurrentSlot ) + ] + ]; + } + + if( NumMaterials > 1 && !bWantToDisplayAllMaterials ) + { + // The current slot has multiple elements to view + bDisplayAllMaterialsInSlot = false; + + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( FText::GetEmpty() ); + + AddMaterialItem( ChildRow, CurrentSlot, FMaterialListItem( NULL, CurrentSlot, true ), !bDisplayAllMaterialsInSlot ); + } + else + { + bDisplayAllMaterialsInSlot = true; + } + + } + + // Display each thumbnail element unless we shouldn't display multiple materials for one slot + if( bDisplayAllMaterialsInSlot ) + { + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( Material.Material.IsValid()? FText::FromString(Material.Material->GetName()) : FText::GetEmpty() ); + + AddMaterialItem( ChildRow, CurrentSlot, Material, !bDisplayAllMaterialsInSlot ); + } + } + } + else + { + FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT("NoMaterials", "No Materials") ); + + ChildRow + [ + SNew( SBox ) + .HAlign( HAlign_Center ) + [ + SNew( STextBlock ) + .Text( LOCTEXT("NoMaterials", "No Materials") ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + ]; + } +} + +bool FMaterialList::OnCanCopyMaterialList() const +{ + if (MaterialListDelegates.OnCanCopyMaterialList.IsBound()) + { + return MaterialListDelegates.OnCanCopyMaterialList.Execute(); + } + + return false; +} + +void FMaterialList::OnCopyMaterialList() +{ + if (MaterialListDelegates.OnCopyMaterialList.IsBound()) + { + MaterialListDelegates.OnCopyMaterialList.Execute(); + } +} + +void FMaterialList::OnPasteMaterialList() +{ + if (MaterialListDelegates.OnPasteMaterialList.IsBound()) + { + MaterialListDelegates.OnPasteMaterialList.Execute(); + } +} + +bool FMaterialList::OnCanCopyMaterialItem(int32 CurrentSlot) const +{ + if (MaterialListDelegates.OnCanCopyMaterialItem.IsBound()) + { + return MaterialListDelegates.OnCanCopyMaterialItem.Execute(CurrentSlot); + } + + return false; +} + +void FMaterialList::OnCopyMaterialItem(int32 CurrentSlot) +{ + if (MaterialListDelegates.OnCopyMaterialItem.IsBound()) + { + MaterialListDelegates.OnCopyMaterialItem.Execute(CurrentSlot); + } +} + +void FMaterialList::OnPasteMaterialItem(int32 CurrentSlot) +{ + if (MaterialListDelegates.OnPasteMaterialItem.IsBound()) + { + MaterialListDelegates.OnPasteMaterialItem.Execute(CurrentSlot); + } +} + +void FMaterialList::AddMaterialItem( FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink ) +{ + uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); + + TSharedRef NewView = FMaterialItemView::Create( Item, MaterialListDelegates.OnMaterialChanged, MaterialListDelegates.OnGenerateCustomNameWidgets, MaterialListDelegates.OnGenerateCustomMaterialWidgets, MaterialListDelegates.OnResetMaterialToDefaultClicked, NumMaterials, bShowUsedTextures, bDisplayCompactSize); + + TSharedPtr RightSideContent; + if( bDisplayLink ) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("NumMaterials"), NumMaterials); + + RightSideContent = + SNew( SBox ) + .HAlign(HAlign_Left) + .VAlign(VAlign_Top) + [ + SNew( SHyperlink ) + .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) + .Text( FText::Format(LOCTEXT("DisplayAllMaterialLinkText", "Display {NumMaterials} materials"), Arguments) ) + .ToolTipText( LOCTEXT("DisplayAllMaterialLink_ToolTip","Display all materials. Drag and drop a material here to replace all materials.") ) + .OnNavigate( this, &FMaterialList::OnDisplayMaterialsForElement, CurrentSlot ) + ]; + } + else + { + RightSideContent = NewView->CreateValueContent( DetailLayoutBuilder.GetThumbnailPool() ); + ViewedMaterials.Add( NewView ); + } + + Row.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialItem, Item.SlotIndex), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialItem, Item.SlotIndex))); + Row.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialItem, Item.SlotIndex))); + + Row.NameContent() + [ + NewView->CreateNameContent() + ] + .ValueContent() + .MinDesiredWidth(250.f) + .MaxDesiredWidth(0.0f) // no maximum + [ + RightSideContent.ToSharedRef() + ]; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp index 6b3d92816276..a0d526059728 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.cpp @@ -16,6 +16,9 @@ #include "Kismet2/KismetEditorUtilities.h" #include "IConfigEditorModule.h" #include "PropertyNode.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "EditConditionParser.h" +#include "EditConditionContext.h" #define LOCTEXT_NAMESPACE "PropertyEditor" @@ -27,11 +30,9 @@ TSharedRef< FPropertyEditor > FPropertyEditor::Create( const TSharedRef< class F } FPropertyEditor::FPropertyEditor( const TSharedRef& InPropertyNode, const TSharedRef& InPropertyUtilities ) - : PropertyEditConditions() - , PropertyHandle( NULL ) + : PropertyHandle( NULL ) , PropertyNode( InPropertyNode ) , PropertyUtilities( InPropertyUtilities ) - , EditConditionProperty( NULL ) { // FPropertyEditor isn't built to handle CategoryNodes check( InPropertyNode->AsCategoryNode() == NULL ); @@ -40,14 +41,19 @@ FPropertyEditor::FPropertyEditor( const TSharedRef& InPropertyNod if( Property ) { - //see if the property supports some kind of edit condition and this isn't the "parent" property of a static array - const bool bStaticArray = Property->ArrayDim > 1 && InPropertyNode->GetArrayIndex() == INDEX_NONE; + static const FName EditConditionName = TEXT("EditCondition"); - if ( Property->HasMetaData( TEXT( "EditCondition" ) ) && !bStaticArray ) + //see if the property supports some kind of edit condition and this isn't the "parent" property of a static array + if (Property->HasMetaData(EditConditionName) && !PropertyEditorHelpers::IsStaticArray(PropertyNode.Get())) { - if ( !GetEditConditionPropertyAddress( /*OUT*/EditConditionProperty, *InPropertyNode, PropertyEditConditions ) ) + TSharedPtr Parser = PropertyUtilities->GetEditConditionParser(); + if (Parser.IsValid()) { - EditConditionProperty = NULL; + EditConditionExpression = Parser->Parse(Property->GetMetaData(EditConditionName)); + if (EditConditionExpression.IsValid()) + { + EditConditionContext = MakeShareable(new FEditConditionContext(PropertyNode.Get())); + } } } } @@ -195,6 +201,12 @@ void FPropertyEditor::AddItem() PropertyUtilities->EnqueueDeferredAction( FSimpleDelegate::CreateSP( this, &FPropertyEditor::OnAddItem ) ); } +void FPropertyEditor::AddGivenItem(const FString& InGivenItem) +{ + // This action must be deferred until next tick so that we avoid accessing invalid data before we have a chance to tick + PropertyUtilities->EnqueueDeferredAction(FSimpleDelegate::CreateSP(this, &FPropertyEditor::OnAddGivenItem, InGivenItem)); +} + void FPropertyEditor::OnAddItem() { // Check to make sure that the property is a valid container @@ -227,6 +239,30 @@ void FPropertyEditor::OnAddItem() } } +void FPropertyEditor::OnAddGivenItem(const FString InGivenItem) +{ + OnAddItem(); + + // Check to make sure that the property is a valid container + TSharedPtr ArrayHandle = PropertyHandle->AsArray(); + + check(ArrayHandle.IsValid()); + + TSharedPtr ElementHandle; + + if (ArrayHandle.IsValid()) + { + uint32 Last; + ArrayHandle->GetNumElements(Last); + ElementHandle = ArrayHandle->GetElement(Last - 1); + } + + if (ElementHandle.IsValid()) + { + ElementHandle->SetValueFromFormattedString(InGivenItem); + } +} + void FPropertyEditor::ClearItem() { OnClearItem(); @@ -244,12 +280,19 @@ void FPropertyEditor::MakeNewBlueprint() UClassProperty* ClassProp = Cast(NodeProperty); UClass* Class = (ClassProp ? ClassProp->MetaClass : FEditorClassUtils::GetClassFromString(NodeProperty->GetMetaData("MetaClass"))); + UClass* RequiredInterface = FEditorClassUtils::GetClassFromString(NodeProperty->GetMetaData("MustImplement")); + if (Class) { UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprintFromClass(LOCTEXT("CreateNewBlueprint", "Create New Blueprint"), Class, FString::Printf(TEXT("New%s"),*Class->GetName())); if(Blueprint != NULL && Blueprint->GeneratedClass) { + if (RequiredInterface != nullptr && FKismetEditorUtilities::CanBlueprintImplementInterface(Blueprint, RequiredInterface)) + { + FBlueprintEditorUtils::ImplementNewInterface(Blueprint, RequiredInterface->GetFName()); + } + PropertyHandle->SetValueFromFormattedString(Blueprint->GeneratedClass->GetPathName()); FAssetEditorManager::Get().OpenEditorForAsset(Blueprint); @@ -402,7 +445,7 @@ bool FPropertyEditor::IsEditConst() const return PropertyNode->IsEditConst(); } -void FPropertyEditor::SetEditConditionState( bool bShouldEnable ) +void FPropertyEditor::ToggleEditConditionState() { const FScopedTransaction Transaction(FText::Format(LOCTEXT("SetEditConditionState", "Set {0} edit condition state "), PropertyNode->GetDisplayName())); @@ -413,17 +456,25 @@ void FPropertyEditor::SetEditConditionState( bool bShouldEnable ) check(ParentNode != nullptr); PropertyNode->NotifyPreChange( PropertyNode->GetProperty(), PropertyUtilities->GetNotifyHook() ); - for ( int32 ValueIdx = 0; ValueIdx < PropertyEditConditions.Num(); ValueIdx++ ) + + const UBoolProperty* EditConditionProperty = EditConditionContext->GetSingleBoolProperty(EditConditionExpression); + check(EditConditionProperty != nullptr); + + FComplexPropertyNode* ComplexParentNode = ParentNode->FindComplexParent(); + for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { + uint8* BaseAddress = ComplexParentNode->GetMemoryOfInstance(Index); + check(BaseAddress != nullptr); + // Get the address corresponding to the base of this property (i.e. if a struct property, set BaseOffset to the address of value for the whole struct) - uint8* BaseOffset = ParentNode->GetValueAddress(PropertyEditConditions[ValueIdx].BaseAddress); + uint8* BaseOffset = ParentNode->GetValueAddress(BaseAddress); check(BaseOffset != NULL); uint8* ValueAddr = EditConditionProperty->ContainerPtrToValuePtr(BaseOffset); - const bool OldValue = EditConditionProperty->GetPropertyValue( ValueAddr ); - const bool NewValue = XOR(bShouldEnable, PropertyEditConditions[ValueIdx].bNegateValue); - EditConditionProperty->SetPropertyValue( ValueAddr, NewValue ); + const bool OldValue = EditConditionProperty->GetPropertyValue(ValueAddr); + const bool NewValue = !OldValue; + EditConditionProperty->SetPropertyValue(ValueAddr, NewValue); if (ObjectNode != nullptr) { @@ -505,8 +556,7 @@ void FPropertyEditor::OnGetActorFiltersForSceneOutliner( TSharedPtrIsPropertyEditingEnabled() ) && - (EditConditionProperty == NULL || IsEditConditionMet( EditConditionProperty, PropertyEditConditions )); + return ( PropertyUtilities->IsPropertyEditingEnabled() ) && (!HasEditCondition() || IsEditConditionMet()); } void FPropertyEditor::ForceRefresh() @@ -521,17 +571,48 @@ void FPropertyEditor::RequestRefresh() bool FPropertyEditor::HasEditCondition() const { - return EditConditionProperty != NULL; + return EditConditionExpression.IsValid(); } bool FPropertyEditor::IsEditConditionMet() const { - return IsEditConditionMet( EditConditionProperty, PropertyEditConditions ); + if (HasEditCondition()) + { + TSharedPtr EditConditionParser = PropertyUtilities->GetEditConditionParser(); + if (EditConditionParser.IsValid()) + { + TOptional Result = EditConditionParser->Evaluate(*EditConditionExpression.Get(), *EditConditionContext.Get()); + if (Result.IsSet()) + { + return Result.GetValue(); + } + } + } + + return true; } bool FPropertyEditor::SupportsEditConditionToggle() const { - return SupportsEditConditionToggle( PropertyNode->GetProperty() ); + UProperty* Property = PropertyNode->GetProperty(); + + static const FName Name_HideEditConditionToggle("HideEditConditionToggle"); + if (!Property->HasMetaData(Name_HideEditConditionToggle) && EditConditionExpression.IsValid()) + { + const UBoolProperty* ConditionalProperty = EditConditionContext->GetSingleBoolProperty(EditConditionExpression); + if (ConditionalProperty != nullptr) + { + // If it's editable, then only put an inline toggle box if the metadata specifies it + static const FName Name_InlineEditConditionToggle("InlineEditConditionToggle"); + if (ConditionalProperty->HasAllPropertyFlags(CPF_Edit) && + ConditionalProperty->HasMetaData(Name_InlineEditConditionToggle)) + { + return true; + } + } + } + + return false; } void FPropertyEditor::AddPropertyEditorChild( const TSharedRef& Child ) @@ -564,145 +645,6 @@ TSharedRef< IPropertyHandle > FPropertyEditor::GetPropertyHandle() const return PropertyHandle.ToSharedRef(); } -bool FPropertyEditor::IsEditConditionMet( UBoolProperty* ConditionProperty, const TArray& ConditionValues ) const -{ - check(ConditionProperty); - - bool bResult = false; - - FPropertyNode* ParentNode = PropertyNode->GetParentNode(); - if (ParentNode) - { - bool bAllConditionsMet = true; - for (int32 ValueIdx = 0; bAllConditionsMet && ValueIdx < ConditionValues.Num(); ValueIdx++) - { - if (!ConditionValues[ValueIdx].Object.IsValid()) - { - bAllConditionsMet = false; - break; - } - - uint8* BaseOffset = ParentNode->GetValueAddress(ConditionValues[ValueIdx].BaseAddress); - if (!BaseOffset) - { - bAllConditionsMet = false; - break; - } - - uint8* ValueAddr = EditConditionProperty->ContainerPtrToValuePtr(BaseOffset); - - if (ConditionValues[ValueIdx].bNegateValue) - { - bAllConditionsMet = !ConditionProperty->GetPropertyValue(ValueAddr); - } - else - { - bAllConditionsMet = ConditionProperty->GetPropertyValue(ValueAddr); - } - } - - bResult = bAllConditionsMet; - } - - return bResult; -} - -bool FPropertyEditor::GetEditConditionPropertyAddress( UBoolProperty*& ConditionProperty, FPropertyNode& InPropertyNode, TArray& ConditionPropertyAddresses ) -{ - bool bResult = false; - bool bNegate = false; - UBoolProperty* EditConditionProperty = PropertyCustomizationHelpers::GetEditConditionProperty(InPropertyNode.GetProperty(), bNegate); - if ( EditConditionProperty != NULL ) - { - FPropertyNode* ParentNode = InPropertyNode.GetParentNode(); - check(ParentNode); - - UProperty* Property = InPropertyNode.GetProperty(); - if (Property) - { - bool bStaticArray = (Property->ArrayDim > 1) && (InPropertyNode.GetArrayIndex() != INDEX_NONE); - if (bStaticArray) - { - //in the case of conditional static arrays, we have to go up one more level to get the proper parent struct. - ParentNode = ParentNode->GetParentNode(); - check(ParentNode); - } - } - - auto ComplexParentNode = ParentNode->FindComplexParent(); - if (ComplexParentNode) - { - for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) - { - uint8* BaseAddress = ComplexParentNode->GetMemoryOfInstance(Index); - if (BaseAddress) - { - // Get the address corresponding to the base of this property (i.e. if a struct property, set BaseOffset to the address of value for the whole struct) - uint8* BaseOffset = ParentNode->GetValueAddress(BaseAddress); - check(BaseOffset != NULL); - - FPropertyConditionInfo NewCondition; - // now calculate the address of the property value being used as the condition and add it to the array. - NewCondition.Object = ComplexParentNode->AsStructureNode() ? TWeakObjectPtr(ComplexParentNode->GetBaseStructure()) : ComplexParentNode->GetInstanceAsUObject(Index); - NewCondition.BaseAddress = BaseAddress; - NewCondition.bNegateValue = bNegate; - ConditionPropertyAddresses.Add(NewCondition); - bResult = true; - } - } - } - } - - if ( bResult ) - { - // set the output variable - ConditionProperty = EditConditionProperty; - } - - return bResult; -} - -bool FPropertyEditor::SupportsEditConditionToggle( UProperty* InProperty ) -{ - bool bShowEditConditionToggle = false; - - static const FName Name_HideEditConditionToggle("HideEditConditionToggle"); - if (!InProperty->HasMetaData(Name_HideEditConditionToggle)) - { - bool bNegateValue = false; - UBoolProperty* ConditionalProperty = PropertyCustomizationHelpers::GetEditConditionProperty( InProperty, bNegateValue ); - if( ConditionalProperty != NULL ) - { - if (!ConditionalProperty->HasAllPropertyFlags(CPF_Edit)) - { - // Warn if the editcondition property is not marked as editable; this will break when the component is added to a Blueprint. - UObject* PropertyScope = InProperty->GetOwnerClass(); - UObject* ConditionalPropertyScope = ConditionalProperty->GetOwnerClass(); - if (!PropertyScope) - { - PropertyScope = InProperty->GetOwnerStruct(); - ConditionalPropertyScope = ConditionalProperty->GetOwnerStruct(); - } - const FString PropertyScopeName = PropertyScope ? PropertyScope->GetName() : FString(); - const FString ConditionalPropertyScopeName = ConditionalPropertyScope ? ConditionalPropertyScope->GetName() : FString(); - - bShowEditConditionToggle = true; - } - else - { - // If it's editable, then only put an inline toggle box if the metadata specifies it - static const FName Name_InlineEditConditionToggle("InlineEditConditionToggle"); - if (ConditionalProperty->HasMetaData(Name_InlineEditConditionToggle)) - { - bShowEditConditionToggle = true; - } - } - } - } - - return bShowEditConditionToggle; -} - void FPropertyEditor::SyncToObjectsInNode( const TWeakPtr< FPropertyNode >& WeakPropertyNode ) { #if WITH_EDITOR diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h index 2856df2d4e31..78370334543d 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyEditor/PropertyEditor.h @@ -6,6 +6,9 @@ #include "PropertyHandle.h" #include "Editor/SceneOutliner/Public/SceneOutlinerFwd.h" +class FEditConditionExpression; +class FEditConditionContext; + class FPropertyEditor : public TSharedFromThis< FPropertyEditor > { public: @@ -38,6 +41,7 @@ public: void RequestRefresh(); bool SupportsEditConditionToggle() const; + void ToggleEditConditionState(); /** @return Whether the property is editconst */ bool IsEditConst() const; @@ -45,9 +49,6 @@ public: /** @return Whether the property has a condition which must be met before allowing editing of it's value */ bool HasEditCondition() const; - /** Sets the state of the property's edit condition to the specified value */ - void SetEditConditionState( bool bShouldEnable ); - /** @return Whether the condition has been met to allow editing of this property's value */ bool IsEditConditionMet() const; @@ -72,6 +73,7 @@ public: void UseSelected(); void AddItem(); + void AddGivenItem(const FString& InGivenItem); void ClearItem(); void InsertItem(); void DeleteItem(); @@ -96,19 +98,9 @@ public: private: FPropertyEditor( const TSharedRef< class FPropertyNode >& InPropertyNode, const TSharedRef& InPropertyUtilities ); - /** Stores information about property condition metadata. */ - struct FPropertyConditionInfo - { - public: - TWeakObjectPtr Object; - - uint8* BaseAddress; - /** Whether the condition should be negated. */ - bool bNegateValue; - }; - void OnUseSelected(); void OnAddItem(); + void OnAddGivenItem(const FString InGivenItem); void OnClearItem(); void OnInsertItem(); void OnDeleteItem(); @@ -116,29 +108,8 @@ private: void OnBrowseTo(); void OnEmptyArray(); - /** - * Returns true if the value of the conditional property matches the value required. Indicates whether editing or otherwise interacting with this item's - * associated property should be allowed. - */ - bool IsEditConditionMet( UBoolProperty* ConditionProperty, const TArray& ConditionValues ) const; - - /** - * Finds the property being used to determine whether this item's associated property should be editable/expandable. - * If the property is successfully found, ConditionPropertyAddresses will be filled with the addresses of the conditional properties' values. - * - * @param ConditionProperty receives the value of the property being used to control this item's ability to be edited. - * @param ConditionPropertyAddresses receives the addresses of the actual property values for the conditional property - * - * @return true if both the conditional property and property value addresses were successfully determined. - */ - static bool GetEditConditionPropertyAddress( UBoolProperty*& ConditionProperty, FPropertyNode& InPropertyNode, TArray& ConditionPropertyAddresses ); - - static bool SupportsEditConditionToggle( UProperty* InProperty ); - private: - TArray< FPropertyConditionInfo > PropertyEditConditions; - TArray< TSharedRef< FPropertyEditor > > ChildPropertyEditors; /** Property handle for actually reading/writing the value of a property */ @@ -150,6 +121,7 @@ private: /** The property view where this widget resides */ TSharedRef< class IPropertyUtilities > PropertyUtilities; - /** Edit condition property used to determine if this property editor can modify its property */ - class UBoolProperty* EditConditionProperty; + /** Edit condition expression used to determine if this property editor can modify its property */ + TSharedPtr EditConditionExpression; + TSharedPtr EditConditionContext; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp index 2a21fb14f476..d2f1c6e6ab03 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.cpp @@ -11,6 +11,8 @@ #include "Presentation/PropertyTable/PropertyTableObjectNameColumn.h" #include "Presentation/PropertyTable/PropertyTablePropertyNameColumn.h" +#include "EditConditionParser.h" + #define LOCTEXT_NAMESPACE "PropertyTable" FPropertyTable::FPropertyTable() @@ -31,6 +33,7 @@ FPropertyTable::FPropertyTable() , AllowUserToChangeRoot( true ) , bRefreshRequested( false ) , Orientation( EPropertyTableOrientation::AlignPropertiesInColumns ) + , EditConditionParser( new FEditConditionParser ) { } @@ -110,6 +113,11 @@ TSharedPtr FPropertyTable::GetThumbnailPool() const return NULL; } +TSharedPtr FPropertyTable::GetEditConditionParser() const +{ + return EditConditionParser; +} + bool FPropertyTable::GetIsUserAllowedToChangeRoot() { return AllowUserToChangeRoot; diff --git a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h index deb759c6d1d9..922e4f721ca3 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h +++ b/Engine/Source/Editor/PropertyEditor/Private/Presentation/PropertyTable/PropertyTable.h @@ -29,6 +29,7 @@ public: virtual void EnqueueDeferredAction( FSimpleDelegate DeferredAction ) override; virtual TSharedPtr GetThumbnailPool() const override; virtual void NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override {} + virtual TSharedPtr GetEditConditionParser() const override; virtual bool GetIsUserAllowedToChangeRoot() override; virtual void SetIsUserAllowedToChangeRoot( bool InAllowUserToChangeRoot ) override; @@ -224,5 +225,7 @@ private: EPropertyTableOrientation::Type Orientation; FDelegateHandle ObjectsReplacedHandle; + + TSharedRef EditConditionParser; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp index 7c07d1e9f886..0d5eca08a316 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyCustomizationHelpers.cpp @@ -687,624 +687,6 @@ bool SProperty::IsValidProperty() const return PropertyHandle.IsValid() && PropertyHandle->IsValidHandle(); } -/** - * Builds up a list of unique materials while creating some information about the materials - */ -class FMaterialListBuilder : public IMaterialListBuilder -{ - friend class FMaterialList; -public: - - /** - * Adds a new material to the list - * - * @param SlotIndex The slot (usually mesh element index) where the material is located on the component - * @param Material The material being used - * @param bCanBeReplced Whether or not the material can be replaced by a user - */ - virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) override - { - int32 NumMaterials = MaterialSlots.Num(); - - FMaterialListItem MaterialItem( Material, SlotIndex, bCanBeReplaced ); - if( !UniqueMaterials.Contains( MaterialItem ) ) - { - MaterialSlots.Add( MaterialItem ); - UniqueMaterials.Add( MaterialItem ); - } - - // Did we actually add material? If we did then we need to increment the number of materials in the element - if( MaterialSlots.Num() > NumMaterials ) - { - // Resize the array to support the slot if needed - if( !MaterialCount.IsValidIndex(SlotIndex) ) - { - int32 NumToAdd = (SlotIndex - MaterialCount.Num()) + 1; - if( NumToAdd > 0 ) - { - MaterialCount.AddZeroed( NumToAdd ); - } - } - - ++MaterialCount[SlotIndex]; - } - } - - /** Empties the list */ - void Empty() - { - UniqueMaterials.Empty(); - MaterialSlots.Reset(); - MaterialCount.Reset(); - } - - /** Sorts the list by slot index */ - void Sort() - { - struct FSortByIndex - { - bool operator()( const FMaterialListItem& A, const FMaterialListItem& B ) const - { - return A.SlotIndex < B.SlotIndex; - } - }; - - MaterialSlots.Sort( FSortByIndex() ); - } - - /** @return The number of materials in the list */ - uint32 GetNumMaterials() const { return MaterialSlots.Num(); } - - /** @return The number of materials in the list at a given slot */ - uint32 GetNumMaterialsInSlot( uint32 Index ) const { return MaterialCount[Index]; } -private: - /** All unique materials */ - TSet UniqueMaterials; - /** All material items in the list */ - TArray MaterialSlots; - /** Material counts for each slot. The slot is the index and the value at that index is the count */ - TArray MaterialCount; -}; - -/** - * A view of a single item in an FMaterialList - */ -class FMaterialItemView : public TSharedFromThis -{ -public: - /** - * Creates a new instance of this class - * - * @param Material The material to view - * @param InOnMaterialChanged Delegate for when the material changes - */ - static TSharedRef Create( - const FMaterialListItem& Material, - FOnMaterialChanged InOnMaterialChanged, - FOnGenerateWidgetsForMaterial InOnGenerateNameWidgetsForMaterial, - FOnGenerateWidgetsForMaterial InOnGenerateWidgetsForMaterial, - FOnResetMaterialToDefaultClicked InOnResetToDefaultClicked, - int32 InMultipleMaterialCount, - bool bShowUsedTextures, - bool bDisplayCompactSize) - { - return MakeShareable( new FMaterialItemView( Material, InOnMaterialChanged, InOnGenerateNameWidgetsForMaterial, InOnGenerateWidgetsForMaterial, InOnResetToDefaultClicked, InMultipleMaterialCount, bShowUsedTextures, bDisplayCompactSize) ); - } - - TSharedRef CreateNameContent() - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ElementIndex"), MaterialItem.SlotIndex); - - return - SNew(SVerticalBox) - +SVerticalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew( STextBlock ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - .Text( FText::Format(LOCTEXT("ElementIndex", "Element {ElementIndex}"), Arguments ) ) - ] - +SVerticalBox::Slot() - .Padding(0.0f,4.0f) - .AutoHeight() - [ - OnGenerateCustomNameWidgets.IsBound() ? OnGenerateCustomNameWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) - ]; - } - - TSharedRef CreateValueContent( const TSharedPtr& ThumbnailPool ) - { - FIntPoint ThumbnailSize(64, 64); - - FResetToDefaultOverride ResetToDefaultOverride = FResetToDefaultOverride::Create( - FIsResetToDefaultVisible::CreateSP(this, &FMaterialItemView::GetReplaceVisibility), - FResetToDefaultHandler::CreateSP(this, &FMaterialItemView::OnResetToBaseClicked) - ); - - return - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - .Padding( 0.0f ) - .VAlign(VAlign_Center) - .HAlign(HAlign_Fill) - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SObjectPropertyEntryBox ) - .ObjectPath(MaterialItem.Material->GetPathName()) - .AllowedClass(UMaterialInterface::StaticClass()) - .OnObjectChanged(this, &FMaterialItemView::OnSetObject) - .ThumbnailPool(ThumbnailPool) - .DisplayCompactSize(bDisplayCompactSize) - .CustomResetToDefault(ResetToDefaultOverride) - .CustomContentSlot() - [ - SNew( SBox ) - .HAlign(HAlign_Left) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .Padding(0.0f, 0.0f, 3.0f, 0.0f) - .AutoWidth() - [ - // Add a menu for displaying all textures - SNew( SComboButton ) - .OnGetMenuContent( this, &FMaterialItemView::OnGetTexturesMenuForMaterial ) - .VAlign(VAlign_Center) - .ContentPadding(2) - .IsEnabled( this, &FMaterialItemView::IsTexturesMenuEnabled ) - .Visibility( bShowUsedTextures ? EVisibility::Visible : EVisibility::Hidden ) - .ButtonContent() - [ - SNew( STextBlock ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - .ToolTipText( LOCTEXT("ViewTexturesToolTip", "View the textures used by this material" ) ) - .Text( LOCTEXT("ViewTextures","Textures") ) - ] - ] - +SHorizontalBox::Slot() - .Padding(3.0f, 0.0f) - .FillWidth(1.0f) - [ - OnGenerateCustomMaterialWidgets.IsBound() && bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute(MaterialItem.Material.Get(), MaterialItem.SlotIndex) : StaticCastSharedRef(SNullWidget::NullWidget) - ] - ] - ] - ] - ] - +SVerticalBox::Slot() - .AutoHeight() - .Padding(2) - .VAlign( VAlign_Center ) - [ - OnGenerateCustomMaterialWidgets.IsBound() && !bDisplayCompactSize ? OnGenerateCustomMaterialWidgets.Execute( MaterialItem.Material.Get(), MaterialItem.SlotIndex ) : StaticCastSharedRef( SNullWidget::NullWidget ) - ] - ]; - } - -private: - - FMaterialItemView( const FMaterialListItem& InMaterial, - FOnMaterialChanged& InOnMaterialChanged, - FOnGenerateWidgetsForMaterial& InOnGenerateNameWidgets, - FOnGenerateWidgetsForMaterial& InOnGenerateMaterialWidgets, - FOnResetMaterialToDefaultClicked& InOnResetToDefaultClicked, - int32 InMultipleMaterialCount, - bool bInShowUsedTextures, - bool bInDisplayCompactSize) - - : MaterialItem( InMaterial ) - , OnMaterialChanged( InOnMaterialChanged ) - , OnGenerateCustomNameWidgets( InOnGenerateNameWidgets ) - , OnGenerateCustomMaterialWidgets( InOnGenerateMaterialWidgets ) - , OnResetToDefaultClicked( InOnResetToDefaultClicked ) - , MultipleMaterialCount( InMultipleMaterialCount ) - , bShowUsedTextures( bInShowUsedTextures ) - , bDisplayCompactSize(bInDisplayCompactSize) - { - - } - - void ReplaceMaterial( UMaterialInterface* NewMaterial, bool bReplaceAll = false ) - { - UMaterialInterface* PrevMaterial = NULL; - if( MaterialItem.Material.IsValid() ) - { - PrevMaterial = MaterialItem.Material.Get(); - } - - if( NewMaterial != PrevMaterial ) - { - // Replace the material - OnMaterialChanged.ExecuteIfBound( NewMaterial, PrevMaterial, MaterialItem.SlotIndex, bReplaceAll ); - } - } - - void OnSetObject( const FAssetData& AssetData ) - { - const bool bReplaceAll = false; - - UMaterialInterface* NewMaterial = Cast(AssetData.GetAsset()); - ReplaceMaterial( NewMaterial, bReplaceAll ); - } - - /** - * @return Whether or not the textures menu is enabled - */ - bool IsTexturesMenuEnabled() const - { - return MaterialItem.Material.Get() != NULL; - } - - TSharedRef OnGetTexturesMenuForMaterial() - { - FMenuBuilder MenuBuilder( true, NULL ); - - if( MaterialItem.Material.IsValid() ) - { - UMaterialInterface* Material = MaterialItem.Material.Get(); - - TArray< UTexture* > Textures; - Material->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, ERHIFeatureLevel::Num, true); - - // Add a menu item for each texture. Clicking on the texture will display it in the content browser - // UObject for delegate compatibility - for( UObject* Texture : Textures ) - { - FUIAction Action( FExecuteAction::CreateSP( this, &FMaterialItemView::GoToAssetInContentBrowser, MakeWeakObjectPtr(Texture) ) ); - - MenuBuilder.AddMenuEntry( FText::FromString( Texture->GetName() ), LOCTEXT( "BrowseTexture_ToolTip", "Find this texture in the content browser" ), FSlateIcon(), Action ); - } - } - - return MenuBuilder.MakeWidget(); - } - - /** - * Finds the asset in the content browser - */ - void GoToAssetInContentBrowser( TWeakObjectPtr Object ) - { - if( Object.IsValid() ) - { - TArray< UObject* > Objects; - Objects.Add( Object.Get() ); - GEditor->SyncBrowserToObjects( Objects ); - } - } - - /** - * Called to get the visibility of the replace button - */ - bool GetReplaceVisibility(TSharedPtr PropertyHandle) const - { - // Only show the replace button if the current material can be replaced - if (OnMaterialChanged.IsBound() && MaterialItem.bCanBeReplaced) - { - return true; - } - - return false; - } - - /** - * Called when reset to base is clicked - */ - void OnResetToBaseClicked(TSharedPtr PropertyHandle) - { - // Only allow reset to base if the current material can be replaced - if( MaterialItem.Material.IsValid() && MaterialItem.bCanBeReplaced ) - { - bool bReplaceAll = false; - ReplaceMaterial( NULL, bReplaceAll ); - OnResetToDefaultClicked.ExecuteIfBound( MaterialItem.Material.Get(), MaterialItem.SlotIndex ); - } - } - -private: - FMaterialListItem MaterialItem; - FOnMaterialChanged OnMaterialChanged; - FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; - FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; - FOnResetMaterialToDefaultClicked OnResetToDefaultClicked; - int32 MultipleMaterialCount; - bool bShowUsedTextures; - bool bDisplayCompactSize; -}; - - -FMaterialList::FMaterialList(IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& InMaterialListDelegates, bool bInAllowCollapse, bool bInShowUsedTextures, bool bInDisplayCompactSize) - : MaterialListDelegates( InMaterialListDelegates ) - , DetailLayoutBuilder( InDetailLayoutBuilder ) - , MaterialListBuilder( new FMaterialListBuilder ) - , bAllowCollpase(bInAllowCollapse) - , bShowUsedTextures(bInShowUsedTextures) - , bDisplayCompactSize(bInDisplayCompactSize) -{ -} - -void FMaterialList::OnDisplayMaterialsForElement( int32 SlotIndex ) -{ - // We now want to display all the materials in the element - ExpandedSlots.Add( SlotIndex ); - - MaterialListBuilder->Empty(); - MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); - - OnRebuildChildren.ExecuteIfBound(); -} - -void FMaterialList::OnHideMaterialsForElement( int32 SlotIndex ) -{ - // No longer want to expand the element - ExpandedSlots.Remove( SlotIndex ); - - // regenerate the materials - MaterialListBuilder->Empty(); - MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); - - OnRebuildChildren.ExecuteIfBound(); -} - - -void FMaterialList::Tick( float DeltaTime ) -{ - // Check each material to see if its still valid. This allows the material list to stay up to date when materials are changed out from under us - if( MaterialListDelegates.OnGetMaterials.IsBound() ) - { - // Whether or not to refresh the material list - bool bRefrestMaterialList = false; - - // Get the current list of materials from the user - MaterialListBuilder->Empty(); - MaterialListDelegates.OnGetMaterials.ExecuteIfBound( *MaterialListBuilder ); - - if( MaterialListBuilder->GetNumMaterials() != DisplayedMaterials.Num() ) - { - // The array sizes differ so we need to refresh the list - bRefrestMaterialList = true; - } - else - { - // Compare the new list against the currently displayed list - for( int32 MaterialIndex = 0; MaterialIndex < MaterialListBuilder->MaterialSlots.Num(); ++MaterialIndex ) - { - const FMaterialListItem& Item = MaterialListBuilder->MaterialSlots[MaterialIndex]; - - // The displayed materials is out of date if there isn't a 1:1 mapping between the material sets - if( !DisplayedMaterials.IsValidIndex( MaterialIndex ) || DisplayedMaterials[ MaterialIndex ] != Item ) - { - bRefrestMaterialList = true; - break; - } - } - } - - if (!bRefrestMaterialList && MaterialListDelegates.OnMaterialListDirty.IsBound()) - { - bRefrestMaterialList = MaterialListDelegates.OnMaterialListDirty.Execute(); - } - - if( bRefrestMaterialList ) - { - OnRebuildChildren.ExecuteIfBound(); - } - } -} - -void FMaterialList::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) -{ - NodeRow.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialList), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialList))); - NodeRow.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialList))); - - if (bAllowCollpase) - { - NodeRow.NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("MaterialHeaderTitle", "Materials")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ]; - } -} - -void FMaterialList::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) -{ - ViewedMaterials.Empty(); - DisplayedMaterials.Empty(); - if( MaterialListBuilder->GetNumMaterials() > 0 ) - { - DisplayedMaterials = MaterialListBuilder->MaterialSlots; - - MaterialListBuilder->Sort(); - TArray& MaterialSlots = MaterialListBuilder->MaterialSlots; - - int32 CurrentSlot = INDEX_NONE; - bool bDisplayAllMaterialsInSlot = true; - for( auto It = MaterialSlots.CreateConstIterator(); It; ++It ) - { - const FMaterialListItem& Material = *It; - - if( CurrentSlot != Material.SlotIndex ) - { - // We've encountered a new slot. Make a widget to display that - CurrentSlot = Material.SlotIndex; - - uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); - - // If an element is expanded we want to display all its materials - bool bWantToDisplayAllMaterials = NumMaterials > 1 && ExpandedSlots.Contains(CurrentSlot); - - // If we are currently displaying an expanded set of materials for an element add a link to collapse all of them - if( bWantToDisplayAllMaterials ) - { - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT( "HideAllMaterialSearchString", "Hide All Materials") ); - - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ElementSlot"), CurrentSlot); - ChildRow - .ValueContent() - .MaxDesiredWidth(0.0f)// No Max Width - [ - SNew( SBox ) - .HAlign( HAlign_Center ) - [ - SNew( SHyperlink ) - .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) - .Text( FText::Format(LOCTEXT("HideAllMaterialLinkText", "Hide All Materials on Element {ElementSlot}"), Arguments ) ) - .OnNavigate( this, &FMaterialList::OnHideMaterialsForElement, CurrentSlot ) - ] - ]; - } - - if( NumMaterials > 1 && !bWantToDisplayAllMaterials ) - { - // The current slot has multiple elements to view - bDisplayAllMaterialsInSlot = false; - - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( FText::GetEmpty() ); - - AddMaterialItem( ChildRow, CurrentSlot, FMaterialListItem( NULL, CurrentSlot, true ), !bDisplayAllMaterialsInSlot ); - } - else - { - bDisplayAllMaterialsInSlot = true; - } - - } - - // Display each thumbnail element unless we shouldn't display multiple materials for one slot - if( bDisplayAllMaterialsInSlot ) - { - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( Material.Material.IsValid()? FText::FromString(Material.Material->GetName()) : FText::GetEmpty() ); - - AddMaterialItem( ChildRow, CurrentSlot, Material, !bDisplayAllMaterialsInSlot ); - } - } - } - else - { - FDetailWidgetRow& ChildRow = ChildrenBuilder.AddCustomRow( LOCTEXT("NoMaterials", "No Materials") ); - - ChildRow - [ - SNew( SBox ) - .HAlign( HAlign_Center ) - [ - SNew( STextBlock ) - .Text( LOCTEXT("NoMaterials", "No Materials") ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - ] - ]; - } -} - -bool FMaterialList::OnCanCopyMaterialList() const -{ - if (MaterialListDelegates.OnCanCopyMaterialList.IsBound()) - { - return MaterialListDelegates.OnCanCopyMaterialList.Execute(); - } - - return false; -} - -void FMaterialList::OnCopyMaterialList() -{ - if (MaterialListDelegates.OnCopyMaterialList.IsBound()) - { - MaterialListDelegates.OnCopyMaterialList.Execute(); - } -} - -void FMaterialList::OnPasteMaterialList() -{ - if (MaterialListDelegates.OnPasteMaterialList.IsBound()) - { - MaterialListDelegates.OnPasteMaterialList.Execute(); - } -} - -bool FMaterialList::OnCanCopyMaterialItem(int32 CurrentSlot) const -{ - if (MaterialListDelegates.OnCanCopyMaterialItem.IsBound()) - { - return MaterialListDelegates.OnCanCopyMaterialItem.Execute(CurrentSlot); - } - - return false; -} - -void FMaterialList::OnCopyMaterialItem(int32 CurrentSlot) -{ - if (MaterialListDelegates.OnCopyMaterialItem.IsBound()) - { - MaterialListDelegates.OnCopyMaterialItem.Execute(CurrentSlot); - } -} - -void FMaterialList::OnPasteMaterialItem(int32 CurrentSlot) -{ - if (MaterialListDelegates.OnPasteMaterialItem.IsBound()) - { - MaterialListDelegates.OnPasteMaterialItem.Execute(CurrentSlot); - } -} - -void FMaterialList::AddMaterialItem( FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink ) -{ - uint32 NumMaterials = MaterialListBuilder->GetNumMaterialsInSlot(CurrentSlot); - - TSharedRef NewView = FMaterialItemView::Create( Item, MaterialListDelegates.OnMaterialChanged, MaterialListDelegates.OnGenerateCustomNameWidgets, MaterialListDelegates.OnGenerateCustomMaterialWidgets, MaterialListDelegates.OnResetMaterialToDefaultClicked, NumMaterials, bShowUsedTextures, bDisplayCompactSize); - - TSharedPtr RightSideContent; - if( bDisplayLink ) - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("NumMaterials"), NumMaterials); - - RightSideContent = - SNew( SBox ) - .HAlign(HAlign_Left) - .VAlign(VAlign_Top) - [ - SNew( SHyperlink ) - .TextStyle( FEditorStyle::Get(), "MaterialList.HyperlinkStyle" ) - .Text( FText::Format(LOCTEXT("DisplayAllMaterialLinkText", "Display {NumMaterials} materials"), Arguments) ) - .ToolTipText( LOCTEXT("DisplayAllMaterialLink_ToolTip","Display all materials. Drag and drop a material here to replace all materials.") ) - .OnNavigate( this, &FMaterialList::OnDisplayMaterialsForElement, CurrentSlot ) - ]; - } - else - { - RightSideContent = NewView->CreateValueContent( DetailLayoutBuilder.GetThumbnailPool() ); - ViewedMaterials.Add( NewView ); - } - - Row.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnCopyMaterialItem, Item.SlotIndex), FCanExecuteAction::CreateSP(this, &FMaterialList::OnCanCopyMaterialItem, Item.SlotIndex))); - Row.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMaterialList::OnPasteMaterialItem, Item.SlotIndex))); - - Row.NameContent() - [ - NewView->CreateNameContent() - ] - .ValueContent() - .MinDesiredWidth(250.f) - .MaxDesiredWidth(0.0f) // no maximum - [ - RightSideContent.ToSharedRef() - ]; -} - TSharedRef PropertyCustomizationHelpers::MakePropertyComboBox(const TSharedPtr& InPropertyHandle, FOnGetPropertyComboBoxStrings OnGetStrings, FOnGetPropertyComboBoxValue OnGetValue, FOnPropertyComboBoxValueSelected OnValueSelected) { FSlateFontInfo FontStyle = FEditorStyle::GetFontStyle(PropertyEditorConstants::PropertyFontStyle); diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp index 0734b0dca4e2..b7830c2c8790 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyEditorHelpers.cpp @@ -362,7 +362,7 @@ void SEditConditionWidget::OnEditConditionCheckChanged( ECheckBoxState CheckStat if( PropertyEditor.IsValid() && PropertyEditor->HasEditCondition() && PropertyEditor->SupportsEditConditionToggle() ) { - PropertyEditor->SetEditConditionState( CheckState == ECheckBoxState::Checked ); + PropertyEditor->ToggleEditConditionState(); } else { diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp index 7f5a0fc3ba60..a7c6d08e4815 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp @@ -725,6 +725,19 @@ TSharedPtr FPropertyNode::FindChildPropertyNode( const FName InPr return nullptr; } +/** + * Returns whether this window's property is read only or has the CPF_EditConst flag. + */ +bool FPropertyNode::IsPropertyConst() const +{ + bool bIsPropertyConst = (HasNodeFlags(EPropertyNodeFlags::IsReadOnly) != 0); + if (!bIsPropertyConst && Property != nullptr) + { + bIsPropertyConst = (Property->PropertyFlags & CPF_EditConst) ? true : false; + } + + return bIsPropertyConst; +} /** @return whether this window's property is constant (can't be edited by the user) */ bool FPropertyNode::IsEditConst() const @@ -734,23 +747,19 @@ bool FPropertyNode::IsEditConst() const // Ask the objects whether this property can be changed const FObjectPropertyNode* ObjectPropertyNode = FindObjectItemParent(); - bIsEditConst = (HasNodeFlags(EPropertyNodeFlags::IsReadOnly) != 0); + bIsEditConst = IsPropertyConst(); if(!bIsEditConst && Property != nullptr && ObjectPropertyNode) { - bIsEditConst = (Property->PropertyFlags & CPF_EditConst) ? true : false; - if(!bIsEditConst) + // travel up the chain to see if this property's owner struct is editconst - if it is, so is this property + FPropertyNode* NextParent = ParentNode; + while(NextParent != nullptr && Cast(NextParent->GetProperty()) != NULL) { - // travel up the chain to see if this property's owner struct is editconst - if it is, so is this property - FPropertyNode* NextParent = ParentNode; - while(NextParent != nullptr && Cast(NextParent->GetProperty()) != NULL) + if(NextParent->IsEditConst()) { - if(NextParent->IsEditConst()) - { - bIsEditConst = true; - break; - } - NextParent = NextParent->ParentNode; + bIsEditConst = true; + break; } + NextParent = NextParent->ParentNode; } if(!bIsEditConst) diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h index 16cbef3eeedb..84215b07593c 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.h @@ -401,6 +401,11 @@ public: */ bool GetChildNode(const int32 ChildArrayIndex, TSharedPtr& OutChildNode) const; + /** + * Returns whether this window's property is read only or has the CPF_EditConst flag. + */ + bool IsPropertyConst() const; + /** @return whether this window's property is constant (can't be edited by the user) */ bool IsEditConst() const; diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp index 1aef926e0731..ea0039ef1b54 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyRowGenerator.cpp @@ -12,13 +12,16 @@ #include "DetailLayoutHelpers.h" #include "PropertyHandleImpl.h" #include "IPropertyGenerationUtilities.h" +#include "EditConditionParser.h" class FPropertyRowGeneratorUtilities : public IPropertyUtilities { public: FPropertyRowGeneratorUtilities(FPropertyRowGenerator& InGenerator) : Generator(&InGenerator) - {} + , EditConditionParser(new FEditConditionParser) + { + } void ResetGenerator() { @@ -87,8 +90,15 @@ public: { return Generator != nullptr && Generator->HasClassDefaultObject(); } + + virtual TSharedPtr GetEditConditionParser() const override + { + return EditConditionParser; + } + private: FPropertyRowGenerator* Generator; + TSharedPtr EditConditionParser; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp index 3bc6e0b39fdb..e9570ab32dbc 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.cpp @@ -221,6 +221,12 @@ FReply SDetailSingleItemRow::OnArrayDrop(const FDragDropEvent& DragDropEvent) return FReply::Handled(); } +FReply SDetailSingleItemRow::OnArrayHeaderDrop(const FDragDropEvent& DragDropEvent) +{ + OnArrayDragLeave(DragDropEvent); + return FReply::Handled(); +} + TSharedPtr SDetailSingleItemRow::GetCopyPastePropertyNode() const { TSharedPtr PropertyNode = Customization->GetPropertyNode(); @@ -361,7 +367,15 @@ void SDetailSingleItemRow::Construct( const FArguments& InArgs, FDetailLayoutCus ArrayDropDelegate = FOnTableRowDrop::CreateSP(this, &SDetailSingleItemRow::OnArrayDrop); SwappablePropertyNode = PropertyNode; } - + else if (PropertyNode.IsValid() + && Cast(PropertyNode->GetProperty()) != nullptr // Is an array + && Cast(Cast(PropertyNode->GetProperty())->Inner) != nullptr) // Is an object array + { + ArrayDragDelegate = FOnTableRowDragEnter::CreateSP(this, &SDetailSingleItemRow::OnArrayDragEnter); + ArrayDragLeaveDelegate = FOnTableRowDragLeave::CreateSP(this, &SDetailSingleItemRow::OnArrayDragLeave); + ArrayDropDelegate = FOnTableRowDrop::CreateSP(this, &SDetailSingleItemRow::OnArrayHeaderDrop); + } + InternalLeftColumnRowBox->AddSlot() .Padding(0.0f, 0.0f) .HAlign(HAlign_Left) @@ -687,6 +701,10 @@ const FSlateBrush* SDetailSingleItemRow::GetBorderImage() const { return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Highlighted"); } + else if (bIsDragDropObject) + { + return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Active"); + } else if (IsHovered() && !bIsHoveredDragTarget) { return FEditorStyle::GetBrush("DetailsView.CategoryMiddle_Hovered"); @@ -788,6 +806,11 @@ bool SDetailSingleItemRow::IsHighlighted() const return OwnerTreeNode.Pin()->IsHighlighted(); } +void SDetailSingleItemRow::SetIsDragDrop(bool bInIsDragDrop) +{ + bIsDragDropObject = bInIsDragDrop; +} + void SArrayRowHandle::Construct(const FArguments& InArgs) { ParentRow = InArgs._ParentRow; @@ -802,12 +825,10 @@ FReply SArrayRowHandle::OnDragDetected(const FGeometry& MyGeometry, const FPoint { if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) { - { - TSharedPtr DragDropOp = CreateDragDropOperation(ParentRow.Pin()); - if (DragDropOp.IsValid()) - { - return FReply::Handled().BeginDragDrop(DragDropOp.ToSharedRef()); - } + TSharedPtr DragDropOp = CreateDragDropOperation(ParentRow.Pin()); + if (DragDropOp.IsValid()) + { + return FReply::Handled().BeginDragDrop(DragDropOp.ToSharedRef()); } } @@ -821,3 +842,46 @@ TSharedPtr SArrayRowHandle::CreateDragDropOperation(TShared return Operation; } + +FArrayRowDragDropOp::FArrayRowDragDropOp(TSharedPtr InRow) +{ + Row = InRow; + + TSharedPtr RowPtr = nullptr; + if (Row.IsValid()) + { + RowPtr = Row.Pin(); + // mark row as being used for drag and drop + RowPtr->SetIsDragDrop(true); + } + + DecoratorWidget = SNew(SBorder) + .Padding(8.f) + .BorderImage(FEditorStyle::GetBrush("Graph.ConnectorFeedback.Border")) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(NSLOCTEXT("ArrayDragDrop", "PlaceRowHere", "Place Row Here")) + ] + ]; + + Construct(); +} + +void FArrayRowDragDropOp::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) +{ + FDecoratedDragDropOp::OnDrop(bDropWasHandled, MouseEvent); + + TSharedPtr RowPtr = nullptr; + if (Row.IsValid()) + { + RowPtr = Row.Pin(); + // reset value + RowPtr->SetIsDragDrop(false); + } +} \ No newline at end of file diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h index 83e0e1a2a852..469e184a2297 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailSingleItemRow.h @@ -80,6 +80,8 @@ public: */ void Construct( const FArguments& InArgs, FDetailLayoutCustomization* InCustomization, bool bHasMultipleColumns, TSharedRef InOwnerTreeNode, const TSharedRef& InOwnerTableView ); + void SetIsDragDrop(bool bInIsDragDrop); + protected: virtual bool OnContextMenuOpening( FMenuBuilder& MenuBuilder ) override; private: @@ -100,6 +102,7 @@ private: void OnArrayDragEnter(const FDragDropEvent& DragDropEvent); void OnArrayDragLeave(const FDragDropEvent& DragDropEvent); FReply OnArrayDrop(const FDragDropEvent& DragDropEvent); + FReply OnArrayHeaderDrop(const FDragDropEvent& DragDropEvent); TSharedPtr GetCopyPastePropertyNode() const; private: @@ -109,6 +112,7 @@ private: FDetailColumnSizeData ColumnSizeData; bool bAllowFavoriteSystem; bool bIsHoveredDragTarget; + bool bIsDragDropObject; TSharedPtr SwappablePropertyNode; }; @@ -117,28 +121,12 @@ class FArrayRowDragDropOp : public FDecoratedDragDropOp public: DRAG_DROP_OPERATOR_TYPE(FArrayRowDragDropOp, FDecoratedDragDropOp) - FArrayRowDragDropOp(TSharedPtr InRow) - { - Row = InRow; - DecoratorWidget = SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Graph.ConnectorFeedback.Border")) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(NSLOCTEXT("ArrayDragDrop", "PlaceRowHere", "Place Row Here")) - ] - ]; - - Construct(); - }; + FArrayRowDragDropOp(TSharedPtr InRow); TSharedPtr DecoratorWidget; + virtual void OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) override; + virtual TSharedPtr GetDefaultDecorator() const override { return DecoratorWidget; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp index e7ba6f3a2c93..707c4c877894 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.cpp @@ -23,6 +23,7 @@ #include "Widgets/Input/SSearchBox.h" #include "Classes/EditorStyleSettings.h" #include "DetailLayoutHelpers.h" +#include "EditConditionParser.h" SDetailsViewBase::~SDetailsViewBase() { @@ -482,6 +483,16 @@ TSharedPtr SDetailsViewBase::GetThumbnailPool() const return ThumbnailPool; } +TSharedPtr SDetailsViewBase::GetEditConditionParser() const +{ + if (!EditConditionParser.IsValid()) + { + EditConditionParser = MakeShareable(new FEditConditionParser); + } + + return EditConditionParser; +} + void SDetailsViewBase::NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { OnFinishedChangingPropertiesDelegate.Broadcast(PropertyChangedEvent); @@ -1035,7 +1046,7 @@ void SDetailsViewBase::UpdateFilteredDetails() FDetailNodeList InitialRootNodeList; - NumVisbleTopLevelObjectNodes = 0; + NumVisibleTopLevelObjectNodes = 0; FRootPropertyNodeList& RootPropertyNodes = GetRootNodes(); if( GetDefault()->bShowAllAdvancedDetails ) @@ -1070,7 +1081,7 @@ void SDetailsViewBase::UpdateFilteredDetails() if (LayoutRoots.Num() > 0) { // A top level object nodes has a non-filtered away root so add one to the total number we have - ++NumVisbleTopLevelObjectNodes; + ++NumVisibleTopLevelObjectNodes; InitialRootNodeList.Append(LayoutRoots); } diff --git a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h index 0416baf0af92..77e9d016fc5a 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h +++ b/Engine/Source/Editor/PropertyEditor/Private/SDetailsViewBase.h @@ -86,7 +86,7 @@ public: , bIsLocked(false) , bHasOpenColorPicker(false) , bDisableCustomDetailLayouts( false ) - , NumVisbleTopLevelObjectNodes(0) + , NumVisibleTopLevelObjectNodes(0) { } @@ -107,10 +107,9 @@ public: virtual int32 GetNumVisibleTopLevelObjects() const override { - return NumVisbleTopLevelObjectNodes; + return NumVisibleTopLevelObjectNodes; } - /** @return The identifier for this details view, or NAME_None is this view is anonymous */ virtual FName GetIdentifier() const override { @@ -162,6 +161,7 @@ public: virtual void NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override; void RefreshTree() override; TSharedPtr GetThumbnailPool() const override; + TSharedPtr GetEditConditionParser() const override; TSharedPtr GetPropertyUtilities() override; void CreateColorPickerWindow(const TSharedRef< class FPropertyEditor >& PropertyEditor, bool bUseAlpha) override; virtual void UpdateSinglePropertyMap(TSharedPtr InRootPropertyNode, FDetailLayoutData& LayoutData, bool bIsExternal) override; @@ -422,7 +422,7 @@ protected: /** True if we want to skip generation of custom layouts for displayed object */ bool bDisableCustomDetailLayouts; - int32 NumVisbleTopLevelObjectNodes; + int32 NumVisibleTopLevelObjectNodes; /** Delegate for overriding the show modified filter */ FSimpleDelegate CustomFilterDelegate; @@ -431,4 +431,6 @@ protected: bool bCustomFilterActive; FText CustomFilterLabel; + + mutable TSharedPtr EditConditionParser; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp b/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp index 29626f50e3a4..169f6dfffa0b 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp @@ -11,6 +11,7 @@ #include "Presentation/PropertyEditor/PropertyEditor.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" +#include "EditConditionParser.h" #include "CategoryPropertyNode.h" #include "UserInterface/PropertyTree/PropertyTreeConstants.h" @@ -31,6 +32,7 @@ public: FPropertyUtilitiesTreeView( SPropertyTreeViewImpl& InView ) : View( InView ) { + EditConditionParser = MakeShareable(new FEditConditionParser()); } virtual class FNotifyHook* GetNotifyHook() const override @@ -95,8 +97,14 @@ public: { return false; } -private: + virtual TSharedPtr GetEditConditionParser() const + { + return EditConditionParser; + } + +private: + TSharedPtr EditConditionParser; SPropertyTreeViewImpl& View; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp b/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp index 94591a555e02..89418b1dde2b 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/SSingleProperty.cpp @@ -90,6 +90,11 @@ public: return false; } + virtual TSharedPtr GetEditConditionParser() const override + { + return nullptr; + } + private: TWeakPtr< SSingleProperty > View; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp index 468e1de143b1..440b78a434d4 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.cpp @@ -73,4 +73,9 @@ const TArray>& FPropertyDetailsUtilities::GetSelectedObj bool FPropertyDetailsUtilities::HasClassDefaultObject() const { return DetailsView.HasClassDefaultObject(); -} \ No newline at end of file +} + +TSharedPtr FPropertyDetailsUtilities::GetEditConditionParser() const +{ + return DetailsView.GetEditConditionParser(); +} diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h index 686765f9817a..6cdd4f0db3fc 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyDetails/PropertyDetailsUtilities.h @@ -26,6 +26,8 @@ public: virtual bool DontUpdateValueWhileEditing() const override; const TArray>& GetSelectedObjects() const override; virtual bool HasClassDefaultObject() const override; + virtual TSharedPtr GetEditConditionParser() const override; + private: IDetailsViewPrivate& DetailsView; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h index a578a1e6f375..6af487f52c68 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorArray.h @@ -11,6 +11,8 @@ #include "PropertyEditorHelpers.h" #include "UserInterface/PropertyEditor/PropertyEditorConstants.h" #include "Widgets/Text/STextBlock.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "SDropTarget.h" #define LOCTEXT_NAMESPACE "PropertyEditor" @@ -43,9 +45,17 @@ public: ChildSlot .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) [ - SNew( STextBlock ) - .Text( TextAttr ) - .Font( InArgs._Font ) + SNew(SDropTarget) + .OnDrop(this, &SPropertyEditorArray::OnDragDropTarget) + .OnAllowDrop(this, &SPropertyEditorArray::WillAddValidElements) + .OnIsRecognized(this, &SPropertyEditorArray::IsValidAssetDropOp) + .Content() + [ + SNew(STextBlock) + .Margin(FMargin(4.0f, 2.0f)) + .Text(TextAttr) + .Font(InArgs._Font) + ] ]; SetEnabled( TAttribute( this, &SPropertyEditorArray::CanEdit ) ); @@ -82,6 +92,85 @@ private: { return PropertyEditor.IsValid() ? !PropertyEditor->IsEditConst() : true; } + + FReply OnDragDropTarget(TSharedPtr InOperation) + { + UObjectProperty* ObjectProperty = nullptr; + if (const UArrayProperty* ArrayProperty = Cast(PropertyEditor->GetProperty())) + { + ObjectProperty = Cast(ArrayProperty->Inner); + } + + // Only try to add entries if we are dropping on an asset array + if (ObjectProperty && InOperation->IsOfType()) + { + TSharedPtr DragDropOp = StaticCastSharedPtr(InOperation); + if (DragDropOp.IsValid()) + { + for (FAssetData AssetData : DragDropOp->GetAssets()) + { + // if the type matches + if (ObjectProperty->PropertyClass == AssetData.GetClass()) + { + PropertyEditor->AddGivenItem(AssetData.ObjectPath.ToString()); + } + } + // Let this bubble up to the rest of the row + return FReply::Unhandled(); + + } + } + return FReply::Unhandled(); + } + + bool IsValidAssetDropOp(TSharedPtr InOperation) + { + UObjectProperty* ObjectProperty = nullptr; + if (const UArrayProperty* ArrayProperty = Cast(PropertyEditor->GetProperty())) + { + ObjectProperty = Cast(ArrayProperty->Inner); + } + + // Only try to add entries if we are dropping on an asset array + if (ObjectProperty && InOperation->IsOfType()) + { + return true; + } + return false; + } + + bool WillAddValidElements(TSharedPtr InOperation) + { + UObjectProperty* ObjectProperty = nullptr; + + if (const UArrayProperty* ArrayProperty = Cast(PropertyEditor->GetProperty())) + { + ObjectProperty = Cast(ArrayProperty->Inner); + } + + // Only try to add entries if we are dropping on an asset array + if (ObjectProperty && InOperation->IsOfType()) + { + bool bHasOnlyValidElements = true; + TSharedPtr DragDropOp = StaticCastSharedPtr(InOperation); + if (DragDropOp.IsValid()) + { + for (FAssetData AssetData : DragDropOp->GetAssets()) + { + // if the type matches + if (ObjectProperty->PropertyClass != AssetData.GetClass()) + { + bHasOnlyValidElements = false; + break; + } + } + } + return bHasOnlyValidElements; + } + + return false; + } + private: TSharedPtr PropertyEditor; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp index df06aa0f8a48..2e80168a3cf0 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorAsset.cpp @@ -680,7 +680,33 @@ void SPropertyEditorAsset::OnMenuOpenChanged(bool bOpen) bool SPropertyEditorAsset::IsFilteredActor( const AActor* const Actor ) const { - return Actor->IsA( ObjectClass ) && !Actor->IsChildActor(); + bool IsAllowed = Actor->IsA(ObjectClass) && !Actor->IsChildActor(); + + if (IsAllowed) + { + bool ClassFilterAllowed = false; + for (const UClass* Class : AllowedClassFilters) + { + if (Actor->GetClass()->IsChildOf(Class)) + { + ClassFilterAllowed = true; + break; + } + } + + for (const UClass* Class : DisallowedClassFilters) + { + if (Actor->GetClass()->IsChildOf(Class)) + { + ClassFilterAllowed = false; + break; + } + } + + IsAllowed = ClassFilterAllowed; + } + + return IsAllowed; } void SPropertyEditorAsset::CloseComboButton() diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp index 17d17d637695..3af35fdf87c8 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.cpp @@ -11,7 +11,6 @@ #include "ClassViewerModule.h" #include "ClassViewerFilter.h" - #define LOCTEXT_NAMESPACE "PropertyEditor" class FPropertyEditorClassFilter : public IClassViewerFilter @@ -26,7 +25,7 @@ public: /** Whether or not abstract classes are allowed. */ bool bAllowAbstract; - bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override + virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override { bool bMatchesFlags = !InClass->HasAnyClassFlags(CLASS_Hidden|CLASS_HideDropDown|CLASS_Deprecated) && (bAllowAbstract || !InClass->HasAnyClassFlags(CLASS_Abstract)); @@ -84,6 +83,8 @@ bool SPropertyEditorClass::Supports(const TSharedRef< class FPropertyEditor >& I void SPropertyEditorClass::Construct(const FArguments& InArgs, const TSharedPtr< class FPropertyEditor >& InPropertyEditor) { PropertyEditor = InPropertyEditor; + + CreateClassFilter(); if (PropertyEditor.IsValid()) { @@ -129,8 +130,6 @@ void SPropertyEditorClass::Construct(const FArguments& InArgs, const TSharedPtr< SelectedClass = InArgs._SelectedClass; OnSetClass = InArgs._OnSetClass; - - } SAssignNew(ComboButton, SComboButton) @@ -199,28 +198,36 @@ FText SPropertyEditorClass::GetDisplayValueAsString() const } -TSharedRef SPropertyEditorClass::GenerateClassPicker() +void SPropertyEditorClass::CreateClassFilter() { - FClassViewerInitializationOptions Options; - Options.bShowUnloadedBlueprints = true; - Options.bShowNoneOption = bAllowNone; + ClassViewerOptions.bShowBackgroundBorder = false; + ClassViewerOptions.bShowUnloadedBlueprints = true; + ClassViewerOptions.bShowNoneOption = bAllowNone; - if(PropertyEditor.IsValid()) + if (PropertyEditor.IsValid()) { - Options.PropertyHandle = PropertyEditor->GetPropertyHandle(); + ClassViewerOptions.PropertyHandle = PropertyEditor->GetPropertyHandle(); } - TSharedPtr ClassFilter = MakeShareable(new FPropertyEditorClassFilter); - Options.ClassFilter = ClassFilter; - ClassFilter->ClassPropertyMetaClass = MetaClass; - ClassFilter->InterfaceThatMustBeImplemented = RequiredInterface; - ClassFilter->bAllowAbstract = bAllowAbstract; - Options.bIsBlueprintBaseOnly = bIsBlueprintBaseOnly; - Options.bIsPlaceableOnly = bAllowOnlyPlaceable; - Options.NameTypeToDisplay = (bShowDisplayNames ? EClassViewerNameTypeToDisplay::DisplayName : EClassViewerNameTypeToDisplay::ClassName); - Options.DisplayMode = bShowTree ? EClassViewerDisplayMode::TreeView : EClassViewerDisplayMode::ListView; - Options.bAllowViewOptions = bShowViewOptions; + ClassViewerOptions.bIsBlueprintBaseOnly = bIsBlueprintBaseOnly; + ClassViewerOptions.bIsPlaceableOnly = bAllowOnlyPlaceable; + ClassViewerOptions.NameTypeToDisplay = (bShowDisplayNames ? EClassViewerNameTypeToDisplay::DisplayName : EClassViewerNameTypeToDisplay::ClassName); + ClassViewerOptions.DisplayMode = bShowTree ? EClassViewerDisplayMode::TreeView : EClassViewerDisplayMode::ListView; + ClassViewerOptions.bAllowViewOptions = bShowViewOptions; + TSharedPtr PropEdClassFilter = MakeShareable(new FPropertyEditorClassFilter); + ClassViewerOptions.ClassFilter = PropEdClassFilter; + + PropEdClassFilter->ClassPropertyMetaClass = MetaClass; + PropEdClassFilter->InterfaceThatMustBeImplemented = RequiredInterface; + PropEdClassFilter->bAllowAbstract = bAllowAbstract; + + ClassFilter = FModuleManager::LoadModuleChecked("ClassViewer").CreateClassFilter(ClassViewerOptions); + ClassFilterFuncs = FModuleManager::LoadModuleChecked("ClassViewer").CreateFilterFuncs(); +} + +TSharedRef SPropertyEditorClass::GenerateClassPicker() +{ FOnClassPicked OnPicked(FOnClassPicked::CreateRaw(this, &SPropertyEditorClass::OnClassPicked)); return SNew(SBox) @@ -231,7 +238,7 @@ TSharedRef SPropertyEditorClass::GenerateClassPicker() .AutoHeight() .MaxHeight(500) [ - FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, OnPicked) + FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(ClassViewerOptions, OnPicked) ] ]; } @@ -272,51 +279,60 @@ void SPropertyEditorClass::SendToObjects(const FString& NewValue) } } +static UObject* LoadDragDropObject(TSharedPtr UnloadedClassOp) +{ + FString AssetPath; + + // Find the class/blueprint path + if (UnloadedClassOp->HasAssets()) + { + AssetPath = UnloadedClassOp->GetAssets()[0].ObjectPath.ToString(); + } + else if (UnloadedClassOp->HasAssetPaths()) + { + AssetPath = UnloadedClassOp->GetAssetPaths()[0]; + } + + // Check to see if the asset can be found, otherwise load it. + UObject* Object = FindObject(nullptr, *AssetPath); + if (Object == nullptr) + { + // Load the package. + GWarn->BeginSlowTask(LOCTEXT("OnDrop_LoadPackage", "Fully Loading Package For Drop"), true, false); + + Object = LoadObject(nullptr, *AssetPath); + + GWarn->EndSlowTask(); + } + + return Object; +} + void SPropertyEditorClass::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { TSharedPtr UnloadedClassOp = DragDropEvent.GetOperationAs(); if (UnloadedClassOp.IsValid()) { - bool bAllAssetWereLoaded = true; + UObject* Object = LoadDragDropObject(UnloadedClassOp); - FString AssetPath; - FString PathName; - - // Find the class/blueprint path - if (UnloadedClassOp->HasAssets()) - { - AssetPath = UnloadedClassOp->GetAssets()[0].ObjectPath.ToString(); - } - else if (UnloadedClassOp->HasAssetPaths()) - { - AssetPath = UnloadedClassOp->GetAssetPaths()[0]; - } - - // Check to see if the asset can be found, otherwise load it. - UObject* Object = FindObject(nullptr, *AssetPath); - if (Object == nullptr) - { - // Load the package. - GWarn->BeginSlowTask(LOCTEXT("OnDrop_LoadPackage", "Fully Loading Package For Drop"), true, false); - - Object = LoadObject(nullptr, *AssetPath); - - GWarn->EndSlowTask(); - } + bool bOK = false; if (UClass* Class = Cast(Object)) { - // This was pointing to a class directly - UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))); + bOK = ClassFilter->IsClassAllowed(ClassViewerOptions, Class, ClassFilterFuncs.ToSharedRef()); } else if (UBlueprint* Blueprint = Cast(Object)) { if (Blueprint->GeneratedClass) { - // This was pointing to a blueprint, get generated class - UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))); + bOK = ClassFilter->IsClassAllowed(ClassViewerOptions, Blueprint->GeneratedClass, ClassFilterFuncs.ToSharedRef()); } } + + if (bOK) + { + UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK"))); + } else { UnloadedClassOp->SetToolTip(FText::GetEmpty(), FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"))); @@ -350,43 +366,25 @@ FReply SPropertyEditorClass::OnDrop(const FGeometry& MyGeometry, const FDragDrop TSharedPtr UnloadedClassOp = DragDropEvent.GetOperationAs(); if (UnloadedClassOp.IsValid()) { - bool bAllAssetWereLoaded = true; - - FString AssetPath; - - // Find the class/blueprint path - if (UnloadedClassOp->HasAssets()) - { - AssetPath = UnloadedClassOp->GetAssets()[0].ObjectPath.ToString(); - } - else if (UnloadedClassOp->HasAssetPaths()) - { - AssetPath = UnloadedClassOp->GetAssetPaths()[0]; - } - - // Check to see if the asset can be found, otherwise load it. - UObject* Object = FindObject(nullptr, *AssetPath); - if(Object == nullptr) - { - // Load the package. - GWarn->BeginSlowTask(LOCTEXT("OnDrop_LoadPackage", "Fully Loading Package For Drop"), true, false); - - Object = LoadObject(nullptr, *AssetPath); - - GWarn->EndSlowTask(); - } + UObject* Object = LoadDragDropObject(UnloadedClassOp); if (UClass* Class = Cast(Object)) { - // This was pointing to a class directly - SendToObjects(Class->GetPathName()); + if (ClassFilter->IsClassAllowed(ClassViewerOptions, Class, ClassFilterFuncs.ToSharedRef())) + { + // This was pointing to a class directly + SendToObjects(Class->GetPathName()); + } } else if (UBlueprint* Blueprint = Cast(Object)) { if (Blueprint->GeneratedClass) { - // This was pointing to a blueprint, get generated class - SendToObjects(Blueprint->GeneratedClass->GetPathName()); + if (ClassFilter->IsClassAllowed(ClassViewerOptions, Blueprint->GeneratedClass, ClassFilterFuncs.ToSharedRef())) + { + // This was pointing to a blueprint, get generated class + SendToObjects(Blueprint->GeneratedClass->GetPathName()); + } } } diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h index 5ce4a17885ea..f4624717500f 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorClass.h @@ -14,6 +14,10 @@ #include "Presentation/PropertyEditor/PropertyEditor.h" #include "UserInterface/PropertyEditor/PropertyEditorConstants.h" #include "PropertyCustomizationHelpers.h" +#include "ClassViewerModule.h" + +class IClassViewerFilter; +class FClassViewerFilterFuncs; /** * A widget used to edit Class properties (UClass type properties). @@ -69,6 +73,8 @@ private: virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override; virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + TSharedRef ConstructClassViewer(); + /** * Generates a class picker with a filter to show only classes allowed to be selected. * @@ -95,6 +101,15 @@ private: /** Used when the property deals with Classes and will display a Class Picker. */ TSharedPtr ComboButton; + /** Class filter that the class viewer is using. */ + TSharedPtr ClassFilter; + + /** Filter functions for class viewer. */ + TSharedPtr ClassFilterFuncs; + + /** Options used for creating the class viewer. */ + FClassViewerInitializationOptions ClassViewerOptions; + /** The meta class that the selected class must be a child-of */ const UClass* MetaClass; /** An interface that the selected class must implement */ @@ -118,4 +133,6 @@ private: TAttribute SelectedClass; /** Delegate used to set the currently selected class (required if PropertyEditor == null) */ FOnSetClass OnSetClass; + + void CreateClassFilter(); }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp index 4cf1b6a51911..5ff07902798d 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp @@ -149,7 +149,7 @@ bool SPropertyEditorEditInline::Supports( const FPropertyNode* InTreeNode, int32 return InTreeNode && InTreeNode->HasNodeFlags(EPropertyNodeFlags::EditInlineNew) && InTreeNode->FindObjectItemParent() - && !InTreeNode->IsEditConst(); + && !InTreeNode->IsPropertyConst(); } bool SPropertyEditorEditInline::Supports( const TSharedRef< class FPropertyEditor >& InPropertyEditor ) @@ -167,6 +167,7 @@ bool SPropertyEditorEditInline::IsClassAllowed( UClass* CheckClass, bool bAllowA TSharedRef SPropertyEditorEditInline::GenerateClassPicker() { FClassViewerInitializationOptions Options; + Options.bShowBackgroundBorder = false; Options.bShowUnloadedBlueprints = true; Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::DisplayName; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp index b7db89a7e017..989defcc2d1a 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorTableRow.cpp @@ -194,7 +194,7 @@ const FSlateBrush* SPropertyEditorTableRow::OnGetFavoriteImage() const void SPropertyEditorTableRow::OnEditConditionCheckChanged( ECheckBoxState CheckState ) { - PropertyEditor->SetEditConditionState( CheckState == ECheckBoxState::Checked ); + PropertyEditor->ToggleEditConditionState(); } ECheckBoxState SPropertyEditorTableRow::OnGetEditConditionCheckState() const diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp index c5de2e5515e5..4e7187a33131 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyMenuActorPicker.cpp @@ -95,11 +95,7 @@ void SPropertyMenuActorPicker::Construct( const FArguments& InArgs ) .WidthOverride(PropertyEditorAssetConstants::SceneOutlinerWindowSize.X) .HeightOverride(PropertyEditorAssetConstants::SceneOutlinerWindowSize.Y) [ - SNew( SBorder ) - .BorderImage( FEditorStyle::GetBrush("Menu.Background") ) - [ - SceneOutlinerModule.CreateSceneOutliner(InitOptions, FOnActorPicked::CreateSP(this, &SPropertyMenuActorPicker::OnActorSelected)) - ] + SceneOutlinerModule.CreateSceneOutliner(InitOptions, FOnActorPicked::CreateSP(this, &SPropertyMenuActorPicker::OnActorSelected)) ]; MenuBuilder.AddWidget(MenuContent.ToSharedRef(), FText::GetEmpty(), true); diff --git a/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h b/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h index 00ed0b47849d..ccb64a2465a0 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h +++ b/Engine/Source/Editor/PropertyEditor/Public/IDetailsView.h @@ -131,6 +131,7 @@ public: , bForceHiddenPropertyVisibility(false) , bShowKeyablePropertiesOption(true) , bShowAnimatedPropertiesOption(true) + , bShowCustomFilterOption(false) , ColumnWidth(.65f) { } @@ -266,6 +267,8 @@ public: virtual void SetExtensionHandler(TSharedPtr InExtensionandler) = 0; + virtual TSharedPtr GetExtensionHandler() = 0; + /** * @return true if property editing is enabled (based on the FIsPropertyEditingEnabled delegate) */ diff --git a/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h b/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h index 4da259dca59d..2593c6108a71 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h +++ b/Engine/Source/Editor/PropertyEditor/Public/IPropertyUtilities.h @@ -5,6 +5,7 @@ #include "AssetThumbnail.h" struct FPropertyChangedEvent; +class FEditConditionParser; /** * Settings for property editor widgets that call up to the base container for the widgets @@ -28,4 +29,5 @@ public: virtual const TArray>& GetSelectedObjects() const = 0; /** If a customization standalone widget is used, the value should be update only once, when its window is closed */ virtual bool DontUpdateValueWhileEditing() const = 0; + virtual TSharedPtr GetEditConditionParser() const = 0; }; diff --git a/Engine/Source/Editor/PropertyEditor/Public/MaterialList.h b/Engine/Source/Editor/PropertyEditor/Public/MaterialList.h new file mode 100644 index 000000000000..cc2547563836 --- /dev/null +++ b/Engine/Source/Editor/PropertyEditor/Public/MaterialList.h @@ -0,0 +1,219 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDetailCustomNodeBuilder.h" +#include "DetailWidgetRow.h" +#include "Materials/MaterialInterface.h" + +class FMaterialItemView; +class FMaterialListBuilder; +class IDetailChildrenBuilder; +class IDetailLayoutBuilder; +class IMaterialListBuilder; + +/** + * Delegate called when we need to get new materials for the list + */ +DECLARE_DELEGATE_OneParam(FOnGetMaterials, IMaterialListBuilder&); + +/** + * Delegate called when a user changes the material + */ +DECLARE_DELEGATE_FourParams( FOnMaterialChanged, UMaterialInterface*, UMaterialInterface*, int32, bool ); + +DECLARE_DELEGATE_RetVal_TwoParams( TSharedRef, FOnGenerateWidgetsForMaterial, UMaterialInterface*, int32 ); + +DECLARE_DELEGATE_TwoParams( FOnResetMaterialToDefaultClicked, UMaterialInterface*, int32 ); + +DECLARE_DELEGATE_RetVal(bool, FOnMaterialListDirty); + +DECLARE_DELEGATE_RetVal(bool, FOnCanCopyMaterialList); +DECLARE_DELEGATE(FOnCopyMaterialList); +DECLARE_DELEGATE(FOnPasteMaterialList); + +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanCopyMaterialItem, int32); +DECLARE_DELEGATE_OneParam(FOnCopyMaterialItem, int32); +DECLARE_DELEGATE_OneParam(FOnPasteMaterialItem, int32); + +struct FMaterialListDelegates +{ + FMaterialListDelegates() + : OnGetMaterials() + , OnMaterialChanged() + , OnGenerateCustomNameWidgets() + , OnGenerateCustomMaterialWidgets() + , OnResetMaterialToDefaultClicked() + {} + + /** Delegate called to populate the list with materials */ + FOnGetMaterials OnGetMaterials; + /** Delegate called when a user changes the material */ + FOnMaterialChanged OnMaterialChanged; + /** Delegate called to generate custom widgets under the name of in the left column of a details panel*/ + FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; + /** Delegate called to generate custom widgets under each material */ + FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; + /** Delegate called when a material list item should be reset to default */ + FOnResetMaterialToDefaultClicked OnResetMaterialToDefaultClicked; + /** Delegate called when we tick the material list to know if the list is dirty*/ + FOnMaterialListDirty OnMaterialListDirty; + + /** Delegate called Copying a material list */ + FOnCopyMaterialList OnCopyMaterialList; + /** Delegate called to know if we can copy a material list */ + FOnCanCopyMaterialList OnCanCopyMaterialList; + /** Delegate called Pasting a material list */ + FOnPasteMaterialList OnPasteMaterialList; + + /** Delegate called Copying a material item */ + FOnCopyMaterialItem OnCopyMaterialItem; + /** Delegate called to know if we can copy a material item */ + FOnCanCopyMaterialItem OnCanCopyMaterialItem; + /** Delegate called Pasting a material item */ + FOnPasteMaterialItem OnPasteMaterialItem; +}; + +/** + * Builds up a list of unique materials while creating some information about the materials + */ +class IMaterialListBuilder +{ +public: + + /** Virtual destructor. */ + virtual ~IMaterialListBuilder(){}; + + /** + * Adds a new material to the list + * + * @param SlotIndex The slot (usually mesh element index) where the material is located on the component. + * @param Material The material being used. + * @param bCanBeReplced Whether or not the material can be replaced by a user. + */ + virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) = 0; +}; + + +/** + * A Material item in a material list slot + */ +struct FMaterialListItem +{ + /** Material being used */ + TWeakObjectPtr Material; + + /** Slot on a component where this material is at (mesh element) */ + int32 SlotIndex; + + /** Whether or not this material can be replaced by a new material */ + bool bCanBeReplaced; + + FMaterialListItem( UMaterialInterface* InMaterial = NULL, uint32 InSlotIndex = 0, bool bInCanBeReplaced = false ) + : Material( InMaterial ) + , SlotIndex( InSlotIndex ) + , bCanBeReplaced( bInCanBeReplaced ) + {} + + friend uint32 GetTypeHash( const FMaterialListItem& InItem ) + { + return GetTypeHash( InItem.Material ) + InItem.SlotIndex ; + } + + bool operator==( const FMaterialListItem& Other ) const + { + return Material == Other.Material && SlotIndex == Other.SlotIndex ; + } + + bool operator!=( const FMaterialListItem& Other ) const + { + return !(*this == Other); + } +}; + + +class FMaterialList + : public IDetailCustomNodeBuilder + , public TSharedFromThis +{ +public: + PROPERTYEDITOR_API FMaterialList( IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& MaterialListDelegates, bool bInAllowCollapse = false, bool bInShowUsedTextures = true, bool bInDisplayCompactSize = false); + + /** + * @return true if materials are being displayed. + */ + bool IsDisplayingMaterials() const { return true; } + +private: + + /** + * Called when a user expands all materials in a slot. + * + * @param SlotIndex The index of the slot being expanded. + */ + void OnDisplayMaterialsForElement( int32 SlotIndex ); + + /** + * Called when a user hides all materials in a slot. + * + * @param SlotIndex The index of the slot being hidden. + */ + void OnHideMaterialsForElement( int32 SlotIndex ); + + /** IDetailCustomNodeBuilder interface */ + virtual void SetOnRebuildChildren( FSimpleDelegate InOnRebuildChildren ) override { OnRebuildChildren = InOnRebuildChildren; } + virtual bool RequiresTick() const override { return true; } + virtual void Tick( float DeltaTime ) override; + virtual void GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) override; + virtual void GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) override; + virtual FName GetName() const override { return NAME_None; } + virtual bool InitiallyCollapsed() const override { return bAllowCollpase; } + + /** + * Adds a new material item to the list + * + * @param Row The row to add the item to + * @param CurrentSlot The slot id of the material + * @param Item The material item to add + * @param bDisplayLink If a link to the material should be displayed instead of the actual item (for multiple materials) + */ + void AddMaterialItem(FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink); + +private: + bool OnCanCopyMaterialList() const; + void OnCopyMaterialList(); + void OnPasteMaterialList(); + + bool OnCanCopyMaterialItem(int32 CurrentSlot) const; + void OnCopyMaterialItem(int32 CurrentSlot); + void OnPasteMaterialItem(int32 CurrentSlot); + + /** Delegates for the material list */ + FMaterialListDelegates MaterialListDelegates; + + /** Called to rebuild the children of the detail tree */ + FSimpleDelegate OnRebuildChildren; + + /** Parent detail layout this list is in */ + IDetailLayoutBuilder& DetailLayoutBuilder; + + /** Set of all unique displayed materials */ + TArray< FMaterialListItem > DisplayedMaterials; + + /** Set of all materials currently in view (may be less than DisplayedMaterials) */ + TArray< TSharedRef > ViewedMaterials; + + /** Set of all expanded slots */ + TSet ExpandedSlots; + + /** Material list builder used to generate materials */ + TSharedRef MaterialListBuilder; + + /** Allow Collapse of material header row. Right now if you allow collapse, it will initially collapse. */ + bool bAllowCollpase; + /** Whether or not to use the used textures menu for each material entry */ + bool bShowUsedTextures; + /** Whether or not to display a compact form of material entry*/ + bool bDisplayCompactSize; +}; + diff --git a/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h b/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h index ed403bf6d78c..3f4953f2453c 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h +++ b/Engine/Source/Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h @@ -20,16 +20,12 @@ #include "SceneDepthPickerMode.h" #include "IDetailPropertyRow.h" - class AActor; struct FAssetData; class FAssetThumbnailPool; -class FMaterialItemView; -class FMaterialListBuilder; class FPropertyEditor; class IDetailChildrenBuilder; class IDetailLayoutBuilder; -class IMaterialListBuilder; class SPropertyEditorAsset; class SPropertyEditorClass; class UFactory; @@ -40,7 +36,6 @@ namespace SceneOutliner struct FOutlinerFilters; } - DECLARE_DELEGATE_OneParam(FOnAssetSelected, const FAssetData& /*AssetData*/); DECLARE_DELEGATE_RetVal_OneParam(bool, FOnShouldSetAsset, const FAssetData& /*AssetData*/); DECLARE_DELEGATE_RetVal_OneParam(bool, FOnShouldFilterAsset, const FAssetData& /*AssetData*/); @@ -232,7 +227,7 @@ private: * Represents a widget that can display a UProperty * With the ability to customize the look of the property */ -class SProperty +class PROPERTYEDITOR_VTABLE SProperty : public SCompoundWidget { public: @@ -418,214 +413,6 @@ private: bool bDisplayElementNum; }; - -/** - * Delegate called when we need to get new materials for the list - */ -DECLARE_DELEGATE_OneParam(FOnGetMaterials, IMaterialListBuilder&); - -/** - * Delegate called when a user changes the material - */ -DECLARE_DELEGATE_FourParams( FOnMaterialChanged, UMaterialInterface*, UMaterialInterface*, int32, bool ); - -DECLARE_DELEGATE_RetVal_TwoParams( TSharedRef, FOnGenerateWidgetsForMaterial, UMaterialInterface*, int32 ); - -DECLARE_DELEGATE_TwoParams( FOnResetMaterialToDefaultClicked, UMaterialInterface*, int32 ); - -DECLARE_DELEGATE_RetVal(bool, FOnMaterialListDirty); - -DECLARE_DELEGATE_RetVal(bool, FOnCanCopyMaterialList); -DECLARE_DELEGATE(FOnCopyMaterialList); -DECLARE_DELEGATE(FOnPasteMaterialList); - -DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCanCopyMaterialItem, int32); -DECLARE_DELEGATE_OneParam(FOnCopyMaterialItem, int32); -DECLARE_DELEGATE_OneParam(FOnPasteMaterialItem, int32); - -struct FMaterialListDelegates -{ - FMaterialListDelegates() - : OnGetMaterials() - , OnMaterialChanged() - , OnGenerateCustomNameWidgets() - , OnGenerateCustomMaterialWidgets() - , OnResetMaterialToDefaultClicked() - {} - - /** Delegate called to populate the list with materials */ - FOnGetMaterials OnGetMaterials; - /** Delegate called when a user changes the material */ - FOnMaterialChanged OnMaterialChanged; - /** Delegate called to generate custom widgets under the name of in the left column of a details panel*/ - FOnGenerateWidgetsForMaterial OnGenerateCustomNameWidgets; - /** Delegate called to generate custom widgets under each material */ - FOnGenerateWidgetsForMaterial OnGenerateCustomMaterialWidgets; - /** Delegate called when a material list item should be reset to default */ - FOnResetMaterialToDefaultClicked OnResetMaterialToDefaultClicked; - /** Delegate called when we tick the material list to know if the list is dirty*/ - FOnMaterialListDirty OnMaterialListDirty; - - /** Delegate called Copying a material list */ - FOnCopyMaterialList OnCopyMaterialList; - /** Delegate called to know if we can copy a material list */ - FOnCanCopyMaterialList OnCanCopyMaterialList; - /** Delegate called Pasting a material list */ - FOnPasteMaterialList OnPasteMaterialList; - - /** Delegate called Copying a material item */ - FOnCopyMaterialItem OnCopyMaterialItem; - /** Delegate called to know if we can copy a material item */ - FOnCanCopyMaterialItem OnCanCopyMaterialItem; - /** Delegate called Pasting a material item */ - FOnPasteMaterialItem OnPasteMaterialItem; -}; - - -/** - * Builds up a list of unique materials while creating some information about the materials - */ -class IMaterialListBuilder -{ -public: - - /** Virtual destructor. */ - virtual ~IMaterialListBuilder(){}; - - /** - * Adds a new material to the list - * - * @param SlotIndex The slot (usually mesh element index) where the material is located on the component. - * @param Material The material being used. - * @param bCanBeReplced Whether or not the material can be replaced by a user. - */ - virtual void AddMaterial( uint32 SlotIndex, UMaterialInterface* Material, bool bCanBeReplaced ) = 0; -}; - - -/** - * A Material item in a material list slot - */ -struct FMaterialListItem -{ - /** Material being used */ - TWeakObjectPtr Material; - - /** Slot on a component where this material is at (mesh element) */ - int32 SlotIndex; - - /** Whether or not this material can be replaced by a new material */ - bool bCanBeReplaced; - - FMaterialListItem( UMaterialInterface* InMaterial = NULL, uint32 InSlotIndex = 0, bool bInCanBeReplaced = false ) - : Material( InMaterial ) - , SlotIndex( InSlotIndex ) - , bCanBeReplaced( bInCanBeReplaced ) - {} - - friend uint32 GetTypeHash( const FMaterialListItem& InItem ) - { - return GetTypeHash( InItem.Material ) + InItem.SlotIndex ; - } - - bool operator==( const FMaterialListItem& Other ) const - { - return Material == Other.Material && SlotIndex == Other.SlotIndex ; - } - - bool operator!=( const FMaterialListItem& Other ) const - { - return !(*this == Other); - } -}; - - -class FMaterialList - : public IDetailCustomNodeBuilder - , public TSharedFromThis -{ -public: - PROPERTYEDITOR_API FMaterialList( IDetailLayoutBuilder& InDetailLayoutBuilder, FMaterialListDelegates& MaterialListDelegates, bool bInAllowCollapse = false, bool bInShowUsedTextures = true, bool bInDisplayCompactSize = false); - - /** - * @return true if materials are being displayed. - */ - bool IsDisplayingMaterials() const { return true; } - -private: - - /** - * Called when a user expands all materials in a slot. - * - * @param SlotIndex The index of the slot being expanded. - */ - void OnDisplayMaterialsForElement( int32 SlotIndex ); - - /** - * Called when a user hides all materials in a slot. - * - * @param SlotIndex The index of the slot being hidden. - */ - void OnHideMaterialsForElement( int32 SlotIndex ); - - /** IDetailCustomNodeBuilder interface */ - virtual void SetOnRebuildChildren( FSimpleDelegate InOnRebuildChildren ) override { OnRebuildChildren = InOnRebuildChildren; } - virtual bool RequiresTick() const override { return true; } - virtual void Tick( float DeltaTime ) override; - virtual void GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) override; - virtual void GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) override; - virtual FName GetName() const override { return NAME_None; } - virtual bool InitiallyCollapsed() const override { return bAllowCollpase; } - - /** - * Adds a new material item to the list - * - * @param Row The row to add the item to - * @param CurrentSlot The slot id of the material - * @param Item The material item to add - * @param bDisplayLink If a link to the material should be displayed instead of the actual item (for multiple materials) - */ - void AddMaterialItem(FDetailWidgetRow& Row, int32 CurrentSlot, const FMaterialListItem& Item, bool bDisplayLink); - -private: - bool OnCanCopyMaterialList() const; - void OnCopyMaterialList(); - void OnPasteMaterialList(); - - bool OnCanCopyMaterialItem(int32 CurrentSlot) const; - void OnCopyMaterialItem(int32 CurrentSlot); - void OnPasteMaterialItem(int32 CurrentSlot); - - /** Delegates for the material list */ - FMaterialListDelegates MaterialListDelegates; - - /** Called to rebuild the children of the detail tree */ - FSimpleDelegate OnRebuildChildren; - - /** Parent detail layout this list is in */ - IDetailLayoutBuilder& DetailLayoutBuilder; - - /** Set of all unique displayed materials */ - TArray< FMaterialListItem > DisplayedMaterials; - - /** Set of all materials currently in view (may be less than DisplayedMaterials) */ - TArray< TSharedRef > ViewedMaterials; - - /** Set of all expanded slots */ - TSet ExpandedSlots; - - /** Material list builder used to generate materials */ - TSharedRef MaterialListBuilder; - - /** Allow Collapse of material header row. Right now if you allow collapse, it will initially collapse. */ - bool bAllowCollpase; - /** Whether or not to use the used textures menu for each material entry */ - bool bShowUsedTextures; - /** Whether or not to display a compact form of material entry*/ - bool bDisplayCompactSize; -}; - - /** * Helper class to create a material slot name widget for material lists */ diff --git a/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp b/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp index 0604d7340a48..f47696bdb1bb 100644 --- a/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp +++ b/Engine/Source/Editor/SceneOutliner/Private/SOutlinerTreeView.cpp @@ -33,7 +33,7 @@ namespace SceneOutliner else if (Operation->IsOfType()) { auto* DecoratedOp = static_cast(Operation); - DecoratedOp->SetToolTip(ValidationInfo.ValidationText, Icon); + DecoratedOp->SetToolTip(LOCTEXT("SceneOutlinerInvalidDragDropOp", "Invalid drag and drop operation, Drag into the Viewport."), Icon); } } } @@ -134,14 +134,9 @@ namespace SceneOutliner FFolderDropTarget DropTarget(NAME_None); auto Reply = HandleDrop(SceneOutlinerWeak, DragDropEvent, DropTarget, ValidationInfo); - - if (Reply.IsEventHandled()) - { - UpdateOperationDecorator(DragDropEvent, ValidationInfo); - return FReply::Handled(); - } - - return FReply::Unhandled(); + UpdateOperationDecorator(DragDropEvent, ValidationInfo); + + return Reply; } void SOutlinerTreeView::OnDragLeave(const FDragDropEvent& DragDropEvent) diff --git a/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp b/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp index 602bee05278e..54721d60985a 100644 --- a/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp +++ b/Engine/Source/Editor/SceneOutliner/Private/SSceneOutliner.cpp @@ -118,7 +118,7 @@ namespace SceneOutliner TSharedPtr< FOutlinerFilter > CreateHideTemporaryActorsFilter() { return MakeShareable( new FOutlinerPredicateFilter( FActorFilterPredicate::CreateStatic( []( const AActor* InActor ){ - return (InActor->GetWorld() && InActor->GetWorld()->WorldType != EWorldType::PIE) || GEditor->ObjectsThatExistInEditorWorld.Get(InActor); + return ((InActor->GetWorld() && InActor->GetWorld()->WorldType != EWorldType::PIE) || GEditor->ObjectsThatExistInEditorWorld.Get(InActor)) && !InActor->HasAnyFlags(EObjectFlags::RF_Transient); } ), EDefaultFilterBehaviour::Pass ) ); } @@ -1163,8 +1163,7 @@ namespace SceneOutliner } // If we are allowing intermediate sorts and met the conditions, or this is the final sort after all ops are complete - if ((bMadeAnySignificantChanges && !SharedData->bRepresentingPlayWorld && !bDisableIntermediateSorting) || - bFinalSort) + if ((bMadeAnySignificantChanges && !bDisableIntermediateSorting) || bFinalSort) { RequestSort(); } diff --git a/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp b/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp index eccb0640fac7..20eb2d8a37b3 100644 --- a/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp +++ b/Engine/Source/Editor/Sequencer/Private/SSequencer.cpp @@ -381,7 +381,7 @@ void SSequencer::Construct(const FArguments& InArgs, TSharedRef InSe } TSharedRef ScrollBar = SNew(SScrollBar) - .Thickness(FVector2D(5.0f, 5.0f)); + .Thickness(FVector2D(9.0f, 9.0f)); SAssignNew(TrackOutliner, SSequencerTrackOutliner); SAssignNew(TrackArea, SSequencerTrackArea, TimeSliderControllerRef, InSequencer); SAssignNew(TreeView, SSequencerTreeView, InSequencer->GetNodeTree(), TrackArea.ToSharedRef()) diff --git a/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h b/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h index 3e18c50cefc9..3f366db2366a 100644 --- a/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h +++ b/Engine/Source/Editor/Sequencer/Public/ISequencerSection.h @@ -43,7 +43,7 @@ namespace SequencerSectionConstants /** * Interface that should be implemented for the UI portion of a section */ -class ISequencerSection +class SEQUENCER_VTABLE ISequencerSection { public: virtual ~ISequencerSection(){} @@ -187,7 +187,7 @@ public: virtual void SlipSection(FFrameNumber SlipTime) {} }; -class FSequencerSection : public ISequencerSection +class SEQUENCER_VTABLE FSequencerSection : public ISequencerSection { public: FSequencerSection(UMovieSceneSection& InSection) @@ -205,4 +205,4 @@ public: protected: TWeakObjectPtr WeakSection; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp index 1e7f8f9b447f..fe705315b752 100644 --- a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp +++ b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlHistory.cpp @@ -655,6 +655,7 @@ public: void Construct( const FArguments& InArgs ) { AddHistoryInfo(InArgs._SourceControlStates.Get()); + ParentWindow = InArgs._ParentWindow.Get(); TSharedRef HeaderRow = SNew(SHeaderRow); @@ -718,10 +719,25 @@ public: { MainHistoryListView->SetItemExpansion(HistoryCollection[i],true); } + + ParentWindow.Pin()->SetWidgetToFocusOnActivate(MainHistoryListView); } private: + /** Used to intercept Escape key press, and interpret it as a close event */ + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override + { + // Pressing escape returns as if the user closed the window + if (InKeyEvent.GetKey() == EKeys::Escape) + { + ParentWindow.Pin()->RequestDestroyWindow(); + return FReply::Handled(); + } + + return FReply::Unhandled(); + } + /** * Constructs the "Additional Info" panel that displays specific revision info */ @@ -1426,6 +1442,9 @@ private: /** The last selected revision item; Displayed in the "additional information" subpanel */ TWeakPtr LastSelectedRevisionItem; + + /** Pointer to the parent window */ + TWeakPtr ParentWindow; }; void FSourceControlWindows::DisplayRevisionHistory( const TArray& InPackageNames ) diff --git a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp index 7d6f0f56c43e..1be3a625058f 100644 --- a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp +++ b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.cpp @@ -69,6 +69,12 @@ TSharedRef SSourceControlSubmitListRow::GenerateWidgetForColumn(const F return SNullWidget::NullWidget; } +FText SSourceControlSubmitWidget::SavedChangeListDescription; + +SSourceControlSubmitWidget::~SSourceControlSubmitWidget() +{ + SavedChangeListDescription = ChangeListDescriptionTextCtrl->GetText(); +} void SSourceControlSubmitWidget::Construct(const FArguments& InArgs) { @@ -133,6 +139,7 @@ void SSourceControlSubmitWidget::Construct(const FArguments& InArgs) [ SAssignNew( ChangeListDescriptionTextCtrl, SMultiLineEditableTextBox ) .SelectAllTextWhenFocused( true ) + .Text(SavedChangeListDescription) .AutoWrapText( true ) ] ] @@ -384,6 +391,9 @@ void SSourceControlSubmitWidget::OnToggleSelectedCheckBox(ECheckBoxState InNewSt void SSourceControlSubmitWidget::FillChangeListDescription(FChangeListDescription& OutDesc) { OutDesc.Description = ChangeListDescriptionTextCtrl->GetText(); + + ChangeListDescriptionTextCtrl->SetText(FText()); // Don't save description on Submit + OutDesc.FilesForAdd.Empty(); OutDesc.FilesForSubmit.Empty(); diff --git a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h index f7426984f2fa..6106b4392d23 100644 --- a/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h +++ b/Engine/Source/Editor/SourceControlWindows/Private/SSourceControlSubmit.h @@ -99,10 +99,7 @@ public: SLATE_END_ARGS() - /** Constructor */ - SSourceControlSubmitWidget() - { - } + ~SSourceControlSubmitWidget(); /** Constructs the widget */ void Construct(const FArguments& InArgs); @@ -216,6 +213,9 @@ private: /** Currently selected sorting mode */ EColumnSortMode::Type SortMode; + + /** Submit Description saved when the widget is destroyed if canceled */ + static FText SavedChangeListDescription; }; class SSourceControlSubmitListRow : public SMultiColumnTableRow> diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp index 019c3f0ee67f..010a63d4612e 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp @@ -2140,6 +2140,19 @@ void FStaticMeshEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyC { RefreshTool(); } + else if (PropertyChangedEvent.GetPropertyName() == TEXT("CollisionResponses")) + { + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == StaticMesh) + { + StaticMeshComponent->UpdateCollisionFromStaticMesh(); + StaticMeshComponent->MarkRenderTransformDirty(); + } + } + } + } } @@ -2232,8 +2245,10 @@ TStatId FStaticMeshEditor::GetStatId() const bool FStaticMeshEditor::CanRemoveUVChannel() { - // Can remove UV channel if there's one that is currently being selected and displayed - return Viewport->GetViewportClient().IsDrawUVOverlayChecked(); + // Can remove UV channel if there's one that is currently being selected and displayed, + // and the current LOD has more than one UV channel + return Viewport->GetViewportClient().IsDrawUVOverlayChecked() && + StaticMesh->GetNumUVChannels(GetCurrentLODIndex()) > 1; } void FStaticMeshEditor::RemoveCurrentUVChannel() diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp index 7cd7eea2493b..d050c2cc5976 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditorTools.cpp @@ -23,6 +23,7 @@ #include "StaticMeshResources.h" #include "StaticMeshEditor.h" #include "PropertyCustomizationHelpers.h" +#include "MaterialList.h" #include "PhysicsEngine/BodySetup.h" #include "FbxMeshUtils.h" #include "Widgets/Input/SVectorInputBox.h" @@ -627,6 +628,7 @@ void FMeshBuildSettingsLayout::GenerateChildContent( IDetailChildrenBuilder& Chi .Z(this, &FMeshBuildSettingsLayout::GetBuildScaleZ) .bColorAxisLabels(false) .AllowResponsiveLayout(true) + .AllowSpin(false) .OnXCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleXChanged) .OnYCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleYChanged) .OnZCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleZChanged) diff --git a/Engine/Source/Editor/UATHelper/UATHelperModule.cpp b/Engine/Source/Editor/UATHelper/UATHelperModule.cpp index 19d4d9afcefb..138e0e4851b4 100644 --- a/Engine/Source/Editor/UATHelper/UATHelperModule.cpp +++ b/Engine/Source/Editor/UATHelper/UATHelperModule.cpp @@ -449,6 +449,10 @@ public: ); TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + if(NotificationItemPtr.IsValid()) + { + NotificationItemPtr.Pin().Get()->Fadeout(); + } if (!NotificationItem.IsValid()) { @@ -461,7 +465,7 @@ public: NotificationItem->SetCompletionState(SNotificationItem::CS_Pending); // launch the packager - TWeakPtr NotificationItemPtr(NotificationItem); + NotificationItemPtr = NotificationItem; EventData Data; Data.StartTime = FPlatformTime::Seconds(); @@ -647,6 +651,10 @@ public: } } + +private: + TWeakPtr NotificationItemPtr; + }; IMPLEMENT_MODULE(FUATHelperModule, UATHelper) diff --git a/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.cpp b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.cpp new file mode 100644 index 000000000000..59f75bb8cd69 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.cpp @@ -0,0 +1,27 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "WidgetPaletteFavorites.h" +#include "UObject/UnrealType.h" + +UWidgetPaletteFavorites::UWidgetPaletteFavorites(FObjectInitializer const& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UWidgetPaletteFavorites::Add(const FString& InWidgetTemplateName) +{ + Favorites.AddUnique(InWidgetTemplateName); + + SaveConfig(); + + OnFavoritesUpdated.Broadcast(); +} + +void UWidgetPaletteFavorites::Remove(const FString& InWidgetTemplateName) +{ + Favorites.Remove(InWidgetTemplateName); + + SaveConfig(); + + OnFavoritesUpdated.Broadcast(); +} \ No newline at end of file diff --git a/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.h b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.h new file mode 100644 index 000000000000..45fa0e3b3008 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Classes/WidgetPaletteFavorites.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "WidgetPaletteFavorites.generated.h" + +UCLASS(config = EditorPerProjectUserSettings) +class UMGEDITOR_API UWidgetPaletteFavorites : public UObject +{ + GENERATED_UCLASS_BODY() +public: + + void Add(const FString& InWidgetTemplateName); + void Remove(const FString& InWidgetTemplateName); + + TArray GetFavorites() const { return Favorites; } + + DECLARE_MULTICAST_DELEGATE(FOnFavoritesUpdated) + + /** Fires after the list of favorites is updated */ + FOnFavoritesUpdated OnFavoritesUpdated; + +private: + UPROPERTY(config) + TArray Favorites; +}; diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp index c6fa475d4dc8..37acbdf570b4 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp @@ -25,6 +25,8 @@ #include "Components/PanelSlot.h" #include "Details/SPropertyBinding.h" #include "Widgets/Layout/SWidgetSwitcher.h" +#include "IDetailsView.h" +#include "IDetailPropertyExtensionHandler.h" #define LOCTEXT_NAMESPACE "UMG" @@ -283,6 +285,11 @@ void FBlueprintWidgetCustomization::CustomizeDetails( IDetailLayoutBuilder& Deta static const FName LayoutCategoryKey(TEXT("Layout")); static const FName LocalizationCategoryKey(TEXT("Localization")); + static const FName AccessibleBehaviorKey(TEXT("AccessibleBehavior")); + static const FName AccessibleTextKey(TEXT("AccessibleText")); + static const FName AccessibleSummaryBehaviorKey(TEXT("AccessibleSummaryBehavior")); + static const FName AccessibleSummaryTextKey(TEXT("AccessibleSummaryText")); + DetailLayout.EditCategory(LocalizationCategoryKey, FText::GetEmpty(), ECategoryPriority::Uncommon); TArray< TWeakObjectPtr > OutObjects; @@ -307,6 +314,9 @@ void FBlueprintWidgetCustomization::CustomizeDetails( IDetailLayoutBuilder& Deta } } + CustomizeAccessibilityProperty(DetailLayout, AccessibleBehaviorKey, AccessibleTextKey); + CustomizeAccessibilityProperty(DetailLayout, AccessibleSummaryBehaviorKey, AccessibleSummaryTextKey); + PerformBindingCustomization(DetailLayout); } @@ -342,4 +352,66 @@ void FBlueprintWidgetCustomization::PerformBindingCustomization(IDetailLayoutBui } } +void FBlueprintWidgetCustomization::CustomizeAccessibilityProperty(IDetailLayoutBuilder& DetailLayout, const FName& BehaviorPropertyName, const FName& TextPropertyName) +{ + static const FName AccessibilityCategoryKey(TEXT("Accessibility")); + + // Treat the *Behavior property as the "base" property for the row, and then add the *Text properties to the end of it. + IDetailCategoryBuilder& AccessibilityCategory = DetailLayout.EditCategory(AccessibilityCategoryKey); + TSharedRef AccessibleBehaviorPropertyHandle = DetailLayout.GetProperty(BehaviorPropertyName); + IDetailPropertyRow& AccessibilityRow = AccessibilityCategory.AddProperty(AccessibleBehaviorPropertyHandle); + + TSharedRef AccessibleTextPropertyHandle = DetailLayout.GetProperty(TextPropertyName); + // Make sure the old *Text properties are hidden so we don't get duplicate widgets + DetailLayout.HideProperty(AccessibleTextPropertyHandle); + // The *Text properties are put into an HBox which will consist of the text box and the delegate's combo box. + // The combo box may not always be valid for the current selection, as its creation is determined by the ExtensionHandler. + TSharedRef CustomTextLayout = SNew(SHorizontalBox) + .Visibility(TAttribute::Create([AccessibleBehaviorPropertyHandle]() -> EVisibility + { + // Toggle the visibility of the *Text properties on only if the *Behavior property is set to Custom + uint8 Behavior = 0; + AccessibleBehaviorPropertyHandle->GetValue(Behavior); + return (ESlateAccessibleBehavior)Behavior == ESlateAccessibleBehavior::Custom ? EVisibility::Visible : EVisibility::Hidden; + })) + + SHorizontalBox::Slot() + .Padding(FMargin(4.0f, 0.0f)) + [ + AccessibleTextPropertyHandle->CreatePropertyValueWidget() + ]; + + TSharedRef ExtensionWidget = const_cast(DetailLayout.GetDetailsView())->GetExtensionHandler()->GenerateExtensionWidget(UWidget::StaticClass(), AccessibleTextPropertyHandle); + if (ExtensionWidget != SNullWidget::NullWidget) + { + CustomTextLayout->AddSlot() + .AutoWidth() + [ + ExtensionWidget + ]; + } + + TSharedPtr AccessibleBehaviorNameWidget, AccessibleBehaviorValueWidget; + AccessibilityRow.GetDefaultWidgets(AccessibleBehaviorNameWidget, AccessibleBehaviorValueWidget); + + AccessibilityRow.CustomWidget() + .NameContent() + [ + AccessibleBehaviorNameWidget.ToSharedRef() + ] + .ValueContent() + .HAlign(HAlign_Fill) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + AccessibleBehaviorValueWidget.ToSharedRef() + ] + + SHorizontalBox::Slot() + [ + CustomTextLayout + ] + ]; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h index efd28ee65739..1e1268115f2d 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.h @@ -43,6 +43,9 @@ private: FReply HandleAddOrViewEventForVariable(const FName EventName, FName PropertyName, TWeakObjectPtr PropertyClass); int32 HandleAddOrViewIndexForButton(const FName EventName, FName PropertyName) const; + + /** Handle specific customizations for ESlateAccessibleBehavior properties */ + void CustomizeAccessibilityProperty(IDetailLayoutBuilder& DetailLayout, const FName& BehaviorPropertyName, const FName& TextPropertyName); private: TWeakPtr Editor; diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp index b3ad2f995641..35de48948499 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/DesignTimeUtils.cpp @@ -20,7 +20,7 @@ bool FDesignTimeUtils::GetArrangedWidget(TSharedRef Widget, FArrangedWi FWidgetPath WidgetPath; if ( FSlateApplication::Get().GeneratePathToWidgetUnchecked(Widget, WidgetPath) ) { - ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::NullWidget); + ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget()); return true; } @@ -40,7 +40,7 @@ bool FDesignTimeUtils::GetArrangedWidgetRelativeToWindow(TSharedRef Wid FWidgetPath WidgetPath; if ( FSlateApplication::Get().GeneratePathToWidgetUnchecked(Widget, WidgetPath) ) { - ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::NullWidget); + ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget()); ArrangedWidget.Geometry.AppendTransform(FSlateLayoutTransform(Inverse(CurrentWindowRef->GetPositionInScreen()))); //ArrangedWidget.Geometry.AppendTransform(Inverse(CurrentWindowRef->GetLocalToScreenTransform())); return true; diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp index ee918a34a585..868b578870f9 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.cpp @@ -70,6 +70,7 @@ #include "DeviceProfiles/DeviceProfile.h" #include "DeviceProfiles/DeviceProfileManager.h" #include "Engine/DPICustomScalingRule.h" +#include "UMGEditorModule.h" #define LOCTEXT_NAMESPACE "UMG" @@ -215,6 +216,7 @@ public: /** The original parent of the widget. */ FWidgetReference ParentWidget; + /** The offset of the original click location, as a percentage of the widget's size. */ FVector2D DraggedOffset; }; @@ -241,7 +243,7 @@ TSharedRef FSelectedWidgetDragDropOp::New(TSharedPtr< Operation->bShowingMessage = false; Operation->Designer = InDesigner; - for (const auto& InDraggedWidget : InWidgets) + for (const FDraggingWidgetReference& InDraggedWidget : InWidgets) { FItem DraggedWidget; DraggedWidget.bStayingInParent = false; @@ -355,6 +357,15 @@ void SDesignerView::Construct(const FArguments& InArgs, TSharedPtr("UMGEditor"); + + TSharedPtrDesignerExtensibilityManager = UMGEditorInterface.GetDesignerExtensibilityManager(); + for (const auto& Extension : DesignerExtensibilityManager->GetExternalDesignerExtensions()) + { + Register(Extension); + } + GEditor->OnBlueprintReinstanced().AddRaw(this, &SDesignerView::OnPreviewNeedsRecreation); BindCommands(); @@ -431,6 +442,11 @@ void SDesignerView::Construct(const FArguments& InArgs, TSharedPtrSetContent(SNullWidget::NullWidget); + PreviewSizeConstraint->SetContent(SNullWidget::NullWidget); } SDesignerView::FWidgetHitResult::FWidgetHitResult() @@ -2288,20 +2304,11 @@ void SDesignerView::UpdatePreviewWidget(bool bForceUpdate) if ( PreviewWidget ) { TSharedRef NewPreviewSlateWidget = PreviewWidget->TakeWidget(); - NewPreviewSlateWidget->SlatePrepass(); + NewPreviewSlateWidget->SlatePrepass(PreviewSizeConstraint->GetCachedGeometry().Scale); PreviewSlateWidget = NewPreviewSlateWidget; - // The constraint box for the widget size needs to inside the DPI scaler in order to make sure it too - // is sized accurately for the size screen it's on. - TSharedRef NewPreviewSizeConstraintBox = SNew(SBox) - .WidthOverride(this, &SDesignerView::GetPreviewSizeWidth) - .HeightOverride(this, &SDesignerView::GetPreviewSizeHeight) - [ - NewPreviewSlateWidget - ]; - - PreviewSurface->SetContent(NewPreviewSizeConstraintBox); + PreviewSizeConstraint->SetContent(NewPreviewSlateWidget); // Notify all selected widgets that they are selected, because there are new preview objects // state may have been lost so this will recreate it if the widget does something special when @@ -2518,7 +2525,7 @@ FReply SDesignerView::OnDragDetected(const FGeometry& MyGeometry, const FPointer // Clear any pending selected widgets, the user has already decided what widget they want. PendingSelectedWidget = FWidgetReference(); - for (const auto& SelectedWidget : SelectedWidgets) + for (const FWidgetReference& SelectedWidget : SelectedWidgets) { // Determine The offset to keep the widget from the mouse while dragging FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry()); @@ -2527,14 +2534,13 @@ FReply SDesignerView::OnDragDetected(const FGeometry& MyGeometry, const FPointer FDragWidget DraggingWidget; DraggingWidget.Widget = SelectedWidget; - DraggingWidget.DraggedOffset = SelectedWidgetContextMenuLocation; - + DraggingWidget.DraggedOffset = SelectedWidgetContextMenuLocation / ArrangedWidget.Geometry.GetLocalSize(); DraggingWidgetCandidates.Add(DraggingWidget); } TArray DraggingWidgets; - for (const auto& Candidate : DraggingWidgetCandidates) + for (const FDragWidget& Candidate : DraggingWidgetCandidates) { // check the parent chain of each dragged widget and ignore those that are children of other dragged widgets bool bIsChild = false; @@ -2965,13 +2971,11 @@ void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const F } else { - Slot = ParentWidget->AddChild(Widget); + Slot = ParentWidget->InsertChildAt(ParentWidget->GetChildIndex(Widget), Widget); } if (Slot != nullptr) { - FVector2D NewPosition = LocalPosition - DraggedWidget.DraggedOffset; - FWidgetBlueprintEditorUtils::ImportPropertiesFromText(Slot, DraggedWidget.ExportedSlotProperties); bool HasChangedLayout = false; @@ -2986,6 +2990,12 @@ void SDesignerView::ProcessDropAndAddWidget(const FGeometry& MyGeometry, const F { CanvasSlot->SaveBaseLayout(); + FArrangedWidget ArrangedWidget(SNullWidget::NullWidget, FGeometry()); + FDesignTimeUtils::GetArrangedWidget(Widget->GetCachedWidget().ToSharedRef(), ArrangedWidget); + + FVector2D Offset = DraggedWidget.DraggedOffset * ArrangedWidget.Geometry.GetLocalSize(); + FVector2D NewPosition = LocalPosition - Offset; + // Perform grid snapping on X and Y if we need to. if (bGridSnapX) { diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h index b969e421b8ce..795c32a11a62 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/SDesignerView.h @@ -318,6 +318,7 @@ private: TSharedPtr PreviewHitTestRoot; TSharedPtr PreviewAreaConstraint; TSharedPtr PreviewSurface; + TSharedPtr PreviewSizeConstraint; TSharedPtr DesignerControls; TSharedPtr DesignerWidgetCanvas; diff --git a/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp b/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp index 200434f27696..baacf29b233b 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Designer/STransformHandle.cpp @@ -128,8 +128,8 @@ FReply STransformHandle::OnMouseMove(const FGeometry& MyGeometry, const FPointer UWidget* Preview = SelectedWidget.GetPreview(); { - FVector2D Delta = MouseEvent.GetScreenSpacePosition() - MouseDownPosition; - FVector2D TranslateAmount = Delta * ( 1.0f / Designer->GetPreviewScale() ); + const FVector2D Delta = MouseEvent.GetScreenSpacePosition() - MouseDownPosition; + const FVector2D TranslateAmount = Delta * (1.0f / (Designer->GetPreviewScale() * MyGeometry.Scale)); Resize(Cast(Preview->Slot), DragDirection, TranslateAmount); Resize(Cast(Template->Slot), DragDirection, TranslateAmount); diff --git a/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp b/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp index 41cc0e2ecfc6..17bc2b9681f9 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Details/SWidgetDetailsView.cpp @@ -549,7 +549,11 @@ void SWidgetDetailsView::NotifyPostChange(const FPropertyChangedEvent& PropertyC // Any time we migrate a property value we need to mark the blueprint as structurally modified so users don't need // to recompile it manually before they see it play in game using the latest version. FBlueprintEditorUtils::MarkBlueprintAsModified(BlueprintEditor.Pin()->GetBlueprintObj()); - ClearFocusIfOwned(); + + if (PropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet) + { + ClearFocusIfOwned(); + } } // If the property that changed is marked as "DesignerRebuild" we invalidate diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp index 03ca05df1c71..2e3856cb454a 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.cpp @@ -32,7 +32,7 @@ void SHierarchyView::Construct(const FArguments& InArgs, TSharedPtrOnObjectsReplaced().AddRaw(this, &SHierarchyView::OnObjectsReplaced); // Create the filter for searching in the tree - SearchBoxWidgetFilter = MakeShareable(new WidgetTextFilter(WidgetTextFilter::FItemToStringArray::CreateSP(this, &SHierarchyView::TransformWidgetToString))); + SearchBoxWidgetFilter = MakeShareable(new WidgetTextFilter(WidgetTextFilter::FItemToStringArray::CreateSP(this, &SHierarchyView::GetWidgetFilterStrings))); UWidgetBlueprint* Blueprint = GetBlueprint(); Blueprint->OnChanged().AddRaw(this, &SHierarchyView::OnBlueprintChanged); @@ -105,11 +105,6 @@ void SHierarchyView::Tick( const FGeometry& AllottedGeometry, const double InCur { if ( bRebuildTreeRequested || bRefreshRequested ) { - if (!bExpandAllNodes) - { - FindExpandedItemNames(); - } - if ( bRebuildTreeRequested ) { RebuildTreeView(); @@ -117,15 +112,12 @@ void SHierarchyView::Tick( const FGeometry& AllottedGeometry, const double InCur RefreshTree(); - RestoreExpandedItems(); + UpdateItemsExpansionFromModel(); OnEditorSelectionChanged(); bRefreshRequested = false; bRebuildTreeRequested = false; - bExpandAllNodes = false; - - ExpandedItemNames.Empty(); } } @@ -176,16 +168,27 @@ bool SHierarchyView::CanRename() const return SelectedItems.Num() == 1 && SelectedItems[0]->CanRename(); } -void SHierarchyView::TransformWidgetToString(TSharedPtr Item, OUT TArray< FString >& Array) +void SHierarchyView::GetWidgetFilterStrings(TSharedPtr Item, TArray& OutStrings) { - Array.Add( Item->GetText().ToString() ); + Item->GetFilterStrings(OutStrings); } void SHierarchyView::OnSearchChanged(const FText& InFilterText) { bRefreshRequested = true; - bExpandAllNodes = InFilterText.IsEmpty(); - FilterHandler->SetIsEnabled(!InFilterText.IsEmpty()); + const bool bFilteringEnabled = !InFilterText.IsEmpty(); + if (bFilteringEnabled != FilterHandler->GetIsEnabled()) + { + FilterHandler->SetIsEnabled(bFilteringEnabled); + if (bFilteringEnabled) + { + SaveItemsExpansion(); + } + else + { + RestoreItemsExpansion(); + } + } SearchBoxWidgetFilter->SetRawFilterText(InFilterText); SearchBoxPtr->SetError(SearchBoxWidgetFilter->GetFilterErrorText()); } @@ -346,17 +349,23 @@ void SHierarchyView::OnObjectsReplaced(const TMap& Replaceme } } -void SHierarchyView::RestoreExpandedItems() +void SHierarchyView::UpdateItemsExpansionFromModel() { - EExpandBehavior ExpandBehavior = bExpandAllNodes ? EExpandBehavior::AlwaysExpand : EExpandBehavior::RestoreFromPrevious; - - for ( TSharedPtr& Model : RootWidgets ) + for (TSharedPtr& Model : RootWidgets) { - RecursiveExpand(Model, ExpandBehavior); + RecursiveExpand(Model, EExpandBehavior::FromModel); } } -void SHierarchyView::FindExpandedItemNames() +void SHierarchyView::RestoreItemsExpansion() +{ + for (TSharedPtr& Model : RootWidgets) + { + RecursiveExpand(Model, EExpandBehavior::RestoreFromPrevious); + } +} + +void SHierarchyView::SaveItemsExpansion() { ExpandedItemNames.Empty(); @@ -394,11 +403,17 @@ void SHierarchyView::RecursiveExpand(TSharedPtr& Model, EExpand break; case EExpandBehavior::AlwaysExpand: - default: { bShouldExpandItem = true; } break; + + case EExpandBehavior::FromModel: + default: + { + bShouldExpandItem = Model->IsExpanded(); + } + break; } WidgetTreeView->SetItemExpansion(Model, bShouldExpandItem); @@ -410,11 +425,6 @@ void SHierarchyView::RecursiveExpand(TSharedPtr& Model, EExpand { RecursiveExpand(ChildModel, ExpandBehavior); } - - if (Model->IsExpanded()) - { - WidgetTreeView->SetItemExpansion(Model, true); - } } void SHierarchyView::RestoreSelectedItems() diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h index 5f8cc1c0abc6..cca99227a3fd 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyView.h @@ -88,24 +88,31 @@ private: /** Gets the search text currently being used to filter the list, also used to highlight text */ FText GetSearchText() const; - /** Transforms the widget into a searchable string */ - void TransformWidgetToString(TSharedPtr Widget, OUT TArray< FString >& Array); + /** Gets an array of strings used for filtering/searching the specified widget. */ + void GetWidgetFilterStrings(TSharedPtr Widget, TArray& OutStrings); /** Called when a Blueprint is recompiled and live objects are swapped out for replacements */ void OnObjectsReplaced(const TMap& ReplacementMap); - /** Restores the state of expanded items based on the saved expanded item state, then clears the expanded state cache. */ - void RestoreExpandedItems(); + /** Sets the expansion state of hierarchy view items based on their model. */ + void UpdateItemsExpansionFromModel(); + + /** Stores the names of all currently expanded nodes in the hierarchy view. */ + void SaveItemsExpansion(); + + /** Sets the expansion state of hierarchy view items based on the state saved by SaveItemsExpansion. */ + void RestoreItemsExpansion(); enum class EExpandBehavior : uint8 { NeverExpand, AlwaysExpand, RestoreFromPrevious, + FromModel }; /** Recursively expands the models based on the expansion set. */ - void RecursiveExpand(TSharedPtr& Model, EExpandBehavior ExpandBehavior = EExpandBehavior::AlwaysExpand); + void RecursiveExpand(TSharedPtr& Model, EExpandBehavior ExpandBehavior); /** */ void RestoreSelectedItems(); @@ -116,9 +123,6 @@ private: /** Handler for recursively expanding/collapsing items */ void SetItemExpansionRecursive(TSharedPtr Model, bool bInExpansionState); - /** Find and store the names of all currently expanded nodes in the hierarchy view. Should only be called when rebuilding the tree */ - void FindExpandedItemNames(); - private: /** Cached pointer to the blueprint editor that owns this tree. */ @@ -159,7 +163,4 @@ private: /** Flag to ignore selections while the hierarchy view is updating the selection. */ bool bIsUpdatingSelection; - - /** Should all nodes in the tree be expanded? */ - bool bExpandAllNodes; }; diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp index 5dceecdb08a3..b8d199cd1161 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.cpp @@ -1093,6 +1093,18 @@ FText FHierarchyWidget::GetLabelToolTipText() const return FText::GetEmpty(); } +void FHierarchyWidget::GetFilterStrings(TArray& OutStrings) const +{ + FHierarchyModel::GetFilterStrings(OutStrings); + + UWidget* WidgetTemplate = Item.GetTemplate(); + if (WidgetTemplate && !WidgetTemplate->IsGeneratedName()) + { + OutStrings.Add(WidgetTemplate->GetClass()->GetName()); + OutStrings.Add(WidgetTemplate->GetClass()->GetDisplayNameText().ToString()); + } +} + const FSlateBrush* FHierarchyWidget::GetImage() const { if (Item.GetTemplate()) diff --git a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h index 751e35ea84d3..f7e35fc47ac2 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h +++ b/Engine/Source/Editor/UMGEditor/Private/Hierarchy/SHierarchyViewItem.h @@ -32,6 +32,9 @@ public: /** @return The tooltip for the tree item label */ virtual FText GetLabelToolTipText() const { return FText::GetEmpty(); } + + /** @param OutStrings - Returns an array of strings used for filtering/searching this item. */ + virtual void GetFilterStrings(TArray& OutStrings) const { OutStrings.Add(GetText().ToString()); } virtual const FSlateBrush* GetImage() const = 0; @@ -196,6 +199,8 @@ public: virtual FText GetText() const override; virtual FText GetImageToolTipText() const override; virtual FText GetLabelToolTipText() const override; + + virtual void GetFilterStrings(TArray& OutStrings) const override; virtual const FSlateBrush* GetImage() const override; diff --git a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp index 20dbc1d1c7de..76b307d6a0f0 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_CreateWidget.cpp @@ -110,6 +110,9 @@ void UK2Node_CreateWidget::ExpandNode(class FKismetCompilerContext& CompilerCont CallCreateNode->FunctionReference.SetExternalMember(Create_FunctionName, UWidgetBlueprintLibrary::StaticClass()); CallCreateNode->AllocateDefaultPins(); + // store off the class to spawn before we mutate pin connections: + UClass* ClassToSpawn = GetClassToSpawn(); + UEdGraphPin* CallCreateExec = CallCreateNode->GetExecPin(); UEdGraphPin* CallCreateWorldContextPin = CallCreateNode->FindPinChecked(WorldContextObject_ParamName); UEdGraphPin* CallCreateWidgetTypePin = CallCreateNode->FindPinChecked(WidgetType_ParamName); @@ -147,7 +150,7 @@ void UK2Node_CreateWidget::ExpandNode(class FKismetCompilerContext& CompilerCont // create 'set var' nodes // Get 'result' pin from 'begin spawn', this is the actual actor we want to set properties on - UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallCreateNode, CreateWidgetNode, CallCreateResult, GetClassToSpawn()); + UEdGraphPin* LastThen = FKismetCompilerUtilities::GenerateAssignmentNodes(CompilerContext, SourceGraph, CallCreateNode, CreateWidgetNode, CallCreateResult, ClassToSpawn); // Move 'then' connection from create widget node to the last 'then' CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *LastThen); diff --git a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.cpp b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.cpp new file mode 100644 index 000000000000..90463ff774fa --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.cpp @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "K2Node_PlayAnimation.h" +#include "Animation/WidgetAnimationPlayCallbackProxy.h" + +#define LOCTEXT_NAMESPACE "UMG" + +UK2Node_PlayAnimation::UK2Node_PlayAnimation(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UWidgetAnimationPlayCallbackProxy, CreatePlayAnimationProxyObject); + ProxyFactoryClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); + ProxyClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); +} + +UK2Node_PlayAnimationTimeRange::UK2Node_PlayAnimationTimeRange(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UWidgetAnimationPlayCallbackProxy, CreatePlayAnimationTimeRangeProxyObject); + ProxyFactoryClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); + ProxyClass = UWidgetAnimationPlayCallbackProxy::StaticClass(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.h b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.h new file mode 100644 index 000000000000..00a5f6238a94 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Nodes/K2Node_PlayAnimation.h @@ -0,0 +1,18 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "K2Node_BaseAsyncTask.h" +#include "K2Node_PlayAnimation.generated.h" + +UCLASS() +class UMGEDITOR_API UK2Node_PlayAnimation : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() +}; + +UCLASS() +class UMGEDITOR_API UK2Node_PlayAnimationTimeRange : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() +}; diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp index 1e5be29c7bc3..3e4f15698ba6 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.cpp @@ -1,6 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "Palette/SPaletteView.h" +#include "Palette/SPaletteViewModel.h" #include "Misc/ConfigCacheIni.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" @@ -24,6 +25,7 @@ #include "AssetRegistryModule.h" #include "Widgets/Input/SSearchBox.h" +#include "Widgets/Input/SCheckBox.h" #include "Settings/ContentBrowserSettings.h" #include "WidgetBlueprintEditorUtils.h" @@ -34,39 +36,66 @@ #define LOCTEXT_NAMESPACE "UMG" -class SPaletteViewItem : public SCompoundWidget +FText SPaletteViewItem::GetFavoriteToggleToolTipText() const { -public: - - SLATE_BEGIN_ARGS(SPaletteViewItem) {} - - /** The current text to highlight */ - SLATE_ATTRIBUTE(FText, HighlightText) - - SLATE_END_ARGS() - - /** - * Constructs this widget - * - * @param InArgs Declaration from which to construct the widget - */ - void Construct(const FArguments& InArgs, TSharedPtr InTemplate) + if (GetFavoritedState() == ECheckBoxState::Checked) { - Template = InTemplate; + return LOCTEXT("Unfavorite", "Click to remove this widget from your favorites."); + } + return LOCTEXT("Favorite", "Click to add this widget to your favorites."); +} - ChildSlot +ECheckBoxState SPaletteViewItem::GetFavoritedState() const +{ + if (WidgetViewModel->IsFavorite()) + { + return ECheckBoxState::Checked; + } + else + { + return ECheckBoxState::Unchecked; + } +} + +void SPaletteViewItem::OnFavoriteToggled(ECheckBoxState InNewState) +{ + if (InNewState == ECheckBoxState::Checked) + { + //Add to favorites + WidgetViewModel->AddToFavorites(); + } + else + { + //Remove from favorites + WidgetViewModel->RemoveFromFavorites(); + } +} + +void SPaletteViewItem::Construct(const FArguments& InArgs, TSharedPtr InWidgetViewModel) +{ + WidgetViewModel = InWidgetViewModel; + + ChildSlot [ SNew(SHorizontalBox) - .Visibility(EVisibility::Visible) - .ToolTip(Template->GetToolTip()) - + .ToolTip(WidgetViewModel->Template->GetToolTip()) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SCheckBox) + .ToolTipText(this, &SPaletteViewItem::GetFavoriteToggleToolTipText) + .IsChecked(this, &SPaletteViewItem::GetFavoritedState) + .OnCheckStateChanged(this, &SPaletteViewItem::OnFavoriteToggled) + .Style(FEditorStyle::Get(), "UMGEditor.Palette.FavoriteToggleStyle") + ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SImage) .ColorAndOpacity(FLinearColor(1, 1, 1, 0.5)) - .Image(Template->GetIcon()) + .Image(WidgetViewModel->Template->GetIcon()) ] + SHorizontalBox::Slot() @@ -75,135 +104,34 @@ public: .VAlign(VAlign_Center) [ SNew(STextBlock) - .Text(Template->Name) + .Text(InWidgetViewModel->GetName()) .HighlightText(InArgs._HighlightText) ] ]; - } +} - virtual FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override - { - return Template->OnDoubleClicked(); - } - -private: - TSharedPtr Template; -}; - -class FWidgetTemplateViewModel : public FWidgetViewModel +FReply SPaletteViewItem::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) { -public: - virtual ~FWidgetTemplateViewModel() - { - } - - virtual FText GetName() const override - { - return Template->Name; - } - - virtual bool IsTemplate() const override - { - return true; - } - - virtual FString GetFilterString() const override - { - return Template->Name.ToString(); - } - - virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override - { - return SNew(STableRow< TSharedPtr >, OwnerTable) - .Padding(2.0f) - .Style(FEditorStyle::Get(), "UMGEditor.PaletteItem") - .OnDragDetected(this, &FWidgetTemplateViewModel::OnDraggingWidgetTemplateItem) - [ - SNew(SPaletteViewItem, Template) - .HighlightText(OwnerView, &SPaletteView::GetSearchText) - ]; - } - - FReply OnDraggingWidgetTemplateItem(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - return FReply::Handled().BeginDragDrop(FWidgetTemplateDragDropOp::New(Template)); - } - - SPaletteView* OwnerView; - TSharedPtr Template; -}; - -class FWidgetHeaderViewModel : public FWidgetViewModel -{ -public: - virtual ~FWidgetHeaderViewModel() - { - } - - virtual FText GetName() const override - { - return GroupName; - } - - virtual bool IsTemplate() const override - { - return false; - } - - virtual FString GetFilterString() const override - { - // Headers should never be included in filtering to avoid showing a header with all of - // it's widgets filtered out, so return an empty filter string. - return TEXT(""); - } - - virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override - { - return SNew(STableRow< TSharedPtr >, OwnerTable) - .Style( FEditorStyle::Get(), "UMGEditor.PaletteHeader" ) - .Padding(2.0f) - .ShowSelection(false) - [ - SNew(STextBlock) - .Text(GroupName) - .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) - .ShadowOffset(FVector2D(1.0f, 1.0f)) - ]; - } - - virtual void GetChildren(TArray< TSharedPtr >& OutChildren) override - { - for ( TSharedPtr& Child : Children ) - { - OutChildren.Add(Child); - } - } - - FText GroupName; - TArray< TSharedPtr > Children; + return WidgetViewModel->Template->OnDoubleClicked(); }; void SPaletteView::Construct(const FArguments& InArgs, TSharedPtr InBlueprintEditor) { - // Register for events that can trigger a palette rebuild - GEditor->OnBlueprintReinstanced().AddRaw(this, &SPaletteView::OnBlueprintReinstanced); - FEditorDelegates::OnAssetsDeleted.AddSP(this, &SPaletteView::HandleOnAssetsDeleted); - IHotReloadModule::Get().OnHotReload().AddSP(this, &SPaletteView::HandleOnHotReload); - - // register for any objects replaced - GEditor->OnObjectsReplaced().AddRaw(this, &SPaletteView::OnObjectsReplaced); - BlueprintEditor = InBlueprintEditor; - UWidgetBlueprint* WBP = InBlueprintEditor->GetWidgetBlueprintObj(); - bAllowEditorWidget = WBP ? WBP->AllowEditorWidget() : false; + UBlueprint* BP = InBlueprintEditor->GetBlueprintObj(); + PaletteViewModel = InBlueprintEditor->GetPaletteViewModel(); + + // Register to the update of the viewmodel. + PaletteViewModel->OnUpdating.AddRaw(this, &SPaletteView::OnViewModelUpdating); + PaletteViewModel->OnUpdated.AddRaw(this, &SPaletteView::OnViewModelUpdated); WidgetFilter = MakeShareable(new WidgetViewModelTextFilter( - WidgetViewModelTextFilter::FItemToStringArray::CreateSP(this, &SPaletteView::TransformWidgetViewModelToString))); + WidgetViewModelTextFilter::FItemToStringArray::CreateSP(this, &SPaletteView::GetWidgetFilterStrings))); FilterHandler = MakeShareable(new PaletteFilterHandler()); FilterHandler->SetFilter(WidgetFilter.Get()); - FilterHandler->SetRootItems(&WidgetViewModels, &TreeWidgetViewModels); + FilterHandler->SetRootItems(&(PaletteViewModel->GetWidgetViewModels()), &TreeWidgetViewModels); FilterHandler->SetGetChildrenDelegate(PaletteFilterHandler::FOnGetChildren::CreateRaw(this, &SPaletteView::OnGetChildren)); SAssignNew(WidgetTemplatesView, STreeView< TSharedPtr >) @@ -242,14 +170,16 @@ void SPaletteView::Construct(const FArguments& InArgs, TSharedPtrUpdate(); LoadItemExpansion(); - - bRebuildRequested = false; } SPaletteView::~SPaletteView() { + // Unregister to the update of the viewmodel. + PaletteViewModel->OnUpdating.RemoveAll(this); + PaletteViewModel->OnUpdated.RemoveAll(this); + // If the filter is enabled, disable it before saving the expanded items since // filtering expands all items by default. if (FilterHandler->GetIsEnabled()) @@ -258,12 +188,6 @@ SPaletteView::~SPaletteView() FilterHandler->RefreshAndFilterTree(); } - GEditor->OnBlueprintReinstanced().RemoveAll(this); - FEditorDelegates::OnAssetsDeleted.RemoveAll(this); - IHotReloadModule::Get().OnHotReload().RemoveAll(this); - GEditor->OnObjectsReplaced().RemoveAll( this ); - - SaveItemExpansion(); } @@ -273,12 +197,7 @@ void SPaletteView::OnSearchChanged(const FText& InFilterText) FilterHandler->SetIsEnabled(!InFilterText.IsEmpty()); WidgetFilter->SetRawFilterText(InFilterText); SearchBoxPtr->SetError(WidgetFilter->GetFilterErrorText()); - SearchText = InFilterText; -} - -FText SPaletteView::GetSearchText() const -{ - return SearchText; + PaletteViewModel->SetSearchText(InFilterText); } void SPaletteView::WidgetPalette_OnSelectionChanged(TSharedPtr SelectedItem, ESelectInfo::Type SelectInfo) @@ -339,7 +258,7 @@ TSharedPtr SPaletteView::GetSelectedTemplateWidget() const void SPaletteView::LoadItemExpansion() { // Restore the expansion state of the widget groups. - for ( TSharedPtr& ViewModel : WidgetViewModels ) + for ( TSharedPtr& ViewModel : PaletteViewModel->GetWidgetViewModels()) { bool IsExpanded; if ( GConfig->GetBool(TEXT("WidgetTemplatesExpanded"), *ViewModel->GetName().ToString(), IsExpanded, GEditorPerProjectIni) && IsExpanded ) @@ -352,285 +271,13 @@ void SPaletteView::LoadItemExpansion() void SPaletteView::SaveItemExpansion() { // Restore the expansion state of the widget groups. - for ( TSharedPtr& ViewModel : WidgetViewModels ) + for ( TSharedPtr& ViewModel : PaletteViewModel->GetWidgetViewModels() ) { const bool IsExpanded = WidgetTemplatesView->IsItemExpanded(ViewModel); GConfig->SetBool(TEXT("WidgetTemplatesExpanded"), *ViewModel->GetName().ToString(), IsExpanded, GEditorPerProjectIni); } } -UWidgetBlueprint* SPaletteView::GetBlueprint() const -{ - if ( BlueprintEditor.IsValid() ) - { - UBlueprint* BP = BlueprintEditor.Pin()->GetBlueprintObj(); - return Cast(BP); - } - - return NULL; -} - -void SPaletteView::BuildWidgetList() -{ - // Clear the current list of view models and categories - WidgetViewModels.Reset(); - WidgetTemplateCategories.Reset(); - - // Generate a list of templates - BuildClassWidgetList(); - BuildSpecialWidgetList(); - - // For each entry in the category create a view model for the widget template - for ( auto& Entry : WidgetTemplateCategories ) - { - TSharedPtr Header = MakeShareable(new FWidgetHeaderViewModel()); - Header->GroupName = FText::FromString(Entry.Key); - - for ( auto& Template : Entry.Value ) - { - TSharedPtr TemplateViewModel = MakeShareable(new FWidgetTemplateViewModel()); - TemplateViewModel->Template = Template; - TemplateViewModel->OwnerView = this; - Header->Children.Add(TemplateViewModel); - } - - Header->Children.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); - - WidgetViewModels.Add(Header); - } - - // Sort the view models by name - WidgetViewModels.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); - - // Take the Advanced Section, and put it at the end. - TSharedPtr* advancedSectionPtr = WidgetViewModels.FindByPredicate([](TSharedPtr widget) {return widget->GetName().CompareTo(LOCTEXT("Advanced", "Advanced")) == 0; }); - if (advancedSectionPtr) - { - TSharedPtr advancedSection = *advancedSectionPtr; - WidgetViewModels.Remove(advancedSection); - WidgetViewModels.Push(advancedSection); - } -} - -void SPaletteView::BuildClassWidgetList() -{ - static const FName DevelopmentStatusKey(TEXT("DevelopmentStatus")); - - TMap> LoadedWidgetBlueprintClassesByName; - - auto ActiveWidgetBlueprintClass = GetBlueprint()->GeneratedClass; - FName ActiveWidgetBlueprintClassName = ActiveWidgetBlueprintClass->GetFName(); - - TArray WidgetClassesToHide = GetDefault()->WidgetClassesToHide; - - // Locate all UWidget classes from code and loaded widget BPs - for (TObjectIterator ClassIt; ClassIt; ++ClassIt) - { - UClass* WidgetClass = *ClassIt; - - if (!FWidgetBlueprintEditorUtils::IsUsableWidgetClass(WidgetClass)) - { - continue; - } - - // Initialize AssetData for checking PackagePath - FAssetData WidgetAssetData = FAssetData(WidgetClass); - - // Excludes engine content if user sets it to false - if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) - { - if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) - { - continue; - } - } - - // Excludes developer content if user sets it to false - if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) - { - if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) - { - continue; - } - } - - // Excludes this widget if it is on the hide list - bool bIsOnList = false; - for (FSoftClassPath Widget : WidgetClassesToHide) - { - if (WidgetAssetData.ObjectPath.ToString().Find(Widget.ToString()) == 0) - { - bIsOnList = true; - break; - } - } - if (bIsOnList) - { - continue; - } - - const bool bIsSameClass = WidgetClass->GetFName() == ActiveWidgetBlueprintClassName; - - // Check that the asset that generated this class is valid (necessary b/c of a larger issue wherein force delete does not wipe the generated class object) - if ( bIsSameClass ) - { - continue; - } - - if (!bAllowEditorWidget) - { - if (WidgetClass->GetOutermost()->IsEditorOnly()) - { - continue; - } - } - - if (WidgetClass->IsChildOf(UUserWidget::StaticClass())) - { - if ( WidgetClass->ClassGeneratedBy ) - { - // Track the widget blueprint classes that are already loaded - LoadedWidgetBlueprintClassesByName.Add(WidgetClass->ClassGeneratedBy->GetFName()) = WidgetClass; - } - } - else - { - TSharedPtr Template = MakeShareable(new FWidgetTemplateClass(WidgetClass)); - - AddWidgetTemplate(Template); - } - - //TODO UMG does not prevent deep nested circular references - } - - // Locate all widget BP assets (include unloaded) - const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); - TArray AllBPsAssetData; - AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), AllBPsAssetData, true); - - for (FAssetData& BPAssetData : AllBPsAssetData) - { - // Blueprints get the class type actions for their parent native class - this avoids us having to load the blueprint - UClass* ParentClass = nullptr; - FString ParentClassName; - if (!BPAssetData.GetTagValue(FBlueprintTags::NativeParentClassPath, ParentClassName)) - { - BPAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassName); - } - if (!ParentClassName.IsEmpty()) - { - UObject* Outer = nullptr; - ResolveName(Outer, ParentClassName, false, false); - ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); - // UUserWidgets have their own loading section, and we don't want to process any blueprints that don't have UWidget parents - if (!ParentClass->IsChildOf(UWidget::StaticClass()) || ParentClass->IsChildOf(UUserWidget::StaticClass())) - { - continue; - } - } - - if (!FilterAssetData(BPAssetData)) - { - // If this object isn't currently loaded, add it to the palette view - if (BPAssetData.ToSoftObjectPath().ResolveObject() == nullptr) - { - auto Template = MakeShareable(new FWidgetTemplateClass(BPAssetData, nullptr)); - AddWidgetTemplate(Template); - } - } - } - - TArray AllWidgetBPsAssetData; - AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetFName(), AllWidgetBPsAssetData, true); - - FName ActiveWidgetBlueprintName = ActiveWidgetBlueprintClass->ClassGeneratedBy->GetFName(); - for (FAssetData& WidgetBPAssetData : AllWidgetBPsAssetData) - { - // Excludes the blueprint you're currently in - if (WidgetBPAssetData.AssetName == ActiveWidgetBlueprintName) - { - continue; - } - - if (!FilterAssetData(WidgetBPAssetData)) - { - // Excludes this widget if it is on the hide list - bool bIsOnList = false; - for (FSoftClassPath Widget : WidgetClassesToHide) - { - if (Widget.ToString().Find(WidgetBPAssetData.ObjectPath.ToString()) == 0) - { - bIsOnList = true; - break; - } - } - if (bIsOnList) - { - continue; - } - - // If the blueprint generated class was found earlier, pass it to the template - TSubclassOf WidgetBPClass = nullptr; - auto LoadedWidgetBPClass = LoadedWidgetBlueprintClassesByName.Find(WidgetBPAssetData.AssetName); - if (LoadedWidgetBPClass) - { - WidgetBPClass = *LoadedWidgetBPClass; - } - - auto Template = MakeShareable(new FWidgetTemplateBlueprintClass(WidgetBPAssetData, WidgetBPClass)); - - AddWidgetTemplate(Template); - } - } -} - -bool SPaletteView::FilterAssetData(FAssetData &InAssetData) -{ - // Excludes engine content if user sets it to false - if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) - { - if (InAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) - { - return true; - } - } - - // Excludes developer content if user sets it to false - if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) - { - if (InAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) - { - return true; - } - } - return false; -} - -void SPaletteView::BuildSpecialWidgetList() -{ - //AddWidgetTemplate(MakeShareable(new FWidgetTemplateButton())); - //AddWidgetTemplate(MakeShareable(new FWidgetTemplateCheckBox())); - - //TODO UMG Make this pluggable. -} - -void SPaletteView::AddWidgetTemplate(TSharedPtr Template) -{ - FString Category = Template->GetCategory().ToString(); - - // Hide user specific categories - TArray CategoriesToHide = GetDefault()->CategoriesToHide; - for (FString CategoryName : CategoriesToHide) - { - if (Category == CategoryName) - { - return; - } - } - WidgetTemplateArray& Group = WidgetTemplateCategories.FindOrAdd(Category); - Group.Add(Template); -} - void SPaletteView::OnGetChildren(TSharedPtr Item, TArray< TSharedPtr >& Children) { return Item->GetChildren(Children); @@ -641,31 +288,32 @@ TSharedRef SPaletteView::OnGenerateWidgetTemplateItem(TSharedPtrBuildRow(OwnerTable); } -void SPaletteView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) +void SPaletteView::OnViewModelUpdating() { - if ( bRebuildRequested ) + // Save the old expanded items temporarily + WidgetTemplatesView->GetExpandedItems(ExpandedItems); +} + +void SPaletteView::OnViewModelUpdated() +{ + bRefreshRequested = true; + + // Restore the expansion state + for (TSharedPtr& ExpandedItem : ExpandedItems) { - bRebuildRequested = false; - - // Save the old expanded items temporarily - TSet> ExpandedItems; - WidgetTemplatesView->GetExpandedItems(ExpandedItems); - - BuildWidgetList(); - - // Restore the expansion state - for ( TSharedPtr& ExpandedItem : ExpandedItems ) + for (TSharedPtr& ViewModel : PaletteViewModel->GetWidgetViewModels()) { - for ( TSharedPtr& ViewModel : WidgetViewModels ) + if (ViewModel->GetName().EqualTo(ExpandedItem->GetName()) || ViewModel->ShouldForceExpansion()) { - if ( ViewModel->GetName().EqualTo(ExpandedItem->GetName()) ) - { - WidgetTemplatesView->SetItemExpansion(ViewModel, true); - } + WidgetTemplatesView->SetItemExpansion(ViewModel, true); } } } + ExpandedItems.Reset(); +} +void SPaletteView::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ if (bRefreshRequested) { bRefreshRequested = false; @@ -673,40 +321,9 @@ void SPaletteView::Tick( const FGeometry& AllottedGeometry, const double InCurre } } -void SPaletteView::TransformWidgetViewModelToString(TSharedPtr WidgetViewModel, OUT TArray< FString >& Array) +void SPaletteView::GetWidgetFilterStrings(TSharedPtr WidgetViewModel, TArray& OutStrings) { - Array.Add(WidgetViewModel->GetFilterString()); -} - -void SPaletteView::OnObjectsReplaced(const TMap& ReplacementMap) -{ - //bRefreshRequested = true; - //bRebuildRequested = true; -} - -void SPaletteView::OnBlueprintReinstanced() -{ - bRebuildRequested = true; - bRefreshRequested = true; -} - -void SPaletteView::HandleOnHotReload(bool bWasTriggeredAutomatically) -{ - bRebuildRequested = true; - bRefreshRequested = true; -} - -void SPaletteView::HandleOnAssetsDeleted(const TArray& DeletedAssetClasses) -{ - for (auto DeletedAssetClass : DeletedAssetClasses) - { - if (DeletedAssetClass->IsChildOf(UWidgetBlueprint::StaticClass())) - { - bRebuildRequested = true; - bRefreshRequested = true; - } - } - + WidgetViewModel->GetFilterStrings(OutStrings); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h index 1aa3e955b9a3..5f759606a6e6 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteView.h @@ -5,8 +5,6 @@ #include "CoreMinimal.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" -#include "Widgets/Views/STableViewBase.h" -#include "Widgets/Views/STableRow.h" #include "WidgetBlueprintEditor.h" #include "Misc/TextFilter.h" #include "Widgets/Views/STreeView.h" @@ -14,28 +12,61 @@ class FWidgetTemplate; class UWidgetBlueprint; +class SPaletteView; +class FPaletteViewModel; +class FWidgetViewModel; +class FWidgetTemplateViewModel; -/** View model for the items in the widget template list */ -class FWidgetViewModel : public TSharedFromThis +/** Widget used to show a single row of the Palette and Palette favorite panel. */ +class SPaletteViewItem : public SCompoundWidget { public: - virtual ~FWidgetViewModel() { } - virtual FText GetName() const = 0; + SLATE_BEGIN_ARGS(SPaletteViewItem) {} - virtual bool IsTemplate() const = 0; + /** The current text to highlight */ + SLATE_ATTRIBUTE(FText, HighlightText) - /** Get the string which should be used for filtering the item. */ - virtual FString GetFilterString() const = 0; + SLATE_END_ARGS() - virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) = 0; + /** + * Constructs this widget + * + * @param InArgs Declaration from which to construct the widget + */ + void Construct(const FArguments& InArgs, TSharedPtr InWidgetViewModel); - virtual void GetChildren(TArray< TSharedPtr >& OutChildren) - { - } +private: + /** + * Retrieves tooltip that describes the current favorited state + * + * @return Text describing what this toggle will do when you click on it. + */ + FText GetFavoriteToggleToolTipText() const; + + /** + * Checks on the associated action's favorite state, and returns a + * corresponding checkbox state to match. + * + * @return ECheckBoxState::Checked if the associated action is already favorited, ECheckBoxState::Unchecked if not. + */ + ECheckBoxState GetFavoritedState() const; + + /** + * Triggers when the user clicks this toggle, adds or removes the associated + * action to the user's favorites. + * + * @param InNewState The new state that the user set the checkbox to. + */ + void OnFavoriteToggled(ECheckBoxState InNewState); + + virtual FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) override; + +private: + TSharedPtr WidgetViewModel; }; -/** */ +/** */ class SPaletteView : public SCompoundWidget { public: @@ -60,12 +91,6 @@ public: TSharedPtr GetSelectedTemplateWidget() const; private: - UWidgetBlueprint* GetBlueprint() const; - - void BuildWidgetList(); - void BuildClassWidgetList(); - bool FilterAssetData(FAssetData &BPAssetData); - void BuildSpecialWidgetList(); void OnGetChildren(TSharedPtr Item, TArray< TSharedPtr >& Children); TSharedRef OnGenerateWidgetTemplateItem(TSharedPtr Item, const TSharedRef& OwnerTable); @@ -73,41 +98,27 @@ private: /** Called when the filter text is changed. */ void OnSearchChanged(const FText& InFilterText); + void OnViewModelUpdating(); + void OnViewModelUpdated(); + private: + /** Load the expansion state for the TreeView */ void LoadItemExpansion(); + + /** Save the expansion state for the TreeView */ void SaveItemExpansion(); - /** Called when a Blueprint is recompiled and live objects are swapped out for replacements */ - void OnObjectsReplaced(const TMap& ReplacementMap); - - void AddWidgetTemplate(TSharedPtr Template); - - /** Transforms the widget view model into a searchable string. */ - void TransformWidgetViewModelToString(TSharedPtr WidgetViewModel, OUT TArray< FString >& Array); - - /** Requests a rebuild of the widget list if a widget blueprint was compiled */ - void OnBlueprintReinstanced(); - - /** Requests a rebuild of the widget list */ - void HandleOnHotReload(bool bWasTriggeredAutomatically); - - /** Requests a rebuild of the widget list if a widget blueprint was deleted */ - void HandleOnAssetsDeleted(const TArray& DeletedAssetClasses); + /** Gets an array of strings used for filtering/searching the specified widget. */ + void GetWidgetFilterStrings(TSharedPtr WidgetViewModel, TArray& OutStrings); TWeakPtr BlueprintEditor; + TSharedPtr PaletteViewModel; /** Handles filtering the palette based on an IFilter. */ typedef TreeFilterHandler> PaletteFilterHandler; TSharedPtr FilterHandler; - typedef TArray< TSharedPtr > WidgetTemplateArray; - TMap< FString, WidgetTemplateArray > WidgetTemplateCategories; - - typedef TArray< TSharedPtr > ViewModelsArray; - - /** The source root view models for the tree. */ - ViewModelsArray WidgetViewModels; - + typedef TArray> ViewModelsArray; /** The root view models which are actually displayed by the TreeView which will be managed by the TreeFilterHandler. */ ViewModelsArray TreeWidgetViewModels; @@ -119,11 +130,11 @@ private: /** The filter instance which is used by the TreeFilterHandler to filter the TreeView. */ TSharedPtr WidgetFilter; - bool bRefreshRequested; - FText SearchText; + /** Expended Items in the Tree view */ + TSet> ExpandedItems; - /** Controls rebuilding the list of spawnable widgets */ - bool bRebuildRequested; + /** Set to true to force a refresh of the treeview */ + bool bRefreshRequested; /** Are blutility widget supported. */ bool bAllowEditorWidget; diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.cpp b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.cpp new file mode 100644 index 000000000000..ed7dfe85c84f --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.cpp @@ -0,0 +1,489 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Palette/SPaletteViewModel.h" +#include "Palette/SPaletteView.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" +#include "WidgetBlueprint.h" +#include "Editor.h" + +#if WITH_EDITOR + #include "EditorStyleSet.h" +#endif // WITH_EDITOR + +#include "DragDrop/WidgetTemplateDragDropOp.h" + +#include "Templates/WidgetTemplateClass.h" +#include "Templates/WidgetTemplateBlueprintClass.h" + +#include "Developer/HotReload/Public/IHotReload.h" + +#include "AssetRegistryModule.h" +#include "WidgetBlueprintEditorUtils.h" + +#include "Settings/ContentBrowserSettings.h" +#include "Settings/WidgetDesignerSettings.h" +#include "UMGEditorProjectSettings.h" +#include "WidgetPaletteFavorites.h" + +#define LOCTEXT_NAMESPACE "UMG" + +FWidgetTemplateViewModel::FWidgetTemplateViewModel() + : PaletteViewModel(nullptr), + bIsFavorite(false) +{ +} + +FText FWidgetTemplateViewModel::GetName() const +{ + return Template->Name; +} + +bool FWidgetTemplateViewModel::IsTemplate() const +{ + return true; +} + +void FWidgetTemplateViewModel::GetFilterStrings(TArray& OutStrings) const +{ + Template->GetFilterStrings(OutStrings); +} + +TSharedRef FWidgetTemplateViewModel::BuildRow(const TSharedRef& OwnerTable) +{ + return SNew(STableRow>, OwnerTable) + .Padding(2.0f) + .Style(FEditorStyle::Get(), "UMGEditor.PaletteItem") + .OnDragDetected(this, &FWidgetTemplateViewModel::OnDraggingWidgetTemplateItem) + [ + SNew(SPaletteViewItem, SharedThis(this)) + .HighlightText(PaletteViewModel, &FPaletteViewModel::GetSearchText) + ]; +} + +FReply FWidgetTemplateViewModel::OnDraggingWidgetTemplateItem(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + return FReply::Handled().BeginDragDrop(FWidgetTemplateDragDropOp::New(Template)); +} + +void FWidgetTemplateViewModel::AddToFavorites() +{ + bIsFavorite = true; + PaletteViewModel->AddToFavorites(this); +} + +void FWidgetTemplateViewModel::RemoveFromFavorites() +{ + bIsFavorite = false; + PaletteViewModel->RemoveFromFavorites(this); +} + +TSharedRef FWidgetHeaderViewModel::BuildRow(const TSharedRef& OwnerTable) +{ + return SNew(STableRow>, OwnerTable) + .Style(FEditorStyle::Get(), "UMGEditor.PaletteHeader") + .Padding(2.0f) + .ShowSelection(false) + [ + SNew(STextBlock) + .Text(GroupName) + .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ]; +} + +void FWidgetHeaderViewModel::GetChildren(TArray< TSharedPtr >& OutChildren) +{ + for (TSharedPtr& Child : Children) + { + OutChildren.Add(Child); + } +} + +FPaletteViewModel::FPaletteViewModel(TSharedPtr InBlueprintEditor) + : bRebuildRequested(true) +{ + BlueprintEditor = InBlueprintEditor; + + FavoriteHeader = MakeShareable(new FWidgetHeaderViewModel()); + FavoriteHeader->GroupName = LOCTEXT("Favorites", "Favorites"); +} + +void FPaletteViewModel::RegisterToEvents() +{ + // Register for events that can trigger a palette rebuild + GEditor->OnBlueprintReinstanced().AddRaw(this, &FPaletteViewModel::OnBlueprintReinstanced); + FEditorDelegates::OnAssetsDeleted.AddSP(this, &FPaletteViewModel::HandleOnAssetsDeleted); + IHotReloadModule::Get().OnHotReload().AddSP(this, &FPaletteViewModel::HandleOnHotReload); + + // register for any objects replaced + GEditor->OnObjectsReplaced().AddRaw(this, &FPaletteViewModel::OnObjectsReplaced); + + // Register for favorite list update to handle the case where a favorite is added in another window of the UMG Designer + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->OnFavoritesUpdated.AddSP(this, &FPaletteViewModel::OnFavoritesUpdated); +} + +FPaletteViewModel::~FPaletteViewModel() +{ + GEditor->OnBlueprintReinstanced().RemoveAll(this); + FEditorDelegates::OnAssetsDeleted.RemoveAll(this); + IHotReloadModule::Get().OnHotReload().RemoveAll(this); + GEditor->OnObjectsReplaced().RemoveAll(this); + + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->OnFavoritesUpdated.RemoveAll(this); +} + +void FPaletteViewModel::AddToFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel) +{ + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->Add(WidgetTemplateViewModel->GetName().ToString()); +} + +void FPaletteViewModel::RemoveFromFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel) +{ + UWidgetPaletteFavorites* Favorites = GetDefault()->Favorites; + Favorites->Remove(WidgetTemplateViewModel->GetName().ToString()); +} + +void FPaletteViewModel::Update() +{ + if (bRebuildRequested) + { + OnUpdating.Broadcast(); + BuildWidgetList(); + bRebuildRequested = false; + OnUpdated.Broadcast(); + } +} + + +UWidgetBlueprint* FPaletteViewModel::GetBlueprint() const +{ + if (BlueprintEditor.IsValid()) + { + UBlueprint* BP = BlueprintEditor.Pin()->GetBlueprintObj(); + return Cast(BP); + } + + return NULL; +} + +void FPaletteViewModel::BuildWidgetList() +{ + // Clear the current list of view models and categories + WidgetViewModels.Reset(); + WidgetTemplateCategories.Reset(); + + // Generate a list of templates + BuildClassWidgetList(); + + // Clear the Favorite section + bool bHasFavorites = FavoriteHeader->Children.Num() != 0; + FavoriteHeader->Children.Reset(); + + // Copy of the list of favorites to be able to do some cleanup in the real list + UWidgetPaletteFavorites* FavoritesPalette = GetDefault()->Favorites; + TArray FavoritesList = FavoritesPalette->GetFavorites(); + + // For each entry in the category create a view model for the widget template + for ( auto& Entry : WidgetTemplateCategories ) + { + TSharedPtr Header = MakeShareable(new FWidgetHeaderViewModel()); + Header->GroupName = FText::FromString(Entry.Key); + + for ( auto& Template : Entry.Value ) + { + TSharedPtr TemplateViewModel = MakeShareable(new FWidgetTemplateViewModel()); + TemplateViewModel->Template = Template; + TemplateViewModel->PaletteViewModel = this; + Header->Children.Add(TemplateViewModel); + + // If it's a favorite, we also add it to the Favorite section + int32 index = FavoritesList.Find(Template->Name.ToString()); + if (index != INDEX_NONE) + { + TemplateViewModel->SetFavorite(); + + // We have to create a second copy of the ViewModel for the treeview has it doesn't support to have the same element twice. + TSharedPtr FavoriteTemplateViewModel = MakeShareable(new FWidgetTemplateViewModel()); + FavoriteTemplateViewModel->Template = Template; + FavoriteTemplateViewModel->PaletteViewModel = this; + FavoriteTemplateViewModel->SetFavorite(); + + FavoriteHeader->Children.Add(FavoriteTemplateViewModel); + + // Remove the favorite from the temporary list + FavoritesList.RemoveAt(index); + } + + } + + Header->Children.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); + + WidgetViewModels.Add(Header); + } + + // Remove all Favorites that may be left in the list.Typically happening when the list of favorite contains widget that were deleted since the last opening. + for (const FString& favoriteName : FavoritesList) + { + FavoritesPalette->Remove(favoriteName); + } + + // Sort the view models by name + WidgetViewModels.Sort([] (TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); + + // Add the Favorite section at the top + if (FavoriteHeader->Children.Num() != 0) + { + // We force expansion of the favorite header when we add favorites for the first time. + FavoriteHeader->SetForceExpansion(!bHasFavorites); + FavoriteHeader->Children.Sort([](TSharedPtr L, TSharedPtr R) { return R->GetName().CompareTo(L->GetName()) > 0; }); + WidgetViewModels.Insert(FavoriteHeader, 0); + } + + // Take the Advanced Section, and put it at the end. + TSharedPtr* advancedSectionPtr = WidgetViewModels.FindByPredicate([](TSharedPtr widget) {return widget->GetName().CompareTo(LOCTEXT("Advanced", "Advanced")) == 0; }); + if (advancedSectionPtr) + { + TSharedPtr advancedSection = *advancedSectionPtr; + WidgetViewModels.Remove(advancedSection); + WidgetViewModels.Push(advancedSection); + } +} + +void FPaletteViewModel::BuildClassWidgetList() +{ + static const FName DevelopmentStatusKey(TEXT("DevelopmentStatus")); + + TMap> LoadedWidgetBlueprintClassesByName; + + auto ActiveWidgetBlueprintClass = GetBlueprint()->GeneratedClass; + FName ActiveWidgetBlueprintClassName = ActiveWidgetBlueprintClass->GetFName(); + + TArray WidgetClassesToHide = GetDefault()->WidgetClassesToHide; + + // Locate all UWidget classes from code and loaded widget BPs + for (TObjectIterator ClassIt; ClassIt; ++ClassIt) + { + UClass* WidgetClass = *ClassIt; + + if (!FWidgetBlueprintEditorUtils::IsUsableWidgetClass(WidgetClass)) + { + continue; + } + + // Initialize AssetData for checking PackagePath + FAssetData WidgetAssetData = FAssetData(WidgetClass); + + // Excludes engine content if user sets it to false + if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) + { + if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) + { + continue; + } + } + + // Excludes developer content if user sets it to false + if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) + { + if (WidgetAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) + { + continue; + } + } + + // Excludes this widget if it is on the hide list + bool bIsOnList = false; + for (FSoftClassPath Widget : WidgetClassesToHide) + { + if (WidgetAssetData.ObjectPath.ToString().Find(Widget.ToString()) == 0) + { + bIsOnList = true; + break; + } + } + if (bIsOnList) + { + continue; + } + + const bool bIsSameClass = WidgetClass->GetFName() == ActiveWidgetBlueprintClassName; + + // Check that the asset that generated this class is valid (necessary b/c of a larger issue wherein force delete does not wipe the generated class object) + if ( bIsSameClass ) + { + continue; + } + + if (WidgetClass->IsChildOf(UUserWidget::StaticClass())) + { + if ( WidgetClass->ClassGeneratedBy ) + { + // Track the widget blueprint classes that are already loaded + LoadedWidgetBlueprintClassesByName.Add(WidgetClass->ClassGeneratedBy->GetFName()) = WidgetClass; + } + } + else + { + TSharedPtr Template = MakeShareable(new FWidgetTemplateClass(WidgetClass)); + + AddWidgetTemplate(Template); + } + + //TODO UMG does not prevent deep nested circular references + } + + // Locate all widget BP assets (include unloaded) + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + TArray AllBPsAssetData; + AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), AllBPsAssetData, true); + + for (FAssetData& BPAssetData : AllBPsAssetData) + { + // Blueprints get the class type actions for their parent native class - this avoids us having to load the blueprint + UClass* ParentClass = nullptr; + FString ParentClassName; + if (!BPAssetData.GetTagValue(FBlueprintTags::NativeParentClassPath, ParentClassName)) + { + BPAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassName); + } + if (!ParentClassName.IsEmpty()) + { + UObject* Outer = nullptr; + ResolveName(Outer, ParentClassName, false, false); + ParentClass = FindObject(ANY_PACKAGE, *ParentClassName); + // UUserWidgets have their own loading section, and we don't want to process any blueprints that don't have UWidget parents + if (!ParentClass->IsChildOf(UWidget::StaticClass()) || ParentClass->IsChildOf(UUserWidget::StaticClass())) + { + continue; + } + } + + if (!FilterAssetData(BPAssetData)) + { + // If this object isn't currently loaded, add it to the palette view + if (BPAssetData.ToSoftObjectPath().ResolveObject() == nullptr) + { + auto Template = MakeShareable(new FWidgetTemplateClass(BPAssetData, nullptr)); + AddWidgetTemplate(Template); + } + } + } + + TArray AllWidgetBPsAssetData; + AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetFName(), AllWidgetBPsAssetData, true); + + FName ActiveWidgetBlueprintName = ActiveWidgetBlueprintClass->ClassGeneratedBy->GetFName(); + for (FAssetData& WidgetBPAssetData : AllWidgetBPsAssetData) + { + // Excludes the blueprint you're currently in + if (WidgetBPAssetData.AssetName == ActiveWidgetBlueprintName) + { + continue; + } + + if (!FilterAssetData(WidgetBPAssetData)) + { + // Excludes this widget if it is on the hide list + bool bIsOnList = false; + for (FSoftClassPath Widget : WidgetClassesToHide) + { + if (Widget.ToString().Find(WidgetBPAssetData.ObjectPath.ToString()) == 0) + { + bIsOnList = true; + break; + } + } + if (bIsOnList) + { + continue; + } + + // If the blueprint generated class was found earlier, pass it to the template + TSubclassOf WidgetBPClass = nullptr; + auto LoadedWidgetBPClass = LoadedWidgetBlueprintClassesByName.Find(WidgetBPAssetData.AssetName); + if (LoadedWidgetBPClass) + { + WidgetBPClass = *LoadedWidgetBPClass; + } + + auto Template = MakeShareable(new FWidgetTemplateBlueprintClass(WidgetBPAssetData, WidgetBPClass)); + + AddWidgetTemplate(Template); + } + } +} + +bool FPaletteViewModel::FilterAssetData(FAssetData &InAssetData) +{ + // Excludes engine content if user sets it to false + if (!GetDefault()->GetDisplayEngineFolder() || !GetDefault()->bShowWidgetsFromEngineContent) + { + if (InAssetData.PackagePath.ToString().Find(TEXT("/Engine")) == 0) + { + return true; + } + } + + // Excludes developer content if user sets it to false + if (!GetDefault()->GetDisplayDevelopersFolder() || !GetDefault()->bShowWidgetsFromDeveloperContent) + { + if (InAssetData.PackagePath.ToString().Find(TEXT("/Game/Developers")) == 0) + { + return true; + } + } + return false; +} + +void FPaletteViewModel::AddWidgetTemplate(TSharedPtr Template) +{ + FString Category = Template->GetCategory().ToString(); + + // Hide user specific categories + TArray CategoriesToHide = GetDefault()->CategoriesToHide; + for (FString CategoryName : CategoriesToHide) + { + if (Category == CategoryName) + { + return; + } + } + WidgetTemplateArray& Group = WidgetTemplateCategories.FindOrAdd(Category); + Group.Add(Template); +} + +void FPaletteViewModel::OnObjectsReplaced(const TMap& ReplacementMap) +{ +} + +void FPaletteViewModel::OnBlueprintReinstanced() +{ + bRebuildRequested = true; +} + +void FPaletteViewModel::OnFavoritesUpdated() +{ + bRebuildRequested = true; +} + +void FPaletteViewModel::HandleOnHotReload(bool bWasTriggeredAutomatically) +{ + bRebuildRequested = true; +} + +void FPaletteViewModel::HandleOnAssetsDeleted(const TArray& DeletedAssetClasses) +{ + for (auto DeletedAssetClass : DeletedAssetClasses) + { + if (DeletedAssetClass->IsChildOf(UWidgetBlueprint::StaticClass())) + { + bRebuildRequested = true; + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.h b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.h new file mode 100644 index 000000000000..0b72dd36368e --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Private/Palette/SPaletteViewModel.h @@ -0,0 +1,200 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "WidgetBlueprintEditor.h" +#include "AssetData.h" + +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" + +class FWidgetTemplate; +class FWidgetBlueprintEditor; +class UWidgetBlueprint; +class SPaletteView; + +/** View model for the items in the widget template list */ +class FWidgetViewModel : public TSharedFromThis +{ +public: + virtual ~FWidgetViewModel() { } + + virtual FText GetName() const = 0; + + virtual bool IsTemplate() const = 0; + + /** @param OutStrings - Returns an array of strings used for filtering/searching this item. */ + virtual void GetFilterStrings(TArray& OutStrings) const = 0; + + virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) = 0; + + virtual void GetChildren(TArray< TSharedPtr >& OutChildren) + { + } + + /** Return true if the widget is a favorite */ + virtual bool IsFavorite() const { return false; } + + /** Set the favorite flag */ + virtual void SetFavorite() + { + } + + virtual bool ShouldForceExpansion() const { return false; } +}; + +class FWidgetTemplateViewModel : public FWidgetViewModel +{ +public: + FWidgetTemplateViewModel(); + + virtual FText GetName() const override; + + virtual bool IsTemplate() const override; + + virtual void GetFilterStrings(TArray& OutStrings) const override; + + virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override; + + FReply OnDraggingWidgetTemplateItem(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent); + + /** Add the widget template to the list of favorites */ + void AddToFavorites(); + + /** Remove the widget template from the list of favorites */ + void RemoveFromFavorites(); + + /** Return true if the widget is a favorite */ + virtual bool IsFavorite() const override { return bIsFavorite; } + + /** Set the favorite flag */ + virtual void SetFavorite() override { bIsFavorite = true; } + + TSharedPtr Template; + FPaletteViewModel* PaletteViewModel; +private: + /** True is the widget is a favorite. It's keep as a state to prevent a search in the favorite list. */ + bool bIsFavorite; +}; + +class FWidgetHeaderViewModel : public FWidgetViewModel +{ +public: + virtual ~FWidgetHeaderViewModel() + { + } + + virtual FText GetName() const override + { + return GroupName; + } + + virtual bool IsTemplate() const override + { + return false; + } + + virtual void GetFilterStrings(TArray& OutStrings) const override + { + // Headers should never be included in filtering to avoid showing a header with all of + // it's widgets filtered out, so return an empty filter string. + } + + virtual TSharedRef BuildRow(const TSharedRef& OwnerTable) override; + + virtual void GetChildren(TArray< TSharedPtr >& OutChildren) override; + + virtual bool ShouldForceExpansion() const { return bForceExpansion; } + + void SetForceExpansion(bool bInForceExpansion) { bForceExpansion = bInForceExpansion; } + + FText GroupName; + TArray< TSharedPtr > Children; + +private: + + bool bForceExpansion = false; +}; + +class FPaletteViewModel: public TSharedFromThis +{ +public: + + DECLARE_MULTICAST_DELEGATE(FOnUpdating) + DECLARE_MULTICAST_DELEGATE(FOnUpdated) + +public: + FPaletteViewModel(TSharedPtr InBlueprintEditor); + ~FPaletteViewModel(); + + /** Register the View Model to events that should trigger a update of the Palette*/ + void RegisterToEvents(); + + /** Update the view model if needed and returns true if it did. */ + void Update(); + + /** Returns true if the view model needs to be updated */ + bool NeedUpdate() const { return bRebuildRequested; } + + /** Add the widget template to the list of favorites */ + void AddToFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel); + + /** Remove the widget template to the list of favorites */ + void RemoveFromFavorites(const FWidgetTemplateViewModel* WidgetTemplateViewModel); + + typedef TArray< TSharedPtr > ViewModelsArray; + ViewModelsArray& GetWidgetViewModels() { return WidgetViewModels; } + + void SetSearchText(const FText& inSearchText) { SearchText = inSearchText; } + FText GetSearchText() const { return SearchText; } + + /** Fires before the view model is updated */ + FOnUpdating OnUpdating; + + /** Fires after the view model is updated */ + FOnUpdated OnUpdated; + +private: + FPaletteViewModel() {}; + + UWidgetBlueprint* GetBlueprint() const; + + void BuildWidgetList(); + void BuildClassWidgetList(); + + static bool FilterAssetData(FAssetData &BPAssetData); + + void AddWidgetTemplate(TSharedPtr Template); + + /** Called when a Blueprint is recompiled and live objects are swapped out for replacements */ + void OnObjectsReplaced(const TMap& ReplacementMap); + + /** Requests a rebuild of the widget list if a widget blueprint was compiled */ + void OnBlueprintReinstanced(); + + /** Called when the favorite list is changed */ + void OnFavoritesUpdated(); + + /** Requests a rebuild of the widget list */ + void HandleOnHotReload(bool bWasTriggeredAutomatically); + + /** Requests a rebuild of the widget list if a widget blueprint was deleted */ + void HandleOnAssetsDeleted(const TArray& DeletedAssetClasses); + + TWeakPtr BlueprintEditor; + + typedef TArray> WidgetTemplateArray; + TMap WidgetTemplateCategories; + + /** The source root view models for the tree. */ + ViewModelsArray WidgetViewModels; + + /** Controls rebuilding the list of spawnable widgets */ + bool bRebuildRequested; + + FText SearchText; + + TSharedPtr FavoriteHeader; +}; + diff --git a/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp b/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp index ee13f0404786..b0a2cfe461d6 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Settings/WidgetDesignerSettings.cpp @@ -4,6 +4,9 @@ UWidgetDesignerSettings::UWidgetDesignerSettings() { + + Favorites = CreateDefaultSubobject(TEXT("WidgetPaletteFavorites")); + CategoryName = TEXT("ContentEditors"); GridSnapEnabled = true; diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp index fade14e18478..b1a417055a0f 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.cpp @@ -87,11 +87,6 @@ FReply FWidgetTemplateBlueprintClass::OnDoubleClicked() return FReply::Handled(); } -FAssetData FWidgetTemplateBlueprintClass::GetWidgetAssetData() -{ - return WidgetAssetData; -} - bool FWidgetTemplateBlueprintClass::Supports(UClass* InClass) { return InClass != nullptr && InClass->IsChildOf(UWidgetBlueprint::StaticClass()); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h index 3a65eb744d83..c33fe714d5f3 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateBlueprintClass.h @@ -44,9 +44,6 @@ public: /** Opens the widget blueprint for edit */ virtual FReply OnDoubleClicked() override; - /** Gets the asset data for this widget blueprint */ - FAssetData GetWidgetAssetData(); - /** Returns true if the supplied class is supported by this template */ static bool Supports(UClass* InClass); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp index e5f330933bc6..e123b2cb4af4 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.cpp @@ -113,6 +113,19 @@ TSharedRef FWidgetTemplateClass::GetToolTip() const } } +void FWidgetTemplateClass::GetFilterStrings(TArray& OutStrings) const +{ + FWidgetTemplate::GetFilterStrings(OutStrings); + if (WidgetClass.IsValid()) + { + OutStrings.Add(WidgetClass->GetName()); + } + if (WidgetAssetData.IsValid()) + { + OutStrings.Add(WidgetAssetData.AssetName.ToString()); + } +} + void FWidgetTemplateClass::OnObjectsReplaced(const TMap& ReplacementMap) { UObject* const* NewObject = ReplacementMap.Find(WidgetClass.Get()); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h index 5d1d6f7182e5..3235cf0188e3 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateClass.h @@ -37,9 +37,15 @@ public: /** Gets the tooltip widget for this palette item. */ virtual TSharedRef GetToolTip() const override; - /** Gets the WidgetClass */ + /** @param OutStrings - Returns an array of strings used for filtering/searching this widget template. */ + virtual void GetFilterStrings(TArray& OutStrings) const override; + + /** Gets the WidgetClass which might be null. */ TWeakObjectPtr GetWidgetClass() const { return WidgetClass; } + /** Returns the asset data for this widget which might be invalid. */ + FAssetData GetWidgetAssetData() { return WidgetAssetData; } + protected: /** Creates a widget template class without any class reference */ FWidgetTemplateClass(); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp index c3e1bc8ed0a9..8ccc96038f14 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.cpp @@ -6,8 +6,7 @@ #include "Engine/Texture.h" FWidgetTemplateImageClass::FWidgetTemplateImageClass(const FAssetData& InAssetData) - : FWidgetTemplateClass(UImage::StaticClass()) - , WidgetAssetData(InAssetData) + : FWidgetTemplateClass(InAssetData, UImage::StaticClass()) { } @@ -30,11 +29,6 @@ UWidget* FWidgetTemplateImageClass::Create(UWidgetTree* WidgetTree) return Widget; } -FAssetData FWidgetTemplateImageClass::GetWidgetAssetData() -{ - return WidgetAssetData; -} - bool FWidgetTemplateImageClass::Supports(UClass* InClass) { static const UClass* SlateTextureAtlasInterface = FindObject(ANY_PACKAGE, TEXT("SlateTextureAtlasInterface")); diff --git a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h index 0db0bffe76ee..b0c0c08d2425 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h +++ b/Engine/Source/Editor/UMGEditor/Private/Templates/WidgetTemplateImageClass.h @@ -27,12 +27,6 @@ public: /** Creates an instance of the widget for the tree */ virtual UWidget* Create(UWidgetTree* WidgetTree) override; - /** Returns the asset data for this widget */ - FAssetData GetWidgetAssetData(); - /** Returns true if the supplied class is supported by this template */ static bool Supports(UClass* InClass); - -private: - FAssetData WidgetAssetData; }; diff --git a/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp b/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp index 5d6f5a3a8a6c..783e545524df 100644 --- a/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/UMGEditorModule.cpp @@ -61,6 +61,7 @@ public: MenuExtensibilityManager = MakeShareable(new FExtensibilityManager()); ToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager()); + DesignerExtensibilityManager = MakeShareable(new FDesignerExtensibilityManager()); // Register widget blueprint compiler we do this no matter what. IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked("KismetCompiler"); @@ -134,6 +135,7 @@ public: /** Gets the extensibility managers for outside entities to extend gui page editor's menus and toolbars */ virtual TSharedPtr GetMenuExtensibilityManager() override { return MenuExtensibilityManager; } virtual TSharedPtr GetToolBarExtensibilityManager() override { return ToolBarExtensibilityManager; } + virtual TSharedPtr GetDesignerExtensibilityManager() override { return DesignerExtensibilityManager; } /** Register settings objects. */ void RegisterSettings() @@ -186,6 +188,7 @@ private: private: TSharedPtr MenuExtensibilityManager; TSharedPtr ToolBarExtensibilityManager; + TSharedPtr DesignerExtensibilityManager; FDelegateHandle SequenceEditorHandle; FDelegateHandle MarginTrackEditorCreateTrackEditorHandle; diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp index d8794524c2d3..1e3c7f6dc10f 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintCompiler.cpp @@ -278,18 +278,41 @@ void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() Super::CreateClassVariablesFromBlueprint(); UWidgetBlueprint* WidgetBP = WidgetBlueprint(); + if (WidgetBP == nullptr) + { + return; + } + UClass* ParentClass = WidgetBP->ParentClass; ValidateWidgetNames(); - // Build the set of variables based on the variable widgets in the widget tree. - TArray Widgets = WidgetBP->GetAllSourceWidgets(); + // Build the set of variables based on the variable widgets in the first Widget Tree we find: + // in the current blueprint, the parent blueprint, and so on, until we find one. + TArray Widgets; + UWidgetBlueprint* WidgetBPToScan = WidgetBP; + bool bSkipVariableCreation = false; + while (WidgetBPToScan != nullptr) + { + Widgets = WidgetBPToScan->GetAllSourceWidgets(); + if (Widgets.Num() != 0) + { + // We found widgets. + break; + } + // We don't want to create variables for widgets that are in a parent blueprint. They will be created at the Parent compilation. + // But we want them to be added to the Member variable map for validation of the BindWidget property + bSkipVariableCreation = true; + + // Get the parent WidgetBlueprint + WidgetBPToScan = WidgetBPToScan->ParentClass && WidgetBPToScan->ParentClass->ClassGeneratedBy ? Cast(WidgetBPToScan->ParentClass->ClassGeneratedBy):nullptr; + } // Sort the widgets alphabetically Widgets.Sort( []( const UWidget& Lhs, const UWidget& Rhs ) { return Rhs.GetFName().LexicalLess(Lhs.GetFName()); } ); // Add widget variables - for ( UWidget* Widget : Widgets ) + for ( UWidget* Widget : Widgets ) { bool bIsVariable = Widget->bIsVariable; @@ -311,6 +334,7 @@ void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() WidgetClass = BPWidgetClass->GetAuthoritativeClass(); } + // Look in the Parent class properties to find a property with the BindWidget meta tag of the same name and Type. UObjectPropertyBase* ExistingProperty = Cast(ParentClass->FindPropertyByName(Widget->GetFName())); if (ExistingProperty && FWidgetBlueprintEditorUtils::IsBindWidgetProperty(ExistingProperty) && @@ -326,6 +350,12 @@ void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint() continue; } + // We skip variable creation if the Widget Tree was in the Parent Blueprint. + if (bSkipVariableCreation) + { + continue; + } + FEdGraphPinType WidgetPinType(UEdGraphSchema_K2::PC_Object, NAME_None, WidgetClass, EPinContainerType::None, false, FEdGraphTerminalType()); // Always name the variable according to the underlying FName of the widget object diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp index 20af96ce6af3..e278007abe4d 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditor.cpp @@ -50,6 +50,8 @@ #include "UMGEditorActions.h" #include "GameProjectGenerationModule.h" +#include "SPaletteViewModel.h" + #define LOCTEXT_NAMESPACE "UMG" FWidgetBlueprintEditor::FWidgetBlueprintEditor() @@ -102,6 +104,10 @@ void FWidgetBlueprintEditor::InitWidgetBlueprintEditor(const EToolkitMode::Type bRespectLocks = GetDefault()->bRespectLocks; TSharedPtr ThisPtr(SharedThis(this)); + + PaletteViewModel = MakeShareable(new FPaletteViewModel(ThisPtr)); + PaletteViewModel->RegisterToEvents(); + WidgetToolbar = MakeShareable(new FWidgetBlueprintEditorToolbar(ThisPtr)); BindToolkitCommands(); @@ -552,6 +558,12 @@ void FWidgetBlueprintEditor::Tick(float DeltaTime) bPreviewInvalidated = false; RefreshPreview(); } + + // Updat the palette view model. + if (PaletteViewModel->NeedUpdate()) + { + PaletteViewModel->Update(); + } } static bool MigratePropertyValue(UObject* SourceObject, UObject* DestinationObject, FEditPropertyChain::TDoubleLinkedListNode* PropertyChainNode, UProperty* MemberProperty, bool bIsModify) @@ -960,17 +972,15 @@ void FWidgetBlueprintEditor::UpdatePreview(UBlueprint* InBlueprint, bool bInForc UWidgetTree* LatestWidgetTree = PreviewBlueprint->WidgetTree; - // HACK NickD: Doing this to match the hack in UUserWidget::Initialize(), to permit some semblance of widgettree - // inheritance. This will correctly show the parent widget tree provided your class does not specify a root. - UWidgetBlueprintGeneratedClass* SuperBGClass = Cast(PreviewBlueprint->GeneratedClass->GetSuperClass()); - if ( SuperBGClass ) + // If there is no RootWidget, we look for a WidgetTree in the parents classes until we find one. + if (LatestWidgetTree->RootWidget == nullptr) { - UWidgetBlueprint* SuperWidgetBlueprint = Cast(SuperBGClass->ClassGeneratedBy); - if ( SuperWidgetBlueprint && (LatestWidgetTree->RootWidget == nullptr) ) + UWidgetBlueprintGeneratedClass* BGClass = PreviewUserWidget->GetWidgetTreeOwningClass(); + if (BGClass) { - LatestWidgetTree = SuperWidgetBlueprint->WidgetTree; + LatestWidgetTree = BGClass->WidgetTree; } - } + } // Update the widget tree directly to match the blueprint tree. That way the preview can update // without needing to do a full recompile. diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp index 11db790f1938..080eaba3d429 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.cpp @@ -783,11 +783,12 @@ void FWidgetBlueprintEditorUtils::ReplaceWidgetWithSelectedTemplate(TSharedRefReplaceChild(ThisWidget, NewReplacementWidget); FString ReplaceName = ThisWidget->GetName(); + bool bIsGeneratedName = ThisWidget->IsGeneratedName(); // Rename the removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name. ThisWidget->Rename(nullptr, nullptr); // Rename the new Widget to maintain the current name if it's not a generic name - if (!IsGenericName(ReplaceName, ThisWidget->GetClass())) + if (!bIsGeneratedName) { ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName); NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree); @@ -948,11 +949,12 @@ void FWidgetBlueprintEditorUtils::ReplaceWidgets(TSharedRefGetName(); + bool bIsGeneratedName = Item.GetTemplate()->IsGeneratedName(); // Rename the removed widget to the transient package so that it doesn't conflict with future widgets sharing the same name. Item.GetTemplate()->Rename(nullptr, nullptr); // Rename the new Widget to maintain the current name if it's not a generic name - if (!IsGenericName(ReplaceName, Item.GetTemplate()->GetClass())) + if (!bIsGeneratedName) { ReplaceName = FindNextValidName(BP->WidgetTree, ReplaceName); NewReplacementWidget->Rename(*ReplaceName, BP->WidgetTree); @@ -1367,6 +1369,17 @@ void FWidgetBlueprintEditorUtils::ImportWidgetsFromText(UWidgetBlueprint* BP, co Widget->SetFlags(RF_Transactional); + // We don't export parent slot pointers, so each panel will need to point it's children back to itself + UPanelWidget* PanelWidget = Cast(Widget); + if (PanelWidget) + { + TArray PanelSlots = PanelWidget->GetSlots(); + for (int32 i = 0; i < PanelWidget->GetChildrenCount(); i++) + { + PanelWidget->GetChildAt(i)->Slot = PanelSlots[i]; + } + } + // If there is an existing widget with the same name, rename the newly placed widget. FString WidgetOldName = Widget->GetName(); FString NewName = FindNextValidName(BP->WidgetTree, WidgetOldName); @@ -1549,10 +1562,4 @@ FString FWidgetBlueprintEditorUtils::FindNextValidName(UWidgetTree* WidgetTree, return Name; } -bool FWidgetBlueprintEditorUtils::IsGenericName(const FString& Name, const UClass* WidgetClass) -{ - FString NewName = RemoveSuffixFromName(Name); - return (NewName == WidgetClass->GetName()); -} - #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h index dbe23abd6437..30f5ddd448b8 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintEditorUtils.h @@ -88,6 +88,4 @@ private: static void ReplaceWidgets(TSharedRef BlueprintEditor, UWidgetBlueprint* BP, TSet Widgets, UClass* WidgetClass); static FString FindNextValidName(UWidgetTree* WidgetTree, const FString& Name); - - static bool IsGenericName(const FString& Name, const UClass* WidgetClass); }; diff --git a/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h b/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h index 5fefe58978c1..6ce221a43e95 100644 --- a/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h +++ b/Engine/Source/Editor/UMGEditor/Public/DesignerExtension.h @@ -109,20 +109,23 @@ public: /** Initializes the designer extension, this is called the first time a designer extension is registered */ virtual void Initialize(IUMGDesigner* InDesigner, UWidgetBlueprint* InBlueprint); + /** Returns true if the designer extension can extend the current selection. */ virtual bool CanExtendSelection(const TArray< FWidgetReference >& Selection) const { return false; } - /** Called every time the selection in the designer changes. */ + /** Called every time a element the designer can extend is selected. */ virtual void ExtendSelection(const TArray< FWidgetReference >& Selection, TArray< TSharedRef >& SurfaceElements) { } + /** Called each frames to tick the extension. */ virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { } + /** Called to paint the extension. */ virtual void Paint(const TSet< FWidgetReference >& Selection, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId) const { } @@ -130,6 +133,11 @@ public: /** Gets the ID identifying this extension. */ FName GetExtensionId() const; + bool operator==(const FDesignerExtension& Other) const + { + return ExtensionId.IsEqual(Other.GetExtensionId()); + } + protected: void BeginTransaction(const FText& SessionName); diff --git a/Engine/Source/Editor/UMGEditor/Public/IHasDesignerExtensibility.h b/Engine/Source/Editor/UMGEditor/Public/IHasDesignerExtensibility.h new file mode 100644 index 000000000000..384a8c5393f8 --- /dev/null +++ b/Engine/Source/Editor/UMGEditor/Public/IHasDesignerExtensibility.h @@ -0,0 +1,39 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DesignerExtension.h" + +/** + * Designer Extensibility Manager keep a series of Designer Extensions. See FDesignerExtension class for more information. + */ +class UMGEDITOR_API FDesignerExtensibilityManager +{ +public: + void AddDesignerExtension(const TSharedRef& Extension) + { + ExternalExtensions.AddUnique(Extension); + } + + void RemoveDesignerExtension(const TSharedRef& Extension) + { + ExternalExtensions.Remove(Extension); + } + + const TArray>& GetExternalDesignerExtensions() const + { + return ExternalExtensions; + } + +private: + TArray> ExternalExtensions; +}; + +/** Indicates that a class has a designer that is extensible */ +class IHasDesignerExtensibility +{ +public: + virtual TSharedPtr GetDesignerExtensibilityManager() = 0; +}; + diff --git a/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h b/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h index 41f8865c86ad..41142cd95980 100644 --- a/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h +++ b/Engine/Source/Editor/UMGEditor/Public/Settings/WidgetDesignerSettings.h @@ -6,6 +6,7 @@ #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "Engine/DeveloperSettings.h" +#include "WidgetPaletteFavorites.h" #include "WidgetDesignerSettings.generated.h" /** @@ -61,4 +62,11 @@ public: */ UPROPERTY(EditAnywhere, config, Category = Interaction) bool bRespectLocks; + + /** + * List of Favorites widgets used to populate the Favorites Palette + */ + UPROPERTY() + UWidgetPaletteFavorites* Favorites; + }; diff --git a/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h b/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h index d1abfc52aa4d..555e15cb999d 100644 --- a/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h +++ b/Engine/Source/Editor/UMGEditor/Public/UMGEditorModule.h @@ -5,6 +5,7 @@ #include "CoreMinimal.h" #include "Modules/ModuleInterface.h" #include "Toolkits/AssetEditorToolkit.h" +#include "IHasDesignerExtensibility.h" extern const FName UMGEditorAppIdentifier; @@ -12,7 +13,7 @@ class FUMGEditor; class FWidgetBlueprintCompiler; /** The public interface of the UMG editor module. */ -class IUMGEditorModule : public IModuleInterface, public IHasMenuExtensibility, public IHasToolBarExtensibility +class IUMGEditorModule : public IModuleInterface, public IHasMenuExtensibility, public IHasToolBarExtensibility, public IHasDesignerExtensibility { public: virtual FWidgetBlueprintCompiler* GetRegisteredCompiler() = 0; diff --git a/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h b/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h index 46362c5ea643..db2572869c7c 100644 --- a/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h +++ b/Engine/Source/Editor/UMGEditor/Public/WidgetBlueprintEditor.h @@ -22,6 +22,7 @@ class STextBlock; class UPanelSlot; class UWidgetAnimation; class UWidgetBlueprint; +class FPaletteViewModel; struct FNamedSlotSelection { @@ -172,6 +173,8 @@ public: bool GetIsRespectingLocks() const; void SetIsRespectingLocks(bool Value); + TSharedPtr GetPaletteViewModel() { return PaletteViewModel; }; + public: /** Fires whenever a new widget is being hovered over */ FOnHoveredWidgetSet OnHoveredWidgetSet; @@ -361,4 +364,7 @@ private: /** When true the animation data in the generated class should be replaced with the current animation data. */ bool bRefreshGeneratedClassAnimations; + + /** ViewModel used by the Palette and Palette Favorite Views */ + TSharedPtr PaletteViewModel; }; diff --git a/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h b/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h index f96ca2d314c2..15975acc28df 100644 --- a/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h +++ b/Engine/Source/Editor/UMGEditor/Public/WidgetTemplate.h @@ -26,16 +26,19 @@ public: /** Constructs the widget template. */ virtual UWidget* Create(class UWidgetTree* Tree) = 0; - /** Gets the icon to display in the template palate for this template. */ + /** Gets the icon to display for this widget template. */ virtual const FSlateBrush* GetIcon() const { static FSlateNoResource NullBrush; return &NullBrush; } - /** Gets tooltip widget for this palette item. */ + /** Gets tooltip widget for this widget template. */ virtual TSharedRef GetToolTip() const = 0; + /** @param OutStrings - Returns an array of strings used for filtering/searching this widget template. */ + virtual void GetFilterStrings(TArray& OutStrings) const { OutStrings.Add(Name.ToString()); } + /** The the action to perform when the template item is double clicked */ virtual FReply OnDoubleClicked() { return FReply::Unhandled(); } diff --git a/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h b/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h index 95e3dc5c5a25..42e24e2c9fb9 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Animation/DebugSkelMeshComponent.h @@ -510,7 +510,13 @@ public: */ virtual FTransform GetDrawTransform(int32 BoneIndex) const { - return GetComponentSpaceTransforms()[BoneIndex]; + const TArray& SpaceTransforms = GetComponentSpaceTransforms(); + if (SpaceTransforms.IsValidIndex(BoneIndex)) + { + return SpaceTransforms[BoneIndex]; + } + + return FTransform::Identity; } }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h b/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h index 1a8dcaab69c4..70a46d58f7c6 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h @@ -1112,6 +1112,13 @@ public: */ void MoveViewportCamerasToComponent(USceneComponent* Component, bool bActiveViewportOnly); + /** + * Moves all viewport cameras to focus on the provided bounding box. + * @param BoundingBox Target box + * @param bActiveViewportOnly If true, move/reorient only the active viewport. + */ + void MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const; + /** * Snaps an actor in a direction. Optionally will align with the trace normal. * @param InActor Actor to move to the floor. @@ -3030,13 +3037,6 @@ private: /** Gets the init values for worlds opened via Map_Load in the editor */ UWorld::InitializationValues GetEditorWorldInitializationValues() const; - /** - * Moves all viewport cameras to focus on the provided bounding box. - * @param BoundingBox Target box - * @param bActiveViewportOnly If true, move/reorient only the active viewport. - */ - void MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const; - public: // Launcher Worker TSharedPtr LauncherWorker; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h b/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h index 46c0b43668a6..c9f9d3560802 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Editor/PropertyEditorTestObject.h @@ -19,7 +19,7 @@ class UStaticMeshComponent; class UTexture; UENUM() -enum PropertEditorTestEnum +enum PropertyEditorTestEnum { /** This comment should appear above enum 1 */ PropertyEditorTest_Enum1 UMETA(Hidden), @@ -124,13 +124,11 @@ class UPropertyEditorTestObject : public UObject { GENERATED_UCLASS_BODY() - - UPROPERTY(EditAnywhere, Category = BasicProperties) int8 Int8Property; UPROPERTY(EditAnywhere, Category = BasicProperties) - int16 Int16roperty; + int16 Int16Property; UPROPERTY(EditAnywhere, Category = BasicProperties) int32 Int32Property; @@ -196,7 +194,10 @@ class UPropertyEditorTestObject : public UObject FColor ColorProperty; UPROPERTY(EditAnywhere, Category=BasicProperties) - TEnumAsByte EnumProperty; + TEnumAsByte EnumByteProperty; + + UPROPERTY(EditAnywhere, Category=BasicProperties) + EditColor EnumProperty; UPROPERTY(EditAnywhere, Category = BasicProperties) FMatrix MatrixProperty; @@ -252,7 +253,7 @@ class UPropertyEditorTestObject : public UObject TArray ColorPropertyArray; UPROPERTY(EditAnywhere, Category=ArraysOfProperties) - TArray > EnumPropertyArray; + TArray > EnumPropertyArray; UPROPERTY(EditAnywhere, Category=ArraysOfProperties) TArray StructPropertyArray; @@ -266,7 +267,7 @@ class UPropertyEditorTestObject : public UObject UPROPERTY(EditAnywhere, Category=ArraysOfProperties) int32 StaticArrayOfIntsWithEnumLabels[ArrayIndex_MAX]; - /** This is a float property tooltip that is overridden */ + // This is a float property tooltip that is overridden UPROPERTY(EditAnywhere, Category=AdvancedProperties, meta=(ClampMin = "0.0", ClampMax = "100.0", UIMin = "0.0", UIMax = "50.0", ToolTip = "This is a custom tooltip that should be shown")) float FloatPropertyWithClampedRange; @@ -385,7 +386,7 @@ class UPropertyEditorTestObject : public UObject TMap ObjectToColorMap; UPROPERTY(EditAnywhere, Category=TMapTests) - TMap > IntToEnumMap; + TMap > IntToEnumMap; UPROPERTY(EditAnywhere, Category=TMapTests) TMap NameToNameMap; @@ -419,4 +420,67 @@ class UPropertyEditorTestObject : public UObject // filter for AllowedClasses correctly. UPROPERTY(EditAnywhere, Category=ObjectPropertyAllowedClasses, meta=(AllowedClasses="Texture,BlendableInterface")) UObject* TextureOrBlendableInterface; + + UPROPERTY(EditAnywhere, Category = "Subcategory") + bool bSubcategory; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Subcategory") + bool bSubcategoryAdvanced; + + UPROPERTY(EditAnywhere, Category = "Subcategory|Foo") + bool bSubcategoryFooSimple; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Subcategory|Foo") + bool bSubcategoryFooAdvanced; + + UPROPERTY(EditAnywhere, Category = "Subcategory|Bar") + bool bSubcategoryBarSimple; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Subcategory|Bar") + bool bSubcategoryBarAdvanced; + + UPROPERTY(EditAnywhere, Category = "Subcategory") + bool bSubcategoryLast; + + UPROPERTY(EditAnywhere, Category = EditCondition) + bool bEnablesNext; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "bEnablesNext == true")) + bool bEnabledByPrevious; + + UPROPERTY(EditAnywhere, Category = EditCondition) + EditColor EnumEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumEditCondition == EditColor::Blue")) + bool bEnabledWhenBlue; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumEditCondition == EditColor::Pink")) + bool bEnabledWhenPink; + + UPROPERTY(EditAnywhere, Category = EditCondition) + TEnumAsByte EnumAsByteEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumAsByteEditCondition == PropertyEditorTestEnum::PropertyEditorTest_Enum2")) + bool bEnabledWhenEnumIs2; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "EnumAsByteEditCondition == PropertyEditorTestEnum::PropertyEditorTest_Enum4")) + bool bEnabledWhenEnumIs4; + + UPROPERTY(EditAnywhere, Category = EditCondition) + int32 IntegerEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "IntegerEditCondition >= 5")) + bool bEnabledWhenIntGreaterOrEqual5; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "IntegerEditCondition <= 10")) + bool bEnabledWhenIntLessOrEqual10; + + UPROPERTY(EditAnywhere, Category = EditCondition) + float FloatEditCondition; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "FloatEditCondition > 5")) + bool bEnabledWhenFloatGreaterThan5; + + UPROPERTY(EditAnywhere, Category = EditCondition, meta = (EditCondition = "FloatEditCondition < 10")) + bool bEnabledWhenFloatLessThan10; }; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h index cd58f3009bdf..76097f58cb61 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/CSVImportFactory.h @@ -17,7 +17,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogCSVImportFactory, Log, All); /** Enum to indicate what to import CSV as */ -UENUM() +UENUM(BlueprintType) enum class ECSVImportType : uint8 { /** Import as UDataTable */ @@ -32,20 +32,20 @@ enum class ECSVImportType : uint8 ECSV_CurveLinearColor, }; -USTRUCT() +USTRUCT(BlueprintType) struct FCSVImportSettings { GENERATED_BODY() FCSVImportSettings(); - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category="Misc") UScriptStruct* ImportRowStruct; - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category="Misc") ECSVImportType ImportType; - UPROPERTY() + UPROPERTY(BlueprintReadWrite, Category="Misc") TEnumAsByte ImportCurveInterpMode; }; @@ -79,7 +79,8 @@ private: /* Reimport object from the given path*/ EReimportResult::Type Reimport(UObject* Obj, const FString& Path); - UPROPERTY() +public: + UPROPERTY(BlueprintReadWrite, Category="Automation") FCSVImportSettings AutomatedImportSettings; /** Temporary data table to use to display import options */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h index da21ce196775..114bdfd9ef82 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxAnimSequenceImportData.h @@ -39,7 +39,7 @@ class UNREALED_API UFbxAnimSequenceImportData : public UFbxAssetImportData GENERATED_UCLASS_BODY() /** If checked, meshes nested in bone hierarchies will be imported instead of being converted to bones. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = ImportSettings, meta = (ImportType = "Animation")) + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings, meta = (ImportType = "Animation")) bool bImportMeshesInBoneHierarchy; /** Which animation range to import. The one defined at Exported, at Animated time or define a range manually */ @@ -55,7 +55,7 @@ class UNREALED_API UFbxAnimSequenceImportData : public UFbxAssetImportData int32 EndFrame_DEPRECATED; /** Frame range used when Set Range is used in Animation Length */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = ImportSettings, meta=(UIMin=0, ClampMin=0)) + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings, meta=(UIMin=0, ClampMin=0)) FInt32Interval FrameImportRange; /** Enable this option to use default sample rate for the imported animation at 30 frames per second */ @@ -73,9 +73,12 @@ class UNREALED_API UFbxAnimSequenceImportData : public UFbxAssetImportData /** Import if custom attribute as a curve within the animation */ UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings) bool bImportCustomAttribute; + + /** If true, all previous custom attribute curves will be deleted when doing a re-import. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings) + bool bDeleteExistingCustomAttributeCurves; /** Import bone transform tracks. If false, this will discard any bone transform tracks. (useful for curves only animations)*/ - UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = ImportSettings) bool bImportBoneTracks; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h index 7810e64689df..212fa12eb59f 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxMeshImportData.h @@ -82,6 +82,10 @@ class UFbxMeshImportData : public UFbxAssetImportData UPROPERTY(EditAnywhere, BlueprintReadWrite, config, AdvancedDisplay, Category = Mesh, meta=(ImportType="Mesh|GeoOnly")) TEnumAsByte NormalGenerationMethod; + /* If checked, The material list will be reorder to the same order has the FBX file. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, config, AdvancedDisplay, Category = Material, meta = (OBJRestrict = "true")) + bool bReorderMaterialToFbxOrder; + bool CanEditChange( const UProperty* InProperty ) const override; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h index 9af226fd72a1..812abc54b120 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSceneImportOptionsSkeletalMesh.h @@ -81,6 +81,10 @@ class UFbxSceneImportOptionsSkeletalMesh : public UObject UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Animation) bool bImportCustomAttribute; + /** If true, all previous custom attribute curves will be deleted when doing a re-import. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Animation) + bool bDeleteExistingCustomAttributeCurves; + /** Type of asset to import from the FBX file */ UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Animation) bool bPreserveLocalTransform; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h index f37483226437..2ba71b834c18 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h @@ -46,7 +46,7 @@ public: FColor VertexOverrideColor; /** Enable this option to update Skeleton (of the mesh)'s reference pose. Mesh's reference pose is always updated. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category=Mesh, meta=(ImportType="SkeletalMesh|RigOnly", ToolTip="If enabled, update the Skeleton (of the mesh being imported)'s reference pose.")) + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category=Mesh, meta=(ImportType="SkeletalMesh|RigOnly", ToolTip="If enabled, update the Skeleton (of the mesh being imported)'s reference pose.")) uint32 bUpdateSkeletonReferencePose:1; /** Enable this option to use frame 0 as reference pose */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h index e663765a2387..86b0b01bc25b 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h @@ -31,14 +31,14 @@ class UFbxStaticMeshImportData : public UFbxMeshImportData FColor VertexOverrideColor; /** Disabling this option will keep degenerate triangles found. In general you should leave this option on. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, config, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) uint32 bRemoveDegenerates:1; /** Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, config, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) uint32 bBuildAdjacencyBuffer:1; - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, config, Category = Mesh, meta = (ImportType = "StaticMesh", ReimportRestrict = "true")) uint32 bBuildReversedIndexBuffer:1; UPROPERTY(EditAnywhere, BlueprintReadWrite, config, AdvancedDisplay, Category= Mesh, meta=(ImportType="StaticMesh", ReimportRestrict = "true")) diff --git a/Engine/Source/Editor/UnrealEd/Classes/Preferences/AnimationBlueprintEditorOptions.h b/Engine/Source/Editor/UnrealEd/Classes/Preferences/AnimationBlueprintEditorOptions.h new file mode 100644 index 000000000000..88a2da95c6cd --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Preferences/AnimationBlueprintEditorOptions.h @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/** + * A configuration class used by the UAnimationBlueprint Editor to save editor + * settings across sessions. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "AnimationBlueprintEditorOptions.generated.h" + +UCLASS(hidecategories=Object, config=EditorPerProjectUserSettings) +class UNREALED_API UAnimationBlueprintEditorOptions : public UObject +{ + GENERATED_UCLASS_BODY() + + /** If true, fade nodes which are not connected to the selected nodes */ + UPROPERTY(EditAnywhere, config, Category=Options) + uint32 bHideUnrelatedNodes:1; + +}; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Preferences/BlueprintEditorOptions.h b/Engine/Source/Editor/UnrealEd/Classes/Preferences/BlueprintEditorOptions.h new file mode 100644 index 000000000000..4419615df363 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Preferences/BlueprintEditorOptions.h @@ -0,0 +1,24 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +/** + * A configuration class used by the UBlueprint Editor to save editor + * settings across sessions. + */ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "BlueprintEditorOptions.generated.h" + +UCLASS(hidecategories=Object, config=EditorPerProjectUserSettings) +class UNREALED_API UBlueprintEditorOptions : public UObject +{ + GENERATED_UCLASS_BODY() + + /** If true, fade nodes which are not connected to the selected nodes */ + UPROPERTY(EditAnywhere, config, Category=Options) + uint32 bHideUnrelatedNodes:1; + +}; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h b/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h index a2b07c3c4809..314dc8353130 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Preferences/MaterialEditorOptions.h @@ -37,6 +37,10 @@ class UNREALED_API UMaterialEditorOptions : public UObject /** If true, always refresh the material preview. */ UPROPERTY(EditAnywhere, config, Category = Options) uint32 bLivePreviewUpdate : 1; + + /** If true, fade nodes which are not connected to the selected nodes */ + UPROPERTY(EditAnywhere, config, Category=Options) + uint32 bHideUnrelatedNodes:1; /** If true, always refresh all expression previews. */ UPROPERTY(EditAnywhere, config, Category=Options) diff --git a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h index 59f5651430e3..7188b84edbc7 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h +++ b/Engine/Source/Editor/UnrealEd/Classes/Settings/LevelEditorMiscSettings.h @@ -79,6 +79,9 @@ public: UPROPERTY(EditAnywhere, config, Category = Levels, Meta = (ClampMin = "0.0", ClampMax = "100.0")) float PercentageThresholdForPrompt; + UPROPERTY(EditAnywhere, config, Category = Levels) + FVector MinimumBoundsForCheckingSize; + public: /** The save directory for newly created screenshots */ diff --git a/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h b/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h index e9f6716806f4..fb556bbb914f 100644 --- a/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h +++ b/Engine/Source/Editor/UnrealEd/Classes/ThumbnailRendering/BlueprintThumbnailRenderer.h @@ -17,7 +17,7 @@ class FCanvas; class FRenderTarget; UCLASS(config=Editor,MinimalAPI) -class UBlueprintThumbnailRenderer : public UDefaultSizedThumbnailRenderer +class UNREALED_VTABLE UBlueprintThumbnailRenderer : public UDefaultSizedThumbnailRenderer { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp b/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp index bb80f0b0e444..ab8cbbefd668 100644 --- a/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/AssetSelection.cpp @@ -45,6 +45,7 @@ #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Settings/LevelEditorMiscSettings.h" +#include "Engine/LevelStreaming.h" #include "Engine/LevelBounds.h" #include "SourceControlHelpers.h" #include "Dialogs/Dialogs.h" @@ -448,9 +449,31 @@ namespace ActorPlacementUtils } } } + if (InLevel && InLevel->OwningWorld) + { + int32 LevelCount = InLevel->OwningWorld->GetStreamingLevels().Num(); + int32 NumLockedLevels = 0; + // Check for streaming level count b/c we know there is > 1 streaming level + for (ULevelStreaming* StreamingLevel : InLevel->OwningWorld->GetStreamingLevels()) + { + StreamingLevel->bLocked ? NumLockedLevels++ : 0; + } + // If there is only one unlocked level, a) ours is the unlocked level b/c of the previous IsLevelLocked test and b) we shouldn't try to check for level bounds on the next test + if (LevelCount - NumLockedLevels == 1) + { + return true; + } + } if (InLevel && GetDefault()->bPromptWhenAddingToLevelOutsideBounds) { FBox CurrentLevelBounds = ALevelBounds::CalculateLevelBounds(InLevel); + FVector BoundsExtent = CurrentLevelBounds.GetExtent(); + if (BoundsExtent.X < GetDefault()->MinimumBoundsForCheckingSize.X + && BoundsExtent.Y < GetDefault()->MinimumBoundsForCheckingSize.Y + && BoundsExtent.Z < GetDefault()->MinimumBoundsForCheckingSize.Z) + { + return true; + } FVector ExpandedScale = FVector(1.0f + (GetDefault()->PercentageThresholdForPrompt / 100.0f)); FTransform ExpandedScaleTransform = FTransform::Identity; ExpandedScaleTransform.SetScale3D(ExpandedScale); diff --git a/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp index 6456754efcc1..4f4238a43858 100644 --- a/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/DataTableEditorUtils.cpp @@ -493,6 +493,31 @@ uint8* FDataTableEditorUtils::AddRow(UDataTable* DataTable, FName RowName) return RowData; } +uint8* FDataTableEditorUtils::DuplicateRow(UDataTable* DataTable, FName SourceRowName, FName RowName) +{ + if (!DataTable || (SourceRowName == NAME_None) || !DataTable->RowMap.Contains(SourceRowName) || DataTable->RowMap.Contains(RowName) || !DataTable->RowStruct) + { + return NULL; + } + + const FScopedTransaction Transaction(LOCTEXT("DuplicateDataTableRow", "Duplicate Data Table Row")); + + BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList); + DataTable->Modify(); + + // Allocate data to store information, using UScriptStruct to know its size + uint8* OldRowData = *DataTable->RowMap.Find(SourceRowName); + uint8* NewRowData = (uint8*)FMemory::Malloc(DataTable->RowStruct->GetStructureSize()); + + DataTable->RowStruct->InitializeStruct(NewRowData); + DataTable->RowStruct->CopyScriptStruct(NewRowData, OldRowData); + + // Add to row map + DataTable->RowMap.Add(RowName, NewRowData); + BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList); + return NewRowData; +} + bool FDataTableEditorUtils::RenameRow(UDataTable* DataTable, FName OldName, FName NewName) { bool bResult = false; @@ -592,7 +617,7 @@ bool FDataTableEditorUtils::MoveRow(UDataTable* DataTable, FName RowName, ERowMo return true; } -bool FDataTableEditorUtils::SelectRow(UDataTable* DataTable, FName RowName) +bool FDataTableEditorUtils::SelectRow(const UDataTable* DataTable, FName RowName) { for (auto Listener : FDataTableEditorManager::Get().GetListeners()) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Dialogs/CustomDialog.cpp b/Engine/Source/Editor/UnrealEd/Private/Dialogs/CustomDialog.cpp new file mode 100644 index 000000000000..a45ad6d3da92 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Private/Dialogs/CustomDialog.cpp @@ -0,0 +1,162 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Dialogs/CustomDialog.h" + +#include "HAL/PlatformApplicationMisc.h" + +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Docking/TabManager.h" +#include "Logging/LogMacros.h" +#include "Styling/SlateBrush.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/SBoxPanel.h" + +DEFINE_LOG_CATEGORY_STATIC(LogCustomDialog, Log, All); + +void SCustomDialog::Construct(const FArguments& InArgs) +{ + UE_LOG(LogCustomDialog, Log, TEXT("Dialog displayed:"), *InArgs._Title.ToString()); + + check(InArgs._Buttons.Num() > 0); + + TSharedPtr ContentBox; + TSharedPtr ButtonBox; + + SWindow::Construct( SWindow::FArguments() + .Title(InArgs._Title) + .SizingRule(ESizingRule::Autosized) + .SupportsMaximize(false) + .SupportsMinimize(false) + [ + SNew(SBorder) + .Padding(4.f) + .BorderImage(FEditorStyle::GetBrush( "ToolPanel.GroupBorder" )) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SAssignNew(ContentBox, SHorizontalBox) + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + SAssignNew(ButtonBox, SHorizontalBox) + ] + ] + ] ); + + if (InArgs._IconBrush.IsValid()) + { + const FSlateBrush* ImageBrush = FEditorStyle::GetBrush(InArgs._IconBrush); + if (ImageBrush != nullptr) + { + ContentBox->AddSlot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SImage) + .Image(ImageBrush) + ]; + } + } + + if (InArgs._UseScrollBox) + { + ContentBox->AddSlot() + [ + SNew(SBox) + .MaxDesiredHeight(InArgs._ScrollBoxMaxHeight) + [ + SNew(SScrollBox) + +SScrollBox::Slot() + [ + InArgs._DialogContent.ToSharedRef() + ] + ] + ]; + } + else + { + ContentBox->AddSlot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + InArgs._DialogContent.ToSharedRef() + ]; + } + + ButtonBox->AddSlot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(20.0f, 1.0f)) + ]; + + TSharedPtr ButtonPanel; + + ButtonBox->AddSlot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Right) + [ + SAssignNew(ButtonPanel, SUniformGridPanel) + .SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding")) + .MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) + .MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + ]; + + for (int i = 0; i < InArgs._Buttons.Num(); ++i) + { + const FButton& Button = InArgs._Buttons[i]; + + ButtonPanel->AddSlot(ButtonPanel->GetChildren()->Num(), 0) + [ + SNew(SButton) + .OnClicked(FOnClicked::CreateSP(this, &SCustomDialog::OnButtonClicked, Button.OnClicked, i)) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(Button.ButtonText) + ] + ] + ]; + } +} + +int SCustomDialog::ShowModal() +{ + FSlateApplication::Get().AddModalWindow(StaticCastSharedRef(this->AsShared()), FGlobalTabmanager::Get()->GetRootWindow()); + + return LastPressedButton; +} + +void SCustomDialog::Show() +{ + FSlateApplication::Get().AddWindow(StaticCastSharedRef(this->AsShared()), true); +} + +/** Handle the button being clicked */ +FReply SCustomDialog::OnButtonClicked(FSimpleDelegate OnClicked, int ButtonIndex) +{ + LastPressedButton = ButtonIndex; + + FSlateApplication::Get().RequestDestroyWindow(StaticCastSharedRef(this->AsShared())); + + OnClicked.ExecuteIfBound(); + return FReply::Handled(); +} diff --git a/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp b/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp index 88b3c1ee8cf0..a21fe2e6d510 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Editor/ActorPositioning.cpp @@ -17,6 +17,7 @@ #include "SnappingUtils.h" #include "LandscapeHeightfieldCollisionComponent.h" #include "LandscapeComponent.h" +#include "Editor/EditorPerProjectUserSettings.h" FActorPositionTraceResult FActorPositioning::TraceWorldForPositionWithDefault(const FViewportCursorLocation& Cursor, const FSceneView& View, const TArray* IgnoreActors) { @@ -86,16 +87,18 @@ bool IsHitIgnored(const FHitResult& InHit, const FSceneView& InSceneView) // Only use this component if it is visible in the specified scene views bool bIsRenderedOnScreen = false; + bool bIgnoreTranslucentPrimitive = false; { if (PrimitiveComponent && PrimitiveComponent->SceneProxy) { const FPrimitiveViewRelevance ViewRelevance = PrimitiveComponent->SceneProxy->GetViewRelevance(&InSceneView); // BSP is a bit special in that its bDrawRelevance is false even when drawn as wireframe because InSceneView.Family->EngineShowFlags.BSPTriangles is off bIsRenderedOnScreen = ViewRelevance.bDrawRelevance || (PrimitiveComponent->IsA(UModelComponent::StaticClass()) && InSceneView.Family->EngineShowFlags.BSP); + bIgnoreTranslucentPrimitive = ViewRelevance.HasTranslucency() && !GetDefault()->bAllowSelectTranslucent; } } - return !bIsRenderedOnScreen; + return !bIsRenderedOnScreen || bIgnoreTranslucentPrimitive; } FActorPositionTraceResult FActorPositioning::TraceWorldForPosition(const UWorld& InWorld, const FSceneView& InSceneView, const FVector& RayStart, const FVector& RayEnd, const TArray* IgnoreActors) diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp index 256c47160e9d..a8451ddb867c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorActor.cpp @@ -64,6 +64,7 @@ #include "IAssetTools.h" #include "AssetToolsModule.h" #include "AssetSelection.h" +#include "Framework/Application/SlateApplication.h" #define LOCTEXT_NAMESPACE "UnrealEd.EditorActor" @@ -702,6 +703,8 @@ bool UUnrealEdEngine::edactDeleteSelected( UWorld* InWorld, bool bVerifyDeletion const double StartSeconds = FPlatformTime::Seconds(); + FSlateApplication::Get().CancelDragDrop(); + if (GetSelectedComponentCount() > 0) { TArray SelectedEditableComponents; diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp index c55cbd1f666c..b65d35bf35b7 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorModeManager.cpp @@ -14,7 +14,6 @@ #include "EditorSupportDelegates.h" #include "EdMode.h" #include "Toolkits/IToolkitHost.h" - #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Engine/LevelStreaming.h" @@ -23,8 +22,12 @@ #include "Editor/EditorEngine.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" - #include "Bookmarks/IBookmarkTypeTools.h" +#include "Widgets/Docking/SDockTab.h" +#include "EditorStyleSet.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Toolkits/BaseToolkit.h" /*------------------------------------------------------------------------------ FEditorModeTools. @@ -32,6 +35,8 @@ The master class that handles tracking of the current mode. ------------------------------------------------------------------------------*/ +const FName FEditorModeTools::EditorModeToolbarTabName = TEXT("EditorModeToolbar"); + FEditorModeTools::FEditorModeTools() : PivotShown(false) , Snapping(false) @@ -297,21 +302,80 @@ void FEditorModeTools::ActivateDefaultMode() void FEditorModeTools::DeactivateModeAtIndex(int32 InIndex) { - check( InIndex >= 0 && InIndex < Modes.Num() ); + check( InIndex >= 0 && InIndex < ActiveModes.Num() ); - auto& Mode = Modes[InIndex]; + auto& Mode = ActiveModes[InIndex]; Mode->Exit(); + + // Remove the toolbar widget + ActiveToolBarRows.RemoveAll( + [&Mode](FEdModeToolbarRow& Row) + { + return Row.Mode == Mode; + } + ); + + RebuildModeToolBar(); + RecycledModes.Add( Mode->GetID(), Mode ); - Modes.RemoveAt( InIndex ); + ActiveModes.RemoveAt( InIndex ); +} + +void FEditorModeTools::RebuildModeToolBar() +{ + // If the tab or box is not valid the toolbar has not been opened or has been closed by the user + TSharedPtr ModeToolbarBoxPinned = ModeToolbarBox.Pin(); + if (ModeToolbarTab.IsValid() && ModeToolbarBoxPinned.IsValid()) + { + ModeToolbarBoxPinned->ClearChildren(); + + if(ActiveToolBarRows.Num()) + { + for(int32 RowIdx = 0; RowIdx < ActiveToolBarRows.Num(); ++RowIdx) + { + const FEdModeToolbarRow& Row = ActiveToolBarRows[RowIdx]; + if (ensure(Row.ToolbarWidget.IsValid())) + { + ModeToolbarBoxPinned->AddSlot() + .HAlign(HAlign_Left) + .AutoHeight() + .Padding(0.0f, RowIdx > 0 ? 5.0f : 0.0f, 0.0f, 0.0f) + [ + Row.ToolbarWidget.ToSharedRef() + ]; + } + } + } + else + { + ModeToolbarTab.Pin()->RequestCloseTab(); + } + } +} + + +void FEditorModeTools::SpawnOrUpdateModeToolbar() +{ + if(ShouldShowModeToolbar()) + { + if (ModeToolbarTab.IsValid()) + { + RebuildModeToolBar(); + } + else if (ToolkitHost.IsValid()) + { + ToolkitHost.Pin()->GetTabManager()->InvokeTab(EditorModeToolbarTabName); + } + } } void FEditorModeTools::DeactivateMode( FEditorModeID InID ) { // Find the mode from the ID and exit it. - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index ) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index ) { - auto& Mode = Modes[Index]; + auto& Mode = ActiveModes[Index]; if( Mode->GetID() == InID ) { DeactivateModeAtIndex(Index); @@ -319,7 +383,7 @@ void FEditorModeTools::DeactivateMode( FEditorModeID InID ) } } - if( Modes.Num() == 0 ) + if( ActiveModes.Num() == 0 ) { // Ensure the default mode is active if there are no active modes. ActivateDefaultMode(); @@ -328,7 +392,7 @@ void FEditorModeTools::DeactivateMode( FEditorModeID InID ) void FEditorModeTools::DeactivateAllModes() { - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index ) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index ) { DeactivateModeAtIndex(Index); } @@ -337,9 +401,9 @@ void FEditorModeTools::DeactivateAllModes() void FEditorModeTools::DestroyMode( FEditorModeID InID ) { // Find the mode from the ID and exit it. - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index ) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index ) { - auto& Mode = Modes[Index]; + auto& Mode = ActiveModes[Index]; if ( Mode->GetID() == InID ) { // Deactivate and destroy @@ -351,6 +415,32 @@ void FEditorModeTools::DestroyMode( FEditorModeID InID ) RecycledModes.Remove(InID); } +TSharedRef FEditorModeTools::MakeModeToolbarTab() +{ + TSharedRef ToolbarTabRef = + SNew(SDockTab) + .Label(NSLOCTEXT("EditorModes", "EditorModesToolbarTitle", "Mode Toolbar")) + .ShouldAutosize(true) + .Icon(FEditorStyle::GetBrush("ToolBar.Icon")) + [ + SAssignNew(ModeToolbarBox, SVerticalBox) + + ]; + + ModeToolbarTab = ToolbarTabRef; + + // Rebuild the toolbar with existing mode tools that may be active + RebuildModeToolBar(); + + return ToolbarTabRef; + +} + +bool FEditorModeTools::ShouldShowModeToolbar() +{ + return ActiveToolBarRows.Num() > 0; +} + void FEditorModeTools::ActivateMode(FEditorModeID InID, bool bToggle) { static bool bReentrant = false; @@ -407,20 +497,39 @@ void FEditorModeTools::ActivateMode(FEditorModeID InID, bool bToggle) } // Remove anything that isn't compatible with this mode - for (int32 ModeIndex = Modes.Num() - 1; ModeIndex >= 0; --ModeIndex) + for (int32 ModeIndex = ActiveModes.Num() - 1; ModeIndex >= 0; --ModeIndex) { - const bool bModesAreCompatible = Mode->IsCompatibleWith(Modes[ModeIndex]->GetID()) || Modes[ModeIndex]->IsCompatibleWith(Mode->GetID()); + const bool bModesAreCompatible = Mode->IsCompatibleWith(ActiveModes[ModeIndex]->GetID()) || ActiveModes[ModeIndex]->IsCompatibleWith(Mode->GetID()); if (!bModesAreCompatible) { DeactivateModeAtIndex(ModeIndex); } } - Modes.Add(Mode); + ActiveModes.Add(Mode); // Enter the new mode Mode->Enter(); + // Ask the mode to build the toolbar. + TSharedPtr CommandList; + const TSharedPtr Toolkit = Mode->GetToolkit(); + if (Toolkit.IsValid()) + { + CommandList = Toolkit->GetToolkitCommands(); + } + + FToolBarBuilder ModeToolbarBuilder(CommandList, FMultiBoxCustomization(Mode->GetModeInfo().ToolbarCustomizationName), TSharedPtr(), Orient_Horizontal, false); + Mode->BuildModeToolbar(ModeToolbarBuilder); + + if (ModeToolbarBuilder.GetMultiBox()->GetBlocks().Num()) + { + TSharedRef ToolbarWidget = ModeToolbarBuilder.MakeWidget(); + ActiveToolBarRows.Emplace(Mode, ToolbarWidget); + + SpawnOrUpdateModeToolbar(); + } + // Update the editor UI FEditorSupportDelegates::UpdateUI.Broadcast(); } @@ -447,7 +556,7 @@ bool FEditorModeTools::EnsureNotInMode(FEditorModeID ModeID, const FText& ErrorM FEdMode* FEditorModeTools::FindMode( FEditorModeID InID ) { - for( auto& Mode : Modes ) + for( auto& Mode : ActiveModes ) { if( Mode->GetID() == InID ) { @@ -474,7 +583,7 @@ FMatrix FEditorModeTools::GetCustomDrawingCoordinateSystem() // If it doesn't want to, create it by looking at the currently selected actors list. bool CustomCoordinateSystemProvided = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { if (Mode->GetCustomDrawingCoordinateSystem(Matrix, nullptr)) { @@ -520,11 +629,11 @@ FMatrix FEditorModeTools::GetCustomInputCoordinateSystem() EAxisList::Type FEditorModeTools::GetWidgetAxisToDraw( FWidget::EWidgetMode InWidgetMode ) const { EAxisList::Type OutAxis = EAxisList::All; - for( int Index = Modes.Num() - 1; Index >= 0 ; Index-- ) + for( int Index = ActiveModes.Num() - 1; Index >= 0 ; Index-- ) { - if ( Modes[Index]->ShouldDrawWidget() ) + if ( ActiveModes[Index]->ShouldDrawWidget() ) { - OutAxis = Modes[Index]->GetWidgetAxisToDraw( InWidgetMode ); + OutAxis = ActiveModes[Index]->GetWidgetAxisToDraw( InWidgetMode ); break; } } @@ -540,9 +649,9 @@ bool FEditorModeTools::StartTracking(FEditorViewportClient* InViewportClient, FV CachedLocation = PivotLocation; // Cache the pivot location - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bTransactionHandled |= Mode->StartTracking(InViewportClient, InViewport); } @@ -555,9 +664,9 @@ bool FEditorModeTools::EndTracking(FEditorViewportClient* InViewportClient, FVie bIsTracking = false; bool bTransactionHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bTransactionHandled |= Mode->EndTracking(InViewportClient, InViewportClient->Viewport); } @@ -569,7 +678,7 @@ bool FEditorModeTools::EndTracking(FEditorViewportClient* InViewportClient, FVie bool FEditorModeTools::AllowsViewportDragTool() const { bool bCanUseDragTool = false; - for (const TSharedPtr& Mode : Modes) + for (const TSharedPtr& Mode : ActiveModes) { bCanUseDragTool |= Mode->AllowsViewportDragTool(); } @@ -579,9 +688,9 @@ bool FEditorModeTools::AllowsViewportDragTool() const /** Notifies all active modes that a map change has occured */ void FEditorModeTools::MapChangeNotify() { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->MapChangeNotify(); } } @@ -590,9 +699,9 @@ void FEditorModeTools::MapChangeNotify() /** Notifies all active modes to empty their selections */ void FEditorModeTools::SelectNone() { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->SelectNone(); } } @@ -601,9 +710,9 @@ void FEditorModeTools::SelectNone() bool FEditorModeTools::BoxSelect( FBox& InBox, bool InSelect ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->BoxSelect( InBox, InSelect ); } return bHandled; @@ -613,9 +722,9 @@ bool FEditorModeTools::BoxSelect( FBox& InBox, bool InSelect ) bool FEditorModeTools::FrustumSelect( const FConvexVolume& InFrustum, FEditorViewportClient* InViewportClient, bool InSelect ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->FrustumSelect( InFrustum, InViewportClient, InSelect ); } return bHandled; @@ -626,7 +735,7 @@ bool FEditorModeTools::FrustumSelect( const FConvexVolume& InFrustum, FEditorVie bool FEditorModeTools::UsesTransformWidget() const { bool bUsesTransformWidget = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bUsesTransformWidget |= Mode->UsesTransformWidget(); } @@ -638,7 +747,7 @@ bool FEditorModeTools::UsesTransformWidget() const bool FEditorModeTools::UsesTransformWidget( FWidget::EWidgetMode CheckMode ) const { bool bUsesTransformWidget = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bUsesTransformWidget |= Mode->UsesTransformWidget(CheckMode); } @@ -649,9 +758,9 @@ bool FEditorModeTools::UsesTransformWidget( FWidget::EWidgetMode CheckMode ) con /** Sets the current widget axis */ void FEditorModeTools::SetCurrentWidgetAxis( EAxisList::Type NewAxis ) { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->SetCurrentWidgetAxis( NewAxis ); } } @@ -660,9 +769,9 @@ void FEditorModeTools::SetCurrentWidgetAxis( EAxisList::Type NewAxis ) bool FEditorModeTools::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy *HitProxy, const FViewportClick& Click ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->HandleClick(InViewportClient, HitProxy, Click); } @@ -673,12 +782,12 @@ bool FEditorModeTools::HandleClick(FEditorViewportClient* InViewportClient, HHi bool FEditorModeTools::ShouldDrawBrushWireframe( AActor* InActor ) const { bool bShouldDraw = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bShouldDraw |= Mode->ShouldDrawBrushWireframe( InActor ); } - if( Modes.Num() == 0 ) + if( ActiveModes.Num() == 0 ) { // We can get into a state where there are no active modes at editor startup if the builder brush is created before the default mode is activated. // Ensure we can see the builder brush when no modes are active. @@ -698,23 +807,23 @@ bool FEditorModeTools::ShouldDrawBrushVertices() const void FEditorModeTools::Tick( FEditorViewportClient* ViewportClient, float DeltaTime ) { // Remove anything pending destruction - for( int32 Index = Modes.Num() - 1; Index >= 0; --Index) + for( int32 Index = ActiveModes.Num() - 1; Index >= 0; --Index) { - if (Modes[Index]->IsPendingDeletion()) + if (ActiveModes[Index]->IsPendingDeletion()) { DeactivateModeAtIndex(Index); } } - if (Modes.Num() == 0) + if (ActiveModes.Num() == 0) { // Ensure the default mode is active if there are no active modes. ActivateDefaultMode(); } - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->Tick( ViewportClient, DeltaTime ); } } @@ -723,9 +832,9 @@ void FEditorModeTools::Tick( FEditorViewportClient* ViewportClient, float DeltaT bool FEditorModeTools::InputDelta( FEditorViewportClient* InViewportClient,FViewport* InViewport,FVector& InDrag,FRotator& InRot,FVector& InScale ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->InputDelta( InViewportClient, InViewport, InDrag, InRot, InScale ); } return bHandled; @@ -735,9 +844,9 @@ bool FEditorModeTools::InputDelta( FEditorViewportClient* InViewportClient,FView bool FEditorModeTools::CapturedMouseMove( FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->CapturedMouseMove( InViewportClient, InViewport, InMouseX, InMouseY ); } return bHandled; @@ -747,9 +856,9 @@ bool FEditorModeTools::CapturedMouseMove( FEditorViewportClient* InViewportClien bool FEditorModeTools::ProcessCapturedMouseMoves( FEditorViewportClient* InViewportClient, FViewport* InViewport, const TArrayView& CapturedMouseMoves ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->ProcessCapturedMouseMoves( InViewportClient, InViewport, CapturedMouseMoves ); } return bHandled; @@ -759,9 +868,9 @@ bool FEditorModeTools::ProcessCapturedMouseMoves( FEditorViewportClient* InViewp bool FEditorModeTools::InputKey(FEditorViewportClient* InViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->InputKey( InViewportClient, Viewport, Key, Event ); } return bHandled; @@ -771,9 +880,9 @@ bool FEditorModeTools::InputKey(FEditorViewportClient* InViewportClient, FViewpo bool FEditorModeTools::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->InputAxis( InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime ); } return bHandled; @@ -782,9 +891,9 @@ bool FEditorModeTools::InputAxis(FEditorViewportClient* InViewportClient, FViewp bool FEditorModeTools::GetPivotForOrbit( FVector& Pivot ) const { // Just return the first pivot point specified by a mode - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; if ( Mode->GetPivotForOrbit( Pivot ) ) { return true; @@ -796,9 +905,9 @@ bool FEditorModeTools::GetPivotForOrbit( FVector& Pivot ) const bool FEditorModeTools::MouseEnter( FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 X, int32 Y ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->MouseEnter( InViewportClient, Viewport, X, Y ); } return bHandled; @@ -807,9 +916,9 @@ bool FEditorModeTools::MouseEnter( FEditorViewportClient* InViewportClient, FVie bool FEditorModeTools::MouseLeave( FEditorViewportClient* InViewportClient, FViewport* Viewport ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->MouseLeave( InViewportClient, Viewport ); } return bHandled; @@ -819,9 +928,9 @@ bool FEditorModeTools::MouseLeave( FEditorViewportClient* InViewportClient, FVie bool FEditorModeTools::MouseMove( FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 X, int32 Y ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->MouseMove( InViewportClient, Viewport, X, Y ); } return bHandled; @@ -830,9 +939,9 @@ bool FEditorModeTools::MouseMove( FEditorViewportClient* InViewportClient, FView bool FEditorModeTools::ReceivedFocus( FEditorViewportClient* InViewportClient, FViewport* Viewport ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->ReceivedFocus( InViewportClient, Viewport ); } return bHandled; @@ -841,9 +950,9 @@ bool FEditorModeTools::ReceivedFocus( FEditorViewportClient* InViewportClient, F bool FEditorModeTools::LostFocus( FEditorViewportClient* InViewportClient, FViewport* Viewport ) { bool bHandled = false; - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; bHandled |= Mode->LostFocus( InViewportClient, Viewport ); } return bHandled; @@ -852,7 +961,7 @@ bool FEditorModeTools::LostFocus( FEditorViewportClient* InViewportClient, FView /** Draws all active mode components */ void FEditorModeTools::DrawActiveModes( const FSceneView* InView, FPrimitiveDrawInterface* PDI ) { - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { Mode->Draw( InView, PDI ); } @@ -861,7 +970,7 @@ void FEditorModeTools::DrawActiveModes( const FSceneView* InView, FPrimitiveDraw /** Renders all active modes */ void FEditorModeTools::Render( const FSceneView* InView, FViewport* Viewport, FPrimitiveDrawInterface* PDI ) { - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { Mode->Render( InView, Viewport, PDI ); } @@ -870,7 +979,7 @@ void FEditorModeTools::Render( const FSceneView* InView, FViewport* Viewport, FP /** Draws the HUD for all active modes */ void FEditorModeTools::DrawHUD( FEditorViewportClient* InViewportClient,FViewport* Viewport, const FSceneView* View, FCanvas* Canvas ) { - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { Mode->DrawHUD( InViewportClient, Viewport, View, Canvas ); } @@ -881,9 +990,9 @@ void FEditorModeTools::PostUndo(bool bSuccess) { if (bSuccess) { - for( int32 ModeIndex = 0; ModeIndex < Modes.Num(); ++ModeIndex ) + for( int32 ModeIndex = 0; ModeIndex < ActiveModes.Num(); ++ModeIndex ) { - const TSharedPtr& Mode = Modes[ ModeIndex ]; + const TSharedPtr& Mode = ActiveModes[ ModeIndex ]; Mode->PostUndo(); } } @@ -897,7 +1006,7 @@ void FEditorModeTools::PostRedo(bool bSuccess) bool FEditorModeTools::AllowWidgetMove() const { bool bAllow = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bAllow |= Mode->AllowWidgetMove(); } @@ -907,7 +1016,7 @@ bool FEditorModeTools::AllowWidgetMove() const bool FEditorModeTools::DisallowMouseDeltaTracking() const { bool bDisallow = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bDisallow |= Mode->DisallowMouseDeltaTracking(); } @@ -917,7 +1026,7 @@ bool FEditorModeTools::DisallowMouseDeltaTracking() const bool FEditorModeTools::GetCursor(EMouseCursor::Type& OutCursor) const { bool bHandled = false; - for( const auto& Mode : Modes) + for( const auto& Mode : ActiveModes) { bHandled |= Mode->GetCursor(OutCursor); } @@ -927,7 +1036,7 @@ bool FEditorModeTools::GetCursor(EMouseCursor::Type& OutCursor) const bool FEditorModeTools::GetOverrideCursorVisibility(bool& bWantsOverride, bool& bHardwareCursorVisible, bool bSoftwareCursorVisible) const { bool bHandled = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { bHandled |= Mode->GetOverrideCursorVisibility(bWantsOverride, bHardwareCursorVisible, bSoftwareCursorVisible); } @@ -937,7 +1046,7 @@ bool FEditorModeTools::GetOverrideCursorVisibility(bool& bWantsOverride, bool& b bool FEditorModeTools::PreConvertMouseMovement(FEditorViewportClient* InViewportClient) { bool bHandled = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { bHandled |= Mode->PreConvertMouseMovement(InViewportClient); } @@ -947,7 +1056,7 @@ bool FEditorModeTools::PreConvertMouseMovement(FEditorViewportClient* InViewport bool FEditorModeTools::PostConvertMouseMovement(FEditorViewportClient* InViewportClient) { bool bHandled = false; - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { bHandled |= Mode->PostConvertMouseMovement(InViewportClient); } @@ -1009,11 +1118,11 @@ void FEditorModeTools::LoadWidgetSettings(void) FVector FEditorModeTools::GetWidgetLocation() const { - for (int Index = Modes.Num() - 1; Index >= 0 ; Index--) + for (int Index = ActiveModes.Num() - 1; Index >= 0 ; Index--) { - if ( Modes[Index]->UsesTransformWidget() ) + if ( ActiveModes[Index]->UsesTransformWidget() ) { - return Modes[Index]->GetWidgetLocation(); + return ActiveModes[Index]->GetWidgetLocation(); } } @@ -1126,15 +1235,15 @@ void FEditorModeTools::ClearAllBookmarks( FEditorViewportClient* InViewportClien void FEditorModeTools::AddReferencedObjects( FReferenceCollector& Collector ) { - for( int32 x = 0 ; x < Modes.Num() ; ++x ) + for( int32 x = 0 ; x < ActiveModes.Num() ; ++x ) { - Modes[x]->AddReferencedObjects( Collector ); + ActiveModes[x]->AddReferencedObjects( Collector ); } } FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) { - for( auto& Mode : Modes ) + for( auto& Mode : ActiveModes ) { if( Mode->GetID() == InID ) { @@ -1146,7 +1255,7 @@ FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) const FEdMode* FEditorModeTools::GetActiveMode( FEditorModeID InID ) const { - for (const auto& Mode : Modes) + for (const auto& Mode : ActiveModes) { if (Mode->GetID() == InID) { @@ -1191,14 +1300,14 @@ void FEditorModeTools::GetActiveModes( TArray& OutActiveModes ) { OutActiveModes.Empty(); // Copy into an array. Do not let users modify the active list directly. - for( auto& Mode : Modes) + for( auto& Mode : ActiveModes) { OutActiveModes.Add(Mode.Get()); } } bool FEditorModeTools::CanCycleWidgetMode() const { - for (auto& Mode : Modes) + for (auto& Mode : ActiveModes) { if (Mode->CanCycleWidgetMode()) { diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp index d34fdfc8026c..65b9f2002607 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorModeRegistry.cpp @@ -32,6 +32,7 @@ FEditorModeInfo::FEditorModeInfo( int32 InPriorityOrder ) : ID(InID) + , ToolbarCustomizationName(*(InID.ToString()+TEXT("Toolbar"))) , Name(InName) , IconBrush(InIconBrush) , bVisible(InIsVisible) diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp index 5bc69af4bc98..635f9a8abdc0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorSelectUtils.cpp @@ -561,6 +561,15 @@ void UUnrealEdEngine::SelectActor(AActor* Actor, bool bInSelected, bool bNotify, // Select the actor and update its internals. if( !bSelectionHandled ) { + if (bInSelected) + { + // If trying to select an Actor spawned by a ChildActorComponent, instead iterate up the hierarchy until we find a valid actor to select + while (Actor->IsChildActor()) + { + Actor = Actor->GetParentComponent()->GetOwner(); + } + } + if (UActorGroupingUtils::IsGroupingActive()) { // if this actor is a group, do a group select/deselect diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp index 1ddfcf605de0..a3143f336f2e 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorServer.cpp @@ -4749,6 +4749,46 @@ void UEditorEngine::MoveViewportCamerasToComponent(USceneComponent* Component, b } } +void UEditorEngine::MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const +{ + // Make sure we had at least one non-null actor in the array passed in. + if (BoundingBox.GetSize() != FVector::ZeroVector || BoundingBox.GetCenter() != FVector::ZeroVector) + { + if (bActiveViewportOnly) + { + if (GCurrentLevelEditingViewportClient) + { + GCurrentLevelEditingViewportClient->FocusViewportOnBox(BoundingBox); + + // Update Linked Orthographic viewports. + if (GCurrentLevelEditingViewportClient->IsOrtho() && GetDefault()->bUseLinkedOrthographicViewports) + { + // Search through all viewports + for (FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) + { + // Only update other orthographic viewports + if (LinkedViewportClient && LinkedViewportClient != GCurrentLevelEditingViewportClient && LinkedViewportClient->IsOrtho()) + { + LinkedViewportClient->FocusViewportOnBox(BoundingBox); + } + } + } + } + + } + else + { + // Update all viewports. + for (FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) + { + //Dont move camera attach to an actor + if (!LinkedViewportClient->IsAnyActorLocked()) + LinkedViewportClient->FocusViewportOnBox(BoundingBox); + } + } + } +} + /** * Snaps an actor in a direction. Optionally will align with the trace normal. * @param InActor Actor to move to the floor. @@ -6838,43 +6878,3 @@ void UEditorEngine::AutoMergeStaticMeshes() } #endif // #if TODO_STATICMESH } - -void UEditorEngine::MoveViewportCamerasToBox(const FBox& BoundingBox, bool bActiveViewportOnly) const -{ - // Make sure we had at least one non-null actor in the array passed in. - if (BoundingBox.GetSize() != FVector::ZeroVector || BoundingBox.GetCenter() != FVector::ZeroVector) - { - if (bActiveViewportOnly) - { - if (GCurrentLevelEditingViewportClient) - { - GCurrentLevelEditingViewportClient->FocusViewportOnBox(BoundingBox); - - // Update Linked Orthographic viewports. - if (GCurrentLevelEditingViewportClient->IsOrtho() && GetDefault()->bUseLinkedOrthographicViewports) - { - // Search through all viewports - for(FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) - { - // Only update other orthographic viewports - if (LinkedViewportClient && LinkedViewportClient != GCurrentLevelEditingViewportClient && LinkedViewportClient->IsOrtho()) - { - LinkedViewportClient->FocusViewportOnBox(BoundingBox); - } - } - } - } - - } - else - { - // Update all viewports. - for(FLevelEditorViewportClient* LinkedViewportClient : GetLevelViewportClients()) - { - //Dont move camera attach to an actor - if (!LinkedViewportClient->IsAnyActorLocked()) - LinkedViewportClient->FocusViewportOnBox(BoundingBox); - } - } - } -} diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp index 5531d7c23642..67113127e974 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp @@ -233,8 +233,24 @@ void FTransaction::FObjectRecord::Load(FTransaction* Owner) if (CustomChange.IsValid()) { - TUniquePtr InvertedChange = CustomChange->Execute( Object.Get() ); - CustomChange = MoveTemp( InvertedChange ); + if (CustomChange->GetChangeType() == FChange::EChangeStyle::InPlaceSwap) + { + TUniquePtr InvertedChange = CustomChange->Execute(Object.Get()); + ensure(InvertedChange->GetChangeType() == FChange::EChangeStyle::InPlaceSwap); + CustomChange = MoveTemp(InvertedChange); + } + else + { + bool bIsRedo = (Owner->Inc == 1); + if (bIsRedo) + { + CustomChange->Apply(Object.Get()); + } + else + { + CustomChange->Revert(Object.Get()); + } + } } else { diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp index 3e3740356882..ccc79a9cbcb5 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorViewportClient.cpp @@ -2296,6 +2296,11 @@ void FEditorViewportClient::InputAxisForOrbit(FViewport* InViewport, const FVect SetViewLocation(RotatedViewLocation); FEditorViewportStats::Using(IsPerspective() ? FEditorViewportStats::CAT_PERSPECTIVE_MOUSE_ORBIT_ROTATION : FEditorViewportStats::CAT_ORTHOGRAPHIC_MOUSE_ORBIT_ROTATION); + + if (IsPerspective()) + { + PerspectiveCameraMoved(); + } } else { diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp index 68aedf89812d..0b7b86a5e26b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp @@ -5477,12 +5477,13 @@ EReimportResult::Type UReimportFbxStaticMeshFactory::Reimport( UObject* Obj ) { if ((*UserData)[Idx] != nullptr) { - bool bAddDupToRoot = !((*UserData)[Idx]->IsRooted()); + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); if (bAddDupToRoot) { - (*UserData)[Idx]->AddToRoot(); + DupObject->AddToRoot(); } - UserDataCopy.Add((UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()), bAddDupToRoot); + UserDataCopy.Add(DupObject, bAddDupToRoot); } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp index 4ad6159d5ed4..512a642e3329 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp @@ -30,6 +30,7 @@ #include "Factories/FbxSkeletalMeshImportData.h" #include "IMeshReductionManagerModule.h" #include "Rendering/SkeletalMeshModel.h" +#include "Engine/AssetUserData.h" DEFINE_LOG_CATEGORY_STATIC(LogSkeletalMeshImport, Log, All); @@ -388,165 +389,184 @@ bool SkeletalMeshIsUsingMaterialSlotNameWorkflow(UAssetImportData* AssetImportDa ExistingSkelMeshData* SaveExistingSkelMeshData(USkeletalMesh* ExistingSkelMesh, bool bSaveMaterials, int32 ReimportLODIndex) { - struct ExistingSkelMeshData* ExistingMeshDataPtr = NULL; - if(ExistingSkelMesh) + ExistingSkelMeshData* ExistingMeshDataPtr = nullptr; + + if (!ExistingSkelMesh) { - bool ReimportSpecificLOD = (ReimportLODIndex > 0) && ExistingSkelMesh->GetLODNum() > ReimportLODIndex; + return nullptr; + } + bool ReimportSpecificLOD = (ReimportLODIndex > 0) && ExistingSkelMesh->GetLODNum() > ReimportLODIndex; - ExistingMeshDataPtr = new ExistingSkelMeshData(); + ExistingMeshDataPtr = new ExistingSkelMeshData(); - ExistingMeshDataPtr->UseMaterialNameSlotWorkflow = SkeletalMeshIsUsingMaterialSlotNameWorkflow(ExistingSkelMesh->AssetImportData); - ExistingMeshDataPtr->MinLOD = ExistingSkelMesh->MinLod; - ExistingMeshDataPtr->DisableBelowMinLodStripping = ExistingSkelMesh->DisableBelowMinLodStripping; + ExistingMeshDataPtr->UseMaterialNameSlotWorkflow = SkeletalMeshIsUsingMaterialSlotNameWorkflow(ExistingSkelMesh->AssetImportData); + ExistingMeshDataPtr->MinLOD = ExistingSkelMesh->MinLod; + ExistingMeshDataPtr->DisableBelowMinLodStripping = ExistingSkelMesh->DisableBelowMinLodStripping; - FSkeletalMeshModel* ImportedResource = ExistingSkelMesh->GetImportedModel(); + FSkeletalMeshModel* ImportedResource = ExistingSkelMesh->GetImportedModel(); - //Add the existing Material slot name data - for (int32 MaterialIndex = 0; MaterialIndex < ExistingSkelMesh->Materials.Num(); ++MaterialIndex) + //Add the existing Material slot name data + for (int32 MaterialIndex = 0; MaterialIndex < ExistingSkelMesh->Materials.Num(); ++MaterialIndex) + { + ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.Add(ExistingSkelMesh->Materials[MaterialIndex].ImportedMaterialSlotName); + } + + for (int32 LodIndex = 0; LodIndex < ImportedResource->LODModels.Num(); ++LodIndex) + { + ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData.AddZeroed(); + for (int32 SectionIndex = 0; SectionIndex < ImportedResource->LODModels[LodIndex].Sections.Num(); ++SectionIndex) { - ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.Add(ExistingSkelMesh->Materials[MaterialIndex].ImportedMaterialSlotName); - } - - for (int32 LodIndex = 0; LodIndex < ImportedResource->LODModels.Num(); ++LodIndex) - { - ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData.AddZeroed(); - for (int32 SectionIndex = 0; SectionIndex < ImportedResource->LODModels[LodIndex].Sections.Num(); ++SectionIndex) + int32 SectionMaterialIndex = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].MaterialIndex; + bool SectionCastShadow = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bCastShadow; + bool SectionRecomputeTangents = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bRecomputeTangent; + int32 GenerateUpTo = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].GenerateUpToLodIndex; + bool bDisabled = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bDisabled; + if (ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.IsValidIndex(SectionMaterialIndex)) { - int32 SectionMaterialIndex = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].MaterialIndex; - bool SectionCastShadow = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bCastShadow; - bool SectionRecomputeTangents = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bRecomputeTangent; - int32 GenerateUpTo = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].GenerateUpToLodIndex; - bool bDisabled = ImportedResource->LODModels[LodIndex].Sections[SectionIndex].bDisabled; - if (ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.IsValidIndex(SectionMaterialIndex)) - { - ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData[LodIndex].Add(ExistingMeshLodSectionData(ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData[SectionMaterialIndex], SectionCastShadow, SectionRecomputeTangents, GenerateUpTo, bDisabled)); - } - } - } - - ExistingMeshDataPtr->ExistingSockets = ExistingSkelMesh->GetMeshOnlySocketList(); - ExistingMeshDataPtr->bSaveRestoreMaterials = bSaveMaterials; - if (ExistingMeshDataPtr->bSaveRestoreMaterials) - { - ExistingMeshDataPtr->ExistingMaterials = ExistingSkelMesh->Materials; - } - ExistingMeshDataPtr->ExistingRetargetBasePose = ExistingSkelMesh->RetargetBasePose; - - if( ImportedResource->LODModels.Num() > 0 && - ExistingSkelMesh->GetLODNum() == ImportedResource->LODModels.Num() ) - { - int32 OffsetReductionLODIndex = 0; - FSkeletalMeshLODInfo* LODInfo = ExistingSkelMesh->GetLODInfo( ReimportLODIndex < 0 ? 0 : ReimportLODIndex); - ExistingMeshDataPtr->bIsReimportLODReduced = (LODInfo && LODInfo->bHasBeenSimplified); - if (ExistingMeshDataPtr->bIsReimportLODReduced) - { - //Save the imported LOD reduction settings - ExistingMeshDataPtr->ExistingReimportLODReductionSettings = LODInfo->ReductionSettings; - } - - // Remove the zero'th LOD (ie: the LOD being reimported). - if (!ReimportSpecificLOD) - { - ImportedResource->LODModels.RemoveAt(0); - ExistingSkelMesh->RemoveLODInfo(0); - OffsetReductionLODIndex = 1; - } - - // Copy off the remaining LODs. - for ( int32 LODModelIndex = 0 ; LODModelIndex < ImportedResource->LODModels.Num() ; ++LODModelIndex ) - { - FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; - LODModel.RawPointIndices.Lock( LOCK_READ_ONLY ); - LODModel.LegacyRawPointIndices.Lock( LOCK_READ_ONLY ); - LODModel.RawSkeletalMeshBulkData.GetBulkData().Lock( LOCK_READ_ONLY ); - int32 ReductionLODIndex = LODModelIndex + OffsetReductionLODIndex; - if (ImportedResource->OriginalReductionSourceMeshData.IsValidIndex(ReductionLODIndex) && !ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->IsEmpty()) - { - FSkeletalMeshLODModel BaseLODModel; - TMap> BaseLODMorphTargetData; - ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->LoadReductionData(BaseLODModel, BaseLODMorphTargetData); - FReductionBaseSkeletalMeshBulkData* ReductionLODData = new FReductionBaseSkeletalMeshBulkData(); - ReductionLODData->SaveReductionData(BaseLODModel, BaseLODMorphTargetData); - //Add necessary empty slot - while (ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Num() < LODModelIndex) - { - FReductionBaseSkeletalMeshBulkData* EmptyReductionLODData = new FReductionBaseSkeletalMeshBulkData(); - ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(EmptyReductionLODData); - } - ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(ReductionLODData); - } - } - ExistingMeshDataPtr->ExistingLODModels = ImportedResource->LODModels; - for (int32 LODModelIndex = 0; LODModelIndex < ImportedResource->LODModels.Num(); ++LODModelIndex) - { - FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; - LODModel.RawPointIndices.Unlock(); - LODModel.LegacyRawPointIndices.Unlock(); - LODModel.RawSkeletalMeshBulkData.GetBulkData().Unlock(); - } - - ExistingMeshDataPtr->ExistingLODInfo = ExistingSkelMesh->GetLODInfoArray(); - ExistingMeshDataPtr->ExistingRefSkeleton = ExistingSkelMesh->RefSkeleton; - - } - - // First asset should be the one that the skeletal mesh should point too - ExistingMeshDataPtr->ExistingPhysicsAssets.Empty(); - ExistingMeshDataPtr->ExistingPhysicsAssets.Add( ExistingSkelMesh->PhysicsAsset ); - for (TObjectIterator It; It; ++It) - { - UPhysicsAsset* PhysicsAsset = *It; - if ( PhysicsAsset->PreviewSkeletalMesh == ExistingSkelMesh && ExistingSkelMesh->PhysicsAsset != PhysicsAsset ) - { - ExistingMeshDataPtr->ExistingPhysicsAssets.Add( PhysicsAsset ); - } - } - - ExistingMeshDataPtr->ExistingShadowPhysicsAsset = ExistingSkelMesh->ShadowPhysicsAsset; - - ExistingMeshDataPtr->ExistingSkeleton = ExistingSkelMesh->Skeleton; - // since copying back original skeleton, this shoudl be safe to do - ExistingMeshDataPtr->ExistingPostProcessAnimBlueprint = ExistingSkelMesh->PostProcessAnimBlueprint; - - ExistingMeshDataPtr->ExistingLODSettings = ExistingSkelMesh->LODSettings; - - ExistingSkelMesh->ExportMirrorTable(ExistingMeshDataPtr->ExistingMirrorTable); - - ExistingMeshDataPtr->ExistingMorphTargets.Empty(ExistingSkelMesh->MorphTargets.Num()); - ExistingMeshDataPtr->ExistingMorphTargets.Append(ExistingSkelMesh->MorphTargets); - - ExistingMeshDataPtr->bExistingUseFullPrecisionUVs = ExistingSkelMesh->bUseFullPrecisionUVs; - ExistingMeshDataPtr->bExistingUseHighPrecisionTangentBasis = ExistingSkelMesh->bUseHighPrecisionTangentBasis; - - ExistingMeshDataPtr->ExistingAssetImportData = ExistingSkelMesh->AssetImportData; - ExistingMeshDataPtr->ExistingThumbnailInfo = ExistingSkelMesh->ThumbnailInfo; - - ExistingMeshDataPtr->ExistingClothingAssets = ExistingSkelMesh->MeshClothingAssets; - - ExistingMeshDataPtr->ExistingSamplingInfo = ExistingSkelMesh->GetSamplingInfo(); - - //Add the last fbx import data - UFbxSkeletalMeshImportData* ImportData = Cast(ExistingSkelMesh->AssetImportData); - if (ImportData && ExistingMeshDataPtr->UseMaterialNameSlotWorkflow) - { - for (int32 ImportMaterialOriginalNameDataIndex = 0; ImportMaterialOriginalNameDataIndex < ImportData->ImportMaterialOriginalNameData.Num(); ++ImportMaterialOriginalNameDataIndex) - { - FName MaterialName = ImportData->ImportMaterialOriginalNameData[ImportMaterialOriginalNameDataIndex]; - ExistingMeshDataPtr->LastImportMaterialOriginalNameData.Add(MaterialName); - } - for (int32 LodIndex = 0; LodIndex < ImportData->ImportMeshLodData.Num(); ++LodIndex) - { - ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData.AddZeroed(); - const FImportMeshLodSectionsData &ImportMeshLodSectionsData = ImportData->ImportMeshLodData[LodIndex]; - for (int32 SectionIndex = 0; SectionIndex < ImportMeshLodSectionsData.SectionOriginalMaterialName.Num(); ++SectionIndex) - { - FName MaterialName = ImportMeshLodSectionsData.SectionOriginalMaterialName[SectionIndex]; - ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData[LodIndex].Add(MaterialName); - } + ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData[LodIndex].Add(ExistingMeshLodSectionData(ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData[SectionMaterialIndex], SectionCastShadow, SectionRecomputeTangents, GenerateUpTo, bDisabled)); } } } + ExistingMeshDataPtr->ExistingSockets = ExistingSkelMesh->GetMeshOnlySocketList(); + ExistingMeshDataPtr->bSaveRestoreMaterials = bSaveMaterials; + if (ExistingMeshDataPtr->bSaveRestoreMaterials) + { + ExistingMeshDataPtr->ExistingMaterials = ExistingSkelMesh->Materials; + } + ExistingMeshDataPtr->ExistingRetargetBasePose = ExistingSkelMesh->RetargetBasePose; + + if( ImportedResource->LODModels.Num() > 0 && + ExistingSkelMesh->GetLODNum() == ImportedResource->LODModels.Num() ) + { + int32 OffsetReductionLODIndex = 0; + FSkeletalMeshLODInfo* LODInfo = ExistingSkelMesh->GetLODInfo( ReimportLODIndex < 0 ? 0 : ReimportLODIndex); + ExistingMeshDataPtr->bIsReimportLODReduced = (LODInfo && LODInfo->bHasBeenSimplified); + if (ExistingMeshDataPtr->bIsReimportLODReduced) + { + //Save the imported LOD reduction settings + ExistingMeshDataPtr->ExistingReimportLODReductionSettings = LODInfo->ReductionSettings; + } + + // Remove the zero'th LOD (ie: the LOD being reimported). + if (!ReimportSpecificLOD) + { + ImportedResource->LODModels.RemoveAt(0); + ExistingSkelMesh->RemoveLODInfo(0); + OffsetReductionLODIndex = 1; + } + + // Copy off the remaining LODs. + for ( int32 LODModelIndex = 0 ; LODModelIndex < ImportedResource->LODModels.Num() ; ++LODModelIndex ) + { + FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; + LODModel.RawPointIndices.Lock( LOCK_READ_ONLY ); + LODModel.LegacyRawPointIndices.Lock( LOCK_READ_ONLY ); + LODModel.RawSkeletalMeshBulkData.GetBulkData().Lock( LOCK_READ_ONLY ); + int32 ReductionLODIndex = LODModelIndex + OffsetReductionLODIndex; + if (ImportedResource->OriginalReductionSourceMeshData.IsValidIndex(ReductionLODIndex) && !ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->IsEmpty()) + { + FSkeletalMeshLODModel BaseLODModel; + TMap> BaseLODMorphTargetData; + ImportedResource->OriginalReductionSourceMeshData[ReductionLODIndex]->LoadReductionData(BaseLODModel, BaseLODMorphTargetData); + FReductionBaseSkeletalMeshBulkData* ReductionLODData = new FReductionBaseSkeletalMeshBulkData(); + ReductionLODData->SaveReductionData(BaseLODModel, BaseLODMorphTargetData); + //Add necessary empty slot + while (ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Num() < LODModelIndex) + { + FReductionBaseSkeletalMeshBulkData* EmptyReductionLODData = new FReductionBaseSkeletalMeshBulkData(); + ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(EmptyReductionLODData); + } + ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(ReductionLODData); + } + } + ExistingMeshDataPtr->ExistingLODModels = ImportedResource->LODModels; + for (int32 LODModelIndex = 0; LODModelIndex < ImportedResource->LODModels.Num(); ++LODModelIndex) + { + FSkeletalMeshLODModel& LODModel = ImportedResource->LODModels[LODModelIndex]; + LODModel.RawPointIndices.Unlock(); + LODModel.LegacyRawPointIndices.Unlock(); + LODModel.RawSkeletalMeshBulkData.GetBulkData().Unlock(); + } + + ExistingMeshDataPtr->ExistingLODInfo = ExistingSkelMesh->GetLODInfoArray(); + ExistingMeshDataPtr->ExistingRefSkeleton = ExistingSkelMesh->RefSkeleton; + + } + + // First asset should be the one that the skeletal mesh should point too + ExistingMeshDataPtr->ExistingPhysicsAssets.Empty(); + ExistingMeshDataPtr->ExistingPhysicsAssets.Add( ExistingSkelMesh->PhysicsAsset ); + for (TObjectIterator It; It; ++It) + { + UPhysicsAsset* PhysicsAsset = *It; + if ( PhysicsAsset->PreviewSkeletalMesh == ExistingSkelMesh && ExistingSkelMesh->PhysicsAsset != PhysicsAsset ) + { + ExistingMeshDataPtr->ExistingPhysicsAssets.Add( PhysicsAsset ); + } + } + + ExistingMeshDataPtr->ExistingShadowPhysicsAsset = ExistingSkelMesh->ShadowPhysicsAsset; + + ExistingMeshDataPtr->ExistingSkeleton = ExistingSkelMesh->Skeleton; + // since copying back original skeleton, this shoudl be safe to do + ExistingMeshDataPtr->ExistingPostProcessAnimBlueprint = ExistingSkelMesh->PostProcessAnimBlueprint; + + ExistingMeshDataPtr->ExistingLODSettings = ExistingSkelMesh->LODSettings; + + ExistingSkelMesh->ExportMirrorTable(ExistingMeshDataPtr->ExistingMirrorTable); + + ExistingMeshDataPtr->ExistingMorphTargets.Empty(ExistingSkelMesh->MorphTargets.Num()); + ExistingMeshDataPtr->ExistingMorphTargets.Append(ExistingSkelMesh->MorphTargets); + + ExistingMeshDataPtr->bExistingUseFullPrecisionUVs = ExistingSkelMesh->bUseFullPrecisionUVs; + ExistingMeshDataPtr->bExistingUseHighPrecisionTangentBasis = ExistingSkelMesh->bUseHighPrecisionTangentBasis; + + ExistingMeshDataPtr->ExistingAssetImportData = ExistingSkelMesh->AssetImportData; + ExistingMeshDataPtr->ExistingThumbnailInfo = ExistingSkelMesh->ThumbnailInfo; + + ExistingMeshDataPtr->ExistingClothingAssets = ExistingSkelMesh->MeshClothingAssets; + + ExistingMeshDataPtr->ExistingSamplingInfo = ExistingSkelMesh->GetSamplingInfo(); + + //Add the last fbx import data + UFbxSkeletalMeshImportData* ImportData = Cast(ExistingSkelMesh->AssetImportData); + if (ImportData && ExistingMeshDataPtr->UseMaterialNameSlotWorkflow) + { + for (int32 ImportMaterialOriginalNameDataIndex = 0; ImportMaterialOriginalNameDataIndex < ImportData->ImportMaterialOriginalNameData.Num(); ++ImportMaterialOriginalNameDataIndex) + { + FName MaterialName = ImportData->ImportMaterialOriginalNameData[ImportMaterialOriginalNameDataIndex]; + ExistingMeshDataPtr->LastImportMaterialOriginalNameData.Add(MaterialName); + } + for (int32 LodIndex = 0; LodIndex < ImportData->ImportMeshLodData.Num(); ++LodIndex) + { + ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData.AddZeroed(); + const FImportMeshLodSectionsData &ImportMeshLodSectionsData = ImportData->ImportMeshLodData[LodIndex]; + for (int32 SectionIndex = 0; SectionIndex < ImportMeshLodSectionsData.SectionOriginalMaterialName.Num(); ++SectionIndex) + { + FName MaterialName = ImportMeshLodSectionsData.SectionOriginalMaterialName[SectionIndex]; + ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData[LodIndex].Add(MaterialName); + } + } + } + //Store the user asset data + const TArray* UserData = ExistingSkelMesh->GetAssetUserDataArray(); + if (UserData) + { + for (int32 Idx = 0; Idx < UserData->Num(); Idx++) + { + if ((*UserData)[Idx] != nullptr) + { + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); + if (bAddDupToRoot) + { + DupObject->AddToRoot(); + } + ExistingMeshDataPtr->ExistingAssetUserData.Add(DupObject, bAddDupToRoot); + } + } + } return ExistingMeshDataPtr; } @@ -1155,5 +1175,18 @@ void RestoreExistingSkelMeshData(ExistingSkelMeshData* MeshData, USkeletalMesh* SkeletalMeshImportedModel->OriginalReductionSourceMeshData[ReimportLODIndex]->EmptyBulkData(); } } + + // Copy user data to newly created mesh + for (auto Kvp : MeshData->ExistingAssetUserData) + { + UAssetUserData* UserDataObject = Kvp.Key; + if (Kvp.Value) + { + //if the duplicated temporary UObject was add to root, we must remove it from the root + UserDataObject->RemoveFromRoot(); + } + UserDataObject->Rename(nullptr, SkeletalMesh, REN_DontCreateRedirectors | REN_DoNotDirty); + SkeletalMesh->AddAssetUserData(UserDataObject); + } } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp index 21fe03fec62f..50ff365895bc 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxAnimationExport.cpp @@ -45,19 +45,26 @@ void FFbxExporter::ExportAnimSequenceToFbx(const UAnimSequence* AnimSeq, } const float FrameRate = FMath::TruncToFloat(((AnimSeq->GetRawNumberOfFrames() - 1) / AnimSeq->SequenceLength) + 0.5f); + //Configure the scene time line + { + FbxGlobalSettings& SceneGlobalSettings = Scene->GetGlobalSettings(); + double CurrentSceneFrameRate = FbxTime::GetFrameRate(SceneGlobalSettings.GetTimeMode()); + if (!bSceneGlobalTimeLineSet || FrameRate > CurrentSceneFrameRate) + { + FbxTime::EMode ComputeTimeMode = FbxTime::ConvertFrameRateToTimeMode(FrameRate); + FbxTime::SetGlobalTimeMode(ComputeTimeMode, ComputeTimeMode == FbxTime::eCustom ? FrameRate : 0.0); + SceneGlobalSettings.SetTimeMode(ComputeTimeMode); + if (ComputeTimeMode == FbxTime::eCustom) + { + SceneGlobalSettings.SetCustomFrameRate(FrameRate); + } + bSceneGlobalTimeLineSet = true; + } + } // set time correctly FbxTime ExportedStartTime, ExportedStopTime; - if ( FMath::IsNearlyEqual(FrameRate, DEFAULT_SAMPLERATE, 1.f) ) - { - ExportedStartTime.SetGlobalTimeMode(FbxTime::eFrames30); - ExportedStopTime.SetGlobalTimeMode(FbxTime::eFrames30); - } - else - { - ExportedStartTime.SetGlobalTimeMode(FbxTime::eCustom, FrameRate); - ExportedStopTime.SetGlobalTimeMode(FbxTime::eCustom, FrameRate); - } + ExportedStartTime.SetSecondDouble(0.f); ExportedStopTime.SetSecondDouble(AnimSeq->SequenceLength); diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp index 21e72412b212..eaed305424f7 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxFactory.cpp @@ -1059,6 +1059,9 @@ void UFbxImportUI::ResetToDefault() StaticMeshImportData->ReloadConfig(); SkeletalMeshImportData->ReloadConfig(); TextureImportData->ReloadConfig(); + + //Make sure the UI do not display the Base Material + TextureImportData->bUseBaseMaterial = false; } namespace ImportCompareHelper diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp index b0eab66ed212..4864fc6bb010 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp @@ -255,6 +255,9 @@ void FFbxExporter::CreateDocument() // Maya use cm by default Scene->GetGlobalSettings().SetSystemUnit(FbxSystemUnit::cm); //FbxScene->GetGlobalSettings().SetOriginalSystemUnit( KFbxSystemUnit::m ); + + bSceneGlobalTimeLineSet = false; + Scene->GetGlobalSettings().SetTimeMode(FbxTime::eDefaultMode); // setup anim stack AnimStack = FbxAnimStack::Create(Scene, "Unreal Take"); @@ -381,6 +384,7 @@ void FFbxExporter::CloseDocument() Scene->Destroy(); Scene = NULL; } + bSceneGlobalTimeLineSet = false; } void FFbxExporter::CreateAnimatableUserProperty(FbxNode* Node, float Value, const char* Name, const char* Label) diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp index 864f49bc8ef0..dc5769b7473c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp @@ -400,6 +400,7 @@ void ApplyImportUIToImportOptions(UFbxImportUI* ImportUI, FBXImportOptions& InOu InOutImportOptions.bConvertSceneUnit = StaticMeshData->bConvertSceneUnit; InOutImportOptions.VertexColorImportOption = StaticMeshData->VertexColorImportOption; InOutImportOptions.VertexOverrideColor = StaticMeshData->VertexOverrideColor; + InOutImportOptions.bReorderMaterialToFbxOrder = StaticMeshData->bReorderMaterialToFbxOrder; } else if ( ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh ) { @@ -419,6 +420,7 @@ void ApplyImportUIToImportOptions(UFbxImportUI* ImportUI, FBXImportOptions& InOu InOutImportOptions.bConvertSceneUnit = SkeletalMeshData->bConvertSceneUnit; InOutImportOptions.VertexColorImportOption = SkeletalMeshData->VertexColorImportOption; InOutImportOptions.VertexOverrideColor = SkeletalMeshData->VertexOverrideColor; + InOutImportOptions.bReorderMaterialToFbxOrder = SkeletalMeshData->bReorderMaterialToFbxOrder; if(ImportUI->bImportAnimations) { @@ -477,6 +479,7 @@ void ApplyImportUIToImportOptions(UFbxImportUI* ImportUI, FBXImportOptions& InOu InOutImportOptions.bRemoveRedundantKeys = ImportUI->AnimSequenceImportData->bRemoveRedundantKeys; InOutImportOptions.bDoNotImportCurveWithZero = ImportUI->AnimSequenceImportData->bDoNotImportCurveWithZero; InOutImportOptions.bImportCustomAttribute = ImportUI->AnimSequenceImportData->bImportCustomAttribute; + InOutImportOptions.bDeleteExistingCustomAttributeCurves = ImportUI->AnimSequenceImportData->bDeleteExistingCustomAttributeCurves; InOutImportOptions.bImportBoneTracks = ImportUI->AnimSequenceImportData->bImportBoneTracks; InOutImportOptions.bSetMaterialDriveParameterOnCustomAttribute = ImportUI->AnimSequenceImportData->bSetMaterialDriveParameterOnCustomAttribute; InOutImportOptions.MaterialCurveSuffixes = ImportUI->AnimSequenceImportData->MaterialCurveSuffixes; @@ -681,7 +684,7 @@ bool FFbxImporter::GetSceneInfo(FString Filename, FbxSceneInfo& SceneInfo, bool } GWarn->UpdateProgress( 90, 100 ); case IMPORTED: - + case FIXEDANDCONVERTED: default: break; } @@ -1423,6 +1426,7 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, * @EventParam ImportUniformScale float Returns the uniform scale apply on the import data * @EventParam MaterialBasePath string Returns the path pointing on the base material use to import material instance * @EventParam MaterialSearchLocation string Returns the scope of the search for existing materials (if material not found it can create one depending on bImportMaterials value) + * @EventParam ReorderMaterialToFbxOrder boolean returns weather the importer should reorder the materials in the same order has the fbx file * @EventParam AutoGenerateCollision boolean Returns whether the importer should create collision primitive * @EventParam CombineToSingle boolean Returns whether the importer should combine all mesh part together or import many meshes * @EventParam BakePivotInVertex boolean Returns whether the importer should bake the fbx mesh pivot into the vertex position @@ -1509,6 +1513,7 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt ImportUniformScale"), ImportOptions->ImportUniformScale)); Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt MaterialBasePath"), ImportOptions->MaterialBasePath)); Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt MaterialSearchLocation"), MaterialSearchLocationEnum->GetNameStringByValue((uint64)(ImportOptions->MaterialSearchLocation)))); + Attribs.Add(FAnalyticsEventAttribute(TEXT("GenOpt ReorderMaterialToFbxOrder"), ImportOptions->bReorderMaterialToFbxOrder)); //We cant capture a this member, so just assign the pointer here FBXImportOptions* CaptureImportOptions = ImportOptions; @@ -1563,6 +1568,7 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt DoNotImportCurveWithZero"), CaptureImportOptions->bDoNotImportCurveWithZero)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt ImportBoneTracks"), CaptureImportOptions->bImportBoneTracks)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt ImportCustomAttribute"), CaptureImportOptions->bImportCustomAttribute)); + Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt DeleteExistingCustomAttributeCurves"), CaptureImportOptions->bDeleteExistingCustomAttributeCurves)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt PreserveLocalTransform"), CaptureImportOptions->bPreserveLocalTransform)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt RemoveRedundantKeys"), CaptureImportOptions->bRemoveRedundantKeys)); Attribs.Add(FAnalyticsEventAttribute(TEXT("AnimOpt Resample"), CaptureImportOptions->bResample)); @@ -1603,8 +1609,10 @@ bool FFbxImporter::ImportFromFile(const FString& Filename, const FString& Type, ConvertLodPrefixToLodGroup(); MeshNamesCache.Empty(); + CurPhase = FIXEDANDCONVERTED; + break; } - + case FIXEDANDCONVERTED: default: break; } @@ -2336,59 +2344,7 @@ FbxNode* FFbxImporter::GetRootSkeleton(FbxNode* Link) return RootBone; } - -void FFbxImporter::DumpFBXNode(FbxNode* Node) -{ - FbxMesh* Mesh = Node->GetMesh(); - const FString NodeName(Node->GetName()); - - if(Mesh) - { - UE_LOG(LogFbx, Log, TEXT("=================================================")); - UE_LOG(LogFbx, Log, TEXT("Dumping Node START [%s] "), *NodeName); - int DeformerCount = Mesh->GetDeformerCount(); - UE_LOG(LogFbx, Log,TEXT("\tTotal Deformer Count %d."), *NodeName, DeformerCount); - for(int i=0; iGetDeformer(i); - const FString DeformerName(Deformer->GetName()); - const FString DeformerTypeName(Deformer->GetTypeName()); - UE_LOG(LogFbx, Log,TEXT("\t\t[Node %d] %s (Type %s)."), i+1, *DeformerName, *DeformerTypeName); - UE_LOG(LogFbx, Log,TEXT("=================================================")); - } - - FbxNodeAttribute* NodeAttribute = Node->GetNodeAttribute(); - if(NodeAttribute) - { - FString NodeAttributeName(NodeAttribute->GetName()); - FbxNodeAttribute::EType Type = NodeAttribute->GetAttributeType(); - UE_LOG(LogFbx, Log,TEXT("\tAttribute (%s) Type (%d)."), *NodeAttributeName, (int32)Type); - - for (int i=0; iGetNodeCount(); ++i) - { - FbxNode * Child = NodeAttribute->GetNode(i); - - if (Child) - { - const FString ChildName(Child->GetName()); - const FString ChildTypeName(Child->GetTypeName()); - UE_LOG(LogFbx, Log,TEXT("\t\t[Node Attribute Child %d] %s (Type %s)."), i+1, *ChildName, *ChildTypeName); - } - } - - } - - UE_LOG(LogFbx, Log,TEXT("Dumping Node END [%s]"), *NodeName); - } - - for(int ChildIdx=0; ChildIdx < Node->GetChildCount(); ChildIdx++) - { - FbxNode* ChildNode = Node->GetChild(ChildIdx); - DumpFBXNode(ChildNode); - } - -} - + void FFbxImporter::ApplyTransformSettingsToFbxNode(FbxNode* Node, UFbxAssetImportData* AssetData) { check(Node); @@ -2489,8 +2445,6 @@ void FFbxImporter::RecursiveFindFbxSkelMesh(FbxNode* Node, TArray< TArrayGetMesh() && Node->GetMesh()->GetDeformerCount(FbxDeformer::eSkin) > 0 ) { SkelMeshNode = Node; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp index 787c3ec87a3f..8589b0cf7743 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMeshImportData.cpp @@ -9,6 +9,7 @@ UFbxMeshImportData::UFbxMeshImportData(const FObjectInitializer& ObjectInitializ NormalImportMethod = FBXNIM_ComputeNormals; NormalGenerationMethod = EFBXNormalGenerationMethod::MikkTSpace; bBakePivotInVertex = false; + bReorderMaterialToFbxOrder = true; } bool UFbxMeshImportData::CanEditChange(const UProperty* InProperty) const diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp index 63cee238a4df..ade9647bf588 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSceneImportOptionsSkeletalMesh.cpp @@ -23,6 +23,7 @@ UFbxSceneImportOptionsSkeletalMesh::UFbxSceneImportOptionsSkeletalMesh(const FOb , bUseDefaultSampleRate(false) , CustomSampleRate(0) , bImportCustomAttribute(true) + , bDeleteExistingCustomAttributeCurves(false) , bPreserveLocalTransform(false) , bDeleteExistingMorphTargetCurves(false) { @@ -53,6 +54,7 @@ void UFbxSceneImportOptionsSkeletalMesh::FillSkeletalMeshInmportData(UFbxSkeleta AnimSequenceImportData->AnimationLength = AnimationLength; AnimSequenceImportData->bDeleteExistingMorphTargetCurves = bDeleteExistingMorphTargetCurves; AnimSequenceImportData->bImportCustomAttribute = bImportCustomAttribute; + AnimSequenceImportData->bDeleteExistingCustomAttributeCurves = bDeleteExistingCustomAttributeCurves; AnimSequenceImportData->bPreserveLocalTransform = bPreserveLocalTransform; AnimSequenceImportData->bUseDefaultSampleRate = bUseDefaultSampleRate; AnimSequenceImportData->CustomSampleRate = CustomSampleRate; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp index 36ea94b2c834..930d276cb6b6 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp @@ -1222,6 +1222,41 @@ bool UnFbx::FFbxImporter::ImportBone(TArray& NodeArray, FSkeletalMeshI BoneName = UTF8_TO_TCHAR( MakeName( LinkName ) ); Bone.Name = BoneName; + //Check for nan and for zero scale + if (AllowImportBoneErrorAndWarning) + { + bool bFoundNan = false; + bool bFoundZeroScale = false; + for (int32 i = 0; i < 4; ++i) + { + if (i < 3) + { + if (FMath::IsNaN(LocalLinkT[i]) || FMath::IsNaN(LocalLinkS[i])) + { + bFoundNan = true; + } + if (FMath::IsNearlyZero(LocalLinkS[i])) + { + bFoundZeroScale = true; + } + } + if (FMath::IsNaN(LocalLinkQ[i])) + { + bFoundNan = true; + } + } + + if (bFoundNan) + { + AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("BoneTransformNAN", "Found NAN value in bone transform. Bone name: [{0}]"), FText::FromString(BoneName))), FFbxErrors::SkeletalMesh_InvalidBindPose); + } + + if (bFoundZeroScale) + { + AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("BoneTransformZeroScale", "Found zero scale value in bone transform. Bone name: [{0}]"), FText::FromString(BoneName))), FFbxErrors::SkeletalMesh_InvalidBindPose); + } + } + SkeletalMeshImportData::FJointPos& JointMatrix = Bone.BonePos; FbxSkeleton* Skeleton = Link->GetSkeleton(); if (Skeleton) @@ -1324,7 +1359,6 @@ bool UnFbx::FFbxImporter::FillSkeletalMeshImportData(TArray& NodeArray { if (!(bIsReimport && ImportOptions->bImportAsSkeletalGeometry)) //Do not import bone if we import only the geometry and we are reimporting { - AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FbxSkeletaLMeshimport_MultipleRootFound", "Multiple roots found")), FFbxErrors::SkeletalMesh_MultipleRoots); return false; } } @@ -4358,6 +4392,9 @@ void UnFbx::FFbxImporter::ImportMorphTargetsInternal( TArray& SkelMesh GWarn->BeginSlowTask( NSLOCTEXT("FbxImporter", "BeginGeneratingMorphModelsTask", "Generating Morph Models"), true); + //Use this TMap to store the name of the blendshape map by the fbx uniqueID. This allow to assign unique name to each blend shape. + TMap UniqueIDToName; + // For each morph in FBX geometries, we create one morph target for the Unreal skeletal mesh for (int32 NodeIndex = 0; NodeIndex < SkelMeshNodeArray.Num(); NodeIndex++) { @@ -4417,6 +4454,36 @@ void UnFbx::FFbxImporter::ImportMorphTargetsInternal( TArray& SkelMesh } } + uint64 UniqueID = Shape->GetUniqueID(); + if (UniqueIDToName.Contains(UniqueID)) + { + ShapeName = UniqueIDToName.FindChecked(UniqueID); + } + else + { + //In case the shape do not have a unique name + //make it unique + int32 NameIndex = 0; + FString CurrentShapeName = ShapeName; + bool bNameNotUnique = ShapeNameToShapeArray.Contains(CurrentShapeName); + while(bNameNotUnique) + { + //Append ShapeX to the shape name (same has Maya) + if (NameIndex == 0) + { + CurrentShapeName = ShapeName + TEXT("Shape"); + NameIndex++; + } + else + { + CurrentShapeName = ShapeName + TEXT("Shape") + FString::FromInt(NameIndex++); + } + bNameNotUnique = ShapeNameToShapeArray.Contains(CurrentShapeName); + } + ShapeName = CurrentShapeName; + UniqueIDToName.Add(UniqueID, ShapeName); + } + TArray & ShapeArray = ShapeNameToShapeArray.FindOrAdd(ShapeName); if (ShapeArray.Num() == 0) { diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp index f7e72f3c40cc..56e08e1f4ff8 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp @@ -34,6 +34,7 @@ #include "PhysicsEngine/BodySetup.h" #include "MeshDescription.h" #include "MeshAttributes.h" +#include "MeshDescriptionOperations.h" #include "IMeshBuilderModule.h" #include "Settings/EditorExperimentalSettings.h" @@ -68,6 +69,14 @@ static FbxString GetNodeNameWithoutNamespace( FbxNode* Node ) } } +void CreateTokenizedErrorForDegeneratedPart(UnFbx::FFbxImporter* FbxImporter, const FString MeshName, const FString NodeName) +{ + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("MeshName"), FText::FromString(MeshName)); + Arguments.Add(TEXT("PartName"), FText::FromString(NodeName)); + FText ErrorMsg = LOCTEXT("MeshHasNoRenderableTriangles", "Mesh name: [{MeshName}] part name: [{PartName}] could not be created because all of its polygons are degenerate."); + FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(ErrorMsg, Arguments)), FFbxErrors::StaticMesh_AllTrianglesDegenerate); +} //------------------------------------------------------------------------- // @@ -270,7 +279,7 @@ bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh { check(StaticMesh->SourceModels.IsValidIndex(LODIndex)); FbxMesh* Mesh = Node->GetMesh(); - FStaticMeshSourceModel& SrcModel = StaticMesh->SourceModels[LODIndex]; + FString FbxNodeName = UTF8_TO_TCHAR(Node->GetName()); FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex); //The mesh description should have been created before calling BuildStaticMeshFromGeometry @@ -390,7 +399,7 @@ bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh if (!Mesh->IsTriangleMesh()) { if(!GIsAutomationTesting) - UE_LOG(LogFbx, Display, TEXT("Triangulating static mesh %s"), UTF8_TO_TCHAR(Node->GetName())); + UE_LOG(LogFbx, Display, TEXT("Triangulating static mesh %s"), *FbxNodeName); const bool bReplace = true; FbxNodeAttribute* ConvertedNode = GeometryConverter->Triangulate(Mesh, bReplace); @@ -907,10 +916,7 @@ bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh if (!bHasNonDegeneratePolygons) { - FFormatNamedArguments Arguments; - Arguments.Add( TEXT("MeshName"), FText::FromString(StaticMesh->GetName())); - FText ErrorMsg = LOCTEXT("MeshHasNoRenderableTriangles", "{MeshName} could not be created because all of its polygons are degenerate."); - AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(ErrorMsg, Arguments)), FFbxErrors::StaticMesh_AllTrianglesDegenerate); + CreateTokenizedErrorForDegeneratedPart(this, StaticMesh->GetName(), FbxNodeName); } bool bIsValidMesh = bHasNonDegeneratePolygons; @@ -1530,7 +1536,9 @@ UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TA StaticMesh->LightMapResolution = 64; StaticMesh->LightMapCoordinateIndex = 1; + float SqrBoundingBoxThreshold = THRESH_POINTS_ARE_NEAR * THRESH_POINTS_ARE_NEAR; + bool bAllDegenerated = true; TArray MeshMaterials; for (MeshIndex = 0; MeshIndex < MeshNodeArray.Num(); MeshIndex++ ) { @@ -1538,14 +1546,33 @@ UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TA if (Node->GetMesh()) { - if (!BuildStaticMeshFromGeometry(Node, StaticMesh, MeshMaterials, LODIndex, - VertexColorImportOption, ExistingVertexColorData, ImportOptions->VertexOverrideColor)) + Node->GetMesh()->ComputeBBox(); + FbxDouble3 BoxExtend; + BoxExtend[0] = Node->GetMesh()->BBoxMax.Get()[0] - Node->GetMesh()->BBoxMin.Get()[0]; + BoxExtend[1] = Node->GetMesh()->BBoxMax.Get()[1] - Node->GetMesh()->BBoxMin.Get()[1]; + BoxExtend[2] = Node->GetMesh()->BBoxMax.Get()[2] - Node->GetMesh()->BBoxMin.Get()[2]; + double SqrSize = (BoxExtend[0] * BoxExtend[0]) + (BoxExtend[1] * BoxExtend[1]) + (BoxExtend[2] * BoxExtend[2]); + //If the bounding box of the mesh part is smaller then the position threshold, the part is consider degenerated and will be skip. + if (SqrSize > SqrBoundingBoxThreshold) { - bBuildStatus = false; - break; + bAllDegenerated = false; + if (!BuildStaticMeshFromGeometry(Node, StaticMesh, MeshMaterials, LODIndex, + VertexColorImportOption, ExistingVertexColorData, ImportOptions->VertexOverrideColor)) + { + bBuildStatus = false; + break; + } + } + else + { + CreateTokenizedErrorForDegeneratedPart(this, StaticMesh->GetName(), UTF8_TO_TCHAR(Node->GetName())); } } } + if (bAllDegenerated) + { + bBuildStatus = false; + } if (bBuildStatus) { @@ -1754,7 +1781,7 @@ UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TA return StaticMesh; } -void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray& MeshNodeArray) +void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray& MeshNodeArray, bool bAllowFbxReorder) { if (StaticMesh == nullptr) { @@ -1843,70 +1870,73 @@ void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray& MeshN } //Reorder the StaticMaterials array to reflect the order in the fbx file - //So we make sure the order reflect the material ID in the DCCs - FMeshSectionInfoMap OldSectionInfoMap = StaticMesh->SectionInfoMap; - TArray FbxRemapMaterials; - TArray NewStaticMaterials; - for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex) + if (bAllowFbxReorder) { - const FString &FbxMaterial = MeshMaterials[FbxMaterialIndex]; - int32 FoundMaterialIndex = INDEX_NONE; + //So we make sure the order reflect the material ID in the DCCs + FMeshSectionInfoMap OldSectionInfoMap = StaticMesh->SectionInfoMap; + TArray FbxRemapMaterials; + TArray NewStaticMaterials; + for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex) + { + const FString &FbxMaterial = MeshMaterials[FbxMaterialIndex]; + int32 FoundMaterialIndex = INDEX_NONE; + for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->StaticMaterials.Num(); ++BuildMaterialIndex) + { + FStaticMaterial &BuildMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; + if (FbxMaterial.Compare(BuildMaterial.ImportedMaterialSlotName.ToString()) == 0) + { + FoundMaterialIndex = BuildMaterialIndex; + break; + } + } + + if (FoundMaterialIndex != INDEX_NONE) + { + FbxRemapMaterials.Add(FoundMaterialIndex); + NewStaticMaterials.Add(StaticMesh->StaticMaterials[FoundMaterialIndex]); + } + } + //Add the materials not used by the LOD 0 at the end of the array. The order here is irrelevant since it can be used by many LOD other then LOD 0 and in different order for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->StaticMaterials.Num(); ++BuildMaterialIndex) { - FStaticMaterial &BuildMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; - if (FbxMaterial.Compare(BuildMaterial.ImportedMaterialSlotName.ToString()) == 0) + const FStaticMaterial &StaticMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; + bool bFoundMaterial = false; + for (const FStaticMaterial &BuildMaterial : NewStaticMaterials) { - FoundMaterialIndex = BuildMaterialIndex; - break; + if (StaticMaterial == BuildMaterial) + { + bFoundMaterial = true; + break; + } + } + if (!bFoundMaterial) + { + FbxRemapMaterials.Add(BuildMaterialIndex); + NewStaticMaterials.Add(StaticMaterial); } } - if (FoundMaterialIndex != INDEX_NONE) - { - FbxRemapMaterials.Add(FoundMaterialIndex); - NewStaticMaterials.Add(StaticMesh->StaticMaterials[FoundMaterialIndex]); - } - } - //Add the materials not used by the LOD 0 at the end of the array. The order here is irrelevant since it can be used by many LOD other then LOD 0 and in different order - for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->StaticMaterials.Num(); ++BuildMaterialIndex) - { - const FStaticMaterial &StaticMaterial = StaticMesh->StaticMaterials[BuildMaterialIndex]; - bool bFoundMaterial = false; + StaticMesh->StaticMaterials.Empty(); for (const FStaticMaterial &BuildMaterial : NewStaticMaterials) { - if (StaticMaterial == BuildMaterial) - { - bFoundMaterial = true; - break; - } + StaticMesh->StaticMaterials.Add(BuildMaterial); } - if (!bFoundMaterial) - { - FbxRemapMaterials.Add(BuildMaterialIndex); - NewStaticMaterials.Add(StaticMaterial); - } - } - StaticMesh->StaticMaterials.Empty(); - for (const FStaticMaterial &BuildMaterial : NewStaticMaterials) - { - StaticMesh->StaticMaterials.Add(BuildMaterial); - } - - //Remap the material instance of the staticmaterial array and remap the material index of all sections - for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->RenderData->LODResources.Num(); ++LODResoureceIndex) - { - FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[LODResoureceIndex]; - int32 NumSections = LOD.Sections.Num(); - for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + //Remap the material instance of the staticmaterial array and remap the material index of all sections + for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->RenderData->LODResources.Num(); ++LODResoureceIndex) { - FMeshSectionInfo Info = OldSectionInfoMap.Get(LODResoureceIndex, SectionIndex); - int32 RemapIndex = FbxRemapMaterials.Find(Info.MaterialIndex); - if (StaticMesh->StaticMaterials.IsValidIndex(RemapIndex)) + FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[LODResoureceIndex]; + int32 NumSections = LOD.Sections.Num(); + for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) { - Info.MaterialIndex = RemapIndex; - StaticMesh->SectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); - StaticMesh->OriginalSectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); + FMeshSectionInfo Info = OldSectionInfoMap.Get(LODResoureceIndex, SectionIndex); + int32 RemapIndex = FbxRemapMaterials.Find(Info.MaterialIndex); + if (StaticMesh->StaticMaterials.IsValidIndex(RemapIndex)) + { + Info.MaterialIndex = RemapIndex; + StaticMesh->SectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); + StaticMesh->OriginalSectionInfoMap.Set(LODResoureceIndex, SectionIndex, Info); + } } } } @@ -2063,7 +2093,7 @@ void UnFbx::FFbxImporter::PostImportStaticMesh(UStaticMesh* StaticMesh, TArrayStaticMaterials.Num() > 1) { - ReorderMaterialAfterImport(StaticMesh, MeshNodeArray); + ReorderMaterialAfterImport(StaticMesh, MeshNodeArray, ImportOptions->bReorderMaterialToFbxOrder); } } @@ -2201,7 +2231,7 @@ void UnFbx::FFbxImporter::ImportStaticMeshLocalSockets(UStaticMesh* StaticMesh, check(Socket); Socket->SocketName = SocketNode.SocketName; - StaticMesh->Sockets.Add(Socket); + StaticMesh->AddSocket(Socket); } if (Socket) @@ -2267,7 +2297,7 @@ void UnFbx::FFbxImporter::ImportStaticMeshGlobalSockets( UStaticMesh* StaticMesh check(Socket); Socket->SocketName = SocketNode.SocketName; - StaticMesh->Sockets.Add(Socket); + StaticMesh->AddSocket(Socket); //Remove the axis conversion for the socket since its attach to a mesh containing this conversion. const FbxAMatrix& SocketMatrix = Scene->GetAnimationEvaluator()->GetNodeGlobalTransform(SocketNode.Node) * FFbxDataConverter::GetAxisConversionMatrixInv(); FTransform SocketTransform; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp index 2e692e754a75..7bb122e3a515 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/ReimportFbxSceneFactory.cpp @@ -1226,6 +1226,25 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportSkeletalMesh(void* VoidF return EReimportResult::Failed; } + const TArray* UserData = Mesh->GetAssetUserDataArray(); + TMap UserDataCopy; + if (UserData) + { + for (int32 Idx = 0; Idx < UserData->Num(); Idx++) + { + if ((*UserData)[Idx] != nullptr) + { + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); + if (bAddDupToRoot) + { + DupObject->AddToRoot(); + } + UserDataCopy.Add(DupObject, bAddDupToRoot); + } + } + } + ApplyMeshInfoFbxOptions(MeshInfo); //TODO support bBakePivotInVertex bool Old_bBakePivotInVertex = GlobalImportSettings->bBakePivotInVertex; @@ -1241,6 +1260,19 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportSkeletalMesh(void* VoidF { Mesh->AssetImportData->Update(FbxImportFileName); + // Copy user data to newly created mesh + for (auto Kvp : UserDataCopy) + { + UAssetUserData* UserDataObject = Kvp.Key; + if (Kvp.Value) + { + //if the duplicated temporary UObject was add to root, we must remove it from the root + UserDataObject->RemoveFromRoot(); + } + UserDataObject->Rename(nullptr, Mesh, REN_DontCreateRedirectors | REN_DoNotDirty); + Mesh->AddAssetUserData(UserDataObject); + } + // Try to find the outer package so we can dirty it up if (Mesh->GetOuter()) { @@ -1432,14 +1464,20 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportStaticMesh(void* VoidFbx FbxImporter->ApplyTransformSettingsToFbxNode(FbxImporter->Scene->GetRootNode(), StaticMeshImportData); const TArray* UserData = Mesh->GetAssetUserDataArray(); - TArray UserDataCopy; + TMap UserDataCopy; if (UserData) { for (int32 Idx = 0; Idx < UserData->Num(); Idx++) { if ((*UserData)[Idx] != nullptr) { - UserDataCopy.Add((UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage())); + UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage()); + bool bAddDupToRoot = !(DupObject->IsRooted()); + if (bAddDupToRoot) + { + DupObject->AddToRoot(); + } + UserDataCopy.Add(DupObject, bAddDupToRoot); } } } @@ -1466,10 +1504,16 @@ EReimportResult::Type UReimportFbxSceneFactory::ReimportStaticMesh(void* VoidFbx Mesh->AssetImportData = StaticMeshImportData; // Copy user data to newly created mesh - for (int32 Idx = 0; Idx < UserDataCopy.Num(); Idx++) + for (auto Kvp : UserDataCopy) { - UserDataCopy[Idx]->Rename(nullptr, Mesh, REN_DontCreateRedirectors | REN_DoNotDirty); - Mesh->AddAssetUserData(UserDataCopy[Idx]); + UAssetUserData* UserDataObject = Kvp.Key; + if (Kvp.Value) + { + //if the duplicated temporary UObject was add to root, we must remove it from the root + UserDataObject->RemoveFromRoot(); + } + UserDataObject->Rename(nullptr, Mesh, REN_DontCreateRedirectors | REN_DoNotDirty); + Mesh->AddAssetUserData(UserDataObject); } if (NavCollision) diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp index d85275cd55ae..8c062c514e8c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/SFbxSceneOptionWindow.cpp @@ -1505,6 +1505,7 @@ void SFbxSceneOptionWindow::CopySkeletalMeshOptionsToFbxOptions(UnFbx::FBXImport ImportSettings->AnimationLengthImportType = SkeletalMeshOptions->AnimationLength; ImportSettings->bDeleteExistingMorphTargetCurves = SkeletalMeshOptions->bDeleteExistingMorphTargetCurves; ImportSettings->bImportCustomAttribute = SkeletalMeshOptions->bImportCustomAttribute; + ImportSettings->bDeleteExistingCustomAttributeCurves = SkeletalMeshOptions->bDeleteExistingCustomAttributeCurves; ImportSettings->bPreserveLocalTransform = SkeletalMeshOptions->bPreserveLocalTransform; ImportSettings->bResample = !SkeletalMeshOptions->bUseDefaultSampleRate; ImportSettings->ResampleRate = SkeletalMeshOptions->CustomSampleRate; @@ -1528,6 +1529,7 @@ void SFbxSceneOptionWindow::CopyFbxOptionsToSkeletalMeshOptions(UnFbx::FBXImport SkeletalMeshOptions->AnimationLength = ImportSettings->AnimationLengthImportType; SkeletalMeshOptions->bDeleteExistingMorphTargetCurves = ImportSettings->bDeleteExistingMorphTargetCurves; SkeletalMeshOptions->bImportCustomAttribute = ImportSettings->bImportCustomAttribute; + SkeletalMeshOptions->bDeleteExistingCustomAttributeCurves = ImportSettings->bDeleteExistingCustomAttributeCurves; SkeletalMeshOptions->bPreserveLocalTransform = ImportSettings->bPreserveLocalTransform; SkeletalMeshOptions->bUseDefaultSampleRate = !ImportSettings->bResample; SkeletalMeshOptions->CustomSampleRate = ImportSettings->ResampleRate; diff --git a/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h b/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h index 4c84977abde9..7863c22f2571 100644 --- a/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h +++ b/Engine/Source/Editor/UnrealEd/Private/FbxExporter.h @@ -483,6 +483,8 @@ private: public: /** Returns currently active FBX export options. Automation or UI dialog based options. */ UFbxExportOption* GetExportOptions(); + + bool bSceneGlobalTimeLineSet = false; }; diff --git a/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp index bb16f401bc86..a16527cee97c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/FbxMeshUtils.cpp @@ -255,6 +255,23 @@ namespace FbxMeshUtils // Check the file extension for FBX. Anything that isn't .FBX is rejected const FString FileExtension = FPaths::GetExtension(Filename); const bool bIsFBX = FCString::Stricmp(*FileExtension, TEXT("FBX")) == 0; + bool bSceneIsCleanUp = false; + TArray< TArray* > MeshArray; + auto CleanUpScene = [&bSceneIsCleanUp, &MeshArray, &FFbxImporter]() + { + if (bSceneIsCleanUp) + { + return; + } + bSceneIsCleanUp = true; + // Cleanup + for (int32 i = 0; i < MeshArray.Num(); i++) + { + delete MeshArray[i]; + } + FFbxImporter->ReleaseScene(); + FFbxImporter = nullptr; + }; //Skip none fbx file if (!bIsFBX) @@ -298,6 +315,26 @@ namespace FbxMeshUtils ClothingAsset->UnbindFromSkeletalMesh(SelectedSkelMesh, LODLevel); } + //Lambda to call to re-apply the clothing + auto ReapplyClothing = [&SelectedSkelMesh, &ClothingAssetsInUse, &ClothingAssetSectionIndices, &ClothingAssetInternalLodIndices, &ImportedResource, &LODLevel]() + { + // Re-apply our clothing assets + int32 NumClothingAssetsToApply = ClothingAssetsInUse.Num(); + if (ImportedResource && ImportedResource->LODModels.IsValidIndex(LODLevel)) + { + FSkeletalMeshLODModel& LodModel = ImportedResource->LODModels[LODLevel]; + for (int32 AssetIndex = 0; AssetIndex < NumClothingAssetsToApply; ++AssetIndex) + { + // Only if the same section exists + if (LodModel.Sections.IsValidIndex(ClothingAssetSectionIndices[AssetIndex])) + { + UClothingAssetBase* AssetToApply = ClothingAssetsInUse[AssetIndex]; + AssetToApply->BindToSkeletalMesh(SelectedSkelMesh, LODLevel, ClothingAssetSectionIndices[AssetIndex], ClothingAssetInternalLodIndices[AssetIndex]); + } + } + } + }; + // don't import material and animation UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); @@ -346,6 +383,7 @@ namespace FbxMeshUtils if ( !FFbxImporter->ImportFromFile( *Filename, FPaths::GetExtension( Filename ), true ) ) { + ReapplyClothing(); // Log the error message and fail the import. FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_ParseFailed", "FBX file parsing failed.")), FFbxErrors::Generic_FBXFileParseFailed); } @@ -353,7 +391,6 @@ namespace FbxMeshUtils { bool bUseLODs = true; int32 MaxLODLevel = 0; - TArray< TArray* > MeshArray; TArray LODStrings; TArray* MeshObject = NULL;; @@ -363,8 +400,9 @@ namespace FbxMeshUtils // Nothing found, error out if (MeshArray.Num() == 0) { + ReapplyClothing(); FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_NoMesh", "No meshes were found in file.")), FFbxErrors::Generic_MeshNotFound); - FFbxImporter->ReleaseScene(); + CleanUpScene(); return false; } @@ -403,6 +441,7 @@ namespace FbxMeshUtils int32 SelectedLOD = LODLevel; if (SelectedLOD > SelectedSkelMesh->GetLODNum()) { + ReapplyClothing(); // Make sure they don't manage to select a bad LOD index FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FBXImport_InvalidLODIdx", "Invalid mesh LOD index {0}, no prior LOD index exists"), FText::AsNumber(SelectedLOD))), FFbxErrors::Generic_Mesh_LOD_InvalidIndex); } @@ -458,7 +497,7 @@ namespace FbxMeshUtils } ExistingSkelMeshData* SkelMeshDataPtr = nullptr; - if (SelectedSkelMesh->GetLODNum() > LODLevel) + if (SelectedSkelMesh->GetLODNum() > SelectedLOD) { SelectedSkelMesh->PreEditChange(NULL); SkelMeshDataPtr = SaveExistingSkelMeshData(SelectedSkelMesh, true, SelectedLOD); @@ -476,61 +515,52 @@ namespace FbxMeshUtils ImportSkeletalMeshArgs.Name = NAME_None; ImportSkeletalMeshArgs.Flags = RF_Transient; ImportSkeletalMeshArgs.TemplateImportData = TempAssetImportData; - ImportSkeletalMeshArgs.LodIndex = LODLevel; + ImportSkeletalMeshArgs.LodIndex = SelectedLOD; ImportSkeletalMeshArgs.OrderedMaterialNames = OrderedMaterialNames.Num() > 0 ? &OrderedMaterialNames : nullptr; ImportSkeletalMeshArgs.ImportMaterialOriginalNameData = &ImportMaterialOriginalNameData; ImportSkeletalMeshArgs.ImportMeshSectionsData = &ImportMeshLodData[0]; ImportSkeletalMeshArgs.OutData = &OutData; TempSkelMesh = (USkeletalMesh*)FFbxImporter->ImportSkeletalMesh( ImportSkeletalMeshArgs ); - - // Add imported mesh to existing model - bool bMeshImportSuccess = false; - if( TempSkelMesh ) + // Add the new imported LOD to the existing model (check skeleton compatibility) + if( TempSkelMesh && FFbxImporter->ImportSkeletalMeshLOD(TempSkelMesh, SelectedSkelMesh, SelectedLOD, true, nullptr, TempAssetImportData)) { - bMeshImportSuccess = FFbxImporter->ImportSkeletalMeshLOD(TempSkelMesh, SelectedSkelMesh, SelectedLOD, true, nullptr, TempAssetImportData); - + TComponentReregisterContext ReregisterContext; + SelectedSkelMesh->ReleaseResources(); + SelectedSkelMesh->ReleaseResourcesFence.Wait(); + //Update the import data for this lod - UnFbx::FFbxImporter::UpdateSkeletalMeshImportData(SelectedSkelMesh, nullptr, LODLevel, &ImportMaterialOriginalNameData, &ImportMeshLodData); + UnFbx::FFbxImporter::UpdateSkeletalMeshImportData(SelectedSkelMesh, nullptr, SelectedLOD, &ImportMaterialOriginalNameData, &ImportMeshLodData); if (SkelMeshDataPtr != nullptr) { RestoreExistingSkelMeshData(SkelMeshDataPtr, SelectedSkelMesh, SelectedLOD, false, ImportOptions->bImportAsSkeletalSkinning); } - SelectedSkelMesh->PostEditChange(); - // Mark package containing skeletal mesh as dirty. - SelectedSkelMesh->MarkPackageDirty(); - // Now iterate over all skeletal mesh components re-initialising them. - for (TObjectIterator It; It; ++It) + if (ImportOptions->bImportMorph) { - USkeletalMeshComponent* SkelComp = *It; - if (SkelComp->SkeletalMesh == SelectedSkelMesh) - { - FComponentReregisterContext ReregisterContext(SkelComp); - } + FFbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, SelectedSkelMesh, SelectedSkelMesh->GetOutermost(), SelectedLOD, OutData); } - } - if(ImportOptions->bImportMorph) - { - FFbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, SelectedSkelMesh, SelectedSkelMesh->GetOutermost(), SelectedLOD, OutData); - //If we have import some morph target we have to rebuild the render resources since morph target are now using GPU - if (SelectedSkelMesh->MorphTargets.Num() > 0) - { - SelectedSkelMesh->ReleaseResources(); - //Rebuild the resources with a post edit change since we have added some morph targets - SelectedSkelMesh->PostEditChange(); - } - } - - if (bMeshImportSuccess) - { bSuccess = true; // Set LOD source filename SelectedSkelMesh->GetLODInfo(SelectedLOD)->SourceImportFilename = UAssetImportData::SanitizeImportFilename(Filename, nullptr); SelectedSkelMesh->GetLODInfo(SelectedLOD)->bImportWithBaseMesh = false; + ReapplyClothing(); + + //Must be the last step because it cleanup the fbx importer to import the alternate skinning FBX + if (bMustReimportAlternateSkinWeightProfile) + { + //We cannot use anymore the FFbxImporter after the cleanup + CleanUpScene(); + FLODUtilities::ReimportAlternateSkinWeight(SelectedSkelMesh, SelectedLOD, false); + } + + SelectedSkelMesh->PostEditChange(); + SelectedSkelMesh->InitResources(); + SelectedSkelMesh->MarkPackageDirty(); + // Notification of success FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportSuccessful", "Mesh for LOD {0} imported successfully!"), FText::AsNumber(SelectedLOD)); @@ -539,6 +569,7 @@ namespace FbxMeshUtils } else { + ReapplyClothing(); // Notification of failure FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportFail", "Failed to import mesh for LOD {0}!"), FText::AsNumber(SelectedLOD)); @@ -546,36 +577,8 @@ namespace FbxMeshUtils FSlateNotificationManager::Get().AddNotification(NotificationInfo); } } - - // Cleanup - for (int32 i=0; iReleaseScene(); - - // Re-apply our clothing assets - int32 NumClothingAssetsToApply = ClothingAssetsInUse.Num(); - if(ImportedResource && ImportedResource->LODModels.IsValidIndex(LODLevel)) - { - FSkeletalMeshLODModel& LodModel = ImportedResource->LODModels[LODLevel]; - for(int32 AssetIndex = 0; AssetIndex < NumClothingAssetsToApply; ++AssetIndex) - { - // Only if the same section exists - if(LodModel.Sections.IsValidIndex(ClothingAssetSectionIndices[AssetIndex])) - { - UClothingAssetBase* AssetToApply = ClothingAssetsInUse[AssetIndex]; - AssetToApply->BindToSkeletalMesh(SelectedSkelMesh, LODLevel, ClothingAssetSectionIndices[AssetIndex], ClothingAssetInternalLodIndices[AssetIndex]); - } - } - } - - if (bMustReimportAlternateSkinWeightProfile) - { - FLODUtilities::ReimportAlternateSkinWeight(SelectedSkelMesh, LODLevel, true); - } - + CleanUpScene(); return bSuccess; } diff --git a/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp b/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp index b98749e155b5..fab4c67ef483 100644 --- a/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/GraphEditor.cpp @@ -117,3 +117,41 @@ void SGraphEditor::NotifyPostPropertyChange( const FPropertyChangedEvent& Proper } } +void SGraphEditor::ResetAllNodesUnrelatedStates() +{ + TArray AllNodes = GetCurrentGraph()->Nodes; + + for (auto& GraphNode : AllNodes) + { + if (GraphNode->IsNodeUnrelated()) + { + GraphNode->SetNodeUnrelated(false); + } + } +} + +void SGraphEditor::FocusCommentNodes(TArray &CommentNodes, TArray &RelatedNodes) +{ + for (auto& CommentNode : CommentNodes) + { + CommentNode->SetNodeUnrelated(true); + + const FSlateRect CommentRect( + CommentNode->NodePosX, + CommentNode->NodePosY, + CommentNode->NodePosX + CommentNode->NodeWidth, + CommentNode->NodePosY + CommentNode->NodeHeight + ); + + for (auto& RelatedNode : RelatedNodes) + { + const FVector2D NodePos(RelatedNode->NodePosX, RelatedNode->NodePosY); + + if (CommentRect.ContainsPoint(NodePos)) + { + CommentNode->SetNodeUnrelated(false); + break; + } + } + } +} diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp index 8c0b7281ae73..42b6d0f169c0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp @@ -2156,6 +2156,13 @@ void FBlueprintEditorUtils::MarkBlueprintAsModified(UBlueprint* Blueprint, FProp // Clear out the cache as the user may have added or removed a latent action to a macro graph FBlueprintEditorUtils::ClearMacroCosmeticInfoCache(Blueprint); } + + IAssetEditorInstance* AssetEditor = FAssetEditorManager::Get().FindEditorForAsset(Blueprint, false); + if (AssetEditor) + { + FBlueprintEditor* BlueprintEditor = static_cast(AssetEditor); + BlueprintEditor->UpdateNodesUnrelatedStatesAfterGraphChange(); + } } bool FBlueprintEditorUtils::ShouldRegenerateBlueprint(UBlueprint* Blueprint) @@ -7809,6 +7816,7 @@ TSharedRef FBlueprintEditorUtils::ConstructBlueprintParentClassPicker( // Fill in options FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; + Options.bShowBackgroundBorder = false; TSharedPtr Filter = MakeShareable(new FBlueprintReparentFilter); Options.ClassFilter = Filter; @@ -7969,6 +7977,7 @@ TSharedRef FBlueprintEditorUtils::ConstructBlueprintInterfaceClassPicke // Fill in options FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; + Options.bShowBackgroundBorder = false; TSharedPtr Filter = MakeShareable(new FBlueprintInterfaceFilter); Options.ClassFilter = Filter; diff --git a/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp b/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp index c59a93fe915d..9294abe1a0e0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/LevelEditorViewport.cpp @@ -2305,6 +2305,17 @@ void TrimLineToFrustum(const FConvexVolume& Frustum, FVector& Start, FVector& En } } +static void GetAttachedActorsRecursive(const AActor* InActor, TArray& OutActors) +{ + TArray AttachedActors; + InActor->GetAttachedActors(AttachedActors); + for (AActor* AttachedActor : AttachedActors) + { + GetAttachedActorsRecursive(AttachedActor, OutActors); + } + OutActors.Append(AttachedActors); +}; + void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray& Actors, FViewport* InViewport, const FVector& Drag, const FRotator& Rot) { // Compile an array of selected actors @@ -2360,6 +2371,10 @@ void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray& A if (bIsOnScreen) { + TArray IgnoreActors; + IgnoreActors.Append(Actors); // Add the whole list of actors so you can't hit the moving set with the ray + GetAttachedActorsRecursive(Actor, IgnoreActors); + // Determine how we're going to attempt to project the object onto the world if (CurrentAxis == EAxisList::XY || CurrentAxis == EAxisList::XZ || CurrentAxis == EAxisList::YZ) { @@ -2378,11 +2393,11 @@ void FLevelEditorViewportClient::ProjectActorsIntoWorld(const TArray& A TrimLineToFrustum(Frustum, RayStart, RayEnd); - TraceResult = FActorPositioning::TraceWorldForPosition(*GetWorld(), *SceneView, RayStart, RayEnd, &Actors); + TraceResult = FActorPositioning::TraceWorldForPosition(*GetWorld(), *SceneView, RayStart, RayEnd, &IgnoreActors); } else { - TraceResult = FActorPositioning::TraceWorldForPosition(Cursor, *SceneView, &Actors); + TraceResult = FActorPositioning::TraceWorldForPosition(Cursor, *SceneView, &IgnoreActors); } } @@ -3334,8 +3349,26 @@ void FLevelEditorViewportClient::MoveLockedActorToCamera() } } - ActiveActorLock->SetActorLocation(GCurrentLevelEditingViewportClient->GetViewLocation(), false); - ActiveActorLock->SetActorRotation(GCurrentLevelEditingViewportClient->GetViewRotation()); + // Need to disable orbit camera before setting actor position so that the viewport camera location is converted back + GCurrentLevelEditingViewportClient->ToggleOrbitCamera(false); + + // If we're locked to a camera then we're reflecting the camera view and not the actor position. We need to reflect that delta when we reposition the piloted actor + if (bUseControllingActorViewInfo) + { + const USceneComponent* ViewComponent = Cast(FindViewComponentForActor(ActiveActorLock)); + if (ViewComponent != nullptr) + { + const FTransform RelativeTransform = ViewComponent->GetComponentTransform().Inverse(); + const FTransform DesiredTransform = FTransform(GCurrentLevelEditingViewportClient->GetViewRotation(), GCurrentLevelEditingViewportClient->GetViewLocation()); + + ActiveActorLock->SetActorTransform(ActiveActorLock->GetActorTransform() * RelativeTransform * DesiredTransform); + } + } + else + { + ActiveActorLock->SetActorLocation(GCurrentLevelEditingViewportClient->GetViewLocation(), false); + ActiveActorLock->SetActorRotation(GCurrentLevelEditingViewportClient->GetViewRotation()); + } } if (ABrush* Brush = Cast(ActiveActorLock)) diff --git a/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp b/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp index 0688746d702b..f3cc0a284b0b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/LevelViewportClickHandlers.cpp @@ -140,7 +140,7 @@ namespace ClickHandlers GEditor->GetSelectedActors()->Modify(); - if( bAllowSelectionChange ) + if( bAllowSelectionChange && GEditor->CanSelectActor(Actor, true, true) ) { // If the actor the user clicked on was already selected, then we won't bother clearing the selection if( !Actor->IsSelected() ) @@ -173,7 +173,7 @@ namespace ClickHandlers GEditor->GetSelectedActors()->Modify(); - if( bAllowSelectionChange ) + if( bAllowSelectionChange && GEditor->CanSelectActor(Actor, true, true) ) { // Clear the selection GEditor->SelectNone( false, true ); @@ -229,7 +229,7 @@ namespace ClickHandlers } else if ( Actor ) { - if( bAllowSelectionChange ) + if( bAllowSelectionChange && GEditor->CanSelectActor(Actor, true, true, true) ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "ClickingOnActors", "Clicking on Actors") ); GEditor->GetSelectedActors()->Modify(); diff --git a/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp b/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp index 0f266873f9c7..5e70fdf1252c 100644 --- a/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/MaterialGraphNode.cpp @@ -553,7 +553,7 @@ void UMaterialGraphNode::CreateInputPins() { // Makes sure pin has a name for lookup purposes but user will never see it NewPin->PinName = CreateUniquePinName(TEXT("Input")); - NewPin->PinFriendlyName = FText::FromString(TEXT(" ")); + NewPin->PinFriendlyName = FText::GetEmpty(); } } } @@ -607,7 +607,7 @@ void UMaterialGraphNode::CreateOutputPins() { // Makes sure pin has a name for lookup purposes but user will never see it NewPin->PinName = CreateUniquePinName(TEXT("Output")); - NewPin->PinFriendlyName = FText::FromString(TEXT(" ")); + NewPin->PinFriendlyName = FText::GetEmpty(); } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp b/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp index 78220e9eb359..e7980db49e63 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ObjectTools.cpp @@ -2019,10 +2019,37 @@ namespace ObjectTools bool ContainsWorldInUse(const TArray< UObject* >& ObjectsToDelete) { - TArray ActiveWorlds; + TArray WorldsToDelete; + + for (const UObject* ObjectToDelete : ObjectsToDelete) + { + if (const UWorld* World = Cast(ObjectToDelete)) + { + WorldsToDelete.AddUnique(World); + } + } + + if (WorldsToDelete.Num() == 0) + { + return false; + } + + auto GetCombinedWorldNames = [](const TArray& Worlds) -> FString + { + return FString::JoinBy(Worlds, TEXT(", "), + [](const UWorld* World) -> FString + { + return World->GetPathName(); + }); + }; + + UE_LOG(LogObjectTools, Log, TEXT("Deleting %d worlds: %s"), WorldsToDelete.Num(), *GetCombinedWorldNames(WorldsToDelete)); + + TArray ActiveWorlds; + for (const FWorldContext& WorldContext : GEditor->GetWorldContexts()) { - if (UWorld* World = WorldContext.World()) + if (const UWorld* World = WorldContext.World()) { ActiveWorlds.AddUnique(World); @@ -2030,20 +2057,22 @@ namespace ObjectTools { if (StreamingLevel && StreamingLevel->GetLoadedLevel() && StreamingLevel->GetLoadedLevel()->GetOuter()) { - ActiveWorlds.AddUnique(StreamingLevel->GetLoadedLevel()->GetOuter()); + if (const UWorld* StreamingWorld = Cast(StreamingLevel->GetLoadedLevel()->GetOuter())) + { + ActiveWorlds.AddUnique(StreamingWorld); + } } } } } - for (const UObject* ObjectToDelete : ObjectsToDelete) + UE_LOG(LogObjectTools, Log, TEXT("Currently %d active worlds: %s"), ActiveWorlds.Num(), *GetCombinedWorldNames(ActiveWorlds)); + + for (const UWorld* World : WorldsToDelete) { - if (const UWorld* World = Cast(ObjectToDelete)) + if (ActiveWorlds.Contains(World)) { - if (ActiveWorlds.Contains(World)) - { - return true; - } + return true; } } @@ -2341,11 +2370,25 @@ namespace ObjectTools return 0; } - // Close all editors to avoid changing references to temporary objects used by the editor - if ( !FAssetEditorManager::Get().CloseAllAssetEditors() ) + // Attempt to close all editors referencing this asset. + bool bClosedAllEditors = true; + + for (UObject* ObjectToDelete : InObjectsToDelete) + { + const TArray ObjectEditors = FAssetEditorManager::Get().FindEditorsForAsset(ObjectToDelete); + for (IAssetEditorInstance* ObjectEditorInstance : ObjectEditors) + { + if (!ObjectEditorInstance->CloseWindow()) + { + bClosedAllEditors = false; + } + } + } + + // Failed to close at least one editor. It is possible that this editor has in-memory object references + // which are not prepared to be changed dynamically so it is not safe to continue + if (!bClosedAllEditors) { - // Failed to close at least one editor. It is possible that this editor has in-memory object references - // which are not prepared to be changed dynamically so it is not safe to continue return 0; } diff --git a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp index 4f3938e88f6d..233e6c41721f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp @@ -91,6 +91,8 @@ #include "Engine/LocalPlayer.h" #include "Slate/SGameLayerManager.h" #include "HAL/PlatformApplicationMisc.h" +#include "Widgets/Input/SHyperlink.h" +#include "Dialogs/CustomDialog.h" #include "IHeadMountedDisplay.h" #include "IXRTrackingSystem.h" @@ -104,6 +106,8 @@ #include "EditorModeRegistry.h" #include "PhysicsManipulationMode.h" #include "CookerSettings.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/SBoxPanel.h" DEFINE_LOG_CATEGORY_STATIC(LogPlayLevel, Log, All); @@ -2270,6 +2274,62 @@ bool UEditorEngine::SpawnPlayFromHereStart( UWorld* World, AActor*& PlayerStart, return true; } +static bool ShowBlueprintErrorDialog( TArray ErroredBlueprints ) +{ + struct Local + { + static void OnHyperlinkClicked( TWeakObjectPtr InBlueprint ) + { + if (UBlueprint* BlueprintToEdit = InBlueprint.Get()) + { + // Open the blueprint + GEditor->EditObject( BlueprintToEdit ); + } + } + }; + + TSharedRef DialogContents = SNew( SVerticalBox ) + + SVerticalBox::Slot() + [ + SNew( STextBlock ) + .Text( NSLOCTEXT( "PlayInEditor", "PrePIE_BlueprintErrors", "One or more blueprints has an unresolved compiler error, are you sure you want to Play in Editor?" ) ) + ] + + SVerticalBox::Slot() + [ + SNew( STextBlock ) + ]; + + for (UBlueprint* Blueprint : ErroredBlueprints) + { + TWeakObjectPtr BlueprintPtr = Blueprint; + + DialogContents->AddSlot() + .AutoHeight() + .HAlign(HAlign_Left) + [ + SNew(SHyperlink) + .Style(FEditorStyle::Get(), "Common.GotoBlueprintHyperlink") + .OnNavigate_Static(&Local::OnHyperlinkClicked, BlueprintPtr) + .Text(FText::FromString(Blueprint->GetName())) + .ToolTipText(NSLOCTEXT("SourceHyperlink", "EditBlueprint_ToolTip", "Click to edit the blueprint")) + ]; + } + + FText DialogTitle = NSLOCTEXT("PlayInEditor", "PrePIE_BlueprintErrorsTitle", "Blueprint Compilation Errors"); + + FText OKText = NSLOCTEXT("PlayInEditor", "PrePIE_OkText", "Play in Editor"); + FText CancelText = NSLOCTEXT("Dialogs", "EAppReturnTypeCancel", "Cancel"); + + TSharedRef CustomDialog = SNew(SCustomDialog) + .Title(DialogTitle) + .IconBrush("NotificationList.DefaultMessage") + .DialogContent(DialogContents) + .Buttons( { SCustomDialog::FButton(OKText), SCustomDialog::FButton(CancelText) } ); + + int ButtonPressed = CustomDialog->ShowModal(); + return ButtonPressed == 0; +} + void UEditorEngine::PlayInEditor( UWorld* InWorld, bool bInSimulateInEditor, FPlayInEditorOverrides Overrides ) { // Broadcast PreBeginPIE before checks that might block PIE below (BeginPIE is broadcast below after the checks) @@ -2357,17 +2417,9 @@ void UEditorEngine::PlayInEditor( UWorld* InWorld, bool bInSimulateInEditor, FPl if (ErroredBlueprints.Num() && !GIsDemoMode) { - FString ErroredBlueprintList; - for (UBlueprint* Blueprint : ErroredBlueprints) - { - ErroredBlueprintList += FString::Printf(TEXT("\n %s"), *Blueprint->GetName()); - } - - FFormatNamedArguments Args; - Args.Add(TEXT("ErrorBlueprints"), FText::FromString(ErroredBlueprintList)); - // There was at least one blueprint with an error, make sure the user is OK with that. - const bool bContinuePIE = EAppReturnType::Yes == FMessageDialog::Open( EAppMsgType::YesNo, FText::Format( NSLOCTEXT("PlayInEditor", "PrePIE_BlueprintErrors", "One or more blueprints has an unresolved compiler error, are you sure you want to Play in Editor?{ErrorBlueprints}"), Args ) ); + bool bContinuePIE = ShowBlueprintErrorDialog( ErroredBlueprints ); + if ( !bContinuePIE ) { FMessageLog("BlueprintLog").Open(EMessageSeverity::Warning); @@ -3311,8 +3363,12 @@ UGameInstance* UEditorEngine::CreatePIEGameInstance(int32 InPIEInstance, bool bI if (index <= 0) { - LevelEditorPlaySettings->NewWindowPosition.X = FPlatformMath::RoundToInt(PIEWindowPos.X); - LevelEditorPlaySettings->NewWindowPosition.Y = FPlatformMath::RoundToInt(PIEWindowPos.Y); + // only override the window position if the window isn't being centered + if (!LevelEditorPlaySettings->CenterNewWindow) + { + LevelEditorPlaySettings->NewWindowPosition.X = FPlatformMath::RoundToInt(PIEWindowPos.X); + LevelEditorPlaySettings->NewWindowPosition.Y = FPlatformMath::RoundToInt(PIEWindowPos.Y); + } } else { @@ -3364,9 +3420,6 @@ UGameInstance* UEditorEngine::CreatePIEGameInstance(int32 InPIEInstance, bool bI // Mark the viewport as PIE viewport ViewportClient->Viewport->SetPlayInEditorViewport( ViewportClient->bIsPlayInEditorViewport ); - // Ensure the window has a valid size before calling BeginPlay - PieWindow->ReshapeWindow(PieWindow->GetPositionInScreen(), FVector2D(NewWindowWidth, NewWindowHeight)); - // Change the system resolution to match our window, to make sure game and slate window are kept syncronised FSystemResolution::RequestResolutionChange(NewWindowWidth, NewWindowHeight, EWindowMode::Windowed); diff --git a/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp b/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp index 4e4ad8caaf60..3eb516050ed1 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PreferenceStubs.cpp @@ -5,7 +5,9 @@ #include "Preferences/CascadeOptions.h" #include "Preferences/CurveEdOptions.h" #include "Preferences/MaterialEditorOptions.h" +#include "Preferences/BlueprintEditorOptions.h" #include "Preferences/PersonaOptions.h" +#include "Preferences/AnimationBlueprintEditorOptions.h" #include "Preferences/PhysicsAssetEditorOptions.h" #include "Preferences/MaterialStatsOptions.h" @@ -70,6 +72,15 @@ UMaterialStatsOptions::UMaterialStatsOptions(const FObjectInitializer& ObjectIni bMaterialQualityUsed[EMaterialQualityLevel::High] = 1; } +UBlueprintEditorOptions::UBlueprintEditorOptions(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UAnimationBlueprintEditorOptions::UAnimationBlueprintEditorOptions(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} UCurveEdOptions::UCurveEdOptions(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) diff --git a/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp b/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp index 95a02c60dfd4..a233660ac86b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SEditorViewportToolBarButton.cpp @@ -18,7 +18,7 @@ void SEditorViewportToolBarButton::Construct( const FArguments& Declaration ) bool bContentOverride = ContentSlotWidget != SNullWidget::NullWidget; - EUserInterfaceActionType::Type ButtonType = Declaration._ButtonType; + EUserInterfaceActionType ButtonType = Declaration._ButtonType; // The style of the image to show in the button const FName ImageStyleName = Declaration._Image.Get(); diff --git a/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp b/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp index 677d02b27e25..d35c46030fea 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SSocketManager.cpp @@ -405,7 +405,7 @@ void SSocketManager::CreateSocket() NewSocket->OnPropertyChanged().AddSP( this, &SSocketManager::OnSocketPropertyChanged ); CurrentStaticMesh->PreEditChange(NULL); - CurrentStaticMesh->Sockets.Add(NewSocket); + CurrentStaticMesh->AddSocket(NewSocket); CurrentStaticMesh->PostEditChange(); CurrentStaticMesh->MarkPackageDirty(); @@ -437,7 +437,7 @@ void SSocketManager::DuplicateSelectedSocket() // Add the new socket to the static mesh CurrentStaticMesh->PreEditChange(NULL); - CurrentStaticMesh->Sockets.Add(NewSocket); + CurrentStaticMesh->AddSocket(NewSocket); CurrentStaticMesh->PostEditChange(); CurrentStaticMesh->MarkPackageDirty(); diff --git a/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h b/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h index 1706b0671320..fbab30cc1bf2 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h +++ b/Engine/Source/Editor/UnrealEd/Private/Settings/LevelEditorPlaySettingsCustomization.h @@ -187,9 +187,9 @@ public: [ SNew(STextBlock) .Font(LayoutBuilder->GetDetailFont()) - .Text(LOCTEXT("CommonResolutionsButtonText", "Common Window Sizes")) + .Text(LOCTEXT("CommonResolutionsButtonText", "Common Resolutions")) ] - .ContentPadding(FMargin(6.0f, 2.0f)) + .ContentPadding(FMargin(6,2)) .MenuContent() [ MakeCommonResolutionsMenu() @@ -197,12 +197,13 @@ public: .ToolTipText(LOCTEXT("CommonResolutionsButtonTooltip", "Pick from a list of common screen resolutions")) ] + SHorizontalBox::Slot() + .Padding(0,0,6,0) .AutoWidth() .VAlign(VAlign_Center) [ SNew(SButton) .OnClicked(this, &SScreenResolutionCustomization::HandleSwapAspectRatioClicked) - .ContentPadding(FMargin(3.0f, 0.0f, 3.0f, 1.0f)) + .ContentPadding(FMargin(3,0,3,1)) .Content() [ SNew(SImage) @@ -221,7 +222,7 @@ public: + SVerticalBox::Slot() .AutoHeight() [ - WindowWidthProperty->CreatePropertyNameWidget(LOCTEXT("WindowWidthLabel", "Window Width")) + WindowWidthProperty->CreatePropertyNameWidget(LOCTEXT("ViewportWidthLabel", "Viewport Width")) ] + SVerticalBox::Slot() [ @@ -229,13 +230,13 @@ public: ] ] + SHorizontalBox::Slot() - .Padding(8.0f, 0.0f, 0.0f, 0.0f) + .Padding(8,0,0,0) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ - WindowHeightProperty->CreatePropertyNameWidget(LOCTEXT("WindowHeightLabel", "Window Height")) + WindowHeightProperty->CreatePropertyNameWidget(LOCTEXT("ViewportHeightLabel", "Viewport Height")) ] + SVerticalBox::Slot() .AutoHeight() @@ -526,7 +527,7 @@ public: } IDetailCategoryBuilder& GameViewportSettings = LayoutBuilder.EditCategory("GameViewportSettings"); { - // new window size + // new window resolution TSharedRef WindowHeightHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowHeight)); TSharedRef WindowWidthHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowWidth)); TSharedRef WindowPositionHandle = LayoutBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, NewWindowPosition)); @@ -539,12 +540,12 @@ public: CenterNewWindowHandle->MarkHiddenByCustomization(); EmulatedDeviceHandle->MarkHiddenByCustomization(); - GameViewportSettings.AddCustomRow(LOCTEXT("NewWindowSizeRow", "New Window Size"), false) + GameViewportSettings.AddCustomRow(LOCTEXT("NewViewportResolutionRow", "New Viewport Resolution"), false) .NameContent() [ SNew(STextBlock) .Font(LayoutBuilder.GetDetailFont()) - .Text(LOCTEXT("NewWindowSizeName", "New Window Size")) + .Text(LOCTEXT("NewViewportResolutionName", "New Viewport Resolution")) .ToolTipText(LOCTEXT("NewWindowSizeTooltip", "Sets the width and height of floating PIE windows (in pixels)")) ] .ValueContent() @@ -553,7 +554,7 @@ public: SNew(SScreenResolutionCustomization, &LayoutBuilder, WindowHeightHandle, WindowWidthHandle) ]; - GameViewportSettings.AddCustomRow(LOCTEXT("NewWindowPositionRow", "New Window Position"), false) + GameViewportSettings.AddCustomRow(LOCTEXT("NewWindowPositionRow", "New Window Position"), false) .NameContent() [ SNew(STextBlock) @@ -567,19 +568,19 @@ public: SNew(SScreenPositionCustomization, &LayoutBuilder, WindowPositionHandle, CenterNewWindowHandle) ]; - GameViewportSettings.AddCustomRow(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview"), false) - .NameContent() - [ - SNew(STextBlock) - .Font(LayoutBuilder.GetDetailFont()) - .Text(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview")) - ] - .ValueContent() - [ - SNew(STextBlock) - .Font(LayoutBuilder.GetDetailFont()) - .Text(this, &FLevelEditorPlaySettingsCustomization::GetPreviewText) - ]; + GameViewportSettings.AddCustomRow(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview"), false) + .NameContent() + [ + SNew(STextBlock) + .Font(LayoutBuilder.GetDetailFont()) + .Text(LOCTEXT("SafeZonePreviewName", "Safe Zone Preview")) + ] + .ValueContent() + [ + SNew(STextBlock) + .Font(LayoutBuilder.GetDetailFont()) + .Text(this, &FLevelEditorPlaySettingsCustomization::GetPreviewText) + ]; } // play in new window settings @@ -699,10 +700,10 @@ public: .DisplayName(LOCTEXT("AdditionalLaunchOptionsLabel", "Command Line Arguments")) .Visibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FLevelEditorPlaySettingsCustomization::HandleCmdLineVisibility))); - NetworkCategory.AddCustomRow(LOCTEXT("PlayInNetworkWindowDetails", "Multiplayer Window Size"), false) + NetworkCategory.AddCustomRow(LOCTEXT("PlayInNetworkViewportSize", "Multiplayer Viewport Size"), false) .NameContent() [ - WindowHeightHandle->CreatePropertyNameWidget(LOCTEXT("ClientWindowSizeName", "Multiplayer Window Size (in pixels)"), LOCTEXT("ClientWindowSizeTooltip", "Width and Height to use when spawning additional windows.")) + WindowHeightHandle->CreatePropertyNameWidget(LOCTEXT("ClientViewportSizeName", "Multiplayer Viewport Size (in pixels)"), LOCTEXT("ClientWindowSizeTooltip", "Width and Height to use when spawning additional windows.")) ] .ValueContent() .MaxDesiredWidth(MaxPropertyWidth) diff --git a/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp b/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp index 41c9f8ee0f79..e71ceebebd9f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Settings/SettingsClasses.cpp @@ -348,6 +348,7 @@ ULevelEditorMiscSettings::ULevelEditorMiscSettings( const FObjectInitializer& Ob bPromptWhenAddingToLevelBeforeCheckout = true; bPromptWhenAddingToLevelOutsideBounds = true; PercentageThresholdForPrompt = 20.0f; + MinimumBoundsForCheckingSize = FVector(500.0f, 500.0f, 50.0f); bCreateNewAudioDeviceForPlayInEditor = true; } diff --git a/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp b/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp index 85d171b2b5d9..b7a7af449e15 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SkeletalMeshEdit.cpp @@ -1317,22 +1317,26 @@ bool UnFbx::FFbxImporter::ImportAnimation(USkeleton* Skeleton, UAnimSequence * D USkeleton* MySkeleton = DestSeq->GetSkeleton(); check(MySkeleton); - if (ImportOptions->bDeleteExistingMorphTargetCurves) + if (ImportOptions->bDeleteExistingMorphTargetCurves || ImportOptions->bDeleteExistingCustomAttributeCurves) { for (int32 CurveIdx=0; CurveIdxRawCurveData.FloatCurves.Num(); ++CurveIdx) { auto& Curve = DestSeq->RawCurveData.FloatCurves[CurveIdx]; const FCurveMetaData* MetaData = MySkeleton->GetCurveMetaData(Curve.Name); - if (MetaData && MetaData->Type.bMorphtarget) + if (MetaData) { - DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); - --CurveIdx; + bool bDeleteCurve = MetaData->Type.bMorphtarget ? ImportOptions->bDeleteExistingMorphTargetCurves : ImportOptions->bDeleteExistingCustomAttributeCurves; + if (bDeleteCurve) + { + DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); + --CurveIdx; + } } } - DestSeq->RawCurveData.FloatCurves.Shrink(); } + // Store float curve tracks which use to exist on the animation TArray ExistingCurveNames; for (int32 CurveIdx = 0; CurveIdx < DestSeq->RawCurveData.FloatCurves.Num(); ++CurveIdx) diff --git a/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp b/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp index 26b38f8a1e0c..4ea9ecc49a47 100644 --- a/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/StaticMeshEdit.cpp @@ -1514,8 +1514,9 @@ void RestoreExistingMeshData(ExistingStaticMeshData* ExistingMeshDataPtr, UStati continue; } + //When re-importing the asset, do not touch the LOD that was imported from file, the material array is keep intact so the section should still be valid. - bool NoRemapForThisLOD = LodLevel == INDEX_NONE && i != 0 && !IsReductionActive(NewMesh->SourceModels[i].ReductionSettings); + bool NoRemapForThisLOD = LodLevel == INDEX_NONE && i != 0 && !NewMesh->SourceModels[i].bImportWithBaseMesh && !IsReductionActive(NewMesh->SourceModels[i].ReductionSettings); FStaticMeshLODResources& LOD = NewMesh->RenderData->LODResources[i]; @@ -1609,7 +1610,7 @@ void RestoreExistingMeshData(ExistingStaticMeshData* ExistingMeshDataPtr, UStati UStaticMeshSocket* Socket = NewMesh->FindSocket(ExistingSocket->SocketName); if (!Socket && !ExistingSocket->bSocketCreatedAtImport) { - NewMesh->Sockets.Add(ExistingSocket); + NewMesh->AddSocket(ExistingSocket); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h b/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h index 3b4bac788a01..4a184d76666b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h +++ b/Engine/Source/Editor/UnrealEd/Private/UnrealEdPrivatePCH.h @@ -392,7 +392,6 @@ #include "Framework/Commands/InputBindingManager.h" #include "Framework/Commands/Commands.h" #include "Framework/Text/TextLayout.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h b/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h index b6897a1dc1fc..0d4fabd9bd24 100644 --- a/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h +++ b/Engine/Source/Editor/UnrealEd/Public/DataTableEditorUtils.h @@ -73,9 +73,10 @@ struct UNREALED_API FDataTableEditorUtils static bool RemoveRow(UDataTable* DataTable, FName Name); static uint8* AddRow(UDataTable* DataTable, FName RowName); + static uint8* DuplicateRow(UDataTable* DataTable, FName SourceRowName, FName RowName); static bool RenameRow(UDataTable* DataTable, FName OldName, FName NewName); static bool MoveRow(UDataTable* DataTable, FName RowName, ERowMoveDirection Direction, int32 NumRowsToMoveBy = 1); - static bool SelectRow(UDataTable* DataTable, FName RowName); + static bool SelectRow(const UDataTable* DataTable, FName RowName); static bool DiffersFromDefault(UDataTable* DataTable, FName RowName); static bool ResetToDefault(UDataTable* DataTable, FName RowName); diff --git a/Engine/Source/Editor/UnrealEd/Public/Dialogs/CustomDialog.h b/Engine/Source/Editor/UnrealEd/Public/Dialogs/CustomDialog.h new file mode 100644 index 000000000000..04cd6deefa23 --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Public/Dialogs/CustomDialog.h @@ -0,0 +1,77 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/SWindow.h" + +/** This is a custom dialog class, which allows any Slate widget to be used as the contents, + * with any number of buttons that have any text. + * It also supports adding a custom icon to the dialog. + * Usage: + * TSharedRef HelloWorldDialog = SNew(SCustomDialog) + .Title(FText("Hello, World!")) + .DialogContent( SNew(SImage).Image(FName("Hello")) ) + .Buttons( { SCustomDialog::FButton(LOCTEXT("OK", "OK"), []() { printf("OK pressed!" ); } ), + SCustomDialog::FButton(LOCTEXT("Cancel", "Cancel") } ); + + // returns 0 when OK is pressed, 1 when Cancel is pressed + int ButtonPressed = CustomDialog->ShowModal(); + */ +class SCustomDialog : public SWindow +{ +public: + struct FButton + { + FButton(const FText& InButtonText, const FSimpleDelegate& InOnClicked = FSimpleDelegate()) + : ButtonText(InButtonText), + OnClicked(InOnClicked) + { + } + + FText ButtonText; + FSimpleDelegate OnClicked; + }; + + SLATE_BEGIN_ARGS(SCustomDialog) + : _UseScrollBox(true) + , _ScrollBoxMaxHeight(300) + { + } + /** Title to display for the dialog. */ + SLATE_ARGUMENT(FText, Title) + + /** Optional icon to display in the dialog. (default: none) */ + SLATE_ARGUMENT(FName, IconBrush) + + /** Should this dialog use a scroll box for over-sized content? (default: true) */ + SLATE_ARGUMENT(bool, UseScrollBox) + + /** Max height for the scroll box (default: 300) */ + SLATE_ARGUMENT(int32, ScrollBoxMaxHeight) + + /** The buttons that this dialog should have. One or more buttons must be added.*/ + SLATE_ARGUMENT(TArray, Buttons) + + /** Content for the dialog */ + SLATE_ARGUMENT(TSharedPtr, DialogContent) + + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + + /** Show the dialog. + * This method will return immediately. + */ + void Show(); + + /** Show a modal dialog. Will block until an input is received. + * Returns the index of the button that was pressed. + */ + int ShowModal(); + +private: + FReply OnButtonClicked(FSimpleDelegate OnClicked, int ButtonIndex); + + /** The index of the button that was pressed last. */ + int LastPressedButton = -1; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/UnrealEd/Public/EdMode.h b/Engine/Source/Editor/UnrealEd/Public/EdMode.h index 80f053b20592..03cc7eb6569f 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EdMode.h +++ b/Engine/Source/Editor/UnrealEd/Public/EdMode.h @@ -278,12 +278,22 @@ public: /** True if this mode uses a toolkit mode (eventually they all should) */ virtual bool UsesToolkits() const; + /** Gets the toolkit created by this mode */ + TSharedPtr GetToolkit() { return Toolkit; } + /** Returns the world this toolkit is editing */ UWorld* GetWorld() const; /** Returns the owning mode manager for this mode */ class FEditorModeTools* GetModeManager() const; + /** + * Called when the editor mode should rebuild its toolbar + * + * @param ToolbarBuilder The builder which should be used to add toolbar widgets + */ + virtual void BuildModeToolbar(class FToolBarBuilder& ToolbarBuilder) {} + // Property Widgets /** diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h b/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h index b83d2772564b..7d8893b00a07 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorModeManager.h @@ -81,11 +81,24 @@ public: */ void DestroyMode(FEditorModeID InID); + /** + * Creates the mode toolbar tab if needed + */ + TSharedRef MakeModeToolbarTab(); + + /** + * Whether or not the mode toolbar should be shown. If any active modes generated a toolbar this method will return true + */ + bool ShouldShowModeToolbar(); + protected: /** Deactivates the editor mode at the specified index */ void DeactivateModeAtIndex( int32 InIndex ); +private: + void RebuildModeToolBar(); + void SpawnOrUpdateModeToolbar(); public: /** @@ -420,6 +433,9 @@ public: /** Is the viewport UI hidden? */ bool IsViewportUIHidden() const { return bHideViewportUI; } + /** The toolbar tab name that should be used as the tab identifier */ + static const FName EditorModeToolbarTabName; + bool PivotShown; bool Snapping; bool SnappedActor; @@ -482,7 +498,7 @@ protected: TArray DefaultModeIDs; /** A list of active editor modes. */ - TArray< TSharedPtr > Modes; + TArray< TSharedPtr > ActiveModes; /** The host of the toolkits created by these modes */ TWeakPtr ToolkitHost; @@ -505,6 +521,18 @@ protected: /** if true the current selection has a scene component */ bool bSelectionHasSceneComponent; private: + struct FEdModeToolbarRow + { + FEdModeToolbarRow(TSharedPtr& InMode, TSharedRef& InToolbarWidget) + : Mode(InMode) + , ToolbarWidget(InToolbarWidget) + {} + TSharedPtr Mode; + TSharedPtr ToolbarWidget; + }; + + /** All toolbar rows generated by active modes. There will be one row per active mode that generates a toolbar */ + TArray ActiveToolBarRows; /** The coordinate system the widget is operating within. */ ECoordSystem CoordSystem; @@ -515,6 +543,12 @@ private: /** Multicast delegate that is broadcast when a widget mode is changed */ FWidgetModeChangedEvent WidgetModeChangedEvent; + /** The dock tab for any modes that generate a toolbar */ + TWeakPtr ModeToolbarTab; + + /** The actual toolbar rows will be placed in this vertical box */ + TWeakPtr ModeToolbarBox; + /** Flag set between calls to StartTracking() and EndTracking() */ bool bIsTracking; }; diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h b/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h index 0a2e9ad3c6e0..c9db8e67f345 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorModeRegistry.h @@ -52,6 +52,9 @@ struct FEditorModeInfo /** The mode ID */ FEditorModeID ID; + /** Name of the toolbar this mode uses and can be used by external systems to customize that mode toolbar */ + FName ToolbarCustomizationName; + /** Name for the editor to display */ FText Name; diff --git a/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h b/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h index 24ac90c38277..f7f37611e8f1 100644 --- a/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h +++ b/Engine/Source/Editor/UnrealEd/Public/EditorReimportHandler.h @@ -10,7 +10,7 @@ class FReimportHandler; /** Reimport manager for package resources with associated source files on disk. */ -class FReimportManager : FGCObject +class UNREALED_VTABLE FReimportManager : FGCObject { public: /** @@ -166,7 +166,7 @@ namespace EReimportResult /** * Reimport handler for package resources with associated source files on disk. */ -class FReimportHandler +class UNREALED_VTABLE FReimportHandler { public: /** Constructor. Add self to manager */ diff --git a/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h b/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h index 3e2ba54af616..1131b66280e6 100644 --- a/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h +++ b/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h @@ -166,6 +166,8 @@ struct FBXImportOptions FString BaseEmmisiveTextureName; FString BaseSpecularTextureName; EMaterialSearchLocation MaterialSearchLocation; + //If true the materials will be reorder to follow the fbx order + bool bReorderMaterialToFbxOrder; // Skeletal Mesh options bool bImportMorph; bool bImportAnimations; @@ -188,6 +190,7 @@ struct FBXImportOptions bool bPreserveLocalTransform; bool bDeleteExistingMorphTargetCurves; bool bImportCustomAttribute; + bool bDeleteExistingCustomAttributeCurves; bool bImportBoneTracks; bool bSetMaterialDriveParameterOnCustomAttribute; bool bRemoveRedundantKeys; @@ -1054,10 +1057,6 @@ public: template static void ShowFbxMaterialConflictWindow(const TArray& InSourceMaterials, const TArray& InResultMaterials, TArray& RemapMaterials, TArray& FuzzyRemapMaterials, EFBXReimportDialogReturnOption& OutReturnOption, bool bIsPreviewConflict = false); - - /** helper function **/ - UNREALED_API static void DumpFBXNode(FbxNode* Node); - /** * Apply asset import settings for transform to an FBX node * @@ -1248,7 +1247,8 @@ protected: { NOTSTARTED, FILEOPENED, - IMPORTED + IMPORTED, + FIXEDANDCONVERTED, }; static TSharedPtr StaticInstance; diff --git a/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h b/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h index 7c6dafd829de..61398f0d4149 100644 --- a/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/GraphEditor.h @@ -461,6 +461,11 @@ public: Implementation->SetNodeFactory(NewNodeFactory); } } + + /** Common methods for MaterialEditor and BlueprintEditor's focusing related nodes feature */ + UNREALED_API void ResetAllNodesUnrelatedStates(); + + UNREALED_API void FocusCommentNodes(TArray &CommentNodes, TArray &RelatedNodes); virtual void OnAlignTop() { diff --git a/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h b/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h index 40a1f5184701..c47b03ebc236 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/SColorGradientEditor.h @@ -17,7 +17,7 @@ class FCurveOwnerInterface; class FPaintArgs; class FSlateWindowElementList; -struct FGradientStopMark +struct UNREALED_API FGradientStopMark { public: diff --git a/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h b/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h index 9e5d9c2590f3..73fe09826346 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h +++ b/Engine/Source/Editor/UnrealEd/Public/SCurveEditor.h @@ -128,7 +128,7 @@ DECLARE_DELEGATE_TwoParams( FOnSetInputViewRange, float, float ) DECLARE_DELEGATE_TwoParams( FOnSetOutputViewRange, float, float ) DECLARE_DELEGATE_OneParam( FOnSetAreCurvesVisible, bool ) -class SCurveEditor : +class UNREALED_VTABLE SCurveEditor : public SCompoundWidget, public FGCObject, public FEditorUndoClient diff --git a/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h b/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h index d8e76e782a42..b06e18443eee 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h +++ b/Engine/Source/Editor/UnrealEd/Public/SEditorViewportToolBarButton.h @@ -27,7 +27,7 @@ public: /** Called when the button is clicked */ SLATE_EVENT( FOnClicked, OnClicked ) /** The button type to use */ - SLATE_ARGUMENT( EUserInterfaceActionType::Type, ButtonType ) + SLATE_ARGUMENT( EUserInterfaceActionType, ButtonType ) /** Checked state of the button */ SLATE_ATTRIBUTE( bool, IsChecked ) /** Style name of an image to use. Simple two state images are supported. An image can be different depending on checked/unchecked state */ diff --git a/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h b/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h index c472a381d925..8e59251a680e 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h +++ b/Engine/Source/Editor/UnrealEd/Public/SScalabilitySettings.h @@ -15,7 +15,7 @@ enum class ECheckBoxState : uint8; * Scalability settings configuration widget **/ -class SScalabilitySettings : public SCompoundWidget +class UNREALED_VTABLE SScalabilitySettings : public SCompoundWidget { public: diff --git a/Engine/Source/Editor/UnrealEd/Public/SkelImport.h b/Engine/Source/Editor/UnrealEd/Public/SkelImport.h index 884584aa8396..c1e499848f03 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SkelImport.h +++ b/Engine/Source/Editor/UnrealEd/Public/SkelImport.h @@ -83,6 +83,8 @@ struct ExistingSkelMeshData FSkeletalMeshSamplingInfo ExistingSamplingInfo; FPerPlatformInt MinLOD; FPerPlatformBool DisableBelowMinLodStripping; + + TMap ExistingAssetUserData; }; /** diff --git a/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h b/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h index 11e0e4e1f097..52ac37aa5847 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h +++ b/Engine/Source/Editor/UnrealEd/Public/SourceCodeNavigation.h @@ -360,5 +360,5 @@ private: static FSourceFileDatabase Instance; /** Cached result of check for compiler availability. Speeds up performance greatly since BlueprintEditor is checking this on draw. */ - static bool bCachedIsCompilerAvailable; + static bool UNREALED_API bCachedIsCompilerAvailable; }; diff --git a/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h b/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h index e5e5b4210933..94617653cc7b 100644 --- a/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h +++ b/Engine/Source/Editor/UnrealEd/Public/UnrealEdSharedPCH.h @@ -382,7 +382,6 @@ #include "Framework/Commands/UICommandList.h" #include "Framework/MultiBox/MultiBoxExtender.h" #include "Framework/Text/TextLayout.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp b/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp index 7b2b97005d8b..9c3a6b33d779 100644 --- a/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp +++ b/Engine/Source/Editor/WorkspaceMenuStructure/Private/WorkspaceMenuStructureModule.cpp @@ -76,7 +76,7 @@ public: LevelEditorCategory->ClearItems(); LevelEditorViewportsCategory = LevelEditorCategory->AddGroup(LOCTEXT( "WorkspaceMenu_LevelEditorViewportCategory", "Viewports" ), LOCTEXT( "WorkspaceMenu_LevelEditorViewportCategoryTooltip", "Open a Viewport tab." ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"), true); LevelEditorDetailsCategory = LevelEditorCategory->AddGroup(LOCTEXT("WorkspaceMenu_LevelEditorDetailCategory", "Details" ), LOCTEXT("WorkspaceMenu_LevelEditorDetailCategoryTooltip", "Open a Details tab." ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"), true ); - LevelEditorModesCategory = LevelEditorCategory->AddGroup(LOCTEXT("WorkspaceMenu_LevelEditorToolsCategory", "Tools" ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.EditorModes"), true ); + LevelEditorModesCategory = LevelEditorCategory->AddGroup(LOCTEXT("WorkspaceMenu_LevelEditorToolsCategory", "Editor Modes" ), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.EditorModes"), true ); } void ResetToolsCategory() diff --git a/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp index af8f91859680..0e322c0caf80 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/LevelModel.cpp @@ -660,7 +660,7 @@ void FLevelModel::DeselectAllActors() AActor* CurActor = (*It); if (CurActor) { - SelectedActors->Deselect(CurActor); + GEditor->SelectActor(CurActor, false, false); } } } diff --git a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp index 20cc73d2090d..4fd2e00c7860 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp @@ -421,7 +421,7 @@ void FStreamingLevelCollectionModel::CreateNewLevel_Executed() IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); if (NewLevelDialogModule.CreateAndShowNewLevelDialog(MainFrameModule.GetParentWindow(), TemplateMapPackageName)) { - UPackage* TemplatePackage = LoadPackage(nullptr, *TemplateMapPackageName, LOAD_None); + UPackage* TemplatePackage = TemplateMapPackageName.Len() ? LoadPackage(nullptr, *TemplateMapPackageName, LOAD_None) : nullptr; UWorld* TemplateWorld = TemplatePackage ? UWorld::FindWorldInPackage(TemplatePackage) : nullptr; // Create the new level @@ -496,9 +496,10 @@ void FStreamingLevelCollectionModel::FixupInvalidReference_Executed() void FStreamingLevelCollectionModel::RemoveInvalidSelectedLevels_Executed() { - for (TSharedPtr LevelModel : InvalidSelectedLevels) + // needs to be an index-based iterator b/c we are removing elements based on it + for (int32 LevelIdx = InvalidSelectedLevels.Num() - 1; LevelIdx >= 0; LevelIdx--) { - TSharedPtr TargetModel = StaticCastSharedPtr(LevelModel); + TSharedPtr TargetModel = StaticCastSharedPtr(InvalidSelectedLevels[LevelIdx]); ULevelStreaming* LevelStreaming = TargetModel->GetLevelStreaming().Get(); if (LevelStreaming) diff --git a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp index e67985d5cfe8..0e402511798a 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCustomization.cpp @@ -67,6 +67,7 @@ void FStreamingLevelCustomization::CustomizeDetails(IDetailLayoutBuilder& Detail .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .bColorAxisLabels(true) .AllowResponsiveLayout(true) + .AllowSpin(false) .X(this, &FStreamingLevelCustomization::OnGetLevelPosition, 0) .Y(this, &FStreamingLevelCustomization::OnGetLevelPosition, 1) .Z(this, &FStreamingLevelCustomization::OnGetLevelPosition, 2) diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp b/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp index 98e1c3981d2e..bc4dc12684b3 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/STiledLandscapeImportDlg.cpp @@ -177,6 +177,7 @@ void STiledLandcapeImportDlg::Construct(const FArguments& InArgs, TSharedPtr 0) { // Tile information(num, resolution) @@ -716,15 +724,15 @@ FText STiledLandcapeImportDlg::GetImportSummaryText() const float WidthX = 0.00001f*ImportSettings.Scale3D.X*WidthInTilesX*ImportSettings.SizeX; float WidthY = 0.00001f*ImportSettings.Scale3D.Y*WidthInTilesY*ImportSettings.SizeX; const FString LandscapeSummary = FString::Printf(TEXT("%.3fx%.3f"), WidthX, WidthY); - - StatusMessage = FText::Format( + + StatusMessageBuilder.AppendLine(FText::Format( LOCTEXT("TiledLandscapeImport_SummaryText", "{0} tiles, {1}km landscape"), FText::FromString(TilesSummary), FText::FromString(LandscapeSummary) - ); + )); } - return StatusMessage; + return StatusMessageBuilder.ToText(); } FText STiledLandcapeImportDlg::GetWeightmapCountText(TSharedPtr InLayerData) const diff --git a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp index 9038edf785c1..e27ee46b8db2 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/Tiles/WorldTileModel.cpp @@ -16,6 +16,8 @@ #include "Engine/WorldComposition.h" #include "GameFramework/WorldSettings.h" #include "LandscapeInfo.h" +#include "LandscapeEditorModule.h" +#include "LandscapeFileFormatInterface.h" #include "LandscapeStreamingProxy.h" #include "Landscape.h" @@ -335,15 +337,22 @@ bool FWorldTileModel::IsTiledLandscapeBased() const if (IsLandscapeBased() && !GetLandscape()->ReimportHeightmapFilePath.IsEmpty()) { // Check if single landscape actor resolution matches heightmap file size - IFileManager& FileManager = IFileManager::Get(); - const int64 ImportFileSize = FileManager.FileSize(*GetLandscape()->ReimportHeightmapFilePath); - - FIntRect ComponentsRect = GetLandscape()->GetBoundingRect(); - int64 LandscapeSamples = (int64)(ComponentsRect.Width()+1)*(ComponentsRect.Height()+1); - // Height samples are 2 bytes wide - if (LandscapeSamples*2 == ImportFileSize) + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + const FString TargetExtension = FPaths::GetExtension(GetLandscape()->ReimportHeightmapFilePath, true); + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*TargetExtension); + + FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*GetLandscape()->ReimportHeightmapFilePath); + if (HeightmapInfo.ResultCode != ELandscapeImportResult::Error) { - return true; + FIntRect ComponentsRect = GetLandscape()->GetBoundingRect(); + + for (FLandscapeFileResolution& PossibleResolution: HeightmapInfo.PossibleResolutions) + { + if ((PossibleResolution.Width == (ComponentsRect.Width() + 1)) && (PossibleResolution.Height == (ComponentsRect.Height() + 1))) + { + return true; + } + } } } diff --git a/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp b/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp index bc47a910c242..6f6339e80c62 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/WorldBrowserModule.cpp @@ -44,25 +44,22 @@ void FWorldBrowserModule::BuildLevelMenu(FMenuBuilder& MenuBuilder) FLevelModelList ModelList = Model->GetFilteredLevels(); for (TSharedPtr LevelModel : ModelList) { - if (LevelModel->GetLevelObject()) - { - FUIAction Action(FExecuteAction::CreateRaw(this, &FWorldBrowserModule::SetCurrentSublevel, LevelModel->GetLevelObject()), - FCanExecuteAction(), - FIsActionChecked::CreateRaw(this, &FWorldBrowserModule::IsCurrentSublevel, LevelModel->GetLevelObject())); - MenuBuilder.AddMenuEntry(FText::FromString(LevelModel->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Button); - } + FUIAction Action(FExecuteAction::CreateRaw(this, &FWorldBrowserModule::SetCurrentSublevel, LevelModel), + FCanExecuteAction(), + FIsActionChecked::CreateRaw(this, &FWorldBrowserModule::IsCurrentSublevel, LevelModel)); + MenuBuilder.AddMenuEntry(FText::FromString(LevelModel->GetDisplayName()), FText::GetEmpty(), FSlateIcon(), Action, NAME_None, EUserInterfaceActionType::Button); } } } -bool FWorldBrowserModule::IsCurrentSublevel(ULevel* InLevel) +bool FWorldBrowserModule::IsCurrentSublevel(TSharedPtr InLevelModel) { - return InLevel ? InLevel->IsCurrentLevel() : false; + return InLevelModel->IsCurrent(); } -void FWorldBrowserModule::SetCurrentSublevel(ULevel* InLevel) +void FWorldBrowserModule::SetCurrentSublevel(TSharedPtr InLevelModel) { - EditorLevelUtils::MakeLevelCurrent(InLevel); + InLevelModel->MakeLevelCurrent(); } void FWorldBrowserModule::StartupModule() diff --git a/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h b/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h index aecbb9ca0961..9750d30ddd2d 100644 --- a/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h +++ b/Engine/Source/Editor/WorldBrowser/Public/WorldBrowserModule.h @@ -63,8 +63,8 @@ private: /** Fill out the level menu with entries for level operations */ void BuildLevelMenu(FMenuBuilder& MenuBuilder); - bool IsCurrentSublevel(ULevel* InLevel); - void SetCurrentSublevel(ULevel* InLevel); + bool IsCurrentSublevel(TSharedPtr InLevelModel); + void SetCurrentSublevel(TSharedPtr InLevelModel); private: TWeakPtr WorldModel; diff --git a/Engine/Source/Programs/TestPAL/Private/Main.cpp b/Engine/Source/Programs/TestPAL/Private/Main.cpp index 1a6b66cf5df1..47bc24029bb4 100644 --- a/Engine/Source/Programs/TestPAL/Private/Main.cpp +++ b/Engine/Source/Programs/TestPAL/Private/Main.cpp @@ -3,6 +3,7 @@ #include "CoreMinimal.h" #include "TestPALLog.h" #include "Parent.h" +#include "Misc/Guid.h" #include "Stats/StatsMisc.h" #include "HAL/RunnableThread.h" #include "HAL/PlatformApplicationMisc.h" @@ -34,6 +35,8 @@ IMPLEMENT_APPLICATION(TestPAL, "TestPAL"); #define ARG_THREAD_PRIO_TEST "threadpriotest" #define ARG_INLINE_CALLSTACK_TEST "inline" #define ARG_STRINGS_ALLOCATION_TEST "stringsallocation" +#define ARG_CREATEGUID_TEST "createguid" +#define ARG_THREADSTACK_TEST "threadstack" namespace TestPAL { @@ -680,6 +683,135 @@ int32 StringsAllocationTest(const TCHAR* CommandLine) return 0; } +/** + * Thread runnable + * bUseGenericMisc Whether to use FGenericPlatformMisc CreateGuid + */ +template +struct FCreateGuidThread : public FRunnable +{ + /** Number of CreateGuid calls to make. */ + int32 NumCalls; + + FCreateGuidThread(int32 InNumCalls) + : FRunnable() + , NumCalls(InNumCalls) + { + } + + virtual uint32 Run() + { + FGuid Result; + + for (int32 IdxRun = 0; IdxRun < NumCalls; ++IdxRun) + { + if (bUseGenericMisc) + { + FGenericPlatformMisc::CreateGuid(Result); + } + else + { + FPlatformMisc::CreateGuid(Result); + } + } + + return 0; + } +}; + + +/** + * CreateGuid test + */ +int32 CreateGuidTest(const TCHAR* CommandLine) +{ + FPlatformMisc::SetCrashHandler(NULL); + FPlatformMisc::SetGracefulTerminationHandler(); + + GEngineLoop.PreInit(CommandLine); + UE_LOG(LogTestPAL, Display, TEXT("Running CreateGuid test.")); + + TArray RunnableArray; + TArray ThreadArray; + + UE_LOG(LogTestPAL, Display, TEXT("Accepted options:")); + UE_LOG(LogTestPAL, Display, TEXT(" -numthreads=N")); + UE_LOG(LogTestPAL, Display, TEXT(" -numcalls=N (how many times each thread will call CreateGuid)")); + UE_LOG(LogTestPAL, Display, TEXT(" -norandomguids (disable SYS_getrandom syscall)")); + + int32 NumTestThreads = 4, NumCalls = 1000000; + if (FParse::Value(CommandLine, TEXT("numthreads="), NumTestThreads)) + { + NumTestThreads = FMath::Max(1, NumTestThreads); + } + if (FParse::Value(CommandLine, TEXT("numcalls="), NumCalls)) + { + NumCalls = FMath::Max(1, NumCalls); + } + + // start all threads + for (int Idx = 0; Idx < 2; ++Idx ) + { + bool bUseGenericMisc = (Idx == 0); + double WallTimeDuration = FPlatformTime::Seconds(); + + for (int IdxThread = 0; IdxThread < NumTestThreads; ++IdxThread) + { + RunnableArray.Add(bUseGenericMisc ? + static_cast(new FCreateGuidThread(NumCalls)) : + static_cast(new FCreateGuidThread(NumCalls)) ); + + ThreadArray.Add( FRunnableThread::Create(RunnableArray[IdxThread], + *FString::Printf(TEXT("GuidTest%d"), IdxThread)) ); + } + + GLog->FlushThreadedLogs(); + GLog->Flush(); + + // join all threads + for (int IdxThread = 0; IdxThread < NumTestThreads; ++IdxThread) + { + ThreadArray[IdxThread]->WaitForCompletion(); + + delete ThreadArray[IdxThread]; + ThreadArray[IdxThread] = nullptr; + + delete RunnableArray[IdxThread]; + RunnableArray[IdxThread] = nullptr; + } + + WallTimeDuration = FPlatformTime::Seconds() - WallTimeDuration; + + UE_LOG(LogTestPAL, Display, TEXT("--- Results for %s ---"), + bUseGenericMisc ? TEXT("FGenericPlatformMisc::CreateGuid") : TEXT("FPlatformMisc::CreateGuid")); + UE_LOG(LogTestPAL, Display, TEXT("Total wall time: %f seconds, Threads: %d, Calls: %d"), + WallTimeDuration, NumTestThreads, NumCalls); + + ThreadArray.Empty(); + RunnableArray.Empty(); + } + + // Spew out a small sample of GUIDs to visually check we haven't horribly broken something + UE_LOG(LogTestPAL, Display, TEXT("--- CreateGuid Samples ---")); + + for (int Idx = 0; Idx < 20; ++Idx) + { + FGuid GuidPlatform; + FGuid GuidGeneric; + + FPlatformMisc::CreateGuid(GuidPlatform); + FGenericPlatformMisc::CreateGuid(GuidGeneric); + + UE_LOG(LogTestPAL, Display, TEXT("%3d GuidGeneric:%s GuidPlatform:%s"), Idx, + *GuidGeneric.ToString(EGuidFormats::DigitsWithHyphensInBraces), + *GuidPlatform.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + } + + // GMalloc = OldGMalloc; + FEngineLoop::AppExit(); + return 0; +} + /** An ugly way to pass a parameters to FRunnable; shouldn't matter for this test code. */ int32 GMallocTestNumRuns = 500; @@ -1337,6 +1469,86 @@ int32 InlineCallstacksTest(const TCHAR* CommandLine) return 0; } +struct FThreadStackTest : public FRunnable +{ + FThreadStackTest(uint64 InThreadId = ~0) : + ThreadId(InThreadId) + { + } + + uint64 ThreadId; + + virtual uint32 Run() + { + // If we dont have a valid ThreadId lets use the threads id as default + if (ThreadId == ~0) + { + ThreadId = FPlatformTLS::GetCurrentThreadId(); + } + + // Hopefully this wont be to much extra stack space for a testing thread + const SIZE_T StackTraceSize = 65536; + ANSICHAR StackTrace[StackTraceSize] = {0}; + FPlatformStackWalk::ThreadStackWalkAndDump(StackTrace, StackTraceSize, 0, ThreadId); + + UE_LOG(LogTestPAL, Warning, TEXT("***** ThreadStackWalkAndDump for ThreadId(%lu) ******\n%s"), ThreadId, ANSI_TO_TCHAR(StackTrace)); + + const SIZE_T BackTraceSize = 100; + uint64 BackTrace[BackTraceSize]; + int32 BackTraceCount = FPlatformStackWalk::CaptureThreadStackBackTrace(ThreadId, BackTrace, BackTraceSize); + + UE_LOG(LogTestPAL, Warning, TEXT("***** CaptureThreadStackBackTrace for ThreadId(%lu) ******"), ThreadId); + for (int i = 0; i < BackTraceCount; i++) + { + UE_LOG(LogTestPAL, Warning, TEXT("0x%llx"), BackTrace[i]); + } + UE_LOG(LogTestPAL, Warning, TEXT("\n\n")); + + UE_LOG(LogTestPAL, Warning, TEXT("***** ProgramCounterToHumanReadableString for BackTrace for ThreadId(%lu) ******"), ThreadId); + for (int i = 0; i < BackTraceCount; i++) + { + ANSICHAR TempString[1024] = {0}; + FPlatformStackWalk::ProgramCounterToHumanReadableString(i, BackTrace[i], TempString, ARRAY_COUNT(TempString)); + + UE_LOG(LogTestPAL, Warning, TEXT("%s"), ANSI_TO_TCHAR(TempString)); + } + + return 0; + } +}; + + +int32 ThreadTraceTest(const TCHAR* CommandLine) +{ + FPlatformMisc::SetCrashHandler(NULL); + FPlatformMisc::SetGracefulTerminationHandler(); + + GEngineLoop.PreInit(CommandLine); + + // Dump the callstack/backtrace of the thread that is running + { + FThreadStackTest* ThreadStackTest = new FThreadStackTest; + FRunnableThread* Runnable = FRunnableThread::Create(ThreadStackTest, ANSI_TO_TCHAR("ThreadStackTest"), 0); + + Runnable->WaitForCompletion(); + delete ThreadStackTest; + } + + // Dump the GGameThreadId callstack/backtrace from the thread + { + FThreadStackTest* ThreadStackTest = new FThreadStackTest(GGameThreadId); + FRunnableThread* Runnable = FRunnableThread::Create(ThreadStackTest, ANSI_TO_TCHAR("ThreadStackTest"), 0); + + Runnable->WaitForCompletion(); + delete ThreadStackTest; + } + + FEngineLoop::AppPreExit(); + FEngineLoop::AppExit(); + + return 0; +} + /** * Selects and runs one of test cases. * @@ -1418,6 +1630,14 @@ int32 MultiplexedMain(int32 ArgC, char* ArgV[]) { return StringsAllocationTest(*TestPAL::CommandLine); } + else if (!FCStringAnsi::Strcmp(ArgV[IdxArg], ARG_CREATEGUID_TEST)) + { + return CreateGuidTest(*TestPAL::CommandLine); + } + else if (!FCStringAnsi::Strcmp(ArgV[IdxArg], ARG_THREADSTACK_TEST)) + { + return ThreadTraceTest(*TestPAL::CommandLine); + } } FPlatformMisc::SetCrashHandler(NULL); @@ -1443,6 +1663,9 @@ int32 MultiplexedMain(int32 ArgC, char* ArgV[]) UE_LOG(LogTestPAL, Warning, TEXT(" %s: test by replaying a saved malloc history saved by -mallocsavereplay. Possible options: -replayfile=File, -stopafter=N (operation), -suppresserrors"), UTF8_TO_TCHAR(ARG_MALLOC_REPLAY)); UE_LOG(LogTestPAL, Warning, TEXT(" %s: test thread priorities."), UTF8_TO_TCHAR(ARG_THREAD_PRIO_TEST)); UE_LOG(LogTestPAL, Warning, TEXT(" %s: test inline callstacks through ensures and a final crash."), UTF8_TO_TCHAR(ARG_INLINE_CALLSTACK_TEST)); + UE_LOG(LogTestPAL, Warning, TEXT(" %s: test string allocations."), UTF8_TO_TCHAR(ARG_STRINGS_ALLOCATION_TEST)); + UE_LOG(LogTestPAL, Warning, TEXT(" %s: test CreateGuid."), UTF8_TO_TCHAR(ARG_CREATEGUID_TEST)); + UE_LOG(LogTestPAL, Warning, TEXT(" %s: test ThreadWalkStackAndDump and CaptureThreadBackTrace."), UTF8_TO_TCHAR(ARG_THREADSTACK_TEST)); UE_LOG(LogTestPAL, Warning, TEXT("")); UE_LOG(LogTestPAL, Warning, TEXT("Pass one of those to run an appropriate test.")); diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs index 9a745c78a512..d8245ab3d56f 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/ModuleRules.cs @@ -562,6 +562,11 @@ namespace UnrealBuildTool /// public bool bAddDefaultIncludePaths = true; + /// + /// Whether to ignore dangling (i.e. unresolved external) symbols in modules + /// + public bool bIgnoreUnresolvedSymbols = false; + /// /// Whether this module should be precompiled. Defaults to the bPrecompile flag from the target. Clear this flag to prevent a module being precompiled. /// diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs index 1918124a96a6..94128b1de1e3 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs @@ -524,9 +524,15 @@ namespace UnrealBuildTool [RequiresUniqueBuildEnvironment] public bool bIncludePluginsForTargetPlatforms = false; - /// - /// Whether to include PerfCounters support. - /// + /// + /// Whether to allow accessibility code in both Slate and the OS layer. + /// + [RequiresUniqueBuildEnvironment] + public bool bCompileWithAccessibilitySupport = true; + + /// + /// Whether to include PerfCounters support. + /// [RequiresUniqueBuildEnvironment] [ConfigFile(ConfigHierarchyType.Engine, "/Script/BuildSettings.BuildSettings", "bWithPerfCounters")] public bool bWithPerfCounters = false; @@ -992,10 +998,11 @@ namespace UnrealBuildTool public bool bParseTimingInfoForTracing = false; /// - /// Whether to hide symbols by default on POSIX platforms + /// Whether to expose all symbols as public by default on POSIX platforms /// - [CommandLine("-HideSymbolsByDefault")] - public bool bHideSymbolsByDefault; + [CommandLine("-PublicSymbolsByDefault")] + [XmlConfigFile(Category = "BuildConfiguration")] + public bool bPublicSymbolsByDefault = false; /// /// Allows overriding the toolchain to be created for this target. This must match the name of a class declared in the UnrealBuildTool assembly. @@ -1868,7 +1875,12 @@ namespace UnrealBuildTool get { return Inner.bIncludePluginsForTargetPlatforms; } } - public bool bWithPerfCounters + public bool bCompileWithAccessibilitySupport + { + get { return Inner.bCompileWithAccessibilitySupport; } + } + + public bool bWithPerfCounters { get { return Inner.bWithPerfCounters; } } @@ -2214,9 +2226,9 @@ namespace UnrealBuildTool get { return Inner.bParseTimingInfoForTracing; } } - public bool bHideSymbolsByDefault + public bool bPublicSymbolsByDefault { - get { return Inner.bHideSymbolsByDefault; } + get { return Inner.bPublicSymbolsByDefault; } } public string ToolChainName diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs index 3ed6533376b3..054f63b57e49 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModule.cs @@ -66,6 +66,11 @@ namespace UnrealBuildTool /// protected readonly string ModuleApiDefine; + /// + /// The name of the _VTABLE define for this module + /// + protected readonly string ModuleVTableDefine; + /// /// Set of all the public definitions /// @@ -165,6 +170,7 @@ namespace UnrealBuildTool this.Rules = Rules; ModuleApiDefine = Name.ToUpperInvariant() + "_API"; + ModuleVTableDefine = Name.ToUpperInvariant() + "_VTABLE"; PublicDefinitions = HashSetFromOptionalEnumerableStringParameter(Rules.PublicDefinitions); PublicIncludePaths = CreateDirectoryHashSet(Rules.PublicIncludePaths); @@ -493,23 +499,28 @@ namespace UnrealBuildTool { if (Rules.Target.bShouldCompileAsDLL && (Rules.Target.bHasExports || Rules.ModuleSymbolVisibility == ModuleRules.SymbolVisibility.VisibileForDll)) { + Definitions.Add(ModuleVTableDefine + "=DLLEXPORT_VTABLE"); Definitions.Add(ModuleApiDefine + "=DLLEXPORT"); } else { + Definitions.Add(ModuleVTableDefine + "="); Definitions.Add(ModuleApiDefine + "="); } } else if(Binary == null || SourceBinary != Binary) { + Definitions.Add(ModuleVTableDefine + "=DLLIMPORT_VTABLE"); Definitions.Add(ModuleApiDefine + "=DLLIMPORT"); } else if(!Binary.bAllowExports) { + Definitions.Add(ModuleVTableDefine + "="); Definitions.Add(ModuleApiDefine + "="); } else { + Definitions.Add(ModuleVTableDefine + "=DLLEXPORT_VTABLE"); Definitions.Add(ModuleApiDefine + "=DLLEXPORT"); } } @@ -737,6 +748,9 @@ namespace UnrealBuildTool // Add all the additional properties LinkEnvironment.AdditionalProperties.AddRange(Rules.AdditionalPropertiesForReceipt.Inner); + + // this is a link-time property that needs to be accumulated (if any modules contributing to this module is ignoring, all are ignoring) + LinkEnvironment.bIgnoreUnresolvedSymbols |= Rules.bIgnoreUnresolvedSymbols; } /// diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs index b5620799ae85..e1698ac45995 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildModuleCPP.cs @@ -385,6 +385,7 @@ namespace UnrealBuildTool { // Remove the module _API definition for cases where there are circular dependencies between the shared PCH module and modules using it Writer.WriteLine("#undef {0}", ModuleApiDefine); + Writer.WriteLine("#undef {0}", ModuleVTableDefine); // Games may choose to use shared PCHs from the engine, so allow them to change the value of these macros if(!Rules.bTreatAsEngineModule) diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs index 66a890340787..7739467611f7 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs @@ -3200,7 +3200,7 @@ namespace UnrealBuildTool GlobalCompileEnvironment.bPrintTimingInfo = Rules.bPrintToolChainTimingInfo; GlobalCompileEnvironment.bUseRTTI = Rules.bForceEnableRTTI; GlobalCompileEnvironment.bUseInlining = Rules.bUseInlining; - GlobalCompileEnvironment.bHideSymbolsByDefault = Rules.bHideSymbolsByDefault; + GlobalCompileEnvironment.bHideSymbolsByDefault = !Rules.bPublicSymbolsByDefault; GlobalCompileEnvironment.CppStandard = Rules.CppStandard; GlobalCompileEnvironment.AdditionalArguments = Rules.AdditionalCompilerArguments; @@ -3358,7 +3358,16 @@ namespace UnrealBuildTool GlobalCompileEnvironment.Definitions.Add("WITH_PLUGIN_SUPPORT=0"); } - if (Rules.bWithPerfCounters) + if (Rules.bCompileWithAccessibilitySupport && !Rules.bIsBuildingConsoleApplication) + { + GlobalCompileEnvironment.Definitions.Add("WITH_ACCESSIBILITY=1"); + } + else + { + GlobalCompileEnvironment.Definitions.Add("WITH_ACCESSIBILITY=0"); + } + + if (Rules.bWithPerfCounters) { GlobalCompileEnvironment.Definitions.Add("WITH_PERFCOUNTERS=1"); } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs index 280195ef21e6..2c05d84a9d8e 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs @@ -577,7 +577,6 @@ namespace UnrealBuildTool if (CompileEnvironment.bHideSymbolsByDefault) { Result += " -fvisibility=hidden"; - Result += " -fvisibility-inlines-hidden"; } if (String.IsNullOrEmpty(ClangPath)) @@ -751,6 +750,11 @@ namespace UnrealBuildTool // glibc/ld.so limit (DTV_SURPLUS) for number of dlopen()'ed DSOs with static TLS (see e.g. https://www.cygwin.com/ml/libc-help/2013-11/msg00033.html) Result += " -ftls-model=local-dynamic"; } + else + { + Result += " -ffunction-sections"; + Result += " -fdata-sections"; + } if (CompileEnvironment.bEnableExceptions) { @@ -950,9 +954,14 @@ namespace UnrealBuildTool // This apparently can help LLDB speed up symbol lookups Result += " -Wl,--build-id"; - if (bSuppressPIE && !LinkEnvironment.bIsBuildingDLL) + if (!LinkEnvironment.bIsBuildingDLL) { - Result += " -Wl,-nopie"; + Result += " -Wl,--gc-sections"; + + if (bSuppressPIE) + { + Result += " -Wl,-nopie"; + } } // Profile Guided Optimization (PGO) and Link Time Optimization (LTO) @@ -1001,10 +1010,10 @@ namespace UnrealBuildTool // Linking with the toolchain on linux appears to not search usr/ if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux) { - Result += String.Format(" -B{0}/usr/lib/", SysRootPath); - Result += String.Format(" -B{0}/usr/lib64/", SysRootPath); - Result += String.Format(" -L{0}/usr/lib/", SysRootPath); - Result += String.Format(" -L{0}/usr/lib64/", SysRootPath); + Result += String.Format(" -B\"{0}/usr/lib/\"", SysRootPath); + Result += String.Format(" -B\"{0}/usr/lib64/\"", SysRootPath); + Result += String.Format(" -L\"{0}/usr/lib/\"", SysRootPath); + Result += String.Format(" -L\"{0}/usr/lib64/\"", SysRootPath); } } @@ -1718,6 +1727,14 @@ namespace UnrealBuildTool LinkCommandString += " -Wl,--start-group"; LinkCommandString += ExternalLibraries; + + // make unresolved symbols an error, unless a) building a cross-referenced DSO b) we opted out + if ((!LinkEnvironment.bIsBuildingDLL || !LinkEnvironment.bIsCrossReferenced) && !LinkEnvironment.bIgnoreUnresolvedSymbols) + { + // This will make the linker report undefined symbols the current module, but ignore in the dependent DSOs. + // It is tempting, but may not be possible to change that report-all - due to circular dependencies between our libs. + LinkCommandString += " -Wl,--unresolved-symbols=ignore-in-shared-libs"; + } LinkCommandString += " -Wl,--end-group"; LinkCommandString += " -lrt"; // needed for clock_gettime() @@ -1732,6 +1749,7 @@ namespace UnrealBuildTool LinkCommandString += " " + "ThirdParty/Linux/LibCxx/lib/Linux/" + LinkEnvironment.Architecture + "/libc++abi.a"; LinkCommandString += " -lm"; LinkCommandString += " -lc"; + LinkCommandString += " -lpthread"; // pthread_mutex_trylock is missing from libc stubs LinkCommandString += " -lgcc_s"; LinkCommandString += " -lgcc"; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs index 4971948cd4c0..fe8f280eedfa 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs @@ -297,7 +297,7 @@ namespace UnrealBuildTool // [bschaefer] 2018-08-24: disabling XGE due to a bug where XGE seems to be lower casing folders names that are headers ie. misc/Header.h vs Misc/Header.h // [bschaefer] 2018-10-04: enabling XGE as an update in xgConsole seems to have fixed it for me // [bschaefer] 2018-12-17: disable XGE again, as the same issue before seems to still be happening but intermittently - return false; //BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64; + return false; // BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64; } public override bool CanUseParallelExecutor() @@ -518,12 +518,6 @@ namespace UnrealBuildTool ); } - // for now only hide by default monolithic builds. - if (Target.LinkType == TargetLinkType.Monolithic) - { - CompileEnvironment.bHideSymbolsByDefault = true; - } - // link with Linux libraries. LinkEnvironment.AdditionalLibraries.Add("pthread"); diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs index d12efa8877b9..0d1e966f9e3c 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Project.cs @@ -306,7 +306,8 @@ namespace UnrealBuildTool // Ignore any API macros being import/export; we'll assume they're valid across the whole project if(Def.EndsWith("_API", StringComparison.Ordinal)) { - CurDef = Def + "="; + CurDef = Def + "="; + CurDef = Def + "_TYPE="; Value = ""; } diff --git a/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs b/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs index c3610784e40f..35fa5ddafcd9 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/CppCompileEnvironment.cs @@ -342,7 +342,7 @@ namespace UnrealBuildTool /// /// Whether to hide symbols by default /// - public bool bHideSymbolsByDefault; + public bool bHideSymbolsByDefault = true; /// /// Which C++ standard to support. May not be compatible with all platforms. diff --git a/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs b/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs index 1865b60c60ab..302f72e5ca07 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/LinkEnvironment.cs @@ -244,6 +244,11 @@ namespace UnrealBuildTool /// public bool bUseFastPDBLinking; + /// + /// Whether to ignore dangling (i.e. unresolved external) symbols in modules + /// + public bool bIgnoreUnresolvedSymbols; + /// /// Whether to log detailed timing information /// @@ -347,6 +352,7 @@ namespace UnrealBuildTool bAllowASLR = Other.bAllowASLR; bUsePDBFiles = Other.bUsePDBFiles; bUseFastPDBLinking = Other.bUseFastPDBLinking; + bIgnoreUnresolvedSymbols = Other.bIgnoreUnresolvedSymbols; bPrintTimingInfo = Other.bPrintTimingInfo; BundleVersion = Other.BundleVersion; InstallName = Other.InstallName; diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp index b8cae3718caa..539bdaee1851 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/BaseParser.cpp @@ -1036,7 +1036,7 @@ bool FBaseParser::ReadOptionalCommaSeparatedListInParens(TArray& Items, void FBaseParser::ParseNameWithPotentialAPIMacroPrefix(FString& DeclaredName, FString& RequiredAPIMacroIfPresent, const TCHAR* FailureMessage) { - // Expecting Name | (MODULE_API Name) + // Expecting Name | (MODULE_API Name) | (MODULE_VTABLE Name) FToken NameToken; // Read an identifier @@ -1058,6 +1058,19 @@ void FBaseParser::ParseNameWithPotentialAPIMacroPrefix(FString& DeclaredName, FS } DeclaredName = NameToken.Identifier; } + else if (NameTokenStr.EndsWith(TEXT("_VTABLE"), ESearchCase::CaseSensitive)) + { + // Read the real name + if (!GetIdentifier(NameToken)) + { + FError::Throwf(TEXT("Missing %s name"), FailureMessage); + } + + DeclaredName = NameToken.Identifier; + + // Not required for things such a Minimal. Only used for Linux/Mac which just exposes the vtable for link-age + RequiredAPIMacroIfPresent.Empty(); + } else { DeclaredName = NameTokenStr; diff --git a/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp b/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp index 70c53f25b31f..664792fd0d94 100644 --- a/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp +++ b/Engine/Source/Runtime/AppFramework/Private/Widgets/Testing/STestSuite.cpp @@ -42,7 +42,6 @@ #include "Framework/Commands/InputChord.h" #include "Framework/Commands/Commands.h" #include "Framework/Commands/UICommandList.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Framework/Text/ISlateRun.h" diff --git a/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs b/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs index 1ba470856f45..f65f44482c1a 100644 --- a/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs +++ b/Engine/Source/Runtime/ApplicationCore/ApplicationCore.Build.cs @@ -32,7 +32,11 @@ public class ApplicationCore : ModuleRules AddEngineThirdPartyPrivateStaticDependencies(Target, "XInput" ); - } + if (Target.bCompileWithAccessibilitySupport && !Target.bIsBuildingConsoleApplication) + { + PublicAdditionalLibraries.Add("uiautomationcore.lib"); + } + } else if (Target.Platform == UnrealTargetPlatform.Mac) { AddEngineThirdPartyPrivateStaticDependencies(Target, diff --git a/Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericAccessibleInterfaces.cpp b/Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericAccessibleInterfaces.cpp new file mode 100644 index 000000000000..54de27f06c69 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericAccessibleInterfaces.cpp @@ -0,0 +1,5 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +DEFINE_LOG_CATEGORY(LogAccessibility); diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityCache.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityCache.cpp new file mode 100644 index 000000000000..897acdeaf369 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityCache.cpp @@ -0,0 +1,139 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/Accessibility/IOSAccessibilityElement.h" +#include "IOS/IOSApplication.h" +#include "IOS/IOSAppDelegate.h" +#include "IOS/IOSAsyncTask.h" +#include "IOS/IOSView.h" + +@implementation FIOSAccessibilityCache + +- (id)init +{ + Cache = [[NSMutableDictionary alloc] init]; + return self; +} + +-(void)dealloc +{ + [Cache release]; + [super dealloc]; +} + +-(FIOSAccessibilityContainer*)GetAccessibilityElement:(AccessibleWidgetId)Id +{ + if (Id == IAccessibleWidget::InvalidAccessibleWidgetId) + { + return nil; + } + + FIOSAccessibilityContainer* ExistingElement = [Cache objectForKey:[NSNumber numberWithInt:Id]]; + if (ExistingElement == nil) + { + AccessibleWidgetId ParentId = IAccessibleWidget::InvalidAccessibleWidgetId; + // All IAccessibleWidget functions must be run on Game Thread + [IOSAppDelegate WaitAndRunOnGameThread : [Id, &ParentId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(Id); + if (Widget.IsValid()) + { + TSharedPtr ParentWidget = Widget->GetParent(); + if (ParentWidget.IsValid()) + { + ParentId = ParentWidget->GetId(); + } + } + }]; + + ExistingElement = [[[FIOSAccessibilityContainer alloc] initWithId:Id AndParentId:ParentId] autorelease]; + [Cache setObject:ExistingElement forKey:[NSNumber numberWithInt:Id]]; + } + return ExistingElement; +} + +-(bool)AccessibilityElementExists:(AccessibleWidgetId)Id +{ + return [Cache objectForKey:[NSNumber numberWithInt:Id]] != nil; +} + +-(void)RemoveAccessibilityElement:(AccessibleWidgetId)Id +{ + [Cache removeObjectForKey:[NSNumber numberWithInt:Id]]; +} + +-(void)Clear +{ + [Cache removeAllObjects]; +} + ++(id)AccessibilityElementCache +{ + static FIOSAccessibilityCache* Cache = nil; + if (Cache == nil) + { + Cache = [[self alloc] init]; + } + return Cache; +} + +-(void)UpdateAllCachedProperties +{ + TArray Ids; + for (NSString* Key in Cache) + { + Ids.Add(Key.intValue); + } + + if (Ids.Num() > 0) + { + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([Ids]() + { + for (const AccessibleWidgetId Id : Ids) + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(Id); + if (Widget.IsValid()) + { + // Children + TArray ChildIds; + for (int32 i = 0; i < Widget->GetNumberOfChildren(); ++i) + { + TSharedPtr Child = Widget->GetChildAt(i); + if (Child.IsValid()) + { + ChildIds.Add(Child->GetId()); + } + } + + // Bounding rect + FBox2D Bounds = Widget->GetBounds(); + const float Scale = [IOSAppDelegate GetDelegate].IOSView.contentScaleFactor; + Bounds.Min /= Scale; + Bounds.Max /= Scale; + + // Visibility + const bool bIsEnabled = Widget->IsEnabled(); + const bool bIsVisible = !Widget->IsHidden(); + + // All UIKit functions must be run on Main Thread + dispatch_async(dispatch_get_main_queue(), ^ + { + FIOSAccessibilityContainer* Element = [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:Id]; + Element.ChildIds = ChildIds; + Element.Bounds = Bounds; + + [[Element GetLeaf] SetAccessibilityTrait:UIAccessibilityTraitNotEnabled Set:!bIsEnabled]; + Element.bIsVisible = bIsVisible; + }); + } + } + }, TStatId(), NULL, ENamedThreads::GameThread); + } +} + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityElement.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityElement.cpp new file mode 100644 index 000000000000..e9328a403896 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/Accessibility/IOSAccessibilityElement.cpp @@ -0,0 +1,333 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "IOS/Accessibility/IOSAccessibilityElement.h" + +#include "Async/TaskGraphInterfaces.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/IOSApplication.h" +#include "IOS/IOSAppDelegate.h" +#include "IOS/IOSView.h" + +@implementation FIOSAccessibilityContainer + +@synthesize Id; +@synthesize ChildIds; +@synthesize Bounds; +@synthesize bIsVisible; + +-(id)initWithId:(AccessibleWidgetId)InId AndParentId:(AccessibleWidgetId)InParentId +{ + id UIParent = nil; + if (InParentId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + UIParent = [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:InParentId]; + } + else + { + UIParent = [IOSAppDelegate GetDelegate].IOSView; + } + + if (self = [super initWithAccessibilityContainer:UIParent]) + { + self.Id = InId; + self.bIsVisible = true; + Leaf = [[FIOSAccessibilityLeaf alloc] initWithParent:self]; + } + + return self; +} + +-(void)dealloc +{ + [Leaf release]; + [super dealloc]; +} + +-(void)SetParent:(AccessibleWidgetId)InParentId +{ + if (InParentId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + self.accessibilityContainer = [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:InParentId]; + } + else + { + self.accessibilityContainer = [IOSAppDelegate GetDelegate].IOSView; + } +} + +-(FIOSAccessibilityLeaf*)GetLeaf +{ + return Leaf; +} + +-(BOOL)isAccessibilityElement +{ + // Containers are never accessible + return NO; +} + +-(CGRect)accessibilityFrame +{ + // This function will be called less than the function to cache the bounds, so make + // the IOS rect here. If we refactor the code to not using a polling-based cache, + // it may make more sense to change the Bounds property itself to a CGRect. + return CGRectMake(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X - Bounds.Min.X, Bounds.Max.Y - Bounds.Min.Y); +} + +-(NSInteger)accessibilityElementCount +{ + // The extra +1 is from the Leaf element + return self.ChildIds.Num() + 1; +} + +-(id)accessibilityElementAtIndex:(NSInteger)index +{ + if (index == self.ChildIds.Num()) + { + return Leaf; + } + else + { + return [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:self.ChildIds[index]]; + } +} + +-(NSInteger)indexOfAccessibilityElement:(id)element +{ + if ([element isKindOfClass : [FIOSAccessibilityLeaf class]]) + { + // If it's a Leaf, it is the last child of the parent + return [[element accessibilityContainer] accessibilityElementCount] - 1; + } + + const AccessibleWidgetId OtherId = ((FIOSAccessibilityContainer*)element).Id; + for (int32 i = 0; i < self.ChildIds.Num(); ++i) + { + if (self.Id == OtherId) + { + return i; + } + } + + return NSNotFound; +} + +-(id)accessibilityHitTest:(CGPoint)point +{ + const AccessibleWidgetId TempId = self.Id; + AccessibleWidgetId FoundId = IAccessibleWidget::InvalidAccessibleWidgetId; + + const float Scale = [IOSAppDelegate GetDelegate].IOSView.contentScaleFactor; + const int32 X = point.x * Scale; + const int32 Y = point.y * Scale; + // Update the labels while we're on the game thread, since IOS is going to request them immediately. + FString TempLabel, TempHint, TempValue; + + [IOSAppDelegate WaitAndRunOnGameThread:[TempId, X, Y, &FoundId, &TempLabel, &TempHint, &TempValue]() + { + const TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(TempId); + if (Widget.IsValid()) + { + const TSharedPtr HitWidget = Widget->GetTopLevelWindow()->AsWindow()->GetChildAtPosition(X, Y); + if (HitWidget.IsValid()) + { + FoundId = HitWidget->GetId(); + TempLabel = HitWidget->GetWidgetName(); + TempHint = HitWidget->GetHelpText(); + if (HitWidget->AsProperty()) + { + TempValue = HitWidget->AsProperty()->GetValue(); + } + } + } + }]; + + if (FoundId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + FIOSAccessibilityLeaf* FoundLeaf = [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:FoundId] GetLeaf]; + if ([FoundLeaf ShouldCacheStrings]) + { + FoundLeaf.Label = MoveTemp(TempLabel); + FoundLeaf.Hint = MoveTemp(TempHint); + FoundLeaf.Value = MoveTemp(TempValue); + FoundLeaf.LastCachedStringTime = FPlatformTime::Seconds(); + } + return FoundLeaf; + } + else + { + return Leaf; + } +} + +@end + +@implementation FIOSAccessibilityLeaf + +@synthesize Label; +@synthesize Hint; +@synthesize Value; +@synthesize Traits; +@synthesize LastCachedStringTime; + +-(id)initWithParent:(FIOSAccessibilityContainer*)Parent +{ + if (self = [super initWithAccessibilityContainer : Parent]) + { + const AccessibleWidgetId ParentId = Parent.Id; + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([ParentId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(ParentId); + if (Widget.IsValid()) + { + // Most accessibility traits cannot be changed after setting, so initialize them here + UIAccessibilityTraits InitialTraits = UIAccessibilityTraitNone; + if (Widget->AsProperty() && !FMath::IsNearlyZero(Widget->AsProperty()->GetStepSize())) + { + InitialTraits |= UIAccessibilityTraitAdjustable; + } + if (Widget->AsActivatable()) + { + InitialTraits |= UIAccessibilityTraitButton; + } + if (Widget->GetWidgetType() == EAccessibleWidgetType::Image) + { + InitialTraits |= UIAccessibilityTraitImage; + } + if (Widget->GetWidgetType() == EAccessibleWidgetType::Hyperlink) + { + InitialTraits |= UIAccessibilityTraitLink; + } + if (!Widget->IsEnabled()) + { + InitialTraits |= UIAccessibilityTraitNotEnabled; + } + + FString InitialLabel = Widget->GetWidgetName(); + FString InitialHint = Widget->GetHelpText(); + FString InitialValue; + if (Widget->AsProperty()) + { + InitialValue = Widget->AsProperty()->GetValue(); + } + + // All UIKit functions must be run on Main Thread + dispatch_async(dispatch_get_main_queue(), ^ + { + FIOSAccessibilityLeaf* Self = [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:ParentId] GetLeaf]; + Self.Traits = InitialTraits; + Self.Label = InitialLabel; + Self.Hint = InitialHint; + Self.Value = InitialValue; + Self.LastCachedStringTime = FPlatformTime::Seconds(); + }); + } + }, TStatId(), NULL, ENamedThreads::GameThread); + } + return self; +} + +-(BOOL)isAccessibilityElement +{ + return YES; +} + +-(CGRect)accessibilityFrame +{ + return [self.accessibilityContainer accessibilityFrame]; +} + +-(bool)ShouldCacheStrings +{ + return FPlatformTime::Seconds() - LastCachedStringTime > 1.0; +} + +-(NSString*)accessibilityLabel +{ + if (!self.Label.IsEmpty()) + { + return [NSString stringWithFString:self.Label]; + } + return nil; +} + +-(NSString*)accessibilityHint +{ + if (!self.Hint.IsEmpty()) + { + return [NSString stringWithFString:self.Hint]; + } + return nil; +} + +-(NSString*)accessibilityValue +{ + if (!self.Value.IsEmpty()) + { + return [NSString stringWithFString:self.Value]; + } + return nil; +} + +-(void)SetAccessibilityTrait:(UIAccessibilityTraits)Trait Set:(bool)IsEnabled +{ + if (IsEnabled) + { + self.Traits |= Trait; + } + else + { + self.Traits &= ~Trait; + } +} + +-(UIAccessibilityTraits)accessibilityTraits +{ + return self.Traits; +} + +-(void)accessibilityIncrement +{ + const AccessibleWidgetId TempId = ((FIOSAccessibilityContainer*)self.accessibilityContainer).Id; + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([TempId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(TempId); + if (Widget.IsValid()) + { + IAccessibleProperty* Property = Widget->AsProperty(); + if (Property && !FMath::IsNearlyZero(Property->GetStepSize())) + { + const float CurrentValue = FCString::Atof(*Property->GetValue()); + Property->SetValue(FString::SanitizeFloat(CurrentValue + Property->GetStepSize())); + } + } + }, TStatId(), NULL, ENamedThreads::GameThread); +} + +-(void)accessibilityDecrement +{ + const AccessibleWidgetId TempId = ((FIOSAccessibilityContainer*)self.accessibilityContainer).Id; + // All IAccessibleWidget functions must be run on Game Thread + FFunctionGraphTask::CreateAndDispatchWhenReady([TempId]() + { + TSharedPtr Widget = [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->GetAccessibleWidgetFromId(TempId); + if (Widget.IsValid()) + { + IAccessibleProperty* Property = Widget->AsProperty(); + if (Property && !FMath::IsNearlyZero(Property->GetStepSize())) + { + const float CurrentValue = FCString::Atof(*Property->GetValue()); + Property->SetValue(FString::SanitizeFloat(CurrentValue - Property->GetStepSize())); + } + } + }, TStatId(), NULL, ENamedThreads::GameThread); +} + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp index 12ffd3f02966..f83c61026c49 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp @@ -6,6 +6,7 @@ #include "Modules/Boilerplate/ModuleBoilerplate.h" #include "Misc/CallbackDevice.h" #include "IOS/IOSView.h" +#include "IOS/IOSWindow.h" #include "Async/TaskGraphInterfaces.h" #include "GenericPlatform/GenericPlatformChunkInstall.h" #include "IOS/IOSPlatformMisc.h" @@ -15,6 +16,7 @@ #include "Misc/OutputDeviceError.h" #include "Misc/CommandLine.h" #include "IOS/IOSPlatformFramePacer.h" +#include "IOS/IOSApplication.h" #include "IOS/IOSAsyncTask.h" #include "Misc/ConfigCacheIni.h" #include "IOS/IOSPlatformCrashContext.h" @@ -34,6 +36,10 @@ #include #include "HAL/IConsoleManager.h" +#if WITH_ACCESSIBILITY +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#endif + // this is the size of the game thread stack, it must be a multiple of 4k #if (UE_BUILD_SHIPPING || UE_BUILD_TEST) #define GAME_THREAD_STACK_SIZE 2 * 1024 * 1024 @@ -152,7 +158,9 @@ bool FIOSCoreDelegates::PassesPushNotificationFilters(NSDictionary* Payload) @synthesize timer; @synthesize IdleTimerEnableTimer; @synthesize IdleTimerEnablePeriod; - +#if WITH_ACCESSIBILITY +@synthesize AccessibilityCacheTimer; +#endif @synthesize savedOpenUrlParameters; @synthesize BackgroundSessionEventCompleteDelegate; @@ -213,6 +221,13 @@ static IOSAppDelegate* CachedDelegate = nil; [IOSView release]; [SlateController release]; [timer release]; +#if WITH_ACCESSIBILITY + if (AccessibilityCacheTimer != nil) + { + [AccessibilityCacheTimer invalidate]; + [AccessibilityCacheTimer release]; + } +#endif [super dealloc]; } @@ -267,6 +282,16 @@ static IOSAppDelegate* CachedDelegate = nil; FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("native")).Broadcast(Helper); #endif +#if WITH_ACCESSIBILITY + // Initialize accessibility code if VoiceOver is enabled. This must happen after Slate has been initialized. + dispatch_async(dispatch_get_main_queue(), ^{ + if (UIAccessibilityIsVoiceOverRunning()) + { + [[IOSAppDelegate GetDelegate] OnVoiceOverStatusChanged]; + } + }); +#endif + while( !GIsRequestingExit ) { if (self.bIsSuspended) @@ -1242,10 +1267,55 @@ static FAutoConsoleVariableRef CVarGEnableThermalsReport( #endif [self InitializeAudioSession]; - + +#if WITH_ACCESSIBILITY + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OnVoiceOverStatusChanged) name:UIAccessibilityVoiceOverStatusDidChangeNotification object:nil]; +#endif + return YES; } +#if WITH_ACCESSIBILITY +-(void)OnVoiceOverStatusChanged +{ + if (UIAccessibilityIsVoiceOverRunning()) + { + // This must happen asynchronously because when the app activates from a suspended state, + // the IOS notification will emit before the game thread wakes up. This does mean that the + // accessibility element tree will probably not be 100% completed when the application + // opens for the first time. If this is a problem we can add separate branches for startup + // vs waking up. + FFunctionGraphTask::CreateAndDispatchWhenReady([]() + { + FIOSApplication* Application = [IOSAppDelegate GetDelegate].IOSApplication; + Application->GetAccessibleMessageHandler()->SetActive(true); + AccessibleWidgetId WindowId = Application->GetAccessibleMessageHandler()->GetAccessibleWindowId(Application->FindWindowByAppDelegateView()); + dispatch_async(dispatch_get_main_queue(), ^{ + IOSAppDelegate* Delegate = [IOSAppDelegate GetDelegate]; + [Delegate.IOSView SetAccessibilityWindow:WindowId]; + if (Delegate.AccessibilityCacheTimer == nil) + { + // Start caching accessibility data so that it can be returned instantly to IOS. If not cached, the data takes too long + // to retrieve due to cross-thread waiting and IOS will timeout. + Delegate.AccessibilityCacheTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:[FIOSAccessibilityCache AccessibilityElementCache] selector:@selector(UpdateAllCachedProperties) userInfo:nil repeats:YES]; + } + }); + }, TStatId(), NULL, ENamedThreads::GameThread); + } + else + { + [AccessibilityCacheTimer invalidate]; + AccessibilityCacheTimer = nil; + [[IOSAppDelegate GetDelegate].IOSView SetAccessibilityWindow : IAccessibleWidget::InvalidAccessibleWidgetId]; + [[FIOSAccessibilityCache AccessibilityElementCache] Clear]; + FFunctionGraphTask::CreateAndDispatchWhenReady([]() + { + [IOSAppDelegate GetDelegate].IOSApplication->GetAccessibleMessageHandler()->SetActive(false); + }, TStatId(), NULL, ENamedThreads::GameThread); + } +} +#endif + - (void) StartGameThread { // create a new thread (the pointer will be retained forever) @@ -1262,6 +1332,23 @@ static FAutoConsoleVariableRef CVarGEnableThermalsReport( } } ++(bool)WaitAndRunOnGameThread:(TUniqueFunction)Function +{ + FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(Function), TStatId(), NULL, ENamedThreads::GameThread); + + const double MaxThreadWaitTime = 2.0; + const double StartTime = FPlatformTime::Seconds(); + while ((FPlatformTime::Seconds() - StartTime) < MaxThreadWaitTime) + { + FPlatformProcess::Sleep(0.05f); + if (Task->IsComplete()) + { + return true; + } + } + return false; +} + #if !PLATFORM_TVOS extern EDeviceScreenOrientation ConvertFromUIInterfaceOrientation(UIInterfaceOrientation Orientation); #endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp index 6542c1704b05..92ec371c7376 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSApplication.cpp @@ -13,6 +13,10 @@ #include "HAL/IConsoleManager.h" #include "IOS/IOSAsyncTask.h" #include "Stats/Stats.h" +#if WITH_ACCESSIBILITY +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/Accessibility/IOSAccessibilityElement.h" +#endif FIOSApplication* FIOSApplication::CreateIOSApplication() { @@ -49,6 +53,15 @@ void FIOSApplication::SetMessageHandler( const TSharedRef< FGenericApplicationMe } } +#if WITH_ACCESSIBILITY +void FIOSApplication::SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) +{ + GenericApplication::SetAccessibleMessageHandler(InAccessibleMessageHandler); + InAccessibleMessageHandler->SetAccessibleEventDelegate(FGenericAccessibleMessageHandler::FAccessibleEvent::CreateRaw(this, &FIOSApplication::OnAccessibleEventRaised)); + InAccessibleMessageHandler->SetActive(UIAccessibilityIsVoiceOverRunning()); +} +#endif + void FIOSApplication::AddExternalInputDevice(TSharedPtr InputDevice) { if (InputDevice.IsValid()) @@ -224,3 +237,53 @@ bool FIOSApplication::IsGamepadAttached() const return false; } + +TSharedRef FIOSApplication::FindWindowByAppDelegateView() +{ + for (const TSharedRef& Window : Windows) + { + if ([IOSAppDelegate GetDelegate].Window == static_cast(Window->GetOSWindowHandle())) + { + return Window; + } + } + check(false); + return Windows[0]; +} + +#if WITH_ACCESSIBILITY +void FIOSApplication::OnAccessibleEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) +{ + // This should only be triggered by the accessible message handler which initiates from the Slate thread. + check(IsInGameThread()); + + const AccessibleWidgetId Id = Widget->GetId(); + switch (Event) + { + case EAccessibleEvent::BeforeRemoveFromParent: + dispatch_async(dispatch_get_main_queue(), ^{ + [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:Id] SetParent:IAccessibleWidget::InvalidAccessibleWidgetId]; + }); + break; + case EAccessibleEvent::AfterAddToParent: + { + AccessibleWidgetId ParentId = IAccessibleWidget::InvalidAccessibleWidgetId; + TSharedPtr ParentWidget = Widget->GetParent(); + if (ParentWidget.IsValid()) + { + ParentId = ParentWidget->GetId(); + } + dispatch_async(dispatch_get_main_queue(), ^{ + [[[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:Id] SetParent:ParentId]; + }); + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + break; + } + case EAccessibleEvent::WidgetRemoved: + dispatch_async(dispatch_get_main_queue(), ^{ + [[FIOSAccessibilityCache AccessibilityElementCache] RemoveAccessibilityElement:Id]; + }); + break; + } +} +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp index 4fd285b7442e..7bce2f2d3e5b 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSView.cpp @@ -3,6 +3,7 @@ #include "IOS/IOSView.h" #include "IOS/IOSAppDelegate.h" #include "IOS/IOSApplication.h" +#include "IOSWindow.h" #include "Misc/ConfigCacheIni.h" #include "HAL/IConsoleManager.h" #include "Misc/CommandLine.h" @@ -16,6 +17,11 @@ #include "IOS/IOSCommandLineHelper.h" +#if WITH_ACCESSIBILITY +#include "IOS/Accessibility/IOSAccessibilityCache.h" +#include "IOS/Accessibility/IOSAccessibilityElement.h" +#endif + #if HAS_METAL id GMetalDevice = nil; #endif @@ -286,6 +292,9 @@ id GMetalDevice = nil; -(void)dealloc { [markedTextStyle release]; +#if WITH_ACCESSIBILITY + [_accessibilityElements release]; +#endif [super dealloc]; } @@ -538,6 +547,37 @@ id GMetalDevice = nil; SwapCount++; } +#if WITH_ACCESSIBILITY + +-(void)SetAccessibilityWindow:(AccessibleWidgetId)WindowId +{ + if (_accessibilityElements == nil) + { + _accessibilityElements = [[NSMutableArray alloc] init]; + } + else + { + [_accessibilityElements removeAllObjects]; + } + + if (WindowId != IAccessibleWidget::InvalidAccessibleWidgetId) + { + [_accessibilityElements addObject : [[FIOSAccessibilityCache AccessibilityElementCache] GetAccessibilityElement:WindowId]]; + } +} + +-(NSArray*)accessibilityElements +{ + return _accessibilityElements; +} + +-(BOOL)isAccessibilityElement +{ + return NO; +} + +#endif + /** * Returns the unique ID for the given touch */ diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp index 0aac82af560b..2af6c3d0b03f 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp @@ -1021,6 +1021,31 @@ void FLinuxApplication::ProcessDeferredMessage( SDL_Event Event ) } } +void FLinuxApplication::CheckIfApplicatioNeedsDeactivation() +{ + // If FocusOutDeactivationTime is set we have had a focus out event and are waiting to see if we need to Deactivate the Application + if (!FMath::IsNearlyZero(FocusOutDeactivationTime)) + { + // We still havent hit our timeout limit, keep waiting + if (FocusOutDeactivationTime > FPlatformTime::Seconds()) + { + return; + } + // If we don't use bIsDragWindowButtonPressed the draged window will be destroyed because we + // deactivate the whole appliacton. TODO Is that a bug? Do we have to do something? + else if (!CurrentFocusWindow.IsValid() && !bIsDragWindowButtonPressed) + { + DeactivateApplication(); + + FocusOutDeactivationTime = 0.0; + } + else + { + FocusOutDeactivationTime = 0.0; + } + } +} + FVector2D FLinuxApplication::GetTouchEventLocation(SDL_HWindow NativeWindow, SDL_Event TouchEvent) { checkf(TouchEvent.type == SDL_FINGERDOWN || TouchEvent.type == SDL_FINGERUP || TouchEvent.type == SDL_FINGERMOTION, TEXT("Wrong touch event.")); diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp index f0f9935ab467..647fe6527f12 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformApplicationMisc.cpp @@ -415,6 +415,7 @@ void FLinuxPlatformApplicationMisc::PumpMessages( bool bFromMainLoop ) LinuxApplication->AddPendingEvent( event ); } + LinuxApplication->CheckIfApplicatioNeedsDeactivation(); LinuxApplication->ClearWindowPropertiesAfterEventLoop(); } else diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp index 695959efb0d9..bcb518778bfb 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxPlatformSplash.cpp @@ -23,15 +23,17 @@ #include "Modules/ModuleManager.h" #include "HAL/PlatformApplicationMisc.h" +#include "SDL.h" + #if WITH_EDITOR -#include "SDL.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" #include "ft2build.h" #include FT_FREETYPE_H +#endif // WITH_EDITOR /** * Splash screen functions and static globals @@ -57,24 +59,29 @@ public: private: static SDL_Surface* LoadImage(const FString &InImagePath); + void Redraw(); + +#if WITH_EDITOR void DrawCharacter(int32 penx, int32 peny, FT_GlyphSlot Glyph, int32 CurTypeIndex, float Red, float Green, float Blue); void RenderStrings(); bool OpenFonts(); - void Redraw(); FT_Library FontLibrary = nullptr; FT_Face FontSmall = nullptr; FT_Face FontNormal = nullptr; FT_Face FontLarge = nullptr; - SDL_Surface *SplashSurface = nullptr; - SDL_Window *SplashWindow = nullptr; SDL_Renderer *SplashRenderer = nullptr; SDL_Texture *SplashTexture = nullptr; FText SplashText[ SplashTextType::NumTextTypes ]; Rect SplashTextRects[ SplashTextType::NumTextTypes ]; unsigned char *ScratchSpace = nullptr; +#endif // WITH_EDITOR + + SDL_Surface *SplashSurface = nullptr; + SDL_Window *SplashWindow = nullptr; + bool bNeedsRedraw = false; bool bStringsChanged = false; }; @@ -82,17 +89,14 @@ private: static FLinuxSplashState *GSplashState = nullptr; +//--------------------------------------------------------- FLinuxSplashState::~FLinuxSplashState() { +#if WITH_EDITOR // Just in case SDL's renderer steps on GL state... SDL_Window *CurrentWindow = SDL_GL_GetCurrentWindow(); SDL_GLContext CurrentContext = SDL_GL_GetCurrentContext(); - if (SplashSurface) - { - SDL_FreeSurface(SplashSurface); - } - if (SplashTexture) { SDL_DestroyTexture(SplashTexture); @@ -102,12 +106,19 @@ FLinuxSplashState::~FLinuxSplashState() { SDL_DestroyRenderer(SplashRenderer); } +#endif // WITH_EDITOR + + if (SplashSurface) + { + SDL_FreeSurface(SplashSurface); + } if (SplashWindow) { SDL_DestroyWindow(SplashWindow); } +#if WITH_EDITOR if (ScratchSpace) { FMemory::Free(ScratchSpace); @@ -137,10 +148,13 @@ FLinuxSplashState::~FLinuxSplashState() { SDL_GL_MakeCurrent(CurrentWindow, CurrentContext); } +#endif // WITH_EDITOR // do not deinit SDL here } +#if WITH_EDITOR + //--------------------------------------------------------- bool FLinuxSplashState::OpenFonts() { @@ -152,7 +166,7 @@ bool FLinuxSplashState::OpenFonts() // small font face FString FontPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Light.ttf")); - + if (FT_New_Face(FontLibrary, TCHAR_TO_UTF8(*FontPath), 0, &FontSmall)) { UE_LOG(LogHAL, Error, TEXT("*** Unable to open small font face for splash screen.")); @@ -164,7 +178,7 @@ bool FLinuxSplashState::OpenFonts() // normal font face FontPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf")); - + if (FT_New_Face(FontLibrary, TCHAR_TO_UTF8(*FontPath), 0, &FontNormal)) { UE_LOG(LogHAL, Error, TEXT("*** Unable to open normal font face for splash screen.")); @@ -173,13 +187,13 @@ bool FLinuxSplashState::OpenFonts() { FT_Set_Pixel_Sizes(FontNormal, 0, 12); } - + // large font face FontPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf")); - + if (FT_New_Face(FontLibrary, TCHAR_TO_UTF8(*FontPath), 0, &FontLarge)) { - UE_LOG(LogHAL, Error, TEXT("*** Unable to open large font face for splash screen.")); + UE_LOG(LogHAL, Error, TEXT("*** Unable to open large font face for splash screen.")); } else { @@ -217,12 +231,12 @@ void FLinuxSplashState::DrawCharacter(int32 PenX, int32 PenY, FT_GlyphSlot Glyph // find pixel position in splash image const int PosX = PenX + GlyphX + (Glyph->metrics.horiBearingX >> 6); const int PosY = PenY + GlyphY - (Glyph->metrics.horiBearingY >> 6); - + // make sure pixel is in drawing rectangle if (PosX < MinX || PosX >= MaxX || PosY < MinY || PosY >= MaxY) continue; - // get index of pixel in glyph bitmap + // get index of pixel in glyph bitmap const int32 SourceIndex = (GlyphY * GlyphPitch) + GlyphX; int32 DestIndex = (PosY * SplashWidth + PosX) * SplashBPP; @@ -258,20 +272,27 @@ void FLinuxSplashState::RenderStrings() const int SplashWidth = SplashSurface->w; const int SplashHeight = SplashSurface->h; const int SplashBPP = SplashSurface->format->BytesPerPixel; + unsigned char *SplashPixels = reinterpret_cast(SplashSurface->pixels); // clear the rendering scratch pad. - FMemory::Memcpy(ScratchSpace, SplashSurface->pixels, SplashWidth*SplashHeight*SplashBPP); + for (int y = 0; y < SplashHeight; ++y) + { + unsigned char *Src = SplashPixels + y * SplashSurface->pitch; + unsigned char *Dst = ScratchSpace + y * SplashWidth * SplashBPP; + + FMemory::Memcpy(Dst, Src, SplashWidth * SplashBPP); + } // draw each type of string for (int CurTypeIndex=0; CurTypeIndexglyph->format != FT_GLYPH_FORMAT_BITMAP) { FT_Render_Glyph(Font->glyph, FT_RENDER_MODE_NORMAL); @@ -339,22 +359,22 @@ void FLinuxSplashState::RenderStrings() { FT_Get_Kerning(Font, GlyphIndex, LastGlyph, FT_KERNING_DEFAULT, &Kerning); } - - PenX -= (Font->glyph->metrics.horiAdvance - Kerning.x) >> 6; + + PenX -= (Font->glyph->metrics.horiAdvance - Kerning.x) >> 6; } else { if (LastGlyph != 0) { FT_Get_Kerning(Font, LastGlyph, GlyphIndex, FT_KERNING_DEFAULT, &Kerning); - } + } } LastGlyph = GlyphIndex; // draw character DrawCharacter(PenX, PenY, Font->glyph, CurTypeIndex, Red, Green, Blue); - + if (!bRightJustify) { PenX += (Font->glyph->metrics.horiAdvance - Kerning.x) >> 6; @@ -367,6 +387,9 @@ void FLinuxSplashState::RenderStrings() } } +#endif // WITH_EDITOR + + /** * @brief Helper function to load an image in any format. * @@ -376,6 +399,7 @@ void FLinuxSplashState::RenderStrings() */ SDL_Surface* FLinuxSplashState::LoadImage(const FString &ImagePath) { +#if WITH_EDITOR TArray RawFileData; // Load the image buffer first (unless it's BMP) @@ -402,23 +426,13 @@ SDL_Surface* FLinuxSplashState::LoadImage(const FString &ImagePath) } } } +#endif // WITH_EDITOR // If for some reason the image cannot be loaded, use the default BMP function return SDL_LoadBMP(TCHAR_TO_UTF8(*ImagePath)); } -// This class helps cleans up SDL_Surfaces when they go out of scope. -class SdlSurfacePtr -{ -public: - SdlSurfacePtr(SDL_Surface *InSurface) : Surface(InSurface) {} - ~SdlSurfacePtr() { if (Surface) { SDL_FreeSurface(Surface); } } -private: - SDL_Surface *Surface; -}; - - /** Helper function to init resources used by the splash window */ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString &SplashPath, const FString &IconPath) { @@ -448,16 +462,11 @@ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString return -1; } - SDL_Surface *SplashIconImage = LoadImage(IconPath); - SdlSurfacePtr SplashIconPtr(SplashIconImage); - if (SplashIconImage == nullptr) - { - UE_LOG(LogHAL, Warning, TEXT("FLinuxSplashState::InitSplashResources() : Splash icon could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); - } - +#if WITH_EDITOR // Just in case SDL's renderer steps on GL state... SDL_Window *CurrentWindow = SDL_GL_GetCurrentWindow(); SDL_GLContext CurrentContext = SDL_GL_GetCurrentContext(); +#endif // on modern X11, your windows might turn gray if they don't pump the event queue fast enough. // But this is because they opt-in to an optional window manager protocol by default; legacy @@ -482,17 +491,31 @@ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString return false; } - if (SplashIconImage != nullptr) + if (!IconPath.IsEmpty()) { - SDL_SetWindowIcon(SplashWindow, SplashIconImage); // SDL_SetWindowIcon() makes a copy of this. + SDL_Surface *SplashIconImage = LoadImage(IconPath); + + if (SplashIconImage == nullptr) + { + UE_LOG(LogHAL, Warning, TEXT("FLinuxSplashState::InitSplashResources() : Splash icon could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); + } + else + { + SDL_SetWindowIcon(SplashWindow, SplashIconImage); // SDL_SetWindowIcon() makes a copy of this. + SDL_FreeSurface(SplashIconImage); + } } +#if WITH_EDITOR SplashRenderer = SDL_CreateRenderer(SplashWindow, -1, 0); +#endif // it's safe to set the hint back once the Renderer is created (since it might recreate the window to get a GL or whatever visual). SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_PING, OriginalHint ? OriginalHint : "1"); SDL_free(OriginalHint); +#if WITH_EDITOR + if (SplashRenderer == nullptr) { UE_LOG(LogHAL, Error, TEXT("FLinuxSplashState::InitSplashResources() : Splash screen renderer could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); @@ -554,9 +577,27 @@ bool FLinuxSplashState::InitSplashResources(const FText &AppName, const FString SDL_GL_MakeCurrent(CurrentWindow, CurrentContext); } +#else + + SDL_Surface *WindowSurface = SDL_GetWindowSurface( SplashWindow ); + if (WindowSurface == nullptr) + { + UE_LOG(LogHAL, Error, TEXT("FLinuxSplashState::InitSplashResources() : Splash window surface could not be created! SDL_Error: %s"), UTF8_TO_TCHAR(SDL_GetError())); + return false; + } + + SDL_BlitSurface(SplashSurface, NULL, WindowSurface, NULL); + SDL_FreeSurface(WindowSurface); + + SDL_ShowWindow(SplashWindow); + Pump(); + +#endif + return true; } + /** * Sets the text displayed on the splash screen (for startup/loading progress) * @@ -574,8 +615,11 @@ void FLinuxSplashState::SetSplashText( const SplashTextType::Type InType, const #endif } + +//--------------------------------------------------------- void FLinuxSplashState::Redraw() { +#if WITH_EDITOR if (bNeedsRedraw || bStringsChanged) { // Just in case SDL's renderer steps on GL state... @@ -592,8 +636,16 @@ void FLinuxSplashState::Redraw() SDL_GL_MakeCurrent(CurrentWindow, CurrentContext); } } +#else + if (bNeedsRedraw) + { + SDL_UpdateWindowSurface(SplashWindow); + bNeedsRedraw = false; + } +#endif } + void FLinuxSplashState::Pump() { if (SplashWindow) @@ -606,10 +658,10 @@ void FLinuxSplashState::Pump() bNeedsRedraw = true; } } + Redraw(); } } -#endif //WITH_EDITOR /** @@ -618,9 +670,8 @@ void FLinuxSplashState::Pump() */ void FLinuxPlatformSplash::Show( ) { -#if WITH_EDITOR // need to do a splash screen? - if(GSplashState || FParse::Param(FCommandLine::Get(),TEXT("NOSPLASH")) == true) + if(GSplashState || !FApp::CanEverRender() || FParse::Param(FCommandLine::Get(), TEXT("NOSPLASH")) == true) { return; } @@ -672,7 +723,7 @@ void FLinuxPlatformSplash::Show( ) if (!bIconFound) { UE_LOG(LogHAL, Warning, TEXT("Game icon not found.")); - return; // early out + IconPath.Reset(); } } @@ -686,7 +737,7 @@ void FLinuxPlatformSplash::Show( ) // In the editor, we'll display loading info FText AppName; - if( GIsEditor ) + if ( GIsEditor ) { // Set initial startup progress info { @@ -725,7 +776,6 @@ void FLinuxPlatformSplash::Show( ) delete GSplashState; GSplashState = nullptr; } -#endif //WITH_EDITOR } @@ -734,10 +784,8 @@ void FLinuxPlatformSplash::Show( ) */ void FLinuxPlatformSplash::Hide() { -#if WITH_EDITOR delete GSplashState; GSplashState = nullptr; -#endif } @@ -749,7 +797,6 @@ void FLinuxPlatformSplash::Hide() */ void FLinuxPlatformSplash::SetSplashText( const SplashTextType::Type InType, const TCHAR* InText ) { -#if WITH_EDITOR if (GSplashState) { // We only want to bother drawing startup progress in the editor, since this information is @@ -760,6 +807,4 @@ void FLinuxPlatformSplash::SetSplashText( const SplashTextType::Type InType, con } GSplashState->Pump(); } - -#endif //WITH_EDITOR } diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp index 38b99382149e..777676fdbcf9 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxWindow.cpp @@ -529,6 +529,11 @@ void FLinuxWindow::ReshapeWindow( int32 NewX, int32 NewY, int32 NewWidth, int32 } } + // If we have set our self to 0,0 Width/Height it will not be allowed we will still show the window + // this is a work around to at least reduce the visibile impact of a window that is lingering + NewWidth = FMath::Max(NewWidth, 1); + NewHeight = FMath::Max(NewHeight, 1); + // X11 will take until the next frame to send a SizeChanged event. This means the X11 window // will most likely have resized already by the time we render but the slate renderer will // not have been updated leading to an incorrect frame. @@ -580,7 +585,8 @@ void FLinuxWindow::ReshapeWindow( int32 NewX, int32 NewY, int32 NewWidth, int32 VirtualWidth = NewWidth; VirtualHeight = NewHeight; - if ( LinuxWindow ) + // Avoid broadcasting we have set a zero size as it will attempt to resize the backbuffer which on some RHI is invalid per the spec (ie. Vulkan) + if (LinuxWindow ) { OwningApplication->GetMessageHandler()->OnSizeChanged( LinuxWindow.ToSharedRef(), @@ -718,7 +724,7 @@ bool FLinuxWindow::IsMaximized() const /** @return true if the native window is minimized, false otherwise */ bool FLinuxWindow::IsMinimized() const { - return SDL_GetWindowFlags(HWnd) & SDL_WINDOW_MINIMIZED; + return SDL_GetWindowFlags(HWnd) & SDL_WINDOW_MINIMIZED || SDL_GetWindowFlags(HWnd) & SDL_WINDOW_HIDDEN; } /** @return true if the native window is visible, false otherwise */ diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIABaseProvider.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIABaseProvider.cpp new file mode 100644 index 000000000000..d13a767335fb --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIABaseProvider.cpp @@ -0,0 +1,55 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIABaseProvider.h" + +#include "Misc/AssertionMacros.h" +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +FWindowsUIABaseProvider::FWindowsUIABaseProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : UIAManager(&InManager) + , Widget(InWidget) + , RefCount(1) +{ + // Register with the UIA Manager in order to receive OnUIAManagerDestroyed signal. + UIAManager->ProviderList.Add(this); +} + +FWindowsUIABaseProvider::~FWindowsUIABaseProvider() +{ + if (UIAManager) + { + UIAManager->ProviderList.Remove(this); + } +} + +void FWindowsUIABaseProvider::OnUIAManagerDestroyed() +{ + UIAManager = nullptr; +} + +uint32 FWindowsUIABaseProvider::IncrementRef() +{ + // todo: check if this needs to be threadsafe using InterlockedIncrement + return ++RefCount; +} + +uint32 FWindowsUIABaseProvider::DecrementRef() +{ + ensure(RefCount > 0); + if (--RefCount == 0) + { + delete this; + return 0; + } + return RefCount; +} + +bool FWindowsUIABaseProvider::IsValid() const +{ + return UIAManager != nullptr && Widget->IsValid(); +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAControlProvider.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAControlProvider.cpp new file mode 100644 index 000000000000..d2353dba787c --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAControlProvider.cpp @@ -0,0 +1,700 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAControlProvider.h" +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +// FWindowsUIATextRangeProvider + +FWindowsUIATextRangeProvider::FWindowsUIATextRangeProvider(FWindowsUIAManager& InManager, TSharedRef InWidget, FTextRange InRange) + : FWindowsUIABaseProvider(InManager, InWidget) + , TextRange(InRange) +{ +} + +FWindowsUIATextRangeProvider::~FWindowsUIATextRangeProvider() +{ +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + *ppInterface = nullptr; + + if (riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(ITextRangeProvider)) + { + *ppInterface = static_cast(this); + } + + if (*ppInterface) + { + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +FString FWindowsUIATextRangeProvider::TextFromTextRange() +{ + return TextFromTextRange(Widget->AsText()->GetText(), TextRange); +} + +FString FWindowsUIATextRangeProvider::TextFromTextRange(const FString& InString, const FTextRange& InRange) +{ + return InString.Mid(InRange.BeginIndex, InRange.EndIndex); +} + +ULONG STDCALL FWindowsUIATextRangeProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIATextRangeProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Clone(ITextRangeProvider** pRetVal) +{ + if (IsValid()) + { + *pRetVal = new FWindowsUIATextRangeProvider(*UIAManager, Widget, TextRange); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Compare(ITextRangeProvider* range, BOOL* pRetVal) +{ + // The documentation states that different endpoints that produce the same text are not equal, + // but doesn't say anything about the same endpoints that come from different control providers. + // Perhaps we can assume that comparing text ranges from different Widgets is not valid. + *pRetVal = TextRange == static_cast(range)->TextRange; + return S_OK; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint, int* pRetVal) +{ + FWindowsUIATextRangeProvider* CastedRange = static_cast(targetRange); + int32 ThisEndpointIndex = (endpoint == TextPatternRangeEndpoint_Start) ? TextRange.BeginIndex : TextRange.EndIndex; + int32 OtherEndpointIndex = (targetEndpoint == TextPatternRangeEndpoint_Start) ? CastedRange->TextRange.BeginIndex : CastedRange->TextRange.EndIndex; + *pRetVal = ThisEndpointIndex - OtherEndpointIndex; + return S_OK; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::ExpandToEnclosingUnit(TextUnit unit) +{ + if (IsValid()) + { + switch (unit) + { + case TextUnit_Character: + TextRange.EndIndex = FMath::Min(TextRange.BeginIndex + 1, Widget->AsText()->GetText().Len()); + break; + case TextUnit_Format: + return E_NOTIMPL; + case TextUnit_Word: + return E_NOTIMPL; + case TextUnit_Line: + return E_NOTIMPL; + case TextUnit_Paragraph: + return E_NOTIMPL; + case TextUnit_Page: + return E_NOTIMPL; + case TextUnit_Document: + TextRange = FTextRange(0, Widget->AsText()->GetText().Len()); + break; + } + + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::FindAttribute(TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, ITextRangeProvider** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::FindText(BSTR text, BOOL backward, BOOL ignoreCase, ITextRangeProvider** pRetVal) +{ + if (IsValid()) + { + FString TextToSearch(text); + int32 FoundIndex = TextFromTextRange().Find(TextToSearch, ignoreCase ? ESearchCase::IgnoreCase : ESearchCase::CaseSensitive, backward ? ESearchDir::FromEnd : ESearchDir::FromStart); + if (FoundIndex == INDEX_NONE) + { + *pRetVal = nullptr; + } + else + { + const int32 StartIndex = TextRange.BeginIndex + FoundIndex; + *pRetVal = new FWindowsUIATextRangeProvider(*UIAManager, Widget, FTextRange(StartIndex, StartIndex + TextToSearch.Len())); + } + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetAttributeValue(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetBoundingRectangles(SAFEARRAY** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetEnclosingElement(IRawElementProviderSimple** pRetVal) +{ + if (IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Widget)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetText(int maxLength, BSTR* pRetVal) +{ + if (IsValid()) + { + *pRetVal = SysAllocString(*TextFromTextRange().Left(maxLength)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Move(TextUnit unit, int count, int* pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int* pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint) +{ + FWindowsUIATextRangeProvider* CastedRange = static_cast(targetRange); + int32 NewIndex = (targetEndpoint == TextPatternRangeEndpoint_Start) ? CastedRange->TextRange.BeginIndex : CastedRange->TextRange.EndIndex; + if (endpoint == TextPatternRangeEndpoint_Start) + { + TextRange.BeginIndex = NewIndex; + if (TextRange.BeginIndex > TextRange.EndIndex) + { + TextRange.EndIndex = TextRange.BeginIndex; + } + } + else + { + TextRange.EndIndex = NewIndex; + if (TextRange.BeginIndex > TextRange.EndIndex) + { + TextRange.BeginIndex = TextRange.EndIndex; + } + } + return S_OK; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::Select() +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::AddToSelection() +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::RemoveFromSelection() +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::ScrollIntoView(BOOL alignToTop) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIATextRangeProvider::GetChildren(SAFEARRAY** pRetVal) +{ + *pRetVal = nullptr; + return S_OK; +} + +// ~ + +// FWindowsUIAControlProvider + +FWindowsUIAControlProvider::FWindowsUIAControlProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : FWindowsUIABaseProvider(InManager, InWidget) +{ +} + +FWindowsUIAControlProvider::~FWindowsUIAControlProvider() +{ +} + +HRESULT STDCALL FWindowsUIAControlProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + *ppInterface = nullptr; + + if (riid == __uuidof(IInvokeProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRangeValueProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(ITextProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IToggleProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IValueProvider)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IWindowProvider)) + { + *ppInterface = static_cast(this); + } + + if (*ppInterface) + { + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG STDCALL FWindowsUIAControlProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIAControlProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +HRESULT STDCALL FWindowsUIAControlProvider::Invoke() +{ + if (IsValid()) + { + Widget->AsActivatable()->Activate(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::SetValue(double val) +{ + if (IsValid()) + { + Widget->AsProperty()->SetValue(FString::SanitizeFloat(val)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Value(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueValuePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_IsReadOnly(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_ValueIsReadOnlyPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Maximum(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueMaximumPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Minimum(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueMinimumPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_LargeChange(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueLargeChangePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_SmallChange(double* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_RangeValueSmallChangePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_DocumentRange(ITextRangeProvider** pRetVal) +{ + if (IsValid()) + { + *pRetVal = static_cast(new FWindowsUIATextRangeProvider(*UIAManager, Widget, FTextRange(0, Widget->AsText()->GetText().Len()))); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_SupportedTextSelection(SupportedTextSelection* pRetVal) +{ + // todo: implement selection + *pRetVal = SupportedTextSelection_None; + return S_OK; +} + +HRESULT STDCALL FWindowsUIAControlProvider::GetSelection(SAFEARRAY** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::GetVisibleRanges(SAFEARRAY** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::RangeFromChild(IRawElementProviderSimple* childElement, ITextRangeProvider** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::RangeFromPoint(UiaPoint point, ITextRangeProvider** pRetVal) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_ToggleState(ToggleState* pRetVal) +{ + //ECheckBoxState CheckState = Widget->AsActivatable()->GetCheckedState(); + //switch (CheckState) + //{ + //case ECheckBoxState::Checked: + // *pRetVal = ToggleState_On; + // break; + //case ECheckBoxState::Unchecked: + // *pRetVal = ToggleState_Off; + // break; + //case ECheckBoxState::Undetermined: + // *pRetVal = ToggleState_Indeterminate; + // break; + //} + if (IsValid()) + { + *pRetVal = static_cast(WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_ToggleToggleStatePropertyId).GetValue()); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::Toggle() +{ + if (IsValid()) + { + Widget->AsActivatable()->Activate(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanMove(BOOL *pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_TransformCanMovePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanResize(BOOL *pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_TransformCanResizePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanRotate(BOOL *pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_TransformCanRotatePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::Move(double x, double y) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::Resize(double width, double height) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::Rotate(double degrees) +{ + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::SetValue(LPCWSTR val) +{ + if (IsValid()) + { + Widget->AsProperty()->SetValue(FString(val)); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_Value(BSTR* pRetVal) +{ + if (IsValid()) + { + *pRetVal = SysAllocString(*WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_ValueValuePropertyId).GetValue()); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +//HRESULT STDCALL FWindowsUIAControlProvider::get_IsReadOnly(BOOL* pRetVal) +//{ +// *pRetVal = Widget->AsProperty()->IsReadOnly(); +// return S_OK; +//} + +HRESULT STDCALL FWindowsUIAControlProvider::Close() +{ + if (IsValid()) + { + Widget->AsWindow()->Close(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanMaximize(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowCanMaximizePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_CanMinimize(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowCanMinimizePropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_IsModal(BOOL* pRetVal) +{ + if (IsValid()) + { + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowIsModalPropertyId).GetValue(); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_IsTopmost(BOOL* pRetVal) +{ + // todo: not 100% sure what this is looking for. top window in hierarchy of child windows? on top of all other windows in Windows OS? + *pRetVal = WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowIsTopmostPropertyId).GetValue(); + return E_NOTIMPL; +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_WindowInteractionState(WindowInteractionState* pRetVal) +{ + if (IsValid()) + { + // todo: do we have a way to identify if the app is processing data vs idling? + *pRetVal = static_cast(WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowWindowInteractionStatePropertyId).GetValue()); + } + else + { + *pRetVal = WindowInteractionState_Closing; + } + return S_OK; +} + +HRESULT STDCALL FWindowsUIAControlProvider::get_WindowVisualState(WindowVisualState* pRetVal) +{ + if (IsValid()) + { + *pRetVal = static_cast(WindowsUIAPropertyGetters::GetPropertyValue(Widget, UIA_WindowWindowVisualStatePropertyId).GetValue()); + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::SetVisualState(WindowVisualState state) +{ + if (IsValid()) + { + switch (state) + { + case WindowVisualState_Normal: + Widget->AsWindow()->SetDisplayState(IAccessibleWindow::EWindowDisplayState::Normal); + break; + case WindowVisualState_Minimized: + Widget->AsWindow()->SetDisplayState(IAccessibleWindow::EWindowDisplayState::Minimize); + break; + case WindowVisualState_Maximized: + Widget->AsWindow()->SetDisplayState(IAccessibleWindow::EWindowDisplayState::Maximize); + break; + } + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAControlProvider::WaitForInputIdle(int milliseconds, BOOL* pRetVal) +{ + return E_NOTIMPL; +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAManager.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAManager.cpp new file mode 100644 index 000000000000..9574d151a98a --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAManager.cpp @@ -0,0 +1,208 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAManager.h" + +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Windows/HideWindowsPlatformTypes.h" +#include + +#include "Misc/Variant.h" +#include "Windows/Accessibility/WindowsUIAControlProvider.h" +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" +#include "Windows/WindowsApplication.h" +#include "Windows/WindowsWindow.h" + +TMap FWindowsUIAManager::WidgetTypeToWindowsTypeMap; +TMap FWindowsUIAManager::WidgetTypeToTextMap; + +#define LOCTEXT_NAMESPACE "SlateAccessibility" + +/** + * Helper function to raise a UIA property changed event. + */ +void EmitPropertyChangedEvent(FWindowsUIAWidgetProvider* Provider, PROPERTYID Property, const FVariant& OldValue, const FVariant& NewValue) +{ + UE_LOG(LogAccessibility, VeryVerbose, TEXT("UIA Property Changed: %i"), Property); + UiaRaiseAutomationPropertyChangedEvent(static_cast(Provider), Property, + WindowsUIAPropertyGetters::FVariantToWindowsVariant(OldValue), + WindowsUIAPropertyGetters::FVariantToWindowsVariant(NewValue)); +} + +FWindowsUIAManager::FWindowsUIAManager(const FWindowsApplication& InApplication) + : WindowsApplication(InApplication) +{ + OnAccessibleMessageHandlerChanged(); + + if (WidgetTypeToWindowsTypeMap.Num() == 0) + { + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Button, UIA_ButtonControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::CheckBox, UIA_CheckBoxControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::ComboBox, UIA_ComboBoxControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Hyperlink, UIA_HyperlinkControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Image, UIA_ImageControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Layout, UIA_PaneControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::ScrollBar, UIA_ScrollBarControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Slider, UIA_SliderControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Text, UIA_TextControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::TextEdit, UIA_EditControlTypeId); + WidgetTypeToWindowsTypeMap.Add(EAccessibleWidgetType::Window, UIA_WindowControlTypeId); + + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Button, LOCTEXT("ControlTypeButton", "button")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::CheckBox, LOCTEXT("ControlTypeCheckBox", "check box")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::ComboBox, LOCTEXT("ControlTypeComboBox", "combobox")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Hyperlink, LOCTEXT("ControlTypeHyperlink", "hyperlink")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Image, LOCTEXT("ControlTypeImage", "image")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Layout, LOCTEXT("ControlTypeLayout", "pane")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::ScrollBar, LOCTEXT("ControlTypeScrollBar", "scroll bar")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Slider, LOCTEXT("ControlTypeSlider", "slider")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Text, LOCTEXT("ControlTypeText", "text")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::TextEdit, LOCTEXT("ControlTypeTextEdit", "edit")); + WidgetTypeToTextMap.Add(EAccessibleWidgetType::Window, LOCTEXT("ControlTypeWindow", "window")); + } +} + +void FWindowsUIAManager::OnAccessibleMessageHandlerChanged() +{ + WindowsApplication.GetAccessibleMessageHandler()->SetAccessibleEventDelegate(FGenericAccessibleMessageHandler::FAccessibleEvent::CreateRaw(this, &FWindowsUIAManager::OnEventRaised)); +} + +FWindowsUIAManager::~FWindowsUIAManager() +{ + WindowsApplication.GetAccessibleMessageHandler()->SetAccessibleEventDelegate(FGenericAccessibleMessageHandler::FAccessibleEvent()); + for (FWindowsUIABaseProvider* Provider : ProviderList) + { + // The UIA Manager may be deleted before the Providers are, and external applications may attempt to call functions on those Providers. + Provider->OnUIAManagerDestroyed(); + } +} + +FWindowsUIAWidgetProvider& FWindowsUIAManager::GetWidgetProvider(TSharedRef InWidget) +{ + FWindowsUIAWidgetProvider** CachedProvider = CachedWidgetProviders.Find(InWidget); + if (CachedProvider) + { + (*CachedProvider)->AddRef(); + return **CachedProvider; + } + else if (InWidget->AsWindow()) + { + return *CachedWidgetProviders.Add(InWidget, new FWindowsUIAWindowProvider(*this, InWidget)); + } + else + { + return *CachedWidgetProviders.Add(InWidget, new FWindowsUIAWidgetProvider(*this, InWidget)); + } +} + +FWindowsUIAWindowProvider& FWindowsUIAManager::GetWindowProvider(TSharedRef InWindow) +{ + if (CachedWidgetProviders.Num() == 0) + { + // Enable application accessibility if this is the first Provider. The first Get*Provider() request + // MUST go through this function since IAccessibleWidgets will not exist until SetActive is called. + WindowsApplication.GetAccessibleMessageHandler()->SetActive(true); + } + + return static_cast(GetWidgetProvider(WindowsApplication.GetAccessibleMessageHandler()->GetAccessibleWindow(InWindow).ToSharedRef())); +} + +void FWindowsUIAManager::OnWidgetProviderRemoved(TSharedRef InWidget) +{ + CachedWidgetProviders.Remove(InWidget); + + if (CachedWidgetProviders.Num() == 0) + { + // If the last widget Provider is being removed, we can disable application accessibility. + // Technically an external application could still be running listening for mouse/keyboard events, + // but in practice I don't think its realistic to do this while having no references to any Provider. + // + // Note that there could still be control Providers with valid references. In practice I don't think + // this is possible, but if we run into problems then we can simply call AddRef and Release on the + // underlying widget Provider whenever a control Provider gets added/removed. + WindowsApplication.GetAccessibleMessageHandler()->SetActive(false); + } +} + +void FWindowsUIAManager::OnEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) +{ + if (UiaClientsAreListening()) + { + FScopedWidgetProvider ScopedProvider(GetWidgetProvider(Widget)); + + switch (Event) + { + case EAccessibleEvent::FocusChange: + { + // On focus change, emit a generic FocusChanged event as well as a per-Provider PropertyChanged event + // todo: handle difference between any focus vs keyboard focus + if (Widget->HasFocus()) + { + UiaRaiseAutomationEvent(&ScopedProvider.Provider, UIA_AutomationFocusChangedEventId); + } + EmitPropertyChangedEvent(&ScopedProvider.Provider, UIA_HasKeyboardFocusPropertyId, OldValue, NewValue); + break; + } + case EAccessibleEvent::Activate: + if (ScopedProvider.Provider.SupportsInterface(UIA_TogglePatternId)) + { + EmitPropertyChangedEvent(&ScopedProvider.Provider, UIA_ToggleToggleStatePropertyId, OldValue, NewValue); + + } + else if (ScopedProvider.Provider.SupportsInterface(UIA_InvokePatternId)) + { + UiaRaiseAutomationEvent(&ScopedProvider.Provider, UIA_Invoke_InvokedEventId); + } + break; + case EAccessibleEvent::Notification: + { + typedef HRESULT(WINAPI* UiaRaiseNotificationEventFunc)(IRawElementProviderSimple*, NotificationKind, NotificationProcessing, BSTR, BSTR); + HMODULE Handle = GetModuleHandle(TEXT("Uiautomationcore.dll")); + if (Handle) + { + // Cast to intermediary void* to avoid compiler warning 4191, since GetProcAddress doesn't know function arguments + UiaRaiseNotificationEventFunc NotificationFunc = (UiaRaiseNotificationEventFunc)(void*)GetProcAddress(Handle, "UiaRaiseNotificationEvent"); + if (NotificationFunc) + { + NotificationFunc(&ScopedProvider.Provider, NotificationKind_ActionCompleted, NotificationProcessing_All, SysAllocString(*NewValue.GetValue()), SysAllocString(TEXT(""))); + } + } + break; + } + case EAccessibleEvent::BeforeRemoveFromParent: + { + // ChildRemoved events require an ID passed along with them to identify which child (which no longer exists) was removed + int id[2] = { UiaAppendRuntimeId, Widget->GetId() }; + FScopedWidgetProvider ParentProvider(GetWidgetProvider(Widget->GetParent().ToSharedRef())); + UiaRaiseStructureChangedEvent(&ParentProvider.Provider, StructureChangeType_ChildRemoved, id, ARRAYSIZE(id)); + break; + } + case EAccessibleEvent::AfterAddToParent: + { + FScopedWidgetProvider ParentProvider(GetWidgetProvider(Widget->GetParent().ToSharedRef())); + UiaRaiseStructureChangedEvent(&ParentProvider.Provider, StructureChangeType_ChildAdded, nullptr, 0); + break; + } + case EAccessibleEvent::WidgetRemoved: + typedef HRESULT(WINAPI* UiaDisconnectProviderFunc)(IRawElementProviderSimple*); + HMODULE Handle = GetModuleHandle(TEXT("Uiautomationcore.dll")); + if (Handle) + { + // Cast to intermediary void* to avoid compiler warning 4191, since GetProcAddress doesn't know function arguments + UiaDisconnectProviderFunc DisconnectFunc = (UiaDisconnectProviderFunc)(void*)GetProcAddress(Handle, "UiaDisconnectProvider"); + if (DisconnectFunc) + { + DisconnectFunc(&ScopedProvider.Provider); + } + } + break; + } + } +} + +#undef LOCTEXT_NAMESPACE + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAPropertyGetters.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAPropertyGetters.cpp new file mode 100644 index 000000000000..8f9c0f8e92a6 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAPropertyGetters.cpp @@ -0,0 +1,106 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" + +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "Misc/Variant.h" + +namespace WindowsUIAPropertyGetters +{ + +VARIANT GetPropertyValueWindows(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId) +{ + return FVariantToWindowsVariant(GetPropertyValue(AccessibleWidget, WindowsPropertyId)); +} + +FVariant GetPropertyValue(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId) +{ + switch (WindowsPropertyId) + { + case UIA_ValueIsReadOnlyPropertyId: + case UIA_RangeValueIsReadOnlyPropertyId: + return AccessibleWidget->AsProperty()->IsReadOnly(); + case UIA_RangeValueLargeChangePropertyId: + case UIA_RangeValueSmallChangePropertyId: + return static_cast(AccessibleWidget->AsProperty()->GetStepSize()); + case UIA_RangeValueMaximumPropertyId: + return static_cast(AccessibleWidget->AsProperty()->GetMaximum()); + case UIA_RangeValueMinimumPropertyId: + return static_cast(AccessibleWidget->AsProperty()->GetMinimum()); + case UIA_RangeValueValuePropertyId: + return static_cast(FCString::Atof(*AccessibleWidget->AsProperty()->GetValue())); + case UIA_ToggleToggleStatePropertyId: + return static_cast(AccessibleWidget->AsActivatable()->GetCheckedState() ? ToggleState_On : ToggleState_Off); + case UIA_TransformCanMovePropertyId: + return false; + case UIA_TransformCanResizePropertyId: + return false; + case UIA_TransformCanRotatePropertyId: + return false; + case UIA_ValueValuePropertyId: + return AccessibleWidget->AsProperty()->GetValue(); + case UIA_WindowCanMaximizePropertyId: + return AccessibleWidget->AsWindow()->SupportsDisplayState(IAccessibleWindow::EWindowDisplayState::Maximize); + case UIA_WindowCanMinimizePropertyId: + return AccessibleWidget->AsWindow()->SupportsDisplayState(IAccessibleWindow::EWindowDisplayState::Minimize); + case UIA_WindowIsModalPropertyId: + return AccessibleWidget->AsWindow()->IsModal(); + case UIA_WindowIsTopmostPropertyId: + return false; + case UIA_WindowWindowInteractionStatePropertyId: + return static_cast(WindowInteractionState_Running); + case UIA_WindowWindowVisualStatePropertyId: + switch (AccessibleWidget->AsWindow()->GetDisplayState()) + { + case IAccessibleWindow::EWindowDisplayState::Normal: + return static_cast(WindowVisualState_Normal); + case IAccessibleWindow::EWindowDisplayState::Minimize: + return static_cast(WindowVisualState_Minimized); + case IAccessibleWindow::EWindowDisplayState::Maximize: + return static_cast(WindowVisualState_Maximized); + } + } + + return FVariant(); +} + +VARIANT FVariantToWindowsVariant(const FVariant& Value) +{ + VARIANT OutValue; + switch (Value.GetType()) + { + case EVariantTypes::Bool: + OutValue.vt = VT_BOOL; + OutValue.boolVal = Value.GetValue() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case EVariantTypes::Double: + OutValue.vt = VT_R8; + OutValue.dblVal = Value.GetValue(); + break; + case EVariantTypes::Float: + OutValue.vt = VT_R4; + OutValue.fltVal = Value.GetValue(); + break; + case EVariantTypes::Int32: + OutValue.vt = VT_I4; + // todo: verify correct behavior on different operating systems + OutValue.intVal = Value.GetValue(); + break; + case EVariantTypes::String: + OutValue.vt = VT_BSTR; + OutValue.bstrVal = SysAllocString(*Value.GetValue()); + break; + case EVariantTypes::Empty: + default: + OutValue.vt = VT_EMPTY; + break; + } + + return OutValue; +} + +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAWidgetProvider.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAWidgetProvider.cpp new file mode 100644 index 000000000000..33cd18f4c1e0 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/Accessibility/WindowsUIAWidgetProvider.cpp @@ -0,0 +1,666 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" + +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "Windows/Accessibility/WindowsUIAControlProvider.h" +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "Windows/Accessibility/WindowsUIAPropertyGetters.h" +#include "Windows/WindowsWindow.h" + +DECLARE_CYCLE_STAT(TEXT("Windows Accessibility: Navigate"), STAT_AccessibilityWindowsNavigate, STATGROUP_Accessibility); +DECLARE_CYCLE_STAT(TEXT("Windows Accessibility: GetProperty"), STAT_AccessibilityWindowsGetProperty, STATGROUP_Accessibility); + +#define LOCTEXT_NAMESPACE "SlateAccessibility" + +/** Convert our accessible widget type to a Windows control ID */ +ULONG WidgetTypeToControlType(TSharedRef Widget) +{ + ULONG* Type = FWindowsUIAManager::WidgetTypeToWindowsTypeMap.Find(Widget->GetWidgetType()); + if (Type) + { + return *Type; + } + else + { + return UIA_CustomControlTypeId; + } +} + +/** + * Convert our accessible widget type to a human-readable localized string + * See https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-automation-element-propids for rules. + */ +FString WidgetTypeToLocalizedString(TSharedRef Widget) +{ + FText* Text = FWindowsUIAManager::WidgetTypeToTextMap.Find(Widget->GetWidgetType()); + if (Text) + { + return Text->ToString(); + } + else + { + static FText CustomControlTypeName = LOCTEXT("ControlTypeCustom", "custom"); + return CustomControlTypeName.ToString(); + } +} + +// FWindowsUIAWidgetProvider methods + +FWindowsUIAWidgetProvider::FWindowsUIAWidgetProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : FWindowsUIABaseProvider(InManager, InWidget) +{ + //UpdateCachedProperties(); +} + +FWindowsUIAWidgetProvider::~FWindowsUIAWidgetProvider() +{ + if (UIAManager) + { + UIAManager->OnWidgetProviderRemoved(Widget); + } +} + +//void FWindowsUIAWidgetProvider::UpdateCachedProperty(PROPERTYID PropertyId) +//{ +// FVariant& CachedValue = CachedPropertyValues.FindOrAdd(PropertyId); +// FVariant CurrentValue = WindowsUIAPropertyGetters::GetPropertyValue(Widget, PropertyId); +// if (CachedValue.IsEmpty()) +// { +// CachedValue = CurrentValue; +// } +// else if (CachedValue != CurrentValue) +// { +// UE_LOG(LogAccessibility, VeryVerbose, TEXT("UIA Property Changed: %i"), PropertyId); +// UiaRaiseAutomationPropertyChangedEvent( +// static_cast(this), PropertyId, +// WindowsUIAPropertyGetters::FVariantToWindowsVariant(CachedValue), +// WindowsUIAPropertyGetters::FVariantToWindowsVariant(CurrentValue)); +// +// CachedValue = CurrentValue; +// } +//} +// +//void FWindowsUIAWidgetProvider::UpdateCachedProperties() +//{ +// if (IsValid()) +// { +// if (SupportsInterface(UIA_RangeValuePatternId)) +// { +// UpdateCachedProperty(UIA_RangeValueIsReadOnlyPropertyId); +// UpdateCachedProperty(UIA_RangeValueValuePropertyId); +// UpdateCachedProperty(UIA_RangeValueIsReadOnlyPropertyId); +// UpdateCachedProperty(UIA_RangeValueMinimumPropertyId); +// UpdateCachedProperty(UIA_RangeValueMaximumPropertyId); +// UpdateCachedProperty(UIA_RangeValueLargeChangePropertyId); +// UpdateCachedProperty(UIA_RangeValueSmallChangePropertyId); +// } +// +// if (SupportsInterface(UIA_TogglePatternId)) +// { +// UpdateCachedProperty(UIA_ToggleToggleStatePropertyId); +// } +// +// if (SupportsInterface(UIA_ValuePatternId)) +// { +// UpdateCachedProperty(UIA_ValueIsReadOnlyPropertyId); +// UpdateCachedProperty(UIA_ValueValuePropertyId); +// } +// +// if (SupportsInterface(UIA_WindowPatternId)) +// { +// UpdateCachedProperty(UIA_WindowCanMaximizePropertyId); +// UpdateCachedProperty(UIA_WindowCanMinimizePropertyId); +// UpdateCachedProperty(UIA_WindowIsModalPropertyId); +// UpdateCachedProperty(UIA_WindowIsTopmostPropertyId); +// UpdateCachedProperty(UIA_WindowWindowInteractionStatePropertyId); +// UpdateCachedProperty(UIA_WindowWindowVisualStatePropertyId); +// UpdateCachedProperty(UIA_TransformCanMovePropertyId); +// UpdateCachedProperty(UIA_TransformCanRotatePropertyId); +// UpdateCachedProperty(UIA_TransformCanResizePropertyId); +// } +// } +//} + +HRESULT STDCALL FWindowsUIAWidgetProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + if (riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRawElementProviderSimple)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRawElementProviderFragment)) + { + *ppInterface = static_cast(this); + } + else + { + *ppInterface = nullptr; + } + + if (*ppInterface) + { + // QueryInterface is the one exception where we need to call AddRef without going through GetWidgetProvider(). + AddRef(); + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +ULONG STDCALL FWindowsUIAWidgetProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIAWidgetProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +bool FWindowsUIAWidgetProvider::SupportsInterface(PATTERNID PatternId) const +{ + switch (PatternId) + { + case UIA_InvokePatternId: + { + IAccessibleActivatable* Activatable = Widget->AsActivatable(); + // Toggle and Invoke are mutually exclusive + return Activatable && !Activatable->IsCheckable(); + } + case UIA_RangeValuePatternId: + { + IAccessibleProperty* Property = Widget->AsProperty(); + // Value and RangeValue are mutually exclusive + return Property && Property->GetStepSize() > 0.0f; + } + case UIA_TextPatternId: + { + return Widget->AsText() != nullptr; + } + case UIA_TogglePatternId: + { + IAccessibleActivatable* Activatable = Widget->AsActivatable(); + return Activatable && Activatable->IsCheckable(); + } + case UIA_ValuePatternId: + { + IAccessibleProperty* Property = Widget->AsProperty(); + return Property && FMath::IsNearlyZero(Property->GetStepSize()); + } + } + return false; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_ProviderOptions(ProviderOptions* pRetVal) +{ + // ServerSideProvider means that we are creating the definition of the accessible widgets for Clients (eg screenreaders) to consume. + // UseComThreading is necessary to ensure that COM messages are properly routed to the main thread. + *pRetVal = static_cast(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading); + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) +{ + if (IsValid()) + { + if (SupportsInterface(patternId)) + { + // FWindowsUIAControlProvider implements all possible control providers that we support. + FWindowsUIAControlProvider* ControlProvider = new FWindowsUIAControlProvider(*UIAManager, Widget); + switch (patternId) + { + case UIA_InvokePatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_RangeValuePatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_TextPatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_TogglePatternId: + *pRetVal = static_cast(ControlProvider); + break; + case UIA_ValuePatternId: + *pRetVal = static_cast(ControlProvider); + break; + default: + UE_LOG(LogAccessibility, Error, TEXT("FWindowsUIAWidgetProvider::SupportsInterface() returned true, but was unhandled in GetPatternProvider(). PatternId = %i"), patternId); + *pRetVal = nullptr; + ControlProvider->Release(); + break; + } + } + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal) +{ + SCOPE_CYCLE_COUNTER(STAT_AccessibilityWindowsGetProperty); + + if (!IsValid()) + { + return UIA_E_ELEMENTNOTAVAILABLE; + } + + bool bValid = true; + + // https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-automation-element-propids + // potential other properties: + // - UIA_CenterPointPropertyId + // - UIA_ControllerForPropertyId (eg. to make clicking on a label also click a checkbox) + // - UIA_DescribedByPropertyId (same as LabeledBy? not sure) + // - UIA_IsDialogPropertyId (doesn't seem to exist in our version of UIA) + // - UIA_ItemStatusPropertyId (eg. busy, loading) + // - UIA_LabeledByPropertyId (eg. to describe a checkbox from a separate label) + // - UIA_NativeWindowHandlePropertyId (unclear if this is only for windows or all widgets) + // - UIA_PositionInSetPropertyId (position in list of items, could be useful) + // - UIA_SizeOfSetPropertyId (number of items in list, could be useful) + switch (propertyId) + { + //case UIA_AcceleratorKeyPropertyId: + // pRetVal->vt = VT_BSTR; + // // todo: hotkey, used with IInvokeProvider + // pRetVal->bstrVal = SysAllocString(L""); + // break; + //case UIA_AccessKeyPropertyId: + // pRetVal->vt = VT_BSTR; + // // todo: activates menu, like F in &File + // pRetVal->bstrVal = SysAllocString(L""); + // break; + //case UIA_AutomationIdPropertyId: + // pRetVal->vt = VT_BSTR; + // // todo: an identifiable name for the widget for automation testing, used like App.Find("MainMenuBar") + // pRetVal->bstrVal = SysAllocString(L""); + // break; + case UIA_BoundingRectanglePropertyId: + pRetVal->vt = VT_R8 | VT_ARRAY; + pRetVal->parray = SafeArrayCreateVector(VT_R8, 0, 4); + if (pRetVal->parray) + { + FBox2D Box = Widget->GetBounds(); + LONG i = 0; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Min.X) != S_OK); + i = 1; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Max.X) != S_OK); + i = 2; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Min.Y) != S_OK); + i = 3; + bValid &= (SafeArrayPutElement(pRetVal->parray, &i, &Box.Max.Y) != S_OK); + } + else + { + bValid = false; + } + break; + case UIA_ClassNamePropertyId: + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(*Widget->GetClassName()); + break; + case UIA_ControlTypePropertyId: + pRetVal->vt = VT_I4; + pRetVal->lVal = WidgetTypeToControlType(Widget); + break; + //case UIA_CulturePropertyId: + // pRetVal->vt = VT_I4; + // // todo: https://docs.microsoft.com/en-us/windows/desktop/Intl/language-identifier-constants-and-strings + // pRetVal->lVal = 0; + // break; + case UIA_FrameworkIdPropertyId: + pRetVal->vt = VT_BSTR; + // todo: figure out what goes here + pRetVal->bstrVal = SysAllocString(*LOCTEXT("Slate", "Slate").ToString()); + break; + case UIA_HasKeyboardFocusPropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->HasFocus() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_HelpTextPropertyId: + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(*Widget->GetHelpText()); + break; + case UIA_IsContentElementPropertyId: + pRetVal->vt = VT_BOOL; + // todo: https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-treeoverview + pRetVal->boolVal = VARIANT_TRUE; + break; + case UIA_IsControlElementPropertyId: + pRetVal->vt = VT_BOOL; + // todo: https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-treeoverview + pRetVal->boolVal = VARIANT_TRUE; + break; + case UIA_IsEnabledPropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->IsEnabled() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_IsKeyboardFocusablePropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->SupportsFocus() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_IsOffscreenPropertyId: + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->IsHidden() ? VARIANT_TRUE : VARIANT_FALSE; + break; + case UIA_IsPasswordPropertyId: + if (Widget->AsProperty()) + { + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = Widget->AsProperty()->IsPassword() ? VARIANT_TRUE : VARIANT_FALSE; + } + break; +//#if WINVER >= 0x0603 // Windows 8.1 +// case UIA_IsPeripheralPropertyId: +// pRetVal->vt = VT_BOOL; +// // todo: see https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-automation-element-propids for list of control types +// pRetVal->boolVal = VARIANT_FALSE; +// break; +//#endif +// case UIA_ItemTypePropertyId: +// pRetVal->vt = VT_BSTR; +// // todo: friendly name of what's in a listview +// pRetVal->bstrVal = SysAllocString(L""); +// break; +//#if WINVER >= 0x0602 // Windows 8 +// case UIA_LiveSettingPropertyId: +// pRetVal->vt = VT_I4; +// // todo: "politeness" setting +// pRetVal->lVal = 0; +// break; +//#endif + case UIA_LocalizedControlTypePropertyId: + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(*WidgetTypeToLocalizedString(Widget)); + break; + case UIA_NamePropertyId: + pRetVal->vt = VT_BSTR; + // todo: slate widgets don't have names, screen reader may read this as accessible text + pRetVal->bstrVal = SysAllocString(*Widget->GetWidgetName()); + break; + //case UIA_OrientationPropertyId: + // pRetVal->vt = VT_I4; + // // todo: sliders, scroll bars, layouts + // pRetVal->lVal = OrientationType_None; + // break; + case UIA_ProcessIdPropertyId: + pRetVal->vt = VT_I4; + pRetVal->lVal = ::GetCurrentProcessId(); + break; + default: + pRetVal->vt = VT_EMPTY; + break; + } + + if (!bValid) + { + pRetVal->vt = VT_EMPTY; + return E_FAIL; + } + + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) +{ + // Only return host provider for native windows + *pRetVal = nullptr; + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment** pRetVal) +{ + SCOPE_CYCLE_COUNTER(STAT_AccessibilityWindowsNavigate); + + if (!IsValid()) + { + return UIA_E_ELEMENTNOTAVAILABLE; + } + + TSharedPtr Relative = nullptr; + switch (direction) + { + case NavigateDirection_Parent: + Relative = Widget->GetParent(); + break; + case NavigateDirection_NextSibling: + Relative = Widget->GetNextSibling(); + break; + case NavigateDirection_PreviousSibling: + Relative = Widget->GetPreviousSibling(); + break; + case NavigateDirection_FirstChild: + if (Widget->GetNumberOfChildren() > 0) + { + Relative = Widget->GetChildAt(0); + } + break; + case NavigateDirection_LastChild: + { + const int32 NumChildren = Widget->GetNumberOfChildren(); + if (NumChildren > 0) + { + Relative = Widget->GetChildAt(NumChildren - 1); + } + break; + } + } + + if (Relative.IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Relative.ToSharedRef())); + } + else + { + *pRetVal = nullptr; + } + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::GetRuntimeId(SAFEARRAY** pRetVal) +{ + if (IsValid()) + { + int rtId[] = { UiaAppendRuntimeId, Widget->GetId() }; + *pRetVal = SafeArrayCreateVector(VT_I4, 0, 2); + if (*pRetVal) + { + for (LONG i = 0; i < 2; ++i) + { + if (SafeArrayPutElement(*pRetVal, &i, &rtId[i]) != S_OK) + { + return E_FAIL; + } + } + }; + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_BoundingRectangle(UiaRect* pRetVal) +{ + if (IsValid()) + { + FBox2D Box = Widget->GetBounds(); + pRetVal->left = Box.Min.X; + pRetVal->top = Box.Min.Y; + pRetVal->width = Box.Max.X - Box.Min.X; + pRetVal->height = Box.Max.Y - Box.Min.Y; + return S_OK; + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} +HRESULT STDCALL FWindowsUIAWidgetProvider::GetEmbeddedFragmentRoots(SAFEARRAY** pRetVal) +{ + // This would technically only be valid in our case for a window within a window + *pRetVal = nullptr; + return S_OK; +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::SetFocus() +{ + if (IsValid()) + { + if (Widget->SupportsFocus()) + { + Widget->SetFocus(); + return S_OK; + } + else + { + return UIA_E_NOTSUPPORTED; + } + } + else + { + return UIA_E_ELEMENTNOTAVAILABLE; + } +} + +HRESULT STDCALL FWindowsUIAWidgetProvider::get_FragmentRoot(IRawElementProviderFragmentRoot** pRetVal) +{ + if (IsValid()) + { + TSharedPtr Window = Widget->GetTopLevelWindow(); + if (Window.IsValid()) + { + *pRetVal = static_cast(&static_cast(UIAManager->GetWidgetProvider(Window.ToSharedRef()))); + return S_OK; + } + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +// ~ + +// FWindowsUIAWindowProvider methods + +FWindowsUIAWindowProvider::FWindowsUIAWindowProvider(FWindowsUIAManager& InManager, TSharedRef InWidget) + : FWindowsUIAWidgetProvider(InManager, InWidget) +{ + ensure(InWidget->AsWindow() != nullptr); +} + +FWindowsUIAWindowProvider::~FWindowsUIAWindowProvider() +{ +} + +HRESULT STDCALL FWindowsUIAWindowProvider::QueryInterface(REFIID riid, void** ppInterface) +{ + if (riid == __uuidof(IRawElementProviderFragmentRoot)) + { + *ppInterface = static_cast(this); + AddRef(); + return S_OK; + } + else + { + return FWindowsUIAWidgetProvider::QueryInterface(riid, ppInterface); + } +} + +ULONG STDCALL FWindowsUIAWindowProvider::AddRef() +{ + return FWindowsUIABaseProvider::IncrementRef(); +} + +ULONG STDCALL FWindowsUIAWindowProvider::Release() +{ + return FWindowsUIABaseProvider::DecrementRef(); +} + +HRESULT STDCALL FWindowsUIAWindowProvider::get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) +{ + if (Widget->IsValid()) + { + TSharedPtr NativeWindow = Widget->AsWindow()->GetNativeWindow(); + if (NativeWindow.IsValid()) + { + HWND Hwnd = static_cast(NativeWindow->GetOSWindowHandle()); + if (Hwnd != nullptr) + { + return UiaHostProviderFromHwnd(Hwnd, pRetVal); + } + } + return UIA_E_INVALIDOPERATION; + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +HRESULT STDCALL FWindowsUIAWindowProvider::GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) +{ + if (IsValid()) + { + switch (patternId) + { + case UIA_WindowPatternId: + *pRetVal = static_cast(new FWindowsUIAControlProvider(*UIAManager, Widget)); + return S_OK; + default: + return FWindowsUIAWidgetProvider::GetPatternProvider(patternId, pRetVal); + } + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +HRESULT STDCALL FWindowsUIAWindowProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment** pRetVal) +{ + if (IsValid()) + { + TSharedPtr Child = Widget->AsWindow()->GetChildAtPosition(x, y); + if (Child.IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Child.ToSharedRef())); + } + else + { + *pRetVal = nullptr; + } + return S_OK; + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +HRESULT STDCALL FWindowsUIAWindowProvider::GetFocus(IRawElementProviderFragment** pRetVal) +{ + *pRetVal = nullptr; + if (IsValid()) + { + TSharedPtr Focus = Widget->AsWindow()->GetFocusedWidget(); + if (Focus.IsValid()) + { + *pRetVal = static_cast(&UIAManager->GetWidgetProvider(Focus.ToSharedRef())); + } + return S_OK; + } + + return UIA_E_ELEMENTNOTAVAILABLE; +} + +// ~ + +#undef LOCTEXT_NAMESPACE + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp index 39ab24b56273..beccf9d7de09 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp @@ -24,6 +24,12 @@ #include "Developer/SourceCodeAccess/Public/ISourceCodeAccessModule.h" #endif +#if WITH_ACCESSIBILITY +#include "Windows/Accessibility/WindowsUIAManager.h" +#include "Windows/Accessibility/WindowsUIAWidgetProvider.h" +#include +#endif + // Allow Windows Platform types in the entire file. #include "Windows/AllowWindowsPlatformTypes.h" THIRD_PARTY_INCLUDES_START @@ -92,6 +98,9 @@ FWindowsApplication::FWindowsApplication( const HINSTANCE HInstance, const HICON bAllowedToDeferMessageProcessing, TEXT( "Whether windows message processing is deferred until tick or if they are processed immediately" ) ) , bInModalSizeLoop( false ) +#if WITH_ACCESSIBILITY + , UIAManager(new FWindowsUIAManager(*this)) +#endif { FMemory::Memzero(ModifierKeyState, EModifierKey::Count); @@ -313,6 +322,14 @@ void FWindowsApplication::SetMessageHandler( const TSharedRef< FGenericApplicati } +#if WITH_ACCESSIBILITY +void FWindowsApplication::SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) +{ + GenericApplication::SetAccessibleMessageHandler(InAccessibleMessageHandler); + UIAManager->OnAccessibleMessageHandlerChanged(); +} +#endif + bool FWindowsApplication::IsGamepadAttached() const { if (bForceNoGamepads) @@ -1354,6 +1371,13 @@ int32 FWindowsApplication::ProcessMessage( HWND hwnd, uint32 msg, WPARAM wParam, case WM_DESTROY: { Windows.Remove( CurrentNativeEventWindow ); +#if WITH_ACCESSIBILITY + // Tell UIA that the window no longer exists so that it can release some resources + if (GetAccessibleMessageHandler()->ApplicationIsAccessible()) + { + UiaReturnRawElementProvider(hwnd, 0, 0, nullptr); + } +#endif return 0; } break; @@ -1517,6 +1541,19 @@ int32 FWindowsApplication::ProcessMessage( HWND hwnd, uint32 msg, WPARAM wParam, } break; +#if WITH_ACCESSIBILITY + case WM_GETOBJECT: + { + if (GetAccessibleMessageHandler()->ApplicationIsAccessible()) + { + FScopedWidgetProvider Provider(UIAManager->GetWindowProvider(CurrentNativeEventWindow)); + LRESULT Result = UiaReturnRawElementProvider(hwnd, wParam, lParam, &Provider.Provider); + return Result; + } + break; + } +#endif + default: if (bMessageExternallyHandled) { diff --git a/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericAccessibleInterfaces.h b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericAccessibleInterfaces.h new file mode 100644 index 000000000000..3adc4142dedc --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericAccessibleInterfaces.h @@ -0,0 +1,535 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** Whether a widget should be included in accessibility, and if so, how its text should be retrieved. */ +enum class EAccessibleBehavior : uint8 +{ + /** Not accessible. */ + NotAccessible, + /** Accessible, for the implementing library to decide what it means. Given all data about a particular widget, it should try to choose the most-relevant text automatically. */ + Auto, + /** Accessible, and traverse all child widgets and concat their summary text together. */ + Summary, + /** Accessible, and retrieve manually-assigned text from a TAttribute. */ + Custom, + /** Accessible, and use the tooltip's accessible text. */ + ToolTip +}; + +DECLARE_LOG_CATEGORY_EXTERN(LogAccessibility, Log, All); + +#if WITH_ACCESSIBILITY + +#include "Misc/Variant.h" +#include "Stats/Stats.h" + +class FGenericWindow; +class IAccessibleWidget; + +DECLARE_STATS_GROUP(TEXT("Accessibility"), STATGROUP_Accessibility, STATCAT_Advanced); + +/** What kind of widget to tell the operating system this is. This may be translated to a different type depending on the platform. */ +enum class EAccessibleWidgetType : uint8 +{ + Unknown, + Button, + CheckBox, + ComboBox, + Hyperlink, + Image, + Layout, + ScrollBar, + Slider, + Text, + TextEdit, + Window +}; + +/** Events that can be raised from accessible widgets to report back to the platform */ +enum class EAccessibleEvent : uint8 +{ + /** A widget has become focused or unfocused. */ + FocusChange, + /** A widget has been clicked, checked, or otherwise activated */ + Activate, + /** Notify the user that something has happened. The user is not guaranteed to get this message. */ + Notification, + /** Right before a widget is removed from its parent widget. */ + BeforeRemoveFromParent, + /** Right after a widget is added to its parent widget. */ + AfterAddToParent, + /** The widget was removed from the UI tree or deleted. */ + WidgetRemoved +}; + +/** + * An accessible window corresponds to a native OS window. Fake windows that are embedded + * within other widgets that simply look and feel like windows are not IAccessibleWindows. + */ +class IAccessibleWindow +{ +public: + /** The different states a window can be in to describe its screen anchors */ + enum class EWindowDisplayState + { + Normal, + Minimize, + Maximize + }; + + /** + * Retrieve the native OS window backing this accessible window. This can be safely + * casted if you know what OS you're in (ie FWindowsWindows on Windows platform). + * + * @return The native window causing this accessible window to exist + */ + virtual TSharedPtr GetNativeWindow() const = 0; + + /** + * Finds the deepest accessible widget in the hierarchy at the specified coordinates. The window + * may return a pointer to itself in the case where there are no accessible children at the position. + * This could return nullptr in the case where the coordinates are outside the window bounds. + * + * @param X The X coordinate in absolute screen space + * @param Y The Y coordinate in absolute screen space + * @return The deepest widget in the UI heirarchy at X,Y + */ + virtual TSharedPtr GetChildAtPosition(int32 X, int32 Y) = 0; + /** + * Retrieves the currently-focused widget, if it is accessible. + * + * @return The widget that has focus, or nullptr if the focused widget is not accessible. + */ + virtual TSharedPtr GetFocusedWidget() const = 0; + /** + * Request that the window closes itself. This may not happen immediately. + */ + virtual void Close() = 0; + + /** + * Check if the window can be minimized or maximized. + * + * @param State Whether to check for minimize or maximize. + * @return True if the display state can be switched to, otherwise false. + */ + virtual bool SupportsDisplayState(EWindowDisplayState State) const = 0; + /** + * Gets the current state minimize/maximize state of the window. + * + * @return The display state corresponding to how the window is displayed. + */ + virtual EWindowDisplayState GetDisplayState() const = 0; + /** + * Sets a window to be minimized, maximized, or restored to normal. + * + * @param State What to change the window's display to. + */ + virtual void SetDisplayState(EWindowDisplayState State) = 0; + /** + * Whether or not the window is modal. + * + * @return true if the window is modal, otherwise false. + */ + virtual bool IsModal() const = 0; +}; + +/** + * A widget that can be triggered to fire an event, such as buttons or checkboxes. + */ +class IAccessibleActivatable +{ +public: + /** Trigger the widget */ + virtual void Activate() = 0; + /** + * Check whether this widget can be toggled between various check states + * + * @return true if this widget supports being in different states + */ + virtual bool IsCheckable() const { return false; } + /** + * If IsCheckable() is true, this gets the current state that the widget is in. + * //todo: return ECheckState + * + * @return true if the current state is considered "on" or "checked" + */ + virtual bool GetCheckedState() const { return false; } +}; + +/** + * An accessible widget that stores an arbitrary value of any type capable of being serialized into a string. + * Optional overrides add support for slider-like functionality. + */ +class IAccessibleProperty +{ +public: + /** + * Whether the widget is in read-only mode, which could be different than IsEnabled(). + * + * @return true if the widget is in read-only mode. + */ + virtual bool IsReadOnly() const { return true; } + /** + * Check if this text is storing password data, indicating that it may need special handling to presenting itself to the user. + * + * @return true if the text is storing a password or otherwise senstive data that should be hidden. + */ + virtual bool IsPassword() const { return false; } + /** + * How much the value should increase/decrease when the user attempts to modify the value using UI controls. + * Note: This should always return a positive value. The caller is responsible for negating it when attempting to decrease. + * + * @return A number suggesting how much to modify GetValue() by when the user wants to increase/decrease the value. + */ + virtual float GetStepSize() const { return 0.0f; } + /** + * The maximum allowed value for this property. This should only be used if GetStepSize is not 0. + * + * @return The maximum value that this property can be assigned when using step sizes. + */ + virtual float GetMaximum() const { return 0.0f; } + /** + * The minimum allowed value for this property. This should only be used if GetStepSize is not 0. + * + * @return The minimum value that this property can be assigned when using step sizes. + */ + virtual float GetMinimum() const { return 0.0f; } + /** + * The current value stored by the widget. Even if the underlying value is not a String, it should be serialized to one + * in order to match the return type. + * + * @return A string representing the serialized value stored in the widget. + */ + virtual FString GetValue() const = 0; + /* + * Set the value stored by the widget. While this function accepts a String, there is no way to know + * what the underlying data is stored as. The platform layer must retain some additional information + * about what kind of widget this is, and ensure it's being called with valid arguments. + * + * @param Value The new value to assign to the widget, which may need to be converted before assigning to a variable. + */ + virtual void SetValue(const FString& Value) {} +}; + +/** + * A widget that contains text, with the potential ability to select sections, read specific words/paragraphs, etc. + * Note: This class is currently incomplete. + */ +class IAccessibleText +{ +public: + /** + * Get the full text contained in this widget, even if some if it is clipped. + * + * @return All the text held by this widget. + */ + virtual const FString& GetText() const = 0; +}; + +typedef int32 AccessibleWidgetId; + +/** + * Provides the core set of accessible data that is necessary in order for widget traversal and TTS to be implemented. + * In order to support functionality beyond this, subclasses must implement the other accessible interfaces and + * then override the As*() functions. + */ +class IAccessibleWidget : public TSharedFromThis +{ +public: + IAccessibleWidget() {} + virtual ~IAccessibleWidget() {} + + static const AccessibleWidgetId InvalidAccessibleWidgetId = -1; + + /** + * Get an application-unique identifier for this widget. If the widget is destroyed, + * a different widget is allowed to re-use that ID. + * + * @return A unique ID that specifically refers to this widget. + */ + virtual AccessibleWidgetId GetId() const = 0; + /** + * Whether or not the underlying widget backing this interface still exists + * + * @return true if functions can be called on this interface and should return valid results + */ + virtual bool IsValid() const = 0; + + /** + * Returns the window at the top of this widget's hierarchy. This function may return itself for accessible windows, + * and could return nullptr in cases where the widget is not currently part of a hierarchy. + * + * @return The root window in this widget's widget tree, or nullptr if there is no window. + */ + virtual TSharedPtr GetTopLevelWindow() const = 0; + /** + * Retrieving the bounding rect in absolute coordinates for this widget. On some platforms this may be used for hit testing. + * + * @return The bounds of the widget. + */ + virtual FBox2D GetBounds() const = 0; + /** + * Get the accessible parent of this widget. This may be nullptr if this widget is a window, or if the widget is + * currently disconnected from the UI tree. + * + * @return The accessible parent widget of this widget. + */ + virtual TSharedPtr GetParent() = 0; + /** + * Retrieves the widget after this one in the parent's list of children. This should return nullptr for the last widget. + * + * @return The next widget on the same level of the UI hierarchy. + */ + virtual TSharedPtr GetNextSibling() = 0; + /** + * Retrieves the widget before this one in the parent's list of children. This should return nullptr for the first widget. + * + * @return The previous widget on the same level of the UI hierarchy. + */ + virtual TSharedPtr GetPreviousSibling() = 0; + /** + * Retrieves the accessible child widget at a certain index. This should return nullptr if Index < 0 or Index >= GetNumberOfChildren(). + * + * @param The index of the child widget to get + * @return The accessible child widget at the specified index. + */ + virtual TSharedPtr GetChildAt(int32 Index) = 0; + /** + * How many accessible children this widget has. + * + * @return The number of accessible children that exist for this widget. + */ + virtual int32 GetNumberOfChildren() = 0; + /** + * What type of accessible widget the underlying widget should be treated as. A widget may be capable of presenting itself + * as multiple different types of widgets, but only one can be reported back to the platform. + * + * @return Which type of widget the platform layer should treat this as. + */ + virtual EAccessibleWidgetType GetWidgetType() const = 0; + + /** + * The name of the underlying class that this accessible widget represents. + * + * @return The class name of the underlying widget. + */ + virtual FString GetClassName() const = 0; + /** + * The name of the widget to report to the platform layer. For screen readers, this is often the text that will be spoken. + * + * @return Ideally, a human-readable name that represents what the widget does. + */ + virtual FString GetWidgetName() const = 0; + /** + * Additional information a user may need in order to effectively interact or use the widget, such as a tooltip. + * + * @return A more-detailed description of what the widget is or how its used. + */ + virtual FString GetHelpText() const = 0; + + /** + * Whether the widget is enabled and can be interacted with. + * + * @return true if the widget is enabled. + */ + virtual bool IsEnabled() const = 0; + /** + * Whether the widget is being rendered on screen or not. + * + * @return true if the widget is hidden off screen, collapsed, or something similar. + */ + virtual bool IsHidden() const = 0; + /** + * Whether the widget supports keyboard focus or not. + * + * @return true if the widget can receive keyboard focus. + */ + virtual bool SupportsFocus() const = 0; + /** + * Whether the widget has keyboard focus or not. + * + * @return true if the widget currently has keyboard focus. + */ + virtual bool HasFocus() const = 0; + /** Assign keyboard focus to this widget, if it supports it. If not, focus should not be affected. */ + virtual void SetFocus() = 0; + + /** + * Attempt to cast this to an IAccessibleWindow + * + * @return 'this' as an IAccessibleWindow if possible, otherwise nullptr + */ + virtual IAccessibleWindow* AsWindow() { return nullptr; } + /** + * Attempt to cast this to an IAccessibleActivatable + * + * @return 'this' as an IAccessibleActivatable if possible, otherwise nullptr + */ + virtual IAccessibleActivatable* AsActivatable() { return nullptr; } + /** + * Attempt to cast this to an IAccessibleProperty + * + * @return 'this' as an IAccessibleProperty if possible, otherwise nullptr + */ + virtual IAccessibleProperty* AsProperty() { return nullptr; } + /** + * Attempt to cast this to an IAccessibleText + * + * @return 'this' as an IAccessibleText if possible, otherwise nullptr + */ + virtual IAccessibleText* AsText() { return nullptr; } +}; + +/** + * Platform and application-agnostic messaging system for accessible events. The message handler + * lives in GenericApplication and any subclass that wishes to support accessibility should subclass + * this and use GenericAppliation::SetAccessibleMessageHandler to enable functionality. + * + * GetAccessibleWindow() is tne entry point to all accessible widgets. Once the window is retrieved, it + * can be queried for children in various ways. RaiseEvent() allows messages to bubble back up to the + * native OS through anything bound to the AccessibleEventDelegate. + * + * Callers can use ApplicationIsAccessible() to see if accessibility is supported or not. Alternatively, + * calling GetAccessibleWindow and seeing if the result is valid should provide the same information. + */ +class FGenericAccessibleMessageHandler +{ +public: + /** A widget raised an event to pass to the native OS implementation. */ + DECLARE_DELEGATE_FourParams( + FAccessibleEvent, + /** The accessible widget that generated the event */ + TSharedRef, + /** The type of event generated */ + EAccessibleEvent, + /** If this was a property changed event, the 'before' value */ + FVariant, + /** If this was a property changed event, the 'after' value. This may also be set for other events such as Notification. */ + FVariant); + + FGenericAccessibleMessageHandler() : bIsActive(false) {} + + virtual ~FGenericAccessibleMessageHandler() + { + if (AccessibleEventDelegate.IsBound()) + { + AccessibleEventDelegate.Unbind(); + } + } + + /** + * Subclasses should return true to indicate that they support accessibility. + * + * @return true if the application intends to return valid accessible widgets when queried. + */ + virtual bool ApplicationIsAccessible() const { return false; } + + /** + * Checks if accessibility is enabled in the application. Usually this happens when screen-reading software is turned on. + * Note: On some platforms, there is no way to deactivate this after enabling it. + * + * @return The last value SetActive() was called with. + */ + bool IsActive() const { return bIsActive; } + + /** + * Notify the application to start or stop processing accessible messages from the platform layer. + * + * @param bActive Whether to enable to disable the message handler. + */ + void SetActive(bool bActive) + { + if (bActive != bIsActive) + { + bIsActive = bActive; + + if (bIsActive) + { + UE_LOG(LogAccessibility, Log, TEXT("Enabling Accessibility")); + OnActivate(); + } + else + { + OnDeactivate(); + UE_LOG(LogAccessibility, Log, TEXT("Accessibility Disabled")); + } + } + } + + /** + * Creates or retrieves an accessible object for a native OS window. + * todo: Behavior for non-native windows (virtual or others) is currently undefined. + * + * @param InWindow The native window to find the accessible window for + * @return The accessible object corresponding to the supplied native window + */ + virtual TSharedPtr GetAccessibleWindow(const TSharedRef& InWindow) const { return nullptr; } + + /** + * Creates or retrieves the identifier for an accessible object for a native OS window. + * todo: Behavior for non-native windows (virtual or others) is currently undefined. + * + * @param InWindow The native window to find the accessible window for + * @return The identifier for the accessible window created + */ + virtual AccessibleWidgetId GetAccessibleWindowId(const TSharedRef& InWindow) const { return IAccessibleWidget::InvalidAccessibleWidgetId; } + + /** + * Retrieves an accessible widget that matches the given identifier. + * + * @param Id The identifier for the widget to get. + * @return The widget that matches this identifier, or nullptr if the widget does not exist. + */ + virtual TSharedPtr GetAccessibleWidgetFromId(AccessibleWidgetId Id) const { return nullptr; } + + /** + * Push an event from an accessible widget back to the platform layer. + * + * @param Widget The widget raising the event + * @param Event The type of event being raised + * @param OldValue If this is a property changed event, the old value of the property + * @param NewValue If this is a property changed event, the current value of the property. This may also be used by non-property events that require data. + */ + void RaiseEvent(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) + { + AccessibleEventDelegate.ExecuteIfBound(Widget, Event, OldValue, NewValue); + } + /** + * Push an event from an accessible widget back to the platform layer. + * This is a convenience function if OldValue and NewValue are irrelevant for this event type. + * + * @param Widget The widget raising the event + * @param Event The type of event being raised + */ + void RaiseEvent(TSharedRef Widget, EAccessibleEvent Event) + { + RaiseEvent(Widget, Event, FVariant(), FVariant()); + } + + /** + * Assign a function to be called whenever an accessible event is raised. + * + * @param Delegate The delegate to execute when an event is raised. + */ + void SetAccessibleEventDelegate(const FAccessibleEvent& Delegate) + { + AccessibleEventDelegate = Delegate; + } + +protected: + /** Triggered when bIsActive changes from false to true. */ + virtual void OnActivate() {} + /** Triggered when bIsActive changes from true to false. */ + virtual void OnDeactivate() {} + +private: + /** Whether or not accessibility is currently enabled in the application */ + bool bIsActive; + /** Delegate for the platform layer to listen to widget events */ + FAccessibleEvent AccessibleEventDelegate; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h index 783ed3c7e915..0f9de9bb8244 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/GenericPlatform/GenericApplication.h @@ -9,6 +9,9 @@ #include "Math/Vector4.h" #include "Templates/SharedPointer.h" #include "Delegates/Delegate.h" +#if WITH_ACCESSIBILITY +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#endif #include "GenericPlatform/GenericApplicationMessageHandler.h" #include "GenericPlatform/GenericWindowDefinition.h" #include "GenericPlatform/GenericWindow.h" @@ -428,6 +431,9 @@ public: GenericApplication( const TSharedPtr< ICursor >& InCursor ) : Cursor( InCursor ) , MessageHandler( MakeShareable( new FGenericApplicationMessageHandler() ) ) +#if WITH_ACCESSIBILITY + , AccessibleMessageHandler(MakeShareable(new FGenericAccessibleMessageHandler())) +#endif { } @@ -438,6 +444,11 @@ public: TSharedRef< FGenericApplicationMessageHandler > GetMessageHandler() { return MessageHandler; } +#if WITH_ACCESSIBILITY + virtual void SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) { AccessibleMessageHandler = InAccessibleMessageHandler; } + TSharedRef GetAccessibleMessageHandler() const { return AccessibleMessageHandler; } +#endif + virtual void PollGameDeviceState( const float TimeDelta ) { } virtual void PumpMessages( const float TimeDelta ) { } @@ -543,6 +554,10 @@ public: protected: TSharedRef< class FGenericApplicationMessageHandler > MessageHandler; + +#if WITH_ACCESSIBILITY + TSharedRef AccessibleMessageHandler; +#endif /** Trigger the OnDisplayMetricsChanged event with the argument 'InMetrics' */ void BroadcastDisplayMetricsChanged( const FDisplayMetrics& InMetrics ){ OnDisplayMetricsChangedEvent.Broadcast( InMetrics ); } diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityCache.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityCache.h new file mode 100644 index 000000000000..9b0a95369526 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityCache.h @@ -0,0 +1,44 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +@class FIOSAccessibilityContainer; + +/** + * This class is a singleton and should be accessed through [FIOSAccessibilityCache AccessibilityElementCache]. + * Stores a list of accessible containers that map to AccessibleWidgetIds for lookup. + * The cache is also responsible for polling attributes from the underlying + * IAccessibleWidgets that are too expensive to be done when requested by IOS due to + * needing to be accessed from a different thread. + * + * Leaf elements can be accessed by getting their container from the cache and calling + * [container GetLeaf] on it. + */ +@interface FIOSAccessibilityCache : NSObject +{ +@private + /** AccessibleWidgetId(String)->FIOSAccessibilityContainer map for all created containers. */ + NSMutableDictionary* Cache; +} + +/** Retrieve a cached container, or create one if it doesn't exist yet. */ +-(FIOSAccessibilityContainer*)GetAccessibilityElement:(AccessibleWidgetId)Id; +/** Returns true if the Cache contains the Id. Does not create one if it doesn't exist. */ +-(bool)AccessibilityElementExists:(AccessibleWidgetId)Id; +/** Removes an entry from the Cache. */ +-(void)RemoveAccessibilityElement:(AccessibleWidgetId)Id; +/** Completely empties the cache. */ +-(void)Clear; +/** Loop over all cached elements and update any properties necessary on the Game thread. */ +-(void)UpdateAllCachedProperties; + +/** Singleton accessor */ ++(id)AccessibilityElementCache; + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityElement.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityElement.h new file mode 100644 index 000000000000..622fce33deff --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/Accessibility/IOSAccessibilityElement.h @@ -0,0 +1,78 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "CoreMinimal.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#import + +@class FIOSAccessibilityLeaf; + +/** + * UIAccessibilityElements cannot be both accessible and have children. While + * the same class can be used in both cases, the value they return for + * isAccessibilityElement determines what type of widget they are. If false, + * the widget is a container and only functions regarding children will be called. + * If true, only functions regarding the value of the wigdet will be called. + * + * Because of this, all IAccessibleWidgets have both a corresponding container + * and leaf. The leaf is always reported as the last child of the container. This + * is our workaround for a widget being accessible and having children at the same time. + */ +@interface FIOSAccessibilityContainer : UIAccessibilityElement +{ +@private + /** A matching Leaf element that shares the same AccessibleWidgetId at this container. */ + FIOSAccessibilityLeaf* Leaf; +} + +/** This must be used instead of initWithAccessibilityContainer in order to work properly. */ +-(id)initWithId:(AccessibleWidgetId)InId AndParentId:(AccessibleWidgetId)InParentId; +/** Updates the accessibilityContainer property from UIAccessibilityElement. */ +-(void)SetParent:(AccessibleWidgetId)InParentId; +/** Get the accessible version of this widget. */ +-(FIOSAccessibilityLeaf*)GetLeaf; + +/** The identifier used to access this widget through the accessible API. */ +@property (nonatomic) AccessibleWidgetId Id; +/** A list of identifiers for all current children of this container. */ +@property (nonatomic) TArray ChildIds; +/** The bounding rect of the container. */ +@property (nonatomic) FBox2D Bounds; +/** Whether or not the widget is currently visible. */ +@property (nonatomic) bool bIsVisible; + +@end + +/** + * The accessible version of a widget for a given AccessibleWidgetId. A Leaf is + * guaranteed to have an FIOSAccessibilityContainer as its container, and can be + * accessed with [self accessibilityContainer] (in order to get things like bounds). + */ +@interface FIOSAccessibilityLeaf : UIAccessibilityElement +{ +} + +/** This must be used instead of initWithAccessibilityContainer in order to work properly. */ +-(id)initWithParent:(FIOSAccessibilityContainer*)Parent; +/** Check if LastCachedStringTime was updated recently. */ +-(bool)ShouldCacheStrings; +/** Toggle an individual trait on or off */ +-(void)SetAccessibilityTrait:(UIAccessibilityTraits)Trait Set:(bool)IsEnabled; + +/** A cached version of the name of the widget. */ +@property (nonatomic) FString Label; +/** A cached version of the help text of the widget. */ +@property (nonatomic) FString Hint; +/** A cached version of the value of property widgets. */ +@property (nonatomic) FString Value; +/** Bitflag of traits that describe the widget. Most are set once on initialization. */ +@property (nonatomic) UIAccessibilityTraits Traits; +/** Timestamp for when Label, Hint, and Value were last cached. */ +@property (nonatomic) double LastCachedStringTime; + +@end + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h index c00b9fb1e04b..259e1ad772b8 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h @@ -133,6 +133,13 @@ APPLICATIONCORE_API /** The time delay (in seconds) between idle timer enable requests and actually enabling the idle timer */ @property (readonly) float IdleTimerEnablePeriod; +#if WITH_ACCESSIBILITY +/** Timer used for updating cached data from game thread for all accessible widgets. */ +@property (nonatomic, retain) NSTimer* AccessibilityCacheTimer; +/** Callback for IOS notification of VoiceOver being enabled or disabled. */ +-(void)OnVoiceOverStatusChanged; +#endif + // parameters passed from openURL @property (nonatomic, retain) NSMutableArray* savedOpenUrlParameters; @@ -170,6 +177,8 @@ APPLICATIONCORE_API -(bool)IsIdleTimerEnabled; -(void)EnableIdleTimer:(bool)bEnable; -(void)StartGameThread; +/** Uses the TaskGraph to execute a function on the game thread, and then blocks until the function is executed. */ ++(bool)WaitAndRunOnGameThread:(TUniqueFunction)Function; -(void)NoUrlCommandLine; -(int)GetAudioVolume; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h index f9c34289b862..cc5d2a71f226 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSApplication.h @@ -18,6 +18,9 @@ public: virtual ~FIOSApplication() {} void SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ); +#if WITH_ACCESSIBILITY + virtual void SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) override; +#endif virtual void PollGameDeviceState( const float TimeDelta ) override; @@ -35,13 +38,17 @@ public: virtual bool IsGamepadAttached() const override; + TSharedRef FindWindowByAppDelegateView(); + protected: virtual void InitializeWindow( const TSharedRef< FGenericWindow >& Window, const TSharedRef< FGenericWindowDefinition >& InDefinition, const TSharedPtr< FGenericWindow >& InParent, const bool bShowImmediately ) override; private: FIOSApplication(); - +#if WITH_ACCESSIBILITY + void OnAccessibleEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue); +#endif private: diff --git a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h index 3e37ea1c4fde..396f640d67f0 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSView.h @@ -14,6 +14,10 @@ #import #endif +#if WITH_ACCESSIBILITY +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#endif + struct FKeyboardConfig { UIKeyboardType KeyboardType; @@ -82,9 +86,17 @@ APPLICATIONCORE_API volatile int32 KeyboardShowCount; - +#if WITH_ACCESSIBILITY +@private + // Single-element array used for the accessibilityElements property. It holds the IAccessibleWindow for the app. + NSMutableArray* _accessibilityElements; +#endif } +#if WITH_ACCESSIBILITY +/** Repopulate _accessibilityElements when the accessible window's ID has changed. */ +-(void)SetAccessibilityWindow:(AccessibleWidgetId)WindowId; +#endif //// SHARED FUNCTIONALITY @property (nonatomic) GLuint SwapCount; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h index 02e57d974fe8..90457232aa4b 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Linux/LinuxApplication.h @@ -90,6 +90,8 @@ public: void RemoveNotificationWindow(SDL_HWindow HWnd); + void CheckIfApplicatioNeedsDeactivation(); + EWindowZone::Type WindowHitTest( const TSharedPtr< FLinuxWindow > &window, int x, int y ); TSharedPtr< FLinuxWindow > FindWindowBySDLWindow( SDL_Window *win ); diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h index 3c911b146ef4..21c73dd6570f 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaMenu.h @@ -3,7 +3,7 @@ #include "CoreMinimal.h" -@interface FCocoaMenu : NSMenu +OBJC_EXPORT @interface FCocoaMenu : NSMenu { @private bool bHighlightingKeyEquivalent; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h index ceaa76d390cc..dc02508b9d84 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/CocoaTextView.h @@ -6,7 +6,7 @@ #ifdef __OBJC__ -@interface FCocoaTextView : NSView +OBJC_EXPORT @interface FCocoaTextView : NSView { TSharedPtr IMMContext; NSRange markedRange; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h index 7473e8be522d..f95332316d13 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacApplication.h @@ -395,4 +395,4 @@ private: friend class FMacWindow; }; -extern FMacApplication* MacApplication; +APPLICATIONCORE_API extern FMacApplication* MacApplication; diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h index 48dc1c9e0a0b..58594b00da2e 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformFramePacer.h @@ -16,7 +16,7 @@ typedef void (^FMacFramePacerHandler)(uint32 CGDirectDisplayID, double OutputSec /** * Mac implementation of FGenericPlatformRHIFramePacer **/ -struct FMacPlatformRHIFramePacer : public FGenericPlatformRHIFramePacer +struct APPLICATIONCORE_API FMacPlatformRHIFramePacer : public FGenericPlatformRHIFramePacer { // FGenericPlatformRHIFramePacer interface static bool IsEnabled(); diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h index 03c7292a1c95..ba389048d508 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Mac/MacPlatformSurvey.h @@ -16,7 +16,7 @@ class FString; struct FMacPlatformSurvey : public FGenericPlatformSurvey { /** Start, or check on, the hardware survey */ - static bool GetSurveyResults( FHardwareSurveyResults& OutResults, bool bWait = false ); + APPLICATIONCORE_API static bool GetSurveyResults( FHardwareSurveyResults& OutResults, bool bWait = false ); private: /** diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h b/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h index 3d710eb9335a..cfd9859d54c9 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Unix/UnixPlatformSurvey.h @@ -10,12 +10,12 @@ #include "GenericPlatform/GenericPlatformSurvey.h" /** -* Android implementation of FGenericPlatformSurvey +* Unix implementation of FGenericPlatformSurvey **/ struct FUnixPlatformSurvey : public FGenericPlatformSurvey { /** Start, or check on, the hardware survey */ - static bool GetSurveyResults(FHardwareSurveyResults& OutResults, bool bWait = false); + APPLICATIONCORE_API static bool GetSurveyResults(FHardwareSurveyResults& OutResults, bool bWait = false); private: diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIABaseProvider.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIABaseProvider.h new file mode 100644 index 000000000000..2cd136d83edd --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIABaseProvider.h @@ -0,0 +1,55 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Windows/HideWindowsPlatformTypes.h" +#include + +#include "Templates/SharedPointer.h" + +class FWindowsUIAManager; +class IAccessibleWidget; + +/** + * Base class for all Windows UIA Providers to inherit from. Provides implementation of IUnknown's + * reference counting functions as well as integration with the UIA Manager class. + */ +class FWindowsUIABaseProvider +{ +public: + /** Notify the Provider that the backing application no longer exists. */ + void OnUIAManagerDestroyed(); + +protected: + FWindowsUIABaseProvider(FWindowsUIAManager& InManager, TSharedRef InWidget); + virtual ~FWindowsUIABaseProvider(); + + /** + * Check whether this Provider can have operations called on it. This might return false if the + * application is being destroyed, or if the underlying accessible widget is no longer valid. + */ + bool IsValid() const; + + /** Add one to RefCount, and return the new RefCount */ + uint32 IncrementRef(); + /** Subtract one from RefCount, and return the new RefCount. If RefCount is 0, deletes the Provider and returns 0. */ + uint32 DecrementRef(); + + /** A pointer to the UIA Manager which is guaranteed to be valid so long as the application is still running. */ + FWindowsUIAManager* UIAManager; + /** + * All Providers must have a valid accessible widget, even if the accessible widget itself does not + * have any valid data backing it. In this case, the accessible widget will return some default values. + */ + TSharedRef Widget; + +private: + /** Counter for the number of strong references to this object. Many of these will be from external applications. */ + uint32 RefCount; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAControlProvider.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAControlProvider.h new file mode 100644 index 000000000000..c559573cd4e5 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAControlProvider.h @@ -0,0 +1,152 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIABaseProvider.h" +#include "Templates/SharedPointer.h" + +class FWindowsUIAControlProvider; +class IAccessibleWidget; + +/** + * A TextRange Provider represents a start and end position within an ITextProvider. This can reference + * a word, a paragraph, a selection, etc. Multiple text ranges can exist for a single piece of text. + */ +class FWindowsUIATextRangeProvider + : public FWindowsUIABaseProvider + , public ITextRangeProvider +{ +public: + FWindowsUIATextRangeProvider(FWindowsUIAManager& InManager, TSharedRef InWidget, FTextRange InRange); + + // IUnknown + HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + ULONG STDCALL AddRef() override; + ULONG STDCALL Release() override; + // ~ + + // ITextRangeProvider + virtual HRESULT STDCALL Clone(ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL Compare(ITextRangeProvider* range, BOOL* pRetVal) override; + virtual HRESULT STDCALL CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint, int* pRetVal) override; + virtual HRESULT STDCALL ExpandToEnclosingUnit(TextUnit unit) override; + virtual HRESULT STDCALL FindAttribute(TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL FindText(BSTR text, BOOL backward, BOOL ignoreCase, ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL GetAttributeValue(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override; + virtual HRESULT STDCALL GetBoundingRectangles(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL GetEnclosingElement(IRawElementProviderSimple** pRetVal) override; + virtual HRESULT STDCALL GetText(int maxLength, BSTR* pRetVal) override; + virtual HRESULT STDCALL Move(TextUnit unit, int count, int* pRetVal) override; + virtual HRESULT STDCALL MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count, int* pRetVal) override; + virtual HRESULT STDCALL MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider* targetRange, TextPatternRangeEndpoint targetEndpoint) override; + virtual HRESULT STDCALL Select() override; + virtual HRESULT STDCALL AddToSelection() override; + virtual HRESULT STDCALL RemoveFromSelection() override; + virtual HRESULT STDCALL ScrollIntoView(BOOL alignToTop) override; + virtual HRESULT STDCALL GetChildren(SAFEARRAY** pRetVal) override; + // ~ + +protected: + /** + * Gets the substring that this text range Provider represents. + * + * @return A substring of the underlying widget's text from TextRange's begin/end indices. + */ + FString TextFromTextRange(); + static FString TextFromTextRange(const FString& InString, const FTextRange& InRange); + + /** The range that this Provider represents in the underlying text. */ + FTextRange TextRange; + +private: + virtual ~FWindowsUIATextRangeProvider(); +}; + +/** + * The control Provider handles all control pattern-related functionality for the widget Provider. Control Providers should only + * be generated through widget Providers in their GetPatternProvider() function. In doing this, a control Pattern is only guaranteed + * to support functions that match the pattern Provider that it was generated for. This means that if GetPatternProvider() is called + * for UIA_ValuePatternId, the control Provider is only guaranteed to work with functions that implement IValueProvider. It is up + * to the caller to ensure that they are making valid function calls. + */ +class FWindowsUIAControlProvider + : public FWindowsUIABaseProvider + , public IInvokeProvider + , public IRangeValueProvider + , public ITextProvider + , public IToggleProvider + , public ITransformProvider + , public IValueProvider + , public IWindowProvider +{ +public: + FWindowsUIAControlProvider(FWindowsUIAManager& Manager, TSharedRef InWidget); + + // IUnknown + HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + ULONG STDCALL AddRef() override; + ULONG STDCALL Release() override; + // ~ + + // IInvokeProvider + virtual HRESULT STDCALL Invoke() override; + // ~ + + // IRangeValueProvider + virtual HRESULT STDCALL SetValue(double val) override; + virtual HRESULT STDCALL get_Value(double* pRetVal) override; + virtual HRESULT STDCALL get_IsReadOnly(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_Maximum(double* pRetVal) override; + virtual HRESULT STDCALL get_Minimum(double* pRetVal) override; + virtual HRESULT STDCALL get_LargeChange(double* pRetVal) override; + virtual HRESULT STDCALL get_SmallChange(double* pRetVal) override; + // ~ + + // ITextProvider + virtual HRESULT STDCALL get_DocumentRange(ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL get_SupportedTextSelection(SupportedTextSelection* pRetVal) override; + virtual HRESULT STDCALL GetSelection(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL GetVisibleRanges(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL RangeFromChild(IRawElementProviderSimple* childElement, ITextRangeProvider** pRetVal) override; + virtual HRESULT STDCALL RangeFromPoint(UiaPoint point, ITextRangeProvider** pRetVal) override; + // ~ + + // IToggleState + virtual HRESULT STDCALL get_ToggleState(ToggleState* pRetVal) override; + virtual HRESULT STDCALL Toggle() override; + // ~ + + // ITransformProvider + virtual HRESULT STDCALL get_CanMove(BOOL *pRetVal) override; + virtual HRESULT STDCALL get_CanResize(BOOL *pRetVal) override; + virtual HRESULT STDCALL get_CanRotate(BOOL *pRetVal) override; + virtual HRESULT STDCALL Move(double x, double y) override; + virtual HRESULT STDCALL Resize(double width, double height) override; + virtual HRESULT STDCALL Rotate(double degrees) override; + // ~ + + // IValueProvider + virtual HRESULT STDCALL SetValue(LPCWSTR val) override; + virtual HRESULT STDCALL get_Value(BSTR* pRetVal) override; + //virtual HRESULT STDCALL get_IsReadOnly(BOOL* pRetVal) override; // Duplicate of IRangeValueProvider + // ~ + + // IWindowProvider + virtual HRESULT STDCALL Close() override; + virtual HRESULT STDCALL get_CanMaximize(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_CanMinimize(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_IsModal(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_IsTopmost(BOOL* pRetVal) override; + virtual HRESULT STDCALL get_WindowInteractionState(WindowInteractionState* pRetVal) override; + virtual HRESULT STDCALL get_WindowVisualState(WindowVisualState* pRetVal) override; + virtual HRESULT STDCALL SetVisualState(WindowVisualState state) override; + virtual HRESULT STDCALL WaitForInputIdle(int milliseconds, BOOL* pRetVal) override; + // ~ + +private: + virtual ~FWindowsUIAControlProvider(); +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAManager.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAManager.h new file mode 100644 index 000000000000..c05aebb63c00 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAManager.h @@ -0,0 +1,89 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Templates/SharedPointer.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +class FWindowsApplication; +class FWindowsWindow; +class FWindowsUIAControlProvider; +class FWindowsUIAWidgetProvider; +class FWindowsUIAWindowProvider; +class IAccessibleWidget; +class FVariant; + +/** + * Manager for Windows implementation of UE4's accessibility API, utilizing Microsoft's UI Automation API. It provides a + * central location for message passing to/from the WindowsApplication, AccessibleMessageHandler, and Windows UIA Providers. + * + * This class only accepts and returns references, so callers should ensure their objects are valid before using it. + */ +class FWindowsUIAManager +{ + /** Allow FWindowsUIABaseProvider to register and unregister itself from ProviderList automatically */ + friend class FWindowsUIABaseProvider; +public: + FWindowsUIAManager(const FWindowsApplication& InApplication); + ~FWindowsUIAManager(); + + /** + * Create a Windows UIA IRawElementProviderSimple from a given accessible widget. Providers are stored in a local cache, + * and this function will allocate a new Provider if one does not already exist for the given widget. Using this function + * will always increase the RefCount of the Provider by one. + * + * If InWidget->IsWindow() returns true, a FWindowsUIAWindowProvider will be created instead of a FWindowsUIAWidgetProvider. + * + * @param InWidget A non-null reference to an accessible widget + * @return A reference to the cached Provider + */ + FWindowsUIAWidgetProvider& GetWidgetProvider(TSharedRef InWidget); + + /** + * Create a Windows UIA IRawElementProviderSimple from a given native window handle. Providers are stored in a local cache, + * and this function will allocate a new Provider if one does not already exist for the given widget. Using this function + * will always increase the RefCount of the Provider by one. + * + * If the Provider cache is empty upon calling this function, it will activate accessibility in the entire application. + * + * @param InWindow A non-null reference to a native window + * @return A reference to the cached Provider + */ + FWindowsUIAWindowProvider& GetWindowProvider(TSharedRef InWindow); + + /** + * Notify the Manager that the RefCount for a Provider has reached 0, which will cause it to be removed from the cache. + * + * If the Provider cache is empty after removing the Provider, accessibility will be disabled through the entire application. + * + * @param InWidget The accessible widget backing the Provider that was removed + */ + void OnWidgetProviderRemoved(TSharedRef InWidget); + + /** + * Notify the Manager that the accessible message handler for the application has changed, in order to relink to the accessible event delegate. + */ + void OnAccessibleMessageHandlerChanged(); + + + static TMap WidgetTypeToWindowsTypeMap; + static TMap WidgetTypeToTextMap; + +private: + /** Callback function for processing events raised from the AccessibleMessageHandler */ + void OnEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue); + + /** Cache of all Providers with a RefCount of at least 1 that map to an accessible widget. */ + TMap, FWindowsUIAWidgetProvider*> CachedWidgetProviders; + /** + * A set of all Providers with a RefCount of at least 1. This also includes non-widget Providers such as text ranges. + * When the application is closed, this is used to notify Providers held by external applications that they are invalid. + */ + TSet ProviderList; + /** A reference back to the owning application that can be used to access the AccessibleMessageHandler */ + const FWindowsApplication& WindowsApplication; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAPropertyGetters.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAPropertyGetters.h new file mode 100644 index 000000000000..f26a33e3f545 --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAPropertyGetters.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Windows/HideWindowsPlatformTypes.h" +#include + +#include "Templates/SharedPointer.h" + +class FVariant; +class IAccessibleWidget; + +namespace WindowsUIAPropertyGetters +{ + VARIANT GetPropertyValueWindows(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId); + FVariant GetPropertyValue(TSharedRef AccessibleWidget, PROPERTYID WindowsPropertyId); + + /** + * Convert an FVariant to a Windows VARIANT. + * Only necessary conversions are implemented. + */ + VARIANT FVariantToWindowsVariant(const FVariant& Value); +} + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAWidgetProvider.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAWidgetProvider.h new file mode 100644 index 000000000000..500588a808fc --- /dev/null +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/Accessibility/WindowsUIAWidgetProvider.h @@ -0,0 +1,119 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "Windows/Accessibility/WindowsUIABaseProvider.h" +#include "Misc/Variant.h" + +class FWindowsUIAManager; +class IAccessibleWidget; + +/** + * Windows UIA Provider implementation for all non-Window widgets. + * + * WidgetProvider does not inherit from IRawElementProviderFragmentRoot and thus cannot be queried + * by external applications for ElementProviderFromPoint(). This should work fine but there could + * be unforeseen consquences for cases like modal popup dialogs which are not necessarily windows. + */ +class FWindowsUIAWidgetProvider + : public FWindowsUIABaseProvider + , public IRawElementProviderSimple + , public IRawElementProviderFragment +{ + friend class FWindowsUIAManager; +public: + // IUnknown + virtual HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + virtual ULONG STDCALL AddRef() override; + virtual ULONG STDCALL Release() override; + // ~ + + // IRawElementProviderSimple + virtual HRESULT STDCALL get_ProviderOptions(ProviderOptions* pRetVal) override; + virtual HRESULT STDCALL GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) override; + virtual HRESULT STDCALL GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal) override; + virtual HRESULT STDCALL get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) override; + // ~ + + // IRawElementProviderFragment + virtual HRESULT STDCALL Navigate(NavigateDirection direction, IRawElementProviderFragment** pRetVal) override; + virtual HRESULT STDCALL GetRuntimeId(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL get_BoundingRectangle(UiaRect* pRetVal) override; + virtual HRESULT STDCALL GetEmbeddedFragmentRoots(SAFEARRAY** pRetVal) override; + virtual HRESULT STDCALL SetFocus() override; + virtual HRESULT STDCALL get_FragmentRoot(IRawElementProviderFragmentRoot** pRetVal) override; + // ~ + + /** + * Check if this Provider implements a specific control pattern. A FWindowsUIAControlProvider can + * then be created of the pattern is supported. Since this is a helper function, the caller should + * separately ensure IsValid() before doing anything with the result. + * + * @param PatternId The control pattern to check for + * @return true if a control Provider can be created for this Provider for this control pattern + */ + bool SupportsInterface(PATTERNID PatternId) const; + +protected: + FWindowsUIAWidgetProvider(FWindowsUIAManager& InManager, TSharedRef InWidget); + virtual ~FWindowsUIAWidgetProvider(); + +// void UpdateCachedProperties(); +//private: +// void UpdateCachedProperty(PROPERTYID PropertyId); +// TMap CachedPropertyValues; +}; + +/** + * Windows UIA Provider implementation for all Window widgets. Widget->AsWindow() must return a valid pointer. + */ +class FWindowsUIAWindowProvider + : public FWindowsUIAWidgetProvider + , public IRawElementProviderFragmentRoot +{ + friend class FWindowsUIAManager; +public: + // IUnknown + virtual HRESULT STDCALL QueryInterface(REFIID riid, void** ppInterface) override; + virtual ULONG STDCALL AddRef() override; + virtual ULONG STDCALL Release() override; + // ~ + + // IRawElementProviderSimple + virtual HRESULT STDCALL get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) override; + virtual HRESULT STDCALL GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) override; + // ~ + + // IRawElementProviderFragmentRoot + virtual HRESULT STDCALL ElementProviderFromPoint(double x, double y, IRawElementProviderFragment** pRetVal) override; + virtual HRESULT STDCALL GetFocus(IRawElementProviderFragment** pRetVal) override; + // ~ + +protected: + /** Note: InWidget must also be an IAccessibleWindow */ + FWindowsUIAWindowProvider(FWindowsUIAManager& InManager, TSharedRef InWidget); + virtual ~FWindowsUIAWindowProvider(); +}; + +/** + * Helper class which can be used in conjunction with FWindowsUIAManager::GetWidgetProvider, which will + * automatically calls Release on the Provider when the variable goes out of scope. Note that GetWidgetProvider + * already increments the ref count when its called, so the scoped provider does not have to also increase it. + */ +class FScopedWidgetProvider +{ +public: + FScopedWidgetProvider(FWindowsUIAWidgetProvider& InProvider) + : Provider(InProvider) + { + } + ~FScopedWidgetProvider() + { + Provider.Release(); + } + FWindowsUIAWidgetProvider& Provider; +}; + +#endif diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h b/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h index 67a1d1b41cc6..e8ddc3996cb0 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Windows/WindowsApplication.h @@ -342,6 +342,9 @@ public: // GenericApplication overrides virtual void SetMessageHandler( const TSharedRef< class FGenericApplicationMessageHandler >& InMessageHandler ) override; +#if WITH_ACCESSIBILITY + virtual void SetAccessibleMessageHandler(const TSharedRef& InAccessibleMessageHandler) override; +#endif virtual void PollGameDeviceState( const float TimeDelta ) override; virtual void PumpMessages( const float TimeDelta ) override; virtual void ProcessDeferredEvents( const float TimeDelta ) override; @@ -507,6 +510,11 @@ private: TSharedPtr TaskbarList; +#if WITH_ACCESSIBILITY + /** Handler for WM_GetObject messages that come in */ + TUniquePtr UIAManager; +#endif + // Accessibility shortcut keys STICKYKEYS StartupStickyKeys; TOGGLEKEYS StartupToggleKeys; diff --git a/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp b/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp index cf51f2767262..009bddb54def 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/DSP/Noise.cpp @@ -5,21 +5,21 @@ namespace Audio { FWhiteNoise::FWhiteNoise(const float InScale, const float InAdd) - : Scale(InScale) - , Add(InAdd) { - + SetScaleAdd(InScale, InAdd); } void FWhiteNoise::SetScaleAdd(const float InScale, const float InAdd) { Scale = InScale; Add = InAdd; + + RandomStream.Initialize(HashCombine(GetTypeHash(Scale), GetTypeHash(Add))); } float FWhiteNoise::Generate() { - return Add + Scale * FMath::SRand() * 2 - 1.0f; + return Add + Scale * RandomStream.FRand() * 2 - 1.0f; } FPinkNoise::FPinkNoise(const float InScale, const float InAdd) diff --git a/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h b/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h index 1a9f8783ec08..4ede3a2a95f3 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h +++ b/Engine/Source/Runtime/AudioMixer/Public/DSP/Noise.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" namespace Audio { @@ -24,6 +25,8 @@ namespace Audio private: float Scale; float Add; + + FRandomStream RandomStream; }; /** diff --git a/Engine/Source/Runtime/Core/Private/Containers/String.cpp b/Engine/Source/Runtime/Core/Private/Containers/String.cpp index 14ff45af6a4e..a2c5b1ed0ab1 100644 --- a/Engine/Source/Runtime/Core/Private/Containers/String.cpp +++ b/Engine/Source/Runtime/Core/Private/Containers/String.cpp @@ -1587,4 +1587,35 @@ FString SlugStringForValidName(const FString& DisplayString, const TCHAR* Replac } return GeneratedName; -} \ No newline at end of file +} + +void FTextRange::CalculateLineRangesFromString(const FString& Input, TArray& LineRanges) +{ + int32 LineBeginIndex = 0; + + // Loop through splitting at new-lines + const TCHAR* const InputStart = *Input; + for (const TCHAR* CurrentChar = InputStart; CurrentChar && *CurrentChar; ++CurrentChar) + { + // Handle a chain of \r\n slightly differently to stop the FChar::IsLinebreak adding two separate new-lines + const bool bIsWindowsNewLine = (*CurrentChar == '\r' && *(CurrentChar + 1) == '\n'); + if (bIsWindowsNewLine || FChar::IsLinebreak(*CurrentChar)) + { + const int32 LineEndIndex = (CurrentChar - InputStart); + check(LineEndIndex >= LineBeginIndex); + LineRanges.Emplace(FTextRange(LineBeginIndex, LineEndIndex)); + + if (bIsWindowsNewLine) + { + ++CurrentChar; // skip the \n of the \r\n chain + } + LineBeginIndex = (CurrentChar - InputStart) + 1; // The next line begins after the end of the current line + } + } + + // Process any remaining string after the last new-line + if (LineBeginIndex <= Input.Len()) + { + LineRanges.Emplace(FTextRange(LineBeginIndex, Input.Len())); + } +} diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp index 2e9fd1c1f578..4f845002ac5c 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/StringTableCore.cpp @@ -577,7 +577,10 @@ void FStringTableRedirects::RedirectTableId(FName& InOutTableId) } // Process the asset redirect (only works if the asset is loaded) - IStringTableEngineBridge::RedirectStringTableAsset(InOutTableId); + if (IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) + { + IStringTableEngineBridge::RedirectStringTableAsset(InOutTableId); + } } void FStringTableRedirects::RedirectKey(const FName InTableId, FString& InOutKey) diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp index 59019f7aa4ed..9982609d591c 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/Text.cpp @@ -245,10 +245,10 @@ FText::FText( TSharedRef InTextData ) } FText::FText( FString&& InSourceString ) - : TextData(new TGeneratedTextData(FString(InSourceString))) + : TextData(new TGeneratedTextData(CopyTemp(InSourceString))) // Copy the source string as the live display string , Flags(0) { - TextData->SetTextHistory(FTextHistory_Base(MoveTemp(InSourceString))); + TextData->SetTextHistory(FTextHistory_Base(MoveTemp(InSourceString))); // Move the source string as the historic source string } FText::FText( FName InTableId, FString InKey, const EStringTableLoadingPolicy InLoadingPolicy ) @@ -321,119 +321,91 @@ FText FText::ToUpper() const FText FText::TrimPreceding( const FText& InText ) { - FString TrimmedString = InText.ToString(); - { - int32 StartPos = 0; - while ( StartPos < TrimmedString.Len() ) - { - if( !FText::IsWhitespace( TrimmedString[StartPos] ) ) - { - break; - } + const FString& CurrentString = InText.ToString(); - ++StartPos; + int32 StartPos = 0; + while (StartPos < CurrentString.Len()) + { + if (!FText::IsWhitespace(CurrentString[StartPos])) + { + break; } - TrimmedString = TrimmedString.Right( TrimmedString.Len() - StartPos ); + ++StartPos; } - FText NewText = FText( MoveTemp( TrimmedString ) ); - - if (!GIsEditor) + if (StartPos == 0) { - if( (NewText.Flags & ETextFlag::CultureInvariant) != 0 ) - { - NewText.Flags |= ETextFlag::Transient; - } - else - { - NewText.Flags |= ETextFlag::CultureInvariant; - } + // Nothing to trim! + return InText; } - return NewText; + // Trim the string, preserving culture invariance if set + FString TrimmedString = CurrentString.Right(CurrentString.Len() - StartPos); + return InText.IsCultureInvariant() ? FText::AsCultureInvariant(MoveTemp(TrimmedString)) : FText::FromString(MoveTemp(TrimmedString)); } FText FText::TrimTrailing( const FText& InText ) { - FString TrimmedString = InText.ToString(); - { - int32 EndPos = TrimmedString.Len() - 1; - while( EndPos >= 0 ) - { - if( !FText::IsWhitespace( TrimmedString[EndPos] ) ) - { - break; - } + const FString& CurrentString = InText.ToString(); - EndPos--; + int32 EndPos = CurrentString.Len() - 1; + while (EndPos >= 0) + { + if (!FText::IsWhitespace(CurrentString[EndPos])) + { + break; } - TrimmedString = TrimmedString.Left( EndPos + 1 ); + --EndPos; } - FText NewText = FText( MoveTemp ( TrimmedString ) ); - - if (!GIsEditor) + if (EndPos == CurrentString.Len() - 1) { - if( (NewText.Flags & ETextFlag::CultureInvariant) != 0 ) - { - NewText.Flags |= ETextFlag::Transient; - } - else - { - NewText.Flags |= ETextFlag::CultureInvariant; - } + // Nothing to trim! + return InText; } - return NewText; + // Trim the string, preserving culture invariance if set + FString TrimmedString = CurrentString.Left(EndPos + 1); + return InText.IsCultureInvariant() ? FText::AsCultureInvariant(MoveTemp(TrimmedString)) : FText::FromString(MoveTemp(TrimmedString)); } FText FText::TrimPrecedingAndTrailing( const FText& InText ) { - FString TrimmedString = InText.ToString(); + const FString& CurrentString = InText.ToString(); + + int32 StartPos = 0; + while (StartPos < CurrentString.Len()) { - int32 StartPos = 0; - while ( StartPos < TrimmedString.Len() ) + if (!FText::IsWhitespace(CurrentString[StartPos])) { - if( !FText::IsWhitespace( TrimmedString[StartPos] ) ) - { - break; - } - - ++StartPos; + break; } - int32 EndPos = TrimmedString.Len(); - while( EndPos > StartPos ) - { - if( !FText::IsWhitespace( TrimmedString[EndPos - 1] ) ) - { - break; - } - - --EndPos; - } - - const int32 Len = EndPos - StartPos; - TrimmedString = TrimmedString.Mid( StartPos, Len ); + ++StartPos; } - FText NewText = FText( MoveTemp( TrimmedString ) ); - - if (!GIsEditor) + int32 EndPos = CurrentString.Len() - 1; + while (EndPos > StartPos) { - if( (NewText.Flags & ETextFlag::CultureInvariant) != 0 ) + if (!FText::IsWhitespace(CurrentString[EndPos])) { - NewText.Flags |= ETextFlag::Transient; - } - else - { - NewText.Flags |= ETextFlag::CultureInvariant; + break; } + + --EndPos; } - return NewText; + if (StartPos == 0 && EndPos == CurrentString.Len() - 1) + { + // Nothing to trim! + return InText; + } + + // Trim the string, preserving culture invariance if set + FString TrimmedString = CurrentString.Mid(StartPos, EndPos - StartPos + 1); + return InText.IsCultureInvariant() ? FText::AsCultureInvariant(MoveTemp(TrimmedString)) : FText::FromString(MoveTemp(TrimmedString)); } void FText::GetFormatPatternParameters(const FTextFormat& Fmt, TArray& ParameterNames) @@ -824,6 +796,8 @@ void FText::SerializeText(FStructuredArchive::FSlot Slot, FText& Value) FArchive& UnderlyingArchive = Slot.GetUnderlyingArchive(); FStructuredArchive::FRecord Record = Slot.EnterRecord(); + UnderlyingArchive.UsingCustomVersion(FEditorObjectVersion::GUID); + //When duplicating, the CDO is used as the template, then values for the instance are assigned. //If we don't duplicate the string, the CDO and the instance are both pointing at the same thing. //This would result in all subsequently duplicated objects stamping over formerly duplicated ones. @@ -900,12 +874,20 @@ void FText::SerializeText(FStructuredArchive::FSlot Slot, FText& Value) if (UnderlyingArchive.IsSaving()) { // Skip the history for empty texts - bSerializeHistory = !Value.IsEmpty(); + bSerializeHistory = !Value.IsEmpty() && !Value.IsCultureInvariant(); if (!bSerializeHistory) { int8 HistoryType = (int8)ETextHistoryType::None; Record << NAMED_FIELD(HistoryType); + + bool bHasCultureInvariantString = !Value.IsEmpty() && Value.IsCultureInvariant(); + Record << NAMED_FIELD(bHasCultureInvariantString); + if (bHasCultureInvariantString) + { + FString CultureInvariantString = Value.GetSourceString(); + Record << NAMED_FIELD(CultureInvariantString); + } } } else if (UnderlyingArchive.IsLoading()) @@ -986,6 +968,18 @@ void FText::SerializeText(FStructuredArchive::FSlot Slot, FText& Value) { bSerializeHistory = false; Value.TextData = FText::GetEmpty().TextData; + + if (UnderlyingArchive.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::CultureInvariantTextSerializationKeyStability) + { + bool bHasCultureInvariantString = false; + Record << NAMED_FIELD(bHasCultureInvariantString); + if (bHasCultureInvariantString) + { + FString CultureInvariantString; + Record << NAMED_FIELD(CultureInvariantString); + Value.TextData = FText(MoveTemp(CultureInvariantString)).TextData; + } + } } } } @@ -1038,7 +1032,7 @@ FText FText::FromName( const FName& Val) FText FText::FromString( const FString& String ) { - FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText( FString(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(CopyTemp(String)); if (!GIsEditor) { @@ -1051,7 +1045,7 @@ FText FText::FromString( const FString& String ) FText FText::FromString( FString&& String ) { - FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText( MoveTemp(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(MoveTemp(String)); if (!GIsEditor) { @@ -1064,7 +1058,7 @@ FText FText::FromString( FString&& String ) FText FText::AsCultureInvariant( const FString& String ) { - FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText( FString(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(CopyTemp(String)); NewText.Flags |= ETextFlag::CultureInvariant; return NewText; @@ -1072,7 +1066,7 @@ FText FText::AsCultureInvariant( const FString& String ) FText FText::AsCultureInvariant( FString&& String ) { - FText NewText = String.IsEmpty() ? FText() : FText( MoveTemp(String) ); + FText NewText = String.IsEmpty() ? FText::GetEmpty() : FText(MoveTemp(String)); NewText.Flags |= ETextFlag::CultureInvariant; return NewText; @@ -1080,7 +1074,7 @@ FText FText::AsCultureInvariant( FString&& String ) FText FText::AsCultureInvariant( FText Text ) { - FText NewText = FText( MoveTemp(Text) ); + FText NewText = FText(MoveTemp(Text)); NewText.Flags |= ETextFlag::CultureInvariant; return NewText; diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h b/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h index e55ae6e06b24..f122a0b3b77b 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h +++ b/Engine/Source/Runtime/Core/Private/Internationalization/TextData.h @@ -158,7 +158,7 @@ public: if (!this->LocalizedString.IsValid()) { // We copy (rather than move) DisplayString here, as other threads may currently be accessing it - this->LocalizedString = MakeShareable(new FString(DisplayString)); + this->LocalizedString = MakeShared(DisplayString); } } } diff --git a/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp b/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp index b366abc102d2..ccfc2045a583 100644 --- a/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp +++ b/Engine/Source/Runtime/Core/Private/Internationalization/TextHistory.cpp @@ -836,34 +836,36 @@ void FTextHistory_Base::SerializeForDisplayString(FStructuredArchive::FRecord Re TextNamespaceUtil::StripPackageNamespaceInline(Namespace); } else -#if USE_STABLE_LOCALIZATION_KEYS - // Make sure the package namespace for this text property is up-to-date - if (GIsEditor && !BaseArchive.HasAnyPortFlags(PPF_DuplicateVerbatim | PPF_DuplicateForPIE)) { - const FString PackageNamespace = TextNamespaceUtil::GetPackageNamespace(BaseArchive); - if (!PackageNamespace.IsEmpty()) +#if USE_STABLE_LOCALIZATION_KEYS + // Make sure the package namespace for this text property is up-to-date + if (GIsEditor && !BaseArchive.HasAnyPortFlags(PPF_DuplicateVerbatim | PPF_DuplicateForPIE)) { - const FString FullNamespace = TextNamespaceUtil::BuildFullNamespace(Namespace, PackageNamespace); - if (!Namespace.Equals(FullNamespace, ESearchCase::CaseSensitive)) + const FString PackageNamespace = TextNamespaceUtil::GetPackageNamespace(BaseArchive); + if (!PackageNamespace.IsEmpty()) { - // We may assign a new key when saving if we don't have the correct package namespace in order to avoid identity conflicts when instancing (which duplicates without any special flags) - // This can happen if an asset was duplicated (and keeps the same keys) but later both assets are instanced into the same world (causing them to both take the worlds package id, and conflict with each other) - Namespace = FullNamespace; - Key = FGuid::NewGuid().ToString(); + const FString FullNamespace = TextNamespaceUtil::BuildFullNamespace(Namespace, PackageNamespace); + if (!Namespace.Equals(FullNamespace, ESearchCase::CaseSensitive)) + { + // We may assign a new key when saving if we don't have the correct package namespace in order to avoid identity conflicts when instancing (which duplicates without any special flags) + // This can happen if an asset was duplicated (and keeps the same keys) but later both assets are instanced into the same world (causing them to both take the worlds package id, and conflict with each other) + Namespace = FullNamespace; + Key = FGuid::NewGuid().ToString(); + } } } - } #endif // USE_STABLE_LOCALIZATION_KEYS - // If this has no key, give it a GUID for a key - if (!bFoundNamespaceAndKey && GIsEditor && (BaseArchive.IsPersistent() && !BaseArchive.HasAnyPortFlags(PPF_Duplicate))) - { - Key = FGuid::NewGuid().ToString(); - if (!FTextLocalizationManager::Get().AddDisplayString(InOutDisplayString.ToSharedRef(), Namespace, Key)) + // If this has no key, give it a GUID for a key + if (!bFoundNamespaceAndKey && GIsEditor && (BaseArchive.IsPersistent() && !BaseArchive.HasAnyPortFlags(PPF_Duplicate))) { - // Could not add display string, reset namespace and key. - Namespace.Empty(); - Key.Empty(); + Key = FGuid::NewGuid().ToString(); + if (!FTextLocalizationManager::Get().AddDisplayString(InOutDisplayString.ToSharedRef(), Namespace, Key)) + { + // Could not add display string, reset namespace and key. + Namespace.Empty(); + Key.Empty(); + } } } @@ -2577,7 +2579,7 @@ void FTextHistory_StringTableEntry::FStringTableReferenceData::Initialize(uint16 LoadingPhase = EStringTableLoadingPhase::Loaded; ResolveStringTableEntry(); } - else if (InLoadingPolicy == EStringTableLoadingPolicy::FindOrFullyLoad && IsInGameThread()) + else if (InLoadingPolicy == EStringTableLoadingPolicy::FindOrFullyLoad && IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) { // Forced synchronous load LoadingPhase = EStringTableLoadingPhase::Loaded; @@ -2667,7 +2669,7 @@ FStringTableEntryConstPtr FTextHistory_StringTableEntry::FStringTableReferenceDa void FTextHistory_StringTableEntry::FStringTableReferenceData::ConditionalBeginAssetLoad() { - if (!IsInGameThread()) + if (!IStringTableEngineBridge::CanFindOrLoadStringTableAsset()) { return; } diff --git a/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp b/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp index cef648b721a1..a332401e6196 100644 --- a/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp +++ b/Engine/Source/Runtime/Core/Private/Mac/MacPlatformMisc.cpp @@ -81,7 +81,7 @@ static TAutoConsoleVariable CVarMacPlatformDumpAllThreadsOnHang( FMacApplicationInfo - class to contain all state for crash reporting that is unsafe to acquire in a signal. ------------------------------------------------------------------------------*/ -FMacMallocCrashHandler* GCrashMalloc = nullptr; + CORE_API FMacMallocCrashHandler* GCrashMalloc = nullptr; /** * Information that cannot be obtained during a signal-handler is initialised here. diff --git a/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp b/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp index 297df5e000ff..db0e4998e748 100644 --- a/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp +++ b/Engine/Source/Runtime/Core/Private/Tests/Math/UnrealMathTest.cpp @@ -1071,6 +1071,18 @@ bool FVectorRegisterAbstractionTest::RunTest(const FString& Parameters) V3 = VectorMod(V0, V1); LogTest( TEXT("VectorMod negative"), TestVectorsEqual(V2, V3)); + // VectorSign + V0 = MakeVectorRegister(2.0f, -2.0f, 0.0f, -3.0f); + V2 = MakeVectorRegister(1.0f, -1.0f, 1.0f, -1.0f); + V3 = VectorSign(V0); + LogTest(TEXT("VectorSign"), TestVectorsEqual(V2, V3)); + + // VectorStep + V0 = MakeVectorRegister(2.0f, -2.0f, 0.0f, -3.0f); + V2 = MakeVectorRegister(1.0f, 0.0f, 1.0f, 0.0f); + V3 = VectorStep(V0); + LogTest(TEXT("VectorStep"), TestVectorsEqual(V2, V3)); + FMatrix M0, M1, M2, M3; FVector Eye, LookAt, Up; // Create Look at Matrix diff --git a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp index 472d4d7e0ae2..419df7eef3dc 100644 --- a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp +++ b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformCrashContext.cpp @@ -24,6 +24,7 @@ #include "Misc/App.h" #include "Misc/EngineVersion.h" #include "HAL/PlatformMallocCrash.h" +#include "Unix/UnixPlatformRealTimeSignals.h" #include "Unix/UnixPlatformRunnableThread.h" #include "HAL/ExceptionHandling.h" #include "Stats/Stats.h" @@ -723,6 +724,27 @@ void PlatformCrashHandler(int32 Signal, siginfo_t* Info, void* Context) } } +void ThreadStackWalker(int32 Signal, siginfo_t* Info, void* Context) +{ + ThreadStackUserData* ThreadStackData = static_cast(Info->si_value.sival_ptr); + + if (ThreadStackData) + { + if (ThreadStackData->bCaptureCallStack) + { + // One for the pthread frame and one for siqueue + int32 IgnoreCount = 2; + FPlatformStackWalk::StackWalkAndDump(ThreadStackData->CallStack, ThreadStackData->CallStackSize, IgnoreCount); + } + else + { + ThreadStackData->BackTraceCount = FPlatformStackWalk::CaptureStackBackTrace(ThreadStackData->BackTrace, ThreadStackData->CallStackSize); + } + + ThreadStackData->bDone = true; + } +} + void FUnixPlatformMisc::SetGracefulTerminationHandler() { struct sigaction Action; @@ -813,6 +835,13 @@ void FUnixPlatformMisc::SetCrashHandler(void (* CrashHandler)(const FGenericCras } } + struct sigaction ActionForThread; + FMemory::Memzero(ActionForThread); + sigfillset(&ActionForThread.sa_mask); + ActionForThread.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK; + ActionForThread.sa_sigaction = ThreadStackWalker; + sigaction(THREAD_CALLSTACK_GENERATOR, &ActionForThread, nullptr); + checkf(IsInGameThread(), TEXT("Crash handler for the game thread should be set from the game thread only.")); FRunnableThreadUnix::SetupSignalHandlerStack(FRunnableThreadUnix::MainThreadSignalHandlerStack, sizeof(FRunnableThreadUnix::MainThreadSignalHandlerStack), nullptr); diff --git a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp index 09bfa267b985..454b41f8287e 100644 --- a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp +++ b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformMisc.cpp @@ -46,6 +46,8 @@ extern bool GInitializedSDL; +static int SysGetRandomSupported = -1; + namespace PlatformMiscLimits { enum @@ -229,6 +231,12 @@ void FUnixPlatformMisc::PlatformInit() // print output immediately setvbuf(stdout, NULL, _IONBF, 0); } + + if (FParse::Param(FCommandLine::Get(), TEXT("norandomguids"))) + { + // If "-norandomguids" specified, don't use SYS_getrandom syscall + SysGetRandomSupported = 0; + } } extern void CORE_API UnixPlatformStackWalk_UnloadPreloadedModuleSymbol(); @@ -900,6 +908,85 @@ bool FUnixPlatformMisc::IsRunningOnBattery() return bIsOnBattery; } +#if PLATFORM_UNIX && defined(_GNU_SOURCE) + +#include + +// http://man7.org/linux/man-pages/man2/getrandom.2.html +// getrandom() was introduced in version 3.17 of the Linux kernel +// and glibc version 2.25. + +// Check known platforms if SYS_getrandom isn't defined +#if !defined(SYS_getrandom) + #if PLATFORM_CPU_X86_FAMILY && PLATFORM_64BITS + #define SYS_getrandom 318 + #elif PLATFORM_CPU_X86_FAMILY && !PLATFORM_64BITS + #define SYS_getrandom 355 + #elif PLATFORM_CPU_ARM_FAMILY && PLATFORM_64BITS + #define SYS_getrandom 278 + #elif PLATFORM_CPU_ARM_FAMILY && !PLATFORM_64BITS + #define SYS_getrandom 384 + #endif +#endif // !defined(SYS_getrandom) + +#endif // PLATFORM_UNIX && _GNU_SOURCE + +namespace +{ +#if defined(SYS_getrandom) + +#if !defined(GRND_NONBLOCK) + #define GRND_NONBLOCK 0x0001 +#endif + + int SysGetRandom(void *buf, size_t buflen) + { + if (SysGetRandomSupported < 0) + { + int Ret = syscall(SYS_getrandom, buf, buflen, GRND_NONBLOCK); + + // If -1 is returned with ENOSYS, kernel doesn't support getrandom + SysGetRandomSupported = ((Ret == -1) && (errno == ENOSYS)) ? 0 : 1; + } + + return SysGetRandomSupported ? + syscall(SYS_getrandom, buf, buflen, GRND_NONBLOCK) : -1; + } + +#else + + int SysGetRandom(void *buf, size_t buflen) + { + return -1; + } + +#endif // !SYS_getrandom +} + +// If we fail to create a Guid with urandom fallback to the generic platform. +// This maybe need to be tweaked for Servers and hard fail here +void FUnixPlatformMisc::CreateGuid(FGuid& Result) +{ + int BytesRead = SysGetRandom(&Result, sizeof(Result)); + + if (BytesRead == sizeof(Result)) + { + // https://tools.ietf.org/html/rfc4122#section-4.4 + // https://en.wikipedia.org/wiki/Universally_unique_identifier + // + // The 4 bits of digit M indicate the UUID version, and the 1–3 + // most significant bits of digit N indicate the UUID variant. + // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + Result[1] = (Result[1] & 0xffff0fff) | 0x00004000; // version 4 + Result[2] = (Result[2] & 0x3fffffff) | 0x80000000; // variant 1 + } + else + { + // Fall back to generic CreateGuid + FGenericPlatformMisc::CreateGuid(Result); + } +} + #if STATS || ENABLE_STATNAMEDEVENTS void FUnixPlatformMisc::BeginNamedEventFrame() { diff --git a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp index 026da8b4981a..86dcab8a9876 100644 --- a/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp +++ b/Engine/Source/Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp @@ -11,11 +11,20 @@ #include "Misc/ScopeLock.h" #include "Misc/CommandLine.h" #include "Unix/UnixPlatformCrashContext.h" +#include "Unix/UnixPlatformRealTimeSignals.h" #include "HAL/ExceptionHandling.h" #include "HAL/PlatformProcess.h" #include "HAL/PlatformTime.h" #include +#include + +#include "HAL/IConsoleManager.h" + +static TAutoConsoleVariable CVarUnixPlatformThreadCallStackMaxWait( + TEXT("UnixPlatformThreadStackWalk.MaxWait"), + 60.0f, + TEXT("The number of seconds allowed to spin before killing the process, with the assumption the signal handler has hung.")); // Init'ed in UnixPlatformMemory. Once this is tested more we can remove this fallback flag extern bool CORE_API GFullCrashCallstack; @@ -494,74 +503,89 @@ bool FUnixPlatformStackWalk::ProgramCounterToHumanReadableString( int32 CurrentC // Get filename, source file and line number FUnixCrashContext* UnixContext = static_cast< FUnixCrashContext* >( Context ); - if (UnixContext) + + // for ensure, use the fast path - do not even attempt to get detailed info as it will result in long hitch + bool bAddDetailedInfo = UnixContext ? UnixContext->GetType() != ECrashContextType::Ensure : false; + + // Program counters in the backtrace point to the location from where the execution will be resumed (in all frames except the one where we crashed), + // which results in callstack pointing to the next lines in code. In order to determine the source line where the actual call happened, we need to go + // back to the line that had the "call" instruction. Since x86(-64) instructions vary in length, we cannot do it reliably without disassembling, + // just go back one byte - even if it's not the actual address of the call site. + int OffsetToCallsite = CurrentCallDepth > 0 ? 1 : 0; + + FProgramCounterSymbolInfo TempSymbolInfo; + + // We can print detail info out during ensures, the only reason not to is if fail to populate the symbol info all the way + bAddDetailedInfo = PopulateProgramCounterSymbolInfoFromSymbolFile(ProgramCounter - OffsetToCallsite, TempSymbolInfo); + + if (bAddDetailedInfo) { - // for ensure, use the fast path - do not even attempt to get detailed info as it will result in long hitch - bool bAddDetailedInfo = UnixContext->GetType() != ECrashContextType::Ensure; + // append Module!FunctionName() [Source.cpp:X] to HumanReadableString + FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.ModuleName, HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.FunctionName, HumanReadableStringSize); + FCStringAnsi::Sprintf(TempArray, " [%s:%d]", TempSymbolInfo.Filename, TempSymbolInfo.LineNumber); + FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); - // Program counters in the backtrace point to the location from where the execution will be resumed (in all frames except the one where we crashed), - // which results in callstack pointing to the next lines in code. In order to determine the source line where the actual call happened, we need to go - // back to the line that had the "call" instruction. Since x86(-64) instructions vary in length, we cannot do it reliably without disassembling, - // just go back one byte - even if it's not the actual address of the call site. - int OffsetToCallsite = CurrentCallDepth > 0 ? 1 : 0; - - FProgramCounterSymbolInfo TempSymbolInfo; - - // We can print detail info out during ensures, the only reason not to is if fail to populate the symbol info all the way - bAddDetailedInfo = PopulateProgramCounterSymbolInfoFromSymbolFile(ProgramCounter - OffsetToCallsite, TempSymbolInfo); - - if (bAddDetailedInfo) + if (UnixContext) { - // append Module!FunctionName() [Source.cpp:X] to HumanReadableString - FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.ModuleName, HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, TempSymbolInfo.FunctionName, HumanReadableStringSize); - FCStringAnsi::Sprintf(TempArray, " [%s:%d]", TempSymbolInfo.Filename, TempSymbolInfo.LineNumber); - FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); - // append Module!FunctioName [Source.cpp:X] to MinidumpCallstackInfo FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempSymbolInfo.ModuleName, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, "!", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempSymbolInfo.FunctionName, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempArray, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); } - else + } + else + { + const char* ModuleName = nullptr; + const char* FunctionName = nullptr; + + // We have failed to fully populate the SymbolInfo, but we could still have basic information. Lets try to print as much info as possible + if (TempSymbolInfo.ModuleName[0] != '\0') { - const char* ModuleName = nullptr; - const char* FunctionName = nullptr; + ModuleName = TempSymbolInfo.ModuleName; + } - // We have failed to fully populate the SymbolInfo, but we could still have basic information. Lets try to print as much info as possible - if (TempSymbolInfo.ModuleName[0] != '\0') - { - ModuleName = TempSymbolInfo.ModuleName; - } + if (TempSymbolInfo.FunctionName[0] != '\0') + { + FunctionName = TempSymbolInfo.FunctionName; + } - if (TempSymbolInfo.FunctionName[0] != '\0') - { - FunctionName = TempSymbolInfo.FunctionName; - } - - FCStringAnsi::Strncat(HumanReadableString, ModuleName != nullptr ? ModuleName : "", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, FunctionName != nullptr ? FunctionName : "UnknownFunction", HumanReadableStringSize); - FCStringAnsi::Strncat(HumanReadableString, FunctionName && TempSymbolInfo.SymbolDisplacement ? "(+": "(", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, ModuleName != nullptr ? ModuleName : "", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, "!", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, FunctionName != nullptr ? FunctionName : "UnknownFunction", HumanReadableStringSize); + FCStringAnsi::Strncat(HumanReadableString, FunctionName && TempSymbolInfo.SymbolDisplacement ? "(+": "(", HumanReadableStringSize); + if (UnixContext) + { FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, ModuleName != nullptr ? ModuleName : "Unknown", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, "!", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, FunctionName != nullptr ? FunctionName : "UnknownFunction", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, FunctionName && TempSymbolInfo.SymbolDisplacement ? "(+": "(", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); - - if (TempSymbolInfo.SymbolDisplacement > 0x0) - { - FCStringAnsi::Sprintf(TempArray, "%p", TempSymbolInfo.SymbolDisplacement); - FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); - FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempArray, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); - } - - FCStringAnsi::Strncat(HumanReadableString, ")", HumanReadableStringSize); - FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, ")", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); } + if (TempSymbolInfo.SymbolDisplacement > 0x0) + { + FCStringAnsi::Sprintf(TempArray, "%p", TempSymbolInfo.SymbolDisplacement); + FCStringAnsi::Strncat(HumanReadableString, TempArray, HumanReadableStringSize); + + if (UnixContext) + { + FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, TempArray, ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); + } + } + + FCStringAnsi::Strncat(HumanReadableString, ")", HumanReadableStringSize); + + if (UnixContext) + { + FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, ")", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); + } + } + + if (UnixContext) + { FCStringAnsi::Strncat(UnixContext->MinidumpCallstackInfo, "\r\n", ARRAY_COUNT( UnixContext->MinidumpCallstackInfo ) - 1); // this one always uses Windows line terminators } } @@ -674,19 +698,72 @@ uint32 FUnixPlatformStackWalk::CaptureStackBackTrace( uint64* BackTrace, uint32 return (uint32)Size; } -void FUnixPlatformStackWalk::ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId) +namespace { - // This is intentional on servers. Right now we cannot symbolicate the other thread, so we crash it instead, which also helps in identifying lock ups - if (UE_SERVER) + void WaitForSignalHandlerToFinishOrCrash(ThreadStackUserData& ThreadStack) { - if (kill(ThreadId, SIGQUIT) == 0) + float EndWaitTimestamp = FPlatformTime::Seconds() + CVarUnixPlatformThreadCallStackMaxWait.AsVariable()->GetFloat(); + float CurrentTimestamp = FPlatformTime::Seconds(); + + while (!ThreadStack.bDone) { - // do not exit, crash is imminent anyway (signals are delivered asynchronously) - for(;;) + if (CurrentTimestamp > EndWaitTimestamp) { + // We have waited for as long as we should for the signal handler to finish. Assume it has hang and we need to kill our selfs + *(int*)0x10 = 0x0; } + + CurrentTimestamp = FPlatformTime::Seconds(); } } + + void GatherCallstackFromThread(ThreadStackUserData& ThreadStack, uint64 ThreadId) + { + sigval UserData; + UserData.sival_ptr = &ThreadStack; + + siginfo_t info; + memset (&info, 0, sizeof (siginfo_t)); + info.si_signo = THREAD_CALLSTACK_GENERATOR; + info.si_code = SI_QUEUE; + info.si_pid = syscall(SYS_getpid); + info.si_uid = syscall(SYS_getuid); + info.si_value = UserData; + + // Avoid using sigqueue here as if the ThreadId is already blocked and in a signal handler + // sigqueue will try a different thread signal handler and report the wrong callstack + if (syscall(SYS_rt_tgsigqueueinfo, info.si_pid, ThreadId, THREAD_CALLSTACK_GENERATOR, &info) == 0) + { + WaitForSignalHandlerToFinishOrCrash(ThreadStack); + } + } +} + +void FUnixPlatformStackWalk::ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId) +{ + ThreadStackUserData ThreadCallStack; + ThreadCallStack.bCaptureCallStack = true; + ThreadCallStack.CallStackSize = HumanReadableStringSize; + ThreadCallStack.CallStack = HumanReadableString; + ThreadCallStack.BackTraceCount = 0; + ThreadCallStack.bDone = false; + + GatherCallstackFromThread(ThreadCallStack, ThreadId); +} + +uint32 FUnixPlatformStackWalk::CaptureThreadStackBackTrace(uint64 ThreadId, uint64* BackTrace, uint32 MaxDepth) +{ + ThreadStackUserData ThreadBackTrace; + ThreadBackTrace.bCaptureCallStack = false; + ThreadBackTrace.CallStackSize = MaxDepth; + ThreadBackTrace.BackTrace = BackTrace; + ThreadBackTrace.BackTraceCount = 0; + ThreadBackTrace.bDone = false; + + GatherCallstackFromThread(ThreadBackTrace, ThreadId); + + // The signal handler will set this value, we just have to make sure we wait for the signal handler we raised to finish + return ThreadBackTrace.BackTraceCount; } namespace diff --git a/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h b/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h index 8ab48e8e1af1..65c22a473390 100644 --- a/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Android/AndroidPlatform.h @@ -126,6 +126,12 @@ typedef FAndroidTypes FPlatformTypes; #define ABSTRACT abstract +// DLL export and import for types, only supported on clang +#if (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 8)) +#define DLLEXPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#define DLLIMPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#endif + // DLL export and import definitions #define DLLEXPORT __attribute__((visibility("default"))) #define DLLIMPORT __attribute__((visibility("default"))) diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h index da0227d9a813..7af4b37f98b7 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformCompilerPreSetup.h @@ -79,3 +79,6 @@ #define PRAGMA_DISABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize off") #define PRAGMA_ENABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize on") #endif + +#define PRAGMA_DEFAULT_VISIBILITY_START _Pragma("GCC visibility push(default)") +#define PRAGMA_DEFAULT_VISIBILITY_END _Pragma("GCC visibility pop") diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h index bce0a25246dc..74a67634306f 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformMemory.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN * This ensures that memory allocated by custom Objective-C types can be tracked by UE4's tools and * that we benefit from the memory allocator's efficiencies. */ -@interface FApplePlatformObject : NSObject +OBJC_EXPORT @interface FApplePlatformObject : NSObject { @private OSQueueHead* AllocatorPtr; diff --git a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h index 7a8a55d13301..5a7258a1963a 100644 --- a/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h +++ b/Engine/Source/Runtime/Core/Public/Apple/ApplePlatformSymbolication.h @@ -25,10 +25,10 @@ struct FApplePlatformSymbolCache */ struct FApplePlatformSymbolDatabase { - FApplePlatformSymbolDatabase(); - FApplePlatformSymbolDatabase(FApplePlatformSymbolDatabase const& Other); - ~FApplePlatformSymbolDatabase(); - FApplePlatformSymbolDatabase& operator=(FApplePlatformSymbolDatabase const& Other); + CORE_API FApplePlatformSymbolDatabase(); + CORE_API FApplePlatformSymbolDatabase(FApplePlatformSymbolDatabase const& Other); + CORE_API ~FApplePlatformSymbolDatabase(); + CORE_API FApplePlatformSymbolDatabase& operator=(FApplePlatformSymbolDatabase const& Other); TSharedPtr GenericDB; FApplePlatformSymbolCache AppleDB; diff --git a/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h b/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h index 7db68f9731dc..31316ab1d3ff 100644 --- a/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h +++ b/Engine/Source/Runtime/Core/Public/Containers/UnrealString.h @@ -22,6 +22,7 @@ #include "Templates/IsValidVariadicFunctionArg.h" #include "Templates/AndOrNot.h" #include "Templates/IsArrayOrRefOfType.h" +#include "Templates/TypeHash.h" struct FStringFormatArg; template class TMap; @@ -1167,7 +1168,8 @@ public: FORCEINLINE FString Mid( int32 Start, int32 Count=MAX_int32 ) const { check(Count >= 0); - uint32 End = Start+Count; + uint32 End = Count; + End += Start; Start = FMath::Clamp( (uint32)Start, (uint32)0, (uint32)Len() ); End = FMath::Clamp( (uint32)End, (uint32)Start, (uint32)Len() ); return FString( End-Start, **this + Start ); @@ -2378,4 +2380,65 @@ CORE_API int32 FindMatchingClosingParenthesis(const FString& TargetString, const */ CORE_API FString SlugStringForValidName(const FString& DisplayString, const TCHAR* ReplaceWith = TEXT("")); +struct CORE_API FTextRange +{ + FTextRange() + : BeginIndex(INDEX_NONE) + , EndIndex(INDEX_NONE) + { + + } + + FTextRange(int32 InBeginIndex, int32 InEndIndex) + : BeginIndex(InBeginIndex) + , EndIndex(InEndIndex) + { + + } + + FORCEINLINE bool operator==(const FTextRange& Other) const + { + return BeginIndex == Other.BeginIndex + && EndIndex == Other.EndIndex; + } + + FORCEINLINE bool operator!=(const FTextRange& Other) const + { + return !(*this == Other); + } + + friend inline uint32 GetTypeHash(const FTextRange& Key) + { + uint32 KeyHash = 0; + KeyHash = HashCombine(KeyHash, GetTypeHash(Key.BeginIndex)); + KeyHash = HashCombine(KeyHash, GetTypeHash(Key.EndIndex)); + return KeyHash; + } + + int32 Len() const { return EndIndex - BeginIndex; } + bool IsEmpty() const { return (EndIndex - BeginIndex) <= 0; } + void Offset(int32 Amount) { BeginIndex += Amount; BeginIndex = FMath::Max(0, BeginIndex); EndIndex += Amount; EndIndex = FMath::Max(0, EndIndex); } + bool Contains(int32 Index) const { return Index >= BeginIndex && Index < EndIndex; } + bool InclusiveContains(int32 Index) const { return Index >= BeginIndex && Index <= EndIndex; } + + FTextRange Intersect(const FTextRange& Other) const + { + FTextRange Intersected(FMath::Max(BeginIndex, Other.BeginIndex), FMath::Min(EndIndex, Other.EndIndex)); + if (Intersected.EndIndex <= Intersected.BeginIndex) + { + return FTextRange(0, 0); + } + + return Intersected; + } + + /** + * Produce an array of line ranges from the given text, breaking at any new-line characters + */ + static void CalculateLineRangesFromString(const FString& Input, TArray& LineRanges); + + int32 BeginIndex; + int32 EndIndex; +}; + #include "Misc/StringFormatArg.h" diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h index 44db4111d6e4..c3815c8ce442 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericPlatformCompilerPreSetup.h @@ -87,3 +87,11 @@ #ifndef DEPRECATED_MACRO #define DEPRECATED_MACRO(Version, Message) EMIT_CUSTOM_WARNING(Message " Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.") #endif + +#ifndef PRAGMA_DEFAULT_VISIBILITY_START + #define PRAGMA_DEFAULT_VISIBILITY_START +#endif + +#ifndef PRAGMA_DEFAULT_VISIBILITY_END + #define PRAGMA_DEFAULT_VISIBILITY_END +#endif diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h index dc49ad727edd..31b2ca25d7b0 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/GenericWidePlatformString.h @@ -13,7 +13,7 @@ /** * Standard implementation **/ -struct FGenericWidePlatformString : public FGenericPlatformString +struct CORE_VTABLE FGenericWidePlatformString : public FGenericPlatformString { template static inline CharType* Strupr(CharType* Dest, SIZE_T DestCount) @@ -30,11 +30,11 @@ public: /** * Unicode implementation **/ - static WIDECHAR* Strcpy(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); - static WIDECHAR* Strncpy(WIDECHAR* Dest, const WIDECHAR* Src, SIZE_T MaxLen); - static WIDECHAR* Strcat(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); + CORE_API static WIDECHAR* Strcpy(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); + CORE_API static WIDECHAR* Strncpy(WIDECHAR* Dest, const WIDECHAR* Src, SIZE_T MaxLen); + CORE_API static WIDECHAR* Strcat(WIDECHAR* Dest, SIZE_T DestCount, const WIDECHAR* Src); - static int32 Strcmp( const WIDECHAR* String1, const WIDECHAR* String2 ) + CORE_API static int32 Strcmp( const WIDECHAR* String1, const WIDECHAR* String2 ) { // walk the strings, comparing them case sensitively for (; *String1 || *String2; String1++, String2++) @@ -48,7 +48,7 @@ public: return 0; } - static int32 Strncmp( const WIDECHAR* String1, const WIDECHAR* String2, SIZE_T Count ) + CORE_API static int32 Strncmp( const WIDECHAR* String1, const WIDECHAR* String2, SIZE_T Count ) { // walk the strings, comparing them case sensitively, up to a max size for (; (*String1 || *String2) && Count; String1++, String2++, Count--) @@ -62,7 +62,7 @@ public: return 0; } - static int32 Strlen( const WIDECHAR* String ) + CORE_API static int32 Strlen( const WIDECHAR* String ) { int32 Length = -1; @@ -75,6 +75,7 @@ public: return Length; } + #if PLATFORM_TCHAR_IS_CHAR16 static int32 Strlen( const wchar_t* String ) { @@ -89,8 +90,8 @@ public: return Length; } #endif - - static const WIDECHAR* Strstr( const WIDECHAR* String, const WIDECHAR* Find) + + CORE_API static const WIDECHAR* Strstr( const WIDECHAR* String, const WIDECHAR* Find) { WIDECHAR Char1, Char2; if ((Char1 = *Find++) != 0) @@ -116,7 +117,7 @@ public: return String; } - static const WIDECHAR* Strchr( const WIDECHAR* String, WIDECHAR C) + CORE_API static const WIDECHAR* Strchr( const WIDECHAR* String, WIDECHAR C) { while (*String != C && *String != 0) { @@ -126,7 +127,7 @@ public: return (*String == C) ? String : nullptr; } - static const WIDECHAR* Strrchr( const WIDECHAR* String, WIDECHAR C) + CORE_API static const WIDECHAR* Strrchr( const WIDECHAR* String, WIDECHAR C) { const WIDECHAR *Last = nullptr; @@ -148,28 +149,28 @@ public: return Last; } - static int32 Strtoi( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); - static int64 Strtoi64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); - static uint64 Strtoui64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); - static float Atof(const WIDECHAR* String); - static double Atod(const WIDECHAR* String); + CORE_API static int32 Strtoi( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); + CORE_API static int64 Strtoi64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); + CORE_API static uint64 Strtoui64( const WIDECHAR* Start, WIDECHAR** End, int32 Base ); + CORE_API static float Atof(const WIDECHAR* String); + CORE_API static double Atod(const WIDECHAR* String); - static FORCEINLINE int32 Atoi(const WIDECHAR* String) + CORE_API static FORCEINLINE int32 Atoi(const WIDECHAR* String) { return Strtoi( String, NULL, 10 ); } - static FORCEINLINE int64 Atoi64(const WIDECHAR* String) + CORE_API static FORCEINLINE int64 Atoi64(const WIDECHAR* String) { return Strtoi64( String, NULL, 10 ); } - static WIDECHAR* Strtok( WIDECHAR* StrToken, const WIDECHAR* Delim, WIDECHAR** Context ); + static CORE_API WIDECHAR* Strtok( WIDECHAR* StrToken, const WIDECHAR* Delim, WIDECHAR** Context ); UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr ) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } @@ -179,100 +180,100 @@ public: /** * Ansi implementation **/ - static FORCEINLINE ANSICHAR* Strcpy(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) + CORE_API static FORCEINLINE ANSICHAR* Strcpy(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) { return strcpy( Dest, Src ); } - static FORCEINLINE ANSICHAR* Strncpy(ANSICHAR* Dest, const ANSICHAR* Src, int32 MaxLen) + CORE_API static FORCEINLINE ANSICHAR* Strncpy(ANSICHAR* Dest, const ANSICHAR* Src, int32 MaxLen) { ::strncpy(Dest, Src, MaxLen); Dest[MaxLen-1]=0; return Dest; } - static FORCEINLINE ANSICHAR* Strcat(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) + CORE_API static FORCEINLINE ANSICHAR* Strcat(ANSICHAR* Dest, SIZE_T DestCount, const ANSICHAR* Src) { return strcat( Dest, Src ); } - static FORCEINLINE int32 Strcmp( const ANSICHAR* String1, const ANSICHAR* String2 ) + CORE_API static FORCEINLINE int32 Strcmp( const ANSICHAR* String1, const ANSICHAR* String2 ) { return strcmp(String1, String2); } - static FORCEINLINE int32 Strncmp( const ANSICHAR* String1, const ANSICHAR* String2, SIZE_T Count ) + CORE_API static FORCEINLINE int32 Strncmp( const ANSICHAR* String1, const ANSICHAR* String2, SIZE_T Count ) { return strncmp( String1, String2, Count ); } - static FORCEINLINE int32 Strlen( const ANSICHAR* String ) + CORE_API static FORCEINLINE int32 Strlen( const ANSICHAR* String ) { return strlen( String ); } - static FORCEINLINE const ANSICHAR* Strstr( const ANSICHAR* String, const ANSICHAR* Find) + CORE_API static FORCEINLINE const ANSICHAR* Strstr( const ANSICHAR* String, const ANSICHAR* Find) { return strstr(String, Find); } - static FORCEINLINE const ANSICHAR* Strchr( const ANSICHAR* String, ANSICHAR C) + CORE_API static FORCEINLINE const ANSICHAR* Strchr( const ANSICHAR* String, ANSICHAR C) { return strchr(String, C); } - static FORCEINLINE const ANSICHAR* Strrchr( const ANSICHAR* String, ANSICHAR C) + CORE_API static FORCEINLINE const ANSICHAR* Strrchr( const ANSICHAR* String, ANSICHAR C) { return strrchr(String, C); } - static FORCEINLINE int32 Atoi(const ANSICHAR* String) + CORE_API static FORCEINLINE int32 Atoi(const ANSICHAR* String) { return atoi( String ); } - static FORCEINLINE int64 Atoi64(const ANSICHAR* String) + CORE_API static FORCEINLINE int64 Atoi64(const ANSICHAR* String) { return strtoll( String, NULL, 10 ); } - static FORCEINLINE float Atof(const ANSICHAR* String) + CORE_API static FORCEINLINE float Atof(const ANSICHAR* String) { return (float)atof( String ); } - static FORCEINLINE double Atod(const ANSICHAR* String) + CORE_API static FORCEINLINE double Atod(const ANSICHAR* String) { return atof( String ); } - static FORCEINLINE int32 Strtoi( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) + CORE_API static FORCEINLINE int32 Strtoi( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) { return strtol( Start, End, Base ); } - static FORCEINLINE int64 Strtoi64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) + CORE_API static FORCEINLINE int64 Strtoi64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) { return strtoll(Start, End, Base); } - static FORCEINLINE uint64 Strtoui64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) + CORE_API static FORCEINLINE uint64 Strtoui64( const ANSICHAR* Start, ANSICHAR** End, int32 Base ) { return strtoull(Start, End, Base); } - static FORCEINLINE ANSICHAR* Strtok(ANSICHAR* StrToken, const ANSICHAR* Delim, ANSICHAR** Context) + CORE_API static FORCEINLINE ANSICHAR* Strtok(ANSICHAR* StrToken, const ANSICHAR* Delim, ANSICHAR** Context) { return strtok(StrToken, Delim); } UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) { int32 Result = vsnprintf(Dest, DestSize, Fmt, ArgPtr); va_end( ArgPtr ); @@ -283,7 +284,7 @@ public: * UCS2 implementation **/ - static FORCEINLINE int32 Strlen( const UCS2CHAR* String ) + CORE_API static FORCEINLINE int32 Strlen( const UCS2CHAR* String ) { int32 Result = 0; while (*String++) diff --git a/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h b/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h index 57bce54f18f6..4ae40f97dd4f 100644 --- a/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h +++ b/Engine/Source/Runtime/Core/Public/GenericPlatform/StandardPlatformString.h @@ -121,13 +121,13 @@ public: } UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs(WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr) + static CORE_API int32 GetVarArgs(WIDECHAR* Dest, SIZE_T DestSize, int32 Count, const WIDECHAR*& Fmt, va_list ArgPtr) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } #if PLATFORM_USE_SYSTEM_VSWPRINTF - static int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ) { #if PLATFORM_USE_LS_SPEC_FOR_WIDECHAR // fix up the Fmt string, as fast as possible, without using an FString @@ -184,7 +184,7 @@ public: return Result; } #else // PLATFORM_USE_SYSTEM_VSWPRINTF - static int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ); + static CORE_API int32 GetVarArgs( WIDECHAR* Dest, SIZE_T DestSize, const WIDECHAR*& Fmt, va_list ArgPtr ); #endif // PLATFORM_USE_SYSTEM_VSWPRINTF /** @@ -278,12 +278,12 @@ public: } UE_DEPRECATED(4.22, "GetVarArgs with DestSize and Count arguments has been deprecated - only DestSize should be passed") - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, int32 Count, const ANSICHAR*& Fmt, va_list ArgPtr ) { return GetVarArgs(Dest, DestSize, Fmt, ArgPtr); } - static int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) + static CORE_API int32 GetVarArgs( ANSICHAR* Dest, SIZE_T DestSize, const ANSICHAR*& Fmt, va_list ArgPtr ) { int32 Result = vsnprintf(Dest, DestSize, Fmt, ArgPtr); va_end( ArgPtr ); diff --git a/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h b/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h index 53f95cebf027..329b05ed20dc 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h +++ b/Engine/Source/Runtime/Core/Public/HAL/IConsoleManager.h @@ -1228,5 +1228,25 @@ public: }; +/** + * Autoregistering console command with a world, arguments, and output device + */ +class CORE_API FAutoConsoleCommandWithWorldArgsAndOutputDevice : private FAutoConsoleObject +{ +public: + /** + * Register a console command that takes arguments, a world argument and an output device + * + * @param Name The name of this command (must not be nullptr) + * @param Help Help text for this command + * @param Command The user function to call when this command is executed + * @param Flags Optional flags bitmask + */ + FAutoConsoleCommandWithWorldArgsAndOutputDevice(const TCHAR* Name, const TCHAR* Help, const FConsoleCommandWithWorldArgsAndOutputDeviceDelegate& Command, uint32 Flags = ECVF_Default) + : FAutoConsoleObject(IConsoleManager::Get().RegisterConsoleCommand(Name, Help, Command, Flags)) + { + } +}; + CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogConsoleResponse, Log, All); diff --git a/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h b/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h index b2009d52cca6..b0d40fd3e88e 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h +++ b/Engine/Source/Runtime/Core/Public/HAL/PThreadEvent.h @@ -8,7 +8,7 @@ /** * This is the PThreads version of FEvent. */ -class FPThreadEvent +class CORE_VTABLE FPThreadEvent : public FEvent { // This is a little complicated, in an attempt to match Win32 Event semantics... diff --git a/Engine/Source/Runtime/Core/Public/HAL/Platform.h b/Engine/Source/Runtime/Core/Public/HAL/Platform.h index f3f2a492fe4e..aa12dc5aa6ca 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/Platform.h +++ b/Engine/Source/Runtime/Core/Public/HAL/Platform.h @@ -685,6 +685,11 @@ #define DLLIMPORT #endif +#ifndef DLLEXPORT_VTABLE + #define DLLEXPORT_VTABLE + #define DLLIMPORT_VTABLE +#endif + // embedded app is not default (embedding UE4 in a native view, right now just for IOS and Android) #ifndef BUILD_EMBEDDED_APP #define BUILD_EMBEDDED_APP 0 diff --git a/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h b/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h index f7b0242f0452..056cb10f6aeb 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h +++ b/Engine/Source/Runtime/Core/Public/HAL/ThreadSingleton.h @@ -35,6 +35,16 @@ public: template < class T > class TThreadSingleton : public FTlsAutoCleanup { +#if PLATFORM_UNIX || PLATFORM_APPLE + /** + * @return TLS slot that holds a TThreadSingleton. + */ + CORE_API static uint32& GetTlsSlot() + { + static uint32 TlsSlot = 0xFFFFFFFF; + return TlsSlot; + } +#else /** * @return TLS slot that holds a TThreadSingleton. */ @@ -43,6 +53,7 @@ class TThreadSingleton : public FTlsAutoCleanup static uint32 TlsSlot = 0xFFFFFFFF; return TlsSlot; } +#endif protected: @@ -79,5 +90,4 @@ public: { return (T*)FThreadSingletonInitializer::TryGet( T::GetTlsSlot() ); } -}; - +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h b/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h index 46dbb8d8c933..a254bb8155da 100644 --- a/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h +++ b/Engine/Source/Runtime/Core/Public/Internationalization/StringTableCore.h @@ -178,6 +178,15 @@ public: */ typedef TFunction FLoadStringTableAssetCallback; + /** + * Check to see whether it is currently safe to attempt to find or load a string table asset. + * @return True if it is safe to attempt to find or load a string table asset, false otherwise. + */ + static bool CanFindOrLoadStringTableAsset() + { + return !InstancePtr || InstancePtr->CanFindOrLoadStringTableAssetImpl(); + } + /** * Load a string table asset by its name, potentially doing so asynchronously. * @note If the string table is already loaded, or loading is perform synchronously, then the callback will be called before this function returns. @@ -185,7 +194,7 @@ public: */ static int32 LoadStringTableAsset(const FName InTableId, FLoadStringTableAssetCallback InLoadedCallback = FLoadStringTableAssetCallback()) { - check(IsInGameThread()); + check(CanFindOrLoadStringTableAsset()); if (InstancePtr) { @@ -206,7 +215,7 @@ public: */ static void FullyLoadStringTableAsset(FName& InOutTableId) { - check(IsInGameThread()); + check(CanFindOrLoadStringTableAsset()); if (InstancePtr) { @@ -217,6 +226,8 @@ public: /** Redirect string table asset by its name */ static void RedirectStringTableAsset(FName& InOutTableId) { + check(CanFindOrLoadStringTableAsset()); + if (InstancePtr) { InstancePtr->RedirectStringTableAssetImpl(InOutTableId); @@ -247,6 +258,7 @@ public: protected: virtual ~IStringTableEngineBridge() {} + virtual bool CanFindOrLoadStringTableAssetImpl() = 0; virtual int32 LoadStringTableAssetImpl(const FName InTableId, FLoadStringTableAssetCallback InLoadedCallback) = 0; virtual void FullyLoadStringTableAssetImpl(FName& InOutTableId) = 0; virtual void RedirectStringTableAssetImpl(FName& InOutTableId) = 0; diff --git a/Engine/Source/Runtime/Core/Public/Internationalization/Text.h b/Engine/Source/Runtime/Core/Public/Internationalization/Text.h index 99fdfe8e927f..52d701f6cb62 100644 --- a/Engine/Source/Runtime/Core/Public/Internationalization/Text.h +++ b/Engine/Source/Runtime/Core/Public/Internationalization/Text.h @@ -498,17 +498,17 @@ public: FText ToUpper() const; /** - * Removes whitespace characters from the front of the string. + * Removes any whitespace characters from the start of the text. */ static FText TrimPreceding( const FText& ); /** - * Removes trailing whitespace characters + * Removes any whitespace characters from the end of the text. */ static FText TrimTrailing( const FText& ); /** - * Does both of the above without needing to create an additional FText in the interim. + * Removes any whitespace characters from the start and end of the text. */ static FText TrimPrecedingAndTrailing( const FText& ); diff --git a/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h b/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h index cbf0c8dc2d71..be1769c18132 100644 --- a/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h +++ b/Engine/Source/Runtime/Core/Public/Mac/CocoaThread.h @@ -4,12 +4,12 @@ #include "CoreMinimal.h" /* Custom run-loop modes for UE4 that process only certain kinds of events to simulate Windows event ordering. */ -extern NSString* UE4NilEventMode; /* Process only mandatory events */ -extern NSString* UE4ShowEventMode; /* Process only show window events */ -extern NSString* UE4ResizeEventMode; /* Process only resize/move window events */ -extern NSString* UE4FullscreenEventMode; /* Process only fullscreen mode events */ -extern NSString* UE4CloseEventMode; /* Process only close window events */ -extern NSString* UE4IMEEventMode; /* Process only input method events */ +CORE_API extern NSString* UE4NilEventMode; /* Process only mandatory events */ +CORE_API extern NSString* UE4ShowEventMode; /* Process only show window events */ +CORE_API extern NSString* UE4ResizeEventMode; /* Process only resize/move window events */ +CORE_API extern NSString* UE4FullscreenEventMode; /* Process only fullscreen mode events */ +CORE_API extern NSString* UE4CloseEventMode; /* Process only close window events */ +CORE_API extern NSString* UE4IMEEventMode; /* Process only input method events */ @interface NSThread (FCocoaThread) + (NSThread*) gameThread; // Returns the main game thread, or nil if has yet to be constructed. @@ -24,7 +24,7 @@ extern NSString* UE4IMEEventMode; /* Process only input method events */ - (void)main; // Override that sets the variable backing +[NSRunLoop gameRunLoop], do not override in a subclass. @end -void MainThreadCall(dispatch_block_t Block, NSString* WaitMode = NSDefaultRunLoopMode, bool const bWait = true); +CORE_API void MainThreadCall(dispatch_block_t Block, NSString* WaitMode = NSDefaultRunLoopMode, bool const bWait = true); template ReturnType MainThreadReturn(ReturnType (^Block)(void), NSString* WaitMode = NSDefaultRunLoopMode) @@ -34,7 +34,7 @@ ReturnType MainThreadReturn(ReturnType (^Block)(void), NSString* WaitMode = NSDe return ReturnValue; } -void GameThreadCall(dispatch_block_t Block, NSArray* SendModes = @[ NSDefaultRunLoopMode ], bool const bWait = true); +CORE_API void GameThreadCall(dispatch_block_t Block, NSArray* SendModes = @[ NSDefaultRunLoopMode ], bool const bWait = true); template ReturnType GameThreadReturn(ReturnType (^Block)(void), NSArray* SendModes = @[ NSDefaultRunLoopMode ]) @@ -44,6 +44,6 @@ ReturnType GameThreadReturn(ReturnType (^Block)(void), NSArray* SendModes = @[ N return ReturnValue; } -void RunGameThread(id Target, SEL Selector); +CORE_API void RunGameThread(id Target, SEL Selector); -void ProcessGameThreadEvents(void); +CORE_API void ProcessGameThreadEvents(void); diff --git a/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h b/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h index a5a709aa592d..fd684aaa3cd7 100644 --- a/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h +++ b/Engine/Source/Runtime/Core/Public/Mac/MacCriticalSection.h @@ -14,20 +14,20 @@ class FMacSystemWideCriticalSection { public: /** Construct a named, system-wide critical section and attempt to get access/ownership of it */ - explicit FMacSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); + CORE_API explicit FMacSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); /** Destructor releases system-wide critical section if it is currently owned */ - ~FMacSystemWideCriticalSection(); + CORE_API ~FMacSystemWideCriticalSection(); /** * Does the calling thread have ownership of the system-wide critical section? * * @return True if the system-wide lock is obtained. WARNING: Returns true for abandoned locks so shared resources can be in undetermined states. */ - bool IsValid() const; + CORE_API bool IsValid() const; /** Releases system-wide critical section if it is currently owned */ - void Release(); + CORE_API void Release(); private: FMacSystemWideCriticalSection(const FMacSystemWideCriticalSection&); diff --git a/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h b/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h index c58c4bf9a1a3..ccaf1e0f2d8a 100644 --- a/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Mac/MacPlatform.h @@ -91,8 +91,12 @@ typedef FMacPlatformTypes FPlatformTypes; #define OPERATOR_NEW_NOTHROW_SPEC _NOEXCEPT #define OPERATOR_DELETE_NOTHROW_SPEC _NOEXCEPT +// DLL export and import for tpyes definitions +#define DLLEXPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#define DLLIMPORT_VTABLE __attribute__ ((__type_visibility__("default"))) + // DLL export and import definitions -#define DLLEXPORT -#define DLLIMPORT +#define DLLEXPORT __attribute__((visibility("default"))) +#define DLLIMPORT __attribute__((visibility("default"))) #define MAC_MAX_PATH 1024 diff --git a/Engine/Source/Runtime/Core/Public/Math/RandomStream.h b/Engine/Source/Runtime/Core/Public/Math/RandomStream.h index b39bf7a5e20b..f4bb05270a49 100644 --- a/Engine/Source/Runtime/Core/Public/Math/RandomStream.h +++ b/Engine/Source/Runtime/Core/Public/Math/RandomStream.h @@ -8,6 +8,7 @@ #include "Math/Matrix.h" #include "Math/RotationMatrix.h" #include "Math/Transform.h" +#include "HAL/PlatformTime.h" /** * Implements a thread-safe SRand based RNG. @@ -36,9 +37,20 @@ public: * @param InSeed The seed value. */ FRandomStream( int32 InSeed ) - : InitialSeed(InSeed) - , Seed(InSeed) - { } + { + Initialize(InSeed); + } + + /** + * Creates and initializes a new random stream from the specified name. + * + * @note If NAME_None is provided, the stream will be seeded using the current time. + * @param InName The name value from which the stream will be initialized. + */ + FRandomStream( FName InName ) + { + Initialize(InName); + } public: @@ -53,6 +65,26 @@ public: Seed = InSeed; } + /** + * Initializes this random stream using the specified name. + * + * @note If NAME_None is provided, the stream will be seeded using the current time. + * @param InName The name value from which the stream will be initialized. + */ + void Initialize( FName InName ) + { + if (InName != NAME_None) + { + InitialSeed = GetTypeHash(InName.ToString()); + } + else + { + InitialSeed = FPlatformTime::Cycles(); + } + + Seed = InitialSeed; + } + /** * Resets this random stream to the initial seed value. */ diff --git a/Engine/Source/Runtime/Core/Public/Math/Ray.h b/Engine/Source/Runtime/Core/Public/Math/Ray.h new file mode 100644 index 000000000000..a5e47ebd727a --- /dev/null +++ b/Engine/Source/Runtime/Core/Public/Math/Ray.h @@ -0,0 +1,112 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Math/Vector.h" + + +/** + * 3D Ray represented by Origin and (normalized) Direction + */ +class FRay +{ +public: + + /** Ray origin point */ + FVector Origin; + + /** Ray direction vector (always normalized) */ + FVector Direction; + +public: + + /** Default constructor initializes ray to Zero origin and Z-axis direction */ + FRay() + { + Origin = FVector::ZeroVector; + Direction = FVector(0, 0, 1); + } + + /** + * Initialize Ray with origin and direction + * + * @param Origin Ray Origin Point + * @param Direction Ray Direction Vector + * @param bDirectionIsNormalized Direction will be normalized unless this is passed as true (default false) + */ + FRay(const FVector& Origin, const FVector& Direction, bool bDirectionIsNormalized = false) + { + this->Origin = Origin; + this->Direction = Direction; + if (bDirectionIsNormalized == false) + { + this->Direction.Normalize(); // is this a full-accuracy sqrt? + } + } + + +public: + + /** + * Calculate position on ray at given distance/parameter + * + * @param RayParameter Scalar distance along Ray + * @return Point on Ray + */ + FVector PointAt(float RayParameter) const + { + return Origin + RayParameter * Direction; + } + + /** + * Calculate ray parameter (distance from origin to closest point) for query Point + * + * @param Point query Point + * @return distance along ray from origin to closest point + */ + float GetParameter(const FVector& Point) const + { + return FVector::DotProduct((Point - Origin), Direction); + } + + /** + * Find minimum squared distance from query point to ray + * + * @param Point query Point + * @return squared distance to Ray + */ + float DistSquared(const FVector& Point) const + { + float RayParameter = FVector::DotProduct((Point - Origin), Direction); + if (RayParameter < 0) + { + return FVector::DistSquared(Origin, Point); + } + else + { + FVector ProjectionPt = Origin + RayParameter * Direction; + return FVector::DistSquared(ProjectionPt, Point); + } + } + + /** + * Find closest point on ray to query point + * @param Point query point + * @return closest point on Ray + */ + FVector ClosestPoint(const FVector& Point) const + { + float RayParameter = FVector::DotProduct((Point - Origin), Direction); + if (RayParameter < 0) + { + return Origin; + } + else + { + return Origin + RayParameter * Direction; + } + } + + +}; + diff --git a/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h b/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h index db32814a34f4..d38e451fe9cd 100644 --- a/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h +++ b/Engine/Source/Runtime/Core/Public/Math/TransformVectorized.h @@ -1373,7 +1373,7 @@ private: checkSlow(VectorAnyGreaterThan(VectorAbs(Scale3D), GlobalVectorConstants::SmallNumber)); // Invert the scale - const VectorRegister InvScale = VectorSet_W0(GetSafeScaleReciprocal(Scale3D , ScalarRegister(GlobalVectorConstants::SmallNumber))); + const VectorRegister InvScale = VectorSet_W0(GetSafeScaleReciprocal(VectorSet_W1(Scale3D), ScalarRegister(GlobalVectorConstants::SmallNumber))); // Invert the rotation const VectorRegister InvRotation = VectorQuaternionInverse(Rotation); diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h index e4b6e81f537a..75e2fab157d3 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMath.h @@ -14,6 +14,7 @@ #include "Math/Vector4.h" #include "Math/VectorRegister.h" #include "Math/TwoVectors.h" +#include "Math/Ray.h" #include "Math/Edge.h" #include "Math/Plane.h" #include "Math/Sphere.h" diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h index 0ea11009c548..471f3bbede48 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathDirectX.h @@ -1059,10 +1059,10 @@ FORCEINLINE VectorRegister VectorSign(const VectorRegister& X) FORCEINLINE VectorRegister VectorStep(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); } ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h index ffc1e8ca9657..0f8d27ba75e2 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathFPU.h @@ -1279,20 +1279,20 @@ FORCEINLINE VectorRegister VectorMod(const VectorRegister& X, const VectorRegist FORCEINLINE VectorRegister VectorSign(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); } //TODO: Vectorize FORCEINLINE VectorRegister VectorStep(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); } /** diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h index d371ae521d00..4de1ee86e107 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h @@ -1294,20 +1294,20 @@ FORCEINLINE VectorRegister VectorMod(const VectorRegister& X, const VectorRegist FORCEINLINE VectorRegister VectorSign(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); } //TODO: Vectorize FORCEINLINE VectorRegister VectorStep(const VectorRegister& X) { return MakeVectorRegister( - (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : -1.0f), - (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : -1.0f)); + (float)(VectorGetComponent(X, 0) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 1) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 2) >= 0.0f ? 1.0f : 0.0f), + (float)(VectorGetComponent(X, 3) >= 0.0f ? 1.0f : 0.0f)); } /** diff --git a/Engine/Source/Runtime/Core/Public/Misc/Build.h b/Engine/Source/Runtime/Core/Public/Misc/Build.h index c6a16da19e57..5e03083392d5 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/Build.h +++ b/Engine/Source/Runtime/Core/Public/Misc/Build.h @@ -91,6 +91,13 @@ #error UBT should always define WITH_PLUGIN_SUPPORT to be 0 or 1 #endif +/** + * Whether we are compiling with Slate accessibility and automation support + */ +#ifndef WITH_ACCESSIBILITY + #define WITH_ACCESSIBILITY 1 +#endif + /** Enable perf counters */ #ifndef WITH_PERFCOUNTERS #define WITH_PERFCOUNTERS 0 diff --git a/Engine/Source/Runtime/Core/Public/Misc/Change.h b/Engine/Source/Runtime/Core/Public/Misc/Change.h index e079fb57b9ab..f3769c838063 100644 --- a/Engine/Source/Runtime/Core/Public/Misc/Change.h +++ b/Engine/Source/Runtime/Core/Public/Misc/Change.h @@ -5,16 +5,42 @@ #include "Templates/UniquePtr.h" #include "Containers/Array.h" -// @todo mesheditor: Comment these classes and enums! - +/** + * FChange modifies a UObject and is meant to be used to implement undo/redo. + * The change is embedded in an FTransaction which executes it *instead* of the standard + * serialization transaction (cannot be combined - see FTransaction). + * + * The original FChange style (used by MeshEditor) was that calling Execute() would return a new + * FChange that applies the opposite action, and FTransaction would swap the two at each undo/redo + * step (eg a "DeleteObject" FChange would return a "CreateObject" FChange) + * + * The alternative "Command Pattern"-style FChange calls Apply() and Revert() on a single FChange. + * + * FChange may eventually be deprecated. You should subclass + * FSwapChange and FCommandChange to implement these different styles. + */ class CORE_API FChange { public: + enum class EChangeStyle + { + InPlaceSwap, // Call Execute() which returns new "opposite" FChange (default) + CommandPattern // call Revert() to Undo and Apply() to Redo + }; + + /** What style of change is this */ + virtual EChangeStyle GetChangeType() = 0; /** Makes the change to the object, returning a new change that can be used to perfectly roll back this change */ virtual TUniquePtr Execute( UObject* Object ) = 0; + /** Makes the change to the object */ + virtual void Apply( UObject* Object ) = 0; + + /** Reverts change to the object */ + virtual void Revert( UObject* Object ) = 0; + /** Describes this change (for debugging) */ virtual FString ToString() const = 0; @@ -42,6 +68,62 @@ private: +/** + * To use FSwapChange you must implement Execute(). + * This function must do two things: + * 1) apply the change to the given UObject + * 2) return a new FSwapChange that does the "opposite" action + */ +class CORE_API FSwapChange : public FChange +{ + +public: + virtual EChangeStyle GetChangeType() override + { + return FChange::EChangeStyle::InPlaceSwap; + } + + /** Makes the change to the object */ + virtual void Apply(UObject* Object) override + { + check(false); + } + + /** Reverts change to the object */ + virtual void Revert(UObject* Object) override + { + check(false); + } + +}; + + +/** + * To use FCommandChange you must implement Apply() and Revert() + * Revert() is called to "Undo" and Apply() is called to "Redo" + */ +class CORE_API FCommandChange : public FChange +{ + +public: + virtual EChangeStyle GetChangeType() override + { + return FChange::EChangeStyle::CommandPattern; + } + + virtual TUniquePtr Execute(UObject* Object) + { + check(false); + return nullptr; + } + +}; + + + + + + struct CORE_API FCompoundChangeInput { FCompoundChangeInput() @@ -63,7 +145,12 @@ private: }; -class CORE_API FCompoundChange : public FChange + +/** + * FCompoundChange applies a sequence of FSwapChanges. + * The changes are executed in reverse order (this is like a mini undo stack) + */ +class CORE_API FCompoundChange : public FSwapChange { public: diff --git a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h index 9453c9891ef2..9a8d60f3f1f6 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/ArchiveProxy.h @@ -12,7 +12,7 @@ * * Archive proxies are archive types that modify the behavior of another archive type. */ -class FArchiveProxy : public FArchive +class CORE_VTABLE FArchiveProxy : public FArchive { public: /** diff --git a/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h b/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h index b8fe227edf35..877ba950eb71 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/NameAsStringProxyArchive.h @@ -8,7 +8,7 @@ /** * Implements a proxy archive that serializes FNames as string data. */ -struct FNameAsStringProxyArchive : public FArchiveProxy +struct CORE_VTABLE FNameAsStringProxyArchive : public FArchiveProxy { /** * Creates and initializes a new instance. diff --git a/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h b/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h index 3e674ded9a9a..885c2228856f 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h +++ b/Engine/Source/Runtime/Core/Public/UObject/EditorObjectVersion.h @@ -71,6 +71,14 @@ struct CORE_API FEditorObjectVersion MeshDescriptionBulkDataGuid, // Change to MeshDescription serialization (removed FMeshPolygon::HoleContours) MeshDescriptionRemovedHoles, + // Change to the WidgetCompoent WindowVisibilty default value + ChangedWidgetComponentWindowVisibilityDefault, + // Avoid keying culture invariant display strings during serialization to avoid non-deterministic cooking issues + CultureInvariantTextSerializationKeyStability, + // Change to UScrollBar and UScrollBox thickness property (removed implicit padding of 2, so thickness value must be incremented by 4). + ScrollBarThicknessChange, + // Deprecated LandscapeHoleMaterial + RemoveLandscapeHoleMaterial, // ------------------------------------------------------ VersionPlusOne, LatestVersion = VersionPlusOne - 1 diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h b/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h index 7770af969290..b3e9c0daa8e4 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixCriticalSection.h @@ -14,20 +14,20 @@ class FUnixSystemWideCriticalSection { public: /** Construct a named, system-wide critical section and attempt to get access/ownership of it */ - explicit FUnixSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); + explicit CORE_API FUnixSystemWideCriticalSection(const FString& InName, FTimespan InTimeout = FTimespan::Zero()); /** Destructor releases system-wide critical section if it is currently owned */ - ~FUnixSystemWideCriticalSection(); + CORE_API ~FUnixSystemWideCriticalSection(); /** * Does the calling thread have ownership of the system-wide critical section? * * @return True if the system-wide lock is obtained. WARNING: Returns true for abandoned locks so shared resources can be in undetermined states. */ - bool IsValid() const; + CORE_API bool IsValid() const; /** Releases system-wide critical section if it is currently owned */ - void Release(); + CORE_API void Release(); private: FUnixSystemWideCriticalSection(const FUnixSystemWideCriticalSection&); diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h index 2c9b00ced1fc..344faba049ef 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatform.h @@ -110,6 +110,12 @@ typedef FUnixPlatformTypes FPlatformTypes; #define ABSTRACT abstract +// DLL export and import for types, only supported on clang +#if defined(__clang__) +#define DLLEXPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#define DLLIMPORT_VTABLE __attribute__ ((__type_visibility__("default"))) +#endif + // DLL export and import definitions #define DLLEXPORT __attribute__((visibility("default"))) #define DLLIMPORT __attribute__((visibility("default"))) diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h index 7cc9a5365a1d..40ecb01d9533 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformCrashContext.h @@ -5,6 +5,23 @@ #include "CoreTypes.h" #include "GenericPlatform/GenericPlatformCrashContext.h" +/** Passed in through sigqueue for gathering of a callstack from a signal */ +struct ThreadStackUserData +{ + // If we want a backtrace or a callstack + // Backtrace is just a list of program counters and callstack is a symbolicated backtrace + bool bCaptureCallStack; + union + { + ANSICHAR* CallStack; + uint64* BackTrace; + }; + + int32 BackTraceCount; + SIZE_T CallStackSize; + TAtomic bDone; +}; + struct CORE_API FUnixCrashContext : public FGenericCrashContext { /** Signal number */ diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h index e3890b7bc524..b06ead99d47d 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformMisc.h @@ -134,6 +134,8 @@ struct CORE_API FUnixPlatformMisc : public FGenericPlatformMisc static FString GetOSVersion(); static FString GetLoginId(); + static void CreateGuid(FGuid& Result); + #if STATS || ENABLE_STATNAMEDEVENTS static void BeginNamedEventFrame(); static void BeginNamedEvent(const struct FColor& Color, const TCHAR* Text); diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h index c827b9f4dd3d..b2fdef4d702e 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformRealTimeSignals.h @@ -10,3 +10,8 @@ // Used in UnixSignalGameHitchHeartBeat.cpp for a hitch signal handler #define HEART_BEAT_SIGNAL SIGRTMIN + 3 + +// Skip SIGRTMIN + 4 default signal used in VTune + +// Using in UnixPlatformCrashContext.cpp/UnixPlatformStackWalk.cpp for gathering current callstack from a thread +#define THREAD_CALLSTACK_GENERATOR SIGRTMIN + 5 diff --git a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h index e5e15bd69e99..d00b57f57e30 100644 --- a/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h +++ b/Engine/Source/Runtime/Core/Public/Unix/UnixPlatformStackWalk.h @@ -18,6 +18,7 @@ struct CORE_API FUnixPlatformStackWalk : public FGenericPlatformStackWalk static uint32 CaptureStackBackTrace( uint64* BackTrace, uint32 MaxDepth, void* Context = nullptr ); static void StackWalkAndDump( ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, void* Context = nullptr ); static void StackWalkAndDumpEx(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 Flags, void* Context = nullptr); + static uint32 CaptureThreadStackBackTrace(uint64 ThreadId, uint64* BackTrace, uint32 MaxDepth); static void ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId); static int32 GetProcessModuleCount(); static int32 GetProcessModuleSignatures(FStackWalkModuleInfo *ModuleSignatures, const int32 ModuleSignaturesSize); diff --git a/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h b/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h index e4179c659f7e..db919a7151cf 100644 --- a/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h +++ b/Engine/Source/Runtime/Core/Public/Windows/COMPointer.h @@ -3,6 +3,7 @@ #pragma once #include +#include "Misc/AssertionMacros.h" /** diff --git a/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp b/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp index cae025965b2e..89a8b86c34a9 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp @@ -2068,6 +2068,7 @@ void FLinkerLoad::ResolveDeferredExports(UClass* LoadClass) if (ensure(PlaceholderExport)) { // replace the placeholder with the proper object instance + PlaceholderExport->SetLinker(nullptr, INDEX_NONE); Export.ResetObject(); UObject* ExportObj = CreateExport(ExportIndex); diff --git a/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp b/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp index bb9965a9cce8..a36501e294d6 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Internationalization/PackageLocalizationManager.cpp @@ -145,6 +145,14 @@ FName FPackageLocalizationManager::FindLocalizedPackageNameNoCache(const FName I return NAME_None; } +void FPackageLocalizationManager::ConditionalUpdateCache() +{ + if (ActiveCache.IsValid()) + { + ActiveCache->ConditionalUpdateCache(); + } +} + FPackageLocalizationManager& FPackageLocalizationManager::Get() { static FPackageLocalizationManager PackageLocalizationManager; diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp index a56f75a7f8e7..7df6cb048818 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp @@ -3998,9 +3998,9 @@ UClass* UClass::FindCommonBase(const TArray& InClasses) return CommonClass; } -bool UClass::IsFunctionImplementedInBlueprint(FName InFunctionName) const +bool UClass::IsFunctionImplementedInScript(FName InFunctionName) const { - // Implemented in UBlueprintGeneratedClass + // Implemented in classes such as UBlueprintGeneratedClass return false; } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp index 56d67e9ff2a8..d06c3e3b541e 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp @@ -3077,12 +3077,13 @@ UObject* StaticConstructObject_Internal !InTemplate || (InName != NAME_None && (bAssumeTemplateIsArchetype || InTemplate == UObject::GetArchetypeFromRequiredInfo(InClass, InOuter, InName, InFlags))) ); + const bool bCanRecycleSubobjects = bIsNativeFromCDO && (!(InFlags & RF_DefaultSubObject) || !FUObjectThreadContext::Get().IsInConstructor) #if WITH_HOT_RELOAD // Do not recycle subobjects when performing hot-reload as they may contain old property values. - const bool bCanRecycleSubobjects = bIsNativeFromCDO && !GIsHotReload; -#else - const bool bCanRecycleSubobjects = bIsNativeFromCDO; + && !GIsHotReload #endif + ; + bool bRecycledSubobject = false; Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject); check(Result != NULL); @@ -3671,7 +3672,7 @@ UObject* FObjectInitializer::CreateDefaultSubobject(UObject* Outer, FName Subobj else { UObject* Template = OverrideClass->GetDefaultObject(); // force the CDO to be created if it hasn't already - EObjectFlags SubobjectFlags = Outer->GetMaskedFlags(RF_PropagateToSubObjects); + EObjectFlags SubobjectFlags = Outer->GetMaskedFlags(RF_PropagateToSubObjects) | RF_DefaultSubObject; bool bOwnerArchetypeIsNotNative; UClass* OuterArchetypeClass; @@ -3721,7 +3722,6 @@ UObject* FObjectInitializer::CreateDefaultSubobject(UObject* Outer, FName Subobj #endif Outer->GetClass()->AddDefaultSubobject(Result, ReturnType); } - Result->SetFlags(RF_DefaultSubObject); // Clear PendingKill flag in case we recycled a subobject of a dead object. // @todo: we should not be recycling subobjects unless we're currently loading from a package Result->ClearInternalFlags(EInternalObjectFlags::PendingKill); diff --git a/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h b/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h index 3a5ba598fe3e..c4628221e766 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Internationalization/PackageLocalizationManager.h @@ -56,6 +56,11 @@ public: */ FName FindLocalizedPackageNameForCulture(const FName InSourcePackageName, const FString& InCultureName); + /** + * Update this cache, but only if it is dirty. + */ + void ConditionalUpdateCache(); + /** * Singleton accessor. * diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h index 0aa72b5c4ad0..0391e55f02cc 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/FindReferencersArchive.h @@ -11,7 +11,7 @@ /** * Archive for mapping out the referencers of a collection of objects. */ -class FFindReferencersArchive : public FArchiveUObject +class COREUOBJECT_VTABLE FFindReferencersArchive : public FArchiveUObject { public: /** diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h index eeb5a76c5946..f1ee3758f1f2 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectAndNameAsStringProxyArchive.h @@ -18,7 +18,7 @@ class UObject; * * @param InInnerArchive The actual FArchive object to serialize normal data types (FStrings, INTs, etc) */ -struct FObjectAndNameAsStringProxyArchive : public FNameAsStringProxyArchive +struct COREUOBJECT_VTABLE FObjectAndNameAsStringProxyArchive : public FNameAsStringProxyArchive { /** * Creates and initializes a new instance. diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h index 9e13ba863dfa..d6d036d31190 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectReader.h @@ -16,7 +16,7 @@ struct FWeakObjectPtr; /** * UObject Memory Reader Archive. Reads from InBytes, writes to Obj. */ -class FObjectReader : public FMemoryArchive +class COREUOBJECT_VTABLE FObjectReader : public FMemoryArchive { public: FObjectReader(UObject* Obj, TArray& InBytes, bool bIgnoreClassRef = false, bool bIgnoreArchetypeRef = false) diff --git a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h index 44fef71f06d5..891fb8eca378 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Serialization/ObjectWriter.h @@ -16,7 +16,7 @@ struct FWeakObjectPtr; /** * UObject Memory Writer Archive. */ -class FObjectWriter : public FMemoryWriter +class COREUOBJECT_VTABLE FObjectWriter : public FMemoryWriter { public: diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h index 44a7fda6229a..d3ae4a889ec6 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/Class.h @@ -855,7 +855,7 @@ FORCEINLINE typename TEnableIf::Value, uint /** * Reflection data for a standalone structure declared in a header or as a user defined struct */ -class UScriptStruct : public UStruct +class COREUOBJECT_VTABLE UScriptStruct : public UStruct { public: /** Interface to template to manage dynamic access to C++ struct construction and destruction **/ @@ -2767,7 +2767,10 @@ public: * @param InFunctionName The name of the function to test * @return True if the specified function exists and is implemented in a blueprint generated class */ - virtual bool IsFunctionImplementedInBlueprint(FName InFunctionName) const; + virtual bool IsFunctionImplementedInScript(FName InFunctionName) const; + + UE_DEPRECATED(4.23, "IsFunctionImplementedInBlueprint is deprecated, call IsFunctionImplementedInScript instead") + bool IsFunctionImplementedInBlueprint(FName InFunctionName) const { return IsFunctionImplementedInScript(InFunctionName); } /** * Checks if the property exists on this class or a parent class. @@ -3380,4 +3383,4 @@ struct FPolyglotTextData; template<> struct TBaseStructure { COREUOBJECT_API static UScriptStruct* Get(); -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h index 4932bac0ab79..987a1e21014f 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h @@ -1146,6 +1146,9 @@ namespace UM /// [PropertyMetadata] Used for FColor and FLinearColor properties. Indicates that the Alpha property should be hidden when displaying the property widget in the details. HideAlphaChannel, + /// [PropertyMetadata] Indicates that the property should be hidden in the details panel. Currently only used by events. + HideInDetailPanel, + /// [PropertyMetadata] Used for Subclass and SoftClass properties. Specifies to hide the ability to change view options in the class picker HideViewOptions, diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h index 66d1f36c95f6..dd89a45d2071 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimLinkableElement.h @@ -36,7 +36,7 @@ namespace EAnimLinkMethod * @see FAnimNotifyEvent */ USTRUCT() -struct FAnimLinkableElement +struct ENGINE_VTABLE FAnimLinkableElement { GENERATED_USTRUCT_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h index ee4ccf395d18..916e08b1c879 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/AudioComponent.h @@ -8,6 +8,7 @@ #include "Sound/SoundAttenuation.h" #include "Sound/SoundWave.h" #include "UObject/ObjectMacros.h" +#include "Math/RandomStream.h" #include "AudioComponent.generated.h" @@ -616,6 +617,8 @@ private: void UpdateSpriteTexture(); #endif + FRandomStream RandomStream; + static uint64 AudioComponentIDCounter; static TMap AudioIDToComponentMap; static FCriticalSection AudioIDToComponentMapLock; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h index 74910ee6dc99..4ce0af3fae1c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/LocalLightComponent.h @@ -23,7 +23,7 @@ class ENGINE_API ULocalLightComponent : public ULightComponent * The peak luminous intensity is measured in candelas, * while the luminous power is measured in lumens. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Light, AdvancedDisplay, meta=(DisplayName="Intensity Units", EditCondition="bUseInverseSquaredFalloff")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Light, AdvancedDisplay, meta=(DisplayName="Intensity Units")) ELightUnits IntensityUnits; UPROPERTY() diff --git a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h index d8256cce8fa6..88cfe1850ab7 100644 --- a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h +++ b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphNode.h @@ -275,24 +275,33 @@ private: /** Whether the node was created as part of an expansion step */ uint8 bIsIntermediateNode : 1; +#if WITH_EDITORONLY_DATA + /** Whether this node is unrelated to the selected nodes or not */ + uint8 bUnrelated : 1; + +#endif + public: + /** Flag to check for compile error/warning */ UPROPERTY() uint8 bHasCompilerMessage:1; /** Comment bubble pinned state */ + + +#if WITH_EDITORONLY_DATA UPROPERTY() - uint8 bCommentBubblePinned:1; + uint8 bCommentBubblePinned : 1; /** Comment bubble visibility */ UPROPERTY() - uint8 bCommentBubbleVisible:1; + uint8 bCommentBubbleVisible : 1; /** Make comment bubble visible */ UPROPERTY(Transient) - uint8 bCommentBubbleMakeVisible:1; + uint8 bCommentBubbleMakeVisible : 1; -#if WITH_EDITORONLY_DATA /** If true, this node can be renamed in the editor */ UPROPERTY() uint8 bCanRenameNode:1; @@ -355,6 +364,20 @@ public: return bUserSetEnabledState; } +#if WITH_EDITOR + /** Set this node unrelated or not. */ + FORCEINLINE void SetNodeUnrelated(bool bNodeUnrelated) + { + bUnrelated = bNodeUnrelated; + } + + /** Determines whether this node is unrelated to the selected nodes or not. */ + FORCEINLINE bool IsNodeUnrelated() const + { + return bUnrelated; + } +#endif + /** Determines whether or not the node will compile in development mode. */ virtual bool IsInDevelopmentMode() const; diff --git a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h index 02d0f4db8bc2..c81ae7c3a0f4 100644 --- a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h +++ b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h @@ -418,7 +418,7 @@ private: // This object is a base class helper used when building a list of actions for some menu or palette -struct FGraphActionListBuilderBase +struct ENGINE_VTABLE FGraphActionListBuilderBase { public: /** A single entry in the list - can contain multiple actions */ @@ -545,7 +545,7 @@ public: }; /** Used to nest all added action under one root category */ -struct FCategorizedGraphActionListBuilder : public FGraphActionListBuilderBase +struct ENGINE_VTABLE FCategorizedGraphActionListBuilder : public FGraphActionListBuilderBase { public: ENGINE_API FCategorizedGraphActionListBuilder(FString Category = FString()); diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h b/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h index f445991bca14..e624b73812ef 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h @@ -704,7 +704,7 @@ public: #endif //WITH_EDITOR virtual void SerializeDefaultObject(UObject* Object, FStructuredArchive::FSlot Slot) override; virtual void PostLoadDefaultObject(UObject* Object) override; - virtual bool IsFunctionImplementedInBlueprint(FName InFunctionName) const override; + virtual bool IsFunctionImplementedInScript(FName InFunctionName) const override; virtual uint8* GetPersistentUberGraphFrame(UObject* Obj, UFunction* FuncToCheck) const override; virtual void CreatePersistentUberGraphFrame(UObject* Obj, bool bCreateOnlyIfEmpty = false, bool bSkipSuperClass = false, UClass* OldClass = nullptr) const override; virtual void DestroyPersistentUberGraphFrame(UObject* Obj, bool bSkipSuperClass = false) const override; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h b/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h index b139b9622ddb..93eb58c1a67e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/CurveTable.h @@ -36,7 +36,7 @@ enum class ECurveTableMode : uint8 * Imported spreadsheet table as curves. */ UCLASS(MinimalAPI) -class UCurveTable +class ENGINE_VTABLE UCurveTable : public UObject , public FCurveOwnerInterface { @@ -210,7 +210,7 @@ protected: */ static FName MakeValidName(const FString& InString); - static int32 GlobalCachedCurveID; + ENGINE_API static int32 GlobalCachedCurveID; private: diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h b/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h index ebe1d6bf3c44..5aa3cbe339f6 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DataAsset.h @@ -36,7 +36,7 @@ private: * To override this behavior, override GetPrimaryAssetId in your native class */ UCLASS(abstract, MinimalAPI, Blueprintable) -class UPrimaryDataAsset : public UDataAsset +class ENGINE_VTABLE UPrimaryDataAsset : public UDataAsset { GENERATED_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h b/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h index c55c070d2a22..33e51c6217b9 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/DataTable.h @@ -53,7 +53,7 @@ struct FTableRowBase * Imported spreadsheet table. */ UCLASS(MinimalAPI, BlueprintType, AutoExpandCategories = "DataTable,ImportOptions") -class UDataTable +class ENGINE_VTABLE UDataTable : public UObject { GENERATED_UCLASS_BODY() @@ -160,11 +160,11 @@ public: { if (RowStruct == nullptr) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::GetAllRows : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); } else if (!RowStruct->IsChildOf(T::StaticStruct())) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::GetAllRows : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); } else { @@ -232,11 +232,11 @@ public: { if (RowStruct == nullptr) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::ForeachRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), ContextString); } else if (!RowStruct->IsChildOf(T::StaticStruct())) { - UE_LOG(LogDataTable, Error, TEXT("UDataTable::FindRow : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); + UE_LOG(LogDataTable, Error, TEXT("UDataTable::ForeachRow : Incorrect type specified for DataTable '%s' (%s)."), *GetPathName(), ContextString); } else { @@ -261,13 +261,13 @@ public: { if(RowStruct == nullptr) { - //UE_CLOG(MustExist, LogDataTable, Error, TEXT("UDataTable::FindRow : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), *ContextString); + //UE_CLOG(MustExist, LogDataTable, Error, TEXT("UDataTable::FindRowUnchecked : DataTable '%s' has no RowStruct specified (%s)."), *GetPathName(), *ContextString); return nullptr; } if(RowName == NAME_None) { - //UE_CLOG(MustExist, LogDataTable, Warning, TEXT("UDataTable::FindRow : NAME_None is invalid row name for DataTable '%s' (%s)."), *GetPathName(), *ContextString); + //UE_CLOG(MustExist, LogDataTable, Warning, TEXT("UDataTable::FindRowUnchecked : NAME_None is invalid row name for DataTable '%s' (%s)."), *GetPathName(), *ContextString); return nullptr; } @@ -408,7 +408,7 @@ struct ENGINE_API FDataTableRowHandle { if (RowName != NAME_None) { - UE_LOG(LogDataTable, Warning, TEXT("FDataTableRowHandle::FindRow : No DataTable for row %s (%s)."), *RowName.ToString(), ContextString); + UE_LOG(LogDataTable, Warning, TEXT("FDataTableRowHandle::GetRow : No DataTable for row %s (%s)."), *RowName.ToString(), ContextString); } return nullptr; } @@ -479,7 +479,7 @@ struct ENGINE_API FDataTableCategoryHandle { if (RowContents != NAME_None) { - UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::FindRow : No DataTable for row %s (%s)."), *RowContents.ToString(), *ContextString); + UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::GetRows : No DataTable for row %s (%s)."), *RowContents.ToString(), *ContextString); } return; @@ -489,7 +489,7 @@ struct ENGINE_API FDataTableCategoryHandle { if (RowContents != NAME_None) { - UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::FindRow : No Column selected for row %s (%s)."), *RowContents.ToString(), *ContextString); + UE_LOG(LogDataTable, Warning, TEXT("FDataTableCategoryHandle::GetRows : No Column selected for row %s (%s)."), *RowContents.ToString(), *ContextString); } return; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h b/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h index 43b996e0cf38..3a2a113b5d7c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/LevelStreaming.h @@ -150,11 +150,14 @@ private: ETargetState TargetState; /** Whether this level streaming object's level should be unloaded and the object be removed from the level list. */ - uint8 bIsRequestingUnloadAndRemoval : 1; + uint8 bIsRequestingUnloadAndRemoval:1; /* Whether CachedWorldAssetPackageFName is valid */ mutable uint8 bHasCachedWorldAssetPackageFName:1; + /* Whether CachedLoadedLevelPackageName is valid */ + mutable uint8 bHasCachedLoadedLevelPackageName:1; + #if WITH_EDITORONLY_DATA /** Whether this level should be visible in the Editor */ UPROPERTY() @@ -476,6 +479,9 @@ private: /** @return Name of the LOD package on disk to load to the new package named PackageName, Name_None otherwise */ FName GetLODPackageNameToLoad() const; + /** @return Name of the level package that is currently loaded. */ + FName GetLoadedLevelPackageName() const; + /** * Try to find loaded level in memory, issue a loading request otherwise * @@ -517,7 +523,8 @@ private: /** The cached package name of the world asset that is loaded by the levelstreaming */ mutable FName CachedWorldAssetPackageFName; - FName CachedLoadedLevelPackageName; + /** The cached package name of the currently loaded level. */ + mutable FName CachedLoadedLevelPackageName; friend struct FStreamingLevelPrivateAccessor; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h b/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h index 8977ec0c3900..ad03e5c91199 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/NetConnection.h @@ -240,7 +240,7 @@ public: }; UCLASS(customConstructor, Abstract, MinimalAPI, transient, config=Engine) -class UNetConnection : public UPlayer +class ENGINE_VTABLE UNetConnection : public UPlayer { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h b/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h index ce8d6ddcc414..264da4d43cbb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h @@ -5,6 +5,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "UObject/Object.h" @@ -598,7 +599,7 @@ struct FDisconnectedClient UCLASS(Abstract, customConstructor, transient, MinimalAPI, config=Engine) -class UNetDriver : public UObject, public FExec +class ENGINE_VTABLE UNetDriver : public UObject, public FExec { GENERATED_UCLASS_BODY() @@ -1534,6 +1535,9 @@ protected: bool bMaySendProperties; + /** Stream of random numbers to be used by this instance of UNetDriver */ + FRandomStream UpdateDelayRandomStream; + private: FDelegateHandle PostGarbageCollectHandle; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h b/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h index 4342a322046f..ff70f2459209 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h @@ -1124,6 +1124,12 @@ public: #endif // WITH_EDITORONLY_DATA } + /** + * Add a socket object in this StaticMesh. + */ + UFUNCTION(BlueprintCallable, Category = "StaticMesh") + ENGINE_API void AddSocket(UStaticMeshSocket* Socket); + /** * Find a socket object in this StaticMesh by name. * Entering NAME_None will return NULL. If there are multiple sockets with the same name, will return the first one. @@ -1131,6 +1137,12 @@ public: UFUNCTION(BlueprintCallable, Category = "StaticMesh") ENGINE_API class UStaticMeshSocket* FindSocket(FName InSocketName) const; + /** + * Remove a socket object in this StaticMesh by providing it's pointer. Use FindSocket() if needed. + */ + UFUNCTION(BlueprintCallable, Category = "StaticMesh") + ENGINE_API void RemoveSocket(UStaticMeshSocket* Socket); + /** * Returns vertex color data by position. * For matching to reimported meshes that may have changed or copying vertex paint data from mesh to mesh. diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h b/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h index 4cf6a921bef7..4324b07c51b0 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/Texture.h @@ -374,7 +374,7 @@ struct FTexturePlatformData }; UCLASS(abstract, MinimalAPI, BlueprintType) -class UTexture : public UStreamableRenderAsset, public IInterface_AssetUserData +class ENGINE_VTABLE UTexture : public UStreamableRenderAsset, public IInterface_AssetUserData { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h b/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h index dbd32ac2c695..5415b201e56c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/TextureCube.h @@ -11,7 +11,7 @@ class FTextureResource; UCLASS(hidecategories=Object, MinimalAPI) -class UTextureCube : public UTexture +class ENGINE_VTABLE UTextureCube : public UTexture { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h b/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h index d0c7093ec856..4310e9c5534c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/TimelineTemplate.h @@ -13,7 +13,7 @@ class UTimelineTemplate; USTRUCT() -struct FTTTrackBase +struct ENGINE_VTABLE FTTTrackBase { GENERATED_USTRUCT_BODY() @@ -43,7 +43,7 @@ public: /** Structure storing information about one event track */ USTRUCT() -struct FTTEventTrack : public FTTTrackBase +struct ENGINE_VTABLE FTTEventTrack : public FTTTrackBase { GENERATED_USTRUCT_BODY() @@ -69,7 +69,7 @@ public: }; USTRUCT() -struct FTTPropertyTrack : public FTTTrackBase +struct ENGINE_VTABLE FTTPropertyTrack : public FTTTrackBase { GENERATED_USTRUCT_BODY() @@ -253,7 +253,7 @@ class UTimelineTemplate : public UObject private: /** Helper function to make sure all the cached FNames for the timeline template are updated relative to the current name of the template. */ - void UpdateCachedNames(); + ENGINE_API void UpdateCachedNames(); friend struct FUpdateTimelineCachedNames; @@ -289,4 +289,4 @@ private: /** Grant external permission to Blueprint editor utility methods. For example, duplicating a Blueprint for conversion to C++ requires that cached names be updated. */ friend class FBlueprintEditorUtils; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/World.h b/Engine/Source/Runtime/Engine/Classes/Engine/World.h index 7b8b1c7409e8..7e21029f97c4 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/World.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/World.h @@ -2387,10 +2387,11 @@ public: /** * Cleans up components, streaming data and assorted other intermediate data. - * @param bSessionEnded whether to notify the viewport that the game session has ended + * @param bSessionEnded whether to notify the viewport that the game session has ended. * @param NewWorld Optional new world that will be loaded after this world is cleaned up. Specify a new world to prevent it and it's sublevels from being GCed during map transitions. + * @param bResetCleanedUpFlag wheter to reset the bCleanedUpWorld flag or not. */ - void CleanupWorld(bool bSessionEnded = true, bool bCleanupResources = true, UWorld* NewWorld = nullptr); + void CleanupWorld(bool bSessionEnded = true, bool bCleanupResources = true, UWorld* NewWorld = nullptr, bool bResetCleanedUpFlag = true); /** * Invalidates the cached data used to render the levels' UModel. diff --git a/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h b/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h index 1516c958b837..37cb1c7472f7 100644 --- a/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h +++ b/Engine/Source/Runtime/Engine/Classes/Exporters/Exporter.h @@ -19,7 +19,7 @@ class UActorComponent; class UAssetExportTask; UCLASS(abstract, transient, MinimalAPI) -class UExporter : public UObject +class ENGINE_VTABLE UExporter : public UObject { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h index 477c97b6bce9..3f2719f3a669 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "Engine/NetSerialization.h" @@ -2126,6 +2127,8 @@ protected: class FNetworkPredictionData_Client_Character* ClientPredictionData; class FNetworkPredictionData_Server_Character* ServerPredictionData; + FRandomStream RandomStream; + /** * Smooth mesh location for network interpolation, based on values set up by SmoothCorrection. * Internally this simply calls SmoothClientPosition_Interpolate() then SmoothClientPosition_UpdateVisuals(). diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h index 75f35de9ee97..c5cb51006202 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h @@ -92,7 +92,7 @@ protected: * an Actor that follows the possessed Pawn location, but that still has the full aim rotation (since a Pawn might * update only some components of the rotation). */ - UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category="Controller|Transform") + UPROPERTY(EditDefaultsOnly, Category="Controller|Transform") uint8 bAttachToPawn:1; /** Whether this controller is a PlayerController. */ diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h index f42009e59aaa..54340c1a15eb 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h @@ -386,15 +386,15 @@ struct FBroadphaseSettings bool bUseMBPOuterBounds; /** Total bounds for MBP, must cover the game world or collisions are disabled for out of bounds actors */ - UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = bUseMBP)) + UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = "bUseMBPOnClient || bUseMBPOnServer")) FBox MBPBounds; /** Total bounds for MBP, should cover absolute maximum bounds of the game world where physics is required */ - UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = bUseMBP)) + UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = "bUseMBPOnClient || bUseMBPOnServer")) FBox MBPOuterBounds; /** Number of times to subdivide the MBP bounds, final number of regions is MBPNumSubdivs^2 */ - UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = bUseMBP, ClampMin=1, ClampMax=16)) + UPROPERTY(EditAnywhere, Category = Broadphase, meta = (EditCondition = "bUseMBPOnClient || bUseMBPOnServer", ClampMin=1, ClampMax=16)) uint32 MBPNumSubdivs; }; diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/Material.h b/Engine/Source/Runtime/Engine/Classes/Materials/Material.h index 57101972ce8c..03e23368e0bc 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/Material.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/Material.h @@ -335,7 +335,7 @@ struct FParameterGroupData * Warning: Creating new materials directly increases shader compile times! Consider creating a Material Instance off of an existing material instead. */ UCLASS(hidecategories=Object, MinimalAPI, BlueprintType) -class UMaterial : public UMaterialInterface +class ENGINE_VTABLE UMaterial : public UMaterialInterface { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h index 845c47966099..9d90277e97b5 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstance.h @@ -252,7 +252,7 @@ bool CompareValueArraysByExpressionGUID(const TArray& InA, const TArray& I UCLASS(abstract, BlueprintType,MinimalAPI) -class UMaterialInstance : public UMaterialInterface +class ENGINE_VTABLE UMaterialInstance : public UMaterialInterface { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h index a8fb97f4fcf9..e1f9e9b1373e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInstanceConstant.h @@ -15,7 +15,7 @@ * predefined material parameters. The parameters are statically defined in the compiled material by a unique name, type and default value. */ UCLASS(hidecategories=Object, collapsecategories, BlueprintType,MinimalAPI) -class UMaterialInstanceConstant : public UMaterialInstance +class ENGINE_VTABLE UMaterialInstanceConstant : public UMaterialInstance { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h index 392c15f81db9..705bf74cdedb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h +++ b/Engine/Source/Runtime/Engine/Classes/Materials/MaterialInterface.h @@ -210,7 +210,7 @@ struct FMaterialTextureInfo }; UCLASS(abstract, BlueprintType, MinimalAPI, HideCategories = (Thumbnail)) -class UMaterialInterface : public UObject, public IBlendableInterface, public IInterface_AssetUserData +class ENGINE_VTABLE UMaterialInterface : public UObject, public IBlendableInterface, public IInterface_AssetUserData { GENERATED_UCLASS_BODY() @@ -285,7 +285,7 @@ private: private: /** Feature level bitfield to compile for all materials */ - static uint32 FeatureLevelsForAllMaterials; + ENGINE_API static uint32 FeatureLevelsForAllMaterials; public: /** Set which feature levels this material instance should compile. GMaxRHIFeatureLevel is always compiled! */ ENGINE_API void SetFeatureLevelToCompile(ERHIFeatureLevel::Type FeatureLevel, bool bShouldCompile); diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h index df63b4ca5624..df55e90570ba 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Color/ParticleModuleColor_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleColor_Seeded : public UParticleModuleColor //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h index 156863fd868d..97e87601ee9b 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Lifetime/ParticleModuleLifetime_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleLifetime_Seeded : public UParticleModuleLifetime //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h index 10846ca7ba5f..6147091cfd1f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Light/ParticleModuleLight_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleLight_Seeded : public UParticleModuleLight //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h index fdb8e4a90eb8..37df1b651b48 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationBoneSocket.h @@ -179,8 +179,8 @@ class ENGINE_API UParticleModuleLocationBoneSocket : public UParticleModuleLocat bool GetBoneInfoForSourceIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, int32 SourceIndex, FMatrix& OutBoneMatrix, FVector& OutOffset)const; /** Selects the next socket or bone index to spawn from. */ - int32 SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent); - void RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent); + int32 SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream); + void RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream); void UpdatePrevBoneLocationsAndVelocities(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, float DeltaTime); diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h index 95bf4fde0e08..88a7e2ba10f6 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveCylinder_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocationPrimitiveCylinder_Seeded : public UParti //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h index 906613086723..b0d3948d6239 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationPrimitiveSphere_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocationPrimitiveSphere_Seeded : public UParticl //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h index 7751370811fb..275e6504a880 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocationWorldOffset_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocationWorldOffset_Seeded : public UParticleMod //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h index 1ce9d0527e5f..8897e8c489cb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Location/ParticleModuleLocation_Seeded.h @@ -21,9 +21,6 @@ class ENGINE_API UParticleModuleLocation_Seeded : public UParticleModuleLocation //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h index 14620dd419d3..71b49c072e57 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Parameter/ParticleModuleParameterDynamic_Seeded.h @@ -19,9 +19,6 @@ class UParticleModuleParameterDynamic_Seeded : public UParticleModuleParameterDy //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h index f474340d445f..9157117b4abd 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleEmitter.h @@ -217,6 +217,9 @@ class UParticleEmitter : public UObject /** Map module pointers to their offset into the instance data. */ TMap ModuleInstanceOffsetMap; + /** Map module pointers to their offset into the instance data. */ + TMap ModuleRandomSeedInstanceOffsetMap; + /** Materials collected from any MeshMaterial modules */ TArray MeshMaterials; @@ -236,6 +239,9 @@ class UParticleEmitter : public UObject // Array of modules that want emitter instance data TArray ModulesNeedingInstanceData; + // Array of modules that want emitter random seed instance data + TArray ModulesNeedingRandomSeedInstanceData; + /** SubUV animation asset to use for cutout geometry. */ class USubUVAnimation* RESTRICT SubUVAnimation; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h index f4f225d1d094..cc1ac9a15633 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModule.h @@ -545,6 +545,15 @@ class ENGINE_API UParticleModule : public UObject /** Returns whether this module is used in any GPU emitters. */ bool IsUsedInGPUEmitter()const; + /** + * Retreive the random stream that should be used for the provided instance. + * + * @param Owner The emitter instance that owns this module + * + * @return FRandomStream& The random stream to use for the provided instance. + */ + FRandomStream& GetRandomStream(FParticleEmitterInstance* Owner); + #if WITH_EDITOR virtual void PostLoadSubobjects( FObjectInstancingGraph* OuterInstanceGraph ) override; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h index 439fda4890e3..8c9408ce781f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleModuleRequired.h @@ -37,24 +37,48 @@ enum class EParticleUVFlipMode : uint8 }; /** Flips the sign of a particle's base size based on it's UV flip mode. */ -FORCEINLINE void AdjustParticleBaseSizeForUVFlipping(FVector& OutSize, EParticleUVFlipMode FlipMode) +FORCEINLINE void AdjustParticleBaseSizeForUVFlipping(FVector& OutSize, EParticleUVFlipMode FlipMode, FRandomStream& InRandomStream) { - static const int32 HalfRandMax = RAND_MAX / 2; + static const float HalfRandMax = 0.5f; + switch (FlipMode) { - case EParticleUVFlipMode::FlipUV: OutSize = -OutSize; return; - case EParticleUVFlipMode::FlipUOnly: OutSize.X = -OutSize.X; return; - case EParticleUVFlipMode::FlipVOnly: OutSize.Y = -OutSize.Y; return; - case EParticleUVFlipMode::RandomFlipUV: OutSize = FMath::Rand() > HalfRandMax ? -OutSize : OutSize; return; - case EParticleUVFlipMode::RandomFlipUOnly: OutSize.X = FMath::Rand() > HalfRandMax ? -OutSize.X : OutSize.X; return; - case EParticleUVFlipMode::RandomFlipVOnly: OutSize.Y = FMath::Rand() > HalfRandMax ? -OutSize.Y : OutSize.Y; return; - case EParticleUVFlipMode::RandomFlipUVIndependent: - { - OutSize.X = FMath::Rand() > HalfRandMax ? -OutSize.X : OutSize.X; - OutSize.Y = FMath::Rand() > HalfRandMax ? -OutSize.Y : OutSize.Y; - return; + case EParticleUVFlipMode::None: + return; + + case EParticleUVFlipMode::FlipUV: + OutSize = -OutSize; + return; + + case EParticleUVFlipMode::FlipUOnly: + OutSize.X = -OutSize.X; + return; + + case EParticleUVFlipMode::FlipVOnly: + OutSize.Y = -OutSize.Y; + return; + + case EParticleUVFlipMode::RandomFlipUV: + OutSize = InRandomStream.FRand() > HalfRandMax ? -OutSize : OutSize; + return; + + case EParticleUVFlipMode::RandomFlipUOnly: + OutSize.X = InRandomStream.FRand() > HalfRandMax ? -OutSize.X : OutSize.X; + return; + + case EParticleUVFlipMode::RandomFlipVOnly: + OutSize.Y = InRandomStream.FRand() > HalfRandMax ? -OutSize.Y : OutSize.Y; + return; + + case EParticleUVFlipMode::RandomFlipUVIndependent: + OutSize.X = InRandomStream.FRand() > HalfRandMax ? -OutSize.X : OutSize.X; + OutSize.Y = InRandomStream.FRand() > HalfRandMax ? -OutSize.Y : OutSize.Y; + return; + + default: + checkNoEntry(); + break; } - }; } UENUM() diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h index ef231159df00..c0c8e8f725e4 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/ParticleSystemComponent.h @@ -1164,6 +1164,9 @@ public: /** Static delegate called for all systems on an activation change. */ static FOnSystemPreActivationChange OnSystemPreActivationChange; + /** Stream of random values to use with this component */ + FRandomStream RandomStream; + private: /** In some cases the async work for this PSC can be created externally by the manager. */ FORCEINLINE void SetAsyncWork(FGraphEventRef& InAsyncWork) { AsyncWork = InAsyncWork; } diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h index 14ffd8d5d224..28db7b1ee8b9 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleMeshRotation_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleMeshRotation_Seeded : public UParticleModuleMeshRotation //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h index e638296b2848..4d95698bf77b 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Rotation/ParticleModuleRotation_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleRotation_Seeded : public UParticleModuleRotation //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h index 874e1f93efb7..e04beca774ac 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleMeshRotationRate_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleMeshRotationRate_Seeded : public UParticleModuleMeshRotatio //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h index 02d9bcd509e9..a36638c490eb 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/RotationRate/ParticleModuleRotationRate_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleRotationRate_Seeded : public UParticleModuleRotationRate //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h index 3f4d62eb214e..a5b21b4ac40f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Size/ParticleModuleSize_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleSize_Seeded : public UParticleModuleSize //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h b/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h index 4177e9ea6865..cd2f2b97849e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h +++ b/Engine/Source/Runtime/Engine/Classes/Particles/Velocity/ParticleModuleVelocity_Seeded.h @@ -21,9 +21,6 @@ class UParticleModuleVelocity_Seeded : public UParticleModuleVelocity //Begin UParticleModule Interface - virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override; - virtual uint32 RequiredBytesPerInstance() override; - virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override; virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override { return &RandomSeedInfo; diff --git a/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h b/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h index 90657179573c..5bc774dee7e5 100644 --- a/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h +++ b/Engine/Source/Runtime/Engine/Classes/PhysicsEngine/PhysicsSettings.h @@ -106,7 +106,7 @@ class ENGINE_API UPhysicsSettings : public UDeveloperSettings float TriangleMeshTriangleMinAreaThreshold; /** Enables shape sharing between sync and async scene for static rigid actors */ - UPROPERTY(config, EditAnywhere, AdvancedDisplay, Category = Simulation, meta = (editcondition="bEnableAsyncScene")) + UPROPERTY(config, EditAnywhere, AdvancedDisplay, Category = Simulation) bool bEnableShapeSharing; /** Enables persistent contact manifolds. This will generate fewer contact points, but with more accuracy. Reduces stability of stacking, but can help energy conservation.*/ diff --git a/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h b/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h index f233e68ef654..3d414293a349 100644 --- a/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h +++ b/Engine/Source/Runtime/Engine/Classes/Sound/SoundNode.h @@ -4,6 +4,7 @@ #pragma once #include "CoreMinimal.h" +#include "Math/RandomStream.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "UObject/Class.h" @@ -69,6 +70,9 @@ class ENGINE_API USoundNode : public UObject class UEdGraphNode* GetGraphNode() const; #endif + /** Stream of random numbers to be used by this instance of USoundNode */ + FRandomStream RandomStream; + public: //~ Begin UObject Interface #if WITH_EDITOR diff --git a/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp b/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp index d447d31f807c..c2531bd43500 100644 --- a/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp +++ b/Engine/Source/Runtime/Engine/Private/BlueprintGeneratedClass.cpp @@ -680,7 +680,7 @@ void UBlueprintGeneratedClass::InitArrayPropertyFromCustomList(const UArrayPrope } } -bool UBlueprintGeneratedClass::IsFunctionImplementedInBlueprint(FName InFunctionName) const +bool UBlueprintGeneratedClass::IsFunctionImplementedInScript(FName InFunctionName) const { UFunction* Function = FindFunctionByName(InFunctionName); return Function && Function->GetOuter() && Function->GetOuter()->IsA(UBlueprintGeneratedClass::StaticClass()); diff --git a/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp index 2850da348d6b..2688e02d5679 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/AudioComponent.cpp @@ -10,6 +10,7 @@ #include "Sound/SoundCue.h" #include "Components/BillboardComponent.h" #include "UObject/FrameworkObjectVersion.h" +#include "Misc/App.h" DECLARE_CYCLE_STAT(TEXT("AudioComponent Play"), STAT_AudioComp_Play, STATGROUP_Audio); @@ -68,6 +69,8 @@ UAudioComponent::UAudioComponent(const FObjectInitializer& ObjectInitializer) AudioDeviceHandle = INDEX_NONE; AudioComponentID = FPlatformAtomics::InterlockedIncrement(reinterpret_cast(&AudioComponentIDCounter)); + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); + { // TODO: Consider only putting played/active components in to the map FScopeLock Lock(&AudioIDToComponentMapLock); @@ -399,7 +402,7 @@ void UAudioComponent::PlayInternal(const float StartTime, const float FadeInDura NewActiveSound.SetSoundClass(SoundClassOverride); NewActiveSound.ConcurrencySet = ConcurrencySet; - const float Volume = (VolumeModulationMax + ((VolumeModulationMin - VolumeModulationMax) * FMath::SRand())) * VolumeMultiplier; + const float Volume = (VolumeModulationMax + ((VolumeModulationMin - VolumeModulationMax) * RandomStream.FRand())) * VolumeMultiplier; NewActiveSound.SetVolume(Volume); // The priority used for the active sound is the audio component's priority scaled with the sound's priority @@ -412,7 +415,7 @@ void UAudioComponent::PlayInternal(const float StartTime, const float FadeInDura NewActiveSound.Priority = Sound->Priority; } - const float Pitch = (PitchModulationMax + ((PitchModulationMin - PitchModulationMax) * FMath::SRand())) * PitchMultiplier; + const float Pitch = (PitchModulationMax + ((PitchModulationMin - PitchModulationMax) * RandomStream.FRand())) * PitchMultiplier; NewActiveSound.SetPitch(Pitch); NewActiveSound.bEnableLowPassFilter = bEnableLowPassFilter; diff --git a/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp index 95ccf3507a01..8e9a268a5e44 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp @@ -24,6 +24,7 @@ #include "AI/Navigation/PathFollowingAgentInterface.h" #include "AI/Navigation/AvoidanceManager.h" #include "Components/BrushComponent.h" +#include "Misc/App.h" #include "Engine/DemoNetDriver.h" #include "Engine/NetworkObjectList.h" @@ -379,6 +380,8 @@ FName FCharacterMovementComponentPostPhysicsTickFunction::DiagnosticContext(bool UCharacterMovementComponent::UCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); + PostPhysicsTickFunction.bCanEverTick = true; PostPhysicsTickFunction.bStartWithTickEnabled = false; PostPhysicsTickFunction.SetTickFunctionEnable(false); @@ -941,7 +944,7 @@ FVector UCharacterMovementComponent::GetBestDirectionOffActor(AActor* BaseActor) float UCharacterMovementComponent::GetNetworkSafeRandomAngleDegrees() const { - float Angle = FMath::SRand() * 360.f; + float Angle = RandomStream.FRand() * 360.f; if (!IsNetMode(NM_Standalone)) { @@ -4403,8 +4406,8 @@ void UCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations) const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D(); if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick) { - Velocity.X += 0.25f * GetMaxSpeed() * (FMath::FRand() - 0.5f); - Velocity.Y += 0.25f * GetMaxSpeed() * (FMath::FRand() - 0.5f); + Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); Velocity.Z = FMath::Max(JumpZVelocity * 0.25f, 1.f); Delta = Velocity * timeTick; SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); @@ -8075,7 +8078,7 @@ void UCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const F bSendServerMove = false; UE_LOG(LogNetPlayerMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CharacterMovementCVars::NetForceClientServerMoveLossDuration - TimeSinceLossStart); } - else if (CharacterMovementCVars::NetForceClientServerMoveLossPercent != 0.f && (FMath::SRand() < CharacterMovementCVars::NetForceClientServerMoveLossPercent)) + else if (CharacterMovementCVars::NetForceClientServerMoveLossPercent != 0.f && (RandomStream.FRand() < CharacterMovementCVars::NetForceClientServerMoveLossPercent)) { bSendServerMove = false; ClientData->DebugForcedPacketLossTimerStart = (CharacterMovementCVars::NetForceClientServerMoveLossDuration > 0) ? MyWorld->RealTimeSeconds : 0.0f; @@ -8822,7 +8825,7 @@ bool UCharacterMovementComponent::ServerCheckClientError(float ClientTimeStamp, #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (CharacterMovementCVars::NetForceClientAdjustmentPercent > SMALL_NUMBER) { - if (FMath::SRand() < CharacterMovementCVars::NetForceClientAdjustmentPercent) + if (RandomStream.FRand() < CharacterMovementCVars::NetForceClientAdjustmentPercent) { UE_LOG(LogNetPlayerMovement, VeryVerbose, TEXT("** ServerCheckClientError forced by p.NetForceClientAdjustmentPercent")); return true; diff --git a/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp b/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp index a5bbddc9e370..b87bd33cd86f 100644 --- a/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp +++ b/Engine/Source/Runtime/Engine/Private/Curves/CurveLinearColorAtlas.cpp @@ -13,9 +13,9 @@ UCurveLinearColorAtlas::UCurveLinearColorAtlas(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { -#if WITH_EDITOR TextureSize = 256; GradientPixelSize = 1; +#if WITH_EDITORONLY_DATA bHasAnyDirtyTextures = false; bShowDebugColorsForNullGradients = false; SizeXY = { (float)TextureSize, (float)GradientPixelSize }; @@ -209,4 +209,4 @@ bool UCurveLinearColorAtlas::GetCurvePosition(UCurveLinearColor* InCurve, float& return true; } return false; -} \ No newline at end of file +} diff --git a/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp b/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp index 57cdb92def80..9309adad26e4 100644 --- a/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp +++ b/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp @@ -2915,7 +2915,7 @@ bool UDemoNetDriver::ReplicatePrioritizedActor(const FActorPriority& ActorPriori const double ClampedExtraTime = FMath::Clamp(ExtraTime, 0.0, NetUpdateDelay); // Try to spread the updates across multiple frames to smooth out spikes. - ActorInfo->NextUpdateTime = (DemoCurrentTime + NextUpdateDelta - ClampedExtraTime + ((FMath::SRand() - 0.5) * Params.ServerTickTime)); + ActorInfo->NextUpdateTime = (DemoCurrentTime + NextUpdateDelta - ClampedExtraTime + ((UpdateDelayRandomStream.FRand() - 0.5) * Params.ServerTickTime)); Actor->CallPreReplication(this); diff --git a/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp b/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp index 63067c6fb341..2e48df87362c 100644 --- a/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp +++ b/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphNode.cpp @@ -158,10 +158,12 @@ UEdGraphNode::UEdGraphNode(const FObjectInitializer& ObjectInitializer) , bIsNodeEnabled_DEPRECATED(true) #if WITH_EDITORONLY_DATA , bCanResizeNode(false) -#endif // WITH_EDITORONLY_DATA + , bUnrelated(false) , bCommentBubblePinned(false) , bCommentBubbleVisible(false) , bCommentBubbleMakeVisible(false) +#endif // WITH_EDITORONLY_DATA + { #if WITH_EDITORONLY_DATA { static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UEdGraphNode::StaticClass(), &GatherGraphNodeForLocalization); } diff --git a/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp b/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp index 230f6629e8bc..d90b4d75953f 100644 --- a/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/HierarchicalInstancedStaticMesh.cpp @@ -2354,19 +2354,25 @@ void UHierarchicalInstancedStaticMeshComponent::ClearInstances() InstanceUpdateCmdBuffer.Reset(); - // Hide all instance until the build tree is completed - int32 NumInstances = PerInstanceSMData.Num(); - - for (int32 Index = 0; Index < NumInstances; ++Index) + // Don't try to queue hide command if there is no render instances + if (PerInstanceRenderData.IsValid() && PerInstanceRenderData->InstanceBuffer_GameThread->GetNumInstances() > 0) { - int32 RenderIndex = InstanceReorderTable.IsValidIndex(Index) ? InstanceReorderTable[Index] : Index; - if (RenderIndex == INDEX_NONE) - { - // could be skipped by density settings - continue; - } + // Hide all instance until the build tree is completed but if there is a mismatch between game thread data and render thread data, only add command matching render thread data, + // this can happen in a case where you perform many time add, clear, add, clear, in the same frame, so you might get a mismatch between both thread data + int32 NumInstances = FMath::Clamp(PerInstanceSMData.Num(), 0, PerInstanceRenderData->InstanceBuffer_GameThread->GetNumInstances()); - InstanceUpdateCmdBuffer.HideInstance(RenderIndex); + for (int32 Index = 0; Index < NumInstances; ++Index) + { + int32 RenderIndex = InstanceReorderTable.IsValidIndex(Index) ? InstanceReorderTable[Index] : Index; + + if (RenderIndex == INDEX_NONE) + { + // could be skipped by density settings + continue; + } + + InstanceUpdateCmdBuffer.HideInstance(RenderIndex); + } } // Clear all the per-instance data @@ -3010,9 +3016,10 @@ void UHierarchicalInstancedStaticMeshComponent::OnPostLoadPerInstanceData() ULevel* OwnerLevel = Owner->GetLevel(); UWorld* OwnerWorld = OwnerLevel ? OwnerLevel->OwningWorld : nullptr; - if (OwnerWorld && OwnerWorld->GetActiveLightingScenario() != nullptr && OwnerWorld->GetActiveLightingScenario() != OwnerLevel) + //update the instance data if the lighting scenario isn't the owner level or if the reorder table do not match the per instance sm data + if ((OwnerWorld && OwnerWorld->GetActiveLightingScenario() != nullptr && OwnerWorld->GetActiveLightingScenario() != OwnerLevel) + || (PerInstanceSMData.Num() > 0 && PerInstanceSMData.Num() != InstanceReorderTable.Num())) { - //update the instance data if the lighting scenario isn't the owner level bForceTreeBuild = true; } diff --git a/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp b/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp index a19f5b240220..5d8d88a36f0d 100644 --- a/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/InstancedStaticMesh.cpp @@ -2312,7 +2312,8 @@ void UInstancedStaticMeshComponent::OnComponentCreated() { // if we are pasting/duplicating this component, it may be created with some instances already in place // in this case, need to ensure that the instance render data is properly created - const bool InitializeFromCurrentData = PerInstanceSMData.Num() > 0; + // We only need to only init from current data if the reorder table == per instance data, but only for the HISM Component, in the case of ISM, the reorder table is never used. + const bool InitializeFromCurrentData = PerInstanceSMData.Num() > 0 && (InstanceReorderTable.Num() == PerInstanceSMData.Num() || InstanceReorderTable.Num() == 0); InitPerInstanceRenderData(InitializeFromCurrentData); } } diff --git a/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp b/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp index 443211684401..8bd214471d08 100644 --- a/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp +++ b/Engine/Source/Runtime/Engine/Private/Internationalization/StringTable.cpp @@ -124,6 +124,11 @@ private: } //~ IStringTableEngineBridge interface + virtual bool CanFindOrLoadStringTableAssetImpl() override + { + return IsInGameThread() && !IsGarbageCollecting() && !GIsSavingPackage; + } + virtual int32 LoadStringTableAssetImpl(const FName InTableId, FLoadStringTableAssetCallback InLoadedCallback) override { const FSoftObjectPath StringTableAssetReference = GetAssetReference(InTableId); diff --git a/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp b/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp index d9dd156530cf..86d419396d4a 100644 --- a/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp +++ b/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp @@ -743,7 +743,7 @@ void ULevelStreaming::SetLoadedLevel(ULevel* Level) check(PendingUnloadLevel == nullptr); PendingUnloadLevel = LoadedLevel; LoadedLevel = Level; - CachedLoadedLevelPackageName = (LoadedLevel ? LoadedLevel->GetOutermost()->GetFName() : NAME_None); + bHasCachedLoadedLevelPackageName = false; // Cancel unloading for this level, in case it was queued for it FLevelStreamingGCHelper::CancelUnloadRequest(LoadedLevel); @@ -800,7 +800,9 @@ bool ULevelStreaming::IsDesiredLevelLoaded() const { const bool bIsGameWorld = GetWorld()->IsGameWorld(); const FName DesiredPackageName = bIsGameWorld ? GetLODPackageName() : GetWorldAssetPackageFName(); - return (CachedLoadedLevelPackageName == DesiredPackageName); + const FName LoadedLevelPackageName = GetLoadedLevelPackageName(); + + return (LoadedLevelPackageName == DesiredPackageName); } return false; @@ -826,9 +828,10 @@ bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoad // Package name we want to load const bool bIsGameWorld = PersistentWorld->IsGameWorld(); const FName DesiredPackageName = bIsGameWorld ? GetLODPackageName() : GetWorldAssetPackageFName(); + const FName LoadedLevelPackageName = GetLoadedLevelPackageName(); // Check if currently loaded level is what we want right now - if (LoadedLevel && CachedLoadedLevelPackageName == DesiredPackageName) + if (LoadedLevel && LoadedLevelPackageName == DesiredPackageName) { return true; } @@ -1363,6 +1366,7 @@ void ULevelStreaming::SetWorldAsset(const TSoftObjectPtr& NewWorldAsset) { WorldAsset = NewWorldAsset; bHasCachedWorldAssetPackageFName = false; + bHasCachedLoadedLevelPackageName = false; if (CurrentState == ECurrentState::FailedToLoad) { @@ -1391,6 +1395,17 @@ FName ULevelStreaming::GetWorldAssetPackageFName() const return CachedWorldAssetPackageFName; } +FName ULevelStreaming::GetLoadedLevelPackageName() const +{ + if( !bHasCachedLoadedLevelPackageName ) + { + CachedLoadedLevelPackageName = (LoadedLevel ? LoadedLevel->GetOutermost()->GetFName() : NAME_None); + bHasCachedLoadedLevelPackageName = true; + } + + return CachedLoadedLevelPackageName; +} + void ULevelStreaming::SetWorldAssetByPackageName(FName InPackageName) { const FString TargetWorldPackageName = InPackageName.ToString(); @@ -1557,6 +1572,7 @@ void ULevelStreaming::PostEditChangeProperty(FPropertyChangedEvent& PropertyChan else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULevelStreaming, WorldAsset)) { bHasCachedWorldAssetPackageFName = false; + bHasCachedLoadedLevelPackageName = false; } } diff --git a/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp b/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp index 83e1addf0927..a795ac1a2450 100644 --- a/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp +++ b/Engine/Source/Runtime/Engine/Private/Materials/Material.cpp @@ -1521,7 +1521,7 @@ bool UMaterial::SetMaterialUsage(bool &bNeedsRecompile, EMaterialUsage Usage) //Do not warn the user during automation testing if (!GIsAutomationTesting) { - UE_LOG(LogMaterial, Warning, TEXT("Material %s needed to have new flag set %s !"), *GetPathName(), *GetUsageName(Usage)); + UE_LOG(LogMaterial, Display, TEXT("Material %s needed to have new flag set %s !"), *GetPathName(), *GetUsageName(Usage)); } // Open a material update context so this material can be modified safely. diff --git a/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp b/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp index 894c7cf8273f..ad5b25c5a547 100644 --- a/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp +++ b/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp @@ -8731,8 +8731,10 @@ uint32 UMaterialExpressionIf::GetInputType(int32 InputIndex) bool UMaterialExpressionIf::IsResultMaterialAttributes(int32 OutputIndex) { - if ((A.GetTracedInput().Expression && !A.Expression->ContainsInputLoop() && A.Expression->IsResultMaterialAttributes(A.OutputIndex)) || - (B.GetTracedInput().Expression && !B.Expression->ContainsInputLoop() && B.Expression->IsResultMaterialAttributes(B.OutputIndex))) + if ((AGreaterThanB.GetTracedInput().Expression && !AGreaterThanB.Expression->ContainsInputLoop() && AGreaterThanB.Expression->IsResultMaterialAttributes(AGreaterThanB.OutputIndex)) + && (!AEqualsB.GetTracedInput().Expression || (!AEqualsB.Expression->ContainsInputLoop() && AEqualsB.Expression->IsResultMaterialAttributes(AEqualsB.OutputIndex))) + && (ALessThanB.GetTracedInput().Expression && !ALessThanB.Expression->ContainsInputLoop() && ALessThanB.Expression->IsResultMaterialAttributes(ALessThanB.OutputIndex)) + ) { return true; } diff --git a/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp b/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp index f8fade6f2bb4..f541c8e5d8b3 100644 --- a/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp +++ b/Engine/Source/Runtime/Engine/Private/NetworkDriver.cpp @@ -9,6 +9,7 @@ #include "Misc/CommandLine.h" #include "Misc/NetworkGuid.h" #include "Stats/Stats.h" +#include "Misc/App.h" #include "Misc/MemStack.h" #include "HAL/IConsoleManager.h" #include "HAL/LowLevelMemTracker.h" @@ -278,6 +279,8 @@ PRAGMA_DISABLE_DEPRECATION_WARNINGS ChannelClasses[CHTYPE_Actor] = UActorChannel::StaticClass(); ChannelClasses[CHTYPE_Voice] = UVoiceChannel::StaticClass(); PRAGMA_ENABLE_DEPRECATION_WARNINGS + + UpdateDelayRandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); } void UNetDriver::InitPacketSimulationSettings() @@ -3670,7 +3673,7 @@ void UNetDriver::ServerReplicateActors_BuildConsiderList( TArrayOptimalNetUpdateDelta : 1.0f / Actor->NetUpdateFrequency; // then set the next update time - ActorInfo->NextUpdateTime = World->TimeSeconds + FMath::SRand() * ServerTickTime + NextUpdateDelta; + ActorInfo->NextUpdateTime = World->TimeSeconds + UpdateDelayRandomStream.FRand() * ServerTickTime + NextUpdateDelta; // and mark when the actor first requested an update //@note: using Time because it's compared against UActorChannel.LastUpdateTime which also uses that value @@ -4033,7 +4036,7 @@ int32 UNetDriver::ServerReplicateActors_ProcessPrioritizedActors( UNetConnection // if it is relevant then mark the channel as relevant for a short amount of time if ( bIsRelevant ) { - Channel->RelevantTime = Time + 0.5f * FMath::SRand(); + Channel->RelevantTime = Time + 0.5f * UpdateDelayRandomStream.FRand(); } // if the channel isn't saturated if ( Channel->IsNetReady( 0 ) ) @@ -4435,7 +4438,7 @@ int32 UNetDriver::ServerReplicateActors(float DeltaSeconds) PriorityActors[k]->ActorInfo->bPendingNetUpdate = true; if ( Channel != NULL ) { - Channel->RelevantTime = Time + 0.5f * FMath::SRand(); + Channel->RelevantTime = Time + 0.5f * UpdateDelayRandomStream.FRand(); } } } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp index 9f46f31a2b4e..a552d6fe6a68 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleBeamModules.cpp @@ -1363,6 +1363,8 @@ void UParticleModuleBeamNoise::Spawn(FParticleEmitterInstance* Owner, int32 Offs UParticleSystemComponent* Component = Owner->Component; UParticleModuleTypeDataBeam2* BeamTD = BeamInst->BeamTypeData; + FRandomStream& RandomStream = GetRandomStream(Owner); + SPAWN_INIT; FBeam2TypeDataPayload* BeamData = NULL; @@ -1395,7 +1397,7 @@ void UParticleModuleBeamNoise::Spawn(FParticleEmitterInstance* Owner, int32 Offs int32 CalcFreq = Frequency; if (Frequency_LowRange > 0) { - CalcFreq = FMath::TruncToInt((FMath::SRand() * (Frequency - Frequency_LowRange)) + Frequency_LowRange); + CalcFreq = FMath::TruncToInt((RandomStream.FRand() * (Frequency - Frequency_LowRange)) + Frequency_LowRange); } BEAM2_TYPEDATA_SETFREQUENCY(BeamData->Lock_Max_NumNoisePoints, CalcFreq); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp index fcd83c876400..49e57ca4cd99 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp @@ -1702,7 +1702,9 @@ void UParticleEmitter::CacheEmitterModuleInfo() LockAxisFlags = EPAL_NONE; ModuleOffsetMap.Empty(); ModuleInstanceOffsetMap.Empty(); + ModuleRandomSeedInstanceOffsetMap.Empty(); ModulesNeedingInstanceData.Empty(); + ModulesNeedingRandomSeedInstanceData.Empty(); MeshMaterials.Empty(); DynamicParameterDataOffset = 0; LightDataOffset = 0; @@ -1792,6 +1794,25 @@ void UParticleEmitter::CacheEmitterModuleInfo() } ReqInstanceBytes += TempInstanceBytes; } + + // Add space for per instance random seed value if required + if (FApp::bUseFixedSeed || ParticleModule->bSupportsRandomSeed) + { + // Add the high-lodlevel offset to the lookup map + ModuleRandomSeedInstanceOffsetMap.Add(ParticleModule, ReqInstanceBytes); + // Remember that this module has emitter-instance data + ModulesNeedingRandomSeedInstanceData.Add(ParticleModule); + + // Add all the other LODLevel modules, using the same offset. + // This removes the need to always also grab the HighestLODLevel pointer. + for (int32 LODIdx = 1; LODIdx < LODLevels.Num(); LODIdx++) + { + UParticleLODLevel* CurLODLevel = LODLevels[LODIdx]; + ModuleRandomSeedInstanceOffsetMap.Add(CurLODLevel->Modules[ModuleIdx], ReqInstanceBytes); + } + + ReqInstanceBytes += sizeof(FParticleRandomSeedInstancePayload); + } } if (ParticleModule->IsA(UParticleModuleOrientationAxisLock::StaticClass())) @@ -3336,6 +3357,8 @@ UParticleSystemComponent::UParticleSystemComponent(const FObjectInitializer& Obj bResetOnDetach = false; OldPosition = FVector(0.0f, 0.0f, 0.0f); + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); + PartSysVelocity = FVector(0.0f, 0.0f, 0.0f); WarmupTime = 0.0f; @@ -6555,7 +6578,7 @@ void UParticleSystemComponent::InitializeSystem() if( Template->bUseDelayRange ) { - const float Rand = FMath::FRand(); + const float Rand = RandomStream.FRand(); EmitterDelay = Template->DelayLow + ((Template->Delay - Template->DelayLow) * Rand); } } @@ -7228,8 +7251,7 @@ bool UParticleSystemComponent::GetFloatParameter(const FName InName,float& OutFl } else if (Param.ParamType == PSPT_ScalarRand) { - // check(IsInGameThread()); this isn't exactly cool to call from multiple threads, but it isn't terrible. - OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * FMath::SRand(); + OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * RandomStream.FRand(); return true; } } @@ -7260,9 +7282,7 @@ bool UParticleSystemComponent::GetVectorParameter(const FName InName,FVector& Ou } else if (Param.ParamType == PSPT_VectorRand) { - check(IsInGameThread()); - FVector RandValue(FMath::SRand(), FMath::SRand(), FMath::SRand()); - OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandValue; + OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandomStream.VRand(); return true; } } @@ -7292,9 +7312,7 @@ bool UParticleSystemComponent::GetAnyVectorParameter(const FName InName,FVector& } if (Param.ParamType == PSPT_VectorRand) { - //check(IsInGameThread()); - FVector RandValue(FMath::SRand(), FMath::SRand(), FMath::SRand()); - OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandValue; + OutVector = Param.Vector + (Param.Vector_Low - Param.Vector) * RandomStream.VRand(); return true; } if (Param.ParamType == PSPT_Scalar) @@ -7305,8 +7323,7 @@ bool UParticleSystemComponent::GetAnyVectorParameter(const FName InName,FVector& } if (Param.ParamType == PSPT_ScalarRand) { - // check(IsInGameThread()); this isn't exactly cool to call from multiple threads, but it isn't terrible. - float OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * FMath::SRand(); + float OutFloat = Param.Scalar + (Param.Scalar_Low - Param.Scalar) * RandomStream.FRand(); OutVector = FVector(OutFloat, OutFloat, OutFloat); return true; } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp index b90adab2ab8d..8beb149743c5 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp @@ -528,12 +528,21 @@ void FParticleEmitterInstance::Init() for (UParticleModule* ParticleModule : SpriteTemplate->ModulesNeedingInstanceData) { - check(ParticleModule); - uint8* PrepInstData = GetModuleInstanceData(ParticleModule); - check(PrepInstData != nullptr); // Shouldn't be in the list if it doesn't have data - ParticleModule->PrepPerInstanceBlock(this, (void*)PrepInstData); + check(ParticleModule); + uint8* PrepInstData = GetModuleInstanceData(ParticleModule); + check(PrepInstData != nullptr); // Shouldn't be in the list if it doesn't have data + ParticleModule->PrepPerInstanceBlock(this, (void*)PrepInstData); } - + + for (UParticleModule* ParticleModule : SpriteTemplate->ModulesNeedingRandomSeedInstanceData) + { + check(ParticleModule); + FParticleRandomSeedInstancePayload* SeedInstancePayload = GetModuleRandomSeedInstanceData(ParticleModule); + check(SeedInstancePayload != nullptr); // Shouldn't be in the list if it doesn't have data + FParticleRandomSeedInfo* RandomSeedInfo = ParticleModule->GetRandomSeedInfo(); + ParticleModule->PrepRandomSeedInstancePayload(this, SeedInstancePayload, RandomSeedInfo ? *RandomSeedInfo : FParticleRandomSeedInfo()); + } + // Offset into emitter specific payload (e.g. TrailComponent requires extra bytes). PayloadOffset = ParticleSize; @@ -544,7 +553,7 @@ void FParticleEmitterInstance::Init() ParticleSize = Align(ParticleSize, 16); // E.g. trail emitters store trailing particles directly after leading one. - ParticleStride = CalculateParticleStride(ParticleSize); + ParticleStride = CalculateParticleStride(ParticleSize); } // Setup the emitter instance material array... @@ -1555,9 +1564,25 @@ uint8* FParticleEmitterInstance::GetModuleInstanceData(UParticleModule* Module) if (Offset) { check(*Offset < (uint32)InstancePayloadSize); - return &(InstanceData[*Offset]); - } + return &(InstanceData[*Offset]); } + } + return NULL; +} + +/** Get pointer to emitter instance random seed payload data for a particular module */ +FParticleRandomSeedInstancePayload* FParticleEmitterInstance::GetModuleRandomSeedInstanceData(UParticleModule* Module) +{ + // If there is instance data present, look up the modules offset + if (InstanceData) + { + uint32* Offset = SpriteTemplate->ModuleRandomSeedInstanceOffsetMap.Find(Module); + if (Offset) + { + check(*Offset < (uint32)InstancePayloadSize); + return (FParticleRandomSeedInstancePayload*)&(InstanceData[*Offset]); + } + } return NULL; } @@ -1620,6 +1645,8 @@ float FParticleEmitterInstance::GetCurrentBurstRateOffset(float& DeltaTime, int3 UParticleLODLevel* LODLevel = GetCurrentLODLevelChecked(); if (LODLevel->SpawnModule->BurstList.Num() > 0) { + FRandomStream& RandomStream = LODLevel->SpawnModule->GetRandomStream(this); + // For each burst in the list for (int32 BurstIdx = 0; BurstIdx < LODLevel->SpawnModule->BurstList.Num(); BurstIdx++) { @@ -1644,7 +1671,7 @@ float FParticleEmitterInstance::GetCurrentBurstRateOffset(float& DeltaTime, int3 int32 Count = BurstEntry->Count; if (BurstEntry->CountLow > -1) { - Count = BurstEntry->CountLow + FMath::RoundToInt(FMath::SRand() * (float)(BurstEntry->Count - BurstEntry->CountLow)); + Count = RandomStream.RandRange(BurstEntry->CountLow, BurstEntry->Count); } // Take in to account scale. float Scale = LODLevel->SpawnModule->BurstScale.GetValue(EmitterTime, Component); @@ -2630,10 +2657,12 @@ void FParticleEmitterInstance::SetupEmitterDuration() UParticleLODLevel* TempLOD = SpriteTemplate->LODLevels[LODIndex]; UParticleModuleRequired* RequiredModule = TempLOD->RequiredModule; + FRandomStream& RandomStream = RequiredModule->GetRandomStream(this); + CurrentDelay = RequiredModule->EmitterDelay + Component->EmitterDelay; if (RequiredModule->bEmitterDelayUseRange) { - const float Rand = FMath::FRand(); + const float Rand = RandomStream.FRand(); CurrentDelay = RequiredModule->EmitterDelayLow + ((RequiredModule->EmitterDelay - RequiredModule->EmitterDelayLow) * Rand) + Component->EmitterDelay; } @@ -2641,7 +2670,7 @@ void FParticleEmitterInstance::SetupEmitterDuration() if (RequiredModule->bEmitterDurationUseRange) { - const float Rand = FMath::FRand(); + const float Rand = RandomStream.FRand(); const float Duration = RequiredModule->EmitterDurationLow + ((RequiredModule->EmitterDuration - RequiredModule->EmitterDurationLow) * Rand); EmitterDurations[TempLOD->Level] = Duration + CurrentDelay; diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp index 5477b7f1fb60..7acc042f9671 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleGpuSimulation.cpp @@ -3565,7 +3565,7 @@ FGPUSpriteParticleEmitterInstance(FFXSystem* InFXSystem, FGPUSpriteEmitterInfo& check(AllocatedTiles.Num() == TileTimeOfDeath.Num()); FreeParticlesInTile = 0; - RandomStream.Initialize( FMath::Rand() ); + RandomStream.Initialize(Component->RandomStream.FRand()); EmitterInstRandom = RandomStream.GetFraction(); FParticleSimulationResources* ParticleSimulationResources = FXSystem->GetParticleSimulationResources(); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp index c08978b363d2..9ae7cc7f2cd2 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules.cpp @@ -14,6 +14,7 @@ #include "UObject/RenderingObjectVersion.h" #include "UObject/UObjectHash.h" #include "UObject/Package.h" +#include "Misc/App.h" #include "GameFramework/WorldSettings.h" #include "Particles/ParticleSystem.h" #include "ParticleHelper.h" @@ -666,12 +667,18 @@ void UParticleModule::GetParticleParametersUtilized(TArray& ParticlePar uint32 UParticleModule::PrepRandomSeedInstancePayload(FParticleEmitterInstance* Owner, FParticleRandomSeedInstancePayload* InRandSeedPayload, const FParticleRandomSeedInfo& InRandSeedInfo) { - if (InRandSeedPayload != NULL) + // These should never be null + if (!ensure( Owner != nullptr && Owner->Component != nullptr )) { - FMemory::Memzero(InRandSeedPayload, sizeof(FParticleRandomSeedInstancePayload)); + return 0xffffffff; + } + + if (InRandSeedPayload != nullptr) + { + new(InRandSeedPayload) FParticleRandomSeedInstancePayload(); // See if the parameter is set on the instance... - if ((Owner != NULL) && (Owner->Component != NULL) && (InRandSeedInfo.bGetSeedFromInstance == true)) + if (InRandSeedInfo.bGetSeedFromInstance == true) { float SeedValue; if (Owner->Component->GetFloatParameter(InRandSeedInfo.ParameterName, SeedValue) == true) @@ -700,15 +707,19 @@ uint32 UParticleModule::PrepRandomSeedInstancePayload(FParticleEmitterInstance* // Pick a seed to use and initialize it!!!! if (InRandSeedInfo.RandomSeeds.Num() > 0) { + int32 Index = 0; + if (InRandSeedInfo.bRandomlySelectSeedArray) { - int32 Index = FMath::RandRange(0, InRandSeedInfo.RandomSeeds.Num() - 1); - InRandSeedPayload->RandomStream.Initialize(InRandSeedInfo.RandomSeeds[Index]); - } - else - { - InRandSeedPayload->RandomStream.Initialize(InRandSeedInfo.RandomSeeds[0]); + Index = Owner->Component->RandomStream.RandHelper(InRandSeedInfo.RandomSeeds.Num()); } + + InRandSeedPayload->RandomStream.Initialize(InRandSeedInfo.RandomSeeds[Index]); + return 0; + } + else if (FApp::bUseFixedSeed) + { + InRandSeedPayload->RandomStream.Initialize(GetFName()); return 0; } } @@ -789,6 +800,13 @@ bool UParticleModule::IsUsedInGPUEmitter()const return false; } +FRandomStream& UParticleModule::GetRandomStream(FParticleEmitterInstance* Owner) +{ + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); + FRandomStream& RandomStream = (Payload != nullptr) ? Payload->RandomStream : Owner->Component->RandomStream; + return RandomStream; +} + #if WITH_EDITOR void UParticleModule::SetTransactionFlag() { @@ -1404,7 +1422,7 @@ void UParticleModuleMeshRotation::PostEditChangeProperty(FPropertyChangedEvent& void UParticleModuleMeshRotation::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } @@ -1445,27 +1463,11 @@ UParticleModuleMeshRotation_Seeded::UParticleModuleMeshRotation_Seeded(const FOb bRequiresLoopingNotification = true; } -void UParticleModuleMeshRotation_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleMeshRotation_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleMeshRotation_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleMeshRotation_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1518,7 +1520,7 @@ void UParticleModuleMeshRotationRate::PostEditChangeProperty(FPropertyChangedEve void UParticleModuleMeshRotationRate::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleMeshRotationRate::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1563,28 +1565,11 @@ UParticleModuleMeshRotationRate_Seeded::UParticleModuleMeshRotationRate_Seeded(c bRequiresLoopingNotification = true; } -void UParticleModuleMeshRotationRate_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleMeshRotationRate_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - - -uint32 UParticleModuleMeshRotationRate_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleMeshRotationRate_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1810,7 +1795,7 @@ void UParticleModuleRotation::PostEditChangeProperty(FPropertyChangedEvent& Prop void UParticleModuleRotation::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleRotation::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1832,27 +1817,11 @@ UParticleModuleRotation_Seeded::UParticleModuleRotation_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleRotation_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleRotation_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleRotation_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleRotation_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1905,7 +1874,7 @@ void UParticleModuleRotationRate::PostEditChangeProperty(FPropertyChangedEvent& void UParticleModuleRotationRate::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleRotationRate::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1941,27 +1910,11 @@ UParticleModuleRotationRate_Seeded::UParticleModuleRotationRate_Seeded(const FOb bRequiresLoopingNotification = true; } -void UParticleModuleRotationRate_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleRotationRate_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleRotationRate_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleRotationRate_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -2248,8 +2201,7 @@ float UParticleModuleSubUV::DetermineImageIndex(FParticleEmitterInstance* Owner, ((Particle->RelativeTime - SubUVPayload.RandomImageTime) > LODLevel->RequiredModule->RandomImageTime) || (SubUVPayload.RandomImageTime == 0.0f)) { - const float RandomNumber = FMath::SRand(); - ImageIndex = FMath::TruncToInt(RandomNumber * TotalSubImages); + ImageIndex = GetRandomStream(Owner).RandHelper(TotalSubImages); SubUVPayload.RandomImageTime = Particle->RelativeTime; } @@ -2381,7 +2333,8 @@ void UParticleModuleSubUVMovie::Spawn(FParticleEmitterInstance* Owner, int32 Off } else if (StartingFrame == 0) { - MoviePayload.Time = FMath::TruncToFloat(FMath::SRand() * (TotalSubImages-1)); + FRandomStream& RandomStream = GetRandomStream(Owner); + MoviePayload.Time = FMath::TruncToFloat(RandomStream.FRand() * (TotalSubImages-1)); } // Update the payload @@ -3117,7 +3070,7 @@ void UParticleModuleLight::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset LightData.RadiusScale = RadiusScale.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); // Exponent of 0 is interpreted by renderer as inverse squared falloff LightData.LightExponent = bUseInverseSquaredFalloff ? 0 : LightExponent.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); - const float RandomNumber = InRandomStream ? InRandomStream->GetFraction() : FMath::SRand(); + const float RandomNumber = InRandomStream->GetFraction(); LightData.bValid = RandomNumber < SpawnFraction; LightData.bAffectsTranslucency = bAffectsTranslucency; LightData.bHighQuality = bHighQualityLights; @@ -3240,7 +3193,7 @@ void UParticleModuleLight::UpdateHQLight(UPointLightComponent* PointLightCompone void UParticleModuleLight::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLight::Update(FParticleEmitterInstance* Owner, int32 Offset, float DeltaTime) @@ -3389,27 +3342,11 @@ UParticleModuleLight_Seeded::UParticleModuleLight_Seeded(const FObjectInitialize bRequiresLoopingNotification = true; } -void UParticleModuleLight_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this) + Super::RequiredBytesPerInstance()); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLight_Seeded::RequiredBytesPerInstance() -{ - return Super::RequiredBytesPerInstance() + RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLight_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)((uint8*)InstData + Super::RequiredBytesPerInstance()), RandomSeedInfo); -} - void UParticleModuleLight_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this) + Super::RequiredBytesPerInstance()); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -3876,7 +3813,7 @@ void UParticleModuleLifetime::CompileModule( struct FParticleEmitterBuildInfo& E void UParticleModuleLifetime::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLifetime::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -3942,39 +3879,18 @@ UParticleModuleLifetime_Seeded::UParticleModuleLifetime_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleLifetime_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLifetime_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLifetime_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLifetime_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } float UParticleModuleLifetime_Seeded::GetLifetimeValue(FParticleEmitterInstance* Owner, float InTime, UObject* Data ) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - if (Payload != NULL) - { - return Lifetime.GetValue(InTime, Data, &(Payload->RandomStream)); - } - return UParticleModuleLifetime::GetLifetimeValue(Owner, InTime, Data); + return Lifetime.GetValue(InTime, Data, &GetRandomStream(Owner)); } /*----------------------------------------------------------------------------- @@ -4208,7 +4124,7 @@ void UParticleModuleAttractorParticle::Spawn(FParticleEmitterInstance* Owner, in switch (SelectionMethod) { case EAPSM_Random: - LastSelIndex = FMath::TruncToInt(FMath::SRand() * AttractorEmitterInst->ActiveParticles); + LastSelIndex = GetRandomStream(Owner).RandHelper(AttractorEmitterInst->ActiveParticles); Data.SourceIndex = LastSelIndex; break; case EAPSM_Sequential: diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp index 568c3eaf4fc9..a2736831839a 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Color.cpp @@ -155,7 +155,7 @@ bool UParticleModuleColor::AddModuleCurvesToEditor(UInterpCurveEdSetup* EdSetup, void UParticleModuleColor::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } @@ -192,27 +192,11 @@ UParticleModuleColor_Seeded::UParticleModuleColor_Seeded(const FObjectInitialize bRequiresLoopingNotification = true; } -void UParticleModuleColor_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleColor_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleColor_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleColor_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp index 4af17d5d0118..2a61259c39b9 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Location.cpp @@ -90,7 +90,7 @@ void UParticleModuleLocation::PostEditChangeProperty(FPropertyChangedEvent& Prop void UParticleModuleLocation::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocation::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -103,7 +103,7 @@ void UParticleModuleLocation::SpawnEx(FParticleEmitterInstance* Owner, int32 Off // Avoid divide by zero. if ((DistributeOverNPoints != 0.0f) && (DistributeOverNPoints != 1.f)) { - float RandomNum = FMath::SRand() * FMath::Fractional(Owner->EmitterTime); + float RandomNum = InRandomStream->FRand() * FMath::Fractional(Owner->EmitterTime); if(RandomNum > DistributeThreshold) { @@ -113,7 +113,7 @@ void UParticleModuleLocation::SpawnEx(FParticleEmitterInstance* Owner, int32 Off { FVector Min, Max; StartLocation.GetRange(Min, Max); - FVector Lerped = FMath::Lerp(Min, Max, FMath::TruncToFloat((FMath::SRand() * (DistributeOverNPoints - 1.0f)) + 0.5f)/(DistributeOverNPoints - 1.0f)); + FVector Lerped = FMath::Lerp(Min, Max, FMath::TruncToFloat((InRandomStream->FRand() * (DistributeOverNPoints - 1.0f)) + 0.5f)/(DistributeOverNPoints - 1.0f)); LocationOffset.Set(Lerped.X, Lerped.Y, Lerped.Z); } } @@ -187,27 +187,11 @@ UParticleModuleLocation_Seeded::UParticleModuleLocation_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleLocation_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocation_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocation_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocation_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -251,27 +235,11 @@ UParticleModuleLocationWorldOffset_Seeded::UParticleModuleLocationWorldOffset_Se bRequiresLoopingNotification = true; } -void UParticleModuleLocationWorldOffset_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocationWorldOffset_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocationWorldOffset_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocationWorldOffset_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -448,6 +416,8 @@ void UParticleModuleLocationEmitter::Spawn(FParticleEmitterInstance* Owner, int3 bool bSourceIsInLocalSpace = LocationEmitterInst->CurrentLODLevel->RequiredModule->bUseLocalSpace; bool bInLocalSpace = Owner->CurrentLODLevel->RequiredModule->bUseLocalSpace; + FRandomStream& RandomStream = GetRandomStream(Owner); + SPAWN_INIT; { int32 Index = 0; @@ -456,11 +426,7 @@ void UParticleModuleLocationEmitter::Spawn(FParticleEmitterInstance* Owner, int3 { case ELESM_Random: { - Index = FMath::TruncToInt(FMath::SRand() * LocationEmitterInst->ActiveParticles); - if (Index >= LocationEmitterInst->ActiveParticles) - { - Index = LocationEmitterInst->ActiveParticles - 1; - } + Index = RandomStream.RandHelper(LocationEmitterInst->ActiveParticles); } break; case ELESM_Sequential: @@ -719,18 +685,9 @@ void UParticleModuleLocationPrimitiveBase::DetermineUnitDirection(FParticleEmitt FVector vRand; // Grab 3 random numbers for the axes - if (InRandomStream == NULL) - { - vRand.X = FMath::SRand(); - vRand.Y = FMath::SRand(); - vRand.Z = FMath::SRand(); - } - else - { - vRand.X = InRandomStream->GetFraction(); - vRand.Y = InRandomStream->GetFraction(); - vRand.Z = InRandomStream->GetFraction(); - } + vRand.X = InRandomStream->GetFraction(); + vRand.Y = InRandomStream->GetFraction(); + vRand.Z = InRandomStream->GetFraction(); // Set the unit dir if (Positive_X && Negative_X) @@ -846,7 +803,7 @@ void UParticleModuleLocationPrimitiveTriangle::PostEditChangeProperty(FPropertyC void UParticleModuleLocationPrimitiveTriangle::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocationPrimitiveTriangle::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -868,21 +825,12 @@ void UParticleModuleLocationPrimitiveTriangle::SpawnEx(FParticleEmitterInstance* float BarycentricCoords[3] = {0}; float ZPos = 0.0f; - if (InRandomStream) - { - BarycentricCoords[0] = InRandomStream->GetFraction(); - BarycentricCoords[1] = InRandomStream->GetFraction(); - BarycentricCoords[2] = InRandomStream->GetFraction(); - ZPos = InRandomStream->GetFraction(); - } - else - { - BarycentricCoords[0] = FMath::SRand(); - BarycentricCoords[1] = FMath::SRand(); - BarycentricCoords[2] = FMath::SRand(); - ZPos = FMath::SRand(); - } + BarycentricCoords[0] = InRandomStream->GetFraction(); + BarycentricCoords[1] = InRandomStream->GetFraction(); + BarycentricCoords[2] = InRandomStream->GetFraction(); + ZPos = InRandomStream->GetFraction(); + FVector LocationOffset = FVector::ZeroVector; float Sum = FMath::Max(KINDA_SMALL_NUMBER, BarycentricCoords[0] + BarycentricCoords[1] + BarycentricCoords[2]); for (int32 i = 0; i < 3; i++) @@ -1000,7 +948,7 @@ void UParticleModuleLocationPrimitiveCylinder::PostEditChangeProperty(FPropertyC void UParticleModuleLocationPrimitiveCylinder::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocationPrimitiveCylinder::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1240,27 +1188,11 @@ UParticleModuleLocationPrimitiveCylinder_Seeded::UParticleModuleLocationPrimitiv bRequiresLoopingNotification = true; } -void UParticleModuleLocationPrimitiveCylinder_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocationPrimitiveCylinder_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocationPrimitiveCylinder_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocationPrimitiveCylinder_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1304,7 +1236,7 @@ void UParticleModuleLocationPrimitiveSphere::PostEditChangeProperty(FPropertyCha void UParticleModuleLocationPrimitiveSphere::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleLocationPrimitiveSphere::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -1456,27 +1388,11 @@ UParticleModuleLocationPrimitiveSphere_Seeded::UParticleModuleLocationPrimitiveS bRequiresLoopingNotification = true; } -void UParticleModuleLocationPrimitiveSphere_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleLocationPrimitiveSphere_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleLocationPrimitiveSphere_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleLocationPrimitiveSphere_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } @@ -1511,7 +1427,7 @@ UParticleModuleLocationBoneSocket::UParticleModuleLocationBoneSocket(const FObje InheritVelocityScale = 1.0f; } -int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent) +int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream) { check(InstancePayload && SourceComponent); @@ -1531,7 +1447,7 @@ int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBon else if (SelectionMethod == BONESOCKETSEL_Random) { // Note: This can select the same socket over and over... - SourceIndex = FMath::TruncToInt(FMath::SRand() * ((float)MaxIndex - 0.5f)); + SourceIndex = FMath::TruncToInt(InRandomStream.FRand() * ((float)MaxIndex - 0.5f)); InstancePayload->LastSelectedIndex = SourceIndex; } @@ -1547,7 +1463,7 @@ int32 UParticleModuleLocationBoneSocket::SelectNextSpawnIndex(FModuleLocationBon return SourceIndex; } -void UParticleModuleLocationBoneSocket::RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent) +void UParticleModuleLocationBoneSocket::RegeneratePreSelectedIndices(FModuleLocationBoneSocketInstancePayload* InstancePayload, USkeletalMeshComponent* SourceComponent, FRandomStream& InRandomStream) { if (SourceIndexMode == EBoneSocketSourceIndexMode::PreSelectedIndices) { @@ -1555,7 +1471,7 @@ void UParticleModuleLocationBoneSocket::RegeneratePreSelectedIndices(FModuleLoca for (int32 i = 0; i < NumPreSelectedIndices; ++i) { //Should we provide sequential selection here? Does that make sense for the pre selected list? - InstancePayload->PreSelectedBoneSocketIndices[i] = FMath::TruncToInt(FMath::SRand() * ((float)MaxIndex - 0.5f)); + InstancePayload->PreSelectedBoneSocketIndices[i] = FMath::TruncToInt(InRandomStream.FRand() * ((float)MaxIndex - 0.5f)); } if (InheritingBoneVelocity()) @@ -1637,6 +1553,8 @@ void UParticleModuleLocationBoneSocket::Spawn(FParticleEmitterInstance* Owner, i return; } + FRandomStream& RandomStream = GetRandomStream(Owner); + // Setup the source skeletal mesh component... GetSkeletalMeshComponentSource(Owner, InstancePayload); @@ -1647,7 +1565,7 @@ void UParticleModuleLocationBoneSocket::Spawn(FParticleEmitterInstance* Owner, i } USkeletalMeshComponent* SourceComponent = InstancePayload->SourceComponent.Get(); - int32 SourceIndex = SelectNextSpawnIndex(InstancePayload, SourceComponent); + int32 SourceIndex = SelectNextSpawnIndex(InstancePayload, SourceComponent, RandomStream); if (SourceIndex == INDEX_NONE) { return; @@ -1821,7 +1739,7 @@ void UParticleModuleLocationBoneSocket::FinalUpdate(FParticleEmitterInstance* Ow } //Select a new set of bones to spawn from next frame. - RegeneratePreSelectedIndices(InstancePayload, SourceComponent); + RegeneratePreSelectedIndices(InstancePayload, SourceComponent, GetRandomStream(Owner)); } uint32 UParticleModuleLocationBoneSocket::RequiredBytes(UParticleModuleTypeDataBase* TypeData) @@ -2070,7 +1988,7 @@ void UParticleModuleLocationBoneSocket::GetSkeletalMeshComponentSource(FParticle if (NewSkelComp) { InstancePayload->SourceComponent = NewSkelComp; - RegeneratePreSelectedIndices(InstancePayload, NewSkelComp); + RegeneratePreSelectedIndices(InstancePayload, NewSkelComp, GetRandomStream(Owner)); } } } @@ -2324,6 +2242,8 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own { return; } + + FRandomStream& RandomStream = GetRandomStream(Owner); GetSkeletalMeshComponentSource(Owner, InstancePayload); @@ -2353,7 +2273,7 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own { int32 SourceLocationsCount(LODData.GetNumVertices()); - SourceIndex = FMath::TruncToInt(FMath::SRand() * ((float)SourceLocationsCount) - 1); + SourceIndex = FMath::TruncToInt(RandomStream.FRand() * ((float)SourceLocationsCount) - 1); InstancePayload->VertIndex = SourceIndex; if(SourceIndex != -1) @@ -2372,10 +2292,10 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own else if(SourceType == VERTSURFACESOURCE_Surface) { int32 SectionCount = LODData.RenderSections.Num(); - int32 RandomSection = FMath::RoundToInt(FMath::SRand() * ((float)SectionCount-1)); + int32 RandomSection = FMath::RoundToInt(RandomStream.FRand() * ((float)SectionCount-1)); SourceIndex = LODData.RenderSections[RandomSection].BaseIndex + - (FMath::TruncToInt(FMath::SRand() * ((float)LODData.RenderSections[RandomSection].NumTriangles))*3); + (FMath::TruncToInt(RandomStream.FRand() * ((float)LODData.RenderSections[RandomSection].NumTriangles))*3); InstancePayload->VertIndex = SourceIndex; @@ -2506,10 +2426,7 @@ void UParticleModuleLocationSkelVertSurface::Spawn(FParticleEmitterInstance* Own { //We have the mesh oriented to the normal of the triangle it's on but this looks fugly as particles on each triangle are facing the same way. //The only valid orientation reference should be the normal. So add an additional random rotation around it. - int32 OldRandSeed = FMath::GetRandSeed(); - FMath::SRandInit((int32)((intptr_t)ParticleBase)); - SourceRotation = SourceRotation * FQuat(FVector::UpVector, FMath::SRand()*(PI*2.0f)); - FMath::SRandInit(OldRandSeed); + SourceRotation = SourceRotation * FQuat(FVector::UpVector, RandomStream.FRand()*(PI*2.0f)); } FVector Rot = SourceRotation.Euler(); @@ -2581,6 +2498,8 @@ void UParticleModuleLocationSkelVertSurface::Update(FParticleEmitterInstance* Ow const bool bMeshRotationActive = MeshRotationOffset > 0 && Owner->IsMeshRotationActive(); const FTransform& OwnerTM = Owner->Component->GetAsyncComponentToWorld(); + FRandomStream& RandomStream = GetRandomStream(Owner); + BEGIN_UPDATE_LOOP; { FModuleLocationVertSurfaceParticlePayload* ParticlePayload = (FModuleLocationVertSurfaceParticlePayload*)((uint8*)&Particle + Offset); @@ -2594,11 +2513,7 @@ void UParticleModuleLocationSkelVertSurface::Update(FParticleEmitterInstance* Ow { //We have the mesh oriented to the normal of the triangle it's on but this looks fugly as particles on each triangle are facing the same way. //The only valid orientation reference should be the normal. So add an additional random rotation around it. - - int32 OldRandSeed = FMath::GetRandSeed(); - FMath::SRandInit((int32)((intptr_t)ParticleBase)); - SourceRotation = SourceRotation * FQuat(FVector::UpVector, FMath::SRand()*(PI*2.0f)); - FMath::SRandInit(OldRandSeed); + SourceRotation = SourceRotation * FQuat(FVector::UpVector, RandomStream.FRand()*(PI*2.0f)); } FMeshRotationPayloadData* PayloadData = (FMeshRotationPayloadData*)((uint8*)&Particle + MeshRotationOffset); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp index 3cc78e995c7e..938b438abd0a 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Parameter.cpp @@ -137,7 +137,7 @@ bool UParticleModuleParameterDynamic::CanTickInAnyThread() void UParticleModuleParameterDynamic::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleParameterDynamic::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -178,6 +178,8 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in return; } + FRandomStream& RandomStream = GetRandomStream(Owner); + // int32 ParameterIndex = ParticleDynamicParameter_GetIndexFromFlag(UpdateFlags); @@ -201,7 +203,7 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue_UserSet(DynParam, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue_UserSet(DynParam, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -212,7 +214,7 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue(DynParam, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[ParameterIndex] = GetParameterValue(DynParam, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -230,8 +232,8 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -242,8 +244,8 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -262,9 +264,9 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -275,9 +277,9 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -296,10 +298,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[3] = GetParameterValue_UserSet(DynParam3, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue_UserSet(DynParam2, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[3] = GetParameterValue_UserSet(DynParam3, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -310,10 +312,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, NULL); - DynamicPayload.DynamicParameterValue[3] = GetParameterValue(DynParam3, Particle, Owner, NULL); + DynamicPayload.DynamicParameterValue[0] = GetParameterValue(DynParam0, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[1] = GetParameterValue(DynParam1, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[2] = GetParameterValue(DynParam2, Particle, Owner, &RandomStream); + DynamicPayload.DynamicParameterValue[3] = GetParameterValue(DynParam3, Particle, Owner, &RandomStream); } END_UPDATE_LOOP; } @@ -332,10 +334,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue_UserSet(DynParam0, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[0]; - DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue_UserSet(DynParam1, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[1]; - DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue_UserSet(DynParam2, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[2]; - DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue_UserSet(DynParam3, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[3]; + DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue_UserSet(DynParam0, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[0]; + DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue_UserSet(DynParam1, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[1]; + DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue_UserSet(DynParam2, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[2]; + DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue_UserSet(DynParam3, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[3]; } END_UPDATE_LOOP; } @@ -346,10 +348,10 @@ void UParticleModuleParameterDynamic::Update(FParticleEmitterInstance* Owner, in FEmitterDynamicParameterPayload& DynamicPayload = *((FEmitterDynamicParameterPayload*)(ParticleBase + CurrentOffset)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride)); FPlatformMisc::Prefetch(ParticleData, (ParticleIndices[i+1] * ParticleStride) + PLATFORM_CACHE_LINE_SIZE); - DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue(DynParam0, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[0]; - DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue(DynParam1, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[1]; - DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue(DynParam2, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[2]; - DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue(DynParam3, Particle, Owner, NULL) : DynamicPayload.DynamicParameterValue[3]; + DynamicPayload.DynamicParameterValue[0] = (UpdateFlags & EDPU_UPDATE_0) ? GetParameterValue(DynParam0, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[0]; + DynamicPayload.DynamicParameterValue[1] = (UpdateFlags & EDPU_UPDATE_1) ? GetParameterValue(DynParam1, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[1]; + DynamicPayload.DynamicParameterValue[2] = (UpdateFlags & EDPU_UPDATE_2) ? GetParameterValue(DynParam2, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[2]; + DynamicPayload.DynamicParameterValue[3] = (UpdateFlags & EDPU_UPDATE_3) ? GetParameterValue(DynParam3, Particle, Owner, &RandomStream) : DynamicPayload.DynamicParameterValue[3]; } END_UPDATE_LOOP; } @@ -575,22 +577,6 @@ UParticleModuleParameterDynamic_Seeded::UParticleModuleParameterDynamic_Seeded(c bRequiresLoopingNotification = true; } -void UParticleModuleParameterDynamic_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleParameterDynamic_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleParameterDynamic_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleParameterDynamic_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp index 0b89b12b1b20..79761cd38292 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Size.cpp @@ -84,7 +84,7 @@ void UParticleModuleSize::PostEditChangeProperty(FPropertyChangedEvent& Property void UParticleModuleSize::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleSize::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -93,7 +93,7 @@ void UParticleModuleSize::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, FVector Size = StartSize.GetValue(Owner->EmitterTime, Owner->Component, 0, InRandomStream); Particle.Size += Size; - AdjustParticleBaseSizeForUVFlipping(Size, Owner->CurrentLODLevel->RequiredModule->UVFlippingMode); + AdjustParticleBaseSizeForUVFlipping(Size, Owner->CurrentLODLevel->RequiredModule->UVFlippingMode, *InRandomStream); Particle.BaseSize += Size; } @@ -108,27 +108,11 @@ UParticleModuleSize_Seeded::UParticleModuleSize_Seeded(const FObjectInitializer& bRequiresLoopingNotification = true; } -void UParticleModuleSize_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleSize_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleSize_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleSize_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) { - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); + FParticleRandomSeedInstancePayload* Payload = Owner->GetModuleRandomSeedInstanceData(this); PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo); } } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp index 6a94923a6fad..779814fc189b 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleModules_Velocity.cpp @@ -73,7 +73,7 @@ void UParticleModuleVelocity::PostEditChangeProperty(FPropertyChangedEvent& Prop void UParticleModuleVelocity::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleVelocity::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -124,22 +124,6 @@ UParticleModuleVelocity_Seeded::UParticleModuleVelocity_Seeded(const FObjectInit bRequiresLoopingNotification = true; } -void UParticleModuleVelocity_Seeded::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) -{ - FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this)); - SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase); -} - -uint32 UParticleModuleVelocity_Seeded::RequiredBytesPerInstance() -{ - return RandomSeedInfo.GetInstancePayloadSize(); -} - -uint32 UParticleModuleVelocity_Seeded::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) -{ - return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo); -} - void UParticleModuleVelocity_Seeded::EmitterLoopingNotify(FParticleEmitterInstance* Owner) { if (RandomSeedInfo.bResetSeedOnEmitterLooping == true) @@ -415,7 +399,7 @@ void UParticleModuleVelocityCone::PostEditChangeProperty(FPropertyChangedEvent& void UParticleModuleVelocityCone::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) { - SpawnEx(Owner, Offset, SpawnTime, NULL, ParticleBase); + SpawnEx(Owner, Offset, SpawnTime, &GetRandomStream(Owner), ParticleBase); } void UParticleModuleVelocityCone::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) @@ -440,7 +424,7 @@ void UParticleModuleVelocityCone::SpawnEx(FParticleEmitterInstance* Owner, int32 // Calculate the default position (prior to the Direction vector being factored in) const float SpawnAngle = Angle.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); const float SpawnVelocity = Velocity.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream); - const float LatheAngle = FMath::SRand() * TwoPI; + const float LatheAngle = InRandomStream->FRand() * TwoPI; const FRotator DefaultDirectionRotater((int32)(SpawnAngle * ToRads * UUPerRad), (int32)(LatheAngle * UUPerRad), 0); const FRotationMatrix DefaultDirectionRotation(DefaultDirectionRotater); const FVector DefaultSpawnDirection = DefaultDirectionRotation.TransformVector(DefaultDirection); diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp index 9b51b1c89b29..0a3c5129c96e 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleTrail2EmitterInstance.cpp @@ -2223,7 +2223,8 @@ bool FParticleRibbonEmitterInstance::ResolveSourcePoint(int32 InTrailIdx, { case EPSSM_Random: { - Index = FMath::TruncToInt(FMath::FRand() * SourceEmitter->ActiveParticles); + FRandomStream& RandomStream = SourceModule->GetRandomStream(this); + Index = RandomStream.RandHelper(SourceEmitter->ActiveParticles); } break; case EPSSM_Sequential: diff --git a/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp b/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp index cdb4a35f354f..cfd66270d987 100644 --- a/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp +++ b/Engine/Source/Runtime/Engine/Private/Slate/SceneViewport.cpp @@ -198,7 +198,7 @@ void FSceneViewport::SetMouse( int32 X, int32 Y ) { const FVector2D NormalizedLocalMousePosition = FVector2D(X, Y) / GetSizeXY(); FVector2D AbsolutePos = CachedGeometry.LocalToAbsolute(NormalizedLocalMousePosition * CachedGeometry.GetLocalSize()); - FSlateApplication::Get().SetCursorPos( AbsolutePos ); + FSlateApplication::Get().SetCursorPos( AbsolutePos.RoundToVector() ); CachedCursorPos = FIntPoint(X, Y); } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNode.cpp b/Engine/Source/Runtime/Engine/Private/SoundNode.cpp index adda9f3c8b25..bcdc235c42fa 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNode.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNode.cpp @@ -4,6 +4,7 @@ #include "Sound/SoundNode.h" #include "EngineUtils.h" #include "Sound/SoundCue.h" +#include "Misc/App.h" /*----------------------------------------------------------------------------- USoundNode implementation. @@ -11,6 +12,7 @@ USoundNode::USoundNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + RandomStream.Initialize(FApp::bUseFixedSeed ? GetFName() : NAME_None); } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp index db4ff37ba221..68daa7dd1fc0 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeDelay.cpp @@ -31,7 +31,7 @@ void USoundNodeDelay::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT NodeW { *RequiresInitialization = false; - const float ActualDelay = FMath::Max(0.f, DelayMax + ( ( DelayMin - DelayMax ) * FMath::SRand() )); + const float ActualDelay = FMath::Max(0.f, DelayMax + ( ( DelayMin - DelayMax ) * RandomStream.FRand() )); if (ActualDelay > 0.0f && ParseParams.StartTime >= ActualDelay) { diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp index 70b670736373..40f7293fc8a5 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeEnveloper.cpp @@ -68,8 +68,8 @@ void USoundNodeEnveloper::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT N if( *RequiresInitialization ) { StartTime = ActiveSound.PlaybackTime - ParseParams.StartTime; - UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * FMath::SRand() ); - UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * FMath::SRand() ); + UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * RandomStream.FRand() ); + UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * RandomStream.FRand() ); LastLoopCount = -1; *RequiresInitialization = false; @@ -95,8 +95,8 @@ void USoundNodeEnveloper::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT N else if ( CurrentLoopCount != LastLoopCount ) { // randomize multipliers for the new repeat - UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * FMath::SRand() ); - UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * FMath::SRand() ); + UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * RandomStream.FRand() ); + UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * RandomStream.FRand() ); LastLoopCount = CurrentLoopCount; } } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp index 0fb4be3cea0a..991fa725b4b2 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeModulator.cpp @@ -24,8 +24,8 @@ void USoundNodeModulator::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT N if( *RequiresInitialization ) { - UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * FMath::SRand() ); - UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * FMath::SRand() ); + UsedVolumeModulation = VolumeMax + ( ( VolumeMin - VolumeMax ) * RandomStream.FRand() ); + UsedPitchModulation = PitchMax + ( ( PitchMin - PitchMax ) * RandomStream.FRand() ); *RequiresInitialization = 0; } diff --git a/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp b/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp index 113afe3153c0..08c5eacf97a4 100644 --- a/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp +++ b/Engine/Source/Runtime/Engine/Private/SoundNodeOscillator.cpp @@ -32,10 +32,10 @@ void USoundNodeOscillator::ParseNodes( FAudioDevice* AudioDevice, const UPTRINT if( *RequiresInitialization ) { - UsedAmplitude = AmplitudeMax + ( ( AmplitudeMin - AmplitudeMax ) * FMath::SRand() ); - UsedFrequency = FrequencyMax + ( ( FrequencyMin - FrequencyMax ) * FMath::SRand() ); - UsedOffset = OffsetMax + ( ( OffsetMin - OffsetMax ) * FMath::SRand() ); - UsedCenter = CenterMax + ( ( CenterMin - CenterMax ) * FMath::SRand() ); + UsedAmplitude = AmplitudeMax + ( ( AmplitudeMin - AmplitudeMax ) * RandomStream.FRand() ); + UsedFrequency = FrequencyMax + ( ( FrequencyMin - FrequencyMax ) * RandomStream.FRand() ); + UsedOffset = OffsetMax + ( ( OffsetMin - OffsetMax ) * RandomStream.FRand() ); + UsedCenter = CenterMax + ( ( CenterMin - CenterMax ) * RandomStream.FRand() ); *RequiresInitialization = 0; } diff --git a/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp b/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp index 2315bb4b3c9a..fb31233717f6 100644 --- a/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/StaticMesh.cpp @@ -6013,6 +6013,11 @@ void UStaticMesh::GenerateLodsInPackage() #endif // #if WITH_EDITOR +void UStaticMesh::AddSocket(UStaticMeshSocket* Socket) +{ + Sockets.AddUnique(Socket); +} + UStaticMeshSocket* UStaticMesh::FindSocket(FName InSocketName) const { if(InSocketName == NAME_None) @@ -6031,6 +6036,11 @@ UStaticMeshSocket* UStaticMesh::FindSocket(FName InSocketName) const return NULL; } +void UStaticMesh::RemoveSocket(UStaticMeshSocket* Socket) +{ + Sockets.Remove(Socket); +} + /*----------------------------------------------------------------------------- UStaticMeshSocket -----------------------------------------------------------------------------*/ diff --git a/Engine/Source/Runtime/Engine/Private/World.cpp b/Engine/Source/Runtime/Engine/Private/World.cpp index 624f03113406..582390c6fa9c 100644 --- a/Engine/Source/Runtime/Engine/Private/World.cpp +++ b/Engine/Source/Runtime/Engine/Private/World.cpp @@ -3962,13 +3962,22 @@ bool UWorld::IsNavigationRebuilt() const return GetNavigationSystem() == NULL || GetNavigationSystem()->IsNavigationBuilt(GetWorldSettings()); } -void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* NewWorld) +void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* NewWorld, bool bResetCleanedUpFlag) { UE_LOG(LogWorld, Log, TEXT("UWorld::CleanupWorld for %s, bSessionEnded=%s, bCleanupResources=%s"), *GetName(), bSessionEnded ? TEXT("true") : TEXT("false"), bCleanupResources ? TEXT("true") : TEXT("false")); + TArray WorldsToResetCleanedUpFlag; + check(IsVisibilityRequestPending() == false); + + check(!bCleanedUpWorld); bCleanedUpWorld = true; + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(this); + } + // Wait on current physics scenes if they are processing if(FPhysScene* CurrPhysicsScene = GetPhysicsScene()) { @@ -4062,7 +4071,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne UWorld* World = CastChecked(GetLevel(LevelIndex)->GetOuter()); if (!World->bCleanedUpWorld) { - World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld); + World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld, false); + + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(World); + } } } @@ -4073,7 +4087,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne UWorld* World = CastChecked(Level->GetOuter()); if (!World->bCleanedUpWorld) { - World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld); + World->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld, false); + + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(World); + } } } } @@ -4092,7 +4111,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne UWorld* const LevelWorld = CastChecked(Level->GetOuter()); if (!LevelWorld->bCleanedUpWorld) { - LevelWorld->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld); + LevelWorld->CleanupWorld(bSessionEnded, bCleanupResources, NewWorld, false); + + if (bResetCleanedUpFlag) + { + WorldsToResetCleanedUpFlag.Add(LevelWorld); + } } } } @@ -4100,6 +4124,12 @@ void UWorld::CleanupWorld(bool bSessionEnded, bool bCleanupResources, UWorld* Ne PSCPool.Cleanup(); FWorldDelegates::OnPostWorldCleanup.Broadcast(this, bSessionEnded, bCleanupResources); + + for (UWorld* WorldToResetCleanedUpFlag: WorldsToResetCleanedUpFlag) + { + check(WorldToResetCleanedUpFlag->bCleanedUpWorld); + WorldToResetCleanedUpFlag->bCleanedUpWorld = false; + } } UGameViewportClient* UWorld::GetGameViewport() const diff --git a/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h b/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h index b16c9b166225..4906636852a1 100644 --- a/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h +++ b/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h @@ -275,7 +275,7 @@ public: * which has its Notify method called and passed to the animation. */ USTRUCT(BlueprintType) -struct FAnimNotifyEvent : public FAnimLinkableElement +struct ENGINE_VTABLE FAnimNotifyEvent : public FAnimLinkableElement { GENERATED_USTRUCT_BODY() diff --git a/Engine/Source/Runtime/Engine/Public/Audio.h b/Engine/Source/Runtime/Engine/Public/Audio.h index 2191e4e9087e..9f309675c3f4 100644 --- a/Engine/Source/Runtime/Engine/Public/Audio.h +++ b/Engine/Source/Runtime/Engine/Public/Audio.h @@ -543,7 +543,7 @@ inline uint32 GetTypeHash(FWaveInstance* A) { return A->TypeHash; } FSoundBuffer. -----------------------------------------------------------------------------*/ -class FSoundBuffer +class ENGINE_VTABLE FSoundBuffer { public: FSoundBuffer(class FAudioDevice * InAudioDevice) @@ -619,7 +619,7 @@ public: FSoundSource. -----------------------------------------------------------------------------*/ -class FSoundSource +class ENGINE_VTABLE FSoundSource { public: /** Constructor */ diff --git a/Engine/Source/Runtime/Engine/Public/ContentStreaming.h b/Engine/Source/Runtime/Engine/Public/ContentStreaming.h index 33d50667a6b9..fee86bfba29f 100644 --- a/Engine/Source/Runtime/Engine/Public/ContentStreaming.h +++ b/Engine/Source/Runtime/Engine/Public/ContentStreaming.h @@ -83,7 +83,7 @@ struct FStreamingViewInfo /** * Pure virtual base class of a streaming manager. */ -struct IStreamingManager +struct ENGINE_VTABLE IStreamingManager { IStreamingManager() : NumWantingResources(0) @@ -447,7 +447,7 @@ struct IAudioStreamingManager : public IStreamingManager * Streaming manager collection, routing function calls to streaming managers that have been added * via AddStreamingManager. */ -struct FStreamingManagerCollection : public IStreamingManager +struct ENGINE_VTABLE FStreamingManagerCollection : public IStreamingManager { /** Default constructor, initializing all member variables. */ ENGINE_API FStreamingManagerCollection(); diff --git a/Engine/Source/Runtime/Engine/Public/DebugRenderSceneProxy.h b/Engine/Source/Runtime/Engine/Public/DebugRenderSceneProxy.h index 65a6179d6cea..6e8843592efe 100644 --- a/Engine/Source/Runtime/Engine/Public/DebugRenderSceneProxy.h +++ b/Engine/Source/Runtime/Engine/Public/DebugRenderSceneProxy.h @@ -19,7 +19,7 @@ class UPrimitiveComponent; DECLARE_DELEGATE_TwoParams(FDebugDrawDelegate, class UCanvas*, class APlayerController*); -class FDebugRenderSceneProxy : public FPrimitiveSceneProxy +class ENGINE_VTABLE FDebugRenderSceneProxy : public FPrimitiveSceneProxy { public: ENGINE_API SIZE_T GetTypeHash() const override; @@ -249,7 +249,7 @@ public: }; -struct FDebugDrawDelegateHelper +struct ENGINE_VTABLE FDebugDrawDelegateHelper { FDebugDrawDelegateHelper() : State(UndefinedState) diff --git a/Engine/Source/Runtime/Engine/Public/DrawDebugHelpers.h b/Engine/Source/Runtime/Engine/Public/DrawDebugHelpers.h index db5aa06950e2..66ba92d9a2a5 100644 --- a/Engine/Source/Runtime/Engine/Public/DrawDebugHelpers.h +++ b/Engine/Source/Runtime/Engine/Public/DrawDebugHelpers.h @@ -153,9 +153,9 @@ FORCEINLINE void DrawDebugSphere(const UWorld* InWorld, FVector const& Center, f FORCEINLINE void DrawDebugCylinder(const UWorld* InWorld, FVector const& Start, FVector const& End, float Radius, int32 Segments, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f) {} FORCEINLINE void DrawDebugCone(const UWorld* InWorld, FVector const& Origin, FVector const& Direction, float Length, float AngleWidth, float AngleHeight, int32 NumSides, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f) {} FORCEINLINE void DrawDebugAltCone(const UWorld* InWorld, FVector const& Origin, FRotator const& Rotation, float Length, float AngleWidth, float AngleHeight, FColor const& DrawColor, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f) {} -FORCEINLINE void DrawDebugString(const UWorld* InWorld, FVector const& TextLocation, const FString& Text, class AActor* TestBaseActor = NULL, FColor const& TextColor = FColor::White, float Duration = -1.000000, bool bDrawShadow = false) {} +FORCEINLINE void DrawDebugString(const UWorld* InWorld, FVector const& TextLocation, const FString& Text, class AActor* TestBaseActor = NULL, FColor const& TextColor = FColor::White, float Duration = -1.000000, bool bDrawShadow = false, float FontScale = 1.f) {} FORCEINLINE void DrawDebugFrustum(const UWorld* InWorld, const FMatrix& FrustumToWorld, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f) {} -FORCEINLINE void DrawCircle(const UWorld* InWorld, const FVector& Base, const FVector& X, const FVector& Y, const FColor& Color, float Radius, int32 NumSides, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness) {} +FORCEINLINE void DrawCircle(const UWorld* InWorld, const FVector& Base, const FVector& X, const FVector& Y, const FColor& Color, float Radius, int32 NumSides, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0) {} FORCEINLINE void DrawDebugCapsule(const UWorld* InWorld, FVector const& Center, float HalfHeight, float Radius, const FQuat& Rotation, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0) {} FORCEINLINE void DrawDebugCamera(const UWorld* InWorld, FVector const& Location, FRotator const& Rotation, float FOVDeg, float Scale = 1.f, FColor const& Color = FColor::White, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0) {} FORCEINLINE void FlushDebugStrings(const UWorld* InWorld) {} diff --git a/Engine/Source/Runtime/Engine/Public/DynamicMeshBuilder.h b/Engine/Source/Runtime/Engine/Public/DynamicMeshBuilder.h index 6a0ff685ebc0..41cf62a43b5f 100644 --- a/Engine/Source/Runtime/Engine/Public/DynamicMeshBuilder.h +++ b/Engine/Source/Runtime/Engine/Public/DynamicMeshBuilder.h @@ -101,7 +101,7 @@ struct FDynamicMeshVertex FColor Color; }; -class FMeshBuilderOneFrameResources : public FOneFrameResource +class ENGINE_VTABLE FMeshBuilderOneFrameResources : public FOneFrameResource { public: class FPooledDynamicMeshVertexBuffer* VertexBuffer = nullptr; @@ -217,4 +217,4 @@ public: virtual void InitRHI() override; void UpdateRHI(); -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/Engine/Public/EngineSharedPCH.h b/Engine/Source/Runtime/Engine/Public/EngineSharedPCH.h index 68c2dbeeac5c..77bd324c136b 100644 --- a/Engine/Source/Runtime/Engine/Public/EngineSharedPCH.h +++ b/Engine/Source/Runtime/Engine/Public/EngineSharedPCH.h @@ -398,7 +398,6 @@ #include "Widgets/Input/IVirtualKeyboardEntry.h" #include "Widgets/Layout/SBorder.h" #include "Framework/Text/TextLayout.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Runtime/Engine/Public/FXSystem.h b/Engine/Source/Runtime/Engine/Public/FXSystem.h index c62914f9d940..cee1ac93b3b0 100644 --- a/Engine/Source/Runtime/Engine/Public/FXSystem.h +++ b/Engine/Source/Runtime/Engine/Public/FXSystem.h @@ -120,7 +120,7 @@ DECLARE_DELEGATE_RetVal_TwoParams(FFXSystemInterface*, FCreateCustomFXSystemDele /** * The interface to an effects system. */ -class FFXSystemInterface +class ENGINE_VTABLE FFXSystemInterface { public: diff --git a/Engine/Source/Runtime/Engine/Public/MaterialShared.h b/Engine/Source/Runtime/Engine/Public/MaterialShared.h index 5dff2da1aca6..7fc2944124d8 100644 --- a/Engine/Source/Runtime/Engine/Public/MaterialShared.h +++ b/Engine/Source/Runtime/Engine/Public/MaterialShared.h @@ -1190,7 +1190,7 @@ extern ENGINE_API bool CanConnectMaterialValueTypes(uint32 InputType, uint32 Out * Stores a cached shader map, and other transient output from a compile, which is necessary with async shader compiling * (when a material finishes async compilation, the shader map and compile errors need to be stored somewhere) */ -class FMaterial +class ENGINE_VTABLE FMaterial { public: @@ -1719,7 +1719,7 @@ class USubsurfaceProfile; /** * A material render proxy used by the renderer. */ -class FMaterialRenderProxy : public FRenderResource +class ENGINE_VTABLE FMaterialRenderProxy : public FRenderResource { public: @@ -1837,7 +1837,7 @@ private: /** * An material render proxy which overrides the material's Color vector parameter. */ -class FColoredMaterialRenderProxy : public FMaterialRenderProxy +class ENGINE_VTABLE FColoredMaterialRenderProxy : public FMaterialRenderProxy { public: @@ -2198,7 +2198,7 @@ public: /** * Material property to attribute data mappings */ -class FMaterialAttributeDefinitionMap +class ENGINE_VTABLE FMaterialAttributeDefinitionMap { public: FMaterialAttributeDefinitionMap() diff --git a/Engine/Source/Runtime/Engine/Public/Model.h b/Engine/Source/Runtime/Engine/Public/Model.h index c4e6f757b042..8d97d97fc207 100644 --- a/Engine/Source/Runtime/Engine/Public/Model.h +++ b/Engine/Source/Runtime/Engine/Public/Model.h @@ -608,7 +608,7 @@ private: /** * A set of BSP nodes which have the same material and relevant lights. */ -class FModelElement +class ENGINE_VTABLE FModelElement { public: diff --git a/Engine/Source/Runtime/Engine/Public/Net/RepLayout.h b/Engine/Source/Runtime/Engine/Public/Net/RepLayout.h index dc55bb2a467d..4ccf5bbc0e6a 100644 --- a/Engine/Source/Runtime/Engine/Public/Net/RepLayout.h +++ b/Engine/Source/Runtime/Engine/Public/Net/RepLayout.h @@ -1069,7 +1069,7 @@ enum class ERepLayoutState * explicit changelist is required. As each Handle is read, a Layout Command is applied * that serializes the data from the network bunch and applies it to an object. */ -class FRepLayout : public FGCObject, public TSharedFromThis +class ENGINE_VTABLE FRepLayout : public FGCObject, public TSharedFromThis { private: @@ -1924,4 +1924,4 @@ private: /** Shared comparison to default state for multicast rpc */ TBitArray<> SharedInfoRPCParentsChanged; -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h b/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h index 7ed5c05b79c8..e85a4b39b81b 100644 --- a/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h +++ b/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h @@ -438,6 +438,8 @@ public: uint32 GetModuleDataOffset(UParticleModule* Module); /** Get pointer to emitter instance payload data for a particular module */ uint8* GetModuleInstanceData(UParticleModule* Module); + /** Get pointer to emitter instance random seed payload data for a particular module */ + FParticleRandomSeedInstancePayload* GetModuleRandomSeedInstanceData(UParticleModule* Module); virtual uint8* GetTypeDataModuleInstanceData(); virtual uint32 CalculateParticleStride(uint32 ParticleSize); virtual void ResetBurstList(); diff --git a/Engine/Source/Runtime/Engine/Public/PrimitiveUniformShaderParameters.h b/Engine/Source/Runtime/Engine/Public/PrimitiveUniformShaderParameters.h index 56b4469773fc..179b221347e0 100644 --- a/Engine/Source/Runtime/Engine/Public/PrimitiveUniformShaderParameters.h +++ b/Engine/Source/Runtime/Engine/Public/PrimitiveUniformShaderParameters.h @@ -225,7 +225,7 @@ struct FPrimitiveSceneShaderData ENGINE_API void Setup(const FPrimitiveUniformShaderParameters& PrimitiveUniformShaderParameters); }; -class FSinglePrimitiveStructuredBuffer : public FRenderResource +class ENGINE_VTABLE FSinglePrimitiveStructuredBuffer : public FRenderResource { public: diff --git a/Engine/Source/Runtime/Engine/Public/RawIndexBuffer.h b/Engine/Source/Runtime/Engine/Public/RawIndexBuffer.h index 48418f4e08c3..ae8142bf70d3 100644 --- a/Engine/Source/Runtime/Engine/Public/RawIndexBuffer.h +++ b/Engine/Source/Runtime/Engine/Public/RawIndexBuffer.h @@ -118,7 +118,7 @@ private: bool b32Bit; }; -class FRawStaticIndexBuffer : public FIndexBuffer +class ENGINE_VTABLE FRawStaticIndexBuffer : public FIndexBuffer { public: /** diff --git a/Engine/Source/Runtime/Engine/Public/SceneInterface.h b/Engine/Source/Runtime/Engine/Public/SceneInterface.h index 1909c4bde5cc..57ccc12fe8d0 100644 --- a/Engine/Source/Runtime/Engine/Public/SceneInterface.h +++ b/Engine/Source/Runtime/Engine/Public/SceneInterface.h @@ -38,7 +38,7 @@ enum EBasePassDrawListType * An interface to the private scene manager implementation of a scene. Use GetRendererModule().AllocateScene to create. * The scene */ -class FSceneInterface +class ENGINE_VTABLE FSceneInterface { public: FSceneInterface(ERHIFeatureLevel::Type InFeatureLevel) diff --git a/Engine/Source/Runtime/Engine/Public/SceneTypes.h b/Engine/Source/Runtime/Engine/Public/SceneTypes.h index bd3297a35845..6868496f5d79 100644 --- a/Engine/Source/Runtime/Engine/Public/SceneTypes.h +++ b/Engine/Source/Runtime/Engine/Public/SceneTypes.h @@ -65,7 +65,7 @@ public: * Class used to reference an FSceneViewStateInterface that allows destruction and recreation of all FSceneViewStateInterface's when needed. * This is used to support reloading the renderer module on the fly. */ -class FSceneViewStateReference +class ENGINE_VTABLE FSceneViewStateReference { public: FSceneViewStateReference() : diff --git a/Engine/Source/Runtime/Engine/Public/StaticLighting.h b/Engine/Source/Runtime/Engine/Public/StaticLighting.h index 48b5260dcce0..44fc636994b7 100644 --- a/Engine/Source/Runtime/Engine/Public/StaticLighting.h +++ b/Engine/Source/Runtime/Engine/Public/StaticLighting.h @@ -371,7 +371,7 @@ FORCEINLINE bool FStaticLightingMesh::ShouldCastShadow(ULightComponent* Light,co } /** A mapping between world-space surfaces and static lighting cache textures. */ -class FStaticLightingTextureMapping : public FStaticLightingMapping +class ENGINE_VTABLE FStaticLightingTextureMapping : public FStaticLightingMapping { public: @@ -431,7 +431,7 @@ public: * Represents an object which will use the global volumetric lightmap. * Hack: currently represented as a texture mapping for Lightmass GI solver surface caching */ -class FStaticLightingGlobalVolumeMapping : public FStaticLightingTextureMapping +class ENGINE_VTABLE FStaticLightingGlobalVolumeMapping : public FStaticLightingTextureMapping { public: diff --git a/Engine/Source/Runtime/Engine/Public/UnrealClient.h b/Engine/Source/Runtime/Engine/Public/UnrealClient.h index 32e082ae488c..70ebd6186e1f 100644 --- a/Engine/Source/Runtime/Engine/Public/UnrealClient.h +++ b/Engine/Source/Runtime/Engine/Public/UnrealClient.h @@ -24,7 +24,7 @@ class UModel; /** * A render target. */ -class FRenderTarget +class ENGINE_VTABLE FRenderTarget { public: @@ -297,7 +297,7 @@ struct FStatHitchesData * Encapsulates the I/O of a viewport. * The viewport display is implemented using the platform independent RHI. */ -class FViewport : public FRenderTarget, protected FRenderResource +class ENGINE_VTABLE FViewport : public FRenderTarget, protected FRenderResource { public: /** delegate type for viewport resize events ( Params: FViewport* Viewport, uint32 ) */ @@ -1070,7 +1070,7 @@ extern ENGINE_API class FCommonViewportClient* GStatProcessingViewportClient; * Common functionality for game and editor viewport clients */ -class FCommonViewportClient : public FViewportClient +class ENGINE_VTABLE FCommonViewportClient : public FViewportClient { public: FCommonViewportClient() diff --git a/Engine/Source/Runtime/Experimental/Chaos/Private/Chaos/BoundingVolumeHierarchy.cpp b/Engine/Source/Runtime/Experimental/Chaos/Private/Chaos/BoundingVolumeHierarchy.cpp index 5ebfcb871720..4e33e55c10c6 100644 --- a/Engine/Source/Runtime/Experimental/Chaos/Private/Chaos/BoundingVolumeHierarchy.cpp +++ b/Engine/Source/Runtime/Experimental/Chaos/Private/Chaos/BoundingVolumeHierarchy.cpp @@ -429,7 +429,7 @@ int32 TBoundingVolumeHierarchy::GenerateNextLevel(const TVec return MinElem; } -template class Chaos::TBoundingVolumeHierarchy*>, float, 3>; -template class Chaos::TBoundingVolumeHierarchy, float, 3>; -template class Chaos::TBoundingVolumeHierarchy, float, 3>; -template class Chaos::TBoundingVolumeHierarchy, float, 3>; \ No newline at end of file +template class CHAOS_API Chaos::TBoundingVolumeHierarchy*>, float, 3>; +template class CHAOS_API Chaos::TBoundingVolumeHierarchy, float, 3>; +template class CHAOS_API Chaos::TBoundingVolumeHierarchy, float, 3>; +template class CHAOS_API Chaos::TBoundingVolumeHierarchy, float, 3>; diff --git a/Engine/Source/Runtime/Foliage/Private/InstancedFoliage.cpp b/Engine/Source/Runtime/Foliage/Private/InstancedFoliage.cpp index 73c88eec660e..effeeee7570a 100644 --- a/Engine/Source/Runtime/Foliage/Private/InstancedFoliage.cpp +++ b/Engine/Source/Runtime/Foliage/Private/InstancedFoliage.cpp @@ -615,6 +615,16 @@ void UFoliageType::Serialize(FArchive& Ar) #endif// WITH_EDITORONLY_DATA } +void UFoliageType::PostLoad() +{ + Super::PostLoad(); + + if (!IsTemplate()) + { + BodyInstance.FixupData(this); + } +} + bool UFoliageType::IsNotAssetOrBlueprint() const { return IsAsset() == false && Cast(GetClass()->ClassGeneratedBy) == nullptr; @@ -2548,6 +2558,8 @@ void AInstancedFoliageActor::MoveInstancesToNewComponent(UPrimitiveComponent* In for (auto& Pair : FoliageInfos) { + InstancesToMove.Reset(); + FFoliageInfo& Info = *Pair.Value; InstancesToMove = Info.GetInstancesOverlappingBox(InBoxWithInstancesToMove); @@ -2558,8 +2570,11 @@ void AInstancedFoliageActor::MoveInstancesToNewComponent(UPrimitiveComponent* In // Add the foliage to the new level for (int32 InstanceIndex : InstancesToMove) { - FFoliageInstance NewInstance = Info.Instances[InstanceIndex]; - TargetMeshInfo->AddInstance(TargetIFA, TargetFoliageType, NewInstance, InNewComponent); + if (Info.Instances.IsValidIndex(InstanceIndex)) + { + FFoliageInstance NewInstance = Info.Instances[InstanceIndex]; + TargetMeshInfo->AddInstance(TargetIFA, TargetFoliageType, NewInstance, InNewComponent); + } } TargetMeshInfo->Refresh(TargetIFA, true, true); @@ -3902,6 +3917,12 @@ bool AInstancedFoliageActor::FoliageTrace(const UWorld* InWorld, FHitResult& Out } } } + + // The foliage we are snapping on doesn't have a valid base + if (!OutHit.Component.IsValid()) + { + continue; + } } return bInsideProceduralVolumeOrArentUsingOne; diff --git a/Engine/Source/Runtime/Foliage/Public/FoliageType.h b/Engine/Source/Runtime/Foliage/Public/FoliageType.h index 6ba6f24514e4..2bced70524d5 100644 --- a/Engine/Source/Runtime/Foliage/Public/FoliageType.h +++ b/Engine/Source/Runtime/Foliage/Public/FoliageType.h @@ -88,6 +88,7 @@ class UFoliageType : public UObject virtual UObject* GetSource() const PURE_VIRTUAL(UFoliageType::GetSource, return nullptr; ); virtual void Serialize(FArchive& Ar) override; + virtual void PostLoad() override; virtual bool IsNotAssetOrBlueprint() const; diff --git a/Engine/Source/Runtime/HeadMountedDisplay/Public/StereoLayerManager.h b/Engine/Source/Runtime/HeadMountedDisplay/Public/StereoLayerManager.h index 466c2917af20..abc87501bbd3 100644 --- a/Engine/Source/Runtime/HeadMountedDisplay/Public/StereoLayerManager.h +++ b/Engine/Source/Runtime/HeadMountedDisplay/Public/StereoLayerManager.h @@ -166,6 +166,6 @@ protected: {} }; -bool GetLayerDescMember(IStereoLayers::FLayerDesc& Layer, IStereoLayers::FLayerDesc& OutLayerDesc); -void SetLayerDescMember(IStereoLayers::FLayerDesc& OutLayer, const IStereoLayers::FLayerDesc& InLayerDesc); -void MarkLayerTextureForUpdate(IStereoLayers::FLayerDesc& Layer); +HEADMOUNTEDDISPLAY_API bool GetLayerDescMember(IStereoLayers::FLayerDesc& Layer, IStereoLayers::FLayerDesc& OutLayerDesc); +HEADMOUNTEDDISPLAY_API void SetLayerDescMember(IStereoLayers::FLayerDesc& OutLayer, const IStereoLayers::FLayerDesc& InLayerDesc); +HEADMOUNTEDDISPLAY_API void MarkLayerTextureForUpdate(IStereoLayers::FLayerDesc& Layer); diff --git a/Engine/Source/Runtime/ImageWriteQueue/Public/ImageWriteTask.h b/Engine/Source/Runtime/ImageWriteQueue/Public/ImageWriteTask.h index ac4abf2b888d..a15d72e9d495 100644 --- a/Engine/Source/Runtime/ImageWriteQueue/Public/ImageWriteTask.h +++ b/Engine/Source/Runtime/ImageWriteQueue/Public/ImageWriteTask.h @@ -37,7 +37,7 @@ public: virtual void OnAbandoned() = 0; }; -class FImageWriteTask +class IMAGEWRITEQUEUE_VTABLE FImageWriteTask : public IImageWriteTaskBase { public: @@ -241,4 +241,4 @@ struct TAsyncAlphaWrite Pixel.A = Alpha; } } -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/InteractiveToolsFramework.Build.cs b/Engine/Source/Runtime/InteractiveToolsFramework/InteractiveToolsFramework.Build.cs new file mode 100644 index 000000000000..1d61befe0daf --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/InteractiveToolsFramework.Build.cs @@ -0,0 +1,55 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class InteractiveToolsFramework : ModuleRules +{ + public InteractiveToolsFramework(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "InputCore", + "ApplicationCore", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Engine" + //"Slate", + //"SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/AnyButtonInputBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/AnyButtonInputBehavior.cpp new file mode 100644 index 000000000000..58bce6a55554 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/AnyButtonInputBehavior.cpp @@ -0,0 +1,112 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseBehaviors/AnyButtonInputBehavior.h" + + + +UAnyButtonInputBehavior::UAnyButtonInputBehavior() +{ + ButtonNumber = 0; +} + + +EInputDevices UAnyButtonInputBehavior::GetSupportedDevices() +{ + return EInputDevices::Mouse; +} + + +bool UAnyButtonInputBehavior::IsPressed(const FInputDeviceState& input) +{ + if (input.IsFromDevice(EInputDevices::Mouse)) + { + ActiveDevice = EInputDevices::Mouse; + return GetMouseButtonState(input).bPressed; + } + else if (input.IsFromDevice(EInputDevices::TabletFingers)) + { + ActiveDevice = EInputDevices::TabletFingers; + //return input.TouchPressed; // not implemented yet + return false; + } + return false; +} + +bool UAnyButtonInputBehavior::IsDown(const FInputDeviceState& input) +{ + if (input.IsFromDevice(EInputDevices::Mouse)) + { + ActiveDevice = EInputDevices::Mouse; + return GetMouseButtonState(input).bDown; + } + return false; +} + +bool UAnyButtonInputBehavior::IsReleased(const FInputDeviceState& input) +{ + if (input.IsFromDevice(EInputDevices::Mouse)) + { + ActiveDevice = EInputDevices::Mouse; + return GetMouseButtonState(input).bReleased; + } + return false; +} + +FVector2D UAnyButtonInputBehavior::GetClickPoint(const FInputDeviceState& input) +{ + if (input.IsFromDevice(EInputDevices::Mouse)) + { + ActiveDevice = EInputDevices::Mouse; + return input.Mouse.Position2D; + } + return FVector2D::ZeroVector; +} + + +FRay UAnyButtonInputBehavior::GetWorldRay(const FInputDeviceState& input) +{ + if (input.IsFromDevice(EInputDevices::Mouse)) + { + ActiveDevice = EInputDevices::Mouse; + return input.Mouse.WorldRay; + } + return FRay(FVector::ZeroVector, FVector(0, 0, 1), true); + +} + + +FInputDeviceRay UAnyButtonInputBehavior::GetDeviceRay(const FInputDeviceState& input) +{ + if (input.IsFromDevice(EInputDevices::Mouse)) + { + ActiveDevice = EInputDevices::Mouse; + return FInputDeviceRay(input.Mouse.WorldRay, input.Mouse.Position2D); + } + return FInputDeviceRay(FRay(FVector::ZeroVector, FVector(0, 0, 1), true)); + +} + + +EInputDevices UAnyButtonInputBehavior::GetActiveDevice() const +{ + return ActiveDevice; +} + + + + +FDeviceButtonState UAnyButtonInputBehavior::GetMouseButtonState(const FInputDeviceState& input) +{ + if (ButtonNumber == 2) + { + return input.Mouse.Right; + } + else if (ButtonNumber == 1) + { + return input.Mouse.Middle; + } + else + { + return input.Mouse.Left; + } +} \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/ClickDragBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/ClickDragBehavior.cpp new file mode 100644 index 000000000000..97667e90a17e --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/ClickDragBehavior.cpp @@ -0,0 +1,77 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseBehaviors/ClickDragBehavior.h" + + + +UClickDragInputBehavior::UClickDragInputBehavior() +{ +} + + +void UClickDragInputBehavior::Initialize(IClickDragBehaviorTarget* TargetIn) +{ + check(TargetIn != nullptr); + this->Target = TargetIn; +} + + +FInputCaptureRequest UClickDragInputBehavior::WantsCapture(const FInputDeviceState& Input) +{ + if (IsPressed(Input) && (ModifierCheckFunc == nullptr || ModifierCheckFunc(Input)) ) + { + if ( Target->CanBeginClickDragSequence(GetDeviceRay(Input)) ) + { + return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any); + } + } + return FInputCaptureRequest::Ignore(); +} + + +FInputCaptureUpdate UClickDragInputBehavior::BeginCapture(const FInputDeviceState& Input, EInputCaptureSide Side) +{ + Modifiers.UpdateModifiers(Input, Target); + OnClickPress(Input, Side); + return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any); +} + + +FInputCaptureUpdate UClickDragInputBehavior::UpdateCapture(const FInputDeviceState& Input, const FInputCaptureData& Data) +{ + Modifiers.UpdateModifiers(Input, Target); + + if (IsReleased(Input)) + { + OnClickRelease(Input, Data); + return FInputCaptureUpdate::End(); + } + else + { + OnClickDrag(Input, Data); + return FInputCaptureUpdate::Continue(); + } +} + + +void UClickDragInputBehavior::ForceEndCapture(const FInputCaptureData& Data) +{ + Target->OnTerminateDragSequence(); +} + + + +void UClickDragInputBehavior::OnClickPress(const FInputDeviceState& Input, EInputCaptureSide Side) +{ + Target->OnClickPress(GetDeviceRay(Input)); +} + +void UClickDragInputBehavior::OnClickDrag(const FInputDeviceState& Input, const FInputCaptureData& Data) +{ + Target->OnClickDrag(GetDeviceRay(Input)); +} + +void UClickDragInputBehavior::OnClickRelease(const FInputDeviceState& Input, const FInputCaptureData& Data) +{ + Target->OnClickRelease(GetDeviceRay(Input)); +} diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/KeyAsModifierInputBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/KeyAsModifierInputBehavior.cpp new file mode 100644 index 000000000000..24333d779c1e --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/KeyAsModifierInputBehavior.cpp @@ -0,0 +1,56 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseBehaviors/KeyAsModifierInputBehavior.h" + +UKeyAsModifierInputBehavior::UKeyAsModifierInputBehavior() +{ +} + +void UKeyAsModifierInputBehavior::Initialize(IModifierToggleBehaviorTarget* TargetIn, int ModifierID, const FKey& ModifierKeyIn) +{ + this->Target = TargetIn; + ModifierKey = ModifierKeyIn; + FKey TempKey = ModifierKeyIn; + Modifiers.RegisterModifier(ModifierID, [TempKey](const FInputDeviceState& Input) + { + return Input.Keyboard.ActiveKey.Button == TempKey; + }); +} + +FInputCaptureRequest UKeyAsModifierInputBehavior::WantsCapture(const FInputDeviceState& Input) +{ + if ((ModifierCheckFunc == nullptr || ModifierCheckFunc(Input))) + { + if (Input.Keyboard.ActiveKey.Button == ModifierKey && Input.Keyboard.ActiveKey.bPressed) + { + return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any); + } + } + return FInputCaptureRequest::Ignore(); +} + + +FInputCaptureUpdate UKeyAsModifierInputBehavior::BeginCapture(const FInputDeviceState& Input, EInputCaptureSide Side) +{ + Modifiers.UpdateModifiers(Input, Target); + return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any); +} + + +FInputCaptureUpdate UKeyAsModifierInputBehavior::UpdateCapture(const FInputDeviceState& Input, const FInputCaptureData& Data) +{ + Modifiers.UpdateModifiers(Input, Target); + + if (Input.Keyboard.ActiveKey.bReleased) + { + return FInputCaptureUpdate::End(); + } + + return FInputCaptureUpdate::Continue(); +} + + +void UKeyAsModifierInputBehavior::ForceEndCapture(const FInputCaptureData& Data) +{ +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/MouseHoverBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/MouseHoverBehavior.cpp new file mode 100644 index 000000000000..73e86842adb4 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/MouseHoverBehavior.cpp @@ -0,0 +1,39 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseBehaviors/MouseHoverBehavior.h" + + +UMouseHoverBehavior::UMouseHoverBehavior() +{ + Target = nullptr; +} + +EInputDevices UMouseHoverBehavior::GetSupportedDevices() +{ + return EInputDevices::Mouse; +} + +void UMouseHoverBehavior::Initialize(IHoverBehaviorTarget* TargetIn) +{ + this->Target = TargetIn; +} + +bool UMouseHoverBehavior::WantsHoverEvents() +{ + return true; +} + +void UMouseHoverBehavior::UpdateHover(const FInputDeviceState& input) +{ + if (Target != nullptr) + { + Modifiers.UpdateModifiers(input, Target); + + Target->OnUpdateHover( FInputDeviceRay(input.Mouse.WorldRay, input.Mouse.Position2D) ); + } +} + +void UMouseHoverBehavior::EndHover(const FInputDeviceState& input) +{ +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/MultiClickSequenceInputBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/MultiClickSequenceInputBehavior.cpp new file mode 100644 index 000000000000..c3d77d2aa65b --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/MultiClickSequenceInputBehavior.cpp @@ -0,0 +1,102 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseBehaviors/MultiClickSequenceInputBehavior.h" + + + +UMultiClickSequenceInputBehavior::UMultiClickSequenceInputBehavior() +{ + bInActiveSequence = false; +} + + +void UMultiClickSequenceInputBehavior::Initialize(IClickSequenceBehaviorTarget* TargetIn) +{ + this->Target = TargetIn; + bInActiveSequence = false; +} + + +FInputCaptureRequest UMultiClickSequenceInputBehavior::WantsCapture(const FInputDeviceState& input) +{ + check(bInActiveSequence == false); // should not happen... + bInActiveSequence = false; + + if ( IsPressed(input) && (ModifierCheckFunc == nullptr || ModifierCheckFunc(input) ) ) + { + if ( Target->CanBeginClickSequence(GetDeviceRay(input)) ) + { + return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any); + } + } + return FInputCaptureRequest::Ignore(); +} + + +FInputCaptureUpdate UMultiClickSequenceInputBehavior::BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide) +{ + Modifiers.UpdateModifiers(input, Target); + + Target->OnBeginClickSequence(GetDeviceRay(input)); + bInActiveSequence = true; + + return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any); +} + + +FInputCaptureUpdate UMultiClickSequenceInputBehavior::UpdateCapture(const FInputDeviceState& input, const FInputCaptureData& data) +{ + check(bInActiveSequence == true); // should always be the case! + + Modifiers.UpdateModifiers(input, Target); + + // allow target to abort click sequence + if (Target->RequestAbortClickSequence()) + { + Target->OnTerminateClickSequence(); + bInActiveSequence = false; + return FInputCaptureUpdate::End(); + } + + if (IsReleased(input)) + { + bool bContinue = Target->OnNextSequenceClick(GetDeviceRay(input)); + if (bContinue == false) + { + bInActiveSequence = false; + return FInputCaptureUpdate::End(); + } + } + else + { + Target->OnNextSequencePreview(GetDeviceRay(input)); + } + + return FInputCaptureUpdate::Continue(); +} + + +void UMultiClickSequenceInputBehavior::ForceEndCapture(const FInputCaptureData& data) +{ + Target->OnTerminateClickSequence(); + bInActiveSequence = false; +} + + +bool UMultiClickSequenceInputBehavior::WantsHoverEvents() +{ + return true; +} + +void UMultiClickSequenceInputBehavior::UpdateHover(const FInputDeviceState& Input) +{ + if (Target != nullptr) + { + Modifiers.UpdateModifiers(Input, Target); + Target->OnBeginSequencePreview(FInputDeviceRay(Input.Mouse.WorldRay, Input.Mouse.Position2D)); + } +} + +void UMultiClickSequenceInputBehavior::EndHover(const FInputDeviceState& input) +{ +} \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/SingleClickBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/SingleClickBehavior.cpp new file mode 100644 index 000000000000..f4c90d6ee258 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseBehaviors/SingleClickBehavior.cpp @@ -0,0 +1,65 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseBehaviors/SingleClickBehavior.h" + + + +USingleClickInputBehavior::USingleClickInputBehavior() +{ + HitTestOnRelease = true; +} + + +void USingleClickInputBehavior::Initialize(IClickBehaviorTarget* TargetIn) +{ + this->Target = TargetIn; +} + + +FInputCaptureRequest USingleClickInputBehavior::WantsCapture(const FInputDeviceState& input) +{ + if (IsPressed(input) && (ModifierCheckFunc == nullptr || ModifierCheckFunc(input)) ) + { + if ( Target->IsHitByClick(GetDeviceRay(input)) ) + { + return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any); + } + } + return FInputCaptureRequest::Ignore(); +} + + +FInputCaptureUpdate USingleClickInputBehavior::BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide) +{ + return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any); +} + + +FInputCaptureUpdate USingleClickInputBehavior::UpdateCapture(const FInputDeviceState& input, const FInputCaptureData& data) +{ + if (IsReleased(input)) + { + if (HitTestOnRelease == false || + Target->IsHitByClick(GetDeviceRay(input)) ) + { + Clicked(input, data); + } + + return FInputCaptureUpdate::End(); + } + + return FInputCaptureUpdate::Continue(); +} + + +void USingleClickInputBehavior::ForceEndCapture(const FInputCaptureData& data) +{ + // nothing to do +} + + +void USingleClickInputBehavior::Clicked(const FInputDeviceState& input, const FInputCaptureData& data) +{ + Target->OnClicked(GetDeviceRay(input)); +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/ClickDragTool.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/ClickDragTool.cpp new file mode 100644 index 000000000000..91f223a49037 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/ClickDragTool.cpp @@ -0,0 +1,72 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseTools/ClickDragTool.h" +#include "InteractiveToolManager.h" +#include "BaseBehaviors/ClickDragBehavior.h" + + +/* + * ToolBuilder + */ + +bool UClickDragToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + return true; +} + +UInteractiveTool* UClickDragToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + UClickDragTool* NewTool = NewObject(SceneState.ToolManager); + return NewTool; +} + + + +/* + * Tool + */ + + +void UClickDragTool::Setup() +{ + UInteractiveTool::Setup(); + + // add default mouse input behavior + UClickDragInputBehavior* MouseBehavior = NewObject(); + MouseBehavior->Initialize(this); + AddInputBehavior(MouseBehavior); +} + + +bool UClickDragTool::CanBeginClickDragSequence(const FInputDeviceRay& ClickPos) +{ + return true; +} + + +void UClickDragTool::OnClickPress(const FInputDeviceRay& ClickPos) +{ + // print debug message + GetToolManager()->PostMessage( + FString::Printf( TEXT("UClickDragTool::OnClickPress at (%f,%f)"), ClickPos.ScreenPosition.X, ClickPos.ScreenPosition.Y), + EToolMessageLevel::Internal ); +} + +void UClickDragTool::OnClickDrag(const FInputDeviceRay& ClickPos) +{ +} + + +void UClickDragTool::OnClickRelease(const FInputDeviceRay& ClickPos) +{ + // print debug message + GetToolManager()->PostMessage( + FString::Printf(TEXT("UClickDragTool::OnClickRelease at (%f,%f)"), ClickPos.ScreenPosition.X, ClickPos.ScreenPosition.Y), + EToolMessageLevel::Internal); +} + + +void UClickDragTool::OnTerminateDragSequence() +{ + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/MeshSurfacePointTool.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/MeshSurfacePointTool.cpp new file mode 100644 index 000000000000..975fa1050370 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/MeshSurfacePointTool.cpp @@ -0,0 +1,166 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseTools/MeshSurfacePointTool.h" +#include "BaseBehaviors/MouseHoverBehavior.h" +#include "InteractiveToolManager.h" +#include "ToolBuilderUtil.h" + + +/* + * ToolBuilder + */ + +bool UMeshSurfacePointToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + return ToolBuilderUtil::CountComponents(SceneState, ToolBuilderUtil::IsMeshDescriptionSourceComponent) == 1; +} + +UInteractiveTool* UMeshSurfacePointToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + UMeshSurfacePointTool* NewTool = CreateNewTool(SceneState); + InitializeNewTool(NewTool, SceneState); + return NewTool; +} + +UMeshSurfacePointTool* UMeshSurfacePointToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const +{ + return NewObject(SceneState.ToolManager); +} + +void UMeshSurfacePointToolBuilder::InitializeNewTool(UMeshSurfacePointTool* NewTool, const FToolBuilderState& SceneState) const +{ + UActorComponent* MeshComponent = ToolBuilderUtil::FindFirstComponent(SceneState, ToolBuilderUtil::IsMeshDescriptionSourceComponent); + check(MeshComponent != nullptr); + NewTool->SetMeshSource( + SceneState.SourceBuilder->MakeMeshDescriptionSource(MeshComponent) ); +} + + +/* + * Tool + */ + +void UMeshSurfacePointTool::SetMeshSource(TUniquePtr MeshSourceIn) +{ + this->MeshSource = TUniquePtr(MoveTemp(MeshSourceIn)); +} + + +void UMeshSurfacePointTool::Setup() +{ + UInteractiveTool::Setup(); + + bShiftToggle = false; + + // add input behaviors + UMeshSurfacePointToolMouseBehavior* mouseBehavior = NewObject(); + mouseBehavior->Initialize(this); + AddInputBehavior(mouseBehavior); + + UMouseHoverBehavior* hoverBehavior = NewObject(); + hoverBehavior->Initialize(this); + AddInputBehavior(hoverBehavior); +} + + +bool UMeshSurfacePointTool::HitTest(const FRay& Ray, FHitResult& OutHit) +{ + return MeshSource->HitTest(Ray, OutHit); +} + + +void UMeshSurfacePointTool::OnBeginDrag(const FRay& Ray) +{ + +} + + +void UMeshSurfacePointTool::OnUpdateDrag(const FRay& Ray) +{ + FHitResult OutHit; + if ( HitTest(Ray, OutHit) ) + { + GetToolManager()->PostMessage( + FString::Printf(TEXT("[UMeshSurfacePointTool::OnUpdateDrag] Hit triangle index %d at ray distance %f"), OutHit.FaceIndex, OutHit.Distance), EToolMessageLevel::Internal); + } +} + +void UMeshSurfacePointTool::OnEndDrag(const FRay& Ray) +{ + //GetToolManager()->PostMessage(TEXT("UMeshSurfacePointTool::OnEndDrag!"), EToolMessageLevel::Internal); +} + + +void UMeshSurfacePointTool::SetShiftToggle(bool bShiftDown) +{ + bShiftToggle = bShiftDown; +} + + + + +/* + * Mouse Input Behavior + */ + + +void UMeshSurfacePointToolMouseBehavior::Initialize(UMeshSurfacePointTool* ToolIn) +{ + this->Tool = ToolIn; + bInDragCapture = false; +} + + +FInputCaptureRequest UMeshSurfacePointToolMouseBehavior::WantsCapture(const FInputDeviceState& input) +{ + if ( IsPressed(input) ) + { + FHitResult OutHit; + if (Tool->HitTest(input.Mouse.WorldRay, OutHit)) + { + return FInputCaptureRequest::Begin(this, EInputCaptureSide::Any, OutHit.Distance); + } + } + return FInputCaptureRequest::Ignore(); +} + +FInputCaptureUpdate UMeshSurfacePointToolMouseBehavior::BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide) +{ + Tool->SetShiftToggle(input.bShiftKeyDown); + Tool->OnBeginDrag(input.Mouse.WorldRay); + LastWorldRay = input.Mouse.WorldRay; + bInDragCapture = true; + return FInputCaptureUpdate::Begin(this, EInputCaptureSide::Any); +} + +FInputCaptureUpdate UMeshSurfacePointToolMouseBehavior::UpdateCapture(const FInputDeviceState& input, const FInputCaptureData& data) +{ + LastWorldRay = input.Mouse.WorldRay; + + if ( IsReleased(input) ) + { + Tool->OnEndDrag(input.Mouse.WorldRay); + bInDragCapture = false; + return FInputCaptureUpdate::End(); + } + + Tool->OnUpdateDrag(input.Mouse.WorldRay); + return FInputCaptureUpdate::Continue(); +} + +void UMeshSurfacePointToolMouseBehavior::ForceEndCapture(const FInputCaptureData& data) +{ + if (bInDragCapture) + { + Tool->OnEndDrag(LastWorldRay); + bInDragCapture = false; + } + + // nothing to do +} + + + + + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/SingleClickTool.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/SingleClickTool.cpp new file mode 100644 index 000000000000..4b683caa56b3 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/BaseTools/SingleClickTool.cpp @@ -0,0 +1,56 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BaseTools/SingleClickTool.h" +#include "InteractiveToolManager.h" + + +/* + * ToolBuilder + */ + +bool USingleClickToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const +{ + return true; +} + +UInteractiveTool* USingleClickToolBuilder::BuildTool(const FToolBuilderState& SceneState) const +{ + USingleClickTool* NewTool = NewObject(SceneState.ToolManager); + return NewTool; +} + + + +/* + * Tool + */ + + +void USingleClickTool::Setup() +{ + UInteractiveTool::Setup(); + + // add default button input behaviors for devices + USingleClickInputBehavior* MouseBehavior = NewObject(); + MouseBehavior->Initialize(this); + AddInputBehavior(MouseBehavior); +} + + +bool USingleClickTool::IsHitByClick(const FInputDeviceRay& ClickPos) +{ + return true; +} + + +void USingleClickTool::OnClicked(const FInputDeviceRay& ClickPos) +{ + // print debug message + GetToolManager()->PostMessage( + FString::Printf( TEXT("USingleClickTool::OnClicked at (%f,%f)"), ClickPos.ScreenPosition.X, ClickPos.ScreenPosition.Y), + EToolMessageLevel::Internal ); +} + + + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputBehavior.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputBehavior.cpp new file mode 100644 index 000000000000..fe06a3c88308 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputBehavior.cpp @@ -0,0 +1,77 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InputBehavior.h" + + +bool operator<(const FInputCaptureRequest& l, const FInputCaptureRequest& r) +{ + if (l.Source->GetPriority() == r.Source->GetPriority()) + { + return l.HitDepth < r.HitDepth; + } + else + { + return l.Source->GetPriority() < r.Source->GetPriority(); + } +} + + + +UInputBehavior::UInputBehavior() +{ + DefaultPriority = FInputCapturePriority(100); +} + +FInputCapturePriority UInputBehavior::GetPriority() +{ + return DefaultPriority; +} + +void UInputBehavior::SetDefaultPriority(const FInputCapturePriority& Priority) +{ + DefaultPriority = Priority; +} + +EInputDevices UInputBehavior::GetSupportedDevices() +{ + return EInputDevices::Mouse; +} + + +FInputCaptureRequest UInputBehavior::WantsCapture(const FInputDeviceState& input) +{ + return FInputCaptureRequest::Ignore(); +} + +FInputCaptureUpdate UInputBehavior::BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide) +{ + return FInputCaptureUpdate::Ignore(); +} + +FInputCaptureUpdate UInputBehavior::UpdateCapture(const FInputDeviceState& input, const FInputCaptureData& data) +{ + return FInputCaptureUpdate::End(); +} + +void UInputBehavior::ForceEndCapture(const FInputCaptureData& data) +{ + return; +} + + +bool UInputBehavior::WantsHoverEvents() +{ + return false; +} + +void UInputBehavior::UpdateHover(const FInputDeviceState& input) +{ + return; +} + +void UInputBehavior::EndHover(const FInputDeviceState& input) +{ + return; +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputBehaviorSet.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputBehaviorSet.cpp new file mode 100644 index 000000000000..12d60cad884a --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputBehaviorSet.cpp @@ -0,0 +1,164 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InputBehaviorSet.h" + + +UInputBehaviorSet::UInputBehaviorSet() +{ +} + +UInputBehaviorSet::~UInputBehaviorSet() +{ +} + + +bool UInputBehaviorSet::IsEmpty() const +{ + return Behaviors.Num() == 0; +} + +void UInputBehaviorSet::Add(UInputBehavior* Behavior, void* Source, const FString& Group) +{ + //if (source == null) + // source = DefaultSource; + + FBehaviorInfo Info; + Info.Behavior = Behavior; + Info.Source = Source; + Info.Group = Group; + + Behaviors.Add(Info); + BehaviorsModified(); +} + + +void UInputBehaviorSet::Add(const UInputBehaviorSet* OtherSet, void* NewSource, const FString& NewGroup) +{ + for (const FBehaviorInfo& OtherInfo : OtherSet->Behaviors) + { + FBehaviorInfo Info; + Info.Behavior = OtherInfo.Behavior; + Info.Source = (NewSource == nullptr) ? OtherInfo.Source : NewSource; + Info.Group = (NewGroup.IsEmpty()) ? OtherInfo.Group : NewGroup; + Behaviors.Add(Info); + } + if (OtherSet->Behaviors.Num() > 0) + { + BehaviorsModified(); + } +} + + +bool UInputBehaviorSet::Remove(UInputBehavior* behavior) +{ + int32 idx = Behaviors.IndexOfByPredicate( + [behavior] (const FBehaviorInfo& b) { return b.Behavior == behavior; } + ); + if ( idx != INDEX_NONE ) { + Behaviors.RemoveAt(idx); + BehaviorsModified(); + return true; + } + return false; +} + + +bool UInputBehaviorSet::RemoveByGroup(const FString& Group) +{ + int RemovedCount = Behaviors.RemoveAll( + [Group](const FBehaviorInfo& b) { return b.Group == Group; } + ); + if (RemovedCount > 0) + { + BehaviorsModified(); + } + return (RemovedCount > 0); +} + + + +bool UInputBehaviorSet::RemoveBySource(void* Source) +{ + int RemovedCount = Behaviors.RemoveAll( + [Source](const FBehaviorInfo& b) { return b.Source == Source; } + ); + if (RemovedCount > 0) + { + BehaviorsModified(); + } + return (RemovedCount > 0); +} + + +void UInputBehaviorSet::RemoveAll() +{ + Behaviors.Reset(); + BehaviorsModified(); +} + + +void UInputBehaviorSet::CollectWantsCapture(const FInputDeviceState& input, TArray& result) +{ + for (auto b : Behaviors) + { + // only call WantsCapture if the Behavior supports the current input device + if (SupportsInputType(b.Behavior, input)) + { + FInputCaptureRequest request = b.Behavior->WantsCapture(input); + if ( request.Type != EInputCaptureRequestType::Ignore ) + { + request.Owner = b.Source; + result.Add(request); + } + } + } +} + + + +bool UInputBehaviorSet::UpdateHover(const FInputDeviceState& input) +{ + bool bAnyWantedHover = false; + for (auto b : Behaviors) + { + if (SupportsInputType(b.Behavior, input)) + { + if (b.Behavior->WantsHoverEvents()) + { + b.Behavior->UpdateHover(input); + bAnyWantedHover = true; + } + } + } + return bAnyWantedHover; +} + +bool UInputBehaviorSet::EndHover(const FInputDeviceState& input) +{ + bool bAnyWantedHover = false; + for (auto b : Behaviors) + { + if (SupportsInputType(b.Behavior, input)) + { + if (b.Behavior->WantsHoverEvents()) + { + b.Behavior->EndHover(input); + bAnyWantedHover = true; + } + } + } + return bAnyWantedHover; +} + + + + + +void UInputBehaviorSet::BehaviorsModified() +{ + // sort by priority + Behaviors.StableSort(); + + //send some kind of event... + //FUtil.SafeSendAnyEvent(OnSetChanged, this); +} diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputRouter.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputRouter.cpp new file mode 100644 index 000000000000..f75c537e7b69 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputRouter.cpp @@ -0,0 +1,318 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InputRouter.h" + + +UInputRouter::UInputRouter() +{ + ActiveLeftCapture = nullptr; + ActiveLeftCaptureOwner = nullptr; + ActiveRightCapture = nullptr; + ActiveRightCaptureOwner = nullptr; + ActiveKeyboardCapture = nullptr; + ActiveKeyboardCaptureOwner = nullptr; + + bAutoInvalidateOnHover = false; + bAutoInvalidateOnCapture = false; + + ActiveInputBehaviors = NewObject(this, "InputBehaviors"); +} + + + +void UInputRouter::Initialize(IToolsContextTransactionsAPI* TransactionsAPIIn) +{ + this->TransactionsAPI = TransactionsAPIIn; +} + +void UInputRouter::Shutdown() +{ + this->TransactionsAPI = nullptr; +} + + + + +void UInputRouter::RegisterSource(IInputBehaviorSource* Source) +{ + ActiveInputBehaviors->Add(Source->GetInputBehaviors(), Source); +} + + +void UInputRouter::DeregisterSource(IInputBehaviorSource* Source) +{ + ActiveInputBehaviors->RemoveBySource(Source); +} + + + +void UInputRouter::PostInputEvent(const FInputDeviceState& Input) +{ + if (ActiveInputBehaviors->IsEmpty()) + { + return; + } + + if (Input.IsFromDevice(EInputDevices::Mouse)) + { + PostInputEvent_Mouse(Input); + } + else if (Input.IsFromDevice(EInputDevices::Keyboard)) + { + PostInputEvent_Keyboard(Input); + } + else + { + unimplemented(); + TransactionsAPI->PostMessage(TEXT("UInteractiveToolManager::PostInputEvent - input device is not currently supported."), EToolMessageLevel::Internal); + return; + } +} + + + +// +// Keyboard event handling +// + + +void UInputRouter::PostInputEvent_Keyboard(const FInputDeviceState& Input) +{ + if (ActiveKeyboardCapture != nullptr) + { + HandleCapturedKeyboardInput(Input); + } + else + { + ActiveKeyboardCaptureData = FInputCaptureData(); + CheckForKeyboardCaptures(Input); + } +} + + +void UInputRouter::CheckForKeyboardCaptures(const FInputDeviceState& Input) +{ + TArray CaptureRequests; + ActiveInputBehaviors->CollectWantsCapture(Input, CaptureRequests); + if (CaptureRequests.Num() == 0) + { + return; + } + + CaptureRequests.StableSort(); + + bool bAccepted = false; + for (int i = 0; i < CaptureRequests.Num() && bAccepted == false; ++i) + { + FInputCaptureUpdate Result = + CaptureRequests[i].Source->BeginCapture(Input, EInputCaptureSide::Left); + if (Result.State == EInputCaptureState::Begin) + { + ActiveKeyboardCapture = Result.Source; + ActiveKeyboardCaptureOwner = CaptureRequests[i].Owner; + ActiveKeyboardCaptureData = Result.Data; + bAccepted = true; + } + } +} + + +void UInputRouter::HandleCapturedKeyboardInput(const FInputDeviceState& Input) +{ + if (ActiveKeyboardCapture == nullptr) + { + return; + } + + FInputCaptureUpdate Result = + ActiveKeyboardCapture->UpdateCapture(Input, ActiveKeyboardCaptureData); + + if (Result.State == EInputCaptureState::End) + { + ActiveKeyboardCapture = nullptr; + ActiveKeyboardCaptureOwner = nullptr; + ActiveKeyboardCaptureData = FInputCaptureData(); + } + else if (Result.State != EInputCaptureState::Continue) + { + TransactionsAPI->PostMessage(TEXT("UInteractiveToolManager::HandleCapturedKeyboardInput - unexpected capture state!"), EToolMessageLevel::Internal); + } + + if (bAutoInvalidateOnCapture) + { + TransactionsAPI->PostInvalidation(); + } +} + + + +// +// Mouse event handling +// + + + +void UInputRouter::PostInputEvent_Mouse(const FInputDeviceState& Input) +{ + if (ActiveLeftCapture != nullptr) + { + HandleCapturedMouseInput(Input); + } + else + { + ActiveLeftCaptureData = FInputCaptureData(); + CheckForMouseCaptures(Input); + } + + // update hover if nobody is capturing + if (ActiveLeftCapture == nullptr && ActiveRightCapture == nullptr) + { + bool bProcessed = ActiveInputBehaviors->UpdateHover(Input); + if (bProcessed && bAutoInvalidateOnHover) + { + TransactionsAPI->PostInvalidation(); + } + } + +} + + +void UInputRouter::PostHoverInputEvent(const FInputDeviceState& Input) +{ + LastHoverInput = Input; + bool bProcessed = ActiveInputBehaviors->UpdateHover(Input); + if (bProcessed && bAutoInvalidateOnHover) + { + TransactionsAPI->PostInvalidation(); + } +} + + + + +bool UInputRouter::HasActiveMouseCapture() const +{ + return (ActiveLeftCapture != nullptr); +} + + +void UInputRouter::CheckForMouseCaptures(const FInputDeviceState& Input) +{ + TArray CaptureRequests; + ActiveInputBehaviors->CollectWantsCapture(Input, CaptureRequests); + if (CaptureRequests.Num() == 0) + { + return; + } + + CaptureRequests.StableSort(); + + bool bAccepted = false; + for (int i = 0; i < CaptureRequests.Num() && bAccepted == false; ++i) + { + FInputCaptureUpdate Result = + CaptureRequests[i].Source->BeginCapture(Input, EInputCaptureSide::Left); + if (Result.State == EInputCaptureState::Begin) + { + // end outstanding hovers + ActiveInputBehaviors->EndHover(Input); + + ActiveLeftCapture = Result.Source; + ActiveLeftCaptureOwner = CaptureRequests[i].Owner; + ActiveLeftCaptureData = Result.Data; + bAccepted = true; + } + + } +} + + +void UInputRouter::HandleCapturedMouseInput(const FInputDeviceState& Input) +{ + if (ActiveLeftCapture == nullptr) + { + return; + } + + // have active capture - give it this event + + FInputCaptureUpdate Result = + ActiveLeftCapture->UpdateCapture(Input, ActiveLeftCaptureData); + + if (Result.State == EInputCaptureState::End) + { + ActiveLeftCapture = nullptr; + ActiveLeftCaptureOwner = nullptr; + ActiveLeftCaptureData = FInputCaptureData(); + } + else if (Result.State != EInputCaptureState::Continue) + { + TransactionsAPI->PostMessage(TEXT("UInteractiveToolManager::HandleCapturedMouseInput - unexpected capture state!"), EToolMessageLevel::Internal); + } + + if (bAutoInvalidateOnCapture) + { + TransactionsAPI->PostInvalidation(); + } +} + + + +void UInputRouter::ForceTerminateAll() +{ + if (ActiveKeyboardCapture != nullptr) + { + ActiveKeyboardCapture->ForceEndCapture(ActiveKeyboardCaptureData); + ActiveKeyboardCapture = nullptr; + ActiveKeyboardCaptureOwner = nullptr; + ActiveKeyboardCaptureData = FInputCaptureData(); + } + + if (ActiveLeftCapture != nullptr) + { + ActiveLeftCapture->ForceEndCapture(ActiveLeftCaptureData); + ActiveLeftCapture = nullptr; + ActiveLeftCaptureOwner = nullptr; + ActiveLeftCaptureData = FInputCaptureData(); + } + + if (ActiveRightCapture != nullptr) + { + ActiveRightCapture->ForceEndCapture(ActiveRightCaptureData); + ActiveRightCapture = nullptr; + ActiveRightCaptureOwner = nullptr; + ActiveRightCaptureData = FInputCaptureData(); + } + + ActiveInputBehaviors->EndHover(LastHoverInput); +} + + +void UInputRouter::ForceTerminateSource(IInputBehaviorSource* Source) +{ + if (ActiveKeyboardCapture != nullptr && ActiveKeyboardCaptureOwner == Source) + { + ActiveKeyboardCapture->ForceEndCapture(ActiveKeyboardCaptureData); + ActiveKeyboardCapture = nullptr; + ActiveKeyboardCaptureOwner = nullptr; + ActiveKeyboardCaptureData = FInputCaptureData(); + } + + if (ActiveLeftCapture != nullptr && ActiveLeftCaptureOwner == Source) + { + ActiveLeftCapture->ForceEndCapture(ActiveLeftCaptureData); + ActiveLeftCapture = nullptr; + ActiveLeftCaptureOwner = nullptr; + ActiveLeftCaptureData = FInputCaptureData(); + } + + if (ActiveRightCapture != nullptr && ActiveRightCaptureOwner == Source) + { + ActiveRightCapture->ForceEndCapture(ActiveRightCaptureData); + ActiveRightCapture = nullptr; + ActiveRightCaptureOwner = nullptr; + ActiveRightCaptureData = FInputCaptureData(); + } +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputState.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputState.cpp new file mode 100644 index 000000000000..7a899fc635a3 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InputState.cpp @@ -0,0 +1,4 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InputState.h" + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveGizmo.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveGizmo.cpp new file mode 100644 index 000000000000..9ce07df04d8e --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveGizmo.cpp @@ -0,0 +1,52 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InteractiveGizmo.h" +#include "InteractiveGizmoManager.h" + + +UInteractiveGizmo::UInteractiveGizmo() +{ + // Gizmos don't get saved but this isn't necessary because they are created in the transient package... + SetFlags(RF_Transient); + + InputBehaviors = NewObject(this, TEXT("GizmoInputBehaviors")); +} + +void UInteractiveGizmo::Setup() +{ +} + +void UInteractiveGizmo::Shutdown() +{ + InputBehaviors->RemoveAll(); +} + +void UInteractiveGizmo::Render(IToolsContextRenderAPI* RenderAPI) +{ +} + + +void UInteractiveGizmo::AddInputBehavior(UInputBehavior* Behavior) +{ + InputBehaviors->Add(Behavior); +} + +const UInputBehaviorSet* UInteractiveGizmo::GetInputBehaviors() const +{ + return InputBehaviors; +} + + + +void UInteractiveGizmo::Tick(float DeltaTime) +{ +} + + +UInteractiveGizmoManager* UInteractiveGizmo::GetGizmoManager() const +{ + UInteractiveGizmoManager* GizmoManager = Cast(GetOuter()); + check(GizmoManager != nullptr); + return GizmoManager; +} \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveGizmoManager.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveGizmoManager.cpp new file mode 100644 index 000000000000..4fb55bc590e3 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveGizmoManager.cpp @@ -0,0 +1,228 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InteractiveGizmoManager.h" + + +UInteractiveGizmoManager::UInteractiveGizmoManager() +{ + QueriesAPI = nullptr; + TransactionsAPI = nullptr; + InputRouter = nullptr; +} + + +void UInteractiveGizmoManager::Initialize(IToolsContextQueriesAPI* QueriesAPIIn, IToolsContextTransactionsAPI* TransactionsAPIIn, UInputRouter* InputRouterIn) +{ + this->QueriesAPI = QueriesAPIIn; + this->TransactionsAPI = TransactionsAPIIn; + this->InputRouter = InputRouterIn; +} + + +void UInteractiveGizmoManager::Shutdown() +{ + this->QueriesAPI = nullptr; + + TArray AllGizmos = ActiveGizmos; + for (FActiveGizmo& ActiveGizmo : AllGizmos) + { + DestroyGizmo(ActiveGizmo.Gizmo); + } + ActiveGizmos.Reset(); + + this->TransactionsAPI = nullptr; +} + + + +void UInteractiveGizmoManager::RegisterGizmoType(const FString& Identifier, UInteractiveGizmoBuilder* Builder) +{ + check(GizmoBuilders.Contains(Identifier) == false); + GizmoBuilders.Add(Identifier, Builder ); +} + + +bool UInteractiveGizmoManager::DeregisterGizmoType(const FString& BuilderIdentifier) +{ + if (GizmoBuilders.Contains(BuilderIdentifier) == false) + { + PostMessage(FString::Printf(TEXT("UInteractiveGizmoManager::DeregisterGizmoType: could not find requested type %s"), *BuilderIdentifier), EToolMessageLevel::Internal); + return false; + } + GizmoBuilders.Remove(BuilderIdentifier); + return true; +} + + + + +UInteractiveGizmo* UInteractiveGizmoManager::CreateGizmo(const FString& BuilderIdentifier, const FString& InstanceIdentifier) +{ + if ( GizmoBuilders.Contains(BuilderIdentifier) == false ) + { + PostMessage(FString::Printf(TEXT("UInteractiveGizmoManager::CreateGizmo: could not find requested type %s"), *BuilderIdentifier), EToolMessageLevel::Internal); + return nullptr; + } + UInteractiveGizmoBuilder* FoundBuilder = GizmoBuilders[BuilderIdentifier]; + + // check if we have used this instance identifier + for (FActiveGizmo& ActiveGizmo : ActiveGizmos) + { + if (ActiveGizmo.InstanceIdentifier == InstanceIdentifier) + { + PostMessage(FString::Printf(TEXT("UInteractiveGizmoManager::CreateGizmo: instance identifier %s already in use!"), *InstanceIdentifier), EToolMessageLevel::Internal); + return nullptr; + } + } + + FToolBuilderState CurrentSceneState; + QueriesAPI->GetCurrentSelectionState(CurrentSceneState); + + UInteractiveGizmo* NewGizmo = FoundBuilder->BuildGizmo(CurrentSceneState); + if (NewGizmo == nullptr) + { + PostMessage(FString::Printf(TEXT("UInteractiveGizmoManager::CreateGizmo: BuildGizmo() returned null")), EToolMessageLevel::Internal); + return nullptr; + } + + NewGizmo->Setup(); + + // register new active input behaviors + InputRouter->RegisterSource(NewGizmo); + + PostInvalidation(); + + FActiveGizmo ActiveGizmo = { NewGizmo, BuilderIdentifier, InstanceIdentifier }; + ActiveGizmos.Add(ActiveGizmo); + + return NewGizmo; +} + + + +bool UInteractiveGizmoManager::DestroyGizmo(UInteractiveGizmo* Gizmo) +{ + int FoundIndex = -1; + for ( int i = 0; i < ActiveGizmos.Num(); ++i ) + { + if (ActiveGizmos[i].Gizmo == Gizmo) + { + FoundIndex = i; + break; + } + } + if (FoundIndex == -1) + { + return false; + } + + InputRouter->ForceTerminateSource(Gizmo); + + Gizmo->Shutdown(); + + InputRouter->DeregisterSource(Gizmo); + + ActiveGizmos.RemoveAt(FoundIndex); + + PostInvalidation(); + + return true; +} + + + + +TArray UInteractiveGizmoManager::FindAllGizmosOfType(const FString& BuilderIdentifier) +{ + TArray Found; + for (int i = 0; i < ActiveGizmos.Num(); ++i) + { + if (ActiveGizmos[i].BuilderIdentifier == BuilderIdentifier) + { + Found.Add(ActiveGizmos[i].Gizmo); + } + } + return Found; +} + + +void UInteractiveGizmoManager::DestroyAllGizmosOfType(const FString& BuilderIdentifier) +{ + TArray ToRemove = FindAllGizmosOfType(BuilderIdentifier); + + for (int i = 0; i < ToRemove.Num(); ++i) + { + DestroyGizmo(ToRemove[i]); + } +} + + + +UInteractiveGizmo* UInteractiveGizmoManager::FindGizmoByInstanceIdentifier(const FString& Identifier) +{ + for (int i = 0; i < ActiveGizmos.Num(); ++i) + { + if (ActiveGizmos[i].InstanceIdentifier == Identifier) + { + return ActiveGizmos[i].Gizmo; + } + } + return nullptr; +} + + + +void UInteractiveGizmoManager::Tick(float DeltaTime) +{ + for (FActiveGizmo& ActiveGizmo : ActiveGizmos) + { + ActiveGizmo.Gizmo->Tick(DeltaTime); + } +} + + +void UInteractiveGizmoManager::Render(IToolsContextRenderAPI* RenderAPI) +{ + for (FActiveGizmo& ActiveGizmo : ActiveGizmos) + { + ActiveGizmo.Gizmo->Render(RenderAPI); + } + +} + + +void UInteractiveGizmoManager::PostMessage(const TCHAR* Message, EToolMessageLevel Level) +{ + TransactionsAPI->PostMessage(Message, Level); +} + +void UInteractiveGizmoManager::PostMessage(const FString& Message, EToolMessageLevel Level) +{ + TransactionsAPI->PostMessage(*Message, Level); +} + +void UInteractiveGizmoManager::PostInvalidation() +{ + TransactionsAPI->PostInvalidation(); +} + + +void UInteractiveGizmoManager::BeginUndoTransaction(const FText& Description) +{ + TransactionsAPI->BeginUndoTransaction(Description); +} + +void UInteractiveGizmoManager::EndUndoTransaction() +{ + TransactionsAPI->EndUndoTransaction(); +} + + + +void UInteractiveGizmoManager::EmitObjectChange(UObject* TargetObject, TUniquePtr Change, const FText& Description) +{ + TransactionsAPI->AppendChange(TargetObject, MoveTemp(Change), Description ); +} + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveTool.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveTool.cpp new file mode 100644 index 000000000000..92e6477c3196 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveTool.cpp @@ -0,0 +1,115 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InteractiveTool.h" +#include "InteractiveToolManager.h" + + +UInteractiveTool::UInteractiveTool() +{ + // tools need to be transactional or undo/redo won't work on their uproperties + SetFlags(RF_Transactional); + + // tools don't get saved but this isn't necessary because they are created in the transient package... + //SetFlags(RF_Transient); + + InputBehaviors = NewObject(this, TEXT("InputBehaviors")); +} + +void UInteractiveTool::Setup() +{ +} + +void UInteractiveTool::Shutdown(EToolShutdownType ShutdownType) +{ + InputBehaviors->RemoveAll(); + ToolPropertyObjects.Reset(); +} + +void UInteractiveTool::Render(IToolsContextRenderAPI* RenderAPI) +{ +} + + +void UInteractiveTool::AddInputBehavior(UInputBehavior* Behavior) +{ + InputBehaviors->Add(Behavior); +} + +const UInputBehaviorSet* UInteractiveTool::GetInputBehaviors() const +{ + return InputBehaviors; +} + + +void UInteractiveTool::AddToolPropertySource(UObject* PropertyObject) +{ + check(ToolPropertyObjects.Contains(PropertyObject) == false); + ToolPropertyObjects.Add(PropertyObject); +} + +void UInteractiveTool::AddToolPropertySource(UInteractiveToolPropertySet* PropertySet) +{ + check(ToolPropertyObjects.Contains(PropertySet) == false); + ToolPropertyObjects.Add(PropertySet); + // @todo do we need to create a lambda every time for this? + PropertySet->GetOnModified().AddLambda([this](UObject* PropertySetArg, UProperty* PropertyArg) + { + OnPropertyModified(PropertySetArg, PropertyArg); + }); +} + + +const TArray& UInteractiveTool::GetToolProperties() const +{ + return ToolPropertyObjects; +} + + +void UInteractiveTool::RegisterActions(FInteractiveToolActionSet& ActionSet) +{ +} + +FInteractiveToolActionSet* UInteractiveTool::GetActionSet() +{ + if (ToolActionSet == nullptr) + { + ToolActionSet = new FInteractiveToolActionSet(); + RegisterActions(*ToolActionSet); + } + return ToolActionSet; +} + +void UInteractiveTool::ExecuteAction(int32 ActionID) +{ + GetActionSet()->ExecuteAction(ActionID); +} + + + +bool UInteractiveTool::HasCancel() const +{ + return false; +} + +bool UInteractiveTool::HasAccept() const +{ + return false; +} + +bool UInteractiveTool::CanAccept() const +{ + return false; +} + + +void UInteractiveTool::Tick(float DeltaTime) +{ +} + +UInteractiveToolManager* UInteractiveTool::GetToolManager() const +{ + UInteractiveToolManager* ToolManager = Cast(GetOuter()); + check(ToolManager != nullptr); + return ToolManager; +} \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolActionSet.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolActionSet.cpp new file mode 100644 index 000000000000..d58822920b21 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolActionSet.cpp @@ -0,0 +1,59 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InteractiveToolActionSet.h" +#include "InteractiveTool.h" + + + +void FInteractiveToolActionSet::RegisterAction(UInteractiveTool* Tool, int32 ActionID, + const FString& ActionName, const FString& ShortUIName, const FString& DescriptionText, + EModifierKey::Type Modifiers, const FKey& ShortcutKey, + TFunction ActionFunction ) +{ + checkf(FindActionByID(ActionID) == nullptr, TEXT("InteractiveToolActionSet::RegisterAction: Action ID is already registered!")); + + FInteractiveToolAction NewAction; + NewAction.ClassType = Tool->GetClass(); + NewAction.ActionID = ActionID; + NewAction.ActionName = ActionName; + NewAction.ShortName = ShortUIName; + NewAction.Description = DescriptionText; + NewAction.DefaultModifiers = Modifiers; + NewAction.DefaultKey = ShortcutKey; + NewAction.OnAction = ActionFunction; + + Actions.Add(NewAction); +} + + +const FInteractiveToolAction* FInteractiveToolActionSet::FindActionByID(int32 ActionID) const +{ + for (const FInteractiveToolAction& Action : Actions) + { + if (Action.ActionID == ActionID) + { + return &Action; + } + } + return nullptr; +} + + + +void FInteractiveToolActionSet::CollectActions(TArray& OutActions) const +{ + for (const FInteractiveToolAction& Action : Actions) + { + OutActions.Add(Action); + } +} + + +void FInteractiveToolActionSet::ExecuteAction(int32 ActionID) const +{ + const FInteractiveToolAction* Found = FindActionByID(ActionID); + if (Found != nullptr) + { + Found->OnAction(); + } +} \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolBuilder.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolBuilder.cpp new file mode 100644 index 000000000000..ea122d955e26 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolBuilder.cpp @@ -0,0 +1,6 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InteractiveToolBuilder.h" + +// Add default functionality here for any UInteractiveToolBuilder functions that are not pure virtual. diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolManager.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolManager.cpp new file mode 100644 index 000000000000..8fc659e16bee --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolManager.cpp @@ -0,0 +1,265 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InteractiveToolManager.h" + + +UInteractiveToolManager::UInteractiveToolManager() +{ + QueriesAPI = nullptr; + TransactionsAPI = nullptr; + InputRouter = nullptr; + + ActiveLeftBuilder = nullptr; + ActiveLeftTool = nullptr; + + ActiveRightBuilder = nullptr; + ActiveRightTool = nullptr; +} + + +void UInteractiveToolManager::Initialize(IToolsContextQueriesAPI* queriesAPI, IToolsContextTransactionsAPI* transactionsAPI, UInputRouter* InputRouterIn) +{ + this->QueriesAPI = queriesAPI; + this->TransactionsAPI = transactionsAPI; + this->InputRouter = InputRouterIn; +} + + +void UInteractiveToolManager::Shutdown() +{ + this->QueriesAPI = nullptr; + + if (ActiveLeftTool != nullptr) + { + DeactivateTool(EToolSide::Left, EToolShutdownType::Cancel); + } + if (ActiveRightTool != nullptr) + { + DeactivateTool(EToolSide::Right, EToolShutdownType::Cancel); + } + + this->TransactionsAPI = nullptr; +} + + + +void UInteractiveToolManager::RegisterToolType(const FString& Identifier, UInteractiveToolBuilder* Builder) +{ + check(ToolBuilders.Contains(Identifier) == false); + ToolBuilders.Add(Identifier, Builder ); +} + + +bool UInteractiveToolManager::SelectActiveToolType(EToolSide Side, const FString& Identifier) +{ + if (ToolBuilders.Contains(Identifier)) + { + UInteractiveToolBuilder* Builder = ToolBuilders[Identifier]; + if (Side == EToolSide::Right) + { + ActiveRightBuilder = Builder; + } + else + { + ActiveLeftBuilder = Builder; + } + return true; + } + return false; +} + + + +bool UInteractiveToolManager::CanActivateTool(EToolSide Side, const FString& Identifier) +{ + check(Side == EToolSide::Left); // TODO: support right-side tool + + if (ActiveLeftTool != nullptr) + { + return false; + } + + if (ToolBuilders.Contains(Identifier)) + { + FToolBuilderState InputState; + QueriesAPI->GetCurrentSelectionState(InputState); + + UInteractiveToolBuilder* Builder = ToolBuilders[Identifier]; + return Builder->CanBuildTool(InputState); + } + + return false; +} + + +bool UInteractiveToolManager::ActivateTool(EToolSide Side) +{ + check(Side == EToolSide::Left); // TODO: support right-side tool + + if (ActiveLeftTool != nullptr) + { + DeactivateTool(EToolSide::Left, EToolShutdownType::Cancel); + } + + if (ActiveLeftBuilder == nullptr) + { + return false; + } + + // construct input state we will pass to tools + FToolBuilderState InputState; + QueriesAPI->GetCurrentSelectionState(InputState); + + if (ActiveLeftBuilder->CanBuildTool(InputState) == false) + { + TransactionsAPI->PostMessage( TEXT("UInteractiveToolManager::ActivateTool: CanBuildTool returned false."), EToolMessageLevel::Internal); + return false; + } + + ActiveLeftTool = ActiveLeftBuilder->BuildTool(InputState); + if (ActiveLeftTool == nullptr) + { + return false; + } + + ActiveLeftTool->Setup(); + + // register new active input behaviors + InputRouter->RegisterSource(ActiveLeftTool); + + PostInvalidation(); + + OnToolStarted.Broadcast(this, ActiveLeftTool); + + return true; +} + + +void UInteractiveToolManager::DeactivateTool(EToolSide Side, EToolShutdownType ShutdownType) +{ + check(Side == EToolSide::Left); // TODO: support right-side tool + + if (ActiveLeftTool != nullptr) + { + InputRouter->ForceTerminateSource(ActiveLeftTool); + + ActiveLeftTool->Shutdown(ShutdownType); + + InputRouter->DeregisterSource(ActiveLeftTool); + + UInteractiveTool* DoneTool = ActiveLeftTool; + ActiveLeftTool = nullptr; + + PostInvalidation(); + + OnToolEnded.Broadcast(this, DoneTool); + } +} + + +bool UInteractiveToolManager::HasActiveTool(EToolSide Side) const +{ + return (Side == EToolSide::Left) ? (ActiveLeftTool != nullptr) : (ActiveRightTool != nullptr); +} + +bool UInteractiveToolManager::HasAnyActiveTool() const +{ + return ActiveLeftTool != nullptr || ActiveRightTool != nullptr; +} + + +UInteractiveTool* UInteractiveToolManager::GetActiveTool(EToolSide Side) +{ + return (Side == EToolSide::Left) ? ActiveLeftTool : ActiveRightTool; +} + + +bool UInteractiveToolManager::CanAcceptActiveTool(EToolSide Side) +{ + if (ActiveLeftTool != nullptr) + { + return ActiveLeftTool->HasAccept() && ActiveLeftTool->CanAccept(); + } + return false; +} + +bool UInteractiveToolManager::CanCancelActiveTool(EToolSide Side) +{ + if (ActiveLeftTool != nullptr) + { + return ActiveLeftTool->HasCancel(); + } + return false; +} + + + + + + +void UInteractiveToolManager::Tick(float DeltaTime) +{ + if (ActiveLeftTool != nullptr) + { + ActiveLeftTool->Tick(DeltaTime); + } + + if (ActiveRightTool!= nullptr) + { + ActiveRightTool->Tick(DeltaTime); + } +} + + +void UInteractiveToolManager::Render(IToolsContextRenderAPI* RenderAPI) +{ + if (ActiveLeftTool != nullptr) + { + ActiveLeftTool->Render(RenderAPI); + } + + if (ActiveRightTool != nullptr) + { + ActiveRightTool->Render(RenderAPI); + } +} + + +void UInteractiveToolManager::PostMessage(const TCHAR* Message, EToolMessageLevel Level) +{ + TransactionsAPI->PostMessage(Message, Level); +} + +void UInteractiveToolManager::PostMessage(const FString& Message, EToolMessageLevel Level) +{ + TransactionsAPI->PostMessage(*Message, Level); +} + +void UInteractiveToolManager::PostInvalidation() +{ + TransactionsAPI->PostInvalidation(); +} + + +void UInteractiveToolManager::BeginUndoTransaction(const FText& Description) +{ + TransactionsAPI->BeginUndoTransaction(Description); +} + +void UInteractiveToolManager::EndUndoTransaction() +{ + TransactionsAPI->EndUndoTransaction(); +} + + + +void UInteractiveToolManager::EmitObjectChange(UObject* TargetObject, TUniquePtr Change, const FText& Description) +{ + TransactionsAPI->AppendChange(TargetObject, MoveTemp(Change), Description ); +} + +bool UInteractiveToolManager::RequestSelectionChange(const FSelectedOjectsChangeList& SelectionChange) +{ + return TransactionsAPI->RequestSelectionChange(SelectionChange); +} diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolsContext.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolsContext.cpp new file mode 100644 index 000000000000..261d33d6f7b1 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolsContext.cpp @@ -0,0 +1,38 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + + +#include "InteractiveToolsContext.h" + + +UInteractiveToolsContext::UInteractiveToolsContext() +{ + InputRouter = nullptr; + ToolManager = nullptr; +} + +void UInteractiveToolsContext::Initialize(IToolsContextQueriesAPI* QueriesAPI, IToolsContextTransactionsAPI* TransactionsAPI) +{ + InputRouter = NewObject(this); + InputRouter->Initialize(TransactionsAPI); + + ToolManager = NewObject(this); + ToolManager->Initialize(QueriesAPI, TransactionsAPI, InputRouter); + + GizmoManager = NewObject(this); + GizmoManager->Initialize(QueriesAPI, TransactionsAPI, InputRouter); +} + + +void UInteractiveToolsContext::Shutdown() +{ + // force-terminate any remaining captures/hovers/etc + InputRouter->ForceTerminateAll(); + InputRouter->Shutdown(); + InputRouter = nullptr; + + GizmoManager->Shutdown(); + GizmoManager = nullptr; + + ToolManager->Shutdown(); + ToolManager = nullptr; +} diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolsFramework.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolsFramework.cpp new file mode 100644 index 000000000000..77baea81857e --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/InteractiveToolsFramework.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "InteractiveToolsFramework.h" + +#define LOCTEXT_NAMESPACE "FInteractiveToolsFrameworkModule" + +void FInteractiveToolsFrameworkModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FInteractiveToolsFrameworkModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FInteractiveToolsFrameworkModule, InteractiveToolsFramework) \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Private/ToolBuilderUtil.cpp b/Engine/Source/Runtime/InteractiveToolsFramework/Private/ToolBuilderUtil.cpp new file mode 100644 index 000000000000..54b85da49b9d --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Private/ToolBuilderUtil.cpp @@ -0,0 +1,137 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ToolBuilderUtil.h" +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Engine/Selection.h" +#include "Components/StaticMeshComponent.h" + + + +bool ToolBuilderUtil::IsMeshDescriptionSourceComponent(UActorComponent* ComponentObject) +{ + UStaticMeshComponent* StaticMeshComp = Cast(ComponentObject); + if (StaticMeshComp != nullptr) + { + return true; + } + return false; +} + + + +int ToolBuilderUtil::CountComponents(const FToolBuilderState& InputState, const TFunction& Predicate) +{ + int nTypedComponents = 0; + + if (InputState.SelectedComponents != nullptr && InputState.SelectedComponents->Num() > 0) + { + for (FSelectionIterator Iter(*InputState.SelectedComponents); Iter; ++Iter) + { + if (UActorComponent* Component = Cast(*Iter)) + { + if (Predicate(Component)) + { + nTypedComponents++; + } + } + } + } + else + { + for (FSelectionIterator Iter(*InputState.SelectedActors); Iter; ++Iter) + { + if (AActor* Actor = Cast(*Iter)) + { + for (UActorComponent* Component : Actor->GetComponents()) + { + if (Predicate(Component)) + { + nTypedComponents++; + } + } + } + } + } + + return nTypedComponents; +} + + + + +UActorComponent* ToolBuilderUtil::FindFirstComponent(const FToolBuilderState& InputState, const TFunction& Predicate) +{ + if (InputState.SelectedComponents != nullptr && InputState.SelectedComponents->Num() > 0) + { + for (FSelectionIterator Iter(*InputState.SelectedComponents); Iter; ++Iter) + { + if (UActorComponent* Component = Cast(*Iter)) + { + if (Predicate(Component)) + { + return Component; + } + } + } + } + else + { + for (FSelectionIterator Iter(*InputState.SelectedActors); Iter; ++Iter) + { + if (AActor* Actor = Cast(*Iter)) + { + for (UActorComponent* Component : Actor->GetComponents()) + { + if (Predicate(Component)) + { + return Component; + } + } + } + } + } + + return nullptr; +} + + + + +TArray ToolBuilderUtil::FindAllComponents(const FToolBuilderState& InputState, const TFunction& Predicate) +{ + TArray Components; + + if (InputState.SelectedComponents != nullptr && InputState.SelectedComponents->Num() > 0) + { + for (FSelectionIterator Iter(*InputState.SelectedComponents); Iter; ++Iter) + { + if (UActorComponent* Component = Cast(*Iter)) + { + if (Predicate(Component)) + { + Components.AddUnique(Component); + } + } + } + } + else + { + for (FSelectionIterator Iter(*InputState.SelectedActors); Iter; ++Iter) + { + if (AActor* Actor = Cast(*Iter)) + { + for (UActorComponent* Component : Actor->GetComponents()) + { + if (Predicate(Component)) + { + Components.AddUnique(Component); + } + } + } + } + } + + return Components; +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/AnyButtonInputBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/AnyButtonInputBehavior.h new file mode 100644 index 000000000000..3d2147388138 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/AnyButtonInputBehavior.h @@ -0,0 +1,70 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputBehavior.h" +#include "AnyButtonInputBehavior.generated.h" + +/** + * UAnyButtonInputBehavior is a base behavior that provides a generic + * interface to a TargetButton on a physical Input Device. You can subclass + * UAnyButtonInputBehavior to write InputBehaviors that can work independent + * of a particular device type or button, by using the UAnyButtonInputBehavior functions below. + * + * The target device button is selected using the .ButtonNumber property, or you can + * override the relevant GetXButtonState() function if you need more control. + * + * @todo spatial controllers + * @todo support tablet fingers + * @todo support gamepad? + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UAnyButtonInputBehavior : public UInputBehavior +{ + GENERATED_BODY() + +public: + UAnyButtonInputBehavior(); + + /** Return set of devices supported by this behavior */ + virtual EInputDevices GetSupportedDevices() override; + + /** @return true if Target Button has been pressed this frame */ + virtual bool IsPressed(const FInputDeviceState& input); + + /** @return true if Target Button is currently held down */ + virtual bool IsDown(const FInputDeviceState& input); + + /** @return true if Target Button was released this frame */ + virtual bool IsReleased(const FInputDeviceState& input); + + /** @return current 2D position of Target Device, or zero if device does not have 2D position */ + virtual FVector2D GetClickPoint(const FInputDeviceState& input); + + /** @return current 3D world ray for Target Device position */ + virtual FRay GetWorldRay(const FInputDeviceState& input); + + /** @return current 3D world ray and optional 2D position for Target Device */ + virtual FInputDeviceRay GetDeviceRay(const FInputDeviceState& input); + + /** @return last-active supported Device */ + EInputDevices GetActiveDevice() const; + +protected: + + /** + * Button number on target device. Button 0 is "default" button on all devices. + * Mouse: Left=0, Middle=1, Right=2 + */ + UPROPERTY() + int ButtonNumber; + + +protected: + /** Which device is currently active */ + EInputDevices ActiveDevice; + + /** @return mouse button state for Target Button */ + virtual FDeviceButtonState GetMouseButtonState(const FInputDeviceState& input); +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/BehaviorTargetInterfaces.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/BehaviorTargetInterfaces.h new file mode 100644 index 000000000000..ce0220c10a96 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/BehaviorTargetInterfaces.h @@ -0,0 +1,173 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +/** + * IModifierToggleBehaviorTarget is an interface that InputBehaviors can use to notify + * a target about modifier toggle states (like shift key being down, etc). + * The meaning of the modifier ID is client-defined (generally provided to the InputBehavior in a registration step) + */ +class IModifierToggleBehaviorTarget +{ +public: + virtual ~IModifierToggleBehaviorTarget() {} + + /** + * Notify target of current modifier state + * @param ModifierID client-defined integer that identifiers modifier + * @param bIsOn is modifier current on or off + */ + virtual void OnUpdateModifierState(int ModifierID, bool bIsOn) + { + } +}; + + +/** + * Functions required to apply standard "Click" state machines to a target object. + * See USingleClickBehavior for an example of this kind of state machine. + */ +class IClickBehaviorTarget : public IModifierToggleBehaviorTarget +{ +public: + virtual ~IClickBehaviorTarget() {} + + /** + * Test if target is hit by a click + * @param ClickPos device position/ray at click point + * @return true if target was hit by click ray/point + */ + virtual bool IsHitByClick(const FInputDeviceRay& ClickPos) = 0; + + + /** + * Notify Target that click ocurred + * @param ClickPos device position/ray at click point + */ + virtual void OnClicked(const FInputDeviceRay& ClickPos) = 0; +}; + + + + +/** + * Functions required to apply standard "Click-Drag" state machines to a target object. + * See UClickDragBehavior for an example of this kind of state machine. + */ +class IClickDragBehaviorTarget : public IModifierToggleBehaviorTarget +{ +public: + virtual ~IClickDragBehaviorTarget() {} + + /** + * Test if target can begin click-drag interaction at this point + * @param PressPos device position/ray at click point + * @return true if target wants to begin sequence + */ + virtual bool CanBeginClickDragSequence(const FInputDeviceRay& PressPos) = 0; + + + /** + * Notify Target that click press ocurred + * @param PressPos device position/ray at click point + */ + virtual void OnClickPress(const FInputDeviceRay& PressPos) = 0; + + /** + * Notify Target that input position has changed + * @param DragPos device position/ray at click point + */ + virtual void OnClickDrag(const FInputDeviceRay& DragPos) = 0; + + /** + * Notify Target that click release occurred + * @param ReleasePos device position/ray at click point + */ + virtual void OnClickRelease(const FInputDeviceRay& ReleasePos) = 0; + + /** + * Notify Target that click-drag sequence has been explicitly terminated (eg by escape key) + */ + virtual void OnTerminateDragSequence() = 0; +}; + + + + + + +/** + * Target interface used by InputBehaviors that want to implement a multi-click sequence + * (eg such as drawing a polygon with multiple clicks) + */ +class IClickSequenceBehaviorTarget : public IModifierToggleBehaviorTarget +{ +public: + virtual ~IClickSequenceBehaviorTarget() {} + + /** + * Notify Target device position has changed but a click sequence hasn't begun yet (eg for interactive previews) + * @param ClickPos device position/ray at click point + */ + virtual void OnBeginSequencePreview(const FInputDeviceRay& ClickPos) { } + + /** + * Test if target would like to begin sequence based on this click + * @param ClickPos device position/ray at click point + * @return true if target wants to begin sequence + */ + virtual bool CanBeginClickSequence(const FInputDeviceRay& ClickPos) = 0; + + /** + * Notify Target that click sequence can begin at click point + * @param ClickPos device position/ray at click point + */ + virtual void OnBeginClickSequence(const FInputDeviceRay& ClickPos) = 0; + + /** + * Notify Target device position has changed but a click hasn't ocurred yet (eg for interactive previews) + * @param ClickPos device position/ray at click point + */ + virtual void OnNextSequencePreview(const FInputDeviceRay& ClickPos) { } + + /** + * Notify Target about next click in sqeuence + * @param ClickPos device position/ray at click point + * @return false if Target wants to terminate sequence + */ + virtual bool OnNextSequenceClick(const FInputDeviceRay& ClickPos) = 0; + + /** + * Notify Target that click sequence has been explicitly terminated (eg by escape key, cancel tool, etc) + */ + virtual void OnTerminateClickSequence() = 0; + + /** + * Target overrides this and returns true if it wants to abort click sequence. + * Behavior checks every update and if this ever returns true, terminates sequence + */ + virtual bool RequestAbortClickSequence() { return false; } +}; + + + + + +/** + * IHoverBehaviorTarget allows Behaviors to notify Tools/etc about + * device event data in a generic way, without requiring that all Tools + * know about the concept of Hovering. + */ +class IHoverBehaviorTarget : public IModifierToggleBehaviorTarget +{ +public: + virtual ~IHoverBehaviorTarget() {} + + /** + * Notify Target about a hover event + */ + virtual void OnUpdateHover(const FInputDeviceRay& DevicePos) = 0; +}; diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/ClickDragBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/ClickDragBehavior.h new file mode 100644 index 000000000000..c62b5f9b6d0a --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/ClickDragBehavior.h @@ -0,0 +1,76 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "BaseBehaviors/AnyButtonInputBehavior.h" +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "BaseBehaviors/InputBehaviorModifierStates.h" +#include "ClickDragBehavior.generated.h" + + +/** + * UClickDragInputBehavior implements a standard "button-click-drag"-style input behavior. + * An IClickDragBehaviorTarget instance must be provided which is manipulated by this behavior. + * + * The state machine works as follows: + * 1) on input-device-button-press, call Target::CanBeginClickDragSequence to determine if capture should begin + * 2) on input-device-move, call Target::OnClickDrag + * 3) on input-device-button-release, call Target::OnClickRelease + * + * If a ForceEndCapture occurs we call Target::OnTerminateDragSequence + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UClickDragInputBehavior : public UAnyButtonInputBehavior +{ + GENERATED_BODY() + +public: + UClickDragInputBehavior(); + + /** + * The modifier set for this behavior + */ + FInputBehaviorModifierStates Modifiers; + + /** + * Initialize this behavior with the given Target + * @param Target implementor of hit-test and on-clicked functions + */ + virtual void Initialize(IClickDragBehaviorTarget* Target); + + + /** + * WantsCapture() will only return capture request if this function returns true (or is null) + */ + TFunction ModifierCheckFunc = nullptr; + + + // UInputBehavior implementation + + virtual FInputCaptureRequest WantsCapture(const FInputDeviceState& Input) override; + virtual FInputCaptureUpdate BeginCapture(const FInputDeviceState& Input, EInputCaptureSide Side) override; + virtual FInputCaptureUpdate UpdateCapture(const FInputDeviceState& Input, const FInputCaptureData& Data) override; + virtual void ForceEndCapture(const FInputCaptureData& Data) override; + + +protected: + /** Click Target object */ + IClickDragBehaviorTarget* Target; + + /** + * Internal function that forwards click evens to Target::OnClickPress, you can customize behavior here + */ + virtual void OnClickPress(const FInputDeviceState& Input, EInputCaptureSide Side); + + /** + * Internal function that forwards click evens to Target::OnClickDrag, you can customize behavior here + */ + virtual void OnClickDrag(const FInputDeviceState& Input, const FInputCaptureData& Data); + + /** + * Internal function that forwards click evens to Target::OnClickRelease, you can customize behavior here + */ + virtual void OnClickRelease(const FInputDeviceState& Input, const FInputCaptureData& Data); +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/InputBehaviorModifierStates.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/InputBehaviorModifierStates.h new file mode 100644 index 000000000000..7bb34cbfc4a9 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/InputBehaviorModifierStates.h @@ -0,0 +1,66 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputState.h" +#include "BehaviorTargetInterfaces.h" + +/** + * FInputBehaviorModifierStates is an object that can be placed in an InputBehavior to allow + * users of the behavior to request that they be notified about modifier keys/buttons/etc state. + * + * We don't know ahead of time what might be used to determine modifier state, which input + * devices might be used, etc. So the user has to register (ModifierID,ModifierTestFunction) pairs. + * The behavior then calls UpdateModifiers() which will query each of the test functions and + * notify the target object about the state of the modifier. + */ +class FInputBehaviorModifierStates +{ +public: + typedef TFunction FModifierTestFunction; + +protected: + /** List of modifier IDs that have been registered */ + TArray ModifierIDs; + /** The modifier test function for each ID */ + TMap ModifierTests; + +public: + + /** + * Register a modifier ID and an associated test function + * @param ModifierID the modifier ID + * @param ModifierTest the test function + */ + void RegisterModifier(int ModifierID, const FModifierTestFunction& ModifierTest) + { + check(ModifierIDs.Contains(ModifierID) == false); + ModifierIDs.Add(ModifierID); + ModifierTests.Add(ModifierID, ModifierTest); + } + + + /** + * @return true if any modifiers have been registered + */ + bool HasModifiers() const + { + return (ModifierIDs.Num() > 0); + } + + + /** + * Look up the current state of each registered modifier and pass to the target + * @param Input the current input device state + * @param ModifiersTarget the target that is interested in the modifier values + */ + void UpdateModifiers(const FInputDeviceState& Input, IModifierToggleBehaviorTarget* ModifiersTarget) const + { + for (int ModifierID : ModifierIDs) + { + bool bIsOn = ModifierTests[ModifierID](Input); + ModifiersTarget->OnUpdateModifierState(ModifierID, bIsOn); + } + } +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/KeyAsModifierInputBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/KeyAsModifierInputBehavior.h new file mode 100644 index 000000000000..39dc936a0396 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/KeyAsModifierInputBehavior.h @@ -0,0 +1,61 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputBehavior.h" +#include "InteractiveTool.h" +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "BaseBehaviors/InputBehaviorModifierStates.h" +#include "KeyAsModifierInputBehavior.generated.h" + + +/** + * UKeyAsModifierInputBehavior converts a specific key press/release into + * a "Modifier" toggle via the IModifierToggleBehaviorTarget interface + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UKeyAsModifierInputBehavior : public UInputBehavior +{ + GENERATED_BODY() + +public: + UKeyAsModifierInputBehavior(); + + virtual EInputDevices GetSupportedDevices() override + { + return EInputDevices::Keyboard; + } + + /** + * Initialize this behavior with the given Target + * @param Target implementor of modifier-toggle behavior + * @param ModifierID integer ID that identifiers the modifier toggle + * @param ModifierKey the key that will be used as the modifier toggle + */ + virtual void Initialize(IModifierToggleBehaviorTarget* Target, int ModifierID, const FKey& ModifierKey); + + /** + * WantsCapture() will only return capture request if this function returns true (or is null) + * Intended to be used for alt/ctrl/cmd/shift modifiers on the main ModifierKey + */ + TFunction ModifierCheckFunc = nullptr; + + // UInputBehavior implementation + + virtual FInputCaptureRequest WantsCapture(const FInputDeviceState& Input) override; + virtual FInputCaptureUpdate BeginCapture(const FInputDeviceState& Input, EInputCaptureSide Side) override; + virtual FInputCaptureUpdate UpdateCapture(const FInputDeviceState& Input, const FInputCaptureData& Data) override; + virtual void ForceEndCapture(const FInputCaptureData& Data) override; + + +protected: + /** Modifier Target object */ + IModifierToggleBehaviorTarget* Target; + + /** Key that is used as modifier */ + FKey ModifierKey; + + /** Modifier set for this behavior, internally initialized with check on ModifierKey */ + FInputBehaviorModifierStates Modifiers; +}; diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/MouseHoverBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/MouseHoverBehavior.h new file mode 100644 index 000000000000..7862b92c4c61 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/MouseHoverBehavior.h @@ -0,0 +1,45 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputBehavior.h" +#include "InteractiveTool.h" +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "BaseBehaviors/InputBehaviorModifierStates.h" +#include "MouseHoverBehavior.generated.h" + + + +/** + * Trivial InputBehavior that forwards InputBehavior hover events to a Target object via + * the IHoverBehaviorTarget interface. + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UMouseHoverBehavior : public UInputBehavior +{ + GENERATED_BODY() + +public: + UMouseHoverBehavior(); + + /** + * The modifier set for this behavior + */ + FInputBehaviorModifierStates Modifiers; + + virtual void Initialize(IHoverBehaviorTarget* Target); + + // UInputBehavior hover implementation + + virtual EInputDevices GetSupportedDevices() override; + + virtual bool WantsHoverEvents() override; + virtual void UpdateHover(const FInputDeviceState& input) override; + virtual void EndHover(const FInputDeviceState& input) override; + +protected: + IHoverBehaviorTarget* Target; +}; + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/MultiClickSequenceInputBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/MultiClickSequenceInputBehavior.h new file mode 100644 index 000000000000..102d0e035615 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/MultiClickSequenceInputBehavior.h @@ -0,0 +1,74 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "BaseBehaviors/AnyButtonInputBehavior.h" +#include "BehaviorTargetInterfaces.h" +#include "BaseBehaviors/InputBehaviorModifierStates.h" +#include "MultiClickSequenceInputBehavior.generated.h" + + +/** + * UMultiClickSequenceInputBehavior implements a generic multi-click-sequence input behavior. + * For example this behavior could be used to implement a multi-click polygon-drawing interaction. + * + * The internal state machine works as follows: + * 1) on input-device-button-press, check if target wants to begin sequence. If so, begin capture. + * 2) on button *release*, check if target wants to continue or terminate sequence + * a) if terminate, release capture + * b) if continue, do nothing (capture continues between presses) + * + * The target will receive "preview" notifications (basically hover) during updates where there is + * not a release. This can be used to (eg) update a rubber-band selection end point + * + * @todo it may be better to implement this as multiple captures, and use hover callbacks to + * do the between-capture previews. holding capture across mouse release is not ideal. + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UMultiClickSequenceInputBehavior : public UAnyButtonInputBehavior +{ + GENERATED_BODY() + +public: + UMultiClickSequenceInputBehavior(); + + /** + * The modifier set for this behavior + */ + FInputBehaviorModifierStates Modifiers; + + /** + * Initialize this behavior with the given Target + * @param Target implementor of hit-test and on-clicked functions + */ + virtual void Initialize(IClickSequenceBehaviorTarget* Target); + + + /** + * WantsCapture() will only return capture request if this function returns true (or is null) + */ + TFunction ModifierCheckFunc = nullptr; + + + // UInputBehavior implementation + + virtual FInputCaptureRequest WantsCapture(const FInputDeviceState& Input) override; + virtual FInputCaptureUpdate BeginCapture(const FInputDeviceState& Input, EInputCaptureSide eSide) override; + virtual FInputCaptureUpdate UpdateCapture(const FInputDeviceState& Input, const FInputCaptureData& Data) override; + virtual void ForceEndCapture(const FInputCaptureData& Data) override; + + virtual bool WantsHoverEvents() override; + virtual void UpdateHover(const FInputDeviceState& input) override; + virtual void EndHover(const FInputDeviceState& input) override; + + +public: + +protected: + /** Click Target object */ + IClickSequenceBehaviorTarget* Target; + + bool bInActiveSequence; +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/SingleClickBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/SingleClickBehavior.h new file mode 100644 index 000000000000..f04a7f199104 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseBehaviors/SingleClickBehavior.h @@ -0,0 +1,67 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "BaseBehaviors/AnyButtonInputBehavior.h" +#include "BehaviorTargetInterfaces.h" +#include "SingleClickBehavior.generated.h" + + +/** + * USingleClickInputBehavior implements a standard "button-click"-style input behavior. + * The state machine works as follows: + * 1) on input-device-button-press, hit-test the target. If hit, begin capture + * 2) on input-device-button-release, hit-test the target. If hit, call Target::OnClicked(). If not hit, ignore click. + * + * The second hit-test is required to allow the click to be "cancelled" by moving away + * from the target. This is standard GUI behavior. You can disable this second hit test + * using the .HitTestOnRelease property. This is strongly discouraged. + * + * The hit-test and on-clicked functions are provided by a IClickBehaviorTarget instance. + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API USingleClickInputBehavior : public UAnyButtonInputBehavior +{ + GENERATED_BODY() + +public: + USingleClickInputBehavior(); + + /** + * Initialize this behavior with the given Target + * @param Target implementor of hit-test and on-clicked functions + */ + virtual void Initialize(IClickBehaviorTarget* Target); + + + /** + * WantsCapture() will only return capture request if this function returns true (or is null) + */ + TFunction ModifierCheckFunc = nullptr; + + + // UInputBehavior implementation + + virtual FInputCaptureRequest WantsCapture(const FInputDeviceState& Input) override; + virtual FInputCaptureUpdate BeginCapture(const FInputDeviceState& Input, EInputCaptureSide eSide) override; + virtual FInputCaptureUpdate UpdateCapture(const FInputDeviceState& Input, const FInputCaptureData& Data) override; + virtual void ForceEndCapture(const FInputCaptureData& Data) override; + + +public: + /** Hit-test is repeated on release (standard behavior). If false, */ + UPROPERTY() + bool HitTestOnRelease; + + +protected: + /** Click Target object */ + IClickBehaviorTarget* Target; + + /** + * Internal function that forwards click evens to Target::OnClicked, you can customize behavior here + */ + virtual void Clicked(const FInputDeviceState& Input, const FInputCaptureData& Data); +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/ClickDragTool.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/ClickDragTool.h new file mode 100644 index 000000000000..245e2a618e5e --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/ClickDragTool.h @@ -0,0 +1,81 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveTool.h" +#include "InteractiveToolBuilder.h" +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "ClickDragTool.generated.h" + + + +/** + * Builder for UClickDragTool + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UClickDragToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + +/** + * UClickDragTool is a base tool that basically just implements IClickDragBehaviorTarget, + * and on setup registers a UClickDragInputBehavior. You can subclass this Tool to + * implement basic click-drag type Tools. If you want to do more advanced things, + * like handle modifier buttons/keys, you will need to implement IClickDragBehaviorTarget yourself + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UClickDragTool : public UInteractiveTool, public IClickDragBehaviorTarget +{ + GENERATED_BODY() + +public: + + /** + * Register default primary-button-click InputBehaviors + */ + virtual void Setup() override; + + + // + // IClickBehaviorTarget implementation + // + + /** + * Test if target can begin click-drag interaction at this point + * @param ClickPos device position/ray at click point + * @return true if target wants to begin sequence + */ + virtual bool CanBeginClickDragSequence(const FInputDeviceRay& PressPos) override; + + /** + * Notify Target that click press ocurred + * @param ClickPos device position/ray at click point + */ + virtual void OnClickPress(const FInputDeviceRay& PressPos) override; + + /** + * Notify Target that input position has changed + * @param ClickPos device position/ray at click point + */ + virtual void OnClickDrag(const FInputDeviceRay& DragPos) override; + + /** + * Notify Target that click release occurred + * @param ClickPos device position/ray at click point + */ + virtual void OnClickRelease(const FInputDeviceRay& ReleasePos) override; + + /** + * Notify Target that click-drag sequence has been explicitly terminated (eg by escape key) + */ + virtual void OnTerminateDragSequence() override; + +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/MeshSurfacePointTool.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/MeshSurfacePointTool.h new file mode 100644 index 000000000000..3a68f7eacde2 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/MeshSurfacePointTool.h @@ -0,0 +1,144 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveTool.h" +#include "InteractiveToolBuilder.h" +#include "InputBehaviorSet.h" +#include "BaseBehaviors/BehaviorTargetInterfaces.h" +#include "BaseBehaviors/AnyButtonInputBehavior.h" +#include "MeshSurfacePointTool.generated.h" + + + +class UMeshSurfacePointTool; + +/** + * + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UMeshSurfacePointToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + /** @return true if a single mesh source can be found in the active selection */ + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + + /** @return new Tool instance initialized with selected mesh source */ + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; + + /** @return new Tool instance. Override this in subclasses to build a different Tool class type */ + virtual UMeshSurfacePointTool* CreateNewTool(const FToolBuilderState& SceneState) const; + + /** Called by BuildTool to configure the Tool with the input MeshSource based on the SceneState */ + virtual void InitializeNewTool(UMeshSurfacePointTool* Tool, const FToolBuilderState& SceneState) const; +}; + + + +/** + * UMeshSurfacePointTool is a base Tool implementation that can be used to implement various + * "point on surface" interactions. The tool acts on an input IMeshDescriptionSource object, + * which the standard Builder can extract from the current selection (eg Editor selection). + * + * Subclasses override the OnBeginDrag/OnUpdateDrag/OnEndDrag and OnUpdateHover functions + * to implement custom behavior. + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UMeshSurfacePointTool : public UInteractiveTool, public IHoverBehaviorTarget +{ + GENERATED_BODY() + +public: + // UInteractiveTool API Implementation + + /** Register InputBehaviors, etc */ + virtual void Setup() override; + + + // UMeshSurfacePointTool API + + /** + * Set the Target MeshDescription-providing Object for this tool + * @param MeshSource object that can provide a MeshDescriptionSource. Note UMeshSurfacePointTool + * only calls MeshSource->HitTest, so this source technically does not have to actually provide + * a valid MeshDescription + */ + virtual void SetMeshSource(TUniquePtr MeshSource); + + /** + * @return true if the target MeshSource is hit by the Ray + */ + virtual bool HitTest(const FRay& Ray, FHitResult& OutHit); + + + /** + * This function is called by registered InputBehaviors when the user begins a click-drag-release interaction + */ + virtual void OnBeginDrag(const FRay& Ray); + + /** + * This function is called by registered InputBehaviorseach frame that the user is in a click-drag-release interaction + */ + virtual void OnUpdateDrag(const FRay& Ray); + + /** + * This function is called by registered InputBehaviors when the user releases the button driving a click-drag-release interaction + */ + virtual void OnEndDrag(const FRay& Ray); + + /** + * This function is called each frame while the tool is active, to support hover updates + */ + virtual void OnUpdateHover(const FInputDeviceRay& DevicePos) {} + + + + /** Called by registered InputBehaviors to set the state of the "shift" button (or device equivalent) */ + virtual void SetShiftToggle(bool bShiftDown); + + /** @return current state of the shift toggle */ + virtual bool GetShiftToggle() { return bShiftToggle; } + + +protected: + /** Target source object that provides a MeshDescription and various other query API calls */ + TUniquePtr MeshSource; + + /** Current state of the shift modifier toggle */ + bool bShiftToggle; +}; + + + + + + + + +/** + * UMeshSurfacePointToolMouseBehavior implements mouse press-drag-release interaction behavior for Mouse devices. + * You can configure the base UAnyButtonInputBehavior to change the mouse button in use (default = left mouse) + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API UMeshSurfacePointToolMouseBehavior : public UAnyButtonInputBehavior +{ + GENERATED_BODY() + +public: + virtual void Initialize(UMeshSurfacePointTool* Tool); + + virtual FInputCaptureRequest WantsCapture(const FInputDeviceState& input) override; + virtual FInputCaptureUpdate BeginCapture(const FInputDeviceState& input, EInputCaptureSide eSide) override; + virtual FInputCaptureUpdate UpdateCapture(const FInputDeviceState& input, const FInputCaptureData& data) override; + virtual void ForceEndCapture(const FInputCaptureData& data) override; + +protected: + UMeshSurfacePointTool* Tool; + FRay LastWorldRay; + bool bInDragCapture; +}; + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/SingleClickTool.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/SingleClickTool.h new file mode 100644 index 000000000000..3c2350f6a524 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/BaseTools/SingleClickTool.h @@ -0,0 +1,62 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveTool.h" +#include "InteractiveToolBuilder.h" +#include "BaseBehaviors/SingleClickBehavior.h" +#include "SingleClickTool.generated.h" + + + +/** + * Builder for USingleClickTool + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API USingleClickToolBuilder : public UInteractiveToolBuilder +{ + GENERATED_BODY() + +public: + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; +}; + + +/** + * USingleClickTool is perhaps the simplest possible interactive tool. It simply + * reacts to default primary button clicks for the active device (eg left-mouse clicks). + * + * The function ::IsHitByClick() determines what is clickable by this Tool. The default is + * to return true, which means the click will activate anywhere (the Tool itself has no + * notion of Actors, Components, etc). You can override this function to, for example, + * filter out clicks that don't hit a target object, etc. + * + * The function ::OnClicked() implements the action that will occur when a click happens. + * You must override this to implement any kind of useful behavior. + */ +UCLASS() +class INTERACTIVETOOLSFRAMEWORK_API USingleClickTool : public UInteractiveTool, public IClickBehaviorTarget +{ + GENERATED_BODY() + +public: + + /** + * Register default primary-button-click InputBehaviors + */ + virtual void Setup() override; + + /** + * Test if the Target is hit at this 2D position / 3D ray + */ + virtual bool IsHitByClick(const FInputDeviceRay& ClickPos); + + /** + * Click the Target at this 2D position / 3D ray. Default behavior is to print debug string. + */ + virtual void OnClicked(const FInputDeviceRay& ClickPos); + +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/ComponentSourceInterfaces.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/ComponentSourceInterfaces.h new file mode 100644 index 000000000000..774a2378c79e --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/ComponentSourceInterfaces.h @@ -0,0 +1,100 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Math/UnrealMath.h" +#include "Engine/EngineTypes.h" // FHitResult + +// predeclarations +class AActor; +class UActorComponent; +struct FMeshDescription; +class UMaterialInterface; + + + +/** + * Wrapper around a UObject Component that can provide a MeshDescription, and + * (optionally) bake a modified MeshDescription back to this Component. + * An example of a Source might be a StaticMeshComponent. How a modified + * MeshDescription is committed back is context-dependent (in Editor vs PIE vs Runtime, etc). + * + * (Conceivably this doesn't have to be backed by a Component, but most usage will assume there is an Actor) + */ +class IMeshDescriptionSource +{ +public: + virtual ~IMeshDescriptionSource() {} + + + /** @return the Actor that owns this Component */ + virtual AActor* GetOwnerActor() const = 0; + + /** @return the Component this is a Source for */ + virtual UActorComponent* GetOwnerComponent() const = 0; + + /** @return Pointer to the MeshDescription this Source is providing */ + virtual FMeshDescription* GetMeshDescription() const = 0; + + /** + * Get pointer to a Material provided by this Source + * @param MaterialIndex index of the material + * @return MaterialInterface pointer, or null if MaterialIndex is invalid + */ + virtual UMaterialInterface* GetMaterial(int32 MaterialIndex) const = 0; + + /** + * @return the transform on this component + * @todo Do we need to return a list of transforms here? + */ + virtual FTransform GetWorldTransform() const = 0; + + /** + * Compute ray intersection with the MeshDescription this Source is providing + * @param WorldRay ray in world space + * @param OutHit hit test data + * @return true if ray intersected Component + */ + virtual bool HitTest(const FRay& WorldRay, FHitResult& OutHit) const = 0; + + /** + * Set the visibility of the Component associated with this Source (ie to hide during Tool usage) + * @param bVisible desired visibility + */ + virtual void SetOwnerVisibility(bool bVisible) const = 0; + + + /** @return true if this Source is read-only, ie Commit functions cannot be called */ + virtual bool IsReadOnly() const { return true; } + + /** + * Call this to modify the MeshDescription provided by the Source. You provide a callback + * function that the Source will immediately call with a suitable MeshDescription instance. + * The Source will then update the Component as necessary. The Source is responsible for + * making sure that it is safe to modify this MeshDescription instance. + * @param ModifyFunction callback function that updates/modifies a MeshDescription + */ + virtual void CommitInPlaceModification(const TFunction& ModifyFunction) { check(false); } +}; + + + + + +/** + * Interface to a Factory that knows how to build Sources for UObjects. + */ +class IComponentSourceFactory +{ +public: + virtual ~IComponentSourceFactory() {} + + /** + * Create a MeshDescription source for the given Component + * @param Component A UObject that can provide a MeshDescription. Assumption is this is a Component of an Actor. + * @return A MeshDescriptionSource instance. Must not return null. + */ + virtual TUniquePtr MakeMeshDescriptionSource(UActorComponent* Component) = 0; +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputBehavior.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputBehavior.h new file mode 100644 index 000000000000..ea04f146831f --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputBehavior.h @@ -0,0 +1,301 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputState.h" +#include "Math/NumericLimits.h" +#include "InputBehavior.generated.h" + + + +/** + * Input can be captured separately for Left and Right sides (eg for VR controllers) + * Currently mouse is Left. + */ +UENUM() +enum class EInputCaptureSide +{ + None = 0, + Left = 1, + Right = 2, + Both = 3, + Any = 99 +}; + + +/** + * An active capturing behavior may need to keep track of additional data that + * cannot be stored within the behavior (for example if the same behavior instance + * is capturing for Left and Right separately). So FInputCaptureUpdate can optionally + * return this structure, and we will pass it to the next UpdateCapture() call + */ +struct INTERACTIVETOOLSFRAMEWORK_API FInputCaptureData +{ + /** Which side do we want to capture on */ + EInputCaptureSide WhichSide; + /** pointer to data defined by the InputBehavior, which is also responsible for cleaning it up */ + void* CustomData; + + FInputCaptureData() + { + WhichSide = EInputCaptureSide::None; + CustomData = nullptr; + } +}; + + +/** + * Used by FInputCaptureRequest to indicate whether the InputBehavior + * wants to capture or ignore an input event + */ +UENUM() +enum class EInputCaptureRequestType +{ + Begin = 1, + Ignore = 2 +}; + + +// predeclaration +class UInputBehavior; + + +/** + * UInputBehavior returns an FInputCaptureRequest from WantsCapture() to indicate + * whether it wants to capture or ignore an input event + */ +struct INTERACTIVETOOLSFRAMEWORK_API FInputCaptureRequest +{ + /** Which input behavior generated this request */ + UInputBehavior* Source; + /** What type of capture request is this (Begin or Ignore) */ + EInputCaptureRequestType Type; + /** Which side does request want to capture on */ + EInputCaptureSide Side; + + /** Depth along hit-test ray */ + float HitDepth; + + /** Owner of the requesting behavior. Behavior doesn't know this, so this is initialized to null */ + void* Owner; + + + FInputCaptureRequest(EInputCaptureRequestType type, UInputBehavior* behavior, EInputCaptureSide whichSide, float hitDepth = TNumericLimits::Max() ) + { + this->Type = type; + this->Source = behavior; + this->Side = whichSide; + this->Owner = nullptr; + this->HitDepth = hitDepth; + } + + /** Create a Begin-capture request */ + static FInputCaptureRequest Begin(UInputBehavior* behavior, EInputCaptureSide whichSide, float hitDepth = TNumericLimits::Max() ) + { + return FInputCaptureRequest(EInputCaptureRequestType::Begin, behavior, whichSide, hitDepth); + } + + /** Create an ignore-capture request */ + static FInputCaptureRequest Ignore() + { + return FInputCaptureRequest(EInputCaptureRequestType::Ignore, nullptr, EInputCaptureSide::Any, TNumericLimits::Max() ); + } + + friend bool operator<(const FInputCaptureRequest& l, const FInputCaptureRequest& r); +}; + + + + + + +/** + * FInputCaptureUpdate uses this type to indicate what state the capturing Behavior + * would like to transition to, based on the input event + */ +UENUM() +enum class EInputCaptureState +{ + Begin = 1, // start capturing (which should always be the case if BeginCapture is called) + Continue = 2, // Behavior wants to continue capturing + End = 3, // Behavior wants to end capturing + Ignore = 4 // Behavior ignored this event +}; + + + +/** + * IInputBehavior returns an FInputCaptureUpdate from BeginCapture() and UpdateCapture(), + * which indicates to the InputRouter what the Behavior would like to have happen. + */ +struct INTERACTIVETOOLSFRAMEWORK_API FInputCaptureUpdate +{ + /** Indicates what capture state the Behavior wants to transition to */ + EInputCaptureState State; + /** Which Behavior did this update come from */ + UInputBehavior* Source; + /** custom data for the active capture that should be propagated to next UpdateCapture() call */ + FInputCaptureData Data; + + /** + * Create a begin-capturing instance of FInputCaptureUpdate + * @param Source UInputBehavior that is returning this update + * @param Which Which side we are capturing on + * @param CustomData client-provided data that will be passed to UInputBehavior::UpdateCapture() calls. Client owns this memory! + */ + static FInputCaptureUpdate Begin(UInputBehavior* SourceBehavior, EInputCaptureSide WhichSide, void* CustomData = nullptr) + { + return FInputCaptureUpdate(EInputCaptureState::Begin, SourceBehavior, WhichSide, CustomData); + } + + /** Create a default continue-capturing instance of FInputCaptureUpdate */ + static FInputCaptureUpdate Continue() + { + return FInputCaptureUpdate(EInputCaptureState::Continue, nullptr, EInputCaptureSide::Any); + } + /** Create a default end-capturing instance of FInputCaptureUpdate */ + static FInputCaptureUpdate End() + { + return FInputCaptureUpdate(EInputCaptureState::End, nullptr, EInputCaptureSide::Any); + } + /** Create a default ignore-capturing instance of FInputCaptureUpdate */ + static FInputCaptureUpdate Ignore() + { + return FInputCaptureUpdate(EInputCaptureState::Ignore, nullptr, EInputCaptureSide::Any); + } + + /** + * @param StateIn desired capture state + * @param Source UInputBehavior that is returning this update + * @param Which Which side we are capturing on + * @param CustomData client-provided data that will be passed to UInputBehavior::UpdateCapture() calls. Client owns this memory! + */ + FInputCaptureUpdate(EInputCaptureState StateIn, UInputBehavior* SourceBehaviorIn, EInputCaptureSide WhichSideIn, void* CustomData = nullptr) + { + State = StateIn; + Source = SourceBehaviorIn; + Data.WhichSide = WhichSideIn; + Data.CustomData = CustomData; + } +}; + + + +/** + * Each UInputBehavior provides a priority that is used to help resolve situations + * when multiple Behaviors want to capture based on the same input event + */ +struct INTERACTIVETOOLSFRAMEWORK_API FInputCapturePriority +{ + static constexpr int DEFAULT_GIZMO_PRIORITY = 50; + static constexpr int DEFAULT_TOOL_PRIORITY = 100; + + /** Constant priority value */ + int Priority; + + FInputCapturePriority(int priority = DEFAULT_TOOL_PRIORITY) + { + Priority = priority; + } + + /** @return a priority lower than this priority */ + FInputCapturePriority MakeLower(int DeltaAmount = 1) const + { + return FInputCapturePriority(Priority + DeltaAmount); + } + + /** @return a priority higher than this priority */ + FInputCapturePriority MakeHigher(int DeltaAmount = 1) const + { + return FInputCapturePriority(Priority - DeltaAmount); + } + + friend bool operator<(const FInputCapturePriority& l, const FInputCapturePriority& r) + { + return l.Priority < r.Priority; + } + friend bool operator==(const FInputCapturePriority& l, const FInputCapturePriority& r) + { + return l.Priority == r.Priority; + } +}; + + + +/** + * An InputBehavior implements a state machine for a user interaction. + * The InputRouter maintains a set of active Behaviors, and when new input + * events occur, it calls WantsCapture() to check if the Behavior would like to + * begin capturing the applicable input event stream (eg for a mouse, one or both VR controllers, etc). + * If the Behavior acquires capture, UpdateCapture() is called until the Behavior + * indicates that it wants to release the device, or until the InputRouter force-terminates + * the capture via ForceEndCapture(). + * + * For example, something like ButtonSetClickBehavior might work as follows: + * - in WantsCapture(), if left mouse is pressed and a button is under cursor, return Begin, otherwise Ignore + * - in BeginCapture(), save identifier for button that is under cursor + * - in UpdateCapture() + * - if left mouse is down, return Continue + * - if left mouse is released: + * - if saved button is still under cursor, call button.Clicked() + * - return End + * + * Written sufficiently generically, the above Behavior doesn't need to know about buttons, + * it just needs to know how to hit-test the clickable object(s). Similarly separate + * Behaviors can be written for mouse, VR, touch, gamepad, etc. + * + * Implementing interactions in this way allows the input handling to be separated from functionality. + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInputBehavior : public UObject +{ + GENERATED_BODY() + +public: + UInputBehavior(); + + /** The priority is used to resolve situations where multiple behaviors want the same capture */ + virtual FInputCapturePriority GetPriority(); + + /** Configure the default priority of an instance of this behavior */ + virtual void SetDefaultPriority(const FInputCapturePriority& Priority); + + + /** Which device types does this Behavior support */ + virtual EInputDevices GetSupportedDevices(); + + /** Given the input state, does this Behavior want to begin capturing some input devices? */ + virtual FInputCaptureRequest WantsCapture(const FInputDeviceState& InputState); + + /** Called after WantsCapture() returns a capture request that was accepted */ + virtual FInputCaptureUpdate BeginCapture(const FInputDeviceState& InputState, EInputCaptureSide eSide); + + /** + * Called for each new input event during a capture sequence. Return Continue to keep + * capturing, or End to finish capturing. + */ + virtual FInputCaptureUpdate UpdateCapture(const FInputDeviceState& InputState, const FInputCaptureData& CaptureData); + + /** If this is called, the Behavior has forcibly lost capture (eg due to app losing focus for example) and needs to clean up accordingly */ + virtual void ForceEndCapture(const FInputCaptureData& CaptureData); + + + // + // hover support (optional) + // + + /** return true if this Behavior supports hover (ie passive input events) */ + virtual bool WantsHoverEvents(); + + /** called on each new hover input event, ie if no other behavior is actively capturing input */ + virtual void UpdateHover(const FInputDeviceState& InputState); + + /** if a capture begins or focus is lost, any active hover visualization needs to terminate */ + virtual void EndHover(const FInputDeviceState& InputState); + + +protected: + /** priority returned by GetPriority() */ + FInputCapturePriority DefaultPriority; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputBehaviorSet.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputBehaviorSet.h new file mode 100644 index 000000000000..44f4514685a0 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputBehaviorSet.h @@ -0,0 +1,179 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "InputBehavior.h" +#include "InputBehaviorSet.generated.h" + + + +/** + * This is an internal structure used by UInputBehaviorSet. + */ +USTRUCT() +struct INTERACTIVETOOLSFRAMEWORK_API FBehaviorInfo +{ + GENERATED_BODY() + + /** Reference to a Behavior */ + UPROPERTY() + UInputBehavior* Behavior; + + /** Source object that provided this Behavior */ + void* Source; + + /** Group identifier for this Behavior */ + FString Group; + + friend bool operator<(const FBehaviorInfo& l, const FBehaviorInfo& r) + { + return l.Behavior->GetPriority() < r.Behavior->GetPriority(); + } +}; + + + + +/** + * UInputBehaviorSet manages a set of UInputBehaviors, and provides various functions + * to query and forward events to the set. Tools and Widgets provide instances of this via + * IInputBehaviorSource, and UInputRouter collects and manages them (see comments there) + * + * Behaviors in the set each have a source pointer and group tag, which allows sets of + * behaviors to be managed together. For example one UInputBehaviorSet can be merged into + * another and removed later. + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInputBehaviorSet : public UObject +{ + GENERATED_BODY() + +public: + UInputBehaviorSet(); + virtual ~UInputBehaviorSet(); + + // + // Set Management + // + + + /** @return true if there are no Behaviors in set */ + virtual bool IsEmpty() const; + + /** + * Add a Behavior to the set + * @param Behavior Behavior to add to set + * @param Source pointer to owning object, used only to identify Behavior later + * @param GroupName string identifier for this Behavior or group of Behaviors + */ + virtual void Add(UInputBehavior* Behavior, void* Source = nullptr, const FString& GroupName = ""); + + /** + * Merge another BehaviorSet into this Set + * @param OtherSet Set of Behaviors to add to this Set + * @param Source pointer to owning object, used only to identify Behavior later. If nullptr, source is copied from other Set. + * @param NewGroupName string identifier for this Behavior or group of Behaviors. If empty string, group is copied from other Set. + */ + virtual void Add(const UInputBehaviorSet* OtherSet, void* NewSource = nullptr, const FString& NewGroupName = ""); + + /** + * Remove a Behavior from the Set + * @param Behavior Behavior to remove + * @return true if Behavior was found and removed + */ + virtual bool Remove(UInputBehavior* Behavior); + /** + * Remove a group of Behaviors from the Set + * @param GroupName name of group, all Behaviors that were added with this GroupName are removed + * @return true if any Behaviors were found and removed + */ + virtual bool RemoveByGroup(const FString& GroupName); + /** + * Remove a group of Behaviors from the Set + * @param Source source object pointer, all Behaviors that were added with this Source pointer are removed. + * @return true if any Behaviors were found and removed + */ + virtual bool RemoveBySource(void* Source); + /** + * Remove all Behaviors from the set + */ + virtual void RemoveAll(); + + + // + // Queries and Event Forwarding + // + + + /** + * Call UInputBehavior::WantsCapture() on each valid Behavior and collect up the requests that indicated a Capture was desired. + * @param InputState current input device state information + * @param ResultOut returned set of non-Ignoring FInputCaptureRequests returned by Behaviors + */ + virtual void CollectWantsCapture(const FInputDeviceState& InputState, TArray& ResultOut); + + /** + * Call UInputBehavior::UpdateHover() on each valid Behavior that wants hover events + * @param InputState current input device state information + * @return true if any InputBehavior indicated that it wants hover events + */ + virtual bool UpdateHover(const FInputDeviceState& InputState); + + /** + * Call UInputBehavior::EndHover() on each valid Behavior that wants hover events + * @param InputState current input device state information + * @return true if any InputBehavior indicated that it wants hover events + */ + virtual bool EndHover(const FInputDeviceState& InputState); + + +protected: + + /** Current set of known Behaviors */ + UPROPERTY() + TArray Behaviors; + + /** + * called internally when Behaviors list is updated, to re-sort by Priority, etc (@todo: and emit change event) + */ + virtual void BehaviorsModified(); + + + /** @return true if Behavior supports InputState.InputDevice */ + bool SupportsInputType(UInputBehavior* Behavior, const FInputDeviceState& InputState) + { + return (Behavior->GetSupportedDevices() & InputState.InputDevice) != EInputDevices::None; + } + +}; + + + + + +// UInterface for IInputBehavior +UINTERFACE(MinimalAPI) +class UInputBehaviorSource : public UInterface +{ + GENERATED_BODY() +}; + + +/** + * UObjects that implement IInputBehaviorSource have an UInputBehaviorSet + * that they can provide (to UInputRouter, primarily) + * @todo callback/delegate for when the provided InputBehaviorSet changes + */ +class IInputBehaviorSource +{ + GENERATED_BODY() + +public: + /** + * @return The current UInputBehaviorSet for this Source + */ + virtual const UInputBehaviorSet* GetInputBehaviors() const = 0; +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputRouter.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputRouter.h new file mode 100644 index 000000000000..6dc65da4b7ce --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputRouter.h @@ -0,0 +1,105 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Misc/Change.h" +#include "InputState.h" +#include "InputBehaviorSet.h" +#include "ToolContextInterfaces.h" +#include "InputRouter.generated.h" + + +/** + * UInputRouter mediates between a higher-level input event source (eg like an FEdMode) + * and a set of InputBehaviors that respond to those events. Sets of InputBehaviors are + * registered, and then PostInputEvent() is called for each event. + * + * Internally one of the active Behaviors may "capture" the event stream. + * Separate "Left" and "Right" captures are supported, which means that (eg) + * an independent capture can be tracked for each VR controller. + * + * If the input device supports "hover", PostHoverInputEvent() will forward + * hover events to InputBehaviors that also support it. + * + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInputRouter : public UObject +{ + GENERATED_BODY() + +protected: + friend class UInteractiveToolsContext; // to call Initialize/Shutdown + + UInputRouter(); + + /** Initialize the InputRouter with the necessary Context-level state. UInteractiveToolsContext calls this, you should not. */ + virtual void Initialize(IToolsContextTransactionsAPI* TransactionsAPI); + + /** Shutdown the InputRouter. Called by UInteractiveToolsContext. */ + virtual void Shutdown(); + +public: + /** Add a new behavior Source. Behaviors from this source will be added to the active behavior set. */ + virtual void RegisterSource(IInputBehaviorSource* Source); + + /** Remove Behaviors from this Source from the active set */ + virtual void DeregisterSource(IInputBehaviorSource* Source); + + /** Insert a new input event which is used to check for new captures, or forwarded to active capture */ + virtual void PostInputEvent(const FInputDeviceState& Input); + + /** Returns true if there is an active mouse capture */ + virtual bool HasActiveMouseCapture() const; + + // TODO: other capture queries + + /** Insert a new hover input event which is forwarded to all hover-enabled Behaviors */ + virtual void PostHoverInputEvent(const FInputDeviceState& Input); + + /** If this Behavior is capturing, call ForceEndCapture() to notify that we are taking capture away */ + virtual void ForceTerminateSource(IInputBehaviorSource* Source); + + /** Terminate any active captures and end all hovers */ + virtual void ForceTerminateAll(); + + +public: + /** If true, then we post an Invalidation (ie redraw) request if any active InputBehavior responds to Hover events (default false) */ + UPROPERTY() + bool bAutoInvalidateOnHover; + + /** If true, then we post an Invalidation (ie redraw) request on every captured input event (default false) */ + UPROPERTY() + bool bAutoInvalidateOnCapture; + + +protected: + IToolsContextTransactionsAPI* TransactionsAPI; + + UPROPERTY() + UInputBehaviorSet* ActiveInputBehaviors; + + UInputBehavior* ActiveKeyboardCapture; + void* ActiveKeyboardCaptureOwner; + FInputCaptureData ActiveKeyboardCaptureData; + + UInputBehavior* ActiveLeftCapture; + void* ActiveLeftCaptureOwner; + FInputCaptureData ActiveLeftCaptureData; + + UInputBehavior* ActiveRightCapture; + void* ActiveRightCaptureOwner; + FInputCaptureData ActiveRightCaptureData; + + FInputDeviceState LastHoverInput; + + virtual void PostInputEvent_Keyboard(const FInputDeviceState& Input); + void CheckForKeyboardCaptures(const FInputDeviceState& Input); + void HandleCapturedKeyboardInput(const FInputDeviceState& Input); + + virtual void PostInputEvent_Mouse(const FInputDeviceState& Input); + void CheckForMouseCaptures(const FInputDeviceState& Input); + void HandleCapturedMouseInput(const FInputDeviceState& Input); +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputState.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputState.h new file mode 100644 index 000000000000..32bc553e992f --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InputState.h @@ -0,0 +1,248 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "InputCoreTypes.h" +#include "Math/UnrealMath.h" + + +/** + * Input event data can be applicable to many possible input devices. + * These flags are used to indicate specific or sets of device types. + */ +UENUM() +enum class EInputDevices +{ + None = 0, + Keyboard = 1, + Mouse = 2, + Gamepad = 4, + + OculusTouch = 8, + HTCViveWands = 16, + AnySpatialDevice = OculusTouch | HTCViveWands, + + TabletFingers = 1024 +}; +ENUM_CLASS_FLAGS(EInputDevices); + + + +/** + * FInputDeviceRay represents a 3D ray created based on an input device. + * If the device is a 2D input device like a mouse, then the ray may + * have an associated 2D screen position. + */ +struct FInputDeviceRay +{ + /** 3D ray in 3D scene, in world coordinates */ + FRay WorldRay; + + /** If true, WorldRay has 2D device position coordinates */ + bool bHas2D; + + /** 2D device position coordinates associated with the ray */ + FVector2D ScreenPosition; + + + FInputDeviceRay(const FRay& WorldRayIn) + { + WorldRay = WorldRayIn; + bHas2D = false; + ScreenPosition = FVector2D(0, 0); + } + + FInputDeviceRay(const FRay& WorldRayIn, const FVector2D& ScreenPositionIn) + { + WorldRay = WorldRayIn; + bHas2D = true; + ScreenPosition = ScreenPositionIn; + } +}; + + +/** + * Current State of a physical device button (mouse, key, etc) at a point in time. + * Each "click" of a button should involve at minimum two separate state + * events, one where bPressed=true and one where bReleased=true. + * Each of these states should occur only once. + * In addition there may be additional frames where the button is + * held down and bDown=true and bPressed=false. + */ +struct FDeviceButtonState +{ + /** Button identifier */ + FKey Button; + + /** Was the button pressed down this frame. This should happen once per "click" */ + bool bPressed; + /** Is the button currently pressed down. This should be true every frame the button is pressed. */ + bool bDown; + /** Was the button released this frame. This should happen once per "click" */ + bool bReleased; + + FDeviceButtonState() + { + Button = FKey(); + bPressed = bDown = bReleased = false; + } + + FDeviceButtonState(const FKey& ButtonIn) + { + Button = ButtonIn; + bPressed = bDown = bReleased = false; + } + + /** Update the states of this button */ + void SetStates(bool bPressedIn, bool bDownIn, bool bReleasedIn) + { + bPressed = bPressedIn; + bDown = bDownIn; + bReleased = bReleasedIn; + } +}; + + + +/** + * Current state of active keyboard key at a point in time + * @todo would be useful to track set of active keys + */ +struct FKeyboardInputDeviceState +{ + /** state of active key that was modified (ie press or release) */ + FDeviceButtonState ActiveKey; +}; + + + +/** + * Current State of a physical Mouse device at a point in time. + */ +struct FMouseInputDeviceState +{ + /** State of the left mouse button */ + FDeviceButtonState Left; + /** State of the middle mouse button */ + FDeviceButtonState Middle; + /** State of the right mouse button */ + FDeviceButtonState Right; + + /** Change in 'ticks' of the mouse wheel since last state event */ + float WheelDelta; + + /** Current 2D position of the mouse, in application-defined coordinate system */ + FVector2D Position2D; + + /** Change in 2D mouse position from last state event */ + FVector2D Delta2D; + + /** Ray into current 3D scene at current 2D mouse position */ + FRay WorldRay; + + + FMouseInputDeviceState() + { + Left = FDeviceButtonState(EKeys::LeftMouseButton); + Middle = FDeviceButtonState(EKeys::MiddleMouseButton); + Right = FDeviceButtonState(EKeys::RightMouseButton); + WheelDelta = false; + Position2D = FVector2D::ZeroVector; + Delta2D = FVector2D::ZeroVector; + WorldRay = FRay(); + } +}; + + + +/** + * Current state of physical input devices at a point in time. + * Assumption is that the state refers to a single physical input device, + * ie InputDevice field is a single value of EInputDevices and not a combination. + */ +struct FInputDeviceState +{ + /** Which InputDevice member is valid in this state */ + EInputDevices InputDevice; + + // + // keyboard modifiers + // + + /** Is they keyboard SHIFT modifier key currently pressed down */ + bool bShiftKeyDown; + /** Is they keyboard ALT modifier key currently pressed down */ + bool bAltKeyDown; + /** Is they keyboard CTRL modifier key currently pressed down */ + bool bCtrlKeyDown; + /** Is they keyboard CMD modifier key currently pressed down (only on Apple devices) */ + bool bCmdKeyDown; + + + /** Current state of Keyboard device, if InputDevice == EInputDevices::Keyboard */ + FKeyboardInputDeviceState Keyboard; + + /** Current state of Mouse device, if InputDevice == EInputDevices::Mouse */ + FMouseInputDeviceState Mouse; + + + FInputDeviceState() + { + InputDevice = EInputDevices::None; + bShiftKeyDown = bAltKeyDown = bCtrlKeyDown = bCmdKeyDown = false; + Keyboard = FKeyboardInputDeviceState(); + Mouse = FMouseInputDeviceState(); + } + + /** Update keyboard modifier key states */ + void SetModifierKeyStates(bool bShiftDown, bool bAltDown, bool bCtrlDown, bool bCmdDown) + { + bShiftKeyDown = bShiftDown; + bAltKeyDown = bAltDown; + bCtrlKeyDown = bCtrlDown; + bCmdKeyDown = bCmdDown; + } + + /** + * @param DeviceType Combination of device-type flags + * @return true if this input state is for an input device that matches the query flags + */ + bool IsFromDevice(EInputDevices DeviceType) const + { + return ((InputDevice & DeviceType) != EInputDevices::None); + } + + + + + // + // utility functions to pass as lambdas + // + + /** @return true if shift key is down in input state */ + static bool IsShiftKeyDown(const FInputDeviceState& InputState) + { + return InputState.bShiftKeyDown; + } + + /** @return true if ctrl key is down in input state */ + static bool IsCtrlKeyDown(const FInputDeviceState& InputState) + { + return InputState.bCtrlKeyDown; + } + + /** @return true if alt key is down in input state */ + static bool IsAltKeyDown(const FInputDeviceState& InputState) + { + return InputState.bAltKeyDown; + } + + /** @return true if Apple Command key is down in input state */ + static bool IsCmdKeyDown(const FInputDeviceState& InputState) + { + return InputState.bCmdKeyDown; + } +}; + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmo.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmo.h new file mode 100644 index 000000000000..808b11f22f1f --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmo.h @@ -0,0 +1,83 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputBehaviorSet.h" +#include "ToolContextInterfaces.h" +#include "InteractiveGizmo.generated.h" + +class UInteractiveGizmoManager; + + + +/** + * UInteractiveGizmo is the base class for all Gizmos in the InteractiveToolsFramework. + * + * @todo callback/delegate for if/when .InputBehaviors changes + * @todo callback/delegate for when Gizmo properties change + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveGizmo : public UObject, public IInputBehaviorSource +{ + GENERATED_BODY() + +public: + UInteractiveGizmo(); + + /** + * Called by GizmoManager to initialize the Gizmo *after* GizmoBuilder::BuildGizmo() has been called + */ + virtual void Setup(); + + /** + * Called by GizmoManager to shut down the Gizmo + */ + virtual void Shutdown(); + + /** + * Allow the Gizmo to do any custom drawing (ie via PDI/RHI) + * @param RenderAPI Abstraction that provides access to Rendering in the current ToolsContext + */ + virtual void Render(IToolsContextRenderAPI* RenderAPI); + + /** + * Allow the Gizmo to do any necessary processing on Tick + * @param DeltaTime the time delta since last tick + */ + virtual void Tick(float DeltaTime); + + + + /** + * @return GizmoManager that owns this Gizmo + */ + virtual UInteractiveGizmoManager* GetGizmoManager() const; + + + + // + // Input Behaviors support + // + + /** + * Add an input behavior for this Gizmo + * @param Behavior behavior to add + */ + virtual void AddInputBehavior(UInputBehavior* Behavior); + + /** + * @return Current input behavior set. + */ + virtual const UInputBehaviorSet* GetInputBehaviors() const; + + + +protected: + + /** The current set of InputBehaviors provided by this Gizmo */ + UPROPERTY() + UInputBehaviorSet* InputBehaviors; +}; + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmoBuilder.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmoBuilder.h new file mode 100644 index 000000000000..ee26a8fcb2d6 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmoBuilder.h @@ -0,0 +1,34 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "InteractiveGizmo.h" +#include "InteractiveGizmoBuilder.generated.h" + + + +/** + * A UInteractiveGizmoBuilder creates a new instance of an InteractiveGizmo (basically this is a Factory). + * These are registered with the InteractiveGizmoManager, which calls BuildGizmo(). + * This is an abstract base class, you must subclass it in order to create your particular Gizmo instance + */ +UCLASS(Transient, Abstract) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveGizmoBuilder : public UObject +{ + GENERATED_BODY() + +public: + + /** + * Create a new instance of this builder's Gizmo + * @param SceneState the current scene selection state, etc + * @return a new instance of the Gizmo, or nullptr on error/failure + */ + virtual UInteractiveGizmo* BuildGizmo(const FToolBuilderState& SceneState) const + { + unimplemented(); + return nullptr; + } +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmoManager.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmoManager.h new file mode 100644 index 000000000000..ebe21894f044 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveGizmoManager.h @@ -0,0 +1,179 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Misc/Change.h" +#include "InteractiveGizmo.h" +#include "InteractiveGizmoBuilder.h" +#include "InputRouter.h" +#include "ToolContextInterfaces.h" +#include "InteractiveGizmoManager.generated.h" + + + +USTRUCT() +struct FActiveGizmo +{ + GENERATED_BODY(); + + UInteractiveGizmo* Gizmo; + FString BuilderIdentifier; + FString InstanceIdentifier; +}; + + +/** + * UInteractiveGizmoManager allows users of the Tools framework to create and operate Gizmo instances. + * For each Gizmo, a (string,GizmoBuilder) pair is registered with the GizmoManager. + * Gizmos can then be activated via the string identifier. + * + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveGizmoManager : public UObject +{ + GENERATED_BODY() + +protected: + friend class UInteractiveToolsContext; // to call Initialize/Shutdown + + UInteractiveGizmoManager(); + + /** Initialize the GizmoManager with the necessary Context-level state. UInteractiveToolsContext calls this, you should not. */ + virtual void Initialize(IToolsContextQueriesAPI* QueriesAPI, IToolsContextTransactionsAPI* TransactionsAPI, UInputRouter* InputRouter); + + /** Shutdown the GizmoManager. Called by UInteractiveToolsContext. */ + virtual void Shutdown(); + +public: + + // + // GizmoBuilder Registration and Gizmo Creation/Shutdown + // + + /** + * Register a new GizmoBuilder + * @param BuilderIdentifier string used to identify this Builder + * @param Builder new GizmoBuilder instance + */ + virtual void RegisterGizmoType(const FString& BuilderIdentifier, UInteractiveGizmoBuilder* Builder); + + /** + * Remove a GizmoBuilder from the set of known GizmoBuilders + * @param BuilderIdentifier identification string that was passed to RegisterGizmoType() + * @return true if Builder was found and deregistered + */ + virtual bool DeregisterGizmoType(const FString& BuilderIdentifier); + + + + /** + * Try to activate a new Gizmo instance on the given Side + * @param BuilderIdentifier string used to identify Builder that should be called + * @param InstanceIdentifier client-defined string that can be used to locate this instance + * @return new Gizmo instance that has been created and initialized + */ + virtual UInteractiveGizmo* CreateGizmo(const FString& BuilderIdentifier, const FString& InstanceIdentifier ); + + + /** + * Shutdown and remove a Gizmo + * @param Gizmo the Gizmo to shutdown and remove + * @return true if the Gizmo was found and removed + */ + virtual bool DestroyGizmo(UInteractiveGizmo* Gizmo); + + /** + * Destroy all Gizmos that were created by the identified GizmoBuilder + * @param BuilderIdentifier the Builder string registered with RegisterGizmoType + */ + virtual void DestroyAllGizmosOfType(const FString& BuilderIdentifier); + + /** + * Find all the existing Gizmo instances that were created by the identified GizmoBuilder + * @param BuilderIdentifier the Builder string registered with RegisterGizmoType + * @return list of found Gizmos + */ + virtual TArray FindAllGizmosOfType(const FString& BuilderIdentifier); + + /** + * Find the Gizmo that was created with the given instance identifier + * @param Identifier the InstanceIdentifier that was passed to CreateGizmo() + * @return the found Gizmo, or null + */ + virtual UInteractiveGizmo* FindGizmoByInstanceIdentifier(const FString& Identifier); + + + + + // + // Functions that Gizmos can call to interact with Transactions API + // + + /** Post a message via the Transactions API */ + virtual void PostMessage(const TCHAR* Message, EToolMessageLevel Level); + + /** Post a message via the Transactions API */ + virtual void PostMessage(const FString& Message, EToolMessageLevel Level); + + /** Request an Invalidation via the Transactions API (ie to cause a repaint, etc) */ + virtual void PostInvalidation(); + + /** + * Request that the Context open a Transaction, whatever that means to the current Context + * @param Description text description of this transaction (this is the string that appears on undo/redo in the UE Editor) + */ + virtual void BeginUndoTransaction(const FText& Description); + + /** Request that the Context close and commit the open Transaction */ + virtual void EndUndoTransaction(); + + /** + * Forward an FChange object to the Context + * @param TargetObject the object that the FChange applies to + * @param Change the change object that the Context should insert into the transaction history + * @param Description text description of this change (this is the string that appears on undo/redo in the UE Editor) + */ + virtual void EmitObjectChange(UObject* TargetObject, TUniquePtr Change, const FText& Description ); + + + // + // State control (@todo: have the Context call these? not safe for anyone to call) + // + + /** Tick any active Gizmos. Called by UInteractiveToolsContext */ + virtual void Tick(float DeltaTime); + + /** Render any active Gizmos. Called by UInteractiveToolsContext. */ + virtual void Render(IToolsContextRenderAPI* RenderAPI); + + + + // + // access to APIs, etc + // + + /** @return current IToolsContextQueriesAPI */ + virtual IToolsContextQueriesAPI* GetContextQueriesAPI() { return QueriesAPI; } + + +public: + /** set of Currently-active Gizmos */ + UPROPERTY() + TArray ActiveGizmos; + + +protected: + /** Current Context-Queries implementation */ + IToolsContextQueriesAPI* QueriesAPI; + /** Current Transactions implementation */ + IToolsContextTransactionsAPI* TransactionsAPI; + + /** Current InputRouter (Context owns this) */ + UInputRouter* InputRouter; + + /** Current set of named GizmoBuilders */ + UPROPERTY() + TMap GizmoBuilders; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveTool.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveTool.h new file mode 100644 index 000000000000..b26f1c408981 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveTool.h @@ -0,0 +1,239 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputBehaviorSet.h" +#include "InteractiveToolActionSet.h" +#include "ToolContextInterfaces.h" +#include "InteractiveTool.generated.h" + +class UInteractiveToolManager; + + +/** Passed to UInteractiveTool::Shutdown to indicate how Tool should shut itself down*/ +enum class EToolShutdownType +{ + /** Tool cleans up and exits. Pass this to tools that do not have Accept/Cancel options. */ + Completed = 0, + /** Tool commits current preview to scene */ + Accept = 1, + /** Tool discards current preview without modifying scene */ + Cancel = 2 +}; + + +/** This delegate is used by UInteractiveToolPropertySet */ +DECLARE_MULTICAST_DELEGATE_TwoParams(FInteractiveToolPropertySetModifiedSignature, UObject*, UProperty*); + + +/** + * a UInteractiveTool contains a set of UObjects that contain "properties" of the Tool, ie + * the configuration flags, parameters, etc that control the Tool. Currently any UObject + * can be added as a property set, however there is no automatic mechanism for those child + * UObjects to notify the Tool when a property changes. + * + * If you make your property set UObjects subclasses of UInteractiveToolPropertySet, then + * when the properties are changed in the Editor, the parent Tool will be automatically notified. + * You can override UInteractiveTool::OnPropertyModified() to act on these notifications + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveToolPropertySet : public UObject +{ + GENERATED_BODY() + +protected: + FInteractiveToolPropertySetModifiedSignature OnModified; + +public: + + /** @return the multicast delegate that is called when properties are modified */ + FInteractiveToolPropertySetModifiedSignature& GetOnModified() + { + return OnModified; + } + + /** posts a message to the OnModified delegate with the modified UProperty */ + void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) + { + OnModified.Broadcast(this, PropertyChangedEvent.Property); + } +}; + + +/** + * UInteractiveTool is the base class for all Tools in the InteractiveToolsFramework. + * A Tool is is a "lightweight mode" that may "own" one or more Actors/Components/etc in + * the current scene, may capture certain input devices or event streams, and so on. + * The base implementation essentially does nothing but provide sane default behaviors. + * + * The BaseTools/ subfolder contains implementations of various kinds of standard + * "tool behavior", like a tool that responds to a mouse click, etc, that can be + * extended to implement custom behaviors. + * + * In the framework, you do not create instances of UInteractiveTool yourself. + * You provide a UInteractiveToolBuilder implementation that can properly construct + * an instance of your Tool, this is where for example default parameters would be set. + * The ToolBuilder is registered with the ToolManager, and then UInteractiveToolManager::ActivateTool() + * is used to kick things off. + * + * @todo callback/delegate for if/when .InputBehaviors changes + * @todo callback/delegate for when tool properties change + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveTool : public UObject, public IInputBehaviorSource +{ + GENERATED_BODY() + +public: + UInteractiveTool(); + + /** + * Called by ToolManager to initialize the Tool *after* ToolBuilder::BuildTool() has been called + */ + virtual void Setup(); + + /** + * Called by ToolManager to shut down the Tool + * @param ShutdownType indicates how the tool should shutdown (ie Accept or Cancel current preview, etc) + */ + virtual void Shutdown(EToolShutdownType ShutdownType); + + /** + * Allow the Tool to do any custom drawing (ie via PDI/RHI) + * @param RenderAPI Abstraction that provides access to Rendering in the current ToolsContext + */ + virtual void Render(IToolsContextRenderAPI* RenderAPI); + + /** + * Allow the Tool to do any necessary processing on Tick + * @param DeltaTime the time delta since last tick + */ + virtual void Tick(float DeltaTime); + + + + /** + * @return ToolManager that owns this Tool + */ + virtual UInteractiveToolManager* GetToolManager() const; + + + /** + * @return true if this Tool support being Cancelled, ie calling Shutdown(EToolShutdownType::Cancel) + */ + virtual bool HasCancel() const; + + /** + * @return true if this Tool support being Accepted, ie calling Shutdown(EToolShutdownType::Accept) + */ + virtual bool HasAccept() const; + + /** + * @return true if this Tool is currently in a state where it can be Accepted. This may be false if for example there was an error in the Tool. + */ + virtual bool CanAccept() const; + + + + // + // Input Behaviors support + // + + /** + * Add an input behavior for this Tool + * @param Behavior behavior to add + */ + virtual void AddInputBehavior(UInputBehavior* Behavior); + + /** + * @return Current input behavior set. + */ + virtual const UInputBehaviorSet* GetInputBehaviors() const; + + + // + // Property support + // + + /** + * @return list of property UObjects for this tool (ie to add to a DetailsViewPanel, for example) + */ + virtual const TArray& GetToolProperties() const; + + + /** + * Automatically called by UInteractiveToolPropertySet.OnModified delegate to notify Tool of child property set changes + * @param PropertySet which UInteractiveToolPropertySet was modified + * @param Property which UProperty in the set was modified + */ + virtual void OnPropertyModified(UObject* PropertySet, UProperty* Property) + { + } + + +protected: + + /** The current set of InputBehaviors provided by this Tool */ + UPROPERTY() + UInputBehaviorSet* InputBehaviors; + + /** The current set of Property UObjects provided by this Tool. May contain pointer to itself. */ + UPROPERTY() + TArray ToolPropertyObjects; + + /** + * Add a Property object for this Tool + * @param Property object to add + */ + virtual void AddToolPropertySource(UObject* PropertyObject); + + /** + * Add a PropertySet object for this Tool + * @param PropertySet Property Set object to add + */ + virtual void AddToolPropertySource(UInteractiveToolPropertySet* PropertySet); + + + + + // + // Action support/system + // + // Your Tool subclass can register a set of "Actions" it can execute + // by overloading RegisterActions(). Then external systems can use GetActionSet() to + // find out what Actions your Tool supports, and ExecuteAction() to run those actions. + // + +public: + /** + * Get the internal Action Set for this Tool. The action set is created and registered on-demand. + * @return pointer to initialized Action set + */ + virtual FInteractiveToolActionSet* GetActionSet(); + + /** + * Request that the Action identified by ActionID be executed. + * Default implementation forwards these requests to internal ToolActionSet. + */ + virtual void ExecuteAction(int32 ActionID); + + +protected: + /** + * Override this function to register the set of Actions this Tool supports, using FInteractiveToolActionSet::RegisterAction. + * Note that + */ + virtual void RegisterActions(FInteractiveToolActionSet& ActionSet); + + +private: + /** + * Set of actions this Tool can execute. This variable is allocated on-demand. + * Use GetActionSet() instead of accessing this pointer directly! + */ + FInteractiveToolActionSet* ToolActionSet = nullptr; + +}; + + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolActionSet.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolActionSet.h new file mode 100644 index 000000000000..33ce92763911 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolActionSet.h @@ -0,0 +1,114 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GenericPlatform/GenericApplication.h" +#include "InputCoreTypes.h" + +class UInteractiveTool; + + +/** + * Standard Actions that can be shared across multiple Tools. + * The enum values are used as FInteractiveToolAction.ActionID. + * If you need to define your own values, start at BaseClientDefinedActionID + */ +enum class EStandardToolActions +{ + IncreaseBrushSize = 100, + DecreaseBrushSize = 101, + + + BaseClientDefinedActionID = 10000 +}; + + + +/** + * FInteractiveToolAction is returned by a UInteractiveTool to represent + * an "Action" the Tool can execute. + */ +struct INTERACTIVETOOLSFRAMEWORK_API FInteractiveToolAction +{ + /** Which type of UInteractiveTool this Action can be applied to*/ + const UClass* ClassType; + + /** Identifier for this Action */ + int32 ActionID; + + /** Internal name for this Action */ + FString ActionName; + /** Short name for this Action */ + FString ShortName; + /** Descriptive name for this Action */ + FString Description; + + /** Suggested modifier keys for this Action */ + EModifierKey::Type DefaultModifiers; + /** Suggested keybinding for this Action. */ + FKey DefaultKey; + + /** Call this function to execute the Action */ + TFunction OnAction; + + + FInteractiveToolAction() + { + ClassType = nullptr; + ActionID = 0; + } + + FInteractiveToolAction(const UClass* ClassTypeIn, int32 ActionIDIn, + const FString& ActionNameIn, const FString& ShortNameIn, const FString& DescriptionIn, + EModifierKey::Type DefaultModifiersIn, const FKey& DefaultKeyIn ) + { + ClassType = ClassTypeIn; + ActionID = ActionIDIn; + ActionName = ActionNameIn; + ShortName = ShortNameIn; + Description = DescriptionIn; + DefaultModifiers = DefaultModifiersIn; + DefaultKey = DefaultKeyIn; + } + +}; + + + +/** + * FInteractiveToolActionSet maintains a list of FInteractiveToolAction. + * Each UInteractiveTool contains an instance of this class. + */ +class INTERACTIVETOOLSFRAMEWORK_API FInteractiveToolActionSet +{ +public: + + /** + * Register an Action with the ActionSet. This function is intended to be called by + * UInteractiveTool::RegisterActions() implementations + */ + void RegisterAction(UInteractiveTool* Tool, int32 ActionID, + const FString& ActionName, const FString& ShortUIName, const FString& DescriptionText, + EModifierKey::Type Modifiers, const FKey& ShortcutKey, + TFunction ActionFunction ); + + /** + * Find an existing Action by ID + * @return located Action, or nullptr if not found + */ + const FInteractiveToolAction* FindActionByID(int32 ActionID) const; + + /** + * Return the internal list of registered Actions by adding to the OutActions array + */ + void CollectActions(TArray& OutActions) const; + + /** + * Execute the action identified by ActionID + */ + void ExecuteAction(int32 ActionID) const; + +protected: + TArray Actions; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolBuilder.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolBuilder.h new file mode 100644 index 000000000000..bb89bb610f83 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolBuilder.h @@ -0,0 +1,46 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "InteractiveTool.h" +#include "InteractiveToolBuilder.generated.h" + + + +/** + * A UInteractiveToolBuilder creates a new instance of an InteractiveTool (basically this is a Factory). + * These are registered with the InteractiveToolManager, which calls BuildTool() if CanBuildTool() returns true. + * In addition CanBuildTool() will be queried to (for example) enable/disable UI buttons, etc. + * This is an abstract base class, you must subclass it in order to create your particular Tool instance + */ +UCLASS(Transient, Abstract) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveToolBuilder : public UObject +{ + GENERATED_BODY() + +public: + + /** + * Check if, given the current scene state, a new instance of this builder's Tool can be created + * @param SceneState the current scene selection state, etc + * @return true if a new Tool instance can be created + */ + virtual bool CanBuildTool(const FToolBuilderState& SceneState) const + { + check(false); + return false; + } + + /** + * Create a new instance of this builder's Tool + * @param SceneState the current scene selection state, etc + * @return a new instance of the Tool, or nullptr on error/failure + */ + virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const + { + check(false); + return nullptr; + } +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolManager.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolManager.h new file mode 100644 index 000000000000..6a9a23af5694 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolManager.h @@ -0,0 +1,235 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Misc/Change.h" +#include "InteractiveTool.h" +#include "InteractiveToolBuilder.h" +#include "InputRouter.h" +#include "ToolContextInterfaces.h" +#include "InteractiveToolManager.generated.h" + + + +/** A Tool can be activated on a particular input device, currently identified by a "side" */ +UENUM() +enum class EToolSide +{ + /** Left-hand Tool, also used for Mouse */ + Left = 1, + Mouse = 1, + /** Right-hand Tool*/ + Right = 2, +}; + + +/** + * UInteractiveToolManager allows users of the tools framework to create and operate Tool instances. + * For each Tool, a (string,ToolBuilder) pair is registered with the ToolManager. + * Tools can then be activated via the string identifier. + * + * Currently a single Tool can be active for each input device. So for mouse input a single + * Tool is available and effectively a lightweight mode. The mouse uses the "Left" tool slot. + * + * For VR controllers and touch input, a "Left" and "Right" tool can be active at the same time. + * @todo this is not fully supported yet + * + * Tools are not directly created. Use SelectActiveToolType(side,string) to set the active ToolBuilder + * on a given side, and then use ActivateTool() to create the new Tool instance. + * + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveToolManager : public UObject +{ + GENERATED_BODY() + +protected: + friend class UInteractiveToolsContext; // to call Initialize/Shutdown + + UInteractiveToolManager(); + + /** Initialize the ToolManager with the necessary Context-level state. UInteractiveToolsContext calls this, you should not. */ + virtual void Initialize(IToolsContextQueriesAPI* QueriesAPI, IToolsContextTransactionsAPI* TransactionsAPI, UInputRouter* InputRouter); + + /** Shutdown the ToolManager. Called by UInteractiveToolsContext. */ + virtual void Shutdown(); + +public: + + // + // Tool registration and Current Tool state + // + + /** + * Register a new ToolBuilder + * @param Identifier string used to identify this Builder + * @param Builder new ToolBuilder instance + */ + virtual void RegisterToolType(const FString& Identifier, UInteractiveToolBuilder* Builder); + + /** + * Set active ToolBuilder for a ToolSide via string identifier + * @param Side which "side" should we set this Builder on + * @param Identifier name of ToolBuilder that was passed to RegisterToolType() + */ + virtual bool SelectActiveToolType(EToolSide Side, const FString& Identifier); + + /** + * Check if a named Tool type can currently be activated on the given ToolSide + * @param Side which "side" you would like to active the tool on + * @param Identifier string name of the Tool type + * @return true if the Tool type could be activated + */ + virtual bool CanActivateTool(EToolSide eSide, const FString& Identifier); + + /** + * Try to activate a new Tool instance on the given Side + * @param Side which "side" you would like to active the tool on + * @return true if a new Tool instance was created and initialized + */ + virtual bool ActivateTool(EToolSide Side); + + /** + * Check if there is an active Tool on the given Side + * @param Side which Side to check + * @return true if there is an active Tool on that side + */ + virtual bool HasActiveTool(EToolSide Side) const; + + + /** + * @return true if there are any active tools + */ + virtual bool HasAnyActiveTool() const; + + + /** + * Get pointer to active Tool on a given side + * @param Side which Side is being requested + * @return pointer to Tool instance active on that Side, or nullptr if no such Tool exists + */ + virtual UInteractiveTool* GetActiveTool(EToolSide Side); + + + /** + * Check if an active Tool on the given Side can be Accepted in its current state + * @param Side which Side to check + * @return true if there is an active Tool and it returns true from HasAccept() and CanAccept() + */ + virtual bool CanAcceptActiveTool(EToolSide Side); + + /** + * Check if an active Tool on the given Side can be Canceled + * @param Side which Side to check + * @return true if there is an active Tool and it returns true from HasCancel() + */ + virtual bool CanCancelActiveTool(EToolSide Side); + + /** + * Shut down an active Tool on the given side + * @param Side which "side" you would like to shut down + * @param ShutdownType how should the tool be terminated (eg Accept/Cancel) + */ + virtual void DeactivateTool(EToolSide Side, EToolShutdownType ShutdownType); + + + + // + // Functions that Tools can call to interact with Transactions API + // + + /** Post a message via the Transactions API */ + virtual void PostMessage(const TCHAR* Message, EToolMessageLevel Level); + + /** Post a message via the Transactions API */ + virtual void PostMessage(const FString& Message, EToolMessageLevel Level); + + /** Request an Invalidation via the Transactions API (ie to cause a repaint, etc) */ + virtual void PostInvalidation(); + + /** + * Request that the Context open a Transaction, whatever that means to the current Context + * @param Description text description of this transaction (this is the string that appears on undo/redo in the UE Editor) + */ + virtual void BeginUndoTransaction(const FText& Description); + + /** Request that the Context close and commit the open Transaction */ + virtual void EndUndoTransaction(); + + /** + * Forward an FChange object to the Context + * @param TargetObject the object that the FChange applies to + * @param Change the change object that the Context should insert into the transaction history + * @param Description text description of this change (this is the string that appears on undo/redo in the UE Editor) + */ + virtual void EmitObjectChange(UObject* TargetObject, TUniquePtr Change, const FText& Description ); + + + /** + * Forward an FChange object to the Context + */ + virtual bool RequestSelectionChange(const FSelectedOjectsChangeList& SelectionChange); + + + + // + // State control (@todo: have the Context call these? not safe for anyone to call) + // + + /** Tick any active Tools. Called by UInteractiveToolsContext */ + virtual void Tick(float DeltaTime); + + /** Render any active Tools. Called by UInteractiveToolsContext. */ + virtual void Render(IToolsContextRenderAPI* RenderAPI); + + + // + // access to APIs, etc + // + + /** @return current IToolsContextQueriesAPI */ + virtual IToolsContextQueriesAPI* GetContextQueriesAPI() { return QueriesAPI; } + + + +public: + /** Currently-active Left Tool, or null if no Tool is active */ + UPROPERTY() + UInteractiveTool* ActiveLeftTool; + + /** Currently-active Right Tool, or null if no Tool is active */ + UPROPERTY() + UInteractiveTool* ActiveRightTool; + + + + +public: + DECLARE_MULTICAST_DELEGATE_TwoParams(FToolManagerToolStartedSignature, UInteractiveToolManager*, UInteractiveTool*); + FToolManagerToolStartedSignature OnToolStarted; + + DECLARE_MULTICAST_DELEGATE_TwoParams(FToolManagerToolEndedSignature, UInteractiveToolManager*, UInteractiveTool*); + FToolManagerToolEndedSignature OnToolEnded; + + +protected: + /** Pointer to current Context-Queries implementation */ + IToolsContextQueriesAPI* QueriesAPI; + /** Pointer to current Transactions implementation */ + IToolsContextTransactionsAPI* TransactionsAPI; + + /** Pointer to current InputRouter (Context owns this) */ + UInputRouter* InputRouter; + + /** Current set of named ToolBuilders */ + UPROPERTY() + TMap ToolBuilders; + + /** Currently-active Left ToolBuilder */ + UInteractiveToolBuilder* ActiveLeftBuilder; + /** Currently-active Right ToolBuilder */ + UInteractiveToolBuilder* ActiveRightBuilder; + +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolsContext.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolsContext.h new file mode 100644 index 000000000000..f4501a0f6547 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolsContext.h @@ -0,0 +1,45 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InteractiveToolManager.h" +#include "InteractiveGizmoManager.h" +#include "InteractiveToolsContext.generated.h" + +/** + * InteractiveToolsContext owns a ToolManager and an InputRouter. This is just a top-level + * UObject container, however implementations like UEdModeInteractiveToolsContext extend + * this class to make it easier to connect external systems (like an FEdMode) to the ToolsFramework. + */ +UCLASS(Transient) +class INTERACTIVETOOLSFRAMEWORK_API UInteractiveToolsContext : public UObject +{ + GENERATED_BODY() + +public: + UInteractiveToolsContext(); + + /** + * Initialize the Context. This creates the InputRouter and ToolManager + * @param QueriesAPI client-provided implementation of the API for querying the higher-evel scene state + * @param TransactionsAPI client-provided implementation of the API for publishing events and transactions + */ + virtual void Initialize(IToolsContextQueriesAPI* QueriesAPI, IToolsContextTransactionsAPI* TransactionsAPI); + + /** Shutdown Context by destroying InputRouter and ToolManager */ + virtual void Shutdown(); + +public: + /** current UInputRouter for this Context */ + UPROPERTY() + UInputRouter* InputRouter; + + /** current UInteractiveToolManager for this Context */ + UPROPERTY() + UInteractiveToolManager* ToolManager; + + /** current UInteractiveGizmoManager for this Context */ + UPROPERTY() + UInteractiveGizmoManager* GizmoManager; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolsFramework.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolsFramework.h new file mode 100644 index 000000000000..7a47b3c116d2 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/InteractiveToolsFramework.h @@ -0,0 +1,15 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FInteractiveToolsFrameworkModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/ToolBuilderUtil.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/ToolBuilderUtil.h new file mode 100644 index 000000000000..a3b58c175891 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/ToolBuilderUtil.h @@ -0,0 +1,117 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Engine/Selection.h" +#include "InteractiveToolBuilder.h" + +/** +* Helper functions that can be used in InteractiveToolBuilder implementations +*/ +namespace ToolBuilderUtil +{ + /** Returns true if this UObject can provide a FMeshDescription */ + INTERACTIVETOOLSFRAMEWORK_API + bool IsMeshDescriptionSourceComponent(UActorComponent* ComponentObject); + + /** Count number of selected components that pass predicate. If Component selection is not empty, returns that count, otherwise counts in all selected Actors */ + INTERACTIVETOOLSFRAMEWORK_API + int CountComponents(const FToolBuilderState& InputState, const TFunction& Predicate); + + /** First first available component that passes predicate. Searches Components selection list first, then all Actors */ + INTERACTIVETOOLSFRAMEWORK_API + UActorComponent* FindFirstComponent(const FToolBuilderState& InputState, const TFunction& Predicate); + + /** First all components that passes predicate. Searches Components selection list first, then all Actors */ + INTERACTIVETOOLSFRAMEWORK_API + TArray FindAllComponents(const FToolBuilderState& InputState, const TFunction& Predicate); + + // @todo not sure that actors with multiple components are handled properly... + /** Count number of components of given type. If Component selection is not empty, returns that count, otherwise counts in all selected Actors */ + template + int CountSelectedComponentsOfType(const FToolBuilderState& InputState); + + /** First first available component of given type. Searches Components selection list first, then all Actors */ + template + ComponentType* FindFirstComponentOfType(const FToolBuilderState& InputState); + +} + + + + +/* + * Template Implementations + */ + + +template +int ToolBuilderUtil::CountSelectedComponentsOfType(const FToolBuilderState& InputState) +{ + int nTypedComponents = 0; + + if (InputState.SelectedComponents != nullptr && InputState.SelectedComponents->Num() > 0) + { + for (FSelectionIterator Iter(*InputState.SelectedComponents); Iter; ++Iter) + { + if (ComponentType* TypedComponent = Cast(*Iter)) + { + nTypedComponents++; + } + } + } + else + { + for (FSelectionIterator Iter(*InputState.SelectedActors); Iter; ++Iter) + { + if (AActor* Actor = Cast(*Iter)) + { + // [RMS] is there a more efficient way to do this? + TArray TypedComponents = Actor->GetComponentsByClass(ComponentType::StaticClass()); + nTypedComponents += TypedComponents.Num(); + } + } + } + + return nTypedComponents; +} + + + + + + +template +ComponentType* ToolBuilderUtil::FindFirstComponentOfType(const FToolBuilderState& InputState) +{ + if (InputState.SelectedComponents != nullptr && InputState.SelectedComponents->Num() > 0) + { + for (FSelectionIterator Iter(*InputState.SelectedComponents); Iter; ++Iter) + { + if (ComponentType* TypedComponent = Cast(*Iter)) + { + return TypedComponent; + } + } + } + else + { + for (FSelectionIterator Iter(*InputState.SelectedActors); Iter; ++Iter) + { + if (AActor* Actor = Cast(*Iter)) + { + // [RMS] is there a more efficient way to do this? + TArray TypedComponents = Actor->GetComponentsByClass(ComponentType::StaticClass()); + if (TypedComponents.Num() > 0) + { + return (ComponentType*)TypedComponents[0]; + } + } + } + } + + return nullptr; +} + diff --git a/Engine/Source/Runtime/InteractiveToolsFramework/Public/ToolContextInterfaces.h b/Engine/Source/Runtime/InteractiveToolsFramework/Public/ToolContextInterfaces.h new file mode 100644 index 000000000000..a876a74005c4 --- /dev/null +++ b/Engine/Source/Runtime/InteractiveToolsFramework/Public/ToolContextInterfaces.h @@ -0,0 +1,339 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ComponentSourceInterfaces.h" + + +// predeclarations so we don't have to include these in all tools +class FChange; +class UPackage; +class FPrimitiveDrawInterface; +class USelection; +class UInteractiveToolManager; +class UInteractiveGizmoManager; + +/** + * FToolBuilderState is a bucket of state information that a ToolBuilder might need + * to construct a Tool. This information comes from a level above the Tools framework, + * and depends on the context we are in (Editor vs Runtime, for example). + */ +struct INTERACTIVETOOLSFRAMEWORK_API FToolBuilderState +{ + /** The current UWorld */ + UWorld* World = nullptr; + /** The current ToolManager */ + UInteractiveToolManager* ToolManager = nullptr; + /** The current GizmoManager */ + UInteractiveGizmoManager* GizmoManager = nullptr; + + /** Current selected Actors. May be empty or nullptr. */ + USelection* SelectedActors = nullptr; + /** Current selected Components. May be empty or nullptr. */ + USelection* SelectedComponents = nullptr; + + /** Implementation that can build Sources (like MeshDescriptionSource) for Components */ + IComponentSourceFactory* SourceBuilder = nullptr; + + FToolBuilderState() + { + } +}; + + +/** + * FViewCameraState is a bucket of state information that a Tool might + * need to implement interactions that depend on the current scene view. + */ +struct INTERACTIVETOOLSFRAMEWORK_API FViewCameraState +{ + /** Current camera/head position */ + FVector Position; + /** Current camera/head orientation */ + FQuat Orientation; + /** Is current view an orthographic view */ + bool bIsOrthographic; + /** Is current view a VR view */ + bool bIsVR; + + /** @return "right"/horizontal direction in camera plane */ + FVector Right() const { return Orientation.GetAxisY(); } + /** @return "up"/vertical direction in camera plane */ + FVector Up() const { return Orientation.GetAxisZ(); } + /** @return forward camera direction */ + FVector Forward() const { return Orientation.GetAxisX(); } +}; + + + +/** Types of Snap Queries that a ToolsContext parent may support, that Tools may request */ +UENUM() +enum class ESceneSnapQueryType +{ + /** snapping a position */ + Position = 1 +}; + +/** Types of snap targets that a Tool may want to run snap queries against. */ +UENUM() +enum class ESceneSnapQueryTargetType +{ + None = 0, + /** Consider any mesh vertex */ + MeshVertex = 1, + /** Consider any mesh edge */ + MeshEdge = 2, + + All = MeshVertex | MeshEdge +}; +ENUM_CLASS_FLAGS(ESceneSnapQueryTargetType); + +/** + * Configuration variables for a IToolsContextQueriesAPI snap query request. + */ +struct INTERACTIVETOOLSFRAMEWORK_API FSceneSnapQueryRequest +{ + /** What type of snap query geometry is this */ + ESceneSnapQueryType RequestType; + /** What does caller want to try to snap to */ + ESceneSnapQueryTargetType TargetTypes; + + /** Snap input position */ + FVector Position; + /** Another position must deviate less than this number of degrees (in visual angle) to be considered an acceptable snap position */ + float VisualAngleThresholdDegrees; + + /** Snap input direction */ + FVector Direction; + /** Another direction must deviate less than this number of degrees from Direction to be considered an acceptable snap direction */ + float DirectionAngleThresholdDegrees; +}; + + +/** + * Computed result of a IToolsContextQueriesAPI snap query request + */ +struct INTERACTIVETOOLSFRAMEWORK_API FSceneSnapQueryResult +{ + /** Actor that owns snap target */ + AActor* TargetActor = nullptr; + /** Component that owns snap target */ + UActorComponent* TargetComponent = nullptr; + /** What kind of geometric element was snapped to */ + ESceneSnapQueryTargetType TargetType = ESceneSnapQueryTargetType::None; + + /** Snap position (may not be set depending on query types) */ + FVector Position; + /** Snap normal (may not be set depending on query types) */ + FVector Normal; + /** Snap direction (may not be set depending on query types) */ + FVector Direction; + + /** Vertices of triangle that contains result (for debugging, may not be set) */ + FVector TriVertices[3]; + /** Vertex/Edge index we snapped to in triangle */ + int TriSnapIndex; + +}; + + + +/** Types of standard materials that Tools may request from Context */ +UENUM() +enum class EStandardToolContextMaterials +{ + /** White material that displays vertex colors set on mesh */ + VertexColorMaterial = 1 +}; + + + +/** + * Users of the Tools Framework need to implement IToolsContextQueriesAPI to provide + * access to scene state information like the current UWorld, active USelections, etc. + */ +class IToolsContextQueriesAPI +{ +public: + virtual ~IToolsContextQueriesAPI() {} + + /** + * Collect up current-selection information for the current scene state (ie what is selected in Editor, etc) + * @param StateOut this structure is populated with available state information + */ + virtual void GetCurrentSelectionState(FToolBuilderState& StateOut) const = 0; + + + /** + * Request information about current view state + * @param StateOut this structure is populated with available state information + */ + virtual void GetCurrentViewState(FViewCameraState& StateOut) const = 0; + + + /** + * Try to find Snap Targets in the scene that satisfy the Snap Query. + * @param Request snap query configuration + * @param Results list of potential snap results + * @return true if any valid snap target was found + * @warning implementations are not required (and may not be able) to support snapping + */ + virtual bool ExecuteSceneSnapQuery(const FSceneSnapQueryRequest& Request, TArray& Results ) const = 0; + + + /** + * Many tools need standard types of materials that the user should provide (eg a vertex-color material, etc) + * @param MaterialType the type of material being requested + * @return Instance of material to use for this purpose + */ + virtual UMaterialInterface* GetStandardMaterial(EStandardToolContextMaterials MaterialType) const = 0; +}; + + + + +/** Level of severity of messages emitted by Tool framework */ +UENUM() +enum class EToolMessageLevel +{ + /** Development message goes into development log */ + Internal = 0, + /** User message should appear in user-facing log */ + UserMessage = 1, + /** Notification message should be shown in a non-modal notification window */ + UserNotification = 2, + /** Error message should be shown in a modal notification window */ + UserError = 3 +}; + +/** Type of change we want to apply to a selection */ +UENUM() +enum class ESelectedObjectsModificationType +{ + Replace = 0, + Add = 1, + Remove = 2, + Clear = 3 +}; + + +/** Represents a change to a set of selected Actors and Components */ +struct FSelectedOjectsChangeList +{ + /** How should this list be interpreted in the context of a larger selection set */ + ESelectedObjectsModificationType ModificationType; + /** List of Actors */ + TArray Actors; + /** List of Componets */ + TArray Components; +}; + + +/** + * Users of the Tools Framework need to implement IToolsContextTransactionsAPI so that + * the Tools have the ability to create Transactions and emit Changes. Note that this is + * technically optional, but that undo/redo won't be supported without it. + */ +class IToolsContextTransactionsAPI +{ +public: + virtual ~IToolsContextTransactionsAPI() {} + + /** + * Request that context display message information. + * @param Message text of message + * @param Level severity level of message + */ + virtual void PostMessage(const TCHAR* Message, EToolMessageLevel Level) = 0; + + /** + * Forward an invalidation request from Tools framework, to cause repaint/etc. + * This is not always necessary but in some situations (eg in Non-Realtime mode in Editor) + * a redraw will not happen every frame. + * See UInputRouter for options to enable auto-invalidation. + */ + virtual void PostInvalidation() = 0; + + /** + * Begin a Transaction, whatever this means in the current Context. For example in the + * Editor it means open a GEditor Transaction. You must call EndUndoTransaction() after calling this. + * @param Description text description of the transaction that could be shown to user + */ + virtual void BeginUndoTransaction(const FText& Description) = 0; + + /** + * Complete the Transaction. Assumption is that Begin/End are called in pairs. + */ + virtual void EndUndoTransaction() = 0; + + /** + * Insert an FChange into the transaction history in the current Context. + * This cannot be called between Begin/EndUndoTransaction, the FChange should be + * automatically inserted into a Transaction. + * @param TargetObject The UObject this Change is applied to + * @param Change The Change implementation + * @param Description text description of the transaction that could be shown to user + */ + virtual void AppendChange(UObject* TargetObject, TUniquePtr Change, const FText& Description) = 0; + + + + /** + * Request a modification to the current selected objects + * @param SelectionChange desired modification to current selection + * @return true if the selection change could be applied + */ + virtual bool RequestSelectionChange(const FSelectedOjectsChangeList& SelectionChange) = 0; + +}; + + +/** + * Users of the Tools Framework need to implement IToolsContextRenderAPI to allow + * Tools, Indicators, and Gizmos to make low-level rendering calls for things like line drawing. + * This API will be passed to eg UInteractiveTool::Render(), so access is only provided when it + * makes sense to call the functions + */ +class IToolsContextRenderAPI +{ +public: + virtual ~IToolsContextRenderAPI() {} + + /** @return Current PDI */ + virtual FPrimitiveDrawInterface* GetPrimitiveDrawInterface() = 0; +}; + + + + +/** + * Users of the Tools Framework need to provide an IToolsContextAssetAPI implementation + * that allows Packages and Assets to be created/saved. Note that this is not strictly + * necessary, for example a trivial implementation could just store things in the Transient + * package and not do any saving. + */ +class IToolsContextAssetAPI +{ +public: + virtual ~IToolsContextAssetAPI() {} + + /** Get default path to save assets in. For example the currently-visible path in the Editor. */ + virtual FString GetActiveAssetFolderPath() = 0; + + /** Combines folder and asset names */ + virtual FString MakePackageName(const FString& AssetName, const FString& FolderPath) = 0; + + /** return "unique" version of AssetName, created by appending _1, _2, ... if AssetName already exists */ + virtual FString MakeUniqueAssetName(const FString& AssetName, const FString& FolderPath) = 0; + + /** create a new package at the given path (calls MakePackageName) */ + virtual UPackage* CreateNewPackage(const FString& AssetName, const FString& FolderPath) = 0; + + /** Request saving of asset to persistent storage via something like an interactive popup dialog */ + virtual void InteractiveSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) = 0; + + /** Autosave asset to persistent storage */ + virtual void AutoSaveGeneratedAsset(UObject* Asset, UPackage* AssetPackage) = 0; +}; + diff --git a/Engine/Source/Runtime/Landscape/Classes/Landscape.h b/Engine/Source/Runtime/Landscape/Classes/Landscape.h index 9004f584cc3e..d8cf98cc819f 100644 --- a/Engine/Source/Runtime/Landscape/Classes/Landscape.h +++ b/Engine/Source/Runtime/Landscape/Classes/Landscape.h @@ -214,6 +214,7 @@ public: //~ Begin UObject Interface. virtual void PreSave(const class ITargetPlatform* TargetPlatform) override; + virtual void PreEditChange(UProperty* PropertyThatWillChange) override; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; virtual void PostEditMove(bool bFinished) override; virtual void PostEditUndo() override; diff --git a/Engine/Source/Runtime/Landscape/Classes/LandscapeComponent.h b/Engine/Source/Runtime/Landscape/Classes/LandscapeComponent.h index bd60ed7f5f1e..a99baf607dc1 100644 --- a/Engine/Source/Runtime/Landscape/Classes/LandscapeComponent.h +++ b/Engine/Source/Runtime/Landscape/Classes/LandscapeComponent.h @@ -251,7 +251,7 @@ struct FLandscapeComponentMaterialOverride { GENERATED_USTRUCT_BODY() - UPROPERTY(EditAnywhere, Category = LandscapeComponent) + UPROPERTY(EditAnywhere, Category = LandscapeComponent, meta=(UIMin=0, UIMax=8, ClampMin=0, ClampMax=8)) FPerPlatformInt LODIndex; UPROPERTY(EditAnywhere, Category = LandscapeComponent) @@ -355,8 +355,8 @@ class ULandscapeComponent : public UPrimitiveComponent UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LandscapeComponent) UMaterialInterface* OverrideMaterial; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LandscapeComponent, AdvancedDisplay) - UMaterialInterface* OverrideHoleMaterial; + UPROPERTY() + UMaterialInterface* OverrideHoleMaterial_DEPRECATED; UPROPERTY(EditAnywhere, Category = LandscapeComponent) TArray OverrideMaterials; @@ -848,9 +848,6 @@ public: /** Returns the actor's LandscapeMaterial, or the Component's OverrideLandscapeMaterial if set */ LANDSCAPE_API UMaterialInterface* GetLandscapeMaterial(int8 InLODIndex = INDEX_NONE) const; - /** Returns the actor's LandscapeHoleMaterial, or the Component's OverrideLandscapeHoleMaterial if set */ - LANDSCAPE_API UMaterialInterface* GetLandscapeHoleMaterial() const; - /** Returns true if this component has visibility painted */ LANDSCAPE_API bool ComponentHasVisibilityPainted() const; diff --git a/Engine/Source/Runtime/Landscape/Classes/LandscapeLayerInfoObject.h b/Engine/Source/Runtime/Landscape/Classes/LandscapeLayerInfoObject.h index 082757131eda..78c02f38a05a 100644 --- a/Engine/Source/Runtime/Landscape/Classes/LandscapeLayerInfoObject.h +++ b/Engine/Source/Runtime/Landscape/Classes/LandscapeLayerInfoObject.h @@ -10,7 +10,7 @@ class UPhysicalMaterial; struct FPropertyChangedEvent; -UCLASS(MinimalAPI) +UCLASS(MinimalAPI, BlueprintType) class ULandscapeLayerInfoObject : public UObject { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/Landscape/Classes/LandscapeProxy.h b/Engine/Source/Runtime/Landscape/Classes/LandscapeProxy.h index 85baeb21ad34..29e739dbade7 100644 --- a/Engine/Source/Runtime/Landscape/Classes/LandscapeProxy.h +++ b/Engine/Source/Runtime/Landscape/Classes/LandscapeProxy.h @@ -316,11 +316,36 @@ struct FLandscapeProxyMaterialOverride { GENERATED_USTRUCT_BODY() - UPROPERTY(EditAnywhere, Category = Landscape) + UPROPERTY(EditAnywhere, Category = Landscape, meta = (UIMin = 0, UIMax = 8, ClampMin = 0, ClampMax = 8)) FPerPlatformInt LODIndex; UPROPERTY(EditAnywhere, Category = Landscape) UMaterialInterface* Material; + +#if WITH_EDITORONLY_DATA + bool operator==(const FLandscapeProxyMaterialOverride& InOther) const + { + if (Material != InOther.Material) + { + return false; + } + + if (LODIndex.Default != InOther.LODIndex.Default || LODIndex.PerPlatform.Num() != InOther.LODIndex.PerPlatform.Num()) + { + return false; + } + + for (auto& ItPair : LODIndex.PerPlatform) + { + if (!InOther.LODIndex.PerPlatform.Contains(ItPair.Key)) + { + return false; + } + } + + return true; + } +#endif }; class FLandscapeLayersTexture2DCPUReadBackResource : public FTextureResource @@ -396,6 +421,10 @@ public: UPROPERTY(EditAnywhere, Category = LOD, meta=(ClampMin = "0.01", ClampMax = "1.0", UIMin = "0.01", UIMax = "1.0", DisplayName= "SubSection Min Component ScreenSize")) float ComponentScreenSizeToUseSubSections; + /** This is the starting screen size used to calculate the distribution, by default it's 1, but you can increase the value if you want less LOD0 component, and you use very large landscape component. */ + UPROPERTY(EditAnywhere, Category = "LOD Distribution", meta = (DisplayName = "LOD 0 Screen Size", ClampMin = "1.0", ClampMax = "10.0", UIMin = "1.0", UIMax = "10.0")) + float LOD0ScreenSize; + /** The distribution setting used to change the LOD 0 generation, 1.75 is the normal distribution, numbers influence directly the LOD0 proportion on screen. */ UPROPERTY(EditAnywhere, Category = "LOD Distribution", meta = (DisplayName = "LOD 0", ClampMin = "1.0", ClampMax = "10.0", UIMin = "1.0", UIMax = "10.0")) float LOD0DistributionSetting; @@ -454,13 +483,23 @@ public: UPROPERTY(EditAnywhere, BlueprintSetter=EditorSetLandscapeMaterial, Category=Landscape) UMaterialInterface* LandscapeMaterial; - /** Material used to render landscape components with holes. If not set, LandscapeMaterial will be used (blend mode will be overridden to Masked if it is set to Opaque) */ - UPROPERTY(EditAnywhere, Category=Landscape, AdvancedDisplay) - UMaterialInterface* LandscapeHoleMaterial; + UPROPERTY() + UMaterialInterface* LandscapeHoleMaterial_DEPRECATED; UPROPERTY(EditAnywhere, Category = Landscape) TArray LandscapeMaterialsOverride; +#if WITH_EDITORONLY_DATA + UPROPERTY(Transient) + UMaterialInterface* PreEditLandscapeMaterial; + + UPROPERTY(Transient) + TArray PreEditLandscapeMaterialsOverride; + + UPROPERTY(Transient) + bool bIsPerformingInteractiveActionOnLandscapeMaterialOverride; +#endif + /** Allows overriding the landscape bounds. This is useful if you distort the landscape with world-position-offset, for example * Extension value in the negative Z axis, positive value increases bound size * Note that this can also be overridden per-component when the component is selected with the component select tool */ @@ -695,15 +734,15 @@ public: /** Set an MID texture parameter value for all landscape components. */ UFUNCTION(BlueprintCallable, Category = "Landscape|Runtime|Material") - void SetLandscapeMaterialTextureParameterValue(FName ParameterName, class UTexture* Value); + LANDSCAPE_API void SetLandscapeMaterialTextureParameterValue(FName ParameterName, class UTexture* Value); /** Set an MID vector parameter value for all landscape components. */ UFUNCTION(BlueprintCallable, meta = (Keywords = "SetColorParameterValue"), Category = "Landscape|Runtime|Material") - void SetLandscapeMaterialVectorParameterValue(FName ParameterName, FLinearColor Value); + LANDSCAPE_API void SetLandscapeMaterialVectorParameterValue(FName ParameterName, FLinearColor Value); /** Set a MID scalar (float) parameter value for all landscape components. */ UFUNCTION(BlueprintCallable, meta = (Keywords = "SetFloatParameterValue"), Category = "Landscape|Runtime|Material") - void SetLandscapeMaterialScalarParameterValue(FName ParameterName, float Value); + LANDSCAPE_API void SetLandscapeMaterialScalarParameterValue(FName ParameterName, float Value); // End blueprint functions @@ -807,9 +846,6 @@ public: // Get Landscape Material assigned to this Landscape virtual UMaterialInterface* GetLandscapeMaterial(int8 InLODIndex = INDEX_NONE) const; - // Get Hole Landscape Material assigned to this Landscape - virtual UMaterialInterface* GetLandscapeHoleMaterial() const; - // void FixupWeightmaps(); @@ -880,8 +916,8 @@ public: /** @return Current size of bounding rectangle in quads space */ LANDSCAPE_API FIntRect GetBoundingRect() const; - /** Creates a Texture2D for use by this landscape proxy or one of it's components. */ - LANDSCAPE_API UTexture2D* CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, bool bCompress = false) const; + /** Creates a Texture2D for use by this landscape proxy or one of it's components. If OptionalOverrideOuter is not specified, the proxy is used. */ + LANDSCAPE_API UTexture2D* CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, UObject* OptionalOverrideOuter = nullptr, bool bCompress = false) const; /** Creates a LandscapeWeightMapUsage object outered to this proxy. */ LANDSCAPE_API ULandscapeWeightmapUsage* CreateWeightmapUsage(); diff --git a/Engine/Source/Runtime/Landscape/Classes/LandscapeStreamingProxy.h b/Engine/Source/Runtime/Landscape/Classes/LandscapeStreamingProxy.h index f2c623744070..7896a49eb6c4 100644 --- a/Engine/Source/Runtime/Landscape/Classes/LandscapeStreamingProxy.h +++ b/Engine/Source/Runtime/Landscape/Classes/LandscapeStreamingProxy.h @@ -23,7 +23,8 @@ public: //~ Begin UObject Interface #if WITH_EDITOR - void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual bool ShouldExport() override { return false; } + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif //~ End UObject Interface @@ -32,7 +33,6 @@ public: virtual const ALandscape* GetLandscapeActor() const override; #if WITH_EDITOR virtual UMaterialInterface* GetLandscapeMaterial(int8 InLODIndex = INDEX_NONE) const override; - virtual UMaterialInterface* GetLandscapeHoleMaterial() const override; #endif //~ End ALandscapeBase Interface diff --git a/Engine/Source/Runtime/Landscape/Classes/Materials/MaterialExpressionLandscapeGrassOutput.h b/Engine/Source/Runtime/Landscape/Classes/Materials/MaterialExpressionLandscapeGrassOutput.h index 99cbdd30d7e6..7b836d074b51 100644 --- a/Engine/Source/Runtime/Landscape/Classes/Materials/MaterialExpressionLandscapeGrassOutput.h +++ b/Engine/Source/Runtime/Landscape/Classes/Materials/MaterialExpressionLandscapeGrassOutput.h @@ -48,6 +48,7 @@ class LANDSCAPE_API UMaterialExpressionLandscapeGrassOutput : public UMaterialEx virtual const TArray GetInputs() override; virtual FExpressionInput* GetInput(int32 InputIndex) override; virtual FName GetInputName(int32 InputIndex) const override; + void ValidateInputName(FGrassInput& Input) const; #endif //~ Begin UObject Interface @@ -64,6 +65,9 @@ class LANDSCAPE_API UMaterialExpressionLandscapeGrassOutput : public UMaterialEx UPROPERTY(EditAnywhere, Category = UMaterialExpressionLandscapeGrassOutput) TArray GrassTypes; + +private: + static FName PinDefaultName; }; diff --git a/Engine/Source/Runtime/Landscape/Private/Landscape.cpp b/Engine/Source/Runtime/Landscape/Private/Landscape.cpp index d6cdda17ce0d..883dcd51ce8e 100644 --- a/Engine/Source/Runtime/Landscape/Private/Landscape.cpp +++ b/Engine/Source/Runtime/Landscape/Private/Landscape.cpp @@ -60,6 +60,7 @@ Landscape.cpp: Terrain rendering #include "LandscapeVersion.h" #include "UObject/FortniteMainBranchObjectVersion.h" #include "LandscapeDataAccess.h" +#include "UObject/EditorObjectVersion.h" /** Landscape stats */ @@ -313,6 +314,7 @@ void ULandscapeComponent::Serialize(FArchive& Ar) LLM_SCOPE(ELLMTag::Landscape); Ar.UsingCustomVersion(FRenderingObjectVersion::GUID); Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); + Ar.UsingCustomVersion(FEditorObjectVersion::GUID); #if WITH_EDITOR if (Ar.IsCooking() && !HasAnyFlags(RF_ClassDefaultObject) && Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::MobileRendering)) @@ -438,7 +440,24 @@ void ULandscapeComponent::Serialize(FArchive& Ar) LegacyComponentData.Data.Emplace(MapBuildDataId, LegacyMapBuildData); GComponentsWithLegacyLightmaps.AddAnnotation(this, LegacyComponentData); } - + + if (Ar.IsLoading() && Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::RemoveLandscapeHoleMaterial) + { + if (OverrideHoleMaterial_DEPRECATED != nullptr) + { + if (OverrideMaterial == nullptr) + { + OverrideMaterial = OverrideHoleMaterial_DEPRECATED; + } + else + { + if (OverrideMaterial->GetBlendMode() != EBlendMode::BLEND_Masked) + { + UE_LOG(LogLandscape, Warning, TEXT("OverrideHoleMaterial was deprecated, your OverrideMaterial is not currently setup correctly to support visibility painting, please correct your material, otherwise your landscape component \"%s\" won't render as before."), *GetFullName()); + } + } + } + } if (Ar.IsLoading() && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::NewLandscapeMaterialPerLOD) { if (MobileMaterialInterface_DEPRECATED != nullptr) @@ -570,20 +589,6 @@ UMaterialInterface* ULandscapeComponent::GetLandscapeMaterial(int8 InLODIndex) c return UMaterial::GetDefaultMaterial(MD_Surface); } -UMaterialInterface* ULandscapeComponent::GetLandscapeHoleMaterial() const -{ - if (OverrideHoleMaterial) - { - return OverrideHoleMaterial; - } - ALandscapeProxy* Proxy = GetLandscapeProxy(); - if (Proxy) - { - return Proxy->GetLandscapeHoleMaterial(); - } - return nullptr; -} - bool ULandscapeComponent::ComponentHasVisibilityPainted() const { for (const FWeightmapLayerAllocationInfo& Allocation : WeightmapLayerAllocations) @@ -970,8 +975,10 @@ void ULandscapeComponent::PostLoad() if (!HasAnyFlags(RF_ClassDefaultObject)) { + UWorld* World = GetWorld(); + // If we're loading on a platform that doesn't require cooked data, but *only* supports OpenGL ES, generate or preload data from the DDC - if (!FPlatformProperties::RequiresCookedData() && GMaxRHIFeatureLevel <= ERHIFeatureLevel::ES3_1) + if (!FPlatformProperties::RequiresCookedData() && ((GMaxRHIFeatureLevel <= ERHIFeatureLevel::ES3_1) || (World && (World->FeatureLevel <= ERHIFeatureLevel::ES3_1)))) { CheckGenerateLandscapePlatformData(false, nullptr); } @@ -1023,6 +1030,7 @@ ALandscapeProxy::ALandscapeProxy(const FObjectInitializer& ObjectInitializer) ComponentScreenSizeToUseSubSections = 0.65f; UseTessellationComponentScreenSizeFalloff = true; TessellationComponentScreenSizeFalloff = 0.75f; + LOD0ScreenSize = 1.0f; LOD0DistributionSetting = 1.75f; LODDistributionSetting = 2.0f; bCastStaticShadow = true; @@ -1758,7 +1766,7 @@ void ALandscapeProxy::PostRegisterAllComponents() #if WITH_EDITOR // Game worlds don't have landscape infos - if (!GetWorld()->IsGameWorld()) + if (!GetWorld()->IsGameWorld() && !IsPendingKillPending()) { // Duplicated Landscapes don't have a valid guid until PostEditImport is called, we'll register then if (LandscapeGuid.IsValid()) @@ -1850,24 +1858,9 @@ void ALandscape::PostLoad() { if (Landscape && Landscape != this && Landscape->LandscapeGuid == LandscapeGuid && Landscape->GetWorld() == CurrentWorld) { - // Duplicated landscape level, need to generate new GUID + // Duplicated landscape level, need to generate new GUID. This can happen during PIE or gameplay when streaming the same landscape actor. Modify(); LandscapeGuid = FGuid::NewGuid(); - - - // Show MapCheck window - - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ProxyName1"), FText::FromString(Landscape->GetName())); - Arguments.Add(TEXT("LevelName1"), FText::FromString(Landscape->GetLevel()->GetOutermost()->GetName())); - Arguments.Add(TEXT("ProxyName2"), FText::FromString(this->GetName())); - Arguments.Add(TEXT("LevelName2"), FText::FromString(this->GetLevel()->GetOutermost()->GetName())); - FMessageLog("LoadErrors").Warning() - ->AddToken(FUObjectToken::Create(this)) - ->AddToken(FTextToken::Create(FText::Format(LOCTEXT("LoadError_DuplicateLandscapeGuid", "Landscape {ProxyName1} of {LevelName1} has the same guid as {ProxyName2} of {LevelName2}. {LevelName2}.{ProxyName2} has had its guid automatically changed, please save {LevelName2}!"), Arguments))); - - // Show MapCheck window - FMessageLog("LoadErrors").Open(); break; } } @@ -1923,6 +1916,7 @@ void ALandscapeProxy::Serialize(FArchive& Ar) Super::Serialize(Ar); Ar.UsingCustomVersion(FLandscapeCustomVersion::GUID); + Ar.UsingCustomVersion(FEditorObjectVersion::GUID); if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::MigrateOldPropertiesToNewRenderingProperties) { @@ -1945,6 +1939,24 @@ void ALandscapeProxy::Serialize(FArchive& Ar) } } } + + if (Ar.IsLoading() && Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::RemoveLandscapeHoleMaterial) + { + if (LandscapeHoleMaterial_DEPRECATED != nullptr) + { + if (LandscapeMaterial == nullptr) + { + LandscapeMaterial = LandscapeHoleMaterial_DEPRECATED; + } + else + { + if (LandscapeMaterial->GetBlendMode() != EBlendMode::BLEND_Masked) + { + UE_LOG(LogLandscape, Warning, TEXT("LandscapeHoleMaterial was deprecated, your landscape material is not currently setup correctly to support visibility painting, please correct your material, otherwise your landscape \"%s\" won't render as before."), *GetFullName()); + } + } + } + } } void ALandscapeProxy::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) @@ -2361,6 +2373,7 @@ void ALandscapeProxy::GetSharedProperties(ALandscapeProxy* Landscape) TessellationComponentScreenSizeFalloff = Landscape->TessellationComponentScreenSizeFalloff; LODDistributionSetting = Landscape->LODDistributionSetting; LOD0DistributionSetting = Landscape->LOD0DistributionSetting; + LOD0ScreenSize = Landscape->LOD0ScreenSize; OccluderGeometryLOD = Landscape->OccluderGeometryLOD; NegativeZBoundsExtension = Landscape->NegativeZBoundsExtension; PositiveZBoundsExtension = Landscape->PositiveZBoundsExtension; @@ -2371,10 +2384,6 @@ void ALandscapeProxy::GetSharedProperties(ALandscapeProxy* Landscape) LandscapeMaterial = Landscape->LandscapeMaterial; LandscapeMaterialsOverride = Landscape->LandscapeMaterialsOverride; } - if (!LandscapeHoleMaterial) - { - LandscapeHoleMaterial = Landscape->LandscapeHoleMaterial; - } if (LandscapeMaterial == Landscape->LandscapeMaterial) { EditorLayerSettings = Landscape->EditorLayerSettings; @@ -2438,6 +2447,12 @@ void ALandscapeProxy::FixupSharedData(ALandscape* Landscape) bUpdated = true; } + if (LOD0ScreenSize != Landscape->LOD0ScreenSize) + { + LOD0ScreenSize = Landscape->LOD0ScreenSize; + bUpdated = true; + } + if (OccluderGeometryLOD != Landscape->OccluderGeometryLOD) { OccluderGeometryLOD = Landscape->OccluderGeometryLOD; @@ -2563,15 +2578,6 @@ UMaterialInterface* ALandscapeProxy::GetLandscapeMaterial(int8 InLODIndex) const return LandscapeMaterial != nullptr ? LandscapeMaterial : UMaterial::GetDefaultMaterial(MD_Surface); } -UMaterialInterface* ALandscapeProxy::GetLandscapeHoleMaterial() const -{ - if (LandscapeHoleMaterial) - { - return LandscapeHoleMaterial; - } - return nullptr; -} - UMaterialInterface* ALandscapeStreamingProxy::GetLandscapeMaterial(int8 InLODIndex) const { if (InLODIndex != INDEX_NONE) @@ -2608,19 +2614,6 @@ UMaterialInterface* ALandscapeStreamingProxy::GetLandscapeMaterial(int8 InLODInd return UMaterial::GetDefaultMaterial(MD_Surface); } -UMaterialInterface* ALandscapeStreamingProxy::GetLandscapeHoleMaterial() const -{ - if (LandscapeHoleMaterial) - { - return LandscapeHoleMaterial; - } - else if (ALandscape* Landscape = LandscapeActor.Get()) - { - return Landscape->GetLandscapeHoleMaterial(); - } - return nullptr; -} - void ALandscape::PreSave(const class ITargetPlatform* TargetPlatform) { Super::PreSave(TargetPlatform); diff --git a/Engine/Source/Runtime/Landscape/Private/LandscapeEdit.cpp b/Engine/Source/Runtime/Landscape/Private/LandscapeEdit.cpp index 32267195d9a6..f00acd7c344e 100644 --- a/Engine/Source/Runtime/Landscape/Private/LandscapeEdit.cpp +++ b/Engine/Source/Runtime/Landscape/Private/LandscapeEdit.cpp @@ -228,9 +228,8 @@ UMaterialInstanceConstant* ULandscapeComponent::GetCombinationMaterial(FMaterial const bool bComponentHasHoles = ComponentHasVisibilityPainted(); UMaterialInterface* const LandscapeMaterial = GetLandscapeMaterial(InLODIndex); - UMaterialInterface* const HoleMaterial = bComponentHasHoles ? GetLandscapeHoleMaterial() : nullptr; - UMaterialInterface* const MaterialToUse = bComponentHasHoles && HoleMaterial ? HoleMaterial : LandscapeMaterial; - bool bOverrideBlendMode = bComponentHasHoles && !HoleMaterial && LandscapeMaterial->GetBlendMode() == BLEND_Opaque; + UMaterialInterface* const MaterialToUse = LandscapeMaterial; + bool bOverrideBlendMode = bComponentHasHoles && LandscapeMaterial->GetBlendMode() == BLEND_Opaque; if (bOverrideBlendMode) { @@ -3996,7 +3995,8 @@ void ALandscapeProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChan ChangeTessellationComponentScreenSizeFalloff(TessellationComponentScreenSizeFalloff); } else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting) - || PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting)) + || PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting) + || PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize)) { MarkComponentsRenderStateDirty(); } @@ -4095,7 +4095,7 @@ void ALandscapeStreamingProxy::PostEditChangeProperty(FPropertyChangedEvent& Pro LandscapeActor = nullptr; } } - else if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeHoleMaterial")) || PropertyName == FName(TEXT("LandscapeMaterialsOverride"))) + else if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeMaterialsOverride"))) { bool RecreateMaterialInstances = true; @@ -4144,6 +4144,14 @@ void ALandscapeStreamingProxy::PostEditChangeProperty(FPropertyChangedEvent& Pro Super::PostEditChangeProperty(PropertyChangedEvent); } +void ALandscape::PreEditChange(UProperty* PropertyThatWillChange) +{ + PreEditLandscapeMaterial = LandscapeMaterial; + PreEditLandscapeMaterialsOverride = LandscapeMaterialsOverride; + + Super::PreEditChange(PropertyThatWillChange); +} + void ALandscape::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None; @@ -4157,16 +4165,42 @@ void ALandscape::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEv ULandscapeInfo* Info = GetLandscapeInfo(); - if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeHoleMaterial")) || MemberPropertyName == FName(TEXT("LandscapeMaterialsOverride"))) + if ((PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeMaterial) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeMaterialsOverride)) + && PropertyChangedEvent.ChangeType != EPropertyChangeType::ArrayAdd) { - bool RecreateMaterialInstances = true; + bool HasMaterialChanged = false; - if (PropertyName == FName(TEXT("LandscapeMaterialsOverride")) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd) + if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { - RecreateMaterialInstances = false; - } + if (PreEditLandscapeMaterial != LandscapeMaterial || PreEditLandscapeMaterialsOverride.Num() != LandscapeMaterialsOverride.Num() || bIsPerformingInteractiveActionOnLandscapeMaterialOverride) + { + HasMaterialChanged = true; + } - if (Info != nullptr && RecreateMaterialInstances) + if (!HasMaterialChanged) + { + for (int32 i = 0; i < LandscapeMaterialsOverride.Num(); ++i) + { + const FLandscapeProxyMaterialOverride& NewMaterialOverride = LandscapeMaterialsOverride[i]; + const FLandscapeProxyMaterialOverride& PreEditMaterialOverride = PreEditLandscapeMaterialsOverride[i]; + + if (!(PreEditMaterialOverride == NewMaterialOverride)) + { + HasMaterialChanged = true; + break; + } + } + } + + bIsPerformingInteractiveActionOnLandscapeMaterialOverride = false; + } + else + { + // We are probably using a slider or something similar in LandscapeMaterialsOverride + bIsPerformingInteractiveActionOnLandscapeMaterialOverride = MemberPropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeMaterialsOverride); + } + + if (Info != nullptr && HasMaterialChanged) { FMaterialUpdateContext MaterialUpdateContext; Info->UpdateLayerInfoMap(/*this*/); @@ -4231,6 +4265,11 @@ void ALandscape::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEv LOD0DistributionSetting = FMath::Clamp(LOD0DistributionSetting, 1.0f, 10.0f); bPropagateToProxies = true; } + else if (PropertyName == FName(TEXT("LOD0ScreenSize"))) + { + LOD0ScreenSize = FMath::Clamp(LOD0ScreenSize, 1.0f, 10.0f); + bPropagateToProxies = true; + } else if (PropertyName == FName(TEXT("CollisionMipLevel"))) { CollisionMipLevel = FMath::Clamp(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); @@ -4355,6 +4394,9 @@ void ALandscape::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEv LandscapeMaterialChangedDelegate.Broadcast(); } } + + PreEditLandscapeMaterial = nullptr; + PreEditLandscapeMaterialsOverride.Empty(); } void ALandscapeProxy::ChangedPhysMaterial() @@ -5483,7 +5525,7 @@ void ULandscapeComponent::GeneratePlatformPixelData() MobileWeightmapTextures.Empty(); - UTexture2D* MobileWeightNormalmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8, GMobileCompressLandscapeWeightMaps ? true : false ); + UTexture2D* MobileWeightNormalmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8, nullptr, GMobileCompressLandscapeWeightMaps ? true : false ); CreateEmptyTextureMips(MobileWeightNormalmapTexture); { @@ -5526,7 +5568,7 @@ void ULandscapeComponent::GeneratePlatformPixelData() // create a new weightmap texture if we've run out of channels CurrentChannel = 0; RemainingChannels = 4; - CurrentWeightmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8, GMobileCompressLandscapeWeightMaps ? true : false); + CurrentWeightmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8, nullptr, GMobileCompressLandscapeWeightMaps ? true : false); CreateEmptyTextureMips(CurrentWeightmapTexture); MobileWeightmapTextures.Add(CurrentWeightmapTexture); } @@ -5865,9 +5907,10 @@ void ULandscapeComponent::GeneratePlatformVertexData(const ITargetPlatform* Targ PlatformData.InitializeFromUncompressedData(NewPlatformData); } -UTexture2D* ALandscapeProxy::CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, bool bCompress) const +UTexture2D* ALandscapeProxy::CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, UObject* OptionalOverrideOuter, bool bCompress) const { - UTexture2D* NewTexture = NewObject(const_cast(this)); + UObject* TexOuter = OptionalOverrideOuter ? OptionalOverrideOuter : const_cast(this); + UTexture2D* NewTexture = NewObject(TexOuter); NewTexture->Source.Init2DWithMipChain(InSizeX, InSizeY, InFormat); NewTexture->SRGB = false; NewTexture->CompressionNone = !bCompress; diff --git a/Engine/Source/Runtime/Landscape/Private/LandscapeEditInterface.cpp b/Engine/Source/Runtime/Landscape/Private/LandscapeEditInterface.cpp index 33d7feccc6b4..b7656984a3f3 100644 --- a/Engine/Source/Runtime/Landscape/Private/LandscapeEditInterface.cpp +++ b/Engine/Source/Runtime/Landscape/Private/LandscapeEditInterface.cpp @@ -2621,7 +2621,7 @@ inline TMap FLandscapeEditDataInterfac for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++) { const FWeightmapLayerAllocationInfo& Allocation = ComponentWeightmapLayerAllocations[LayerIdx]; - if (Allocation.LayerInfo->bNoWeightBlend) + if (Allocation.LayerInfo == nullptr || Allocation.LayerInfo->bNoWeightBlend) { continue; } @@ -2890,6 +2890,12 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const LayerNoWeightBlends[LayerIdx] = Allocation.LayerInfo->bNoWeightBlend; LayerEditDataAllZero[LayerIdx] = true; } + else + { + LayerDataPtrs[LayerIdx] = nullptr; + LayerNoWeightBlends[LayerIdx] = true; + LayerEditDataAllZero[LayerIdx] = false; + } } // Find the texture data corresponding to this vertex @@ -2965,6 +2971,11 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const // Adjust other layers' weights accordingly for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++) { + if (LayerDataPtrs[LayerIdx] == nullptr) + { + continue; + } + uint8& ExistingWeight = LayerDataPtrs[LayerIdx][TexDataIndex]; if (LayerIdx == UpdateLayerIdx) @@ -2991,6 +3002,11 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const // Normalize for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++) { + if (LayerDataPtrs[LayerIdx] == nullptr) + { + continue; + } + uint8& ExistingWeight = LayerDataPtrs[LayerIdx][TexDataIndex]; if (LayerNoWeightBlends[LayerIdx] == false) @@ -3008,6 +3024,7 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const if ((255 - OtherLayerWeightSum) && MaxLayerIdx >= 0) { + // No need to check for nullptr here because MaxLayerIdx can only be set to a valid layer LayerDataPtrs[MaxLayerIdx][TexDataIndex] += 255 - OtherLayerWeightSum; } } @@ -3017,9 +3034,10 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const // Adjust other layers' weights accordingly for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++) { - // Exclude bNoWeightBlend layers + // Exclude bNoWeightBlend layers if (LayerIdx != UpdateLayerIdx && LayerNoWeightBlends[LayerIdx] == false) { + // No need to check for nullptr here because invalid layers have LayerNoWeightBlends set to true. OtherLayerWeightSum += LayerDataPtrs.IsValidIndex(LayerIdx) ? LayerDataPtrs[LayerIdx][TexDataIndex] : 0; } } @@ -3034,6 +3052,7 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const { const int32 ReplacementLayerIndex = ComponentWeightmapLayerAllocations.IndexOfByPredicate([&](const FWeightmapLayerAllocationInfo& AllocationInfo) { return AllocationInfo.LayerInfo == ReplacementLayer; }); + // No need to check for nullptr here because ChooseReplacementLayer can't return an invalid layer. LayerDataPtrs[ReplacementLayerIndex][TexDataIndex] = 255 - NewWeight; LayerEditDataAllZero[ReplacementLayerIndex] = false; } @@ -3049,12 +3068,18 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const NewWeight = 255; } + // No need to check for nullptr here because UpdateLayerIdx is always valid LayerDataPtrs[UpdateLayerIdx][TexDataIndex] = NewWeight; } else { for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++) { + if (LayerDataPtrs[LayerIdx] == nullptr) + { + continue; + } + uint8& Weight = LayerDataPtrs[LayerIdx][TexDataIndex]; if (LayerIdx == UpdateLayerIdx) @@ -3080,6 +3105,7 @@ void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const } else { + // No need to check for nullptr here because UpdateLayerIdx is always valid // Weight value set without adjusting other layers' weights uint8& Weight = LayerDataPtrs[UpdateLayerIdx][TexDataIndex]; Weight = NewWeight; diff --git a/Engine/Source/Runtime/Landscape/Private/LandscapeGrass.cpp b/Engine/Source/Runtime/Landscape/Private/LandscapeGrass.cpp index 4c91d917f1fe..6fea383e71ad 100644 --- a/Engine/Source/Runtime/Landscape/Private/LandscapeGrass.cpp +++ b/Engine/Source/Runtime/Landscape/Private/LandscapeGrass.cpp @@ -1042,6 +1042,8 @@ public: // // UMaterialExpressionLandscapeGrassOutput // +FName UMaterialExpressionLandscapeGrassOutput::PinDefaultName = TEXT("Pin"); + UMaterialExpressionLandscapeGrassOutput::UMaterialExpressionLandscapeGrassOutput(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -1128,6 +1130,11 @@ void UMaterialExpressionLandscapeGrassOutput::PostEditChangeProperty(FPropertyCh const FName PropertyName = PropertyChangedEvent.MemberProperty->GetFName(); if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterialExpressionLandscapeGrassOutput, GrassTypes)) { + for (FGrassInput& Input : GrassTypes) + { + ValidateInputName(Input); + } + if (GraphNode) { GraphNode->ReconstructNode(); @@ -1135,6 +1142,46 @@ void UMaterialExpressionLandscapeGrassOutput::PostEditChangeProperty(FPropertyCh } } } + +void UMaterialExpressionLandscapeGrassOutput::ValidateInputName(FGrassInput& InInput) const +{ + if (Material != nullptr) + { + int32 NameIndex = 1; + bool bFoundValidName = false; + + // Parameters cannot be named Name_None, use the default name instead + FName PotentialName = InInput.Name == NAME_None ? UMaterialExpressionLandscapeGrassOutput::PinDefaultName : InInput.Name; + + // Find an available unique name + while (!bFoundValidName) + { + if (NameIndex != 1) + { + PotentialName.SetNumber(NameIndex); + } + + bFoundValidName = true; + + // Make sure the name is unique among others pins of this node + for (const FGrassInput& OtherInput : GrassTypes) + { + if (&OtherInput != &InInput) + { + if (OtherInput.Name == PotentialName) + { + bFoundValidName = false; + break; + } + } + } + + ++NameIndex; + } + + InInput.Name = PotentialName; + } +} #endif // diff --git a/Engine/Source/Runtime/Landscape/Private/LandscapeRender.cpp b/Engine/Source/Runtime/Landscape/Private/LandscapeRender.cpp index 5a0e35a4a965..032b3f6db541 100644 --- a/Engine/Source/Runtime/Landscape/Private/LandscapeRender.cpp +++ b/Engine/Source/Runtime/Landscape/Private/LandscapeRender.cpp @@ -571,11 +571,6 @@ void ULandscapeComponent::GetUsedMaterials(TArray& OutMater OutMaterials.Add(OverrideMaterial); } - if (OverrideHoleMaterial) - { - OutMaterials.Add(OverrideHoleMaterial); - } - OutMaterials.Append(MobileMaterialInterfaces); #if WITH_EDITORONLY_DATA @@ -709,7 +704,7 @@ FLandscapeComponentSceneProxy::FLandscapeComponentSceneProxy(ULandscapeComponent } float ScreenSizeRatioDivider = FMath::Max(InComponent->GetLandscapeProxy()->LOD0DistributionSetting * GLandscapeLOD0DistributionScale, 1.01f); - float CurrentScreenSizeRatio = 1.0f; + float CurrentScreenSizeRatio = InComponent->GetLandscapeProxy()->LOD0ScreenSize; LODScreenRatioSquared.AddUninitialized(MaxLOD + 1); @@ -1293,12 +1288,12 @@ float FLandscapeComponentSceneProxy::GetComponentScreenSize(const FSceneView* Vi // Calculate screen-space projected radius float SquaredScreenRadius = FMath::Square(ScreenMultiple * ElementRadius) / FMath::Max(1.0f, DistSquared); - return FMath::Min(SquaredScreenRadius * 2.0f, 1.0f); + return SquaredScreenRadius * 2.0f; } void FLandscapeComponentSceneProxy::BuildDynamicMeshElement(const FViewCustomDataLOD* InPrimitiveCustomData, bool InToolMesh, bool InHasTessellation, bool InDisableTessellation, FMeshBatch& OutMeshBatch, TArray& OutStaticBatchParamArray) const { - if (AvailableMaterials.Num() == 0) + if (AvailableMaterials.Num() == 0 || InPrimitiveCustomData == nullptr || InPrimitiveCustomData->SubSections.Num() == 0) { return; } @@ -1716,7 +1711,7 @@ void FLandscapeComponentSceneProxy::CalculateLODFromScreenSize(const FSceneView& } else { - PreferedLOD = FMath::Clamp(ComputeBatchElementCurrentLOD(GetLODFromScreenSize(InMeshScreenSizeSquared, InViewLODScale), InMeshScreenSizeSquared) + LocalLODBias, FMath::Max((float)MinStreamedLOD, MinValidLOD), FMath::Min((float)LastLOD, MaxValidLOD)); + PreferedLOD = FMath::Clamp(ComputeBatchElementCurrentLOD(GetLODFromScreenSize(InMeshScreenSizeSquared, InViewLODScale), InMeshScreenSizeSquared, InViewLODScale) + LocalLODBias, FMath::Max((float)MinStreamedLOD, MinValidLOD), FMath::Min((float)LastLOD, MaxValidLOD)); } check(PreferedLOD != -1.0f && PreferedLOD <= MaxLOD); @@ -1801,7 +1796,7 @@ void FLandscapeComponentSceneProxy::CalculateBatchElementLOD(const FSceneView& I { float SquaredViewLODScale = FMath::Square(InViewLODScale); - check(InMeshScreenSizeSquared >= 0.0f && InMeshScreenSizeSquared <= 1.0f); + check(InMeshScreenSizeSquared >= 0.0f); float ComponentScreenSize = InMeshScreenSizeSquared; if (NumSubsections > 1) @@ -1896,7 +1891,7 @@ int32 FLandscapeComponentSceneProxy::ConvertBatchElementLODToBatchElementIndex(i return BatchElementIndex; } -float FLandscapeComponentSceneProxy::ComputeBatchElementCurrentLOD(int32 InSelectedLODIndex, float InComponentScreenSize) const +float FLandscapeComponentSceneProxy::ComputeBatchElementCurrentLOD(int32 InSelectedLODIndex, float InComponentScreenSize, float InViewLODScale) const { check(LODScreenRatioSquared.IsValidIndex(InSelectedLODIndex)); @@ -1905,17 +1900,18 @@ float FLandscapeComponentSceneProxy::ComputeBatchElementCurrentLOD(int32 InSelec float NextLODScreenRatio = LastElement ? 0 : LODScreenRatioSquared[InSelectedLODIndex + 1]; float LODScreenRatioRange = CurrentLODScreenRatio - NextLODScreenRatio; + float ScreenSizeWithLODScale = FMath::Clamp(InComponentScreenSize / InViewLODScale, 0.0f, LODScreenRatioSquared[0]); - if (InComponentScreenSize > CurrentLODScreenRatio || InComponentScreenSize < NextLODScreenRatio) + if (ScreenSizeWithLODScale > CurrentLODScreenRatio || ScreenSizeWithLODScale < NextLODScreenRatio) { // Find corresponding LODIndex to appropriately calculate Ratio and apply it to new LODIndex - int32 LODFromScreenSize = GetLODFromScreenSize(InComponentScreenSize, 1.0f); // for 4.19 only + int32 LODFromScreenSize = GetLODFromScreenSize(InComponentScreenSize, InViewLODScale); CurrentLODScreenRatio = LODScreenRatioSquared[LODFromScreenSize]; NextLODScreenRatio = LODFromScreenSize == LODScreenRatioSquared.Num() - 1 ? 0 : LODScreenRatioSquared[LODFromScreenSize + 1]; LODScreenRatioRange = CurrentLODScreenRatio - NextLODScreenRatio; } - float CurrentLODRangeRatio = (InComponentScreenSize - NextLODScreenRatio) / LODScreenRatioRange; + float CurrentLODRangeRatio = (ScreenSizeWithLODScale - NextLODScreenRatio) / LODScreenRatioRange; float fLOD = (float)InSelectedLODIndex + (1.0f - CurrentLODRangeRatio); return fLOD; @@ -1965,7 +1961,6 @@ void* FLandscapeComponentSceneProxy::InitViewCustomData(const FSceneView& InView FViewCustomDataLOD* LODData = (FViewCustomDataLOD*)new(InCustomDataMemStack) FViewCustomDataLOD(); - check(InMeshScreenSizeSquared <= 1.0f); LODData->ComponentScreenSize = InMeshScreenSizeSquared; // If a valid screen size was provided, we use it instead of recomputing it @@ -2269,7 +2264,11 @@ bool FLandscapeComponentSceneProxy::CanUseMeshBatchForShadowCascade(int8 InLODIn const FStaticMeshBatch* MeshBatch = nullptr; const TArray& PrimitiveStaticMeshes = GetPrimitiveSceneInfo()->StaticMeshes; - check(PrimitiveStaticMeshes.IsValidIndex(InLODIndex)); + if (!PrimitiveStaticMeshes.IsValidIndex(InLODIndex)) + { + return false; + } + check(PrimitiveStaticMeshes[InLODIndex].CastShadow); check(InLODIndex == PrimitiveStaticMeshes[InLODIndex].LODIndex); MeshBatch = &PrimitiveStaticMeshes[InLODIndex]; diff --git a/Engine/Source/Runtime/Landscape/Public/LandscapeRender.h b/Engine/Source/Runtime/Landscape/Public/LandscapeRender.h index 0a577a3736f3..4c26171d574e 100644 --- a/Engine/Source/Runtime/Landscape/Public/LandscapeRender.h +++ b/Engine/Source/Runtime/Landscape/Public/LandscapeRender.h @@ -654,7 +654,7 @@ protected: void CalculateLODFromScreenSize(const FSceneView& InView, float InMeshScreenSizeSquared, float InViewLODScale, int32 InSubSectionIndex, FViewCustomDataLOD& InOutLODData) const; FORCEINLINE void ComputeStaticBatchIndexToRender(FViewCustomDataLOD& OutLODData, int32 InSubSectionIndex); int8 GetLODFromScreenSize(float InScreenSizeSquared, float InViewLODScale) const; - FORCEINLINE float ComputeBatchElementCurrentLOD(int32 InSelectedLODIndex, float InComponentScreenSize) const; + FORCEINLINE_DEBUGGABLE float ComputeBatchElementCurrentLOD(int32 InSelectedLODIndex, float InComponentScreenSize, float InViewLODScale) const; FORCEINLINE void GetShaderCurrentNeighborLOD(const FSceneView& InView, float InBatchElementCurrentLOD, int8 InSubSectionX, int8 InSubSectionY, int8 InCurrentSubSectionIndex, FVector4& OutShaderCurrentNeighborLOD) const; FORCEINLINE FVector4 GetShaderLODBias() const; diff --git a/Engine/Source/Runtime/Landscape/Public/LandscapeSplineProxies.h b/Engine/Source/Runtime/Landscape/Public/LandscapeSplineProxies.h index 517ed2a5faf0..b32e7329e619 100644 --- a/Engine/Source/Runtime/Landscape/Public/LandscapeSplineProxies.h +++ b/Engine/Source/Runtime/Landscape/Public/LandscapeSplineProxies.h @@ -59,7 +59,7 @@ struct HLandscapeSplineProxy_ControlPoint : public HLandscapeSplineProxy } }; -struct HLandscapeSplineProxy_Tangent : public HLandscapeSplineProxy +struct LANDSCAPE_VTABLE HLandscapeSplineProxy_Tangent : public HLandscapeSplineProxy { DECLARE_HIT_PROXY( LANDSCAPE_API ); diff --git a/Engine/Source/Runtime/Launch/Private/Unix/LaunchUnix.cpp b/Engine/Source/Runtime/Launch/Private/Unix/LaunchUnix.cpp index 1f0dd7574ac7..5e063be6ae2c 100644 --- a/Engine/Source/Runtime/Launch/Private/Unix/LaunchUnix.cpp +++ b/Engine/Source/Runtime/Launch/Private/Unix/LaunchUnix.cpp @@ -12,15 +12,13 @@ extern int32 GuardedMain( const TCHAR* CmdLine ); * Other platforms call FEngineLoop::AppExit() in their main() (removed by preprocessor if compiled without engine), but on Unix we want to share a common main() in CommonUnixStartup module, * so not just the engine but all the programs could share this logic. Unfortunately, AppExit() practice breaks this nice approach since FEngineLoop cannot be moved outside of Launch without * making too many changes. Hence CommonUnixMain will call it through this function if WITH_ENGINE is defined. - * - * If you change the prototype here, update CommonUnixMain() too! */ -void LAUNCH_API LaunchUnix_FEngineLoop_AppExit() +void LaunchUnix_FEngineLoop_AppExit() { return FEngineLoop::AppExit(); } int main(int argc, char *argv[]) { - return CommonUnixMain(argc, argv, &GuardedMain); + return CommonUnixMain(argc, argv, &GuardedMain, &LaunchUnix_FEngineLoop_AppExit); } diff --git a/Engine/Source/Runtime/MediaAssets/Public/MediaPlayer.h b/Engine/Source/Runtime/MediaAssets/Public/MediaPlayer.h index bd5d9f407323..81fc074b7020 100644 --- a/Engine/Source/Runtime/MediaAssets/Public/MediaPlayer.h +++ b/Engine/Source/Runtime/MediaAssets/Public/MediaPlayer.h @@ -849,11 +849,11 @@ public: public: /** A delegate that is invoked when playback has reached the end of the media. */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaEvent OnEndReached; /** A delegate that is invoked when a media source has been closed. */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaEvent OnMediaClosed; /** @@ -865,7 +865,7 @@ public: * * @see OnMediaOpenFailed, OnTracksChanged */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaOpened OnMediaOpened; /** @@ -877,7 +877,7 @@ public: * * @see OnMediaOpened */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaOpenFailed OnMediaOpenFailed; /** @@ -885,7 +885,7 @@ public: * * @see OnPlaybackSuspended */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaEvent OnPlaybackResumed; /** @@ -893,7 +893,7 @@ public: * * @see OnPlaybackResumed */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaEvent OnPlaybackSuspended; /** @@ -903,7 +903,7 @@ public: * synchronously or asynchronously, this event may be executed before or * after the call to Seek returns. */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaEvent OnSeekCompleted; /** @@ -911,7 +911,7 @@ public: * * @see OnMediaOpened */ - UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer") + UPROPERTY(BlueprintAssignable, Category="Media|MediaPlayer", meta = (HideInDetailPanel)) FOnMediaPlayerMediaEvent OnTracksChanged; public: diff --git a/Engine/Source/Runtime/MovieScene/Public/Evaluation/Blending/MovieSceneInitialValueStore.h b/Engine/Source/Runtime/MovieScene/Public/Evaluation/Blending/MovieSceneInitialValueStore.h index 847ec305bde7..48a3cd350d24 100644 --- a/Engine/Source/Runtime/MovieScene/Public/Evaluation/Blending/MovieSceneInitialValueStore.h +++ b/Engine/Source/Runtime/MovieScene/Public/Evaluation/Blending/MovieSceneInitialValueStore.h @@ -14,7 +14,7 @@ template struct TBlendableTokenStack; template struct TMovieSceneBlendingActuator; /** Pre animated token producer that reverts the object's initial value from the actuator when its state is restored */ -struct FMovieSceneRemoveInitialValueTokenProducer : IMovieScenePreAnimatedTokenProducer +struct MOVIESCENE_VTABLE FMovieSceneRemoveInitialValueTokenProducer : IMovieScenePreAnimatedTokenProducer { /** Construction from the object whose initial value to remove, and the actuator to remove it from */ MOVIESCENE_API FMovieSceneRemoveInitialValueTokenProducer(TWeakPtr InWeakActuator); @@ -27,7 +27,7 @@ private: }; /** Pre animated token producer that reverts a global initial value from the actuator when its state is restored */ -struct FMovieSceneRemoveInitialGlobalValueTokenProducer : IMovieScenePreAnimatedGlobalTokenProducer +struct MOVIESCENE_VTABLE FMovieSceneRemoveInitialGlobalValueTokenProducer : IMovieScenePreAnimatedGlobalTokenProducer { /** Construction from the object whose initial value to remove, and the actuator to remove it from */ MOVIESCENE_API FMovieSceneRemoveInitialGlobalValueTokenProducer(TWeakPtr InWeakActuator); diff --git a/Engine/Source/Runtime/MovieScene/Public/Evaluation/MovieSceneSequenceTemplateStore.h b/Engine/Source/Runtime/MovieScene/Public/Evaluation/MovieSceneSequenceTemplateStore.h index 30a38e0bdc8e..05963719db73 100644 --- a/Engine/Source/Runtime/MovieScene/Public/Evaluation/MovieSceneSequenceTemplateStore.h +++ b/Engine/Source/Runtime/MovieScene/Public/Evaluation/MovieSceneSequenceTemplateStore.h @@ -25,7 +25,7 @@ struct IMovieSceneSequenceTemplateStore /** * Implementation of a template store that just returns UMovieSceneSequence::PrecompiledEvaluationTemplate */ -struct FMovieSceneSequencePrecompiledTemplateStore : IMovieSceneSequenceTemplateStore +struct MOVIESCENE_VTABLE FMovieSceneSequencePrecompiledTemplateStore : IMovieSceneSequenceTemplateStore { MOVIESCENE_API virtual FMovieSceneEvaluationTemplate& AccessTemplate(UMovieSceneSequence& Sequence); -}; \ No newline at end of file +}; diff --git a/Engine/Source/Runtime/MovieScene/Public/IMovieScenePlayer.h b/Engine/Source/Runtime/MovieScene/Public/IMovieScenePlayer.h index ccf2e3dc5ba0..556af6ce8d4c 100644 --- a/Engine/Source/Runtime/MovieScene/Public/IMovieScenePlayer.h +++ b/Engine/Source/Runtime/MovieScene/Public/IMovieScenePlayer.h @@ -49,7 +49,7 @@ struct EMovieSceneViewportParams * Interface for movie scene players * Provides information for playback of a movie scene */ -class IMovieScenePlayer +class MOVIESCENE_VTABLE IMovieScenePlayer { public: virtual ~IMovieScenePlayer() { } diff --git a/Engine/Source/Runtime/MovieScene/Public/MovieSceneNameableTrack.h b/Engine/Source/Runtime/MovieScene/Public/MovieSceneNameableTrack.h index 0065a4801a7d..b63ee1ecc59a 100644 --- a/Engine/Source/Runtime/MovieScene/Public/MovieSceneNameableTrack.h +++ b/Engine/Source/Runtime/MovieScene/Public/MovieSceneNameableTrack.h @@ -12,7 +12,7 @@ * Base class for movie scene tracks that can be renamed by the user. */ UCLASS(abstract, MinimalAPI) -class UMovieSceneNameableTrack +class MOVIESCENE_VTABLE UMovieSceneNameableTrack : public UMovieSceneTrack { GENERATED_BODY() diff --git a/Engine/Source/Runtime/MovieScene/Public/MovieSceneSection.h b/Engine/Source/Runtime/MovieScene/Public/MovieSceneSection.h index 51c6c72e07dd..b53c7cbdcdd4 100644 --- a/Engine/Source/Runtime/MovieScene/Public/MovieSceneSection.h +++ b/Engine/Source/Runtime/MovieScene/Public/MovieSceneSection.h @@ -133,7 +133,7 @@ public: * Base class for movie scene sections */ UCLASS(abstract, DefaultToInstanced, MinimalAPI, BlueprintType) -class UMovieSceneSection +class MOVIESCENE_VTABLE UMovieSceneSection : public UMovieSceneSignedObject { GENERATED_UCLASS_BODY() diff --git a/Engine/Source/Runtime/MovieScene/Public/MovieSceneSequence.h b/Engine/Source/Runtime/MovieScene/Public/MovieSceneSequence.h index c5e9362ca5ef..26b3eeac3388 100644 --- a/Engine/Source/Runtime/MovieScene/Public/MovieSceneSequence.h +++ b/Engine/Source/Runtime/MovieScene/Public/MovieSceneSequence.h @@ -19,7 +19,7 @@ struct FMovieSceneObjectCache; * Abstract base class for movie scene animations (C++ version). */ UCLASS(MinimalAPI, Config = Engine, BlueprintType) -class UMovieSceneSequence +class MOVIESCENE_VTABLE UMovieSceneSequence : public UMovieSceneSignedObject { public: diff --git a/Engine/Source/Runtime/MovieSceneCapture/Private/FrameGrabber.cpp b/Engine/Source/Runtime/MovieSceneCapture/Private/FrameGrabber.cpp index 887bdb42b711..8e30e5590717 100644 --- a/Engine/Source/Runtime/MovieSceneCapture/Private/FrameGrabber.cpp +++ b/Engine/Source/Runtime/MovieSceneCapture/Private/FrameGrabber.cpp @@ -226,7 +226,7 @@ FFrameGrabber::FFrameGrabber(TSharedRef Viewport, FIntPoint Desi FWidgetPath WidgetPath(Window.ToSharedRef(), JustWindow); if (WidgetPath.ExtendPathTo(FWidgetMatcher(ViewportWidget.ToSharedRef()), EVisibility::Visible)) { - FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(ViewportWidget.ToSharedRef()).Get(FArrangedWidget::NullWidget); + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(ViewportWidget.ToSharedRef()).Get(FArrangedWidget::GetNullWidget()); FVector2D Position = ArrangedWidget.Geometry.GetAbsolutePosition(); FVector2D Size = ArrangedWidget.Geometry.GetAbsoluteSize(); diff --git a/Engine/Source/Runtime/NavigationSystem/Public/NavMesh/NavMeshRenderingComponent.h b/Engine/Source/Runtime/NavigationSystem/Public/NavMesh/NavMeshRenderingComponent.h index 6e419704d30e..0005401d357a 100644 --- a/Engine/Source/Runtime/NavigationSystem/Public/NavMesh/NavMeshRenderingComponent.h +++ b/Engine/Source/Runtime/NavigationSystem/Public/NavMesh/NavMeshRenderingComponent.h @@ -131,7 +131,7 @@ private: }; #if WITH_RECAST && !UE_BUILD_SHIPPING && !UE_BUILD_TEST -class FNavMeshDebugDrawDelegateHelper : public FDebugDrawDelegateHelper +class NAVIGATIONSYSTEM_VTABLE FNavMeshDebugDrawDelegateHelper : public FDebugDrawDelegateHelper { typedef FDebugDrawDelegateHelper Super; diff --git a/Engine/Source/Runtime/Navmesh/Public/Detour/DetourCommon.h b/Engine/Source/Runtime/Navmesh/Public/Detour/DetourCommon.h index 57650b54428d..e4e396b9465a 100644 --- a/Engine/Source/Runtime/Navmesh/Public/Detour/DetourCommon.h +++ b/Engine/Source/Runtime/Navmesh/Public/Detour/DetourCommon.h @@ -74,7 +74,7 @@ template inline T dtClamp(T v, T mn, T mx) { return v < mn ? mn : (v > /// Returns the square root of the value. /// @param[in] x The value. /// @return The square root of the vlaue. -float dtSqrt(float x); +NAVMESH_API float dtSqrt(float x); /// @} /// @name Vector helper functions. diff --git a/Engine/Source/Runtime/Online/HTTP/Public/HttpRetrySystem.h b/Engine/Source/Runtime/Online/HTTP/Public/HttpRetrySystem.h index e9f390d878aa..0cefd061cc9e 100644 --- a/Engine/Source/Runtime/Online/HTTP/Public/HttpRetrySystem.h +++ b/Engine/Source/Runtime/Online/HTTP/Public/HttpRetrySystem.h @@ -63,7 +63,7 @@ namespace FHttpRetrySystem /** * class FRequest is what the retry system accepts as inputs */ - class FRequest + class HTTP_VTABLE FRequest : public FHttpRequestAdapterBase { public: diff --git a/Engine/Source/Runtime/OpenGLDrv/Private/Linux/OpenGLLinux.h b/Engine/Source/Runtime/OpenGLDrv/Private/Linux/OpenGLLinux.h index 23ef87fb2571..a5978c01f1a1 100644 --- a/Engine/Source/Runtime/OpenGLDrv/Private/Linux/OpenGLLinux.h +++ b/Engine/Source/Runtime/OpenGLDrv/Private/Linux/OpenGLLinux.h @@ -363,7 +363,7 @@ THIRD_PARTY_INCLUDES_END ENUM_GL_ENTRYPOINTS_OPTIONAL(EnumMacro) /** Declare all GL functions. */ -#define DECLARE_GL_ENTRYPOINTS(Type,Func) extern Type Func; +#define DECLARE_GL_ENTRYPOINTS(Type,Func) extern Type OPENGLDRV_API Func; // We need to make pointer names different from GL functions otherwise we may end up getting // addresses of those symbols when looking for extensions. diff --git a/Engine/Source/Runtime/RHI/Public/RHIResources.h b/Engine/Source/Runtime/RHI/Public/RHIResources.h index 372f10e8486d..f5c2c753387a 100644 --- a/Engine/Source/Runtime/RHI/Public/RHIResources.h +++ b/Engine/Source/Runtime/RHI/Public/RHIResources.h @@ -203,12 +203,12 @@ private: FSHAHash Hash; }; -class FRHIVertexShader : public FRHIShader {}; -class FRHIHullShader : public FRHIShader {}; -class FRHIDomainShader : public FRHIShader {}; -class FRHIPixelShader : public FRHIShader {}; -class FRHIGeometryShader : public FRHIShader {}; -class FRHIRayTracingShader : public FRHIShader {}; +class RHI_VTABLE FRHIVertexShader : public FRHIShader {}; +class RHI_VTABLE FRHIHullShader : public FRHIShader {}; +class RHI_VTABLE FRHIDomainShader : public FRHIShader {}; +class RHI_VTABLE FRHIPixelShader : public FRHIShader {}; +class RHI_VTABLE FRHIGeometryShader : public FRHIShader {}; +class RHI_VTABLE FRHIRayTracingShader : public FRHIShader {}; class RHI_API FRHIComputeShader : public FRHIShader { diff --git a/Engine/Source/Runtime/RenderCore/Public/RenderGraphResources.h b/Engine/Source/Runtime/RenderCore/Public/RenderGraphResources.h index 10fb72db33f2..699c21a0e1af 100644 --- a/Engine/Source/Runtime/RenderCore/Public/RenderGraphResources.h +++ b/Engine/Source/Runtime/RenderCore/Public/RenderGraphResources.h @@ -408,7 +408,7 @@ struct FPooledRDGBuffer return ++RefCount; } - uint32 Release(); + RENDERCORE_API uint32 Release(); inline uint32 GetRefCount() { diff --git a/Engine/Source/Runtime/RenderCore/Public/Shader.h b/Engine/Source/Runtime/RenderCore/Public/Shader.h index 448612bfb0a5..0331e0d6c896 100644 --- a/Engine/Source/Runtime/RenderCore/Public/Shader.h +++ b/Engine/Source/Runtime/RenderCore/Public/Shader.h @@ -225,13 +225,6 @@ public: checkf(BufferIndex == InBufferIndex, TEXT("Tweak FShaderLooseParameterBufferInfo type sizes")); } - inline uint32 GetHash() const - { - uint32 Hash = FCrc::TypeCrc32(BufferIndex, 0); - Hash = FCrc::TypeCrc32(BufferSize, Hash); - return Hash; - } - friend FArchive& operator<<(FArchive& Ar,FShaderLooseParameterBufferInfo& Info) { Ar << Info.BufferIndex; @@ -265,16 +258,6 @@ public: return Ar; } - inline uint32 GetHash() const - { - uint32 Hash = 0; - for (const FShaderLooseParameterBufferInfo& ShaderLooseParameterBufferInfo: LooseParameterBuffers) - { - Hash = HashCombine(ShaderLooseParameterBufferInfo.GetHash(), Hash); - } - return Hash; - } - inline bool operator==(const FShaderParameterMapInfo& Rhs) const { return UniformBuffers == Rhs.UniformBuffers diff --git a/Engine/Source/Runtime/RenderCore/Public/VertexFactory.h b/Engine/Source/Runtime/RenderCore/Public/VertexFactory.h index 5aa9f7256ad3..a49e276fc395 100644 --- a/Engine/Source/Runtime/RenderCore/Public/VertexFactory.h +++ b/Engine/Source/Runtime/RenderCore/Public/VertexFactory.h @@ -641,7 +641,7 @@ public: ERHIFeatureLevel::Type FeatureLevel, const FVertexFactory* VertexFactory, const struct FMeshBatchElement& BatchElement, - FMeshDrawSingleShaderBindings& ShaderBindings, + class FMeshDrawSingleShaderBindings& ShaderBindings, FVertexInputStreamArray& VertexStreams ) const { diff --git a/Engine/Source/Runtime/Renderer/Public/MeshDrawShaderBindings.h b/Engine/Source/Runtime/Renderer/Public/MeshDrawShaderBindings.h index ced1dae03ed7..26d3fa1bccef 100644 --- a/Engine/Source/Runtime/Renderer/Public/MeshDrawShaderBindings.h +++ b/Engine/Source/Runtime/Renderer/Public/MeshDrawShaderBindings.h @@ -28,8 +28,7 @@ public: inline uint32 GetHash() const { uint32 LocalFrequency = Frequency; - uint32 Hash = FCrc::TypeCrc32(LocalFrequency, ParameterMapInfo.GetHash()); - return Hash; + return FCrc::TypeCrc32(LocalFrequency, 0); } bool operator==(const FMeshDrawShaderBindingsLayout& Rhs) const diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Application/SlateApplication.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Application/SlateApplication.cpp index 8f910b151af7..125980eb43a7 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/Application/SlateApplication.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/Application/SlateApplication.cpp @@ -24,6 +24,9 @@ #include "Framework/Application/SWindowTitleBar.h" #include "Input/HittestGrid.h" #include "HAL/PlatformApplicationMisc.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#endif #include "Framework/Application/IWidgetReflector.h" #include "Framework/Commands/GenericCommands.h" @@ -460,6 +463,13 @@ FAutoConsoleVariableRef CVarAllowCursorQueries( TEXT("") ); +int32 bRequireFocusForGamepadInput = 0; +FAutoConsoleVariableRef CVarRequireFocusForGamepadInput( + TEXT("Slate.RequireFocusForGamepadInput"), + bRequireFocusForGamepadInput, + TEXT("") +); + ////////////////////////////////////////////////////////////////////////// bool FSlateApplication::MouseCaptorHelper::HasCapture() const { @@ -882,9 +892,15 @@ void FPopupSupport::SendNotifications( const FWidgetPath& WidgetsUnderCursor ) void FSlateApplication::SetPlatformApplication(const TSharedRef& InPlatformApplication) { PlatformApplication->SetMessageHandler(MakeShareable(new FGenericApplicationMessageHandler())); +#if WITH_ACCESSIBILITY + PlatformApplication->SetAccessibleMessageHandler(MakeShareable(new FGenericAccessibleMessageHandler())); +#endif PlatformApplication = InPlatformApplication; PlatformApplication->SetMessageHandler(CurrentApplication.ToSharedRef()); +#if WITH_ACCESSIBILITY + PlatformApplication->SetAccessibleMessageHandler(CurrentApplication->GetAccessibleMessageHandler().ToSharedRef()); +#endif } void FSlateApplication::OverridePlatformApplication(TSharedPtr InPlatformApplication) @@ -910,6 +926,9 @@ TSharedRef FSlateApplication::Create(const TSharedRefSetMessageHandler( CurrentApplication.ToSharedRef() ); +#if WITH_ACCESSIBILITY + PlatformApplication->SetAccessibleMessageHandler(CurrentApplication->GetAccessibleMessageHandler().ToSharedRef()); +#endif // The grid needs to know the size and coordinate system of the desktop. // Some monitor setups have a primary monitor on the right and below the @@ -962,6 +981,9 @@ FSlateApplication::FSlateApplication() , bSlateWindowActive(true) , Scale( 1.0f ) , DragTriggerDistance( 0 ) +#if WITH_ACCESSIBILITY + , AccessibleMessageHandler(new FSlateAccessibleMessageHandler()) +#endif , CursorRadius( 0.0f ) , LastUserInteractionTime( 0.0 ) , LastUserInteractionTimeForThrottling( 0.0 ) @@ -1009,6 +1031,9 @@ FSlateApplication::FSlateApplication() , AppIcon( FCoreStyle::Get().GetBrush("DefaultAppIcon") ) , VirtualDesktopRect( 0,0,0,0 ) , NavigationConfig(MakeShared()) +#if WITH_EDITOR + , EditorNavigationConfig(MakeShared()) +#endif , SimulateGestures(false, (int32)EGestureEvent::Count) , ProcessingInput(0) , InputManager(MakeShared()) @@ -1042,6 +1067,9 @@ FSlateApplication::FSlateApplication() RegisterUser(MakeShareable(new FSlateUser(0, false))); NavigationConfig->OnRegister(); +#if WITH_EDITOR + EditorNavigationConfig->OnRegister(); +#endif SimulateGestures[(int32)EGestureEvent::LongPress] = true; @@ -1589,7 +1617,7 @@ void FSlateApplication::PrivateDrawWindows( TSharedPtr DrawOnlyThisWind void FSlateApplication::PollGameDeviceState() { - if( ActiveModalWindows.Num() == 0 && !GIntraFrameDebuggingGameThread ) + if( ActiveModalWindows.Num() == 0 && !GIntraFrameDebuggingGameThread && (!bRequireFocusForGamepadInput || IsActive())) { // Don't poll when a modal window open or intra frame debugging is happening PlatformApplication->PollGameDeviceState( GetDeltaTime() ); @@ -2076,13 +2104,53 @@ bool FSlateApplication::CanDisplayWindows() const return Renderer.IsValid() && Renderer->AreShadersInitialized(); } +#if WITH_EDITOR +static bool IsFocusInViewport(const TSet> Viewports, const FWeakWidgetPath& FocusPath) +{ + if (Viewports.Num() > 0) + { + for (const TWeakPtr FocusWidget : FocusPath.Widgets) + { + for (const TWeakPtr Viewport : Viewports) + { + if (FocusWidget == Viewport) + { + return true; + } + } + } + } + return false; +} +#endif + EUINavigation FSlateApplication::GetNavigationDirectionFromKey(const FKeyEvent& InKeyEvent) const { +#if WITH_EDITOR + // Check if the focused widget is an editor widget or a PIE widget so we know which config to use. + if (const FSlateUser* User = GetUser(GetUserIndexForKeyboard())) + { + if (!IsFocusInViewport(AllGameViewports, User->GetWeakFocusPath())) + { + return EditorNavigationConfig->GetNavigationDirectionFromKey(InKeyEvent); + } + } +#endif return NavigationConfig->GetNavigationDirectionFromKey(InKeyEvent); } EUINavigation FSlateApplication::GetNavigationDirectionFromAnalog(const FAnalogInputEvent& InAnalogEvent) { +#if WITH_EDITOR + // Check if the focused widget is an editor widget or a PIE widget so we know which config to use. + if (const FSlateUser* User = GetUser(GetUserIndexForKeyboard())) + { + if (!IsFocusInViewport(AllGameViewports, User->GetWeakFocusPath())) + { + return EditorNavigationConfig->GetNavigationDirectionFromAnalog(InAnalogEvent); + } + } +#endif return NavigationConfig->GetNavigationDirectionFromAnalog(InAnalogEvent); } @@ -2473,6 +2541,10 @@ void FSlateApplication::InvalidateAllViewports() void FSlateApplication::RegisterGameViewport( TSharedRef InViewport ) { RegisterViewport(InViewport); + +#if WITH_EDITOR + AllGameViewports.Add(InViewport); +#endif if (GameViewportWidget != InViewport) { @@ -2503,6 +2575,10 @@ void FSlateApplication::UnregisterGameViewport() bIsFakingTouched = false; bIsGameFakingTouch = false; +#if WITH_EDITOR + AllGameViewports.Empty(); +#endif + if (GameViewportWidget.IsValid()) { GameViewportWidget.Pin()->SetActive(false); @@ -2955,6 +3031,9 @@ bool FSlateApplication::SetUserFocus(FSlateUser* User, const FWidgetPath& InFocu // Let previously-focused widget know that it's losing focus OldFocusedWidget->OnFocusLost(FocusEvent); +#if WITH_ACCESSIBILITY + GetAccessibleMessageHandler()->OnWidgetEventRaised(OldFocusedWidget.ToSharedRef(), EAccessibleEvent::FocusChange, true, false); +#endif } #if SLATE_HAS_WIDGET_REFLECTOR @@ -2988,6 +3067,10 @@ bool FSlateApplication::SetUserFocus(FSlateUser* User, const FWidgetPath& InFocu } NavigationConfig->OnNavigationChangedFocus(OldFocusedWidget, NewFocusedWidget, FocusEvent); + +#if WITH_ACCESSIBILITY + GetAccessibleMessageHandler()->OnWidgetEventRaised(NewFocusedWidget.ToSharedRef(), EAccessibleEvent::FocusChange, false, true); +#endif } return true; @@ -4566,7 +4649,7 @@ bool FSlateApplication::TakeScreenshot(const TSharedRef& Widget, const FWidgetPath WidgetPath; FSlateApplication::Get().GeneratePathToWidgetChecked(Widget, WidgetPath); - FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::NullWidget); + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget()); FVector2D Position = ArrangedWidget.Geometry.AbsolutePosition; FVector2D Size = ArrangedWidget.Geometry.GetDrawSize(); FVector2D WindowPosition = WidgetWindow->GetPositionInScreen(); @@ -4771,6 +4854,9 @@ void FSlateApplication::UnregisterUser(int32 UserIndex) Users[UserIndex].Reset(); NavigationConfig->OnUserRemoved(UserIndex); +#if WITH_EDITOR + EditorNavigationConfig->OnUserRemoved(UserIndex); +#endif } } @@ -5350,8 +5436,8 @@ bool FSlateApplication::OnMouseDown(const TSharedPtr< FGenericWindow >& Platform bool FSlateApplication::OnMouseDown( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button, const FVector2D CursorPos ) { - // convert to touch event if we are faking it - if (bIsFakingTouch || bIsGameFakingTouch) + // convert a left mouse button click to touch event if we are faking it + if ((bIsFakingTouch || bIsGameFakingTouch) && Button == EMouseButtons::Left) { bIsFakingTouched = true; return OnTouchStarted( PlatformWindow, PlatformApplication->Cursor->GetPosition(), 1.0f, 0, 0 ); @@ -6173,8 +6259,8 @@ bool FSlateApplication::OnMouseUp( const EMouseButtons::Type Button ) bool FSlateApplication::OnMouseUp( const EMouseButtons::Type Button, const FVector2D CursorPos ) { - // convert to touch event if we are faking it - if (bIsFakingTouch || bIsGameFakingTouch) + // convert left mouse click to touch event if we are faking it + if ((bIsFakingTouch || bIsGameFakingTouch) && Button == EMouseButtons::Left) { bIsFakingTouched = false; return OnTouchEnded(PlatformApplication->Cursor->GetPosition(), 0, 0); @@ -6340,7 +6426,8 @@ FReply FSlateApplication::RouteMouseWheelOrGestureEvent(const FWidgetPath& Widge bool FSlateApplication::OnMouseMove() { - if (bIsFakingTouched || bIsGameFakingTouch) + // If the left button is pressed we fake + if ((bIsFakingTouched || bIsGameFakingTouch) && GetPressedMouseButtons().Contains(EKeys::LeftMouseButton)) { // convert to touch event if we are faking it if (bIsFakingTouched) @@ -6382,7 +6469,8 @@ bool FSlateApplication::OnMouseMove() bool FSlateApplication::OnRawMouseMove( const int32 X, const int32 Y ) { - if (bIsFakingTouched || bIsGameFakingTouch) + // We fake a move only if the left mous button is down + if ((bIsFakingTouch || bIsGameFakingTouch) && GetPressedMouseButtons().Contains(EKeys::LeftMouseButton)) { // convert to touch event if we are faking it if (bIsFakingTouched) diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Commands/Commands.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Commands/Commands.cpp index f5ebfa79d1ae..ef75abd1809a 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/Commands/Commands.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/Commands/Commands.cpp @@ -5,7 +5,7 @@ #define LOC_DEFINE_REGION -void UI_COMMAND_Function( FBindingContext* This, TSharedPtr< FUICommandInfo >& OutCommand, const TCHAR* OutSubNamespace, const TCHAR* OutCommandName, const TCHAR* OutCommandNameUnderscoreTooltip, const ANSICHAR* DotOutCommandName, const TCHAR* FriendlyName, const TCHAR* InDescription, const EUserInterfaceActionType::Type CommandType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord) +void UI_COMMAND_Function( FBindingContext* This, TSharedPtr< FUICommandInfo >& OutCommand, const TCHAR* OutSubNamespace, const TCHAR* OutCommandName, const TCHAR* OutCommandNameUnderscoreTooltip, const ANSICHAR* DotOutCommandName, const TCHAR* FriendlyName, const TCHAR* InDescription, const EUserInterfaceActionType CommandType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord) { static const FString UICommandsStr(TEXT("UICommands")); const FString Namespace = OutSubNamespace && FCString::Strlen(OutSubNamespace) > 0 ? UICommandsStr + TEXT(".") + OutSubNamespace : UICommandsStr; diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Commands/UICommandInfo.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Commands/UICommandInfo.cpp index 29cb61bb0681..666a7df6ed66 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/Commands/UICommandInfo.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/Commands/UICommandInfo.cpp @@ -36,7 +36,7 @@ FUICommandInfoDecl& FUICommandInfoDecl::DefaultChord( const FInputChord& InDefau Info->DefaultChords[static_cast(InChordIndex)] = InDefaultChord; return *this; } -FUICommandInfoDecl& FUICommandInfoDecl::UserInterfaceType( EUserInterfaceActionType::Type InType ) +FUICommandInfoDecl& FUICommandInfoDecl::UserInterfaceType( EUserInterfaceActionType InType ) { Info->UserInterfaceType = InType; return *this; @@ -75,7 +75,7 @@ const FText FUICommandInfo::GetInputText() const } -void FUICommandInfo::MakeCommandInfo( const TSharedRef& InContext, TSharedPtr< FUICommandInfo >& OutCommand, const FName InCommandName, const FText& InCommandLabel, const FText& InCommandDesc, const FSlateIcon& InIcon, const EUserInterfaceActionType::Type InUserInterfaceType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord) +void FUICommandInfo::MakeCommandInfo( const TSharedRef& InContext, TSharedPtr< FUICommandInfo >& OutCommand, const FName InCommandName, const FText& InCommandLabel, const FText& InCommandDesc, const FSlateIcon& InIcon, const EUserInterfaceActionType InUserInterfaceType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord) { ensureMsgf( !InCommandLabel.IsEmpty(), TEXT("Command labels cannot be empty") ); diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Docking/TabManager.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Docking/TabManager.cpp index d63d10e849c5..0d81a78b7407 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/Docking/TabManager.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/Docking/TabManager.cpp @@ -415,6 +415,8 @@ void FTabManager::FLayout::ProcessExtensions(const FLayoutExtender& Extender) TSharedPtr Stack = Child->AsStack(); if (Stack.IsValid()) { + StackToParentSplitterMap.Add(Stack.Get(), &Splitter); + AllStacks.Add(Stack.Get()); for (FTabManager::FTab& Tab : Stack->Tabs) @@ -446,6 +448,7 @@ void FTabManager::FLayout::ProcessExtensions(const FLayoutExtender& Extender) return AllDefinedTabs.Contains(TabId); } + TMap StackToParentSplitterMap; TArray AllStacks; TSet AllDefinedTabs; }; @@ -455,11 +458,12 @@ void FTabManager::FLayout::ProcessExtensions(const FLayoutExtender& Extender) for (FTabManager::FStack* Stack : AllTabs.AllStacks) { + FSplitter* ParentSplitter = AllTabs.StackToParentSplitterMap.FindRef(Stack); for (int32 TabIndex = 0; TabIndex < Stack->Tabs.Num();) { FTabId TabId = Stack->Tabs[TabIndex].TabId; - Extender.FindExtensions(TabId, ELayoutExtensionPosition::Before, ExtendedTabs); + Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::Before, ExtendedTabs); for (FTab& NewTab : ExtendedTabs) { if (!AllTabs.Contains(NewTab.TabId)) @@ -470,7 +474,7 @@ void FTabManager::FLayout::ProcessExtensions(const FLayoutExtender& Extender) ++TabIndex; - Extender.FindExtensions(TabId, ELayoutExtensionPosition::After, ExtendedTabs); + Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::After, ExtendedTabs); for (FTab& NewTab : ExtendedTabs) { if (!AllTabs.Contains(NewTab.TabId)) @@ -478,6 +482,42 @@ void FTabManager::FLayout::ProcessExtensions(const FLayoutExtender& Extender) Stack->Tabs.Insert(NewTab, TabIndex++); } } + + + if (ParentSplitter) + { + Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::Below, ExtendedTabs); + if (ExtendedTabs.Num()) + { + for (FTab& NewTab : ExtendedTabs) + { + if (!AllTabs.Contains(NewTab.TabId)) + { + ParentSplitter->InsertAfter(Stack->AsShared(), + FTabManager::NewStack() + ->SetHideTabWell(true) + ->AddTab(NewTab) + ); + } + } + } + + Extender.FindTabExtensions(TabId, ELayoutExtensionPosition::Above, ExtendedTabs); + if (ExtendedTabs.Num()) + { + for (FTab& NewTab : ExtendedTabs) + { + if (!AllTabs.Contains(NewTab.TabId)) + { + ParentSplitter->InsertBefore(Stack->AsShared(), + FTabManager::NewStack() + ->SetHideTabWell(true) + ->AddTab(NewTab) + ); + } + } + } + } } } } @@ -766,13 +806,13 @@ void FTabManager::SavePersistentLayout() OnPersistLayout_Handler.ExecuteIfBound( MyLayout ); } -FTabSpawnerEntry& FTabManager::RegisterTabSpawner( const FName TabId, const FOnSpawnTab& OnSpawnTab ) +FTabSpawnerEntry& FTabManager::RegisterTabSpawner(const FName TabId, const FOnSpawnTab& OnSpawnTab, const FCanSpawnTab& CanSpawnTab) { - ensure( !TabSpawner.Contains(TabId) ); - ensure( !FGlobalTabmanager::Get()->IsLegacyTabType(TabId) ); + ensure(!TabSpawner.Contains(TabId)); + ensure(!FGlobalTabmanager::Get()->IsLegacyTabType(TabId)); - TSharedRef NewSpawnerEntry = MakeShareable( new FTabSpawnerEntry( TabId, OnSpawnTab ) ); - TabSpawner.Add( TabId, NewSpawnerEntry ); + TSharedRef NewSpawnerEntry = MakeShareable(new FTabSpawnerEntry(TabId, OnSpawnTab, CanSpawnTab)); + TabSpawner.Add(TabId, NewSpawnerEntry); return NewSpawnerEntry.Get(); } @@ -912,23 +952,29 @@ void FTabManager::PopulateTabSpawnerMenu_Helper( FMenuBuilder& PopulateMe, FPopu } } -void FTabManager::MakeSpawnerMenuEntry( FMenuBuilder &PopulateMe, const TSharedPtr &SpawnerNode ) +void FTabManager::MakeSpawnerMenuEntry( FMenuBuilder &PopulateMe, const TSharedPtr &InSpawnerNode ) { - auto CanExecuteMenuEntry = [](TAttribute InMenuType) -> bool + auto CanExecuteMenuEntry = [](TWeakPtr SpawnerNode) -> bool { - return InMenuType.Get() == ETabSpawnerMenuType::Enabled; + TSharedPtr SpawnerNodePinned = SpawnerNode.Pin(); + if (SpawnerNodePinned.IsValid() && SpawnerNodePinned->MenuType.Get() == ETabSpawnerMenuType::Enabled) + { + return SpawnerNodePinned->CanSpawnTab.IsBound() ? SpawnerNodePinned->CanSpawnTab.Execute(FSpawnTabArgs(TSharedPtr(), SpawnerNodePinned->TabType)) : true; + } + + return false; }; - if ( SpawnerNode->MenuType.Get() != ETabSpawnerMenuType::Hidden ) + if (InSpawnerNode->MenuType.Get() != ETabSpawnerMenuType::Hidden ) { PopulateMe.AddMenuEntry( - SpawnerNode->GetDisplayName().IsEmpty() ? FText::FromName( SpawnerNode->TabType ) : SpawnerNode->GetDisplayName(), - SpawnerNode->GetTooltipText(), - SpawnerNode->GetIcon(), + InSpawnerNode->GetDisplayName().IsEmpty() ? FText::FromName(InSpawnerNode->TabType ) : InSpawnerNode->GetDisplayName(), + InSpawnerNode->GetTooltipText(), + InSpawnerNode->GetIcon(), FUIAction( - FExecuteAction::CreateSP(SharedThis(this), &FTabManager::InvokeTabForMenu, SpawnerNode->TabType), - FCanExecuteAction::CreateStatic(CanExecuteMenuEntry, SpawnerNode->MenuType), - FIsActionChecked::CreateSP(SpawnerNode.ToSharedRef(), &FTabSpawnerEntry::IsSoleTabInstanceSpawned) + FExecuteAction::CreateSP(SharedThis(this), &FTabManager::InvokeTabForMenu, InSpawnerNode->TabType), + FCanExecuteAction::CreateStatic(CanExecuteMenuEntry, TWeakPtr(InSpawnerNode)), + FIsActionChecked::CreateSP(InSpawnerNode.ToSharedRef(), &FTabSpawnerEntry::IsSoleTabInstanceSpawned) ), NAME_None, EUserInterfaceActionType::Check @@ -1077,7 +1123,7 @@ void FTabManager::RestoreDocumentTab( FName PlaceholderId, ESearchPreference::Ty TSharedRef FTabManager::InvokeTab( const FTabId& TabId ) { - TSharedRef NewTab = InvokeTab_Internal( TabId ); + TSharedPtr NewTab = InvokeTab_Internal( TabId ); TSharedPtr ParentWindowPtr = NewTab->GetParentWindow(); if ((NewTab->GetTabRole() == ETabRole::MajorTab || NewTab->GetTabRole() == ETabRole::NomadTab) && ParentWindowPtr.IsValid() && ParentWindowPtr != FGlobalTabmanager::Get()->GetRootWindow()) { @@ -1086,10 +1132,11 @@ TSharedRef FTabManager::InvokeTab( const FTabId& TabId ) #if PLATFORM_MAC FPlatformApplicationMisc::bChachedMacMenuStateNeedsUpdate = true; #endif - return NewTab; + // Note: we expect tabs that are manually invoked to always succeed + return NewTab.ToSharedRef(); } -TSharedRef FTabManager::InvokeTab_Internal( const FTabId& TabId ) +TSharedPtr FTabManager::InvokeTab_Internal( const FTabId& TabId ) { // Tab Spawning Rules: // @@ -1136,10 +1183,14 @@ TSharedRef FTabManager::InvokeTab_Internal( const FTabId& TabId ) if (StackToSpawnIn.IsValid()) { - const TSharedRef NewTab = SpawnTab( TabId, TSharedPtr() ); + const TSharedPtr NewTab = SpawnTab( TabId, TSharedPtr() ); + + if(NewTab.IsValid()) + { + StackToSpawnIn->OpenTab(NewTab.ToSharedRef()); + NewTab->PlaySpawnAnim(); + } - StackToSpawnIn->OpenTab( NewTab ); - NewTab->PlaySpawnAnim(); return NewTab; } else if ( FGlobalTabmanager::Get() != SharedThis(this) && NomadTabSpawner->Contains(TabId.TabType) ) @@ -1272,14 +1323,17 @@ TSharedRef FTabManager::RestoreArea_Helper( const TSharedRef NewTabWidget = SpawnTab( SomeTab.TabId, ParentWindow ); + const TSharedPtr NewTabWidget = SpawnTab( SomeTab.TabId, ParentWindow ); - if(SomeTab.TabId == NodeAsStack->ForegroundTabId) + if (NewTabWidget.IsValid()) { - WidgetToActivate = NewTabWidget; - } + if (SomeTab.TabId == NodeAsStack->ForegroundTabId) + { + WidgetToActivate = NewTabWidget; + } - NewStackWidget->AddTabWidget( NewTabWidget ); + NewStackWidget->AddTabWidget(NewTabWidget.ToSharedRef()); + } } } @@ -1374,7 +1428,7 @@ TSharedRef FTabManager::RestoreArea_Helper( const TSharedRef NewStackWidget = SNew(SDockingTabStack, FTabManager::NewStack()); - NewStackWidget->OpenTab( SpawnTab( FName(NAME_None), ParentWindow ) ); + NewStackWidget->OpenTab(SpawnTab( FName(NAME_None), ParentWindow ).ToSharedRef()); return NewStackWidget; } } @@ -1392,10 +1446,21 @@ void FTabManager::RestoreSplitterContent( const TSharedRef& SplitterN } } -bool FTabManager::CanSpawnTab(FName TabId) +bool FTabManager::CanSpawnTab(FName TabId) const { - TSharedPtr Spawner = FindTabSpawnerFor(TabId); - return Spawner.IsValid(); + return HasTabSpawner(TabId); +} + +bool FTabManager::HasTabSpawner(FName TabId) const +{ + // Look for a spawner in this tab manager. + const TSharedRef* Spawner = TabSpawner.Find(TabId); + if (Spawner == nullptr) + { + Spawner = NomadTabSpawner->Find(TabId); + } + + return Spawner != nullptr; } bool FTabManager::IsValidTabForSpawning( const FTab& SomeTab ) const @@ -1405,24 +1470,35 @@ bool FTabManager::IsValidTabForSpawning( const FTab& SomeTab ) const return ( !NomadSpawner || !NomadSpawner->Get().IsSoleTabInstanceSpawned() ); } -TSharedRef FTabManager::SpawnTab( const FTabId& TabId, const TSharedPtr& ParentWindow ) +TSharedPtr FTabManager::SpawnTab( const FTabId& TabId, const TSharedPtr& ParentWindow ) { TSharedPtr NewTabWidget; + // Whether or not the spawner overrode the ability for the tab to even spawn. This is not a failure case. + bool bSpawningAllowedBySpawner = true; // Do we know how to spawn such a tab? TSharedPtr Spawner = FindTabSpawnerFor(TabId.TabType); if ( Spawner.IsValid() ) { - NewTabWidget = Spawner->OnSpawnTab.Execute( FSpawnTabArgs( ParentWindow, TabId ) ); - NewTabWidget->SetLayoutIdentifier( TabId ); - NewTabWidget->ProvideDefaultLabel( Spawner->GetDisplayName().IsEmpty() ? FText::FromName( Spawner->TabType ) : Spawner->GetDisplayName() ); - NewTabWidget->ProvideDefaultIcon( Spawner->GetIcon().GetIcon() ); - - // The spawner tracks that last tab it spawned - Spawner->SpawnedTabPtr = NewTabWidget; + if (Spawner->CanSpawnTab.IsBound()) + { + bSpawningAllowedBySpawner = Spawner->CanSpawnTab.Execute(FSpawnTabArgs(ParentWindow, TabId)); + } + + if(bSpawningAllowedBySpawner) + { + NewTabWidget = Spawner->OnSpawnTab.Execute(FSpawnTabArgs(ParentWindow, TabId)); + NewTabWidget->SetLayoutIdentifier(TabId); + NewTabWidget->ProvideDefaultLabel(Spawner->GetDisplayName().IsEmpty() ? FText::FromName(Spawner->TabType) : Spawner->GetDisplayName()); + NewTabWidget->ProvideDefaultIcon(Spawner->GetIcon().GetIcon()); + + // The spawner tracks that last tab it spawned + Spawner->SpawnedTabPtr = NewTabWidget; + } } - if ( !NewTabWidget.IsValid() ) + // The tab was allowed to be spawned but failed for some reason + if (bSpawningAllowedBySpawner && !NewTabWidget.IsValid() ) { // We don't know how to spawn this tab. // Make a dummy tab so that things aren't entirely broken. @@ -1443,9 +1519,12 @@ TSharedRef FTabManager::SpawnTab( const FTabId& TabId, const TSharedPt NewTabWidget->SetLayoutIdentifier(TabId); } - NewTabWidget->SetTabManager( SharedThis(this) ); + if (NewTabWidget.IsValid()) + { + NewTabWidget->SetTabManager(SharedThis(this)); + } - return NewTabWidget.ToSharedRef(); + return NewTabWidget; } TSharedPtr FTabManager::FindExistingLiveTab( const FTabId& TabId ) const @@ -1717,18 +1796,17 @@ TSharedPtr FTabManager::FindTabUnderNode( const FTabMatcher TSharedPtr FTabManager::FindTabSpawnerFor(FName TabId) { // Look for a spawner in this tab manager. - TSharedRef* Spawner = TabSpawner.Find( TabId ); - if (Spawner == NULL) + TSharedRef* Spawner = TabSpawner.Find(TabId); + if (Spawner == nullptr) { - Spawner = NomadTabSpawner->Find( TabId ); + Spawner = NomadTabSpawner->Find(TabId); } - return (Spawner != NULL) + return (Spawner != nullptr) ? TSharedPtr(*Spawner) : TSharedPtr(); } - int32 FTabManager::FindTabInCollapsedAreas( const FTabMatcher& Matcher ) { for ( int32 CollapsedDockAreaIndex=0; CollapsedDockAreaIndex < CollapsedDockAreas.Num(); ++CollapsedDockAreaIndex ) @@ -1855,13 +1933,13 @@ void FGlobalTabmanager::SetActiveTab( const TSharedPtr& NewActiv } } -FTabSpawnerEntry& FGlobalTabmanager::RegisterNomadTabSpawner( const FName TabId, const FOnSpawnTab& OnSpawnTab ) +FTabSpawnerEntry& FGlobalTabmanager::RegisterNomadTabSpawner(const FName TabId, const FOnSpawnTab& OnSpawnTab, const FCanSpawnTab& CanSpawnTab) { - ensure( !NomadTabSpawner->Contains(TabId) ); - ensure( !IsLegacyTabType(TabId) ); + ensure(!NomadTabSpawner->Contains(TabId)); + ensure(!IsLegacyTabType(TabId)); - TSharedRef NewSpawnerEntry = MakeShareable( new FTabSpawnerEntry( TabId, OnSpawnTab ) ); - NomadTabSpawner->Add( TabId, NewSpawnerEntry ); + TSharedRef NewSpawnerEntry = MakeShareable(new FTabSpawnerEntry(TabId, OnSpawnTab, CanSpawnTab)); + NomadTabSpawner->Add(TabId, NewSpawnerEntry); return NewSpawnerEntry.Get(); } diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/Mac/MacMenu.cpp b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/Mac/MacMenu.cpp index d00b7d082857..d6a187d3c8c4 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/Mac/MacMenu.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/Mac/MacMenu.cpp @@ -13,7 +13,7 @@ struct FMacMenuItemState { TSharedPtr Block; - EMultiBlockType::Type Type; + EMultiBlockType Type; NSString* Title; NSString* KeyEquivalent; uint32 KeyModifiers; diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBox.cpp b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBox.cpp index 2b621aaba205..65c888759422 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBox.cpp @@ -172,7 +172,7 @@ bool FMultiBlock::GetSearchable() const * @param InType Type of MultiBox * @param bInShouldCloseWindowAfterMenuSelection Sets whether or not the window that contains this multibox should be destroyed after the user clicks on a menu item in this box */ -FMultiBox::FMultiBox( const EMultiBoxType::Type InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ) +FMultiBox::FMultiBox( const EMultiBoxType InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ) : CustomizationData( new FMultiBoxCustomizationData( InCustomization.GetCustomizationName() ) ) , CommandLists() , Blocks() @@ -187,7 +187,7 @@ FMultiBox::~FMultiBox() { } -TSharedRef FMultiBox::Create( const EMultiBoxType::Type InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ) +TSharedRef FMultiBox::Create( const EMultiBoxType InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ) { TSharedRef NewBox = MakeShareable( new FMultiBox( InType, InCustomization, bInShouldCloseWindowAfterMenuSelection ) ); diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBoxBuilder.cpp b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBoxBuilder.cpp index be6022ebefef..02fcb7025ea3 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBoxBuilder.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/MultiBoxBuilder.cpp @@ -16,7 +16,7 @@ #include "Framework/MultiBox/SWidgetBlock.h" #include "Framework/MultiBox/SGroupMarkerBlock.h" -FMultiBoxBuilder::FMultiBoxBuilder( const EMultiBoxType::Type InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection, const TSharedPtr< const FUICommandList >& InCommandList, TSharedPtr InExtender, FName InTutorialHighlightName ) +FMultiBoxBuilder::FMultiBoxBuilder( const EMultiBoxType InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection, const TSharedPtr< const FUICommandList >& InCommandList, TSharedPtr InExtender, FName InTutorialHighlightName ) : MultiBox( FMultiBox::Create( InType, InCustomization, bInShouldCloseWindowAfterMenuSelection ) ) , CommandListStack() , TutorialHighlightName(InTutorialHighlightName) @@ -118,7 +118,7 @@ static FName GenerateTutorialIdentfierName(FName InContainerName, FName InElemen } } -FBaseMenuBuilder::FBaseMenuBuilder( const EMultiBoxType::Type InType, const bool bInShouldCloseWindowAfterMenuSelection, TSharedPtr< const FUICommandList > InCommandList, bool bInCloseSelfOnly, TSharedPtr InExtender, const ISlateStyle* InStyleSet, FName InTutorialHighlightName ) +FBaseMenuBuilder::FBaseMenuBuilder( const EMultiBoxType InType, const bool bInShouldCloseWindowAfterMenuSelection, TSharedPtr< const FUICommandList > InCommandList, bool bInCloseSelfOnly, TSharedPtr InExtender, const ISlateStyle* InStyleSet, FName InTutorialHighlightName ) : FMultiBoxBuilder( InType, FMultiBoxCustomization::None, bInShouldCloseWindowAfterMenuSelection, InCommandList, InExtender, InTutorialHighlightName ) , bCloseSelfOnly( bInCloseSelfOnly ) { @@ -140,7 +140,7 @@ void FBaseMenuBuilder::AddMenuEntry( const TSharedPtr< const FUICommandInfo > In ApplyHook(InExtensionHook, EExtensionHook::After); } -void FBaseMenuBuilder::AddMenuEntry( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& InAction, FName InExtensionHook, const EUserInterfaceActionType::Type UserInterfaceActionType, FName InTutorialHighlightName ) +void FBaseMenuBuilder::AddMenuEntry( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& InAction, FName InExtensionHook, const EUserInterfaceActionType UserInterfaceActionType, FName InTutorialHighlightName ) { ApplySectionBeginning(); @@ -153,7 +153,7 @@ void FBaseMenuBuilder::AddMenuEntry( const TAttribute& InLabel, const TAt ApplyHook(InExtensionHook, EExtensionHook::After); } -void FBaseMenuBuilder::AddMenuEntry( const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const FName& InExtensionHook, const TAttribute& InToolTip, const EUserInterfaceActionType::Type UserInterfaceActionType, FName InTutorialHighlightName ) +void FBaseMenuBuilder::AddMenuEntry( const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const FName& InExtensionHook, const TAttribute& InToolTip, const EUserInterfaceActionType UserInterfaceActionType, FName InTutorialHighlightName ) { ApplySectionBeginning(); @@ -219,7 +219,7 @@ void FMenuBuilder::AddMenuSeparator(FName InExtensionHook) ApplyHook(InExtensionHook, EExtensionHook::After); } -void FMenuBuilder::AddSubMenu( const TAttribute& InMenuLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InSubMenu, const FUIAction& InUIAction, FName InExtensionHook, const EUserInterfaceActionType::Type InUserInterfaceActionType, const bool bInOpenSubMenuOnClick, const FSlateIcon& InIcon, const bool bInShouldCloseWindowAfterMenuSelection /*= true*/ ) +void FMenuBuilder::AddSubMenu( const TAttribute& InMenuLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InSubMenu, const FUIAction& InUIAction, FName InExtensionHook, const EUserInterfaceActionType InUserInterfaceActionType, const bool bInOpenSubMenuOnClick, const FSlateIcon& InIcon, const bool bInShouldCloseWindowAfterMenuSelection /*= true*/ ) { ApplySectionBeginning(); @@ -376,7 +376,7 @@ void FToolBarBuilder::AddToolBarButton(const TSharedPtr< const FUICommandInfo > ApplyHook(InExtensionHook, EExtensionHook::After); } -void FToolBarBuilder::AddToolBarButton(const FUIAction& InAction, FName InExtensionHook, const TAttribute& InLabelOverride, const TAttribute& InToolTipOverride, const TAttribute& InIconOverride, const EUserInterfaceActionType::Type UserInterfaceActionType, FName InTutorialHighlightName ) +void FToolBarBuilder::AddToolBarButton(const FUIAction& InAction, FName InExtensionHook, const TAttribute& InLabelOverride, const TAttribute& InToolTipOverride, const TAttribute& InIconOverride, const EUserInterfaceActionType UserInterfaceActionType, FName InTutorialHighlightName ) { ApplySectionBeginning(); @@ -528,7 +528,7 @@ void FButtonRowBuilder::AddButton( const TSharedPtr< const FUICommandInfo > InCo MultiBox->AddMultiBlock( NewButtonRowBlock ); } -void FButtonRowBuilder::AddButton( const FText& InLabel, const FText& InToolTip, const FUIAction& UIAction, const FSlateIcon& InIcon, const EUserInterfaceActionType::Type UserInterfaceActionType ) +void FButtonRowBuilder::AddButton( const FText& InLabel, const FText& InToolTip, const FUIAction& UIAction, const FSlateIcon& InIcon, const EUserInterfaceActionType UserInterfaceActionType ) { ApplySectionBeginning(); diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.cpp b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.cpp index 894c24e2d7da..1fb461acc53d 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.cpp @@ -18,7 +18,7 @@ FButtonRowBlock::FButtonRowBlock( const TSharedPtr< const FUICommandInfo > InCom { } -FButtonRowBlock::FButtonRowBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType ) +FButtonRowBlock::FButtonRowBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, const EUserInterfaceActionType InUserInterfaceActionType ) : FMultiBlock( UIAction, NAME_None, EMultiBlockType::ButtonRow ) , LabelOverride( InLabel ) , ToolTipOverride( InToolTip ) @@ -156,7 +156,7 @@ void SButtonRowBlock::BuildMultiBlockWidget(const ISlateStyle* StyleSet, const F // What type of UI should we create for this block? - const EUserInterfaceActionType::Type UserInterfaceType = ButtonRowBlock->GetAction().IsValid() ? ButtonRowBlock->GetAction()->GetUserInterfaceType() : ButtonRowBlock->UserInterfaceActionTypeOverride; + const EUserInterfaceActionType UserInterfaceType = ButtonRowBlock->GetAction().IsValid() ? ButtonRowBlock->GetAction()->GetUserInterfaceType() : ButtonRowBlock->UserInterfaceActionTypeOverride; if( UserInterfaceType == EUserInterfaceActionType::Button ) { ChildSlot diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.h b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.h index dcdcbe9b02cd..692d6a7c7c9c 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.h +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SButtonRowBlock.h @@ -44,7 +44,7 @@ public: * @param UIAction Action to execute when the button is clicked or when state should be checked * @param UserInterfaceActionType The style of button to display */ - FButtonRowBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType ); + FButtonRowBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, const EUserInterfaceActionType InUserInterfaceActionType ); /** FMultiBlock interface */ virtual bool HasIcon() const override; @@ -73,7 +73,7 @@ private: /** In the case where a command is not bound, the user interface action type to use. If a command is bound, we simply use the action type associated with that command. */ - EUserInterfaceActionType::Type UserInterfaceActionTypeOverride; + EUserInterfaceActionType UserInterfaceActionTypeOverride; }; diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.cpp b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.cpp index 31968113feb2..893b1888882d 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.cpp @@ -27,7 +27,7 @@ FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TSharedPtr } -FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InEntryBuilder, TSharedPtr InExtender, bool bInSubMenu, bool bInSubMenuOnClick, const FSlateIcon& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection) +FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InEntryBuilder, TSharedPtr InExtender, bool bInSubMenu, bool bInSubMenuOnClick, const FSlateIcon& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection) : FMultiBlock( InUIAction, InExtensionHook, EMultiBlockType::MenuEntry ) , LabelOverride( InLabel ) , ToolTipOverride( InToolTip ) @@ -43,7 +43,7 @@ FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TAttribute } -FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection) +FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, const EUserInterfaceActionType InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection) : FMultiBlock( UIAction, InExtensionHook, EMultiBlockType::MenuEntry ) , LabelOverride( InLabel ) , ToolTipOverride( InToolTip ) @@ -104,7 +104,7 @@ FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const TAttribute { } -FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const TAttribute& InToolTip, const EUserInterfaceActionType::Type InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection) +FMenuEntryBlock::FMenuEntryBlock( const FName& InExtensionHook, const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const TAttribute& InToolTip, const EUserInterfaceActionType InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection) : FMultiBlock( UIAction, InExtensionHook, EMultiBlockType::MenuEntry ) , ToolTipOverride( InToolTip ) , EntryWidget( Contents ) @@ -464,7 +464,7 @@ TSharedRef< SWidget > SMenuEntryBlock::BuildMenuEntryWidget( const FMenuEntryBui } // What type of UI should we create for this block? - EUserInterfaceActionType::Type UserInterfaceType = MenuEntryBlock->UserInterfaceActionType; + EUserInterfaceActionType UserInterfaceType = MenuEntryBlock->UserInterfaceActionType; if ( UICommand.IsValid() ) { // If we have a UICommand, then this is specified in the command. @@ -714,7 +714,7 @@ TSharedRef< SWidget> SMenuEntryBlock::BuildSubMenuWidget( const FMenuEntryBuildP } // What type of UI should we create for this block? - EUserInterfaceActionType::Type UserInterfaceType = MenuEntryBlock->UserInterfaceActionType; + EUserInterfaceActionType UserInterfaceType = MenuEntryBlock->UserInterfaceActionType; if ( UICommand.IsValid() ) { // If we have a UICommand, then this is specified in the command. diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.h b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.h index f8bc515ab008..ce71c9045197 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.h +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SMenuEntryBlock.h @@ -101,11 +101,11 @@ public: * @param bInShouldCloseWindowAfterMenuSelection In the case of a submenu, whether it should close after an item is selected * @param bInInvertLabelOnHover Whether to invert the label text's color on hover */ - FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InEntryBuilder, TSharedPtr InExtender, bool bInSubMenu, bool bInSubMenuOnClick, const FSlateIcon& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); + FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InEntryBuilder, TSharedPtr InExtender, bool bInSubMenu, bool bInSubMenuOnClick, const FSlateIcon& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); - FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); + FMenuEntryBlock( const FName& InExtensionHook, const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); - FMenuEntryBlock( const FName& InExtensionHook, const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const TAttribute& InToolTip, const EUserInterfaceActionType::Type InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); + FMenuEntryBlock( const FName& InExtensionHook, const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const TAttribute& InToolTip, const EUserInterfaceActionType InUserInterfaceActionType, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); FMenuEntryBlock( const FName& InExtensionHook, const TSharedRef< SWidget > Contents, const FNewMenuDelegate& InEntryBuilder, TSharedPtr InExtender, bool bInSubMenu, bool bInSubMenuOnClick, TSharedPtr< const FUICommandList > InCommandList, bool bInCloseSelfOnly, bool bInShouldCloseWindowAfterMenuSelection = true); @@ -154,7 +154,7 @@ private: /** In the case where a command is not bound, the user interface action type to use. If a command is bound, we simply use the action type associated with that command. */ - EUserInterfaceActionType::Type UserInterfaceActionType; + EUserInterfaceActionType UserInterfaceActionType; /** True if the menu should close itself and all its children or the entire open menu stack */ bool bCloseSelfOnly; diff --git a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SToolBarButtonBlock.cpp b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SToolBarButtonBlock.cpp index e7ce4e63eb66..a972d02bfe05 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SToolBarButtonBlock.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/MultiBox/SToolBarButtonBlock.cpp @@ -22,7 +22,7 @@ FToolBarButtonBlock::FToolBarButtonBlock( const TSharedPtr< const FUICommandInfo { } -FToolBarButtonBlock::FToolBarButtonBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const TAttribute& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType ) +FToolBarButtonBlock::FToolBarButtonBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const TAttribute& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType InUserInterfaceActionType ) : FMultiBlock( InUIAction ) , LabelOverride( InLabel ) , ToolTipOverride( InToolTip ) @@ -211,7 +211,7 @@ void SToolBarButtonBlock::BuildMultiBlockWidget(const ISlateStyle* StyleSet, con EMultiBlockLocation::Type BlockLocation = GetMultiBlockLocation(); // What type of UI should we create for this block? - EUserInterfaceActionType::Type UserInterfaceType = ToolBarButtonBlock->UserInterfaceActionType; + EUserInterfaceActionType UserInterfaceType = ToolBarButtonBlock->UserInterfaceActionType; if ( Action.IsValid() ) { // If we have a UICommand, then this is specified in the command. diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Text/PlainTextLayoutMarshaller.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Text/PlainTextLayoutMarshaller.cpp index cf14ec163aff..9fdbbffda293 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/Text/PlainTextLayoutMarshaller.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/Text/PlainTextLayoutMarshaller.cpp @@ -1,7 +1,6 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "Framework/Text/PlainTextLayoutMarshaller.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Text/TextLayout.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Text/TextLayout.cpp index 6854d9df5e4e..022cc1108144 100644 --- a/Engine/Source/Runtime/Slate/Private/Framework/Text/TextLayout.cpp +++ b/Engine/Source/Runtime/Slate/Private/Framework/Text/TextLayout.cpp @@ -1664,7 +1664,8 @@ bool FTextLayout::InsertAt(const FTextLocation& Location, const FString& Text) { // Insert the new text run to the left of the non-text run TSharedRef NewTextRun = CreateDefaultTextRun( LineModel.Text, FTextRange( RunRange.BeginIndex, RunRange.BeginIndex + Text.Len() ) ); - RunModel.SetTextRange( FTextRange( RunRange.BeginIndex + 1, RunRange.EndIndex + Text.Len() ) ); + // Move the Non-Text Run to right to free space for the new run. + RunModel.SetTextRange( FTextRange( RunRange.BeginIndex + Text.Len(), RunRange.EndIndex + Text.Len() ) ); LineModel.Runs.Insert( NewTextRun, RunIndex++ ); } else diff --git a/Engine/Source/Runtime/Slate/Private/Framework/Text/TextRange.cpp b/Engine/Source/Runtime/Slate/Private/Framework/Text/TextRange.cpp deleted file mode 100644 index 3b3fa56567a7..000000000000 --- a/Engine/Source/Runtime/Slate/Private/Framework/Text/TextRange.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "Framework/Text/TextRange.h" - -void FTextRange::CalculateLineRangesFromString(const FString& Input, TArray& LineRanges) -{ - int32 LineBeginIndex = 0; - - // Loop through splitting at new-lines - const TCHAR* const InputStart = *Input; - for(const TCHAR* CurrentChar = InputStart; CurrentChar && *CurrentChar; ++CurrentChar) - { - // Handle a chain of \r\n slightly differently to stop the FChar::IsLinebreak adding two separate new-lines - const bool bIsWindowsNewLine = (*CurrentChar == '\r' && *(CurrentChar + 1) == '\n'); - if(bIsWindowsNewLine || FChar::IsLinebreak(*CurrentChar)) - { - const int32 LineEndIndex = (CurrentChar - InputStart); - check(LineEndIndex >= LineBeginIndex); - LineRanges.Emplace(FTextRange(LineBeginIndex, LineEndIndex)); - - if(bIsWindowsNewLine) - { - ++CurrentChar; // skip the \n of the \r\n chain - } - LineBeginIndex = (CurrentChar - InputStart) + 1; // The next line begins after the end of the current line - } - } - - // Process any remaining string after the last new-line - if(LineBeginIndex <= Input.Len()) - { - LineRanges.Emplace(FTextRange(LineBeginIndex, Input.Len())); - } -} diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Accessibility/SlateAccessibleWidgets.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Accessibility/SlateAccessibleWidgets.cpp new file mode 100644 index 000000000000..b036248845a5 --- /dev/null +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Accessibility/SlateAccessibleWidgets.cpp @@ -0,0 +1,189 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableText.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SSlider.h" +#include "Widgets/Text/STextBlock.h" + +void FSlateAccessibleButton::Activate() +{ + if (Widget.IsValid()) + { + StaticCastSharedPtr(Widget.Pin())->ExecuteOnClick(); + } +} + +void FSlateAccessibleCheckBox::Activate() +{ + if (Widget.IsValid()) + { + StaticCastSharedPtr(Widget.Pin())->ToggleCheckedState(); + } +} + +bool FSlateAccessibleCheckBox::IsCheckable() const { + return true; +} + +bool FSlateAccessibleCheckBox::GetCheckedState() const { + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->IsChecked(); + } + return false; +} + +const FString& FSlateAccessibleEditableText::GetText() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetText().ToString(); + } + static FString EmptyString; + return EmptyString; +} + +bool FSlateAccessibleEditableText::IsPassword() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->IsTextPassword(); + } + return true; +} + +bool FSlateAccessibleEditableText::IsReadOnly() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->IsTextReadOnly(); + } + return true; +} + +FString FSlateAccessibleEditableText::GetValue() const +{ + return GetText(); +} + +void FSlateAccessibleEditableText::SetValue(const FString& Value) +{ + if (Widget.IsValid()) + { + StaticCastSharedPtr(Widget.Pin())->SetText(FText::FromString(Value)); + } +} + +const FString& FSlateAccessibleEditableTextBox::GetText() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetText().ToString(); + } + static FString EmptyString; + return EmptyString; +} + +bool FSlateAccessibleEditableTextBox::IsPassword() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->IsPassword(); + } + return true; +} + +bool FSlateAccessibleEditableTextBox::IsReadOnly() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->IsReadOnly(); + } + return true; +} + +FString FSlateAccessibleEditableTextBox::GetValue() const +{ + return GetText(); +} + +void FSlateAccessibleEditableTextBox::SetValue(const FString& Value) +{ + if (Widget.IsValid()) + { + StaticCastSharedPtr(Widget.Pin())->SetText(FText::FromString(Value)); + } +} + +bool FSlateAccessibleSlider::IsReadOnly() const +{ + if (Widget.IsValid()) + { + return !Widget.Pin()->IsEnabled() || !Widget.Pin()->IsInteractable(); + } + return true; +} + +float FSlateAccessibleSlider::GetStepSize() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetStepSize(); + } + return 0.0f; +} + +float FSlateAccessibleSlider::GetMaximum() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetMaxValue(); + } + return 1.0f; +} + +float FSlateAccessibleSlider::GetMinimum() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetMinValue(); + } + return 0.0f; +} + +FString FSlateAccessibleSlider::GetValue() const +{ + if (Widget.IsValid()) + { + return FString::SanitizeFloat(StaticCastSharedPtr(Widget.Pin())->GetValue()); + } + return FString::SanitizeFloat(0.0f); +} + +void FSlateAccessibleSlider::SetValue(const FString& Value) +{ + if (Widget.IsValid()) + { + StaticCastSharedPtr(Widget.Pin())->SetValue(FCString::Atof(*Value)); + } +} + +const FString& FSlateAccessibleTextBlock::GetText() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetText().ToString(); + } + else + { + static const FString EmptyString; + return EmptyString; + } +} + +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SButton.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SButton.cpp index 40a40ec4ba50..6fbd35cd2b11 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SButton.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SButton.cpp @@ -4,7 +4,10 @@ #include "Rendering/DrawElements.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Text/STextBlock.h" - +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#endif static FName SButtonTypeName("SButton"); @@ -97,7 +100,7 @@ int32 SButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry AllottedGeometry.ToPaintGeometry(), BrushResource, DrawEffects, - BrushResource->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint() * BorderBackgroundColor.Get().GetColor(InWidgetStyle) + BrushResource->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint() * BorderBackgroundColor.Get().GetColor(InWidgetStyle) * ColorAndOpacity.Get() ); } @@ -161,7 +164,7 @@ FReply SButton::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEv if (PressMethod == EButtonPressMethod::ButtonPress) { //execute our "OnClicked" delegate, and get the reply - Reply = OnClicked.IsBound() ? OnClicked.Execute() : FReply::Handled(); + Reply = ExecuteOnClick(); //You should ALWAYS handle the OnClicked event. ensure(Reply.IsEventHandled() == true); @@ -194,7 +197,7 @@ FReply SButton::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent if ( PressMethod == EButtonPressMethod::ButtonRelease || ( PressMethod == EButtonPressMethod::DownAndUp && bWasPressed ) ) { //execute our "OnClicked" delegate, and get the reply - Reply = OnClicked.IsBound() ? OnClicked.Execute() : FReply::Handled(); + Reply = ExecuteOnClick(); //You should ALWAYS handle the OnClicked event. ensure(Reply.IsEventHandled() == true); @@ -222,7 +225,7 @@ FReply SButton::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEv if(InputClickMethod == EButtonClickMethod::MouseDown) { //get the reply from the execute function - Reply = OnClicked.IsBound() ? OnClicked.Execute() : FReply::Handled(); + Reply = ExecuteOnClick(); //You should ALWAYS handle the OnClicked event. ensure(Reply.IsEventHandled() == true); @@ -288,9 +291,9 @@ FReply SButton::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEven // pressed the button down first, then we'll allow the click to proceed without an active capture const bool bTriggerForMouseEvent = (InputClickMethod == EButtonClickMethod::MouseUp || HasMouseCapture() ); - if ( ( bTriggerForTouchEvent || bTriggerForMouseEvent ) && OnClicked.IsBound() == true ) + if ( ( bTriggerForTouchEvent || bTriggerForMouseEvent ) ) { - Reply = OnClicked.Execute(); + Reply = ExecuteOnClick(); } } } @@ -362,6 +365,22 @@ void SButton::OnMouseCaptureLost(const FCaptureLostEvent& CaptureLostEvent) Release(); } +FReply SButton::ExecuteOnClick() +{ + if (OnClicked.IsBound()) + { + FReply Reply = OnClicked.Execute(); +#if WITH_ACCESSIBILITY + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetEventRaised(AsShared(), EAccessibleEvent::Activate); +#endif + return Reply; + } + else + { + return FReply::Handled(); + } +} + void SButton::Press() { if ( !bIsPressed ) @@ -499,4 +518,11 @@ void SButton::SetTouchMethod(EButtonTouchMethod::Type InTouchMethod) void SButton::SetPressMethod(EButtonPressMethod::Type InPressMethod) { PressMethod = InPressMethod; -} \ No newline at end of file +} + +#if WITH_ACCESSIBILITY +TSharedPtr SButton::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleButton(SharedThis(this))); +} +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SCheckBox.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SCheckBox.cpp index 3739b6becb4d..abd676c7b9c4 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SCheckBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SCheckBox.cpp @@ -7,7 +7,10 @@ #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Images/SImage.h" - +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#endif /** * Construct this widget @@ -297,6 +300,10 @@ void SCheckBox::ToggleCheckedState() // The state of the check box changed. Execute the delegate to notify users OnCheckStateChanged.ExecuteIfBound( ECheckBoxState::Checked ); } + +#if WITH_ACCESSIBILITY + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetEventRaised(AsShared(), EAccessibleEvent::Activate, State == ECheckBoxState::Checked, IsCheckboxChecked.Get() == ECheckBoxState::Checked); +#endif } void SCheckBox::SetIsChecked(TAttribute InIsChecked) @@ -509,3 +516,10 @@ const FSlateBrush* SCheckBox::GetUndeterminedPressedImage() const { return UndeterminedPressedImage ? UndeterminedPressedImage : &Style->UndeterminedPressedImage; } + +#if WITH_ACCESSIBILITY +TSharedPtr SCheckBox::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleCheckBox(SharedThis(this))); +} +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableText.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableText.cpp index d58ccd07fbff..d22a44228cb9 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableText.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableText.cpp @@ -5,6 +5,9 @@ #include "Framework/Text/PlainTextLayoutMarshaller.h" #include "Widgets/Text/SlateEditableTextLayout.h" #include "Types/ReflectionMetadata.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#endif SEditableText::SEditableText() { @@ -598,3 +601,16 @@ float SEditableText::UpdateAndClampVerticalScrollBar(const float InViewOffset, c { return 0.0f; } + +#if WITH_ACCESSIBILITY +TSharedPtr SEditableText::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleEditableText(SharedThis(this))); +} + +void SEditableText::SetDefaultAccessibleText(EAccessibleType AccessibleType) +{ + TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + Text.Bind(this, &SEditableText::GetHintText); +} +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableTextBox.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableTextBox.cpp index fed0a9655870..40f2d0693ebf 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableTextBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SEditableTextBox.cpp @@ -5,6 +5,10 @@ #include "Widgets/Layout/SBox.h" #include "Widgets/Notifications/SPopUpErrorText.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#endif + /** * Construct this widget * @@ -80,6 +84,17 @@ void SEditableTextBox::Construct( const FArguments& InArgs ) ]; } +#if WITH_ACCESSIBILITY + // These functions need to be called again because SWidget::Construct is called before SEditableText is created. + if (!InArgs._AccessibleParams.AccessibleText.IsSet()) + { + SetDefaultAccessibleText(EAccessibleType::Main); + } + if (!InArgs._AccessibleParams.AccessibleSummaryText.IsSet()) + { + SetDefaultAccessibleText(EAccessibleType::Summary); + } +#endif } void SEditableTextBox::SetStyle(const FEditableTextBoxStyle* InStyle) @@ -396,3 +411,21 @@ void SEditableTextBox::SetVirtualKeyboardDismissAction(TAttributeSetVirtualKeyboardDismissAction(InVirtualKeyboardDismissAction); } + +#if WITH_ACCESSIBILITY +TSharedPtr SEditableTextBox::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleEditableTextBox(SharedThis(this))); +} + +void SEditableTextBox::SetDefaultAccessibleText(EAccessibleType AccessibleType) +{ + // The parent Construct() function will call this before EditableText exists, + // so we need a guard here to ignore that function call. + if (EditableText.IsValid()) + { + TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + Text.Bind(EditableText.Get(), &SEditableText::GetHintText); + } +} +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SMultiLineEditableTextBox.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SMultiLineEditableTextBox.cpp index e7b84c92e9d6..b00fe4dbcddc 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SMultiLineEditableTextBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SMultiLineEditableTextBox.cpp @@ -57,7 +57,7 @@ void SMultiLineEditableTextBox::Construct( const FArguments& InArgs ) .Style(&InArgs._Style->ScrollBarStyle) .Orientation(Orient_Horizontal) .AlwaysShowScrollbar(InArgs._AlwaysShowScrollbars) - .Thickness(FVector2D(5.0f, 5.0f)); + .Thickness(FVector2D(9.0f, 9.0f)); } bHasExternalVScrollBar = InArgs._VScrollBar.IsValid(); @@ -69,7 +69,7 @@ void SMultiLineEditableTextBox::Construct( const FArguments& InArgs ) .Style(&InArgs._Style->ScrollBarStyle) .Orientation(Orient_Vertical) .AlwaysShowScrollbar(InArgs._AlwaysShowScrollbars) - .Thickness(FVector2D(5.0f, 5.0f)); + .Thickness(FVector2D(9.0f, 9.0f)); } SBorder::Construct( SBorder::FArguments() @@ -198,6 +198,11 @@ void SMultiLineEditableTextBox::SetStyle(const FEditableTextBoxStyle* InStyle) BorderImageReadOnly = &Style->BackgroundImageReadOnly; } +void SMultiLineEditableTextBox::SetTextStyle(const FTextBlockStyle* InTextStyle) +{ + EditableText->SetTextStyle(InTextStyle); +} + FSlateColor SMultiLineEditableTextBox::DetermineForegroundColor() const { check(Style); diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SSlider.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SSlider.cpp index 875bd110093e..1eb2dd2caf28 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SSlider.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SSlider.cpp @@ -3,6 +3,9 @@ #include "Widgets/Input/SSlider.h" #include "Rendering/DrawElements.h" #include "Framework/Application/SlateApplication.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#endif void SSlider::Construct( const SSlider::FArguments& InDeclaration ) { @@ -17,6 +20,8 @@ void SSlider::Construct( const SSlider::FArguments& InDeclaration ) Orientation = InDeclaration._Orientation; StepSize = InDeclaration._StepSize; ValueAttribute = InDeclaration._Value; + MinValue = InDeclaration._MinValue; + MaxValue = InDeclaration._MaxValue; SliderBarColor = InDeclaration._SliderBarColor; SliderHandleColor = InDeclaration._SliderHandleColor; bIsFocusable = InDeclaration._IsFocusable; @@ -47,7 +52,7 @@ int32 SSlider::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometr const float Indentation = IndentHandle.Get() ? HandleSize.X : 0.0f; // We clamp to make sure that the slider cannot go out of the slider Length. - const float SliderPercent = FMath::Clamp(ValueAttribute.Get(), 0.0f, 1.0f); + const float SliderPercent = FMath::Clamp(GetNormalizedValue(), 0.0f, 1.0f); const float SliderLength = AllottedWidth - (Indentation + HandleSize.X); const float SliderHandleOffset = SliderPercent * SliderLength; const float SliderY = 0.5f * AllottedHeight; @@ -180,7 +185,7 @@ FNavigationReply SSlider::OnNavigation(const FGeometry& MyGeometry, const FNavig } if (ValueAttribute.Get() != NewValue) { - CommitValue(FMath::Clamp(NewValue, 0.0f, 1.0f)); + CommitValue(FMath::Clamp(NewValue, MinValue, MaxValue)); } } @@ -349,6 +354,8 @@ FReply SSlider::OnTouchEnded(const FGeometry& MyGeometry, const FPointerEvent& I void SSlider::CommitValue(float NewValue) { + const float OldValue = GetValue(); + if (!ValueAttribute.IsBound()) { ValueAttribute.Set(NewValue); @@ -379,17 +386,17 @@ float SSlider::PositionToValue( const FGeometry& MyGeometry, const FVector2D& Ab RelativeValue = (Denominator != 0.f) ? ((MyGeometry.Size.Y - LocalPosition.Y) - HalfIndentation) / Denominator : 0.f; } - RelativeValue = FMath::Clamp(RelativeValue, 0.0f, 1.0f); + RelativeValue = FMath::Clamp(RelativeValue, 0.0f, 1.0f) * (MaxValue - MinValue) + MinValue; if (bMouseUsesStep) { float direction = ValueAttribute.Get() - RelativeValue; if (direction > StepSize.Get() / 2.0f) { - return FMath::Clamp(ValueAttribute.Get() - StepSize.Get(), 0.0f, 1.0f); + return FMath::Clamp(ValueAttribute.Get() - StepSize.Get(), MinValue, MaxValue); } else if (direction < StepSize.Get() / -2.0f) { - return FMath::Clamp(ValueAttribute.Get() + StepSize.Get(), 0.0f, 1.0f); + return FMath::Clamp(ValueAttribute.Get() + StepSize.Get(), MinValue, MaxValue); } else { @@ -434,11 +441,33 @@ float SSlider::GetValue() const return ValueAttribute.Get(); } +float SSlider::GetNormalizedValue() const +{ + if (MaxValue == MinValue) + { + return 1.0f; + } + else + { + return (ValueAttribute.Get() - MinValue) / (MaxValue - MinValue); + } +} + void SSlider::SetValue(const TAttribute& InValueAttribute) { ValueAttribute = InValueAttribute; } +void SSlider::SetMinAndMaxValues(float InMinValue, float InMaxValue) +{ + MinValue = InMinValue; + MaxValue = InMaxValue; + if (MinValue > MaxValue) + { + MaxValue = MinValue; + } +} + void SSlider::SetIndentHandle(const TAttribute& InIndentHandle) { IndentHandle = InIndentHandle; @@ -482,3 +511,9 @@ void SSlider::SetRequiresControllerLock(bool RequiresControllerLock) { bRequiresControllerLock = RequiresControllerLock; } +#if WITH_ACCESSIBILITY +TSharedPtr SSlider::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleSlider(SharedThis(this))); +} +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SVectorInputBox.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SVectorInputBox.cpp index 209ae6601d40..2ef7987c35a6 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Input/SVectorInputBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Input/SVectorInputBox.cpp @@ -56,6 +56,14 @@ void SVectorInputBox::ConstructX( const FArguments& InArgs, TSharedRef()) + .MaxValue(TOptional()) + .MinSliderValue(TOptional()) + .MaxSliderValue(TOptional()) + .LinearDeltaSensitivity(1) + .Delta(InArgs._SpinDelta) + .OnBeginSliderMovement(InArgs._OnBeginSliderMovement) + .OnEndSliderMovement(InArgs._OnEndSliderMovement) .Label() [ LabelWidget @@ -91,6 +99,14 @@ void SVectorInputBox::ConstructY( const FArguments& InArgs, TSharedRef()) + .MaxValue(TOptional()) + .MinSliderValue(TOptional()) + .MaxSliderValue(TOptional()) + .LinearDeltaSensitivity(1) + .Delta(InArgs._SpinDelta) + .OnBeginSliderMovement(InArgs._OnBeginSliderMovement) + .OnEndSliderMovement(InArgs._OnEndSliderMovement) .Label() [ LabelWidget @@ -126,6 +142,14 @@ void SVectorInputBox::ConstructZ( const FArguments& InArgs, TSharedRef()) + .MaxValue(TOptional()) + .MinSliderValue(TOptional()) + .MaxSliderValue(TOptional()) + .LinearDeltaSensitivity(1) + .Delta(InArgs._SpinDelta) + .OnBeginSliderMovement(InArgs._OnBeginSliderMovement) + .OnEndSliderMovement(InArgs._OnEndSliderMovement) .Label() [ LabelWidget diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SBox.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SBox.cpp index 026aea0dc145..cc55556b1ef0 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SBox.cpp @@ -22,23 +22,24 @@ void SBox::Construct( const FArguments& InArgs ) MaxDesiredWidth = InArgs._MaxDesiredWidth; MaxDesiredHeight = InArgs._MaxDesiredHeight; + MinAspectRatio = InArgs._MinAspectRatio; MaxAspectRatio = InArgs._MaxAspectRatio; ChildSlot .HAlign( InArgs._HAlign ) .VAlign( InArgs._VAlign ) .Padding( InArgs._Padding ) - [ - InArgs._Content.Widget - ]; + [ + InArgs._Content.Widget + ]; } void SBox::SetContent(const TSharedRef< SWidget >& InContent) { ChildSlot - [ - InContent - ]; + [ + InContent + ]; Invalidate(EInvalidateWidget::Layout); } @@ -124,6 +125,15 @@ void SBox::SetMaxDesiredHeight(TAttribute InMaxDesiredHeight) } } +void SBox::SetMinAspectRatio(TAttribute InMinAspectRatio) +{ + if (!MinAspectRatio.IdenticalTo(InMinAspectRatio)) + { + MinAspectRatio = InMinAspectRatio; + Invalidate(EInvalidateWidget::LayoutAndVolatility); + } +} + void SBox::SetMaxAspectRatio(TAttribute InMaxAspectRatio) { if (!MaxAspectRatio.IdenticalTo(InMaxAspectRatio)) @@ -214,6 +224,7 @@ void SBox::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildr const EVisibility ChildVisibility = ChildSlot.GetWidget()->GetVisibility(); if ( ArrangedChildren.Accepts(ChildVisibility) ) { + const FOptionalSize CurrentMinAspectRatio = MinAspectRatio.Get(); const FOptionalSize CurrentMaxAspectRatio = MaxAspectRatio.Get(); const FMargin SlotPadding(ChildSlot.SlotPadding.Get()); bool bAlignChildren = true; @@ -221,26 +232,39 @@ void SBox::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildr AlignmentArrangeResult XAlignmentResult(0, 0); AlignmentArrangeResult YAlignmentResult(0, 0); - if ( CurrentMaxAspectRatio.IsSet() ) + if (CurrentMaxAspectRatio.IsSet() || CurrentMinAspectRatio.IsSet()) { float CurrentWidth = FMath::Min(AllottedGeometry.Size.X, ChildSlot.GetWidget()->GetDesiredSize().X); float CurrentHeight = FMath::Min(AllottedGeometry.Size.Y, ChildSlot.GetWidget()->GetDesiredSize().Y); - float AspectRatioWidth = CurrentMaxAspectRatio.Get(); - if ( AspectRatioWidth != 0 && CurrentHeight > 0 && CurrentWidth > 0 ) + float MinAspectRatioWidth = CurrentMinAspectRatio.IsSet() ? CurrentMinAspectRatio.Get() : 0; + float MaxAspectRatioWidth = CurrentMaxAspectRatio.IsSet() ? CurrentMaxAspectRatio.Get() : 0; + if (CurrentHeight > 0 && CurrentWidth > 0) { - const float AspectRatioHeight = 1.0f / AspectRatioWidth; + const float CurrentRatioWidth = (AllottedGeometry.GetLocalSize().X / AllottedGeometry.GetLocalSize().Y); - const float CurrentRatioWidth = ( AllottedGeometry.GetLocalSize().X / AllottedGeometry.GetLocalSize().Y ); - const float CurrentRatioHeight = 1.0f / CurrentRatioWidth; - - if ( CurrentRatioWidth > AspectRatioWidth /*|| CurrentRatioHeight > AspectRatioHeight*/ ) + bool bFitMaxRatio = (CurrentRatioWidth > MaxAspectRatioWidth && MaxAspectRatioWidth != 0); + bool bFitMinRatio = (CurrentRatioWidth < MinAspectRatioWidth && MinAspectRatioWidth != 0); + if (bFitMaxRatio || bFitMinRatio) { XAlignmentResult = AlignChild(AllottedGeometry.GetLocalSize().X, ChildSlot, SlotPadding); YAlignmentResult = AlignChild(AllottedGeometry.GetLocalSize().Y, ChildSlot, SlotPadding); - float NewWidth = AspectRatioWidth * XAlignmentResult.Size; - float NewHeight = AspectRatioHeight * NewWidth; + float NewWidth; + float NewHeight; + + if (bFitMaxRatio) + { + const float MaxAspectRatioHeight = 1.0f / MaxAspectRatioWidth; + NewWidth = MaxAspectRatioWidth * XAlignmentResult.Size; + NewHeight = MaxAspectRatioHeight * NewWidth; + } + else + { + const float MinAspectRatioHeight = 1.0f / MinAspectRatioWidth; + NewWidth = MinAspectRatioWidth * XAlignmentResult.Size; + NewHeight = MinAspectRatioHeight * NewWidth; + } const float MaxWidth = AllottedGeometry.Size.X - SlotPadding.GetTotalSpaceAlong(); const float MaxHeight = AllottedGeometry.Size.Y - SlotPadding.GetTotalSpaceAlong(); @@ -306,4 +330,4 @@ int32 SBox::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, return TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); } return LayerId; -} +} \ No newline at end of file diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBar.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBar.cpp index 8570313d8a7e..0f446f0a2d91 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBar.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBar.cpp @@ -27,11 +27,12 @@ void SScrollBar::Construct(const FArguments& InArgs) SBorder::Construct( SBorder::FArguments() .BorderImage(FCoreStyle::Get().GetBrush("NoBorder")) + .Padding(InArgs._Padding) [ SNew(SVerticalBox) + SVerticalBox::Slot() - .FillHeight( 1 ) + .FillHeight(1) [ SNew(SBorder) .BorderImage(BackgroundBrush) @@ -55,10 +56,11 @@ void SScrollBar::Construct(const FArguments& InArgs) .ThumbSlot() [ SAssignNew(DragThumb, SBorder) - .BorderImage( this, &SScrollBar::GetDragThumbImage ) + .BorderImage(this, &SScrollBar::GetDragThumbImage) .ColorAndOpacity(this, &SScrollBar::GetThumbOpacity) .HAlign(HAlign_Center) .VAlign(VAlign_Center) + .Padding(0) [ SAssignNew(ThicknessSpacer, SSpacer) .Size(InArgs._Thickness) diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBox.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBox.cpp index 88f62b2aed2d..5305144d074b 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBox.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SScrollBox.cpp @@ -144,6 +144,7 @@ void SScrollBox::Construct( const FArguments& InArgs ) ScrollBar = ConstructScrollBar(); ScrollBar->SetDragFocusCause(InArgs._ScrollBarDragFocusCause); ScrollBar->SetThickness(InArgs._ScrollBarThickness); + ScrollBar->SetPadding(InArgs._ScrollBarPadding); ScrollBar->SetUserVisibility(InArgs._ScrollBarVisibility); ScrollBar->SetScrollBarAlwaysVisible(InArgs._ScrollBarAlwaysVisible); @@ -323,6 +324,13 @@ float SScrollBox::GetScrollOffset() const return DesiredScrollOffset; } +float SScrollBox::GetScrollOffsetOfEnd() const +{ + const FGeometry ScrollPanelGeometry = FindChildGeometry(CachedGeometry, ScrollPanel.ToSharedRef()); + const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize()); + return FMath::Max(ContentSize - GetScrollComponentFromVector(ScrollPanelGeometry.Size), 0.0f); +} + float SScrollBox::GetViewFraction() const { const FGeometry ScrollPanelGeometry = FindChildGeometry(CachedGeometry, ScrollPanel.ToSharedRef()); @@ -489,6 +497,11 @@ void SScrollBox::SetScrollBarThickness(FVector2D InThickness) ScrollBar->SetThickness(InThickness); } +void SScrollBox::SetScrollBarPadding(const FMargin& InPadding) +{ + ScrollBar->SetPadding(InPadding); +} + void SScrollBox::SetScrollBarRightClickDragAllowed(bool bIsAllowed) { bAllowsRightClickDragScrolling = bIsAllowed; diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWidgetSwitcher.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWidgetSwitcher.cpp index eb78e89f7b55..d22ed15985e9 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWidgetSwitcher.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWidgetSwitcher.cpp @@ -2,6 +2,10 @@ #include "Widgets/Layout/SWidgetSwitcher.h" #include "Layout/LayoutUtils.h" +#if WITH_ACCESSIBILITY +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#endif SWidgetSwitcher::SWidgetSwitcher() : WidgetIndex() @@ -131,6 +135,18 @@ void SWidgetSwitcher::OnArrangeChildren(const FGeometry& AllottedGeometry, FArra if (const FSlot* ActiveSlotPtr = GetActiveSlot()) { ArrangeSingleChild(GSlateFlowDirection, AllottedGeometry, ArrangedChildren, *ActiveSlotPtr, ContentScale); + +#if WITH_ACCESSIBILITY + // This would go in SetActiveWidgetIndex were this not a TAttribute. WidgetIndex changing does not affect + // parent or visibility assignments, so we need a special custom notification to tell the accessibility + // tree to update itself. + if (LastActiveWidget != ActiveSlotPtr->GetWidget()) + { + SWidgetSwitcher* UnconstThis = const_cast(this); + UnconstThis->LastActiveWidget = ActiveSlotPtr->GetWidget(); + FSlateApplication::Get().GetAccessibleMessageHandler()->OnWidgetChildrenChanged(SharedThis(UnconstThis)); + } +#endif } } diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWindowTitleBarArea.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWindowTitleBarArea.cpp index b483f925cd00..451acc6ad424 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWindowTitleBarArea.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWindowTitleBarArea.cpp @@ -79,6 +79,7 @@ void SWindowTitleBarArea::Construct( const FArguments& InArgs ) [ SNew(SImage) .Image(this, &SWindowTitleBarArea::GetMinimizeImage) + .AccessibleText(NSLOCTEXT("WindowTitleBar", "Minimize", "Minimize")) ]; MaximizeRestoreButton = SNew(SButton) @@ -91,6 +92,7 @@ void SWindowTitleBarArea::Construct( const FArguments& InArgs ) [ SNew(SImage) .Image(this, &SWindowTitleBarArea::GetMaximizeRestoreImage) + .AccessibleText(NSLOCTEXT("WindowTitleBar", "Maximize", "Maximize")) ]; CloseButton = SNew(SButton) @@ -103,6 +105,7 @@ void SWindowTitleBarArea::Construct( const FArguments& InArgs ) [ SNew(SImage) .Image(this, &SWindowTitleBarArea::GetCloseImage) + .AccessibleText(NSLOCTEXT("WindowTitleBar", "Close", "Close")) ]; ChildSlot diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Notifications/SPopUpErrorText.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Notifications/SPopUpErrorText.cpp index 4fb309461d24..af86644b7226 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Notifications/SPopUpErrorText.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Notifications/SPopUpErrorText.cpp @@ -9,6 +9,7 @@ void SPopupErrorText::Construct( const FArguments& InArgs ) .ComboButtonStyle( FCoreStyle::Get(), "MessageLogListingComboButton" ) .HasDownArrow(false) .ContentPadding(0) + .IsFocusable(false) .ButtonContent() [ SAssignNew( HasErrorSymbol, SErrorText ) diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/SInvalidationPanel.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/SInvalidationPanel.cpp index aa0ab78872f9..d75d0e800279 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/SInvalidationPanel.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/SInvalidationPanel.cpp @@ -142,6 +142,7 @@ SInvalidationPanel::~SInvalidationPanel() if ( FSlateApplication::IsInitialized() ) { + FSlateApplicationBase::Get().OnGlobalInvalidate().RemoveAll(this); FSlateApplication::Get().ReleaseResourcesForLayoutCache(this); } } @@ -617,7 +618,7 @@ int32 SInvalidationPanel::OnPaint( const FPaintArgs& Args, const FGeometry& Allo FWidgetPath WidgetPath; if ( FSlateApplication::Get().GeneratePathToWidgetUnchecked(SafeInvalidator.ToSharedRef(), WidgetPath, EVisibility::All) ) { - FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(SafeInvalidator.ToSharedRef()).Get(FArrangedWidget::NullWidget); + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(SafeInvalidator.ToSharedRef()).Get(FArrangedWidget::GetNullWidget()); ArrangedWidget.Geometry.AppendTransform( FSlateLayoutTransform(Inverse(Args.GetWindowToDesktopTransform())) ); FSlateDrawElement::MakeBox( diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Text/STextBlock.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Text/STextBlock.cpp index 6a880db84602..09b24affc542 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Text/STextBlock.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Text/STextBlock.cpp @@ -8,6 +8,9 @@ #include "Rendering/DrawElements.h" #include "Framework/Application/SlateApplication.h" #include "Fonts/FontMeasure.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#endif DECLARE_CYCLE_STAT(TEXT("STextBlock::SetText Time"), Stat_SlateTextBlockSetText, STATGROUP_SlateVerbose) DECLARE_CYCLE_STAT(TEXT("STextBlock::OnPaint Time"), Stat_SlateTextBlockOnPaint, STATGROUP_SlateVerbose) @@ -473,3 +476,16 @@ FTextBlockStyle STextBlock::GetComputedTextStyle() const ComputedStyle.SetHighlightShape( *GetHighlightShape() ); return ComputedStyle; } + +#if WITH_ACCESSIBILITY +TSharedPtr STextBlock::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleTextBlock(SharedThis(this))); +} + +void STextBlock::SetDefaultAccessibleText(EAccessibleType AccessibleType) +{ + TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + Text.Bind(this, &STextBlock::GetTextCopy); +} +#endif diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Text/SlateEditableTextLayout.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Text/SlateEditableTextLayout.cpp index b5db6bf01910..a052001f98c5 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Text/SlateEditableTextLayout.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Text/SlateEditableTextLayout.cpp @@ -856,25 +856,43 @@ FReply FSlateEditableTextLayout::HandleKeyDown(const FKeyEvent& InKeyEvent) if (Key == EKeys::Left) { - Reply = BoolToReply(MoveCursor(FMoveCursor::Cardinal( - // Ctrl moves a whole word instead of one character. - InKeyEvent.IsControlDown() ? ECursorMoveGranularity::Word : ECursorMoveGranularity::Character, - // Move left - FIntPoint(-1, 0), - // Shift selects text. - InKeyEvent.IsShiftDown() ? ECursorAction::SelectText : ECursorAction::MoveCursor + if (OwnerWidget->IsTextPassword() && InKeyEvent.IsControlDown()) + { + // If the text is sensitive, we should not clue the user in to where word breaks are + JumpTo(ETextLocation::BeginningOfLine, InKeyEvent.IsShiftDown() ? ECursorAction::SelectText : ECursorAction::MoveCursor); + Reply = FReply::Handled(); + } + else + { + Reply = BoolToReply(MoveCursor(FMoveCursor::Cardinal( + // Ctrl moves a whole word instead of one character. + InKeyEvent.IsControlDown() ? ECursorMoveGranularity::Word : ECursorMoveGranularity::Character, + // Move left + FIntPoint(-1, 0), + // Shift selects text. + InKeyEvent.IsShiftDown() ? ECursorAction::SelectText : ECursorAction::MoveCursor ))); + } } else if (Key == EKeys::Right) { - Reply = BoolToReply(MoveCursor(FMoveCursor::Cardinal( - // Ctrl moves a whole word instead of one character. - InKeyEvent.IsControlDown() ? ECursorMoveGranularity::Word : ECursorMoveGranularity::Character, - // Move right - FIntPoint(+1, 0), - // Shift selects text. - InKeyEvent.IsShiftDown() ? ECursorAction::SelectText : ECursorAction::MoveCursor + if (OwnerWidget->IsTextPassword() && InKeyEvent.IsControlDown()) + { + // If the text is sensitive, we should not clue the user in to where word breaks are + JumpTo(ETextLocation::EndOfLine, InKeyEvent.IsShiftDown() ? ECursorAction::SelectText : ECursorAction::MoveCursor); + Reply = FReply::Handled(); + } + else + { + Reply = BoolToReply(MoveCursor(FMoveCursor::Cardinal( + // Ctrl moves a whole word instead of one character. + InKeyEvent.IsControlDown() ? ECursorMoveGranularity::Word : ECursorMoveGranularity::Character, + // Move right + FIntPoint(+1, 0), + // Shift selects text. + InKeyEvent.IsShiftDown() ? ECursorAction::SelectText : ECursorAction::MoveCursor ))); + } } else if (Key == EKeys::Up) { @@ -934,24 +952,6 @@ FReply FSlateEditableTextLayout::HandleKeyDown(const FKeyEvent& InKeyEvent) HandleCarriageReturn(); Reply = FReply::Handled(); } - else if (Key == EKeys::Delete && !OwnerWidget->IsTextReadOnly()) - { - // @Todo: Slate keybindings support more than one set of keys. - // Delete to next word boundary (Ctrl+Delete) - if (InKeyEvent.IsControlDown() && !InKeyEvent.IsAltDown() && !InKeyEvent.IsShiftDown()) - { - MoveCursor(FMoveCursor::Cardinal( - ECursorMoveGranularity::Word, - // Move right - FIntPoint(+1, 0), - // selects text. - ECursorAction::SelectText - )); - } - - FScopedEditableTextTransaction TextTransaction(*this); - Reply = BoolToReply(HandleDelete()); - } else if (Key == EKeys::Tab && OwnerWidget->CanTypeCharacter(TEXT('\t'))) { Reply = FReply::Handled(); @@ -969,7 +969,33 @@ FReply FSlateEditableTextLayout::HandleKeyDown(const FKeyEvent& InKeyEvent) CutSelectedTextToClipboard(); Reply = FReply::Handled(); } + // This must come after the Cut hotkey or else Cut is unreachable + else if (Key == EKeys::Delete && !OwnerWidget->IsTextReadOnly()) + { + // @Todo: Slate keybindings support more than one set of keys. + // Delete to next word boundary (Ctrl+Delete) + if (InKeyEvent.IsControlDown() && !InKeyEvent.IsAltDown() && !InKeyEvent.IsShiftDown()) + { + if (OwnerWidget->IsTextPassword()) + { + // If the text is sensitive, we should not clue the user in to where word breaks are + JumpTo(ETextLocation::EndOfLine, ECursorAction::SelectText); + } + else + { + MoveCursor(FMoveCursor::Cardinal( + ECursorMoveGranularity::Word, + // Move right + FIntPoint(+1, 0), + // selects text. + ECursorAction::SelectText + )); + } + } + FScopedEditableTextTransaction TextTransaction(*this); + Reply = BoolToReply(HandleDelete()); + } // @Todo: Slate keybindings support more than one set of keys. // Alternate key for copy (Ctrl+Insert) else if (Key == EKeys::Insert && InKeyEvent.IsControlDown() && CanExecuteCopy()) @@ -1013,12 +1039,20 @@ FReply FSlateEditableTextLayout::HandleKeyDown(const FKeyEvent& InKeyEvent) { FScopedEditableTextTransaction TextTransaction(*this); - MoveCursor(FMoveCursor::Cardinal( - ECursorMoveGranularity::Word, - // Move left - FIntPoint(-1, 0), - ECursorAction::SelectText + if (OwnerWidget->IsTextPassword()) + { + // If the text is sensitive, we should not clue the user in to where word breaks are + JumpTo(ETextLocation::BeginningOfLine, ECursorAction::SelectText); + } + else + { + MoveCursor(FMoveCursor::Cardinal( + ECursorMoveGranularity::Word, + // Move left + FIntPoint(-1, 0), + ECursorAction::SelectText )); + } Reply = BoolToReply(HandleBackspace()); } diff --git a/Engine/Source/Runtime/Slate/Private/Widgets/Views/SHeaderRow.cpp b/Engine/Source/Runtime/Slate/Private/Widgets/Views/SHeaderRow.cpp index bc7691cc83d2..ba1c349aadb6 100644 --- a/Engine/Source/Runtime/Slate/Private/Widgets/Views/SHeaderRow.cpp +++ b/Engine/Source/Runtime/Slate/Private/Widgets/Views/SHeaderRow.cpp @@ -48,7 +48,8 @@ public: { check(InArgs._Style); - SWidget::Construct( InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, InArgs._FlowDirectionPreference, InArgs.MetaData ); + SWidget::Construct( InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, + InArgs._FlowDirectionPreference, InArgs._AccessibleParams, InArgs.MetaData ); Style = InArgs._Style; ColumnId = Column.ColumnId; @@ -414,7 +415,8 @@ void SHeaderRow::Construct( const FArguments& InArgs ) { check(InArgs._Style); - SWidget::Construct( InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, InArgs._FlowDirectionPreference, InArgs.MetaData ); + SWidget::Construct( InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, + InArgs._FlowDirectionPreference, InArgs._AccessibleParams, InArgs.MetaData ); ScrollBarThickness = FVector2D::ZeroVector; ScrollBarVisibility = EVisibility::Collapsed; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Application/SWindowTitleBar.h b/Engine/Source/Runtime/Slate/Public/Framework/Application/SWindowTitleBar.h index 63d785a390c3..b361a8656b0b 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Application/SWindowTitleBar.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Application/SWindowTitleBar.h @@ -43,7 +43,9 @@ class SAppIconWidget { SLATE_BEGIN_ARGS( SAppIconWidget ) : _IconColorAndOpacity( FLinearColor::White ) - {} + { + _AccessibleText = NSLOCTEXT("AppIconWidget", "System", "System Menu"); + } /** Icon color and opacity */ SLATE_ATTRIBUTE( FSlateColor, IconColorAndOpacity ) @@ -207,6 +209,7 @@ protected: SNew(SImage) .Image(this, &SWindowTitleBar::GetMinimizeImage) .ColorAndOpacity(this, &SWindowTitleBar::GetWindowTitleContentColor) + .AccessibleText(NSLOCTEXT("WindowTitleBar", "Minimize", "Minimize")) ] ; @@ -222,6 +225,7 @@ protected: SNew(SImage) .Image(this, &SWindowTitleBar::GetMaximizeRestoreImage) .ColorAndOpacity(this, &SWindowTitleBar::GetWindowTitleContentColor) + .AccessibleText(NSLOCTEXT("WindowTitleBar", "Maximize", "Maximize")) ] ; @@ -237,6 +241,7 @@ protected: SNew(SImage) .Image(this, &SWindowTitleBar::GetCloseImage) .ColorAndOpacity(this, &SWindowTitleBar::GetWindowTitleContentColor) + .AccessibleText(NSLOCTEXT("WindowTitleBar", "Close", "Close")) ] ; } diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Application/SlateApplication.h b/Engine/Source/Runtime/Slate/Public/Framework/Application/SlateApplication.h index c75022999b17..abc6be8b6fba 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Application/SlateApplication.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Application/SlateApplication.h @@ -30,6 +30,9 @@ #include "Framework/Application/GestureDetector.h" class FNavigationConfig; +#if WITH_ACCESSIBILITY +class FSlateAccessibleMessageHandler; +#endif class IInputInterface; class IInputProcessor; class IPlatformTextField; @@ -1416,6 +1419,10 @@ public: virtual void SetAllUserFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause) override; virtual void SetAllUserFocusAllowingDescendantFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause) override; virtual TSharedPtr GetUserFocusedWidget(uint32 UserIndex) const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleMessageHandler() const override { return AccessibleMessageHandler; } +#endif + virtual const TArray> GetTopLevelWindows() const override { return SlateWindows; } DECLARE_EVENT_OneParam(FSlateApplication, FApplicationActivationStateChangedEvent, const bool /*IsActive*/) virtual FApplicationActivationStateChangedEvent& OnApplicationActivationStateChanged() { return ApplicationActivationStateChangedEvent; } @@ -1744,7 +1751,10 @@ private: /** These windows will be destroyed next tick. */ TArray< TSharedRef > WindowDestroyQueue; - +#if WITH_ACCESSIBILITY + /** Manager for widgets and application to interact with accessibility API */ + TSharedRef AccessibleMessageHandler; +#endif /** The stack of menus that are open */ FMenuStack MenuStack; @@ -1945,6 +1955,11 @@ private: /** Pointer to the currently registered game viewport widget if any */ TWeakPtr GameViewportWidget; +#if WITH_EDITOR + /** List of all registered game viewports since the last time UnregisterGameViewport was called. */ + TSet> AllGameViewports; +#endif + TSharedPtr SlateSoundDevice; /** The current cached absolute real time, right before we tick widgets */ @@ -2141,6 +2156,12 @@ private: /** This factory function creates a navigation config for each slate user. */ TSharedRef NavigationConfig; +#if WITH_EDITOR + /** When PIE runs, the game's navigation config will overwrite the editor's navigation config. + This separate config allows editor navigation to work even when PIE is running. */ + TSharedRef EditorNavigationConfig; +#endif + /** The simulated gestures Slate Application will be in charge of. */ TBitArray SimulateGestures; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Commands/Commands.h b/Engine/Source/Runtime/Slate/Public/Framework/Commands/Commands.h index d7ede7fe01f1..c05ca56ecac2 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Commands/Commands.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Commands/Commands.h @@ -12,7 +12,7 @@ #define LOC_DEFINE_REGION -SLATE_API void UI_COMMAND_Function(FBindingContext* This, TSharedPtr< FUICommandInfo >& OutCommand, const TCHAR* OutSubNamespace, const TCHAR* OutCommandName, const TCHAR* OutCommandNameUnderscoreTooltip, const ANSICHAR* DotOutCommandName, const TCHAR* FriendlyName, const TCHAR* InDescription, const EUserInterfaceActionType::Type CommandType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord = FInputChord()); +SLATE_API void UI_COMMAND_Function(FBindingContext* This, TSharedPtr< FUICommandInfo >& OutCommand, const TCHAR* OutSubNamespace, const TCHAR* OutCommandName, const TCHAR* OutCommandNameUnderscoreTooltip, const ANSICHAR* DotOutCommandName, const TCHAR* FriendlyName, const TCHAR* InDescription, const EUserInterfaceActionType CommandType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord = FInputChord()); // This macro requires LOCTEXT_NAMESPACE to be defined. If you don't want the command to be placed under a sub namespace, provide "" as the namespace. #define UI_COMMAND_EXT( BindingContext, OutUICommandInfo, CommandIdName, FriendlyName, InDescription, CommandType, InDefaultChord, ... ) \ @@ -106,7 +106,11 @@ protected: } /** A static instance of the command set. */ +#if PLATFORM_UNIX || PLATFORM_APPLE + static SLATE_API TWeakPtr< CommandContextType > Instance; +#else static TWeakPtr< CommandContextType > Instance; +#endif /** Pure virtual to override; describe and instantiate the commands in here by using the UI COMMAND macro. */ virtual void RegisterCommands() = 0; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Commands/UICommandInfo.h b/Engine/Source/Runtime/Slate/Public/Framework/Commands/UICommandInfo.h index bcd056ff8dea..876c5d1c2a9b 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Commands/UICommandInfo.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Commands/UICommandInfo.h @@ -8,33 +8,32 @@ #include "Layout/Visibility.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/InputChord.h" +#include "UICommandInfo.generated.h" class FBindingContext; class FUICommandInfo; /** Types of user interfaces that can be associated with a user interface action */ -namespace EUserInterfaceActionType +UENUM(BlueprintType) +enum class EUserInterfaceActionType : uint8 { - enum Type - { - /** An action which should not be associated with a user interface action */ - None, + /** An action which should not be associated with a user interface action */ + None, - /** Momentary buttons or menu items. These support enable state, and execute a delegate when clicked. */ - Button, + /** Momentary buttons or menu items. These support enable state, and execute a delegate when clicked. */ + Button, - /** Toggleable buttons or menu items that store on/off state. These support enable state, and execute a delegate when toggled. */ - ToggleButton, + /** Toggleable buttons or menu items that store on/off state. These support enable state, and execute a delegate when toggled. */ + ToggleButton, - /** Radio buttons are similar to toggle buttons in that they are for menu items that store on/off state. However they should be used to indicate that menu items in a group can only be in one state */ - RadioButton, + /** Radio buttons are similar to toggle buttons in that they are for menu items that store on/off state. However they should be used to indicate that menu items in a group can only be in one state */ + RadioButton, - /** Similar to Button but will display a readonly checkbox next to the item. */ - Check, + /** Similar to Button but will display a readonly checkbox next to the item. */ + Check, - /** Similar to Button but has the checkbox area collapsed */ - CollapsedButton - }; + /** Similar to Button but has the checkbox area collapsed */ + CollapsedButton }; UENUM() @@ -58,7 +57,7 @@ class SLATE_API FUICommandInfoDecl public: FUICommandInfoDecl& DefaultChord( const FInputChord& InDefaultChord, const EMultipleKeyBindingIndex InChordIndex = EMultipleKeyBindingIndex::Primary); - FUICommandInfoDecl& UserInterfaceType( EUserInterfaceActionType::Type InType ); + FUICommandInfoDecl& UserInterfaceType( EUserInterfaceActionType InType ); FUICommandInfoDecl& Icon( const FSlateIcon& InIcon ); FUICommandInfoDecl& Description( const FText& InDesc ); @@ -223,7 +222,7 @@ public: const FInputChord& GetDefaultChord(const EMultipleKeyBindingIndex InChordIndex) const { return DefaultChords[static_cast(InChordIndex)]; } /** Utility function to make an FUICommandInfo */ - static void MakeCommandInfo( const TSharedRef& InContext, TSharedPtr< FUICommandInfo >& OutCommand, const FName InCommandName, const FText& InCommandLabel, const FText& InCommandDesc, const FSlateIcon& InIcon, const EUserInterfaceActionType::Type InUserInterfaceType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord = FInputChord()); + static void MakeCommandInfo( const TSharedRef& InContext, TSharedPtr< FUICommandInfo >& OutCommand, const FName InCommandName, const FText& InCommandLabel, const FText& InCommandDesc, const FSlateIcon& InIcon, const EUserInterfaceActionType InUserInterfaceType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord = FInputChord()); /** Utility function to unregister an FUICommandInfo */ static void UnregisterCommandInfo(const TSharedRef& InContext, const TSharedRef& InCommand); @@ -238,7 +237,7 @@ public: const FSlateIcon& GetIcon() const { return Icon; } /** @return The type of command this is. Used to determine what UI to create for it */ - EUserInterfaceActionType::Type GetUserInterfaceType() const { return UserInterfaceType; } + EUserInterfaceActionType GetUserInterfaceType() const { return UserInterfaceType; } /** @return The name of the command */ FName GetCommandName() const { return CommandName; } @@ -286,5 +285,5 @@ private: FName BindingContext; /** The type of user interface to associated with this action */ - EUserInterfaceActionType::Type UserInterfaceType; + EUserInterfaceActionType UserInterfaceType; }; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Docking/LayoutExtender.h b/Engine/Source/Runtime/Slate/Public/Framework/Docking/LayoutExtender.h index 37a198d67ff3..f8a4244959fd 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Docking/LayoutExtender.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Docking/LayoutExtender.h @@ -16,7 +16,11 @@ enum class ELayoutExtensionPosition /** Extend the layout before the specified element */ Before, /** Extend the layout after the specified element */ - After + After, + /** Extend the layout above the specified element in the parent splitter */ + Above, + /** Extend the layout below the specified element in the parent splitter */ + Below, }; /** Class used for extending default layouts */ @@ -51,7 +55,7 @@ public: * @param OutValues The container to populate with extended tabs */ template - void FindExtensions(FTabId TabId, ELayoutExtensionPosition Position, TArray& OutValues) const + void FindTabExtensions(FTabId TabId, ELayoutExtensionPosition Position, TArray& OutValues) const { OutValues.Reset(); diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Docking/TabManager.h b/Engine/Source/Runtime/Slate/Public/Framework/Docking/TabManager.h index 829356b349ec..7fe9aa52b290 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Docking/TabManager.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Docking/TabManager.h @@ -110,7 +110,7 @@ class FSpawnTabArgs * Invoked when a tab needs to be spawned. */ DECLARE_DELEGATE_RetVal_OneParam( TSharedRef, FOnSpawnTab, const FSpawnTabArgs& ); - +DECLARE_DELEGATE_RetVal_OneParam(bool, FCanSpawnTab, const FSpawnTabArgs&); /** * Allows users to provide custom logic when searching for a tab to reuse. * The TabId that is being searched for is provided as a courtesy, but does not have to be respected. @@ -130,10 +130,11 @@ namespace ETabSpawnerMenuType struct FTabSpawnerEntry : public FWorkspaceItem { - FTabSpawnerEntry( const FName& InTabType, const FOnSpawnTab& InSpawnTabMethod ) - : FWorkspaceItem( FText(), FSlateIcon(), false ) - , TabType( InTabType ) - , OnSpawnTab( InSpawnTabMethod ) + FTabSpawnerEntry(const FName& InTabType, const FOnSpawnTab& InSpawnTabMethod, const FCanSpawnTab& InCanSpawnTab) + : FWorkspaceItem(FText(), FSlateIcon(), false) + , TabType(InTabType) + , OnSpawnTab(InSpawnTabMethod) + , CanSpawnTab(InCanSpawnTab) , OnFindTabToReuse() , MenuType(ETabSpawnerMenuType::Enabled) , bAutoGenerateMenuEntry(true) @@ -191,6 +192,7 @@ struct FTabSpawnerEntry : public FWorkspaceItem private: FName TabType; FOnSpawnTab OnSpawnTab; + FCanSpawnTab CanSpawnTab; /** When this method is not provided, we assume that the tab should only allow 0 or 1 instances */ FOnFindTabToReuse OnFindTabToReuse; /** Whether this menu item should be enabled, disabled, or hidden */ @@ -294,6 +296,12 @@ class SLATE_API FTabManager : public TSharedFromThis return SharedThis(this); } + TSharedRef AddTab(const FTab& Tab) + { + Tabs.Add(Tab); + return SharedThis(this); + } + TSharedRef SetSizeCoefficient( const float InSizeCoefficient ) { SizeCoefficient = InSizeCoefficient; @@ -349,6 +357,23 @@ class SLATE_API FTabManager : public TSharedFromThis return SharedThis(this); } + TSharedRef InsertBefore(TSharedRef NodeToInsertBefore, TSharedRef NodeToInsert) + { + int32 InsertAtIndex = ChildNodes.Find(NodeToInsertBefore); + check(InsertAtIndex != INDEX_NONE); + ChildNodes.Insert(NodeToInsert, InsertAtIndex); + return SharedThis(this); + } + + TSharedRef InsertAfter(TSharedRef NodeToInsertAfter, TSharedRef NodeToInsert) + { + int32 InsertAtIndex = ChildNodes.Find(NodeToInsertAfter); + check(InsertAtIndex != INDEX_NONE); + ChildNodes.Insert(NodeToInsert, InsertAtIndex + 1); + return SharedThis(this); + } + + TSharedRef SetSizeCoefficient( const float InSizeCoefficient ) { SizeCoefficient = InSizeCoefficient; @@ -626,10 +651,11 @@ class SLATE_API FTabManager : public TSharedFromThis * InvokeTab(). * @param TabId The TabId to register the spawner for. * @param OnSpawnTab The callback that will be used to spawn the tab. + * @param CanSpawnTab The callback that will be used to ask if spawning the tab is allowed * @return The registration entry for the spawner. */ - FTabSpawnerEntry& RegisterTabSpawner( const FName TabId, const FOnSpawnTab& OnSpawnTab ); - + FTabSpawnerEntry& RegisterTabSpawner(const FName TabId, const FOnSpawnTab& OnSpawnTab, const FCanSpawnTab& CanSpawnTab = FCanSpawnTab()); + /** * Unregisters the tab spawner matching the provided TabId. * @param TabId The TabId to remove the spawner for. @@ -732,7 +758,11 @@ class SLATE_API FTabManager : public TSharedFromThis void ClearLocalWorkspaceMenuCategories(); /** @return true if the tab has a factory registered for it that allows it to be spawned. */ - bool CanSpawnTab( FName TabId ); + UE_DEPRECATED(4.23, "CanSpawnTab has been replaced by HasTabSpawner") + bool CanSpawnTab(FName TabId) const; + + /** @return true if the tab has a factory registered for it that allows it to be spawned. */ + bool HasTabSpawner(FName TabId) const; /** Returns the owner tab (if it exists) */ TSharedPtr GetOwnerTab() { return OwnerTabPtr.Pin(); } @@ -748,9 +778,9 @@ class SLATE_API FTabManager : public TSharedFromThis void PopulateTabSpawnerMenu_Helper( FMenuBuilder& PopulateMe, struct FPopulateTabSpawnerMenu_Args Args ); - void MakeSpawnerMenuEntry( FMenuBuilder &PopulateMe, const TSharedPtr &SpawnerNode ); + void MakeSpawnerMenuEntry( FMenuBuilder &PopulateMe, const TSharedPtr &InSpawnerNode ); - TSharedRef InvokeTab_Internal( const FTabId& TabId ); + TSharedPtr InvokeTab_Internal( const FTabId& TabId ); /** Finds the last major or nomad tab in a particular window. */ TSharedPtr FindLastTabInWindow(TSharedPtr Window) const; @@ -773,7 +803,7 @@ class SLATE_API FTabManager : public TSharedFromThis void RestoreSplitterContent( const TSharedRef& SplitterNode, const TSharedRef& SplitterWidget, const TSharedPtr& ParentWindow ); bool IsValidTabForSpawning( const FTab& SomeTab ) const; - TSharedRef SpawnTab( const FTabId& TabId, const TSharedPtr& ParentWindow ); + TSharedPtr SpawnTab( const FTabId& TabId, const TSharedPtr& ParentWindow ); TSharedPtr FindTabInLiveAreas( const FTabMatcher& TabMatcher ) const; static TSharedPtr FindTabInLiveArea( const FTabMatcher& TabMatcher, const TSharedRef& InArea ); @@ -812,6 +842,7 @@ class SLATE_API FTabManager : public TSharedFromThis FTabSpawner TabSpawner; TSharedRef NomadTabSpawner; TSharedPtr FindTabSpawnerFor(FName TabId); + bool HasTabSpawnerFor(FName TabId) const; TArray< TWeakPtr > DockAreas; TArray< TSharedRef > CollapsedDockAreas; @@ -899,8 +930,17 @@ public: /** Activate the NewActiveTab. If NewActiveTab is NULL, the active tab is cleared. */ void SetActiveTab( const TSharedPtr& NewActiveTab ); - FTabSpawnerEntry& RegisterNomadTabSpawner( const FName TabId, const FOnSpawnTab& OnSpawnTab ); - + /** + * Register a new normad tab spawner with the tab manager. The spawner will be called when anyone calls + * InvokeTab(). + * A nomad tab is a tab that can be placed with major tabs or minor tabs in any tab well + * @param TabId The TabId to register the spawner for. + * @param OnSpawnTab The callback that will be used to spawn the tab. + * @param CanSpawnTab The callback that will be used to ask if spawning the tab is allowed + * @return The registration entry for the spawner. + */ + FTabSpawnerEntry& RegisterNomadTabSpawner( const FName TabId, const FOnSpawnTab& OnSpawnTab, const FCanSpawnTab& CanSpawnTab = FCanSpawnTab()); + void UnregisterNomadTabSpawner( const FName TabId ); void SetApplicationTitle( const FText& AppTitle ); diff --git a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBox.h b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBox.h index cdc52a781821..74524102be83 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBox.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBox.h @@ -60,7 +60,7 @@ public: * @param InCommand The command info that describes what action to take when this block is activated * @param InCommandList The list of mappings from command info to delegates so we can find the delegates to process for the provided action */ - FMultiBlock( const TSharedPtr< const FUICommandInfo > InCommand, TSharedPtr< const FUICommandList > InCommandList, FName InExtensionHook = NAME_None, EMultiBlockType::Type InType = EMultiBlockType::None ) + FMultiBlock( const TSharedPtr< const FUICommandInfo > InCommand, TSharedPtr< const FUICommandList > InCommandList, FName InExtensionHook = NAME_None, EMultiBlockType InType = EMultiBlockType::None ) : Action( InCommand ) , ActionList( InCommandList ) , ExtensionHook( InExtensionHook ) @@ -74,7 +74,7 @@ public: * * @InAction UI action delegates that should be used in place of UI commands (dynamic menu items) */ - FMultiBlock( const FUIAction& InAction, FName InExtensionHook = NAME_None, EMultiBlockType::Type InType = EMultiBlockType::None ) + FMultiBlock( const FUIAction& InAction, FName InExtensionHook = NAME_None, EMultiBlockType InType = EMultiBlockType::None ) : DirectActions( InAction ) , ExtensionHook( InExtensionHook ) , Type( InType ) @@ -142,7 +142,7 @@ public: * * @return The MultiBlock's type */ - const EMultiBlockType::Type GetType() const + const EMultiBlockType GetType() const { return Type; } @@ -189,7 +189,7 @@ private: FName ExtensionHook; /** Type of MultiBlock */ - EMultiBlockType::Type Type; + EMultiBlockType Type; /** Name to identify a widget for tutorials */ FName TutorialHighlightName; @@ -214,7 +214,7 @@ public: /** * Creates a new multibox instance */ - static TSharedRef Create( const EMultiBoxType::Type InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ); + static TSharedRef Create( const EMultiBoxType InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ); /** @@ -222,7 +222,7 @@ public: * * @return The MultiBox's type */ - const EMultiBoxType::Type GetType() const + const EMultiBoxType GetType() const { return Type; } @@ -319,7 +319,7 @@ private: * @param InType Type of MultiBox * @param bInShouldCloseWindowAfterMenuSelection Sets whether or not the window that contains this multibox should be destroyed after the user clicks on a menu item in this box */ - FMultiBox( const EMultiBoxType::Type InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ); + FMultiBox( const EMultiBoxType InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection ); /** * @return true if this box can be customized by a user @@ -348,7 +348,7 @@ private: FName StyleName; /** Type of MultiBox */ - EMultiBoxType::Type Type; + EMultiBoxType Type; /** True if window that owns any widgets created from this multibox should be closed automatically after the user commits to a menu choice */ bool bShouldCloseWindowAfterMenuSelection; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxBuilder.h b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxBuilder.h index e37981188e8c..46a68b78d436 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxBuilder.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxBuilder.h @@ -42,7 +42,7 @@ public: * @param InCommandList The action list that maps command infos to delegates that should be called for each command associated with a multiblock widget. This can be modified after the MultiBox is created by calling the PushCommandList() and PopCommandList() methods. * @param InTutorialHighlightName Optional name to identify this widget and highlight during tutorials */ - FMultiBoxBuilder( const EMultiBoxType::Type InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection, const TSharedPtr< const FUICommandList >& InCommandList, TSharedPtr InExtender = TSharedPtr(), FName InTutorialHighlightName = NAME_None ); + FMultiBoxBuilder( const EMultiBoxType InType, FMultiBoxCustomization InCustomization, const bool bInShouldCloseWindowAfterMenuSelection, const TSharedPtr< const FUICommandList >& InCommandList, TSharedPtr InExtender = TSharedPtr(), FName InTutorialHighlightName = NAME_None ); virtual ~FMultiBoxBuilder() {} @@ -161,7 +161,7 @@ public: * @param bInCloseSelfOnly True if clicking on a menu entry closes itself only and its children but not the entire stack * @param InTutorialHighlightName Optional name to identify this widget and highlight during tutorials */ - FBaseMenuBuilder( const EMultiBoxType::Type InType, const bool bInShouldCloseWindowAfterMenuSelection, TSharedPtr< const FUICommandList > InCommandList, bool bInCloseSelfOnly, TSharedPtr InExtender = TSharedPtr(), const ISlateStyle* InStyleSet = &FCoreStyle::Get(), FName InTutorialHighlightName = NAME_None ); + FBaseMenuBuilder( const EMultiBoxType InType, const bool bInShouldCloseWindowAfterMenuSelection, TSharedPtr< const FUICommandList > InCommandList, bool bInCloseSelfOnly, TSharedPtr InExtender = TSharedPtr(), const ISlateStyle* InStyleSet = &FCoreStyle::Get(), FName InTutorialHighlightName = NAME_None ); /** * Adds a menu entry @@ -186,9 +186,9 @@ public: * @param UserInterfaceActionType Type of interface action * @param InTutorialHighlightName Optional name to identify this widget and highlight during tutorials */ - void AddMenuEntry( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, FName InExtensionHook = NAME_None, const EUserInterfaceActionType::Type UserInterfaceActionType = EUserInterfaceActionType::Button, FName InTutorialHighlightName = NAME_None ); + void AddMenuEntry( const TAttribute& InLabel, const TAttribute& InToolTip, const FSlateIcon& InIcon, const FUIAction& UIAction, FName InExtensionHook = NAME_None, const EUserInterfaceActionType UserInterfaceActionType = EUserInterfaceActionType::Button, FName InTutorialHighlightName = NAME_None ); - void AddMenuEntry( const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const FName& InExtensionHook = NAME_None, const TAttribute& InToolTip = TAttribute(), const EUserInterfaceActionType::Type UserInterfaceActionType = EUserInterfaceActionType::Button, FName InTutorialHighlightName = NAME_None ); + void AddMenuEntry( const FUIAction& UIAction, const TSharedRef< SWidget > Contents, const FName& InExtensionHook = NAME_None, const TAttribute& InToolTip = TAttribute(), const EUserInterfaceActionType UserInterfaceActionType = EUserInterfaceActionType::Button, FName InTutorialHighlightName = NAME_None ); protected: /** True if clicking on a menu entry closes itself only and its children and not the entire stack */ @@ -261,7 +261,7 @@ public: * @param InIcon The icon to use * @param bInShouldCloseWindowAfterMenuSelection Whether the submenu should close after an item is selected */ - void AddSubMenu( const TAttribute& InMenuLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InSubMenu, const FUIAction& InUIAction, FName InExtensionHook, const EUserInterfaceActionType::Type InUserInterfaceActionType, const bool bInOpenSubMenuOnClick = false, const FSlateIcon& InIcon = FSlateIcon(), const bool bInShouldCloseWindowAfterMenuSelection = true ); + void AddSubMenu( const TAttribute& InMenuLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InSubMenu, const FUIAction& InUIAction, FName InExtensionHook, const EUserInterfaceActionType InUserInterfaceActionType, const bool bInOpenSubMenuOnClick = false, const FSlateIcon& InIcon = FSlateIcon(), const bool bInShouldCloseWindowAfterMenuSelection = true ); void AddSubMenu( const TAttribute& InMenuLabel, const TAttribute& InToolTip, const FNewMenuDelegate& InSubMenu, const bool bInOpenSubMenuOnClick = false, const FSlateIcon& InIcon = FSlateIcon(), const bool bInShouldCloseWindowAfterMenuSelection = true ); @@ -409,7 +409,7 @@ public: * @param UserInterfaceActionType Type of interface action * @param InTutorialHighlightName Name to identify this widget and highlight during tutorials */ - void AddToolBarButton(const FUIAction& InAction, FName InExtensionHook = NAME_None, const TAttribute& InLabelOverride = TAttribute(), const TAttribute& InToolTipOverride = TAttribute(), const TAttribute& InIconOverride = TAttribute(), const EUserInterfaceActionType::Type UserInterfaceActionType = EUserInterfaceActionType::Button, FName InTutorialHighlightName = NAME_None ); + void AddToolBarButton(const FUIAction& InAction, FName InExtensionHook = NAME_None, const TAttribute& InLabelOverride = TAttribute(), const TAttribute& InToolTipOverride = TAttribute(), const TAttribute& InIconOverride = TAttribute(), const EUserInterfaceActionType UserInterfaceActionType = EUserInterfaceActionType::Button, FName InTutorialHighlightName = NAME_None ); /** * Adds a combo button @@ -518,7 +518,7 @@ public: * @param InIcon The icon for the button * @param UserInterfaceActionType The style of button to display */ - void AddButton( const FText& InLabel, const FText& InToolTip, const FUIAction& UIAction, const FSlateIcon& InIcon = FSlateIcon(), const EUserInterfaceActionType::Type UserInterfaceActionType = EUserInterfaceActionType::Button ); + void AddButton( const FText& InLabel, const FText& InToolTip, const FUIAction& UIAction, const FSlateIcon& InIcon = FSlateIcon(), const EUserInterfaceActionType UserInterfaceActionType = EUserInterfaceActionType::Button ); protected: /** FMultiBoxBuilder interface */ diff --git a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxDefs.h b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxDefs.h index cd8101bbae6a..e5eb285d5dd6 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxDefs.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/MultiBoxDefs.h @@ -6,6 +6,7 @@ #include "Misc/Attribute.h" #include "Styling/ISlateStyle.h" #include "Framework/Commands/UICommandInfo.h" +#include "MultiBoxDefs.generated.h" class SToolTip; class SWidget; @@ -13,49 +14,47 @@ class SWidget; /** * Types of MultiBoxes */ -namespace EMultiBoxType +UENUM(BlueprintType) +enum class EMultiBoxType : uint8 { - enum Type - { - /** Horizontal menu bar */ - MenuBar, + /** Horizontal menu bar */ + MenuBar, - /** Horizontal tool bar */ - ToolBar, + /** Horizontal tool bar */ + ToolBar, - /** Vertical tool bar */ - VerticalToolBar, + /** Vertical tool bar */ + VerticalToolBar, - /** Vertical menu (pull-down menu, or context menu) */ - Menu, + /** Vertical menu (pull-down menu, or context menu) */ + Menu, - /** Buttons arranged in rows, with a maximum number of buttons per row, like a toolbar but can have multiple rows*/ - ButtonRow, + /** Buttons arranged in rows, with a maximum number of buttons per row, like a toolbar but can have multiple rows*/ + ButtonRow, + + /** Horizontal menu bar used as a tool bar */ + ToolMenuBar +}; - /** Horizontal menu bar used as a tool bar */ - ToolMenuBar - }; -} /** * Types of MultiBlocks */ -namespace EMultiBlockType +UENUM(BlueprintType) +enum class EMultiBlockType : uint8 { - enum Type - { - None = 0, - ButtonRow, - EditableText, - Heading, - MenuEntry, - MenuSeparator, - ToolBarButton, - ToolBarComboButton, - ToolBarSeparator, - Widget, - }; -} + None = 0, + ButtonRow, + EditableText, + Heading, + MenuEntry, + MenuSeparator, + ToolBarButton, + ToolBarComboButton, + ToolBarSeparator, + Widget, +}; + class SLATE_API FMultiBoxSettings { diff --git a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/SToolBarButtonBlock.h b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/SToolBarButtonBlock.h index f42d6583700d..3ffee2c8c504 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/SToolBarButtonBlock.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/MultiBox/SToolBarButtonBlock.h @@ -41,7 +41,7 @@ public: * @param InUIAction UI action to take when this menu item is clicked as well as to determine if the menu entry can be executed or appears "checked" * @param InUserInterfaceActionType Type of interface action */ - FToolBarButtonBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const TAttribute& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType::Type InUserInterfaceActionType ); + FToolBarButtonBlock( const TAttribute& InLabel, const TAttribute& InToolTip, const TAttribute& InIcon, const FUIAction& InUIAction, const EUserInterfaceActionType InUserInterfaceActionType ); void SetLabelVisibility( EVisibility InLabelVisibility ) { LabelVisibility = InLabelVisibility ; } @@ -77,7 +77,7 @@ private: /** In the case where a command is not bound, the user interface action type to use. If a command is bound, we simply use the action type associated with that command. */ - EUserInterfaceActionType::Type UserInterfaceActionType; + EUserInterfaceActionType UserInterfaceActionType; /** Whether ToolBar will have Focusable buttons */ bool bIsFocusable; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/DefaultLayoutBlock.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/DefaultLayoutBlock.h index 9f9c644f40bf..8cf9f12ddbcf 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/DefaultLayoutBlock.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/DefaultLayoutBlock.h @@ -2,7 +2,6 @@ #pragma once #include "CoreMinimal.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/ILayoutBlock.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/ILayoutBlock.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/ILayoutBlock.h index 4ae31219b8a8..c6d3b5605969 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/ILayoutBlock.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/ILayoutBlock.h @@ -2,7 +2,6 @@ #pragma once #include "CoreMinimal.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/IRunRenderer.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/IRun.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/IRun.h index 33549d372797..8ad5fb3b7982 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/IRun.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/IRun.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "Framework/Text/ShapedTextCacheFwd.h" #include "Misc/EnumClassFlags.h" -#include "Framework/Text/TextRange.h" class ILayoutBlock; class IRunRenderer; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/ITextDecorator.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/ITextDecorator.h index f26d15376fba..a62377b6c46c 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/ITextDecorator.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/ITextDecorator.h @@ -3,7 +3,6 @@ #include "CoreMinimal.h" #include "SlateGlobals.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/ISlateRun.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/RichTextMarkupProcessing.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/RichTextMarkupProcessing.h index 856742797d92..a54e817b4472 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/RichTextMarkupProcessing.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/RichTextMarkupProcessing.h @@ -3,7 +3,6 @@ #include "CoreMinimal.h" #include "SlateGlobals.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/ITextDecorator.h" #include "Internationalization/Regex.h" #include "Framework/Text/IRichTextMarkupParser.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/RunUtils.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/RunUtils.h index 5fb5471f7472..479d8776d447 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/RunUtils.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/RunUtils.h @@ -2,7 +2,6 @@ #pragma once #include "CoreMinimal.h" -#include "Framework/Text/TextRange.h" enum class ETextHitPoint : uint8; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/ShapedTextCache.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/ShapedTextCache.h index d4f6a50fb260..7b51f6b027bd 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/ShapedTextCache.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/ShapedTextCache.h @@ -5,7 +5,6 @@ #include "CoreMinimal.h" #include "Fonts/ShapedTextFwd.h" #include "Fonts/SlateFontInfo.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" class FSlateFontCache; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateHyperlinkRun.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateHyperlinkRun.h index f48c02b60e9a..e0adebcd651e 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateHyperlinkRun.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateHyperlinkRun.h @@ -5,7 +5,6 @@ #include "SlateGlobals.h" #include "Widgets/SWidget.h" #include "Styling/SlateTypes.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Widgets/IToolTip.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateImageRun.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateImageRun.h index ddd491bd8720..7f84678f391e 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateImageRun.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateImageRun.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "SlateGlobals.h" #include "Widgets/SWidget.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Framework/Text/ILayoutBlock.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateTextRun.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateTextRun.h index 76fb92680294..4d6652146b55 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateTextRun.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateTextRun.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "Widgets/SWidget.h" #include "Styling/SlateTypes.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Framework/Text/ILayoutBlock.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateWidgetRun.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateWidgetRun.h index 5b7ef6f2385f..6f205f33d956 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateWidgetRun.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/SlateWidgetRun.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "SlateGlobals.h" #include "Widgets/SWidget.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" #include "Framework/Text/ILayoutBlock.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/SyntaxTokenizer.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/SyntaxTokenizer.h index 3f880a87cd3b..5649b52f970b 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/SyntaxTokenizer.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/SyntaxTokenizer.h @@ -2,7 +2,6 @@ #pragma once #include "CoreMinimal.h" -#include "Framework/Text/TextRange.h" /** * Tokenize the text based upon the given rule set diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayout.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayout.h index 32d61f8f7123..34828d0d5a60 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayout.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayout.h @@ -5,7 +5,6 @@ #include "UObject/ObjectMacros.h" #include "Misc/Attribute.h" #include "Layout/Margin.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayoutEngine.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayoutEngine.h index d21cdfc50211..04152f3b8a47 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayoutEngine.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLayoutEngine.h @@ -5,7 +5,6 @@ MONOLITHIC_HEADER_BOILERPLATE() // Generic Text Layout -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLineHighlight.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLineHighlight.h index 2d9a03be747e..5a752ae30109 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLineHighlight.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextLineHighlight.h @@ -2,7 +2,6 @@ #pragma once #include "CoreMinimal.h" -#include "Framework/Text/TextRange.h" class ILineHighlighter; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRange.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRange.h index f6f6d7710ac6..52a1dfaf720c 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRange.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRange.h @@ -1,65 +1,14 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once -#include "CoreMinimal.h" +#include "Containers/UnrealString.h" -struct SLATE_API FTextRange +UE_DEPRECATED(4.23, "Including TextRange.h has been deprecated. Please include UnrealString.h in the Core module instead.") +inline void TextRangeHeaderDeprecatedWarning() { - FTextRange() - : BeginIndex(INDEX_NONE) - , EndIndex(INDEX_NONE) - { +} - } - - FTextRange( int32 InBeginIndex, int32 InEndIndex ) - : BeginIndex( InBeginIndex ) - , EndIndex( InEndIndex ) - { - - } - - FORCEINLINE bool operator==(const FTextRange& Other) const - { - return BeginIndex == Other.BeginIndex - && EndIndex == Other.EndIndex; - } - - FORCEINLINE bool operator!=(const FTextRange& Other) const - { - return !(*this == Other); - } - - friend inline uint32 GetTypeHash(const FTextRange& Key) - { - uint32 KeyHash = 0; - KeyHash = HashCombine(KeyHash, GetTypeHash(Key.BeginIndex)); - KeyHash = HashCombine(KeyHash, GetTypeHash(Key.EndIndex)); - return KeyHash; - } - - int32 Len() const { return EndIndex - BeginIndex; } - bool IsEmpty() const { return (EndIndex - BeginIndex) <= 0; } - void Offset(int32 Amount) { BeginIndex += Amount; BeginIndex = FMath::Max(0, BeginIndex); EndIndex += Amount; EndIndex = FMath::Max(0, EndIndex); } - bool Contains(int32 Index) const { return Index >= BeginIndex && Index < EndIndex; } - bool InclusiveContains(int32 Index) const { return Index >= BeginIndex && Index <= EndIndex; } - - FTextRange Intersect(const FTextRange& Other) const - { - FTextRange Intersected(FMath::Max(BeginIndex, Other.BeginIndex), FMath::Min(EndIndex, Other.EndIndex)); - if (Intersected.EndIndex <= Intersected.BeginIndex) - { - return FTextRange(0, 0); - } - - return Intersected; - } - - /** - * Produce an array of line ranges from the given text, breaking at any new-line characters - */ - static void CalculateLineRangesFromString(const FString& Input, TArray& LineRanges); - - int32 BeginIndex; - int32 EndIndex; -}; +inline void TriggerTextRangeHeaderDeprecatedWarning() +{ + TextRangeHeaderDeprecatedWarning(); +} diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRunRenderer.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRunRenderer.h index fdf82e361834..a195b8623e5c 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRunRenderer.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/TextRunRenderer.h @@ -2,7 +2,6 @@ #pragma once #include "CoreMinimal.h" -#include "Framework/Text/TextRange.h" class IRunRenderer; diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Text/WidgetLayoutBlock.h b/Engine/Source/Runtime/Slate/Public/Framework/Text/WidgetLayoutBlock.h index 9aaff1a13516..bf384969b804 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Text/WidgetLayoutBlock.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Text/WidgetLayoutBlock.h @@ -3,7 +3,6 @@ #include "CoreMinimal.h" #include "Widgets/SWidget.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/IRun.h" #include "Framework/Text/ILayoutBlock.h" diff --git a/Engine/Source/Runtime/Slate/Public/Framework/Views/TableViewTypeTraits.h b/Engine/Source/Runtime/Slate/Public/Framework/Views/TableViewTypeTraits.h index 12c6e919c409..31d0a173e125 100644 --- a/Engine/Source/Runtime/Slate/Public/Framework/Views/TableViewTypeTraits.h +++ b/Engine/Source/Runtime/Slate/Public/Framework/Views/TableViewTypeTraits.h @@ -102,7 +102,11 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs, FSparseItemInfo, false>; using SetKeyFuncs = DefaultKeyFuncs>; - static void AddReferencedObjects( FReferenceCollector&, TArray< TSharedPtr >&, TSet< TSharedPtr >& ) + template + static void AddReferencedObjects( FReferenceCollector&, + TArray< TSharedPtr >&, + TSet< TSharedPtr >&, + TMap< const U*, TSharedPtr >& ) { } @@ -144,7 +148,11 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs, FSparseItemInfo, false>; using SetKeyFuncs = DefaultKeyFuncs>; - static void AddReferencedObjects( FReferenceCollector&, TArray< TSharedPtr >&, TSet< TSharedPtr >& ) + template + static void AddReferencedObjects( FReferenceCollector&, + TArray< TSharedPtr >&, + TSet< TSharedPtr >&, + TMap< const U*, TSharedPtr >& WidgetToItemMap) { } @@ -186,8 +194,11 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs, FSparseItemInfo, false>; using SetKeyFuncs = DefaultKeyFuncs>; - - static void AddReferencedObjects( FReferenceCollector&, TArray< TSharedRef >&, TSet< TSharedRef >& ) + template + static void AddReferencedObjects( FReferenceCollector&, + TArray< TSharedRef >&, + TSet< TSharedRef >&, + TMap< const U*, TSharedRef >& ) { } @@ -229,7 +240,11 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs, FSparseItemInfo, false>; using SetKeyFuncs = DefaultKeyFuncs>; - static void AddReferencedObjects( FReferenceCollector&, TArray< TSharedRef >&, TSet< TSharedRef >& ) + template + static void AddReferencedObjects( FReferenceCollector&, + TArray< TSharedRef >&, + TSet< TSharedRef >&, + TMap< const U*, TSharedRef >&) { } @@ -274,7 +289,11 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs, FSparseItemInfo, false>; using SetKeyFuncs = DefaultKeyFuncs< TWeakObjectPtr >; - static void AddReferencedObjects( FReferenceCollector&, TArray< TWeakObjectPtr >&, TSet< TWeakObjectPtr >& ) + template + static void AddReferencedObjects( FReferenceCollector&, + TArray< TWeakObjectPtr >&, + TSet< TWeakObjectPtr >&, + TMap< const U*, TWeakObjectPtr >&) { } @@ -323,10 +342,21 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs; using SetKeyFuncs = DefaultKeyFuncs; - static void AddReferencedObjects( FReferenceCollector& Collector, TArray& ItemsWithGeneratedWidgets, TSet& SelectedItems ) + template + static void AddReferencedObjects( FReferenceCollector& Collector, + TArray& ItemsWithGeneratedWidgets, + TSet& SelectedItems, + TMap< const U*, T* >& WidgetToItemMap) { // Serialize generated items Collector.AddReferencedObjects(ItemsWithGeneratedWidgets); + + // Serialize the map Value. We only do it for the WidgetToItemMap because we know that both maps are updated at the same time and contains the same objects + // Also, we cannot AddReferencedObject to the Keys of the ItemToWidgetMap or we end up with keys being set to 0 when the UObject is destroyed which generate an invalid id in the map. + for (auto& It : WidgetToItemMap) + { + Collector.AddReferencedObject(*(UObject**)&It.Value); + } // Serialize the selected items Collector.AddReferencedObjects(SelectedItems); @@ -358,11 +388,22 @@ public: using MapKeyFuncsSparse = TDefaultMapHashableKeyFuncs; using SetKeyFuncs = DefaultKeyFuncs; - static void AddReferencedObjects( FReferenceCollector& Collector, TArray& ItemsWithGeneratedWidgets, TSet& SelectedItems ) + template + static void AddReferencedObjects( FReferenceCollector& Collector, + TArray& ItemsWithGeneratedWidgets, + TSet& SelectedItems, + TMap< const U*, const T* >& WidgetToItemMap) { // Serialize generated items Collector.AddReferencedObjects(ItemsWithGeneratedWidgets); + // Serialize the map Value. We only do it for the WidgetToItemMap because we know that both maps are updated at the same time and contains the same objects + // Also, we cannot AddReferencedObject to the Keys of the ItemToWidgetMap or we end up with keys being set to 0 when the UObject is destroyed which generate an invalid id in the map. + for (auto& It : WidgetToItemMap) + { + Collector.AddReferencedObject(*(UObject**)&It.Value); + } + // Serialize the selected items Collector.AddReferencedObjects(SelectedItems); } diff --git a/Engine/Source/Runtime/Slate/Public/SlateBasics.h b/Engine/Source/Runtime/Slate/Public/SlateBasics.h index 9b75603372b4..e988cb6c5c04 100644 --- a/Engine/Source/Runtime/Slate/Public/SlateBasics.h +++ b/Engine/Source/Runtime/Slate/Public/SlateBasics.h @@ -36,7 +36,6 @@ MONOLITHIC_HEADER_BOILERPLATE() // Legacy #include "Widgets/SWeakWidget.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/TextHitPoint.h" diff --git a/Engine/Source/Runtime/Slate/Public/SlateExtras.h b/Engine/Source/Runtime/Slate/Public/SlateExtras.h index 50fd3b81e4b7..9e43bb8f2cfb 100644 --- a/Engine/Source/Runtime/Slate/Public/SlateExtras.h +++ b/Engine/Source/Runtime/Slate/Public/SlateExtras.h @@ -27,7 +27,6 @@ MONOLITHIC_HEADER_BOILERPLATE() #include "Framework/Commands/Commands.h" #include "Framework/Commands/UICommandList.h" #include "Widgets/SWeakWidget.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/TextHitPoint.h" diff --git a/Engine/Source/Runtime/Slate/Public/SlateSharedPCH.h b/Engine/Source/Runtime/Slate/Public/SlateSharedPCH.h index 311510c6ac40..2d9c46e84bb4 100644 --- a/Engine/Source/Runtime/Slate/Public/SlateSharedPCH.h +++ b/Engine/Source/Runtime/Slate/Public/SlateSharedPCH.h @@ -333,7 +333,6 @@ #include "Framework/SlateDelegates.h" #include "Widgets/Layout/SBorder.h" #include "Framework/Text/TextLayout.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextRunRenderer.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Accessibility/SlateAccessibleWidgets.h b/Engine/Source/Runtime/Slate/Public/Widgets/Accessibility/SlateAccessibleWidgets.h new file mode 100644 index 000000000000..56af342559c7 --- /dev/null +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Accessibility/SlateAccessibleWidgets.h @@ -0,0 +1,161 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "CoreMinimal.h" +#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h" + +// SButton +class SLATE_API FSlateAccessibleButton + : public FSlateAccessibleWidget + , public IAccessibleActivatable +{ +public: + FSlateAccessibleButton(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::Button) {} + virtual ~FSlateAccessibleButton() {} + + virtual IAccessibleActivatable* AsActivatable() override { return this; } + + // IAccessibleActivatable + virtual void Activate() override; + // ~ +}; +// ~ + +// SCheckBox +class SLATE_API FSlateAccessibleCheckBox + : public FSlateAccessibleWidget + , public IAccessibleActivatable +{ +public: + FSlateAccessibleCheckBox(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::CheckBox) {} + virtual ~FSlateAccessibleCheckBox() {} + + virtual IAccessibleActivatable* AsActivatable() override { return this; } + + // IAccessibleActivatable + virtual void Activate() override; + virtual bool IsCheckable() const override; + virtual bool GetCheckedState() const override; + // ~ +}; +// ~ + +// SEditableText +class SLATE_API FSlateAccessibleEditableText + : public FSlateAccessibleWidget + , public IAccessibleText + , public IAccessibleProperty +{ +public: + FSlateAccessibleEditableText(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::TextEdit) {} + + // IAccessibleWidget + virtual IAccessibleText* AsText() override { return this; } + virtual IAccessibleProperty* AsProperty() override { return this; } + // ~ + + // IAccessibleText + virtual const FString& GetText() const override; + // ~ + + // IAccessibleProperty + virtual bool IsReadOnly() const override; + virtual bool IsPassword() const override; + virtual FString GetValue() const override; + virtual void SetValue(const FString& Value) override; + // ~ +}; +// ~ + +// SEditableTextBox +class SLATE_API FSlateAccessibleEditableTextBox + : public FSlateAccessibleWidget + , public IAccessibleText + , public IAccessibleProperty +{ +public: + FSlateAccessibleEditableTextBox(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::TextEdit) {} + + // IAccessibleWidget + virtual IAccessibleText* AsText() override { return this; } + virtual IAccessibleProperty* AsProperty() override { return this; } + // ~ + + // IAccessibleText + virtual const FString& GetText() const override; + // ~ + + // IAccessibleProperty + virtual bool IsReadOnly() const override; + virtual bool IsPassword() const override; + virtual FString GetValue() const override; + virtual void SetValue(const FString& Value) override; + // ~ +}; +// ~ + +// SHyperlink +class SLATE_API FSlateAccessibleHyperlink + : public FSlateAccessibleButton +{ +public: + FSlateAccessibleHyperlink(TWeakPtr InWidget) : FSlateAccessibleButton(InWidget) { WidgetType = EAccessibleWidgetType::Hyperlink; } + + // todo: add way to get URL for external hyperlinks? may need to go with SHyperlinkLaunchURL +}; +// ~ + +// Layouts +class SLATE_API FSlateAccessibleLayout + : public FSlateAccessibleWidget +{ +public: + FSlateAccessibleLayout(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::Layout) {} +}; +// ~ + +// SSlider +class SLATE_API FSlateAccessibleSlider + : public FSlateAccessibleWidget + , public IAccessibleProperty +{ +public: + FSlateAccessibleSlider(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::Slider) {} + + // IAccessibleWidget + virtual IAccessibleProperty* AsProperty() override { return this; } + // ~ + + // IAccessibleProperty + virtual bool IsReadOnly() const override; + virtual float GetStepSize() const override; + virtual float GetMaximum() const override; + virtual float GetMinimum() const override; + virtual FString GetValue() const override; + virtual void SetValue(const FString& Value) override; + // ~ +}; +// ~ + +// STextBlock +class SLATE_API FSlateAccessibleTextBlock + : public FSlateAccessibleWidget + //, public IAccessibleText // Disabled until we have better support for text. JAWS will not read these properly as-is. +{ +public: + FSlateAccessibleTextBlock(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::Text) {} + + // IAccessibleWidget + //virtual IAccessibleText* AsText() override { return this; } + // ~ + + // IAccessibleText + virtual const FString& GetText() const /*override*/; + // ~ +}; +// ~ + +#endif diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SButton.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SButton.h index 050fff0402a4..60cf9d16c173 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SButton.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SButton.h @@ -26,6 +26,10 @@ enum class ETextShapingMethod : uint8; class SLATE_API SButton : public SBorder { +#if WITH_ACCESSIBILITY + // Allow the accessible button to "click" this button + friend class FSlateAccessibleButton; +#endif public: SLATE_BEGIN_ARGS( SButton ) @@ -45,6 +49,7 @@ public: , _ForegroundColor( FCoreStyle::Get().GetSlateColor( "InvertedForeground" ) ) , _IsFocusable( true ) { + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Summary, EAccessibleBehavior::Auto, false); } /** Slot for this button's content (optional) */ @@ -178,9 +183,13 @@ public: virtual void OnMouseCaptureLost(const FCaptureLostEvent& CaptureLostEvent) override; virtual bool IsInteractable() const override; virtual bool ComputeVolatility() const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; +#endif // SWidget protected: + FReply ExecuteOnClick(); /** @return combines the user-specified margin and the button's internal margin. */ FMargin GetCombinedPadding() const; diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SCheckBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SCheckBox.h index 02e48654b5a2..0e8bdcc8f5ca 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SCheckBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SCheckBox.h @@ -52,7 +52,9 @@ public: , _UndeterminedImage( nullptr ) , _UndeterminedHoveredImage( nullptr ) , _UndeterminedPressedImage( nullptr ) - {} + { + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Summary, EAccessibleBehavior::Auto, false); + } /** Content to be placed next to the check box, or for a toggle button, the content to be placed inside the button */ SLATE_DEFAULT_SLOT( FArguments, Content ) @@ -146,6 +148,9 @@ public: virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override; virtual bool IsInteractable() const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; +#endif // End of SWidget interface /** diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SComboBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SComboBox.h index b7f814b53ab7..cea7fe16319b 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SComboBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SComboBox.h @@ -342,7 +342,13 @@ protected: } else if (NavAction == EUINavigationAction::Back || KeyPressed == EKeys::BackSpace) { + const bool bWasInputCaptured = bControllerInputCaptured; + OnMenuOpenChanged(false); + if (bWasInputCaptured) + { + return FReply::Handled(); + } } else { diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableText.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableText.h index 157566709094..d9d19e1eae1e 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableText.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableText.h @@ -65,6 +65,7 @@ public: , _TextFlowDirection() { _Clipping = EWidgetClipping::ClipToBounds; + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Auto, EAccessibleBehavior::Auto, false); } /** Sets the text content for this editable text widget */ @@ -129,7 +130,7 @@ public: */ SLATE_EVENT( FOnIsTypedCharValid, OnIsTypedCharValid ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnTextChanged, OnTextChanged ) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ @@ -352,6 +353,10 @@ protected: virtual const FSlateBrush* GetFocusBrush() const; virtual bool IsInteractable() const override; virtual bool ComputeVolatility() const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; + virtual void SetDefaultAccessibleText(EAccessibleType AccessibleType = EAccessibleType::Main) override; +#endif //~ End SWidget Interface protected: @@ -436,7 +441,7 @@ protected: /** Called when a character is typed and we want to know if the text field supports typing this character. */ FOnIsTypedCharValid OnIsTypedCharValid; - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ FOnTextChanged OnTextChangedCallback; /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableTextBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableTextBox.h index db47457db14d..a2f054eaf827 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableTextBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SEditableTextBox.h @@ -52,7 +52,9 @@ public: , _VirtualKeyboardOptions(FVirtualKeyboardOptions()) , _VirtualKeyboardTrigger(EVirtualKeyboardTrigger::OnFocusByPointer) , _VirtualKeyboardDismissAction(EVirtualKeyboardDismissAction::TextChangeOnDismiss) - {} + { + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Auto, EAccessibleBehavior::Auto, false); + } /** The styling of the textbox */ SLATE_STYLE_ARGUMENT( FEditableTextBoxStyle, Style ) @@ -102,7 +104,7 @@ public: /** Delegate to call before a context menu is opened. User returns the menu content or null to the disable context menu */ SLATE_EVENT(FOnContextMenuOpening, OnContextMenuOpening) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnTextChanged, OnTextChanged ) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ @@ -187,10 +189,14 @@ public: /** See the IsReadOnly attribute */ void SetIsReadOnly( TAttribute< bool > InIsReadOnly ); + + bool IsReadOnly() const { return EditableText->IsTextReadOnly(); } /** See the IsPassword attribute */ void SetIsPassword( TAttribute< bool > InIsPassword ); + bool IsPassword() const { return EditableText->IsTextPassword(); } + /** See the AllowContextMenu attribute */ void SetAllowContextMenu(TAttribute< bool > InAllowContextMenu); @@ -336,6 +342,11 @@ public: virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; protected: +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; + virtual void SetDefaultAccessibleText(EAccessibleType AccessibleType = EAccessibleType::Main) override; +#endif + const FEditableTextBoxStyle* Style; /** Box widget that adds padding around the editable text */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SHyperlink.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SHyperlink.h index 67134cd5c690..7c9b9faf1d98 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SHyperlink.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SHyperlink.h @@ -12,6 +12,9 @@ #include "Styling/SlateTypes.h" #include "Styling/CoreStyle.h" #include "Widgets/Input/SButton.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateAccessibleWidgets.h" +#endif enum class ETextFlowDirection : uint8; enum class ETextShapingMethod : uint8; @@ -81,6 +84,12 @@ public: { return FCursorReply::Cursor( EMouseCursor::Hand ); } +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override + { + return MakeShareable(new FSlateAccessibleHyperlink(SharedThis(this))); + } +#endif protected: diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SMultiLineEditableTextBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SMultiLineEditableTextBox.h index 27ed7b00fbbd..1b55e6d30cd5 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SMultiLineEditableTextBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SMultiLineEditableTextBox.h @@ -156,7 +156,7 @@ public: */ SLATE_EVENT( FOnIsTypedCharValid, OnIsTypedCharValid ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnTextChanged, OnTextChanged ) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ @@ -260,6 +260,9 @@ public: /** See attribute Style */ void SetStyle(const FEditableTextBoxStyle* InStyle); + /** See attribute TextStyle */ + void SetTextStyle(const FTextBlockStyle* InTextStyle); + /** * Sets the text string currently being edited * diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SNumericEntryBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SNumericEntryBox.h index 680ee294f244..2b63577c33c4 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SNumericEntryBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SNumericEntryBox.h @@ -106,6 +106,8 @@ public: SLATE_ARGUMENT( bool, AllowSpin ) /** How many pixel the mouse must move to change the value of the delta step (only use if there is a spinbox allow) */ SLATE_ARGUMENT( int32, ShiftMouseMovePixelPerDelta ) + /** If we're an unbounded spinbox, what value do we divide mouse movement by before multiplying by Delta. Requires Delta to be set. */ + SLATE_ATTRIBUTE( int32, LinearDeltaSensitivity) /** Tell us if we want to support dynamically changing of the max value using ctrl (only use if there is a spinbox allow) */ SLATE_ATTRIBUTE(bool, SupportDynamicSliderMaxValue) /** Tell us if we want to support dynamically changing of the min value using ctrl (only use if there is a spinbox allow) */ @@ -132,11 +134,11 @@ public: SLATE_ATTRIBUTE( float, MinDesiredValueWidth ) /** The text margin to use if overridden. */ SLATE_ATTRIBUTE( FMargin, OverrideTextMargin ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnValueChanged, OnValueChanged ) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ SLATE_EVENT( FOnValueCommitted, OnValueCommitted ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnUndeterminedValueChanged, OnUndeterminedValueChanged ) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ SLATE_EVENT( FOnUndeterminedValueCommitted, OnUndeterminedValueCommitted ) @@ -183,6 +185,7 @@ public: .Value( this, &SNumericEntryBox::OnGetValueForSpinBox ) .Delta( InArgs._Delta ) .ShiftMouseMovePixelPerDelta(InArgs._ShiftMouseMovePixelPerDelta) + .LinearDeltaSensitivity(InArgs._LinearDeltaSensitivity) .SupportDynamicSliderMaxValue(InArgs._SupportDynamicSliderMaxValue) .SupportDynamicSliderMinValue(InArgs._SupportDynamicSliderMinValue) .OnDynamicSliderMaxValueChanged(InArgs._OnDynamicSliderMaxValueChanged) diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSlider.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSlider.h index 1311d856259c..fee055dc2fe9 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSlider.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSlider.h @@ -35,11 +35,15 @@ public: , _Style(&FCoreStyle::Get().GetWidgetStyle("Slider")) , _StepSize(0.01f) , _Value(1.f) + , _MinValue(0.0f) + , _MaxValue(1.0f) , _IsFocusable(true) , _OnMouseCaptureBegin() , _OnMouseCaptureEnd() , _OnValueChanged() - { } + { + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Summary, EAccessibleBehavior::Auto, false); + } /** Whether the slidable area should be indented to fit the handle. */ SLATE_ATTRIBUTE( bool, IndentHandle ) @@ -71,6 +75,11 @@ public: /** A value that drives where the slider handle appears. Value is normalized between 0 and 1. */ SLATE_ATTRIBUTE( float, Value ) + /** The minimum value that can be specified by using the slider. */ + SLATE_ARGUMENT(float, MinValue) + /** The maximum value that can be specified by using the slider. */ + SLATE_ARGUMENT(float, MaxValue) + /** Sometimes a slider should only be mouse-clickable and never keyboard focusable. */ SLATE_ARGUMENT(bool, IsFocusable) @@ -98,37 +107,49 @@ public: */ void Construct( const SSlider::FArguments& InDeclaration ); - /** See the Value attribute */ + /** Get the MinValue attribute */ + float GetMinValue() const { return MinValue; } + + /** Get the MaxValue attribute */ + float GetMaxValue() const { return MaxValue; } + + /** Get the Value attribute */ float GetValue() const; - /** See the Value attribute */ + /** Get the Value attribute scaled from 0 to 1 */ + float GetNormalizedValue() const; + + /** Set the Value attribute */ void SetValue(const TAttribute& InValueAttribute); + + /** Set the MinValue and MaxValue attributes. If the new MinValue is more than the new MaxValue, MaxValue will be changed to equal MinValue. */ + void SetMinAndMaxValues(float InMinValue, float InMaxValue); - /** See the IndentHandle attribute */ + /** Set the IndentHandle attribute */ void SetIndentHandle(const TAttribute& InIndentHandle); - /** See the Locked attribute */ + /** Set the Locked attribute */ void SetLocked(const TAttribute& InLocked); - /** See the Orientation attribute */ + /** Set the Orientation attribute */ void SetOrientation(EOrientation InOrientation); - /** See the SliderBarColor attribute */ + /** Set the SliderBarColor attribute */ void SetSliderBarColor(FSlateColor InSliderBarColor); - /** See the SliderHandleColor attribute */ + /** Set the SliderHandleColor attribute */ void SetSliderHandleColor(FSlateColor InSliderHandleColor); /** Get the StepSize attribute */ float GetStepSize() const; - /** See the StepSize attribute */ + /** Set the StepSize attribute */ void SetStepSize(const TAttribute& InStepSize); - /** See the MouseUsesStep attribute */ + /** Set the MouseUsesStep attribute */ void SetMouseUsesStep(bool MouseUsesStep); - /** See the RequiresControllerLock attribute */ + /** Set the RequiresControllerLock attribute */ void SetRequiresControllerLock(bool RequiresControllerLock); public: @@ -150,6 +171,9 @@ public: virtual bool SupportsKeyboardFocus() const override; virtual bool IsInteractable() const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; +#endif /** @return Is the handle locked or not? Defaults to false */ bool IsLocked() const; @@ -204,6 +228,9 @@ protected: /** Holds the amount to adjust the value by when using a controller or keyboard */ TAttribute StepSize; + float MinValue; + float MaxValue; + // Holds a flag indicating whether a controller/keyboard is manipulating the slider's value. // When true, navigation away from the widget is prevented until a new value has been accepted or canceled. bool bControllerInputCaptured; diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSpinBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSpinBox.h index 0848f99482e1..cffd2e28de5f 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSpinBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSpinBox.h @@ -689,7 +689,7 @@ public: void SetValue(const TAttribute& InValueAttribute) { ValueAttribute = InValueAttribute; - CommitValue(InValueAttribute.Get(), ECommitMethod::CommittedViaSpin, ETextCommit::Default); + CommitValue(InValueAttribute.Get(), ECommitMethod::CommittedViaTypeIn, ETextCommit::Default); } /** See the MinValue attribute */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSuggestionTextBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSuggestionTextBox.h index 2eb4b143b57a..61abfabc009c 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSuggestionTextBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSuggestionTextBox.h @@ -96,7 +96,7 @@ public: /** Called before the suggestion list is shown. */ SLATE_EVENT(FOnShowingSuggestions, OnShowingSuggestions) - /** Called whenever the text is changed interactively by the user. */ + /** Called whenever the text is changed programmatically or interactively by the user. */ SLATE_EVENT(FOnTextChanged, OnTextChanged) /** Called when the text has been committed. */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/STextEntryPopup.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/STextEntryPopup.h index b53761f1aa42..df893f628aad 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/STextEntryPopup.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/STextEntryPopup.h @@ -26,7 +26,7 @@ public: /** Test to place into text entry box before anything is typed */ SLATE_ARGUMENT( FText, DefaultText ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnTextChanged, OnTextChanged ) /** Called when the text is committed. */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVectorInputBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVectorInputBox.h index 91ec7482c0e3..0e98e054ef19 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVectorInputBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVectorInputBox.h @@ -26,7 +26,8 @@ class SLATE_API SVectorInputBox : public SCompoundWidget public: SLATE_BEGIN_ARGS( SVectorInputBox ) : _Font( FCoreStyle::Get().GetFontStyle("NormalFont") ) - , _AllowSpin(true) + , _AllowSpin( false ) + , _SpinDelta( 1 ) , _bColorAxisLabels( false ) , _AllowResponsiveLayout( false ) {} @@ -44,13 +45,16 @@ public: SLATE_ATTRIBUTE( FSlateFontInfo, Font ) /** Whether or not values can be spun or if they should be typed in */ - SLATE_ARGUMENT(bool, AllowSpin) + SLATE_ARGUMENT( bool, AllowSpin ) + + /** The delta amount to apply, per pixel, when the spinner is dragged. */ + SLATE_ATTRIBUTE( float, SpinDelta ) /** Should the axis labels be colored */ SLATE_ARGUMENT( bool, bColorAxisLabels ) /** Allow responsive layout to crush the label and margins when there is not a lot of room */ - SLATE_ARGUMENT(bool, AllowResponsiveLayout) + SLATE_ARGUMENT( bool, AllowResponsiveLayout ) /** Called when the x value of the vector is changed */ SLATE_EVENT( FOnFloatValueChanged, OnXChanged ) @@ -79,6 +83,12 @@ public: /** Menu extender delegate for the Z value */ SLATE_EVENT( FMenuExtensionDelegate, ContextMenuExtenderZ ) + /** Called right before the slider begins to move for any of the vector components */ + SLATE_EVENT( FSimpleDelegate, OnBeginSliderMovement ) + + /** Called right after the slider handle is released by the user for any of the vector components */ + SLATE_EVENT( FOnFloatValueChanged, OnEndSliderMovement ) + /** Provide custom type functionality for the vector */ SLATE_ARGUMENT( TSharedPtr< INumericTypeInterface >, TypeInterface ) diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVirtualKeyboardEntry.h b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVirtualKeyboardEntry.h index 0cbaf4268f99..9d25ab8e59fd 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVirtualKeyboardEntry.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Input/SVirtualKeyboardEntry.h @@ -52,7 +52,7 @@ public: /** Whether to clear keyboard focus when pressing enter to commit changes */ SLATE_ATTRIBUTE( bool, ClearKeyboardFocusOnCommit ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnTextChanged, OnTextChanged ) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ @@ -179,7 +179,7 @@ private: /** Whether to clear keyboard focus when pressing enter to commit changes */ TAttribute< bool > ClearKeyboardFocusOnCommit; - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ FOnTextChanged OnTextChanged; /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SBox.h index ec18c5a0d7c6..1b84eb5c7e95 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SBox.h @@ -21,107 +21,111 @@ class FSlateWindowElementList; */ class SLATE_API SBox : public SPanel { +public: + class FBoxSlot : public TSupportsOneChildMixin, public TSupportsContentAlignmentMixin, public TSupportsContentPaddingMixin + { public: - class FBoxSlot : public TSupportsOneChildMixin, public TSupportsContentAlignmentMixin, public TSupportsContentPaddingMixin + FBoxSlot(SWidget* InOwner) + : TSupportsOneChildMixin(InOwner) + , TSupportsContentAlignmentMixin(HAlign_Fill, VAlign_Fill) { - public: - FBoxSlot(SWidget* InOwner) - : TSupportsOneChildMixin(InOwner) - , TSupportsContentAlignmentMixin(HAlign_Fill, VAlign_Fill) - { - } - }; + } + }; - SLATE_BEGIN_ARGS(SBox) - : _HAlign( HAlign_Fill ) - , _VAlign( VAlign_Fill ) - , _Padding( 0.0f ) - , _Content() - , _WidthOverride(FOptionalSize()) - , _HeightOverride(FOptionalSize()) - , _MinDesiredWidth(FOptionalSize()) - , _MinDesiredHeight(FOptionalSize()) - , _MaxDesiredWidth(FOptionalSize()) - , _MaxDesiredHeight(FOptionalSize()) - { - _Visibility = EVisibility::SelfHitTestInvisible; - } + SLATE_BEGIN_ARGS(SBox) + : _HAlign(HAlign_Fill) + , _VAlign(VAlign_Fill) + , _Padding(0.0f) + , _Content() + , _WidthOverride(FOptionalSize()) + , _HeightOverride(FOptionalSize()) + , _MinDesiredWidth(FOptionalSize()) + , _MinDesiredHeight(FOptionalSize()) + , _MaxDesiredWidth(FOptionalSize()) + , _MaxDesiredHeight(FOptionalSize()) + { + _Visibility = EVisibility::SelfHitTestInvisible; + } - /** Horizontal alignment of content in the area allotted to the SBox by its parent */ - SLATE_ARGUMENT( EHorizontalAlignment, HAlign ) + /** Horizontal alignment of content in the area allotted to the SBox by its parent */ + SLATE_ARGUMENT(EHorizontalAlignment, HAlign) - /** Vertical alignment of content in the area allotted to the SBox by its parent */ - SLATE_ARGUMENT( EVerticalAlignment, VAlign ) + /** Vertical alignment of content in the area allotted to the SBox by its parent */ + SLATE_ARGUMENT(EVerticalAlignment, VAlign) - /** Padding between the SBox and the content that it presents. Padding affects desired size. */ - SLATE_ATTRIBUTE( FMargin, Padding ) + /** Padding between the SBox and the content that it presents. Padding affects desired size. */ + SLATE_ATTRIBUTE(FMargin, Padding) - /** The widget content presented by the SBox */ - SLATE_DEFAULT_SLOT( FArguments, Content ) + /** The widget content presented by the SBox */ + SLATE_DEFAULT_SLOT(FArguments, Content) - /** When specified, ignore the content's desired size and report the WidthOverride as the Box's desired width. */ - SLATE_ATTRIBUTE( FOptionalSize, WidthOverride ) + /** When specified, ignore the content's desired size and report the WidthOverride as the Box's desired width. */ + SLATE_ATTRIBUTE(FOptionalSize, WidthOverride) - /** When specified, ignore the content's desired size and report the HeightOverride as the Box's desired height. */ - SLATE_ATTRIBUTE( FOptionalSize, HeightOverride) + /** When specified, ignore the content's desired size and report the HeightOverride as the Box's desired height. */ + SLATE_ATTRIBUTE(FOptionalSize, HeightOverride) - /** When specified, will report the MinDesiredWidth if larger than the content's desired width. */ - SLATE_ATTRIBUTE(FOptionalSize, MinDesiredWidth) + /** When specified, will report the MinDesiredWidth if larger than the content's desired width. */ + SLATE_ATTRIBUTE(FOptionalSize, MinDesiredWidth) - /** When specified, will report the MinDesiredHeight if larger than the content's desired height. */ - SLATE_ATTRIBUTE(FOptionalSize, MinDesiredHeight) + /** When specified, will report the MinDesiredHeight if larger than the content's desired height. */ + SLATE_ATTRIBUTE(FOptionalSize, MinDesiredHeight) - /** When specified, will report the MaxDesiredWidth if smaller than the content's desired width. */ - SLATE_ATTRIBUTE(FOptionalSize, MaxDesiredWidth) + /** When specified, will report the MaxDesiredWidth if smaller than the content's desired width. */ + SLATE_ATTRIBUTE(FOptionalSize, MaxDesiredWidth) - /** When specified, will report the MaxDesiredHeight if smaller than the content's desired height. */ - SLATE_ATTRIBUTE(FOptionalSize, MaxDesiredHeight) + /** When specified, will report the MaxDesiredHeight if smaller than the content's desired height. */ + SLATE_ATTRIBUTE(FOptionalSize, MaxDesiredHeight) - SLATE_ATTRIBUTE(FOptionalSize, MaxAspectRatio) + SLATE_ATTRIBUTE(FOptionalSize, MinAspectRatio) - SLATE_END_ARGS() + SLATE_ATTRIBUTE(FOptionalSize, MaxAspectRatio) - SBox(); + SLATE_END_ARGS() - void Construct( const FArguments& InArgs ); + SBox(); - virtual void OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const override; - virtual FChildren* GetChildren() override; - virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override; + void Construct(const FArguments& InArgs); - /** - * See the Content slot. - */ - void SetContent(const TSharedRef< SWidget >& InContent); + virtual void OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const override; + virtual FChildren* GetChildren() override; + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - /** See HAlign argument */ - void SetHAlign(EHorizontalAlignment HAlign); + /** + * See the Content slot. + */ + void SetContent(const TSharedRef< SWidget >& InContent); - /** See VAlign argument */ - void SetVAlign(EVerticalAlignment VAlign); + /** See HAlign argument */ + void SetHAlign(EHorizontalAlignment HAlign); - /** See Padding attribute */ - void SetPadding(const TAttribute& InPadding); + /** See VAlign argument */ + void SetVAlign(EVerticalAlignment VAlign); - /** See WidthOverride attribute */ - void SetWidthOverride(TAttribute InWidthOverride); + /** See Padding attribute */ + void SetPadding(const TAttribute& InPadding); - /** See HeightOverride attribute */ - void SetHeightOverride(TAttribute InHeightOverride); + /** See WidthOverride attribute */ + void SetWidthOverride(TAttribute InWidthOverride); - /** See MinDesiredWidth attribute */ - void SetMinDesiredWidth(TAttribute InMinDesiredWidth); + /** See HeightOverride attribute */ + void SetHeightOverride(TAttribute InHeightOverride); - /** See MinDesiredHeight attribute */ - void SetMinDesiredHeight(TAttribute InMinDesiredHeight); + /** See MinDesiredWidth attribute */ + void SetMinDesiredWidth(TAttribute InMinDesiredWidth); - /** See MaxDesiredWidth attribute */ - void SetMaxDesiredWidth(TAttribute InMaxDesiredWidth); + /** See MinDesiredHeight attribute */ + void SetMinDesiredHeight(TAttribute InMinDesiredHeight); - /** See MaxDesiredHeight attribute */ - void SetMaxDesiredHeight(TAttribute InMaxDesiredHeight); + /** See MaxDesiredWidth attribute */ + void SetMaxDesiredWidth(TAttribute InMaxDesiredWidth); - void SetMaxAspectRatio(TAttribute InMaxAspectRatio); + /** See MaxDesiredHeight attribute */ + void SetMaxDesiredHeight(TAttribute InMaxDesiredHeight); + + void SetMinAspectRatio(TAttribute InMinAspectRatio); + + void SetMaxAspectRatio(TAttribute InMaxAspectRatio); protected: // Begin SWidget overrides. @@ -133,28 +137,29 @@ protected: protected: - FBoxSlot ChildSlot; + FBoxSlot ChildSlot; private: - /** When specified, ignore the content's desired size and report the.WidthOverride as the Box's desired width. */ - TAttribute WidthOverride; + /** When specified, ignore the content's desired size and report the.WidthOverride as the Box's desired width. */ + TAttribute WidthOverride; - /** When specified, ignore the content's desired size and report the.HeightOverride as the Box's desired height. */ - TAttribute HeightOverride; - - /** When specified, will report the MinDesiredWidth if larger than the content's desired width. */ - TAttribute MinDesiredWidth; + /** When specified, ignore the content's desired size and report the.HeightOverride as the Box's desired height. */ + TAttribute HeightOverride; - /** When specified, will report the MinDesiredHeight if larger than the content's desired height. */ - TAttribute MinDesiredHeight; + /** When specified, will report the MinDesiredWidth if larger than the content's desired width. */ + TAttribute MinDesiredWidth; - /** When specified, will report the MaxDesiredWidth if smaller than the content's desired width. */ - TAttribute MaxDesiredWidth; + /** When specified, will report the MinDesiredHeight if larger than the content's desired height. */ + TAttribute MinDesiredHeight; - /** When specified, will report the MaxDesiredHeight if smaller than the content's desired height. */ - TAttribute MaxDesiredHeight; + /** When specified, will report the MaxDesiredWidth if smaller than the content's desired width. */ + TAttribute MaxDesiredWidth; - TAttribute MaxAspectRatio; + /** When specified, will report the MaxDesiredHeight if smaller than the content's desired height. */ + TAttribute MaxDesiredHeight; + + TAttribute MinAspectRatio; + + TAttribute MaxAspectRatio; }; - diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBar.h b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBar.h index 77586baab41a..9238761d8f64 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBar.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBar.h @@ -39,7 +39,8 @@ public: #endif , _Orientation( Orient_Vertical ) , _DragFocusCause( EFocusCause::Mouse ) - , _Thickness( FVector2D(12.0f, 12.0f) ) + , _Thickness( FVector2D(16.0f, 16.0f) ) + , _Padding( 2.0f ) {} /** The style to use for this scrollbar */ @@ -52,6 +53,8 @@ public: SLATE_ARGUMENT( EFocusCause, DragFocusCause ) /** The thickness of the scrollbar thumb */ SLATE_ATTRIBUTE( FVector2D, Thickness ) + /** The margin around the scrollbar */ + SLATE_ATTRIBUTE( FMargin, Padding ) SLATE_END_ARGS() /** diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBox.h b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBox.h index ffe4c8cb0393..324f8d604859 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBox.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SScrollBox.h @@ -89,7 +89,8 @@ public: , _ScrollBarVisibility(EVisibility::Visible) , _ScrollBarAlwaysVisible(false) , _ScrollBarDragFocusCause(EFocusCause::Mouse) - , _ScrollBarThickness(FVector2D(5, 5)) + , _ScrollBarThickness(FVector2D(9.0f, 9.0f)) + , _ScrollBarPadding(2.0f) , _AllowOverscroll(EAllowOverscroll::Yes) , _NavigationDestination(EDescendantScrollDestination::IntoView) , _NavigationScrollPadding(0.0f) @@ -121,6 +122,8 @@ public: SLATE_ARGUMENT( FVector2D, ScrollBarThickness ) + SLATE_ARGUMENT( FMargin, ScrollBarPadding ) + SLATE_ARGUMENT(EAllowOverscroll, AllowOverscroll); SLATE_ARGUMENT(EDescendantScrollDestination, NavigationDestination); @@ -168,6 +171,9 @@ public: float GetViewOffsetFraction() const; + /** Gets the scroll offset of the bottom of the ScrollBox in Slate Units. */ + float GetScrollOffsetOfEnd() const; + void SetScrollOffset( float NewScrollOffset ); void ScrollToStart(); @@ -201,6 +207,8 @@ public: void SetScrollBarThickness(FVector2D InThickness); + void SetScrollBarPadding(const FMargin& InPadding); + void SetScrollBarRightClickDragAllowed(bool bIsAllowed); public: diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SWidgetSwitcher.h b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SWidgetSwitcher.h index 274cef808cc4..077367af3b80 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SWidgetSwitcher.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Layout/SWidgetSwitcher.h @@ -169,4 +169,9 @@ private: /** Required to implement GetChildren() in a way that can dynamically return the currently active child. */ TOneDynamicChild OneDynamicChild; + +#if WITH_ACCESSIBILITY + /** Used to detect when WidgetIndex changes while bound. */ + TWeakPtr LastActiveWidget; +#endif }; diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Text/SInlineEditableTextBlock.h b/Engine/Source/Runtime/Slate/Public/Widgets/Text/SInlineEditableTextBlock.h index df019228dc2e..700b5beb14ed 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Text/SInlineEditableTextBlock.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Text/SInlineEditableTextBlock.h @@ -102,7 +102,7 @@ class SLATE_API SInlineEditableTextBlock: public SCompoundWidget /** Callback to check if the widget is selected, should only be hooked up if parent widget is handling selection or focus. */ SLATE_EVENT( FIsSelected, IsSelected ) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT( FOnVerifyTextChanged, OnVerifyTextChanged ) SLATE_END_ARGS() diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Text/SMultiLineEditableText.h b/Engine/Source/Runtime/Slate/Public/Widgets/Text/SMultiLineEditableText.h index 39eabbeec35f..5c49369d60ee 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Text/SMultiLineEditableText.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Text/SMultiLineEditableText.h @@ -133,7 +133,7 @@ public: */ SLATE_EVENT(FOnIsTypedCharValid, OnIsTypedCharValid) - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ SLATE_EVENT(FOnTextChanged, OnTextChanged) /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ @@ -476,7 +476,7 @@ protected: /** Called when a character is typed and we want to know if the text field supports typing this character. */ FOnIsTypedCharValid OnIsTypedCharValid; - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ FOnTextChanged OnTextChangedCallback; /** Called whenever the text is committed. This happens when the user presses enter or the text box loses focus. */ diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Text/STextBlock.h b/Engine/Source/Runtime/Slate/Public/Widgets/Text/STextBlock.h index bd52942ed492..59434c6c6210 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Text/STextBlock.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Text/STextBlock.h @@ -65,6 +65,7 @@ public: , _SimpleTextMode(false) { _Clipping = EWidgetClipping::OnDemand; + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Auto, EAccessibleBehavior::Auto, false); } /** The text displayed in this text block */ @@ -163,7 +164,13 @@ public: { return BoundText.Get(); } +private: + FText GetTextCopy() const + { + return BoundText.Get(); + } +public: /** * Sets the text for this text block * @@ -233,6 +240,10 @@ public: // SWidget interface virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override; virtual FVector2D ComputeDesiredSize(float) const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; + virtual void SetDefaultAccessibleText(EAccessibleType AccessibleType = EAccessibleType::Main) override; +#endif // End of SWidget interface protected: diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Text/SlateEditableTextLayout.h b/Engine/Source/Runtime/Slate/Public/Widgets/Text/SlateEditableTextLayout.h index 0626a95a2346..62ecd167af34 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Text/SlateEditableTextLayout.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Text/SlateEditableTextLayout.h @@ -13,7 +13,6 @@ #include "Widgets/Input/IVirtualKeyboardEntry.h" #include "Widgets/Text/ISlateEditableTextWidget.h" #include "Framework/Text/ITextLayoutMarshaller.h" -#include "Framework/Text/TextRange.h" #include "Framework/Text/TextLineHighlight.h" #include "Framework/Text/IRun.h" #include "Framework/Text/TextLayout.h" diff --git a/Engine/Source/Runtime/Slate/Public/Widgets/Views/SListView.h b/Engine/Source/Runtime/Slate/Public/Widgets/Views/SListView.h index c4cc1c820cfa..57b3dbd71bc9 100644 --- a/Engine/Source/Runtime/Slate/Public/Widgets/Views/SListView.h +++ b/Engine/Source/Runtime/Slate/Public/Widgets/Views/SListView.h @@ -507,6 +507,7 @@ private: */ void OnItemSeen( ItemType InItem, TSharedRef InGeneratedWidget) { + ensure(TListTypeTraits::IsPtrValid(InItem)); TSharedRef* LookupResult = ItemToWidgetMap.Find( InItem ); const bool bWidgetIsNewlyGenerated = (LookupResult == nullptr); if ( bWidgetIsNewlyGenerated ) @@ -575,6 +576,26 @@ private: OwnerList->OnRowReleased.ExecuteIfBound(WidgetToCleanUp); } } + else if(!TListTypeTraits::IsPtrValid(ItemToBeCleanedUp)) + { + // If we get here, it means we have an invalid object. We will need to remove that object from both maps. + // This may happen for example when ItemType is a UObject* and the object is garbage collected. + auto Widget = WidgetMapToItem.FindKey(ItemToBeCleanedUp); + if (Widget != nullptr) + { + for (auto WidgetItemPair = ItemToWidgetMap.CreateIterator(); WidgetItemPair; ++WidgetItemPair) + { + const ITableRow* Item = &(WidgetItemPair.Value().Get()); + if (Item == *Widget) + { + WidgetItemPair.RemoveCurrent(); + break; + } + } + + WidgetMapToItem.Remove(*Widget); + } + } } ItemsToBeCleanedUp.Reset(); @@ -971,6 +992,12 @@ public: { const ItemType& CurItem = (*SourceItems)[ItemIndex]; + // We do not generatie a new widget if the CurItem is an invalid object. + if (!TListTypeTraits::IsPtrValid(CurItem)) + { + continue; + } + const float ItemHeight = GenerateWidgetForItem(CurItem, ItemIndex, StartIndex, LayoutScaleMultiplier); const bool bIsFirstItem = ItemIndex == StartIndex; @@ -1029,6 +1056,11 @@ public: for( int32 ItemIndex = StartIndex-1; HeightGeneratedSoFar < MyGeometry.GetLocalSize().Y && ItemIndex >= 0; --ItemIndex ) { const ItemType& CurItem = (*SourceItems)[ItemIndex]; + // When the item is not valid, we do not generate a widget for it. + if (!TListTypeTraits::IsPtrValid(CurItem)) + { + continue; + } const float ItemHeight = GenerateWidgetForItem(CurItem, ItemIndex, StartIndex, LayoutScaleMultiplier); @@ -1054,6 +1086,7 @@ public: float GenerateWidgetForItem( const ItemType& CurItem, int32 ItemIndex, int32 StartIndex, float LayoutScaleMultiplier ) { + ensure(TListTypeTraits::IsPtrValid(CurItem)); // Find a previously generated Widget for this item, if one exists. TSharedPtr WidgetForItem = WidgetGenerator.GetWidgetForItem( CurItem ); if ( !WidgetForItem.IsValid() ) @@ -1395,7 +1428,7 @@ public: */ virtual void AddReferencedObjects( FReferenceCollector& Collector ) { - TListTypeTraits::AddReferencedObjects( Collector, WidgetGenerator.ItemsWithGeneratedWidgets, SelectedItems ); + TListTypeTraits::AddReferencedObjects( Collector, WidgetGenerator.ItemsWithGeneratedWidgets, SelectedItems, WidgetGenerator.WidgetMapToItem ); } /** @@ -1606,6 +1639,13 @@ protected: while( AbsScrollByAmount != 0 && ItemIndex < SourceItems->Num() && ItemIndex >= 0 ) { const ItemType CurItem = (*SourceItems)[ ItemIndex ]; + // If the CurItem is not valid, we do not generate a new widget for it, we skip it. + if (!TListTypeTraits::IsPtrValid(CurItem)) + { + ++ItemIndex; + continue; + } + TSharedPtr RowWidget = WidgetGenerator.GetWidgetForItem( CurItem ); if ( !RowWidget.IsValid() ) { diff --git a/Engine/Source/Runtime/SlateCore/Private/Fonts/LegacySlateFontInfoCache.cpp b/Engine/Source/Runtime/SlateCore/Private/Fonts/LegacySlateFontInfoCache.cpp index eafc7e8fc8b4..a059d1daf6fb 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Fonts/LegacySlateFontInfoCache.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Fonts/LegacySlateFontInfoCache.cpp @@ -9,7 +9,7 @@ #include "Fonts/FontProviderInterface.h" #include "Fonts/UnicodeBlockRange.h" -static int32 GSlateEnableLegacyLocalizedFallbackFont = 1; +static int32 GSlateEnableLegacyLocalizedFallbackFont = 0; static FAutoConsoleVariableRef CVarSlateEnableLocalizedFallbackFont(TEXT("Slate.EnableLegacyLocalizedFallbackFont"), GSlateEnableLegacyLocalizedFallbackFont, TEXT("Enable the legacy localized fallback fonts? (0/1)."), ECVF_Default); FString FLegacySlateFontInfoCache::FFallbackContext::ToString() const diff --git a/Engine/Source/Runtime/SlateCore/Private/Input/Events.cpp b/Engine/Source/Runtime/SlateCore/Private/Input/Events.cpp index fd27254cc6c1..1c0ef440b9fc 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Input/Events.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Input/Events.cpp @@ -15,7 +15,7 @@ const FTouchKeySet FTouchKeySet::EmptySet(EKeys::Invalid); FGeometry FInputEvent::FindGeometry(const TSharedRef& WidgetToFind) const { - return EventPath->FindArrangedWidget(WidgetToFind).Get(FArrangedWidget::NullWidget).Geometry; + return EventPath->FindArrangedWidget(WidgetToFind).Get(FArrangedWidget::GetNullWidget()).Geometry; } TSharedRef FInputEvent::GetWindow() const diff --git a/Engine/Source/Runtime/SlateCore/Private/Layout/ArrangedWidget.cpp b/Engine/Source/Runtime/SlateCore/Private/Layout/ArrangedWidget.cpp index 7932ef7c826d..9e4665e573bf 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Layout/ArrangedWidget.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Layout/ArrangedWidget.cpp @@ -4,8 +4,13 @@ #include "Widgets/SNullWidget.h" #include "Widgets/SWidget.h" - -FArrangedWidget FArrangedWidget::NullWidget(SNullWidget::NullWidget, FGeometry()); +// Use function to initialize because SNullWidget::NullWidget is not statically initialized yet +const FArrangedWidget& FArrangedWidget::GetNullWidget() +{ + static FArrangedWidget NullArrangedWidget(SNullWidget::NullWidget, FGeometry()); + checkSlow(&SNullWidget::NullWidget.Get() != nullptr); + return NullArrangedWidget; +} FString FArrangedWidget::ToString( ) const { @@ -14,7 +19,7 @@ FString FArrangedWidget::ToString( ) const FWidgetAndPointer::FWidgetAndPointer() -: FArrangedWidget(FArrangedWidget::NullWidget) +: FArrangedWidget(FArrangedWidget::GetNullWidget()) , PointerPosition(TSharedPtr()) {} diff --git a/Engine/Source/Runtime/SlateCore/Private/Layout/WidgetPath.cpp b/Engine/Source/Runtime/SlateCore/Private/Layout/WidgetPath.cpp index 47b40193d7c8..4453b44dd62f 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Layout/WidgetPath.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Layout/WidgetPath.cpp @@ -416,7 +416,7 @@ bool FWeakWidgetPath::ContainsWidget( const TSharedRef< const SWidget >& SomeWid FWidgetPath FWeakWidgetPath::ToNextFocusedPath(EUINavigation NavigationType) const { - return ToNextFocusedPath(NavigationType, FNavigationReply::Escape(), FArrangedWidget::NullWidget); + return ToNextFocusedPath(NavigationType, FNavigationReply::Escape(), FArrangedWidget::GetNullWidget()); } FWidgetPath FWeakWidgetPath::ToNextFocusedPath(EUINavigation NavigationType, const FNavigationReply& NavigationReply, const FArrangedWidget& RuleWidget) const diff --git a/Engine/Source/Runtime/SlateCore/Private/Widgets/Accessibility/SlateAccessibleMessageHandler.cpp b/Engine/Source/Runtime/SlateCore/Private/Widgets/Accessibility/SlateAccessibleMessageHandler.cpp new file mode 100644 index 000000000000..0f3d8a0f52e7 --- /dev/null +++ b/Engine/Source/Runtime/SlateCore/Private/Widgets/Accessibility/SlateAccessibleMessageHandler.cpp @@ -0,0 +1,181 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#include "Widgets/Accessibility/SlateAccessibleWidgetCache.h" +#include "Application/SlateApplicationBase.h" +#include "Application/SlateWindowHelper.h" +#include "Widgets/SWidget.h" +#include "Widgets/SWindow.h" +#include "Input/HittestGrid.h" + +DECLARE_CYCLE_STAT(TEXT("Slate Accessibility: Parent Updated"), STAT_AccessibilitySlateParentUpdated, STATGROUP_Accessibility); +DECLARE_CYCLE_STAT(TEXT("Slate Accessibility: Children Updated"), STAT_AccessibilitySlateChildrenUpdated, STATGROUP_Accessibility); +DECLARE_CYCLE_STAT(TEXT("Slate Accessibility: Behavior Changed"), STAT_AccessibilitySlateBehaviorChanged, STATGROUP_Accessibility); +DECLARE_CYCLE_STAT(TEXT("Slate Accessibility: Event Raised"), STAT_AccessibilitySlateEventRaised, STATGROUP_Accessibility); + +void FSlateAccessibleMessageHandler::OnActivate() +{ + // widgets are initialized when their accessible window is created +} + +void FSlateAccessibleMessageHandler::OnDeactivate() +{ + FSlateAccessibleWidgetCache::Get().ClearAll(); +} + +TSharedPtr FSlateAccessibleMessageHandler::GetAccessibleWindow(const TSharedRef& InWindow) const +{ + if (IsActive()) + { + TSharedPtr SlateWindow = FSlateWindowHelper::FindWindowByPlatformWindow(FSlateApplicationBase::Get().GetTopLevelWindows(), InWindow); + if (SlateWindow.IsValid()) + { + return FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(SlateWindow); + } + } + return nullptr; +} + +AccessibleWidgetId FSlateAccessibleMessageHandler::GetAccessibleWindowId(const TSharedRef& InWindow) const +{ + TSharedPtr AccessibleWindow = GetAccessibleWindow(InWindow); + if (AccessibleWindow.IsValid()) + { + return AccessibleWindow->GetId(); + } + return IAccessibleWidget::InvalidAccessibleWidgetId; +} + +TSharedPtr FSlateAccessibleMessageHandler::GetAccessibleWidgetFromId(AccessibleWidgetId Id) const +{ + return FSlateAccessibleWidgetCache::Get().GetAccessibleWidgetFromId(Id); +} + +void FSlateAccessibleMessageHandler::OnWidgetRemoved(SWidget* Widget) +{ + if (IsActive()) + { + TSharedPtr RemovedWidget = FSlateAccessibleWidgetCache::Get().RemoveWidget(Widget); + if (RemovedWidget.IsValid()) + { + RaiseEvent(StaticCastSharedPtr(RemovedWidget).ToSharedRef(), EAccessibleEvent::WidgetRemoved); + } + } +} + +void FSlateAccessibleMessageHandler::OnWidgetParentChanged(TSharedRef Widget) +{ + if (IsActive()) + { + SCOPE_CYCLE_COUNTER(STAT_AccessibilitySlateParentUpdated); + + TSharedPtr Parent = Widget->GetParentWidget(); + while (Parent.IsValid() && !Parent->IsAccessible()) + { + Parent = Parent->GetParentWidget(); + } + + if (Widget->IsAccessible()) + { + if (Parent.IsValid()) + { + FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Widget)->UpdateParent(FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Parent)); + } + else + { + FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Widget)->UpdateParent(nullptr); + } + } + else if (Parent.IsValid() && Parent->CanChildrenBeAccessible()) + { + TSharedPtr AccessibleParent = FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Parent); + TArray> AccessibleChildren = FSlateAccessibleWidget::GetAccessibleChildren(Widget); + for (int32 i = 0; i < AccessibleChildren.Num(); ++i) + { + FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(AccessibleChildren[i])->UpdateParent(AccessibleParent); + } + } + } +} + +void FSlateAccessibleMessageHandler::OnWidgetChildrenChanged(TSharedRef Widget) +{ + if (IsActive()) + { + SCOPE_CYCLE_COUNTER(STAT_AccessibilitySlateChildrenUpdated); + + TSharedPtr Parent = Widget; + while (Parent.IsValid() && !Parent->IsAccessible()) + { + Parent = Parent->GetParentWidget(); + } + if (Parent.IsValid()) + { + TSharedPtr AccessibleParent = FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Parent); + StaticCastSharedPtr(AccessibleParent)->MarkChildrenDirty(); + } + } +} + +void FSlateAccessibleMessageHandler::OnWidgetAccessibleBehaviorChanged(TSharedRef Widget) +{ + if (IsActive()) + { + SCOPE_CYCLE_COUNTER(STAT_AccessibilitySlateBehaviorChanged); + + TSharedPtr Parent = Widget->GetParentWidget(); + while (Parent.IsValid()) + { + if (Parent->IsAccessible()) + { + break; + } + + Parent = Parent->GetParentWidget(); + } + + if (Parent.IsValid()) + { + TSharedPtr AccessibleParent = FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Parent); + TArray> AccessibleChildren = FSlateAccessibleWidget::GetAccessibleChildren(Widget); + if (Widget->IsAccessible()) + { + TSharedPtr AccessibleWidget = StaticCastSharedPtr(FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Widget)); + for (int32 i = 0; i < AccessibleChildren.Num(); ++i) + { + FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(AccessibleChildren[i])->UpdateParent(AccessibleWidget); + } + AccessibleWidget->UpdateParent(AccessibleParent); + } + else + { + for (int32 i = 0; i < AccessibleChildren.Num(); ++i) + { + FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(AccessibleChildren[i])->UpdateParent(AccessibleParent); + } + } + } + } +} + +void FSlateAccessibleMessageHandler::OnWidgetEventRaised(TSharedRef Widget, EAccessibleEvent Event) +{ + OnWidgetEventRaised(Widget, Event, FVariant(), FVariant()); +} + +void FSlateAccessibleMessageHandler::OnWidgetEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue) +{ + if (IsActive()) + { + SCOPE_CYCLE_COUNTER(STAT_AccessibilitySlateEventRaised); + // todo: not sure what to do for a case like focus changed to not-accessible widget. maybe pass through a nullptr? + if (Widget->IsAccessible()) + { + FSlateAccessibleMessageHandler::RaiseEvent(FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(Widget).ToSharedRef(), Event, OldValue, NewValue); + } + } +} + +#endif diff --git a/Engine/Source/Runtime/SlateCore/Private/Widgets/Accessibility/SlateCoreAccessibleWidgets.cpp b/Engine/Source/Runtime/SlateCore/Private/Widgets/Accessibility/SlateCoreAccessibleWidgets.cpp new file mode 100644 index 000000000000..018be29aec6f --- /dev/null +++ b/Engine/Source/Runtime/SlateCore/Private/Widgets/Accessibility/SlateCoreAccessibleWidgets.cpp @@ -0,0 +1,520 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#if WITH_ACCESSIBILITY + +#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h" +#include "Widgets/Accessibility/SlateAccessibleWidgetCache.h" +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#include "Layout/WidgetPath.h" +#include "Widgets/IToolTip.h" +#include "Widgets/SWidget.h" +#include "Widgets/SWindow.h" +#include "Input/HittestGrid.h" +#include "Application/SlateApplicationBase.h" +#include "Application/SlateWindowHelper.h" +#include "Math/NumericLimits.h" + +DECLARE_CYCLE_STAT(TEXT("Slate Accessibility: Get Widget At Point"), STAT_AccessibilitySlateGetChildAtPosition, STATGROUP_Accessibility); + +FSlateAccessibleWidget::FSlateAccessibleWidget(TWeakPtr InWidget, EAccessibleWidgetType InWidgetType) + : Widget(InWidget) + , WidgetType(InWidgetType) + , SiblingIndex(INDEX_NONE) + , bChildrenDirty(true) +{ + static AccessibleWidgetId RuntimeIdCounter = 0; + if (RuntimeIdCounter == TNumericLimits::Max()) + { + RuntimeIdCounter = TNumericLimits::Min(); + } + if (RuntimeIdCounter == InvalidAccessibleWidgetId) + { + ++RuntimeIdCounter; + } + Id = RuntimeIdCounter++; +} + +FSlateAccessibleWidget::~FSlateAccessibleWidget() +{ +} + +AccessibleWidgetId FSlateAccessibleWidget::GetId() const +{ + return Id; +} + +bool FSlateAccessibleWidget::IsValid() const +{ + return Widget.IsValid(); +} + +TSharedPtr FSlateAccessibleWidget::GetTopLevelSlateWindow() const +{ + if (Widget.IsValid()) + { + TSharedPtr WindowWidget = Widget.Pin(); + // todo: fix this for nested windows + while (WindowWidget.IsValid()) + { + if (WindowWidget->Advanced_IsWindow()) + { + return StaticCastSharedPtr(WindowWidget); + } + WindowWidget = WindowWidget->GetParentWidget(); + } + } + return nullptr; +} + +TSharedPtr FSlateAccessibleWidget::GetTopLevelWindow() const +{ + return FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(GetTopLevelSlateWindow()); +} + +FBox2D FSlateAccessibleWidget::GetBounds() const +{ + if (Widget.IsValid()) + { + const FGeometry& Geometry = Widget.Pin()->GetCachedGeometry(); + return FBox2D(Geometry.GetAbsolutePosition(), Geometry.GetAbsolutePosition() + Geometry.GetAbsoluteSize()); + } + return FBox2D(); +} + +FString FSlateAccessibleWidget::GetClassName() const +{ + if (Widget.IsValid()) + { + // Note: this is technically debug code and not guaranteed to work + return Widget.Pin()->GetTypeAsString(); + } + return FString(); +} + +FString FSlateAccessibleWidget::GetWidgetName() const +{ + if (Widget.IsValid()) + { + FText AccessibleText = Widget.Pin()->GetAccessibleText(); + if (AccessibleText.IsEmpty()) + { + TSharedPtr Tag = Widget.Pin()->GetMetaData(); + if (Tag.IsValid()) + { + return Tag->Tag.ToString(); + } + else + { + return GetClassName(); + } + } + else + { + return AccessibleText.ToString(); + } + } + return FString(); +} + +FString FSlateAccessibleWidget::GetHelpText() const +{ + if (Widget.IsValid()) + { + TSharedPtr ToolTip = Widget.Pin()->GetToolTip(); + if (ToolTip.IsValid()) + { + return ToolTip->GetContentWidget()->GetAccessibleText().ToString(); + } + } + return FString(); +} + +bool FSlateAccessibleWidget::IsEnabled() const +{ + if (Widget.IsValid()) + { + return Widget.Pin()->IsEnabled(); + } + return false; +} + +bool FSlateAccessibleWidget::IsHidden() const +{ + if (Widget.IsValid()) + { + return !Widget.Pin()->GetVisibility().IsVisible(); + } + return true; +} + +bool FSlateAccessibleWidget::SupportsFocus() const +{ + if (Widget.IsValid()) + { + return Widget.Pin()->SupportsKeyboardFocus(); + } + return false; +} + +bool FSlateAccessibleWidget::HasFocus() const +{ + if (Widget.IsValid()) + { + return Widget.Pin()->HasKeyboardFocus(); + } + return false; +} + +void FSlateAccessibleWidget::SetFocus() +{ + if (SupportsFocus()) + { + TSharedPtr WidgetWindow = GetTopLevelSlateWindow(); + if (WidgetWindow.IsValid()) + { + TArray> WindowArray; + WindowArray.Add(WidgetWindow.ToSharedRef()); + FWidgetPath WidgetPath; + if (FSlateWindowHelper::FindPathToWidget(WindowArray, Widget.Pin().ToSharedRef(), WidgetPath)) + { + FSlateApplicationBase::Get().SetKeyboardFocus(WidgetPath, EFocusCause::SetDirectly); + } + } + } +} + +void FSlateAccessibleWidget::MarkChildrenDirty() +{ + for (int32 i = 0; i < Children.Num(); ++i) + { + if (Children[i].IsValid()) + { + Children[i].Pin()->SiblingIndex = INDEX_NONE; + } + } + + Children.Reset(); + bChildrenDirty = true; +} + +void FSlateAccessibleWidget::UpdateAllChildren(bool bUpdateRecursively) +{ + if (bChildrenDirty) + { + bChildrenDirty = false; + if (Widget.IsValid()) + { + TArray> AccessibleChildren = GetAccessibleChildren(Widget.Pin().ToSharedRef()); + Children.Reset(AccessibleChildren.Num()); + for (int32 i = 0; i < AccessibleChildren.Num(); ++i) + { + TSharedPtr Child = FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(AccessibleChildren[i]); + Children.Add(Child); + Child->Parent = StaticCastSharedRef(AsShared()); + Child->SiblingIndex = i; + + if (bUpdateRecursively) + { + Child->UpdateAllChildren(true); + } + } + } + } +} + +void FSlateAccessibleWidget::UpdateParent(TSharedPtr NewParent) +{ + if (Parent == NewParent) + { + return; + } + + if (Parent.IsValid()) + { + // Even though we're storing SiblingIndex, we have no guarantee + // that GetChildren will return the same order after a widget is + // added or removed, so we have to re-update all indices. + Parent.Pin()->MarkChildrenDirty(); + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->RaiseEvent(AsShared(), EAccessibleEvent::BeforeRemoveFromParent); + } + + Parent = StaticCastSharedPtr(NewParent); + + if (Parent.IsValid()) + { + Parent.Pin()->MarkChildrenDirty(); + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->RaiseEvent(AsShared(), EAccessibleEvent::AfterAddToParent); + } + else + { + SiblingIndex = INDEX_NONE; + } +} + +TSharedPtr FSlateAccessibleWidget::GetParent() +{ + if (Parent.IsValid()) + { + return Parent.Pin(); + } + return nullptr; +} + +TSharedPtr FSlateAccessibleWidget::GetNextSibling() +{ + if (Parent.IsValid()) + { + TSharedPtr SharedParent = Parent.Pin(); + SharedParent->UpdateAllChildren(); + if (SiblingIndex >= 0 && SiblingIndex < SharedParent->Children.Num() - 1) + { + const TWeakPtr& Child = SharedParent->Children[SiblingIndex + 1]; + if (Child.IsValid()) + { + return Child.Pin(); + } + } + } + return nullptr; +} + +TSharedPtr FSlateAccessibleWidget::GetPreviousSibling() +{ + if (Parent.IsValid()) + { + TSharedPtr SharedParent = Parent.Pin(); + SharedParent->UpdateAllChildren(); + if (SiblingIndex >= 1 && SiblingIndex < SharedParent->Children.Num()) + { + const TWeakPtr& Child = SharedParent->Children[SiblingIndex - 1]; + if (Child.IsValid()) + { + return Child.Pin(); + } + } + } + return nullptr; +} + +TSharedPtr FSlateAccessibleWidget::GetChildAt(int32 Index) +{ + UpdateAllChildren(); + if (Index >= 0 && Index < Children.Num() && Widget.IsValid() && Widget.Pin()->CanChildrenBeAccessible()) + { + const TWeakPtr& Child = Children[Index]; + if (Child.IsValid()) + { + return Child.Pin(); + } + + } + return nullptr; +} + +int32 FSlateAccessibleWidget::GetNumberOfChildren() +{ + UpdateAllChildren(); + if (Widget.IsValid() && Widget.Pin()->CanChildrenBeAccessible()) + { + return Children.Num(); + } + return 0; +} + +TArray> FSlateAccessibleWidget::GetAccessibleChildren(TSharedRef Widget) +{ + TArray> AccessibleChildren; + if (!Widget->CanChildrenBeAccessible()) + { + return AccessibleChildren; + } + + FChildren* Children = Widget->GetChildren(); + if (Children) + { + for (int32 i = 0; i < Children->Num(); ++i) + { + TSharedRef Child = Children->GetChildAt(i); + if (Child->GetAccessibleBehavior() != EAccessibleBehavior::NotAccessible) + { + AccessibleChildren.Add(Child); + } + else + { + AccessibleChildren.Append(GetAccessibleChildren(Child)); + } + } + } + return AccessibleChildren; +} + +TSharedPtr FSlateAccessibleWidget::GetChildAtUsingGeometry(int32 X, int32 Y) +{ + // This is slow and we should use the HitTest grid when possible. + if (!IsHidden() && GetBounds().IsInside(FVector2D(X, Y))) + { + UpdateAllChildren(); + // Traverse the hierarchy backwards in order to handle the case where widgets are overlaid on top of each other. + for (int32 i = Children.Num() - 1; i >= 0; --i) + { + if (Children[i].IsValid()) + { + TSharedPtr Child = Children[i].Pin()->GetChildAtUsingGeometry(X, Y); + if (Child.IsValid()) + { + return Child; + } + } + } + return AsShared(); + } + return nullptr; +} + +// SWindow +TSharedPtr FSlateAccessibleWindow::GetNativeWindow() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetNativeWindow(); + } + return nullptr; +} + +TSharedPtr FSlateAccessibleWindow::GetChildAtPosition(int32 X, int32 Y) +{ + TSharedPtr HitWidget; + if (Widget.IsValid()) + { + static const bool UseHitTestGrid = false; + + SCOPE_CYCLE_COUNTER(STAT_AccessibilitySlateGetChildAtPosition); + if (UseHitTestGrid) + { + TSharedPtr SlateWindow = StaticCastSharedPtr(Widget.Pin()); + TArray Hits = SlateWindow->GetHittestGrid()->GetBubblePath(FVector2D(X, Y), 0.0f, false); + TSharedPtr LastAccessibleWidget = nullptr; + for (int32 i = 0; i < Hits.Num(); ++i) + { + if (Hits[i].Widget->GetAccessibleBehavior() != EAccessibleBehavior::NotAccessible) + { + LastAccessibleWidget = Hits[i].Widget; + } + if (!Hits[i].Widget->CanChildrenBeAccessible()) + { + break; + } + } + HitWidget = FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(LastAccessibleWidget); + } + else + { + HitWidget = GetChildAtUsingGeometry(X, Y); + } + } + + return HitWidget; +} + +TSharedPtr FSlateAccessibleWindow::GetFocusedWidget() const +{ + return FSlateAccessibleWidgetCache::Get().GetAccessibleWidget(FSlateApplicationBase::Get().GetKeyboardFocusedWidget()); +} + +FString FSlateAccessibleWindow::GetWidgetName() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->GetTitle().ToString(); + } + else + { + return FSlateAccessibleWidget::GetWidgetName(); + } +} + +void FSlateAccessibleWindow::Close() +{ + if (Widget.IsValid()) + { + StaticCastSharedPtr(Widget.Pin())->RequestDestroyWindow(); + } +} + +bool FSlateAccessibleWindow::SupportsDisplayState(EWindowDisplayState State) const +{ + if (Widget.IsValid()) + { + switch (State) + { + case IAccessibleWindow::EWindowDisplayState::Normal: + return true; + case IAccessibleWindow::EWindowDisplayState::Minimize: + return StaticCastSharedPtr(Widget.Pin())->HasMinimizeBox(); + case IAccessibleWindow::EWindowDisplayState::Maximize: + return StaticCastSharedPtr(Widget.Pin())->HasMaximizeBox(); + } + } + return false; +} + +IAccessibleWindow::EWindowDisplayState FSlateAccessibleWindow::GetDisplayState() const +{ + if (Widget.IsValid()) + { + TSharedPtr Window = StaticCastSharedPtr(Widget.Pin()); + if (Window->IsWindowMaximized()) + { + return IAccessibleWindow::EWindowDisplayState::Maximize; + } + else if (Window->IsWindowMinimized()) + { + return IAccessibleWindow::EWindowDisplayState::Minimize; + } + else + { + return IAccessibleWindow::EWindowDisplayState::Normal; + } + } + return IAccessibleWindow::EWindowDisplayState::Normal; +} + +void FSlateAccessibleWindow::SetDisplayState(EWindowDisplayState State) +{ + if (Widget.IsValid() && GetDisplayState() != State) + { + switch (State) + { + case IAccessibleWindow::EWindowDisplayState::Normal: + StaticCastSharedPtr(Widget.Pin())->Restore(); + break; + case IAccessibleWindow::EWindowDisplayState::Minimize: + StaticCastSharedPtr(Widget.Pin())->Minimize(); + break; + case IAccessibleWindow::EWindowDisplayState::Maximize: + StaticCastSharedPtr(Widget.Pin())->Maximize(); + break; + } + } +} + +bool FSlateAccessibleWindow::IsModal() const +{ + if (Widget.IsValid()) + { + return StaticCastSharedPtr(Widget.Pin())->IsModalWindow(); + } + return false; +} + +// ~ + +// SImage +FString FSlateAccessibleImage::GetHelpText() const +{ + // todo: See UIA_HelpTextPropertyId on https://docs.microsoft.com/en-us/windows/desktop/winauto/uiauto-supportimagecontroltype + return FString(); +} +// ~ + +#endif diff --git a/Engine/Source/Runtime/SlateCore/Private/Widgets/Images/SImage.cpp b/Engine/Source/Runtime/SlateCore/Private/Widgets/Images/SImage.cpp index cbf2053cdf65..9d130886e228 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Widgets/Images/SImage.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Widgets/Images/SImage.cpp @@ -2,6 +2,10 @@ #include "Widgets/Images/SImage.h" #include "Rendering/DrawElements.h" +#include "Widgets/IToolTip.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h" +#endif void SImage::Construct( const FArguments& InArgs ) { @@ -74,3 +78,10 @@ void SImage::SetImage(TAttribute InImage) Invalidate(EInvalidateWidget::LayoutAndVolatility); } } + +#if WITH_ACCESSIBILITY +TSharedPtr SImage::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleImage(SharedThis(this))); +} +#endif diff --git a/Engine/Source/Runtime/SlateCore/Private/Widgets/SWidget.cpp b/Engine/Source/Runtime/SlateCore/Private/Widgets/SWidget.cpp index b16dd4279823..764bffb91fdd 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Widgets/SWidget.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Widgets/SWidget.cpp @@ -15,6 +15,10 @@ #include "Application/ActiveTimerHandle.h" #include "Input/HittestGrid.h" #include "Debugging/SlateDebugging.h" +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h" +#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h" +#endif DECLARE_DWORD_COUNTER_STAT(TEXT("Widgets Created (Per Frame)"), STAT_SlateTotalWidgetsPerFrame, STATGROUP_Slate); DECLARE_DWORD_COUNTER_STAT(TEXT("SWidget::Paint (Count)"), STAT_SlateNumPaintedWidgets, STATGROUP_Slate); @@ -142,6 +146,9 @@ SWidget::~SWidget() { FSlateApplicationBase::Get().UnRegisterActiveTimer(ActiveTimerHandle); } +#if WITH_ACCESSIBILITY + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetRemoved(this); +#endif } DEC_DWORD_STAT(STAT_SlateTotalWidgets); @@ -161,6 +168,7 @@ void SWidget::Construct( const bool InForceVolatile, const EWidgetClipping InClipping, const EFlowDirectionPreference InFlowPreference, + const FAccessibleWidgetData& InAccessibleData, const TArray>& InMetaData ) { @@ -191,11 +199,29 @@ void SWidget::Construct( Clipping = InClipping; FlowDirectionPreference = InFlowPreference; MetaData = InMetaData; + +#if WITH_ACCESSIBILITY + AccessibleData.bCanChildrenBeAccessible = InAccessibleData.bCanChildrenBeAccessible; + // If custom text is provided, force behavior to custom. Otherwise, use the passed-in behavior and set their default text. + if (!InAccessibleData.AccessibleText.IsSet()) + { + SetDefaultAccessibleText(EAccessibleType::Main); + } + if (!InAccessibleData.AccessibleSummaryText.IsSet()) + { + SetDefaultAccessibleText(EAccessibleType::Summary); + } + SetAccessibleBehavior(InAccessibleData.AccessibleText.IsSet() ? EAccessibleBehavior::Custom : InAccessibleData.AccessibleBehavior, InAccessibleData.AccessibleText, EAccessibleType::Main); + SetAccessibleBehavior(InAccessibleData.AccessibleSummaryText.IsSet() ? EAccessibleBehavior::Custom : InAccessibleData.AccessibleSummaryBehavior, InAccessibleData.AccessibleSummaryText, EAccessibleType::Summary); +#endif } -void SWidget::SWidgetConstruct(const TAttribute& InToolTipText, const TSharedPtr& InToolTip, const TAttribute< TOptional >& InCursor, const TAttribute& InEnabledState, const TAttribute& InVisibility, const float InRenderOpacity, const TAttribute>& InTransform, const TAttribute& InTransformPivot, const FName& InTag, const bool InForceVolatile, const EWidgetClipping InClipping, const EFlowDirectionPreference InFlowPreference, const TArray>& InMetaData) +void SWidget::SWidgetConstruct(const TAttribute& InToolTipText, const TSharedPtr& InToolTip, const TAttribute< TOptional >& InCursor, const TAttribute& InEnabledState, + const TAttribute& InVisibility, const float InRenderOpacity, const TAttribute>& InTransform, const TAttribute& InTransformPivot, + const FName& InTag, const bool InForceVolatile, const EWidgetClipping InClipping, const EFlowDirectionPreference InFlowPreference, const FAccessibleWidgetData& InAccessibleData, + const TArray>& InMetaData) { - Construct(InToolTipText, InToolTip, InCursor, InEnabledState, InVisibility, InRenderOpacity, InTransform, InTransformPivot, InTag, InForceVolatile, InClipping, InFlowPreference, InMetaData); + Construct(InToolTipText, InToolTip, InCursor, InEnabledState, InVisibility, InRenderOpacity, InTransform, InTransformPivot, InTag, InForceVolatile, InClipping, InFlowPreference, InAccessibleData, InMetaData); } FReply SWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) @@ -566,6 +592,12 @@ void SWidget::AssignParentWidget(TSharedPtr InParent) #endif ParentWidgetPtr = InParent; +#if WITH_ACCESSIBILITY + if (FSlateApplicationBase::IsInitialized()) + { + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetParentChanged(AsShared()); + } +#endif if (InParent.IsValid()) { InParent->Invalidate(EInvalidateWidget::Layout); @@ -582,6 +614,12 @@ bool SWidget::ConditionallyDetatchParentWidget(SWidget* InExpectedParent) if (Parent.Get() == InExpectedParent) { ParentWidgetPtr.Reset(); +#if WITH_ACCESSIBILITY + if (FSlateApplicationBase::IsInitialized()) + { + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetParentChanged(AsShared()); + } +#endif if (Parent.IsValid()) { @@ -1236,6 +1274,139 @@ void SWidget::SetOnMouseLeave(FSimpleNoReplyPointerEventHandler EventHandler) MouseLeaveHandler = EventHandler; } +#if WITH_ACCESSIBILITY +TSharedPtr SWidget::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleWidget(AsShared())); +} + +void SWidget::SetAccessibleBehavior(EAccessibleBehavior InBehavior, const TAttribute& InText, EAccessibleType AccessibleType) +{ + EAccessibleBehavior& Behavior = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleBehavior : AccessibleData.AccessibleSummaryBehavior; + if (Behavior != InBehavior) + { + // If switching off of custom, revert back to default text + if (Behavior == EAccessibleBehavior::Custom) + { + SetDefaultAccessibleText(AccessibleType); + } + else if (InBehavior == EAccessibleBehavior::Custom) + { + TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + Text = InText; + } + const bool bWasAccessible = IsAccessible(); + Behavior = InBehavior; + if (AccessibleType == EAccessibleType::Main && bWasAccessible != IsAccessible()) + { + FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetAccessibleBehaviorChanged(AsShared()); + } + } +} + +void SWidget::SetCanChildrenBeAccessible(bool InCanChildrenBeAccessible) +{ + AccessibleData.bCanChildrenBeAccessible = InCanChildrenBeAccessible; + // todo: emit notifications +} + +void SWidget::SetDefaultAccessibleText(EAccessibleType AccessibleType) +{ + TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + Text = TAttribute(); +} + +FText SWidget::GetAccessibleText(EAccessibleType AccessibleType) const +{ + const EAccessibleBehavior Behavior = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleBehavior : AccessibleData.AccessibleSummaryBehavior; + const EAccessibleBehavior OtherBehavior = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleSummaryBehavior : AccessibleData.AccessibleBehavior; + const TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + const TAttribute& OtherText = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleSummaryText : AccessibleData.AccessibleText; + + switch (Behavior) + { + case EAccessibleBehavior::Custom: + return Text.Get(FText::GetEmpty()); + case EAccessibleBehavior::Summary: + return GetAccessibleSummary(); + case EAccessibleBehavior::ToolTip: + if (ToolTip.IsValid() && !ToolTip->IsEmpty()) + { + return ToolTip->GetContentWidget()->GetAccessibleText(EAccessibleType::Main); + } + break; + case EAccessibleBehavior::Auto: + // Auto first checks if custom text was set. This should never happen with user-defined values as custom should be + // used instead in that case - however, this will be used for widgets with special default text such as TextBlocks. + // If no text is found, then it will attempt to use the other variable's text, so that a developer can do things like + // leave Summary on Auto, set Main to Custom, and have Summary automatically use Main's value without having to re-type it. + if (Text.IsSet()) + { + return Text.Get(FText::GetEmpty()); + } + switch (OtherBehavior) + { + case EAccessibleBehavior::Custom: + case EAccessibleBehavior::ToolTip: + return GetAccessibleText(AccessibleType == EAccessibleType::Main ? EAccessibleType::Summary : EAccessibleType::Main); + case EAccessibleBehavior::NotAccessible: + case EAccessibleBehavior::Summary: + return GetAccessibleSummary(); + } + break; + } + return FText::GetEmpty(); +} + +FText SWidget::GetAccessibleSummary() const +{ + FTextBuilder Builder; + FChildren* Children = const_cast(this)->GetChildren(); + if (Children) + { + for (int32 i = 0; i < Children->Num(); ++i) + { + FText Text = Children->GetChildAt(i)->GetAccessibleText(EAccessibleType::Summary); + if (!Text.IsEmpty()) + { + Builder.AppendLine(Text); + } + } + } + return Builder.ToText(); +} + +bool SWidget::IsAccessible() const +{ + if (AccessibleData.AccessibleBehavior == EAccessibleBehavior::NotAccessible) + { + return false; + } + + TSharedPtr Parent = GetParentWidget(); + while (Parent.IsValid()) + { + if (!Parent->CanChildrenBeAccessible()) + { + return false; + } + Parent = Parent->GetParentWidget(); + } + return true; +} + +EAccessibleBehavior SWidget::GetAccessibleBehavior(EAccessibleType AccessibleType) const +{ + return AccessibleType == EAccessibleType::Main ? AccessibleData.AccessibleBehavior : AccessibleData.AccessibleSummaryBehavior; +} + +bool SWidget::CanChildrenBeAccessible() const +{ + return AccessibleData.bCanChildrenBeAccessible; +} + +#endif + #if SLATE_CULL_WIDGETS bool SWidget::IsChildWidgetCulled(const FSlateRect& MyCullingRect, const FArrangedWidget& ArrangedChild) const diff --git a/Engine/Source/Runtime/SlateCore/Private/Widgets/SWindow.cpp b/Engine/Source/Runtime/SlateCore/Private/Widgets/SWindow.cpp index 4bd55a1168e2..af888766f249 100644 --- a/Engine/Source/Runtime/SlateCore/Private/Widgets/SWindow.cpp +++ b/Engine/Source/Runtime/SlateCore/Private/Widgets/SWindow.cpp @@ -6,7 +6,9 @@ #include "Layout/WidgetPath.h" #include "Input/HittestGrid.h" #include "HAL/PlatformApplicationMisc.h" - +#if WITH_ACCESSIBILITY +#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h" +#endif FOverlayPopupLayer::FOverlayPopupLayer(const TSharedRef& InitHostWindow, const TSharedRef& InitPopupContent, TSharedPtr InitOverlay) : FPopupLayer(InitHostWindow, InitPopupContent) @@ -880,7 +882,7 @@ void SWindow::ReshapeWindow( FVector2D NewPosition, FVector2D NewSize ) const FVector2D CurrentSize = GetSizeInScreen(); const FVector2D NewPositionTruncated = FVector2D(FMath::TruncToInt(NewPosition.X), FMath::TruncToInt(NewPosition.Y)); - const FVector2D NewSizeRounded = FVector2D(FMath::CeilToInt(NewSize.X), FMath::CeilToInt(NewSize.Y)); + const FVector2D NewSizeRounded = FVector2D(FMath::TruncToInt(NewSize.X), FMath::TruncToInt(NewSize.Y)); if ( CurrentPosition != NewPositionTruncated || CurrentSize != NewSizeRounded ) { @@ -906,7 +908,7 @@ void SWindow::ReshapeWindow( FVector2D NewPosition, FVector2D NewSize ) void SWindow::ReshapeWindow( const FSlateRect& InNewShape ) { - ReshapeWindow( FVector2D(InNewShape.Left, InNewShape.Top), FVector2D( InNewShape.Right - InNewShape.Left, InNewShape.Bottom - InNewShape.Top) ); + ReshapeWindow(FVector2D(InNewShape.Left, InNewShape.Top), FVector2D(InNewShape.Right - InNewShape.Left, InNewShape.Bottom - InNewShape.Top)); } void SWindow::Resize( FVector2D NewSize ) @@ -1317,7 +1319,7 @@ void SWindow::ShowWindow() { SlatePrepass( FSlateApplicationBase::Get().GetApplicationScale() * NativeWindow->GetDPIScaleFactor() ); const FVector2D WindowDesiredSizePixels = GetDesiredSizeDesktopPixels(); - ReshapeWindow( InitialDesiredScreenPosition - (WindowDesiredSizePixels * 0.5f), WindowDesiredSizePixels); + ReshapeWindow(InitialDesiredScreenPosition - (WindowDesiredSizePixels * 0.5f), WindowDesiredSizePixels); } // Set the window to be maximized if we need to. Note that this won't actually show the window if its not @@ -2067,3 +2069,16 @@ FScopedSwitchWorldHack::FScopedSwitchWorldHack( const FWidgetPath& WidgetPath ) } } #endif + +#if WITH_ACCESSIBILITY +TSharedPtr SWindow::CreateAccessibleWidget() +{ + return MakeShareable(new FSlateAccessibleWindow(SharedThis(this))); +} + +void SWindow::SetDefaultAccessibleText(EAccessibleType AccessibleType) +{ + TAttribute& Text = (AccessibleType == EAccessibleType::Main) ? AccessibleData.AccessibleText : AccessibleData.AccessibleSummaryText; + Text.Bind(this, &SWindow::GetTitle); +} +#endif diff --git a/Engine/Source/Runtime/SlateCore/Public/Application/SlateApplicationBase.h b/Engine/Source/Runtime/SlateCore/Public/Application/SlateApplicationBase.h index 8036efbac683..c79a9b2bf1eb 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Application/SlateApplicationBase.h +++ b/Engine/Source/Runtime/SlateCore/Public/Application/SlateApplicationBase.h @@ -133,6 +133,13 @@ public: */ virtual TSharedPtr GetActiveTopLevelWindow() const = 0; + /** + * Get a list of all top-level windows in the application, excluding virtual windows. + * + * @return An array of all current top-level SWindows. + */ + virtual const TArray> GetTopLevelWindows() const = 0; + /** * Gets the global application icon. * @@ -228,6 +235,15 @@ public: /** @return Returns true if there are any pop-up menus summoned */ virtual bool AnyMenusVisible() const = 0; + +#if WITH_ACCESSIBILITY + /** + * Subclasses should store their own accessibility handler and return it through this function. + * + * @return A pointer to the accessibility handler, if one exists + */ + virtual TSharedPtr GetAccessibleMessageHandler() const = 0; +#endif protected: /** * Implementation of GetMouseCaptor which can be overridden without warnings. diff --git a/Engine/Source/Runtime/SlateCore/Public/Input/Events.h b/Engine/Source/Runtime/SlateCore/Public/Input/Events.h index 375661fda720..7ca978497bb8 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Input/Events.h +++ b/Engine/Source/Runtime/SlateCore/Public/Input/Events.h @@ -152,7 +152,7 @@ struct FVirtualPointerPosition * Base class for all mouse and keyevents. */ USTRUCT(BlueprintType) -struct FInputEvent +struct SLATECORE_VTABLE FInputEvent { GENERATED_USTRUCT_BODY() @@ -370,7 +370,7 @@ struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 : public TStructOpsTypeTraitsBase2 : public TStructOpsTypeTraitsBase * FCharacterEvent describes a keyboard action where the utf-16 code is given. Used for OnKeyChar messages */ USTRUCT(BlueprintType) -struct FCharacterEvent +struct SLATECORE_VTABLE FCharacterEvent : public FInputEvent { GENERATED_USTRUCT_BODY() @@ -609,7 +609,7 @@ public: * It is passed to event handlers dealing with pointer-based input. */ USTRUCT(BlueprintType) -struct FPointerEvent +struct SLATECORE_VTABLE FPointerEvent : public FInputEvent { GENERATED_USTRUCT_BODY() diff --git a/Engine/Source/Runtime/SlateCore/Public/Layout/ArrangedWidget.h b/Engine/Source/Runtime/SlateCore/Public/Layout/ArrangedWidget.h index 8a614b36d9d8..6e92ca093cb2 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Layout/ArrangedWidget.h +++ b/Engine/Source/Runtime/SlateCore/Public/Layout/ArrangedWidget.h @@ -21,7 +21,7 @@ public: , Widget(InWidget) { } - SLATECORE_API static FArrangedWidget NullWidget; + SLATECORE_API static const FArrangedWidget& GetNullWidget(); public: diff --git a/Engine/Source/Runtime/SlateCore/Public/Layout/Visibility.h b/Engine/Source/Runtime/SlateCore/Public/Layout/Visibility.h index dfbfaa9a0e67..6b046d79dfa6 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Layout/Visibility.h +++ b/Engine/Source/Runtime/SlateCore/Public/Layout/Visibility.h @@ -7,19 +7,19 @@ /** Is an entity visible? */ struct SLATECORE_API EVisibility { - /** Default widget visibility - visible and can interact with the cursor */ + /** Visible and hit-testable (can interact with cursor). Default value. */ static const EVisibility Visible; - /** Not visible and takes up no space in the layout; can never be clicked on because it takes up no space. */ + /** Not visible and takes up no space in the layout (obviously not hit-testable). */ static const EVisibility Collapsed; - /** Not visible, but occupies layout space. Not interactive for obvious reasons. */ + /** Not visible but occupies layout space (obviously not hit-testable). */ static const EVisibility Hidden; - /** Visible to the user, but only as art. The cursors hit tests will never see this widget. */ + /** Visible but not hit-testable (cannot interact with cursor) and children in the hierarchy (if any) are also not hit-testable. */ static const EVisibility HitTestInvisible; - /** Same as HitTestInvisible, but doesn't apply to child widgets. */ + /** Visible but not hit-testable (cannot interact with cursor) and doesn't affect hit-testing on children (if any). */ static const EVisibility SelfHitTestInvisible; /** Any visibility will do */ diff --git a/Engine/Source/Runtime/SlateCore/Public/Styling/SlateBrush.h b/Engine/Source/Runtime/SlateCore/Public/Styling/SlateBrush.h index 8cf049a3ac35..195b403dc14e 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Styling/SlateBrush.h +++ b/Engine/Source/Runtime/SlateCore/Public/Styling/SlateBrush.h @@ -209,6 +209,15 @@ public: return TintColor.GetColor(InWidgetStyle); } + /** + * Unlinks all colors in this brush. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + TintColor.Unlink(); + } + /** * Checks whether this brush has a UTexture object * diff --git a/Engine/Source/Runtime/SlateCore/Public/Styling/SlateColor.h b/Engine/Source/Runtime/SlateCore/Public/Styling/SlateColor.h index 354c344acd46..0289e4f8814d 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Styling/SlateColor.h +++ b/Engine/Source/Runtime/SlateCore/Public/Styling/SlateColor.h @@ -136,6 +136,24 @@ public: return (ColorUseRule == ESlateColorStylingMode::UseColor_Specified) || (ColorUseRule == ESlateColorStylingMode::UseColor_Specified_Link); } + /** + * If the color rule is set to UseColor_Specified_Link, this will copy the linked color internally, + * unlink it and set the color rule to UseColor_Specified. + * Nothing happens if the color rule is not set to UseColor_Specified_Link. + */ + void Unlink() + { + if (ColorUseRule == ESlateColorStylingMode::UseColor_Specified_Link) + { + ColorUseRule = ESlateColorStylingMode::UseColor_Specified; + if (ensure(LinkedSpecifiedColor.IsValid())) + { + SpecifiedColor = *LinkedSpecifiedColor; + LinkedSpecifiedColor.Reset(); + } + } + } + /** * Compares this color with another for equality. * diff --git a/Engine/Source/Runtime/SlateCore/Public/Styling/SlateTypes.h b/Engine/Source/Runtime/SlateCore/Public/Styling/SlateTypes.h index 285420019a42..b242b6ae6c7a 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Styling/SlateTypes.h +++ b/Engine/Source/Runtime/SlateCore/Public/Styling/SlateTypes.h @@ -185,6 +185,25 @@ struct SLATECORE_API FCheckBoxStyle : public FSlateWidgetStyle */ void PostSerialize(const FArchive& Ar); #endif + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + UncheckedImage.UnlinkColors(); + UncheckedHoveredImage.UnlinkColors(); + UncheckedPressedImage.UnlinkColors(); + CheckedImage.UnlinkColors(); + CheckedHoveredImage.UnlinkColors(); + CheckedPressedImage.UnlinkColors(); + UndeterminedImage.UnlinkColors(); + UndeterminedHoveredImage.UnlinkColors(); + UndeterminedPressedImage.UnlinkColors(); + ForegroundColor.Unlink(); + BorderBackgroundColor.Unlink(); + } }; #if WITH_EDITORONLY_DATA @@ -273,6 +292,19 @@ struct SLATECORE_API FTextBlockStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance, AdvancedDisplay) FSlateBrush UnderlineBrush; FTextBlockStyle& SetUnderlineBrush( const FSlateBrush& InUnderlineBrush ){ UnderlineBrush = InUnderlineBrush; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + ColorAndOpacity.Unlink(); + SelectedBackgroundColor.Unlink(); + HighlightShape.UnlinkColors(); + StrikeBrush.UnlinkColors(); + UnderlineBrush.UnlinkColors(); + } }; /** @@ -356,6 +388,18 @@ struct SLATECORE_API FButtonStyle : public FSlateWidgetStyle */ void PostSerialize(const FArchive& Ar); #endif + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + Normal.UnlinkColors(); + Hovered.UnlinkColors(); + Pressed.UnlinkColors(); + Disabled.UnlinkColors(); + } }; template<> @@ -416,6 +460,17 @@ struct SLATECORE_API FComboButtonStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) FMargin MenuBorderPadding; FComboButtonStyle& SetMenuBorderPadding( const FMargin& InMenuBorderPadding ){ MenuBorderPadding = InMenuBorderPadding; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + ButtonStyle.UnlinkColors(); + DownArrowImage.UnlinkColors(); + MenuBorderBrush.UnlinkColors(); + } }; @@ -470,6 +525,16 @@ struct SLATECORE_API FComboBoxStyle : public FSlateWidgetStyle */ void PostSerialize(const FArchive& Ar); #endif + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + ComboButtonStyle.UnlinkColors(); + } + }; #if WITH_EDITORONLY_DATA @@ -562,6 +627,18 @@ struct SLATECORE_API FEditableTextStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) FSlateBrush CaretImage; FEditableTextStyle& SetCaretImage( const FSlateBrush& InCaretImage ){ CaretImage = InCaretImage; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + ColorAndOpacity.Unlink(); + BackgroundImageSelected.UnlinkColors(); + BackgroundImageComposing.UnlinkColors(); + CaretImage.UnlinkColors(); + } }; @@ -628,6 +705,23 @@ struct SLATECORE_API FScrollBarStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) FSlateBrush DraggedThumbImage; FScrollBarStyle& SetDraggedThumbImage( const FSlateBrush& InDraggedThumbImage ){ DraggedThumbImage = InDraggedThumbImage; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + HorizontalBackgroundImage.UnlinkColors(); + VerticalBackgroundImage.UnlinkColors(); + VerticalTopSlotImage.UnlinkColors(); + HorizontalTopSlotImage.UnlinkColors(); + VerticalBottomSlotImage.UnlinkColors(); + HorizontalBottomSlotImage.UnlinkColors(); + NormalThumbImage.UnlinkColors(); + HoveredThumbImage.UnlinkColors(); + DraggedThumbImage.UnlinkColors(); + } }; @@ -710,6 +804,22 @@ struct SLATECORE_API FEditableTextBoxStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) FScrollBarStyle ScrollBarStyle; FEditableTextBoxStyle& SetScrollBarStyle( const FScrollBarStyle& InScrollBarStyle ){ ScrollBarStyle = InScrollBarStyle; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + BackgroundImageNormal.UnlinkColors(); + BackgroundImageHovered.UnlinkColors(); + BackgroundImageFocused.UnlinkColors(); + BackgroundImageReadOnly.UnlinkColors(); + ForegroundColor.Unlink(); + BackgroundColor.Unlink(); + ReadOnlyForegroundColor.Unlink(); + ScrollBarStyle.UnlinkColors(); + } }; @@ -777,6 +887,17 @@ struct SLATECORE_API FProgressBarStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) FSlateBrush MarqueeImage; FProgressBarStyle& SetMarqueeImage( const FSlateBrush& InMarqueeImage ){ MarqueeImage = InMarqueeImage; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + BackgroundImage.UnlinkColors(); + FillImage.UnlinkColors(); + MarqueeImage.UnlinkColors(); + } }; @@ -813,6 +934,16 @@ struct SLATECORE_API FExpandableAreaStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, Category = Appearance) float RolloutAnimationSeconds; FExpandableAreaStyle& SetRolloutAnimationSeconds(float InLengthSeconds) { RolloutAnimationSeconds = InLengthSeconds; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + CollapsedImage.UnlinkColors(); + ExpandedImage.UnlinkColors(); + } }; @@ -929,6 +1060,20 @@ struct SLATECORE_API FSliderStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) float BarThickness; FSliderStyle& SetBarThickness(float InBarThickness) { BarThickness = InBarThickness; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + NormalBarImage.UnlinkColors(); + HoveredBarImage.UnlinkColors(); + DisabledBarImage.UnlinkColors(); + NormalThumbImage.UnlinkColors(); + HoveredThumbImage.UnlinkColors(); + DisabledThumbImage.UnlinkColors(); + } }; @@ -1065,6 +1210,20 @@ struct SLATECORE_API FSpinBoxStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Appearance) FMargin TextPadding; FSpinBoxStyle& SetTextPadding( const FMargin& InTextPadding ){ TextPadding = InTextPadding; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + BackgroundBrush.UnlinkColors(); + HoveredBackgroundBrush.UnlinkColors(); + ActiveFillBrush.UnlinkColors(); + InactiveFillBrush.UnlinkColors(); + ArrowsImage.UnlinkColors(); + ForegroundColor.Unlink(); + } }; @@ -1197,6 +1356,28 @@ struct SLATECORE_API FTableRowStyle : public FSlateWidgetStyle UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Appearance) FSlateBrush InactiveHighlightedBrush; FTableRowStyle& SetInactiveHighlightedBrush( const FSlateBrush& InInactiveHighlightedBrush){ InactiveHighlightedBrush = InInactiveHighlightedBrush; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + SelectorFocusedBrush.UnlinkColors(); + ActiveHoveredBrush.UnlinkColors(); + ActiveBrush.UnlinkColors(); + InactiveHoveredBrush.UnlinkColors(); + InactiveBrush.UnlinkColors(); + EvenRowBackgroundHoveredBrush.UnlinkColors(); + EvenRowBackgroundBrush.UnlinkColors(); + OddRowBackgroundHoveredBrush.UnlinkColors(); + OddRowBackgroundBrush.UnlinkColors(); + TextColor.Unlink(); + SelectedTextColor.Unlink(); + DropIndicator_Above.UnlinkColors(); + DropIndicator_Onto.UnlinkColors(); + DropIndicator_Below.UnlinkColors(); + } }; @@ -1440,6 +1621,18 @@ struct SLATECORE_API FScrollBoxStyle : public FSlateWidgetStyle RightShadowBrush = InRightShadowBrush; return *this; } + + /** + * Unlinks all colors in this style. + * @see FSlateColor::Unlink + */ + void UnlinkColors() + { + TopShadowBrush.UnlinkColors(); + BottomShadowBrush.UnlinkColors(); + LeftShadowBrush.UnlinkColors(); + RightShadowBrush.UnlinkColors(); + } }; diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateAccessibleMessageHandler.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateAccessibleMessageHandler.h new file mode 100644 index 000000000000..862fd1bd8952 --- /dev/null +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateAccessibleMessageHandler.h @@ -0,0 +1,73 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "GenericPlatform/GenericAccessibleInterfaces.h" +#include "Misc/Variant.h" + +class SWidget; + +/** + * Message handling system for Slate Accessibility API, dealing with both receiving events and pushing them back to the platform layer. + */ +class SLATECORE_API FSlateAccessibleMessageHandler : public FGenericAccessibleMessageHandler +{ +public: + // FGenericAccessibleMessageHandler + virtual void OnActivate() override; + virtual void OnDeactivate() override; + virtual TSharedPtr GetAccessibleWindow(const TSharedRef& InWindow) const override; + virtual AccessibleWidgetId GetAccessibleWindowId(const TSharedRef& InWindow) const override; + virtual TSharedPtr GetAccessibleWidgetFromId(AccessibleWidgetId Id) const override; + virtual bool ApplicationIsAccessible() const override { return true; } + //~ + + /** + * Callback for SWidget destructor. Removes the corresponding accessible widget for the Slate widget. + * + * @param Widget The widget that is being deleted. + */ + void OnWidgetRemoved(SWidget* Widget); + /** + * Callback for a Slate widget's parent changing. + * + * @param Widget The widget whose parent changed. + */ + void OnWidgetParentChanged(TSharedRef Widget); + /** + * Callback for a Slate widget's children changing. Although somewhat rare, widgets are not required + * to return a child even if the child was parented to it (for instance, SWidgetSwitcher). + * + * Note: This should not be called if the children are already calling OnWidgetParentChanged(). + * + * @param Widget The widget whose children changed. + */ + void OnWidgetChildrenChanged(TSharedRef Widget); + /** + * Callback for a Slate widget's main accessible behavior changing. + * + * @param Widget The widget whose behavior changed. + */ + void OnWidgetAccessibleBehaviorChanged(TSharedRef Widget); + /** + * Callback for a Slate widget indicating that some event happened to it. + * + * @param Widget The widget raising the event. + * @param Event The type of event being raised. + */ + void OnWidgetEventRaised(TSharedRef Widget, EAccessibleEvent Event); + /** + * Callback for a Slate widget indicating that a property change occurred. This may also be used by certain events + * such as Notification which don't have an 'OldValue'. Only NewValue should be set for those types of events. + * + * @param Widget The widget raising the event. + * @param Event The type of event being raised. + * @param OldValue The value of the property being changed before the change occurred. + * @param NewValue The value of the property being changed after the change occurred, or any miscellaneous data for the event. + */ + void OnWidgetEventRaised(TSharedRef Widget, EAccessibleEvent Event, FVariant OldValue, FVariant NewValue); +}; + +#endif diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateAccessibleWidgetCache.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateAccessibleWidgetCache.h new file mode 100644 index 000000000000..8ce5e896c1e8 --- /dev/null +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateAccessibleWidgetCache.h @@ -0,0 +1,120 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "CoreMinimal.h" +#include "Widgets/Accessibility/SlateCoreAccessibleWidgets.h" +#include "Widgets/SWidget.h" + +/** + * Singleton used to retrieve accessible widgets for a given Slate widget. Accessible widgets will persist + * so longer as the OS is sending accessibility events, after which the platform layer should call ClearAll(). + */ +class SLATECORE_API FSlateAccessibleWidgetCache +{ +public: + static FSlateAccessibleWidgetCache& Get() + { + static FSlateAccessibleWidgetCache AccessibleWidgetCache; + return AccessibleWidgetCache; + } + + /** Empty the cache and release all cached accessible widgets. This should generally only be done with accessibility is turned off. */ + void ClearAll() + { + AccessibleWidgetMap.Empty(); + } + + /** + * Callback for when an SWidget is deleted. FSlateAccessibleMessageHandler should be the only thing to call this. + * The widget is removed from the cache and removed from the accessible tree. + * + * @param Widget the Slate widget that is being deleted + */ + TSharedPtr RemoveWidget(SWidget* Widget) + { + TSharedPtr RemovedWidget; + TSharedPtr* AccessibleWidget = AccessibleWidgetMap.Find(Widget); + if (AccessibleWidget) + { + // Make sure to disconnect this widget from the tree + (*AccessibleWidget)->UpdateParent(nullptr); + AccessibleWidgetMap.RemoveAndCopyValue(Widget, RemovedWidget); + if (RemovedWidget.IsValid()) + { + AccessibleIdMap.Remove(RemovedWidget->GetId()); + } + } + return RemovedWidget; + } + + /** + * Get a cached accessible widget for a given Slate widget, or create a new one is bCreateIfMissing is true. + * This may return nullptr in the case where the Slate widget is not a valid accessible widget. A non-valid + * accessible widget includes the case where the widget has accessible flags set but its parent cannot have + * accessible children. + * + * @param Widget The Slate widget to get the accessible widget for + * @param bCreateIfMissing If the widget is not already in the cache, whether or not it should be created + * @return The accessible widget for the given Slate widget, or nullptr if the widget is not accessible. + */ + TSharedPtr GetAccessibleWidget(const TSharedPtr Widget, bool bCreateIfMissing = true) + { + if (!Widget.IsValid() || !Widget->IsAccessible()) + { + return nullptr; + } + + TSharedPtr* AccessibleWidget = AccessibleWidgetMap.Find(Widget.Get()); + if (AccessibleWidget) + { + return *AccessibleWidget; + } + else if (bCreateIfMissing) + { + TSharedPtr NewWidget = AccessibleWidgetMap.Add(Widget.Get(), Widget->CreateAccessibleWidget()); + AccessibleIdMap.Add(NewWidget->GetId(), NewWidget); + // todo: fix this for nested windows + if (NewWidget->AsWindow()) + { + NewWidget->UpdateAllChildren(true); + } + return NewWidget; + } + return nullptr; + } + + /** + * Get a cached accessible widget for an identifier that matches the accessible widget's GetId() return value. + * This will NOT create the widget if it does not exist yet, since Ids can only be generated by the widget itself. + * Note that InvalidAccessibleWidgetId will always return null. + * + * @param Id The Id of a widget that corresponds to that widget's GetId() function + * @return The accessible widget for the given Id, or nullptr if there is no widget with that Id. + */ + TSharedPtr GetAccessibleWidgetFromId(AccessibleWidgetId Id) + { + TSharedPtr* AccessibleWidget = AccessibleIdMap.Find(Id); + if (AccessibleWidget) + { + return *AccessibleWidget; + } + return nullptr; + } + +private: + /** + * Hold on to a shared pointer for each accessible widget until the SWidget is deleted. A raw pointer is used + * because widgets are removed from the cache via the SWidget destructor. + */ + TMap> AccessibleWidgetMap; + /** Map each accessible widget to its ID to provide a second way to do lookups. */ + TMap> AccessibleIdMap; + + FSlateAccessibleWidgetCache() {} + ~FSlateAccessibleWidgetCache() {} +}; + +#endif diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateCoreAccessibleWidgets.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateCoreAccessibleWidgets.h new file mode 100644 index 000000000000..003f2ec9fde2 --- /dev/null +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateCoreAccessibleWidgets.h @@ -0,0 +1,151 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_ACCESSIBILITY + +#include "CoreMinimal.h" +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +class SWidget; +class SWindow; + +/** + * The base implementation of IAccessibleWidget for all Slate widgets. Any new accessible widgets should + * inherit directly from FSlateAccessibleWidget, and optionally inherit from other IAccessible interfaces to + * provide more functionality. + */ +class SLATECORE_API FSlateAccessibleWidget : public IAccessibleWidget +{ +public: + FSlateAccessibleWidget(TWeakPtr InWidget, EAccessibleWidgetType InWidgetType = EAccessibleWidgetType::Unknown); + virtual ~FSlateAccessibleWidget(); + + // IAccessibleWidget + virtual AccessibleWidgetId GetId() const override final; + virtual bool IsValid() const override final; + virtual TSharedPtr GetTopLevelWindow() const override final; + virtual FBox2D GetBounds() const override final; + virtual TSharedPtr GetParent() override final; + virtual TSharedPtr GetNextSibling() override final; + virtual TSharedPtr GetPreviousSibling() override final; + virtual TSharedPtr GetChildAt(int32 Index) override final; + virtual int32 GetNumberOfChildren() override final; + virtual FString GetClassName() const override final; + virtual bool IsEnabled() const override final; + virtual bool IsHidden() const override final; + virtual bool SupportsFocus() const override final; + virtual bool HasFocus() const override final; + virtual void SetFocus() override final; + + virtual EAccessibleWidgetType GetWidgetType() const override { return WidgetType; } + virtual FString GetWidgetName() const override; + virtual FString GetHelpText() const override; + // ~ + + /** Tell this widget to recompute its children the next time they are requested. */ + void MarkChildrenDirty(); + /** + * Detach this widget from its current parent and attach it to a new parent. This will emit notifications back to the accessible message handler. + * + * @param NewParent The widget to assign as the new parent widget. + */ + void UpdateParent(TSharedPtr NewParent); + /** + * If MarkChildrenDirty() has been called, recalculate the list of all accessible widgets below this one. + * Because SWidget->GetChildren() has no guarantees about what it returns and how it returns it, we can + * never truly 100% guarantee that the accessible tree will be in sync with the Slate tree. + * + * We make a reasonable assumption that widgets are smart about implementing this function to return the + * same widgets every time. However, we can't assume anything about when a child gets added or removed + * with respect to the ordering of the children. Because of this, we have to recompute their indices + * any time we suspect the hierarchy may have changed. + * + * @param bUpdateRecursively If true, calls UpdateAllChildren() on any children found. + */ + void UpdateAllChildren(bool bUpdateRecursively = false); + + /** + * Search the Slate hierarchy recursively and generate a list of all accessible widgets whose parent is this widget. + * + * @param AccessibleWidget The root widget to find children for. + * @return All Slate widgets whose accessible parent is the passed-in widget. + */ + static TArray> GetAccessibleChildren(TSharedRef AccessibleWidget); + +protected: + /** + * Recursively find the accessible widget under the specified X,Y coordinates. + * + * @param X The X coordinate to search in absolute screen space. + * @param Y The Y coordinate to search in absolute screen space. + * @return The deepest accessible widget found. + */ + TSharedPtr GetChildAtUsingGeometry(int32 X, int32 Y); + + /** The underlying Slate widget backing this accessible widget. */ + TWeakPtr Widget; + /** What type of widget the platform's accessibility API should treat this as. */ + EAccessibleWidgetType WidgetType; + /** The accessible parent to this widget. This should usually be valid on widgets in the hierarchy, except for SWindows. */ + TWeakPtr Parent; + /** All accessible widgets whose parent is this widget. This is not necessarily correct unless UpdateAllChildren() is called first. */ + TArray> Children; + /** The index of this widget in its parent's list of children. */ + int32 SiblingIndex; + /** An application-unique identifier for GetId(). */ + AccessibleWidgetId Id; + /** Whether the contents of the Children array has changed and UpdateAllChildren() needs to be called. */ + bool bChildrenDirty; + +private: + /** + * Find the Slate window containing this widget's underlying Slate widget. + * + * @return The parent SWindow for the Slate widget referenced by this accessible widget. + */ + TSharedPtr GetTopLevelSlateWindow() const; +}; + +// SWindow +class FSlateAccessibleWindow + : public FSlateAccessibleWidget + , public IAccessibleWindow +{ +public: + FSlateAccessibleWindow(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::Window) {} + virtual ~FSlateAccessibleWindow() {} + + // IAccessibleWidget + virtual IAccessibleWindow* AsWindow() override { return this; } + virtual FString GetWidgetName() const override; + // ~ + + // IAccessibleWindow + virtual TSharedPtr GetNativeWindow() const override; + virtual TSharedPtr GetChildAtPosition(int32 X, int32 Y) override; + virtual TSharedPtr GetFocusedWidget() const override; + virtual void Close() override; + virtual bool SupportsDisplayState(EWindowDisplayState State) const override; + virtual EWindowDisplayState GetDisplayState() const override; + virtual void SetDisplayState(EWindowDisplayState State) override; + virtual bool IsModal() const override; + // ~ +}; +// ~ + +// SImage +class SLATECORE_API FSlateAccessibleImage + : public FSlateAccessibleWidget +{ +public: + FSlateAccessibleImage(TWeakPtr InWidget) : FSlateAccessibleWidget(InWidget, EAccessibleWidgetType::Image) {} + virtual ~FSlateAccessibleImage() {} + + // IAccessibleWidget + virtual FString GetHelpText() const override; + // ~ +}; +// ~ + +#endif diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateWidgetAccessibleTypes.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateWidgetAccessibleTypes.h new file mode 100644 index 000000000000..17974c7c1367 --- /dev/null +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/Accessibility/SlateWidgetAccessibleTypes.h @@ -0,0 +1,29 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "GenericPlatform/GenericAccessibleInterfaces.h" + +struct FAccessibleWidgetData +{ + FAccessibleWidgetData(EAccessibleBehavior InBehavior = EAccessibleBehavior::NotAccessible, EAccessibleBehavior InSummaryBehavior = EAccessibleBehavior::Auto, bool bInCanChildrenBeAccessible = true) + : bCanChildrenBeAccessible(bInCanChildrenBeAccessible) + , AccessibleBehavior(InBehavior) + , AccessibleSummaryBehavior(InSummaryBehavior) + { + } + FAccessibleWidgetData(const TAttribute& InAccessibleText, const TAttribute& InAccessibleSummaryText = TAttribute(), bool bInCanChildrenBeAccessible = true) + : bCanChildrenBeAccessible(bInCanChildrenBeAccessible) + , AccessibleBehavior(InAccessibleSummaryText.IsSet() ? EAccessibleBehavior::Custom : EAccessibleBehavior::NotAccessible) + , AccessibleSummaryBehavior(InAccessibleSummaryText.IsSet() ? EAccessibleBehavior::Custom : EAccessibleBehavior::Auto) + , AccessibleText(InAccessibleText) + , AccessibleSummaryText(InAccessibleSummaryText) + { + } + + uint8 bCanChildrenBeAccessible : 1; + EAccessibleBehavior AccessibleBehavior; + EAccessibleBehavior AccessibleSummaryBehavior; + TAttribute AccessibleText; + TAttribute AccessibleSummaryText; +}; diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/DeclarativeSyntaxSupport.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/DeclarativeSyntaxSupport.h index c4f94f0dffc0..7ae85428531e 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Widgets/DeclarativeSyntaxSupport.h +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/DeclarativeSyntaxSupport.h @@ -11,6 +11,7 @@ #include "GenericPlatform/ICursor.h" #include "Types/ISlateMetaData.h" #include "Widgets/SNullWidget.h" +#include "Widgets/Accessibility/SlateWidgetAccessibleTypes.h" class IToolTip; class SUserWidget; @@ -788,6 +789,8 @@ struct TSlateBaseNamedArgs , _ForceVolatile( false ) , _Clipping( EWidgetClipping::Inherit ) , _FlowDirectionPreference( EFlowDirectionPreference::Inherit ) + , _AccessibleParams() + , _AccessibleText() { } @@ -832,6 +835,8 @@ struct TSlateBaseNamedArgs SLATE_ARGUMENT( bool, ForceVolatile ) SLATE_ARGUMENT( EWidgetClipping, Clipping ) SLATE_ARGUMENT( EFlowDirectionPreference, FlowDirectionPreference) + SLATE_ARGUMENT(FAccessibleWidgetData, AccessibleParams) + SLATE_ATTRIBUTE(FText, AccessibleText) TArray> MetaData; }; @@ -1105,6 +1110,7 @@ struct TDecl InArgs._ForceVolatile, InArgs._Clipping, InArgs._FlowDirectionPreference, + InArgs._AccessibleText.IsSet() ? FAccessibleWidgetData(InArgs._AccessibleText) : InArgs._AccessibleParams, InArgs.MetaData ); _RequiredArgs.CallConstruct(_Widget, InArgs); diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/Images/SImage.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/Images/SImage.h index 33eee138bec0..46e1bd47a92e 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Widgets/Images/SImage.h +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/Images/SImage.h @@ -24,7 +24,7 @@ public: : _Image( FCoreStyle::Get().GetDefaultBrush() ) , _ColorAndOpacity( FLinearColor::White ) , _FlipForRightToLeftFlowDirection( false ) - {} + { } /** Image resource */ SLATE_ATTRIBUTE( const FSlateBrush*, Image ) @@ -68,6 +68,10 @@ public: // SWidget overrides virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; +#endif + protected: // Begin SWidget overrides. virtual FVector2D ComputeDesiredSize(float) const override; diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/SWidget.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/SWidget.h index b263b550bd0b..67df4238be97 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Widgets/SWidget.h +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/SWidget.h @@ -24,6 +24,7 @@ #include "Types/WidgetActiveTimerDelegate.h" #include "Textures/SlateShaderResource.h" #include "SlateGlobals.h" +#include "Widgets/Accessibility/SlateWidgetAccessibleTypes.h" class FActiveTimerHandle; class FArrangedChildren; @@ -38,6 +39,12 @@ class IToolTip; class SWidget; struct FSlateBrush; +enum class EAccessibleType : uint8 +{ + Main, + Summary +}; + /** Widget update flags for fast path. Work in progress, do not modify */ enum class EWidgetUpdateFlags : uint8 { @@ -284,6 +291,7 @@ public: const bool InForceVolatile, const EWidgetClipping InClipping, const EFlowDirectionPreference InFlowPreference, + const FAccessibleWidgetData& InAccessibleData, const TArray>& InMetaData); void SWidgetConstruct(const TAttribute& InToolTipText, @@ -298,6 +306,7 @@ public: const bool InForceVolatile, const EWidgetClipping InClipping, const EFlowDirectionPreference InFlowPreference, + const FAccessibleWidgetData& InAccessibleData, const TArray>& InMetaData); // @@ -681,6 +690,9 @@ public: */ virtual EWindowZone::Type GetWindowZoneOverride() const; +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget(); +#endif public: // @@ -962,6 +974,70 @@ public: } } +#if WITH_ACCESSIBILITY + /** + * Get the text that should be reported to the user when attempting to access this widget. + * + * @param AccessibleType Whether the widget is being accessed directly or through a summary query. + * @return The text that should be conveyed to the user describing this widget. + */ + FText GetAccessibleText(EAccessibleType AccessibleType = EAccessibleType::Main) const; + + /** + * Traverse all child widgets and concat their results of GetAccessibleText(Summary). + * + * @return The combined text of all child widget's summary text. + */ + FText GetAccessibleSummary() const; + + /** + * Whether this widget is considered accessible or not. A widget is accessible if its behavior + * is set to something other than NotAccessible, and all of its parent widgets support accessible children. + * + * @return true if an accessible widget should be created for this widget. + */ + bool IsAccessible() const; + + /** + * Get the behavior describing how the accessible text of this widget should be retrieved. + * + * @param AccessibleType Whether the widget is being accessed directly or through a summary query. + * @return The accessible behavior of the widget. + */ + EAccessibleBehavior GetAccessibleBehavior(EAccessibleType AccessibleType = EAccessibleType::Main) const; + + /** + * Checks whether this widget allows its children to be accessible or not. + * + * @return true if children can be accessible. + */ + bool CanChildrenBeAccessible() const; + + /** + * Set a new accessible behavior, and if the behavior is custom, new accessible text to go along with it. + * + * @param InBehavior The new behavior for the widget. If the new behavior is custom, InText should also be set. + * @param InText, If the new behavior is custom, this will be the custom text assigned to the widget. + * @param AccessibleType Whether the widget is being accessed directly or through a summary query. + */ + void SetAccessibleBehavior(EAccessibleBehavior InBehavior, const TAttribute& InText = TAttribute(), EAccessibleType AccessibleType = EAccessibleType::Main); + + /** + * Sets whether children are allowed to be accessible or not. + * Warning: Calling this function after accessibility is enabled will cause the accessibility tree to become unsynced. + * + * @param InCanChildrenBeAccessible Whether children should be accessible or not. + */ + void SetCanChildrenBeAccessible(bool InCanChildrenBeAccessible); + + /** + * Assign AccessibleText with a default value that can be used when AccessibleBehavior is set to Auto or Custom. + * + * @param AccessibleType Whether the widget is being accessed directly or through a summary query. + */ + virtual void SetDefaultAccessibleText(EAccessibleType AccessibleType = EAccessibleType::Main); +#endif + /** * When performing a caching pass, volatile widgets are not cached as part of everything * else, instead they and their children are drawn as normal standard widgets and excluded @@ -1578,6 +1654,11 @@ protected: /** Render transform pivot of this widget (in normalized local space) */ TAttribute< FVector2D > RenderTransformPivot; +#if WITH_ACCESSIBILITY + /** All variables surrounding how this widget is exposed to the platform's accessibility API. */ + FAccessibleWidgetData AccessibleData; +#endif + protected: /** Debugging information on the type of widget we're creating for the Widget Reflector. */ FName TypeOfWidget; diff --git a/Engine/Source/Runtime/SlateCore/Public/Widgets/SWindow.h b/Engine/Source/Runtime/SlateCore/Public/Widgets/SWindow.h index 54bc40ff4e47..e7479adcc2a8 100644 --- a/Engine/Source/Runtime/SlateCore/Public/Widgets/SWindow.h +++ b/Engine/Source/Runtime/SlateCore/Public/Widgets/SWindow.h @@ -155,8 +155,10 @@ public: , _SaneWindowPlacement( true ) , _LayoutBorder(FMargin(5, 5, 5, 5)) , _UserResizeBorder(FMargin(5, 5, 5, 5)) + , _bManualManageDPI( false ) { + _AccessibleParams = FAccessibleWidgetData(EAccessibleBehavior::Auto); } /** Type of this window */ @@ -784,6 +786,11 @@ public: bool IsDrawingEnabled() const { return bIsDrawingEnabled; } virtual bool Advanced_IsWindow() const { return true; } + +#if WITH_ACCESSIBILITY + virtual TSharedPtr CreateAccessibleWidget() override; + virtual void SetDefaultAccessibleText(EAccessibleType AccessibleType = EAccessibleType::Main) override; +#endif private: virtual FReply OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent ) override; virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; diff --git a/Engine/Source/Runtime/SlateRHIRenderer/Private/SlateRHIRenderer.cpp b/Engine/Source/Runtime/SlateRHIRenderer/Private/SlateRHIRenderer.cpp index 3d8c3b290711..2ddf430bea87 100644 --- a/Engine/Source/Runtime/SlateRHIRenderer/Private/SlateRHIRenderer.cpp +++ b/Engine/Source/Runtime/SlateRHIRenderer/Private/SlateRHIRenderer.cpp @@ -281,8 +281,8 @@ void FSlateRHIRenderer::CreateViewport(const TSharedRef Window) // Clamp the window size to a reasonable default anything below 8 is a d3d warning and 8 is used anyway. // @todo Slate: This is a hack to work around menus being summoned with 0,0 for window size until they are ticked. - int32 Width = FMath::Max(MIN_VIEWPORT_SIZE, FMath::CeilToInt(WindowSize.X)); - int32 Height = FMath::Max(MIN_VIEWPORT_SIZE, FMath::CeilToInt(WindowSize.Y)); + int32 Width = FMath::Max(MIN_VIEWPORT_SIZE, FMath::TruncToInt(WindowSize.X)); + int32 Height = FMath::Max(MIN_VIEWPORT_SIZE, FMath::TruncToInt(WindowSize.Y)); // Sanity check dimensions if (!ensureMsgf(Width <= MAX_VIEWPORT_SIZE && Height <= MAX_VIEWPORT_SIZE, TEXT("Invalid window with Width=%u and Height=%u"), Width, Height)) diff --git a/Engine/Source/Runtime/UMG/Private/Animation/WidgetAnimationPlayCallbackProxy.cpp b/Engine/Source/Runtime/UMG/Private/Animation/WidgetAnimationPlayCallbackProxy.cpp new file mode 100644 index 000000000000..adc882fd09df --- /dev/null +++ b/Engine/Source/Runtime/UMG/Private/Animation/WidgetAnimationPlayCallbackProxy.cpp @@ -0,0 +1,85 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "Animation/WidgetAnimationPlayCallbackProxy.h" +#include "Animation/UMGSequencePlayer.h" +#include "Engine/World.h" +#include "TimerManager.h" + +#define LOCTEXT_NAMESPACE "UMG" + +UWidgetAnimationPlayCallbackProxy* UWidgetAnimationPlayCallbackProxy::CreatePlayAnimationProxyObject(class UUMGSequencePlayer*& Result, class UUserWidget* Widget, UWidgetAnimation* InAnimation, float StartAtTime, int32 NumLoopsToPlay, EUMGSequencePlayMode::Type PlayMode, float PlaybackSpeed) +{ + UWidgetAnimationPlayCallbackProxy* Proxy = NewObject(); + Proxy->SetFlags(RF_StrongRefOnFrame); + Result = Proxy->ExecutePlayAnimation(Widget, InAnimation, StartAtTime, NumLoopsToPlay, PlayMode, PlaybackSpeed); + return Proxy; +} + +UWidgetAnimationPlayCallbackProxy* UWidgetAnimationPlayCallbackProxy::CreatePlayAnimationTimeRangeProxyObject(class UUMGSequencePlayer*& Result, class UUserWidget* Widget, UWidgetAnimation* InAnimation, float StartAtTime, float EndAtTime, int32 NumLoopsToPlay, EUMGSequencePlayMode::Type PlayMode, float PlaybackSpeed) +{ + UWidgetAnimationPlayCallbackProxy* Proxy = NewObject(); + Proxy->SetFlags(RF_StrongRefOnFrame); + Result = Proxy->ExecutePlayAnimationTimeRange(Widget, InAnimation, StartAtTime, EndAtTime, NumLoopsToPlay, PlayMode, PlaybackSpeed); + return Proxy; +} + +UWidgetAnimationPlayCallbackProxy::UWidgetAnimationPlayCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +class UUMGSequencePlayer* UWidgetAnimationPlayCallbackProxy::ExecutePlayAnimation(class UUserWidget* Widget, UWidgetAnimation* InAnimation, float StartAtTime, int32 NumLoopsToPlay, EUMGSequencePlayMode::Type PlayMode, float PlaybackSpeed) +{ + if (!Widget) + { + return nullptr; + } + + WorldPtr = Widget->GetWorld(); + + UUMGSequencePlayer* Player = Widget->PlayAnimation(InAnimation, StartAtTime, NumLoopsToPlay, PlayMode, PlaybackSpeed); + if (Player) + { + Player->OnSequenceFinishedPlaying().AddUObject(this, &UWidgetAnimationPlayCallbackProxy::OnFinished); + } + + return Player; +} + +class UUMGSequencePlayer* UWidgetAnimationPlayCallbackProxy::ExecutePlayAnimationTimeRange(class UUserWidget* Widget, UWidgetAnimation* InAnimation, float StartAtTime, float EndAtTime, int32 NumLoopsToPlay, EUMGSequencePlayMode::Type PlayMode, float PlaybackSpeed) +{ + if (!Widget) + { + return nullptr; + } + + WorldPtr = Widget->GetWorld(); + + UUMGSequencePlayer* Player = Widget->PlayAnimationTimeRange(InAnimation, StartAtTime, EndAtTime, NumLoopsToPlay, PlayMode, PlaybackSpeed); + if (Player) + { + OnFinishedHandle = Player->OnSequenceFinishedPlaying().AddUObject(this, &UWidgetAnimationPlayCallbackProxy::OnFinished); + } + + return Player; +} + +void UWidgetAnimationPlayCallbackProxy::OnFinished(class UUMGSequencePlayer& Player) +{ + Player.OnSequenceFinishedPlaying().Remove(OnFinishedHandle); + + // We delay the Finish trigger to next frame. + if (UWorld* World = WorldPtr.Get()) + { + // Use a dummy timer handle as we don't need to store it for later but we don't need to look for something to clear + FTimerHandle TimerHandle; + World->GetTimerManager().SetTimer(TimerHandle, this, &UWidgetAnimationPlayCallbackProxy::OnFinished_Delayed, 0.001f, false); + } +} + +void UWidgetAnimationPlayCallbackProxy::OnFinished_Delayed() +{ + Finished.Broadcast(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Runtime/UMG/Private/Binding/TextBinding.cpp b/Engine/Source/Runtime/UMG/Private/Binding/TextBinding.cpp index 06f16de3a2ef..44755402465f 100644 --- a/Engine/Source/Runtime/UMG/Private/Binding/TextBinding.cpp +++ b/Engine/Source/Runtime/UMG/Private/Binding/TextBinding.cpp @@ -4,10 +4,6 @@ #define LOCTEXT_NAMESPACE "UMG" -UTextBinding::UTextBinding() -{ -} - bool UTextBinding::IsSupportedDestination(UProperty* Property) const { return IsConcreteTypeCompatibleWithReflectedType(Property); @@ -17,7 +13,9 @@ bool UTextBinding::IsSupportedSource(UProperty* Property) const { return IsConcreteTypeCompatibleWithReflectedType(Property) || - IsConcreteTypeCompatibleWithReflectedType(Property); + IsConcreteTypeCompatibleWithReflectedType(Property) || + IsConcreteTypeCompatibleWithReflectedType(Property) || + IsConcreteTypeCompatibleWithReflectedType(Property); } void UTextBinding::Bind(UProperty* Property, FScriptDelegate* Delegate) @@ -40,25 +38,43 @@ FText UTextBinding::GetTextValue() const if ( UObject* Source = SourceObject.Get() ) { - if ( !bNeedsConversion.Get(false) ) + if (NeedsConversion.Get(EConversion::None) == EConversion::None) { FText TextValue = FText::GetEmpty(); if ( SourcePath.GetValue(Source, TextValue) ) { - bNeedsConversion = false; + NeedsConversion = EConversion::None; return TextValue; } } - if ( bNeedsConversion.Get(true) ) + if (NeedsConversion.Get(EConversion::String) == EConversion::String) { FString StringValue; - if ( SourcePath.GetValue(Source, StringValue) ) + if (SourcePath.GetValue(Source, StringValue)) { - bNeedsConversion = true; + NeedsConversion = EConversion::String; return FText::FromString(StringValue); } } + if (NeedsConversion.Get(EConversion::Integer) == EConversion::Integer) + { + int32 IntegerValue; + if (SourcePath.GetValue(Source, IntegerValue)) + { + NeedsConversion = EConversion::Integer; + return FText::AsNumber(IntegerValue); + } + } + if (NeedsConversion.Get(EConversion::Float) == EConversion::Float) + { + float FloatValue; + if (SourcePath.GetValue(Source, FloatValue)) + { + NeedsConversion = EConversion::Float; + return FText::AsNumber(FloatValue); + } + } } return FText::GetEmpty(); @@ -68,27 +84,45 @@ FString UTextBinding::GetStringValue() const { //SCOPE_CYCLE_COUNTER(STAT_UMGBinding); - if ( UObject* Source = SourceObject.Get() ) + if(UObject* Source = SourceObject.Get()) { - if ( !bNeedsConversion.Get(false) ) + if (NeedsConversion.Get(EConversion::None) == EConversion::None) { FString StringValue; if ( SourcePath.GetValue(Source, StringValue) ) { - bNeedsConversion = false; + NeedsConversion = EConversion::None; return StringValue; } } - if ( bNeedsConversion.Get(true) ) + if (NeedsConversion.Get(EConversion::Words) == EConversion::Words) { FText TextValue = FText::GetEmpty(); - if ( SourcePath.GetValue(Source, TextValue) ) + if (SourcePath.GetValue(Source, TextValue)) { - bNeedsConversion = true; + NeedsConversion = EConversion::Words; return TextValue.ToString(); } } + if (NeedsConversion.Get(EConversion::Integer) == EConversion::Integer) + { + int32 IntegerValue; + if (SourcePath.GetValue(Source, IntegerValue)) + { + NeedsConversion = EConversion::Integer; + return FString::FromInt(IntegerValue); + } + } + if (NeedsConversion.Get(EConversion::Float) == EConversion::Float) + { + float FloatValue; + if (SourcePath.GetValue(Source, FloatValue)) + { + NeedsConversion = EConversion::Float; + return FString::SanitizeFloat(FloatValue); + } + } } return FString(); diff --git a/Engine/Source/Runtime/UMG/Private/Components/Button.cpp b/Engine/Source/Runtime/UMG/Private/Components/Button.cpp index e0abb8d2e180..8e4765ad5e82 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/Button.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/Button.cpp @@ -11,12 +11,21 @@ ///////////////////////////////////////////////////// // UButton +static FButtonStyle* DefaultButtonStyle = nullptr; + UButton::UButton(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FButtonStyle StaticButtonStyle = FCoreStyle::Get().GetWidgetStyle< FButtonStyle >("Button"); - WidgetStyle = StaticButtonStyle; + if (DefaultButtonStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultButtonStyle = new FButtonStyle(FCoreStyle::Get().GetWidgetStyle("Button")); + + // Unlink UMG default colors from the editor settings colors. + DefaultButtonStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultButtonStyle; ColorAndOpacity = FLinearColor::White; BackgroundColor = FLinearColor::White; @@ -25,6 +34,9 @@ UButton::UButton(const FObjectInitializer& ObjectInitializer) TouchMethod = EButtonTouchMethod::DownAndUp; IsFocusable = true; + + AccessibleBehavior = ESlateAccessibleBehavior::Summary; + bCanChildrenBeAccessible = false; } void UButton::ReleaseSlateResources(bool bReleaseChildren) @@ -211,6 +223,13 @@ void UButton::SlateHandleUnhovered() OnUnhovered.Broadcast(); } +#if WITH_ACCESSIBILITY +TSharedPtr UButton::GetAccessibleWidget() const +{ + return MyButton; +} +#endif + #if WITH_EDITOR const FText UButton::GetPaletteCategory() diff --git a/Engine/Source/Runtime/UMG/Private/Components/CheckBox.cpp b/Engine/Source/Runtime/UMG/Private/Components/CheckBox.cpp index f2c5d6d28604..61f7bf9bce73 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/CheckBox.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/CheckBox.cpp @@ -11,12 +11,21 @@ ///////////////////////////////////////////////////// // UCheckBox +static FCheckBoxStyle* DefaultCheckboxStyle = nullptr; + UCheckBox::UCheckBox(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FCheckBoxStyle StaticCheckboxStyle = FCoreStyle::Get().GetWidgetStyle< FCheckBoxStyle >("Checkbox"); - WidgetStyle = StaticCheckboxStyle; + if (DefaultCheckboxStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultCheckboxStyle = new FCheckBoxStyle(FCoreStyle::Get().GetWidgetStyle("Checkbox")); + + // Unlink UMG default colors from the editor settings colors. + DefaultCheckboxStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultCheckboxStyle; CheckedState = ECheckBoxState::Unchecked; @@ -26,6 +35,8 @@ UCheckBox::UCheckBox(const FObjectInitializer& ObjectInitializer) BorderBackgroundColor_DEPRECATED = FLinearColor::White; IsFocusable = true; + AccessibleBehavior = ESlateAccessibleBehavior::Summary; + bCanChildrenBeAccessible = false; } void UCheckBox::ReleaseSlateResources(bool bReleaseChildren) @@ -220,6 +231,13 @@ void UCheckBox::PostLoad() } } +#if WITH_ACCESSIBILITY +TSharedPtr UCheckBox::GetAccessibleWidget() const +{ + return MyCheckbox; +} +#endif + #if WITH_EDITOR const FText UCheckBox::GetPaletteCategory() diff --git a/Engine/Source/Runtime/UMG/Private/Components/CircularThrobber.cpp b/Engine/Source/Runtime/UMG/Private/Components/CircularThrobber.cpp index 6763f2bd8ceb..48c9aa7634e4 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/CircularThrobber.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/CircularThrobber.cpp @@ -11,13 +11,22 @@ ///////////////////////////////////////////////////// // UCircularThrobber +static FSlateBrush* DefaultCircularThrobberBrushStyle = nullptr; + UCircularThrobber::UCircularThrobber(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bEnableRadius(true) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FSlateBrush StaticBrushStyle = *FCoreStyle::Get().GetBrush("Throbber.CircleChunk"); - Image = StaticBrushStyle; + if (DefaultCircularThrobberBrushStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultCircularThrobberBrushStyle = new FSlateBrush(*FCoreStyle::Get().GetBrush("Throbber.CircleChunk")); + + // Unlink UMG default colors from the editor settings colors. + DefaultCircularThrobberBrushStyle->UnlinkColors(); + } + + Image = *DefaultCircularThrobberBrushStyle; NumberOfPieces = 6; Period = 0.75f; diff --git a/Engine/Source/Runtime/UMG/Private/Components/ComboBoxString.cpp b/Engine/Source/Runtime/UMG/Private/Components/ComboBoxString.cpp index 3c18b8b42ecc..68090333e8ac 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/ComboBoxString.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/ComboBoxString.cpp @@ -10,15 +10,32 @@ ///////////////////////////////////////////////////// // UComboBoxString +static FComboBoxStyle* DefaultComboBoxStyle = nullptr; +static FTableRowStyle* DefaultComboBoxRowStyle = nullptr; + UComboBoxString::UComboBoxString(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FComboBoxStyle StaticComboboxStyle = FCoreStyle::Get().GetWidgetStyle< FComboBoxStyle >("ComboBox"); - static const FTableRowStyle StaticRowStyle = FCoreStyle::Get().GetWidgetStyle< FTableRowStyle >("TableView.Row"); + if (DefaultComboBoxStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultComboBoxStyle = new FComboBoxStyle(FCoreStyle::Get().GetWidgetStyle("ComboBox")); - WidgetStyle = StaticComboboxStyle; - ItemStyle = StaticRowStyle; + // Unlink UMG default colors from the editor settings colors. + DefaultComboBoxStyle->UnlinkColors(); + } + + if (DefaultComboBoxRowStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultComboBoxRowStyle = new FTableRowStyle(FCoreStyle::Get().GetWidgetStyle("TableView.Row")); + + // Unlink UMG default colors from the editor settings colors. + DefaultComboBoxRowStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultComboBoxStyle; + ItemStyle = *DefaultComboBoxRowStyle; ItemStyle.SelectorFocusedBrush.TintColor = ItemStyle.SelectorFocusedBrush.TintColor.GetSpecifiedColor(); ItemStyle.ActiveHoveredBrush.TintColor = ItemStyle.ActiveHoveredBrush.TintColor.GetSpecifiedColor(); ItemStyle.ActiveBrush.TintColor = ItemStyle.ActiveBrush.TintColor.GetSpecifiedColor(); @@ -122,7 +139,7 @@ TSharedRef UComboBoxString::RebuildWidget() if ( InitialIndex != -1 ) { // Generate the widget for the initially selected widget if needed - ComboBoxContent->SetContent(HandleGenerateWidget(CurrentOptionPtr)); + UpdateOrGenerateWidget(CurrentOptionPtr); } return MyComboBox.ToSharedRef(); @@ -226,16 +243,23 @@ void UComboBoxString::SetSelectedIndex(const int32 Index) if (Options.IsValidIndex(Index)) { CurrentOptionPtr = Options[Index]; - SelectedOption = *CurrentOptionPtr; - - if ( ComboBoxContent.IsValid() ) + // Don't select item if its already selected + if (SelectedOption != *CurrentOptionPtr) { - MyComboBox->SetSelectedItem(CurrentOptionPtr); - ComboBoxContent->SetContent(HandleGenerateWidget(CurrentOptionPtr)); + SelectedOption = *CurrentOptionPtr; + + if (ComboBoxContent.IsValid()) + { + MyComboBox->SetSelectedItem(CurrentOptionPtr); + UpdateOrGenerateWidget(CurrentOptionPtr); + } + else + { + HandleSelectionChanged(CurrentOptionPtr, ESelectInfo::Direct); + } } } } - FString UComboBoxString::GetSelectedOption() const { if (CurrentOptionPtr.IsValid()) @@ -266,6 +290,22 @@ int32 UComboBoxString::GetOptionCount() const return Options.Num(); } +void UComboBoxString::UpdateOrGenerateWidget(TSharedPtr Item) +{ + // If no custom widget was supplied and the default STextBlock already exists, + // just update its text instead of rebuilding the widget. + if (DefaultComboBoxContent.IsValid() && (IsDesignTime() || OnGenerateWidgetEvent.IsBound())) + { + const FString StringItem = Item.IsValid() ? *Item : FString(); + DefaultComboBoxContent.Pin()->SetText(FText::FromString(StringItem)); + } + else + { + DefaultComboBoxContent.Reset(); + ComboBoxContent->SetContent(HandleGenerateWidget(Item)); + } +} + TSharedRef UComboBoxString::HandleGenerateWidget(TSharedPtr Item) const { FString StringItem = Item.IsValid() ? *Item : FString(); @@ -294,7 +334,7 @@ void UComboBoxString::HandleSelectionChanged(TSharedPtr Item, ESelectIn // When the selection changes we always generate another widget to represent the content area of the combobox. if ( ComboBoxContent.IsValid() ) { - ComboBoxContent->SetContent(HandleGenerateWidget(CurrentOptionPtr)); + UpdateOrGenerateWidget(CurrentOptionPtr); } if ( !IsDesignTime() ) diff --git a/Engine/Source/Runtime/UMG/Private/Components/EditableText.cpp b/Engine/Source/Runtime/UMG/Private/Components/EditableText.cpp index 06d9b0236033..e382249eb808 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/EditableText.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/EditableText.cpp @@ -12,12 +12,21 @@ ///////////////////////////////////////////////////// // UEditableText +static FEditableTextStyle* DefaultEditableTextStyle = nullptr; + UEditableText::UEditableText(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FEditableTextStyle StaticEditableTextStyle = FCoreStyle::Get().GetWidgetStyle< FEditableTextStyle >("NormalEditableText"); - WidgetStyle = StaticEditableTextStyle; + if (DefaultEditableTextStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultEditableTextStyle = new FEditableTextStyle(FCoreStyle::Get().GetWidgetStyle("NormalEditableText")); + + // Unlink UMG default colors from the editor settings colors. + DefaultEditableTextStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultEditableTextStyle; ColorAndOpacity_DEPRECATED = FLinearColor::Black; @@ -38,6 +47,9 @@ UEditableText::UEditableText(const FObjectInitializer& ObjectInitializer) AllowContextMenu = true; VirtualKeyboardDismissAction = EVirtualKeyboardDismissAction::TextChangeOnDismiss; Clipping = EWidgetClipping::ClipToBounds; + + AccessibleBehavior = ESlateAccessibleBehavior::Auto; + bCanChildrenBeAccessible = false; } void UEditableText::ReleaseSlateResources(bool bReleaseChildren) @@ -200,6 +212,13 @@ void UEditableText::PostLoad() } } +#if WITH_ACCESSIBILITY +TSharedPtr UEditableText::GetAccessibleWidget() const +{ + return MyEditableText; +} +#endif + #if WITH_EDITOR const FText UEditableText::GetPaletteCategory() diff --git a/Engine/Source/Runtime/UMG/Private/Components/EditableTextBox.cpp b/Engine/Source/Runtime/UMG/Private/Components/EditableTextBox.cpp index d39b6032d906..1d2c7501814d 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/EditableTextBox.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/EditableTextBox.cpp @@ -11,6 +11,8 @@ ///////////////////////////////////////////////////// // UEditableTextBox +static FEditableTextBoxStyle* DefaultEditableTextBoxStyle = nullptr; + UEditableTextBox::UEditableTextBox(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -36,9 +38,19 @@ UEditableTextBox::UEditableTextBox(const FObjectInitializer& ObjectInitializer) AllowContextMenu = true; VirtualKeyboardDismissAction = EVirtualKeyboardDismissAction::TextChangeOnDismiss; - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FEditableTextBoxStyle StaticNormalEditableTextBox = FCoreStyle::Get().GetWidgetStyle< FEditableTextBoxStyle >("NormalEditableTextBox"); - WidgetStyle = StaticNormalEditableTextBox; + if (DefaultEditableTextBoxStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultEditableTextBoxStyle = new FEditableTextBoxStyle(FCoreStyle::Get().GetWidgetStyle("NormalEditableTextBox")); + + // Unlink UMG default colors from the editor settings colors. + DefaultEditableTextBoxStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultEditableTextBoxStyle; + + AccessibleBehavior = ESlateAccessibleBehavior::Auto; + bCanChildrenBeAccessible = false; } void UEditableTextBox::ReleaseSlateResources(bool bReleaseChildren) @@ -227,11 +239,18 @@ void UEditableTextBox::PostLoad() } } +#if WITH_ACCESSIBILITY +TSharedPtr UEditableTextBox::GetAccessibleWidget() const +{ + return MyEditableTextBlock; +} +#endif + #if WITH_EDITOR const FText UEditableTextBox::GetPaletteCategory() { - return LOCTEXT("Common", "Common"); + return LOCTEXT("Input", "Input"); } #endif diff --git a/Engine/Source/Runtime/UMG/Private/Components/ExpandableArea.cpp b/Engine/Source/Runtime/UMG/Private/Components/ExpandableArea.cpp index c27a7fd465a5..26918be0e090 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/ExpandableArea.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/ExpandableArea.cpp @@ -14,18 +14,35 @@ static const FName HeaderName(TEXT("Header")); static const FName BodyName(TEXT("Body")); +static FExpandableAreaStyle* DefaultExpandableAreaStyle = nullptr; +static FSlateBrush* DefaultExpandableAreaBorderBrush = nullptr; + UExpandableArea::UExpandableArea(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bIsExpanded(false) { bIsVariable = true; - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FExpandableAreaStyle StaticExpandableArea = FCoreStyle::Get().GetWidgetStyle("ExpandableArea"); - static const FSlateBrush StaticBorderBrush = *FCoreStyle::Get().GetBrush("ExpandableArea.Border"); + if (DefaultExpandableAreaStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultExpandableAreaStyle = new FExpandableAreaStyle(FCoreStyle::Get().GetWidgetStyle("ExpandableArea")); - Style = StaticExpandableArea; - BorderBrush = StaticBorderBrush; + // Unlink UMG default colors from the editor settings colors. + DefaultExpandableAreaStyle->UnlinkColors(); + } + + if (DefaultExpandableAreaBorderBrush == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultExpandableAreaBorderBrush = new FSlateBrush(*FCoreStyle::Get().GetBrush("ExpandableArea.Border")); + + // Unlink UMG default colors from the editor settings colors. + DefaultExpandableAreaBorderBrush->UnlinkColors(); + } + + Style = *DefaultExpandableAreaStyle; + BorderBrush = *DefaultExpandableAreaBorderBrush; BorderColor = FLinearColor::White; AreaPadding = FMargin(1); diff --git a/Engine/Source/Runtime/UMG/Private/Components/GridPanel.cpp b/Engine/Source/Runtime/UMG/Private/Components/GridPanel.cpp index cfc5cf54db8f..aa435ab002b4 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/GridPanel.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/GridPanel.cpp @@ -67,9 +67,17 @@ TSharedRef UGridPanel::RebuildWidget() return MyGridPanel.ToSharedRef(); } -UGridSlot* UGridPanel::AddChildToGrid(UWidget* Content) +UGridSlot* UGridPanel::AddChildToGrid(UWidget* Content, int32 InRow, int32 InColumn) { - return Cast(Super::AddChild(Content)); + UGridSlot* GridSlot = Cast(Super::AddChild(Content)); + + if (GridSlot != nullptr) + { + GridSlot->SetRow(InRow); + GridSlot->SetColumn(InColumn); + } + + return GridSlot; } void UGridPanel::SynchronizeProperties() diff --git a/Engine/Source/Runtime/UMG/Private/Components/Image.cpp b/Engine/Source/Runtime/UMG/Private/Components/Image.cpp index 4bfebeda3474..d46d2cc14e32 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/Image.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/Image.cpp @@ -395,6 +395,13 @@ FReply UImage::HandleMouseButtonDown(const FGeometry& Geometry, const FPointerEv return FReply::Unhandled(); } +#if WITH_ACCESSIBILITY +TSharedPtr UImage::GetAccessibleWidget() const +{ + return MyImage; +} +#endif + #if WITH_EDITOR const FText UImage::GetPaletteCategory() diff --git a/Engine/Source/Runtime/UMG/Private/Components/InputKeySelector.cpp b/Engine/Source/Runtime/UMG/Private/Components/InputKeySelector.cpp index f7a6f8c3951e..0405b3d6f7bf 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/InputKeySelector.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/InputKeySelector.cpp @@ -10,14 +10,32 @@ #define LOCTEXT_NAMESPACE "UMG" +static FButtonStyle* DefaultInputKeySelectorButtonStyle = nullptr; +static FTextBlockStyle* DefaultInputKeySelectorTextStyle = nullptr; + UInputKeySelector::UInputKeySelector( const FObjectInitializer& ObjectInitializer ) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FButtonStyle StaticButtonStyle = FCoreStyle::Get().GetWidgetStyle("Button"); - static const FTextBlockStyle StaticNormalTextStyle = FCoreStyle::Get().GetWidgetStyle< FTextBlockStyle >("NormalText"); - WidgetStyle = StaticButtonStyle; - TextStyle = StaticNormalTextStyle; + if (DefaultInputKeySelectorButtonStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultInputKeySelectorButtonStyle = new FButtonStyle(FCoreStyle::Get().GetWidgetStyle("Button")); + + // Unlink UMG default colors from the editor settings colors. + DefaultInputKeySelectorButtonStyle->UnlinkColors(); + } + + if (DefaultInputKeySelectorTextStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultInputKeySelectorTextStyle = new FTextBlockStyle(FCoreStyle::Get().GetWidgetStyle("NormalText")); + + // Unlink UMG default colors from the editor settings colors. + DefaultInputKeySelectorTextStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultInputKeySelectorButtonStyle; + TextStyle = *DefaultInputKeySelectorTextStyle; KeySelectionText = NSLOCTEXT("InputKeySelector", "DefaultKeySelectionText", "..."); NoKeySpecifiedText = NSLOCTEXT("InputKeySelector", "DefaultEmptyText", "Empty"); diff --git a/Engine/Source/Runtime/UMG/Private/Components/ListViewBase.cpp b/Engine/Source/Runtime/UMG/Private/Components/ListViewBase.cpp index 183b9d317ba9..e290f6c8a039 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/ListViewBase.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/ListViewBase.cpp @@ -28,6 +28,10 @@ void UListViewBase::ValidateCompiledDefaults(IWidgetCompilerLog& CompileLog) con { CompileLog.Error(FText::Format(LOCTEXT("Error_ListViewBase_MissingEntryClass", "{0} has no EntryWidgetClass specified - required for any UListViewBase to function."), FText::FromString(GetName()))); } + else if (!EntryWidgetClass->ImplementsInterface(UUserListEntry::StaticClass())) + { + CompileLog.Error(FText::Format(LOCTEXT("Error_ListViewBase_EntryClassNotImplementingInterface", "'{0}' has EntryWidgetClass property set to'{1}' and that Class doesn't implement User List Entry Interface - required for any UListViewBase to function."), FText::FromString(GetName()), FText::FromString(EntryWidgetClass->GetName()))); + } } #endif diff --git a/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableText.cpp b/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableText.cpp index e96f7eb68804..5d274632a9de 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableText.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableText.cpp @@ -11,12 +11,21 @@ ///////////////////////////////////////////////////// // UMultiLineEditableText +static FTextBlockStyle* DefaultMultiLineEditableTextStyle = nullptr; + UMultiLineEditableText::UMultiLineEditableText(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FTextBlockStyle StaticNormalText = FCoreStyle::Get().GetWidgetStyle("NormalText"); - WidgetStyle = StaticNormalText; + if (DefaultMultiLineEditableTextStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultMultiLineEditableTextStyle = new FTextBlockStyle(FCoreStyle::Get().GetWidgetStyle("NormalText")); + + // Unlink UMG default colors from the editor settings colors. + DefaultMultiLineEditableTextStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultMultiLineEditableTextStyle; bIsReadOnly = false; SelectAllTextWhenFocused = false; @@ -144,6 +153,16 @@ void UMultiLineEditableText::SetIsReadOnly(bool bReadOnly) } } +void UMultiLineEditableText::SetWidgetStyle(const FTextBlockStyle& InWidgetStyle) +{ + WidgetStyle = InWidgetStyle; + + if (MyMultiLineEditableText.IsValid()) + { + MyMultiLineEditableText->SetTextStyle(&WidgetStyle); + } +} + void UMultiLineEditableText::HandleOnTextChanged(const FText& InText) { OnTextChanged.Broadcast(InText); diff --git a/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableTextBox.cpp b/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableTextBox.cpp index 6bdc8ccd23ac..0cf954e531b6 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableTextBox.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/MultiLineEditableTextBox.cpp @@ -11,6 +11,9 @@ ///////////////////////////////////////////////////// // UMultiLineEditableTextBox +static FEditableTextBoxStyle* DefaultMultiLineEditableTextBoxStyle = nullptr; +static FTextBlockStyle* DefaultMultiLineEditableTextBoxTextStyle = nullptr; + UMultiLineEditableTextBox::UMultiLineEditableTextBox(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -18,11 +21,26 @@ UMultiLineEditableTextBox::UMultiLineEditableTextBox(const FObjectInitializer& O BackgroundColor_DEPRECATED = FLinearColor::White; ReadOnlyForegroundColor_DEPRECATED = FLinearColor::Black; - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FEditableTextBoxStyle StaticNormalEditableTextBox = FCoreStyle::Get().GetWidgetStyle("NormalEditableTextBox"); - static const FTextBlockStyle StaticNormalText = FCoreStyle::Get().GetWidgetStyle("NormalText"); - WidgetStyle = StaticNormalEditableTextBox; - TextStyle = StaticNormalText; + if (DefaultMultiLineEditableTextBoxStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultMultiLineEditableTextBoxStyle = new FEditableTextBoxStyle(FCoreStyle::Get().GetWidgetStyle("NormalEditableTextBox")); + + // Unlink UMG default colors from the editor settings colors. + DefaultMultiLineEditableTextBoxStyle->UnlinkColors(); + } + + if (DefaultMultiLineEditableTextBoxTextStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultMultiLineEditableTextBoxTextStyle = new FTextBlockStyle(FCoreStyle::Get().GetWidgetStyle("NormalText")); + + // Unlink UMG default colors from the editor settings colors. + DefaultMultiLineEditableTextBoxTextStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultMultiLineEditableTextBoxStyle; + TextStyle = *DefaultMultiLineEditableTextBoxTextStyle; bIsReadOnly = false; AllowContextMenu = true; @@ -150,6 +168,16 @@ void UMultiLineEditableTextBox::SetIsReadOnly(bool bReadOnly) } } +void UMultiLineEditableTextBox::SetTextStyle(const FTextBlockStyle& InTextStyle) +{ + TextStyle = InTextStyle; + + if (MyEditableTextBlock.IsValid()) + { + MyEditableTextBlock->SetTextStyle(&TextStyle); + } +} + void UMultiLineEditableTextBox::HandleOnTextChanged(const FText& InText) { OnTextChanged.Broadcast(InText); diff --git a/Engine/Source/Runtime/UMG/Private/Components/ProgressBar.cpp b/Engine/Source/Runtime/UMG/Private/Components/ProgressBar.cpp index 2880deaddd37..dab3c8a7f1ee 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/ProgressBar.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/ProgressBar.cpp @@ -8,13 +8,21 @@ ///////////////////////////////////////////////////// // UProgressBar +static FProgressBarStyle* DefaultProgressBarStyle = nullptr; + UProgressBar::UProgressBar(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FProgressBarStyle StaticProgressBar = FCoreStyle::Get().GetWidgetStyle("ProgressBar"); + if (DefaultProgressBarStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultProgressBarStyle = new FProgressBarStyle(FCoreStyle::Get().GetWidgetStyle("ProgressBar")); - WidgetStyle = StaticProgressBar; + // Unlink UMG default colors from the editor settings colors. + DefaultProgressBarStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultProgressBarStyle; WidgetStyle.FillImage.TintColor = FLinearColor::White; BarFillType = EProgressBarFillType::LeftToRight; diff --git a/Engine/Source/Runtime/UMG/Private/Components/RichTextBlock.cpp b/Engine/Source/Runtime/UMG/Private/Components/RichTextBlock.cpp index 5ac865800005..9261964bb0bb 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/RichTextBlock.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/RichTextBlock.cpp @@ -108,6 +108,16 @@ void URichTextBlock::UpdateStyleData() } } +FText URichTextBlock::GetText() const +{ + if (MyRichTextBlock.IsValid()) + { + return MyRichTextBlock->GetText(); + } + + return Text; +} + void URichTextBlock::SetText(const FText& InText) { Text = InText; diff --git a/Engine/Source/Runtime/UMG/Private/Components/ScrollBar.cpp b/Engine/Source/Runtime/UMG/Private/Components/ScrollBar.cpp index 679a4240ed51..3996509dbcbc 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/ScrollBar.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/ScrollBar.cpp @@ -1,12 +1,15 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "Components/ScrollBar.h" +#include "UObject/EditorObjectVersion.h" #define LOCTEXT_NAMESPACE "UMG" ///////////////////////////////////////////////////// // UScrollBar +static FScrollBarStyle* DefaultScrollBarStyle = nullptr; + UScrollBar::UScrollBar(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -15,11 +18,19 @@ UScrollBar::UScrollBar(const FObjectInitializer& ObjectInitializer) bAlwaysShowScrollbar = true; bAlwaysShowScrollbarTrack = true; Orientation = Orient_Vertical; - Thickness = FVector2D(12.0f, 12.0f); + Thickness = FVector2D(16.0f, 16.0f); + Padding = FMargin(2.0f); - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FScrollBarStyle StaticScrollbar = FCoreStyle::Get().GetWidgetStyle("Scrollbar"); - WidgetStyle = StaticScrollbar; + if (DefaultScrollBarStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultScrollBarStyle = new FScrollBarStyle(FCoreStyle::Get().GetWidgetStyle("Scrollbar")); + + // Unlink UMG default colors from the editor settings colors. + DefaultScrollBarStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultScrollBarStyle; } void UScrollBar::ReleaseSlateResources(bool bReleaseChildren) @@ -36,7 +47,8 @@ TSharedRef UScrollBar::RebuildWidget() .AlwaysShowScrollbar(bAlwaysShowScrollbar) .AlwaysShowScrollbarTrack(bAlwaysShowScrollbarTrack) .Orientation(Orientation) - .Thickness(Thickness); + .Thickness(Thickness) + .Padding(Padding); //SLATE_EVENT(FOnUserScrolled, OnUserScrolled) @@ -58,6 +70,28 @@ void UScrollBar::SetState(float InOffsetFraction, float InThumbSizeFraction) } } +#if WITH_EDITORONLY_DATA + +void UScrollBar::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FEditorObjectVersion::GUID); + + const bool bDeprecateThickness = Ar.IsLoading() && Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::ScrollBarThicknessChange; + if (bDeprecateThickness) + { + // Set Thickness property to previous default value. + Thickness.Set(12.0f, 12.0f); + } + + Super::Serialize(Ar); + + if (bDeprecateThickness) + { + // Implicit padding of 2 was removed, so Thickness value must be incremented by 4. + Thickness += FVector2D(4.0f, 4.0f); + } +} + void UScrollBar::PostLoad() { Super::PostLoad(); @@ -77,6 +111,8 @@ void UScrollBar::PostLoad() } } +#endif // if WITH_EDITORONLY_DATA + #if WITH_EDITOR const FText UScrollBar::GetPaletteCategory() diff --git a/Engine/Source/Runtime/UMG/Private/Components/ScrollBox.cpp b/Engine/Source/Runtime/UMG/Private/Components/ScrollBox.cpp index 0108b6a942c7..aa78a12ef320 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/ScrollBox.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/ScrollBox.cpp @@ -3,18 +3,23 @@ #include "Components/ScrollBox.h" #include "Containers/Ticker.h" #include "Components/ScrollBoxSlot.h" +#include "UObject/EditorObjectVersion.h" #define LOCTEXT_NAMESPACE "UMG" ///////////////////////////////////////////////////// // UScrollBox +static FScrollBoxStyle* DefaultScrollBoxStyle = nullptr; +static FScrollBarStyle* DefaultScrollBoxBarStyle = nullptr; + UScrollBox::UScrollBox(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , Orientation(Orient_Vertical) , ScrollBarVisibility(ESlateVisibility::Visible) , ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible) - , ScrollbarThickness(5.0f, 5.0f) + , ScrollbarThickness(9.0f, 9.0f) + , ScrollbarPadding(2.0f) , AlwaysShowScrollbar(false) , AlwaysShowScrollbarTrack(false) , AllowOverscroll(true) @@ -26,15 +31,27 @@ UScrollBox::UScrollBox(const FObjectInitializer& ObjectInitializer) Visibility = ESlateVisibility::Visible; Clipping = EWidgetClipping::ClipToBounds; + if (DefaultScrollBoxStyle == nullptr) { - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FScrollBoxStyle StaticScrollBoxStyle = FCoreStyle::Get().GetWidgetStyle("ScrollBox"); - static const FScrollBarStyle StaticScrollBarStyle = FCoreStyle::Get().GetWidgetStyle("ScrollBar"); + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultScrollBoxStyle = new FScrollBoxStyle(FCoreStyle::Get().GetWidgetStyle("ScrollBox")); - WidgetStyle = StaticScrollBoxStyle; - WidgetBarStyle = StaticScrollBarStyle; + // Unlink UMG default colors from the editor settings colors. + DefaultScrollBoxStyle->UnlinkColors(); } + if (DefaultScrollBoxBarStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultScrollBoxBarStyle = new FScrollBarStyle(FCoreStyle::Get().GetWidgetStyle("ScrollBar")); + + // Unlink UMG default colors from the editor settings colors. + DefaultScrollBoxBarStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultScrollBoxStyle; + WidgetBarStyle = *DefaultScrollBoxBarStyle; + bAllowRightClickDragScrolling = true; } @@ -103,6 +120,7 @@ void UScrollBox::SynchronizeProperties() MyScrollBox->SetOrientation(Orientation); MyScrollBox->SetScrollBarVisibility(UWidget::ConvertSerializedVisibilityToRuntime(ScrollBarVisibility)); MyScrollBox->SetScrollBarThickness(ScrollbarThickness); + MyScrollBox->SetScrollBarPadding(ScrollbarPadding); MyScrollBox->SetScrollBarAlwaysVisible(AlwaysShowScrollbar); MyScrollBox->SetScrollBarTrackAlwaysVisible(AlwaysShowScrollbarTrack); MyScrollBox->SetAllowOverscroll(AllowOverscroll ? EAllowOverscroll::Yes : EAllowOverscroll::No); @@ -120,6 +138,16 @@ float UScrollBox::GetScrollOffset() const return 0; } +float UScrollBox::GetScrollOffsetOfEnd() const +{ + if (MyScrollBox.IsValid()) + { + return MyScrollBox->GetScrollOffsetOfEnd(); + } + + return 0; +} + float UScrollBox::GetViewOffsetFraction() const { if ( MyScrollBox.IsValid() ) @@ -172,6 +200,28 @@ void UScrollBox::ScrollWidgetIntoView(UWidget* WidgetToFind, bool AnimateScroll, } } +#if WITH_EDITORONLY_DATA + +void UScrollBox::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FEditorObjectVersion::GUID); + + const bool bDeprecateThickness = Ar.IsLoading() && Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::ScrollBarThicknessChange; + if (bDeprecateThickness) + { + // Set ScrollbarThickness property to previous default value. + ScrollbarThickness.Set(5.0f, 5.0f); + } + + Super::Serialize(Ar); + + if (bDeprecateThickness) + { + // Implicit padding of 2 was removed, so ScrollbarThickness value must be incremented by 4. + ScrollbarThickness += FVector2D(4.0f, 4.0f); + } +} + void UScrollBox::PostLoad() { Super::PostLoad(); @@ -202,6 +252,8 @@ void UScrollBox::PostLoad() } } +#endif // if WITH_EDITORONLY_DATA + void UScrollBox::SetConsumeMouseWheel(EConsumeMouseWheel NewConsumeMouseWheel) { ConsumeMouseWheel = NewConsumeMouseWheel; @@ -249,6 +301,16 @@ void UScrollBox::SetScrollbarThickness(const FVector2D& NewScrollbarThickness) } } +void UScrollBox::SetScrollbarPadding(const FMargin& NewScrollbarPadding) +{ + ScrollbarPadding = NewScrollbarPadding; + + if (MyScrollBox.IsValid()) + { + MyScrollBox->SetScrollBarPadding(ScrollbarPadding); + } +} + void UScrollBox::SetAlwaysShowScrollbar(bool NewAlwaysShowScrollbar) { AlwaysShowScrollbar = NewAlwaysShowScrollbar; diff --git a/Engine/Source/Runtime/UMG/Private/Components/SizeBox.cpp b/Engine/Source/Runtime/UMG/Private/Components/SizeBox.cpp index 221caeee44a9..d70272af1dae 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/SizeBox.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/SizeBox.cpp @@ -16,6 +16,7 @@ USizeBox::USizeBox(const FObjectInitializer& ObjectInitializer) { bIsVariable = false; Visibility = ESlateVisibility::SelfHitTestInvisible; + MinAspectRatio = 1; MaxAspectRatio = 1; } @@ -29,8 +30,8 @@ void USizeBox::ReleaseSlateResources(bool bReleaseChildren) TSharedRef USizeBox::RebuildWidget() { MySizeBox = SNew(SBox); - - if ( GetChildrenCount() > 0 ) + + if (GetChildrenCount() > 0) { Cast(GetContentSlot())->BuildSlot(MySizeBox.ToSharedRef()); } @@ -42,7 +43,7 @@ void USizeBox::SetWidthOverride(float InWidthOverride) { bOverride_WidthOverride = true; WidthOverride = InWidthOverride; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetWidthOverride(InWidthOverride); } @@ -51,7 +52,7 @@ void USizeBox::SetWidthOverride(float InWidthOverride) void USizeBox::ClearWidthOverride() { bOverride_WidthOverride = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetWidthOverride(FOptionalSize()); } @@ -61,7 +62,7 @@ void USizeBox::SetHeightOverride(float InHeightOverride) { bOverride_HeightOverride = true; HeightOverride = InHeightOverride; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetHeightOverride(InHeightOverride); } @@ -70,7 +71,7 @@ void USizeBox::SetHeightOverride(float InHeightOverride) void USizeBox::ClearHeightOverride() { bOverride_HeightOverride = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetHeightOverride(FOptionalSize()); } @@ -80,7 +81,7 @@ void USizeBox::SetMinDesiredWidth(float InMinDesiredWidth) { bOverride_MinDesiredWidth = true; MinDesiredWidth = InMinDesiredWidth; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMinDesiredWidth(InMinDesiredWidth); } @@ -89,7 +90,7 @@ void USizeBox::SetMinDesiredWidth(float InMinDesiredWidth) void USizeBox::ClearMinDesiredWidth() { bOverride_MinDesiredWidth = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMinDesiredWidth(FOptionalSize()); } @@ -99,7 +100,7 @@ void USizeBox::SetMinDesiredHeight(float InMinDesiredHeight) { bOverride_MinDesiredHeight = true; MinDesiredHeight = InMinDesiredHeight; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMinDesiredHeight(InMinDesiredHeight); } @@ -108,7 +109,7 @@ void USizeBox::SetMinDesiredHeight(float InMinDesiredHeight) void USizeBox::ClearMinDesiredHeight() { bOverride_MinDesiredHeight = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMinDesiredHeight(FOptionalSize()); } @@ -118,7 +119,7 @@ void USizeBox::SetMaxDesiredWidth(float InMaxDesiredWidth) { bOverride_MaxDesiredWidth = true; MaxDesiredWidth = InMaxDesiredWidth; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMaxDesiredWidth(InMaxDesiredWidth); } @@ -127,7 +128,7 @@ void USizeBox::SetMaxDesiredWidth(float InMaxDesiredWidth) void USizeBox::ClearMaxDesiredWidth() { bOverride_MaxDesiredWidth = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMaxDesiredWidth(FOptionalSize()); } @@ -137,7 +138,7 @@ void USizeBox::SetMaxDesiredHeight(float InMaxDesiredHeight) { bOverride_MaxDesiredHeight = true; MaxDesiredHeight = InMaxDesiredHeight; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMaxDesiredHeight(InMaxDesiredHeight); } @@ -146,17 +147,36 @@ void USizeBox::SetMaxDesiredHeight(float InMaxDesiredHeight) void USizeBox::ClearMaxDesiredHeight() { bOverride_MaxDesiredHeight = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMaxDesiredHeight(FOptionalSize()); } } +void USizeBox::SetMinAspectRatio(float InMinAspectRatio) +{ + bOverride_MinAspectRatio = true; + MinAspectRatio = InMinAspectRatio; + if (MySizeBox.IsValid()) + { + MySizeBox->SetMinAspectRatio(InMinAspectRatio); + } +} + +void USizeBox::ClearMinAspectRatio() +{ + bOverride_MinAspectRatio = false; + if (MySizeBox.IsValid()) + { + MySizeBox->SetMinAspectRatio(FOptionalSize()); + } +} + void USizeBox::SetMaxAspectRatio(float InMaxAspectRatio) { bOverride_MaxAspectRatio = true; MaxAspectRatio = InMaxAspectRatio; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMaxAspectRatio(InMaxAspectRatio); } @@ -165,7 +185,7 @@ void USizeBox::SetMaxAspectRatio(float InMaxAspectRatio) void USizeBox::ClearMaxAspectRatio() { bOverride_MaxAspectRatio = false; - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetMaxAspectRatio(FOptionalSize()); } @@ -175,7 +195,7 @@ void USizeBox::SynchronizeProperties() { Super::SynchronizeProperties(); - if ( bOverride_WidthOverride ) + if (bOverride_WidthOverride) { SetWidthOverride(WidthOverride); } @@ -184,7 +204,7 @@ void USizeBox::SynchronizeProperties() ClearWidthOverride(); } - if ( bOverride_HeightOverride ) + if (bOverride_HeightOverride) { SetHeightOverride(HeightOverride); } @@ -193,7 +213,7 @@ void USizeBox::SynchronizeProperties() ClearHeightOverride(); } - if ( bOverride_MinDesiredWidth ) + if (bOverride_MinDesiredWidth) { SetMinDesiredWidth(MinDesiredWidth); } @@ -202,7 +222,7 @@ void USizeBox::SynchronizeProperties() ClearMinDesiredWidth(); } - if ( bOverride_MinDesiredHeight ) + if (bOverride_MinDesiredHeight) { SetMinDesiredHeight(MinDesiredHeight); } @@ -211,7 +231,7 @@ void USizeBox::SynchronizeProperties() ClearMinDesiredHeight(); } - if ( bOverride_MaxDesiredWidth ) + if (bOverride_MaxDesiredWidth) { SetMaxDesiredWidth(MaxDesiredWidth); } @@ -220,7 +240,7 @@ void USizeBox::SynchronizeProperties() ClearMaxDesiredWidth(); } - if ( bOverride_MaxDesiredHeight ) + if (bOverride_MaxDesiredHeight) { SetMaxDesiredHeight(MaxDesiredHeight); } @@ -229,7 +249,16 @@ void USizeBox::SynchronizeProperties() ClearMaxDesiredHeight(); } - if ( bOverride_MaxAspectRatio ) + if (bOverride_MinAspectRatio) + { + SetMinAspectRatio(MinAspectRatio); + } + else + { + ClearMinAspectRatio(); + } + + if (bOverride_MaxAspectRatio) { SetMaxAspectRatio(MaxAspectRatio); } @@ -247,7 +276,7 @@ UClass* USizeBox::GetSlotClass() const void USizeBox::OnSlotAdded(UPanelSlot* InSlot) { // Add the child to the live slot if it already exists - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { CastChecked(InSlot)->BuildSlot(MySizeBox.ToSharedRef()); } @@ -256,7 +285,7 @@ void USizeBox::OnSlotAdded(UPanelSlot* InSlot) void USizeBox::OnSlotRemoved(UPanelSlot* InSlot) { // Remove the widget from the live slot if it exists. - if ( MySizeBox.IsValid() ) + if (MySizeBox.IsValid()) { MySizeBox->SetContent(SNullWidget::NullWidget); } @@ -273,4 +302,4 @@ const FText USizeBox::GetPaletteCategory() ///////////////////////////////////////////////////// -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Runtime/UMG/Private/Components/Slider.cpp b/Engine/Source/Runtime/UMG/Private/Components/Slider.cpp index 84fc87004901..5620385817b7 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/Slider.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/Slider.cpp @@ -9,9 +9,13 @@ ///////////////////////////////////////////////////// // USlider +static FSliderStyle* DefaultSliderStyle = nullptr; + USlider::USlider(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + MinValue = 0.0f; + MaxValue = 1.0f; Orientation = EOrientation::Orient_Horizontal; SliderBarColor = FLinearColor::White; SliderHandleColor = FLinearColor::White; @@ -20,9 +24,19 @@ USlider::USlider(const FObjectInitializer& ObjectInitializer) MouseUsesStep = false; RequiresControllerLock = true; - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FSliderStyle StaticSlider = FCoreStyle::Get().GetWidgetStyle("Slider"); - WidgetStyle = StaticSlider; + if (DefaultSliderStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultSliderStyle = new FSliderStyle(FCoreStyle::Get().GetWidgetStyle("Slider")); + + // Unlink UMG default colors from the editor settings colors. + DefaultSliderStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultSliderStyle; + + AccessibleBehavior = ESlateAccessibleBehavior::Summary; + bCanChildrenBeAccessible = false; } TSharedRef USlider::RebuildWidget() @@ -51,6 +65,7 @@ void USlider::SynchronizeProperties() MySlider->SetSliderBarColor(SliderBarColor); MySlider->SetSliderHandleColor(SliderHandleColor); MySlider->SetValue(ValueBinding); + MySlider->SetMinAndMaxValues(MinValue, MaxValue); MySlider->SetLocked(Locked); MySlider->SetIndentHandle(IndentHandle); MySlider->SetStepSize(StepSize); @@ -98,6 +113,23 @@ float USlider::GetValue() const return Value; } +float USlider::GetNormalizedValue() const +{ + if (MySlider.IsValid()) + { + return MySlider->GetNormalizedValue(); + } + + if (MinValue == MaxValue) + { + return 1.0f; + } + else + { + return (Value - MinValue) / (MaxValue - MinValue); + } +} + void USlider::SetValue(float InValue) { Value = InValue; @@ -107,6 +139,27 @@ void USlider::SetValue(float InValue) } } +void USlider::SetMinValue(float InValue) +{ + MinValue = InValue; + if (MySlider.IsValid()) + { + // Because SSlider clamps min/max values upon setting them, + // we have to send both values together to ensure that they + // don't get out of sync. + MySlider->SetMinAndMaxValues(MinValue, MaxValue); + } +} + +void USlider::SetMaxValue(float InValue) +{ + MaxValue = InValue; + if (MySlider.IsValid()) + { + MySlider->SetMinAndMaxValues(MinValue, MaxValue); + } +} + void USlider::SetIndentHandle(bool InIndentHandle) { IndentHandle = InIndentHandle; @@ -152,6 +205,12 @@ void USlider::SetSliderBarColor(FLinearColor InValue) } } +#if WITH_ACCESSIBILITY +TSharedPtr USlider::GetAccessibleWidget() const +{ + return MySlider; +} +#endif #if WITH_EDITOR diff --git a/Engine/Source/Runtime/UMG/Private/Components/SpinBox.cpp b/Engine/Source/Runtime/UMG/Private/Components/SpinBox.cpp index 7afd6168fa91..13c5e1299cd5 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/SpinBox.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/SpinBox.cpp @@ -9,6 +9,8 @@ ///////////////////////////////////////////////////// // USpinBox +static FSpinBoxStyle* DefaultSpinBoxStyle = nullptr; + USpinBox::USpinBox(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -30,9 +32,16 @@ USpinBox::USpinBox(const FObjectInitializer& ObjectInitializer) SelectAllTextOnCommit = true; ForegroundColor = FSlateColor(FLinearColor::Black); - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FSpinBoxStyle StaticSpinBox = FCoreStyle::Get().GetWidgetStyle("SpinBox"); - WidgetStyle = StaticSpinBox; + if (DefaultSpinBoxStyle == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultSpinBoxStyle = new FSpinBoxStyle(FCoreStyle::Get().GetWidgetStyle("SpinBox")); + + // Unlink UMG default colors from the editor settings colors. + DefaultSpinBoxStyle->UnlinkColors(); + } + + WidgetStyle = *DefaultSpinBoxStyle; } void USpinBox::ReleaseSlateResources(bool bReleaseChildren) diff --git a/Engine/Source/Runtime/UMG/Private/Components/TextBlock.cpp b/Engine/Source/Runtime/UMG/Private/Components/TextBlock.cpp index beec5cf86c23..6b65aa58da0c 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/TextBlock.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/TextBlock.cpp @@ -30,6 +30,9 @@ UTextBlock::UTextBlock(const FObjectInitializer& ObjectInitializer) static ConstructorHelpers::FObjectFinder RobotoFontObj(*UWidget::GetDefaultFontName()); Font = FSlateFontInfo(RobotoFontObj.Object, 24, FName("Bold")); } + + AccessibleBehavior = ESlateAccessibleBehavior::Auto; + bCanChildrenBeAccessible = false; } void UTextBlock::PostLoad() @@ -220,6 +223,13 @@ EVisibility UTextBlock::GetTextWarningImageVisibility() const return Text.IsCultureInvariant() ? EVisibility::Visible : EVisibility::Collapsed; } +#if WITH_ACCESSIBILITY +TSharedPtr UTextBlock::GetAccessibleWidget() const +{ + return MyTextBlock; +} +#endif + void UTextBlock::OnBindingChanged(const FName& Property) { Super::OnBindingChanged(Property); diff --git a/Engine/Source/Runtime/UMG/Private/Components/Throbber.cpp b/Engine/Source/Runtime/UMG/Private/Components/Throbber.cpp index ac81627df450..bb7a93ae6add 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/Throbber.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/Throbber.cpp @@ -9,6 +9,8 @@ ///////////////////////////////////////////////////// // UThrobber +static FSlateBrush* DefaultThrobberBrush = nullptr; + UThrobber::UThrobber(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -18,9 +20,16 @@ UThrobber::UThrobber(const FObjectInitializer& ObjectInitializer) bAnimateHorizontally = true; bAnimateOpacity = true; - // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BY DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS - static const FSlateBrush StaticImage = *FCoreStyle::Get().GetBrush("Throbber.Chunk"); - Image = StaticImage; + if (DefaultThrobberBrush == nullptr) + { + // HACK: THIS SHOULD NOT COME FROM CORESTYLE AND SHOULD INSTEAD BE DEFINED BY ENGINE TEXTURES/PROJECT SETTINGS + DefaultThrobberBrush = new FSlateBrush(*FCoreStyle::Get().GetBrush("Throbber.Chunk")); + + // Unlink UMG default colors from the editor settings colors. + DefaultThrobberBrush->UnlinkColors(); + } + + Image = *DefaultThrobberBrush; } void UThrobber::ReleaseSlateResources(bool bReleaseChildren) diff --git a/Engine/Source/Runtime/UMG/Private/Components/UniformGridPanel.cpp b/Engine/Source/Runtime/UMG/Private/Components/UniformGridPanel.cpp index fbef7ed702af..c252951654db 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/UniformGridPanel.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/UniformGridPanel.cpp @@ -67,9 +67,17 @@ TSharedRef UUniformGridPanel::RebuildWidget() return MyUniformGridPanel.ToSharedRef(); } -UUniformGridSlot* UUniformGridPanel::AddChildToUniformGrid(UWidget* Content) +UUniformGridSlot* UUniformGridPanel::AddChildToUniformGrid(UWidget* Content, int32 InRow, int32 InColumn) { - return Cast(Super::AddChild(Content)); + UUniformGridSlot* GridSlot = Cast(Super::AddChild(Content)); + + if (GridSlot != nullptr) + { + GridSlot->SetRow(InRow); + GridSlot->SetColumn(InColumn); + } + + return GridSlot; } void UUniformGridPanel::SetSlotPadding(FMargin InSlotPadding) diff --git a/Engine/Source/Runtime/UMG/Private/Components/Widget.cpp b/Engine/Source/Runtime/UMG/Private/Components/Widget.cpp index 30b07a02ae4c..a0e3856aec3d 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/Widget.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/Widget.cpp @@ -162,6 +162,10 @@ UWidget::UWidget(const FObjectInitializer& ObjectInitializer) RenderTransformPivot = FVector2D(0.5f, 0.5f); Cursor = EMouseCursor::Default; + AccessibleBehavior = ESlateAccessibleBehavior::NotAccessible; + AccessibleSummaryBehavior = ESlateAccessibleBehavior::Auto; + bCanChildrenBeAccessible = true; + #if WITH_EDITORONLY_DATA { static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UWidget::StaticClass(), &GatherWidgetForLocalization); } #endif @@ -187,12 +191,17 @@ void UWidget::SetRenderShear(FVector2D Shear) UpdateRenderTransform(); } -void UWidget::SetRenderAngle(float Angle) +void UWidget::SetRenderTransformAngle(float Angle) { RenderTransform.Angle = Angle; UpdateRenderTransform(); } +float UWidget::GetRenderTransformAngle() const +{ + return RenderTransform.Angle; +} + void UWidget::SetRenderTranslation(FVector2D Translation) { RenderTransform.Translation = Translation; @@ -1161,8 +1170,25 @@ void UWidget::SynchronizeProperties() { SafeWidget->SetToolTipText(PROPERTY_BINDING(FText, ToolTipText)); } + +#if WITH_ACCESSIBILITY + TSharedPtr AccessibleWidget = GetAccessibleWidget(); + if (AccessibleWidget.IsValid()) + { + AccessibleWidget->SetAccessibleBehavior((EAccessibleBehavior)AccessibleBehavior, PROPERTY_BINDING(FText, AccessibleText), EAccessibleType::Main); + AccessibleWidget->SetAccessibleBehavior((EAccessibleBehavior)AccessibleSummaryBehavior, PROPERTY_BINDING(FText, AccessibleSummaryText), EAccessibleType::Summary); + AccessibleWidget->SetCanChildrenBeAccessible(bCanChildrenBeAccessible); + } +#endif } +#if WITH_ACCESSIBILITY +TSharedPtr UWidget::GetAccessibleWidget() const +{ + return GetCachedWidget(); +} +#endif + UObject* UWidget::GetSourceAssetOrClass() const { UObject* SourceAsset = nullptr; diff --git a/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp b/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp index d13cbf6ba7e0..36bce177cb64 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp @@ -571,6 +571,7 @@ UWidgetComponent::UWidgetComponent( const FObjectInitializer& PCIP ) , LastWidgetRenderTime(0) , bReceiveHardwareInput(false) , bWindowFocusable(true) + , WindowVisibility(EWindowVisibility::SelfHitTestInvisible) , bApplyGammaCorrection(false) , BackgroundColor( FLinearColor::Transparent ) , TintColorAndOpacity( FLinearColor::White ) @@ -582,6 +583,7 @@ UWidgetComponent::UWidgetComponent( const FObjectInitializer& PCIP ) , LayerZOrder(-100) , GeometryMode(EWidgetGeometryMode::Plane) , CylinderArcAngle( 180.0f ) + , bRenderCleared(false) { PrimaryComponentTick.bCanEverTick = true; bTickInEditor = true; @@ -619,6 +621,19 @@ UWidgetComponent::UWidgetComponent( const FObjectInitializer& PCIP ) bAddedToScreen = false; } +void UWidgetComponent::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FEditorObjectVersion::GUID); + + if (Ar.CustomVer(FEditorObjectVersion::GUID) < FEditorObjectVersion::ChangedWidgetComponentWindowVisibilityDefault) + { + // Reset the default value for visibility + WindowVisibility = EWindowVisibility::Visible; + } +} + void UWidgetComponent::BeginPlay() { Super::BeginPlay(); @@ -837,6 +852,38 @@ void UWidgetComponent::OnRegister() #endif // !UE_SERVER } +void UWidgetComponent::SetWindowFocusable(bool bInWindowFocusable) +{ + bWindowFocusable = bInWindowFocusable; + if (SlateWindow.IsValid()) + { + SlateWindow->SetIsFocusable(bWindowFocusable); + } +}; + +EVisibility UWidgetComponent::ConvertWindowVisibilityToVisibility(EWindowVisibility visibility) +{ + switch (visibility) + { + case EWindowVisibility::Visible: + return EVisibility::Visible; + case EWindowVisibility::SelfHitTestInvisible: + return EVisibility::SelfHitTestInvisible; + default: + checkNoEntry(); + return EVisibility::SelfHitTestInvisible; + } +} + +void UWidgetComponent::SetWindowVisibility(EWindowVisibility InVisibility) +{ + WindowVisibility = InVisibility; + if (SlateWindow.IsValid()) + { + SlateWindow->SetVisibility(ConvertWindowVisibilityToVisibility(WindowVisibility)); + } +} + bool UWidgetComponent::CanReceiveHardwareInput() const { return bReceiveHardwareInput && GeometryMode == EWidgetGeometryMode::Plane; @@ -988,9 +1035,10 @@ void UWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, { UpdateWidget(); - if ( Widget == nullptr && !SlateWidget.IsValid() ) + if (Widget == nullptr && !SlateWidget.IsValid() && bRenderCleared) { - return; + // We will enter here if the WidgetClass is empty and we already renderered an empty widget. No need to continue. + return; } if ( Space != EWidgetSpace::Screen ) @@ -1002,6 +1050,12 @@ void UWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, // a different rate than the rest of the world. const float DeltaTimeFromLastDraw = LastWidgetRenderTime == 0 ? 0 : (GetCurrentTime() - LastWidgetRenderTime); DrawWidgetToRenderTarget(DeltaTimeFromLastDraw); + + // We draw an empty widget. + if (Widget == nullptr && !SlateWidget.IsValid()) + { + bRenderCleared = true; + } } } else @@ -1013,7 +1067,7 @@ void UWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, ULocalPlayer* TargetPlayer = GetOwnerPlayer(); APlayerController* PlayerController = TargetPlayer ? TargetPlayer->PlayerController : nullptr; - if ( TargetPlayer && PlayerController && IsVisible() ) + if ( TargetPlayer && PlayerController && IsVisible() && !(GetOwner()->bHidden)) { if ( !bAddedToScreen ) { @@ -1243,6 +1297,7 @@ bool UWidgetComponent::CanEditChange(const UProperty* InProperty) const if ( PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, GeometryMode) || PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, TimingPolicy) || PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, bWindowFocusable) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, WindowVisibility) || PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, bManuallyRedraw) || PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, RedrawTime) || PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UWidgetComponent, BackgroundColor) || @@ -1287,6 +1342,8 @@ void UWidgetComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyCha static FName BlendModeName( TEXT( "BlendMode" ) ); static FName GeometryModeName( TEXT("GeometryMode") ); static FName CylinderArcAngleName( TEXT("CylinderArcAngle") ); + static FName bWindowFocusableName(TEXT("bWindowFocusable")); + static FName WindowVisibilityName(TEXT("WindowVisibility")); auto PropertyName = Property->GetFName(); @@ -1315,6 +1372,15 @@ void UWidgetComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyCha { MarkRenderStateDirty(); } + else if (PropertyName == bWindowFocusableName) + { + SetWindowFocusable(bWindowFocusable); + } + else if (PropertyName == WindowVisibilityName) + { + SetWindowVisibility(WindowVisibility); + } + } Super::PostEditChangeProperty(PropertyChangedEvent); @@ -1432,6 +1498,7 @@ void UWidgetComponent::UpdateWidget() SlateWindow = SNew(SVirtualWindow).Size(CurrentDrawSize); SlateWindow->SetIsFocusable(bWindowFocusable); + SlateWindow->SetVisibility(ConvertWindowVisibilityToVisibility(WindowVisibility)); RegisterWindow(); bNeededNewWindow = true; @@ -1463,6 +1530,7 @@ void UWidgetComponent::UpdateWidget() if (CurrentSlateWidget != SNullWidget::NullWidget) { CurrentSlateWidget = SNullWidget::NullWidget; + bRenderCleared = false; bWidgetChanged = true; } SlateWindow->SetContent( SNullWidget::NullWidget ); diff --git a/Engine/Source/Runtime/UMG/Private/Slate/SObjectWidget.cpp b/Engine/Source/Runtime/UMG/Private/Slate/SObjectWidget.cpp index d9198f65e152..66da79933f7e 100644 --- a/Engine/Source/Runtime/UMG/Private/Slate/SObjectWidget.cpp +++ b/Engine/Source/Runtime/UMG/Private/Slate/SObjectWidget.cpp @@ -513,13 +513,13 @@ FReply SObjectWidget::OnTouchForceChanged(const FGeometry& MyGeometry, const FPo FNavigationReply SObjectWidget::OnNavigation(const FGeometry& MyGeometry, const FNavigationEvent& InNavigationEvent) { - if (WidgetObject->NativeSupportsCustomNavigation()) + if (WidgetObject && WidgetObject->NativeSupportsCustomNavigation()) { return WidgetObject->NativeOnNavigation(MyGeometry, InNavigationEvent); } FNavigationReply Reply = SCompoundWidget::OnNavigation(MyGeometry, InNavigationEvent); - if ( CanRouteEvent() ) + if (WidgetObject && CanRouteEvent() ) { return WidgetObject->NativeOnNavigation(MyGeometry, InNavigationEvent, Reply); } diff --git a/Engine/Source/Runtime/UMG/Private/Slate/SRetainerWidget.cpp b/Engine/Source/Runtime/UMG/Private/Slate/SRetainerWidget.cpp index 2387303747af..251fad2404cd 100644 --- a/Engine/Source/Runtime/UMG/Private/Slate/SRetainerWidget.cpp +++ b/Engine/Source/Runtime/UMG/Private/Slate/SRetainerWidget.cpp @@ -102,6 +102,7 @@ SRetainerWidget::~SRetainerWidget() if( FSlateApplication::IsInitialized() ) { + FSlateApplicationBase::Get().OnGlobalInvalidate().RemoveAll( this ); #if !UE_BUILD_SHIPPING OnRetainerModeChangedDelegate.RemoveAll( this ); #endif diff --git a/Engine/Source/Runtime/UMG/Private/WidgetLayoutLibrary.cpp b/Engine/Source/Runtime/UMG/Private/WidgetLayoutLibrary.cpp index bac8b9ecab18..a81be1d5078b 100644 --- a/Engine/Source/Runtime/UMG/Private/WidgetLayoutLibrary.cpp +++ b/Engine/Source/Runtime/UMG/Private/WidgetLayoutLibrary.cpp @@ -9,6 +9,7 @@ #include "Components/CanvasPanelSlot.h" #include "Components/HorizontalBoxSlot.h" #include "Components/VerticalBoxSlot.h" +#include "Components/ScrollBoxSlot.h" #include "Components/UniformGridSlot.h" #include "Components/GridSlot.h" #include "Components/OverlaySlot.h" @@ -280,6 +281,16 @@ UVerticalBoxSlot* UWidgetLayoutLibrary::SlotAsVerticalBoxSlot(UWidget* Widget) return nullptr; } +UScrollBoxSlot* UWidgetLayoutLibrary::SlotAsScrollBoxSlot(UWidget* Widget) +{ + if (Widget) + { + return Cast(Widget->Slot); + } + + return nullptr; +} + void UWidgetLayoutLibrary::RemoveAllWidgets(UObject* WorldContextObject) { UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); diff --git a/Engine/Source/Runtime/UMG/Public/Animation/WidgetAnimationPlayCallbackProxy.h b/Engine/Source/Runtime/UMG/Public/Animation/WidgetAnimationPlayCallbackProxy.h new file mode 100644 index 000000000000..040ce4b05554 --- /dev/null +++ b/Engine/Source/Runtime/UMG/Public/Animation/WidgetAnimationPlayCallbackProxy.h @@ -0,0 +1,35 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Blueprint/UserWidget.h" +#include "WidgetAnimationPlayCallbackProxy.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWidgetAnimationResult); + +UCLASS(MinimalAPI) +class UWidgetAnimationPlayCallbackProxy : public UObject +{ + GENERATED_UCLASS_BODY() + + // Called when animation has been completed + UPROPERTY(BlueprintAssignable) + FWidgetAnimationResult Finished; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Play Animation with Finished event", ShortToolTip = "Play Animation and trigger event on Finished", ToolTip="Play Animation on widget and trigger Finish event when the animation is done."), Category = "User Interface|Animation") + static UWidgetAnimationPlayCallbackProxy* CreatePlayAnimationProxyObject(class UUMGSequencePlayer*& Result, class UUserWidget* Widget, class UWidgetAnimation* InAnimation, float StartAtTime = 0.0f, int32 NumLoopsToPlay = 1, EUMGSequencePlayMode::Type PlayMode = EUMGSequencePlayMode::Forward, float PlaybackSpeed = 1.0f); + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "Play Animation Time Range with Finished event", ShortToolTip = "Play Animation Time Range and trigger event on Finished", ToolTip = "Play Animation Time Range on widget and trigger Finish event when the animation is done."), Category = "User Interface|Animation") + static UWidgetAnimationPlayCallbackProxy* CreatePlayAnimationTimeRangeProxyObject(class UUMGSequencePlayer*& Result, class UUserWidget* Widget, class UWidgetAnimation* InAnimation, float StartAtTime = 0.0f, float EndAtTime = 0.0f, int32 NumLoopsToPlay = 1, EUMGSequencePlayMode::Type PlayMode = EUMGSequencePlayMode::Forward, float PlaybackSpeed = 1.0f); + +private: + class UUMGSequencePlayer* ExecutePlayAnimation(class UUserWidget* Widget, class UWidgetAnimation* InAnimation, float StartAtTime, int32 NumLoopsToPlay, EUMGSequencePlayMode::Type PlayMode, float PlaybackSpeed); + class UUMGSequencePlayer* ExecutePlayAnimationTimeRange(class UUserWidget* Widget, class UWidgetAnimation* InAnimation, float StartAtTime, float EndAtTime, int32 NumLoopsToPlay, EUMGSequencePlayMode::Type PlayMode, float PlaybackSpeed); + void OnFinished(class UUMGSequencePlayer& Player); + void OnFinished_Delayed(); + + // Pointer to the world, needed to delay the results slightly + TWeakObjectPtr WorldPtr; + + FDelegateHandle OnFinishedHandle; +}; diff --git a/Engine/Source/Runtime/UMG/Public/Binding/TextBinding.h b/Engine/Source/Runtime/UMG/Public/Binding/TextBinding.h index 12f89a6a7d34..5efe34964b28 100644 --- a/Engine/Source/Runtime/UMG/Public/Binding/TextBinding.h +++ b/Engine/Source/Runtime/UMG/Public/Binding/TextBinding.h @@ -15,8 +15,6 @@ class UMG_API UTextBinding : public UPropertyBinding public: - UTextBinding(); - virtual bool IsSupportedSource(UProperty* Property) const override; virtual bool IsSupportedDestination(UProperty* Property) const override; @@ -29,5 +27,15 @@ public: FString GetStringValue() const; private: - mutable TOptional bNeedsConversion; + + enum class EConversion : uint8 + { + None, + String, + Words, + Integer, + Float + }; + + mutable TOptional NeedsConversion; }; diff --git a/Engine/Source/Runtime/UMG/Public/Blueprint/UserWidget.h b/Engine/Source/Runtime/UMG/Public/Blueprint/UserWidget.h index 4c4d24066689..e83600892f95 100644 --- a/Engine/Source/Runtime/UMG/Public/Blueprint/UserWidget.h +++ b/Engine/Source/Runtime/UMG/Public/Blueprint/UserWidget.h @@ -230,8 +230,9 @@ public: EWidgetTickFrequency GetDesiredTickFrequency() const { return TickFrequency; } -protected: UWidgetBlueprintGeneratedClass* GetWidgetTreeOwningClass(); + +protected: virtual void TemplateInitInner(); bool VerifyTemplateIntegrity(UUserWidget* TemplateRoot, TArray& OutErrors); diff --git a/Engine/Source/Runtime/UMG/Public/Blueprint/WidgetLayoutLibrary.h b/Engine/Source/Runtime/UMG/Public/Blueprint/WidgetLayoutLibrary.h index 72cb9d95a49b..e8a89b0401b7 100644 --- a/Engine/Source/Runtime/UMG/Public/Blueprint/WidgetLayoutLibrary.h +++ b/Engine/Source/Runtime/UMG/Public/Blueprint/WidgetLayoutLibrary.h @@ -15,6 +15,7 @@ class UHorizontalBoxSlot; class UOverlaySlot; class UUniformGridSlot; class UVerticalBoxSlot; +class UScrollBoxSlot; class UWidget; UCLASS() @@ -147,6 +148,15 @@ public: UFUNCTION(BlueprintPure, Category = "Slot") static UVerticalBoxSlot* SlotAsVerticalBoxSlot(UWidget* Widget); + + /** + * Gets the slot object on the child widget as a Scroll Box Slot, allowing you to manipulate its information. + * @param Widget The child widget of a Scroll Box. + */ + UFUNCTION(BlueprintPure, Category = "Slot") + static UScrollBoxSlot* SlotAsScrollBoxSlot(UWidget* Widget); + + /** * Removes all widgets from the viewport. */ diff --git a/Engine/Source/Runtime/UMG/Public/Components/Button.h b/Engine/Source/Runtime/UMG/Public/Components/Button.h index 7271e66c34d4..2d002c1319a6 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/Button.h +++ b/Engine/Source/Runtime/UMG/Public/Components/Button.h @@ -158,6 +158,10 @@ protected: #endif //~ End UWidget Interface +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif + protected: /** Cached pointer to the underlying slate button owned by this UWidget */ TSharedPtr MyButton; diff --git a/Engine/Source/Runtime/UMG/Public/Components/CheckBox.h b/Engine/Source/Runtime/UMG/Public/Components/CheckBox.h index 860300792736..691e1c0abe72 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/CheckBox.h +++ b/Engine/Source/Runtime/UMG/Public/Components/CheckBox.h @@ -163,6 +163,10 @@ protected: //~ End UWidget Interface void SlateOnCheckStateChangedCallback(ECheckBoxState NewState); + +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif protected: TSharedPtr MyCheckbox; diff --git a/Engine/Source/Runtime/UMG/Public/Components/ComboBoxString.h b/Engine/Source/Runtime/UMG/Public/Components/ComboBoxString.h index 81dbaaaab3bd..c957b1c7a438 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/ComboBoxString.h +++ b/Engine/Source/Runtime/UMG/Public/Components/ComboBoxString.h @@ -152,6 +152,9 @@ public: #endif protected: + /** Refresh ComboBoxContent with the correct widget/data when the selected option changes */ + void UpdateOrGenerateWidget(TSharedPtr Item); + /** Called by slate when it needs to generate a new item for the combobox */ virtual TSharedRef HandleGenerateWidget(TSharedPtr Item) const; @@ -175,6 +178,9 @@ protected: /** A shared pointer to a container that holds the combobox content that is selected */ TSharedPtr< SBox > ComboBoxContent; + /** If OnGenerateWidgetEvent is not bound, this will store the default STextBlock generated */ + TWeakPtr DefaultComboBoxContent; + /** A shared pointer to the current selected string */ TSharedPtr CurrentOptionPtr; }; diff --git a/Engine/Source/Runtime/UMG/Public/Components/EditableText.h b/Engine/Source/Runtime/UMG/Public/Components/EditableText.h index be5eea41a731..321aea9f1e6c 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/EditableText.h +++ b/Engine/Source/Runtime/UMG/Public/Components/EditableText.h @@ -135,7 +135,7 @@ public: public: - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ UPROPERTY(BlueprintAssignable, Category="Widget Event", meta=(DisplayName="OnTextChanged (Editable Text)")) FOnEditableTextChangedEvent OnTextChanged; @@ -196,6 +196,10 @@ protected: void HandleOnTextChanged(const FText& Text); void HandleOnTextCommitted(const FText& Text, ETextCommit::Type CommitMethod); +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif + protected: TSharedPtr MyEditableText; diff --git a/Engine/Source/Runtime/UMG/Public/Components/EditableTextBox.h b/Engine/Source/Runtime/UMG/Public/Components/EditableTextBox.h index c1eaa0357131..ce8708da85fd 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/EditableTextBox.h +++ b/Engine/Source/Runtime/UMG/Public/Components/EditableTextBox.h @@ -137,7 +137,7 @@ public: public: - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ UPROPERTY(BlueprintAssignable, Category="TextBox|Event") FOnEditableTextBoxChangedEvent OnTextChanged; @@ -200,6 +200,10 @@ protected: virtual void HandleOnTextChanged(const FText& Text); virtual void HandleOnTextCommitted(const FText& Text, ETextCommit::Type CommitMethod); +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif + protected: TSharedPtr MyEditableTextBlock; diff --git a/Engine/Source/Runtime/UMG/Public/Components/GridPanel.h b/Engine/Source/Runtime/UMG/Public/Components/GridPanel.h index 88f73e353649..7514e0221b3f 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/GridPanel.h +++ b/Engine/Source/Runtime/UMG/Public/Components/GridPanel.h @@ -35,7 +35,7 @@ public: /** */ UFUNCTION(BlueprintCallable, Category="Widget") - UGridSlot* AddChildToGrid(UWidget* Content); + UGridSlot* AddChildToGrid(UWidget* Content, int32 InRow = 0, int32 InColumn = 0); UFUNCTION(BlueprintCallable, Category = "Widget") void SetColumnFill(int32 ColumnIndex, float Coefficient); diff --git a/Engine/Source/Runtime/UMG/Public/Components/GridSlot.h b/Engine/Source/Runtime/UMG/Public/Components/GridSlot.h index 77a5e732c1f0..3e1d645f3acd 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/GridSlot.h +++ b/Engine/Source/Runtime/UMG/Public/Components/GridSlot.h @@ -84,6 +84,7 @@ public: void SetLayer(int32 InLayer); /** Sets the offset for this slot's content by some amount; positive values offset to lower right*/ + UFUNCTION(BlueprintCallable, Category = "Layout|Grid Slot") void SetNudge(FVector2D InNudge); /** */ diff --git a/Engine/Source/Runtime/UMG/Public/Components/Image.h b/Engine/Source/Runtime/UMG/Public/Components/Image.h index a0d8d8c80637..bda068e3c3df 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/Image.h +++ b/Engine/Source/Runtime/UMG/Public/Components/Image.h @@ -166,6 +166,10 @@ protected: // FReply HandleMouseButtonDown(const FGeometry& Geometry, const FPointerEvent& MouseEvent); +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif + protected: TSharedPtr MyImage; diff --git a/Engine/Source/Runtime/UMG/Public/Components/ListView.h b/Engine/Source/Runtime/UMG/Public/Components/ListView.h index 4c02e84cd27f..d876bce1735e 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/ListView.h +++ b/Engine/Source/Runtime/UMG/Public/Components/ListView.h @@ -20,7 +20,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnListItemScrolledIntoViewDynamic, * The list itself is based on a list of n items, but only creates as many entry widgets as can fit on screen. * For example, a scrolling ListView of 200 items with 5 currently visible will only have created 5 entry widgets. * - * To make a widget usable an an entry in a ListView, it must inherit from the IUserObjectListEntry interface. + * To make a widget usable as an entry in a ListView, it must inherit from the IUserObjectListEntry interface. */ UCLASS(meta = (EntryInterface = UserObjectListEntry)) class UMG_API UListView : public UListViewBase, public ITypedUMGListView diff --git a/Engine/Source/Runtime/UMG/Public/Components/ListViewBase.h b/Engine/Source/Runtime/UMG/Public/Components/ListViewBase.h index 6a821e7a0944..81571e39c7c3 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/ListViewBase.h +++ b/Engine/Source/Runtime/UMG/Public/Components/ListViewBase.h @@ -527,7 +527,7 @@ protected: { bNeedsToCallRefreshDesignerItems = false; bool bRefresh = false; - if (EntryWidgetClass && NumDesignerPreviewEntries > 0) + if (EntryWidgetClass && NumDesignerPreviewEntries > 0 && EntryWidgetClass->ImplementsInterface(UUserListEntry::StaticClass())) { if (ListItems.Num() < NumDesignerPreviewEntries) { @@ -562,7 +562,7 @@ protected: // Note: Options for this property can be configured via class and property metadata. See class declaration comment above. /** The type of widget to create for each entry displayed in the list. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ListEntries, meta = (DesignerRebuild, AllowPrivateAccess = true)) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ListEntries, meta = (DesignerRebuild, AllowPrivateAccess = true, MustImplement = UserListEntry)) TSubclassOf EntryWidgetClass; private: diff --git a/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableText.h b/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableText.h index 436c3ffb110f..c3a2f873a0f3 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableText.h +++ b/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableText.h @@ -41,7 +41,7 @@ public: public: /** The style */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Style", meta=(ShowOnlyInnerProperties)) + UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintSetter=SetWidgetStyle, Category="Style", meta=(ShowOnlyInnerProperties)) FTextBlockStyle WidgetStyle; /** Sets whether this text block can be modified interactively by the user */ @@ -80,7 +80,7 @@ public: UPROPERTY(EditAnywhere, Category=Behavior, AdvancedDisplay) EVirtualKeyboardDismissAction VirtualKeyboardDismissAction; - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ UPROPERTY(BlueprintAssignable, Category="Widget Event", meta=(DisplayName="OnTextChanged (Multi-Line Editable Text)")) FOnMultiLineEditableTextChangedEvent OnTextChanged; @@ -108,6 +108,9 @@ public: UFUNCTION(BlueprintCallable, Category="Widget", meta=(DisplayName="SetIsReadOnly (Multi-Line Editable Text")) void SetIsReadOnly(bool bReadOnly); + + UFUNCTION(BlueprintSetter) + void SetWidgetStyle(const FTextBlockStyle& InWidgetStyle); //~ Begin UWidget Interface virtual void SynchronizeProperties() override; diff --git a/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableTextBox.h b/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableTextBox.h index 4f87eaec0444..f075cd3f2384 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableTextBox.h +++ b/Engine/Source/Runtime/UMG/Public/Components/MultiLineEditableTextBox.h @@ -47,7 +47,7 @@ public: FEditableTextBoxStyle WidgetStyle; /** The text style */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Style", meta=(DisplayName="Text Style")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintSetter = SetTextStyle, Category="Style", meta=(DisplayName="Text Style")) FTextBlockStyle TextStyle; /** Sets whether this text block can be modified interactively by the user */ @@ -85,7 +85,7 @@ public: UPROPERTY() FLinearColor ReadOnlyForegroundColor_DEPRECATED; - /** Called whenever the text is changed interactively by the user */ + /** Called whenever the text is changed programmatically or interactively by the user */ UPROPERTY(BlueprintAssignable, Category="Widget Event", meta=(DisplayName="OnTextChanged (Multi-Line Text Box)")) FOnMultiLineEditableTextBoxChangedEvent OnTextChanged; @@ -120,6 +120,9 @@ public: UFUNCTION(BlueprintCallable, Category="Widget", meta=(DisplayName="SetIsReadOnly (Multi-Line Text Box)")) void SetIsReadOnly(bool bReadOnly); + UFUNCTION(BlueprintSetter) + void SetTextStyle(const FTextBlockStyle& InTextStyle); + //TODO UMG Add Set ReadOnlyForegroundColor //TODO UMG Add Set BackgroundColor //TODO UMG Add Set ForegroundColor diff --git a/Engine/Source/Runtime/UMG/Public/Components/RichTextBlock.h b/Engine/Source/Runtime/UMG/Public/Components/RichTextBlock.h index f9e541262e60..304584e014a1 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/RichTextBlock.h +++ b/Engine/Source/Runtime/UMG/Public/Components/RichTextBlock.h @@ -125,6 +125,12 @@ public: // End UWidget interface #endif + /** + * Returns widgets text. + */ + UFUNCTION(BlueprintCallable, Category = "Widget") + FText GetText() const; + /** * Directly sets the widget text. * Warning: This will wipe any binding created for the Text property! diff --git a/Engine/Source/Runtime/UMG/Public/Components/ScrollBar.h b/Engine/Source/Runtime/UMG/Public/Components/ScrollBar.h index d12a7c77677c..7fb7d91e5d45 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/ScrollBar.h +++ b/Engine/Source/Runtime/UMG/Public/Components/ScrollBar.h @@ -41,6 +41,10 @@ public: UPROPERTY(EditAnywhere, Category="Behavior") FVector2D Thickness; + /** The margin around the scrollbar */ + UPROPERTY(EditAnywhere, Category = "Behavior") + FMargin Padding; + public: /** @@ -78,7 +82,10 @@ public: //~ End UVisual Interface //~ Begin UObject Interface +#if WITH_EDITORONLY_DATA + virtual void Serialize(FArchive& Ar) override; virtual void PostLoad() override; +#endif // if WITH_EDITORONLY_DATA //~ End UObject Interface #if WITH_EDITOR diff --git a/Engine/Source/Runtime/UMG/Public/Components/ScrollBox.h b/Engine/Source/Runtime/UMG/Public/Components/ScrollBox.h index 85d98692f4b6..be5e4f5ff208 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/ScrollBox.h +++ b/Engine/Source/Runtime/UMG/Public/Components/ScrollBox.h @@ -48,10 +48,14 @@ public: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Scroll") EConsumeMouseWheel ConsumeMouseWheel; - /** */ + /** The thickness of the scrollbar thumb */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Scroll") FVector2D ScrollbarThickness; + /** The margin around the scrollbar */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Scroll") + FMargin ScrollbarPadding; + /** */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Scroll") bool AlwaysShowScrollbar; @@ -92,6 +96,9 @@ public: UFUNCTION(BlueprintCallable, Category = "Scroll") void SetScrollbarThickness(const FVector2D& NewScrollbarThickness); + UFUNCTION(BlueprintCallable, Category = "Scroll") + void SetScrollbarPadding(const FMargin& NewScrollbarPadding); + UFUNCTION(BlueprintCallable, Category = "Scroll") void SetAlwaysShowScrollbar(bool NewAlwaysShowScrollbar); @@ -121,6 +128,10 @@ public: UFUNCTION(BlueprintCallable, Category="Widget") float GetScrollOffset() const; + /** Gets the scroll offset of the bottom of the ScrollBox in Slate Units. */ + UFUNCTION(BlueprintCallable, Category = "Widget") + float GetScrollOffsetOfEnd() const; + UFUNCTION(BlueprintCallable, Category="Widget") float GetViewOffsetFraction() const; @@ -145,7 +156,10 @@ public: //~ End UVisual Interface //~ Begin UObject Interface +#if WITH_EDITORONLY_DATA + virtual void Serialize(FArchive& Ar) override; virtual void PostLoad() override; +#endif // if WITH_EDITORONLY_DATA //~ End UObject Interface #if WITH_EDITOR diff --git a/Engine/Source/Runtime/UMG/Public/Components/SizeBox.h b/Engine/Source/Runtime/UMG/Public/Components/SizeBox.h index 66bae10e1680..a4118eaae60a 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/SizeBox.h +++ b/Engine/Source/Runtime/UMG/Public/Components/SizeBox.h @@ -26,108 +26,122 @@ public: /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) - uint32 bOverride_WidthOverride : 1; + uint32 bOverride_WidthOverride : 1; /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) - uint32 bOverride_HeightOverride : 1; + uint32 bOverride_HeightOverride : 1; /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) - uint32 bOverride_MinDesiredWidth : 1; + uint32 bOverride_MinDesiredWidth : 1; /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) - uint32 bOverride_MinDesiredHeight : 1; + uint32 bOverride_MinDesiredHeight : 1; /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) - uint32 bOverride_MaxDesiredWidth : 1; + uint32 bOverride_MaxDesiredWidth : 1; /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) - uint32 bOverride_MaxDesiredHeight : 1; + uint32 bOverride_MaxDesiredHeight : 1; /** */ UPROPERTY(EditAnywhere, Category="Child Layout", meta=(InlineEditConditionToggle)) + uint32 bOverride_MinAspectRatio : 1; + + /** */ + UPROPERTY(EditAnywhere, Category = "Child Layout", meta=(InlineEditConditionToggle)) uint32 bOverride_MaxAspectRatio : 1; /** When specified, ignore the content's desired size and report the WidthOverride as the Box's desired width. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_WidthOverride" )) - float WidthOverride; + float WidthOverride; /** When specified, ignore the content's desired size and report the HeightOverride as the Box's desired height. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_HeightOverride" )) - float HeightOverride; + float HeightOverride; /** When specified, will report the MinDesiredWidth if larger than the content's desired width. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_MinDesiredWidth" )) - float MinDesiredWidth; + float MinDesiredWidth; /** When specified, will report the MinDesiredHeight if larger than the content's desired height. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_MinDesiredHeight" )) - float MinDesiredHeight; + float MinDesiredHeight; /** When specified, will report the MaxDesiredWidth if smaller than the content's desired width. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_MaxDesiredWidth" )) - float MaxDesiredWidth; + float MaxDesiredWidth; /** When specified, will report the MaxDesiredHeight if smaller than the content's desired height. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_MaxDesiredHeight" )) - float MaxDesiredHeight; + float MaxDesiredHeight; + + /** */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Child Layout", meta = (editcondition = "bOverride_MinAspectRatio")) + float MinAspectRatio; /** */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Child Layout", meta=( editcondition="bOverride_MaxAspectRatio" )) - float MaxAspectRatio; + float MaxAspectRatio; public: /** When specified, ignore the content's desired size and report the WidthOverride as the Box's desired width. */ UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void SetWidthOverride(float InWidthOverride); + void SetWidthOverride(float InWidthOverride); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void ClearWidthOverride(); + void ClearWidthOverride(); /** When specified, ignore the content's desired size and report the HeightOverride as the Box's desired height. */ UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void SetHeightOverride(float InHeightOverride); + void SetHeightOverride(float InHeightOverride); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void ClearHeightOverride(); + void ClearHeightOverride(); /** When specified, will report the MinDesiredWidth if larger than the content's desired width. */ UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void SetMinDesiredWidth(float InMinDesiredWidth); + void SetMinDesiredWidth(float InMinDesiredWidth); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void ClearMinDesiredWidth(); + void ClearMinDesiredWidth(); /** When specified, will report the MinDesiredHeight if larger than the content's desired height. */ UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void SetMinDesiredHeight(float InMinDesiredHeight); + void SetMinDesiredHeight(float InMinDesiredHeight); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void ClearMinDesiredHeight(); + void ClearMinDesiredHeight(); /** When specified, will report the MaxDesiredWidth if smaller than the content's desired width. */ UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void SetMaxDesiredWidth(float InMaxDesiredWidth); + void SetMaxDesiredWidth(float InMaxDesiredWidth); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void ClearMaxDesiredWidth(); + void ClearMaxDesiredWidth(); /** When specified, will report the MaxDesiredHeight if smaller than the content's desired height. */ UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void SetMaxDesiredHeight(float InMaxDesiredHeight); + void SetMaxDesiredHeight(float InMaxDesiredHeight); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") - void ClearMaxDesiredHeight(); + void ClearMaxDesiredHeight(); + + UFUNCTION(BlueprintCallable, Category="Layout|Size Box") + void SetMinAspectRatio(float InMinAspectRatio); UFUNCTION(BlueprintCallable, Category="Layout|Size Box") void SetMaxAspectRatio(float InMaxAspectRatio); + UFUNCTION(BlueprintCallable, Category="Layout|Size Box") + void ClearMinAspectRatio(); + UFUNCTION(BlueprintCallable, Category="Layout|Size Box") void ClearMaxAspectRatio(); @@ -160,4 +174,4 @@ protected: // UWidget interface virtual TSharedRef RebuildWidget() override; // End of UWidget interface -}; +}; \ No newline at end of file diff --git a/Engine/Source/Runtime/UMG/Public/Components/SlateWrapperTypes.h b/Engine/Source/Runtime/UMG/Public/Components/SlateWrapperTypes.h index 39bf2865013a..81ec3d5482c9 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/SlateWrapperTypes.h +++ b/Engine/Source/Runtime/UMG/Public/Components/SlateWrapperTypes.h @@ -20,16 +20,37 @@ UENUM(BlueprintType) enum class ESlateVisibility : uint8 { - /** Default widget visibility - visible and can interact with the cursor */ + /** Visible and hit-testable (can interact with cursor). Default value. */ Visible, - /** Not visible and takes up no space in the layout; can never be clicked on because it takes up no space. */ + /** Not visible and takes up no space in the layout (obviously not hit-testable). */ Collapsed, - /** Not visible, but occupies layout space. Not interactive for obvious reasons. */ + /** Not visible but occupies layout space (obviously not hit-testable). */ Hidden, - /** Visible to the user, but only as art. The cursors hit tests will never see this widget. */ - HitTestInvisible, - /** Same as HitTestInvisible, but doesn't apply to child widgets. */ - SelfHitTestInvisible + /** Visible but not hit-testable (cannot interact with cursor) and children in the hierarchy (if any) are also not hit-testable. */ + HitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self & All Children)"), + /** Visible but not hit-testable (cannot interact with cursor) and doesn't affect hit-testing on children (if any). */ + SelfHitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self Only)") +}; + +/** Whether a widget should be included in accessibility, and if so, how its text should be retrieved. */ +UENUM(BlueprintType) +enum class ESlateAccessibleBehavior : uint8 +{ + /** Not accessible. */ + NotAccessible, + /** + * Accessible, first checking to see if there's any custom default text assigned for widgets of this type. + * If not, then it will attempt to use the alternate behavior (ie AccessibleSummaryBehavior instead of AccessibleBehavior) + * and return that value instead. This acts as a reference so that you only need to set one value for both of them + * to return the same thing. + */ + Auto, + /** Accessible, and traverse all child widgets and concat their AccessibleSummaryText together. */ + Summary, + /** Accessible, and retrieve manually-assigned text from a TAttribute. */ + Custom, + /** Accessible, and use the tooltip's accessible text. */ + ToolTip }; /** The sizing options of UWidgets */ diff --git a/Engine/Source/Runtime/UMG/Public/Components/Slider.h b/Engine/Source/Runtime/UMG/Public/Components/Slider.h index 0e1cd5a857aa..537bc97196ea 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/Slider.h +++ b/Engine/Source/Runtime/UMG/Public/Components/Slider.h @@ -29,13 +29,21 @@ class UMG_API USlider : public UWidget public: /** The volume value to display. */ - UPROPERTY(EditAnywhere, Category=Appearance, meta=( ClampMin="0", ClampMax="1", UIMin="0", UIMax="1")) + UPROPERTY(EditAnywhere, Category=Appearance, meta=(UIMin="0", UIMax="1")) float Value; /** A bindable delegate to allow logic to drive the value of the widget */ UPROPERTY() FGetFloat ValueDelegate; + /** The minimum value the slider can be set to. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Appearance) + float MinValue; + + /** The maximum value the slider can be set to. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Appearance) + float MaxValue; + public: /** The progress bar style */ @@ -71,7 +79,7 @@ public: bool RequiresControllerLock; /** The amount to adjust the value by, when using a controller or keyboard */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance, meta=( ClampMin="0", ClampMax="1", UIMin="0", UIMax="1")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance, meta=(UIMin="0", UIMax="1")) float StepSize; /** Should the slider be focusable? */ @@ -104,10 +112,22 @@ public: UFUNCTION(BlueprintCallable, Category="Behavior") float GetValue() const; + /** Get the current value scaled from 0 to 1 */ + UFUNCTION(BlueprintCallable, Category = "Behavior") + float GetNormalizedValue() const; + /** Sets the current value of the slider. */ UFUNCTION(BlueprintCallable, Category="Behavior") void SetValue(float InValue); + /** Sets the minimum value of the slider. */ + UFUNCTION(BlueprintCallable, Category = "Behavior") + void SetMinValue(float InValue); + + /** Sets the maximum value of the slider. */ + UFUNCTION(BlueprintCallable, Category = "Behavior") + void SetMaxValue(float InValue); + /** Sets if the slidable area should be indented to fit the handle */ UFUNCTION(BlueprintCallable, Category="Behavior") void SetIndentHandle(bool InValue); @@ -154,5 +174,9 @@ protected: void HandleOnControllerCaptureBegin(); void HandleOnControllerCaptureEnd(); +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif + PROPERTY_BINDING_IMPLEMENTATION(float, Value); }; diff --git a/Engine/Source/Runtime/UMG/Public/Components/TextBlock.h b/Engine/Source/Runtime/UMG/Public/Components/TextBlock.h index 086aa4df2623..cee8c17f1063 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/TextBlock.h +++ b/Engine/Source/Runtime/UMG/Public/Components/TextBlock.h @@ -212,6 +212,10 @@ protected: EVisibility GetTextWarningImageVisibility() const; +#if WITH_ACCESSIBILITY + virtual TSharedPtr GetAccessibleWidget() const override; +#endif + protected: TSharedPtr MyTextBlock; diff --git a/Engine/Source/Runtime/UMG/Public/Components/UniformGridPanel.h b/Engine/Source/Runtime/UMG/Public/Components/UniformGridPanel.h index 4089d0e498aa..34cd03d2e328 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/UniformGridPanel.h +++ b/Engine/Source/Runtime/UMG/Public/Components/UniformGridPanel.h @@ -52,7 +52,7 @@ public: /** */ UFUNCTION(BlueprintCallable, Category="Widget") - UUniformGridSlot* AddChildToUniformGrid(UWidget* Content); + UUniformGridSlot* AddChildToUniformGrid(UWidget* Content, int32 InRow = 0, int32 InColumn = 0); public: diff --git a/Engine/Source/Runtime/UMG/Public/Components/Widget.h b/Engine/Source/Runtime/UMG/Public/Components/Widget.h index b1b2d3ce7854..b305a4136921 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/Widget.h +++ b/Engine/Source/Runtime/UMG/Public/Components/Widget.h @@ -254,7 +254,7 @@ public: /** * The parent slot of the UWidget. Allows us to easily inline edit the layout controlling this widget. */ - UPROPERTY(Instanced, EditAnywhere, BlueprintReadOnly, Category=Layout, meta=(ShowOnlyInnerProperties)) + UPROPERTY(Instanced, TextExportTransient, EditAnywhere, BlueprintReadOnly, Category=Layout, meta=(ShowOnlyInnerProperties)) UPanelSlot* Slot; /** A bindable delegate for bIsEnabled */ @@ -318,6 +318,32 @@ public: UPROPERTY(EditAnywhere, Category="Behavior", meta=(InlineEditConditionToggle)) uint8 bOverride_Cursor : 1; + /** Whether or not children of this widget can appear as distinct accessible widgets. */ + UPROPERTY(EditAnywhere, Category = "Accessibility") + uint8 bCanChildrenBeAccessible : 1; + + /** Whether or not the widget is accessible, and how to describe it. If set to custom, additional customization options will appear. */ + UPROPERTY(EditAnywhere, Category = "Accessibility") + ESlateAccessibleBehavior AccessibleBehavior; + + /** When AccessibleBehavior is set to Custom, this is the text that will be used to describe the widget. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Accessibility", meta = (MultiLine = true)) + FText AccessibleText; + + UPROPERTY() + FGetText AccessibleTextDelegate; + + /** How to describe this widget when it's being presented through a summary of a parent widget. If set to custom, additional customization options will appear. */ + UPROPERTY(EditAnywhere, Category = "Accessibility", AdvancedDisplay) + ESlateAccessibleBehavior AccessibleSummaryBehavior; + + /** When AccessibleSummaryBehavior is set to Custom, this is the text that will be used to describe the widget. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Accessibility", meta = (MultiLine = true), AdvancedDisplay) + FText AccessibleSummaryText; + + UPROPERTY() + FGetText AccessibleSummaryTextDelegate; + protected: /** @@ -426,7 +452,11 @@ public: /** */ UFUNCTION(BlueprintCallable, Category="Widget|Transform") - void SetRenderAngle(float Angle); + void SetRenderTransformAngle(float Angle); + + /** */ + UFUNCTION(BlueprintCallable, Category = "Widget|Transform") + float GetRenderTransformAngle() const; /** */ UFUNCTION(BlueprintCallable, Category="Widget|Transform") @@ -926,6 +956,11 @@ protected: void SetNavigationRuleInternal(EUINavigation Direction, EUINavigationRule Rule, FName WidgetToFocus); +#if WITH_ACCESSIBILITY + /** Gets the widget that accessibility properties should synchronize to. */ + virtual TSharedPtr GetAccessibleWidget() const; +#endif + protected: /** The underlying SWidget. */ TWeakPtr MyWidget; @@ -972,4 +1007,6 @@ private: private: PROPERTY_BINDING_IMPLEMENTATION(FText, ToolTipText); PROPERTY_BINDING_IMPLEMENTATION(bool, bIsEnabled); + PROPERTY_BINDING_IMPLEMENTATION(FText, AccessibleText); + PROPERTY_BINDING_IMPLEMENTATION(FText, AccessibleSummaryText); }; diff --git a/Engine/Source/Runtime/UMG/Public/Components/WidgetComponent.h b/Engine/Source/Runtime/UMG/Public/Components/WidgetComponent.h index f3cd88d7d079..6ce042c9f2b1 100644 --- a/Engine/Source/Runtime/UMG/Public/Components/WidgetComponent.h +++ b/Engine/Source/Runtime/UMG/Public/Components/WidgetComponent.h @@ -56,6 +56,16 @@ enum class EWidgetGeometryMode : uint8 Cylinder }; +UENUM(BlueprintType) +enum class EWindowVisibility : uint8 +{ + /** The window visibility is Visible */ + Visible, + + /** The window visibility is SelfHitTestInvisible */ + SelfHitTestInvisible +}; + /** * The widget component provides a surface in the 3D environment on which to render widgets normally rendered to the screen. @@ -73,6 +83,10 @@ class UMG_API UWidgetComponent : public UMeshComponent GENERATED_UCLASS_BODY() public: + //UObject interface + virtual void Serialize(FArchive& Ar) override; + //~ End UObject Interface + /** UActorComponent Interface */ virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; @@ -273,7 +287,7 @@ public: /** */ UFUNCTION(BlueprintCallable, Category = UserInterface) - void SetDrawAtDesiredSize(bool InbDrawAtDesiredSize) { bDrawAtDesiredSize = InbDrawAtDesiredSize; } + void SetDrawAtDesiredSize(bool bInDrawAtDesiredSize) { bDrawAtDesiredSize = bInDrawAtDesiredSize; } /** */ UFUNCTION(BlueprintCallable, Category = UserInterface) @@ -281,7 +295,7 @@ public: /** */ UFUNCTION(BlueprintCallable, Category = UserInterface) - void SetRedrawTime(float bInRedrawTime) { RedrawTime = bInRedrawTime; } + void SetRedrawTime(float InRedrawTime) { RedrawTime = InRedrawTime; } /** Get the fake window we create for widgets displayed in the world. */ TSharedPtr< SWindow > GetVirtualWindow() const; @@ -344,10 +358,18 @@ public: /** @see bWindowFocusable */ UFUNCTION(BlueprintCallable, Category = UserInterface) - void SetWindowFocusable(bool bInWindowFocusable) + void SetWindowFocusable(bool bInWindowFocusable); + + /** Gets the visibility of the virtual window created to host the widget focusable. */ + UFUNCTION(BlueprintCallable, Category = UserInterface) + EWindowVisibility GetWindowVisiblility() const { - bWindowFocusable = bInWindowFocusable; - }; + return WindowVisibility; + } + + /** Sets the visibility of the virtual window created to host the widget focusable. */ + UFUNCTION(BlueprintCallable, Category = UserInterface) + void SetWindowVisibility(EWindowVisibility InVisibility); protected: void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld); @@ -450,6 +472,10 @@ protected: UPROPERTY(EditAnywhere, Category=Interaction) bool bWindowFocusable; + /** The visibility of the virtual window created to host the widget */ + UPROPERTY(EditAnywhere, Category = Interaction) + EWindowVisibility WindowVisibility; + /** * Widget components that appear in the world will be gamma corrected by the 3D renderer. * In some cases, widget components are blitted directly into the backbuffer, in which case gamma correction should be enabled. @@ -572,6 +598,11 @@ protected: /** Helper class for drawing widgets to a render target. */ class FWidgetRenderer* WidgetRenderer; + +private: + static EVisibility ConvertWindowVisibilityToVisibility(EWindowVisibility visibility); + /** Set to true after a draw of an empty component.*/ + bool bRenderCleared; }; USTRUCT() diff --git a/Engine/Source/Runtime/Unix/UnixCommonStartup/Private/UnixCommonStartup.cpp b/Engine/Source/Runtime/Unix/UnixCommonStartup/Private/UnixCommonStartup.cpp index 828d0f1ec5bc..831f27dad962 100644 --- a/Engine/Source/Runtime/Unix/UnixCommonStartup/Private/UnixCommonStartup.cpp +++ b/Engine/Source/Runtime/Unix/UnixCommonStartup/Private/UnixCommonStartup.cpp @@ -18,11 +18,6 @@ static FString GSavedCommandLine; extern int32 GuardedMain( const TCHAR* CmdLine ); extern void LaunchStaticShutdownAfterError(); -#if WITH_ENGINE -// see comment in LaunchUnix.cpp for details why it is done this way -extern void LaunchUnix_FEngineLoop_AppExit(); -#endif // WITH_ENGINE - /** * Game-specific crash reporter */ @@ -166,7 +161,7 @@ static bool IncreasePerProcessLimits() return true; } -int CommonUnixMain(int argc, char *argv[], int (*RealMain)(const TCHAR * CommandLine)) +int CommonUnixMain(int argc, char *argv[], int (*RealMain)(const TCHAR * CommandLine), void (*AppExitCallback)()) { FString EarlyInitCommandLine; FPlatformApplicationMisc::EarlyUnixInitialization(EarlyInitCommandLine); @@ -246,9 +241,17 @@ int CommonUnixMain(int argc, char *argv[], int (*RealMain)(const TCHAR * Command } // Final shut down. -#if WITH_ENGINE - LaunchUnix_FEngineLoop_AppExit(); -#endif // WITH_ENGINE + if (AppExitCallback) + { + // Workaround function to avoid circular dependencies between Launch and CommonUnixStartup modules. + + // Other platforms call FEngineLoop::AppExit() in their main() (removed by preprocessor if compiled without engine), + // but on Unix we want to share a common main() in CommonUnixStartup module, so not just the engine but all the programs + // could share this logic. Unfortunately, AppExit() practice breaks this nice approach since FEngineLoop cannot be moved outside of + // Launch module without making too many changes. Hence CommonUnixMain will call it through this function if provided. + + AppExitCallback(); + } // check if a specific return code has been set uint8 OverriddenErrorLevel = 0; diff --git a/Engine/Source/Runtime/Unix/UnixCommonStartup/Public/UnixCommonStartup.h b/Engine/Source/Runtime/Unix/UnixCommonStartup/Public/UnixCommonStartup.h index 1e253a464e16..dd6e0fa3920c 100644 --- a/Engine/Source/Runtime/Unix/UnixCommonStartup/Public/UnixCommonStartup.h +++ b/Engine/Source/Runtime/Unix/UnixCommonStartup/Public/UnixCommonStartup.h @@ -9,6 +9,7 @@ * @param argc - number of arguments in argv[] * @param argv - array of arguments * @param RealMain - the next main routine to call in chain + * @param AppExitCallback - workaround for Launch module that needs to call FEngineLoop::AppExit() at certain point * @return error code to return to the OS */ -int UNIXCOMMONSTARTUP_API CommonUnixMain(int argc, char *argv[], int (*RealMain)(const TCHAR * CommandLine)); +int UNIXCOMMONSTARTUP_API CommonUnixMain(int argc, char *argv[], int (*RealMain)(const TCHAR * CommandLine), void (*AppExitCallback)() = nullptr); diff --git a/Engine/Source/Runtime/Unix/UnixCommonStartup/UnixCommonStartup.Build.cs b/Engine/Source/Runtime/Unix/UnixCommonStartup/UnixCommonStartup.Build.cs index 9de19eea2135..3508ba4f34c3 100644 --- a/Engine/Source/Runtime/Unix/UnixCommonStartup/UnixCommonStartup.Build.cs +++ b/Engine/Source/Runtime/Unix/UnixCommonStartup/UnixCommonStartup.Build.cs @@ -8,6 +8,7 @@ public class UnixCommonStartup : ModuleRules public UnixCommonStartup(ReadOnlyTargetRules Target) : base(Target) { PrivateDependencyModuleNames.Add("Core"); + PrivateDependencyModuleNames.Add("ApplicationCore"); PrivateDependencyModuleNames.Add("Projects"); PrivateDependencyModuleNames.Add("Slate"); diff --git a/Engine/Source/Runtime/VulkanRHI/VulkanRHI.Build.cs b/Engine/Source/Runtime/VulkanRHI/VulkanRHI.Build.cs index fea552aa4f6c..2fbbe77d0563 100644 --- a/Engine/Source/Runtime/VulkanRHI/VulkanRHI.Build.cs +++ b/Engine/Source/Runtime/VulkanRHI/VulkanRHI.Build.cs @@ -44,6 +44,7 @@ public class VulkanRHI : ModuleRules { if (Target.Platform == UnrealTargetPlatform.Linux) { + PrivateDependencyModuleNames.Add("ApplicationCore"); AddEngineThirdPartyPrivateStaticDependencies(Target, "SDL2"); string VulkanSDKPath = Environment.GetEnvironmentVariable("VULKAN_SDK"); diff --git a/Engine/Source/Runtime/Windows/D3D11RHI/Private/D3D11Viewport.cpp b/Engine/Source/Runtime/Windows/D3D11RHI/Private/D3D11Viewport.cpp index d99c2fb05079..98928b633376 100644 --- a/Engine/Source/Runtime/Windows/D3D11RHI/Private/D3D11Viewport.cpp +++ b/Engine/Source/Runtime/Windows/D3D11RHI/Private/D3D11Viewport.cpp @@ -196,7 +196,10 @@ FD3D11Viewport::~FD3D11Viewport() // If the swap chain was in fullscreen mode, switch back to windowed before releasing the swap chain. // DXGI throws an error otherwise. - VERIFYD3D11RESULT_EX(SwapChain->SetFullscreenState(false,NULL), D3DRHI->GetDevice()); + if (SwapChain) + { + VERIFYD3D11RESULT_EX(SwapChain->SetFullscreenState(false, NULL), D3DRHI->GetDevice()); + } FrameSyncEvent.ReleaseResource(); diff --git a/Engine/Source/Runtime/XmlParser/Private/XmlFile.cpp b/Engine/Source/Runtime/XmlParser/Private/XmlFile.cpp index 97b242a723fa..c6ff0779f4c2 100644 --- a/Engine/Source/Runtime/XmlParser/Private/XmlFile.cpp +++ b/Engine/Source/Runtime/XmlParser/Private/XmlFile.cpp @@ -496,6 +496,7 @@ FXmlNode* FXmlFile::CreateNodeRecursive(const TArray& Tokens, int32 Sta // - Continue parsing until for self is found // - Return own constructed node (and index of next starting point + const int32 RecursiveStartIndex = StartIndex; int32 SavedIndex = StartIndex; // Get the tag & any attributes @@ -608,7 +609,11 @@ FXmlNode* FXmlFile::CreateNodeRecursive(const TArray& Tokens, int32 Sta if(Tokens[i] == TEXT("<")) { // Recursively enter function creating a child at the new tag - FXmlNode* Child = CreateNodeRecursive(Tokens, i, &SavedIndex); + FXmlNode* Child = nullptr; + if (i > RecursiveStartIndex) + { + Child = CreateNodeRecursive(Tokens, i, &SavedIndex); + } // Save child to parent if(Child != nullptr) diff --git a/Engine/Source/ThirdParty/Python/Python.Build.cs b/Engine/Source/ThirdParty/Python/Python.Build.cs index 10ab70d69f48..86d1decf14ea 100644 --- a/Engine/Source/ThirdParty/Python/Python.Build.cs +++ b/Engine/Source/ThirdParty/Python/Python.Build.cs @@ -54,7 +54,7 @@ public class Python : ModuleRules } ); } - else if (Target.Platform == UnrealTargetPlatform.Linux) + else if (Target.IsInPlatformGroup(UnrealPlatformGroup.Unix)) { if (Target.Architecture.StartsWith("x86_64")) { @@ -70,6 +70,7 @@ public class Python : ModuleRules }, Path.Combine(PythonSourceTPSDir, PlatformDir, "lib"), "libpython2.7.a"), }); + PublicAdditionalLibraries.Add("util"); // part of libc } } diff --git a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/SDL_video.c b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/SDL_video.c index cadc82333034..915146468c50 100644 --- a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/SDL_video.c +++ b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/SDL_video.c @@ -4166,11 +4166,13 @@ void SDL_Vulkan_UnloadLibrary(void) SDL_bool SDL_Vulkan_GetInstanceExtensions(SDL_Window *window, unsigned *count, const char **names) { - CHECK_WINDOW_MAGIC(window, SDL_FALSE); + if (window) { + CHECK_WINDOW_MAGIC(window, SDL_FALSE); - if (!(window->flags & SDL_WINDOW_VULKAN)) { - SDL_SetError(NOT_A_VULKAN_WINDOW); - return SDL_FALSE; + if (!(window->flags & SDL_WINDOW_VULKAN)) { + SDL_SetError(NOT_A_VULKAN_WINDOW); + return SDL_FALSE; + } } if (!count) { diff --git a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvideo.c b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvideo.c index bd063aa6f2ab..38c5f905e3d2 100644 --- a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvideo.c +++ b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvideo.c @@ -198,6 +198,12 @@ Wayland_CreateDevice(int devindex) device->Vulkan_CreateSurface = Wayland_Vulkan_CreateSurface; #endif + /* EG BEGIN */ +#ifdef SDL_WITH_EPIC_EXTENSIONS + device->Vulkan_GetRequiredInstanceExtensions = Wayland_Vulkan_GetRequiredInstanceExtensions; +#endif /* SDL_WITH_EPIC_EXTENSIONS */ + /* EG END */ + device->free = Wayland_DeleteDevice; return device; diff --git a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.c b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.c index d67472cdc290..1317cc0cd6be 100644 --- a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.c +++ b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.c @@ -171,6 +171,33 @@ SDL_bool Wayland_Vulkan_CreateSurface(_THIS, return SDL_TRUE; } +/* EG BEGIN */ +#ifdef SDL_WITH_EPIC_EXTENSIONS +char** +Wayland_Vulkan_GetRequiredInstanceExtensions(_THIS, unsigned int* count) { + /** If we didn't allocated memory for the strings, let's do it now. */ + if(NULL == _this->vulkan_config.required_instance_extensions) { + size_t length; + _this->vulkan_config.required_instance_extensions = (char**)malloc(sizeof(char*) * 3); + + length = SDL_strlen(VK_KHR_SURFACE_EXTENSION_NAME) + 1; + _this->vulkan_config.required_instance_extensions[0] = (char*)malloc(sizeof(char) * length ); + SDL_strlcpy(_this->vulkan_config.required_instance_extensions[0], VK_KHR_SURFACE_EXTENSION_NAME, length); + + length = SDL_strlen(VK_KHR_XLIB_SURFACE_EXTENSION_NAME) + 1; + _this->vulkan_config.required_instance_extensions[1] = (char*)malloc(sizeof(char) * length ); + SDL_strlcpy(_this->vulkan_config.required_instance_extensions[1], VK_KHR_XLIB_SURFACE_EXTENSION_NAME, length); + + length = SDL_strlen(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME) + 1; + _this->vulkan_config.required_instance_extensions[2] = (char*)malloc(sizeof(char) * length ); + SDL_strlcpy(_this->vulkan_config.required_instance_extensions[2], VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, length); + } + *count = 3; + return _this->vulkan_config.required_instance_extensions; +} +#endif /* SDL_WITH_EPIC_EXTENSIONS */ +/* EG END */ + #endif /* vim: set ts=4 sw=4 expandtab: */ diff --git a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.h b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.h index 5ad3a466c12a..a393865884f6 100644 --- a/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.h +++ b/Engine/Source/ThirdParty/SDL2/SDL-gui-backend/src/video/wayland/SDL_waylandvulkan.h @@ -45,6 +45,12 @@ SDL_bool Wayland_Vulkan_CreateSurface(_THIS, VkInstance instance, VkSurfaceKHR *surface); +/* EG BEGIN */ +#ifdef SDL_WITH_EPIC_EXTENSIONS +extern char** Wayland_Vulkan_GetRequiredInstanceExtensions(_THIS, unsigned int* count); +#endif /* SDL_WITH_EPIC_EXTENSIONS */ +/* EG END */ + #endif #endif /* SDL_waylandvulkan_h_ */ diff --git a/Engine/Source/ThirdParty/libSampleRate/Public/samplerate.h b/Engine/Source/ThirdParty/libSampleRate/Public/samplerate.h index b97e3c3a35f2..1f6baeb2ba44 100644 --- a/Engine/Source/ThirdParty/libSampleRate/Public/samplerate.h +++ b/Engine/Source/ThirdParty/libSampleRate/Public/samplerate.h @@ -15,6 +15,8 @@ #ifdef PLATFORM_WINDOWS #if PLATFORM_WINDOWS #define SRC_EXPORT __declspec(dllexport) + #elif PLATFORM_MAC + #define SRC_EXPORT __attribute__((visibility("default"))) #else #define SRC_EXPORT #endif