Skip to content

Commit c4678e7

Browse files
committed
Update + BestMatchAxis boilerplate
1 parent b96256d commit c4678e7

File tree

4 files changed

+311
-2
lines changed

4 files changed

+311
-2
lines changed

Source/PCGExtendedToolkit/Private/Geometry/PCGExGeo.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ namespace PCGExGeo
331331
}
332332
}
333333

334-
FBestFitPlane::FBestFitPlane(const TArrayView<FVector> InPositions)
334+
FBestFitPlane::FBestFitPlane(const TArrayView<const FVector> InPositions)
335335
{
336336
TRACE_CPUPROFILER_EVENT_SCOPE(FBestFitPlane::FBestFitPlane);
337337

@@ -357,6 +357,32 @@ namespace PCGExGeo
357357
}
358358
}
359359

360+
FBestFitPlane::FBestFitPlane(const TArrayView<const FVector2D> InPositions)
361+
{
362+
TRACE_CPUPROFILER_EVENT_SCOPE(FBestFitPlane::FBestFitPlane);
363+
364+
UE::Geometry::FOrientedBox3d OrientedBox{};
365+
UE::Geometry::TMinVolumeBox3<double> Box;
366+
367+
Centroid = FVector::ZeroVector;
368+
369+
Box.Solve(
370+
InPositions.Num(), [&](int32 i)
371+
{
372+
const FVector P = FVector(InPositions[i], 0);
373+
Centroid += P;
374+
return P;
375+
});
376+
377+
Centroid /= InPositions.Num();
378+
379+
if (Box.IsSolutionAvailable())
380+
{
381+
Box.GetResult(OrientedBox);
382+
ProcessBox(OrientedBox);
383+
}
384+
}
385+
360386
FVector FBestFitPlane::Normal() const { return Axis[2]; }
361387

362388
FTransform FBestFitPlane::GetTransform() const
@@ -384,6 +410,10 @@ namespace PCGExGeo
384410

385411
Algo::Sort(Swizzle, [&](const int32 A, const int32 B) { return Box.Extents[A] > Box.Extents[B]; });
386412

413+
Extents[0] = Box.Extents[Swizzle[0]];
414+
Extents[1] = Box.Extents[Swizzle[1]];
415+
Extents[2] = Box.Extents[Swizzle[2]];
416+
387417
// Pick raw axes
388418
FVector X = Box.Frame.GetAxis(Swizzle[0]); // Longest
389419
FVector Y = Box.Frame.GetAxis(Swizzle[1]); // Median
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright 2025 Timothé Lapetite and contributors
2+
// Released under the MIT license https://opensource.org/license/MIT/
3+
4+
#include "Transform/PCGExBestMatchAxis.h"
5+
6+
#include "Data/PCGExData.h"
7+
#include "Data/Matching/PCGExMatchRuleFactoryProvider.h"
8+
9+
#define LOCTEXT_NAMESPACE "PCGExBestMatchAxisElement"
10+
#define PCGEX_NAMESPACE BestMatchAxis
11+
12+
TArray<FPCGPinProperties> UPCGExBestMatchAxisSettings::InputPinProperties() const
13+
{
14+
TArray<FPCGPinProperties> PinProperties = Super::InputPinProperties();
15+
if (Mode == EPCGExBestMatchAxisTargetMode::ClosestTarget) { PCGEX_PIN_POINTS(PCGEx::SourceTargetsLabel, TEXT("Target points"), Required, {}) }
16+
return PinProperties;
17+
}
18+
19+
PCGEX_INITIALIZE_ELEMENT(BestMatchAxis)
20+
PCGEX_ELEMENT_BATCH_POINT_IMPL(BestMatchAxis)
21+
22+
bool FPCGExBestMatchAxisElement::Boot(FPCGExContext* InContext) const
23+
{
24+
if (!FPCGExPointsProcessorElement::Boot(InContext)) { return false; }
25+
26+
PCGEX_CONTEXT_AND_SETTINGS(BestMatchAxis)
27+
28+
if (Settings->Mode == EPCGExBestMatchAxisTargetMode::ClosestTarget)
29+
{
30+
Context->TargetsHandler = MakeShared<PCGExSampling::FTargetsHandler>();
31+
Context->TargetsHandler->Init(Context, PCGEx::SourceTargetsLabel);
32+
33+
Context->NumMaxTargets = Context->TargetsHandler->GetMaxNumTargets();
34+
if (!Context->NumMaxTargets)
35+
{
36+
if (!Settings->bQuietMissingInputError) { PCGE_LOG_C(Error, GraphAndLog, InContext, FTEXT("No targets (empty datasets)")); }
37+
return false;
38+
}
39+
40+
Context->TargetsHandler->SetDistances(Settings->DistanceDetails);
41+
Context->TargetsHandler->SetMatchingDetails(Context, &Settings->DataMatching);
42+
}
43+
44+
return true;
45+
}
46+
47+
bool FPCGExBestMatchAxisElement::ExecuteInternal(FPCGContext* InContext) const
48+
{
49+
TRACE_CPUPROFILER_EVENT_SCOPE(FPCGExBestMatchAxisElement::Execute);
50+
51+
PCGEX_CONTEXT_AND_SETTINGS(BestMatchAxis)
52+
PCGEX_EXECUTION_CHECK
53+
PCGEX_ON_INITIAL_EXECUTION
54+
{
55+
if (!Context->StartBatchProcessingPoints(
56+
[&](const TSharedPtr<PCGExData::FPointIO>& Entry) { return true; },
57+
[&](const TSharedPtr<PCGExPointsMT::IBatch>& NewBatch)
58+
{
59+
NewBatch->bSkipCompletion = true;
60+
}))
61+
{
62+
return Context->CancelExecution(TEXT("No data."));
63+
}
64+
}
65+
66+
PCGEX_POINTS_BATCH_PROCESSING(PCGExCommon::State_Done)
67+
68+
Context->MainPoints->StageOutputs();
69+
70+
return Context->TryComplete();
71+
}
72+
73+
namespace PCGExBestMatchAxis
74+
{
75+
FProcessor::~FProcessor()
76+
{
77+
}
78+
79+
bool FProcessor::Process(const TSharedPtr<PCGExMT::FTaskManager>& InAsyncManager)
80+
{
81+
TRACE_CPUPROFILER_EVENT_SCOPE(PCGExBestMatchAxis::Process);
82+
83+
if (!IProcessor::Process(InAsyncManager)) { return false; }
84+
85+
PCGEX_INIT_IO(PointDataFacade->Source, PCGExData::EIOInit::Duplicate)
86+
87+
if (Context->TargetsHandler)
88+
{
89+
IgnoreList.Add(PointDataFacade->GetIn());
90+
91+
if (PCGExMatching::FMatchingScope MatchingScope(Context->InitialMainPointsNum, true);
92+
!Context->TargetsHandler->PopulateIgnoreList(PointDataFacade->Source, MatchingScope, IgnoreList))
93+
{
94+
if (!Context->TargetsHandler->HandleUnmatchedOutput(PointDataFacade, true)) { PCGEX_INIT_IO(PointDataFacade->Source, PCGExData::EIOInit::Forward) }
95+
return false;
96+
}
97+
}else
98+
{
99+
MatchGetter = Settings->GetValueSettingMatch();
100+
if (!MatchGetter->Init(PointDataFacade)) { return false; }
101+
}
102+
103+
PCGEX_INIT_IO(PointDataFacade->Source, PCGExData::EIOInit::Duplicate)
104+
105+
EPCGPointNativeProperties AllocateFor = EPCGPointNativeProperties::None;
106+
AllocateFor |= EPCGPointNativeProperties::Transform;
107+
108+
PointDataFacade->GetOut()->AllocateProperties(AllocateFor);
109+
110+
StartParallelLoopForPoints();
111+
112+
return true;
113+
}
114+
115+
void FProcessor::ProcessPoints(const PCGExMT::FScope& Scope)
116+
{
117+
TRACE_CPUPROFILER_EVENT_SCOPE(PCGEx::BestMatchAxis::ProcessPoints);
118+
119+
PointDataFacade->Fetch(Scope);
120+
121+
UPCGBasePointData* OutPoints = PointDataFacade->GetOut();
122+
TPCGValueRange<FTransform> OutTransforms = OutPoints->GetTransformValueRange(false);
123+
124+
const bool bUseTargets = Settings->Mode == EPCGExBestMatchAxisTargetMode::ClosestTarget;
125+
126+
PCGEX_SCOPE_LOOP(Index)
127+
{
128+
FVector AxisToMatch = FVector::ForwardVector;
129+
130+
if (bUseTargets)
131+
{
132+
PCGExData::FConstPoint TargetPoint;
133+
double Distance = MAX_dbl;
134+
Context->TargetsHandler->FindClosestTarget(PointDataFacade->GetInPoint(Index), TargetPoint, Distance, &IgnoreList);
135+
if (TargetPoint.Index == -1) { continue; }
136+
}
137+
else if (Settings->Mode == EPCGExBestMatchAxisTargetMode::Direction)
138+
{
139+
}
140+
else if (Settings->Mode == EPCGExBestMatchAxisTargetMode::LookAtRelativePosition)
141+
{
142+
}
143+
else if (Settings->Mode == EPCGExBestMatchAxisTargetMode::LookAtWorldPosition)
144+
{
145+
}
146+
147+
/*
148+
FVector Offset;
149+
OutTransforms[Index].SetLocation(UVW.GetPosition(Index, Offset));
150+
OutBoundsMin[Index] += Offset;
151+
OutBoundsMax[Index] += Offset;
152+
*/
153+
}
154+
}
155+
}
156+
157+
158+
#undef LOCTEXT_NAMESPACE
159+
#undef PCGEX_NAMESPACE

Source/PCGExtendedToolkit/Public/Geometry/PCGExGeo.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,11 @@ namespace PCGExGeo
195195

196196
explicit FBestFitPlane(const TConstPCGValueRange<FTransform>& InTransforms);
197197
explicit FBestFitPlane(const TConstPCGValueRange<FTransform>& InTransforms, TArrayView<int32> InIndices);
198-
explicit FBestFitPlane(const TArrayView<FVector> InPositions);
198+
explicit FBestFitPlane(const TArrayView<const FVector> InPositions);
199+
explicit FBestFitPlane(const TArrayView<const FVector2D> InPositions);
199200

200201
FVector Centroid = FVector::ZeroVector;
202+
FVector Extents = FVector::OneVector;
201203

202204
int32 Swizzle[3] = {0, 1, 2};
203205
FVector Axis[3] = {FVector::ForwardVector, FVector::RightVector, FVector::UpVector};
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 Timothé Lapetite and contributors
2+
// Released under the MIT license https://opensource.org/license/MIT/
3+
4+
#pragma once
5+
6+
#include "CoreMinimal.h"
7+
#include "PCGExGlobalSettings.h"
8+
9+
#include "PCGExPointsProcessor.h"
10+
#include "PCGExTransform.h"
11+
#include "Data/Matching/PCGExMatching.h"
12+
13+
14+
#include "PCGExBestMatchAxis.generated.h"
15+
16+
class FPCGExComputeIOBounds;
17+
18+
UENUM()
19+
enum class EPCGExBestMatchAxisTargetMode : uint8
20+
{
21+
Direction = 0 UMETA(DisplayName = "Direction", ToolTip="Best match against a direction vector."),
22+
LookAtWorldPosition = 1 UMETA(DisplayName = "Look at Position (World)", ToolTip="Best match against the look at vector toward a world position."),
23+
LookAtRelativePosition = 2 UMETA(DisplayName = "Look at Position (Relative)", ToolTip="Best match against the look at vector toward a relative position."),
24+
ClosestTarget = 3 UMETA(DisplayName = "Look at Closest Target", ToolTip="Best match against the look at vector toward the closest target point.")
25+
};
26+
27+
UCLASS(Hidden, BlueprintType, ClassGroup = (Procedural), Category="PCGEx|Misc", meta=(PCGExNodeLibraryDoc="transform/move-pivot"))
28+
class UPCGExBestMatchAxisSettings : public UPCGExPointsProcessorSettings
29+
{
30+
GENERATED_BODY()
31+
32+
public:
33+
//~Begin UPCGSettings
34+
#if WITH_EDITOR
35+
PCGEX_NODE_INFOS(BestMatchAxis, "Best Match Axis", "Rotate a point or transform to closely match an input direction (or look at location) but preserve orthogonality.");
36+
virtual FLinearColor GetNodeTitleColor() const override { return GetDefault<UPCGExGlobalSettings>()->NodeColorTransform; }
37+
#endif
38+
39+
protected:
40+
virtual TArray<FPCGPinProperties> InputPinProperties() const override;
41+
virtual FPCGElementPtr CreateElement() const override;
42+
//~End UPCGSettings
43+
44+
public:
45+
46+
/** Drive the best match axis */
47+
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta=(PCG_Overridable))
48+
EPCGExBestMatchAxisTargetMode Mode = EPCGExBestMatchAxisTargetMode::Direction;
49+
50+
51+
/** Up vector source.*/
52+
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta=(PCG_Overridable, EditCondition="Mode != EPCGExBestMatchAxisTargetMode::ClosestTarget", EditConditionHides))
53+
EPCGExInputValueType MatchInput = EPCGExInputValueType::Attribute;
54+
55+
/** The attribute or property on selected source to use as Up vector for the look at transform.*/
56+
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta=(PCG_Overridable, DisplayName=" └─ Match (Attr)", EditCondition="Mode != EPCGExBestMatchAxisTargetMode::ClosestTarget && MatchInput != EPCGExInputValueType::Constant", EditConditionHides))
57+
FPCGAttributePropertyInputSelector MatchSource;
58+
59+
/** The constant to use as Up vector for the look at transform.*/
60+
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta=(PCG_Overridable, DisplayName=" └─ Match", EditCondition="Mode != EPCGExBestMatchAxisTargetMode::ClosestTarget && MatchInput == EPCGExInputValueType::Constant", EditConditionHides))
61+
FVector MatchConstant = FVector::UpVector;
62+
63+
PCGEX_SETTING_VALUE_GET(Match, FVector, MatchInput, MatchSource, MatchConstant)
64+
65+
// TODO : Support attribute mutation such as transform, rotator, vector
66+
// TODO : Auto-pick axis based on unsigned dot product (so we only mutate where it make the most meaingful)
67+
68+
/** If enabled, allows you to filter out which targets get sampled by which data */
69+
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta=(PCG_Overridable, EditCondition="Mode == EPCGExBestMatchAxisTargetMode::ClosestTarget", EditConditionHides))
70+
FPCGExMatchingDetails DataMatching = FPCGExMatchingDetails(EPCGExMatchingDetailsUsage::Sampling);
71+
72+
/** Distance method to be used for source & target points. */
73+
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta=(PCG_Overridable, EditCondition="Mode == EPCGExBestMatchAxisTargetMode::ClosestTarget", EditConditionHides))
74+
FPCGExDistanceDetails DistanceDetails;
75+
76+
private:
77+
friend class FPCGExBestMatchAxisElement;
78+
};
79+
80+
struct FPCGExBestMatchAxisContext final : FPCGExPointsProcessorContext
81+
{
82+
friend class FPCGExBestMatchAxisElement;
83+
84+
TSharedPtr<PCGExSampling::FTargetsHandler> TargetsHandler;
85+
int32 NumMaxTargets = 0;
86+
87+
protected:
88+
PCGEX_ELEMENT_BATCH_POINT_DECL
89+
};
90+
91+
class FPCGExBestMatchAxisElement final : public FPCGExPointsProcessorElement
92+
{
93+
protected:
94+
PCGEX_ELEMENT_CREATE_CONTEXT(BestMatchAxis)
95+
96+
virtual bool Boot(FPCGExContext* InContext) const override;
97+
virtual bool ExecuteInternal(FPCGContext* Context) const override;
98+
};
99+
100+
namespace PCGExBestMatchAxis
101+
{
102+
class FProcessor final : public PCGExPointsMT::TProcessor<FPCGExBestMatchAxisContext, UPCGExBestMatchAxisSettings>
103+
{
104+
TSet<const UPCGData*> IgnoreList;
105+
TSharedPtr<PCGExDetails::TSettingValue<FVector>> MatchGetter;
106+
107+
public:
108+
explicit FProcessor(const TSharedRef<PCGExData::FFacade>& InPointDataFacade):
109+
TProcessor(InPointDataFacade)
110+
{
111+
}
112+
113+
virtual ~FProcessor() override;
114+
115+
virtual bool Process(const TSharedPtr<PCGExMT::FTaskManager>& InAsyncManager) override;
116+
virtual void ProcessPoints(const PCGExMT::FScope& Scope) override;
117+
};
118+
}

0 commit comments

Comments
 (0)