// Copyright Epic Games, Inc. All Rights Reserved. #include "AddPrimitiveTool.h" #include "BaseGizmos/TransformGizmoUtil.h" #include "BaseGizmos/TransformProxy.h" #include "ToolBuilderUtil.h" #include "InteractiveToolManager.h" #include "SceneQueries/SceneSnappingManager.h" #include "BaseBehaviors/MouseHoverBehavior.h" #include "Selection/ToolSelectionUtil.h" #include "Mechanics/DragAlignmentMechanic.h" #include "ModelingObjectsCreationAPI.h" #include "ToolSceneQueriesUtil.h" #include "ToolSetupUtil.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMeshActor.h" #include "Generators/CapsuleGenerator.h" #include "Generators/SweepGenerator.h" #include "Generators/GridBoxMeshGenerator.h" #include "Generators/RectangleMeshGenerator.h" #include "Generators/SphereGenerator.h" #include "Generators/BoxSphereGenerator.h" #include "Generators/DiscMeshGenerator.h" #include "Generators/StairGenerator.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "FaceGroupUtil.h" #include "DynamicMeshEditor.h" #include "UObject/PropertyIterator.h" #include "UObject/UnrealType.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(AddPrimitiveTool) using namespace UE::Geometry; #define LOCTEXT_NAMESPACE "UAddPrimitiveTool" /* * ToolBuilder */ bool UAddPrimitiveToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { return true; } UInteractiveTool* UAddPrimitiveToolBuilder::BuildTool(const FToolBuilderState& SceneState) const { UAddPrimitiveTool* NewTool = nullptr; switch (ShapeType) { case EMakeMeshShapeType::Box: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Cylinder: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Cone: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Arrow: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Rectangle: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Disc: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Torus: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Sphere: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Stairs: NewTool = NewObject(SceneState.ToolManager); break; case EMakeMeshShapeType::Capsule: NewTool = NewObject(SceneState.ToolManager); break; default: break; } NewTool->SetWorld(SceneState.World); return NewTool; } void UAddPrimitiveTool::SetWorld(UWorld* World) { this->TargetWorld = World; } UAddPrimitiveTool::UAddPrimitiveTool(const FObjectInitializer&) { ShapeSettings = CreateDefaultSubobject(TEXT("ShapeSettings")); // CreateDefaultSubobject automatically sets RF_Transactional flag, we need to clear it so that undo/redo doesn't affect tool properties ShapeSettings->ClearFlags(RF_Transactional); } bool UAddPrimitiveTool::CanAccept() const { return CurrentState == EState::AdjustingSettings; } void UAddPrimitiveTool::Setup() { USingleClickTool::Setup(); UMouseHoverBehavior* HoverBehavior = NewObject(this); HoverBehavior->Initialize(this); AddInputBehavior(HoverBehavior); OutputTypeProperties = NewObject(this); OutputTypeProperties->RestoreProperties(this); OutputTypeProperties->InitializeDefault(); OutputTypeProperties->WatchProperty(OutputTypeProperties->OutputType, [this](FString) { OutputTypeProperties->UpdatePropertyVisibility(); }); AddToolPropertySource(OutputTypeProperties); AddToolPropertySource(ShapeSettings); ShapeSettings->WatchProperty(ShapeSettings->TargetSurface, [this](EMakeMeshPlacementType){UpdateTargetSurface();}); ShapeSettings->PolygroupMode = GetDefaultPolygroupMode(); ShapeSettings->RestoreProperties(this); MaterialProperties = NewObject(this); AddToolPropertySource(MaterialProperties); MaterialProperties->RestoreProperties(this); // create preview mesh object PreviewMesh = NewObject(this); PreviewMesh->CreateInWorld(TargetWorld, FTransform::Identity); ToolSetupUtil::ApplyRenderingConfigurationToPreview(PreviewMesh, nullptr); PreviewMesh->SetVisible(false); PreviewMesh->SetMaterial(MaterialProperties->Material.Get()); PreviewMesh->EnableWireframe(MaterialProperties->bShowWireframe); UTransformProxy* TransformProxy = NewObject(this); TransformProxy->OnTransformChanged.AddWeakLambda(this, [this](UTransformProxy*, FTransform NewTransform) { PreviewMesh->SetTransform(NewTransform); }); // TODO: It might be nice to use a repositionable gizmo, but the drag alignment mechanic can't currently // hit the preview mesh, which makes middle click repositioning feel broken and not very useful. Gizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GetToolManager(), ETransformGizmoSubElements::StandardTranslateRotate, this); Gizmo->SetActiveTarget(TransformProxy, GetToolManager()); DragAlignmentMechanic = NewObject(this); DragAlignmentMechanic->Setup(this); DragAlignmentMechanic->AddToGizmo(Gizmo); UpdatePreviewMesh(); SetState(EState::PlacingPrimitive); } void UAddPrimitiveTool::SetState(EState NewState) { CurrentState = NewState; bool bGizmoActive = (CurrentState == EState::AdjustingSettings); Gizmo->SetVisibility(bGizmoActive && ShapeSettings->bShowGizmo); ShapeSettings->bShowGizmoOptions = bGizmoActive; NotifyOfPropertyChangeByTool(ShapeSettings); if (CurrentState == EState::PlacingPrimitive) { GetToolManager()->DisplayMessage( LOCTEXT("OnStartAddPrimitiveTool", "This Tool creates new shapes. Click in the scene to choose initial placement of mesh."), EToolMessageLevel::UserNotification); } else { // Initialize gizmo to current preview location Gizmo->ReinitializeGizmoTransform(PreviewMesh->GetTransform()); GetToolManager()->DisplayMessage( LOCTEXT("OnStartAddPrimitiveTool2", "Alter shape settings in the detail panel or modify placement with gizmo, then accept the tool."), EToolMessageLevel::UserNotification); } } void UAddPrimitiveTool::Shutdown(EToolShutdownType ShutdownType) { if (ShutdownType == EToolShutdownType::Accept) { GenerateAsset(); } DragAlignmentMechanic->Shutdown(); DragAlignmentMechanic = nullptr; GetToolManager()->GetPairedGizmoManager()->DestroyAllGizmosByOwner(this); Gizmo = nullptr; PreviewMesh->SetVisible(false); PreviewMesh->Disconnect(); PreviewMesh = nullptr; OutputTypeProperties->SaveProperties(this); ShapeSettings->SaveProperties(this); MaterialProperties->SaveProperties(this); } void UAddPrimitiveTool::Render(IToolsContextRenderAPI* RenderAPI) { DragAlignmentMechanic->Render(RenderAPI); } void UAddPrimitiveTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) { // Because of how the ShapeSettings property set is implemented in this Tool, changes to it are transacted, // and if the user exits the Tool and then tries to undo/redo those transactions, this function will end up being called. // So we need to ensure that we handle this case. if (PreviewMesh) { PreviewMesh->EnableWireframe(MaterialProperties->bShowWireframe); PreviewMesh->SetMaterial(MaterialProperties->Material.Get()); UpdatePreviewMesh(); } if (Gizmo) { Gizmo->SetVisibility(CurrentState == EState::AdjustingSettings && ShapeSettings->bShowGizmo); } } FInputRayHit UAddPrimitiveTool::BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) { FInputRayHit Result(0); Result.bHit = (CurrentState == EState::PlacingPrimitive); return Result; } void UAddPrimitiveTool::OnBeginHover(const FInputDeviceRay& DevicePos) { UpdatePreviewPosition(DevicePos); } bool UAddPrimitiveTool::OnUpdateHover(const FInputDeviceRay& DevicePos) { UpdatePreviewPosition(DevicePos); return true; } void UAddPrimitiveTool::OnEndHover() { // do nothing } void UAddPrimitiveTool::UpdatePreviewPosition(const FInputDeviceRay& DeviceClickPos) { FRay ClickPosWorldRay = DeviceClickPos.WorldRay; // hit position (temp) bool bHit = false; auto RaycastPlaneWithFallback = [](const FVector3d& Origin, const FVector3d& Direction, const FPlane& Plane, double FallbackDistance = 1000) -> FVector3d { double IntersectTime = FMath::RayPlaneIntersectionParam(Origin, Direction, Plane); if (IntersectTime < 0 || !FMath::IsFinite(IntersectTime)) { IntersectTime = FallbackDistance; } return Origin + Direction * IntersectTime; }; FPlane DrawPlane(FVector::ZeroVector, FVector(0, 0, 1)); if (ShapeSettings->TargetSurface == EMakeMeshPlacementType::GroundPlane) { FVector3d DrawPlanePos = RaycastPlaneWithFallback(ClickPosWorldRay.Origin, ClickPosWorldRay.Direction, DrawPlane); bHit = true; ShapeFrame = FFrame3d(DrawPlanePos); } else if (ShapeSettings->TargetSurface == EMakeMeshPlacementType::OnScene) { // cast ray into scene FHitResult Result; bHit = ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, Result, ClickPosWorldRay); if (bHit) { FVector3d Normal = (FVector3d)Result.ImpactNormal; if (!ShapeSettings->bAlignToNormal) { Normal = FVector3d::UnitZ(); } ShapeFrame = FFrame3d((FVector3d)Result.ImpactPoint, Normal); ShapeFrame.ConstrainedAlignPerpAxes(); } else { // fall back to ground plane if we don't have a scene hit FVector3d DrawPlanePos = RaycastPlaneWithFallback(ClickPosWorldRay.Origin, ClickPosWorldRay.Direction, DrawPlane); bHit = true; ShapeFrame = FFrame3d(DrawPlanePos); } } else { bHit = true; ShapeFrame = FFrame3d(); } // Snap to grid USceneSnappingManager* SnapManager = USceneSnappingManager::Find(GetToolManager()); if (SnapManager) { FSceneSnapQueryRequest Request; Request.RequestType = ESceneSnapQueryType::Position; Request.TargetTypes = ESceneSnapQueryTargetType::Grid; Request.Position = (FVector)ShapeFrame.Origin; TArray Results; if (SnapManager->ExecuteSceneSnapQuery(Request, Results)) { ShapeFrame.Origin = (FVector3d)Results[0].Position; } } if (ShapeSettings->Rotation != 0) { ShapeFrame.Rotate(FQuaterniond(ShapeFrame.Z(), ShapeSettings->Rotation, true)); } if (bHit) { PreviewMesh->SetVisible(true); PreviewMesh->SetTransform(ShapeFrame.ToFTransform()); } else { PreviewMesh->SetVisible(false); } } void UAddPrimitiveTool::UpdatePreviewMesh() const { FDynamicMesh3 NewMesh; GenerateMesh( &NewMesh ); if (ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerShape) { FaceGroupUtil::SetGroupID(NewMesh, 0); } if (MaterialProperties->UVScale != 1.0 || MaterialProperties->bWorldSpaceUVScale) { FDynamicMeshEditor Editor(&NewMesh); const float WorldUnitsInMetersFactor = MaterialProperties->bWorldSpaceUVScale ? .01f : 1.0f; Editor.RescaleAttributeUVs(MaterialProperties->UVScale * WorldUnitsInMetersFactor, MaterialProperties->bWorldSpaceUVScale); } // set mesh position const FAxisAlignedBox3d Bounds = NewMesh.GetBounds(true); FVector3d TargetOrigin = Bounds.Center(); if (!ShouldCenterXY()) { TargetOrigin.X = TargetOrigin.Y = 0; } if (ShapeSettings->PivotLocation == EMakeMeshPivotLocation::Base) { TargetOrigin.Z = Bounds.Min.Z; } else if (ShapeSettings->PivotLocation == EMakeMeshPivotLocation::Top) { TargetOrigin.Z = Bounds.Max.Z; } for (const int Vid : NewMesh.VertexIndicesItr()) { FVector3d Pos = NewMesh.GetVertex(Vid); Pos -= TargetOrigin; NewMesh.SetVertex(Vid, Pos); } PreviewMesh->UpdatePreview(&NewMesh); PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated); const bool CalculateTangentsSuccessful = PreviewMesh->CalculateTangents(); checkSlow(CalculateTangentsSuccessful); } void UAddPrimitiveTool::UpdateTargetSurface() { if (ShapeSettings->TargetSurface == EMakeMeshPlacementType::AtOrigin) { // default ray is used as coordinates will not be needed to set position at origin const FInputDeviceRay DefaultRay = FInputDeviceRay(); UpdatePreviewPosition(DefaultRay); SetState(EState::AdjustingSettings); GetToolManager()->EmitObjectChange(this, MakeUnique(PreviewMesh->GetTransform()), LOCTEXT("PlaceMeshTransaction", "Place Mesh")); } else { SetState(EState::PlacingPrimitive); } } bool UAddPrimitiveTool::SupportsWorldSpaceFocusBox() { return PreviewMesh != nullptr; } FBox UAddPrimitiveTool::GetWorldSpaceFocusBox() { if (PreviewMesh) { if (UPrimitiveComponent* Component = PreviewMesh->GetRootComponent()) { return Component->Bounds.GetBox(); } } return FBox(); } bool UAddPrimitiveTool::SupportsWorldSpaceFocusPoint() { return PreviewMesh != nullptr; } bool UAddPrimitiveTool::GetWorldSpaceFocusPoint(const FRay& WorldRay, FVector& PointOut) { if (PreviewMesh) { FHitResult HitResult; if (PreviewMesh->FindRayIntersection(WorldRay, HitResult)) { PointOut = HitResult.ImpactPoint; return true; } } return false; } FInputRayHit UAddPrimitiveTool::IsHitByClick(const FInputDeviceRay& ClickPos) { FInputRayHit Result(0); Result.bHit = (CurrentState == EState::PlacingPrimitive); return Result; } void UAddPrimitiveTool::OnClicked(const FInputDeviceRay& ClickPos) { if (!ensure(CurrentState == EState::PlacingPrimitive)) { return; } UpdatePreviewPosition(ClickPos); SetState(EState::AdjustingSettings); GetToolManager()->EmitObjectChange(this, MakeUnique(PreviewMesh->GetTransform()), LOCTEXT("PlaceMeshTransaction", "Place Mesh")); } void UAddPrimitiveTool::GenerateAsset() { UMaterialInterface* Material = PreviewMesh->GetMaterial(); const FDynamicMesh3* CurMesh = PreviewMesh->GetPreviewDynamicMesh(); GetToolManager()->BeginUndoTransaction(LOCTEXT("AddPrimitiveToolTransactionName", "Add Primitive Tool")); FCreateMeshObjectParams NewMeshObjectParams; NewMeshObjectParams.TargetWorld = TargetWorld; NewMeshObjectParams.Transform = PreviewMesh->GetTransform(); NewMeshObjectParams.BaseName = AssetName; NewMeshObjectParams.Materials.Add(Material); NewMeshObjectParams.SetMesh(CurMesh); OutputTypeProperties->ConfigureCreateMeshObjectParams(NewMeshObjectParams); FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams)); if (Result.IsOK() ) { if (Result.NewActor != nullptr) { // select newly-created object ToolSelectionUtil::SetNewActorSelection(GetToolManager(), Result.NewActor); } } GetToolManager()->EndUndoTransaction(); } void UAddPrimitiveTool::FStateChange::Apply(UObject* Object) { UAddPrimitiveTool* Tool = Cast(Object); // Set preview transform before changing state so that the adjustment gizmo is initialized properly Tool->PreviewMesh->SetTransform(MeshTransform); Tool->SetState(EState::AdjustingSettings); } void UAddPrimitiveTool::FStateChange::Revert(UObject* Object) { UAddPrimitiveTool* Tool = Cast(Object); Tool->SetState(EState::PlacingPrimitive); } UAddBoxPrimitiveTool::UAddBoxPrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Box"); UInteractiveTool::SetToolDisplayName(LOCTEXT("BoxToolName", "Create Box")); } void UAddBoxPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { FGridBoxMeshGenerator BoxGen; const UProceduralBoxToolProperties* BoxSettings = Cast(ShapeSettings); BoxGen.Box = UE::Geometry::FOrientedBox3d(FVector3d::Zero(), 0.5*FVector3d(BoxSettings->Depth, BoxSettings->Width, BoxSettings->Height)); BoxGen.EdgeVertices = FIndex3i(BoxSettings->DepthSubdivisions + 1, BoxSettings->WidthSubdivisions + 1, BoxSettings->HeightSubdivisions + 1); BoxGen.bPolygroupPerQuad = ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad; BoxGen.Generate(); OutMesh->Copy(&BoxGen); } UAddRectanglePrimitiveTool::UAddRectanglePrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Rectangle"); UInteractiveTool::SetToolDisplayName(LOCTEXT("RectToolName", "Create Rectangle")); } void UAddRectanglePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { auto* RectangleSettings = Cast(ShapeSettings); switch (RectangleSettings->RectangleType) { case EProceduralRectType::Rectangle: { FRectangleMeshGenerator RectGen; RectGen.Width = RectangleSettings->Depth; RectGen.Height = RectangleSettings->Width; RectGen.WidthVertexCount = RectangleSettings->DepthSubdivisions + 1; RectGen.HeightVertexCount = RectangleSettings->WidthSubdivisions + 1; RectGen.bSinglePolyGroup = (ShapeSettings->PolygroupMode != EMakeMeshPolygroupMode::PerQuad); RectGen.Generate(); OutMesh->Copy(&RectGen); break; } case EProceduralRectType::RoundedRectangle: { FRoundedRectangleMeshGenerator RectGen; RectGen.Width = RectangleSettings->Depth; RectGen.Height = RectangleSettings->Width; RectGen.Radius = RectangleSettings->CornerRadius; if (RectangleSettings->bMaintainDimension) { constexpr double MinSide = UE_DOUBLE_KINDA_SMALL_NUMBER; constexpr double MinRadius = MinSide * .5; double SmallSide = FMath::Min(RectGen.Width, RectGen.Height); RectGen.Radius = FMath::Clamp(RectGen.Radius, MinRadius, (SmallSide - MinSide) * .5); RectGen.Width = FMath::Max(MinSide, RectGen.Width - RectGen.Radius * 2); RectGen.Height = FMath::Max(MinSide, RectGen.Height - RectGen.Radius * 2); // Update the Radius in the UI, if it was clamped if (RectGen.Radius != RectangleSettings->CornerRadius) { RectangleSettings->CornerRadius = RectGen.Radius; NotifyOfPropertyChangeByTool(RectangleSettings); } } RectGen.WidthVertexCount = RectangleSettings->DepthSubdivisions + 1; RectGen.HeightVertexCount = RectangleSettings->WidthSubdivisions + 1; RectGen.bSinglePolyGroup = (ShapeSettings->PolygroupMode != EMakeMeshPolygroupMode::PerQuad); RectGen.AngleSamples = RectangleSettings->CornerSlices - 1; RectGen.Generate(); OutMesh->Copy(&RectGen); break; } } } UAddDiscPrimitiveTool::UAddDiscPrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Disc"); UInteractiveTool::SetToolDisplayName(LOCTEXT("DiscToolName", "Create Disc")); } void UAddDiscPrimitiveTool::Setup() { Super::Setup(); // add watchers to ensure the hole radius is never larger than the disc radius when updating a punctured disc UProceduralDiscToolProperties* DiscSettings = Cast(ShapeSettings); int32 HoleRadiusWatchID = DiscSettings->WatchProperty(DiscSettings->HoleRadius, [DiscSettings](float HR) { if (DiscSettings->DiscType == EProceduralDiscType::PuncturedDisc) { DiscSettings->HoleRadius = FMath::Min(DiscSettings->Radius, HR); } }); DiscSettings->WatchProperty(DiscSettings->Radius, [DiscSettings, HoleRadiusWatchID](float R) { if (DiscSettings->DiscType == EProceduralDiscType::PuncturedDisc) { DiscSettings->HoleRadius = FMath::Min(DiscSettings->HoleRadius, R); DiscSettings->SilentUpdateWatcherAtIndex(HoleRadiusWatchID); } }); } void UAddDiscPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { const UProceduralDiscToolProperties* DiscSettings = Cast(ShapeSettings); switch (DiscSettings->DiscType) { case EProceduralDiscType::Disc: { FDiscMeshGenerator Gen; Gen.Radius = DiscSettings->Radius; Gen.AngleSamples = DiscSettings->RadialSlices; Gen.RadialSamples = DiscSettings->RadialSubdivisions; Gen.bSinglePolygroup = (ShapeSettings->PolygroupMode != EMakeMeshPolygroupMode::PerQuad); Gen.Generate(); OutMesh->Copy(&Gen); break; } case EProceduralDiscType::PuncturedDisc: { FPuncturedDiscMeshGenerator Gen; Gen.Radius = DiscSettings->Radius; Gen.HoleRadius = FMath::Min(DiscSettings->HoleRadius, Gen.Radius * .999f); // hole cannot be bigger than outer radius Gen.AngleSamples = DiscSettings->RadialSlices; Gen.RadialSamples = DiscSettings->RadialSubdivisions; Gen.bSinglePolygroup = (ShapeSettings->PolygroupMode != EMakeMeshPolygroupMode::PerQuad); Gen.Generate(); OutMesh->Copy(&Gen); break; } } } UAddTorusPrimitiveTool::UAddTorusPrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Torus"); UInteractiveTool::SetToolDisplayName(LOCTEXT("TorusToolName", "Create Torus")); } void UAddTorusPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { FGeneralizedCylinderGenerator Gen; const UProceduralTorusToolProperties* TorusSettings = Cast(ShapeSettings); Gen.CrossSection = FPolygon2d::MakeCircle(TorusSettings->MinorRadius, TorusSettings->MinorSlices); FPolygon2d PathCircle = FPolygon2d::MakeCircle(TorusSettings->MajorRadius, TorusSettings->MajorSlices); for (int Idx = 0; Idx < PathCircle.VertexCount(); Idx++) { Gen.Path.Add( FVector3d(PathCircle[Idx].X, PathCircle[Idx].Y, 0) ); } Gen.bLoop = true; Gen.bCapped = false; Gen.bPolygroupPerQuad = ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad; Gen.InitialFrame = FFrame3d(Gen.Path[0]); Gen.Generate(); OutMesh->Copy(&Gen); } UAddCylinderPrimitiveTool::UAddCylinderPrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Cylinder"); UInteractiveTool::SetToolDisplayName(LOCTEXT("CylinderToolName", "Create Cylinder")); } void UAddCylinderPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { FCylinderGenerator CylGen; const UProceduralCylinderToolProperties* CylinderSettings = Cast(ShapeSettings); CylGen.Radius[1] = CylGen.Radius[0] = CylinderSettings->Radius; CylGen.Height = CylinderSettings->Height; CylGen.AngleSamples = CylinderSettings->RadialSlices; CylGen.LengthSamples = CylinderSettings->HeightSubdivisions - 1; CylGen.bCapped = true; CylGen.bPolygroupPerQuad = ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad; CylGen.Generate(); OutMesh->Copy(&CylGen); } UAddCapsulePrimitiveTool::UAddCapsulePrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Capsule"); UInteractiveTool::SetToolDisplayName(LOCTEXT("CapsuleToolName", "Create Capsule")); } void UAddCapsulePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { FCapsuleGenerator CapGen; const UProceduralCapsuleToolProperties* CapsuleSettings = Cast(ShapeSettings); CapGen.Radius = CapsuleSettings->Radius; CapGen.SegmentLength = CapsuleSettings->CylinderLength; CapGen.NumHemisphereArcSteps = CapsuleSettings->HemisphereSlices; CapGen.NumCircleSteps = CapsuleSettings->CylinderSlices; CapGen.NumSegmentSteps = CapsuleSettings->CylinderSubdivisions; CapGen.bPolygroupPerQuad = ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad; CapGen.Generate(); OutMesh->Copy(&CapGen); } UAddConePrimitiveTool::UAddConePrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Cone"); UInteractiveTool::SetToolDisplayName(LOCTEXT("ConeToolName", "Create Cone")); } void UAddConePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { // Unreal's standard cone is just a cylinder with a very small top FCylinderGenerator CylGen; const UProceduralConeToolProperties* ConeSettings = Cast(ShapeSettings); CylGen.Radius[0] = ConeSettings->Radius; CylGen.Radius[1] = .01; CylGen.Height = ConeSettings->Height; CylGen.AngleSamples = ConeSettings->RadialSlices; CylGen.LengthSamples = ConeSettings->HeightSubdivisions - 1; CylGen.bCapped = true; CylGen.bPolygroupPerQuad = ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad; CylGen.Generate(); OutMesh->Copy(&CylGen); } UAddArrowPrimitiveTool::UAddArrowPrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Arrow"); UInteractiveTool::SetToolDisplayName(LOCTEXT("ArrowToolName", "Create Arrow")); } void UAddArrowPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { FArrowGenerator ArrowGen; const UProceduralArrowToolProperties* ArrowSettings = Cast(ShapeSettings); ArrowGen.StickRadius = ArrowSettings->ShaftRadius; ArrowGen.StickLength = ArrowSettings->ShaftHeight; ArrowGen.HeadBaseRadius = ArrowSettings->HeadRadius; ArrowGen.HeadTipRadius = .01f; ArrowGen.HeadLength = ArrowSettings->HeadHeight; ArrowGen.AngleSamples = ArrowSettings->RadialSlices; ArrowGen.bCapped = true; ArrowGen.bPolygroupPerQuad = ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad; if (ArrowSettings->HeightSubdivisions > 1) { const int AdditionalLengthsSamples = ArrowSettings->HeightSubdivisions - 1; ArrowGen.AdditionalLengthSamples[0] = AdditionalLengthsSamples; ArrowGen.AdditionalLengthSamples[1] = AdditionalLengthsSamples; ArrowGen.AdditionalLengthSamples[2] = AdditionalLengthsSamples; } ArrowGen.Generate(); OutMesh->Copy(&ArrowGen); } UAddSpherePrimitiveTool::UAddSpherePrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Sphere"); UInteractiveTool::SetToolDisplayName(LOCTEXT("SphereToolName", "Create Sphere")); } void UAddSpherePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { const UProceduralSphereToolProperties* SphereSettings = Cast(ShapeSettings); switch (SphereSettings->SubdivisionType) { case EProceduralSphereType::LatLong: { FSphereGenerator SphereGen; SphereGen.Radius = SphereSettings->Radius; SphereGen.NumTheta = SphereSettings->VerticalSlices; SphereGen.NumPhi = SphereSettings->HorizontalSlices + 1; // In FSphereGenerator, PerFace is effectively ignored, and does the same thing as // PerShape, which is unlikely to be useful. So we might as well have PerFace do // the same thing as PerQuad. SphereGen.bPolygroupPerQuad = (ShapeSettings->PolygroupMode != EMakeMeshPolygroupMode::PerShape); SphereGen.Generate(); OutMesh->Copy(&SphereGen); break; } case EProceduralSphereType::Box: { FBoxSphereGenerator SphereGen; SphereGen.Radius = SphereSettings->Radius; SphereGen.Box = FOrientedBox3d(FVector3d::Zero(), 0.5 * FVector3d(SphereSettings->Subdivisions + 1, SphereSettings->Subdivisions + 1, SphereSettings->Subdivisions + 1)); int EdgeNum = SphereSettings->Subdivisions + 1; SphereGen.EdgeVertices = FIndex3i(EdgeNum, EdgeNum, EdgeNum); SphereGen.bPolygroupPerQuad = (ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad); SphereGen.Generate(); OutMesh->Copy(&SphereGen); break; } } } UAddStairsPrimitiveTool::UAddStairsPrimitiveTool(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("ShapeSettings"))) { AssetName = TEXT("Stairs"); UInteractiveTool::SetToolDisplayName(LOCTEXT("StairsToolName", "Create Stairs")); } void UAddStairsPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const { const UProceduralStairsToolProperties* StairSettings = Cast(ShapeSettings); switch (StairSettings->StairsType) { case EProceduralStairsType::Linear: { FLinearStairGenerator StairGen; StairGen.StepWidth = StairSettings->StepWidth; StairGen.StepHeight = StairSettings->StepHeight; StairGen.StepDepth = StairSettings->StepDepth; StairGen.NumSteps = StairSettings->NumSteps; StairGen.bPolygroupPerQuad = (ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad); StairGen.Generate(); OutMesh->Copy(&StairGen); break; } case EProceduralStairsType::Floating: { FFloatingStairGenerator StairGen; StairGen.StepWidth = StairSettings->StepWidth; StairGen.StepHeight = StairSettings->StepHeight; StairGen.StepDepth = StairSettings->StepDepth; StairGen.NumSteps = StairSettings->NumSteps; StairGen.bPolygroupPerQuad = (ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad); StairGen.Generate(); OutMesh->Copy(&StairGen); break; } case EProceduralStairsType::Curved: { FCurvedStairGenerator StairGen; StairGen.StepWidth = StairSettings->StepWidth; StairGen.StepHeight = StairSettings->StepHeight; StairGen.NumSteps = StairSettings->NumSteps; StairGen.InnerRadius = StairSettings->InnerRadius; StairGen.CurveAngle = StairSettings->CurveAngle; StairGen.bPolygroupPerQuad = (ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad); StairGen.Generate(); OutMesh->Copy(&StairGen); break; } case EProceduralStairsType::Spiral: { FSpiralStairGenerator StairGen; StairGen.StepWidth = StairSettings->StepWidth; StairGen.StepHeight = StairSettings->StepHeight; StairGen.NumSteps = StairSettings->NumSteps; StairGen.InnerRadius = StairSettings->InnerRadius; StairGen.CurveAngle = StairSettings->SpiralAngle; StairGen.bPolygroupPerQuad = (ShapeSettings->PolygroupMode == EMakeMeshPolygroupMode::PerQuad); StairGen.Generate(); OutMesh->Copy(&StairGen); break; } } } #undef LOCTEXT_NAMESPACE