// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "EngineDefines.h" #include "Misc/MessageDialog.h" #include "Misc/ScopedSlowTask.h" #include "GameFramework/Actor.h" #include "MaterialDomain.h" #include "Materials/Material.h" #include "Engine/Brush.h" #include "Editor/EditorEngine.h" #include "Engine/Polys.h" #include "Engine/Selection.h" #include "EdMode.h" #include "EditorModeManager.h" #include "SurfaceIterators.h" #include "BSPOps.h" #include "BSPUtils.h" #include "ActorEditorUtils.h" #include "Misc/FeedbackContext.h" #include "EngineUtils.h" // Globals: static TArray GFlags1; static TArray GFlags2; /*----------------------------------------------------------------------------- Helper classes. -----------------------------------------------------------------------------*/ /** * Iterator used to iterate over all static brush actors in the current level. */ class FStaticBrushIterator { public: /** * Default constructor, initializing all member variables and iterating to first. */ FStaticBrushIterator( UWorld* InWorld ) : ActorIndex( -1 ), ReachedEnd( false ), World( InWorld ) { // Iterate to first. ++(*this); } /** * Iterates to next suitable actor. */ void operator++() { bool FoundSuitableActor = false; while( !ReachedEnd && !FoundSuitableActor ) { if( ++ActorIndex >= World->GetCurrentLevel()->Actors.Num() ) { ReachedEnd = true; } else { //@todo locked levels - should we skip brushes contained by locked levels? ABrush* Brush = Cast(World->GetCurrentLevel()->Actors[ActorIndex]); FoundSuitableActor = Brush && Brush->IsStaticBrush(); } } } /** * Returns the current suitable actor pointed at by the Iterator * * @return Current suitable actor */ AActor* operator*() { check(ActorIndex<= World->GetCurrentLevel()->Actors.Num()); check(!ReachedEnd); AActor* Actor = World->GetCurrentLevel()->Actors[ActorIndex]; return Actor; } /** * Returns the current suitable actor pointed at by the Iterator * * @return Current suitable actor */ AActor* operator->() { check(ActorIndex<= World->GetCurrentLevel()->Actors.Num()); check(!ReachedEnd); AActor* Actor = World->GetCurrentLevel()->Actors[ActorIndex]; return Actor; } /** * Returns whether the iterator has reached the end and no longer points * to a suitable actor. * * @return true if iterator points to a suitable actor, false if it has reached the end */ explicit operator bool() const { return !ReachedEnd; } protected: /** Current index into actors array */ int32 ActorIndex; /** Whether we already reached the end */ bool ReachedEnd; /** Relevant world context */ UWorld* World; }; void UEditorEngine::bspRepartition( UWorld* InWorld, int32 iNode ) { FBSPUtils::bspRepartition(InWorld, iNode); } // // Build list of leaves. // static void EnlistLeaves( UModel* Model, TArray& iFronts, TArray& iBacks, int32 iNode ) { FBspNode& Node=Model->Nodes[iNode]; if( Node.iFront==INDEX_NONE ) iFronts.Add(iNode); else EnlistLeaves( Model, iFronts, iBacks, Node.iFront ); if( Node.iBack==INDEX_NONE ) iBacks.Add(iNode); else EnlistLeaves( Model, iFronts, iBacks, Node.iBack ); } void UEditorEngine::csgRebuild( UWorld* InWorld ) { GWarn->BeginSlowTask( NSLOCTEXT("UnrealEd", "RebuildingGeometry", "Rebuilding geometry"), false ); FBSPOps::GFastRebuild = 1; ABrush::GGeometryRebuildCause = TEXT("csgRebuild"); FinishAllSnaps(); // Empty the model out. InWorld->GetModel()->Modify(false); InWorld->GetModel()->EmptyModel(1, 1); // Count brushes. int32 BrushTotal=0, BrushCount=0; for( FStaticBrushIterator It(InWorld); It; ++It ) { ABrush* Brush = CastChecked(*It); if( !FActorEditorUtils::IsABuilderBrush(Brush) ) { BrushTotal++; } } // Check for the giant cube brush that is created for subtractive levels. // If it's found, apply the RemoveSurfaceMaterial to its polygons so they aren't lit or drawn. for(FStaticBrushIterator GiantCubeBrushIt(InWorld);GiantCubeBrushIt;++GiantCubeBrushIt) { ABrush* GiantCubeBrush = CastChecked(*GiantCubeBrushIt); if(GiantCubeBrush->Brush && GiantCubeBrush->Brush->Bounds.SphereRadius > HALF_WORLD_MAX) { check(GiantCubeBrush->Brush->Polys); for(int32 PolyIndex = 0;PolyIndex < GiantCubeBrush->Brush->Polys->Element.Num();PolyIndex++) { FPoly& Polygon = GiantCubeBrush->Brush->Polys->Element[PolyIndex]; const float PolygonArea = Polygon.Area(); if(PolygonArea > WORLD_MAX * WORLD_MAX * 0.99f && PolygonArea < WORLD_MAX * WORLD_MAX * 1.01f) { Polygon.Material = GEngine->RemoveSurfaceMaterial; } } } } // Compose all structural brushes and portals. for( FStaticBrushIterator It(InWorld); It; ++It ) { ABrush* Brush = CastChecked(*It); if( !FActorEditorUtils::IsABuilderBrush(Brush) ) { if ( !(Brush->PolyFlags&PF_Semisolid) || (Brush->BrushType!=Brush_Add) || (Brush->PolyFlags&PF_Portal) ) { // Treat portals as solids for cutting. if( Brush->PolyFlags & PF_Portal ) { Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; } BrushCount++; FFormatNamedArguments Args; Args.Add( TEXT("BrushCount"), BrushCount ); Args.Add( TEXT("BrushTotal"), BrushTotal ); GWarn->StatusUpdate( BrushCount, BrushTotal, FText::Format( NSLOCTEXT("UnrealEd", "ApplyingStructuralBrushF", "Applying structural brush {BrushCount} of {BrushTotal}"), Args ) ); Brush->Modify(false); bspBrushCSG( Brush, InWorld->GetModel(), Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false ); } } } // Repartition the structural BSP. { GWarn->StatusUpdate( 0, 4, NSLOCTEXT("UnrealEd", "RebuildBSPBuildingPolygons", "Rebuild BSP: Building polygons") ); bspBuildFPolys( InWorld->GetModel(), 0, 0 ); GWarn->StatusUpdate( 1, 4, NSLOCTEXT("UnrealEd", "RebuildBSPMergingPlanars", "Rebuild BSP: Merging planars") ); bspMergeCoplanars( InWorld->GetModel(), 0, 0 ); GWarn->StatusUpdate( 2, 4, NSLOCTEXT("UnrealEd", "RebuildBSPPartitioning", "Rebuild BSP: Partitioning") ); FBSPOps::bspBuild( InWorld->GetModel(), FBSPOps::BSP_Optimal, 15, 70, 0, 0 ); GWarn->UpdateProgress( 4, 4 ); } // Remember leaves. TArray iFronts, iBacks; if( InWorld->GetModel()->Nodes.Num() ) { EnlistLeaves( InWorld->GetModel(), iFronts, iBacks, 0 ); } // Compose all detail brushes. for( FStaticBrushIterator It(InWorld); It; ++It ) { ABrush* Brush = CastChecked(*It); if ( !FActorEditorUtils::IsABuilderBrush(Brush) && (Brush->PolyFlags&PF_Semisolid) && !(Brush->PolyFlags&PF_Portal) && Brush->BrushType==Brush_Add ) { BrushCount++; FFormatNamedArguments Args; Args.Add( TEXT("BrushCount"), BrushCount ); Args.Add( TEXT("BrushTotal"), BrushTotal ); GWarn->StatusUpdate( BrushCount, BrushTotal, FText::Format( NSLOCTEXT("UnrealEd", "ApplyingDetailBrushF", "Applying detail brush {BrushCount} of {BrushTotal}"), Args ) ); Brush->Modify(false); bspBrushCSG( Brush, InWorld->GetModel(), Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false ); } } // Optimize the sub-bsp's. GWarn->StatusUpdate( 0, 4, NSLOCTEXT("UnrealEd", "RebuildCSGOptimizingSubBSPs", "Rebuild CSG: Optimizing Sub-BSPs") ); int32 iNode; for( TArray::TIterator ItF(iFronts); ItF; ++ItF ) { if( (iNode=InWorld->GetModel()->Nodes[*ItF].iFront)!=INDEX_NONE ) { bspRepartition( InWorld, iNode ); } } for( TArray::TIterator ItB(iBacks); ItB; ++ItB ) { if( (iNode=InWorld->GetModel()->Nodes[*ItB].iBack)!=INDEX_NONE ) { bspRepartition( InWorld, iNode ); } } GWarn->StatusUpdate( 1, 4, NSLOCTEXT("UnrealEd", "RebuildBSPOptimizingGeometry", "Rebuild BSP: Optimizing geometry") ); bspOptGeom( InWorld->GetModel() ); // Build bounding volumes. GWarn->StatusUpdate( 2, 4, NSLOCTEXT("UnrealEd", "RebuildCSGBuildingBoundingVolumes", "Rebuild CSG: Building Bounding Volumes") ); FBSPOps::bspBuildBounds( InWorld->GetModel() ); // Rebuild dynamic brush BSP's. GWarn->StatusUpdate( 3, 4, NSLOCTEXT("UnrealEd", "RebuildCSGRebuildingDynamicBrushBSPs", "Rebuild CSG: Rebuilding Dynamic Brush BSPs") ); TArray DynamicBrushes; DynamicBrushes.Empty(); for( TActorIterator It(InWorld); It; ++It ) { ABrush* B=*It; if ( B && B->GetLevel() == InWorld->GetCurrentLevel() && B->Brush && !B->IsStaticBrush() ) { DynamicBrushes.Add(B); } } { FScopedSlowTask SlowTask(static_cast(DynamicBrushes.Num()), NSLOCTEXT("UnrealEd", "RebuildCSGRebuildingDynamicBrushBSPs", "Rebuild CSG: Rebuilding Dynamic Brush BSPs") ); for ( int32 BrushIndex = 0; BrushIndex < DynamicBrushes.Num(); BrushIndex++ ) { SlowTask.EnterProgressFrame(); ABrush* B = DynamicBrushes[BrushIndex]; FBSPOps::csgPrepMovingBrush(B); if ( GEngine->GetMapBuildCancelled() ) { break; } } } GWarn->UpdateProgress( 4, 4 ); // update static navigable geometry in current level RebuildStaticNavigableGeometry(InWorld->GetCurrentLevel()); // Empty EdPolys. InWorld->GetModel()->Polys->Element.Empty(); // Done. ABrush::GGeometryRebuildCause = nullptr; FBSPOps::GFastRebuild = 0; InWorld->GetCurrentLevel()->MarkPackageDirty(); GWarn->EndSlowTask(); } void UEditorEngine::polySetAndClearPolyFlags(UModel *Model, uint32 SetBits, uint32 ClearBits,bool SelectedOnly, bool UpdateBrush) { FBSPUtils::polySetAndClearPolyFlags(Model, SetBits, ClearBits, SelectedOnly, UpdateBrush); } bool UEditorEngine::polyFindBrush(UModel* InModel, int32 iSurf, FPoly& Poly) { return FBSPUtils::polyFindBrush(InModel, iSurf, Poly); } void UEditorEngine::polyUpdateBrush ( UModel* Model, int32 iSurf, bool bUpdateTexCoords, bool bOnlyRefreshSurfaceMaterials ) { FBSPUtils::polyUpdateBrush(Model, iSurf, bUpdateTexCoords, bOnlyRefreshSurfaceMaterials); } void UEditorEngine::polyGetLinkedPolys ( ABrush* InBrush, FPoly* InPoly, TArray* InPolyList ) { FBSPUtils::polyGetLinkedPolys(InBrush, InPoly, InPolyList); } void UEditorEngine::polySplitOverlappingEdges( TArray* InPolyList, TArray* InResult ) { FBSPUtils::polySplitOverlappingEdges(InPolyList, InResult); } void UEditorEngine::polyGetOuterEdgeList ( TArray* InPolyList, TArray* InEdgeList ) { FBSPUtils::polyGetOuterEdgeList(InPolyList, InEdgeList); } /*----------------------------------------------------------------------------- All transactional polygon selection functions -----------------------------------------------------------------------------*/ /** * Generates a list of brushes corresponding to the set of selected surfaces for the specified model. */ static void GetListOfUniqueBrushes( TArray* InBrushes, UModel *Model ) { InBrushes->Empty(); // Generate a list of unique brushes. for( int32 i = 0 ; i < Model->Surfs.Num() ; i++ ) { FBspSurf* Surf = &Model->Surfs[i]; if( Surf->PolyFlags & PF_Selected ) { if ( Surf->Actor ) { // See if we've already got this brush ... int32 brush; for( brush = 0 ; brush < InBrushes->Num() ; brush++ ) { if( Surf->Actor == (*InBrushes)[brush] ) { break; } } // ... if not, add it to the list. if( brush == InBrushes->Num() ) { (*InBrushes)[ InBrushes->AddUninitialized() ] = Surf->Actor; } } } } } void UEditorEngine::polySelectAll(UModel *Model) { polySetAndClearPolyFlags(Model,PF_Selected,0,0,0); } void UEditorEngine::polySelectMatchingGroups( UModel* Model ) { // @hack: polySelectMatchingGroups: do nothing for now as temp fix until this can be rewritten (crashes a lot) #if 0 FMemory::Memzero( GFlags1, sizeof(GFlags1) ); for( int32 i=0; iSurfs.Num(); i++ ) { FBspSurf *Surf = &Model->Surfs(i); if( Surf->PolyFlags&PF_Selected ) { FPoly Poly; polyFindBrush(Model,i,Poly); GFlags1[Poly.Actor->Layer.GetIndex()]=1; } } for( int32 i=0; iSurfs.Num(); i++ ) { FBspSurf *Surf = &Model->Surfs(i); FPoly Poly; polyFindBrush(Model,i,Poly); if ( (GFlags1[Poly.Actor->Layer.GetIndex()]) && (!(Surf->PolyFlags & PF_Selected)) ) { Model->ModifySurf( i, 0 ); GEditor->SelectBSPSurf( Model, i, true, false ); } } NoteSelectionChange(); #endif } void UEditorEngine::polySelectMatchingItems(UModel *InModel) { #if 0 FMemory::Memzero(GFlags1,sizeof(GFlags1)); FMemory::Memzero(GFlags2,sizeof(GFlags2)); for( int32 i=0; iSurfs.Num(); i++ ) { FBspSurf *Surf = &InModel->Surfs(i); if( Surf->Actor ) { if( Surf->PolyFlags & PF_Selected ) GFlags2[Surf->Actor->Brush->GetIndex()]=1; } if( Surf->PolyFlags&PF_Selected ) { FPoly Poly; polyFindBrush(InModel,i,Poly); GFlags1[Poly.ItemName.GetIndex()]=1; } } for( int32 i=0; iSurfs.Num(); i++ ) { FBspSurf *Surf = &InModel->Surfs(i); if( Surf->Actor ) { FPoly Poly; polyFindBrush(InModel,i,Poly); if ((GFlags1[Poly.ItemName.GetIndex()]) && ( GFlags2[Surf->Actor->Brush->GetIndex()]) && (!(Surf->PolyFlags & PF_Selected))) { InModel->ModifySurf( i, 0 ); GEditor->SelectBSPSurf( InModel, i, true, false ); } } } NoteSelectionChange(); #endif } enum EAdjacentsType { ADJACENT_ALL, // All adjacent polys ADJACENT_COPLANARS, // Adjacent coplanars only ADJACENT_WALLS, // Adjacent walls ADJACENT_FLOORS, // Adjacent floors or ceilings ADJACENT_SLANTS, // Adjacent slants }; /** * Selects all adjacent polygons (only coplanars if Coplanars==1) * @return Number of polygons newly selected. */ static int32 TagAdjacentsType(UWorld* InWorld, EAdjacentsType AdjacentType) { // Allocate GFlags1 check( GFlags1.Num() == 0 ); for( FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator ) { UModel* Model = (*Iterator)->Model; uint8* Ptr = new uint8[MAX_uint16+1]; FMemory::Memzero( Ptr, MAX_uint16+1 ); GFlags1.Add( Ptr ); } FVert *VertPool; FVector3f *Base,*Normal; uint8 b; int32 i; int Selected,Found; Selected = 0; // Find all points corresponding to selected vertices: int32 ModelIndex1 = 0; for( FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator ) { UModel* Model = (*Iterator)->Model; uint8* Flags1 = GFlags1[ModelIndex1++]; for (i=0; iNodes.Num(); i++) { FBspNode &Node = Model->Nodes[i]; FBspSurf &Poly = Model->Surfs[Node.iSurf]; if (Poly.PolyFlags & PF_Selected) { VertPool = &Model->Verts[Node.iVertPool]; for (b=0; bpVertex] = 1; } } } } // Select all unselected nodes for which two or more vertices are selected: ModelIndex1 = 0; int32 ModelIndex2 = -1; for( FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator ) { UModel* Model = (*Iterator)->Model; uint8* Flags1 = GFlags1[ModelIndex1]; ModelIndex1++; ModelIndex2++; for( i = 0 ; i < Model->Nodes.Num() ; i++ ) { FBspNode &Node = Model->Nodes[i]; FBspSurf &Poly = Model->Surfs[Node.iSurf]; if (!(Poly.PolyFlags & PF_Selected)) { Found = 0; VertPool = &Model->Verts[Node.iVertPool]; // Base = &Model->Points [Poly.pBase]; Normal = &Model->Vectors[Poly.vNormal]; // for (b=0; bpVertex]; // if (AdjacentType == ADJACENT_COPLANARS) { if (!GFlags2[ModelIndex2][Node.iSurf]) Found=0; } else if (AdjacentType == ADJACENT_FLOORS) { if (FMath::Abs(Normal->Z) <= 0.85) Found = 0; } else if (AdjacentType == ADJACENT_WALLS) { if (FMath::Abs(Normal->Z) >= 0.10) Found = 0; } else if (AdjacentType == ADJACENT_SLANTS) { if (FMath::Abs(Normal->Z) > 0.85) Found = 0; if (FMath::Abs(Normal->Z) < 0.10) Found = 0; } if (Found > 0) { Model->ModifySurf( Node.iSurf, 0 ); GEditor->SelectBSPSurf( Model, Node.iSurf, true, false ); Selected++; } } } } // Free GFlags1. for ( i = 0 ; i < GFlags1.Num() ; ++i ) { delete[] GFlags1[i]; } GFlags1.Empty(); GEditor->NoteSelectionChange(); return Selected; } void UEditorEngine::polySelectAdjacents(UWorld* InWorld, UModel* InModel) { do {} while (TagAdjacentsType(InWorld, ADJACENT_ALL) > 0); } void UEditorEngine::polySelectCoplanars(UWorld* InWorld, UModel* InModel) { // Allocate GFlags2 check( GFlags2.Num() == 0 ); for( FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator ) { UModel* Model = (*Iterator)->Model; uint8* Ptr = new uint8[MAX_uint16+1]; FMemory::Memzero( Ptr, MAX_uint16+1 ); GFlags2.Add( Ptr ); } ///////////// // Tag coplanars. int32 ModelIndex = 0; for( FConstLevelIterator Iterator = InWorld->GetLevelIterator(); Iterator; ++Iterator ) { UModel* Model = (*Iterator)->Model; uint8* Flags2 = GFlags2[ModelIndex++]; for(int32 SelectedNodeIndex = 0;SelectedNodeIndex < Model->Nodes.Num();SelectedNodeIndex++) { FBspNode& SelectedNode = Model->Nodes[SelectedNodeIndex]; FBspSurf& SelectedSurf = Model->Surfs[SelectedNode.iSurf]; if(SelectedSurf.PolyFlags & PF_Selected) { const FVector SelectedBase = (FVector)Model->Points[Model->Verts[SelectedNode.iVertPool].pVertex]; const FVector SelectedNormal = (FVector)Model->Vectors[SelectedSurf.vNormal]; for(int32 NodeIndex = 0;NodeIndex < Model->Nodes.Num();NodeIndex++) { FBspNode& Node = Model->Nodes[NodeIndex]; FBspSurf& Surf = Model->Surfs[Node.iSurf]; const FVector Base = (FVector)Model->Points[Model->Verts[Node.iVertPool].pVertex]; const FVector Normal = (FVector)Model->Vectors[Surf.vNormal]; const float ParallelDotThreshold = 0.98f; // roughly 11.4 degrees (!), but this is the long-standing behavior. if (FVector::Coincident(SelectedNormal, Normal, ParallelDotThreshold) && FVector::Coplanar(SelectedBase, SelectedNormal, Base, Normal, ParallelDotThreshold) && !(Surf.PolyFlags & PF_Selected)) { Flags2[Node.iSurf] = 1; } } } } } do {} while (TagAdjacentsType(InWorld, ADJACENT_COPLANARS) > 0); // Free GFlags2 for ( int32 i = 0 ; i < GFlags2.Num() ; ++i ) { delete[] GFlags2[i]; } GFlags2.Empty(); } void UEditorEngine::polySelectMatchingBrush(UModel *InModel) { TArray Brushes; GetListOfUniqueBrushes( &Brushes, InModel ); // Select all the faces. for( int32 i = 0 ; i < InModel->Surfs.Num() ; i++ ) { FBspSurf* Surf = &InModel->Surfs[i]; if ( Surf->Actor ) { // Select all the polys on each brush in the unique list. for( int32 brush = 0 ; brush < Brushes.Num() ; brush++ ) { ABrush* CurBrush = Brushes[brush]; if( Surf->Actor == CurBrush ) { for( int32 poly = 0 ; poly < CurBrush->Brush->Polys->Element.Num() ; poly++ ) { if( Surf->iBrushPoly == poly ) { InModel->ModifySurf( i, 0 ); GEditor->SelectBSPSurf( InModel, i, true, false ); } } } } } } NoteSelectionChange(); } void UEditorEngine::polySelectMatchingMaterial(UWorld* InWorld, bool bCurrentLevelOnly) { // true if at least one surface was selected. bool bSurfaceWasSelected = false; // true if default material representations have already been added to the materials list. bool bDefaultMaterialAdded = false; TArray Materials; if ( bCurrentLevelOnly ) { // Get list of unique materials that are on selected faces. for ( TSelectedSurfaceIterator It(InWorld) ; It ; ++It ) { if ( It->Material && It->Material != UMaterial::GetDefaultMaterial(MD_Surface) ) { Materials.AddUnique( It->Material ); } else if ( !bDefaultMaterialAdded ) { bDefaultMaterialAdded = true; // Add both representations of the default material. Materials.AddUnique( NULL ); Materials.AddUnique( UMaterial::GetDefaultMaterial(MD_Surface) ); } } // Select all surfaces with matching materials. for ( TSurfaceIterator It(InWorld) ; It ; ++It ) { // Map the default material to NULL, so that NULL assignments match manual default material assignments. if( Materials.Contains( It->Material ) ) { UModel* Model = It.GetModel(); const int32 SurfaceIndex = It.GetSurfaceIndex(); Model->ModifySurf( SurfaceIndex, 0 ); GEditor->SelectBSPSurf( Model, SurfaceIndex, true, false ); bSurfaceWasSelected = true; } } } else { // Get list of unique materials that are on selected faces. for ( TSelectedSurfaceIterator<> It(InWorld) ; It ; ++It ) { if ( It->Material && It->Material != UMaterial::GetDefaultMaterial(MD_Surface) ) { Materials.AddUnique( It->Material ); } else if ( !bDefaultMaterialAdded ) { bDefaultMaterialAdded = true; // Add both representations of the default material. Materials.AddUnique( NULL ); Materials.AddUnique( UMaterial::GetDefaultMaterial(MD_Surface) ); } } // Select all surfaces with matching materials. for ( TSurfaceIterator<> It(InWorld) ; It ; ++It ) { // Map the default material to NULL, so that NULL assignments match manual default material assignments. if( Materials.Contains( It->Material ) ) { UModel* Model = It.GetModel(); const int32 SurfaceIndex = It.GetSurfaceIndex(); Model->ModifySurf( SurfaceIndex, 0 ); GEditor->SelectBSPSurf( Model, SurfaceIndex, true, false ); bSurfaceWasSelected = true; } } } if ( bSurfaceWasSelected ) { NoteSelectionChange(); } } void UEditorEngine::polySelectMatchingResolution(UWorld* InWorld, bool bCurrentLevelOnly) { // true if at least one surface was selected. bool bSurfaceWasSelected = false; TArray SelectedResolutions; if (bCurrentLevelOnly == true) { for (TSelectedSurfaceIterator It(InWorld); It; ++It) { SelectedResolutions.AddUnique(It->LightMapScale); } if (SelectedResolutions.Num() > 0) { if (SelectedResolutions.Num() > 1) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "BSPSelect_DifferentResolutionsSelected", "Different selected resolutions.\nCan only select matching for a single resolution.")); } else { // Select all surfaces with matching materials. for (TSurfaceIterator It(InWorld); It; ++It) { if (SelectedResolutions.Contains(It->LightMapScale)) { UModel* Model = It.GetModel(); const int32 SurfaceIndex = It.GetSurfaceIndex(); Model->ModifySurf( SurfaceIndex, 0 ); GEditor->SelectBSPSurf( Model, SurfaceIndex, true, false ); bSurfaceWasSelected = true; } } } } } else { for (TSelectedSurfaceIterator<> It(InWorld); It; ++It) { SelectedResolutions.AddUnique(It->LightMapScale); } if (SelectedResolutions.Num() > 0) { if (SelectedResolutions.Num() > 1) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "BSPSelect_DifferentResolutionsSelected", "Different selected resolutions.\nCan only select matching for a single resolution.")); } else { // Select all surfaces with matching materials. for (TSurfaceIterator<> It(InWorld); It; ++It) { if (SelectedResolutions.Contains(It->LightMapScale)) { UModel* Model = It.GetModel(); const int32 SurfaceIndex = It.GetSurfaceIndex(); Model->ModifySurf( SurfaceIndex, 0 ); GEditor->SelectBSPSurf( Model, SurfaceIndex, true, false ); bSurfaceWasSelected = true; } } } } } if ( bSurfaceWasSelected ) { NoteSelectionChange(); } } void UEditorEngine::polySelectAdjacentWalls( UWorld* InWorld, UModel* InModel ) { do { } while (TagAdjacentsType(InWorld, ADJACENT_WALLS) > 0); } void UEditorEngine::polySelectAdjacentFloors( UWorld* InWorld, UModel* InModel ) { do { } while (TagAdjacentsType(InWorld, ADJACENT_FLOORS) > 0); } void UEditorEngine::polySelectAdjacentSlants( UWorld* InWorld, UModel* InModel ) { do { } while (TagAdjacentsType(InWorld, ADJACENT_SLANTS) > 0); } void UEditorEngine::polySelectReverse( UModel* InModel ) { for (int32 i=0; iSurfs.Num(); i++) { FBspSurf *Poly = &InModel->Surfs[i]; InModel->ModifySurf( i, 0 ); Poly->PolyFlags ^= PF_Selected; } } void UEditorEngine::polyMemorizeSet( UModel* InModel ) { for (int32 i=0; iSurfs.Num(); i++) { FBspSurf *Poly = &InModel->Surfs[i]; if (Poly->PolyFlags & PF_Selected) { if (!(Poly->PolyFlags & PF_Memorized)) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags |= (PF_Memorized); } } else { if (Poly->PolyFlags & PF_Memorized) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags &= (~PF_Memorized); } } } } void UEditorEngine::polyRememberSet( UModel* InModel ) { for (int32 i=0; iSurfs.Num(); i++) { FBspSurf *Poly = &InModel->Surfs[i]; if (Poly->PolyFlags & PF_Memorized) { if (!(Poly->PolyFlags & PF_Selected)) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags |= (PF_Selected); } } else { if (Poly->PolyFlags & PF_Selected) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags &= (~PF_Selected); } } } } void UEditorEngine::polyXorSet( UModel* InModel ) { int Flag1,Flag2; // for (int32 i=0; iSurfs.Num(); i++) { FBspSurf *Poly = &InModel->Surfs[i]; Flag1 = (Poly->PolyFlags & PF_Selected ) != 0; Flag2 = (Poly->PolyFlags & PF_Memorized) != 0; // if (Flag1 ^ Flag2) { if (!(Poly->PolyFlags & PF_Selected)) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags |= PF_Selected; } } else { if (Poly->PolyFlags & PF_Selected) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags &= (~PF_Selected); } } } } void UEditorEngine::polyUnionSet( UModel* InModel ) { for (int32 i=0; iSurfs.Num(); i++) { FBspSurf *Poly = &InModel->Surfs[i]; if (!(Poly->PolyFlags & PF_Memorized)) { if (Poly->PolyFlags & PF_Selected) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags &= (~PF_Selected); } } } } void UEditorEngine::polyIntersectSet( UModel* InModel ) { for (int32 i=0; iSurfs.Num(); i++) { FBspSurf *Poly = &InModel->Surfs[i]; if ((Poly->PolyFlags & PF_Memorized) && !(Poly->PolyFlags & PF_Selected)) { InModel->ModifySurf( i, 0 ); Poly->PolyFlags |= PF_Selected; } } } void UEditorEngine::polySelectZone( UModel* InModel ) { // identify the list of currently selected zones TArray iZoneList; for( int32 i = 0; i < InModel->Nodes.Num(); i++ ) { FBspNode* Node = &InModel->Nodes[i]; FBspSurf* Poly = &InModel->Surfs[ Node->iSurf ]; if( Poly->PolyFlags & PF_Selected ) { if( Node->iZone[1] != 0 ) { iZoneList.AddUnique( Node->iZone[1] ); //front zone } if( Node->iZone[0] != 0 ) { iZoneList.AddUnique( Node->iZone[0] ); //back zone } } } // select all polys that are match one of the zones identified above for( int32 i = 0; i < InModel->Nodes.Num(); i++ ) { FBspNode* Node = &InModel->Nodes[i]; for( int32 j = 0; j < iZoneList.Num(); j++ ) { if( Node->iZone[1] == iZoneList[j] || Node->iZone[0] == iZoneList[j] ) { FBspSurf* Poly = &InModel->Surfs[ Node->iSurf ]; InModel->ModifySurf( i, 0 ); Poly->PolyFlags |= PF_Selected; } } } } /*--------------------------------------------------------------------------------------- Brush selection functions ---------------------------------------------------------------------------------------*/ // // Generic selection routines // typedef int32 (*BRUSH_SEL_FUNC)( ABrush* Brush, int32 Tag ); static void MapSelect( UWorld* InWorld, BRUSH_SEL_FUNC Func, int32 Tag ) { for( FStaticBrushIterator It(InWorld); It; ++It ) { ABrush* Brush = CastChecked(*It); if( Func( Brush, Tag ) ) { GEditor->SelectActor( Brush, true, false ); } else { GEditor->SelectActor( Brush, false, false ); } } } /** * Selects no brushes. */ static int32 BrushSelectNoneFunc( ABrush* Actor, int32 Tag ) { return 0; } /** * Selects brushes by their CSG operation. */ static int32 BrushSelectOperationFunc( ABrush* Actor, int32 Tag ) { return ((EBrushType)Actor->BrushType == Tag) && !(Actor->PolyFlags & (PF_NotSolid | PF_Semisolid)); } void UEditorEngine::MapSelectOperation( UWorld* InWorld, EBrushType BrushType) { MapSelect( InWorld, BrushSelectOperationFunc, BrushType ); } int32 BrushSelectFlagsFunc( ABrush* Actor, int32 Tag ) { return Actor->PolyFlags & Tag; } void UEditorEngine::MapSelectFlags( UWorld* InWorld, uint32 Flags) { MapSelect( InWorld, BrushSelectFlagsFunc, (int)Flags ); } void UEditorEngine::MapBrushGet(UWorld* InWorld) { for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It ) { AActor* Actor = static_cast( *It ); checkSlow( Actor->IsA(AActor::StaticClass()) ); ABrush* BrushActor = Cast< ABrush >( Actor ); if( BrushActor && !FActorEditorUtils::IsABuilderBrush(Actor) ) { check( BrushActor->GetWorld() ); ABrush* WorldBrush = BrushActor->GetWorld()->GetDefaultBrush(); check( WorldBrush ); WorldBrush->Modify(false); WorldBrush->Brush->Polys->Element = BrushActor->Brush->Polys->Element; WorldBrush->CopyPosRotScaleFrom( BrushActor ); WorldBrush->ReregisterAllComponents(); break; } } GEditor->SelectNone( false, true ); GEditor->SelectActor(InWorld->GetDefaultBrush(), true, true); } void UEditorEngine::mapBrushPut() { for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It ) { AActor* Actor = static_cast( *It ); checkSlow( Actor->IsA(AActor::StaticClass()) ); ABrush* BrushActor = Cast< ABrush >( Actor ); if( BrushActor && !FActorEditorUtils::IsABuilderBrush(Actor) ) { check( BrushActor->GetWorld() ); ABrush* WorldBrush = BrushActor->GetWorld()->GetDefaultBrush(); check( WorldBrush ); BrushActor->Modify(false); BrushActor->Brush->Polys->Element = WorldBrush->Brush->Polys->Element; BrushActor->CopyPosRotScaleFrom( WorldBrush ); BrushActor->SetNeedRebuild(BrushActor->GetLevel()); WorldBrush->ReregisterAllComponents(); GLevelEditorModeTools().UpdateInternalData(); } } } // // Generic private routine for send to front / send to back // static void SendTo( UWorld* InWorld, int32 bSendToFirst ) { ULevel* Level = InWorld->GetCurrentLevel(); for (AActor* Actor : Level->Actors) { if(Actor) { Actor->Modify(); } } // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; //@todo locked levels - do we need to skip locked levels? // Partition. TArray Lists[2]; for( int32 i=2; iActors.Num(); i++ ) { if( Level->Actors[i] ) { Lists[(Level->Actors[i]->IsSelected() ? 1 : 0) ^ bSendToFirst ^ 1].Add( Level->Actors[i] ); Level->Actors[i]->MarkPackageDirty(); LevelDirtyCallback.Request(); } } // Refill. check(Level->Actors.Num()>=2); Level->Actors.RemoveAt(2,Level->Actors.Num()-2); for( int32 i=0; i<2; i++ ) { for( int32 j=0; jActors.Add( Lists[i][j] ); } } } void UEditorEngine::mapSendToFirst(UWorld* InWorld) { SendTo( InWorld, 0 ); } void UEditorEngine::mapSendToLast(UWorld* InWorld) { SendTo( InWorld, 1 ); } void UEditorEngine::mapSendToSwap(UWorld* InWorld) { int32 Count = 0; ULevel* Level = InWorld->GetCurrentLevel(); TObjectPtr* Actors[2]; // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; //@todo locked levels - skip for locked levels? for( int32 i=2; iActors.Num() && Count < 2; i++ ) { auto& Actor = Level->Actors[i]; if( Actor && Actor->IsSelected() ) { Actors[Count] = &Actor; Count++; Actor->MarkPackageDirty(); LevelDirtyCallback.Request(); } } if( Count == 2 ) { for (AActor* Actor : InWorld->GetCurrentLevel()->Actors) { Actor->Modify(); } Exchange( *Actors[0], *Actors[1] ); } } void UEditorEngine::MapSetBrush( UWorld* InWorld, EMapSetBrushFlags PropertiesMask, uint16 BrushColor, FName GroupName, uint32 SetPolyFlags, uint32 ClearPolyFlags, uint32 BrushType, int32 DrawType ) { // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; for( FStaticBrushIterator It(InWorld); It; ++It ) { ABrush* Brush = CastChecked(*It); if( !FActorEditorUtils::IsABuilderBrush(Brush) && Brush->IsSelected() ) { if( PropertiesMask & MSB_PolyFlags ) { Brush->Modify(false); Brush->PolyFlags = (Brush->PolyFlags & ~ClearPolyFlags) | SetPolyFlags; Brush->UpdateComponentTransforms(); Brush->MarkPackageDirty(); LevelDirtyCallback.Request(); } if( PropertiesMask & MSB_BrushType ) { Brush->Modify(false); Brush->BrushType = EBrushType(BrushType); Brush->UpdateComponentTransforms(); Brush->MarkPackageDirty(); LevelDirtyCallback.Request(); } } } } void UEditorEngine::polyTexPan(UModel *Model,int32 PanU,int32 PanV,int32 Absolute) { for(int32 SurfaceIndex = 0;SurfaceIndex < Model->Surfs.Num();SurfaceIndex++) { const FBspSurf& Surf = Model->Surfs[SurfaceIndex]; if(Surf.PolyFlags & PF_Selected) { if(Absolute) { Model->Points[Surf.pBase] = FVector3f::ZeroVector; } const FVector TextureU = (FVector)Model->Vectors[Surf.vTextureU]; const FVector TextureV = (FVector)Model->Vectors[Surf.vTextureV]; Model->Points[Surf.pBase] += FVector3f(PanU * (TextureU / TextureU.SizeSquared())); Model->Points[Surf.pBase] += FVector3f(PanV * (TextureV / TextureV.SizeSquared())); const bool bUpdateTexCoords = true; const bool bOnlyRefreshSurfaceMaterials = true; polyUpdateBrush(Model, SurfaceIndex, bUpdateTexCoords, bOnlyRefreshSurfaceMaterials); } } } void UEditorEngine::polyTexScale( UModel* Model, float UU, float UV, float VU, float VV, bool Absolute ) { for( int32 i=0; iSurfs.Num(); i++ ) { FBspSurf *Poly = &Model->Surfs[i]; if (Poly->PolyFlags & PF_Selected) { FVector OriginalU = (FVector)Model->Vectors[Poly->vTextureU]; FVector OriginalV = (FVector)Model->Vectors[Poly->vTextureV]; if( Absolute ) { OriginalU *= 1.0/OriginalU.Size(); OriginalV *= 1.0/OriginalV.Size(); } // Calc new vectors. Model->Vectors[Poly->vTextureU] = FVector3f(OriginalU * UU + OriginalV * UV); Model->Vectors[Poly->vTextureV] = FVector3f(OriginalU * VU + OriginalV * VV); // Update generating brush poly. const bool bUpdateTexCoords = true; const bool bOnlyRefreshSurfaceMaterials = true; polyUpdateBrush(Model, i, bUpdateTexCoords, bOnlyRefreshSurfaceMaterials); } } }