UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
KMeans.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Math/RandomStream.h"
6#include "MathUtil.h"
7#include "VectorTypes.h"
8#include "MatrixTypes.h"
11#include "Async/ParallelFor.h"
12
13namespace UE
14{
15namespace Geometry
16{
17
18using namespace UE::Math;
19
20// Not all platforms support c++20 or have their standard library containing floating atomic operations,
21// which is required for using std::atomic::+= opertor on floating types
22#define PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD (PLATFORM_WINDOWS && (__cplusplus >= 202002L))
23
24// Abstract array of positions for running the KMeans algorithm in serial or parallel
25template<typename TVectorType, bool bUseAtomicType>
27{
28private:
29 // Atomic values for parallel algorithm
30 TStaticArray<TArray<std::atomic<typename TVectorType::FReal>>, TVectorType::NumComponents> AtomicValues;
31
32 // Regular values for serial algorithm
34public:
36 {
37 #if PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD
39 {
40 for (uint32 ComponentIndex = 0; ComponentIndex < TVectorType::NumComponents; ++ComponentIndex)
41 {
42 AtomicValues[ComponentIndex].SetNumZeroed(Num);
43 }
44 }
45 else
46 #endif
47 {
48 Values.SetNumZeroed(Num);
49 }
50 }
51
52 void Assign(int32 Index, const TVectorType& In)
53 {
54 #if PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD
56 {
57 for (uint32 ComponentIndex = 0; ComponentIndex < TVectorType::NumComponents; ++ComponentIndex)
58 {
59 AtomicValues[ComponentIndex][Index] = In[ComponentIndex];
60 }
61 }
62 else
63 #endif
64 {
65 Values[Index] = In;
66 }
67 }
68
70 {
71 #if PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD
73 {
74 TVectorType Out;
75 for (uint32 ComponentIndex = 0; ComponentIndex < TVectorType::NumComponents; ++ComponentIndex)
76 {
77 Out[ComponentIndex] = AtomicValues[ComponentIndex][Index] / InDivider;
78 }
79 return Out;
80 }
81 else
82 #endif
83 {
84 return Values[Index] / InDivider;
85 }
86 }
87
89 {
90 #if PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD
92 {
93 for (uint32 ComponentIndex = 0; ComponentIndex < TVectorType::NumComponents; ++ComponentIndex)
94 {
95 AtomicValues[ComponentIndex][Index].fetch_add(In[ComponentIndex]);
96 }
97 }
98 else
99 #endif
100 {
101 Values[Index] += In;
102 }
103 }
104
106 {
107 #if PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD
108 if (bUseAtomicType)
109 {
110 for (uint32 ComponentIndex = 0; ComponentIndex < TVectorType::NumComponents; ++ComponentIndex)
111 {
112 AtomicValues[ComponentIndex].RemoveAtSwap(Index, AllowShrinking);
113 }
114 }
115 else
116 #endif
117 {
119 }
120 }
121};
122
124{
126
127 // Max iterations of K-Means clustering. Will use fewer iterations if the clustering method converges.
129
130 // Random Seed used to initialize clustering (if InitialCenters are not provided)
132
134
135 // Mapping from input points to cluster IDs
137
138 // Number of points in each cluster
140
151 template<typename TVectorType = FVector, bool bRunParallel=false>
156 {
157 constexpr bool bRunParallelIfCompatible = PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD && bRunParallel && std::atomic<typename TVectorType::FReal>::is_always_lock_free;
158
159 if (PointsToCluster.IsEmpty())
160 {
161 // nothing to cluster
162 return 0;
163 }
164
165 // Initialize cluster centers
169 {
171 UseCenters->Reset();
172 }
173 if (InitialCenters.IsEmpty())
174 {
177 Ordering.SetNumUninitialized(OrderingNum);
178 for (int32 Idx = 0; Idx < OrderingNum; ++Idx)
179 {
180 Ordering[Idx] = Idx;
181 }
182 // Shuffle the first NumClusters indices and use them as the initial centers
183 NumClusters = FMath::Min(PointsToCluster.Num(), NumClusters);
184 UseCenters->Reserve(NumClusters);
186 for (int32 Idx = 0; Idx < NumClusters; ++Idx)
187 {
188 Swap(Ordering[Idx], Ordering[Idx + RandomStream.RandHelper(OrderingNum - Idx)]);
190 }
191 }
192 else
193 {
194 // Note: We intentionally do not check if more centers were provided than points here.
195 // The excess centers will instead be removed below when no points are assigned to them.
196 UseCenters->Append(InitialCenters);
197 }
198
199 NumClusters = UseCenters->Num();
201 ClusterSizes.SetNumZeroed(NumClusters);
202
203 if (NumClusters == 0)
204 {
205 return NumClusters;
206 }
207 else if (NumClusters == 1)
208 {
209 for (int32 PointIdx = 0; PointIdx < PointsToCluster.Num(); ++PointIdx)
210 {
211 ClusterIDs[PointIdx] = 0;
212 }
214 return NumClusters;
215 }
216
217
219 NextCenters.SetNumZeroed(NumClusters);
220
221 TArray<int32> ClusterIDRemap; // array to use if we need to remap cluster IDs due to an empty cluster
222
223 int32 UseMaxIterations = FMath::Max(1, MaxIterations); // always use at least one iteration to make sure the initial assignment happens
224 for (int32 Iterations = 0; Iterations < UseMaxIterations; ++Iterations)
225 {
226 bool bClustersChanged = Iterations == 0; // clusters always change on first iteration; otherwise we must check
228 {
230 [
231 this,
232 Iterations,
235 &UseCenters,
237 ] (uint32 PointIdx)
238 {
239 const TVectorType& Point = PointsToCluster[PointIdx];
240 double ClosestDistSq = (double)UE::Geometry::DistanceSquared(Point, (*UseCenters)[0]);
241 int32 ClosestCenter = 0;
242 for (int32 CenterIdx = 1; CenterIdx < UseCenters->Num(); ++CenterIdx)
243 {
244 double DistSq = UE::Geometry::DistanceSquared(PointsToCluster[PointIdx], (*UseCenters)[CenterIdx]);
245 if (DistSq < ClosestDistSq)
246 {
247 ClosestDistSq = DistSq;
248 ClosestCenter = CenterIdx;
249 }
250 }
251
253 if (Iterations > 0)
254 {
258 {
259 bClustersChanged = true;
260 }
261 }
262
264 NextCenters.Accumulate(ClosestCenter, Point);
265 });
266 }
267 else
268 {
269 for (int32 PointIdx = 0; PointIdx < PointsToCluster.Num(); ++PointIdx)
270 {
274 for (int32 CenterIdx = 1; CenterIdx < UseCenters->Num(); ++CenterIdx)
275 {
277 if (DistSq < ClosestDistSq)
278 {
281 }
282 }
283
285 if (Iterations > 0)
286 {
290 {
291 bClustersChanged = true;
292 }
293 }
294
296 NextCenters.Accumulate(ClosestCenter, Point);
297 }
298 } // bParallel
299
300 // Stop iterating if clusters are unchanged
301 if (!bClustersChanged)
302 {
303 break;
304 }
305
306 // Update cluster centers and detect/delete any empty clusters
307 bool bDeletedClusters = false;
308 for (int32 ClusterIdx = 0; ClusterIdx < UseCenters->Num(); ++ClusterIdx)
309 {
310 if (ClusterSizes[ClusterIdx] > 0)
311 {
312 (*UseCenters)[ClusterIdx] = NextCenters.DivideBy(ClusterIdx, int32(ClusterSizes[ClusterIdx]));
313 NextCenters.Assign(ClusterIdx, TVectorType::ZeroVector);
314 }
315 else
316 {
317 if (!bDeletedClusters)
318 {
320 for (int32 Idx = 0; Idx < UseCenters->Num(); ++Idx)
321 {
322 ClusterIDRemap[Idx] = Idx;
323 }
324 }
325 bDeletedClusters = true;
326 ClusterIDRemap[ClusterSizes.Num() - 1] = ClusterIdx;
328 UseCenters->RemoveAtSwap(ClusterIdx, EAllowShrinking::No);
329 NextCenters.RemoveAtSwap(ClusterIdx, EAllowShrinking::No);
330 }
331 }
333 {
336 for (int32& ClusterID : ClusterIDs)
337 {
339 }
340 }
341 }
342
343 return ClusterSizes.Num();
344 }
345
349 template<typename TVectorType = FVector>
351 {
352 FPriorityOrderPoints OrderPoints;
353 OrderPoints.ComputeUniformSpaced(PointsToCluster, TArrayView<const float>(), NumClusters);
354 int32 NumOut = FMath::Min(NumClusters, OrderPoints.Order.Num());
355 OutCenters.Reset(NumOut);
356 for (int32 OrderIdx = 0; OrderIdx < NumOut; ++OrderIdx)
357 {
358 OutCenters.Add(PointsToCluster[OrderPoints.Order[OrderIdx]]);
359 }
360 }
361
362
364};
365
366} // end namespace UE::Geometry
367} // end namespace UE
EAllowShrinking
Definition AllowShrinking.h:10
#define checkSlow(expr)
Definition AssertionMacros.h:332
void ParallelFor(int32 Num, TFunctionRef< void(int32)> Body, bool bForceSingleThread, bool bPumpRenderingThread=false)
Definition ParallelFor.h:481
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define PLATFORM_SUPPORT_FLOATING_INTERLOCKED_ADD
Definition KMeans.h:22
@ Num
Definition MetalRHIPrivate.h:234
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition ArrayView.h:139
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void SetNumZeroed(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2340
void Reset(SizeType NewSize=0)
Definition Array.h:2246
UE_FORCEINLINE_HINT void RemoveAtSwap(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2185
UE_REWRITE bool IsEmpty() const
Definition Array.h:1133
void Init(const ElementType &Element, SizeType Number)
Definition Array.h:3043
void SetNumUninitialized(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2369
Definition StaticArray.h:26
T DistanceSquared(const UE::Math::TVector2< T > &V1, const UE::Math::TVector2< T > &V2)
Definition VectorTypes.h:82
Definition Sphere.cpp:10
Definition AdvancedWidgetsModule.cpp:13
U16 Index
Definition radfft.cpp:71
Definition RandomStream.h:20
Definition KMeans.h:124
TArray< std::atomic< int32 > > ClusterSizes
Definition KMeans.h:139
TArray< int32 > ClusterIDs
Outputs.
Definition KMeans.h:136
void GetUniformSpacedInitialCenters(TArrayView< const TVectorType > PointsToCluster, int32 NumClusters, TArray< TVectorType > &OutCenters)
Definition KMeans.h:350
int32 RandomSeed
Definition KMeans.h:131
int32 MaxIterations
Parameters.
Definition KMeans.h:128
int32 ComputeClusters(TArrayView< const TVectorType > PointsToCluster, int32 NumClusters, TArrayView< const TVectorType > InitialCenters=TArrayView< const TVectorType >(), TArray< TVectorType > *OutClusterCenters=nullptr)
Definition KMeans.h:152
Definition PriorityOrderPoints.h:26
Definition KMeans.h:27
void SetNumZeroed(uint32 Num)
Definition KMeans.h:35
void RemoveAtSwap(size_t Index, EAllowShrinking AllowShrinking)
Definition KMeans.h:105
void Accumulate(int32 Index, const TVectorType &In)
Definition KMeans.h:88
void Assign(int32 Index, const TVectorType &In)
Definition KMeans.h:52
TVectorType DivideBy(int32 Index, int32 InDivider)
Definition KMeans.h:69