Files
UnrealEngine/Engine/Plugins/Experimental/MeshModelingToolsetExp/Source/MeshModelingToolsExp/Private/PatternTool.cpp
2025-05-18 13:04:45 +08:00

2005 lines
66 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PatternTool.h"
#include "InteractiveToolManager.h"
#include "InteractiveGizmoManager.h"
#include "Mechanics/DragAlignmentMechanic.h"
#include "Mechanics/ConstructionPlaneMechanic.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMeshEditor.h"
#include "BaseBehaviors/ClickDragBehavior.h"
#include "ToolSceneQueriesUtil.h"
#include "ModelingToolTargetUtil.h"
#include "TransformSequence.h"
#include "Selection/ToolSelectionUtil.h"
#include "ModelingObjectsCreationAPI.h"
#include "ModelingComponentsSettings.h"
#include "Drawing/PreviewGeometryActor.h"
#include "Components/PrimitiveComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/DynamicMeshComponent.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "ContextObjectStore.h"
#include "BaseGizmos/AxisPositionGizmo.h"
#include "BaseGizmos/CombinedTransformGizmo.h"
#include "BaseGizmos/ComponentBoundTransformProxy.h"
#include "BaseGizmos/GizmoActor.h"
#include "BaseGizmos/GizmoComponents.h"
#include "BaseGizmos/GizmoUtil.h"
#include "BaseGizmos/GizmoViewContext.h"
#include "BaseGizmos/GizmoBoxComponent.h"
#include "BaseGizmos/PlanePositionGizmo.h"
#include "BaseGizmos/TransformGizmoUtil.h"
#include "BaseGizmos/TransformSubGizmoUtil.h"
#include "Engine/World.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "ToolTargetManager.h"
#include "Engine/StaticMeshActor.h"
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UPatternTool"
/*
* ToolBuilder
*/
bool UPatternToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
int32 ValidTargets = 0;
// This tool currently only has 5 output modes, each of them either supporting either static or dynamic meshes.
// Other targets which are UPrimitiveComponent backed are not currently supported.
// todo: Add support for skeletal meshes and volumes
SceneState.TargetManager->EnumerateSelectedAndTargetableComponents(SceneState, GetTargetRequirements(), [&ValidTargets](UActorComponent* Component)
{
if (Cast<UStaticMeshComponent>(Component) || Cast<UDynamicMeshComponent>(Component))
{
ValidTargets++;
}
});
return ValidTargets > 0;
}
UMultiSelectionMeshEditingTool* UPatternToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
UPatternTool* NewTool = NewObject<UPatternTool>(SceneState.ToolManager);
NewTool->SetEnableCreateISMCs(bEnableCreateISMCs);
return NewTool;
}
void UPatternToolBuilder::InitializeNewTool(UMultiSelectionMeshEditingTool* NewTool, const FToolBuilderState& SceneState) const
{
TArray<TObjectPtr<UToolTarget>> Targets;
SceneState.TargetManager->EnumerateSelectedAndTargetableComponents(SceneState, GetTargetRequirements(), [this, &Targets, &SceneState](UActorComponent* Component)
{
if (Cast<UStaticMeshComponent>(Component) || Cast<UDynamicMeshComponent>(Component))
{
Targets.Add(SceneState.TargetManager->BuildTarget(Component, GetTargetRequirements()));
}
});
NewTool->SetTargets(Targets);
NewTool->SetWorld(SceneState.World);
}
const FToolTargetTypeRequirements& UPatternToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements(
UPrimitiveComponentBackedTarget::StaticClass()
);
return TypeRequirements;
}
/*
* PatternGenerators
*/
// these pattern generators should be promoted to GeometryProcessing, however they need some
// work to clean up the API and make them more correct (ie handling of step size seems a bit
// flaky, particularly for circle patterns)
class FPatternGenerator
{
public:
enum class ESpacingMode
{
ByCount = 0,
ByStepSize = 1,
Packed = 2
};
public:
// input settings
FRandomStream RotationRandomStream;
FRandomStream ScaleRandomStream;
FRandomStream TranslationRandomStream;
FQuaterniond StartRotation = FQuaterniond::Identity();
FQuaterniond EndRotation = FQuaterniond::Identity();
FRotator RotationJitterRange = FRotator::ZeroRotator; // using an FRotator so that the sampling can be done about 3 axes individually and converted to a quat when needed
bool bInterpolateRotation = false; // if false, only StartRotation is used
bool bJitterRotation = false;
FVector3d StartTranslation = FVector3d::Zero();
FVector3d EndTranslation = FVector3d::Zero();
FVector3d TranslationJitterRange = FVector3d::Zero();
bool bInterpolateTranslation = false; // if false, only StartTranslation is used
bool bJitterTranslation = false;
FVector3d StartScale = FVector3d::One();
FVector3d EndScale = FVector3d::One();
FVector3d ScaleJitterRange = FVector3d::Zero();
bool bInterpolateScale = false; // if false, only StartScale is used
bool bJitterScale = false;
FAxisAlignedBox3d Dimensions = FAxisAlignedBox3d(FVector3d::Zero(), 10.0);
public:
// current result pattern
TArray<FTransformSRT3d> Pattern;
public:
void ResetPattern()
{
Pattern.Reset();
}
void AddPatternElement(FTransformSRT3d NewTransform, double Alpha)
{
ApplyElementTransforms(NewTransform, Alpha);
Pattern.Add(NewTransform);
}
void ApplyElementTransforms(FTransformSRT3d& Transform, double Alpha)
{
if (bInterpolateRotation)
{
FQuaterniond InterpRotation(StartRotation, EndRotation, Alpha);
Transform.SetRotation(Transform.GetRotation() * InterpRotation);
}
else
{
Transform.SetRotation(Transform.GetRotation() * StartRotation);
}
if (bInterpolateScale)
{
Transform.SetScale( Transform.GetScale() * Lerp(StartScale, EndScale, Alpha) );
}
else
{
Transform.SetScale( Transform.GetScale() * StartScale );
}
if (bInterpolateTranslation)
{
Transform.SetTranslation( Transform.GetTranslation() + Lerp(StartTranslation, EndTranslation, Alpha) );
}
else
{
Transform.SetTranslation( Transform.GetTranslation() + StartTranslation );
}
// Lerp operations are performed on a vector-component basis because each component should be able to independently
// vary without respect for the other two components. This is true for rotation, scale, and translation.
if (bJitterRotation)
{
FRotator RotationJitter;
// TODO: maybe RotationJitterRange should have user-definable lower and upper bound like scale.
RotationJitter.Pitch = FMath::Lerp(-RotationJitterRange.Pitch, RotationJitterRange.Pitch, RotationRandomStream.GetFraction());
RotationJitter.Roll = FMath::Lerp(-RotationJitterRange.Roll, RotationJitterRange.Roll, RotationRandomStream.GetFraction());
RotationJitter.Yaw = FMath::Lerp(-RotationJitterRange.Yaw, RotationJitterRange.Yaw, RotationRandomStream.GetFraction());
Transform.SetRotation(Transform.GetRotation() * FQuaterniond(RotationJitter));
}
if (bJitterScale)
{
FVector3d ScaleJitter;
FVector3d TransformScale = Transform.GetScale();
ScaleJitter.X = FMath::Max(UPatternTool_ScaleSettings::MinScale, TransformScale.X + FMath::Lerp(-ScaleJitterRange.X, ScaleJitterRange.X, ScaleRandomStream.GetFraction()));
ScaleJitter.Y = FMath::Max(UPatternTool_ScaleSettings::MinScale, TransformScale.Y + FMath::Lerp(-ScaleJitterRange.Y, ScaleJitterRange.Y, ScaleRandomStream.GetFraction()));
ScaleJitter.Z = FMath::Max(UPatternTool_ScaleSettings::MinScale, TransformScale.Z + FMath::Lerp(-ScaleJitterRange.Z, ScaleJitterRange.Z, ScaleRandomStream.GetFraction()));
Transform.SetScale( ScaleJitter );
}
if (bJitterTranslation)
{
FVector3d TranslationJitter;
// TODO: maybe TranslationJitterRange should have user-definable lower and upper bound like scale.
TranslationJitter.X = FMath::Lerp(-TranslationJitterRange.X, TranslationJitterRange.X, TranslationRandomStream.GetFraction());
TranslationJitter.Y = FMath::Lerp(-TranslationJitterRange.Y, TranslationJitterRange.Y, TranslationRandomStream.GetFraction());
TranslationJitter.Z = FMath::Lerp(-TranslationJitterRange.Z, TranslationJitterRange.Z, TranslationRandomStream.GetFraction());
Transform.SetTranslation( Transform.GetTranslation() + TranslationJitter);
}
}
double GetAlpha(double AlphaStep, int32 k, int32 Iterations, bool bForceLastStepToOne)
{
if (k == 0)
{
return 0;
}
else if (bForceLastStepToOne && k == Iterations-1)
{
return 1.0;
}
else
{
return FMathd::Clamp((double)k * AlphaStep, 0.0, 1.0);
}
}
void ComputeSteps(FPatternGenerator::ESpacingMode UseSpacingMode, int32 CountIn, double StepSizeIn, double LengthIn, int32& Iterations, double& AlphaStep, bool& bForceLastStepToOne)
{
Iterations = 1;
AlphaStep = 0.5;
bForceLastStepToOne = true;
if (UseSpacingMode == ESpacingMode::ByCount)
{
Iterations = CountIn;
AlphaStep = 1.0 / FMath::Max(Iterations-1, 1);
}
else if (UseSpacingMode == ESpacingMode::ByStepSize)
{
bForceLastStepToOne = false;
AlphaStep = StepSizeIn / LengthIn;
Iterations = FMath::Clamp( (int)(1.0 / (AlphaStep + FMathd::ZeroTolerance) ) + 1, 1, 1000);
}
else if (UseSpacingMode == ESpacingMode::Packed)
{
bForceLastStepToOne = false;
AlphaStep = StepSizeIn / LengthIn;
Iterations = FMath::Clamp( (int)(1.0 / (AlphaStep + FMathd::ZeroTolerance) ) + 1, 1, 1000);
}
else
{
check(false);
}
}
};
class FLinearPatternGenerator : public FPatternGenerator
{
public:
FFrame3d StartFrame;
FFrame3d EndFrame;
ESpacingMode SpacingMode = ESpacingMode::ByCount;
int Axis = 0;
int32 Count = 5;
double StepSize = 1.0;
// Not used for LineFill
ESpacingMode SpacingModeY = ESpacingMode::ByCount;
int AxisY = 1;
int32 CountY = 5;
double StepSizeY = 1.0;
enum class EFillMode
{
LineFill,
RectangleFill
};
EFillMode FillMode = EFillMode::LineFill;
void UpdatePattern()
{
ResetPattern();
if (FillMode == EFillMode::LineFill)
{
UpdatePattern_LineFill();
}
else if (FillMode == EFillMode::RectangleFill)
{
UpdatePattern_RectangleFill();
}
else
{
ensure(false);
AddPatternElement(StartFrame.ToTransform(), 1.0);
}
}
void UpdatePattern_LineFill();
void UpdatePattern_RectangleFill();
};
void FLinearPatternGenerator::UpdatePattern_LineFill()
{
double LineLength = Distance(StartFrame.Origin, EndFrame.Origin);
int32 Iterations = 1;
double AlphaStep = 0.5;
bool bForceLastStepToOne = true;
double UseStepSize = (SpacingMode == ESpacingMode::Packed) ? Dimensions.Dimension(Axis) : StepSize;
ComputeSteps(SpacingMode, Count, UseStepSize, LineLength, Iterations, AlphaStep, bForceLastStepToOne);
for (int32 k = 0; k < Iterations; ++k)
{
FFrame3d PatternFrame = StartFrame;
double Alpha = 0;
if (bForceLastStepToOne && k == Iterations-1)
{
Alpha = 1;
PatternFrame = EndFrame;
}
else if (k != 0)
{
Alpha = GetAlpha(AlphaStep, k, Iterations, bForceLastStepToOne);
PatternFrame = Lerp(StartFrame, EndFrame, Alpha);
}
AddPatternElement(PatternFrame.ToTransform(), Alpha);
}
}
void FLinearPatternGenerator::UpdatePattern_RectangleFill()
{
FVector3d LocalPt = StartFrame.ToFramePoint(EndFrame.Origin);
double ExtentX = LocalPt[Axis];
double ExtentY = LocalPt[AxisY];
int32 IterationsX = 1;
double AlphaStepX = 0.5;
bool bForceLastStepToOneX = true;
double UseStepSizeX = (SpacingMode == ESpacingMode::Packed) ? Dimensions.Dimension(Axis) : StepSize;
ComputeSteps(SpacingMode, Count, UseStepSizeX, ExtentX, IterationsX, AlphaStepX, bForceLastStepToOneX);
int32 IterationsY = 1;
double AlphaStepY = 0.5;
bool bForceLastStepToOneY = true;
double UseStepSizeY = (SpacingModeY == ESpacingMode::Packed) ? Dimensions.Dimension(AxisY): StepSizeY;
ComputeSteps(SpacingModeY, CountY, UseStepSizeY, ExtentY, IterationsY, AlphaStepY, bForceLastStepToOneY);
FFrame3d MidFrameOrientation = Lerp(StartFrame, EndFrame, 0.5);
FFrame3d Frame00 = StartFrame;
FFrame3d Frame11 = EndFrame;
FFrame3d Frame10 = StartFrame;
Frame10.Origin += ExtentX * StartFrame.GetAxis(Axis);
Frame10.Rotation = MidFrameOrientation.Rotation;
FFrame3d Frame01 = StartFrame;
Frame01.Origin += ExtentY * StartFrame.GetAxis(AxisY);
Frame01.Rotation = MidFrameOrientation.Rotation;
for (int32 yi = 0; yi < IterationsY; ++yi)
{
double AlphaY = GetAlpha(AlphaStepY, yi, IterationsY, bForceLastStepToOneY);
FFrame3d Frame0 = Lerp(Frame00, Frame01, AlphaY);
FFrame3d Frame1 = Lerp(Frame10, Frame11, AlphaY);
for (int32 xi = 0; xi < IterationsX; ++xi)
{
double AlphaX = GetAlpha(AlphaStepX, xi, IterationsX, bForceLastStepToOneX);
FFrame3d PatternFrame = Lerp(Frame0, Frame1, AlphaX);
AddPatternElement(PatternFrame.ToTransform(), AlphaX*AlphaY);
}
}
}
class FRadialPatternGenerator : public FPatternGenerator
{
public:
FFrame3d CenterFrame;
double StartAngleDeg = 0.0;
double EndAngleDeg = 360.0;
double AngleShift = 0.0;
double Radius = 100.0;
bool bOriented = true;
int AxisIndexToAlign = 0; // depends on SinglePlane of tool
ESpacingMode SpacingMode = ESpacingMode::ByCount;
int32 Count = 5;
double StepSizeDeg = 1.0;
enum class EFillMode
{
CircleFill,
};
EFillMode FillMode = EFillMode::CircleFill;
void UpdatePattern()
{
ResetPattern();
if (FillMode == EFillMode::CircleFill)
{
UpdatePattern_CircleFill();
}
}
void UpdatePattern_CircleFill();
};
void FRadialPatternGenerator::UpdatePattern_CircleFill()
{
double ArcLengthDeg = FMathd::Abs(EndAngleDeg - StartAngleDeg);
int32 Iterations = 1;
double AlphaStep = 0.5;
if (SpacingMode == ESpacingMode::ByCount)
{
Iterations = Count;
AlphaStep = 1.0 / FMath::Max(Iterations, 1);
}
else if (SpacingMode == ESpacingMode::ByStepSize)
{
AlphaStep = StepSizeDeg / ArcLengthDeg;
Iterations = FMath::Clamp( (int)(1.0 / AlphaStep) + 1, 1, 1000);
}
else if (SpacingMode == ESpacingMode::Packed)
{
double ArcLenStepSize = Dimensions.Dimension(0); // currently only support X axis in circle pattern?
double CalcStepSizeRad = ArcLenStepSize / Radius;
AlphaStep = (CalcStepSizeRad * FMathd::RadToDeg) / ArcLengthDeg;
Iterations = FMath::Clamp( (int)(1.0 / AlphaStep) + 1, 1, 1000);
}
else
{
check(false);
}
for (int32 k = 0; k < Iterations; ++k)
{
double Alpha = (double)k * AlphaStep;
if (k == 0)
{
Alpha = 0;
}
Alpha = FMathd::Clamp(Alpha, 0.0, 1.0);
double Angle = (1.0-Alpha)*StartAngleDeg + (Alpha)*EndAngleDeg;
Angle += AngleShift;
double FrameX = Radius * FMathd::Cos(Angle * FMathd::DegToRad);
double FrameY = Radius * FMathd::Sin(Angle * FMathd::DegToRad);
FFrame3d PatternFrame(CenterFrame.FromPlaneUV(FVector2d(FrameX, FrameY), 2));
if (bOriented)
{
FVector3d Axis = Normalized(PatternFrame.Origin - CenterFrame.Origin);
if (IsNormalized(Axis))
{
PatternFrame.ConstrainedAlignAxis(AxisIndexToAlign, Axis, CenterFrame.Z());
}
}
AddPatternElement(PatternFrame.ToTransform(), Alpha);
}
}
/*
* Tool
*
* TODO: convert dynamic mesh to temporary static mesh to allow instance rendering (does it work?)
* TODO: when outputting a merged dynamic mesh, scale/rotation will be baked in, and so there are no limits
* on non-uniform scaling, but it would need to be baked into the preview dynamicmesh components
* (and for static meshes being converted, static preview would have to become dynamic preview)
* TODO: investigate using temporary ISMComponents instead of multiple SMComponents
*
*/
UPatternTool::UPatternTool()
{
}
void UPatternTool::Setup()
{
UInteractiveTool::Setup();
PreviewGeometry = NewObject<UPreviewGeometry>(this);
PreviewGeometry->CreateInWorld(GetTargetWorld(), FTransform::Identity);
// Must be done before creating gizmos, so that we can bind the mechanic to them.
DragAlignmentMechanic = NewObject<UDragAlignmentMechanic>(this);
DragAlignmentMechanic->Setup(this);
Settings = NewObject<UPatternToolSettings>();
AddToolPropertySource(Settings);
Settings->RestoreProperties(this);
Settings->WatchProperty(Settings->SingleAxis, [this](EPatternToolSingleAxis SingleAxis) { OnSingleAxisUpdated(); });
Settings->WatchProperty(Settings->SinglePlane, [this](EPatternToolSinglePlane SinglePlane) { OnSinglePlaneUpdated(); });
Settings->WatchProperty(Settings->Shape, [this](EPatternToolShape) { OnShapeUpdated(); } );
Settings->WatchProperty(Settings->bHideSources, [this](bool bNewValue) { OnSourceVisibilityToggled(!bNewValue); } );
Settings->WatchProperty(Settings->Seed, [this](int32 NewSeed) { MarkPatternDirty(); } );
Settings->WatchProperty(Settings->bProjectElementsDown, [this](bool bNewValue) { MarkPatternDirty(); } );
Settings->WatchProperty(Settings->ProjectionOffset, [this](float NewValue) { MarkPatternDirty(); } );
Settings->WatchProperty(Settings->bHideSources, [this](bool bNewValue) { OnSourceVisibilityToggled(!bNewValue); } );
Settings->WatchProperty(Settings->bUseRelativeTransforms, [this](bool bNewValue) { MarkPatternDirty(); } );
Settings->WatchProperty(Settings->bRandomlyPickElements, [this](bool bNewValue) { MarkPatternDirty(); });
BoundingBoxSettings = NewObject<UPatternTool_BoundingBoxSettings>();
AddToolPropertySource(BoundingBoxSettings);
BoundingBoxSettings->RestoreProperties(this);
BoundingBoxSettings->WatchProperty(BoundingBoxSettings->bIgnoreTransforms, [this](bool bNewValue) { MarkPatternDirty(); } );
BoundingBoxSettings->WatchProperty(BoundingBoxSettings->Adjustment, [this](float NewScale) { MarkPatternDirty(); });
SetToolPropertySourceEnabled(BoundingBoxSettings, false);
LinearSettings = NewObject<UPatternTool_LinearSettings>();
AddToolPropertySource(LinearSettings);
LinearSettings->RestoreProperties(this);
LinearSettings->WatchProperty(LinearSettings->SpacingMode, [this](EPatternToolAxisSpacingMode NewSpacingMode) { OnSpacingModeUpdated(); });
LinearSettings->WatchProperty(LinearSettings->bCentered, [this](bool bNewValue) { ResetTransformGizmoPosition(); });
LinearExtentWatcherIdx = LinearSettings->WatchProperty(LinearSettings->Extent, [this](double NewValue){ ResetTransformGizmoPosition(); });
SetToolPropertySourceEnabled(LinearSettings, false);
GridSettings = NewObject<UPatternTool_GridSettings>();
AddToolPropertySource(GridSettings);
GridSettings->RestoreProperties(this);
GridSettings->WatchProperty(GridSettings->SpacingX, [this](EPatternToolAxisSpacingMode NewSpacingMode) { OnSpacingModeUpdated(); });
GridSettings->WatchProperty(GridSettings->SpacingY, [this](EPatternToolAxisSpacingMode NewSpacingMode) { OnSpacingModeUpdated(); });
GridSettings->WatchProperty(GridSettings->bCenteredX, [this](bool bNewValue) { ResetTransformGizmoPosition(); });
GridSettings->WatchProperty(GridSettings->bCenteredY, [this](bool bNewValue) { ResetTransformGizmoPosition(); });
GridExtentXWatcherIdx = GridSettings->WatchProperty(GridSettings->ExtentX, [this](double NewValue){ ResetTransformGizmoPosition(); });
GridExtentYWatcherIdx = GridSettings->WatchProperty(GridSettings->ExtentY, [this](double NewValue){ ResetTransformGizmoPosition(); });
SetToolPropertySourceEnabled(GridSettings, false);
RadialSettings = NewObject<UPatternTool_RadialSettings>();
AddToolPropertySource(RadialSettings);
RadialSettings->RestoreProperties(this);
RadialSettings->WatchProperty(RadialSettings->SpacingMode, [this](EPatternToolAxisSpacingMode NewSpacingMode) { OnSpacingModeUpdated(); });
RadiusWatcherIdx = RadialSettings->WatchProperty(RadialSettings->Radius, [this](double NewValue){ ResetTransformGizmoPosition(); });
SetToolPropertySourceEnabled(RadialSettings, false);
RotationSettings = NewObject<UPatternTool_RotationSettings>();
AddToolPropertySource(RotationSettings);
RotationSettings->RestoreProperties(this);
TranslationSettings = NewObject<UPatternTool_TranslationSettings>();
AddToolPropertySource(TranslationSettings);
TranslationSettings->RestoreProperties(this);
ScaleSettings = NewObject<UPatternTool_ScaleSettings>();
AddToolPropertySource(ScaleSettings);
ScaleSettings->RestoreProperties(this);
auto OnProportionalChanged = [this](bool bNewValue)
{
if (bNewValue)
{
CachedStartScale = ScaleSettings->StartScale;
CachedEndScale = ScaleSettings->EndScale;
CachedJitterScale = ScaleSettings->Jitter;
}
};
ScaleSettings->WatchProperty(ScaleSettings->bProportional, OnProportionalChanged);
OnProportionalChanged(true); // Initialize StartScaleDirection and EndScaleDirection
auto ApplyProportionalScale = [this](FVector& NewVector, FVector& CachedVector)
{
// Determines which component of the vector is being changed by looking at which component is
// most different from the previous values
FVector Difference = NewVector - CachedVector;
int32 DifferenceMaxElementIndex = MaxAbsElementIndex(Difference);
// This approach to proportional scaling is desirable because when a user manually enters data
// numerically, we scale the other two components such that the entered value is unchanged unless
// doing so would result in component values less than MinScale, in which case the the resulting
// vector will be in the correct direction but lengthened to a degree to ensure all components are
// greater than or equal to MinScale.
double ScaleFactor = FMath::Max(NewVector[DifferenceMaxElementIndex] / CachedVector[DifferenceMaxElementIndex], UPatternTool_ScaleSettings::MinScale / CachedVector[MinElementIndex(CachedVector)]);
NewVector = CachedVector * ScaleFactor;
CachedVector = NewVector;
};
StartScaleWatcherIdx = ScaleSettings->WatchProperty(ScaleSettings->StartScale, [this, &ApplyProportionalScale](const FVector& NewStartScale)
{
if (ScaleSettings->bProportional)
{
ApplyProportionalScale(ScaleSettings->StartScale, CachedStartScale);
ScaleSettings->SilentUpdateWatcherAtIndex(StartScaleWatcherIdx);
// This is needed in addition to the call in OnPropertyModified due to the fact that these watchers are
// called after OnPropertyModified and in some cases the scale values used were inconsistent with the scale
// values being displayed in the details panel.
OnParametersUpdated();
}
});
EndScaleWatcherIdx = ScaleSettings->WatchProperty(ScaleSettings->EndScale, [this, &ApplyProportionalScale](const FVector& NewEndScale)
{
if (ScaleSettings->bProportional)
{
ApplyProportionalScale(ScaleSettings->EndScale, CachedEndScale);
ScaleSettings->SilentUpdateWatcherAtIndex(EndScaleWatcherIdx);
OnParametersUpdated();
}
});
JitterScaleWatcherIdx = ScaleSettings->WatchProperty(ScaleSettings->Jitter, [this, &ApplyProportionalScale](const FVector& NewJitterScale)
{
if (ScaleSettings->bProportional)
{
ApplyProportionalScale(ScaleSettings->Jitter, CachedJitterScale);
ScaleSettings->SilentUpdateWatcherAtIndex(JitterScaleWatcherIdx);
OnParametersUpdated();
}
});
OutputSettings = NewObject<UPatternTool_OutputSettings>();
AddToolPropertySource(OutputSettings);
OutputSettings->RestoreProperties(this);
OutputSettings->bCreateISMCs &= bEnableCreateISMCs;
OutputSettings->bEnableCreateISMCs = bEnableCreateISMCs;
BoundingBoxVisualizer.LineThickness = 2.0f;
InitializeElements();
for (const FPatternElement& Element : Elements)
{
if (Element.SourceStaticMesh != nullptr)
{
OutputSettings->bHaveStaticMeshes = true;
}
}
CurrentStartFrameWorld = FFrame3d(Elements[0].SourceTransform);
PlaneMechanic = NewObject<UConstructionPlaneMechanic>(this);
PlaneMechanic->Setup(this);
PlaneMechanic->Initialize( GetTargetWorld(), CurrentStartFrameWorld );
PlaneMechanic->OnPlaneChanged.AddLambda([this]() { OnMainFrameUpdated(); });
UGizmoViewContext* GizmoViewContext = nullptr;
UContextObjectStore* ContextObjectStore = GetToolManager()->GetContextObjectStore();
if (ContextObjectStore)
{
GizmoViewContext = ContextObjectStore->FindContext<UGizmoViewContext>();
}
// The advantage of using a fully visually symmetric component here is that we can use the same component
// regardless of whether we're translating in a line or a plane, so we don't need to swap it out when that
// changes.
// Note that this helper method already attaches the component to the parent gizmo actor.
UGizmoBoxComponent* BoxComponent = AGizmoActor::AddDefaultBoxComponent(GetWorld(), PlaneMechanic->PlaneTransformGizmo->GetGizmoActor(),
GizmoViewContext, FLinearColor::Red, FVector::ZeroVector);
BoxComponent->LineThickness = 5.0f;
BoxComponent->Dimensions = FVector(BoxComponent->LineThickness * 1.5);
BoxComponent->NotifyExternalPropertyUpdates();
PatternGizmoComponent = BoxComponent;
PatternGizmoProxy = NewObject<UComponentBoundTransformProxy>(this);
PatternGizmoProxy->OnTransformChanged.AddUObject(this, &UPatternTool::OnTransformGizmoUpdated);
PatternGizmoProxy->BindToComponent(BoxComponent,
// bStoreScaleSeparately, doesn't matter because scale isn't used
false);
// Needs to be called before any of the watchers call ResetTransformGizmoPosition in order to set the
// proxy as the target of the gizmo, otherwise the gizmo will attempt to dereference an invalid StateTarget
// due to SetActiveTarget having never been called
ReconstructTransformGizmos();
SetToolDisplayName(LOCTEXT("ToolName", "Pattern"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartPatternTool", "Create Patterns for the selected Objects"),
EToolMessageLevel::UserNotification);
if (bHaveNonUniformScaleElements)
{
// todo: this message only applies in certain contexts like if bSeparateActors=true or if
// emitting an ISMC. Should make it dynamic and/or smarter.
GetToolManager()->DisplayMessage(LOCTEXT("NonUniformScaleWarning",
"Source Objects have Non-Uniform Scaling, which may prevent Pattern Transforms from working correctly."), EToolMessageLevel::UserWarning);
}
}
void UPatternTool::OnShutdown(EToolShutdownType ShutdownType)
{
PlaneMechanic->Shutdown();
PlaneMechanic = nullptr;
// destroy all the preview components we created
for (UPrimitiveComponent* Component : AllComponents)
{
Component->UnregisterComponent();
Component->DestroyComponent();
}
AllComponents.Reset();
AllPreviewComponents.Reset();
PreviewComponents.Reset();
StaticMeshPools.Reset();
DynamicMeshPools.Reset();
PreviewGeometry->Disconnect();
Settings->SaveProperties(this);
BoundingBoxSettings->SaveProperties(this);
LinearSettings->SaveProperties(this);
GridSettings->SaveProperties(this);
RadialSettings->SaveProperties(this);
RotationSettings->SaveProperties(this);
TranslationSettings->SaveProperties(this);
ScaleSettings->SaveProperties(this);
OutputSettings->SaveProperties(this);
DragAlignmentMechanic->Shutdown();
GetToolManager()->GetPairedGizmoManager()->DestroyAllGizmosByOwner(this);
OnSourceVisibilityToggled(true);
if (ShutdownType == EToolShutdownType::Accept)
{
EmitResults();
}
}
void UPatternTool::Render(IToolsContextRenderAPI* RenderAPI)
{
DragAlignmentMechanic->Render(RenderAPI);
PlaneMechanic->Render(RenderAPI);
// We should only render here if bVisualize is true and also visible in the details panel.
// We don't want to leave the user with no obvious way to turn it off which might happen if they
// enable bVisualize and then change the spacing mode to something other than packed
if (BoundingBoxSettings->IsPropertySetEnabled() && BoundingBoxSettings->bVisualize)
{
RenderBoundingBoxes(RenderAPI);
}
if (bPatternNeedsUpdating)
{
// throttle update rate as it is somewhat expensive
double DeltaTime = (FDateTime::Now() - LastPatternUpdateTime).GetTotalSeconds();
if (DeltaTime > 0.05)
{
UpdatePattern();
bPatternNeedsUpdating = false;
LastPatternUpdateTime = FDateTime::Now();
}
}
}
void UPatternTool::RenderBoundingBoxes(IToolsContextRenderAPI* RenderAPI)
{
BoundingBoxVisualizer.BeginFrame(RenderAPI);
// Render the individual elements' PatternBounds along the current pattern with green lines
BoundingBoxVisualizer.LineColor = FLinearColor::Green;
for (int32 ElemIdx = 0; ElemIdx < Elements.Num(); ++ElemIdx)
{
FPatternElement& Element = Elements[ElemIdx];
if (Settings->bUseRelativeTransforms)
{
const FTransformSRT3d RelativePositionTransform(FQuaterniond(RotationSettings->StartRotation), FVector3d::ZeroVector, ScaleSettings->StartScale);
BoundingBoxVisualizer.PushTransform(FTransform(RelativePositionTransform.TransformVector(Element.RelativePosition)));
}
for (int32 k = 0; k < CurrentPattern.Num(); ++k)
{
BoundingBoxVisualizer.PushTransform(FTransform(CurrentPattern[k].GetTranslation()) * CurrentStartFrameWorld.ToFTransform());
BoundingBoxVisualizer.DrawWireBox(FBox(Element.PatternBounds));
BoundingBoxVisualizer.PopTransform();
}
if (Settings->bUseRelativeTransforms)
{
BoundingBoxVisualizer.PopTransform();
}
}
// Render the CombinedPatternBounds along the current pattern with red lines
BoundingBoxVisualizer.LineColor = FLinearColor::Red;
for (int32 k = 0; k < CurrentPattern.Num(); ++k)
{
BoundingBoxVisualizer.PushTransform(FTransform(CurrentPattern[k].GetTranslation()) * CurrentStartFrameWorld.ToFTransform());
BoundingBoxVisualizer.DrawWireBox(FBox(CombinedPatternBounds));
BoundingBoxVisualizer.PopTransform();
}
BoundingBoxVisualizer.EndFrame();
}
void UPatternTool::OnSourceVisibilityToggled(bool bVisible)
{
for (int32 k = 0; k < Targets.Num(); ++k)
{
UE::ToolTarget::SetSourceObjectVisible(Targets[k], bVisible);
}
}
void UPatternTool::MarkPatternDirty()
{
if (bPatternNeedsUpdating == false)
{
bPatternNeedsUpdating = true;
LastPatternUpdateTime = FDateTime::Now();
}
}
void UPatternTool::InitializeElements()
{
int32 NumElements = Targets.Num();
Elements.SetNum(NumElements);
for (int32 TargetIdx = 0; TargetIdx < NumElements; TargetIdx++)
{
FPatternElement& Element = Elements[TargetIdx];
Element.TargetIndex = TargetIdx;
Element.SourceComponent = UE::ToolTarget::GetTargetComponent(Targets[TargetIdx]);
Element.SourceMaterials = UE::ToolTarget::GetMaterialSet(Targets[TargetIdx], false).Materials;
Element.SourceTransform = UE::ToolTarget::GetLocalToWorldTransform(Targets[TargetIdx]);
Element.RelativePosition = Element.SourceTransform.GetTranslation() - Elements[0].SourceTransform.GetTranslation();
Element.BaseRotateScale = Element.SourceTransform;
Element.BaseRotateScale.SetTranslation(FVector3d::Zero()); // clear translation from base transform
Element.SourceTransform.SetRotation(FQuaterniond::Identity()); // clear rotate/scale from source transform, so only location is used
Element.SourceTransform.SetScale(FVector::One());
bHaveNonUniformScaleElements = bHaveNonUniformScaleElements || Element.BaseRotateScale.HasNonUniformScale();
if (UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(Element.SourceComponent))
{
Element.SourceStaticMesh = StaticMeshComp->GetStaticMesh();
Element.LocalBounds = Element.SourceStaticMesh->GetBounds().GetBox();
}
else if (UDynamicMeshComponent* DynamicMeshComp = Cast<UDynamicMeshComponent>(Element.SourceComponent))
{
Element.SourceDynamicMesh = DynamicMeshComp->GetDynamicMesh();
Element.SourceDynamicMesh->ProcessMesh([&](const FDynamicMesh3& Mesh) {
Element.LocalBounds = Mesh.GetBounds(true);
});
}
else
{
Element.bValid = false;
}
Element.PatternBounds = Element.LocalBounds;
}
PreviewComponents.SetNum(NumElements);
ComputeCombinedPatternBounds();
}
void UPatternTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
if (PropertySet == Settings || PropertySet == OutputSettings)
{
return;
}
if (PropertySet == ScaleSettings && ScaleSettings->bProportional)
{
// We are silencing all watchers if Property corresponds to an FVector UProperty because this indicates that
// the "Reset to Default" button was pressed. If individual components are modified instead, then Property will
// not be castable to an FStructProperty
const FStructProperty* StructProperty = CastField<FStructProperty>(Property);
if (StructProperty != nullptr && StructProperty->Struct->GetName() == FString("Vector"))
{
CachedStartScale = ScaleSettings->StartScale;
CachedEndScale = ScaleSettings->EndScale;
CachedJitterScale = ScaleSettings->Jitter;
ScaleSettings->SilentUpdateWatcherAtIndex(StartScaleWatcherIdx);
ScaleSettings->SilentUpdateWatcherAtIndex(EndScaleWatcherIdx);
ScaleSettings->SilentUpdateWatcherAtIndex(JitterScaleWatcherIdx);
}
}
OnParametersUpdated();
}
void UPatternTool::OnMainFrameUpdated()
{
CurrentStartFrameWorld = PlaneMechanic->Plane;
MarkPatternDirty();
}
void UPatternTool::OnShapeUpdated()
{
bool bLinearSettings = (Settings->Shape == EPatternToolShape::Line);
bool bGridSettings = (Settings->Shape == EPatternToolShape::Grid);
bool bRadialSettings = (Settings->Shape == EPatternToolShape::Circle);
SetToolPropertySourceEnabled(LinearSettings, bLinearSettings);
SetToolPropertySourceEnabled(GridSettings, bGridSettings);
SetToolPropertySourceEnabled(RadialSettings, bRadialSettings);
// This keeps bUsingSingleAxis correct, fixes up gizmos, and calls OnParametersUpdated
if (bLinearSettings)
{
OnSingleAxisUpdated();
}
else if (bGridSettings || bRadialSettings)
{
OnSinglePlaneUpdated();
}
}
void UPatternTool::OnSingleAxisUpdated()
{
bUsingSingleAxis = true;
OnParametersUpdated();
ReconstructTransformGizmos();
}
void UPatternTool::OnSinglePlaneUpdated()
{
bUsingSingleAxis = false;
OnParametersUpdated();
ReconstructTransformGizmos();
}
void UPatternTool::OnSpacingModeUpdated()
{
bool bBoundingBoxSettings = false;
if (Settings->Shape == EPatternToolShape::Line)
{
if (LinearSettings->SpacingMode == EPatternToolAxisSpacingMode::Packed)
{
bBoundingBoxSettings = true;
}
}
else if (Settings->Shape == EPatternToolShape::Grid)
{
if (GridSettings->SpacingX == EPatternToolAxisSpacingMode::Packed || GridSettings->SpacingY == EPatternToolAxisSpacingMode::Packed)
{
bBoundingBoxSettings = true;
}
}
else if (Settings->Shape == EPatternToolShape::Circle)
{
if (RadialSettings->SpacingMode == EPatternToolAxisSpacingMode::Packed)
{
bBoundingBoxSettings = true;
}
}
SetToolPropertySourceEnabled(BoundingBoxSettings, bBoundingBoxSettings);
OnParametersUpdated();
}
void UPatternTool::OnParametersUpdated()
{
MarkPatternDirty();
}
void UPatternTool::SetEnableCreateISMCs(bool bEnable)
{
bEnableCreateISMCs = bEnable;
if (OutputSettings)
{
OutputSettings->bCreateISMCs &= bEnableCreateISMCs;
OutputSettings->bEnableCreateISMCs = bEnableCreateISMCs;
}
}
void UPatternTool::OnTransformGizmoUpdated(UTransformProxy* Proxy, FTransform Transform)
{
const FVector3d GizmoToToolOrigin = PatternGizmoProxy->GetTransform().GetLocation() - CurrentStartFrameWorld.Origin;
// Recompute extents using current shape, SingleAxis/SinglePlane, and PatternGizmo position
switch (Settings->Shape)
{
case EPatternToolShape::Line:
LinearSettings->Extent = GizmoToToolOrigin.ProjectOnTo(CurrentStartFrameWorld.GetAxis((int) Settings->SingleAxis)).Length();
if (LinearSettings->bCentered) { LinearSettings->Extent *= 2.0f; }
LinearSettings->SilentUpdateWatcherAtIndex(LinearExtentWatcherIdx);
break;
case EPatternToolShape::Grid:
{
const int32 XIndex = Settings->SinglePlane == EPatternToolSinglePlane::YZPlane ? 1 : 0;
const int32 YIndex = Settings->SinglePlane == EPatternToolSinglePlane::XYPlane ? 1 : 2;
GridSettings->ExtentX = GizmoToToolOrigin.ProjectOnTo(CurrentStartFrameWorld.GetAxis(XIndex)).Length();
if (GridSettings->bCenteredX) { GridSettings->ExtentX *= 2.0f; }
GridSettings->ExtentY = GizmoToToolOrigin.ProjectOnTo(CurrentStartFrameWorld.GetAxis(YIndex)).Length();
if (GridSettings->bCenteredY) { GridSettings->ExtentY *= 2.0f; }
GridSettings->SilentUpdateWatcherAtIndex(GridExtentXWatcherIdx);
GridSettings->SilentUpdateWatcherAtIndex(GridExtentYWatcherIdx);
break;
}
case EPatternToolShape::Circle:
RadialSettings->Radius = GizmoToToolOrigin.Length(); // This is actually the simplest case because of how conveniently a circle is defined
RadialSettings->SilentUpdateWatcherAtIndex(RadiusWatcherIdx);
break;
}
MarkPatternDirty();
}
void UPatternTool::ResetTransformGizmoPosition()
{
if (!PatternGizmo || !ensure(PatternGizmoProxy))
{
return;
}
// There are two ways we can deal with our transform, particularly the rotation. Either we can
// keep the sub gizmo unrotated in parent gizmo space, and change which axes we use on the gizmo,
// or we can have the gizmo operate on the same axis/axes and change the rotation here to align
// that axis in the proper direction.
// For now we do the latter.
FVector3d OffsetFromOrigin = FVector3d::ZeroVector;
double DistanceFromOriginX = 0.0f;
double DistanceFromOriginY = 0.0f;
bool bDistancesSet = false;
// The gizmo is always restricted to moving in its local x-axis or xy-plane even when other axes or planes
// are used in the tool. This rotation correctly adjusts the gizmo's local frame such that the local x-axis
// or xy-plane correspond to the proper directions in the frame of the tool.
FRotator RotationInLocalSpace = FRotator::ZeroRotator;
switch (Settings->Shape)
{
case EPatternToolShape::Line:
DistanceFromOriginX = LinearSettings->Extent;
if (LinearSettings->bCentered) { DistanceFromOriginX /= 2.0f; }
if (Settings->SingleAxis == EPatternToolSingleAxis::XAxis)
{
OffsetFromOrigin = FVector3d(DistanceFromOriginX, 0, 0);
}
else if (Settings->SingleAxis == EPatternToolSingleAxis::YAxis)
{
OffsetFromOrigin = FVector3d(0, DistanceFromOriginX, 0);
RotationInLocalSpace.Add(0, 90, 0);
}
else if (Settings->SingleAxis == EPatternToolSingleAxis::ZAxis)
{
OffsetFromOrigin = FVector3d(0, 0, DistanceFromOriginX);
RotationInLocalSpace.Add(90, 0, 0);
}
break;
case EPatternToolShape::Grid:
DistanceFromOriginX = GridSettings->ExtentX;
if (GridSettings->bCenteredX) { DistanceFromOriginX /= 2.0f; }
DistanceFromOriginY = GridSettings->ExtentY;
if (GridSettings->bCenteredY) { DistanceFromOriginY /= 2.0f; }
bDistancesSet = true;
// FALLTHROUGH TO NEXT CASE
case EPatternToolShape::Circle:
if (!bDistancesSet)
{
DistanceFromOriginX = RadialSettings->Radius;
DistanceFromOriginY = 0.0f;
}
if (Settings->SinglePlane == EPatternToolSinglePlane::XYPlane)
{
OffsetFromOrigin = FVector3d(DistanceFromOriginX, DistanceFromOriginY, 0);
}
else if (Settings->SinglePlane == EPatternToolSinglePlane::XZPlane)
{
OffsetFromOrigin = FVector3d(DistanceFromOriginX, 0, DistanceFromOriginY);
RotationInLocalSpace.Add(0, 0, 90);
}
else if (Settings->SinglePlane == EPatternToolSinglePlane::YZPlane)
{
OffsetFromOrigin = FVector3d(0, DistanceFromOriginX, DistanceFromOriginY);
RotationInLocalSpace.Add(90, 0, 0);
}
break;
}
const FVector3d ProxyTranslation = CurrentStartFrameWorld.Origin + FRotator(CurrentStartFrameWorld.Rotation).RotateVector(OffsetFromOrigin);
TGuardValue<bool> SetPivotGuard(PatternGizmoProxy->bSetPivotMode, true);
PatternGizmoProxy->SetTransform(FTransform(RotationInLocalSpace, OffsetFromOrigin) * CurrentStartFrameWorld.ToFTransform());
}
void UPatternTool::ReconstructTransformGizmos()
{
UInteractiveGizmoManager* GizmoManager = GetToolManager()->GetPairedGizmoManager();
if (PatternGizmo)
{
GizmoManager->DestroyGizmo(PatternGizmo);
}
// There are a couple ways to create a sub gizmo here. The proper way is to add the extra element individually,
// as we do here, but it could have been done with a TRS subgizmo.
UE::GizmoUtil::FTransformSubGizmoCommonParams Params;
Params.Component = PatternGizmoComponent;
Params.TransformProxy = PatternGizmoProxy;
Params.bManipulatesRootComponent = false;
Params.bAxisIsBasedOnRootComponent = false;
if (bUsingSingleAxis)
{
UAxisPositionGizmo* AxisGizmo = UE::GizmoUtil::CreateGizmoViaSimpleBuilder<UAxisPositionGizmo>(
GetToolManager()->GetPairedGizmoManager(), FString(), this);
Params.OuterForSubobjects = AxisGizmo;
Params.Axis = EAxis::X;
AxisGizmo->InitializeAsTranslateGizmo(Params,
// No shared state
nullptr);
PatternGizmo = AxisGizmo;
}
else
{
UPlanePositionGizmo* PlaneGizmo = UE::GizmoUtil::CreateGizmoViaSimpleBuilder<UPlanePositionGizmo>(
GetToolManager()->GetPairedGizmoManager(), FString(), this);
Params.OuterForSubobjects = PlaneGizmo;
Params.Axis = EAxis::Z;
PlaneGizmo->InitializeAsTranslateGizmo(Params,
// No shared state
nullptr);
PatternGizmo = PlaneGizmo;
}
ResetTransformGizmoPosition();
}
void UPatternTool::ResetPreviews()
{
int32 NumElements = Elements.Num();
if (PreviewComponents.Num() != NumElements)
{
return;
}
for (int32 ElemIdx = 0; ElemIdx < NumElements; ++ElemIdx)
{
FPatternElement& Element = Elements[ElemIdx];
if (Element.bValid)
{
FComponentSet& ElemComponents = PreviewComponents[ElemIdx];
if (Element.SourceStaticMesh)
{
ReturnStaticMeshes(Element, ElemComponents);
}
else if (Element.SourceDynamicMesh != nullptr)
{
ReturnDynamicMeshes(Element, ElemComponents);
}
}
}
}
static void InitializeGenerator(FPatternGenerator& Generator, UPatternTool* Tool)
{
// The way this is seeded is kind of arbitrary but is reliable for the purpose of deterministically
// providing each type of jitter a random stream independent of the others given a single input seed.
Generator.RotationRandomStream.Initialize(Tool->Settings->Seed);
Generator.ScaleRandomStream.Initialize(Generator.RotationRandomStream.GetUnsignedInt());
Generator.TranslationRandomStream.Initialize(Generator.RotationRandomStream.GetUnsignedInt());
Generator.bInterpolateRotation = Tool->RotationSettings->bInterpolate;
Generator.bJitterRotation = Tool->RotationSettings->bJitter;
Generator.RotationJitterRange = Tool->RotationSettings->Jitter;
if (Generator.bInterpolateRotation)
{
Generator.StartRotation = FQuaterniond(Tool->RotationSettings->StartRotation);
Generator.EndRotation = FQuaterniond(Tool->RotationSettings->EndRotation);
}
else
{
Generator.StartRotation = Generator.EndRotation = FQuaterniond(Tool->RotationSettings->StartRotation);
}
Generator.bInterpolateTranslation = Tool->TranslationSettings->bInterpolate;
Generator.bJitterTranslation = Tool->TranslationSettings->bJitter;
Generator.TranslationJitterRange = Tool->TranslationSettings->Jitter;
if (Generator.bInterpolateTranslation)
{
Generator.StartTranslation = Tool->TranslationSettings->StartTranslation;
Generator.EndTranslation = Tool->TranslationSettings->EndTranslation;
}
else
{
Generator.StartTranslation = Generator.EndTranslation = Tool->TranslationSettings->StartTranslation;
}
Generator.bInterpolateScale = Tool->ScaleSettings->bInterpolate;
Generator.bJitterScale = Tool->ScaleSettings->bJitter;
Generator.ScaleJitterRange = Tool->ScaleSettings->Jitter;
if (Generator.bInterpolateScale)
{
Generator.StartScale = Tool->ScaleSettings->StartScale;
Generator.EndScale = Tool->ScaleSettings->EndScale;
}
else
{
Generator.StartScale = Generator.EndScale = Tool->ScaleSettings->StartScale;
}
}
void UPatternTool::GetPatternTransforms_Linear(TArray<UE::Geometry::FTransformSRT3d>& TransformsOut)
{
FLinearPatternGenerator Generator;
InitializeGenerator(Generator, this);
ComputeCombinedPatternBounds();
Generator.Dimensions = CombinedPatternBounds;
double ExtentX = LinearSettings->Extent;
Generator.StartFrame = FFrame3d();
Generator.Axis = (int32)Settings->SingleAxis;
FVector3d AxisX = Generator.StartFrame.GetAxis(Generator.Axis);
if (LinearSettings->bCentered)
{
Generator.StartFrame.Origin -= 0.5 * ExtentX * AxisX;
}
Generator.EndFrame = Generator.StartFrame;
Generator.EndFrame.Origin += ExtentX * AxisX;
Generator.FillMode = FLinearPatternGenerator::EFillMode::LineFill;
Generator.SpacingMode = (FLinearPatternGenerator::ESpacingMode)(int)LinearSettings->SpacingMode;
Generator.Count = LinearSettings->Count;
Generator.StepSize = LinearSettings->StepSize;
Generator.UpdatePattern();
TransformsOut = MoveTemp(Generator.Pattern);
}
void UPatternTool::GetPatternTransforms_Grid(TArray<UE::Geometry::FTransformSRT3d>& TransformsOut)
{
FLinearPatternGenerator Generator;
InitializeGenerator(Generator, this);
ComputeCombinedPatternBounds();
Generator.Dimensions = CombinedPatternBounds;
double ExtentX = GridSettings->ExtentX;
double ExtentY = GridSettings->ExtentY;
Generator.StartFrame = FFrame3d();
switch (Settings->SinglePlane)
{
case EPatternToolSinglePlane::XYPlane:
Generator.Axis = 0; Generator.AxisY = 1; break;
case EPatternToolSinglePlane::XZPlane:
Generator.Axis = 0; Generator.AxisY = 2; break;
case EPatternToolSinglePlane::YZPlane:
Generator.Axis = 1; Generator.AxisY = 2; break;
}
FVector3d AxisX = Generator.StartFrame.GetAxis(Generator.Axis);
FVector3d AxisY = Generator.StartFrame.GetAxis(Generator.AxisY);
if (GridSettings->bCenteredX)
{
Generator.StartFrame.Origin -= 0.5 * ExtentX * AxisX;
}
if (GridSettings->bCenteredY)
{
Generator.StartFrame.Origin -= 0.5 * ExtentY * AxisY;
}
Generator.EndFrame = Generator.StartFrame;
Generator.EndFrame.Origin += ExtentX * AxisX + ExtentY * AxisY;
Generator.FillMode = FLinearPatternGenerator::EFillMode::RectangleFill;
Generator.SpacingMode = (FLinearPatternGenerator::ESpacingMode)(int)GridSettings->SpacingX;
Generator.Count = GridSettings->CountX;
Generator.StepSize = GridSettings->StepSizeX;
Generator.SpacingModeY = (FLinearPatternGenerator::ESpacingMode)(int)GridSettings->SpacingY;
Generator.CountY = GridSettings->CountY;
Generator.StepSizeY = GridSettings->StepSizeY;
Generator.UpdatePattern();
TransformsOut = MoveTemp(Generator.Pattern);
}
void UPatternTool::GetPatternTransforms_Radial(TArray<UE::Geometry::FTransformSRT3d>& TransformsOut)
{
FRadialPatternGenerator Generator;
InitializeGenerator(Generator, this);
ComputeCombinedPatternBounds();
Generator.Dimensions = CombinedPatternBounds;
Generator.Radius = RadialSettings->Radius;
// Orient the CenterFrame based on the plane setting of the tool & determine which axis is used if bOriented is true
const FVector3d Origin(0, 0, 0);
switch (Settings->SinglePlane)
{
case EPatternToolSinglePlane::XYPlane:
Generator.CenterFrame = FFrame3d(Origin, FVector3d(0, 0, 1));
Generator.AxisIndexToAlign = 0;
break;
case EPatternToolSinglePlane::XZPlane:
Generator.CenterFrame = FFrame3d(Origin, FVector3d(0, 1, 0));
Generator.AxisIndexToAlign = 2;
break;
case EPatternToolSinglePlane::YZPlane:
Generator.CenterFrame = FFrame3d(Origin, FVector3d(1, 0, 0));
Generator.AxisIndexToAlign = 2;
break;
default:
Generator.CenterFrame = FFrame3d();
}
Generator.StartAngleDeg = RadialSettings->StartAngle;
Generator.EndAngleDeg = RadialSettings->EndAngle;
Generator.AngleShift = RadialSettings->AngleShift;
Generator.bOriented = RadialSettings->bOriented;
Generator.SpacingMode = (FRadialPatternGenerator::ESpacingMode)(int)RadialSettings->SpacingMode;
Generator.FillMode = FRadialPatternGenerator::EFillMode::CircleFill;
Generator.Count = RadialSettings->Count;
Generator.StepSizeDeg = RadialSettings->StepSize;
Generator.UpdatePattern();
TransformsOut = MoveTemp(Generator.Pattern);
}
void UPatternTool::UpdatePattern()
{
TArray<FTransformSRT3d> Pattern;
if (Settings->Shape == EPatternToolShape::Line)
{
GetPatternTransforms_Linear(Pattern);
}
if (Settings->Shape == EPatternToolShape::Grid)
{
GetPatternTransforms_Grid(Pattern);
}
else if (Settings->Shape == EPatternToolShape::Circle)
{
GetPatternTransforms_Radial(Pattern);
}
// Return all current preview components in use to the preview component pool
ResetPreviews();
int32 NumPatternItems = Pattern.Num();
int32 NumElements = Elements.Num();
check(PreviewComponents.Num() == NumElements);
auto AddComponent = [this, &Pattern](int32 PatternItemIdx, const FPatternElement& Element, const FTransformSRT3d& ElementTransform, FComponentSet& ElemComponents)
{
UPrimitiveComponent* Component = nullptr;
if (Element.SourceDynamicMesh != nullptr)
{
Component = GetPreviewDynamicMesh(Element);
}
else if (Element.SourceStaticMesh != nullptr)
{
Component = GetPreviewStaticMesh(Element);
}
if (Component != nullptr)
{
FTransform WorldTransform;
ComputeWorldTransform(WorldTransform, ElementTransform, Pattern[PatternItemIdx]);
Component->SetWorldTransform(WorldTransform);
Component->SetVisibility(true);
ElemComponents.Components.Add(Component);
}
};
if (Settings->bRandomlyPickElements)
{
FRandomStream Stream(Settings->Seed);
for (int32 k = 0; k < NumPatternItems; ++k)
{
int32 ElemIdx = Stream.RandHelper(NumElements);
FPatternElement& Element = Elements[ElemIdx];
FTransformSRT3d ElementTransform = Element.BaseRotateScale;
FComponentSet& ElemComponents = PreviewComponents[ElemIdx];
if (Settings->bUseRelativeTransforms)
{
ElementTransform.SetTranslation(ElementTransform.GetTranslation() + Element.RelativePosition);
}
AddComponent(k, Element, ElementTransform, ElemComponents);
}
}
else // always pick all elements
{
for (int32 ElemIdx = 0; ElemIdx < NumElements; ++ElemIdx)
{
FPatternElement& Element = Elements[ElemIdx];
FTransformSRT3d ElementTransform = Element.BaseRotateScale;
FComponentSet& ElemComponents = PreviewComponents[ElemIdx];
if (Settings->bUseRelativeTransforms)
{
ElementTransform.SetTranslation(ElementTransform.GetTranslation() + Element.RelativePosition);
}
for (int32 k = 0; k < NumPatternItems; ++k)
{
AddComponent(k, Element, ElementTransform, ElemComponents);
}
}
}
// ResetPreviews() does not hide the preview components because changing their visibility
// will destroy their SceneProxies, which is expensive, and in most cases the same set of
// objects will immediately be shown, re-creating the proxy. So instead they are just all
// left visible, and HideReturnedPreviewMeshes() fixes up the visibility on any Components
// returned to the pools that were not immediately re-used
HideReturnedPreviewMeshes();
CurrentPattern = MoveTemp(Pattern);
}
void UPatternTool::ComputeWorldTransform(FTransform& OutWorldTransform, const FTransform& InElementTransform, const FTransform& InPatternTransform) const
{
OutWorldTransform = InElementTransform * InPatternTransform * CurrentStartFrameWorld.ToFTransform();
if (Settings->bProjectElementsDown)
{
FVector3d ProjectionAxis = -CurrentStartFrameWorld.Z();
const FRay WorldRay(OutWorldTransform.GetTranslation(), ProjectionAxis);
FHitResult HitResult;
if (ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, HitResult, WorldRay, &AllPreviewComponents, nullptr))
{
FVector3d ProjectionTranslation = ProjectionAxis * (HitResult.Distance + Settings->ProjectionOffset);
OutWorldTransform.SetTranslation(OutWorldTransform.GetTranslation() + ProjectionTranslation);
}
}
}
void UPatternTool::ComputePatternBounds(int32 ElemIdx)
{
FPatternElement& Element = Elements[ElemIdx];
FTransformSRT3d Transform = Element.BaseRotateScale;
if (!BoundingBoxSettings->bIgnoreTransforms)
{
Transform = (FTransform) FTransformSRT3d(FQuaterniond(RotationSettings->StartRotation), FVector3d::ZeroVector, ScaleSettings->StartScale) * Transform;
}
Element.PatternBounds = FAxisAlignedBox3d(Element.LocalBounds, Transform);
}
void UPatternTool::ComputeCombinedPatternBounds()
{
// CombinedPatternBounds is the bounding box that contains every pattern element and is computed by setting it
// to the first element's pattern bounds and then expanding the box to contain each following element.
ComputePatternBounds(0);
CombinedPatternBounds = Elements[0].PatternBounds;
// If StartScale or StartRotation are not zero vectors, then Element.RelativePosition must be transformed to be accurate if bUseRelativeTransforms is true
const FTransformSRT3d RelativePositionTransform(FQuaterniond(RotationSettings->StartRotation), FVector3d::ZeroVector, ScaleSettings->StartScale);
for (int32 ElemIdx = 1; ElemIdx < Elements.Num(); ++ElemIdx)
{
const FPatternElement& Element = Elements[ElemIdx];
ComputePatternBounds(ElemIdx);
if (Settings->bUseRelativeTransforms)
{
CombinedPatternBounds.Contain(Element.PatternBounds.Min + RelativePositionTransform.TransformVector(Element.RelativePosition));
CombinedPatternBounds.Contain(Element.PatternBounds.Max + RelativePositionTransform.TransformVector(Element.RelativePosition));
}
else
{
CombinedPatternBounds.Contain(Element.PatternBounds);
}
}
// The user can manually adjust the box if desired to fine tune packed behavior
CombinedPatternBounds.Expand(BoundingBoxSettings->Adjustment);
}
UStaticMeshComponent* UPatternTool::GetPreviewStaticMesh(const FPatternElement& Element)
{
FComponentSet* FoundPool = StaticMeshPools.Find(Element.TargetIndex);
if (FoundPool == nullptr)
{
StaticMeshPools.Add(Element.TargetIndex, FComponentSet{});
FoundPool = StaticMeshPools.Find(Element.TargetIndex);
check(FoundPool != nullptr);
}
if (FoundPool->Components.Num() > 0)
{
UStaticMeshComponent* FoundComponent = Cast<UStaticMeshComponent>(FoundPool->Components.Pop(EAllowShrinking::No));
check(FoundComponent != nullptr);
return FoundComponent;
}
UStaticMeshComponent* StaticMeshComp = NewObject<UStaticMeshComponent>(PreviewGeometry->GetActor());
StaticMeshComp->SetStaticMesh(Element.SourceStaticMesh);
for (int32 j = 0; j < Element.SourceMaterials.Num(); ++j)
{
StaticMeshComp->SetMaterial(j, Element.SourceMaterials[j]);
}
StaticMeshComp->SetupAttachment(PreviewGeometry->GetActor()->GetRootComponent());
StaticMeshComp->RegisterComponent();
AllComponents.Add(StaticMeshComp);
AllPreviewComponents.Add(StaticMeshComp);
return StaticMeshComp;
}
void UPatternTool::ReturnStaticMeshes(FPatternElement& Element, FComponentSet& ComponentSet)
{
if (ComponentSet.Components.Num() == 0) return;
FComponentSet* FoundPool = StaticMeshPools.Find(Element.TargetIndex);
check(FoundPool != nullptr); // should never happen as we would have created in GetPreviewStaticMesh()
for (UPrimitiveComponent* Component : ComponentSet.Components)
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
{
FoundPool->Components.Add(StaticMeshComponent);
}
}
ComponentSet.Components.Reset();
}
UDynamicMeshComponent* UPatternTool::GetPreviewDynamicMesh(const FPatternElement& Element)
{
FComponentSet* FoundPool = DynamicMeshPools.Find(Element.TargetIndex);
if (FoundPool == nullptr)
{
DynamicMeshPools.Add(Element.TargetIndex, FComponentSet{});
FoundPool = DynamicMeshPools.Find(Element.TargetIndex);
check(FoundPool != nullptr);
}
if (FoundPool->Components.Num() > 0)
{
UDynamicMeshComponent* FoundComponent = Cast<UDynamicMeshComponent>(FoundPool->Components.Pop(EAllowShrinking::No));
check(FoundComponent != nullptr);
return FoundComponent;
}
UDynamicMeshComponent* DynamicMeshComp = NewObject<UDynamicMeshComponent>(PreviewGeometry->GetActor());
DynamicMeshComp->SetGenerateOverlapEvents(false);
DynamicMeshComp->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
DynamicMeshComp->CollisionType = ECollisionTraceFlag::CTF_UseSimpleAsComplex;
DynamicMeshComp->ConfigureMaterialSet(Element.SourceMaterials);
DynamicMeshComp->SetupAttachment(PreviewGeometry->GetActor()->GetRootComponent());
DynamicMeshComp->RegisterComponent();
FDynamicMesh3 ElementMeshCopy(Element.SourceDynamicMesh->GetMeshRef());
DynamicMeshComp->SetMesh(MoveTemp(ElementMeshCopy));
AllComponents.Add(DynamicMeshComp);
AllPreviewComponents.Add(DynamicMeshComp);
return DynamicMeshComp;
}
void UPatternTool::ReturnDynamicMeshes(FPatternElement& Element, FComponentSet& ComponentSet)
{
if (ComponentSet.Components.Num() == 0) return;
FComponentSet* FoundPool = DynamicMeshPools.Find(Element.TargetIndex);
check(FoundPool != nullptr); // should never happen as we would have created in GetPreviewStaticMesh()
for (UPrimitiveComponent* Component : ComponentSet.Components)
{
if (UDynamicMeshComponent* DynamicMeshComponent = Cast<UDynamicMeshComponent>(Component))
{
FoundPool->Components.Add(DynamicMeshComponent);
}
}
ComponentSet.Components.Reset();
}
void UPatternTool::HideReturnedPreviewMeshes()
{
for (TPair<int32, FComponentSet> Pair : StaticMeshPools)
{
for (UPrimitiveComponent* Component : Pair.Value.Components)
{
if (Component->IsVisible())
{
Component->SetVisibility(false);
}
}
}
for (TPair<int32, FComponentSet> Pair : DynamicMeshPools)
{
for (UPrimitiveComponent* Component : Pair.Value.Components)
{
if (Component->IsVisible())
{
Component->SetVisibility(false);
}
}
}
}
void UPatternTool::EmitResults()
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("CreatePattern", "Create Pattern"));
bool bSeparateActorPerItem = OutputSettings->bSeparateActors;
bool bCreateISMForStaticMeshes = OutputSettings->bCreateISMCs;
bool bConvertToDynamic = OutputSettings->bConvertToDynamic;
const UModelingComponentsSettings* ModelingSettings = GetDefault<UModelingComponentsSettings>();
int32 NumElements = Elements.Num();
int32 NumPatternItems = CurrentPattern.Num();
TArray<AActor*> NewActors; // set of new actors created by operation
// Used when constructing actors, not appending components to an actor. Otherwise UPrimitiveComponent::SetVisibility()
// is used instead. Keeping everything invisible until the end allows us to avoid hitting the created components
// when projecting downward, just like we do in the previews via ignored components.
constexpr bool bPropagateToChildren = true;
auto SetActorComponentsVisibility = [bPropagateToChildren](AActor* InActor, const bool bNewVisibility)
{
for (UActorComponent* Component : InActor->GetComponents())
{
if (UPrimitiveComponent* ActorComponentAsPrimitiveComponent = Cast<UPrimitiveComponent, UActorComponent>(Component))
{
ActorComponentAsPrimitiveComponent->SetVisibility(bNewVisibility, bPropagateToChildren);
}
}
};
// TODO: investigate use of CopyPropertiesForUnrelatedObjects to transfer settings from source to target Components/Actors
for (int32 ElemIdx = 0; ElemIdx < NumElements; ++ElemIdx)
{
FPatternElement& Element = Elements[ElemIdx];
if (Element.bValid == false)
{
continue;
}
FTransformSRT3d ElementTransform = Element.BaseRotateScale;
if (Settings->bUseRelativeTransforms)
{
ElementTransform.SetTranslation(ElementTransform.GetTranslation() + Element.RelativePosition);
}
if (Element.SourceDynamicMesh != nullptr || bConvertToDynamic )
{
static FGetMeshParameters GetMeshParams;
GetMeshParams.bWantMeshTangents = true;
FDynamicMesh3 ElementMesh = UE::ToolTarget::GetDynamicMeshCopy(Targets[ElemIdx], GetMeshParams);
// this lambda creates a new dynamic mesh actor w/ the materials from the current Element
auto EmitDynamicMeshActor = [this, &Element, ModelingSettings](FDynamicMesh3&& MoveMesh, FString BaseName, FTransformSRT3d Transform)
{
FCreateMeshObjectParams NewMeshObjectParams;
NewMeshObjectParams.TargetWorld = GetTargetWorld();
NewMeshObjectParams.Transform = (FTransform)Transform;
NewMeshObjectParams.BaseName = BaseName;
NewMeshObjectParams.Materials = Element.SourceMaterials;
NewMeshObjectParams.SetMesh(MoveTemp(MoveMesh));
NewMeshObjectParams.TypeHint = ECreateObjectTypeHint::DynamicMeshActor;
NewMeshObjectParams.bEnableCollision = ModelingSettings->bEnableCollision;
NewMeshObjectParams.CollisionMode = ModelingSettings->CollisionMode;
NewMeshObjectParams.bEnableRaytracingSupport = ModelingSettings->bEnableRayTracing;
FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams));
return Result;
};
if (bSeparateActorPerItem)
{
for (int32 k = 0; k < NumPatternItems; ++k)
{
FTransformSRT3d PatternTransform = CurrentPattern[k];
FTransform WorldTransform;
ComputeWorldTransform(WorldTransform, (FTransform)ElementTransform, (FTransform)PatternTransform);
FDynamicMesh3 PatternItemMesh = ElementMesh;
// TODO: may need to bake nonuniform scale in here, if we allow scaling in transform
FCreateMeshObjectResult Result = EmitDynamicMeshActor(MoveTemp(PatternItemMesh),
FString::Printf(TEXT("Pattern_%d_%d"), ElemIdx, k), WorldTransform);
if (Result.IsOK())
{
NewActors.Add(Result.NewActor);
SetActorComponentsVisibility(Result.NewActor, false);
}
}
}
else
{
FDynamicMesh3 CombinedPatternMesh;
CombinedPatternMesh.EnableMatchingAttributes(ElementMesh, true);
FDynamicMeshEditor Editor(&CombinedPatternMesh);
FMeshIndexMappings Mappings;
for (int32 k = 0; k < NumPatternItems; ++k)
{
FTransformSRT3d PatternTransform = CurrentPattern[k];
FTransform WorldTransform;
ComputeWorldTransform(WorldTransform, (FTransform)ElementTransform, (FTransform)PatternTransform);
// We have world transforms of the components, but need their local transforms in the space of the
// final mesh, whose pivot is determined by CurrentStartFrameWorld. So we apply the inverse of that
// while appending them (the inverse FTransform is accurate in this case because
// CurrentStartFrameWorld can't be nonuniformly scaled).
FTransformSequence3d TransformSeq;
TransformSeq.Append(WorldTransform);
TransformSeq.Append(CurrentStartFrameWorld.ToFTransform().Inverse());
Mappings.Reset();
Editor.AppendMesh(&ElementMesh, Mappings,
[&](int, const FVector3d& Position) { return TransformSeq.TransformPosition(Position); },
[&](int, const FVector3d& Normal) { return TransformSeq.TransformNormal(Normal); } );
}
FCreateMeshObjectResult Result = EmitDynamicMeshActor(MoveTemp(CombinedPatternMesh),
FString::Printf(TEXT("Pattern_%d"), ElemIdx), CurrentStartFrameWorld.ToFTransform());
if (Result.IsOK())
{
NewActors.Add(Result.NewActor);
SetActorComponentsVisibility(Result.NewActor, false);
}
}
}
else if (Element.SourceStaticMesh != nullptr)
{
UStaticMeshComponent* SourceComponent = Cast<UStaticMeshComponent>(Elements[ElemIdx].SourceComponent);
if (bSeparateActorPerItem)
{
for (int32 k = 0; k < NumPatternItems; ++k)
{
FTransformSRT3d PatternTransform = CurrentPattern[k];
FTransform WorldTransform;
ComputeWorldTransform(WorldTransform, (FTransform)ElementTransform, (FTransform)PatternTransform);
FCreateActorParams SpawnInfo;
SpawnInfo.BaseName = FString::Printf(TEXT("Pattern_%d_%d"), ElemIdx, k);
SpawnInfo.TargetWorld = GetTargetWorld();
SpawnInfo.TemplateAsset = SourceComponent->GetStaticMesh();
SpawnInfo.Transform = WorldTransform;
FCreateActorResult Result = UE::Modeling::CreateNewActor(GetToolManager(), MoveTemp(SpawnInfo));
if (AStaticMeshActor* NewStaticMeshActor = Cast<AStaticMeshActor>(Result.NewActor); Result.IsOK() && NewStaticMeshActor)
{
UStaticMeshComponent* NewStaticMeshComponent = NewStaticMeshActor->GetStaticMeshComponent();
// Ensure the new static mesh component is properly configured based on the source component
NewStaticMeshComponent->SetStaticMesh(SourceComponent->GetStaticMesh());
NewStaticMeshComponent->SetWorldTransform(WorldTransform);
for (int32 j = 0; j < Element.SourceMaterials.Num(); ++j)
{
NewStaticMeshComponent->SetMaterial(j, Element.SourceMaterials[j]);
}
NewActors.Add(NewStaticMeshActor);
SetActorComponentsVisibility(NewStaticMeshActor, false);
}
}
}
else if (bCreateISMForStaticMeshes)
{
FActorSpawnParameters SpawnInfo;
AActor* NewActor = GetTargetWorld()->SpawnActor<AActor>(SpawnInfo);
if (NewActor != nullptr)
{
NewActors.Add(NewActor);
UInstancedStaticMeshComponent* ISMComponent = NewObject<UInstancedStaticMeshComponent>(NewActor);
ISMComponent->SetVisibility(false, bPropagateToChildren);
ISMComponent->SetFlags(RF_Transactional);
ISMComponent->bHasPerInstanceHitProxies = true;
ISMComponent->SetStaticMesh(Element.SourceStaticMesh);
for (int32 j = 0; j < Element.SourceMaterials.Num(); ++j)
{
ISMComponent->SetMaterial(j, Element.SourceMaterials[j]);
}
NewActor->SetRootComponent(ISMComponent);
ISMComponent->OnComponentCreated();
NewActor->AddInstanceComponent(ISMComponent);
NewActor->SetActorTransform(CurrentStartFrameWorld.ToFTransform());
for (int32 k = 0; k < NumPatternItems; ++k)
{
FTransformSRT3d PatternTransform = CurrentPattern[k];
FTransform WorldTransform;
ComputeWorldTransform(WorldTransform, (FTransform)ElementTransform, (FTransform)PatternTransform);
constexpr bool bTransformInWorldSpace = true;
ISMComponent->AddInstance(WorldTransform, bTransformInWorldSpace);
}
ISMComponent->RegisterComponent();
}
}
else
{
AStaticMeshActor* NewStaticMeshActor = nullptr;
UStaticMeshComponent* TemplateStaticMeshComponent = nullptr;
for (int32 k = 0; k < NumPatternItems; ++k)
{
FTransformSRT3d PatternTransform = CurrentPattern[k];
FTransform WorldTransform;
ComputeWorldTransform(WorldTransform, (FTransform)ElementTransform, (FTransform)PatternTransform);
if (k == 0)
{
FCreateActorParams SpawnInfo;
SpawnInfo.BaseName = FString::Printf(TEXT("Pattern_%d"), ElemIdx);
SpawnInfo.TargetWorld = GetTargetWorld();
SpawnInfo.TemplateAsset = SourceComponent->GetStaticMesh();
SpawnInfo.Transform = FTransform::Identity;
FCreateActorResult Result = UE::Modeling::CreateNewActor(GetToolManager(), MoveTemp(SpawnInfo));
if (NewStaticMeshActor = Cast<AStaticMeshActor>(Result.NewActor); Result.IsOK() && NewStaticMeshActor)
{
TemplateStaticMeshComponent = NewStaticMeshActor->GetStaticMeshComponent();
// Ensure the new static mesh component is properly configured based on the source component
TemplateStaticMeshComponent->SetStaticMesh(SourceComponent->GetStaticMesh());
TemplateStaticMeshComponent->SetWorldTransform(WorldTransform);
for (int32 j = 0; j < Element.SourceMaterials.Num(); ++j)
{
TemplateStaticMeshComponent->SetMaterial(j, Element.SourceMaterials[j]);
}
NewActors.Add(NewStaticMeshActor);
SetActorComponentsVisibility(NewStaticMeshActor, false);
}
else
{
break;
}
}
else
{
// Create new component based on TemplateStaticMeshComponent
UStaticMeshComponent* NewCloneComponent = DuplicateObject<UStaticMeshComponent>(TemplateStaticMeshComponent, NewStaticMeshActor);
NewCloneComponent->ClearFlags(RF_DefaultSubObject);
NewCloneComponent->SetupAttachment(TemplateStaticMeshComponent);
NewCloneComponent->OnComponentCreated();
NewStaticMeshActor->AddInstanceComponent(NewCloneComponent);
NewCloneComponent->RegisterComponent();
NewCloneComponent->SetWorldTransform(WorldTransform);
// Using SetVisibility here instead of SetActorComponentsVisibility because in this particular output
// case with large patterns, there could be a lot of redundant iteration over and casting for components
// which have already been set to invisible.
NewCloneComponent->SetVisibility(false, bPropagateToChildren);
}
}
}
}
}
// Make all components visible now that they have all been created
for (auto Actor : NewActors)
{
SetActorComponentsVisibility(Actor, true);
}
if (NewActors.Num() > 0)
{
ToolSelectionUtil::SetNewActorSelection(GetToolManager(), NewActors);
}
GetToolManager()->EndUndoTransaction();
}
#undef LOCTEXT_NAMESPACE