// Copyright Epic Games, Inc. All Rights Reserved. #include "BSPUtils.h" #include "CoreMinimal.h" #include "Misc/MemStack.h" #include "Misc/FeedbackContext.h" #include "Materials/MaterialInterface.h" #include "Model.h" #include "MaterialDomain.h" #include "Materials/Material.h" #include "Engine/Polys.h" #include "BSPOps.h" #include "Engine/Selection.h" #include "Modules/ModuleManager.h" #include "EngineDefines.h" #include "Misc/MessageDialog.h" #include "Misc/ScopedSlowTask.h" #include "GameFramework/Actor.h" #include "Engine/Brush.h" //#include "Editor/EditorEngine.h" //#include "EdMode.h" //#include "EditorModeManager.h" #include "SurfaceIterators.h" //#include "ActorEditorUtils.h" #include "Misc/FeedbackContext.h" #include "EngineUtils.h" IMPLEMENT_MODULE(FDefaultModuleImpl, BSPUtils) DEFINE_LOG_CATEGORY_STATIC(LogBspUtils, Log, All); /*--------------------------------------------------------------------------------------- Globals. ---------------------------------------------------------------------------------------*/ // Magic numbers. #define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ #define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ // // Status of filtered polygons: // enum EPolyNodeFilter { F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. }; // // Generic filter function called by BspFilterEdPolys. A and B are pointers // to any integers that your specific routine requires (or NULL if not needed). // typedef void (*BSP_FILTER_FUNC) ( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Leaf, ENodePlace ENodePlace ); // // Information used by FilterEdPoly. // class FCoplanarInfo { public: int32 iOriginalNode; int32 iBackNode; int BackNodeOutside; int FrontLeafOutside; int ProcessingBack; }; // // Function to filter an EdPoly through the Bsp, calling a callback // function for all chunks that fall into leaves. // void FilterEdPoly ( BSP_FILTER_FUNC FilterFunc, UModel *Model, int32 iNode, FPoly *EdPoly, FCoplanarInfo CoplanarInfo, int Outside ); // // Bsp statistics used by link topic function. // class FBspStats { public: int Polys; // Number of BspSurfs. int Nodes; // Total number of Bsp nodes. int MaxDepth; // Maximum tree depth. int AvgDepth; // Average tree depth. int Branches; // Number of nodes with two children. int Coplanars; // Number of nodes holding coplanar polygons (in same plane as parent). int Fronts; // Number of nodes with only a front child. int Backs; // Number of nodes with only a back child. int Leaves; // Number of nodes with no children. int FrontLeaves;// Number of leaf nodes that are in front of their parent. int BackLeaves; // Number of leaf nodes that are in back of their parent. int DepthCount; // Depth counter (used for finding average depth). } GBspStats; // // Global variables shared between bspBrushCSG and AddWorldToBrushFunc. These are very // tightly tied into the function AddWorldToBrush, not general-purpose. // int32 GDiscarded; // Number of polys discarded and not added. int32 GNode; // Node AddBrushToWorld is adding to. int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. UModel *GModel; // Level map Model we're adding to. /*---------------------------------------------------------------------------- EdPoly building and compacting. ----------------------------------------------------------------------------*/ // // Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 // and returns 1. Otherwise, returns 0. // int TryToMerge( FPoly *Poly1, FPoly *Poly2 ) { // Find one overlapping point. int32 Start1=0, Start2=0; for( Start1=0; Start1Vertices.Num(); Start1++ ) for( Start2=0; Start2Vertices.Num(); Start2++ ) if( FVector3f::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) goto FoundOverlap; return 0; FoundOverlap: // Wrap around trying to merge. int32 End1 = Start1; int32 End2 = Start2; int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; if( FVector3f::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) { End1 = Test1; Start2 = Test2; } else { Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; if( FVector3f::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) { Start1 = Test1; End2 = Test2; } else return 0; } // Build a new edpoly containing both polygons merged. FPoly NewPoly = *Poly1; NewPoly.Vertices.Empty(); int32 Vertex = End1; for( int32 i=0; iVertices.Num(); i++ ) { NewPoly.Vertices.Add(Poly1->Vertices[Vertex]); if( ++Vertex >= Poly1->Vertices.Num() ) Vertex=0; } Vertex = End2; for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) { if( ++Vertex >= Poly2->Vertices.Num() ) Vertex=0; NewPoly.Vertices.Add(Poly2->Vertices[Vertex]); } // Remove colinear vertices and check convexity. if( NewPoly.RemoveColinears() ) { *Poly1 = NewPoly; Poly2->Vertices.Empty(); return true; } else return 0; } // // Merge all polygons in coplanar list that can be merged convexly. // void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) { int32 MergeAgain = 1; while( MergeAgain ) { MergeAgain = 0; for( int32 i=0; iPolys->Element[PolyList[i]]; if( Poly1.Vertices.Num() > 0 ) { for( int32 j=i+1; jPolys->Element[PolyList[j]]; if( Poly2.Vertices.Num() > 0 ) { if( TryToMerge( &Poly1, &Poly2 ) ) MergeAgain=1; } } } } } } // // Convert a Bsp node's polygon to an EdPoly, add it to the list, and recurse. // void MakeEdPolys( UModel* Model, int32 iNode, TArray* DestArray ) { FBspNode* Node = &Model->Nodes[iNode]; FPoly Temp; if( FBSPUtils::bspNodeToFPoly(Model,iNode,&Temp) >= 3 ) { DestArray->Add(Temp); } if( Node->iFront!=INDEX_NONE ) { MakeEdPolys( Model, Node->iFront, DestArray ); } if( Node->iBack !=INDEX_NONE ) { MakeEdPolys( Model, Node->iBack, DestArray ); } if( Node->iPlane!=INDEX_NONE ) { MakeEdPolys( Model, Node->iPlane, DestArray ); } } void FBSPUtils::bspBuildFPolys( UModel* Model, bool SurfLinks, int32 iNode, TArray* DestArray ) { if (DestArray == NULL) { DestArray = &Model->Polys->Element; } DestArray->Reset(); if( Model->Nodes.Num() ) { MakeEdPolys( Model, iNode, DestArray ); } if( !SurfLinks ) { const int32 ElementsCount = DestArray->Num(); for( int32 i=0; iPolys->Element.Num(); // Mark all polys as unprocessed. for( int32 i=0; iPolys->Element.Num(); i++ ) Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; // Find matching coplanars and merge them. FMemMark Mark(FMemStack::Get()); int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; int32 n=0; for( int32 i=0; iPolys->Element.Num(); i++ ) { FPoly* EdPoly = &Model->Polys->Element[i]; if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) { int32 PolyCount = 0; PolyList[PolyCount++] = i; EdPoly->PolyFlags |= PF_EdProcessed; for( int32 j=i+1; jPolys->Element.Num(); j++ ) { FPoly* OtherPoly = &Model->Polys->Element[j]; if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) { float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; if ( Dist>-0.001 && Dist<0.001 && (OtherPoly->Normal|EdPoly->Normal)>0.9999 && (MergeDisparateTextures || ( FVector::PointsAreNear((FVector)OtherPoly->TextureU, (FVector)EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) && FVector::PointsAreNear((FVector)OtherPoly->TextureV, (FVector)EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) { OtherPoly->PolyFlags |= PF_EdProcessed; PolyList[PolyCount++] = j; } } } if( PolyCount > 1 ) { MergeCoplanars( Model, PolyList, PolyCount ); n++; } } } // UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); Mark.Pop(); // Get rid of empty EdPolys while remapping iLinks. FMemMark Mark2(FMemStack::Get()); int32 j=0; int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; for( int32 i=0; iPolys->Element.Num(); i++ ) { if( Model->Polys->Element[i].Vertices.Num() ) { Remap[i] = j; Model->Polys->Element[j] = Model->Polys->Element[i]; j++; } } Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); if( RemapLinks ) { for( int32 i=0; iPolys->Element.Num(); i++ ) { if (Model->Polys->Element[i].iLink != INDEX_NONE) { CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; } } } // UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); Mark2.Pop(); } /*---------------------------------------------------------------------------- CSG types & general-purpose callbacks. ----------------------------------------------------------------------------*/ // // Recursive worker function called by BspCleanup. // void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) { FBspNode *Node = &Model->Nodes[iNode]; // Transactionally empty vertices of tag-for-empty nodes. Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); // Recursively clean up front, back, and plane nodes. if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); // Reload Node since the recusive call aliases it. Node = &Model->Nodes[iNode]; // If this is an empty node with a coplanar, replace it with the coplanar. if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) { FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; // Stick our front, back, and parent nodes on the coplanar. if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) { PlaneNode->iFront = Node->iFront; PlaneNode->iBack = Node->iBack; } else { PlaneNode->iFront = Node->iBack; PlaneNode->iBack = Node->iFront; } if( iParent == INDEX_NONE ) { // This node is the root. *Node = *PlaneNode; // Replace root. PlaneNode->NumVertices = 0; // Mark as unused. } else { // This is a child node. FBspNode *ParentNode = &Model->Nodes[iParent]; if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; else UE_LOG(LogBspUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); } } else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) { // Delete empty nodes with no fronts or backs. // Replace empty nodes with only fronts. // Replace empty nodes with only backs. int32 iReplacementNode; if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; else iReplacementNode = INDEX_NONE; if( iParent == INDEX_NONE ) { // Root. if( iReplacementNode == INDEX_NONE ) { Model->Nodes.Empty(); } else { *Node = Model->Nodes[iReplacementNode]; } } else { // Regular node. FBspNode *ParentNode = &Model->Nodes[iParent]; if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; else UE_LOG(LogBspUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); } } } void FBSPUtils::bspCleanup( UModel *Model ) { if( Model->Nodes.Num() > 0 ) CleanupNodes( Model, 0, INDEX_NONE ); } /*---------------------------------------------------------------------------- CSG leaf filter callbacks. ----------------------------------------------------------------------------*/ void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) { switch( Filter ) { case F_OUTSIDE: case F_COPLANAR_OUTSIDE: FBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly); break; case F_COSPATIAL_FACING_OUT: if( !(EdPoly->PolyFlags & PF_Semisolid) ) FBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly); break; case F_INSIDE: case F_COPLANAR_INSIDE: case F_COSPATIAL_FACING_IN: break; } } void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) { switch( Filter ) { case F_OUTSIDE: case F_COPLANAR_OUTSIDE: // Only affect the world poly if it has been cut. if( EdPoly->PolyFlags & PF_EdCut ) FBSPOps::bspAddNode( GModel, GLastCoplanar, FBSPOps::NODE_Plane, NF_IsNew, EdPoly ); break; case F_INSIDE: case F_COPLANAR_INSIDE: case F_COSPATIAL_FACING_IN: case F_COSPATIAL_FACING_OUT: // Discard original poly. GDiscarded++; if( GModel->Nodes[GNode].NumVertices ) { GModel->Nodes[GNode].NumVertices = 0; } break; } } void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) { switch (Filter) { case F_OUTSIDE: case F_COSPATIAL_FACING_OUT: case F_COSPATIAL_FACING_IN: case F_COPLANAR_OUTSIDE: break; case F_COPLANAR_INSIDE: case F_INSIDE: EdPoly->Reverse(); FBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly); // Add to Bsp back EdPoly->Reverse(); break; } } void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) { switch( Filter ) { case F_OUTSIDE: case F_COPLANAR_OUTSIDE: case F_COSPATIAL_FACING_IN: // Only affect the world poly if it has been cut. if( EdPoly->PolyFlags & PF_EdCut ) FBSPOps::bspAddNode( GModel, GLastCoplanar, FBSPOps::NODE_Plane, NF_IsNew, EdPoly ); break; case F_INSIDE: case F_COPLANAR_INSIDE: case F_COSPATIAL_FACING_OUT: // Discard original poly. GDiscarded++; if( GModel->Nodes[GNode].NumVertices ) { GModel->Nodes[GNode].NumVertices = 0; } break; } } void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter,ENodePlace ENodePlace ) { switch( Filter ) { case F_OUTSIDE: case F_COPLANAR_OUTSIDE: case F_COSPATIAL_FACING_IN: case F_COSPATIAL_FACING_OUT: // Ignore. break; case F_INSIDE: case F_COPLANAR_INSIDE: if( EdPoly->Fix()>=3 ) GModel->Polys->Element.Add(*EdPoly); break; } } void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter,ENodePlace ENodePlace ) { switch( Filter ) { case F_OUTSIDE: case F_COPLANAR_OUTSIDE: case F_COSPATIAL_FACING_IN: // Ignore. break; case F_INSIDE: case F_COPLANAR_INSIDE: case F_COSPATIAL_FACING_OUT: if( EdPoly->Fix() >= 3 ) GModel->Polys->Element.Add(*EdPoly); break; } } void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter,ENodePlace ENodePlace ) { switch( Filter ) { case F_INSIDE: case F_COPLANAR_INSIDE: case F_COSPATIAL_FACING_OUT: case F_COSPATIAL_FACING_IN: // Ignore. break; case F_OUTSIDE: case F_COPLANAR_OUTSIDE: if( EdPoly->Fix()>=3 ) GModel->Polys->Element.Add(*EdPoly); break; } } void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter,ENodePlace ENodePlace ) { switch( Filter ) { case F_OUTSIDE: case F_COPLANAR_OUTSIDE: case F_COSPATIAL_FACING_OUT: // Ignore. break; case F_COPLANAR_INSIDE: case F_INSIDE: case F_COSPATIAL_FACING_IN: if( EdPoly->Fix() >= 3 ) { EdPoly->Reverse(); GModel->Polys->Element.Add(*EdPoly); EdPoly->Reverse(); } break; } } /*---------------------------------------------------------------------------- CSG polygon filtering routine (calls the callbacks). ----------------------------------------------------------------------------*/ // // Handle a piece of a polygon that was filtered to a leaf. // void FilterLeaf ( BSP_FILTER_FUNC FilterFunc, UModel* Model, int32 iNode, FPoly* EdPoly, FCoplanarInfo CoplanarInfo, int32 LeafOutside, ENodePlace ENodePlace ) { EPolyNodeFilter FilterType; if( CoplanarInfo.iOriginalNode == INDEX_NONE ) { // Processing regular, non-coplanar polygons. FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; FilterFunc( Model, iNode, EdPoly, FilterType, ENodePlace ); } else if( CoplanarInfo.ProcessingBack ) { // Finished filtering polygon through tree in back of parent coplanar. DoneFilteringBack: if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; else { UE_LOG(LogBspUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); return; } FilterFunc( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FBSPOps::NODE_Plane ); } else { CoplanarInfo.FrontLeafOutside = LeafOutside; if( CoplanarInfo.iBackNode == INDEX_NONE ) { // Back tree is empty. LeafOutside = CoplanarInfo.BackNodeOutside; goto DoneFilteringBack; } else { // Call FilterEdPoly to filter through the back. This will result in // another call to FilterLeaf with iNode = leaf this falls into in the // back tree and EdPoly = the final EdPoly to insert. CoplanarInfo.ProcessingBack=1; FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside ); } } } // // Filter an EdPoly through the Bsp recursively, calling FilterFunc // for all chunks that fall into leaves. FCoplanarInfo is used to // handle the tricky case of double-recursion for polys that must be // filtered through a node's front, then filtered through the node's back, // in order to handle coplanar CSG properly. // void FilterEdPoly ( BSP_FILTER_FUNC FilterFunc, UModel *Model, int32 iNode, FPoly *EdPoly, FCoplanarInfo CoplanarInfo, int32 Outside ) { int32 SplitResult,iOurFront,iOurBack; int32 NewFrontOutside,NewBackOutside; FilterLoop: // Split em. FPoly TempFrontEdPoly,TempBackEdPoly; SplitResult = EdPoly->SplitWithPlane ( Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], &TempFrontEdPoly, &TempBackEdPoly, 0 ); // Process split results. if( SplitResult == SP_Front ) { Front: FBspNode *Node = &Model->Nodes[iNode]; Outside = Outside || Node->IsCsg(); if( Node->iFront == INDEX_NONE ) { FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FBSPOps::NODE_Front); } else { iNode = Node->iFront; goto FilterLoop; } } else if( SplitResult == SP_Back ) { FBspNode *Node = &Model->Nodes[iNode]; Outside = Outside && !Node->IsCsg(); if( Node->iBack == INDEX_NONE ) { FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FBSPOps::NODE_Back ); } else { iNode=Node->iBack; goto FilterLoop; } } else if( SplitResult == SP_Coplanar ) { if( CoplanarInfo.iOriginalNode != INDEX_NONE ) { // This will happen once in a blue moon when a polygon is barely outside the // coplanar threshold and is split up into a new polygon that is // is barely inside the coplanar threshold. To handle this, just classify // it as front and it will be handled properly. FBSPOps::GErrors++; // UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); goto Front; } CoplanarInfo.iOriginalNode = iNode; CoplanarInfo.iBackNode = INDEX_NONE; CoplanarInfo.ProcessingBack = 0; CoplanarInfo.BackNodeOutside = Outside; NewFrontOutside = Outside; // See whether Node's iFront or iBack points to the side of the tree on the front // of this polygon (will be as expected if this polygon is facing the same // way as first coplanar in link, otherwise opposite). if( (FVector(Model->Nodes[iNode].Plane) | (FVector)EdPoly->Normal) >= 0.0 ) { iOurFront = Model->Nodes[iNode].iFront; iOurBack = Model->Nodes[iNode].iBack; if( Model->Nodes[iNode].IsCsg() ) { CoplanarInfo.BackNodeOutside = 0; NewFrontOutside = 1; } } else { iOurFront = Model->Nodes[iNode].iBack; iOurBack = Model->Nodes[iNode].iFront; if( Model->Nodes[iNode].IsCsg() ) { CoplanarInfo.BackNodeOutside = 1; NewFrontOutside = 0; } } // Process front and back. if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) { // No front or back. CoplanarInfo.ProcessingBack = 1; CoplanarInfo.FrontLeafOutside = NewFrontOutside; FilterLeaf ( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, CoplanarInfo.BackNodeOutside, FBSPOps::NODE_Plane ); } else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) { // Back but no front. CoplanarInfo.ProcessingBack = 1; CoplanarInfo.iBackNode = iOurBack; CoplanarInfo.FrontLeafOutside = NewFrontOutside; iNode = iOurBack; Outside = CoplanarInfo.BackNodeOutside; goto FilterLoop; } else { // Has a front and maybe a back. // Set iOurBack up to process back on next call to FilterLeaf, and loop // to process front. Next call to FilterLeaf will set FrontLeafOutside. CoplanarInfo.ProcessingBack = 0; // May be a node or may be INDEX_NONE. CoplanarInfo.iBackNode = iOurBack; iNode = iOurFront; Outside = NewFrontOutside; goto FilterLoop; } } else if( SplitResult == SP_Split ) { // Front half of split. if( Model->Nodes[iNode].IsCsg() ) { NewFrontOutside = 1; NewBackOutside = 0; } else { NewFrontOutside = Outside; NewBackOutside = Outside; } if( Model->Nodes[iNode].iFront==INDEX_NONE ) { FilterLeaf ( FilterFunc, Model, iNode, &TempFrontEdPoly, CoplanarInfo, NewFrontOutside, FBSPOps::NODE_Front ); } else { FilterEdPoly ( FilterFunc, Model, Model->Nodes[iNode].iFront, &TempFrontEdPoly, CoplanarInfo, NewFrontOutside ); } // Back half of split. if( Model->Nodes[iNode].iBack==INDEX_NONE ) { FilterLeaf ( FilterFunc, Model, iNode, &TempBackEdPoly, CoplanarInfo, NewBackOutside, FBSPOps::NODE_Back ); } else { FilterEdPoly ( FilterFunc, Model, Model->Nodes[iNode].iBack, &TempBackEdPoly, CoplanarInfo, NewBackOutside ); } } } // // Regular entry into FilterEdPoly (so higher-level callers don't have to // deal with unnecessary info). Filters starting at root. // void BspFilterFPoly( BSP_FILTER_FUNC FilterFunc, UModel *Model, FPoly *EdPoly ) { FCoplanarInfo StartingCoplanarInfo; StartingCoplanarInfo.iOriginalNode = INDEX_NONE; if( Model->Nodes.Num() == 0 ) { // If Bsp is empty, process at root. FilterFunc( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FBSPOps::NODE_Root ); } else { // Filter through Bsp. FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside ); } } int FBSPUtils::bspNodeToFPoly ( UModel *Model, int32 iNode, FPoly *EdPoly ) { FPoly BrushEdPoly; FBspNode &Node = Model->Nodes[iNode]; FBspSurf &Poly = Model->Surfs[Node.iSurf]; FVert *VertPool = &Model->Verts[ Node.iVertPool ]; EdPoly->Base = Model->Points [Poly.pBase]; EdPoly->Normal = Model->Vectors[Poly.vNormal]; EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); EdPoly->iLinkSurf = Node.iSurf; EdPoly->Material = Poly.Material; EdPoly->Actor = Poly.Actor; EdPoly->iBrushPoly = Poly.iBrushPoly; if( polyFindBrush(Model,Node.iSurf,BrushEdPoly) ) EdPoly->ItemName = BrushEdPoly.ItemName; else EdPoly->ItemName = NAME_None; EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; EdPoly->LightMapScale = Poly.LightMapScale; EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; EdPoly->Vertices.Empty(); for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) { EdPoly->Vertices.Add(Model->Points[VertPool[VertexIndex].pVertex]); } if(EdPoly->Vertices.Num() < 3) { EdPoly->Vertices.Empty(); } else { // Remove colinear points and identical points (which will appear // if T-joints were eliminated). EdPoly->RemoveColinears(); } return EdPoly->Vertices.Num(); } /*--------------------------------------------------------------------------------------- World filtering. ---------------------------------------------------------------------------------------*/ // // Filter all relevant world polys through the brush. // void FilterWorldThroughBrush ( UModel* Model, UModel* Brush, EBrushType BrushType, ECsgOper CSGOper, int32 iNode, FSphere* BrushSphere ) { // Loop through all coplanars. while( iNode != INDEX_NONE ) { // Get surface. int32 iSurf = Model->Nodes[iNode].iSurf; // Skip new nodes and their children, which are guaranteed new. if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) return; // Sphere reject. int DoFront = 1, DoBack = 1; if( BrushSphere ) { float Dist = Model->Nodes[iNode].Plane.PlaneDot( (FVector3f)BrushSphere->Center ); DoFront = (Dist >= -BrushSphere->W); DoBack = (Dist <= +BrushSphere->W); } // Process only polys that aren't empty. FPoly TempEdPoly; if( DoFront && DoBack && (FBSPUtils::bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) { TempEdPoly.Actor = Model->Surfs[iSurf].Actor; TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; if( BrushType==Brush_Add || BrushType==Brush_Subtract ) { // Add and subtract work the same in this step. GNode = iNode; GModel = Model; GDiscarded = 0; GNumNodes = Model->Nodes.Num(); // Find last coplanar in chain. GLastCoplanar = iNode; while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; // Do the filter operation. BspFilterFPoly ( BrushType==Brush_Add ? AddWorldToBrushFunc : SubtractWorldToBrushFunc, Brush, &TempEdPoly ); if( GDiscarded == 0 ) { // Get rid of all the fragments we added. Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, EAllowShrinking::No ); } else { // Tag original world poly for deletion; has been deleted or replaced by partial fragments. if( GModel->Nodes[GNode].NumVertices ) { GModel->Nodes[GNode].NumVertices = 0; } } } else if( CSGOper == CSG_Intersect ) { BspFilterFPoly( IntersectWorldWithBrushFunc, Brush, &TempEdPoly ); } else if( CSGOper == CSG_Deintersect ) { BspFilterFPoly( DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly ); } } // Now recurse to filter all of the world's children nodes. if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush ( Model, Brush, BrushType, CSGOper, Model->Nodes[iNode].iFront, BrushSphere ); if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush ( Model, Brush, BrushType, CSGOper, Model->Nodes[iNode].iBack, BrushSphere ); iNode = Model->Nodes[iNode].iPlane; } } int FBSPUtils::bspBrushCSG ( ABrush* Actor, UModel* Model, UModel* TempModel, UMaterialInterface* SelectedMaterialInstance, uint32 PolyFlags, EBrushType BrushType, ECsgOper CSGOper, bool bBuildBounds, bool bMergePolys, bool bReplaceNULLMaterialRefs, bool bShowProgressBar/*=true*/ ) { uint32 NotPolyFlags = 0; int32 NumPolysFromBrush=0,i,j,ReallyBig; UModel* Brush = Actor->Brush; // Note no errors. FBSPOps::GErrors = 0; // Make sure we're in an acceptable state. if( !Brush ) { return 0; } // Non-solid and semisolid stuff can only be added. if( BrushType != Brush_Add ) { NotPolyFlags |= (PF_Semisolid | PF_NotSolid); } TempModel->EmptyModel(1,1); // Update status. ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; if( ReallyBig ) { FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); if (BrushType != Brush_MAX) { if (BrushType == Brush_Add) { Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); } else if (BrushType == Brush_Subtract) { Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); } } else if (CSGOper != CSG_None) { if (CSGOper == CSG_Intersect) { Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); } else if (CSGOper == CSG_Deintersect) { Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); } } GWarn->BeginSlowTask( Description, true ); // Transform original brush poly into same coordinate system as world // so Bsp filtering operations make sense. GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); } const FVector Scale = Actor->GetActorScale(); const FRotator Rotation = Actor->GetActorRotation(); const FVector Location = Actor->GetActorLocation(); const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); // Cache actor transform which is used for the geometry being built Brush->OwnerLocationWhenLastBuilt = (FVector3f)Location; Brush->OwnerRotationWhenLastBuilt = Rotation; Brush->OwnerScaleWhenLastBuilt = (FVector3f)Scale; Brush->bCachedOwnerTransformValid = true; for( i=0; iPolys->Element.Num(); i++ ) { FPoly& CurrentPoly = Brush->Polys->Element[i]; // Set texture the first time. if ( bReplaceNULLMaterialRefs ) { auto& PolyMat = CurrentPoly.Material; if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) { PolyMat = SelectedMaterialInstance; } } // Get the brush poly. FPoly DestEdPoly = CurrentPoly; check(CurrentPoly.iLinkPolys->Element.Num()); // Set its backward brush link. DestEdPoly.Actor = Actor; DestEdPoly.iBrushPoly = i; // Update its flags. DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; // Set its internal link. if (DestEdPoly.iLink == INDEX_NONE) { DestEdPoly.iLink = i; } // Transform it. DestEdPoly.Scale( (FVector3f)Scale ); DestEdPoly.Rotate( FRotator3f(Rotation) ); // LWC_TODO: Precision loss? DestEdPoly.Transform( (FVector3f)Location ); // LWC_TODO: Precision loss // Reverse winding and normal if the parent brush is mirrored if (bIsMirrored) { DestEdPoly.Reverse(); DestEdPoly.CalcNormal(); } // Add poly to the temp model. TempModel->Polys->Element.Add( DestEdPoly ); } if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); // Pass the brush polys through the world Bsp. if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) { // Empty the brush. Brush->EmptyModel(1,1); // Intersect and deintersect. for( i=0; iPolys->Element.Num(); i++ ) { FPoly EdPoly = TempModel->Polys->Element[i]; GModel = Brush; // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? BspFilterFPoly( CSGOper==CSG_Intersect ? IntersectBrushWithWorldFunc : DeIntersectBrushWithWorldFunc, Model, &EdPoly ); } NumPolysFromBrush = Brush->Polys->Element.Num(); } else { // Add and subtract. TMap SurfaceIndexRemap; for( i=0; iPolys->Element.Num(); i++ ) { FPoly EdPoly = TempModel->Polys->Element[i]; // Mark the polygon as non-cut so that it won't be harmed unless it must // be split, and set iLink so that BspAddNode will know to add its information // if a node is added based on this poly. EdPoly.PolyFlags &= ~(PF_EdCut); const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); if (SurfaceIndexPtr == nullptr) { const int32 NewSurfaceIndex = Model->Surfs.Num(); SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; } else { EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; } // Filter brush through the world. BspFilterFPoly( BrushType==Brush_Add ? AddBrushToWorldFunc : SubtractBrushFromWorldFunc, Model, &EdPoly ); } } if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) { // Quickly build a Bsp for the brush, tending to minimize splits rather than balance // the tree. We only need the cutting planes, though the entire Bsp struct (polys and // all) is built. FBspPointsGrid* LevelModelPointsGrid = FBspPointsGrid::GBspPoints; FBspPointsGrid* LevelModelVectorsGrid = FBspPointsGrid::GBspVectors; // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. TUniquePtr BspPoints = MakeUnique(50.0f, THRESH_POINTS_ARE_SAME); TUniquePtr BspVectors = MakeUnique(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); FBspPointsGrid::GBspPoints = BspPoints.Get(); FBspPointsGrid::GBspVectors = BspVectors.Get(); if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); FBSPOps::bspBuild( TempModel, FBSPOps::BSP_Lame, 0, 70, 1, 0 ); // Reinstate the original BspPointsGrids used for building the level Model. FBspPointsGrid::GBspPoints = LevelModelPointsGrid; FBspPointsGrid::GBspVectors = LevelModelVectorsGrid; if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); GModel = Brush; TempModel->BuildBound(); FSphere BrushSphere = TempModel->Bounds.GetSphere(); FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere ); } if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) { if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); // Link polys obtained from the original brush. for( i=NumPolysFromBrush-1; i>=0; i-- ) { FPoly *DestEdPoly = &Brush->Polys->Element[i]; for( j=0; jiLink == Brush->Polys->Element[j].iLink ) { DestEdPoly->iLink = j; break; } } if( j >= i ) DestEdPoly->iLink = i; } // Link polys obtained from the world. for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) { FPoly *DestEdPoly = &Brush->Polys->Element[i]; for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) { DestEdPoly->iLink = j; break; } } if( j >= i ) DestEdPoly->iLink = i; } Brush->Linked = 1; // Detransform the obtained brush back into its original coordinate system. for( i=0; iPolys->Element.Num(); i++ ) { FPoly *DestEdPoly = &Brush->Polys->Element[i]; DestEdPoly->Transform((FVector3f)-Location); DestEdPoly->Rotate(FRotator3f(Rotation.GetInverse())); DestEdPoly->Scale(FVector3f(1.0f) / (FVector3f)Scale); DestEdPoly->Fix(); DestEdPoly->Actor = NULL; DestEdPoly->iBrushPoly = i; } } if( BrushType==Brush_Add || BrushType==Brush_Subtract ) { // Clean up nodes, reset node flags. bspCleanup( Model ); // Rebuild bounding volumes. if( bBuildBounds ) { FBSPOps::bspBuildBounds( Model ); } } Brush->NumUniqueVertices = TempModel->Points.Num(); // Release TempModel. TempModel->EmptyModel(1,1); // Merge coplanars if needed. if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) { if( ReallyBig ) { GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); } if( bMergePolys ) { bspMergeCoplanars( Brush, 1, 0 ); } } if( ReallyBig ) { GWarn->EndSlowTask(); } return 1 + FBSPOps::GErrors; } /*--------------------------------------------------------------------------------------- Functions for maintaining linked geometry lists. ---------------------------------------------------------------------------------------*/ // // A node and vertex number corresponding to a point, used in generating Bsp side links. // class FPointVert { public: int32 iNode; int32 nVertex; FPointVert* Next; }; // // A list of point/vertex links, used in generating Bsp side links. // class FPointVertList { public: // Variables. UModel *Model; FPointVert **Index; FMemMark* Mark; /** Initialization constructor. */ FPointVertList() : Model(NULL) , Index(NULL) , Mark(NULL) {} /** Destructor. */ ~FPointVertList() { check(!Mark); } // Allocate. void Alloc( UModel *ThisModel ) { check(!Mark); Mark = new FMemMark(FMemStack::Get()); // Allocate working tables. Model = ThisModel; Index = new(FMemStack::Get(),MEM_Zeroed,Model->Points.Num())FPointVert*; } // Free. void Free() { delete Mark; Mark = NULL; } // Add all of a node's vertices to a node-vertex list. void AddNode( int32 iNode ) { FBspNode& Node = Model->Nodes[iNode]; FVert* VertPool = &Model->Verts[Node.iVertPool]; for( int32 i=0; i < Node.NumVertices; i++ ) { int32 pVertex = VertPool[i].pVertex; // Add new point/vertex pair to array, and insert new array entry // between index and first entry. FPointVert *PointVert = new(FMemStack::Get())FPointVert; PointVert->iNode = iNode; PointVert->nVertex = i; PointVert->Next = Index[pVertex]; Index[pVertex] = PointVert; } } // Add all nodes' vertices in the model to a node-vertex list. void AddAllNodes() { for( int32 iNode=0; iNode < Model->Nodes.Num(); iNode++ ) AddNode( iNode ); } // Remove all of a node's vertices from a node-vertex list. void RemoveNode( int32 iNode ) { FBspNode& Node = Model->Nodes[iNode]; FVert* VertPool = &Model->Verts[Node.iVertPool]; // Loop through all of the node's vertices and search through the // corresponding point's node-vert list, and delink this node. int Count=0; for( int32 i = 0; i < Node.NumVertices; i++ ) { int32 pVertex = VertPool[i].pVertex; for( FPointVert **PrevLink = &Index[pVertex]; *PrevLink; PrevLink=&(*PrevLink)->Next ) { if( (*PrevLink)->iNode == iNode ) { // Delink this entry from the list. *PrevLink = (*PrevLink)->Next; Count++; if( *PrevLink == NULL ) break; } } // Node's vertex wasn't found, there's a bug. check(Count>=1); } } }; /*--------------------------------------------------------------------------------------- Geometry optimization. ---------------------------------------------------------------------------------------*/ // // Add a point to a Bsp node before a specified vertex (between it and the previous one). // VertexNumber can be from 0 (before first) to Node->NumVertices (after last). // // Splits node into two coplanar polys if necessary. If the polygon is split, the // vertices will be distributed among this node and it's newly-linked iPlane node // in an arbitrary way, that preserves the clockwise orientation of the vertices. // // Maintains node-vertex list, if not NULL. // void AddPointToNode ( UModel* Model, FPointVertList* PointVerts, int32 iNode, int32 VertexNumber, int32 pVertex ) { FBspNode& Node = Model->Nodes[iNode]; if( Node.NumVertices >= FBspNode::MAX_NODE_VERTICES - 1 ) { // Just refuse to add point: This is a non-fatal problem. // UE_LOG(LogEditorBsp, Warning, TEXT("Node side limit reached") ); return; } // Remove node from vertex list, since vertex numbers will be reordered. if( PointVerts ) PointVerts->RemoveNode( iNode ); int32 iOldVert = Node.iVertPool; Node.iVertPool = Model->Verts.AddUninitialized( Node.NumVertices+1 ); // Make sure this node doesn't already contain the vertex. for( int32 i=0; iVerts[iOldVert + i].pVertex != pVertex ); // Copy the old vertex pool to the new one. for( int32 i=0; iVerts[Node.iVertPool + i] = Model->Verts[iOldVert + i]; for( int32 i=VertexNumber; iVerts[Node.iVertPool + i + 1] = Model->Verts[iOldVert + i]; // Add the new point to the new vertex pool. Model->Verts[Node.iVertPool + VertexNumber].pVertex = pVertex; Model->Verts[Node.iVertPool + VertexNumber].iSide = INDEX_NONE; // Increment number of node vertices. Node.NumVertices++; // Update the point-vertex list. if( PointVerts ) PointVerts->AddNode( iNode ); } // // Add a point to all sides of polygons in which the side intersects with // this point but doesn't contain it, and has the correct (clockwise) orientation // as this side. pVertex is the index of the point to handle, and // ReferenceVertex defines the direction of this side. // int DistributePoint ( UModel* Model, FPointVertList* PointVerts, int32 iNode, int32 pVertex ) { int32 Count = 0; // Handle front, back, and plane. float Dist = Model->Nodes[iNode].Plane.PlaneDot(Model->Points[pVertex]); if( Dist < THRESH_OPTGEOM_COPLANAR ) { // Back. if( Model->Nodes[iNode].iBack != INDEX_NONE ) Count += DistributePoint( Model, PointVerts, Model->Nodes[iNode].iBack, pVertex ); } if( Dist > -THRESH_OPTGEOM_COPLANAR ) { // Front. if( Model->Nodes[iNode].iFront != INDEX_NONE ) Count += DistributePoint( Model, PointVerts, Model->Nodes[iNode].iFront, pVertex ); } if( Dist > -THRESH_OPTGEOM_COPLANAR && Dist < THRESH_OPTGEOM_COPLANAR ) { // This point is coplanar with this node, so check point for intersection with // this node's sides, then loop with its coplanars. for( ; iNode!=INDEX_NONE; iNode=Model->Nodes[iNode].iPlane ) { FVert* VertPool = &Model->Verts[Model->Nodes[iNode].iVertPool]; // Skip this node if it already contains the point in question. int32 i; for( i=0; iNodes[iNode].NumVertices; i++ ) if( VertPool[i].pVertex == pVertex ) break; if( i != Model->Nodes[iNode].NumVertices ) continue; // Loop through all sides and see if (A) side is colinear with point, and // (B) point falls within inside of this side. int32 FoundSide = -1; int32 SkippedColinear = 0; int32 SkippedInside = 0; for( i=0; iNodes[iNode].NumVertices; i++ ) { int32 j = (i>0) ? (i-1) : (Model->Nodes[iNode].NumVertices-1); // Create cutting plane perpendicular to both this side and the polygon's normal. FVector Side = FVector(Model->Points[VertPool[i].pVertex] - Model->Points[VertPool[j].pVertex]); FVector SidePlaneNormal = Side ^ (FVector)Model->Nodes[iNode].Plane; const float SizeSquared = static_cast(SidePlaneNormal.SizeSquared()); if( SizeSquared>FMath::Square(0.001) ) { // Points aren't coincedent. Dist = static_cast((FVector(Model->Points[pVertex] - Model->Points[VertPool[i].pVertex]) | SidePlaneNormal) / FMath::Sqrt(SizeSquared)); if( Dist >= THRESH_OPTGEOM_COSIDAL ) { // Point is outside polygon, can't possibly fall on a side. break; } else if( Dist > -THRESH_OPTGEOM_COSIDAL ) { // The point we're adding falls on this line. // // Verify that it falls within this side; though it's colinear // it may be out of the bounds of the line's endpoints if this side // is colinear with an adjacent side. // // Do this by checking distance from point to side's midpoint and // comparing with the side's half-length. FVector MidPoint = FVector(Model->Points[VertPool[i].pVertex] + Model->Points[VertPool[j].pVertex])*0.5; FVector MidDistVect = (FVector)Model->Points[pVertex] - MidPoint; if( MidDistVect.SizeSquared() <= FMath::Square(0.501)*Side.SizeSquared() ) { FoundSide = i; } else { SkippedColinear = 1; } } else { // Point is inside polygon, so continue checking. SkippedInside = 1; } } else { FBSPOps::GErrors++; //UE_LOG(LogEditorBsp, Warning, "Found tiny side" ); //Poly.PolyFlags |= PF_Selected; } } if( i==Model->Nodes[iNode].NumVertices && FoundSide>=0 ) { // AddPointToNode will reorder the vertices in this node. This is okay // because it's called outside of the vertex loop. AddPointToNode( Model, PointVerts, iNode, FoundSide, pVertex ); Count++; } else if( SkippedColinear ) { // This happens occasionally because of the fuzzy Dist comparison. It is // not a sign of a problem when the vertex being distributed is colinear // with one of this polygon's sides, but slightly outside of this polygon. FBSPOps::GErrors++; //UE_LOG(LogEditorBsp, Warning, "Skipped colinear" ); //Poly.PolyFlags |= PF_Selected; } else if( SkippedInside ) { // Point is on interior of polygon. FBSPOps::GErrors++; //UE_LOG(LogEditorBsp, Warning, "Skipped interior" ); //Poly.PolyFlags |= PF_Selected; } } } return Count; } // // Merge points that are near. // void MergeNearPoints( UModel *Model, float Dist ) { FMemMark Mark(FMemStack::Get()); int32* PointRemap = new(FMemStack::Get(),Model->Points.Num())int32; int32 Merged=0,Collapsed=0; // Find nearer point for all points. for( int32 i=0; iPoints.Num(); i++ ) { PointRemap[i] = i; FVector3f &Point = Model->Points[i]; for( int32 j=0; jPoints[j]; if( (TestPoint - Point).SizeSquared() < Dist*Dist ) { PointRemap[i] = j; Merged++; break; } } } // Remap VertPool. for( int32 i=0; iVerts.Num(); i++ ) { if( Model->Verts[i].pVertex>=0 && Model->Verts[i].pVertexPoints.Num() ) { CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*PointRemap'. Model->Verts[i].pVertex = PointRemap[Model->Verts[i].pVertex]; } } // Remap Surfs. for( int32 i=0; iSurfs.Num(); i++ ) { if( Model->Surfs[i].pBase>=0 && Model->Surfs[i].pBasePoints.Num() ) { CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*PointRemap'. Model->Surfs[i].pBase = PointRemap[Model->Surfs[i].pBase]; } } // Remove duplicate points from nodes. for( int32 i=0; iNodes.Num(); i++ ) { FBspNode &Node = Model->Nodes[i]; FVert *Pool = &Model->Verts[Node.iVertPool]; int k=0; for( int j=0; jpVertex != B->pVertex ) Pool[k++] = Pool[j]; } Node.NumVertices = static_cast(k>=3 ? k : 0); if( k < 3 ) Collapsed++; } // Success. // UE_LOG(LogEditorBsp, Log, TEXT("MergeNearPoints merged %i/%i, collapsed %i"), Merged, Model->Points.Num(), Collapsed ); Mark.Pop(); } void FBSPUtils::bspOptGeom( UModel *Model ) { FPointVertList PointVerts; //UE_LOG(LogEditorBsp, Log, TEXT("BspOptGeom begin") ); MergeNearPoints (Model,0.25); FBSPOps::bspRefresh (Model,0); PointVerts.Alloc (Model); PointVerts.AddAllNodes (); // First four entries are reserved for view-clipped sides. Model->NumSharedSides = 4; // Mark all sides as unlinked. int32 i; for( i=0; iVerts.Num(); i++ ) Model->Verts[i].iSide = INDEX_NONE; int32 TeesFound = 0, Distributed = 0; // Eliminate T-joints on each node by finding all vertices that aren't attached to // two shared sides, then filtering them down through the BSP and adding them to // the sides they belong on. for( int32 iNode=0; iNode < Model->Nodes.Num(); iNode++ ) { FBspNode &Node = Model->Nodes[iNode]; // Loop through all sides (side := line from PrevVert to ThisVert) for( uint8 ThisVert=0; ThisVert < Node.NumVertices; ThisVert++ ) { uint8 PrevVert = (ThisVert>0) ? (ThisVert - 1) : (Node.NumVertices-1); // Count number of nodes sharing this side, i.e. number of nodes for // which two adjacent vertices are identical to this side's two vertices. for( FPointVert* PV1 = PointVerts.Index[Model->Verts[Node.iVertPool+ThisVert].pVertex]; PV1; PV1=PV1->Next ) for( FPointVert* PV2 = PointVerts.Index[Model->Verts[Node.iVertPool+PrevVert].pVertex]; PV2; PV2=PV2->Next ) if( PV1->iNode==PV2->iNode && PV1->iNode!=iNode ) goto SkipIt; // Didn't find another node that shares our two vertices; must add each // vertex to all polygons where the vertex lies on the polygon's side. // DistributePoint will not affect the current node but may change others // and may increase the number of nodes in the Bsp. TeesFound++; Distributed = 0; Distributed += DistributePoint( Model, &PointVerts, 0, Model->Verts[Node.iVertPool+ThisVert].pVertex ); Distributed += DistributePoint( Model, &PointVerts, 0, Model->Verts[Node.iVertPool+PrevVert].pVertex ); SkipIt:; } } // Build side links // Definition of side: Side (i) links node vertex (i) to vertex ((i+1)%n) //UE_LOG(LogEditorBsp, Log, TEXT("BspOptGeom building sidelinks") ); PointVerts.Free (); PointVerts.Alloc (Model); PointVerts.AddAllNodes (); for( int32 iNode=0; iNode < Model->Nodes.Num(); iNode++ ) { FBspNode &Node = Model->Nodes[iNode]; for( uint8 ThisVert=0; ThisVert < Node.NumVertices; ThisVert++ ) { if( Model->Verts[Node.iVertPool+ThisVert].iSide == INDEX_NONE ) { // See if this node links to another one. uint8 PrevVert = (ThisVert>0) ? (ThisVert - 1) : (Node.NumVertices-1); for( FPointVert* PV1=PointVerts.Index[Model->Verts[Node.iVertPool+ThisVert].pVertex]; PV1; PV1=PV1->Next ) { for( FPointVert* PV2=PointVerts.Index[Model->Verts[Node.iVertPool+PrevVert].pVertex]; PV2; PV2=PV2->Next ) { if( PV1->iNode==PV2->iNode && PV1->iNode!=iNode ) { // Make sure that the other node's two vertices are adjacent and // ordered opposite this node's vertices. int32 iOtherNode = PV2->iNode; FBspNode &OtherNode = Model->Nodes[iOtherNode]; int32 Delta = (OtherNode.NumVertices + PV2->nVertex - PV1->nVertex) % OtherNode.NumVertices; if( Delta==1 ) { // Side is properly linked! int32 OtherVert = PV2->nVertex; int32 iSide; if( Model->Verts[OtherNode.iVertPool+OtherVert].iSide==INDEX_NONE ) iSide = Model->NumSharedSides++; else iSide = Model->Verts[OtherNode.iVertPool+OtherVert].iSide; // Link both sides to the shared side. Model->Verts[ Node.iVertPool + ThisVert ].iSide = Model->Verts[ OtherNode.iVertPool+OtherVert ].iSide = iSide; goto SkipSide; } } } } // This node doesn't have correct side linking //Model->Surfs(Node.iSurf).PolyFlags |= PF_Selected; FBSPOps::GErrors++; //UE_LOG(LogEditorBsp, Warning, "Failed to link side" ); } // Go to next side. SkipSide:; } } // Gather stats. i=0; int j=0; for( int32 iNode=0; iNode < Model->Nodes.Num(); iNode++ ) { FBspNode& Node = Model->Nodes[iNode]; FVert* VertPool = &Model->Verts[Node.iVertPool]; i += Node.NumVertices; for( uint8 ThisVert=0; ThisVert < Node.NumVertices; ThisVert++ ) if( VertPool[ThisVert].iSide!=INDEX_NONE ) j++; } // Done. //UE_LOG(LogEditorBsp, Log, TEXT("BspOptGeom end") ); // UE_LOG(LogEditorBsp, Log, TEXT("Processed %i T-points, linked: %i/%i sides"), TeesFound, j, i ); PointVerts.Free(); // Remove unused vertices from the vertex streams. // This is necessary to ensure the vertices added to eliminate T junctions // don't overflow the 65536 vertex/stream limit. FBSPOps::bspRefresh(Model,0); } void FBSPUtils::bspRepartition( UWorld* InWorld, int32 iNode ) { bspBuildFPolys( InWorld->GetModel(), 1, iNode ); bspMergeCoplanars( InWorld->GetModel(), 0, 0 ); FBSPOps::bspBuild( InWorld->GetModel(), FBSPOps::BSP_Good, 12, 70, 2, iNode ); FBSPOps::bspRefresh( InWorld->GetModel(), 1 ); } void FBSPUtils::polySetAndClearPolyFlags(UModel *Model, uint32 SetBits, uint32 ClearBits,bool SelectedOnly, bool UpdateBrush) { for( int32 i=0; iSurfs.Num(); i++ ) { FBspSurf& Poly = Model->Surfs[i]; if( !SelectedOnly || (Poly.PolyFlags & PF_Selected) ) { uint32 NewFlags = (Poly.PolyFlags & ~ClearBits) | SetBits; if( NewFlags != Poly.PolyFlags ) { Model->ModifySurf( i, UpdateBrush ); Poly.PolyFlags = NewFlags; if (UpdateBrush) { const bool bUpdateTexCoords = false; const bool bOnlyRefreshSurfaceMaterials = false; polyUpdateBrush(Model, i, bUpdateTexCoords, bOnlyRefreshSurfaceMaterials); } } } } } bool FBSPUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) { return polyFindBrush(InModel, iSurf, Poly); } bool FBSPUtils::polyFindBrush(UModel* InModel, int32 iSurf, FPoly &Poly) { FBspSurf &Surf = InModel->Surfs[iSurf]; if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) { return false; } else { Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; return true; } } void FBSPUtils::polyUpdateMaster(UModel* Model, int32 iSurf, bool bUpdateTexCoords, bool bOnlyRefreshSurfaceMaterials) { polyUpdateBrush(Model, iSurf, bUpdateTexCoords, bOnlyRefreshSurfaceMaterials); } void FBSPUtils::polyUpdateBrush ( UModel* Model, int32 iSurf, bool bUpdateTexCoords, bool bOnlyRefreshSurfaceMaterials ) { FBspSurf &Surf = Model->Surfs[iSurf]; ABrush* Actor = Surf.Actor; if( !Actor ) return; UModel* Brush = Actor->Brush; check(Brush); FVector ActorLocation; FVector ActorScale; FRotator ActorRotation; if (Brush->bCachedOwnerTransformValid) { // Use transform cached when the geometry was last built, in case the current Actor transform has changed since then // (e.g. because Auto Update BSP is disabled) ActorLocation = (FVector)Brush->OwnerLocationWhenLastBuilt; ActorScale = (FVector)Brush->OwnerScaleWhenLastBuilt; ActorRotation = Brush->OwnerRotationWhenLastBuilt; } else { // No cached owner transform, so use the current one ActorLocation = Actor->GetActorLocation(); ActorScale = Actor->GetActorScale(); ActorRotation = Actor->GetActorRotation(); } const FRotationMatrix RotationMatrix(ActorRotation); for (int32 iEdPoly = Surf.iBrushPoly; iEdPoly < Brush->Polys->Element.Num(); iEdPoly++) { FPoly& BrushEdPoly = Brush->Polys->Element[iEdPoly]; if (iEdPoly == Surf.iBrushPoly || BrushEdPoly.iLink == Surf.iBrushPoly) { BrushEdPoly.Material = Surf.Material; BrushEdPoly.PolyFlags = Surf.PolyFlags & ~(PF_NoEdit); if (bUpdateTexCoords) { BrushEdPoly.Base = FVector3f(RotationMatrix.InverseTransformVector((FVector)Model->Points[Surf.pBase] - ActorLocation) / ActorScale); BrushEdPoly.TextureU = FVector3f(RotationMatrix.InverseTransformVector((FVector)Model->Vectors[Surf.vTextureU]) * ActorScale); BrushEdPoly.TextureV = FVector3f(RotationMatrix.InverseTransformVector((FVector)Model->Vectors[Surf.vTextureV]) * ActorScale); } } } Model->InvalidSurfaces = true; if (bOnlyRefreshSurfaceMaterials) { Model->bOnlyRebuildMaterialIndexBuffers = true; } } void FBSPUtils::polyGetLinkedPolys ( ABrush* InBrush, FPoly* InPoly, TArray* InPolyList ) { InPolyList->Empty(); if( InPoly->iLink == INDEX_NONE ) { // If this poly has no links, just stick the one poly in the final list. InPolyList->Add( *InPoly ); } else { // Find all polys that match the source polys link value. for( int32 poly = 0 ; poly < InBrush->Brush->Polys->Element.Num() ; poly++ ) if( InBrush->Brush->Polys->Element[poly].iLink == InPoly->iLink ) InPolyList->Add( InBrush->Brush->Polys->Element[poly] ); } } void FBSPUtils::polySplitOverlappingEdges( TArray* InPolyList, TArray* InResult ) { InResult->Empty(); for( int32 poly = 0 ; poly < InPolyList->Num() ; poly++ ) { FPoly* SrcPoly = &(*InPolyList)[poly]; FPoly NewPoly = *SrcPoly; for( int32 edge = 0 ; edge < SrcPoly->Vertices.Num() ; edge++ ) { FEdge SrcEdge = FEdge( (FVector)SrcPoly->Vertices[edge], (FVector)SrcPoly->Vertices[ edge+1 < SrcPoly->Vertices.Num() ? edge+1 : 0 ] ); FPlane SrcEdgePlane( SrcEdge.Vertex[0], SrcEdge.Vertex[1], SrcEdge.Vertex[0] + (FVector)(SrcPoly->Normal * 16) ); for( int32 poly2 = 0 ; poly2 < InPolyList->Num() ; poly2++ ) { FPoly* CmpPoly = &(*InPolyList)[poly2]; // We can't compare to ourselves. if( CmpPoly == SrcPoly ) continue; for( int32 edge2 = 0 ; edge2 < CmpPoly->Vertices.Num() ; edge2++ ) { FEdge CmpEdge = FEdge( (FVector)CmpPoly->Vertices[edge2], (FVector)CmpPoly->Vertices[ edge2+1 < CmpPoly->Vertices.Num() ? edge2+1 : 0 ] ); // If both vertices on this edge lie on the same plane as the original edge, create // a sphere around the original 2 vertices. If either of this edges vertices are inside of // that sphere, we need to split the original edge by adding a vertex to it's poly. if( FMath::Abs( FVector::PointPlaneDist( CmpEdge.Vertex[0], SrcEdge.Vertex[0], SrcEdgePlane ) ) < THRESH_POINT_ON_PLANE && FMath::Abs( FVector::PointPlaneDist( CmpEdge.Vertex[1], SrcEdge.Vertex[0], SrcEdgePlane ) ) < THRESH_POINT_ON_PLANE ) { // // Check THIS edge against the SOURCE edge // FVector Dir = SrcEdge.Vertex[1] - SrcEdge.Vertex[0]; Dir.Normalize(); const float Dist = static_cast(FVector::Dist(SrcEdge.Vertex[1], SrcEdge.Vertex[0])); FVector Origin = SrcEdge.Vertex[0] + (Dir * (Dist / 2.0f)); float Radius = Dist / 2.0f; for( int32 vtx = 0 ; vtx < 2 ; vtx++ ) if( FVector::Dist( Origin, CmpEdge.Vertex[vtx] ) && FVector::Dist( Origin, CmpEdge.Vertex[vtx] ) < Radius ) NewPoly.InsertVertex( edge2+1, CmpEdge.Vertex[vtx] ); } } } } InResult->Add( NewPoly ); } } void FBSPUtils::polyGetOuterEdgeList ( TArray* InPolyList, TArray* InEdgeList ) { TArray NewPolyList; polySplitOverlappingEdges( InPolyList, &NewPolyList ); TArray TempEdges; // Create a list of edges. for( int32 poly = 0 ; poly < NewPolyList.Num() ; poly++ ) { FPoly* Poly = &NewPolyList[poly]; for( int32 vtx = 0 ; vtx < Poly->Vertices.Num() ; vtx++ ) TempEdges.Emplace( (FVector)Poly->Vertices[vtx], (FVector)Poly->Vertices[ vtx+1 < Poly->Vertices.Num() ? vtx+1 : 0] ); } // Add all the unique edges into the final edge list. TArray FinalEdges; for( int32 tedge = 0 ; tedge < TempEdges.Num() ; tedge++ ) { FEdge* TestEdge = &TempEdges[tedge]; int32 EdgeCount = 0; for( int32 edge = 0 ; edge < TempEdges.Num() ; edge++ ) { if( TempEdges[edge] == *TestEdge ) EdgeCount++; } if( EdgeCount == 1 ) FinalEdges.Add( *TestEdge ); } // Reorder all the edges so that they line up, end to end. InEdgeList->Empty(); if( !FinalEdges.Num() ) return; InEdgeList->Add( FinalEdges[0] ); FVector Comp = FinalEdges[0].Vertex[1]; FinalEdges.RemoveAt(0); FEdge DebuG; for( int32 x = 0 ; x < FinalEdges.Num() ; x++ ) { DebuG = FinalEdges[x]; // If the edge is backwards, flip it if( FinalEdges[x].Vertex[1] == Comp ) Exchange( FinalEdges[x].Vertex[0], FinalEdges[x].Vertex[1] ); if( FinalEdges[x].Vertex[0] == Comp ) { InEdgeList->Add( FinalEdges[x] ); Comp = FinalEdges[x].Vertex[1]; FinalEdges.RemoveAt(x); x = -1; } } }