// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= UEditorBrushBuilder.cpp: UnrealEd brush builder. =============================================================================*/ #include "Builders/EditorBrushBuilder.h" #include "EditorStyleSet.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(); BuilderBrush->Modify(); 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; i < It->VertexIndices.Num() / 2; i++) { Exchange(It->VertexIndices[i], It->VertexIndices.Last(i)); } } FPoly Poly; Poly.Init(); Poly.ItemName = It->ItemName; Poly.Base = Vertices[It->VertexIndices[0]]; Poly.PolyFlags = It->PolyFlags; // Try and maintain the polygons material where possible Poly.Material = (bUseCachedPolysMaterial) ? CachedPolys[CachedPolyIdx++].Material : CachedMaterial; for (int32 j = 0; j < It->VertexIndices.Num(); j++) { new (Poly.Vertices) FVector(Vertices[It->VertexIndices[j]]); } if (Poly.Finalize(BuilderBrush, 1) == 0) { new (Brush->Polys->Element) FPoly(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 = FEditorStyle::GetBrush(TEXT("MessageLog.Error")); FSlateNotificationManager::Get().AddNotification(Info); } return 0; } int32 UEditorBrushBuilder::Vertexv(FVector V) { int32 Result = Vertices.Num(); new (Vertices) FVector(V); return Result; } int32 UEditorBrushBuilder::Vertex3f(float X, float Y, float Z) { int32 Result = Vertices.Num(); new (Vertices) FVector(X, Y, Z); return Result; } void UEditorBrushBuilder::Poly3i(int32 Direction, int32 i, int32 j, int32 k, FName ItemName, bool bIsTwoSidedNonSolid) { new (Polys) FBuilderPoly; Polys.Last().Direction = Direction; Polys.Last().ItemName = ItemName; new (Polys.Last().VertexIndices) int32(i); new (Polys.Last().VertexIndices) int32(j); new (Polys.Last().VertexIndices) int32(k); Polys.Last().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) { new (Polys) FBuilderPoly; Polys.Last().Direction = Direction; Polys.Last().ItemName = ItemName; new (Polys.Last().VertexIndices) int32(i); new (Polys.Last().VertexIndices) int32(j); new (Polys.Last().VertexIndices) int32(k); new (Polys.Last().VertexIndices) int32(l); Polys.Last().PolyFlags = PF_DefaultFlags | (bIsTwoSidedNonSolid ? (PF_TwoSided | PF_NotSolid) : 0); } void UEditorBrushBuilder::PolyBegin(int32 Direction, FName ItemName) { new (Polys) FBuilderPoly; Polys.Last().ItemName = ItemName; Polys.Last().Direction = Direction; Polys.Last().PolyFlags = PF_DefaultFlags; } void UEditorBrushBuilder::Polyi(int32 i) { new (Polys.Last().VertexIndices) int32(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; i < InSides; i++) Vertex3f(Radius * FMath::Sin((2 * i + Ofs) * PI / InSides), Radius * FMath::Cos((2 * i + Ofs) * PI / InSides), 0); Vertex3f(0, 0, InZ); // Polys. for (int32 i = 0; i < InSides; i++) Poly3i(Direction, n + i, n + InSides, n + ((i + 1) % InSides), Item); } void UConeBuilder::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property) { static FName Name_Z(GET_MEMBER_NAME_CHECKED(UConeBuilder, Z)); static FName Name_CapZ(GET_MEMBER_NAME_CHECKED(UConeBuilder, CapZ)); const float ZEpsilon = 0.1f; if (Hollow && PropertyChangedEvent.Property->GetFName() == 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; i < Sides; i++) Poly4i(1, i, ((i + 1) % Sides), Sides + 1 + ((i + 1) % Sides), Sides + 1 + i, FName(TEXT("Bottom"))); } else { PolyBegin(1, FName(TEXT("Bottom"))); for (int32 i = 0; i < Sides; i++) Polyi(i); PolyEnd(); } return EndBrush(InWorld, InBrush); } UCubeBuilder::UCubeBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_Cube; FConstructorStatics() : NAME_Cube(TEXT("Cube")) { } }; static FConstructorStatics ConstructorStatics; X = 200.0f; Y = 200.0f; Z = 200.0; WallThickness = 10.0f; GroupName = ConstructorStatics.NAME_Cube; Hollow = false; Tessellated = false; BitmapFilename = TEXT("Btn_Box"); ToolTip = TEXT("BrushBuilderName_Cube"); } void UCubeBuilder::BuildCube(int32 Direction, float dx, float dy, float dz, bool _tessellated) { int32 n = GetVertexCount(); for (int32 i = -1; i < 2; i += 2) for (int32 j = -1; j < 2; j += 2) for (int32 k = -1; k < 2; k += 2) Vertex3f(i * dx / 2, j * dy / 2, k * dz / 2); // If the user wants a Tessellated cube, create the sides out of tris instead of quads. if (_tessellated) { Poly3i(Direction, n + 0, n + 1, n + 3); Poly3i(Direction, n + 3, n + 2, n + 0); Poly3i(Direction, n + 2, n + 3, n + 7); Poly3i(Direction, n + 7, n + 6, n + 2); Poly3i(Direction, n + 6, n + 7, n + 5); Poly3i(Direction, n + 5, n + 4, n + 6); Poly3i(Direction, n + 4, n + 5, n + 1); Poly3i(Direction, n + 1, n + 0, n + 4); Poly3i(Direction, n + 3, n + 1, n + 5); Poly3i(Direction, n + 5, n + 7, n + 3); Poly3i(Direction, n + 0, n + 2, n + 6); Poly3i(Direction, n + 6, n + 4, n + 0); } else { Poly4i(Direction, n + 0, n + 1, n + 3, n + 2); Poly4i(Direction, n + 2, n + 3, n + 7, n + 6); Poly4i(Direction, n + 6, n + 7, n + 5, n + 4); Poly4i(Direction, n + 4, n + 5, n + 1, n + 0); Poly4i(Direction, n + 3, n + 1, n + 5, n + 7); Poly4i(Direction, n + 0, n + 2, n + 6, n + 4); } } void UCubeBuilder::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property) { static FName Name_X(GET_MEMBER_NAME_CHECKED(UCubeBuilder, X)); static FName Name_Y(GET_MEMBER_NAME_CHECKED(UCubeBuilder, Y)); static FName Name_Z(GET_MEMBER_NAME_CHECKED(UCubeBuilder, Z)); static FName Name_WallThickness(GET_MEMBER_NAME_CHECKED(UCubeBuilder, WallThickness)); const float ThicknessEpsilon = 0.1f; if (Hollow && PropertyChangedEvent.Property->GetFName() == 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; i < InSides; i++) for (int32 j = -1; j < 2; j += 2) Vertex3f(Radius * FMath::Sin((2 * i + Ofs) * PI / InSides), Radius * FMath::Cos((2 * i + Ofs) * PI / InSides), j * InZ / 2); // Polys. for (int32 i = 0; i < InSides; i++) Poly4i(Direction, n + i * 2, n + i * 2 + 1, n + ((i * 2 + 3) % (2 * InSides)), n + ((i * 2 + 2) % (2 * InSides)), FName(TEXT("Wall"))); } void UCylinderBuilder::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property) { static FName Name_OuterRadius(GET_MEMBER_NAME_CHECKED(UCylinderBuilder, OuterRadius)); static FName Name_InnerRadius(GET_MEMBER_NAME_CHECKED(UCylinderBuilder, 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 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; i < Sides; i++) Poly4i(j, i * 2 + (1 - j) / 2, ((i + 1) % Sides) * 2 + (1 - j) / 2, ((i + 1) % Sides) * 2 + (1 - j) / 2 + Sides * 2, i * 2 + (1 - j) / 2 + Sides * 2, FName(TEXT("Cap"))); } else { for (int32 j = -1; j < 2; j += 2) { PolyBegin(j, FName(TEXT("Cap"))); for (int32 i = 0; i < Sides; i++) Polyi(i * 2 + (1 - j) / 2); PolyEnd(); } } return EndBrush(InWorld, InBrush); } ULinearStairBuilder::ULinearStairBuilder(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FName NAME_LinearStair; FConstructorStatics() : NAME_LinearStair(TEXT("LinearStair")) { } }; static FConstructorStatics ConstructorStatics; StepLength = 30; StepHeight = 20; StepWidth = 200; NumSteps = 10; AddToFirstStep = 0; GroupName = ConstructorStatics.NAME_LinearStair; BitmapFilename = TEXT("Btn_Staircase"); ToolTip = TEXT("BrushBuilderName_LinearStair"); } bool ULinearStairBuilder::Build(UWorld* InWorld, ABrush* InBrush) { // Check for bad input. if (StepLength <= 0 || StepHeight <= 0 || StepWidth <= 0) return BadParameters(LOCTEXT("LinearStairInvalidStepParams", "Invalid step parameters.")); if (NumSteps <= 1 || NumSteps > 45) 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