UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
PolyBezierSpline.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "BSpline.h"
6#include "Misc/ScopeExit.h"
7
8namespace UE
9{
10namespace Geometry
11{
12namespace Spline
13{
14
15template<typename VALUETYPE> class UE_EXPERIMENTAL(5.7, "New spline APIs are experimental.") TPolyBezierSpline;
16
17/* Augments B-Spline interface */
18template<typename VALUETYPE>
20 public TBSpline<VALUETYPE, 3>,
21 private TSelfRegisteringSpline<TPolyBezierSpline<VALUETYPE>, VALUETYPE>
22{
23public:
25 typedef typename Base::ValueType ValueType;
26 using FWindow = typename Base::FWindow;
27
28 // Generate compile-time type ID for PolyBezier
30 TEXT("PolyBezier"),
32 );
33
34 TPolyBezierSpline() = default;
35 virtual ~TPolyBezierSpline() override = default;
36
38 const ValueType& P0,
39 const ValueType& P1,
40 const ValueType& P2,
41 const ValueType& P3,
42 EParameterizationPolicy Parameterization = EParameterizationPolicy::Uniform)
43 : Base()
44 {
45 // Add the initial segment
46 AddBezierSegmentInternal( 0, P0, P1, P2, P3, Parameterization);
48 }
49
54 {
55 // Create a minimal valid default segment for serialization
56 ValueType P0(0, 0, 0);
57 ValueType P1(0.33f, 0, 0);
58 ValueType P2(0.66f, 0, 0);
59 ValueType P3(1, 0, 0);
60
61 return TPolyBezierSpline(P0, P1, P2, P3, EParameterizationPolicy::Uniform);
62 }
63
64 // Static factory methods for common shapes
65 // Create a straight line between two points
67 const ValueType& Start,
68 const ValueType& End)
69 {
70 // Calculate control points for a straight line
71 // For a straight line, the control points should be evenly spaced
72 ValueType P0 = Start;
73 ValueType P3 = End;
74 ValueType Direction = (End - Start);
75 ValueType P1 = Start + Direction * 0.33f;
76 ValueType P2 = Start + Direction * 0.66f;
77
78 TPolyBezierSpline<ValueType> Result(P0, P1, P2, P3);
79 Result.Reparameterize(EParameterizationPolicy::Uniform);
80 return Result;
81 }
82
83 // Create a circular arc with given center, radius, and angle range
85 const ValueType& Center,
86 float Radius,
87 float StartAngle,
88 float EndAngle,
89 int32 NumSegments = 4)
90 {
91 // Ensure angles are in the right order
92 if (EndAngle < StartAngle)
93 {
94 float Temp = StartAngle;
95 StartAngle = EndAngle;
96 EndAngle = Temp;
97 }
98
99 // Ensure reasonable segment count
100 NumSegments = FMath::Max(1, NumSegments);
101
102 // Calculate angle per segment
103 float AngleSpan = EndAngle - StartAngle;
104 float AnglePerSegment = AngleSpan / static_cast<float>(NumSegments);
105
106 // For large angles per segment, increase segment count for better approximation
107 if (AnglePerSegment > UE_PI/4)
108 {
109 NumSegments = FMath::CeilToInt(AngleSpan / (UE_PI/4));
110 AnglePerSegment = AngleSpan / static_cast<float>(NumSegments);
111 }
112
113 // Pre-compute all points along the arc for exact positioning
114 TArray<ValueType> Points;
115 Points.SetNum(NumSegments + 1);
116
117 for (int32 i = 0; i <= NumSegments; i++)
118 {
119 float Angle = StartAngle + (static_cast<float>(i) * AnglePerSegment);
120 Points[i] = Center + ValueType(
121 Radius * FMath::Cos(Angle),
122 Radius * FMath::Sin(Angle),
123 0);
124 }
125
126 // Calculate first segment with precise tangents
127 ValueType P0 = Points[0];
128 ValueType P3 = Points[1];
129
130 // Get normalized direction vectors from center to points
133
134 // Calculate perpendicular vectors (normalized)
135 ValueType Perp0 = ValueType(-Dir0.Y, Dir0.X, 0);
136 ValueType Perp3 = ValueType(-Dir3.Y, Dir3.X, 0);
137
138 // Calculate precise tangent scale
139 float TangentScale = Radius * (4.0f / 3.0f) *
140 FMath::Tan(AnglePerSegment / 4.0f);
141
142 // Create control points with exact perpendicular tangents
143 ValueType P1 = P0 + Perp0 * TangentScale;
144 ValueType P2 = P3 - Perp3 * TangentScale;
145
146 // Create spline with first segment
147 TPolyBezierSpline<ValueType> Result(P0, P1, P2, P3);
148
149 // Add remaining segments with carefully calculated control points
150 for (int32 i = 1; i < NumSegments; i++)
151 {
152 P0 = Points[i];
153 P3 = Points[i+1];
154
155 // Calculate exact perpendicular vectors for this segment
158 Perp0 = ValueType(-Dir0.Y, Dir0.X, 0);
159 Perp3 = ValueType(-Dir3.Y, Dir3.X, 0);
160
161 // Create control points with exact perpendicular tangents
162 P1 = P0 + Perp0 * TangentScale;
163 P2 = P3 - Perp3 * TangentScale;
164
165 Result.AppendBezierSegment(P1, P2, P3);
166 }
167
168 return Result;
169 }
170
171 // Create a complete circle
173 const ValueType& Center,
174 float Radius,
175 int32 NumSegments = 4)
176 {
177 // Ensure reasonable segment count
178 NumSegments = FMath::Max(4, NumSegments);
179
180 // Create a full circle arc (0 to 2π)
182 CreateCircleArc(Center, Radius, 0.0f, 2.0f * UE_PI, NumSegments);
183
184 // Set as closed loop
185 Result.SetClosedLoop(true);
186 // Apply consistent parameterization
187 Result.Reparameterize(EParameterizationPolicy::Centripetal);
188 return Result;
189 }
190
191 // Create an ellipse with X and Y radii
193 const ValueType& Center,
194 float RadiusX,
195 float RadiusY,
196 int32 NumSegments = 4)
197 {
198 // Ensure at least 4 segments for good quality
199 if (NumSegments < 4) NumSegments = 4;
200
201 // Calculate angle per segment
202 const float AnglePerSegment = 2.0f * UE_PI / static_cast<float>(NumSegments);
203
204 // Start with first point at angle 0
205 float CurrentAngle = 0.0f;
206 ValueType P0 = Center + ValueType(RadiusX * FMath::Cos(CurrentAngle),
207 RadiusY * FMath::Sin(CurrentAngle), 0);
208
209 // Calculate next point
211 ValueType P3 = Center + ValueType(RadiusX * FMath::Cos(CurrentAngle),
212 RadiusY * FMath::Sin(CurrentAngle), 0);
213
214 // Calculate correct tangent scale for this angle
215 const float TangentScale = (4.0f/3.0f) * FMath::Tan(AnglePerSegment / 4.0f);
216
217 // Calculate tangent vectors (scaled appropriately for ellipse)
218 ValueType Tangent0 = ValueType(-RadiusX * FMath::Sin(0.0f),
219 RadiusY * FMath::Cos(0.0f), 0) * TangentScale;
221 RadiusY * FMath::Cos(CurrentAngle), 0) * TangentScale;
222
223 ValueType P1 = P0 + Tangent0;
224 ValueType P2 = P3 - Tangent3;
225
226 // Create the spline with the first segment
227 TPolyBezierSpline<ValueType> Result(P0, P1, P2, P3);
228
229 // Add remaining segments
230 for (int32 i = 1; i < NumSegments; ++i)
231 {
232 P0 = P3; // Start from previous endpoint
233 Tangent0 = Tangent3; // Reuse previous tangent
234
235 // Calculate next endpoint
237 P3 = Center + ValueType(RadiusX * FMath::Cos(CurrentAngle),
238 RadiusY * FMath::Sin(CurrentAngle), 0);
239
240 // Calculate tangent at next point
241 Tangent3 = ValueType(-RadiusX * FMath::Sin(CurrentAngle),
242 RadiusY * FMath::Cos(CurrentAngle), 0) * TangentScale;
243
244 // Calculate control points
245 P1 = P0 + Tangent0;
246 P2 = P3 - Tangent3;
247
248 // Add the segment
249 Result.AppendBezierSegment(P1, P2, P3);
250 }
251
252 // Set as closed loop
253 Result.SetClosedLoop(true);
254
255 // Apply consistent parameterization
256 Result.Reparameterize(EParameterizationPolicy::Centripetal);
257
258 return Result;
259 }
260
261 // ISplineInterface Implementation
262 virtual void Clear() override { Base::Clear(); }
263 virtual TUniquePtr<ISplineInterface> Clone() const override
264 {
266
267 // Copy base class members
268 Clone->Values = this->Values;
269 Clone->PairKnots = this->PairKnots;
270 Clone->bIsClosedLoop = this->bIsClosedLoop;
271 Clone->bClampEnds = this->bClampEnds;
272
273 // Copy infinity modes
274 Clone->PreInfinityMode = this->PreInfinityMode;
275 Clone->PostInfinityMode = this->PostInfinityMode;
276
277 return Clone;
278 }
279
280 float FindNearestOnSegment(const ValueType& Point, int32 SegmentIndex, float& OutSquaredDistance) const
281 {
282 if (!IsValidSegmentIndex(SegmentIndex))
283 {
285 return 0.0f;
286 }
287
288 // Get Bezier control points for this segment
289 TStaticArray<ValueType, 4> Coeffs = {};
290 const int32 BaseIndex = SegmentIndex * 4;
291
292 // Get the control points
293 const ValueType& P0 = Base::GetValue(BaseIndex);
294 const ValueType& P1 = Base::GetValue(BaseIndex + 1);
295 const ValueType& P2 = Base::GetValue(BaseIndex + 2);
296 const ValueType& P3 = Base::GetValue(BaseIndex + 3);
297
298 // Setup coefficients in standard Bezier polynomial form relative to test point
299 Coeffs[0] = P0 - Point; // constant term
300 Coeffs[1] = (P1 - P0) * 3; // linear coefficient
301 Coeffs[2] = (P2 - P1*2 + P0) * 3; // quadratic coefficient
302 Coeffs[3] = P3 - P2*3 + P1*3 - P0; // cubic coefficient
303
304 const float LocalT = Math::FindNearestPoint_Cubic(MakeArrayView(Coeffs), 0.0f, 1.0f, OutSquaredDistance);
305 return MapLocalSegmentParameterToGlobal(SegmentIndex, LocalT);
306 }
307
308 virtual float FindNearest(const ValueType& Point, float& OutSquaredDistance) const override
309 {
311 float BestParam = 0.0f;
312
313 const int32 NumSegments = Base::NumKeys() / 4;
314 for (int32 i = 0; i < NumSegments; ++i)
315 {
316 float SegmentDistSq;
317 const float SplineParam = FindNearestOnSegment(Point, i, SegmentDistSq);
318
320 {
322 BestParam = SplineParam;
323 }
324 }
325
327
328 return BestParam;
329 }
330
331 // Evaluator methods
332 static int32 GetDegree() { return 3; }
333
340 template<int32 Order>
341 ValueType EvaluateDerivative(float Parameter) const
342 {
343 FWindow Window = FindWindow(Parameter);
344
345 // If FindWindow fails, it will return an array of nullptr. InterpolateWindow assumes validity of elements.
346 if (Window[0] == nullptr) { return ValueType(); }
347
348 // Order 0 is just regular evaluation
349 if constexpr (Order == 0)
350 {
351 return InterpolateWindow(Window, Parameter);
352 }
353
354 // Convert global parameter to segment information
355 int32 SegmentIndex;
356 float LocalT;
357 MapGlobalParameterToLocalSegment(Parameter, SegmentIndex, LocalT);
358
359 // For PolyBezier, each segment is normalized to unit parameter space
360 constexpr float SegmentScale = 1.0f;
361
362 // Compute the derivative with proper scaling
365 }
366
380 const TArray<ValueType>& Points,
382 {
383 const int32 NumPoints = Points.Num();
384
385 // Need at least 4 points for a valid cubic bezier segment
386 if (NumPoints < 4)
387 {
388 UE_LOG(LogSpline, Warning, TEXT("SetControlPoints requires at least 4 points to add a valid segment. Got %d points."), NumPoints);
389 return false;
390 }
391
392 // Clear existing points
393 Clear();
394 const bool bIsClosedLoop = Base::IsClosedLoop();
395
396 // For cubic Bezier curves, points come in groups of 4:
397 // P0 (start), P1 (control), P2 (control), P3 (end)
398 // Each complete segment requires 4 points
399
400 // Calculate how many complete segments we have
401 int32 NumSegments = (NumPoints) / 4;
402
403 // Make sure we have at least one complete segment
404 if (NumSegments < 1)
405 {
406 UE_LOG(LogSpline, Warning, TEXT("Not enough points to create a complete segment. Need at least 4 points."));
407 return false;
408 }
409
410 // Add each segment
411 for (int32 SegmentIndex = 0; SegmentIndex < NumSegments; ++SegmentIndex)
412 {
413 const int32 BaseIndex = SegmentIndex * 4;
414
415 // Ensure we have enough points for this segment
416 if (BaseIndex + 3 >= NumPoints)
417 {
418 break;
419 }
420
421 // Extract the 4 control points for this segment
422 ValueType P0 = Points[BaseIndex];
423 ValueType P1 = Points[BaseIndex + 1];
424 ValueType P2 = Points[BaseIndex + 2];
425 ValueType P3 = Points[BaseIndex + 3];
426
427 // Add the segment
428 if (SegmentIndex == 0)
429 {
430 // For the first segment, use AddBezierSegmentInternal
431 this->AddBezierSegmentInternal(0, P0, P1, P2, P3, ParameterizationPolicy);
432 }
433 else
434 {
435 // For subsequent segments, check if P0 matches the last point of the previous segment
437
439 {
440 // Points are the same, use AppendBezierSegment which only needs P1, P2, P3
442 }
443 else
444 {
445
446 // Points don't match, use AddBezierSegmentInternal with appropriately computed parameter
447 this->AddBezierSegmentInternal(NumSegments, P0, P1, P2, P3, ParameterizationPolicy);
448 }
449 }
450 }
451
452 // Update closed loop state if needed
453 if (bIsClosedLoop)
454 {
455 // If we're supposed to be a closed loop, make sure the last point connects to the first
456 ValueType FirstPoint = Base::GetValue(0);
458
459 // If not already a closed loop, add a segment to close it
460 if (Math::SizeSquared(LastPoint - FirstPoint) > UE_KINDA_SMALL_NUMBER && NumSegments > 0)
461 {
462 // Add a segment that connects back to the start
463 // We need to compute appropriate control points
464 int32 LastBaseIndex = (NumSegments - 1) * 4;
465
466 // We'll try to maintain tangent continuity if possible
467 ValueType OutTangent = (Points[LastBaseIndex + 2] - Points[LastBaseIndex + 1]);
468 ValueType InTangent = (Points[1] - Points[0]);
469
470 // Scale tangents to match segment length
471 float SegmentLength = static_cast<float>(Math::Distance(FirstPoint, LastPoint));
472 OutTangent = Math::GetSafeNormal(OutTangent) * (SegmentLength * 0.33f);
473 InTangent = Math::GetSafeNormal(InTangent) * (SegmentLength * 0.33f);
474
475 // Create control points
477 ValueType P2 = FirstPoint - InTangent;
478
479 // Add the closing segment
480 this->AppendBezierSegment(P1, P2, FirstPoint, ParameterizationPolicy);
481 }
482
484 }
485 else
486 {
487 Base::SetClosedLoop(false);
488 }
489
490 // Reparameterize for consistent parameter distribution
492
493 return true;
494 }
495
506 const TArray<ValueType>& Points,
507 bool bAppend,
509 {
510 const int32 NumPoints = Points.Num();
511
512 // Need at least 2 points to add a valid segment
513 if (NumPoints < 4)
514 {
515 UE_LOG(LogSpline, Warning, TEXT("AddBezierSegments requires at least 4 points to add a valid segment. Got %d points."), NumPoints);
516 return false;
517 }
518
519 // If the spline is currently empty (even though it shouldn't be according to design), this is equivalent to SetControlPoints
520 if (this->Base::NumKeys() == 0)
521 {
523 }
524
525 // Check points format based on append/prepend direction
526 if (bAppend)
527 {
528 // For appending, we expect 3N+1 points: P0 (shared with last existing point), then (P1,P2,P3) per segment
529 if (NumPoints % 3 != 1)
530 {
531 UE_LOG(LogSpline, Warning, TEXT("Invalid point count for appending segments. Expected 3N+1, got %d."), NumPoints);
532 return false;
533 }
534 }
535 else
536 {
537 // For prepending, we expect 3N+1 points: (P0,P1,P2) per segment, then P3 (shared with first existing point)
538 if (NumPoints % 3 != 1)
539 {
540 UE_LOG(LogSpline, Warning, TEXT("Invalid point count for prepending segments. Expected 3N+1, got %d."), NumPoints);
541 return false;
542 }
543 }
544
545 // Check connection point
546 if (bAppend)
547 {
548 // First point of new segments should match the last point of existing spline
549 const ValueType& FirstNewPoint = Points[0];
550 const ValueType& LastExistingPoint = this->Base::GetValue(this->Base::NumKeys() - 1);
551
552 // Check if the connection point is reasonably close
553 const double DistanceSquared = Math::SizeSquared(LastExistingPoint, FirstNewPoint);
555 {
556 UE_LOG(LogSpline, Warning, TEXT("Connection point mismatch when appending segments. Distance: %f"),
557 FMath::Sqrt(static_cast<float>(DistanceSquared)));
558 // Continue anyway but log warning
559 }
560 }
561 else
562 {
563 // Last point of new segments should match the first point of existing spline
564 const ValueType& LastNewPoint = Points.Last();
566
567 // Check if the connection point is reasonably close
568 const double DistanceSquared = Math::SizeSquared(LastNewPoint, FirstExistingPoint);
570 {
571 UE_LOG(LogSpline, Warning, TEXT("Connection point mismatch when prepending segments. Distance: %f"),
572 FMath::Sqrt(static_cast<float>(DistanceSquared)));
573 // Continue anyway but log warning
574 }
575 }
576
577 if (bAppend)
578 {
579 // Append segments to the end of the spline
580 const int32 NumSegmentsToAdd = (NumPoints - 1) / 3;
581
582 // Add each segment
583 for (int32 SegmentIndex = 0; SegmentIndex < NumSegmentsToAdd; ++SegmentIndex)
584 {
585 const int32 BaseIndex = 1 + SegmentIndex * 3; // Skip the first point (P0) for first segment
586
587 // For each segment, we need P1, P2, P3 (P0 is the last point of existing spline)
588 const ValueType& P1 = Points[BaseIndex];
589 const ValueType& P2 = Points[BaseIndex + 1];
590 const ValueType& P3 = Points[BaseIndex + 2];
591
592 // Use the existing AppendBezierSegment method
594 }
595 }
596 else
597 {
598 // Prepend segments to the beginning of the spline
599 const int32 NumSegmentsToPrepend = (NumPoints - 1) / 3;
600
601 // Prepend segments in order starting from the first segment
602 // For prepending, we need to work backwards since each PrependBezierSegment
603 // adds to the beginning
604 for (int32 i = NumSegmentsToPrepend - 1; i >= 0; --i)
605 {
606 const int32 BaseIndex = i * 3;
607
608 // For each segment, we need P0, P1, P2 (P3 will connect to the existing spline)
609 const ValueType& P0 = Points[BaseIndex];
610 const ValueType& P1 = Points[BaseIndex + 1];
611 const ValueType& P2 = Points[BaseIndex + 2];
612
613 // Use the existing PrependBezierSegment method
615 }
616 }
617
618 // Reparameterize the spline to ensure consistent parameterization
620
621 return true;
622 }
634 const ValueType& P1,
635 const ValueType& P2,
636 const ValueType& P3,
637 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
638 {
639 const TOptional<float> LoopKnotDelta = GetLoopKnotDelta();
640 SetClosedLoop(false);
642 {
643 if (LoopKnotDelta.IsSet())
644 {
645 SetClosedLoop(true);
647 }
648 };
649
650 // Get P0 from the last point of the previous segment
652 int32 NumSegments = Base::NumKeys() / 4;
653 // Add segment with append behavior
654
655 return AddBezierSegmentInternal(NumSegments, P0, P1, P2, P3, ParameterizationPolicy);
656 }
657
669 const ValueType& P0,
670 const ValueType& P1,
671 const ValueType& P2,
672 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
673 {
674 const TOptional<float> LoopKnotDelta = GetLoopKnotDelta();
675 SetClosedLoop(false);
677 {
678 if (LoopKnotDelta.IsSet())
679 {
680 SetClosedLoop(true);
682 }
683 };
684
685 // Get P3 from the first point of the existing spline
687 // Add segment with prepend behavior
688 return AddBezierSegmentInternal(0, P0, P1, P2, P3,
690 false); // Prepend to first segment
691 }
692
702 int32 SegmentIndex,
703 const ValueType& Position,
704 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
705 {
706 // Find the closest point on the segment to the given position
707 float SquaredDistance;
708 float LocalT = FindLocalParameterNearestToPosition(SegmentIndex, Position, SquaredDistance);
709
710 // Insert at that local parameter
712 }
713
726 int32 SegmentIndex,
727 float LocalT,
728 const ValueType& Position,
729 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
730 {
731 int32 NumSegments = Base::NumKeys() / 4;
732 // Validate inputs
733 if (NumSegments == 0)
734 {
735 // Special case for empty spline
737 if (InitializeFirstSegment(0.0f, Position, NewPointIndex))
738 return NewPointIndex;
739 }
740
741 SegmentIndex = FMath::Clamp(SegmentIndex, 0, NumSegments - 1);
742
743 // Get the segment's parameter range
745
746 // Convert local parameter to global parameter
747 float Parameter = SegmentRange.Min + LocalT * (SegmentRange.Max - SegmentRange.Min);
748
749 return InsertPoint(Parameter, Position, ParameterizationPolicy);
750 }
751
764 float Parameter,
765 const ValueType& Position,
766 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
767 {
768 // todo: stop returning bool, instead interpret INDEX_NONE as failure
770 InitializeFirstSegment(Parameter, Position, NewPointIndex))
771 {
772 return NewPointIndex;
773 }
774
775 // Make sure we have a valid parameter
776 Parameter = Base::GetNearestAvailableKnotValue(Parameter);
777
779 int32 SegmentIndex;
780 float LocalT;
781 MapGlobalParameterToLocalSegment(Parameter, SegmentIndex, LocalT);
782
783 const int32 BaseIndex = SegmentIndex * 4;
784 ValueType P0 = Base::GetValue(BaseIndex);
785
786 // Handle out-of-range Knots
787 if (Parameter <= ParameterRange.Min)
788 {
789 // Prepend
790 return PrependPoint(Position, ParameterizationPolicy);
791 }
792
793 if (Parameter >= ParameterRange.Max)
794 {
795 return AppendPoint(Position, ParameterizationPolicy);
796 }
797
798 ValueType P1 = Base::GetValue(BaseIndex + 1);
799 ValueType P2 = Base::GetValue(BaseIndex + 2);
800 ValueType P3 = Base::GetValue(BaseIndex + 3);
801
802 // Use De Casteljau algorithm to split the curve
803 const float t = LocalT;
804 const float mt = 1.0f - t;
805
806 // Calculate split control points
807 ValueType Q0 = P0;
808 ValueType Q1 = P0 * mt + P1 * t;
809 ValueType Q2 = (P0 * mt + P1 * t) * mt + (P1 * mt + P2 * t) * t;
810 ValueType Q3 = Position; // Use provided position for the split point
811
812 ValueType R0 = Position; // Use provided position for the split point
813 ValueType R1 = (P1 * mt + P2 * t) * mt + (P2 * mt + P3 * t) * t;
814 ValueType R2 = P2 * mt + P3 * t;
815 ValueType R3 = P3;
816
818 float TargetSplitParam = SegmentRange.Interpolate(LocalT);
819
821 {
823 }
824
826 {
828 }
829
830 for (int32 i = 0; i < 4; ++i)
831 {
832 Base::RemoveValue(BaseIndex);
833 }
834
835 // Add the two new segments using the policy
836 AddBezierSegmentInternal(SegmentIndex, Q0, Q1, Q2, Q3, ParameterizationPolicy);
838 int32 IndexToReturn = AddBezierSegmentInternal(SegmentIndex + 1, R0, R1, R2, R3, ParameterizationPolicy);
839
840 return IndexToReturn;
841 }
842
855 float Parameter,
856 const ValueType& P1,
857 const ValueType& P2,
858 const ValueType& P3,
859 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
860 {
862 // Handle edge cases
863 if (Parameter >= ParameterRange.Max)
864 {
865 // Add new segment at the end
867 }
868
869 if (Parameter <= ParameterRange.Min)
870 {
871 // we treat P1, P2, P3 as the starting three control points, and connect to existing spline at the start
873 }
874
875 // Normal case - insert at parameter
876 const int32 NumSegments = Base::NumKeys() / 4;
877
878 // Find which segment contains this parameter
879 int32 SegmentIndex;
880 float LocalParam;
881 MapGlobalParameterToLocalSegment(Parameter, SegmentIndex, LocalParam);
882
883 // Evaluate position at the parameter
885
886 // Use the InsertPoint method to split the curve at the parameter
888
889 // Add the new segment from the inserted point
890 int32 Result = AddBezierSegmentInternal(SegmentIndex+1, PositionAtParam, P1, P2, P3, ParameterizationPolicy);
891
892 return Result;
893 }
894
902 bool RemoveSegment(const int32 SegmentIndex)
903 {
904 const int32 NumSegments = Base::NumKeys() / 4;
905 if (SegmentIndex < 0 || SegmentIndex >= NumSegments)
906 {
907 return false;
908 }
909
910 // Calculate storage indices
911 const bool bIsLastSegment = (SegmentIndex == NumSegments - 1);
912
913 UE_LOG(LogSpline, Verbose, TEXT("Removing segment at index %d"), SegmentIndex);
914
915 if (bIsLastSegment)
916 {
918 }
919 else
920 {
921 // in all other cases, removing the segment would be the same as removing the point at that index
922 return RemovePoint(SegmentIndex);
923 }
924 }
925
931 {
932 const int32 NumSegments = GetNumberOfSegments();
933 if (PointIndex < 0 || PointIndex > NumSegments)
934 {
935 return false;
936 }
937 bool bSuccess = true;
938 const int32 SegmentIndex = FMath::Clamp(PointIndex, 0, NumSegments - 1);
939 const int32 BaseIndex = SegmentIndex * 4;
940 const int32 Degree = GetDegree();
941 if (PointIndex == 0)
942 {
943 // Removing the first point - remove the first segment
944 for (int32 i = 3; i >= 0; --i)
945 {
946 bSuccess &= Base::RemoveValue(BaseIndex + i);
947 }
948 // Remove first knot
950
952 {
953 Base::PairKnots[0].Multiplicity = Degree + 1;
954 }
955 }
956 // else if last point
957 else if (PointIndex == GetNumberOfSegments())
958 {
959 // Removing the last point - remove the last segment
960 for (int32 i = 3; i >= 0; --i)
961 {
962 bSuccess &= Base::RemoveValue(BaseIndex + i);
963 }
964
965 // Remove end knot
966 Base::RemoveKnot(SegmentIndex + 1);
968 {
969 Base::PairKnots.Last().Multiplicity = Degree + 1;
970 }
971 }
972 else
973 {
974 // For an internal point, we need to remove exactly 4 control points:
975 // - P2 and P3 from the previous segment (where P3 is the point)
976 // - P0 and P1 from the next segment (where P0 is the same point)
977
978 // Start index for removal: P2 of previous segment
979 int32 StartRemoveIndex = (PointIndex - 1) * 4 + 2;
980
981 // Remove 4 consecutive control points
982 for (int32 i = 0; i < 4; ++i)
983 {
985 }
986
988 }
989 Base::Dump();
990 return bSuccess;
991 }
992
1002 const int32 SegmentIndex,
1003 const int32 PointIndex,
1004 const ValueType& NewValue)
1005 {
1006 const int32 NumSegments = Base::NumKeys() / 4;
1007 if (SegmentIndex < 0 || SegmentIndex >= NumSegments ||
1009 {
1010 return false;
1011 }
1012
1013 // Convert segment+point index to global index in spline values
1014 const int32 GlobalIndex = SegmentIndex * 4 + PointIndex;
1015 return Base::SetValue(GlobalIndex, NewValue);
1016 }
1017
1028 int32 SegmentIndex,
1029 const ValueType& P0,
1030 const ValueType& P1,
1031 const ValueType& P2,
1032 const ValueType& P3)
1033 {
1034 const int32 NumSegments = Base::NumKeys() / 4;
1035 if (SegmentIndex < 0 || SegmentIndex >= NumSegments)
1036 {
1037 return false;
1038 }
1039
1040 const int32 BaseIndex = SegmentIndex * 4;
1041 bool bSuccess = true;
1042 bSuccess &= Base::SetValue(BaseIndex, P0);
1043 bSuccess &= Base::SetValue(BaseIndex + 1, P1);
1044 bSuccess &= Base::SetValue(BaseIndex + 2, P2);
1045 bSuccess &= Base::SetValue(BaseIndex + 3, P3);
1046
1047 return bSuccess;
1048 }
1049
1051 virtual int32 GetNumberOfSegments() const override
1052 {
1053 const int32 NumPoints = this->Base::NumKeys();
1054 return NumPoints/4;
1055 }
1056
1062 virtual FInterval1f GetSegmentParameterRange(int32 SegmentIndex) const override
1063 {
1065 if (SegmentIndex >= 0 && SegmentIndex < Base::PairKnots.Num() - 1)
1066 {
1067 SegmentRange.Min = Base::PairKnots[SegmentIndex].Value;
1068 SegmentRange.Max = Base::PairKnots[SegmentIndex + 1].Value;
1069 }
1070
1071 return SegmentRange;
1072 }
1073
1074 // Parameterization methods
1081 virtual void Reparameterize(EParameterizationPolicy Mode = EParameterizationPolicy::Centripetal) override
1082 {
1083 const TArray<ValueType>& Points = this->Values;
1084 const int32 NumValues = Points.Num();
1085
1086 GenerateDistanceKnotsForBezier(Mode);
1087
1088 UE_LOG(LogSpline, Verbose, TEXT("Set knot vector - Points: %d, Knots: %d, Mode: %d"),
1089 NumValues, this->PairKnots.Num(), static_cast<int32>(Mode));
1090
1092 }
1093
1094 virtual FInterval1f GetParameterSpace() const override
1095 {
1096 return Base::PairKnots.Num() > 0
1099 }
1100
1101 virtual float GetParameter(int32 Index) const override
1102 {
1104 return 0.f;
1105
1106 int32 NumSegments = Base::PairKnots.Num() - 1;
1107 int32 SegmentIndex = Index / 4;
1109
1110 if (SegmentIndex >= NumSegments)
1111 return Base::PairKnots.Last().Value;
1112
1113 FInterval1f Range(Base::PairKnots[SegmentIndex].Value, Base::PairKnots[SegmentIndex + 1].Value);
1114 const float T = static_cast<float>(LocalPointIndex) / 3.0f;
1115 return Range.Interpolate(T);
1116 }
1117
1118 virtual int32 SetParameter(int32 Index, float NewParameter) override
1119 {
1121 {
1122 return INDEX_NONE;
1123 }
1124
1125 constexpr int32 PointsPerSegment = 4;
1126 const int32 SegmentIndex = Index / PointsPerSegment;
1128 int32 NumSegments = GetNumberOfSegments();
1129
1130 // Get the original parameter value
1131 int32 OldParameterIndex = LocalPointIndex == 0 ? SegmentIndex : SegmentIndex + 1;
1133
1134 // Early exit if parameter isn't changing
1136 {
1137 return Index;
1138 }
1139
1140 UE_LOG(LogSpline, Verbose, TEXT("\t"));
1141 UE_LOG(LogSpline, Verbose, TEXT("Before SetParameter(%d, %f):"), Index, NewParameter);
1142 Base::Dump();
1143
1144 // Prevent reuse of an existing knot.
1145 const float DesiredNewParameter = NewParameter;
1146 const float ActualNewParameter = this->GetNearestAvailableKnotValue(DesiredNewParameter);
1147
1148 const float DesiredToOldDistance = FMath::Abs(DesiredNewParameter - OldParameter);
1150
1152 {
1153 // If our nearest valid knot is further away from the desired value than our current value is, we need to just keep our current value
1154 return Index;
1155 }
1156
1158
1159 if (LocalPointIndex == 0) // P0
1160 {
1161 int32 LeftSegmentIndex = SegmentIndex - 1;
1162 int32 RightSegmentIndex = SegmentIndex;
1163
1165
1166 int32 Segment = SegmentIndex;
1168 {
1170 {
1175 }
1176 }
1177 else if (NewParameter > OldParameter)
1178 {
1180 {
1185 }
1186 }
1187
1189 }
1190 else if (LocalPointIndex == 3) // P3
1191 {
1192 int32 LeftSegmentIndex = SegmentIndex;
1193 int32 RightSegmentIndex = SegmentIndex + 1;
1194
1196
1197 int32 Segment = SegmentIndex;
1199 {
1201 {
1206 }
1207 }
1208 else if (NewParameter > OldParameter)
1209 {
1211 {
1216 }
1217 }
1218
1220 }
1221
1222 UE_LOG(LogSpline, Verbose, TEXT("\t"));
1223 UE_LOG(LogSpline, Verbose, TEXT("After SetParameter(%d, %f):"), Index, NewParameter);
1224 Base::Dump();
1225
1226 return ReturnIndex;
1227 }
1228
1230 {
1231 static constexpr int32 ControlPointsPerSegment = 4;
1232
1233 const int32 NumSegments = GetNumberOfSegments();
1234 if (Segment < 0 || Segment >= NumSegments)
1235 {
1236 return;
1237 }
1238
1239 UE_LOG(LogSpline, Verbose, TEXT("\t"));
1240 UE_LOG(LogSpline, Verbose, TEXT("Before FlipSegment(%d):"), Segment);
1241 Base::Dump();
1242
1243 // Flip the segment
1244 {
1249
1250 Base::Values.Swap(P0, P3);
1251 Base::Values.Swap(P1, P2);
1252 }
1253
1254 // Fix up neighbor segment by keeping P3 and P0 coincident and preserving the out-tangent of the end of the neighbor segment
1255 if (Segment != 0)
1256 {
1257 // Set P3 of Segment - 1 to P0 of Segment
1258 // Need to preserve P2 relative to P3 on Segment - 1
1259 int32 P2 = (Segment - 1) * ControlPointsPerSegment + 2;
1260 int32 P3 = (Segment - 1) * ControlPointsPerSegment + 3;
1262
1264 Base::Values[P3] = Base::Values[P0];
1265 Base::Values[P2] = Base::Values[P3] - Delta;
1266 }
1267
1268 // Fix up neighbor segment by keeping P0 and P3 coincident and preserving the in-tangent of the beginning of the neighbor segment
1269 if (Segment != NumSegments - 1)
1270 {
1271 // Set P0 of Segment + 1 to P3 of Segment
1272 // Need to preserve P1 relative to P0 on Segment + 1
1273 int32 P0 = (Segment + 1) * ControlPointsPerSegment + 0;
1274 int32 P1 = (Segment + 1) * ControlPointsPerSegment + 1;
1276
1278 Base::Values[P0] = Base::Values[P3];
1279 Base::Values[P1] = Base::Values[P0] - Delta;
1280 }
1281
1282 // Fix up the knot vector
1284
1285 UE_LOG(LogSpline, Verbose, TEXT("\t"));
1286 UE_LOG(LogSpline, Verbose, TEXT("After FlipSegment(%d):"), Segment);
1287 Base::Dump();
1288 }
1289
1290 virtual int32 FindIndexForParameter(float Parameter, float& OutLocalParam) const override
1291 {
1292 int32 NumSegments = Base::PairKnots.Num() - 1;
1293
1294 // todo: either delete this function in favor of MapGlobalParameterToLocalSegment or fix the loop to binary search with caching
1295
1296 for (int32 i = 0; i < NumSegments; ++i)
1297 {
1298 float Start = Base::PairKnots[i].Value;
1299 float End = Base::PairKnots[i + 1].Value;
1300 // Exact match on last segment end point
1301 if (i == NumSegments - 1 && Parameter == End)
1302 {
1303 OutLocalParam = 1.0f;
1304 return i * 4;
1305 }
1306
1307 if (Parameter >= Start && Parameter < End)
1308 {
1309 float SpanLength = End - Start;
1310
1312 ? (Parameter - Start) / SpanLength
1313 : 0.0f;
1314
1315 return i * 4;
1316 }
1317 }
1318 OutLocalParam = 0.0f;
1319 return 0; // Fallback to first segment
1320 }
1321
1326 void SetClosedLoopFlag(bool bClosed)
1327 {
1328 Base::bIsClosedLoop = bClosed;
1329 }
1330
1331 virtual void SetClosedLoop(bool bShouldClose) override
1332 {
1333 // Skip if state isn't changing
1334 if (bShouldClose == this->IsClosedLoop())
1335 return;
1336
1337 if (bShouldClose)
1338 {
1339 // When closing a loop, add a segment to connect the last point back to the first
1340 const int32 NumSegments = GetNumberOfSegments();
1341
1342 if (NumSegments >= 1) // Need at least one segment to close
1343 {
1344 // Get first and last points
1345 ValueType FirstPoint = this->GetValue(0);
1346 ValueType LastPoint = this->GetValue((NumSegments - 1) * 4 + 3);
1347
1348 // If the last point isn't already equal to the first point
1349 if (!Math::Equals(FirstPoint, LastPoint, UE_SMALL_NUMBER))
1350 {
1351 // Calculate control points for a smooth transition
1352 ValueType Direction = FirstPoint - LastPoint;
1353 ValueType P1 = LastPoint + Direction * 0.33f;
1354 ValueType P2 = FirstPoint - Direction * 0.33f;
1355
1356 // Add the closing segment
1357 AppendBezierSegment(P1, P2, FirstPoint);
1358 }
1359 }
1360 }
1361 else
1362 {
1363 // For an open spline, remove the closing segment
1364 const int32 NumSegments = GetNumberOfSegments();
1365 if (NumSegments > 0)
1366 {
1367 // Remove the last segment
1368 this->RemoveSegment(NumSegments - 1);
1369 }
1370 }
1371
1372 // Set the flag
1374 }
1375
1382 float MapLocalSegmentParameterToGlobal(int32 SegmentIndex, float LocalParam) const
1383 {
1385 return SegmentRange.Interpolate(LocalParam);
1386 }
1387
1396 {
1397 // Handle parameter outside range
1398 if (Base::PairKnots.Num() < 2)
1399 {
1400 OutSegmentIndex = 0;
1401 OutLocalParam = 0.0f;
1402 return false;
1403 }
1404
1406 {
1407 OutSegmentIndex = 0;
1408 OutLocalParam = 0.0f;
1409 return true;
1410 }
1411
1413 {
1414 OutSegmentIndex = Base::PairKnots.Num() - 2;
1415 OutLocalParam = 1.0f;
1416 return true;
1417 }
1418
1419 int32 SegmentIndex = INDEX_NONE;
1420
1421 // todo: make CVar
1422 constexpr bool bEnableSegmentCaching = true;
1423 if (bEnableSegmentCaching && LastEvaluatedSegment != INDEX_NONE && LastEvaluatedSegment < Base::PairKnots.Num() - 1)
1424 {
1425 if (GlobalParam < Base::PairKnots[LastEvaluatedSegment + 1].Value && GlobalParam >= Base::PairKnots[LastEvaluatedSegment].Value)
1426 {
1427 // cache hit!
1428 SegmentIndex = LastEvaluatedSegment;
1429 }
1430 }
1431
1432 if (SegmentIndex == INDEX_NONE)
1433 {
1434 // cache miss!
1435 int32 SegmentLow = 0;
1436 int32 SegmentHigh = Base::PairKnots.Num() - 2;
1437 SegmentIndex = SegmentLow + (SegmentHigh - SegmentLow) / 2;
1438
1439 while (SegmentLow <= SegmentHigh)
1440 {
1441 int32 Mid = SegmentLow + (SegmentHigh - SegmentLow) / 2;
1442 const float& StartParam = Base::PairKnots[Mid].Value;
1443 const float& EndParam = Base::PairKnots[Mid + 1].Value;
1444
1446 {
1447 SegmentIndex = Mid;
1448 break;
1449 }
1450 else if (GlobalParam < StartParam)
1451 {
1452 SegmentHigh = Mid - 1;
1453 }
1454 else
1455 {
1456 SegmentLow = Mid + 1;
1457 }
1458 }
1459 }
1460
1461 OutSegmentIndex = SegmentIndex;
1462 LastEvaluatedSegment = SegmentIndex;
1463
1464 // Calculate local parameter
1465 float SegmentLength = Base::PairKnots[SegmentIndex + 1].Value - Base::PairKnots[SegmentIndex].Value;
1466 if (SegmentLength > UE_SMALL_NUMBER)
1467 {
1468 OutLocalParam = (GlobalParam - Base::PairKnots[SegmentIndex].Value) / SegmentLength;
1469 }
1470 else
1471 {
1472 OutLocalParam = 0.0f;
1473 }
1474
1475 return true;
1476 }
1477
1482 {
1483 return FMath::Max(0, Base::PairKnots.Num() - 1);
1484 }
1485
1486 int32 FindSegmentIndex(float Parameter, float &OutLocalParam) const
1487 {
1489 return ControlPointIdx / 4; // Each segment has 4 control points
1490 }
1491
1492 virtual int32 GetExpectedNumKnots() const override
1493 {
1494 const int32 NumValues = Base::NumKeys();
1495 // Each segment has 4 control points. For 1 segment, we need 8 knots (4 start, 4 end).
1496 int32 NumSegments = NumValues / 4;
1497
1498 // Special handling for incomplete first segment
1499 if (NumSegments == 0)
1500 {
1501 return 0;
1502 }
1503 return 4 + (NumSegments - 1) * 3 + 4;
1504 }
1505
1510
1511private:
1512
1518 void GenerateDistanceKnotsForBezier(EParameterizationPolicy Mode)
1519 {
1520 TArray<ValueType> Points = this->Values;
1521
1522 // For a Bezier spline, the control points don't all lie on the curve
1523 // So we need to compute chord lengths between segment endpoints only
1524 int NumSegments = Points.Num() / 4;
1525
1527 SegmentLengths.Reserve(NumSegments);
1528
1529 for (int32 i = 0; i < NumSegments; ++i)
1530 {
1531 const int32 StartIndex = i * 4;
1532 const int32 EndIndex = StartIndex + 3;
1533
1534 if (EndIndex < Points.Num())
1535 {
1536 SegmentLengths.Add(ComputeSegmentLength(Points[StartIndex], Points[EndIndex], Mode));
1537 }
1538 }
1539
1540 // Now create a knot vector that respects:
1541 // 1. Bezier segment structure (knot multiplicity)
1542 // 2. Proportional chord/centripetal lengths
1543
1544 // This will only return Val if Offset is exactly 0.f. Otherwise, it is guaranteed to change in the direction of Offset.
1545 // We sacrifice a bit of accuracy for the assumption that Val will actually change.
1546 auto SafeAdd = [](float Val, float Offset) -> float
1547 {
1548 if (Offset == 0.f) return Val;
1549
1550 const float Result = Val + Offset;
1551 if (Result != Val) return Result;
1552
1553 // Guaranteed one-step progress even under FTZ/DAZ:
1556 : UE::Geometry::Spline::Param::EDir::Left);
1557 };
1558
1559 // Reset pair knots
1560 this->PairKnots.Reset();
1561
1562 typename Base::FValidKnotSearchParams SearchParams;
1563 SearchParams.bSearchLeft = false; // we are appending repeatedly, we want insertion order to be predictable (always go after conflicting knots).
1564
1565 // Add start knot with multiplicity 4
1566 SearchParams.DesiredParameter = 0.f;
1567 this->InsertKnot(FKnot(this->GetNearestAvailableKnotValue(SearchParams), 4));
1568
1569 // Calculate accumulated distances for internal knots
1570 float AccumLength = 0.0f;
1571 for (int32 i = 0; i < NumSegments - 1; ++i) // -1 because we don't need knots at the very end
1572 {
1573 if (i < SegmentLengths.Num())
1574 {
1576
1577 // Use normalized accumulated length as the desired knot value
1578 SearchParams.DesiredParameter = AccumLength;
1579
1580 // Interior knots at segment boundaries with multiplicity 3
1581 this->InsertKnot(FKnot(this->GetNearestAvailableKnotValue(SearchParams), 3));
1582 }
1583 }
1584
1585 SearchParams.DesiredParameter = SafeAdd(AccumLength, SegmentLengths.Num() > 0
1586 ? SegmentLengths.Last()
1587 : 0.0f);
1588
1589 this->InsertKnot(FKnot(this->GetNearestAvailableKnotValue(SearchParams), 4));
1590
1591 // Mark flat knots cache as dirty
1593 }
1594
1595 virtual FWindow FindWindow(float Parameter) const override
1596 {
1597 FWindow Window = {};
1598 if (Base::NumKeys() < 4) // Need at least one complete Bezier segment
1599 {
1600 return Window;
1601 }
1602 const TArray<ValueType>& Entries = this->Values;
1603 // Find which segment we're in
1604 const int32 NumSegments = Base::NumKeys() / 4;
1605
1606 int32 SegmentIndex;
1607 float LocalT;
1608 MapGlobalParameterToLocalSegment(Parameter, SegmentIndex, LocalT);
1609
1610 if (Base::IsClosedLoop())
1611 {
1612 // For closed loop, wrap the segment index
1613 SegmentIndex = SegmentIndex % NumSegments;
1614 if (SegmentIndex < 0) SegmentIndex += NumSegments;
1615 }
1616
1617 // Each Bezier segment has exactly 4 control points
1618 const int32 BaseIndex = SegmentIndex * 4;
1619
1620 if (Base::IsClosedLoop())
1621 {
1622 // Handle wrapping for closed loop
1623 const int32 NumPoints = Entries.Num();
1624 auto WrapIndex = [NumPoints](int32 Index) -> int32
1625 {
1626 return ((Index % NumPoints) + NumPoints) % NumPoints;
1627 };
1628
1629 for (int Idx = 0; Idx < Base::WindowSize; ++Idx)
1630 {
1631 Window[Idx] = &Entries[WrapIndex(BaseIndex + Idx)];
1632 }
1633 }
1634 else
1635 {
1636 // Return null window if BaseIndex is too large to fully populate the window.
1637 if (BaseIndex + 3 >= Entries.Num())
1638 {
1639 return Window;
1640 }
1641
1642 // Standard case - no wrapping
1643 for (int Idx = 0; Idx < Base::WindowSize; ++Idx)
1644 {
1645 Window[Idx] = &Entries[BaseIndex + Idx];
1646 }
1647 }
1648
1649 return Window;
1650 }
1651
1652 virtual ValueType InterpolateWindow(TArrayView<const ValueType* const> Window, float Parameter) const override
1653 {
1654 if (Window.Num() < 4)
1655 {
1656 return ValueType();
1657 }
1658 int32 NumSegments = GetNumberOfSegments();
1659 int32 SegmentIndex;
1660 float LocalT;
1661 MapGlobalParameterToLocalSegment(Parameter, SegmentIndex, LocalT);
1662
1663 // Calculate Bernstein basis functions
1664 const float t = LocalT;
1665 const float OneMinusT = 1.0f - t;
1666 const float t2 = t * t;
1667 const float OneMinusT2 = OneMinusT * OneMinusT;
1668
1669 // Cubic Bernstein polynomials
1670 const float Basis[4] =
1671 {
1672 OneMinusT * OneMinusT2, // (1-t)³
1673 3.0f * t * OneMinusT2, // 3t(1-t)²
1674 3.0f * t2 * OneMinusT, // 3t²(1-t)
1675 t * t2 // t³
1676 };
1677
1679 }
1680
1689 float FindLocalParameterNearestToPosition(
1690 int32 SegmentIndex,
1691 const ValueType& Position,
1692 float& OutSquaredDistance) const
1693 {
1694 int32 NumSegments = Base::NumKeys() / 4;
1695 // Validate segment index
1696 if (SegmentIndex < 0 || SegmentIndex >= NumSegments)
1697 {
1699 return 0.0f;
1700 }
1701
1702 // Get global parameter nearest to position
1704
1705 // Convert to local parameter
1707 float SegmentLength = SegmentRange.Max - SegmentRange.Min;
1708
1709 if (SegmentLength == 0.f)
1710 return 0.0f;
1711
1712 return (GlobalParam - SegmentRange.Min) / SegmentLength;
1713 }
1714
1720 bool IsValidSegmentIndex(int32 Index) const
1721 {
1722 int32 NumSegments = Base::NumKeys() / 4;
1723 return Index >= 0 && Index < NumSegments;
1724 }
1725
1733 bool InitializeFirstSegment(
1734 float Parameter,
1735 const ValueType& ControlPoint,
1737 {
1738 if (Base::NumKeys() < GetDegree())
1739 {
1740 // First point - just store it directly
1741 PointIndex = Base::AddValue(ControlPoint);
1742 return true;
1743 }
1744
1745 // Adding the fourth control point would create a segment
1746 if (Base::NumKeys() == GetDegree())
1747 {
1748 const ValueType& P0 = Base::GetValue(0);
1749 const ValueType& P1 = Base::GetValue(1);
1750 const ValueType& P2 = Base::GetValue(2);
1751 const ValueType& P3 = ControlPoint;
1752
1753 // Add the first segment
1754 SetControlPoints({P0, P1, P2, P3}, EParameterizationPolicy::Centripetal);
1755 PointIndex = 3;
1756 return true;
1757 }
1758 return false;
1759 }
1760
1768 int32 PrependPoint(
1769 const ValueType& Position,
1770 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal)
1771 {
1772 const TOptional<float> LoopKnotDelta = GetLoopKnotDelta();
1773 SetClosedLoop(false);
1775 {
1776 if (LoopKnotDelta.IsSet())
1777 {
1778 SetClosedLoop(true);
1780 }
1781 };
1782
1783 // Create a control point at the beginning
1784 ValueType P3 = Base::GetValue(0); // First point of the spline
1785
1786 // Calculate reasonable auto-tangents
1787 ValueType Dir = P3 - Position;
1788 ValueType P1 = Position + Dir * 0.33f;
1789 ValueType P2 = Position + Dir * 0.66f;
1790
1791 // Add the segment by reusing existing code
1792 int32 NewIndex = AddBezierSegmentInternal(0, Position, P1, P2, P3, ParameterizationPolicy, false);
1793 check (NewIndex == 0);
1794 return NewIndex;
1795 }
1796
1805 int32 AppendPoint(
1806 const ValueType& Position,
1808 {
1809 const TOptional<float> LoopKnotDelta = GetLoopKnotDelta();
1810 SetClosedLoop(false);
1812 {
1813 if (LoopKnotDelta.IsSet())
1814 {
1815 SetClosedLoop(true);
1817 }
1818 };
1819
1820 // Append - create a point at the end
1821 int32 LastIndex = Base::NumKeys() - 1;
1822 ValueType P0 = Base::GetValue(LastIndex);
1823
1824 // Calculate reasonable auto-tangents
1825 ValueType Dir = Position - P0;
1826 ValueType P1 = P0 + Dir * 0.33f;
1827 ValueType P2 = P0 + Dir * 0.66f;
1828
1829 int32 NumSegments = Base::NumKeys() / 4;
1830 // Add the segment
1831 int32 NewIndex = AddBezierSegmentInternal(NumSegments, P0, P1, P2, Position, ParameterizationPolicy/*, StartParam*/);
1832
1833 return NewIndex;
1834 }
1835
1839 float ComputeSegmentLength(
1840 const ValueType& P0,
1841 const ValueType& P3,
1843 {
1844 switch (ParameterizationPolicy)
1845 {
1846 case EParameterizationPolicy::ChordLength:
1847 return static_cast<float>(Math::Distance(P0, P3));
1848 case EParameterizationPolicy::Centripetal:
1849 return static_cast<float>(Math::CentripetalDistance(P0, P3));
1850 case EParameterizationPolicy::Uniform:
1851 default:
1852 return 1.0f;
1853 }
1854 }
1855
1859 void ApplyInternalKnotsMultiplicity()
1860 {
1861 // fix internal knots to be clamped to Degree
1862 for (int32 i = 1; i < Base::PairKnots.Num() - 1; ++i)
1863 {
1864 Base::PairKnots[i].Multiplicity = GetDegree();
1865 }
1866 }
1867
1868 int32 AddBezierSegmentInternal(
1869 int32 SegmentIndex,
1870 const ValueType& P0,
1871 const ValueType& P1,
1872 const ValueType& P2,
1873 const ValueType& P3,
1874 EParameterizationPolicy ParameterizationPolicy = EParameterizationPolicy::Centripetal,
1875 bool bAppendToSegment = true) // true = connect to end, false = connect to start
1876 {
1877 const int32 NumSegments = Base::NumKeys() / 4;
1878 SegmentIndex = FMath::Clamp(SegmentIndex, 0, NumSegments);
1879
1880 // Calculate segment length
1881 float SegmentLength = ComputeSegmentLength(P0, P3, ParameterizationPolicy);
1882
1883 // Add the control points
1884 int32 ControlPointIndex = SegmentIndex * 4;
1889
1890 // FInterval1f RefRange = GetSegmentParameterRange(SegmentIndex);
1891 bool bAppendToEnd = bAppendToSegment && SegmentIndex > (this->GetNumDistinctSegments() - 1);
1892 bool bPrependToStart = !bAppendToSegment && SegmentIndex == 0;
1893
1894 // Insert knots based on append/prepend to the segment
1895 if (NumSegments == 0) // First segment
1896 {
1897 this->ResetKnotVector();
1898 this->InsertKnot( FKnot(0.0f, this->bClampEnds ? this->GetDegree() + 1 : 1));
1899 this->InsertKnot( FKnot(1.f, this->bClampEnds ? this->GetDegree() + 1 : 1));
1900 }
1901 else if (bAppendToEnd)
1902 {
1903 const float LastParameter = Base::PairKnots.Last().Value;
1904 const float CandidateByLength = LastParameter + SegmentLength;
1906 const float NextParameter = FMath::Max(CandidateByLength, CandidateByStep);
1907 this->InsertKnot(FKnot(NextParameter, this->bClampEnds ? this->GetDegree() : 1));
1908 }
1909 else if (bPrependToStart)
1910 {
1911 const float FirstParameter = Base::PairKnots[0].Value;
1912 const float CandidateByLength = FirstParameter - SegmentLength;
1914 const float NextParameter = FMath::Min(CandidateByLength, CandidateByStep);
1915 this->InsertKnot( FKnot(NextParameter, this->bClampEnds ? this->GetDegree() : 1));
1916 }
1917
1919
1920 // make sure the internal knots have the correct multiplicity as one of the end knots
1921 // might have become an internal knot
1922 this->ApplyInternalKnotsMultiplicity();
1923 Base::Dump();
1924 return SegmentIndex;
1925 }
1926
1928 TOptional<float> GetLoopKnotDelta() const
1929 {
1930 return Base::IsClosedLoop()
1931 ? Base::PairKnots[Base::PairKnots.Num() - 1].Value - Base::PairKnots[Base::PairKnots.Num() - 2].Value
1932 : TOptional<float>();
1933 }
1934
1935private:
1936
1938 mutable int32 LastEvaluatedSegment = INDEX_NONE;
1939};
1940
1941
1942
1943// Common type definitions
1948} // end namespace UE::Geometry::Spline
1949} // end namespace UE::Geometry
1950} // end namespace UE
constexpr auto MakeArrayView(OtherRangeType &&Other)
Definition ArrayView.h:873
#define check(expr)
Definition AssertionMacros.h:314
bool bSuccess
Definition ConvexDecomposition3.cpp:819
@ INDEX_NONE
Definition CoreMiscDefines.h:150
#define UE_EXPERIMENTAL(Version, Message)
Definition CoreMiscDefines.h:369
#define TEXT(x)
Definition Platform.h:1272
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
@ Num
Definition MetalRHIPrivate.h:234
#define ON_SCOPE_EXIT
Definition ScopeExit.h:73
#define UE_PI
Definition UnrealMathUtility.h:129
#define UE_SMALL_NUMBER
Definition UnrealMathUtility.h:130
#define UE_KINDA_SMALL_NUMBER
Definition UnrealMathUtility.h:131
float Val(const FString &Value)
Definition UnrealMath.cpp:3163
uint32 Offset
Definition VulkanMemory.cpp:4033
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 SetNum(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2308
UE_FORCEINLINE_HINT void Swap(SizeType FirstIndexToSwap, SizeType SecondIndexToSwap)
Definition Array.h:3300
UE_FORCEINLINE_HINT void Reserve(SizeType Number)
Definition Array.h:3016
Definition StaticArray.h:26
Definition UniquePtr.h:107
Definition BSpline.h:126
int32 InsertValue(int32 Idx, const ValueType &NewValue)
Definition BSpline.h:320
virtual void SetClosedLoop(bool bClosed) override
Definition BSpline.h:228
virtual bool IsClosedLoop() const override
Definition BSpline.h:233
TStaticArray< const ValueType *, WindowSize > FWindow
Definition BSpline.h:132
float GetNearestAvailableKnotValue(const FValidKnotSearchParams &InSearchParams) const
Definition BSpline.h:679
static constexpr int32 Degree
Definition BSpline.h:128
void MarkFlatKnotsCacheDirty() const
Definition BSpline.h:1115
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
void ApplyClampedKnotsMultiplicity()
Definition BSpline.h:1089
void PrintKnotVector() const
Definition BSpline.h:1123
TArray< ValueType > Values
Definition BSpline.h:1258
virtual bool RemoveValue(int32 Index)
Definition BSpline.h:329
typename TSplineInterface< VALUETYPE >::ValueType ValueType
Definition BSpline.h:129
int32 NumKeys() const
Definition BSpline.h:295
bool IsClampedEnds() const
Definition BSpline.h:554
bool bIsClosedLoop
Definition BSpline.h:1267
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
bool RemoveKnot(int32 KnotIdx)
Definition BSpline.h:593
void SwapKnots(int32 KnotIdxA, int32 KnotIdxB)
Definition BSpline.h:611
int32 AddValue(const ValueType &NewValue)
Definition BSpline.h:305
void Dump() const
Definition BSpline.h:152
void ResetKnotVector()
Definition BSpline.h:433
bool SetCustomKnots(const TArray< FKnot > &NewKnots)
Definition BSpline.h:459
static constexpr int32 WindowSize
Definition BSpline.h:131
Definition PolyBezierSpline.h:22
bool AddBezierSegments(const TArray< ValueType > &Points, bool bAppend, EParameterizationPolicy ParameterizationPolicy)
Definition PolyBezierSpline.h:505
bool UpdateSegment(int32 SegmentIndex, const ValueType &P0, const ValueType &P1, const ValueType &P2, const ValueType &P3)
Definition PolyBezierSpline.h:1027
int32 GetNumDistinctSegments() const
Definition PolyBezierSpline.h:1481
ValueType EvaluateDerivative(float Parameter) const
Definition PolyBezierSpline.h:341
int32 AppendBezierSegment(const ValueType &P1, const ValueType &P2, const ValueType &P3, EParameterizationPolicy ParameterizationPolicy=EParameterizationPolicy::Centripetal)
Definition PolyBezierSpline.h:633
virtual int32 GetNumberOfSegments() const override
Definition PolyBezierSpline.h:1051
virtual void Clear() override
Definition PolyBezierSpline.h:262
virtual void SetClosedLoop(bool bShouldClose) override
Definition PolyBezierSpline.h:1331
virtual FInterval1f GetSegmentParameterRange(int32 SegmentIndex) const override
Definition PolyBezierSpline.h:1062
int32 FindSegmentIndex(float Parameter, float &OutLocalParam) const
Definition PolyBezierSpline.h:1486
bool RemoveSegment(const int32 SegmentIndex)
Definition PolyBezierSpline.h:902
static TPolyBezierSpline CreateDefault()
Definition PolyBezierSpline.h:53
virtual FInterval1f GetParameterSpace() const override
Definition PolyBezierSpline.h:1094
void SetClosedLoopFlag(bool bClosed)
Definition PolyBezierSpline.h:1326
void FlipSegment(int32 Segment)
Definition PolyBezierSpline.h:1229
int32 InsertBezierSegment(float Parameter, const ValueType &P1, const ValueType &P2, const ValueType &P3, EParameterizationPolicy ParameterizationPolicy=EParameterizationPolicy::Centripetal)
Definition PolyBezierSpline.h:854
virtual int32 SetParameter(int32 Index, float NewParameter) override
Definition PolyBezierSpline.h:1118
virtual float GetParameter(int32 Index) const override
Definition PolyBezierSpline.h:1101
Base::ValueType ValueType
Definition PolyBezierSpline.h:25
static int32 GetDegree()
Definition PolyBezierSpline.h:332
static TPolyBezierSpline< ValueType > CreateLine(const ValueType &Start, const ValueType &End)
Definition PolyBezierSpline.h:66
int32 InsertPoint(float Parameter, const ValueType &Position, EParameterizationPolicy ParameterizationPolicy=EParameterizationPolicy::Centripetal)
Definition PolyBezierSpline.h:763
TPolyBezierSpline(const ValueType &P0, const ValueType &P1, const ValueType &P2, const ValueType &P3, EParameterizationPolicy Parameterization=EParameterizationPolicy::Uniform)
Definition PolyBezierSpline.h:37
bool MapGlobalParameterToLocalSegment(float GlobalParam, int32 &OutSegmentIndex, float &OutLocalParam) const
Definition PolyBezierSpline.h:1395
virtual void Reparameterize(EParameterizationPolicy Mode=EParameterizationPolicy::Centripetal) override
Definition PolyBezierSpline.h:1081
int32 InsertPointAtPosition(int32 SegmentIndex, const ValueType &Position, EParameterizationPolicy ParameterizationPolicy=EParameterizationPolicy::Centripetal)
Definition PolyBezierSpline.h:701
bool UpdateSegmentPoint(const int32 SegmentIndex, const int32 PointIndex, const ValueType &NewValue)
Definition PolyBezierSpline.h:1001
virtual TUniquePtr< ISplineInterface > Clone() const override
Definition PolyBezierSpline.h:263
typename Base::FWindow FWindow
Definition PolyBezierSpline.h:26
bool RemovePoint(int32 PointIndex)
Definition PolyBezierSpline.h:930
static TPolyBezierSpline< ValueType > CreateCircleArc(const ValueType &Center, float Radius, float StartAngle, float EndAngle, int32 NumSegments=4)
Definition PolyBezierSpline.h:84
virtual ~TPolyBezierSpline() override=default
DECLARE_SPLINE_TYPE_ID(TEXT("PolyBezier"), *TSplineValueTypeTraits< VALUETYPE >::Name)
int32 InsertPointAtSegmentParam(int32 SegmentIndex, float LocalT, const ValueType &Position, EParameterizationPolicy ParameterizationPolicy=EParameterizationPolicy::Centripetal)
Definition PolyBezierSpline.h:725
static TPolyBezierSpline< ValueType > CreateEllipse(const ValueType &Center, float RadiusX, float RadiusY, int32 NumSegments=4)
Definition PolyBezierSpline.h:192
virtual int32 GetExpectedNumKnots() const override
Definition PolyBezierSpline.h:1492
static TPolyBezierSpline< ValueType > CreateCircle(const ValueType &Center, float Radius, int32 NumSegments=4)
Definition PolyBezierSpline.h:172
virtual int32 FindIndexForParameter(float Parameter, float &OutLocalParam) const override
Definition PolyBezierSpline.h:1290
float MapLocalSegmentParameterToGlobal(int32 SegmentIndex, float LocalParam) const
Definition PolyBezierSpline.h:1382
float FindNearestOnSegment(const ValueType &Point, int32 SegmentIndex, float &OutSquaredDistance) const
Definition PolyBezierSpline.h:280
void SetKnotVector(const TArray< FKnot > &NewKnots)
Definition PolyBezierSpline.h:1506
virtual float FindNearest(const ValueType &Point, float &OutSquaredDistance) const override
Definition PolyBezierSpline.h:308
int32 PrependBezierSegment(const ValueType &P0, const ValueType &P1, const ValueType &P2, EParameterizationPolicy ParameterizationPolicy=EParameterizationPolicy::Centripetal)
Definition PolyBezierSpline.h:668
bool SetControlPoints(const TArray< ValueType > &Points, EParameterizationPolicy ParameterizationPolicy)
Definition PolyBezierSpline.h:379
Definition SplineTypeRegistry.h:236
EOutOfBoundsHandlingMode PostInfinityMode
Definition SplineInterfaces.h:191
EOutOfBoundsHandlingMode PreInfinityMode
Definition SplineInterfaces.h:190
ValueType Evaluate(float Parameter) const
Definition SplineInterfaces.h:117
static T InterpolateWithBasis(TArrayView< const T *const > Window, TArrayView< const float > Basis)
Definition InterpolationPolicies.h:61
CORE_API void Reset(int32 NewReservedSize=0)
Definition String.cpp.inl:326
int32 WrapIndex(int32 V, int32 Begin, int32 End)
Definition Utilities.h:632
@ ControlPoint
Definition Visu.h:27
float FindNearestPoint_Cubic(const TArrayView< ValueType > Coeffs, float StartParam, float EndParam, float &OutSquaredDistance)
Definition SplineMath.h:757
bool Equals(const T &A, const T &B, float Tolerance=UE_KINDA_SMALL_NUMBER)
Definition SplineMath.h:242
double CentripetalDistance(const T &A, const T &B)
Definition SplineMath.h:198
T GetSafeNormal(const T &Value)
Definition SplineMath.h:221
double Distance(const T &A, const T &B)
Definition SplineMath.h:186
double SizeSquared(const T &Value)
Definition SplineMath.h:172
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
T DistanceSquared(const UE::Math::TVector2< T > &V1, const UE::Math::TVector2< T > &V2)
Definition VectorTypes.h:82
Definition AdvancedWidgetsModule.cpp:13
U16 Index
Definition radfft.cpp:71
static constexpr UE_FORCEINLINE_HINT T Clamp(const T X, const T MinValue, const T MaxValue)
Definition UnrealMathUtility.h:592
Definition NumericLimits.h:41
Definition Optional.h:131
Definition BSpline.h:34
static T Compute(TArrayView< const T *const > Window, float Parameter, float SegmentScale)
Definition SplineMath.h:702
RealType Min
Definition BoxTypes.h:20
static TInterval1< float > Empty()
Definition BoxTypes.h:34