Files
UnrealEngine/Engine/Plugins/Runtime/MeshModelingToolset/Source/MeshModelingTools/Private/AddPrimitiveTool.cpp
2025-05-18 13:04:45 +08:00

902 lines
30 KiB
C++

// 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<UAddBoxPrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Cylinder:
NewTool = NewObject<UAddCylinderPrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Cone:
NewTool = NewObject<UAddConePrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Arrow:
NewTool = NewObject<UAddArrowPrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Rectangle:
NewTool = NewObject<UAddRectanglePrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Disc:
NewTool = NewObject<UAddDiscPrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Torus:
NewTool = NewObject<UAddTorusPrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Sphere:
NewTool = NewObject<UAddSpherePrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Stairs:
NewTool = NewObject<UAddStairsPrimitiveTool>(SceneState.ToolManager);
break;
case EMakeMeshShapeType::Capsule:
NewTool = NewObject<UAddCapsulePrimitiveTool>(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<UProceduralShapeToolProperties>(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<UMouseHoverBehavior>(this);
HoverBehavior->Initialize(this);
AddInputBehavior(HoverBehavior);
OutputTypeProperties = NewObject<UCreateMeshObjectTypeProperties>(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<UNewMeshMaterialProperties>(this);
AddToolPropertySource(MaterialProperties);
MaterialProperties->RestoreProperties(this);
// create preview mesh object
PreviewMesh = NewObject<UPreviewMesh>(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<UTransformProxy>(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<UDragAlignmentMechanic>(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<FSceneSnapQueryResult> 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<FStateChange>(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<FStateChange>(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<UAddPrimitiveTool>(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<UAddPrimitiveTool>(Object);
Tool->SetState(EState::PlacingPrimitive);
}
UAddBoxPrimitiveTool::UAddBoxPrimitiveTool(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UProceduralBoxToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Box");
UInteractiveTool::SetToolDisplayName(LOCTEXT("BoxToolName", "Create Box"));
}
void UAddBoxPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
FGridBoxMeshGenerator BoxGen;
const UProceduralBoxToolProperties* BoxSettings = Cast<UProceduralBoxToolProperties>(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<UProceduralRectangleToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Rectangle");
UInteractiveTool::SetToolDisplayName(LOCTEXT("RectToolName", "Create Rectangle"));
}
void UAddRectanglePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
auto* RectangleSettings = Cast<UProceduralRectangleToolProperties>(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<UProceduralDiscToolProperties>(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<UProceduralDiscToolProperties>(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<UProceduralDiscToolProperties>(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<UProceduralTorusToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Torus");
UInteractiveTool::SetToolDisplayName(LOCTEXT("TorusToolName", "Create Torus"));
}
void UAddTorusPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
FGeneralizedCylinderGenerator Gen;
const UProceduralTorusToolProperties* TorusSettings = Cast<UProceduralTorusToolProperties>(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<UProceduralCylinderToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Cylinder");
UInteractiveTool::SetToolDisplayName(LOCTEXT("CylinderToolName", "Create Cylinder"));
}
void UAddCylinderPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
FCylinderGenerator CylGen;
const UProceduralCylinderToolProperties* CylinderSettings = Cast<UProceduralCylinderToolProperties>(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<UProceduralCapsuleToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Capsule");
UInteractiveTool::SetToolDisplayName(LOCTEXT("CapsuleToolName", "Create Capsule"));
}
void UAddCapsulePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
FCapsuleGenerator CapGen;
const UProceduralCapsuleToolProperties* CapsuleSettings = Cast<UProceduralCapsuleToolProperties>(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<UProceduralConeToolProperties>(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<UProceduralConeToolProperties>(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<UProceduralArrowToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Arrow");
UInteractiveTool::SetToolDisplayName(LOCTEXT("ArrowToolName", "Create Arrow"));
}
void UAddArrowPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
FArrowGenerator ArrowGen;
const UProceduralArrowToolProperties* ArrowSettings = Cast<UProceduralArrowToolProperties>(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<UProceduralSphereToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Sphere");
UInteractiveTool::SetToolDisplayName(LOCTEXT("SphereToolName", "Create Sphere"));
}
void UAddSpherePrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
const UProceduralSphereToolProperties* SphereSettings = Cast<UProceduralSphereToolProperties>(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<UProceduralStairsToolProperties>(TEXT("ShapeSettings")))
{
AssetName = TEXT("Stairs");
UInteractiveTool::SetToolDisplayName(LOCTEXT("StairsToolName", "Create Stairs"));
}
void UAddStairsPrimitiveTool::GenerateMesh(FDynamicMesh3* OutMesh) const
{
const UProceduralStairsToolProperties* StairSettings = Cast<UProceduralStairsToolProperties>(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