You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
3307 lines
93 KiB
C++
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
|