// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= UEditorBrushBuilder.cpp: UnrealEd brush builder. =============================================================================*/ #include "Builders/EditorBrushBuilder.h" #include "Model.h" #include "Styling/AppStyle.h" #include "GameFramework/Actor.h" #include "Builders/ConeBuilder.h" #include "Builders/CubeBuilder.h" #include "Builders/CurvedStairBuilder.h" #include "Builders/CylinderBuilder.h" #include "Builders/LinearStairBuilder.h" #include "Builders/SheetBuilder.h" #include "Builders/SpiralStairBuilder.h" #include "Builders/TetrahedronBuilder.h" #include "Builders/VolumetricBuilder.h" #include "Engine/Brush.h" #include "Engine/Polys.h" #include "Editor.h" #include "BSPOps.h" #include "SnappingUtils.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Engine/Selection.h" #define LOCTEXT_NAMESPACE "BrushBuilder" /*----------------------------------------------------------------------------- UEditorBrushBuilder. -----------------------------------------------------------------------------*/ UEditorBrushBuilder::UEditorBrushBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { BitmapFilename = TEXT("BBGeneric"); ToolTip = TEXT("BrushBuilderName_Generic"); NotifyBadParams = true; } void UEditorBrushBuilder::BeginBrush(bool InMergeCoplanars, FName InLayer) { Layer = InLayer; MergeCoplanars = InMergeCoplanars; Vertices.Empty(); Polys.Empty(); } bool UEditorBrushBuilder::EndBrush( UWorld* InWorld, ABrush* InBrush ) { //!!validate if (!GEditor) { return false; } check( InWorld != nullptr ); ABrush* BuilderBrush = (InBrush != nullptr) ? InBrush : InWorld->GetDefaultBrush(); // Ensure the builder brush is unhidden. BuilderBrush->SetHidden(false); BuilderBrush->bHiddenEdLayer = false; AActor* Actor = GEditor->GetSelectedActors()->GetTop(); FVector Location; if ( InBrush == nullptr ) { Location = Actor ? Actor->GetActorLocation() : BuilderBrush->GetActorLocation(); } else { Location = InBrush->GetActorLocation(); } UModel* Brush = BuilderBrush->Brush; if (Brush == nullptr) { return true; } Brush->Modify(false); BuilderBrush->Modify(false); FRotator Temp(0.0f,0.0f,0.0f); FSnappingUtils::SnapToBSPVertex( Location, FVector::ZeroVector, Temp ); BuilderBrush->SetActorLocation(Location, false); BuilderBrush->SetPivotOffset( FVector::ZeroVector ); // Try and maintain the materials assigned to the surfaces. TArray CachedPolys; UMaterialInterface* CachedMaterial = nullptr; if( Brush->Polys->Element.Num() == Polys.Num() ) { // If the number of polygons match we assume its the same shape. CachedPolys.Append(Brush->Polys->Element); } else if( Brush->Polys->Element.Num() > 0 ) { // If the polygons have changed check if we only had one material before. CachedMaterial = Brush->Polys->Element[0].Material; if (CachedMaterial != NULL) { for( auto Poly : Brush->Polys->Element ) { if( CachedMaterial != Poly.Material ) { CachedMaterial = NULL; break; } } } } // Clear existing polys. Brush->Polys->Element.Empty(); const bool bUseCachedPolysMaterial = CachedPolys.Num() > 0; int32 CachedPolyIdx = 0; for( TArray::TIterator It(Polys); It; ++It ) { if( It->Direction<0 ) { for( int32 i=0; iVertexIndices.Num()/2; i++ ) { Exchange( It->VertexIndices[i], It->VertexIndices.Last(i) ); } } FPoly Poly; Poly.Init(); Poly.ItemName = It->ItemName; Poly.Base = (FVector3f)Vertices[It->VertexIndices[0]]; Poly.PolyFlags = It->PolyFlags; // Try and maintain the polygons material where possible Poly.Material = ( bUseCachedPolysMaterial ) ? ToRawPtr(CachedPolys[CachedPolyIdx++].Material) : ToRawPtr(CachedMaterial); for( int32 j=0; jVertexIndices.Num(); j++ ) { Poly.Vertices.Emplace(Vertices[It->VertexIndices[j]]); } if( Poly.Finalize( BuilderBrush, 1 ) == 0 ) { Brush->Polys->Element.Add(Poly); } } if( MergeCoplanars ) { GEditor->bspMergeCoplanars( Brush, 0, 1 ); FBSPOps::bspValidateBrush( Brush, 1, 1 ); } Brush->Linked = 1; FBSPOps::bspValidateBrush( Brush, 0, 1 ); Brush->BuildBound(); GEditor->RedrawLevelEditingViewports(); GEditor->SetPivot(BuilderBrush->GetActorLocation(), false, true); BuilderBrush->ReregisterAllComponents(); return true; } int32 UEditorBrushBuilder::GetVertexCount() const { return Vertices.Num(); } FVector UEditorBrushBuilder::GetVertex(int32 i) const { return Vertices.IsValidIndex(i) ? Vertices[i] : FVector::ZeroVector; } int32 UEditorBrushBuilder::GetPolyCount() const { return Polys.Num(); } bool UEditorBrushBuilder::BadParameters(const FText& Msg) { if ( NotifyBadParams ) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("Msg"), Msg); FNotificationInfo Info( FText::Format( LOCTEXT( "BadParameters", "Bad parameters in brush builder\n{Msg}" ), Arguments ) ); Info.bFireAndForget = true; Info.ExpireDuration = Msg.IsEmpty() ? 4.0f : 6.0f; Info.bUseLargeFont = Msg.IsEmpty(); Info.Image = FAppStyle::GetBrush(TEXT("MessageLog.Error")); FSlateNotificationManager::Get().AddNotification( Info ); } return 0; } int32 UEditorBrushBuilder::Vertexv(FVector V) { int32 Result = Vertices.Num(); Vertices.Add(V); return Result; } int32 UEditorBrushBuilder::Vertex3f(float X, float Y, float Z) { int32 Result = Vertices.Num(); Vertices.Emplace(X,Y,Z); return Result; } void UEditorBrushBuilder::Poly3i(int32 Direction, int32 i, int32 j, int32 k, FName ItemName, bool bIsTwoSidedNonSolid ) { FBuilderPoly& Poly = Polys.AddDefaulted_GetRef(); Poly.Direction=Direction; Poly.ItemName=ItemName; Poly.VertexIndices.Add(i); Poly.VertexIndices.Add(j); Poly.VertexIndices.Add(k); Poly.PolyFlags = PF_DefaultFlags | (bIsTwoSidedNonSolid ? (PF_TwoSided|PF_NotSolid) : 0); } void UEditorBrushBuilder::Poly4i(int32 Direction, int32 i, int32 j, int32 k, int32 l, FName ItemName, bool bIsTwoSidedNonSolid ) { FBuilderPoly& Poly = Polys.AddDefaulted_GetRef(); Poly.Direction=Direction; Poly.ItemName=ItemName; Poly.VertexIndices.Add(i); Poly.VertexIndices.Add(j); Poly.VertexIndices.Add(k); Poly.VertexIndices.Add(l); Poly.PolyFlags = PF_DefaultFlags | (bIsTwoSidedNonSolid ? (PF_TwoSided|PF_NotSolid) : 0); } void UEditorBrushBuilder::PolyBegin(int32 Direction, FName ItemName) { FBuilderPoly& Poly = Polys.AddDefaulted_GetRef(); Poly.ItemName=ItemName; Poly.Direction = Direction; Poly.PolyFlags = PF_DefaultFlags; } void UEditorBrushBuilder::Polyi(int32 i) { Polys.Last().VertexIndices.Add(i); } void UEditorBrushBuilder::PolyEnd() { } bool UEditorBrushBuilder::Build( UWorld* InWorld, ABrush* InBrush ) { return false; } void UEditorBrushBuilder::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (!GIsTransacting) { // Rebuild brush on property change ABrush* Brush = Cast(GetOuter()); UWorld* BrushWorld = Brush ? Brush->GetWorld() : nullptr; if (BrushWorld) { Brush->bInManipulation = PropertyChangedEvent.ChangeType == EPropertyChangeType::Interactive; Build(BrushWorld, Brush); } } } UConeBuilder::UConeBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Cone; FConstructorStatics() : NAME_Cone(TEXT("Cone")) { } }; static FConstructorStatics ConstructorStatics; Z = 300.0f; CapZ = 290.0f; OuterRadius = 200.0f; InnerRadius = 190.0f; Sides = 8; GroupName = ConstructorStatics.NAME_Cone; AlignToSide = true; Hollow = false; BitmapFilename = TEXT("Btn_Cone"); ToolTip = TEXT("BrushBuilderName_Cone"); } void UConeBuilder::BuildCone( int32 Direction, bool InAlignToSide, int32 InSides, float InZ, float Radius, FName Item ) { int32 n = GetVertexCount(); int32 Ofs = 0; if( InAlignToSide ) { Radius /= FMath::Cos(PI/InSides); Ofs = 1; } // Vertices. for( int32 i=0; iGetFName() == Name_Z && Z <= CapZ) { Z = CapZ + ZEpsilon; } if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_CapZ && CapZ >= Z) { CapZ = FMath::Max(0.0f, Z - ZEpsilon); } static FName Name_OuterRadius(GET_MEMBER_NAME_CHECKED(UConeBuilder, OuterRadius)); static FName Name_InnerRadius(GET_MEMBER_NAME_CHECKED(UConeBuilder, InnerRadius)); const float RadiusEpsilon = 0.1f; if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_OuterRadius && OuterRadius <= InnerRadius) { OuterRadius = InnerRadius + RadiusEpsilon; } if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_InnerRadius && InnerRadius >= OuterRadius) { InnerRadius = FMath::Max(0.0f, OuterRadius - RadiusEpsilon); } } Super::PostEditChangeProperty(PropertyChangedEvent); } bool UConeBuilder::Build( UWorld* InWorld, ABrush* InBrush ) { if( Sides<3 ) return BadParameters(LOCTEXT("ConeNotEnoughSides", "Not enough sides in cone brush")); if( Z<=0 || OuterRadius<=0 ) return BadParameters(LOCTEXT("ConeInvalidRadius", "Invalid cone brush radius")); if( Hollow && (InnerRadius<=0 || InnerRadius>=OuterRadius) ) return BadParameters(LOCTEXT("ConeInvalidRadius", "Invalid cone brush radius")); if( Hollow && CapZ>Z ) return BadParameters(LOCTEXT("ConeInvalidZ", "Invalid cone brush Z value")); if( Hollow && (CapZ==Z && InnerRadius==OuterRadius) ) return BadParameters(LOCTEXT("ConeInvalidRadius", "Invalid cone brush radius")); BeginBrush( false, GroupName ); BuildCone( +1, AlignToSide, Sides, Z, OuterRadius, FName(TEXT("Top")) ); if( Hollow ) { BuildCone( -1, AlignToSide, Sides, CapZ, InnerRadius, FName(TEXT("Cap")) ); if( OuterRadius!=InnerRadius ) for( int32 i=0; iGetFName() == Name_X && X <= WallThickness) { X = WallThickness + ThicknessEpsilon; } if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_Y && Y <= WallThickness) { Y = WallThickness + ThicknessEpsilon; } if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_Z && Z <= WallThickness) { Z = WallThickness + ThicknessEpsilon; } if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_WallThickness && WallThickness >= FMath::Min3(X, Y, Z)) { WallThickness = FMath::Max(0.0f, FMath::Min3(X, Y, Z) - ThicknessEpsilon); } static FName Name_Hollow(GET_MEMBER_NAME_CHECKED(UCubeBuilder, Hollow)); static FName Name_Tesselated(GET_MEMBER_NAME_CHECKED(UCubeBuilder, Tessellated)); if (PropertyChangedEvent.Property->GetFName() == Name_Hollow && Hollow && Tessellated) { Hollow = false; } if (PropertyChangedEvent.Property->GetFName() == Name_Tesselated && Hollow && Tessellated) { Tessellated = false; } } Super::PostEditChangeProperty(PropertyChangedEvent); } bool UCubeBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( Z<=0 || Y<=0 || X<=0 ) return BadParameters(LOCTEXT("CubeInvalidDimensions", "Invalid cube dimensions")); if( Hollow && (Z<=WallThickness || Y<=WallThickness || X<=WallThickness) ) return BadParameters(LOCTEXT("CubeInvalidWallthickness", "Invalid cube wall thickness")); if( Hollow && Tessellated ) return BadParameters(LOCTEXT("TessellatedIncompatibleWithHollow", "The 'Tessellated' option can't be specified with the 'Hollow' option.")); BeginBrush( false, GroupName ); BuildCube( +1, X, Y, Z, Tessellated ); if( Hollow ) BuildCube( -1, X-WallThickness, Y-WallThickness, Z-WallThickness, Tessellated ); return EndBrush( InWorld, InBrush ); } UCurvedStairBuilder::UCurvedStairBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_CStair; FConstructorStatics() : NAME_CStair(TEXT("CStair")) { } }; static FConstructorStatics ConstructorStatics; InnerRadius = 200; StepHeight = 20; StepWidth = 200; AngleOfCurve = 90; NumSteps = 10; GroupName = ConstructorStatics.NAME_CStair; CounterClockwise = false; AddToFirstStep = 0; BitmapFilename = TEXT("Btn_CurvedStairs"); ToolTip = TEXT("BrushBuilderName_CurvedStair"); } void UCurvedStairBuilder::BuildCurvedStair( int32 Direction ) { FRotator RotStep(ForceInit); RotStep.Yaw = static_cast(AngleOfCurve) / NumSteps; if( CounterClockwise ) { RotStep.Yaw *= -1; Direction *= -1; } // Generate the inner curve points. int32 InnerStart = GetVertexCount(); int32 Adjustment; FVector vtx(ForceInit); FVector NewVtx; vtx.X = InnerRadius; for( int32 x = 0 ; x < (NumSteps + 1) ; x++ ) { if( x == 0 ) Adjustment = AddToFirstStep; else Adjustment = 0; NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z - Adjustment ); vtx.Z += StepHeight; Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z ); } // Generate the outer curve points. int32 OuterStart = GetVertexCount(); vtx.X = InnerRadius + StepWidth; vtx.Z = 0; for( int32 x = 0 ; x < (NumSteps + 1) ; x++ ) { if( x == 0 ) Adjustment = AddToFirstStep; else Adjustment = 0; NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z - Adjustment ); vtx.Z += StepHeight; Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z ); } // Generate the bottom inner curve points. int32 BottomInnerStart = GetVertexCount(); vtx.X = InnerRadius; vtx.Z = 0; for( int32 x = 0 ; x < (NumSteps + 1) ; x++ ) { NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z - AddToFirstStep ); } // Generate the bottom outer curve points. int32 BottomOuterStart = GetVertexCount(); vtx.X = InnerRadius + StepWidth; for( int32 x = 0 ; x < (NumSteps + 1) ; x++ ) { NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z - AddToFirstStep ); } for( int32 x = 0 ; x < NumSteps ; x++ ) { Poly4i( Direction, InnerStart + (x * 2) + 2, InnerStart + (x * 2) + 1, OuterStart + (x * 2) + 1, OuterStart + (x * 2) + 2, FName(TEXT("steptop")) ); Poly4i( Direction, InnerStart + (x * 2) + 1, InnerStart + (x * 2), OuterStart + (x * 2), OuterStart + (x * 2) + 1, FName(TEXT("stepfront")) ); Poly4i( Direction, BottomInnerStart + x, InnerStart + (x * 2) + 1, InnerStart + (x * 2) + 2, BottomInnerStart + x + 1, FName(TEXT("innercurve")) ); Poly4i( Direction, OuterStart + (x * 2) + 1, BottomOuterStart + x, BottomOuterStart + x + 1, OuterStart + (x * 2) + 2, FName(TEXT("outercurve")) ); Poly4i( Direction, BottomInnerStart + x, BottomInnerStart + x + 1, BottomOuterStart + x + 1, BottomOuterStart + x, FName(TEXT("Bottom")) ); } // Back panel. Poly4i( Direction, BottomInnerStart + NumSteps, InnerStart + (NumSteps * 2), OuterStart + (NumSteps * 2), BottomOuterStart + NumSteps, FName(TEXT("back")) ); } bool UCurvedStairBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( AngleOfCurve<1 || AngleOfCurve>360 ) return BadParameters(LOCTEXT("StairAngleOutOfRange", "Angle is out of range.")); if( InnerRadius<1 || StepWidth<1 || NumSteps<1 ) return BadParameters(LOCTEXT("StairInvalidStepParams", "Invalid step parameters.")); BeginBrush( false, GroupName ); BuildCurvedStair( +1 ); return EndBrush( InWorld, InBrush ); } UCylinderBuilder::UCylinderBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Cylinder; FConstructorStatics() : NAME_Cylinder(TEXT("Cylinder")) { } }; static FConstructorStatics ConstructorStatics; Z = 200.0f; OuterRadius = 200.0f; InnerRadius = 190.0f; Sides = 8; GroupName = ConstructorStatics.NAME_Cylinder; AlignToSide = true; Hollow = false; BitmapFilename = TEXT("Btn_Cylinder"); ToolTip = TEXT("BrushBuilderName_Cylinder"); } void UCylinderBuilder::BuildCylinder( int32 Direction, bool InAlignToSide, int32 InSides, float InZ, float Radius ) { int32 n = GetVertexCount(); int32 Ofs = 0; if( InAlignToSide ) { Radius /= FMath::Cos(PI/InSides); Ofs = 1; } // Vertices. for( int32 i=0; iGetFName() == Name_OuterRadius && OuterRadius <= InnerRadius) { OuterRadius = InnerRadius + RadiusEpsilon; } if (Hollow && PropertyChangedEvent.Property->GetFName() == Name_InnerRadius && InnerRadius >= OuterRadius) { InnerRadius = FMath::Max(0.0f, OuterRadius - RadiusEpsilon); } } Super::PostEditChangeProperty(PropertyChangedEvent); } bool UCylinderBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( Sides<3 ) return BadParameters(LOCTEXT("CylinderInvalidSides", "Not enough cylinder sides.")); if( Z<=0 || OuterRadius<=0 ) return BadParameters(LOCTEXT("CylinderInvalidRadius", "Invalid cylinder radius")); if( Hollow && (InnerRadius<=0 || InnerRadius>=OuterRadius) ) return BadParameters(LOCTEXT("CylinderInvalidRadius", "Invalid cylinder radius")); BeginBrush( false, GroupName ); BuildCylinder( +1, AlignToSide, Sides, Z, OuterRadius ); if( Hollow ) { BuildCylinder( -1, AlignToSide, Sides, Z, InnerRadius ); for( int32 j=-1; j<2; j+=2 ) for( int32 i=0; i45 ) return BadParameters(LOCTEXT("LinearStairNumStepsOutOfRange", "NumSteps must be greater than 1 and less than 46.f")); // Build the brush. BeginBrush( false, GroupName ); int32 CurrentX = 0; int32 CurrentY = 0; int32 CurrentZ = 0; int32 LastIdx = GetVertexCount(); // Bottom poly. Vertex3f( 0, 0, -StepHeight ); Vertex3f( 0, StepWidth, -StepHeight ); Vertex3f( StepLength * NumSteps, StepWidth, -StepHeight ); Vertex3f( StepLength * NumSteps, 0, -StepHeight ); Poly4i(1, 0, 1, 2, 3, FName(TEXT("Base"))); LastIdx += 4; // Back poly. Vertex3f( StepLength * NumSteps, StepWidth, -StepHeight ); Vertex3f( StepLength * NumSteps, StepWidth, (StepHeight * (NumSteps - 1)) + AddToFirstStep ); Vertex3f( StepLength * NumSteps, 0, (StepHeight * (NumSteps - 1)) + AddToFirstStep ); Vertex3f( StepLength * NumSteps, 0, -StepHeight ); Poly4i(1, 4, 5, 6, 7, FName(TEXT("Back"))); LastIdx += 4; // Tops of steps. for( int32 i = 0 ; i < NumSteps ; i++ ) { CurrentX = (i * StepLength); CurrentZ = (i * StepHeight) + AddToFirstStep; // Top of the step Vertex3f( CurrentX, CurrentY, CurrentZ ); Vertex3f( CurrentX, CurrentY + StepWidth, CurrentZ ); Vertex3f( CurrentX + StepLength, CurrentY + StepWidth, CurrentZ ); Vertex3f( CurrentX + StepLength, CurrentY, CurrentZ ); Poly4i(1, LastIdx+(i*4)+3, LastIdx+(i*4)+2, LastIdx+(i*4)+1, LastIdx+(i*4), FName(TEXT("Step"))); } LastIdx += (NumSteps*4); // Fronts of steps. for( int32 i = 0 ; i < NumSteps ; i++ ) { CurrentX = (i * StepLength); CurrentZ = (i * StepHeight) + AddToFirstStep; int32 Adjustment = 0; if( i == 0 ) Adjustment = AddToFirstStep; // Top of the step Vertex3f( CurrentX, CurrentY, CurrentZ ); Vertex3f( CurrentX, CurrentY, CurrentZ - StepHeight - Adjustment ); Vertex3f( CurrentX, CurrentY + StepWidth, CurrentZ - StepHeight - Adjustment ); Vertex3f( CurrentX, CurrentY + StepWidth, CurrentZ ); Poly4i(1, LastIdx+(i*12)+3, LastIdx+(i*12)+2, LastIdx+(i*12)+1, LastIdx+(i*12), FName(TEXT("Rise"))); // Sides of the step Vertex3f( CurrentX, CurrentY, CurrentZ ); Vertex3f( CurrentX, CurrentY, CurrentZ - StepHeight - Adjustment ); Vertex3f( CurrentX + (StepLength*(NumSteps-i)), CurrentY, CurrentZ - StepHeight - Adjustment ); Vertex3f( CurrentX + (StepLength*(NumSteps-i)), CurrentY, CurrentZ ); Poly4i(1, LastIdx+(i*12)+4, LastIdx+(i*12)+5, LastIdx+(i*12)+6, LastIdx+(i*12)+7, FName(TEXT("Side"))); Vertex3f( CurrentX, CurrentY + StepWidth, CurrentZ ); Vertex3f( CurrentX, CurrentY + StepWidth, CurrentZ - StepHeight - Adjustment ); Vertex3f( CurrentX + (StepLength*(NumSteps-i)), CurrentY + StepWidth, CurrentZ - StepHeight - Adjustment ); Vertex3f( CurrentX + (StepLength*(NumSteps-i)), CurrentY + StepWidth, CurrentZ ); Poly4i(1, LastIdx+(i*12)+11, LastIdx+(i*12)+10, LastIdx+(i*12)+9, LastIdx+(i*12)+8, FName(TEXT("Side"))); } return EndBrush( InWorld, InBrush ); } USheetBuilder::USheetBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Sheet; FConstructorStatics() : NAME_Sheet(TEXT("Sheet")) { } }; static FConstructorStatics ConstructorStatics; X = 256; Y = 256; XSegments = 1; YSegments = 1; Axis = AX_Horizontal; GroupName = ConstructorStatics.NAME_Sheet; BitmapFilename = TEXT("Btn_Sheet"); ToolTip = TEXT("BrushBuilderName_Sheet"); } bool USheetBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( Y<=0 || X<=0 || XSegments<=0 || YSegments<=0 ) return BadParameters(LOCTEXT("SheetInvalidParams", "Invalid sheet parameters.")); BeginBrush( false, GroupName ); int32 XStep = X/XSegments; int32 YStep = Y/YSegments; int32 count = 0; for( int32 i = 0 ; i < XSegments ; i++ ) { for( int32 j = 0 ; j < YSegments ; j++ ) { if( Axis==AX_Horizontal ) { Vertex3f( (i*XStep)-X/2, (j*YStep)-Y/2, 0 ); Vertex3f( (i*XStep)-X/2, ((j+1)*YStep)-Y/2, 0 ); Vertex3f( ((i+1)*XStep)-X/2, ((j+1)*YStep)-Y/2, 0 ); Vertex3f( ((i+1)*XStep)-X/2, (j*YStep)-Y/2, 0 ); } else if( Axis==AX_XAxis ) { Vertex3f( 0, (i*XStep)-X/2, (j*YStep)-Y/2 ); Vertex3f( 0, (i*XStep)-X/2, ((j+1)*YStep)-Y/2 ); Vertex3f( 0, ((i+1)*XStep)-X/2, ((j+1)*YStep)-Y/2 ); Vertex3f( 0, ((i+1)*XStep)-X/2, (j*YStep)-Y/2 ); } else { Vertex3f( (i*XStep)-X/2, 0, (j*YStep)-Y/2 ); Vertex3f( (i*XStep)-X/2, 0, ((j+1)*YStep)-Y/2 ); Vertex3f( ((i+1)*XStep)-X/2, 0, ((j+1)*YStep)-Y/2 ); Vertex3f( ((i+1)*XStep)-X/2, 0, (j*YStep)-Y/2 ); } Poly4i(+1,count,count+1,count+2,count+3,FName(TEXT("Sheet")),true); count = GetVertexCount(); } } return EndBrush( InWorld, InBrush ); } USpiralStairBuilder::USpiralStairBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Spiral; FConstructorStatics() : NAME_Spiral(TEXT("Spiral")) { } }; static FConstructorStatics ConstructorStatics; InnerRadius = 100; StepWidth = 200; StepHeight = 20; StepThickness = 50; NumStepsPer360 = 16; NumSteps = 16; SlopedCeiling = false; SlopedFloor = false; GroupName = ConstructorStatics.NAME_Spiral; CounterClockwise = false; BitmapFilename = TEXT("Btn_SpiralStairs"); ToolTip = TEXT("BrushBuilderName_SpiralStair"); } void USpiralStairBuilder::BuildCurvedStair( int32 Direction ) { FRotator RotStep(ForceInit); RotStep.Yaw = 360.0f / NumStepsPer360; if( CounterClockwise ) { RotStep.Yaw *= -1; Direction *= -1; } // Generate the vertices for the first stair. FVector Template[8]; int32 idx = 0; int32 VertexStart = GetVertexCount(); FVector vtx(ForceInit); vtx.X = InnerRadius; FVector NewVtx; for( int32 x = 0 ; x < 2 ; x++ ) { NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); vtx.Z = 0; if( SlopedCeiling && x == 1 ) vtx.Z = StepHeight; Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z ); Template[idx].X = NewVtx.X; Template[idx].Y = NewVtx.Y; Template[idx].Z = vtx.Z; idx++; vtx.Z = StepThickness; if( SlopedFloor && x == 0 ) vtx.Z -= StepHeight; Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z ); Template[idx].X = NewVtx.X; Template[idx].Y = NewVtx.Y; Template[idx].Z = vtx.Z; idx++; } vtx.X = InnerRadius + StepWidth; for( int32 x = 0 ; x < 2 ; x++ ) { NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); vtx.Z = 0; if( SlopedCeiling && x == 1 ) vtx.Z = StepHeight; Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z ); Template[idx].X = NewVtx.X; Template[idx].Y = NewVtx.Y; Template[idx].Z = vtx.Z; idx++; vtx.Z = StepThickness; if( SlopedFloor && x == 0 ) vtx.Z -= StepHeight; Vertex3f( NewVtx.X, NewVtx.Y, vtx.Z ); Template[idx].X = NewVtx.X; Template[idx].Y = NewVtx.Y; Template[idx].Z = vtx.Z; idx++; } // Create steps from the template for( int32 x = 0 ; x < NumSteps ; x++ ) { if( SlopedFloor ) { Poly3i( Direction, VertexStart + 3, VertexStart + 1, VertexStart + 5, FName(TEXT("steptop")) ); Poly3i( Direction, VertexStart + 3, VertexStart + 5, VertexStart + 7, FName(TEXT("steptop")) ); } else Poly4i( Direction, VertexStart + 3, VertexStart + 1, VertexStart + 5, VertexStart + 7, FName(TEXT("steptop")) ); Poly4i( Direction, VertexStart + 0, VertexStart + 1, VertexStart + 3, VertexStart + 2, FName(TEXT("inner")) ); Poly4i( Direction, VertexStart + 5, VertexStart + 4, VertexStart + 6, VertexStart + 7, FName(TEXT("GetOuter()")) ); Poly4i( Direction, VertexStart + 1, VertexStart + 0, VertexStart + 4, VertexStart + 5, FName(TEXT("stepfront")) ); Poly4i( Direction, VertexStart + 2, VertexStart + 3, VertexStart + 7, VertexStart + 6, FName(TEXT("stepback")) ); if( SlopedCeiling ) { Poly3i( Direction, VertexStart + 0, VertexStart + 2, VertexStart + 6, FName(TEXT("stepbottom")) ); Poly3i( Direction, VertexStart + 0, VertexStart + 6, VertexStart + 4, FName(TEXT("stepbottom")) ); } else Poly4i( Direction, VertexStart + 0, VertexStart + 2, VertexStart + 6, VertexStart + 4, FName(TEXT("stepbottom")) ); VertexStart = GetVertexCount(); for( int32 y = 0 ; y < 8 ; y++ ) { NewVtx = FRotationMatrix(RotStep * (x + 1)).TransformVector(Template[y]); Vertex3f( NewVtx.X, NewVtx.Y, NewVtx.Z + (StepHeight * (x + 1)) ); } } } bool USpiralStairBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( InnerRadius<1 || StepWidth<1 || NumSteps<1 || NumStepsPer360<3 ) return BadParameters(LOCTEXT("SpiralStairInvalidStepParams", "Invalid step parameters.")); BeginBrush( false, GroupName ); BuildCurvedStair( +1 ); return EndBrush( InWorld, InBrush ); } UTetrahedronBuilder::UTetrahedronBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Tetrahedron; FConstructorStatics() : NAME_Tetrahedron(TEXT("Tetrahedron")) { } }; static FConstructorStatics ConstructorStatics; Radius = 256.0f; SphereExtrapolation = 2; GroupName = ConstructorStatics.NAME_Tetrahedron; BitmapFilename = TEXT("Btn_Sphere"); ToolTip = TEXT("BrushBuilderName_Tetrahedron"); } void UTetrahedronBuilder::Extrapolate( int32 a, int32 b, int32 c, int32 Count, float InRadius ) { if( Count>1 ) { int32 ab=Vertexv( InRadius*(GetVertex(a)+GetVertex(b)).GetSafeNormal() ); int32 bc=Vertexv( InRadius*(GetVertex(b)+GetVertex(c)).GetSafeNormal() ); int32 ca=Vertexv( InRadius*(GetVertex(c)+GetVertex(a)).GetSafeNormal() ); Extrapolate(a,ab,ca,Count-1,InRadius); Extrapolate(b,bc,ab,Count-1,InRadius); Extrapolate(c,ca,bc,Count-1,InRadius); Extrapolate(ab,bc,ca,Count-1,InRadius); //wastes shared vertices } else Poly3i(+1,a,b,c); } void UTetrahedronBuilder::BuildTetrahedron( float R, int32 InSphereExtrapolation ) { Vertex3f( R,0,0); Vertex3f(-R,0,0); Vertex3f(0, R,0); Vertex3f(0,-R,0); Vertex3f(0,0, R); Vertex3f(0,0,-R); Extrapolate(2,1,4,InSphereExtrapolation,Radius); Extrapolate(1,3,4,InSphereExtrapolation,Radius); Extrapolate(3,0,4,InSphereExtrapolation,Radius); Extrapolate(0,2,4,InSphereExtrapolation,Radius); Extrapolate(1,2,5,InSphereExtrapolation,Radius); Extrapolate(3,1,5,InSphereExtrapolation,Radius); Extrapolate(0,3,5,InSphereExtrapolation,Radius); Extrapolate(2,0,5,InSphereExtrapolation,Radius); } bool UTetrahedronBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( Radius<=0 || SphereExtrapolation<=0 ) return BadParameters(LOCTEXT("TetrahedronInvalidParams", "Invalid tetrahedron parameters.")); if( SphereExtrapolation > 5 ) return BadParameters(LOCTEXT("TetrahedronSphereExtrapolationTooLarge", "Setting 'SphereExtrapolation' to more than 5 is invalid. The resulting ABrush* will have too many polygons to be useful.")); BeginBrush( false, GroupName ); BuildTetrahedron( Radius, SphereExtrapolation ); return EndBrush( InWorld, InBrush ); } UVolumetricBuilder::UVolumetricBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Volumetric; FConstructorStatics() : NAME_Volumetric(TEXT("Volumetric")) { } }; static FConstructorStatics ConstructorStatics; Z = 128.0f; Radius = 64.0f; NumSheets = 2; GroupName = ConstructorStatics.NAME_Volumetric; BitmapFilename = TEXT("Btn_Volumetric"); ToolTip = TEXT("BrushBuilderName_Volumetric"); } void UVolumetricBuilder::BuildVolumetric( int32 Direction, int32 InNumSheets, float InZ, float InRadius ) { int32 n = GetVertexCount(); FRotator RotStep(ForceInit); RotStep.Yaw = 360.f / (InNumSheets * 2); // Vertices. FVector NewVtx; FVector vtx(ForceInit); vtx.X = Radius; vtx.Z = InZ / 2; for( int32 x = 0 ; x < (InNumSheets * 2) ; x++ ) { NewVtx = FRotationMatrix(RotStep * x).TransformVector(vtx); Vertex3f( NewVtx.X, NewVtx.Y, NewVtx.Z ); Vertex3f( NewVtx.X, NewVtx.Y, NewVtx.Z - InZ ); } // Polys. for( int32 x = 0 ; x < InNumSheets ; x++ ) { int32 y = (x*2) + 1; if( y >= (InNumSheets * 2) ) y -= (InNumSheets * 2); Poly4i( Direction, n+(x*2), n+y, n+y+(InNumSheets*2), n+(x*2)+(InNumSheets*2), FName(TEXT("Sheets")), true); } } bool UVolumetricBuilder::Build(UWorld* InWorld, ABrush* InBrush) { if( NumSheets<2 ) return BadParameters(LOCTEXT("VolumetricInvalidSheets", "Invalid volumetric sheet count.")); if( Z<=0 || Radius<=0 ) return BadParameters(LOCTEXT("VolumetricInvalidRadius", "Invalid volumetric radius parameters.")); BeginBrush( true, GroupName ); BuildVolumetric( +1, NumSheets, Z, Radius ); return EndBrush( InWorld, InBrush ); } #undef LOCTEXT_NAMESPACE