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