UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
BSpline.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include <cmath>
6#include <limits>
7#include "Spline.h"
9#include "SplineMath.h"
10
11namespace UE
12{
13namespace Geometry
14{
15namespace Spline
16{
17
18struct UE_EXPERIMENTAL(5.7, "New spline APIs are experimental.") FKnot;
19template <typename VALUETYPE, int32 DEGREE> class UE_EXPERIMENTAL(5.7, "New spline APIs are experimental.") TBSpline;
20
21enum class UE_EXPERIMENTAL(5.7, "New spline APIs are experimental.") EParameterizationPolicy
22{
24 Uniform,
25
28
31};
32
33struct FKnot
34{
35 float Value;
37
39 : Value(0.0f)
40 , Multiplicity(1)
41 {
42 }
43
49
56 {
57 TArray<float> FlatKnots;
58 for (const FKnot& Knot : PairKnots)
59 {
60 for (uint32 i = 0; i < Knot.Multiplicity; ++i)
61 {
62 FlatKnots.Add(Knot.Value);
63 }
64 }
65 return FlatKnots;
66 }
67
68 static bool IsValidKnotVector(const TArray<FKnot>& InKnots, int32 Degree)
69 {
71 // Need at least 2*(degree+1) knots
72 if (FlatKnots.Num() < 2 * (Degree + 1))
73 {
74 return false;
75 }
76
77 // Check monotonicity
78 for (int32 i = 1; i < FlatKnots.Num(); ++i)
79 {
80 if (FlatKnots[i] < FlatKnots[i - 1])
81 {
82 return false;
83 }
84 }
85
86 return true;
87 }
88
89 bool operator==(const FKnot& Other) const
90 {
91 return FMath::IsNearlyEqual(Value, Other.Value) && Multiplicity == Other.Multiplicity;
92 }
93
95 {
96 Ar << Knot.Value;
97 Ar << Knot.Multiplicity;
98 return Ar;
99 }
100};
101
102// Generate compile-time type ID for this BSpline specialization
103// Using degree in the implementation name for uniqueness
104template <int Degree>
106{
107 static constexpr const TCHAR* Name = TEXT("BSpline");
108};
109
110template <>
112{
113 static constexpr const TCHAR* Name = TEXT("BSpline2");
114};
115
116template <>
118{
119 static constexpr const TCHAR* Name = TEXT("BSpline3");
120};
121
122template <typename VALUETYPE, int32 DEGREE>
124 : public TSplineInterface<VALUETYPE>,
125 private TSelfRegisteringSpline<TBSpline<VALUETYPE, DEGREE>, VALUETYPE>
126{
127public:
128 static constexpr int32 Degree = DEGREE;
130
131 static constexpr int32 WindowSize = Degree + 1;
133
137 );
138
139 TBSpline() = default;
140 virtual ~TBSpline() override = default;
141
142 template <typename T, typename = void>
143 struct THasToString : std::false_type
144 {
145 };
146
147 template <typename T>
148 struct THasToString<T, std::void_t<decltype(std::declval<T>().ToString())>> : std::true_type
149 {
150 };
151
152 void Dump() const
153 {
154 if constexpr (std::is_floating_point_v<ValueType>)
155 {
156 UE_LOG(LogSpline, Verbose, TEXT("Values:"));
157 for (int32 Index = 0; Index < Values.Num(); ++Index)
158 {
159 UE_LOG(LogSpline, Verbose, TEXT(" [%d] %f"), Index, Values[Index]);
160 }
161 }
162 else if constexpr (THasToString<ValueType>::value)
163 {
164 UE_LOG(LogSpline, Verbose, TEXT("Values:"));
165 for (int32 Index = 0; Index < Values.Num(); ++Index)
166 {
167 UE_LOG(LogSpline, Verbose, TEXT(" [%d] %s"), Index, *Values[Index].ToString());
168 }
169 }
170
171 UE_LOG(LogSpline, Verbose, TEXT("Knots:"));
173 }
174
175 // ISplineInterface Implementation
176 virtual void Clear() override
177 {
178 Values.Empty();
179 PairKnots.Empty();
180 }
181
182 virtual bool IsEqual(const ISplineInterface* OtherSpline) const override
183 {
184 if (OtherSpline->GetTypeId() == GetTypeId())
185 {
186 const TBSpline* Other = static_cast<const TBSpline*>(OtherSpline);
187 return operator==(*Other);
188 }
189
190 return false;
191 }
192
193 virtual bool Serialize(FArchive& Ar) override
194 {
195 // Call immediate parent's Serialize
197 {
198 return false;
199 }
200
201 Ar << Values;
202 Ar << PairKnots;
203 Ar << bIsClosedLoop;
204 Ar << bClampEnds;
206 return true;
207 }
208
209 bool operator==(const TBSpline& Other) const
210 {
211 return Values == Other.Values &&
212 PairKnots == Other.PairKnots &&
213 bIsClosedLoop == Other.bIsClosedLoop &&
214 bClampEnds == Other.bClampEnds;
215 }
216
218 {
219 BSpline.Serialize(Ar);
220 return Ar;
221 }
222
223 virtual FInterval1f GetParameterSpace() const override
224 {
225 return GetKnotRange();
226 }
227
228 virtual void SetClosedLoop(bool bClosed) override
229 {
230 bIsClosedLoop = bClosed;
231 }
232
233 virtual bool IsClosedLoop() const override
234 {
235 return bIsClosedLoop;
236 }
237
238 virtual TUniquePtr<ISplineInterface> Clone() const override
239 {
241
242 // Copy all member variables
243 Clone->Values = this->Values;
244 Clone->PairKnots = this->PairKnots;
245 Clone->bIsClosedLoop = this->bIsClosedLoop;
246 Clone->bClampEnds = this->bClampEnds;
247
248 // Copy infinity modes
249 Clone->PreInfinityMode = this->PreInfinityMode;
250 Clone->PostInfinityMode = this->PostInfinityMode;
251
252 return Clone;
253 }
254
256 virtual int32 GetNumberOfSegments() const override
257 {
258 // todo: implement
259 ensureAlwaysMsgf(false, TEXT("TBSpline::GetNumberOfSegments is unimplemented!"));
260 return INDEX_NONE;
261 }
262
268 virtual FInterval1f GetSegmentParameterRange(int32 SegmentIndex) const override
269 {
270 // todo: implement
271 ensureAlwaysMsgf(false, TEXT("TBSpline::GetSegmentParameterRange is unimplemented!"));
272 return FInterval1f();
273 }
274
275 // TSplineInterface Implementation
276 virtual ValueType EvaluateImpl(float Parameter) const override
277 {
278 int32 NumValues = NumKeys();
279 if (NumValues == 0) return ValueType();
280 if (NumValues == 1) return GetValue(0);
281
282 FWindow Window = FindWindow(Parameter);
283
284 // If FindWindow fails, it will return an array of nullptr.
285 if (Window[0] == nullptr) return ValueType();
286
287 return InterpolateWindow(Window, Parameter);
288 }
289
290 virtual float FindNearest(const ValueType& Point, float& OutSquaredDistance) const override
291 {
292 return 0;/* todo */
293 }
294
296 {
297 return Values.Num();
298 }
299
300 const ValueType& GetValue(int32 Idx) const
301 {
302 return Values[Idx];
303 }
304
305 int32 AddValue(const ValueType& NewValue)
306 {
307 return Values.Add(NewValue);
308 }
309
310 bool SetValue(int32 Idx, const ValueType& NewValue)
311 {
312 if (!Values.IsValidIndex(Idx))
313 {
314 return false;
315 }
316 Values[Idx] = NewValue;
317 return true;
318 }
319
320 int32 InsertValue(int32 Idx, const ValueType& NewValue)
321 {
323 {
324 return Values.Add(NewValue);
325 }
326 return Values.Insert(NewValue, Idx);
327 }
328
329 virtual bool RemoveValue(int32 Index)
330 {
332 {
334 return true;
335 }
336 return false;
337 }
338
339 // Parameterization methods
341 {
342 UE_LOG(LogSpline, Warning, TEXT("SetParameter is disabled. Use Reparameterize() or a linear spline if you need manual control."));
343 return INDEX_NONE;
344 }
345
346 // Returns parameter value for a control point index using Greville Abscissae
347 virtual float GetParameter(int32 Index) const
348 {
351 return 0.0f;
352
353 float Sum = 0.0f;
354 for (int j = 1; j <= Degree; ++j)
355 {
356 Sum += FlatKnots[Index + j];
357 }
358 return Sum / static_cast<float>(Degree);
359 }
360
361 // Find the control point index and local parameter for global parameter
362 virtual int32 FindIndexForParameter(float Parameter, float& OutLocalParam) const
363 {
366
367 if (Knots.IsEmpty())
368 return INDEX_NONE;
369
370 // Clamp parameter to valid range
371 const float FirstValidKnot = Knots[Degree];
372 const float LastValidKnot = Knots[Knots.Num() - Degree - 1];
373
374 // Check for exact match with last knot (for insertion at the end)
375 if (FMath::IsNearlyEqual(Parameter, LastValidKnot))
376 {
377 OutLocalParam = 0.0f; // No interpolation needed for exact match
378 return NumKeys(); // Return index for appending
379 }
380 // Handle parameter near the end
381 else if (Parameter >= LastValidKnot - UE_SMALL_NUMBER)
382 {
383 OutLocalParam = 1.0f;
384 return NumKeys() - 2; // Last segment index
385 }
386
387 if (Parameter <= FirstValidKnot + UE_SMALL_NUMBER)
388 {
389 OutLocalParam = 0.0f;
390 return 0;
391 }
392
393
394 // Find the appropriate span
395 int32 Span = FindKnotSpan(Parameter);
396 if (Span < Degree || Span >= Knots.Num() - 1)
397 return INDEX_NONE;
398
399 int32 Index = Span - Degree;
400
401 // Calculate local parameter within span, with protection against near-zero denominators
402 float SpanStart = Knots[Span];
403 float SpanEnd = Knots[Span + 1];
404 float SpanLength = SpanEnd - SpanStart;
405
407 {
408 // For nearly identical knots, use 0 or 1 based on which end is closer
409 OutLocalParam = (Parameter - SpanStart < SpanEnd - Parameter)
410 ? 0.0f
411 : 1.0f;
412 }
413 else
414 {
415 OutLocalParam = (Parameter - SpanStart) / SpanLength;
416 }
417
418 return Index;
419 }
420
421 // Knot vector management
423 {
424 return PairKnots;
425 }
426
427 // Get the pair-based knot vector
429 {
430 return PairKnots;
431 }
432
434 {
435 PairKnots.Reset();
437 }
438
445 {
446 if (PairKnots.IsValidIndex(KnotIndex))
447 {
448 return PairKnots[KnotIndex].Multiplicity;
449 }
450
451 return 0;
452 }
453
460 {
462
463 // Validate knot multiplicities and calculate total expanded knots
465 for (const FKnot& Knot : NewKnots)
466 {
467 if (Knot.Multiplicity == 0)
468 {
469 UE_LOG(LogSpline, Warning, TEXT("Invalid knot with zero multiplicity."));
470 return false;
471 }
472 TotalExpandedKnots += Knot.Multiplicity;
473 }
474
476 {
477 UE_LOG(LogSpline, Warning, TEXT("Invalid knot vector size. Need %d knots, got %d."),
479 return false;
480 }
481
482 // Verify knot values are non-decreasing (strict comparison, no epsilon)
483 for (int32 i = 1; i < NewKnots.Num(); ++i)
484 {
485 if (NewKnots[i].Value < NewKnots[i - 1].Value)
486 {
487 UE_LOG(LogSpline, Warning, TEXT("Invalid knot vector: knot values must be non-decreasing."));
488 return false;
489 }
490 }
491
492 // Directly assign the knots - no epsilon comparison needed
494
495 // Mark the flat knots cache as dirty
497
498 UE_LOG(LogSpline, Verbose, TEXT("Set Custom knot vector - Points: %d, Degree: %d, Total Knots: %d, Unique Knots: %d"),
501
502 return true;
503 }
504
512 {
513 const TArray<ValueType>& Points = Values;
514 int32 NumValues = Points.Num();
515 if (NumValues < Degree + 1)
516 {
517 UE_LOG(LogSpline, Warning, TEXT("Not enough points for B-spline: %d (need >= %d)"),
518 NumValues, Degree + 1);
519 return;
520 }
521
522 const int32 KnotCount = NumValues + Degree + 1;
523 PairKnots.Reset();
524
526 {
527 case EParameterizationPolicy::Uniform:
529 break;
530 case EParameterizationPolicy::ChordLength:
532 break;
533 case EParameterizationPolicy::Centripetal:
535 break;
536 }
537
538 // Mark the flat knots cache as dirty
540
541 UE_LOG(LogSpline, Verbose, TEXT("Updated knot vector - Points: %d, Degree: %d, Knots: %d"),
542 Points.Num(), Degree, PairKnots.Num());
543 }
544
550 {
552 }
553
554 bool IsClampedEnds() const
555 {
556 return bClampEnds;
557 }
558
559 // Get the valid parameter range in knot space
561 {
563 if (FlatKnots.Num() - Degree - 1 < 0)
564 {
565 return FInterval1f::Empty();
566 }
567 return FInterval1f(PairKnots[0].Value, PairKnots.Last().Value);
568 }
569
570protected:
576 void SetKnot(int32 KnotIdx, float NewValue)
577 {
578 if (KnotIdx < 0 || KnotIdx >= PairKnots.Num() || PairKnots[KnotIdx].Value == NewValue)
579 {
580 // Nothing to replace
581 return;
582 }
583
584 PairKnots[KnotIdx].Value = NewValue;
585 }
586
594 {
596 {
597 return false;
598 }
599 PairKnots.RemoveAt(KnotIdx);
600 // Mark the flat knots cache as dirty
602 return true;
603 }
604
612 {
613 if (KnotIdxA < 0 || KnotIdxB < 0 || KnotIdxA >= PairKnots.Num() || KnotIdxB >= PairKnots.Num())
614 {
615 return;
616 }
617
621 }
622
629 {
630 // Binary search the knot vector:
631
632 int32 Low = 0; // This will be our insert index
633 int32 High = PairKnots.Num() - 1;
634
635 while (Low <= High)
636 {
637 const int32 Mid = Low + (High - Low) / 2;
638
639 if (PairKnots[Mid].Value == InKnot.Value)
640 {
641 UE_LOG(LogSpline, Warning, TEXT("Knot insertion failed, knot already exists!"));
642 return false;
643 }
644 else if (PairKnots[Mid].Value < InKnot.Value)
645 {
646 Low = Mid + 1;
647 }
648 else
649 {
650 High = Mid - 1;
651 }
652 }
653
654 PairKnots.Insert(InKnot, Low);
656
657 return true;
658 }
659
673
680 {
681 const float& DesiredParameter = InSearchParams.DesiredParameter;
682
683 if (!ensureAlwaysMsgf(!std::isnan(DesiredParameter), TEXT("DesiredParameter is NaN!")) ||
684 !ensureAlwaysMsgf(InSearchParams.bSearchLeft || InSearchParams.bSearchRight, TEXT("Invalid search params!")) ||
685 PairKnots.Num() == 0)
686 {
687 return DesiredParameter;
688 }
689
690 // Build a set of existing knot values (normalize zeros to avoid -0/+0 duplicates)
692 KnotValuesSet.Reserve(PairKnots.Num());
693 for (const FKnot& Knot : PairKnots)
694 {
696 }
697
698 // Return the caller's exact value; NormalizeKey is only for set membership (folds -0.0f/+0.0f).
699 if (!KnotValuesSet.Contains(Param::NormalizeKey(DesiredParameter)))
700 {
701 return DesiredParameter;
702 }
703
704 // Walk outward alternating sides
705 float Left = DesiredParameter;
706 float Right = DesiredParameter;
707
708 // Copy so we can disable directions mid-search
709 bool bSearchLeft = InSearchParams.bSearchLeft;
710 bool bSearchRight = InSearchParams.bSearchRight;
711
712 // Tiny dead-man budget to catch regressions; normal exits happen via guard rails.
713 int32 StepBudget = 4096;
714 for (;;)
715 {
716 bool AnyProgress = false;
717
718 if (bSearchLeft)
719 {
720 const float NewLeft = Param::PrevDistinct(Left);
721 if (NewLeft == Left)
722 {
723 bSearchLeft = false;
724 } // Guard #1: no progress
725 else
726 {
727 Left = NewLeft;
728 AnyProgress = true;
729 if (!KnotValuesSet.Contains(Param::NormalizeKey(Left)))
730 {
731 return Left;
732 }
733 if (Left <= -std::numeric_limits<float>::max()) // Guard #2: saturated
734 {
735 bSearchLeft = false;
736 }
737 }
738 }
739
740 if (bSearchRight)
741 {
742 const float NewRight = Param::NextDistinct(Right);
743 if (NewRight == Right)
744 {
745 bSearchRight = false;
746 } // Guard #1: no progress
747 else
748 {
749 Right = NewRight;
750 AnyProgress = true;
752 {
753 return Right;
754 }
755 if (Right >= +std::numeric_limits<float>::max()) // Guard #2: saturated
756 {
757 bSearchRight = false;
758 }
759 }
760 }
761
762 if (!bSearchLeft && !bSearchRight) break; // Guard #3: nowhere left to search
763 if (!AnyProgress) break; // Paranoid: should be covered by #1
764
765 if (--StepBudget <= 0)
766 {
767 // Dead-man: catch unexpected spins
768 ensureMsgf(false, TEXT("Step budget exhausted in GetNearestAvailableKnotValue"));
769 break;
770 }
771 }
772
773 // Fallback: never return the conflicting original. Pick a deterministic, distinct nudge.
774 {
775 // Canonicalize -0/+0, then use its *bits* to choose a side deterministically.
776 const float Canon = Param::NormalizeKey(DesiredParameter);
777 const uint32 Bits = Internal::FloatToBits(Canon);
778
779 const bool bPreferRight =
780 (InSearchParams.bSearchRight && (!InSearchParams.bSearchLeft || ((Bits & 1u) != 0)));
781
782 // Step from the *original* DesiredParameter (not Canon) so we don’t alter the value unless needed.
783 float Fallback = Param::Step(
784 DesiredParameter, bPreferRight
787
788 float Probe = Param::NormalizeKey(Fallback);
789 if (KnotValuesSet.Contains(Probe))
790 {
791 // Try the opposite direction once
792 Fallback = Param::Step(
793 DesiredParameter, bPreferRight
796 Probe = Param::NormalizeKey(Fallback);
797 }
798
799 ensureMsgf(!KnotValuesSet.Contains(Probe), TEXT("Fallback still conflicts; check knot packing logic."));
800 return Fallback;
801 }
802 }
803
805 {
806 const int32 NumValues = NumKeys();
807 // For closed loops, we repeat the first Degree points at the end
808 return this->IsClosedLoop()
809 ? (NumValues + (2 * Degree) + 1) // closed loop: n + 2p + 1
810 : (NumValues + Degree + 1);
811 }
812
818 {
819 PairKnots.Reset();
820
821 int32 NumPoints = NumKeys();
822 if (this->IsClosedLoop())
823 {
824 // For a closed B-spline of degree d with n points:
825 // - Need additional d points wrapping from start
826 // - Need n+2d+1 knots total
827 // - Knot spacing should be uniform to maintain periodicity
828 const int32 n = NumPoints;
829 const int32 d = Degree;
830 const int32 NumKnots = n + 2 * d + 1;
831
832
833 // For periodic B-splines, use uniform knot spacing
834 for (int32 i = 0; i < NumKnots; ++i)
835 {
836 // Map to [0,n] range for n segments
837 PairKnots.Add(FKnot(static_cast<float>(i), 1));
838 }
839 }
840 else
841 {
842 for (int32 i = 0; i < KnotCount; ++i)
843 {
844 float Value;
845 if (bClampEnds)
846 {
847 if (i <= Degree)
848 {
849 // Multiple knots at start for endpoint interpolation
850 Value = 0.0f;
851 }
852 else if (i >= KnotCount - Degree - 1)
853 {
854 // Multiple knots at end for endpoint interpolation
855 Value = static_cast<float>(KnotCount - 2 * Degree);
856 }
857 else
858 {
859 // Uniform spacing for internal knots
860 Value = static_cast<float>(i - Degree);
861 }
862 }
863 else
864 {
865 // Simple uniform spacing when not clamped
866 Value = static_cast<float>(i);
867 }
868 PairKnots.Add(FKnot(Value, 1));
869 }
870 }
872 }
873
879 {
880 const TArray<ValueType>& Points = Values;
881 if (Points.Num() < 2)
882 {
884 return;
885 }
886
887 const int32 n = Points.Num();
888 const int32 d = Degree;
889 const int32 NumKnots = this->IsClosedLoop()
890 ? (n + 2 * d + 1)
891 : KnotCount;
892 PairKnots.Reset();
893
894 // Calculate chord lengths between consecutive points
895 TArray<float> Chords;
896 Chords.Reserve(Points.Num() - 1);
897 float TotalLength = 0.0f;
898
899 for (int32 i = 1; i < Points.Num(); ++i)
900 {
901 const float Length = static_cast<float>(Math::Distance(Points[i], Points[i - 1]));
902 Chords.Add(Length);
904 }
905
906 // For closed loop, add the closing segment length
907 if (this->IsClosedLoop() && Points.Num() > 0)
908 {
909 const float ClosureLength = static_cast<float>(Math::Distance(Points[0], Points.Last()));
910 Chords.Add(ClosureLength);
912 }
913
914 if (this->IsClosedLoop())
915 {
916 // For closed loop, generate periodic knot vector based on chord lengths
917 for (int32 i = 0; i < NumKnots; ++i)
918 {
919 PairKnots.Add(FKnot(static_cast<float>(i) * TotalLength / static_cast<float>(n + d)));
920 }
921 }
922 else if (bClampEnds)
923 {
924 // For clamped ends, we need:
925 // - First (Degree+1) knots equal to 0.0
926 // - Last (Degree+1) knots equal to 1.0
927 // - Middle knots based on chord lengths
928
929 // First set starting knots to 0
930 PairKnots.Add(FKnot(0.0f, Degree + 1));
931
932 // Calculate internal knots based on accumulated chord lengths
933 // We have (KnotCount - 2*(Degree+1)) internal knots to set
934 const int32 NumInternalKnots = KnotCount - 2 * (Degree + 1);
935 if (NumInternalKnots > 0)
936 {
937 // Compute normalized Knots at each control point
938 TArray<float> Params;
939 Params.SetNum(Points.Num());
940 Params[0] = 0.0f;
941
942 float AccumulatedLength = 0.0f;
943 for (int32 i = 1; i < Points.Num(); ++i)
944 {
945 AccumulatedLength += Chords[i - 1];
946 Params[i] = (TotalLength > UE_SMALL_NUMBER)
948 : (static_cast<float>(i) / static_cast<float>(Points.Num() - 1));
949 }
950
951 // Map these Knots to internal knots
952 for (int32 i = 0; i < NumInternalKnots; ++i)
953 {
954 // Map i to appropriate parameter range
955 const float t = static_cast<float>(i + 1) / static_cast<float>(NumInternalKnots + 1);
956 // Map t to control point indices
957 const int32 LowIndex = FMath::FloorToInt(t * static_cast<float>(Points.Num() - 1));
958 const int32 HighIndex = FMath::Min(LowIndex + 1, Points.Num() - 1);
959 const float Alpha = t * static_cast<float>(Points.Num() - 1 - LowIndex);
960
961 // Interpolate Knots
962 const float KnotValue = FMath::Lerp(Params[LowIndex], Params[HighIndex], Alpha);
963 PairKnots.Add(FKnot(KnotValue, 1));
964 }
965 }
966 // Set the last (Degree+1) knots to 1.0
967 PairKnots.Add(FKnot(1.0f, Degree + 1));
968 }
969 else
970 {
971 // Unclamped knots - simple uniform distribution based on chord lengths
972 for (int32 i = 0; i < KnotCount; ++i)
973 {
974 const float Param = static_cast<float>(i) / static_cast<float>(KnotCount - 1);
975 PairKnots.Add(FKnot(Param * TotalLength));
976 }
977 }
979 }
980
987 {
988 const TArray<ValueType>& Points = Values;
989 if (Points.Num() < 2)
990 {
992 return;
993 }
994
995 const int32 n = Points.Num();
996 const int32 d = Degree;
997 const int32 NumKnots = this->IsClosedLoop()
998 ? (n + 2 * d + 1)
999 : KnotCount;
1000 PairKnots.Reset();
1001
1002 TArray<float> Lengths;
1003 Lengths.Reserve(Points.Num() - 1);
1004 float TotalLength = 0.0f;
1005
1006 for (int32 i = 1; i < Points.Num(); ++i)
1007 {
1008 // Square root of distance for centripetal parameterization
1009 const float Length = static_cast<float>(Math::CentripetalDistance(Points[i], Points[i - 1]));
1010 Lengths.Add(Length);
1012 }
1013
1014 // For closed loop, add the closing segment
1015 if (this->IsClosedLoop() && Points.Num() > 0)
1016 {
1017 const float ClosureLength = static_cast<float>(Math::CentripetalDistance(Points[0], Points.Last()));
1018 Lengths.Add(ClosureLength);
1020 }
1021
1022 if (this->IsClosedLoop())
1023 {
1024 // Generate periodic knot vector using centripetal parameterization
1025 for (int32 i = 0; i < NumKnots; ++i)
1026 {
1027 PairKnots.Add(FKnot(static_cast<float>(i) * TotalLength / static_cast<float>(n + d)));
1028 }
1029 }
1030 else if (bClampEnds)
1031 {
1032 // For clamped ends, same approach as chord length but with square root distances
1033
1034 // First set starting knots to 0
1035 PairKnots.Add(FKnot(0.0f, Degree + 1));
1036
1037 // Calculate internal knots based on accumulated centripetal Knots
1038 const int32 NumInternalKnots = KnotCount - 2 * (Degree + 1);
1039 if (NumInternalKnots > 0)
1040 {
1041 // Compute normalized Knots at each control point
1042 TArray<float> Params;
1043 Params.SetNum(Points.Num());
1044 Params[0] = 0.0f;
1045
1046 float AccumulatedLength = 0.0f;
1047 for (int32 i = 1; i < Points.Num(); ++i)
1048 {
1049 AccumulatedLength += Lengths[i - 1];
1050 Params[i] = (TotalLength > UE_SMALL_NUMBER)
1052 : (static_cast<float>(i) / static_cast<float>(Points.Num() - 1));
1053 }
1054
1055 // Map these Knots to internal knots
1056 for (int32 i = 0; i < NumInternalKnots; ++i)
1057 {
1058 // Map i to appropriate parameter range
1059 const float t = static_cast<float>(i + 1) / static_cast<float>(NumInternalKnots + 1);
1060 // Map t to control point indices
1061 const int32 LowIndex = FMath::FloorToInt(t * static_cast<float>(Points.Num() - 1));
1062 const int32 HighIndex = FMath::Min(LowIndex + 1, Points.Num() - 1);
1063 const float Alpha = t * static_cast<float>(Points.Num() - 1 - LowIndex);
1064
1065 // Interpolate Knots
1066 const float KnotValue = FMath::Lerp(Params[LowIndex], Params[HighIndex], Alpha);
1068 }
1069 }
1070 // Set the last (Degree+1) knots to 1.0
1071 PairKnots.Add(FKnot(1.0f, Degree + 1));
1072 }
1073 else
1074 {
1075 // Unclamped knots
1076 for (int32 i = 0; i < KnotCount; ++i)
1077 {
1078 const float Param = static_cast<float>(i) / static_cast<float>(KnotCount - 1);
1079 PairKnots.Add(FKnot(Param * TotalLength));
1080 }
1081 }
1083 }
1084
1090 {
1091 if (IsClampedEnds() && PairKnots.Num() > 0)
1092 {
1093 // enforce degree + 1 multiplicity at the endpoints
1094 PairKnots[0].Multiplicity = Degree + 1;
1095 PairKnots.Last().Multiplicity = Degree + 1;
1097 }
1098 }
1099
1104 {
1106 {
1108 bFlatKnotsCacheDirty = false;
1109 }
1110 }
1111
1116 {
1117 bFlatKnotsCacheDirty = true;
1118 }
1119
1123 void PrintKnotVector() const
1124 {
1125 for (int32 Index = 0; Index < PairKnots.Num(); ++Index)
1126 {
1127 const FKnot& Knot = PairKnots[Index];
1128 UE_LOG(LogSpline, Verbose, TEXT(" [%d] Value: %f, Multiplicity: %u"), Index, Knot.Value, Knot.Multiplicity);
1129 }
1130
1131 for (int32 j = 0; j < this->FlatKnots.Num(); ++j)
1132 {
1133 UE_LOG(LogSpline, Verbose, TEXT(" FlatKnot[%d] = %f"), j, this->FlatKnots[j]);
1134 }
1135 }
1136
1137private:
1139 int32 FindKnotSpan(float Parameter) const
1140 {
1142 const TArray<float>& Knots = FlatKnots;
1143 // First, check that the knot vector is populated.
1144 if (Knots.Num() == 0)
1145 {
1146 return 0; // Return a safe fallback.
1147 }
1148
1149 const int32 NumValues = NumKeys();
1150 if (NumValues <= 0)
1151 {
1152 UE_LOG(LogSpline, Error, TEXT("FindKnotSpan: Not enough knots to define a valid span."));
1153 return 0; // Safe fallback.
1154 }
1155
1156 // Handle the last knot explicitly for clamped endpoints
1157 if (Parameter >= Knots.Last() - UE_SMALL_NUMBER)
1158 {
1159 return NumValues - 1; // Last span index
1160 }
1161
1162 if (Parameter <= Knots[Degree] + UE_SMALL_NUMBER)
1163 {
1164 return Degree; // First valid span
1165 }
1166
1167 // Binary search for the span
1168 int32 Low = Degree;
1169 int32 High = Knots.Num() - Degree - 1;
1170
1171 while (Low <= High)
1172 {
1173 const int32 Mid = (Low + High) / 2;
1174
1175 if (Parameter >= Knots[Mid] && Parameter < Knots[Mid + 1])
1176 {
1177 return Mid;
1178 }
1179
1180 if (Parameter < Knots[Mid])
1181 {
1182 High = Mid - 1;
1183 }
1184 else
1185 {
1186 Low = Mid + 1;
1187 }
1188 }
1189
1190 // Shouldn't reach here; return fallback
1191 UE_LOG(LogSpline, Warning, TEXT("Fallback in FindKnotSpan for t=%f"), Parameter);
1192 return Degree;
1193 }
1194
1195 virtual FWindow FindWindow(float Parameter) const
1196 {
1197 FWindow Window = {};
1198 if (NumKeys() < Degree + 1)
1199 {
1200 return Window;
1201 }
1202
1203 int32 Span = FindKnotSpan(Parameter);
1204 // For closed loop, we need to ensure the span wraps properly
1205 if (this->IsClosedLoop())
1206 {
1207 // For closed loop:
1208 // - Span needs to wrap around number of control points
1209 // - Window indices wrap around array
1210 const int32 NumPoints = NumKeys();
1211 for (int32 i = 0; i <= Degree; ++i)
1212 {
1213 int32 Index = (Span - Degree + i) % NumPoints;
1214 if (Index < 0) Index += NumPoints;
1215 Window[i] = &GetValue(Index);
1216 }
1217 }
1218 else
1219 {
1220 // Get control points for the window
1221 for (int32 i = 0; i <= Degree; ++i)
1222 {
1223 const int32 Index = Span - Degree + i;
1224 if (Index >= 0 && Index < NumKeys())
1225 {
1226 Window[i] = &GetValue(Index);
1227 }
1228 }
1229 }
1230
1231 return Window;
1232 }
1233
1240 virtual ValueType InterpolateWindow(TArrayView<const ValueType* const> Window, float Parameter) const
1241 {
1242 TArray<float> Basis;
1243
1244 if (PairKnots.IsEmpty())
1245 {
1246 return ValueType();
1247 }
1248
1250
1251 int32 Span = FindKnotSpan(Parameter);
1252
1253 Math::ComputeBSplineBasisFunctions(FlatKnots, Span, Parameter, Degree, Basis);
1255 }
1256
1257protected:
1259
1260 // Knot representation in pair form having multiplicity
1262
1263 // Cached flattened knot vector for compatible algorithms
1266
1267 bool bIsClosedLoop = false;
1268
1270 bool bClampEnds = true;
1271};
1272
1273// Common type definitions
1278// Explicit degree type definitions if needed
1283} // end namespace UE::Geometry::Spline
1284} // end namespace UE::Geometry
1285} // end namespace UE
#define ensureAlwaysMsgf(InExpression, InFormat,...)
Definition AssertionMacros.h:467
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
@ INDEX_NONE
Definition CoreMiscDefines.h:150
#define UE_EXPERIMENTAL(Version, Message)
Definition CoreMiscDefines.h:369
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::TCHAR TCHAR
Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requir...
Definition Platform.h:1135
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 UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
#define UE_SMALL_NUMBER
Definition UnrealMathUtility.h:130
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition Archive.h:1208
virtual void Serialize(void *V, int64 Length)
Definition Archive.h:1689
Definition ArrayView.h:139
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
UE_NODEBUG UE_FORCEINLINE_HINT ElementType & Last(SizeType IndexFromTheEnd=0) UE_LIFETIMEBOUND
Definition Array.h:1263
void RemoveAt(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2083
void Reset(SizeType NewSize=0)
Definition Array.h:2246
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
UE_NODEBUG UE_FORCEINLINE_HINT bool IsValidIndex(SizeType Index) const
Definition Array.h:1122
SizeType Insert(std::initializer_list< ElementType > InitList, const SizeType InIndex)
Definition Array.h:1875
void Empty(SizeType Slack=0)
Definition Array.h:2273
UE_FORCEINLINE_HINT void Reserve(SizeType Number)
Definition Array.h:3016
Definition StaticArray.h:26
Definition UniquePtr.h:107
Definition SplineInterfaces.h:35
Definition BSpline.h:126
int32 InsertValue(int32 Idx, const ValueType &NewValue)
Definition BSpline.h:320
virtual FInterval1f GetParameterSpace() const override
Definition BSpline.h:223
virtual void SetClosedLoop(bool bClosed) override
Definition BSpline.h:228
virtual bool IsClosedLoop() const override
Definition BSpline.h:233
virtual TUniquePtr< ISplineInterface > Clone() const override
Definition BSpline.h:238
void UpdateFlatKnotsCache() const
Definition BSpline.h:1103
TStaticArray< const ValueType *, WindowSize > FWindow
Definition BSpline.h:132
virtual FInterval1f GetSegmentParameterRange(int32 SegmentIndex) const override
Definition BSpline.h:268
virtual int32 GetExpectedNumKnots() const
Definition BSpline.h:804
float GetNearestAvailableKnotValue(const FValidKnotSearchParams &InSearchParams) const
Definition BSpline.h:679
static constexpr int32 Degree
Definition BSpline.h:128
virtual void Reparameterize(EParameterizationPolicy ParameterizationPolicy)
Definition BSpline.h:511
void MarkFlatKnotsCacheDirty() const
Definition BSpline.h:1115
virtual bool Serialize(FArchive &Ar) override
Definition BSpline.h:193
virtual bool IsEqual(const ISplineInterface *OtherSpline) const override
Definition BSpline.h:182
virtual int32 SetParameter(int32 Index, float NewParameter)
Definition BSpline.h:340
bool operator==(const TBSpline &Other) const
Definition BSpline.h:209
virtual ~TBSpline() override=default
virtual int32 GetNumberOfSegments() const override
Definition BSpline.h:256
bool InsertKnot(FKnot InKnot)
Definition BSpline.h:628
bool bClampEnds
Definition BSpline.h:1270
TArray< FKnot > PairKnots
Definition BSpline.h:1261
void SetKnot(int32 KnotIdx, float NewValue)
Definition BSpline.h:576
const TArray< FKnot > & GetPairKnots() const
Definition BSpline.h:428
void ApplyClampedKnotsMultiplicity()
Definition BSpline.h:1089
void SetClampedEnds(bool bInClampEnds)
Definition BSpline.h:549
void PrintKnotVector() const
Definition BSpline.h:1123
TArray< ValueType > Values
Definition BSpline.h:1258
virtual bool RemoveValue(int32 Index)
Definition BSpline.h:329
virtual float FindNearest(const ValueType &Point, float &OutSquaredDistance) const override
Definition BSpline.h:290
FInterval1f GetKnotRange() const
Definition BSpline.h:560
typename TSplineInterface< VALUETYPE >::ValueType ValueType
Definition BSpline.h:129
int32 NumKeys() const
Definition BSpline.h:295
int32 GetKnotMultiplicity(int32 KnotIndex) const
Definition BSpline.h:444
bool IsClampedEnds() const
Definition BSpline.h:554
bool bIsClosedLoop
Definition BSpline.h:1267
void GenerateChordLengthKnots(int32 KnotCount)
Definition BSpline.h:878
virtual int32 FindIndexForParameter(float Parameter, float &OutLocalParam) const
Definition BSpline.h:362
virtual void Clear() override
Definition BSpline.h:176
bool SetValue(int32 Idx, const ValueType &NewValue)
Definition BSpline.h:310
const ValueType & GetValue(int32 Idx) const
Definition BSpline.h:300
friend FArchive & operator<<(FArchive &Ar, TBSpline &BSpline)
Definition BSpline.h:217
const TArray< FKnot > & GetKnotVector() const
Definition BSpline.h:422
bool RemoveKnot(int32 KnotIdx)
Definition BSpline.h:593
void GenerateCentripetalKnots(int32 KnotCount)
Definition BSpline.h:986
void SwapKnots(int32 KnotIdxA, int32 KnotIdxB)
Definition BSpline.h:611
TArray< float > FlatKnots
Definition BSpline.h:1264
int32 AddValue(const ValueType &NewValue)
Definition BSpline.h:305
void Dump() const
Definition BSpline.h:152
void ResetKnotVector()
Definition BSpline.h:433
bool bFlatKnotsCacheDirty
Definition BSpline.h:1265
bool SetCustomKnots(const TArray< FKnot > &NewKnots)
Definition BSpline.h:459
virtual ValueType EvaluateImpl(float Parameter) const override
Definition BSpline.h:276
DECLARE_SPLINE_TYPE_ID(BSplineNameSelector< DEGREE >::Name, *TSplineValueTypeTraits< VALUETYPE >::Name)
static constexpr int32 WindowSize
Definition BSpline.h:131
virtual float GetParameter(int32 Index) const
Definition BSpline.h:347
void GenerateUniformKnots(int32 KnotCount)
Definition BSpline.h:817
Definition SplineTypeRegistry.h:236
Definition SplineInterfaces.h:92
EOutOfBoundsHandlingMode PostInfinityMode
Definition SplineInterfaces.h:191
VALUETYPE ValueType
Definition SplineInterfaces.h:96
virtual FSplineTypeId::IdType GetTypeId() const override
Definition SplineInterfaces.h:168
EOutOfBoundsHandlingMode PreInfinityMode
Definition SplineInterfaces.h:190
static T InterpolateWithBasis(TArrayView< const T *const > Window, TArrayView< const float > Basis)
Definition InterpolationPolicies.h:61
uint32 FloatToBits(float f)
Definition SplineMath.h:22
double CentripetalDistance(const T &A, const T &B)
Definition SplineMath.h:198
double Distance(const T &A, const T &B)
Definition SplineMath.h:186
float NormalizeKey(float t)
Definition SplineMath.h:146
float PrevDistinct(float t)
Definition SplineMath.h:128
float Step(float t, EDir d)
Definition SplineMath.h:134
float NextDistinct(float t)
Definition SplineMath.h:123
TInterval1< float > FInterval1f
Definition BoxTypes.h:241
Definition AdvancedWidgetsModule.cpp:13
U16 Index
Definition radfft.cpp:71
static UE_FORCEINLINE_HINT bool IsNearlyEqual(float A, float B, float ErrorTolerance=UE_SMALL_NUMBER)
Definition UnrealMathUtility.h:388
static constexpr UE_FORCEINLINE_HINT T Lerp(const T &A, const T &B, const U &Alpha)
Definition UnrealMathUtility.h:1116
static constexpr const TCHAR * Name
Definition BSpline.h:107
Definition BSpline.h:34
static bool IsValidKnotVector(const TArray< FKnot > &InKnots, int32 Degree)
Definition BSpline.h:68
bool operator==(const FKnot &Other) const
Definition BSpline.h:89
FKnot()
Definition BSpline.h:38
static TArray< float > ConvertPairToFlatKnots(const TArray< FKnot > &PairKnots)
Definition BSpline.h:55
FKnot(float InValue, uint32 InMultiplicity=1)
Definition BSpline.h:44
friend FArchive & operator<<(FArchive &Ar, FKnot &Knot)
Definition BSpline.h:94
float Value
Definition BSpline.h:35
uint32 Multiplicity
Definition BSpline.h:36
FValidKnotSearchParams(float InDesiredParameter)
Definition BSpline.h:664
static TInterval1< float > Empty()
Definition BoxTypes.h:34