// Copyright Epic Games, Inc. All Rights Reserved. #include "EditMeshPolygonsTool.h" #include "InteractiveToolManager.h" #include "ToolBuilderUtil.h" #include "CompGeom/PolygonTriangulation.h" #include "SegmentTypes.h" #include "DynamicMeshAttributeSet.h" #include "MeshNormals.h" #include "ToolSceneQueriesUtil.h" #include "Intersection/IntersectionUtil.h" #include "Transforms/MultiTransformer.h" #include "TransformTypes.h" #include "BaseBehaviors/SingleClickBehavior.h" #include "Util/ColorConstants.h" #include "Util/CompactMaps.h" #include "ToolSetupUtil.h" #include "Operations/MeshPlaneCut.h" #include "Selection/GroupTopologyStorableSelection.h" #include "Selections/MeshEdgeSelection.h" #include "Selections/MeshFaceSelection.h" #include "Selections/MeshConnectedComponents.h" #include "FaceGroupUtil.h" #include "DynamicMeshEditor.h" #include "DynamicMeshChangeTracker.h" #include "Changes/MeshChange.h" #include "MeshIndexUtil.h" #include "MeshRegionBoundaryLoops.h" #include "Operations/OffsetMeshRegion.h" #include "Operations/InsetMeshRegion.h" #include "Operations/SimpleHoleFiller.h" #include "MeshTransforms.h" #include "Algo/ForEach.h" #include "Async/ParallelFor.h" #include "Containers/BitArray.h" #include "Materials/MaterialInstanceDynamic.h" #include "Components/BrushComponent.h" #define LOCTEXT_NAMESPACE "UEditMeshPolygonsTool" /* * ToolBuilder */ UMeshSurfacePointTool* UEditMeshPolygonsToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const { UEditMeshPolygonsTool* EditPolygonsTool = NewObject(SceneState.ToolManager); if (bTriangleMode) { EditPolygonsTool->EnableTriangleMode(); } // This passes in nullptr if the stored selection is not the right type. The tool // will figure out whether the selection is still relevant. EditPolygonsTool->SetStoredToolSelection(Cast(SceneState.StoredToolSelection)); return EditPolygonsTool; } void UEditMeshPolygonsToolActionPropertySet::PostAction(EEditMeshPolygonsToolActions Action) { if (ParentTool.IsValid()) { ParentTool->RequestAction(Action); } } /* * Tool methods */ UEditMeshPolygonsTool::UEditMeshPolygonsTool() { SetToolDisplayName(LOCTEXT("EditMeshPolygonsToolName", "Edit PolyGroups Tool")); } void UEditMeshPolygonsTool::EnableTriangleMode() { check(DynamicMeshComponent == nullptr); // must not have been initialized! bTriangleMode = true; } void UEditMeshPolygonsTool::Setup() { UMeshSurfacePointTool::Setup(); // register click behavior USingleClickInputBehavior* ClickBehavior = NewObject(); ClickBehavior->Initialize(this); AddInputBehavior(ClickBehavior); // create dynamic mesh component to use for live preview DynamicMeshComponent = NewObject(ComponentTarget->GetOwnerActor(), "DynamicMesh"); DynamicMeshComponent->SetupAttachment(ComponentTarget->GetOwnerActor()->GetRootComponent()); DynamicMeshComponent->RegisterComponent(); DynamicMeshComponent->SetWorldTransform(ComponentTarget->GetWorldTransform()); WorldTransform = FTransform3d(DynamicMeshComponent->GetComponentTransform()); // set materials FComponentMaterialSet MaterialSet; ComponentTarget->GetMaterialSet(MaterialSet); for (int k = 0; k < MaterialSet.Materials.Num(); ++k) { DynamicMeshComponent->SetMaterial(k, MaterialSet.Materials[k]); } // configure secondary render material UMaterialInterface* SelectionMaterial = ToolSetupUtil::GetSelectionMaterial(FLinearColor::Yellow, GetToolManager()); if (SelectionMaterial != nullptr) { DynamicMeshComponent->SetSecondaryRenderMaterial(SelectionMaterial); } // enable secondary triangle buffers DynamicMeshComponent->EnableSecondaryTriangleBuffers( [this](const FDynamicMesh3* Mesh, int32 TriangleID) { return SelectionMechanic->GetActiveSelection().IsSelectedTriangle(Mesh, Topology.Get(), TriangleID); }); // dynamic mesh configuration settings DynamicMeshComponent->TangentsType = EDynamicMeshTangentCalcType::AutoCalculated; DynamicMeshComponent->InitializeMesh(ComponentTarget->GetMesh()); FMeshNormals::QuickComputeVertexNormals(*DynamicMeshComponent->GetMesh()); OnDynamicMeshComponentChangedHandle = DynamicMeshComponent->OnMeshChanged.Add( FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &UEditMeshPolygonsTool::OnDynamicMeshComponentChanged)); // add properties CommonProps = NewObject(this); CommonProps->RestoreProperties(this); AddToolPropertySource(CommonProps); CommonProps->WatchProperty(CommonProps->LocalFrameMode, [this](ELocalFrameMode) { UpdateMultiTransformerFrame(); }); CommonProps->WatchProperty(CommonProps->bLockRotation, [this](bool) { LockedTransfomerFrame = LastTransformerFrame; UpdateMultiTransformerFrame(); }); // We are going to SilentUpdate here because otherwise the Watches above will immediately fire (why??) // and cause UpdateMultiTransformerFrame() to be called for each, emitting two spurious Transform changes. CommonProps->SilentUpdateWatched(); // set up SelectionMechanic SelectionMechanic = NewObject(this); SelectionMechanic->Setup(this); SelectionMechanic->Properties->RestoreProperties(this); SelectionMechanic->OnSelectionChanged.AddUObject(this, &UEditMeshPolygonsTool::OnSelectionModifiedEvent); if (bTriangleMode) { SelectionMechanic->PolyEdgesRenderer.LineThickness = 1.0; } // initialize AABBTree MeshSpatial.SetMesh(DynamicMeshComponent->GetMesh()); PrecomputeTopology(); // Have to load selection after initializing the selection mechanic since we need to have // the topology built. if (IsStoredToolSelectionUsable(StoredToolSelection)) { SelectionMechanic->LoadStorableSelection(*StoredToolSelection); } bSelectionStateDirty = SelectionMechanic->HasSelection(); // Set UV Scale factor based on initial mesh bounds float BoundsMaxDim = DynamicMeshComponent->GetMesh()->GetBounds().MaxDim(); if (BoundsMaxDim > 0) { UVScaleFactor = 1.0 / BoundsMaxDim; } // hide input StaticMeshComponent ComponentTarget->SetOwnerVisibility(false); // init state flags flags bInDrag = false; MultiTransformer = NewObject(this); MultiTransformer->Setup(GetToolManager()->GetPairedGizmoManager(), GetToolManager()); MultiTransformer->OnTransformStarted.AddUObject(this, &UEditMeshPolygonsTool::OnMultiTransformerTransformBegin); MultiTransformer->OnTransformUpdated.AddUObject(this, &UEditMeshPolygonsTool::OnMultiTransformerTransformUpdate); MultiTransformer->OnTransformCompleted.AddUObject(this, &UEditMeshPolygonsTool::OnMultiTransformerTransformEnd); MultiTransformer->SetSnapToWorldGridSourceFunc([this]() { return CommonProps->bSnapToWorldGrid && GetToolManager()->GetContextQueriesAPI()->GetCurrentCoordinateSystem() == EToolContextCoordinateSystem::World; }); MultiTransformer->SetGizmoVisibility(false); if (bTriangleMode == false) { EditActions = NewObject(); EditActions->Initialize(this); AddToolPropertySource(EditActions); EditEdgeActions = NewObject(); EditEdgeActions->Initialize(this); AddToolPropertySource(EditEdgeActions); EditUVActions = NewObject(); EditUVActions->Initialize(this); AddToolPropertySource(EditUVActions); } else { EditActions_Triangles = NewObject(); EditActions_Triangles->Initialize(this); AddToolPropertySource(EditActions_Triangles); EditEdgeActions_Triangles = NewObject(); EditEdgeActions_Triangles->Initialize(this); AddToolPropertySource(EditEdgeActions_Triangles); } ExtrudeProperties = NewObject(); ExtrudeProperties->RestoreProperties(this); AddToolPropertySource(ExtrudeProperties); SetToolPropertySourceEnabled(ExtrudeProperties, false); ExtrudeProperties->WatchProperty(ExtrudeProperties->Direction, [this](EPolyEditExtrudeDirection){ RestartExtrude(); }); OffsetProperties = NewObject(); OffsetProperties->RestoreProperties(this); AddToolPropertySource(OffsetProperties); SetToolPropertySourceEnabled(OffsetProperties, false); InsetProperties = NewObject(); InsetProperties->RestoreProperties(this); AddToolPropertySource(InsetProperties); SetToolPropertySourceEnabled(InsetProperties, false); OutsetProperties = NewObject(); OutsetProperties->RestoreProperties(this); AddToolPropertySource(OutsetProperties); SetToolPropertySourceEnabled(OutsetProperties, false); CutProperties = NewObject(); CutProperties->RestoreProperties(this); AddToolPropertySource(CutProperties); SetToolPropertySourceEnabled(CutProperties, false); SetUVProperties = NewObject(); SetUVProperties->RestoreProperties(this); AddToolPropertySource(SetUVProperties); SetToolPropertySourceEnabled(SetUVProperties, false); if (bTriangleMode) { SetToolDisplayName(LOCTEXT("EditMeshTrianglesToolName", "Edit Triangles Tool")); GetToolManager()->DisplayMessage( LOCTEXT("OnStartEditMeshPolygonsTool_TriangleMode", "Select Triangles to edit mesh. Q to toggle Gizmo Orientation Lock."), EToolMessageLevel::UserNotification); } else { GetToolManager()->DisplayMessage( LOCTEXT("OnStartEditMeshPolygonsTool", "Select PolyGroups to edit mesh. Q to toggle Gizmo Orientation Lock."), EToolMessageLevel::UserNotification); } if (Topology->Groups.Num() < 2) { GetToolManager()->DisplayMessage( LOCTEXT("NoGroupsWarning", "This object has a single PolyGroup. Use the PolyGroups or Select Tool to assign PolyGroups."), EToolMessageLevel::UserWarning); } } bool UEditMeshPolygonsTool::IsStoredToolSelectionUsable(const UGroupTopologyStorableSelection* StoredSelection) { // TODO: We currently don't support persistent selection on volume brushes because // a conversion back to a brush involves a simplification step that may make the // same vids unrecoverable. Once we have persistence of dynamic meshes, this will // hopefully not become a problem, and this function (along with stored selection // identifying info) will change. return !Cast(ComponentTarget->GetOwnerComponent()) && StoredSelection && StoredSelection->IdentifyingInfo.TopologyType == (bTriangleMode ? UGroupTopologyStorableSelection::ETopologyType::FTriangleGroupTopology : UGroupTopologyStorableSelection::ETopologyType::FGroupTopology) && StoredSelection->IdentifyingInfo.ComponentTarget == ComponentTarget->GetOwnerComponent() && !StoredSelection->IsEmpty(); } void UEditMeshPolygonsTool::Shutdown(EToolShutdownType ShutdownType) { CommonProps->SaveProperties(this); ExtrudeProperties->SaveProperties(this); OffsetProperties->SaveProperties(this); InsetProperties->SaveProperties(this); CutProperties->SaveProperties(this); SetUVProperties->SaveProperties(this); SelectionMechanic->Properties->SaveProperties(this); MultiTransformer->Shutdown(); // We wait to shut down the selection mechanic in case we need to do work to store the selection. if (EditPreview != nullptr) { EditPreview->Disconnect(); EditPreview = nullptr; } if (DynamicMeshComponent != nullptr) { DynamicMeshComponent->OnMeshChanged.Remove(OnDynamicMeshComponentChangedHandle); ComponentTarget->SetOwnerVisibility(true); if (ShutdownType == EToolShutdownType::Accept) { UGroupTopologyStorableSelection* NewStoredToolSelection = nullptr; FCompactMaps CompactMaps; // Prep if we have a selection to store. We don't support storing selections for volumes // because the conversion will change vids. if (!SelectionMechanic->GetActiveSelection().IsEmpty() && !Cast(ComponentTarget->GetOwnerComponent())) { NewStoredToolSelection = NewObject(); NewStoredToolSelection->IdentifyingInfo.ComponentTarget = ComponentTarget->GetOwnerComponent(); NewStoredToolSelection->IdentifyingInfo.TopologyType = (bTriangleMode ? UGroupTopologyStorableSelection::ETopologyType::FTriangleGroupTopology : UGroupTopologyStorableSelection::ETopologyType::FGroupTopology); } // may need to compact the mesh if we did undo on a mesh edit, then vertices will be dense but compact checks will fail... if (bWasTopologyEdited) { // Store the compact maps if we have a selection that we need to update DynamicMeshComponent->GetMesh()->CompactInPlace(NewStoredToolSelection ? &CompactMaps : nullptr); } // Finish prepping the stored selection if (NewStoredToolSelection) { SelectionMechanic->GetStorableSelection(*NewStoredToolSelection, bWasTopologyEdited ? &CompactMaps : nullptr); } // this block bakes the modified DynamicMeshComponent back into the StaticMeshComponent inside an undo transaction GetToolManager()->BeginUndoTransaction(LOCTEXT("EditMeshPolygonsToolTransactionName", "Deform Mesh")); ComponentTarget->CommitMesh([=](const FPrimitiveComponentTarget::FCommitParams& CommitParams) { FConversionToMeshDescriptionOptions ConversionOptions; bool bModifiedTopology = (ModifiedTopologyCounter > 0); ConversionOptions.bSetPolyGroups = bModifiedTopology; DynamicMeshComponent->Bake(CommitParams.MeshDescription, bModifiedTopology, ConversionOptions); }); // The stored selection change should go into this transaction as well. // If we're keeping the same selection, we still need to store it back, though we could do it outside // the transaction if we wanted to (but no real reason to). We do want to keep the same UObject if // the selection is the same though, since it's probably getting kept alive by the undo stack anyway. if (StoredToolSelection && NewStoredToolSelection && *StoredToolSelection == *NewStoredToolSelection) { GetToolManager()->RequestToolSelectionStore(StoredToolSelection); } else { // If NewStoredToolSelection is empty, this will clear the stored selection GetToolManager()->RequestToolSelectionStore(NewStoredToolSelection); } GetToolManager()->EndUndoTransaction(); } DynamicMeshComponent->UnregisterComponent(); DynamicMeshComponent->DestroyComponent(); DynamicMeshComponent = nullptr; } // The seleciton mechanic shutdown has to happen after (potentially) saving selection above SelectionMechanic->Shutdown(); } void UEditMeshPolygonsTool::RegisterActions(FInteractiveToolActionSet& ActionSet) { ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 2, TEXT("ToggleLockRotation"), LOCTEXT("ToggleLockRotationUIName", "Lock Rotation"), LOCTEXT("ToggleLockRotationTooltip", "Toggle Frame Rotation Lock on and off"), EModifierKey::None, EKeys::Q, [this]() { CommonProps->bLockRotation = !CommonProps->bLockRotation; }); } void UEditMeshPolygonsTool::RequestAction(EEditMeshPolygonsToolActions ActionType) { if (PendingAction != EEditMeshPolygonsToolActions::NoAction) { return; } PendingAction = ActionType; } FDynamicMeshAABBTree3& UEditMeshPolygonsTool::GetSpatial() { if (bSpatialDirty) { MeshSpatial.Build(); bSpatialDirty = false; } return MeshSpatial; } bool UEditMeshPolygonsTool::HitTest(const FRay& WorldRay, FHitResult& OutHit) { // If we're in the middle of an action, take the click (to finish an inset, etc). if (CurrentToolMode != ECurrentToolMode::TransformSelection) { OutHit.Distance = 100.0; OutHit.ImpactPoint = WorldRay.PointAt(100.0); return true; } // The selection mechanic and gizmo will take care of the TransformSelection state. return false; } FInputRayHit UEditMeshPolygonsTool::IsHitByClick(const FInputDeviceRay& ClickPos) { FHitResult OutHit; if (HitTest(ClickPos.WorldRay, OutHit)) { return FInputRayHit(OutHit.Distance); } return FInputRayHit(); // bHit is set to false. } void UEditMeshPolygonsTool::OnClicked(const FInputDeviceRay& ClickPos) { if ( CurrentToolMode == ECurrentToolMode::ExtrudeSelection ) { ApplyExtrude(false); return; } if (CurrentToolMode == ECurrentToolMode::OffsetSelection) { ApplyExtrude(true); return; } else if (CurrentToolMode == ECurrentToolMode::InsetSelection || CurrentToolMode == ECurrentToolMode::OutsetSelection) { ApplyInset(CurrentToolMode == ECurrentToolMode::OutsetSelection); return; } else if (CurrentToolMode == ECurrentToolMode::CutSelection) { if (SurfacePathMechanic->TryAddPointFromRay(ClickPos.WorldRay)) { if (SurfacePathMechanic->IsDone()) { ApplyCutFaces(); } } return; } else if (CurrentToolMode == ECurrentToolMode::SetUVs) { if (SurfacePathMechanic->TryAddPointFromRay(ClickPos.WorldRay)) { if (SurfacePathMechanic->IsDone()) { ApplySetUVs(); } } return; } ensureMsgf(CurrentToolMode != ECurrentToolMode::TransformSelection, TEXT("EditMeshPolygonsTool: " "Should not receive click requests in transform mode- they should have been handled by " "selection mechanic or gizmo.")); } void UEditMeshPolygonsTool::UpdateMultiTransformerFrame(const FFrame3d* UseFrame) { FFrame3d SetFrame = LastTransformerFrame; if (UseFrame == nullptr) { if (CommonProps->LocalFrameMode == ELocalFrameMode::FromGeometry) { SetFrame = LastGeometryFrame; } else { SetFrame = FFrame3d(LastGeometryFrame.Origin, WorldTransform.GetRotation()); } } else { SetFrame = *UseFrame; } if (CommonProps->bLockRotation) { SetFrame.Rotation = LockedTransfomerFrame.Rotation; } LastTransformerFrame = SetFrame; //MultiTransformer->UpdateGizmoPositionFromWorldFrame(SetFrame, true); MultiTransformer->InitializeGizmoPositionFromWorldFrame(SetFrame, true); } void UEditMeshPolygonsTool::OnSelectionModifiedEvent() { FVector3d LocalLastHitPosition, LocalLastHitNormal; SelectionMechanic->GetClickedHitPosition(LocalLastHitPosition, LocalLastHitNormal); FFrame3d LocalFrame(LocalLastHitPosition, LocalLastHitNormal); LastGeometryFrame = SelectionMechanic->GetSelectionFrame(true, &LocalFrame); UpdateMultiTransformerFrame(); bSelectionStateDirty = true; } FInputRayHit UEditMeshPolygonsTool::CanBeginClickDragSequence(const FInputDeviceRay& PressPos) { // disable this for now return FInputRayHit(); //return UMeshSurfacePointTool::CanBeginClickDragSequence(PressPos); } void UEditMeshPolygonsTool::OnBeginDrag(const FRay& WorldRay) { } void UEditMeshPolygonsTool::OnUpdateDrag(const FRay& Ray) { check(false); } void UEditMeshPolygonsTool::OnEndDrag(const FRay& Ray) { check(false); } void UEditMeshPolygonsTool::OnMultiTransformerTransformBegin() { SelectionMechanic->ClearHighlight(); UpdateDeformerFromSelection( SelectionMechanic->GetActiveSelection() ); InitialGizmoFrame = MultiTransformer->GetCurrentGizmoFrame(); InitialGizmoScale = MultiTransformer->GetCurrentGizmoScale(); BeginChange(); } void UEditMeshPolygonsTool::OnMultiTransformerTransformUpdate() { if (MultiTransformer->InGizmoEdit()) { CacheUpdate_Gizmo(); } } void UEditMeshPolygonsTool::OnMultiTransformerTransformEnd() { bGizmoUpdatePending = false; bSpatialDirty = true; SelectionMechanic->NotifyMeshChanged(false); MultiTransformer->ResetScale(); // close change record EndChange(); } bool UEditMeshPolygonsTool::OnUpdateHover(const FInputDeviceRay& DevicePos) { if (CurrentToolMode == ECurrentToolMode::ExtrudeSelection) { ExtrudeHeightMechanic->UpdateCurrentDistance(DevicePos.WorldRay); bPreviewUpdatePending = true; return true; } else if (CurrentToolMode == ECurrentToolMode::OffsetSelection) { ExtrudeHeightMechanic->UpdateCurrentDistance(DevicePos.WorldRay); bPreviewUpdatePending = true; return true; } else if (CurrentToolMode == ECurrentToolMode::InsetSelection || CurrentToolMode == ECurrentToolMode::OutsetSelection) { CurveDistMechanic->UpdateCurrentDistance(DevicePos.WorldRay); bPreviewUpdatePending = true; return true; } else if (CurrentToolMode == ECurrentToolMode::CutSelection) { SurfacePathMechanic->UpdatePreviewPoint(DevicePos.WorldRay); return true; } else if (CurrentToolMode == ECurrentToolMode::SetUVs) { SurfacePathMechanic->UpdatePreviewPoint(DevicePos.WorldRay); bPreviewUpdatePending = true; return true; } ensureMsgf(CurrentToolMode != ECurrentToolMode::TransformSelection, TEXT("EditMeshPolygonsTool: " "Should not receive hover requests in transform mode- they should have been handled by " "selection mechanic or gizmo.")); return true; } void UEditMeshPolygonsTool::OnEndHover() { SelectionMechanic->ClearHighlight(); } void UEditMeshPolygonsTool::UpdateDeformerFromSelection(const FGroupTopologySelection& Selection) { //Determine which of the following (corners, edges or faces) has been selected by counting the associated feature's IDs if (Selection.SelectedCornerIDs.Num() > 0) { //Add all the the Corner's adjacent poly-groups (NbrGroups) to the ongoing array of groups. LinearDeformer.SetActiveHandleCorners(Selection.SelectedCornerIDs.Array()); } else if (Selection.SelectedEdgeIDs.Num() > 0) { //Add all the the edge's adjacent poly-groups (NbrGroups) to the ongoing array of groups. LinearDeformer.SetActiveHandleEdges(Selection.SelectedEdgeIDs.Array()); } else if (Selection.SelectedGroupIDs.Num() > 0) { LinearDeformer.SetActiveHandleFaces(Selection.SelectedGroupIDs.Array()); } } void UEditMeshPolygonsTool::CacheUpdate_Gizmo() { LastUpdateGizmoFrame = MultiTransformer->GetCurrentGizmoFrame(); LastUpdateGizmoScale = MultiTransformer->GetCurrentGizmoScale(); GetToolManager()->PostInvalidation(); bGizmoUpdatePending = true; } void UEditMeshPolygonsTool::ComputeUpdate_Gizmo() { if (SelectionMechanic->HasSelection() == false || bGizmoUpdatePending == false) { return; } bGizmoUpdatePending = false; FFrame3d CurFrame = LastUpdateGizmoFrame; FVector3d CurScale = LastUpdateGizmoScale; FVector3d TranslationDelta = CurFrame.Origin - InitialGizmoFrame.Origin; FQuaterniond RotateDelta = CurFrame.Rotation - InitialGizmoFrame.Rotation; FVector3d CurScaleDelta = CurScale - InitialGizmoScale; FVector3d LocalTranslation = WorldTransform.InverseTransformVector((FVector)TranslationDelta); FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); if (TranslationDelta.SquaredLength() > 0.0001 || RotateDelta.SquaredLength() > 0.0001 || CurScaleDelta.SquaredLength() > 0.0001) { LinearDeformer.UpdateSolution(Mesh, [&](FDynamicMesh3* TargetMesh, int VertIdx) { FVector3d PosLocal = TargetMesh->GetVertex(VertIdx); FVector3d PosWorld = WorldTransform.TransformPosition(PosLocal); FVector3d PosGizmo = InitialGizmoFrame.ToFramePoint(PosWorld); PosGizmo = CurScale * PosGizmo; FVector3d NewPosWorld = CurFrame.FromFramePoint(PosGizmo); FVector3d NewPosLocal = WorldTransform.InverseTransformPosition(NewPosWorld); return NewPosLocal; }); } else { // Reset mesh to initial positions. LinearDeformer.ClearSolution(Mesh); } DynamicMeshComponent->FastNotifyPositionsUpdated(true); GetToolManager()->PostInvalidation(); } void UEditMeshPolygonsTool::OnTick(float DeltaTime) { MultiTransformer->Tick(DeltaTime); if (bGizmoUpdatePending) { ComputeUpdate_Gizmo(); } if (bSelectionStateDirty) { // update color highlights DynamicMeshComponent->FastNotifySecondaryTrianglesChanged(); if (SelectionMechanic->HasSelection()) { MultiTransformer->SetGizmoVisibility(true); // update frame because we might be here due to an undo event/etc, rather than an explicit selection change LastGeometryFrame = SelectionMechanic->GetSelectionFrame(true, &LastGeometryFrame); UpdateMultiTransformerFrame(); } else { MultiTransformer->SetGizmoVisibility(false); } bSelectionStateDirty = false; } if (PendingAction != EEditMeshPolygonsToolActions::NoAction) { CancelMeshEditChange(); if (PendingAction == EEditMeshPolygonsToolActions::Extrude || PendingAction == EEditMeshPolygonsToolActions::Offset) { GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentOperationTimestamp), LOCTEXT("PolyMeshEditBeginExtrude", "Extrude")); BeginExtrude( (PendingAction == EEditMeshPolygonsToolActions::Offset) ); } else if (PendingAction == EEditMeshPolygonsToolActions::Inset) { GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentOperationTimestamp), LOCTEXT("PolyMeshEditBeginInset", "Begin Inset")); BeginInset(false); } else if (PendingAction == EEditMeshPolygonsToolActions::Outset) { GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentOperationTimestamp), LOCTEXT("PolyMeshEditBeginOutset", "Begin Outset")); BeginInset(true); } else if (PendingAction == EEditMeshPolygonsToolActions::CutFaces) { GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentOperationTimestamp), LOCTEXT("PolyMeshEditBeginCutFaces", "Cut Faces")); BeginCutFaces(); } else if (PendingAction == EEditMeshPolygonsToolActions::PlanarProjectionUV) { GetToolManager()->EmitObjectChange(this, MakeUnique(CurrentOperationTimestamp), LOCTEXT("PolyMeshEditBeginUVPlanarProjection", "Set UVs")); BeginSetUVs(); } else if (PendingAction == EEditMeshPolygonsToolActions::Merge) { ApplyMerge(); } else if (PendingAction == EEditMeshPolygonsToolActions::Delete) { ApplyDelete(); } else if (PendingAction == EEditMeshPolygonsToolActions::RecalculateNormals) { ApplyRecalcNormals(); } else if (PendingAction == EEditMeshPolygonsToolActions::FlipNormals) { ApplyFlipNormals(); } else if (PendingAction == EEditMeshPolygonsToolActions::CollapseEdge) { ApplyCollapseEdge(); } else if (PendingAction == EEditMeshPolygonsToolActions::WeldEdges) { ApplyWeldEdges(); } else if (PendingAction == EEditMeshPolygonsToolActions::StraightenEdge) { ApplyStraightenEdges(); } else if (PendingAction == EEditMeshPolygonsToolActions::FillHole) { ApplyFillHole(); } else if (PendingAction == EEditMeshPolygonsToolActions::Retriangulate) { ApplyRetriangulate(); } else if (PendingAction == EEditMeshPolygonsToolActions::Decompose) { ApplyDecompose(); } else if (PendingAction == EEditMeshPolygonsToolActions::Disconnect) { ApplyDisconnect(); } else if (PendingAction == EEditMeshPolygonsToolActions::PokeSingleFace) { ApplyPokeSingleFace(); } else if (PendingAction == EEditMeshPolygonsToolActions::SplitSingleEdge) { ApplySplitSingleEdge(); } else if (PendingAction == EEditMeshPolygonsToolActions::CollapseSingleEdge) { ApplyCollapseSingleEdge(); } else if (PendingAction == EEditMeshPolygonsToolActions::FlipSingleEdge) { ApplyFlipSingleEdge(); } PendingAction = EEditMeshPolygonsToolActions::NoAction; } // todo: convert to ValueWatcher if (CurrentToolMode == ECurrentToolMode::SetUVs) { EPreviewMaterialType WantMaterial = (SetUVProperties->bShowMaterial) ? EPreviewMaterialType::SourceMaterials : EPreviewMaterialType::UVMaterial; if (CurrentPreviewMaterial != WantMaterial) { UpdateEditPreviewMaterials(WantMaterial); } } if (bPreviewUpdatePending) { if (CurrentToolMode == ECurrentToolMode::ExtrudeSelection) { EditPreview->UpdateExtrudeType(ExtrudeHeightMechanic->CurrentHeight); } else if (CurrentToolMode == ECurrentToolMode::OffsetSelection) { if (OffsetProperties->bUseFaceNormals) { EditPreview->UpdateExtrudeType_FaceNormalAvg(ExtrudeHeightMechanic->CurrentHeight); } else { EditPreview->UpdateExtrudeType(ExtrudeHeightMechanic->CurrentHeight, true); } } else if (CurrentToolMode == ECurrentToolMode::InsetSelection || CurrentToolMode == ECurrentToolMode::OutsetSelection) { bool bOutset = (CurrentToolMode == ECurrentToolMode::OutsetSelection); double Sign = bOutset ? -1.0 : 1.0; bool bReproject = (bOutset) ? false : InsetProperties->bReproject; double Softness = (bOutset) ? OutsetProperties->Softness : InsetProperties->Softness; bool bBoundaryOnly = (bOutset) ? OutsetProperties->bBoundaryOnly : InsetProperties->bBoundaryOnly; double AreaCorrection = (bOutset) ? OutsetProperties->AreaScale : InsetProperties->AreaScale; EditPreview->UpdateInsetType(Sign* CurveDistMechanic->CurrentDistance, bReproject, Softness, AreaCorrection, bBoundaryOnly); } else if (CurrentToolMode == ECurrentToolMode::SetUVs) { UpdateSetUVS(); } bPreviewUpdatePending = false; } } void UEditMeshPolygonsTool::PrecomputeTopology() { FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); Topology = (bTriangleMode) ? MakeUnique(Mesh, false) : MakeUnique(Mesh, false); Topology->RebuildTopology(); // update selection mechanic SelectionMechanic->Initialize(DynamicMeshComponent, Topology.Get(), [this]() { return &GetSpatial(); } ); LinearDeformer.Initialize(Mesh, Topology.Get()); } void UEditMeshPolygonsTool::Render(IToolsContextRenderAPI* RenderAPI) { GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState); DynamicMeshComponent->bExplicitShowWireframe = CommonProps->bShowWireframe; SelectionMechanic->Render(RenderAPI); if (ExtrudeHeightMechanic != nullptr) { ExtrudeHeightMechanic->Render(RenderAPI); } if (CurveDistMechanic != nullptr) { CurveDistMechanic->Render(RenderAPI); } if (SurfacePathMechanic != nullptr) { SurfacePathMechanic->Render(RenderAPI); } } void UEditMeshPolygonsTool::DrawHUD(FCanvas* Canvas, IToolsContextRenderAPI* RenderAPI) { SelectionMechanic->DrawHUD(Canvas, RenderAPI); } // // Change Tracking // void UEditMeshPolygonsTool::UpdateChangeFromROI(bool bFinal) { if (ActiveVertexChange == nullptr) { return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); ActiveVertexChange->SaveVertices(Mesh, LinearDeformer.GetModifiedVertices(), !bFinal); ActiveVertexChange->SaveOverlayNormals(Mesh, LinearDeformer.GetModifiedOverlayNormals(), !bFinal); } void UEditMeshPolygonsTool::BeginChange() { if (ActiveVertexChange == nullptr) { ActiveVertexChange = new FMeshVertexChangeBuilder(EMeshVertexChangeComponents::VertexPositions | EMeshVertexChangeComponents::OverlayNormals); UpdateChangeFromROI(false); } } void UEditMeshPolygonsTool::EndChange() { if (ActiveVertexChange != nullptr) { UpdateChangeFromROI(true); GetToolManager()->EmitObjectChange(DynamicMeshComponent, MoveTemp(ActiveVertexChange->Change), LOCTEXT("PolyMeshDeformationChange", "PolyMesh Edit")); } delete ActiveVertexChange; ActiveVertexChange = nullptr; CurrentOperationTimestamp++; } void UEditMeshPolygonsTool::OnDynamicMeshComponentChanged() { bSpatialDirty = true; SelectionMechanic->NotifyMeshChanged(false); } void UEditMeshPolygonsTool::AfterTopologyEdit() { bSpatialDirty = true; bWasTopologyEdited = true; SelectionMechanic->NotifyMeshChanged(true); DynamicMeshComponent->NotifyMeshUpdated(); MeshSpatial.SetMesh(DynamicMeshComponent->GetMesh(), true); PrecomputeTopology(); } void UEditMeshPolygonsTool::ApplyPlaneCut() { FFrame3d PlaneFrame; FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FMeshPlaneCut Cut(Mesh, PlaneFrame.Origin, PlaneFrame.Z()); Cut.UVScaleFactor = UVScaleFactor; FMeshEdgeSelection Edges(Mesh); const FGroupTopologySelection& ActiveSelection = SelectionMechanic->GetActiveSelection(); if (ActiveSelection.SelectedGroupIDs.Num() > 0) { for (int32 GroupID : ActiveSelection.SelectedGroupIDs) { Edges.SelectTriangleEdges( Topology->GetGroupTriangles(GroupID) ); } Cut.EdgeFilterFunc = [&](int EdgeID) { return Edges.IsSelected(EdgeID); }; } Cut.SplitEdgesOnly(true); DynamicMeshComponent->NotifyMeshUpdated(); MeshSpatial.SetMesh(DynamicMeshComponent->GetMesh(), true); PrecomputeTopology(); } void UEditMeshPolygonsTool::BeginExtrude(bool bIsNormalOffset) { FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); if (bIsNormalOffset) { // yikes... } if (BeginMeshFaceEditChangeWithPreview() == false) { return; } ActiveSelectionFrameWorld.AlignAxis(2, GetExtrudeDirection()); EditPreview->InitializeExtrudeType(Mesh, ActiveTriangleSelection, ActiveSelectionFrameWorld.Z(), &WorldTransform, true); // move world extrude frame to point on surface ActiveSelectionFrameWorld.Origin = EditPreview->GetInitialPatchMeshSpatial().FindNearestPoint(ActiveSelectionFrameWorld.Origin); // make inifinite-extent hit-test mesh FDynamicMesh3 ExtrudeHitTargetMesh; EditPreview->MakeExtrudeTypeHitTargetMesh(ExtrudeHitTargetMesh); ExtrudeHeightMechanic = NewObject(this); ExtrudeHeightMechanic->Setup(this); ExtrudeHeightMechanic->WorldHitQueryFunc = [this](const FRay& WorldRay, FHitResult& HitResult) { return ToolSceneQueriesUtil::FindNearestVisibleObjectHit(DynamicMeshComponent->GetWorld(), HitResult, WorldRay); }; ExtrudeHeightMechanic->WorldPointSnapFunc = [this](const FVector3d& WorldPos, FVector3d& SnapPos) { return CommonProps->bSnapToWorldGrid && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos); }; ExtrudeHeightMechanic->CurrentHeight = 1.0f; // initialize to something non-zero...prob should be based on polygon bounds maybe? ExtrudeHeightMechanic->Initialize(MoveTemp(ExtrudeHitTargetMesh), ActiveSelectionFrameWorld, true); CurrentToolMode = (bIsNormalOffset) ? ECurrentToolMode::OffsetSelection : ECurrentToolMode::ExtrudeSelection; if (bIsNormalOffset == false) { SetToolPropertySourceEnabled(ExtrudeProperties, true); } else { SetToolPropertySourceEnabled(OffsetProperties, true); } SetActionButtonPanelsVisible(false); } void UEditMeshPolygonsTool::ApplyExtrude(bool bIsOffset) { check(ExtrudeHeightMechanic != nullptr && EditPreview != nullptr); FVector3d ExtrudeDir = WorldTransform.InverseTransformVector(ActiveSelectionFrameWorld.Z()); double ExtrudeDist = ExtrudeHeightMechanic->CurrentHeight; FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FOffsetMeshRegion Extruder(Mesh); Extruder.UVScaleFactor = UVScaleFactor; Extruder.Triangles = ActiveTriangleSelection; TSet TriangleSet(ActiveTriangleSelection); Extruder.OffsetPositionFunc = [&](const FVector3d& Pos, const FVector3f& Normal, int32 VertexID) { return Pos + ExtrudeDist * (bIsOffset ? (FVector3d)Normal : ExtrudeDir); }; Extruder.bIsPositiveOffset = (ExtrudeDist > 0); Extruder.bUseFaceNormals = (bIsOffset && OffsetProperties->bUseFaceNormals); Extruder.bOffsetFullComponentsAsSolids = bIsOffset || ExtrudeProperties->bShellsToSolids; Extruder.ChangeTracker = MakeUnique(Mesh); Extruder.ChangeTracker->BeginChange(); Extruder.Apply(); FMeshNormals::QuickComputeVertexNormalsForTriangles(*Mesh, Extruder.AllModifiedTriangles); // construct new selection FGroupTopologySelection NewSelection; for (const FOffsetMeshRegion::FOffsetInfo& Info : Extruder.OffsetRegions) { for (int32 gid : Info.OffsetGroups) { NewSelection.SelectedGroupIDs.Add(gid); } } // emit undo TUniquePtr MeshChange = MakeUnique(Extruder.ChangeTracker->EndChange()); CompleteMeshEditChange( (bIsOffset) ? LOCTEXT("PolyMeshOffsetChange", "Offset") : LOCTEXT("PolyMeshExtrudeChange", "Extrude"), MoveTemp(MeshChange), NewSelection); ExtrudeHeightMechanic = nullptr; CurrentToolMode = ECurrentToolMode::TransformSelection; SetToolPropertySourceEnabled(ExtrudeProperties, false); SetToolPropertySourceEnabled(OffsetProperties, false); SetActionButtonPanelsVisible(true); } void UEditMeshPolygonsTool::RestartExtrude() { if (CurrentToolMode == ECurrentToolMode::ExtrudeSelection) { CancelMeshEditChange(); BeginExtrude(false); } } FVector3d UEditMeshPolygonsTool::GetExtrudeDirection() const { switch (ExtrudeProperties->Direction) { default: case EPolyEditExtrudeDirection::SelectionNormal: return ActiveSelectionFrameWorld.Z(); case EPolyEditExtrudeDirection::WorldX: return FVector3d::UnitX(); case EPolyEditExtrudeDirection::WorldY: return FVector3d::UnitY(); case EPolyEditExtrudeDirection::WorldZ: return FVector3d::UnitZ(); case EPolyEditExtrudeDirection::LocalX: return WorldTransform.GetRotation().AxisX(); case EPolyEditExtrudeDirection::LocalY: return WorldTransform.GetRotation().AxisY(); case EPolyEditExtrudeDirection::LocalZ: return WorldTransform.GetRotation().AxisZ(); } return ActiveSelectionFrameWorld.Z(); } void UEditMeshPolygonsTool::BeginInset(bool bOutset) { FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); if (BeginMeshFaceEditChangeWithPreview() == false) { return; } EditPreview->InitializeInsetType(Mesh, ActiveTriangleSelection, &WorldTransform); // make infinite-extent hit-test mesh FDynamicMesh3 InsetHitTargetMesh; EditPreview->MakeInsetTypeTargetMesh(InsetHitTargetMesh); CurveDistMechanic = NewObject(this); CurveDistMechanic->Setup(this); CurveDistMechanic->WorldPointSnapFunc = [this](const FVector3d& WorldPos, FVector3d& SnapPos) { return CommonProps->bSnapToWorldGrid && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos); }; CurveDistMechanic->CurrentDistance = 1.0f; // initialize to something non-zero...prob should be based on polygon bounds maybe? FMeshBoundaryLoops Loops(&InsetHitTargetMesh); TArray LoopVertices; Loops.Loops[0].GetVertices(LoopVertices); CurveDistMechanic->InitializePolyLoop(LoopVertices, FTransform3d::Identity()); CurrentToolMode = (bOutset) ? ECurrentToolMode::OutsetSelection : ECurrentToolMode::InsetSelection; SetToolPropertySourceEnabled((bOutset) ? (UInteractiveToolPropertySet*)OutsetProperties : (UInteractiveToolPropertySet*)InsetProperties, true); SetActionButtonPanelsVisible(false); } void UEditMeshPolygonsTool::ApplyInset(bool bOutset) { check(CurveDistMechanic != nullptr && EditPreview != nullptr); FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FInsetMeshRegion Inset(Mesh); Inset.UVScaleFactor = UVScaleFactor; Inset.Triangles = ActiveTriangleSelection; Inset.InsetDistance = (bOutset) ? -CurveDistMechanic->CurrentDistance : CurveDistMechanic->CurrentDistance; Inset.bReproject = (bOutset) ? false : InsetProperties->bReproject; Inset.Softness = (bOutset) ? OutsetProperties->Softness : InsetProperties->Softness; Inset.bSolveRegionInteriors = (bOutset) ? (!OutsetProperties->bBoundaryOnly) : (!InsetProperties->bBoundaryOnly); Inset.AreaCorrection = (bOutset) ? OutsetProperties->AreaScale : InsetProperties->AreaScale; Inset.ChangeTracker = MakeUnique(Mesh); Inset.ChangeTracker->BeginChange(); Inset.Apply(); FMeshNormals::QuickComputeVertexNormalsForTriangles(*Mesh, Inset.AllModifiedTriangles); // emit undo FGroupTopologySelection CurSelection = SelectionMechanic->GetActiveSelection(); TUniquePtr MeshChange = MakeUnique(Inset.ChangeTracker->EndChange()); CompleteMeshEditChange( bOutset ? LOCTEXT("PolyMeshOutsetChange", "Outset") : LOCTEXT("PolyMeshInsetChange", "Inset"), MoveTemp(MeshChange), CurSelection); CurveDistMechanic = nullptr; CurrentToolMode = ECurrentToolMode::TransformSelection; SetToolPropertySourceEnabled((bOutset) ? (UInteractiveToolPropertySet*)OutsetProperties : (UInteractiveToolPropertySet*)InsetProperties, false); SetActionButtonPanelsVisible(true); } void UEditMeshPolygonsTool::BeginCutFaces() { FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); if (BeginMeshFaceEditChangeWithPreview() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnCutFacesFailedMessage", "Cannot Cut Current Selection"), EToolMessageLevel::UserWarning); return; } GetToolManager()->DisplayMessage( LOCTEXT("OnBeginCutFacesMessage", "Click twice on selected face to define cut line"), EToolMessageLevel::UserMessage); EditPreview->InitializeStaticType(Mesh, ActiveTriangleSelection, &WorldTransform); FDynamicMesh3 StaticHitTargetMesh; EditPreview->MakeInsetTypeTargetMesh(StaticHitTargetMesh); SurfacePathMechanic = NewObject(this); SurfacePathMechanic->Setup(this); SurfacePathMechanic->InitializeMeshSurface(MoveTemp(StaticHitTargetMesh)); SurfacePathMechanic->SetFixedNumPointsMode(2); SurfacePathMechanic->bSnapToTargetMeshVertices = true; double SnapTol = ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD(); SurfacePathMechanic->SpatialSnapPointsFunc = [this, SnapTol](FVector3d Position1, FVector3d Position2) { return CutProperties->bSnapToVertices && ToolSceneQueriesUtil::PointSnapQuery(this->CameraState, Position1, Position2, SnapTol); }; CurrentToolMode = ECurrentToolMode::CutSelection; SetToolPropertySourceEnabled(CutProperties, true); SetActionButtonPanelsVisible(false); } void UEditMeshPolygonsTool::ApplyCutFaces() { check(SurfacePathMechanic != nullptr && EditPreview != nullptr); FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); // construct cut plane normal from line points FFrame3d Point0(SurfacePathMechanic->HitPath[0]), Point1(SurfacePathMechanic->HitPath[1]); FVector3d PlaneNormal; if (CutProperties->Orientation == EPolyEditCutPlaneOrientation::ViewDirection) { FVector3d Direction0 = (Point0.Origin - CameraState.Position).Normalized(); FVector3d Direction1 = (Point1.Origin - CameraState.Position).Normalized(); PlaneNormal = Direction1.Cross(Direction0); } else { FVector3d LineDirection = (Point1.Origin - Point0.Origin).Normalized(); FVector3d UpVector = (Point0.Z() + Point1.Z()).Normalized(); PlaneNormal = LineDirection.Cross(UpVector); } FVector3d PlaneOrigin = 0.5 * (Point0.Origin + Point1.Origin); // map into local space of target mesh PlaneOrigin = WorldTransform.InverseTransformPosition(PlaneOrigin); PlaneNormal = WorldTransform.InverseTransformNormal(PlaneNormal); PlaneNormal.Normalize(); // track changes FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); TArray VertexSelection; MeshIndexUtil::TriangleToVertexIDs(Mesh, ActiveTriangleSelection, VertexSelection); ChangeTracker.SaveVertexOneRingTriangles(VertexSelection, true); // apply the cut to edges of selected triangles FGroupTopologySelection OutputSelection; FMeshPlaneCut Cut(Mesh, PlaneOrigin, PlaneNormal); FMeshEdgeSelection Edges(Mesh); Edges.SelectTriangleEdges(ActiveTriangleSelection); Cut.EdgeFilterFunc = [&](int EdgeID) { return Edges.IsSelected(EdgeID); }; if (Cut.SplitEdgesOnly(true)) { for (FMeshPlaneCut::FCutResultRegion& Region : Cut.ResultRegions) { OutputSelection.SelectedGroupIDs.Add(Region.GroupID); } } // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshCutFacesChange", "Cut Faces"), MoveTemp(MeshChange), OutputSelection); SurfacePathMechanic = nullptr; CurrentToolMode = ECurrentToolMode::TransformSelection; SetToolPropertySourceEnabled(CutProperties, false); SetActionButtonPanelsVisible(true); } void UEditMeshPolygonsTool::BeginSetUVs() { FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); if (BeginMeshFaceEditChangeWithPreview() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnSetUVsFailedMesssage", "Cannot Set UVs for Current Selection"), EToolMessageLevel::UserWarning); return; } GetToolManager()->DisplayMessage( LOCTEXT("OnBeginSetUVsMessage", "Click on the face to Set UVs"), EToolMessageLevel::UserMessage); EditPreview->InitializeStaticType(Mesh, ActiveTriangleSelection, &WorldTransform); UpdateEditPreviewMaterials((SetUVProperties->bShowMaterial) ? EPreviewMaterialType::SourceMaterials : EPreviewMaterialType::UVMaterial); FDynamicMesh3 StaticHitTargetMesh; EditPreview->MakeInsetTypeTargetMesh(StaticHitTargetMesh); SurfacePathMechanic = NewObject(this); SurfacePathMechanic->Setup(this); SurfacePathMechanic->InitializeMeshSurface(MoveTemp(StaticHitTargetMesh)); SurfacePathMechanic->SetFixedNumPointsMode(2); SurfacePathMechanic->bSnapToTargetMeshVertices = true; double SnapTol = ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD(); SurfacePathMechanic->SpatialSnapPointsFunc = [this, SnapTol](FVector3d Position1, FVector3d Position2) { return ToolSceneQueriesUtil::PointSnapQuery(this->CameraState, Position1, Position2, SnapTol); }; CurrentToolMode = ECurrentToolMode::SetUVs; SetToolPropertySourceEnabled(SetUVProperties, true); SetActionButtonPanelsVisible(false); } void UEditMeshPolygonsTool::UpdateSetUVS() { // align projection frame to line user is drawing out from plane origin FFrame3d PlanarFrame = SurfacePathMechanic->PreviewPathPoint; double UVScale = 1.0 / ActiveSelectionBounds.MaxDim(); if (SurfacePathMechanic->HitPath.Num() == 1) { SurfacePathMechanic->InitializePlaneSurface(PlanarFrame); FVector3d Delta = PlanarFrame.Origin - SurfacePathMechanic->HitPath[0].Origin; double Dist = Delta.Normalize(); UVScale *= FMathd::Lerp(1.0, 25.0, Dist / ActiveSelectionBounds.MaxDim()); PlanarFrame = SurfacePathMechanic->HitPath[0]; PlanarFrame.ConstrainedAlignAxis(0, Delta, PlanarFrame.Z()); } EditPreview->UpdateStaticType([&](FDynamicMesh3& Mesh) { FDynamicMeshEditor Editor(&Mesh); TArray AllTriangles; for (int32 tid : Mesh.TriangleIndicesItr()) { AllTriangles.Add(tid); } Editor.SetTriangleUVsFromProjection(AllTriangles, PlanarFrame, UVScale, FVector2f::Zero(), false); }, false); } void UEditMeshPolygonsTool::ApplySetUVs() { FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); // align projection frame to line user drew FFrame3d PlanarFrame = SurfacePathMechanic->HitPath[0]; double UVScale = 1.0 / ActiveSelectionBounds.MaxDim(); FVector3d Delta = SurfacePathMechanic->HitPath[1].Origin - PlanarFrame.Origin; double Dist = Delta.Normalize(); UVScale *= FMathd::Lerp(1.0, 25.0, Dist / ActiveSelectionBounds.MaxDim()); PlanarFrame.ConstrainedAlignAxis(0, Delta, PlanarFrame.Z()); // transform to local, use 3D point to transfer UV scale value FVector3d ScalePt = PlanarFrame.Origin + UVScale * PlanarFrame.Z(); FTransform3d ToLocalXForm(WorldTransform.Inverse()); PlanarFrame.Transform(ToLocalXForm); ScalePt = ToLocalXForm.TransformPosition(ScalePt); UVScale = ScalePt.Distance(PlanarFrame.Origin); // track changes FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); ChangeTracker.SaveTriangles(ActiveTriangleSelection, true); FDynamicMeshEditor Editor(Mesh); Editor.SetTriangleUVsFromProjection(ActiveTriangleSelection, PlanarFrame, UVScale, FVector2f::Zero(), false, 0); // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshSetUVsChange", "Set UVs"), MoveTemp(MeshChange), ActiveSelection); SurfacePathMechanic = nullptr; CurrentToolMode = ECurrentToolMode::TransformSelection; SetToolPropertySourceEnabled(SetUVProperties, false); SetActionButtonPanelsVisible(true); } void UEditMeshPolygonsTool::ApplyMerge() { if (BeginMeshFaceEditChangeWithPreview() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnMergeFailedMessage", "Cannot Merge Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); ChangeTracker.SaveTriangles(ActiveTriangleSelection, false); FMeshConnectedComponents Components(Mesh); Components.FindConnectedTriangles(ActiveTriangleSelection); FGroupTopologySelection NewSelection; for (const FMeshConnectedComponents::FComponent& Component : Components) { int32 NewGroupID = Mesh->AllocateTriangleGroup(); FaceGroupUtil::SetGroupID(*Mesh, Component.Indices, NewGroupID); NewSelection.SelectedGroupIDs.Add(NewGroupID); } // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshMergeChange", "Merge"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyDelete() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnDeleteFailedMessage", "Cannot Delete Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); ChangeTracker.SaveTriangles(ActiveTriangleSelection, true); FDynamicMeshEditor Editor(Mesh); Editor.RemoveTriangles(ActiveTriangleSelection, true); // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); FGroupTopologySelection NewSelection; CompleteMeshEditChange(LOCTEXT("PolyMeshDeleteChange", "Delete"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyRecalcNormals() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnRecalcNormalsFailedMessage", "Cannot Recalculate Normals for Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); FDynamicMeshEditor Editor(Mesh); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); for (int32 GroupID : ActiveSelection.SelectedGroupIDs) { ChangeTracker.SaveTriangles(Topology->GetGroupTriangles(GroupID), true); Editor.SetTriangleNormals(Topology->GetGroupTriangles(GroupID)); } // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshRecalcNormalsChange", "Recalc Normals"), MoveTemp(MeshChange), ActiveSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyFlipNormals() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnFlipNormalsFailedMessage", "Cannot Flip Normals for Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); FDynamicMeshEditor Editor(Mesh); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); for (int32 GroupID : ActiveSelection.SelectedGroupIDs) { for ( int32 tid : Topology->GetGroupTriangles(GroupID) ) { ChangeTracker.SaveTriangle(tid, true); Mesh->ReverseTriOrientation(tid); } } // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshFlipNormalsChange", "Flip Normals"), MoveTemp(MeshChange), ActiveSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyRetriangulate() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnRetriangulateFailed", "Cannot Retriangulate Current Selection"), EToolMessageLevel::UserWarning); return; } int32 nCompleted = 0; FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); FDynamicMeshEditor Editor(Mesh); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); for (int32 GroupID : ActiveSelection.SelectedGroupIDs) { const TArray& Triangles = Topology->GetGroupTriangles(GroupID); ChangeTracker.SaveTriangles(Triangles, true); FMeshRegionBoundaryLoops RegionLoops(Mesh, Triangles, true); if (!RegionLoops.bFailed && RegionLoops.Loops.Num() == 1 && Triangles.Num() > 1) { TArray> VidUVMaps; if (Mesh->HasAttributes()) { const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); for (int i = 0; i < Attributes->NumUVLayers(); ++i) { VidUVMaps.Emplace(); RegionLoops.GetLoopOverlayMap(RegionLoops.Loops[0], *Attributes->GetUVLayer(i), VidUVMaps.Last()); } } // We don't want to remove isolated vertices while removing triangles because we don't // want to throw away boundary verts. However, this means that we'll have to go back // through these vertices later to throw away isolated internal verts. TArray OldVertices; MeshIndexUtil::TriangleToVertexIDs(Mesh, Triangles, OldVertices); Editor.RemoveTriangles(Topology->GetGroupTriangles(GroupID), false); RegionLoops.Loops[0].Reverse(); FSimpleHoleFiller Filler(Mesh, RegionLoops.Loops[0]); Filler.FillType = FSimpleHoleFiller::EFillType::PolygonEarClipping; Filler.Fill(GroupID); // Throw away any of the old verts that are still isolated (they were in the interior of the group) Algo::ForEachIf(OldVertices, [Mesh](int32 Vid) { return !Mesh->IsReferencedVertex(Vid); }, [Mesh](int32 Vid) { Mesh->RemoveVertex(Vid, false, false); } // Don't try to remove attached tris, don't care about bowties ); if (Mesh->HasAttributes()) { const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); for (int i = 0; i < Attributes->NumUVLayers(); ++i) { RegionLoops.UpdateLoopOverlayMapValidity(VidUVMaps[i], *Attributes->GetUVLayer(i)); } Filler.UpdateAttributes(VidUVMaps); } nCompleted++; } } if (nCompleted != ActiveSelection.SelectedGroupIDs.Num()) { GetToolManager()->DisplayMessage(LOCTEXT("OnRetriangulateFailures", "Some faces could not be retriangulated"), EToolMessageLevel::UserWarning); } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshRetriangulateChange", "Retriangulate"), MoveTemp(MeshChange), ActiveSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyDecompose() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnDecomposeFailed", "Cannot Decompose Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); FGroupTopologySelection NewSelection; for (int32 GroupID : SelectionMechanic->GetActiveSelection().SelectedGroupIDs) { const TArray& Triangles = Topology->GetGroupTriangles(GroupID); ChangeTracker.SaveTriangles(Triangles, false); for (int32 tid : Triangles) { int32 NewGroupID = Mesh->AllocateTriangleGroup(); Mesh->SetTriangleGroup(tid, NewGroupID); NewSelection.SelectedGroupIDs.Add(NewGroupID); } } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshDecomposeChange", "Decompose"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyDisconnect() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnDisconnectFailed", "Cannot Disconnect Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); TArray AllTriangles; for (int32 GroupID : ActiveSelection.SelectedGroupIDs) { AllTriangles.Append(Topology->GetGroupTriangles(GroupID)); } ChangeTracker.SaveTriangles(AllTriangles, true); FDynamicMeshEditor Editor(Mesh); Editor.DisconnectTriangles(AllTriangles, false); TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshDisconnectChange", "Disconnect"), MoveTemp(MeshChange), ActiveSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyCollapseEdge() { // AAAHHH cannot do because of overlays! return; if (SelectionMechanic->GetActiveSelection().SelectedEdgeIDs.Num() != 1 || BeginMeshEdgeEditChange() == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnEdgeColllapseFailed", "Cannot Collapse current selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); //const TArray& EdgeIDs = ActiveEdgeSelection[0].EdgeIDs; //for (int32 eid : EdgeIDs) //{ // if (Mesh->IsEdge(eid)) // { // FIndex2i EdgeVerts = Mesh->GetEdgeV(eid); // ChangeTracker.SaveVertexOneRingTriangles(EdgeVerts.A, true); // ChangeTracker.SaveVertexOneRingTriangles(EdgeVerts.B, true); // FDynamicMesh3::FEdgeCollapseInfo CollapseInfo; // Mesh->CollapseEdge() // } //} // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); FGroupTopologySelection NewSelection; CompleteMeshEditChange(LOCTEXT("PolyMeshEdgeCollapseChange", "Collapse"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyWeldEdges() { bool bValidInput = SelectionMechanic->GetActiveSelection().SelectedEdgeIDs.Num() == 2 && BeginMeshBoundaryEdgeEditChange(true); if ( bValidInput == false ) { GetToolManager()->DisplayMessage( LOCTEXT("OnWeldEdgesFailed", "Cannot Weld current selection"), EToolMessageLevel::UserWarning); CancelMeshEditChange(); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); int32 EdgeIDA = Topology->GetGroupEdgeEdges(ActiveEdgeSelection[0].EdgeTopoID)[0]; int32 EdgeIDB = Topology->GetGroupEdgeEdges(ActiveEdgeSelection[1].EdgeTopoID)[0]; FIndex2i EdgeVerts[2] = { Mesh->GetEdgeV(EdgeIDA), Mesh->GetEdgeV(EdgeIDB) }; for (int j = 0; j < 2; ++j) { ChangeTracker.SaveVertexOneRingTriangles(EdgeVerts[j].A, true); ChangeTracker.SaveVertexOneRingTriangles(EdgeVerts[j].B, true); } FDynamicMesh3::FMergeEdgesInfo MergeInfo; EMeshResult Result = Mesh->MergeEdges(EdgeIDB, EdgeIDA, MergeInfo); if (Result != EMeshResult::Ok) { GetToolManager()->DisplayMessage( LOCTEXT("OnWeldEdgesFailed", "Cannot Weld current selection"), EToolMessageLevel::UserWarning); CancelMeshEditChange(); return; } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); FGroupTopologySelection NewSelection; CompleteMeshEditChange(LOCTEXT("PolyMeshWeldEdgeChange", "Weld Edges"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyStraightenEdges() { if (BeginMeshEdgeEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnStraightenEdgesFailed", "Cannot Straighten current selection"), EToolMessageLevel::UserWarning); CancelMeshEditChange(); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); for (const FSelectedEdge& Edge : ActiveEdgeSelection) { const TArray& EdgeVerts = Topology->GetGroupEdgeVertices(Edge.EdgeTopoID); int32 NumV = EdgeVerts.Num(); if ( NumV > 2 ) { ChangeTracker.SaveVertexOneRingTriangles(EdgeVerts, true); FVector3d A(Mesh->GetVertex(EdgeVerts[0])), B(Mesh->GetVertex(EdgeVerts[NumV-1])); TArray VtxArcLengths; double EdgeArcLen = Topology->GetEdgeArcLength(Edge.EdgeTopoID, &VtxArcLengths); for (int k = 1; k < NumV-1; ++k) { double t = VtxArcLengths[k] / EdgeArcLen; Mesh->SetVertex(EdgeVerts[k], FVector3d::Lerp(A, B, t)); } } } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); FGroupTopologySelection NewSelection; CompleteMeshEditChange(LOCTEXT("PolyMeshStraightenEdgeChange", "Straighten Edges"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyFillHole() { if (BeginMeshBoundaryEdgeEditChange(false) == false) { GetToolManager()->DisplayMessage( LOCTEXT("OnEdgeFillFailed", "Cannot Fill current selection"), EToolMessageLevel::UserWarning); CancelMeshEditChange(); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); FGroupTopologySelection NewSelection; for (FSelectedEdge& FillEdge : ActiveEdgeSelection) { if (Mesh->IsBoundaryEdge(FillEdge.EdgeIDs[0])) // may no longer be boundary due to previous fill { FMeshBoundaryLoops BoundaryLoops(Mesh); int32 LoopID = BoundaryLoops.FindLoopContainingEdge(FillEdge.EdgeIDs[0]); if (LoopID >= 0) { FEdgeLoop& Loop = BoundaryLoops.Loops[LoopID]; FSimpleHoleFiller Filler(Mesh, Loop); Filler.FillType = FSimpleHoleFiller::EFillType::PolygonEarClipping; int32 NewGroupID = Mesh->AllocateTriangleGroup(); Filler.Fill(NewGroupID); NewSelection.SelectedGroupIDs.Add(NewGroupID); // Compute normals and UVs if (Mesh->HasAttributes()) { TArray VertexPositions; Loop.GetVertices(VertexPositions); FVector3d PlaneOrigin; FVector3d PlaneNormal; PolygonTriangulation::ComputePolygonPlane(VertexPositions, PlaneNormal, PlaneOrigin); FDynamicMeshEditor Editor(Mesh); FFrame3d ProjectionFrame(PlaneOrigin, PlaneNormal); Editor.SetTriangleNormals(Filler.NewTriangles); Editor.SetTriangleUVsFromProjection(Filler.NewTriangles, ProjectionFrame, UVScaleFactor); } } } } // emit undo TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshFillHoleChange", "Fill Hole"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyPokeSingleFace() { if (BeginMeshFaceEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnPokeFailedMessage", "Cannot Poke Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); ChangeTracker.SaveTriangles(ActiveTriangleSelection, false); FGroupTopologySelection NewSelection; for (int32 tid : ActiveTriangleSelection) { FDynamicMesh3::FPokeTriangleInfo PokeInfo; NewSelection.SelectedGroupIDs.Add(tid); if (Mesh->PokeTriangle(tid, PokeInfo) == EMeshResult::Ok) { NewSelection.SelectedGroupIDs.Add(PokeInfo.NewTriangles.A); NewSelection.SelectedGroupIDs.Add(PokeInfo.NewTriangles.B); } } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshPokeChange", "Poke Faces"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyFlipSingleEdge() { if (BeginMeshEdgeEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnFlipFailedMessage", "Cannot Flip Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); for (FSelectedEdge& Edge : ActiveEdgeSelection) { int32 eid = Edge.EdgeIDs[0]; if (Mesh->IsEdge(eid) && Mesh->IsBoundaryEdge(eid) == false && Mesh->Attributes()->IsSeamEdge(eid) == false) { FIndex2i et = Mesh->GetEdgeT(eid); ChangeTracker.SaveTriangle(et.A, true); ChangeTracker.SaveTriangle(et.B, true); FDynamicMesh3::FEdgeFlipInfo FlipInfo; Mesh->FlipEdge(eid, FlipInfo); } } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshFlipChange", "Flip Edges"), MoveTemp(MeshChange), ActiveSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplyCollapseSingleEdge() { if (BeginMeshEdgeEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnCollapseFailedMessage", "Cannot Collapse Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FGroupTopologySelection ActiveSelection = SelectionMechanic->GetActiveSelection(); FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); TSet ValidEdgeIDs; for (FSelectedEdge& Edge : ActiveEdgeSelection) { int32 eid = Edge.EdgeIDs[0]; if (Mesh->IsEdge(eid) && Mesh->Attributes()->IsSeamEdge(eid) == false) { ValidEdgeIDs.Add(eid); } } TSet DoneEdgeIDs; for (int32 eid : ValidEdgeIDs) { if (DoneEdgeIDs.Contains(eid) == false && Mesh->IsEdge(eid)) { FIndex2i ev = Mesh->GetEdgeV(eid); ChangeTracker.SaveVertexOneRingTriangles(ev.A, true); ChangeTracker.SaveVertexOneRingTriangles(ev.B, true); FDynamicMesh3::FEdgeCollapseInfo CollapseInfo; if (Mesh->CollapseEdge(ev.A, ev.B, CollapseInfo) == EMeshResult::Ok) { DoneEdgeIDs.Add(eid); DoneEdgeIDs.Add(CollapseInfo.RemovedEdges.A); DoneEdgeIDs.Add(CollapseInfo.RemovedEdges.B); } } } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshCollapseChange", "Collapse Edges"), MoveTemp(MeshChange), FGroupTopologySelection()); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::ApplySplitSingleEdge() { if (BeginMeshEdgeEditChange() == false) { GetToolManager()->DisplayMessage(LOCTEXT("OnSplitFailedMessage", "Cannot Split Current Selection"), EToolMessageLevel::UserWarning); return; } FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); FGroupTopologySelection NewSelection; FDynamicMeshChangeTracker ChangeTracker(Mesh); ChangeTracker.BeginChange(); for (FSelectedEdge& Edge : ActiveEdgeSelection) { int32 eid = Edge.EdgeIDs[0]; if (Mesh->IsEdge(eid)) { FIndex2i et = Mesh->GetEdgeT(eid); ChangeTracker.SaveTriangle(et.A, true); NewSelection.SelectedGroupIDs.Add(et.A); if (et.B != FDynamicMesh3::InvalidID) { ChangeTracker.SaveTriangle(et.B, true); NewSelection.SelectedGroupIDs.Add(et.B); } FDynamicMesh3::FEdgeSplitInfo SplitInfo; if (Mesh->SplitEdge(eid, SplitInfo) == EMeshResult::Ok) { NewSelection.SelectedGroupIDs.Add(SplitInfo.NewTriangles.A); if (SplitInfo.NewTriangles.B != FDynamicMesh3::InvalidID) { NewSelection.SelectedGroupIDs.Add(SplitInfo.NewTriangles.A); } } } } TUniquePtr MeshChange = MakeUnique(ChangeTracker.EndChange()); CompleteMeshEditChange(LOCTEXT("PolyMeshSplitChange", "Split Edges"), MoveTemp(MeshChange), NewSelection); CurrentToolMode = ECurrentToolMode::TransformSelection; } bool UEditMeshPolygonsTool::BeginMeshFaceEditChange() { check(EditPreview == nullptr); ActiveTriangleSelection.Reset(); // need some selected faces const FGroupTopologySelection& ActiveSelection = SelectionMechanic->GetActiveSelection(); Topology->GetSelectedTriangles(ActiveSelection, ActiveTriangleSelection); if (ActiveSelection.SelectedGroupIDs.Num() == 0 || ActiveTriangleSelection.Num() == 0) { return false; } const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh(); ActiveSelectionBounds = FAxisAlignedBox3d::Empty(); for (int tid : ActiveTriangleSelection) { ActiveSelectionBounds.Contain(Mesh->GetTriBounds(tid)); } // world and local frames ActiveSelectionFrameLocal = Topology->GetSelectionFrame(ActiveSelection); ActiveSelectionFrameWorld = ActiveSelectionFrameLocal; ActiveSelectionFrameWorld.Transform(WorldTransform); return true; } bool UEditMeshPolygonsTool::BeginMeshFaceEditChangeWithPreview() { bool bOK = BeginMeshFaceEditChange(); if (bOK) { EditPreview = NewObject(this); EditPreview->CreateInWorld(ComponentTarget->GetOwnerActor()->GetWorld(), FTransform::Identity); UpdateEditPreviewMaterials(EPreviewMaterialType::PreviewMaterial); EditPreview->EnableWireframe(true); // hide gizmo and selected triangles MultiTransformer->SetGizmoVisibility(false); DynamicMeshComponent->SetSecondaryBuffersVisibility(false); } return bOK; } void UEditMeshPolygonsTool::CompleteMeshEditChange( const FText& TransactionLabel, TUniquePtr EditChange, const FGroupTopologySelection& OutputSelection) { // open top-level transaction GetToolManager()->BeginUndoTransaction(TransactionLabel); // clear current selection SelectionMechanic->BeginChange(); SelectionMechanic->ClearSelection(); GetToolManager()->EmitObjectChange(SelectionMechanic, SelectionMechanic->EndChange(), LOCTEXT("PolyMeshExtrudeChangeClearSelection", "ClearSelection")); // emit the pre-edit change GetToolManager()->EmitObjectChange(this, MakeUnique(), LOCTEXT("PolyMeshExtrudeChangePreEdit", "PreEdit")); // emit the mesh change GetToolManager()->EmitObjectChange(DynamicMeshComponent, MoveTemp(EditChange), TransactionLabel); // emit the post-edit change GetToolManager()->EmitObjectChange(this, MakeUnique(), TransactionLabel); // call this (PostEditChange will do this) AfterTopologyEdit(); // increment topology-change counter ModifiedTopologyCounter++; // set output selection if (OutputSelection.IsEmpty() == false) { SelectionMechanic->BeginChange(); SelectionMechanic->SetSelection(OutputSelection); GetToolManager()->EmitObjectChange(SelectionMechanic, SelectionMechanic->EndChange(), LOCTEXT("PolyMeshExtrudeChangeSetSelection", "SetSelection")); } // complete the transaction GetToolManager()->EndUndoTransaction(); // clean up preview mesh, hiding of things, etc if (EditPreview != nullptr) { EditPreview->Disconnect(); EditPreview = nullptr; } DynamicMeshComponent->SetSecondaryBuffersVisibility(true); CurrentOperationTimestamp++; } bool UEditMeshPolygonsTool::BeginMeshEdgeEditChange() { return BeginMeshEdgeEditChange([](int32) {return true; }); } bool UEditMeshPolygonsTool::BeginMeshBoundaryEdgeEditChange(bool bOnlySimple) { if (bOnlySimple) { return BeginMeshEdgeEditChange( [&](int32 GroupEdgeID) { return Topology->IsBoundaryEdge(GroupEdgeID) && Topology->IsSimpleGroupEdge(GroupEdgeID); }); } else { return BeginMeshEdgeEditChange( [&](int32 GroupEdgeID) { return Topology->IsBoundaryEdge(GroupEdgeID); }); } } bool UEditMeshPolygonsTool::BeginMeshEdgeEditChange(TFunctionRef GroupEdgeIDFilterFunc) { check(EditPreview == nullptr); ActiveEdgeSelection.Reset(); const FGroupTopologySelection& ActiveSelection = SelectionMechanic->GetActiveSelection(); int NumEdges = ActiveSelection.SelectedEdgeIDs.Num(); if (NumEdges == 0) { return false; } ActiveEdgeSelection.Reserve(NumEdges); for (int32 EdgeID : ActiveSelection.SelectedEdgeIDs) { if (GroupEdgeIDFilterFunc(EdgeID)) { FSelectedEdge& Edge = ActiveEdgeSelection.Emplace_GetRef(); Edge.EdgeTopoID = EdgeID; Edge.EdgeIDs = Topology->GetGroupEdgeEdges(EdgeID); } } return ActiveEdgeSelection.Num() > 0; } void UEditMeshPolygonsTool::CancelMeshEditChange() { if (EditPreview != nullptr) { EditPreview->Disconnect(); EditPreview = nullptr; } DynamicMeshComponent->SetSecondaryBuffersVisibility(true); // disable any mechanics ExtrudeHeightMechanic = nullptr; CurveDistMechanic = nullptr; SurfacePathMechanic = nullptr; // hide properties that might be visible SetToolPropertySourceEnabled(ExtrudeProperties, false); SetToolPropertySourceEnabled(OffsetProperties, false); SetToolPropertySourceEnabled(InsetProperties, false); SetToolPropertySourceEnabled(OutsetProperties, false); SetToolPropertySourceEnabled(CutProperties, false); SetToolPropertySourceEnabled(SetUVProperties, false); SetActionButtonPanelsVisible(true); CurrentToolMode = ECurrentToolMode::TransformSelection; } void UEditMeshPolygonsTool::UpdateEditPreviewMaterials(EPreviewMaterialType MaterialType) { if (EditPreview != nullptr) { if (MaterialType == EPreviewMaterialType::SourceMaterials) { EditPreview->ClearOverrideRenderMaterial(); EditPreview->SetMaterials(DynamicMeshComponent->GetMaterials()); } else if (MaterialType == EPreviewMaterialType::PreviewMaterial) { EditPreview->ClearOverrideRenderMaterial(); EditPreview->SetMaterial( ToolSetupUtil::GetSelectionMaterial(FLinearColor(0.8f, 0.75f, 0.0f), GetToolManager())); } else if (MaterialType == EPreviewMaterialType::UVMaterial) { UMaterial* CheckerMaterialBase = LoadObject(nullptr, TEXT("/MeshModelingToolset/Materials/CheckerMaterial")); if (CheckerMaterialBase != nullptr) { UMaterialInstanceDynamic* CheckerMaterial = UMaterialInstanceDynamic::Create(CheckerMaterialBase, NULL); CheckerMaterial->SetScalarParameterValue("Density", 1); EditPreview->SetOverrideRenderMaterial(CheckerMaterial); } } CurrentPreviewMaterial = MaterialType; } } void UEditMeshPolygonsTool::SetActionButtonPanelsVisible(bool bVisible) { if (bTriangleMode == false) { if (EditActions) { SetToolPropertySourceEnabled(EditActions, bVisible); } if (EditEdgeActions) { SetToolPropertySourceEnabled(EditEdgeActions, bVisible); } if (EditUVActions) { SetToolPropertySourceEnabled(EditUVActions, bVisible); } } else { if (EditActions_Triangles) { SetToolPropertySourceEnabled(EditActions_Triangles, bVisible); } if (EditEdgeActions_Triangles) { SetToolPropertySourceEnabled(EditEdgeActions_Triangles, bVisible); } } } void FEditPolygonsTopologyPreEditChange::Apply(UObject* Object) { } void FEditPolygonsTopologyPreEditChange::Revert(UObject* Object) { Cast(Object)->AfterTopologyEdit(); Cast(Object)->ModifiedTopologyCounter--; } FString FEditPolygonsTopologyPreEditChange::ToString() const { return TEXT("FEditPolygonsTopologyPreEditChange"); } void FEditPolygonsTopologyPostEditChange::Apply(UObject* Object) { Cast(Object)->AfterTopologyEdit(); Cast(Object)->ModifiedTopologyCounter++; } void FEditPolygonsTopologyPostEditChange::Revert(UObject* Object) { } FString FEditPolygonsTopologyPostEditChange::ToString() const { return TEXT("FEditPolygonsTopologyPostEditChange"); } void FBeginInteractivePolyEditChange::Revert(UObject* Object) { Cast(Object)->CancelMeshEditChange(); bHaveDoneUndo = true; } bool FBeginInteractivePolyEditChange::HasExpired(UObject* Object) const { return bHaveDoneUndo || (Cast(Object)->CheckInOperation(OperationTimestamp) == false); } FString FBeginInteractivePolyEditChange::ToString() const { return TEXT("FBeginInteractivePolyEditChange"); } #undef LOCTEXT_NAMESPACE