Files
UnrealEngineUWP/Engine/Source/Editor/GeometryMode/Private/GeometryModifiers.cpp
Mikolaj Sieluzycki fa01cacb6c Trim down the amount of UObject constructors.
#codereview Robert.Manuszewski

[CL 2429641 by Mikolaj Sieluzycki in Main branch]
2015-02-03 05:40:57 -05:00

3307 lines
93 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "GeometryModePrivatePCH.h"
#include "Engine/BrushShape.h"
#include "EditorSupportDelegates.h"
#include "ScopedTransaction.h"
#include "BSPOps.h"
#include "GeomTools.h"
#include "Layers/ILayers.h"
#include "ActorEditorUtils.h"
#include "SNotificationList.h"
#include "NotificationManager.h"
#include "Engine/Polys.h"
#include "Engine/Selection.h"
#include "Components/BrushComponent.h"
DEFINE_LOG_CATEGORY_STATIC(LogGeomModifier, Log, All);
#define LOCTEXT_NAMESPACE "UnrealEd.GeomModifier"
static FVector ComputeWorldSpaceMousePos( FEditorViewportClient* ViewportClient )
{
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
ViewportClient->Viewport,
ViewportClient->GetScene(),
ViewportClient->EngineShowFlags)
.SetRealtimeUpdate( ViewportClient->IsRealtime() ));
FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily);
// Note only works for ortho viewports
return View->PixelToWorld(ViewportClient->Viewport->GetMouseX(),ViewportClient->Viewport->GetMouseY(),0.5f);
}
UGeomModifier::UGeomModifier(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bPushButton = false;
bInitialized = false;
CachedPolys = NULL;
}
const FText& UGeomModifier::GetModifierDescription() const
{
return Description;
}
void UGeomModifier::Initialize()
{
}
bool UGeomModifier::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
return false;
}
bool UGeomModifier::InputDelta(FEditorViewportClient* InViewportClient,FViewport* InViewport,FVector& InDrag,FRotator& InRot,FVector& InScale)
{
if( GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
if( !bInitialized )
{
Initialize();
bInitialized = true;
}
}
return false;
}
bool UGeomModifier::Apply()
{
bool bResult = false;
if( GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
StartTrans();
bResult = OnApply();
EndTrans();
EndModify();
}
return bResult;
}
bool UGeomModifier::OnApply()
{
return false;
}
bool UGeomModifier::Supports()
{
return true;
}
void UGeomModifier::GeomError(const FString& InErrorMsg)
{
FMessageDialog::Open( EAppMsgType::Ok, FText::Format( NSLOCTEXT("UnrealEd", "Error_Modifier", "Modifier ({0}) : {1}"), GetModifierDescription(), FText::FromString(InErrorMsg) ) );
}
bool UGeomModifier::StartModify()
{
bInitialized = false;
return false;
}
bool UGeomModifier::EndModify()
{
StoreAllCurrentGeomSelections();
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
return true;
}
void UGeomModifier::Render(const FSceneView* View,FViewport* Viewport,FPrimitiveDrawInterface* PDI)
{
}
void UGeomModifier::DrawHUD(FEditorViewportClient* ViewportClient,FViewport* Viewport,const FSceneView* View,FCanvas* Canvas)
{
}
void UGeomModifier::CacheBrushState()
{
FEdModeGeometry* GeomMode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
ABrush* BuilderBrush = GeomMode->GetWorld()->GetDefaultBrush();
if( !CachedPolys )
{
//Create the list of polys
CachedPolys = NewObject<UPolys>(this);
}
CachedPolys->Element.Empty();
//Create duplicates of all of the polys in the brush
for( int32 polyIndex = 0 ; polyIndex < BuilderBrush->Brush->Polys->Element.Num() ; ++polyIndex )
{
FPoly currentPoly = BuilderBrush->Brush->Polys->Element[polyIndex];
FPoly newPoly;
newPoly.Init();
newPoly.Base = currentPoly.Base;
//Add all of the verts to the new poly
for( int32 vertIndex = 0; vertIndex < currentPoly.Vertices.Num(); ++vertIndex )
{
FVector newVertex = currentPoly.Vertices[vertIndex];
newPoly.Vertices.Add( newVertex );
}
CachedPolys->Element.Add(newPoly);
}
}
void UGeomModifier::RestoreBrushState()
{
FEdModeGeometry* GeomMode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
ABrush* BuilderBrush = GeomMode->GetWorld()->GetDefaultBrush();
//Remove all of the current polys
BuilderBrush->Brush->Polys->Element.Empty();
//Add all of the cached polys
for( int32 polyIndex = 0 ; polyIndex < CachedPolys->Element.Num() ; polyIndex++ )
{
BuilderBrush->Brush->Polys->Element.Push(CachedPolys->Element[polyIndex]);
}
BuilderBrush->Brush->BuildBound();
BuilderBrush->ReregisterAllComponents();
GeomMode->FinalizeSourceData();
GeomMode->GetFromSource();
GEditor->SelectNone( true, true );
GEditor->RedrawLevelEditingViewports(true);
//Tell the user what just happened
FMessageDialog::Debugf(LOCTEXT("InvalidBrushState", "Invalid brush state could fail to triangulate. Reverting to previous state."));
}
bool UGeomModifier::DoEdgesOverlap()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
//Loop through all of the geometry objects
for( FEdModeGeometry::TGeomObjectIterator itor( mode->GeomObjectItor() ) ; itor ; ++itor )
{
FGeomObject* geomObject = *itor;
//Loop through all of the edges
for( int32 edgeIndex1 = 0 ; edgeIndex1 < geomObject->EdgePool.Num() ; ++edgeIndex1 )
{
FGeomEdge* edge1 = &geomObject->EdgePool[edgeIndex1];
for( int32 edgeIndex2 = 0 ; edgeIndex2 < geomObject->EdgePool.Num() ; ++edgeIndex2 )
{
FGeomEdge* edge2 = &geomObject->EdgePool[edgeIndex2];
//Don't compare an edge with itself
if( !(edge1->IsSameEdge(*edge2)) )
{
FVector closestPoint1, closestPoint2;
FVector edge1Vert1 = geomObject->VertexPool[ edge1->VertexIndices[0] ];
FVector edge2Vert1 = geomObject->VertexPool[ edge2->VertexIndices[0] ];
FVector edge1Vert2 = geomObject->VertexPool[ edge1->VertexIndices[1] ];
FVector edge2Vert2 = geomObject->VertexPool[ edge2->VertexIndices[1] ];
//Find the distance between the two segments
FMath::SegmentDistToSegment( edge1Vert1, edge1Vert2, edge2Vert1, edge2Vert2, closestPoint1, closestPoint2 );
if ( (closestPoint1.Equals(closestPoint2)) )
{
//Identical closest points indicates that lines cross
bool bSharedVertex = ((edge1Vert1.Equals(edge2Vert1)) || (edge1Vert1.Equals(edge2Vert2))
|| (edge1Vert2.Equals(edge2Vert1)) || (edge1Vert2.Equals(edge2Vert2)));
// Edges along the same line are exempt
if ( !bSharedVertex )
{
bool bIntersectionIsVert = ((edge1Vert1.Equals(closestPoint2, THRESH_POINTS_ARE_SAME)) || (edge1Vert2.Equals(closestPoint2, THRESH_POINTS_ARE_SAME))
|| (edge2Vert1.Equals(closestPoint2, THRESH_POINTS_ARE_SAME )) || (edge2Vert2.Equals(closestPoint2, THRESH_POINTS_ARE_SAME)) );
// Edges intersecting at a vertex are exempt
if ( !bIntersectionIsVert )
{
// Edges cross. The shape drawn with this brush will likely be undesireable
return true;
}
}
}
}
}
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Transaction tracking.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace {
/**
* @return The shared transaction object used by
*/
static FScopedTransaction*& StaticTransaction()
{
static FScopedTransaction* STransaction = NULL;
return STransaction;
}
/**
* Ends the outstanding transaction, if one exists.
*/
static void EndTransaction()
{
delete StaticTransaction();
StaticTransaction() = NULL;
}
/**
* Begins a new transaction, if no outstanding transaction exists.
*/
static void BeginTransaction(const FText& Description)
{
if ( !StaticTransaction() )
{
StaticTransaction() = new FScopedTransaction( Description );
}
}
} // namespace
void UGeomModifier::StartTrans()
{
if( !GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
return;
}
StoreAllCurrentGeomSelections();
// Start the transaction.
BeginTransaction( FText::Format( NSLOCTEXT("UnrealEd", "Modifier_F", "Modifier [{0}]"), GetModifierDescription() ) );
// Mark all selected brushes as modified.
FEdModeGeometry* CurMode = static_cast<FEdModeGeometry*>( GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry) );
for( FEdModeGeometry::TGeomObjectIterator Itor( CurMode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
ABrush* Actor = go->GetActualBrush();
Actor->Modify();
}
}
void UGeomModifier::EndTrans()
{
EndTransaction();
}
void UGeomModifier::StoreCurrentGeomSelections( TArray<struct FGeomSelection>& SelectionArray, FGeomObject* go )
{
SelectionArray.Empty();
FGeomSelection* gs = NULL;
for( int32 v = 0 ; v < go->VertexPool.Num() ; ++v )
{
FGeomVertex* gv = &go->VertexPool[v];
if( gv->IsSelected() )
{
gs = new( SelectionArray )FGeomSelection;
gs->Type = GS_Vertex;
gs->Index = v;
gs->SelectionIndex = gv->GetSelectionIndex();
}
}
for( int32 e = 0 ; e < go->EdgePool.Num() ; ++e )
{
FGeomEdge* ge = &go->EdgePool[e];
if( ge->IsSelected() )
{
gs = new( SelectionArray )FGeomSelection;
gs->Type = GS_Edge;
gs->Index = e;
gs->SelectionIndex = ge->GetSelectionIndex();
}
}
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
if( gp->IsSelected() )
{
gs = new( SelectionArray )FGeomSelection;
gs->Type = GS_Poly;
gs->Index = p;
gs->SelectionIndex = gp->GetSelectionIndex();
}
}
}
void UGeomModifier::StoreAllCurrentGeomSelections()
{
if( !GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
return;
}
FEdModeGeometry* CurMode = static_cast<FEdModeGeometry*>( GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry) );
// Record the current selection list into the selected brushes.
for( FEdModeGeometry::TGeomObjectIterator Itor( CurMode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
go->CompileSelectionOrder();
ABrush* Actor = go->GetActualBrush();
StoreCurrentGeomSelections( Actor->SavedSelections , go );
}
}
UGeomModifier_Edit::UGeomModifier_Edit(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Edit", "Edit");
}
bool UGeomModifier_Edit::InputDelta(FEditorViewportClient* InViewportClient,FViewport* InViewport,FVector& InDrag,FRotator& InRot,FVector& InScale)
{
if( UGeomModifier::InputDelta( InViewportClient, InViewport, InDrag, InRot, InScale ) )
{
return true;
}
if( !GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
return false;
}
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
FModeTool_GeometryModify* tool = (FModeTool_GeometryModify*)mode->GetCurrentTool();
TArray<FGeomVertex*> UniqueVertexList;
/**
* All geometry objects can be manipulated by transforming the vertices that make
* them up. So based on the type of thing we're editing, we need to dig for those
* vertices a little differently.
*/
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
if( gp->IsSelected() )
{
for( int32 e = 0 ; e < gp->EdgeIndices.Num() ; ++e )
{
FGeomEdge* ge = &go->EdgePool[ gp->EdgeIndices[e] ];
UniqueVertexList.AddUnique( &go->VertexPool[ ge->VertexIndices[0] ] );
UniqueVertexList.AddUnique( &go->VertexPool[ ge->VertexIndices[1] ] );
}
}
}
for( int32 e = 0 ; e < go->EdgePool.Num() ; ++e )
{
FGeomEdge* ge = &go->EdgePool[e];
if( ge->IsSelected() )
{
UniqueVertexList.AddUnique( &go->VertexPool[ ge->VertexIndices[0] ] );
UniqueVertexList.AddUnique( &go->VertexPool[ ge->VertexIndices[1] ] );
}
}
for( int32 v = 0 ; v < go->VertexPool.Num() ; ++v )
{
FGeomVertex* gv = &go->VertexPool[v];
if( gv->IsSelected() )
{
UniqueVertexList.AddUnique( gv );
}
}
}
// If we didn't move any vertices, then tell the caller that we didn't handle the input.
// This allows LDs to drag brushes around in geometry mode as long as no geometry
// objects are selected.
if( !UniqueVertexList.Num() )
{
return false;
}
const bool bShiftPressed = InViewportClient->IsShiftPressed();
// If we're trying to rotate vertices, only allow that if Shift is held down. This just makes it easier
// to rotate brushes around while working in geometry mode, since you rarely ever want to rotate vertices
FRotator FinalRot = InRot;
if( !bShiftPressed )
{
FinalRot = FRotator::ZeroRotator;
}
if( InDrag.IsZero() && FinalRot.IsZero() && InScale.IsZero() )
{
// No change, but handled
return true;
}
StartTrans();
// Let tool know that some modification has actually taken place
tool->bGeomModified = true;
/**
* Scaling needs to know the bounding box for the selected verts, so generate that before looping.
*/
FBox VertBBox(0);
for( int32 x = 0 ; x < UniqueVertexList.Num() ; ++x )
{
VertBBox += *UniqueVertexList[x];
}
FVector BBoxExtent = VertBBox.GetExtent();
FGeomVertex* vertex0 = UniqueVertexList[0];
const ABrush* Brush = vertex0->GetParentObject()->GetActualBrush();
FVector vertOffset = FVector(0, 0, 0);
//Calculate translation now so that it isn't done every iteration of the proceeding loop
if( !InDrag.IsZero() )
{
// Transform the drag vector into the brush's local space before applying it.
vertOffset = Brush->ActorToWorld().InverseTransformVector(InDrag);
}
/**
* We first generate a list of unique vertices and then transform that list
* in one shot. This prevents vertices from being touched more than once (which
* would result in them transforming x times as fast as others).
*/
for( int32 x = 0 ; x < UniqueVertexList.Num() ; ++x )
{
FGeomVertex* vtx = UniqueVertexList[x];
//Translate
*vtx += vertOffset;
// Rotate
if( !FinalRot.IsZero() )
{
FRotationMatrix matrix( FinalRot );
FVector Wk( vtx->X, vtx->Y, vtx->Z );
Wk = vtx->GetParentObject()->GetActualBrush()->ActorToWorld().TransformPosition( Wk );
Wk -= GLevelEditorModeTools().PivotLocation;
Wk = matrix.TransformPosition( Wk );
Wk += GLevelEditorModeTools().PivotLocation;
*vtx = vtx->GetParentObject()->GetActualBrush()->ActorToWorld().InverseTransformPosition( Wk );
}
// Scale
if( !InScale.IsZero() )
{
// TODO: sort this out properly, taking into account the 'fake' local transform according to the poly normal.
// Also use a scaling method which actually works properly.
float XFactor = FMath::Sign(InScale.X);
float YFactor = FMath::Sign(InScale.Y);
float ZFactor = FMath::Sign(InScale.Z);
float Strength;
FVector Wk( vtx->X, vtx->Y, vtx->Z );
// Move vert to the origin
Wk -= (GLevelEditorModeTools().PivotLocation - Brush->GetActorLocation());
// Move it along each axis based on it's distance from the origin
if( Wk.X != 0 )
{
Strength = (BBoxExtent.X / Wk.X) * XFactor;
Wk.X += GEditor->GetGridSize() * Strength;
}
if( Wk.Y != 0 )
{
Strength = (BBoxExtent.Y / Wk.Y) * YFactor;
Wk.Y += GEditor->GetGridSize() * Strength;
}
if( Wk.Z != 0 )
{
Strength = (BBoxExtent.Z / Wk.Z) * ZFactor;
Wk.Z += GEditor->GetGridSize() * Strength;
}
// Move it back into world space
Wk += (GLevelEditorModeTools().PivotLocation - Brush->GetActorLocation());
*vtx = Wk;
}
}
if( DoEdgesOverlap() )
{
//Two edges overlap, which causes triangulation problems, so move the vertices back to their previous location
for( int32 x = 0 ; x < UniqueVertexList.Num() ; ++x )
{
FGeomVertex* vtx = UniqueVertexList[x];
*vtx -= vertOffset;
}
GLevelEditorModeTools().PivotLocation -= vertOffset;
GLevelEditorModeTools().SnappedLocation -= vertOffset;
}
const bool bIsCtrlPressed = InViewportClient->IsCtrlPressed();
const bool bIsAltPressed = InViewportClient->IsAltPressed();
if(!InDrag.IsZero() && bShiftPressed && bIsCtrlPressed && !bIsAltPressed)
{
FVector CameraDelta(InDrag);
// Only apply camera speed modifiers to the drag if we aren't zooming in an ortho viewport.
if( !InViewportClient->IsOrtho() || !(InViewport->KeyState(EKeys::LeftMouseButton) && InViewport->KeyState(EKeys::RightMouseButton)) )
{
const float CameraSpeed = InViewportClient->GetCameraSpeed();
CameraDelta *= CameraSpeed;
}
InViewportClient->MoveViewportCamera( CameraDelta, InRot );
}
EndTrans();
GEditor->RedrawLevelEditingViewports(true);
return true;
}
/*------------------------------------------------------------------------------
UGeomModifier_Extrude
------------------------------------------------------------------------------*/
UGeomModifier_Extrude::UGeomModifier_Extrude(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Extrude", "Extrude");
Length = 16;
Segments = 1;
}
bool UGeomModifier_Extrude::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return mode->HavePolygonsSelected();
}
void UGeomModifier_Extrude::WasActivated()
{
// Extrude requires a local coordinate system to work properly so automatically enable
// that here while saving the current coordinate system for restoration later.
const bool bGetRawValue = true;
SaveCoordSystem = GLevelEditorModeTools().GetCoordSystem(bGetRawValue);
CheckCoordinatesMode();
GEditor->RedrawLevelEditingViewports(true);
}
void UGeomModifier_Extrude::WasDeactivated()
{
// When the user leaves this modifier, restore their old coordinate system.
GLevelEditorModeTools().SetCoordSystem((ECoordSystem)SaveCoordSystem);
GEditor->RedrawLevelEditingViewports(true);
}
void UGeomModifier_Extrude::CheckCoordinatesMode()
{
const bool bGetRawValue = true;
if( GLevelEditorModeTools().GetCoordSystem(bGetRawValue) != COORD_Local )
{
FSuppressableWarningDialog::FSetupInfo Info( LOCTEXT("ExtrudeCoordinateWarningBody","Extrude only works with Local Coordinates System"), LOCTEXT("ExtrudeCoordinateWarningTitle","Extrude Coordinates Mode Warning"), "ExtrudeCoordsWarning" );
Info.ConfirmText = LOCTEXT( "Close", "Close");
FSuppressableWarningDialog WarnAboutCoordinatesSystem( Info );
WarnAboutCoordinatesSystem.ShowModal();
GLevelEditorModeTools().SetCoordSystem(COORD_Local);
}
}
void UGeomModifier_Extrude::Initialize()
{
//the user may have changed the mode AFTER going into extrude - double check its LOCAL not WORLD
CheckCoordinatesMode();
Apply( GEditor->GetGridSize(), 1 );
}
bool UGeomModifier_Extrude::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
// When applying via the keyboard, we force the local coordinate system.
const bool bGetRawValue = true;
const ECoordSystem SaveCS = GLevelEditorModeTools().GetCoordSystem(bGetRawValue);
GLevelEditorModeTools().SetCoordSystem(COORD_Local);
//GApp->DlgGeometryTools->PropertyWindow->FinalizeValues();
Apply( Length, Segments );
// Restore the coordinate system.
GLevelEditorModeTools().SetCoordSystem(SaveCS);
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
void ExtrudePolygonGroup( ABrush* InBrush, FVector InGroupNormal, int32 InStartOffset, int32 InLength, TArray<FPoly>& InPolygonGroup )
{
TArray< TArray<FVector> > Windings;
FPoly::GetOutsideWindings( InBrush, InPolygonGroup, Windings );
for( int32 w = 0 ; w < Windings.Num() ; ++w )
{
TArray<FVector>* WindingVerts = &Windings[w];
FVector Offset = InGroupNormal * InLength;
FVector StartOffset = InGroupNormal * InStartOffset;
for( int32 v = 0 ; v < WindingVerts->Num() ; ++v )
{
FVector vtx0 = StartOffset + (*WindingVerts)[ v ];
FVector vtx1 = StartOffset + (*WindingVerts)[ v ] + Offset;
FVector vtx2 = StartOffset + (*WindingVerts)[ (v + 1) % WindingVerts->Num() ] + Offset;
FVector vtx3 = StartOffset + (*WindingVerts)[ (v + 1) % WindingVerts->Num() ];
FPoly NewPoly;
NewPoly.Init();
NewPoly.Base = InBrush->GetActorLocation();
NewPoly.Vertices.Add( vtx1 );
NewPoly.Vertices.Add( vtx0 );
NewPoly.Vertices.Add( vtx3 );
NewPoly.Vertices.Add( vtx2 );
if( NewPoly.Finalize( InBrush, 1 ) == 0 )
{
InBrush->Brush->Polys->Element.Add( NewPoly );
}
}
}
}
void UGeomModifier_Extrude::Apply(int32 InLength, int32 InSegments)
{
if( !GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
return;
}
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
// Force user input to be valid
InLength = FMath::Max( 1, InLength );
InSegments = FMath::Max( 1, InSegments );
//
TArray<int32> SavedSelectionIndices;
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
ABrush* Brush = go->GetActualBrush();
go->SendToSource();
TArray<FPoly> Polygons;
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
FVector Normal = mode->GetWidgetNormalFromCurrentAxis( gp );
if( gp->IsSelected() )
{
SavedSelectionIndices.Add( p );
FPoly* Poly = gp->GetActualPoly();
Polygons.Add( *Poly );
// Move the existing poly along the normal by InLength units.
for( int32 v = 0 ; v < Poly->Vertices.Num() ; ++v )
{
FVector* vtx = &Poly->Vertices[v];
*vtx += Normal * (InLength * InSegments);
}
Poly->Base += Normal * (InLength * InSegments);
}
}
if( Polygons.Num() )
{
struct FCompareFPolyNormal
{
FORCEINLINE bool operator()( const FPoly& A, const FPoly& B ) const
{
return (B.Normal - A.Normal).Size() < 0;
}
};
Polygons.Sort( FCompareFPolyNormal() );
FVector NormalCompare;
TArray<FPoly> PolygonGroup;
for( int32 p = 0 ; p < Polygons.Num() ; ++p )
{
FPoly* Poly = &Polygons[p];
if( p == 0 )
{
NormalCompare = Poly->Normal;
}
if( NormalCompare.Equals( Poly->Normal ) )
{
PolygonGroup.Add( *Poly );
}
else
{
if( PolygonGroup.Num() )
{
for( int32 s = 0 ; s < InSegments ; ++s )
{
ExtrudePolygonGroup( Brush, NormalCompare, InLength * s, InLength, PolygonGroup );
}
}
NormalCompare = Poly->Normal;
PolygonGroup.Empty();
PolygonGroup.Add( *Poly );
}
}
if( PolygonGroup.Num() )
{
for( int32 s = 0 ; s < InSegments ; ++s )
{
ExtrudePolygonGroup( Brush, NormalCompare, InLength * s, InLength, PolygonGroup );
}
}
}
go->FinalizeSourceData();
go->GetFromSource();
for( int32 x = 0 ; x < SavedSelectionIndices.Num() ; ++x )
{
FGeomPoly* Poly = &go->PolyPool[ SavedSelectionIndices[x] ];
Poly->Select(1);
}
}
}
UGeomModifier_Lathe::UGeomModifier_Lathe(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Lathe", "Lathe");
Axis = EAxis::Y;
TotalSegments = 16;
Segments = 4;
AlignToSide = false;
}
bool UGeomModifier_Lathe::Supports()
{
// Lathe mode requires ABrushShape actors to be selected.
for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It )
{
ABrush* Brush = Cast<ABrush>( *It );
if( Brush && Brush->IsBrushShape() )
{
return true;
}
}
return false;
}
void UGeomModifier_Lathe::Initialize()
{
}
bool UGeomModifier_Lathe::OnApply()
{
//FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetCurrentMode();
//GApp->DlgGeometryTools->PropertyWindow->FinalizeValues();
Apply( TotalSegments, Segments, Axis );
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
void UGeomModifier_Lathe::Apply( int32 InTotalSegments, int32 InSegments, EAxis::Type InAxis )
{
if( !GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Geometry) )
{
return;
}
// Determine the axis from the active ortho viewport
if ( !GLastKeyLevelEditingViewportClient || !GLastKeyLevelEditingViewportClient->IsOrtho() )
{
return;
}
//Save the brush state in case a bogus shape is generated
CacheBrushState();
switch( GLastKeyLevelEditingViewportClient->ViewportType )
{
case LVT_OrthoXZ:
Axis = EAxis::X;
break;
case LVT_OrthoXY:
Axis = EAxis::Y;
break;
case LVT_OrthoYZ:
Axis = EAxis::Z;
break;
}
FEdModeGeometry* GeomMode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
InTotalSegments = FMath::Max( 3, InTotalSegments );
InSegments = FMath::Max( 1, InSegments );
if( InSegments > InTotalSegments )
{
InTotalSegments = InSegments;
}
// We will be replacing the builder brush, so get it prepped.
ABrush* BuilderBrush = GeomMode->GetWorld()->GetDefaultBrush();
BuilderBrush->SetActorLocation(GeomMode->GetWidgetLocation(), false);
BuilderBrush->SetPrePivot(FVector::ZeroVector);
BuilderBrush->SetFlags( RF_Transactional );
BuilderBrush->Brush->Polys->Element.Empty();
// Ensure the builder brush is unhidden.
BuilderBrush->bHidden = false;
BuilderBrush->bHiddenEdLayer = false;
BuilderBrush->SetIsTemporarilyHiddenInEditor( false );
// Some convenience flags
bool bNeedCaps = (InSegments < InTotalSegments);
// Lathe every selected ABrushShape actor into the builder brush
for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It )
{
TArray<FEdge> EdgePool;
ABrushShape* BrushShape = Cast<ABrushShape>( *It );
if( BrushShape )
{
if( BrushShape->Brush->Polys->Element.Num() < 1 )
{
continue;
}
TArray< TArray<FVector> > Windings;
FPoly::GetOutsideWindings( BrushShape, BrushShape->Brush->Polys->Element, Windings );
FVector delta = GeomMode->GetWidgetLocation() - BrushShape->GetActorLocation();
//
// Let's lathe...
//
// Build up an array of vertices that represents the entire lathe.
float AngleStep = 360.f / (float)InTotalSegments;
float Angle = 0;
for( int32 w = 0 ; w < Windings.Num() ; ++w )
{
TArray<FVector>* WindingVerts = &Windings[w];
TArray<FVector> ShapeVertices;
for( int32 s = 0 ; s < (InSegments + 1 + (AlignToSide?1:0)) ; ++s )
{
FRotator rot = FRotator( 0, Angle, 0 );
if( Axis == EAxis::X )
{
rot = FRotator( Angle, 0, 0 );
}
else if( Axis == EAxis::Z )
{
rot = FRotator( 0, 0, Angle );
}
FRotationMatrix RotationMatrix( rot );
for( int32 e = 0 ; e < WindingVerts->Num() ; ++e )
{
FVector vtx = (*WindingVerts)[e] - delta - BrushShape->GetPrePivot();
vtx = RotationMatrix.TransformPosition( vtx );
ShapeVertices.Add( vtx );
}
if( AlignToSide && (s == 0 || s == InSegments) )
{
Angle += AngleStep / 2.0f;
}
else
{
Angle += AngleStep;
}
}
int32 NumVertsInShape = WindingVerts->Num();
for( int32 s = 0 ; s < InSegments + (AlignToSide?1:0) ; ++s )
{
int32 BaseIdx = s * WindingVerts->Num();
for( int32 v = 0 ; v < NumVertsInShape ; ++v )
{
FVector vtx0 = ShapeVertices[ BaseIdx + v ];
FVector vtx1 = ShapeVertices[ BaseIdx + NumVertsInShape + v ];
FVector vtx2 = ShapeVertices[ BaseIdx + NumVertsInShape + ((v + 1) % NumVertsInShape) ];
FVector vtx3 = ShapeVertices[ BaseIdx + ((v + 1) % NumVertsInShape) ];
FPoly NewPoly;
NewPoly.Init();
NewPoly.Base = BuilderBrush->GetActorLocation();
NewPoly.Vertices.Add( vtx0 );
NewPoly.Vertices.Add( vtx1 );
NewPoly.Vertices.Add( vtx2 );
NewPoly.Vertices.Add( vtx3 );
if( NewPoly.Finalize( BuilderBrush, 1 ) == 0 )
{
BuilderBrush->Brush->Polys->Element.Add( NewPoly );
}
}
}
}
// Create start/end capping polygons if they are necessary
if( bNeedCaps )
{
for( int32 w = 0 ; w < Windings.Num() ; ++w )
{
TArray<FVector>* WindingVerts = &Windings[w];
//
// Create the start cap
//
FPoly Poly;
Poly.Init();
Poly.Base = BrushShape->GetActorLocation();
// Add the verts from the shape
for( int32 v = 0 ; v < WindingVerts->Num() ; ++v )
{
Poly.Vertices.Add( (*WindingVerts)[v] - delta - BrushShape->GetPrePivot() );
}
Poly.Finalize( BuilderBrush, 1 );
// Break the shape down into convex shapes.
TArray<FPoly> Polygons;
Poly.Triangulate( BuilderBrush, Polygons );
FPoly::OptimizeIntoConvexPolys( BuilderBrush, Polygons );
// Add the resulting convex polygons into the brush
for( int32 p = 0 ; p < Polygons.Num() ; ++p )
{
FPoly Polygon = Polygons[p];
if (Polygon.Finalize(BuilderBrush, 1) == 0)
{
BuilderBrush->Brush->Polys->Element.Add(Polygon);
}
}
//
// Create the end cap
//
Poly.Init();
Poly.Base = BrushShape->GetActorLocation();
// Add the verts from the shape
FRotator rot = FRotator( 0, AngleStep * InSegments, 0 );
if( Axis == EAxis::X )
{
rot = FRotator( AngleStep * InSegments, 0, 0 );
}
else if( Axis == EAxis::Z )
{
rot = FRotator( 0, 0, AngleStep * InSegments );
}
FRotationMatrix RotationMatrix( rot );
for( int32 v = 0 ; v < WindingVerts->Num() ; ++v )
{
Poly.Vertices.Add( RotationMatrix.TransformPosition( (*WindingVerts)[v] - delta - BrushShape->GetPrePivot() ) );
}
Poly.Finalize( BuilderBrush, 1 );
// Break the shape down into convex shapes.
Polygons.Empty();
Poly.Triangulate( BuilderBrush, Polygons );
FPoly::OptimizeIntoConvexPolys( BuilderBrush, Polygons );
// Add the resulting convex polygons into the brush
for( int32 p = 0 ; p < Polygons.Num() ; ++p )
{
FPoly Polygon = Polygons[p];
Polygon.Reverse();
if (Polygon.Finalize(BuilderBrush, 1) == 0)
{
BuilderBrush->Brush->Polys->Element.Add(Polygon);
}
}
}
}
}
}
// Finalize the builder brush
BuilderBrush->Brush->BuildBound();
BuilderBrush->ReregisterAllComponents();
GeomMode->FinalizeSourceData();
GeomMode->GetFromSource();
GEditor->SelectNone( true, true );
GEditor->SelectActor( BuilderBrush, true, true );
if( DoEdgesOverlap() )
{//Overlapping edges yielded an invalid brush state
RestoreBrushState();
}
else
{
GEditor->RedrawLevelEditingViewports(true);
}
// Create additive brush from builder brush
GEditor->Exec(GeomMode->GetWorld(), TEXT("BRUSH ADD SELECTNEWBRUSH"));
// Deselect & hide builder brush
BuilderBrush->SetIsTemporarilyHiddenInEditor(true);
GEditor->SelectActor(BuilderBrush, false, false);
}
/*------------------------------------------------------------------------------
UGeomModifier_Pen
------------------------------------------------------------------------------*/
UGeomModifier_Pen::UGeomModifier_Pen(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Pen", "Pen");
bCreateBrushShape = false;
bAutoExtrude = true;
ExtrudeDepth = 256;
bCreateConvexPolygons = true;
}
/**
* Gives the modifier a chance to initialize it's internal state when activated.
*/
void UGeomModifier_Pen::WasActivated()
{
ShapeVertices.Empty();
}
/**
* Implements the modifier application.
*/
bool UGeomModifier_Pen::OnApply()
{
Apply();
return true;
}
void UGeomModifier_Pen::Apply()
{
if( ShapeVertices.Num() > 2 )
{
FEdModeGeometry* GeomMode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
ABrush* ResultingBrush = GeomMode->GetWorld()->GetDefaultBrush();
ABrush* BuilderBrush = GeomMode->GetWorld()->GetDefaultBrush();
// Move all the vertices that the user placed to the same "height" as the builder brush, based on
// viewport orientation. This is preferable to always creating the new builder brush at height zero.
for( int32 v = 0 ; v < ShapeVertices.Num() ; ++v )
{
FVector* vtx = &ShapeVertices[v];
switch( GLastKeyLevelEditingViewportClient->ViewportType )
{
case LVT_OrthoXY:
vtx->Z = BuilderBrush->GetActorLocation().Z;
break;
case LVT_OrthoXZ:
vtx->Y = BuilderBrush->GetActorLocation().Y;
break;
case LVT_OrthoYZ:
vtx->X = BuilderBrush->GetActorLocation().X;
break;
}
}
// Generate center location from the shape's center
FBox WorldBounds(ShapeVertices.GetData(), ShapeVertices.Num());
FVector BaseLocation = WorldBounds.GetCenter();
//create a scoped transaction so that we can undo the creation/modification
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "BrushSet", "Brush Set") );
//if we are creating a brush we need to first create an actor for it
if( bCreateBrushShape )
{
// Create a shape brush instead of modifying the builder brush
ResultingBrush = BuilderBrush->GetWorld()->SpawnActor<ABrushShape>(BaseLocation, FRotator::ZeroRotator);
ResultingBrush->PreEditChange(NULL);
// It's OK to create an empty brush here as we are going to re-create the polys anyway.
FBSPOps::csgCopyBrush( ResultingBrush, BuilderBrush, PF_DefaultFlags, BuilderBrush->GetFlags(), true, true, true );
ResultingBrush->PostEditChange();
}
else
{
ResultingBrush = FBSPOps::csgAddOperation( BuilderBrush, PF_DefaultFlags, Brush_Add );
}
// Make sure the graphics engine isn't busy rendering this geometry before we go and modify it
FlushRenderingCommands();
ResultingBrush->SetActorLocation(BaseLocation, false);
ResultingBrush->SetPrePivot(FVector::ZeroVector);
ResultingBrush->SetFlags( RF_Transactional );
ResultingBrush->Brush->Polys->Element.Empty();
// Ensure the brush is unhidden.
ResultingBrush->bHidden = false;
ResultingBrush->bHiddenEdLayer = false;
ResultingBrush->SetIsTemporarilyHiddenInEditor( false );
FPoly Poly;
Poly.Init();
Poly.Base = BaseLocation;
for( int32 v = 0 ; v < ShapeVertices.Num() ; ++v )
{
new(Poly.Vertices) FVector( ShapeVertices[v] - BaseLocation );
}
if( Poly.Finalize( ResultingBrush, 1 ) == 0 )
{
// Break the shape down into triangles.
TArray<FPoly> Triangles;
Poly.Triangulate( ResultingBrush, Triangles );
TArray<FPoly> Polygons = Triangles;
// Optionally, optimize the resulting triangles into convex polygons.
if( bCreateConvexPolygons )
{
FPoly::OptimizeIntoConvexPolys( ResultingBrush, Polygons );
}
// If the user isn't creating an ABrushShape, then carry on adding the sides and bottom face
// If the user wants a full brush created, add the rest of the polys
if( !bCreateBrushShape && bAutoExtrude && ExtrudeDepth > 0 )
{
// Extruding along delta
FVector HalfDelta;
// Create another set of polygons that will represent the bottom face
for( int32 p = 0 ; p < Polygons.Num() ; ++p )
{
FPoly poly = Polygons[p];
if (p == 0)
{
HalfDelta = 0.5f * poly.Normal * ExtrudeDepth;
}
if( poly.Finalize( ResultingBrush, 0 ) == 0 )
{
for( int32 v = 0 ; v < poly.Vertices.Num() ; ++v )
{
FVector* vtx = &poly.Vertices[v];
*vtx += HalfDelta;
}
new(ResultingBrush->Brush->Polys->Element)FPoly( poly );
}
poly.Reverse();
if( poly.Finalize( ResultingBrush, 0 ) == 0 )
{
for( int32 v = 0 ; v < poly.Vertices.Num() ; ++v )
{
FVector* vtx = &poly.Vertices[v];
*vtx -= 2.0f * HalfDelta;
}
new(ResultingBrush->Brush->Polys->Element)FPoly( poly );
}
}
// Create the polygons that make up the sides of the brush
if( Polygons.Num() > 0 )
{
for( int32 v = 0 ; v < ShapeVertices.Num() ; ++v )
{
FVector vtx0 = ShapeVertices[v] + HalfDelta;
FVector vtx1 = ShapeVertices[(v+1)%ShapeVertices.Num()] + HalfDelta;
FVector vtx2 = vtx1 - 2.0f * HalfDelta;
FVector vtx3 = vtx0 - 2.0f * HalfDelta;
FPoly SidePoly;
SidePoly.Init();
SidePoly.Vertices.Add( vtx1 - BaseLocation );
SidePoly.Vertices.Add( vtx0 - BaseLocation );
SidePoly.Vertices.Add( vtx3 - BaseLocation );
SidePoly.Vertices.Add( vtx2 - BaseLocation );
if( SidePoly.Finalize( ResultingBrush, 1 ) == 0 )
{
new(ResultingBrush->Brush->Polys->Element)FPoly( SidePoly );
}
}
}
}
else // not extruding a solid brush
{
// Now that we have a set of convex polygons, add them all to the brush. These will form the top face.
for( int32 p = 0 ; p < Polygons.Num() ; ++p )
{
if( Polygons[p].Finalize( ResultingBrush, 0 ) == 0 )
{
new(ResultingBrush->Brush->Polys->Element)FPoly(Polygons[p]);
}
}
}
}
// Finish up
ResultingBrush->Brush->BuildBound();
ResultingBrush->ReregisterAllComponents();
ShapeVertices.Empty();
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->SelectNone( true, true );
GEditor->SelectActor( ResultingBrush, true, true );
// Switch back to edit mode
//FModeTool_GeometryModify* tool = (FModeTool_GeometryModify*)mode->GetCurrentTool();
//tool->SetCurrentModifier( tool->GetModifier(0) );
//force a rebuild of the brush (otherwise the auto-update will do it and this will result in the undo buffer being incorrect)
FBSPOps::RebuildBrush( ResultingBrush->Brush );
GEditor->RedrawLevelEditingViewports(true);
}
}
#if 0
static bool DoesFinalLineIntersectWithShape(const TArray<FVector>& Vertices, const FVector& FinalVertex)
{
// TODO: improve this, it often fails.
// Determine if the next line segment would intersect with any of the previous ones in the shape
for (int32 Index = 0; Index < Vertices.Num() - 1; Index++)
{
const FVector& Segment1Start = Vertices[Index];
const FVector& Segment1End = Vertices[Index + 1];
const FVector& Segment2Start = Vertices[Vertices.Num() - 1];
const FVector& Segment2End = FinalVertex;
// Check that the two line segments are coplanar
check(FMath::IsNearlyZero(FVector::DotProduct(Segment2Start - Segment1Start, FVector::CrossProduct(Segment1End - Segment1Start, Segment2End - Segment2Start))));
FVector Segment1Result;
FVector Segment2Result;
FMath::SegmentDistToSegmentSafe(Segment1Start, Segment1End, Segment2Start, Segment2End, Segment1Result, Segment2Result);
if (Segment1Result.Equals(Segment2Result) && !Segment1Result.Equals(Segment1Start) && !Segment1Result.Equals(Segment1End))
{
return true;
}
}
return false;
}
#endif
static bool DoLineSegmentsIntersect(const FVector2D& Segment1Start, const FVector2D& Segment1End, const FVector2D& Segment2Start, const FVector2D& Segment2End)
{
const FVector2D Segment1Dir = Segment1End - Segment1Start;
const FVector2D Segment2Dir = Segment2End - Segment2Start;
const float Determinant = FVector2D::CrossProduct(Segment1Dir, Segment2Dir);
if (!FMath::IsNearlyZero(Determinant))
{
const FVector2D SegmentStartDelta = Segment2Start - Segment1Start;
const float OneOverDet = 1.0f / Determinant;
const float Seg1Intersection = FVector2D::CrossProduct(SegmentStartDelta, Segment2Dir) * OneOverDet;
const float Seg2Intersection = FVector2D::CrossProduct(SegmentStartDelta, Segment1Dir) * OneOverDet;
const float Epsilon = 1/128.0f;
return (Seg1Intersection > Epsilon && Seg1Intersection < 1.0f - Epsilon && Seg2Intersection > Epsilon && Seg2Intersection < 1.0f - Epsilon);
}
return false;
}
/**
* Given an array of points forming an unclosed polygon, determines whether a line segment formed from the final polygon vertex and a given endpoint
* intersects with any edge of the polygon in the 2D plane in which both segments lie.
*
* @param Vertices Array of vertices forming unclosed polygon
* @param EndVertex Endpoint of line segment starting from final point of polygon
*/
static bool DoesFinalLineIntersectWithShape(const TArray<FVector>& Vertices, const FVector& EndVertex)
{
// Can't intersect if there are fewer than 2 vertices
if (Vertices.Num() < 2)
{
return false;
}
// All line segments in the polygon ought to be coplanar. Hence the problem can be reduced to detecting intersections of line segments
// projected onto their common plane, using 2D coordinates.
// Line segment 1 is the line to test against the rest of the shape
const FVector& Segment1Start = Vertices[Vertices.Num() - 1];
const FVector& Segment1End = EndVertex;
const FVector Segment1Dir = Segment1End - Segment1Start;
const float Segment1Len = Segment1Dir.Size();
if (FMath::IsNearlyZero(Segment1Len))
{
// Treat zero length line segments as non-intersecting
return false;
}
// The direction of segment 1 on the plane will provide the X axis of the 2D basis on the plane
const FVector ProjectedXAxis = Segment1Dir / Segment1Len;
for (int32 Index = 0; Index < Vertices.Num() - 1; Index++)
{
// Line segment 2 is each edge of the shape
const FVector& Segment2Start = Vertices[Index];
const FVector& Segment2End = Vertices[Index + 1];
const FVector Segment2Dir = Segment2End - Segment2Start;
const FVector SegmentStartDelta = Segment2Start - Segment1Start;
// If line segments 1 and 2 are coplanar, the plane normal will be shared, and be calculated from a cross product of their two directions
FVector PlaneNormal = FVector::CrossProduct(Segment1Dir, Segment2Dir);
// Check that they are indeed coplanar
const bool bIsCoplanar = FMath::IsNearlyZero(FVector::DotProduct(SegmentStartDelta, PlaneNormal));
if (!bIsCoplanar)
{
// Non-coplanar line segments can't possibly intersect (ignoring the case of coincident start/end points)
return false;
}
// Parallel line segments will have yielded a zero normal. Attempt to calculate it from the segment start delta.
// If the lines are coincident, this will also yield a zero normal, resulting in a 1D basis (which is still sufficient to detect segment overlaps in projection space).
const bool bParallel = (FMath::IsNearlyZero(PlaneNormal.SizeSquared()));
if (bParallel)
{
PlaneNormal = FVector::CrossProduct(Segment1Dir, SegmentStartDelta);
}
// Get the Y axis of the 2D basis from the X axis and the plane normal
const FVector ProjectedYAxis = FVector::CrossProduct(PlaneNormal.GetSafeNormal(), ProjectedXAxis);
// Project 3d points onto plane
const FVector2D ProjectedSegment1Start(0.0f, 0.0f);
const FVector2D ProjectedSegment1End(Segment1Len, 0.0f);
const FVector2D ProjectedSegment2Start(FVector::DotProduct(ProjectedXAxis, SegmentStartDelta), FVector::DotProduct(ProjectedYAxis, SegmentStartDelta));
const FVector2D ProjectedSegment2End(FVector::DotProduct(ProjectedXAxis, Segment2End - Segment1Start), FVector::DotProduct(ProjectedYAxis, Segment2End - Segment1Start));
// Now check intersection of 2d segments.
if (DoLineSegmentsIntersect(ProjectedSegment1Start, ProjectedSegment1End, ProjectedSegment2Start, ProjectedSegment2End))
{
return true;
}
}
return false;
}
/**
* @return true if the key was handled by this editor mode tool.
*/
bool UGeomModifier_Pen::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
bool bResult = false;
#if WITH_EDITORONLY_DATA
if( ViewportClient->IsOrtho() && Event == IE_Pressed )
{
const bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl);
const bool bShiftDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift);
const bool bAltDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt);
// CTRL+RightClick (or SPACE bar) adds a vertex to the world
if( (bCtrlDown && !bShiftDown && !bAltDown && Key == EKeys::RightMouseButton) || Key == EKeys::SpaceBar )
{
// if we're trying to edit vertices in a different viewport to the one we started in then popup a warning
if( ShapeVertices.Num() && ViewportClient != UsingViewportClient )
{
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "GeomModifierPen_Warning_AddingVertexInWrongViewport", "Vertices can only be added to one viewport at a time." ) );
return true;
}
if( ShapeVertices.Num() && MouseWorldSpacePos.Equals( ShapeVertices[0] ) )
{
if (!DoesFinalLineIntersectWithShape(ShapeVertices, ShapeVertices[0]))
{
Apply();
bResult = true;
}
}
else
{
if (!DoesFinalLineIntersectWithShape(ShapeVertices, MouseWorldSpacePos))
{
UsingViewportClient = ViewportClient;
ShapeVertices.Add( MouseWorldSpacePos );
bResult = true;
}
}
}
else if( Key == EKeys::Escape || Key == EKeys::BackSpace )
{
if( ShapeVertices.Num() )
{
ShapeVertices.RemoveAt( ShapeVertices.Num() - 1 );
}
bResult = true;
}
else if( Key == EKeys::Enter )
{
if (!DoesFinalLineIntersectWithShape(ShapeVertices, ShapeVertices[0]))
{
Apply();
bResult = true;
}
}
}
if( bResult )
{
GEditor->RedrawLevelEditingViewports( true );
}
#endif // WITH_EDITORONLY_DATA
return bResult;
}
void UGeomModifier_Pen::Render(const FSceneView* View,FViewport* Viewport,FPrimitiveDrawInterface* PDI)
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
FModeTool_GeometryModify* tool = (FModeTool_GeometryModify*)mode->GetCurrentTool();
if( tool->GetCurrentModifier() != this )
{
return;
}
// Only draw in ortho viewports
if( !((FEditorViewportClient*)(Viewport->GetClient()))->IsOrtho() )
{
return;
}
FLinearColor Color = bCreateBrushShape ? GEngine->C_BrushShape : GEngine->C_BrushWire;
// If we have more than 2 vertices placed, connect them with lines
if( ShapeVertices.Num() > 1 )
{
for( int32 v = 0 ; v < ShapeVertices.Num() - 1 ; ++v )
{
PDI->DrawLine( ShapeVertices[v], ShapeVertices[ v+1 ], Color, SDPG_Foreground );
}
}
// Draw vertices for each point the user has put down
for( int32 v = 0 ; v < ShapeVertices.Num() ; ++v )
{
PDI->DrawPoint( ShapeVertices[v], Color, 6.f, SDPG_Foreground );
}
if( ShapeVertices.Num() )
{
if (!DoesFinalLineIntersectWithShape(ShapeVertices, MouseWorldSpacePos))
{
// Draw a dashed line from the last placed vertex to the current mouse position
DrawDashedLine(PDI, ShapeVertices[ShapeVertices.Num() - 1], MouseWorldSpacePos, FLinearColor(1, 0.5f, 0), GEditor->GetGridSize(), SDPG_Foreground);
}
}
if( ShapeVertices.Num() > 2 )
{
if (!DoesFinalLineIntersectWithShape(ShapeVertices, ShapeVertices[0]))
{
// Draw a darkened dashed line to show what the completed shape will look like
DrawDashedLine(PDI, ShapeVertices[ShapeVertices.Num() - 1], ShapeVertices[0], FLinearColor(.5, 0, 0), GEditor->GetGridSize(), SDPG_Foreground);
}
}
// Draw a box where the next vertex will be placed
int32 BoxSz = FMath::Max( GEditor->GetGridSize() / 2, 1.f );
DrawWireBox(PDI, FBox::BuildAABB( MouseWorldSpacePos, FVector(BoxSz,BoxSz,BoxSz) ), FLinearColor(1,1,1), SDPG_Foreground);
}
void UGeomModifier_Pen::DrawHUD(FEditorViewportClient* ViewportClient,FViewport* Viewport,const FSceneView* View,FCanvas* Canvas)
{
}
void UGeomModifier_Pen::Tick(FEditorViewportClient* ViewportClient,float DeltaTime)
{
if( GCurrentLevelEditingViewportClient == ViewportClient )
{
FVector NewMouseWorldSpacePos = ComputeWorldSpaceMousePos(ViewportClient);
// If the grid is enabled, figure out where the nearest grid location is to the mouse cursor
if( GetDefault<ULevelEditorViewportSettings>()->GridEnabled )
{
NewMouseWorldSpacePos = NewMouseWorldSpacePos.GridSnap( GEditor->GetGridSize() );
}
// If the mouse position has moved, update the viewport
if( NewMouseWorldSpacePos != MouseWorldSpacePos )
{
MouseWorldSpacePos = NewMouseWorldSpacePos;
GEditor->RedrawLevelEditingViewports( true );
}
}
}
/*------------------------------------------------------------------------------
UGeomModifier_Clip
------------------------------------------------------------------------------*/
namespace GeometryClipping {
/**
* Creates a giant brush aligned with this plane.
*
* @param OutGiantBrush [out] The new brush.
* @param InPlane Plane with which to align the brush.
*
* NOTE: it is up to the caller to set up the new brush upon return in regards to it's CSG operation and flags.
*/
static void BuildGiantAlignedBrush( ABrush& OutGiantBrush, const FPlane& InPlane )
{
OutGiantBrush.SetActorLocation(FVector::ZeroVector, false);
OutGiantBrush.SetPrePivot(FVector::ZeroVector);
verify( OutGiantBrush.Brush );
verify( OutGiantBrush.Brush->Polys );
OutGiantBrush.Brush->Polys->Element.Empty();
// Create a list of vertices that can be used for the new brush
FVector vtxs[8];
FPlane FlippedPlane = InPlane.Flip();
FPoly TempPoly = FPoly::BuildInfiniteFPoly( FlippedPlane );
TempPoly.Finalize(&OutGiantBrush,0);
vtxs[0] = TempPoly.Vertices[0];
vtxs[1] = TempPoly.Vertices[1];
vtxs[2] = TempPoly.Vertices[2];
vtxs[3] = TempPoly.Vertices[3];
FlippedPlane = FlippedPlane.Flip();
FPoly TempPoly2 = FPoly::BuildInfiniteFPoly( FlippedPlane );
vtxs[4] = TempPoly2.Vertices[0] + (TempPoly2.Normal * -(WORLD_MAX)); vtxs[5] = TempPoly2.Vertices[1] + (TempPoly2.Normal * -(WORLD_MAX));
vtxs[6] = TempPoly2.Vertices[2] + (TempPoly2.Normal * -(WORLD_MAX)); vtxs[7] = TempPoly2.Vertices[3] + (TempPoly2.Normal * -(WORLD_MAX));
// Create the polys for the new brush.
FPoly newPoly;
// TOP
newPoly.Init();
newPoly.Base = vtxs[0];
newPoly.Vertices.Add( vtxs[0] );
newPoly.Vertices.Add( vtxs[1] );
newPoly.Vertices.Add( vtxs[2] );
newPoly.Vertices.Add( vtxs[3] );
newPoly.Finalize(&OutGiantBrush,0);
new(OutGiantBrush.Brush->Polys->Element)FPoly(newPoly);
// BOTTOM
newPoly.Init();
newPoly.Base = vtxs[4];
newPoly.Vertices.Add( vtxs[4] );
newPoly.Vertices.Add( vtxs[5] );
newPoly.Vertices.Add( vtxs[6] );
newPoly.Vertices.Add( vtxs[7] );
newPoly.Finalize(&OutGiantBrush,0);
new(OutGiantBrush.Brush->Polys->Element)FPoly(newPoly);
// SIDES
// 1
newPoly.Init();
newPoly.Base = vtxs[1];
newPoly.Vertices.Add( vtxs[1] );
newPoly.Vertices.Add( vtxs[0] );
newPoly.Vertices.Add( vtxs[7] );
newPoly.Vertices.Add( vtxs[6] );
newPoly.Finalize(&OutGiantBrush,0);
new(OutGiantBrush.Brush->Polys->Element)FPoly(newPoly);
// 2
newPoly.Init();
newPoly.Base = vtxs[2];
newPoly.Vertices.Add( vtxs[2] );
newPoly.Vertices.Add( vtxs[1] );
newPoly.Vertices.Add( vtxs[6] );
newPoly.Vertices.Add( vtxs[5] );
newPoly.Finalize(&OutGiantBrush,0);
new(OutGiantBrush.Brush->Polys->Element)FPoly(newPoly);
// 3
newPoly.Init();
newPoly.Base = vtxs[3];
newPoly.Vertices.Add( vtxs[3] );
newPoly.Vertices.Add( vtxs[2] );
newPoly.Vertices.Add( vtxs[5] );
newPoly.Vertices.Add( vtxs[4] );
newPoly.Finalize(&OutGiantBrush,0);
new(OutGiantBrush.Brush->Polys->Element)FPoly(newPoly);
// 4
newPoly.Init();
newPoly.Base = vtxs[0];
newPoly.Vertices.Add( vtxs[0] );
newPoly.Vertices.Add( vtxs[3] );
newPoly.Vertices.Add( vtxs[4] );
newPoly.Vertices.Add( vtxs[7] );
newPoly.Finalize(&OutGiantBrush,0);
new(OutGiantBrush.Brush->Polys->Element)FPoly(newPoly);
// Finish creating the new brush.
OutGiantBrush.Brush->BuildBound();
}
/**
* Clips the specified brush against the specified plane.
*
* @param InWorld World context
* @param InPlane The plane to clip against.
* @param InBrush The brush to clip.
* @return The newly created brush representing the portion of the brush in the plane's positive halfspace.
*/
static ABrush* ClipBrushAgainstPlane( const FPlane& InPlane, ABrush* InBrush)
{
UWorld* World = InBrush->GetWorld();
ULevel* BrushLevel = InBrush->GetLevel();
// Create a giant brush in the level of the source brush to use in the intersection process.
ABrush* ClippedBrush = NULL;
// When clipping non-builder brushes, create a duplicate of the brush
// to clip. This duplicate will replace the existing brush.
if( !FActorEditorUtils::IsABuilderBrush(InBrush) )
{
// Select only the original brush to prevent other actors from being duplicated.
GEditor->SelectNone( false, true );
GEditor->SelectActor( InBrush, true, false, false );
// Duplicate the original brush. This will serve as our clipped brush.
GEditor->edactDuplicateSelected( BrushLevel, false );
// Clipped brush should be the only selected
// actor if the duplication didn't fail.
ClippedBrush = GEditor->GetSelectedActors()->GetTop<ABrush>();
}
// To clip the builder brush, instead of replacing it, spawn a
// temporary brush to clip. Then, copy that to the builder brush.
else
{
// NOTE: This brush is discarded later on after copying the values to the builder brush.
FActorSpawnParameters SpawnInfo;
SpawnInfo.OverrideLevel = BrushLevel;
SpawnInfo.Template = InBrush;
ClippedBrush = World->SpawnActor<ABrush>( InBrush->GetClass(), SpawnInfo );
check( ClippedBrush );
}
// It's possible that the duplication failed.
if( !ClippedBrush )
{
return NULL;
}
// The brushes should have the same class otherwise
// perhaps there were additional brushes were selected.
check( ClippedBrush->GetClass() == InBrush->GetClass() );
ClippedBrush->Brush = NewObject<UModel>(InBrush->GetOuter());
ClippedBrush->Brush->Initialize(nullptr);
ClippedBrush->GetBrushComponent()->Brush = ClippedBrush->Brush;
GeometryClipping::BuildGiantAlignedBrush( *ClippedBrush, InPlane );
ClippedBrush->BrushType = InBrush->BrushType;
ClippedBrush->SetFlags( InBrush->GetFlags() );
ClippedBrush->PolyFlags = InBrush->PolyFlags;
// Create a BSP for the brush that is being clipped.
FBSPOps::bspBuild( InBrush->Brush, FBSPOps::BSP_Optimal, 15, 70, 1, 0 );
FBSPOps::bspRefresh( InBrush->Brush, true );
FBSPOps::bspBuildBounds( InBrush->Brush );
// Intersect the giant brush with the source brush's BSP. This will give us the finished, clipping brush
// contained inside of the giant brush.
GEditor->bspBrushCSG( ClippedBrush, InBrush->Brush, 0, Brush_MAX, CSG_Intersect, false, false, true );
FBSPOps::bspUnlinkPolys( ClippedBrush->Brush );
// Remove all polygons on the giant brush that don't match the normal of the clipping plane
for( int32 p = 0 ; p < ClippedBrush->Brush->Polys->Element.Num() ; ++p )
{
FPoly* P = &ClippedBrush->Brush->Polys->Element[p];
if( P->Finalize( ClippedBrush, 1 ) == 0 )
{
if( !FPlane( P->Vertices[0], P->Normal ).Equals( InPlane, 0.01f ) )
{
ClippedBrush->Brush->Polys->Element.RemoveAt( p );
p = -1;
}
}
}
// The BSP "CSG_Intersect" code sometimes creates some nasty polygon fragments so clean those up here before going further.
FPoly::OptimizeIntoConvexPolys( ClippedBrush, ClippedBrush->Brush->Polys->Element );
// Clip each polygon in the original brush against the clipping plane. For every polygon that is behind the plane or split by it, keep the back portion.
FVector PlaneBase = FVector( InPlane.X, InPlane.Y, InPlane.Z ) * InPlane.W;
for( int32 p = 0 ; p < InBrush->Brush->Polys->Element.Num() ; ++p )
{
FPoly Poly = InBrush->Brush->Polys->Element[p];
FPoly front, back;
int32 res = Poly.SplitWithPlane( PlaneBase, InPlane.GetSafeNormal(), &front, &back, true );
switch( res )
{
case SP_Back:
ClippedBrush->Brush->Polys->Element.Add( Poly );
break;
case SP_Split:
ClippedBrush->Brush->Polys->Element.Add( back );
break;
}
}
// At this point we have a clipped brush with optimized capping polygons so we can finish up by fixing it's ordering in the actor array and other misc things.
ClippedBrush->CopyPosRotScaleFrom( InBrush );
ClippedBrush->PolyFlags = InBrush->PolyFlags;
// Clean the brush up.
for( int32 poly = 0 ; poly < ClippedBrush->Brush->Polys->Element.Num() ; poly++ )
{
FPoly* Poly = &(ClippedBrush->Brush->Polys->Element[poly]);
Poly->iLink = poly;
Poly->Normal = FVector::ZeroVector;
Poly->Finalize(ClippedBrush,0);
}
// One final pass to clean the polyflags of all temporary settings.
for( int32 poly = 0 ; poly < ClippedBrush->Brush->Polys->Element.Num() ; poly++ )
{
FPoly* Poly = &(ClippedBrush->Brush->Polys->Element[poly]);
Poly->PolyFlags &= ~PF_EdCut;
Poly->PolyFlags &= ~PF_EdProcessed;
}
// Move the new brush to where the new brush was to preserve brush ordering.
ABrush* BuilderBrush = World->GetDefaultBrush();
if( InBrush == BuilderBrush )
{
// Special-case behavior for the builder brush.
// Copy the temporary brush back over onto the builder brush (keeping object flags)
BuilderBrush->Modify();
FBSPOps::csgCopyBrush( BuilderBrush, ClippedBrush, BuilderBrush->GetFlags(), RF_NoFlags, 0, true );
GEditor->Layers->DisassociateActorFromLayers( ClippedBrush );
World->EditorDestroyActor( ClippedBrush, false );
// Note that we're purposefully returning non-NULL here to report that the clip was successful,
// even though the ClippedBrush has been destroyed!
}
else
{
// Remove the old brush.
const int32 ClippedBrushIndex = BrushLevel->Actors.Num() - 1;
check( BrushLevel->Actors[ClippedBrushIndex] == ClippedBrush );
BrushLevel->Actors.RemoveAt(ClippedBrushIndex);
// Add the new brush right after the old brush.
const int32 OldBrushIndex = BrushLevel->Actors.Find( InBrush );
check( OldBrushIndex != INDEX_NONE );
BrushLevel->Actors.Insert( ClippedBrush, OldBrushIndex+1 );
}
return ClippedBrush;
}
} // namespace GeometryClipping
UGeomModifier_Clip::UGeomModifier_Clip(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "BrushClip", "Brush Clip");
bFlipNormal = false;
bSplit = false;
}
void UGeomModifier_Clip::WasActivated()
{
ClipMarkers.Empty();
}
bool UGeomModifier_Clip::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return mode->GetSelectionState() ? false : true;
}
bool UGeomModifier_Clip::OnApply()
{
ApplyClip( bSplit, bFlipNormal );
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
void UGeomModifier_Clip::ApplyClip( bool InSplit, bool InFlipNormal )
{
if ( !GLastKeyLevelEditingViewportClient )
{
return;
}
// Assemble the set of selected brushes.
TArray<ABrush*> Brushes;
for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It )
{
AActor* Actor = static_cast<AActor*>( *It );
checkSlow( Actor->IsA(AActor::StaticClass()) );
ABrush* Brush = Cast< ABrush >( Actor );
if( Brush )
{
Brushes.Add( Brush );
}
}
// Do nothing if no brushes are selected.
if ( Brushes.Num() == 0 )
{
return;
}
// Make sure enough clip markers have been placed.
if( ClipMarkers.Num() != 2 )
{
GeomError( NSLOCTEXT("UnrealEd", "Error_NotEnoughClipMarkers", "You haven't placed enough clip markers to perform this operation.").ToString() );
return;
}
// Focus has to be in an orthographic viewport so the editor can determine where the third point on the plane is
if( !GLastKeyLevelEditingViewportClient->IsOrtho() )
{
GeomError( NSLOCTEXT("UnrealEd", "Error_BrushClipViewportNotOrthographic", "The focus needs to be in an orthographic viewport for brush clipping to work.").ToString() );
return;
}
// Create a clipping plane based on ClipMarkers present in the level.
const FVector vtx1 = ClipMarkers[0];
const FVector vtx2 = ClipMarkers[1];
FVector vtx3;
// Compute the third vertex based on the viewport orientation.
vtx3 = vtx1;
switch( GLastKeyLevelEditingViewportClient->ViewportType )
{
case LVT_OrthoXY:
vtx3.Z -= 64;
break;
case LVT_OrthoXZ:
vtx3.Y -= 64;
break;
case LVT_OrthoYZ:
vtx3.X -= 64;
break;
}
// Perform the clip.
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "BrushClip", "Brush Clip") );
GEditor->SelectNone( false, true );
// Clip the brush list.
TArray<ABrush*> NewBrushes;
TArray<ABrush*> OldBrushes;
for ( int32 BrushIndex = 0 ; BrushIndex < Brushes.Num() ; ++BrushIndex )
{
ABrush* SrcBrush = Brushes[ BrushIndex ];
// Compute a clipping plane in the local frame of the brush.
const FTransform ToBrushWorld( SrcBrush->ActorToWorld() );
const FVector LocalVtx1( ToBrushWorld.InverseTransformPosition( vtx1 ) );
const FVector LocalVtx2( ToBrushWorld.InverseTransformPosition( vtx2 ) );
const FVector LocalVtx3( ToBrushWorld.InverseTransformPosition( vtx3 ) );
FVector PlaneNormal( (LocalVtx2 - LocalVtx1) ^ (LocalVtx3 - LocalVtx1) );
if( PlaneNormal.SizeSquared() < THRESH_ZERO_NORM_SQUARED )
{
GeomError( NSLOCTEXT("UnrealEd", "Error_ClipUnableToComputeNormal", "Unable to compute normal for brush clip!").ToString() );
continue;
}
PlaneNormal.Normalize();
FPlane ClippingPlane( LocalVtx1, PlaneNormal );
if ( InFlipNormal )
{
ClippingPlane = ClippingPlane.Flip();
}
// Is the brush a builder brush?
const bool bIsBuilderBrush = FActorEditorUtils::IsABuilderBrush(SrcBrush);
// Perform the clip.
bool bCreatedBrush = false;
ABrush* NewBrush = GeometryClipping::ClipBrushAgainstPlane( ClippingPlane, SrcBrush );
if ( NewBrush )
{
// Select the src brush for builders, or the returned brush for non-builders.
if ( !bIsBuilderBrush )
{
NewBrushes.Add( NewBrush );
}
else
{
NewBrushes.Add( SrcBrush );
}
bCreatedBrush = true;
}
// If we're doing a split instead of just a plain clip . . .
if( InSplit )
{
// Don't perform a second clip if the builder brush was already split.
if ( !bIsBuilderBrush || !bCreatedBrush )
{
// Clip the brush against the flipped clipping plane.
ABrush* NewBrush2 = GeometryClipping::ClipBrushAgainstPlane( ClippingPlane.Flip(), SrcBrush );
if ( NewBrush2 )
{
// We don't add the brush to the list of new brushes, so that only new brushes
// in the non-cleaved halfspace of the clipping plane will be selected.
bCreatedBrush = true;
}
}
}
// Destroy source brushes that aren't builders.
if ( !bIsBuilderBrush )
{
OldBrushes.Add( SrcBrush );
}
}
// Clear selection to prevent the second clipped brush from being selected.
// When both are selected, it's hard to tell that the brush is clipped.
GEditor->SelectNone( false, true );
// Delete old brushes.
for ( int32 BrushIndex = 0 ; BrushIndex < OldBrushes.Num() ; ++BrushIndex )
{
ABrush* OldBrush = OldBrushes[ BrushIndex ];
GEditor->Layers->DisassociateActorFromLayers( OldBrush );
OldBrush->GetWorld()->EditorDestroyActor( OldBrush, true );
}
// Select new brushes.
for ( int32 BrushIndex = 0 ; BrushIndex < NewBrushes.Num() ; ++BrushIndex )
{
ABrush* NewBrush = NewBrushes[ BrushIndex ];
GEditor->SelectActor( NewBrush, true, false );
}
// Notify editor of new selection state.
GEditor->NoteSelectionChange();
}
FEdModeGeometry* Mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
Mode->FinalizeSourceData();
Mode->GetFromSource();
}
bool UGeomModifier_Clip::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
bool bResult = false;
if( ViewportClient->IsOrtho() && Event == IE_Pressed )
{
const bool bCtrlDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl);
const bool bShiftDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift);
const bool bAltDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt);
if( (bCtrlDown && !bShiftDown && !bAltDown && Key == EKeys::RightMouseButton) || Key == EKeys::SpaceBar )
{
// if the user has 2 markers placed and the click location is on top of the second point, perform the cllck. This is a shortcut the LDs wanted.
if( ClipMarkers.Num() == 2 )
{
const FVector* Pos = &ClipMarkers[1];
if( Pos->Equals( SnappedMouseWorldSpacePos ) )
{
OnApply();
return true;
}
}
// If there are already 2 clip markers in the world, clear them out.
if( ClipMarkers.Num() > 1 )
{
ClipMarkers.Empty();
}
ClipMarkers.Add( SnappedMouseWorldSpacePos );
bResult = true;
}
else if( Key == EKeys::Escape || Key == EKeys::BackSpace )
{
if( ClipMarkers.Num() )
{
ClipMarkers.RemoveAt( ClipMarkers.Num() - 1 );
}
bResult = true;
}
else if( Key == EKeys::Enter )
{
// If the user has 1 marker placed when they press ENTER, go ahead and place a second one at the current mouse location.
// This allows LDs to place one point, move to a good spot and press ENTER for a quick clip.
if( ClipMarkers.Num() == 1 )
{
ClipMarkers.Add( SnappedMouseWorldSpacePos );
}
ApplyClip( bAltDown, bShiftDown );
bResult = true;
}
}
if( bResult )
{
GEditor->RedrawLevelEditingViewports( true );
}
return bResult;
}
void UGeomModifier_Clip::Render(const FSceneView* View,FViewport* Viewport,FPrimitiveDrawInterface* PDI)
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
FModeTool_GeometryModify* tool = (FModeTool_GeometryModify*)mode->GetCurrentTool();
if( tool->GetCurrentModifier() != this )
{
return;
}
// Only draw in ortho viewports
if( !((FEditorViewportClient*)(Viewport->GetClient()))->IsOrtho() )
{
return;
}
// Draw a yellow box on each clip marker
for( int32 x = 0 ; x < ClipMarkers.Num() ; ++x )
{
FVector* vtx = &ClipMarkers[x];
PDI->DrawPoint( *vtx, FLinearColor(1,0,0), 6.f, SDPG_Foreground );
}
// If 2 markers are placed, draw a line connecting them and a line showing the clip normal.
// If 1 marker is placed, draw a dashed line and normal to show where the clip plane will appear if the user commits.
if( ClipMarkers.Num() )
{
FVector LineStart = ClipMarkers[0];
FVector LineEnd = (ClipMarkers.Num() == 2) ? ClipMarkers[1] : SnappedMouseWorldSpacePos;
if( ClipMarkers.Num() == 1 )
{
DrawDashedLine( PDI, LineStart, LineEnd, FLinearColor(1,.5,0), GEditor->GetGridSize(), SDPG_Foreground );
}
else
{
PDI->DrawLine( LineStart, LineEnd, FLinearColor(1,0,0), SDPG_Foreground );
}
FVector vtx1, vtx2, vtx3;
FPoly NormalPoly;
vtx1 = LineStart;
vtx2 = LineEnd;
vtx3 = vtx1;
const FEditorViewportClient* ViewportClient = static_cast<FEditorViewportClient*>( Viewport->GetClient() );
switch( ViewportClient->ViewportType )
{
case LVT_OrthoXY:
vtx3.Z -= 64;
break;
case LVT_OrthoXZ:
vtx3.Y -= 64;
break;
case LVT_OrthoYZ:
vtx3.X -= 64;
break;
}
NormalPoly.Vertices.Add( vtx1 );
NormalPoly.Vertices.Add( vtx2 );
NormalPoly.Vertices.Add( vtx3 );
if( !NormalPoly.CalcNormal(1) )
{
FVector Start = ( vtx1 + vtx2 ) / 2.f;
float NormalLength = (vtx2 - vtx1).Size() / 2.f;
if( ClipMarkers.Num() == 1 )
{
DrawDashedLine( PDI, Start, Start + NormalPoly.Normal * NormalLength, FLinearColor(1,.5,0), GEditor->GetGridSize(), SDPG_Foreground );
}
else
{
PDI->DrawLine( Start, Start + NormalPoly.Normal * NormalLength, FLinearColor(1,0,0), SDPG_Foreground );
}
}
}
// Draw a box at the cursor location
int32 BoxSz = FMath::Max( GEditor->GetGridSize() / 2, 1.f );
DrawWireBox(PDI, FBox::BuildAABB( SnappedMouseWorldSpacePos, FVector(BoxSz,BoxSz,BoxSz) ), FLinearColor(1,1,1), SDPG_Foreground);
}
void UGeomModifier_Clip::DrawHUD(FEditorViewportClient* ViewportClient,FViewport* Viewport,const FSceneView* View,FCanvas* Canvas)
{
}
void UGeomModifier_Clip::Tick(FEditorViewportClient* ViewportClient,float DeltaTime)
{
if( GCurrentLevelEditingViewportClient == ViewportClient )
{
// Figure out where the nearest grid location is to the mouse cursor
FVector NewSnappedMouseWorldSpacePos = ComputeWorldSpaceMousePos(ViewportClient).GridSnap( GEditor->GetGridSize() );
// If the snapped mouse position has moved, update the viewport
if( NewSnappedMouseWorldSpacePos != SnappedMouseWorldSpacePos )
{
SnappedMouseWorldSpacePos = NewSnappedMouseWorldSpacePos;
GEditor->RedrawLevelEditingViewports( true );
}
}
}
UGeomModifier_Delete::UGeomModifier_Delete(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Delete", "Delete");
bPushButton = true;
}
bool UGeomModifier_Delete::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return (mode->HavePolygonsSelected() || mode->HaveVerticesSelected());
}
bool UGeomModifier_Delete::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
bool bHandled = false;
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
// Polys
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
if( gp->IsSelected() )
{
gp->GetParentObject()->GetActualBrush()->Brush->Polys->Element[ gp->ActualPolyIndex ].PolyFlags |= PF_GeomMarked;
bHandled = 1;
}
}
for( int32 p = 0 ; p < go->GetActualBrush()->Brush->Polys->Element.Num() ; ++p )
{
if( (go->GetActualBrush()->Brush->Polys->Element[ p ].PolyFlags&PF_GeomMarked) > 0 )
{
go->GetActualBrush()->Brush->Polys->Element.RemoveAt( p );
p = -1;
}
}
// Verts
for( int32 v = 0 ; v < go->VertexPool.Num() ; ++v )
{
FGeomVertex* gv = &go->VertexPool[v];
if( gv->IsSelected() )
{
for( int32 x = 0 ; x < gv->GetParentObject()->GetActualBrush()->Brush->Polys->Element.Num() ; ++x )
{
FPoly* Poly = &gv->GetParentObject()->GetActualBrush()->Brush->Polys->Element[x];
Poly->RemoveVertex( *gv );
bHandled = 1;
}
}
}
go->GetActualBrush()->SavedSelections.Empty();
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
// Reset the pivot point to the newest selected object.
AActor* SelectedActor = Cast<AActor>(GEditor->GetSelectedActors()->GetBottom(AActor::StaticClass()));
GEditor->GetSelectedActors()->Modify();
if(SelectedActor)
{
FEditorModeTools& Tools = GLevelEditorModeTools();
Tools.SetPivotLocation( SelectedActor->GetActorLocation() , false );
}
return bHandled;
}
UGeomModifier_Create::UGeomModifier_Create(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Create", "Create");
bPushButton = true;
}
bool UGeomModifier_Create::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return mode->HaveVerticesSelected();
}
bool UGeomModifier_Create::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
go->CompileSelectionOrder();
// Create an ordered list of vertices based on the selection order.
TArray<FGeomVertex*> Verts;
for( int32 x = 0 ; x < go->SelectionOrder.Num() ; ++x )
{
FGeomBase* obj = go->SelectionOrder[x];
if( obj->IsVertex() )
{
Verts.Add( (FGeomVertex*)obj );
}
}
if( Verts.Num() > 2 )
{
// Create new geometry based on the selected vertices
FPoly* NewPoly = new( go->GetActualBrush()->Brush->Polys->Element )FPoly();
NewPoly->Init();
for( int32 x = 0 ; x < Verts.Num() ; ++x )
{
FGeomVertex* gv = Verts[x];
new(NewPoly->Vertices) FVector(*gv);
}
NewPoly->Normal = FVector::ZeroVector;
NewPoly->Base = *Verts[0];
NewPoly->PolyFlags = PF_DefaultFlags;
}
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
UGeomModifier_Flip::UGeomModifier_Flip(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Flip", "Flip");
bPushButton = true;
}
bool UGeomModifier_Flip::Supports()
{
// Supports polygons selected and objects selected
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return (!mode->HaveEdgesSelected() && !mode->HaveVerticesSelected());
}
bool UGeomModifier_Flip::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
bool bHavePolygonsSelected = mode->HavePolygonsSelected();
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
if( gp->IsSelected() || !bHavePolygonsSelected )
{
FPoly* Poly = &go->GetActualBrush()->Brush->Polys->Element[ gp->ActualPolyIndex ];
Poly->Reverse();
}
}
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
UGeomModifier_Split::UGeomModifier_Split(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Split", "Split");
bPushButton = true;
}
bool UGeomModifier_Split::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
// This modifier assumes that a single geometry object is selected
if( mode->CountObjectsSelected() != 1 )
{
return false;
}
int32 NumPolygonsSelected = mode->CountSelectedPolygons();
int32 NumEdgesSelected = mode->CountSelectedEdges();
int32 NumVerticesSelected = mode->CountSelectedVertices();
if( (NumPolygonsSelected == 1 && NumEdgesSelected == 1 && NumVerticesSelected == 0) // Splitting a face at an edge mid point (scalpel)
|| (NumPolygonsSelected == 0 && NumEdgesSelected > 0 && NumVerticesSelected == 0) // Splitting a brush at an edge mid point (ring cut)
|| (NumPolygonsSelected == 1 && NumEdgesSelected == 0 && NumVerticesSelected == 2) // Splitting a polygon across 2 vertices
|| (NumPolygonsSelected == 0 && NumEdgesSelected == 0 && NumVerticesSelected == 2) // Splitting a brush across 2 vertices
)
{
return true;
}
return false;
}
bool UGeomModifier_Split::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
// Get a pointer to the selected geom object
FGeomObject* GeomObject = NULL;
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
GeomObject = *Itor;
break;
}
if( GeomObject == NULL )
{
return false;
}
// Count up how many of each subobject are selected so we can determine what the user is trying to split
int32 NumPolygonsSelected = mode->CountSelectedPolygons();
int32 NumEdgesSelected = mode->CountSelectedEdges();
int32 NumVerticesSelected = mode->CountSelectedVertices();
if( NumPolygonsSelected == 1 && NumEdgesSelected == 1 && NumVerticesSelected == 0 )
{
//
// Splitting a face at an edge mid point (scalpel)
//
// Get the selected edge
TArray<FGeomEdge*> Edges;
mode->GetSelectedEdges( Edges );
check( Edges.Num() == 1 );
FGeomEdge* SelectedEdge = Edges[0];
// Figure out the verts that are part of that edge
FGeomVertex* Vertex0 = &GeomObject->VertexPool[ SelectedEdge->VertexIndices[0] ];
FGeomVertex* Vertex1 = &GeomObject->VertexPool[ SelectedEdge->VertexIndices[1] ];
const FVector Vtx0 = *Vertex0->GetActualVertex( Vertex0->ActualVertexIndices[0] );
const FVector Vtx1 = *Vertex1->GetActualVertex( Vertex1->ActualVertexIndices[0] );
// Get the selected polygon
TArray<FGeomPoly*> Polygons;
mode->GetSelectedPolygons( Polygons );
check( Polygons.Num() == 1 );
FGeomPoly* Polygon = Polygons[0];
FPoly* SelectedPoly = Polygon->GetActualPoly();
// Get the selected brush
ABrush* Brush = GeomObject->GetActualBrush();
//
// Sanity checking
//
{
// 1. Make sure that the selected edge is part of the selected polygon
if( !SelectedPoly->Vertices.Contains( Vtx0 ) || !SelectedPoly->Vertices.Contains( Vtx1 ) )
{
GeomError( NSLOCTEXT("UnrealEd", "Error_SelectedEdgeMustBelongToSelectedPoly", "The edge used for splitting must be part of the selected polygon.").ToString() );
return false;
}
}
// Generate a base and a normal for the cutting plane
const FVector PlaneNormal( (Vtx1 - Vtx0).GetSafeNormal() );
const FVector PlaneBase = 0.5f*(Vtx1 + Vtx0);
// Clip the selected polygon against the cutting plane
FPoly Front, Back;
Front.Init();
Back.Init();
int32 Res = SelectedPoly->SplitWithPlane( PlaneBase, PlaneNormal, &Front, &Back, 1 );
if( Res == SP_Split )
{
TArray<FPoly> NewPolygons;
NewPolygons.Add( Front );
NewPolygons.Add( Back );
// At this point, see if any other polygons in the brush need to have a vertex added to an edge
FPlane CuttingPlane( PlaneBase, PlaneNormal );
for( int32 p = 0 ; p < Brush->Brush->Polys->Element.Num() ; ++p )
{
FPoly* P = &Brush->Brush->Polys->Element[p];
if( P != SelectedPoly )
{
for( int32 v = 0 ; v < P->Vertices.Num() ; ++v )
{
FVector* v0 = &P->Vertices[v];
FVector* v1 = &P->Vertices[ (v + 1) % P->Vertices.Num() ];
// Make sure the line formed by the edge actually crosses the plane before checking for the intersection point.
if( FMath::IsNegativeFloat( CuttingPlane.PlaneDot( *v0 ) ) != FMath::IsNegativeFloat( CuttingPlane.PlaneDot( *v1 ) ) )
{
FVector Intersection = FMath::LinePlaneIntersection( *v0, *v1, CuttingPlane );
// Make sure that the intersection point lies on the same plane as the selected polygon as we only need to add it there and not
// to any other edge that might intersect the cutting plane.
if( SelectedPoly->OnPlane( Intersection ) )
{
P->Vertices.Insert( Intersection, (v+1) % P->Vertices.Num() );
break;
}
}
}
NewPolygons.Add( *P );
}
}
// Replace the old polygon list with the new one
Brush->Brush->Polys->Element.AssignButKeepOwner(NewPolygons);
}
}
else if( NumPolygonsSelected == 0 && NumEdgesSelected > 0 && NumVerticesSelected == 0 )
{
//
// Splitting a brush at an edge mid point (ring cut)
//
// Get the selected edge
TArray<FGeomEdge*> Edges;
mode->GetSelectedEdges( Edges );
check( Edges.Num() > 0 );
FGeomEdge* Edge = Edges[0];
// Generate a base and a normal for the cutting plane
FGeomVertex* Vertex0 = &GeomObject->VertexPool[ Edge->VertexIndices[0] ];
FGeomVertex* Vertex1 = &GeomObject->VertexPool[ Edge->VertexIndices[1] ];
const FVector v0 = *Vertex0->GetActualVertex( Vertex0->ActualVertexIndices[0] );
const FVector v1 = *Vertex1->GetActualVertex( Vertex1->ActualVertexIndices[0] );
const FVector PlaneNormal( (v1 - v0).GetSafeNormal() );
const FVector PlaneBase = 0.5f*(v1 + v0);
ABrush* Brush = GeomObject->GetActualBrush();
// The polygons for the new brush are stored in here and the polys inside of the original brush are replaced at the end of the loop
TArray<FPoly> NewPolygons;
// Clip each polygon against the cutting plane
for( int32 p = 0 ; p < Brush->Brush->Polys->Element.Num() ; ++p )
{
FPoly* Poly = &Brush->Brush->Polys->Element[p];
FPoly Front, Back;
Front.Init();
Back.Init();
int32 Res = Poly->SplitWithPlane( PlaneBase, PlaneNormal, &Front, &Back, 1 );
switch( Res )
{
case SP_Split:
NewPolygons.Add( Front );
NewPolygons.Add( Back );
break;
default:
NewPolygons.Add( *Poly );
break;
}
}
// Replace the old polygon list with the new one
Brush->Brush->Polys->Element.AssignButKeepOwner(NewPolygons);
}
else if( NumPolygonsSelected == 1 && NumEdgesSelected == 0 && NumVerticesSelected == 2 )
{
//
// Splitting a polygon across 2 vertices
//
// Get the selected verts
TArray<FGeomVertex*> Verts;
mode->GetSelectedVertices( Verts );
check( Verts.Num() == 2 );
FGeomVertex* Vertex0 = Verts[0];
FGeomVertex* Vertex1 = Verts[1];
const FVector v0 = *Vertex0->GetActualVertex( Vertex0->ActualVertexIndices[0] );
const FVector v1 = *Vertex1->GetActualVertex( Vertex1->ActualVertexIndices[0] );
// Get the selected polygon
TArray<FGeomPoly*> Polys;
mode->GetSelectedPolygons( Polys );
check( Polys.Num() == 1 );
FGeomPoly* SelectedPoly = Polys[0];
FPoly* Poly = SelectedPoly->GetActualPoly();
//
// Sanity checking
//
{
// 1. Make sure that the selected vertices are part of the selected polygon
if( !SelectedPoly->GetActualPoly()->Vertices.Contains( v0 ) || !SelectedPoly->GetActualPoly()->Vertices.Contains( v1 ) )
{
GeomError( NSLOCTEXT("UnrealEd", "Error_SelectedVerticesMustBelongToSelectedPoly", "The vertices used for splitting must be part of the selected polygon.").ToString() );
return false;
}
}
// Generate a base and a normal for the cutting plane
FVector v2 = v0 + (SelectedPoly->GetNormal() * 64.0f);
const FPlane PlaneNormal( v0, v1, v2 );
const FVector PlaneBase = 0.5f*(v1 + v0);
ABrush* Brush = GeomObject->GetActualBrush();
// The polygons for the new brush are stored in here and the polys inside of the original brush are replaced at the end of the loop
TArray<FPoly> NewPolygons;
// Clip the selected polygon against the cutting plane.
for( int32 p = 0 ; p < Brush->Brush->Polys->Element.Num() ; ++p )
{
FPoly* P = &Brush->Brush->Polys->Element[p];
if( P == Poly )
{
FPoly Front, Back;
Front.Init();
Back.Init();
int32 Res = P->SplitWithPlane( PlaneBase, PlaneNormal, &Front, &Back, 1 );
switch( Res )
{
case SP_Split:
NewPolygons.Add( Front );
NewPolygons.Add( Back );
break;
default:
NewPolygons.Add( *P );
break;
}
}
else
{
NewPolygons.Add( *P );
}
}
// Replace the old polygon list with the new one
Brush->Brush->Polys->Element.AssignButKeepOwner(NewPolygons);
}
else if( NumPolygonsSelected == 0 && NumEdgesSelected == 0 && NumVerticesSelected == 2 )
{
//
// Splitting a brush across 2 vertices
//
// Get the selected verts
TArray<FGeomVertex*> Verts;
mode->GetSelectedVertices( Verts );
check( Verts.Num() == 2 );
// Generate a base and a normal for the cutting plane
FGeomVertex* Vertex0 = Verts[0];
FGeomVertex* Vertex1 = Verts[1];
const FVector v0 = *Vertex0->GetActualVertex( Vertex0->ActualVertexIndices[0] );
const FVector v1 = *Vertex1->GetActualVertex( Vertex1->ActualVertexIndices[0] );
FVector v2 = ((Vertex0->GetNormal() + Vertex1->GetNormal()) / 2.0f) * 64.f;
const FPlane PlaneNormal( v0, v1, v2 );
const FVector PlaneBase = 0.5f*(v1 + v0);
ABrush* Brush = GeomObject->GetActualBrush();
// The polygons for the new brush are stored in here and the polys inside of the original brush are replaced at the end of the loop
TArray<FPoly> NewPolygons;
// Clip each polygon against the cutting plane
for( int32 p = 0 ; p < Brush->Brush->Polys->Element.Num() ; ++p )
{
FPoly* Poly = &Brush->Brush->Polys->Element[p];
FPoly Front, Back;
Front.Init();
Back.Init();
int32 Res = Poly->SplitWithPlane( PlaneBase, PlaneNormal, &Front, &Back, 1 );
switch( Res )
{
case SP_Split:
NewPolygons.Add( Front );
NewPolygons.Add( Back );
break;
default:
NewPolygons.Add( *Poly );
break;
}
}
// Replace the old polygon list with the new one
Brush->Brush->Polys->Element.AssignButKeepOwner(NewPolygons);
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
UGeomModifier_Triangulate::UGeomModifier_Triangulate(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Triangulate", "Triangulate");
bPushButton = true;
}
bool UGeomModifier_Triangulate::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return (!mode->HaveEdgesSelected() && !mode->HaveVerticesSelected());
}
bool UGeomModifier_Triangulate::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
bool bHavePolygonsSelected = mode->HavePolygonsSelected();
// Mark the selected polygons so we can find them in the next loop, and create
// a local list of FPolys to triangulate later.
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
TArray<FPoly> PolyList;
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
if( gp->IsSelected() || !bHavePolygonsSelected )
{
gp->GetParentObject()->GetActualBrush()->Brush->Polys->Element[ gp->ActualPolyIndex ].PolyFlags |= PF_GeomMarked;
PolyList.Add( gp->GetParentObject()->GetActualBrush()->Brush->Polys->Element[ gp->ActualPolyIndex ] );
}
}
// Delete existing polygons
for( int32 p = 0 ; p < go->GetActualBrush()->Brush->Polys->Element.Num() ; ++p )
{
if( (go->GetActualBrush()->Brush->Polys->Element[ p ].PolyFlags&PF_GeomMarked) > 0 )
{
go->GetActualBrush()->Brush->Polys->Element.RemoveAt( p );
p = -1;
}
}
// Triangulate the old polygons into the brush
for( int32 p = 0 ; p < PolyList.Num() ; ++p )
{
TArray<FPoly> Triangles;
PolyList[p].Triangulate( go->GetActualBrush(), Triangles );
for( int32 t = 0 ; t < Triangles.Num() ; ++t )
{
go->GetActualBrush()->Brush->Polys->Element.Add( Triangles[t] );
}
}
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
UGeomModifier_Optimize::UGeomModifier_Optimize(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Optimize", "Optimize");
bPushButton = true;
}
bool UGeomModifier_Optimize::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return !mode->HaveVerticesSelected() && !mode->HaveEdgesSelected();
}
bool UGeomModifier_Optimize::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
TArray<FPoly> Polygons;
if( mode->HavePolygonsSelected() )
{
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
ABrush* ActualBrush = go->GetActualBrush();
// Gather a list of polygons that are
for( int32 p = 0 ; p < go->PolyPool.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[p];
if( gp->IsSelected() )
{
ActualBrush->Brush->Polys->Element[ gp->ActualPolyIndex ].PolyFlags |= PF_GeomMarked;
Polygons.Add( ActualBrush->Brush->Polys->Element[ gp->ActualPolyIndex ] );
}
}
// Delete existing polygons
for( int32 p = 0 ; p < go->GetActualBrush()->Brush->Polys->Element.Num() ; ++p )
{
if( (ActualBrush->Brush->Polys->Element[ p ].PolyFlags&PF_GeomMarked) > 0 )
{
ActualBrush->Brush->Polys->Element.RemoveAt( p );
p = -1;
}
}
// Optimize the polygons in the list
FPoly::OptimizeIntoConvexPolys( ActualBrush, Polygons );
// Copy the new polygons into the brush
for( int32 p = 0 ; p < Polygons.Num() ; ++p )
{
FPoly Poly = Polygons[p];
Poly.PolyFlags &= ~PF_GeomMarked;
ActualBrush->Brush->Polys->Element.Add( Poly );
}
}
}
else
{
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
ABrush* ActualBrush = go->GetActualBrush();
// Optimize the polygons
FPoly::OptimizeIntoConvexPolys( ActualBrush, ActualBrush->Brush->Polys->Element );
}
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
UGeomModifier_Turn::UGeomModifier_Turn(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Turn", "Turn");
bPushButton = true;
}
bool UGeomModifier_Turn::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return mode->HaveEdgesSelected();
}
bool UGeomModifier_Turn::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
// Edges
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
TArray<FGeomEdge> Edges;
go->CompileUniqueEdgeArray( &Edges );
// Make sure that all polygons involved are triangles
for( int32 e = 0 ; e < Edges.Num() ; ++e )
{
FGeomEdge* ge = &Edges[e];
for( int32 p = 0 ; p < ge->ParentPolyIndices.Num() ; ++p )
{
FGeomPoly* gp = &go->PolyPool[ ge->ParentPolyIndices[p] ];
FPoly* Poly = gp->GetActualPoly();
if( Poly->Vertices.Num() != 3 )
{
FNotificationInfo NotificationInfo(LOCTEXT("Error_PolygonsOnEdgeToTurnMustBeTriangles", "The polygons on each side of the edge you want to turn must be triangles."));
NotificationInfo.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
EndTrans();
return 0;
}
}
}
// Turn the edges, one by one
for( int32 e = 0 ; e < Edges.Num() ; ++e )
{
FGeomEdge* ge = &Edges[e];
TArray<FVector> Quad;
// Since we're doing each edge individually, they should each have exactly 2 polygon
// parents (and each one is a triangle (verified above))
if( ge->ParentPolyIndices.Num() == 2 )
{
FGeomPoly* gp = &go->PolyPool[ ge->ParentPolyIndices[0] ];
FPoly* Poly = gp->GetActualPoly();
FPoly SavePoly0 = *Poly;
int32 idx0 = Poly->GetVertexIndex( go->VertexPool[ ge->VertexIndices[0] ] );
int32 idx1 = Poly->GetVertexIndex( go->VertexPool[ ge->VertexIndices[1] ] );
int32 idx2 = INDEX_NONE;
if( idx0 + idx1 == 1 )
{
idx2 = 2;
}
else if( idx0 + idx1 == 3 )
{
idx2 = 0;
}
else
{
idx2 = 1;
}
Quad.Add( Poly->Vertices[idx0] );
Quad.Add( Poly->Vertices[idx2] );
Quad.Add( Poly->Vertices[idx1] );
gp = &go->PolyPool[ ge->ParentPolyIndices[1] ];
Poly = gp->GetActualPoly();
FPoly SavePoly1 = *Poly;
for( int32 v = 0 ; v < Poly->Vertices.Num() ; ++v )
{
Quad.AddUnique( Poly->Vertices[v] );
}
// If the adjoining polys were coincident, don't try to turn the edge
if (Quad.Num() == 3)
{
continue;
}
// Create new polygons
FPoly* NewPoly;
NewPoly = new( gp->GetParentObject()->GetActualBrush()->Brush->Polys->Element )FPoly();
NewPoly->Init();
new(NewPoly->Vertices) FVector(Quad[2]);
new(NewPoly->Vertices) FVector(Quad[1]);
new(NewPoly->Vertices) FVector(Quad[3]);
NewPoly->Base = SavePoly0.Base;
NewPoly->Material = SavePoly0.Material;
NewPoly->PolyFlags = SavePoly0.PolyFlags;
NewPoly->TextureU = SavePoly0.TextureU;
NewPoly->TextureV = SavePoly0.TextureV;
NewPoly->Normal = FVector::ZeroVector;
NewPoly->Finalize(go->GetActualBrush(),1);
NewPoly = new( gp->GetParentObject()->GetActualBrush()->Brush->Polys->Element )FPoly();
NewPoly->Init();
new(NewPoly->Vertices) FVector(Quad[3]);
new(NewPoly->Vertices) FVector(Quad[1]);
new(NewPoly->Vertices) FVector(Quad[0]);
NewPoly->Base = SavePoly1.Base;
NewPoly->Material = SavePoly1.Material;
NewPoly->PolyFlags = SavePoly1.PolyFlags;
NewPoly->TextureU = SavePoly1.TextureU;
NewPoly->TextureV = SavePoly1.TextureV;
NewPoly->Normal = FVector::ZeroVector;
NewPoly->Finalize(go->GetActualBrush(),1);
// Tag the old polygons
for( int32 p = 0 ; p < ge->ParentPolyIndices.Num() ; ++p )
{
FGeomPoly* GeomPoly = &go->PolyPool[ ge->ParentPolyIndices[p] ];
go->GetActualBrush()->Brush->Polys->Element[GeomPoly->ActualPolyIndex].PolyFlags |= PF_GeomMarked;
}
}
}
// Delete the old polygons
for( int32 p = 0 ; p < go->GetActualBrush()->Brush->Polys->Element.Num() ; ++p )
{
if( (go->GetActualBrush()->Brush->Polys->Element[ p ].PolyFlags&PF_GeomMarked) > 0 )
{
go->GetActualBrush()->Brush->Polys->Element.RemoveAt( p );
p = -1;
}
}
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
return true;
}
UGeomModifier_Weld::UGeomModifier_Weld(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Description = NSLOCTEXT("UnrealEd", "Weld", "Weld");
bPushButton = true;
}
bool UGeomModifier_Weld::Supports()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
return (mode->HaveVerticesSelected() && !mode->HaveEdgesSelected() && !mode->HavePolygonsSelected());
}
bool UGeomModifier_Weld::OnApply()
{
FEdModeGeometry* mode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Geometry);
// Verts
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
go->CompileSelectionOrder();
if( go->SelectionOrder.Num() > 1 )
{
//NOTE: function assumes ONLY vertices are selected, UGeomModifier_Weld::Supports must ensure this.
FGeomVertex* FirstSel = (FGeomVertex*)go->SelectionOrder[0];
// Move all selected vertices to the location of the first vertex that was selected.
for( int32 v = 1 ; v < go->SelectionOrder.Num() ; ++v )
{
FGeomVertex* gv = (FGeomVertex*)go->SelectionOrder[v];
if( gv->IsSelected() )
{
gv->X = FirstSel->X;
gv->Y = FirstSel->Y;
gv->Z = FirstSel->Z;
}
}
go->SendToSource();
}
}
mode->FinalizeSourceData();
mode->GetFromSource();
GEditor->RebuildAlteredBSP(); // Brush has been altered, update the Bsp
//finally, cache the selections AFTER the weld and set the widget to the appropriate selection
for( FEdModeGeometry::TGeomObjectIterator Itor( mode->GeomObjectItor() ) ; Itor ; ++Itor )
{
FGeomObject* go = *Itor;
go->CompileSelectionOrder();
ABrush* Actor = go->GetActualBrush();
StoreCurrentGeomSelections( Actor->SavedSelections , go );
go->SelectNone();
int32 res = go->SetPivotFromSelectionArray( Actor->SavedSelections );
if( res == INDEX_NONE )
{
FEditorModeTools& Tools = GLevelEditorModeTools();
Tools.SetPivotLocation( Actor->GetActorLocation() , false );
}
go->ForceLastSelectionIndex( res );
}
return true;
}
#undef LOCTEXT_NAMESPACE