Files
ProjectEleri/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesRuntime/Private/Libraries/DirectiveUtilMathFunctionLibrary.cpp

2098 lines
65 KiB
C++

// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#include "Libraries/DirectiveUtilMathFunctionLibrary.h"
#include "Components/SplineComponent.h"
#include "Math/RotationMatrix.h"
#include <limits>
namespace
{
constexpr double DirectionDotTolerance = 8.0 * std::numeric_limits<double>::epsilon();
constexpr double ProjectionTolerance = 32.0 * std::numeric_limits<double>::epsilon();
bool IsSupportedGeneratedElementCount(const int64 Count)
{
return Count > 0 && Count <= UDirectiveUtilMathFunctionLibrary::MaximumGeneratedElementCount;
}
bool IsFiniteVector2D(const FVector2D& Value)
{
return FMath::IsFinite(Value.X) && FMath::IsFinite(Value.Y);
}
bool IsFiniteVector(const FVector& Value)
{
return FMath::IsFinite(Value.X) && FMath::IsFinite(Value.Y) && FMath::IsFinite(Value.Z);
}
bool TryGetNormalizedVector(const FVector& Value, FVector& Normalized, double* Length = nullptr)
{
Normalized = FVector::ZeroVector;
if (Value.ContainsNaN())
{
return false;
}
const double MaximumComponent = Value.GetAbsMax();
if (MaximumComponent == 0.0)
{
return false;
}
const FVector Scaled = Value / MaximumComponent;
const double ScaledLength = Scaled.Size();
if (!FMath::IsFinite(ScaledLength) || ScaledLength == 0.0)
{
return false;
}
Normalized = Scaled / ScaledLength;
if (Length)
{
if (ScaledLength > TNumericLimits<double>::Max() / MaximumComponent)
{
Normalized = FVector::ZeroVector;
return false;
}
*Length = MaximumComponent * ScaledLength;
}
return !Normalized.ContainsNaN();
}
bool TryGetProjectedDirection(const FVector& Value, const FVector& NormalizedAxis, FVector& Direction)
{
Direction = FVector::ZeroVector;
if (Value.ContainsNaN())
{
return false;
}
const double MaximumComponent = Value.GetAbsMax();
if (MaximumComponent == 0.0)
{
return false;
}
const FVector Scaled = Value / MaximumComponent;
const FVector Projected = FVector::VectorPlaneProject(Scaled, NormalizedAxis);
if (Projected.GetAbsMax() <= ProjectionTolerance)
{
return false;
}
return TryGetNormalizedVector(Projected, Direction);
}
bool IsNormalizedDirectionWithinCone(const FVector& NormalizedDirection, const FVector& ConeDirection,
const float ConeHalfAngleDegrees)
{
FVector NormalizedConeDirection;
if (!FMath::IsFinite(ConeHalfAngleDegrees)
|| !TryGetNormalizedVector(ConeDirection, NormalizedConeDirection))
{
return false;
}
const double Dot = FMath::Clamp(
FVector::DotProduct(NormalizedDirection, NormalizedConeDirection), -1.0, 1.0);
const double ClampedHalfAngle = FMath::Clamp(static_cast<double>(ConeHalfAngleDegrees), 0.0, 180.0);
return Dot + DirectionDotTolerance >= FMath::Cos(FMath::DegreesToRadians(ClampedHalfAngle));
}
bool TryGetRotationQuaternion(const FRotator& Rotation, FQuat& Quaternion)
{
Quaternion = FQuat::Identity;
if (!FMath::IsFinite(Rotation.Pitch) || !FMath::IsFinite(Rotation.Yaw) || !FMath::IsFinite(Rotation.Roll))
{
return false;
}
Quaternion = Rotation.Quaternion();
return !Quaternion.ContainsNaN();
}
bool TryGetRotatedAxes(const FRotator& Rotation, FVector& AxisX, FVector& AxisY, FVector& AxisZ)
{
AxisX = FVector::ZeroVector;
AxisY = FVector::ZeroVector;
AxisZ = FVector::ZeroVector;
FQuat Quaternion;
if (!TryGetRotationQuaternion(Rotation, Quaternion))
{
return false;
}
AxisX = Quaternion.GetAxisX();
AxisY = Quaternion.GetAxisY();
AxisZ = Quaternion.GetAxisZ();
return !AxisX.ContainsNaN() && !AxisY.ContainsNaN() && !AxisZ.ContainsNaN();
}
struct FHexLayout
{
FVector Origin = FVector::ZeroVector;
FVector AxisX = FVector::ZeroVector;
FVector AxisY = FVector::ZeroVector;
FVector StepQ = FVector::ZeroVector;
FVector StepR = FVector::ZeroVector;
double LayoutRadius = 0.0;
EDirectiveUtilHexOrientation Orientation = EDirectiveUtilHexOrientation::PointyTop;
FVector GetLocation(const int64 Q, const int64 R) const
{
return Origin + StepQ * static_cast<double>(Q) + StepR * static_cast<double>(R);
}
};
bool TryMakeHexLayout(const FVector& Origin, const FRotator& Rotation, const double CellRadius,
const double Gap, const EDirectiveUtilHexOrientation Orientation, FHexLayout& Layout)
{
FVector AxisX;
FVector AxisY;
FVector AxisZ;
if (Origin.ContainsNaN() || !FMath::IsFinite(CellRadius) || CellRadius <= 0.0
|| !FMath::IsFinite(Gap) || !TryGetRotatedAxes(Rotation, AxisX, AxisY, AxisZ))
{
return false;
}
const double LayoutRadius = CellRadius + Gap / UE_DOUBLE_SQRT_3;
if (!FMath::IsFinite(LayoutRadius) || LayoutRadius <= 0.0)
{
return false;
}
Layout.Origin = Origin;
Layout.AxisX = AxisX;
Layout.AxisY = AxisY;
Layout.LayoutRadius = LayoutRadius;
Layout.Orientation = Orientation;
switch (Orientation)
{
case EDirectiveUtilHexOrientation::PointyTop:
Layout.StepQ = AxisX * (UE_DOUBLE_SQRT_3 * LayoutRadius);
Layout.StepR = (AxisX * (UE_DOUBLE_SQRT_3 * 0.5) + AxisY * 1.5) * LayoutRadius;
break;
case EDirectiveUtilHexOrientation::FlatTop:
Layout.StepQ = (AxisX * 1.5 + AxisY * (UE_DOUBLE_SQRT_3 * 0.5)) * LayoutRadius;
Layout.StepR = AxisY * (UE_DOUBLE_SQRT_3 * LayoutRadius);
break;
default:
return false;
}
return !Layout.StepQ.ContainsNaN() && !Layout.StepR.ContainsNaN();
}
constexpr int32 HexDirections[6][2] = {
{ 1, 0 }, { 1, -1 }, { 0, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }
};
void GetHexOffsetCoordinate(const EDirectiveUtilHexOrientation Orientation, const int32 Column,
const int32 Row, int64& Q, int64& R)
{
if (Orientation == EDirectiveUtilHexOrientation::PointyTop)
{
Q = static_cast<int64>(Column) - (Row - (Row & 1)) / 2;
R = Row;
}
else
{
Q = Column;
R = static_cast<int64>(Row) - (Column - (Column & 1)) / 2;
}
}
bool TryMakeHexCoordinate(const int64 Q, const int64 R, FIntPoint& Coordinate)
{
if (Q < MIN_int32 || Q > MAX_int32 || R < MIN_int32 || R > MAX_int32)
{
return false;
}
Coordinate = FIntPoint(static_cast<int32>(Q), static_cast<int32>(R));
return true;
}
bool TryValidateHexLayoutSpan(const FHexLayout& Layout, const int64 MaxAbsQ, const int64 MaxAbsR)
{
if (MaxAbsQ < 0 || MaxAbsR < 0)
{
return false;
}
const FVector Bound = Layout.Origin.GetAbs()
+ Layout.StepQ.GetAbs() * static_cast<double>(MaxAbsQ)
+ Layout.StepR.GetAbs() * static_cast<double>(MaxAbsR);
return IsFiniteVector(Bound);
}
void WriteHexLocationUnchecked(const FHexLayout& Layout, const int64 Q, const int64 R, FVector& Destination)
{
Destination = Layout.GetLocation(Q, R);
}
bool TryApplyRectangularHexCentering(FHexLayout& Layout, const FIntPoint Dimensions, const bool bCentered)
{
if (!bCentered)
{
return true;
}
const double NeighborSpacing = UE_DOUBLE_SQRT_3 * Layout.LayoutRadius;
double CenterX;
double CenterY;
if (Layout.Orientation == EDirectiveUtilHexOrientation::PointyTop)
{
CenterX = NeighborSpacing * (Dimensions.X - 1 + (Dimensions.Y > 1 ? 0.5 : 0.0)) * 0.5;
CenterY = 1.5 * Layout.LayoutRadius * (Dimensions.Y - 1) * 0.5;
}
else
{
CenterX = 1.5 * Layout.LayoutRadius * (Dimensions.X - 1) * 0.5;
CenterY = NeighborSpacing * (Dimensions.Y - 1 + (Dimensions.X > 1 ? 0.5 : 0.0)) * 0.5;
}
Layout.Origin -= Layout.AxisX * CenterX + Layout.AxisY * CenterY;
return !Layout.Origin.ContainsNaN();
}
int64 GetRectangularHexSpan(const FIntPoint Dimensions)
{
// Offset coordinates stay within |Q|,|R| ≤ Columns+Rows for both orientations.
return static_cast<int64>(Dimensions.X) + Dimensions.Y;
}
template <typename VisitorType>
bool VisitHexagonalCoordinates(const int64 Radius, VisitorType Visitor)
{
for (int64 R = -Radius; R <= Radius; ++R)
{
const int64 MinimumQ = FMath::Max(-Radius, -R - Radius);
const int64 MaximumQ = FMath::Min(Radius, -R + Radius);
for (int64 Q = MinimumQ; Q <= MaximumQ; ++Q)
{
if (!Visitor(Q, R))
{
return false;
}
}
}
return true;
}
template <typename VisitorType>
bool VisitRectangularHexCoordinates(const FIntPoint Dimensions,
const EDirectiveUtilHexOrientation Orientation, VisitorType Visitor)
{
for (int32 Row = 0; Row < Dimensions.Y; ++Row)
{
for (int32 Column = 0; Column < Dimensions.X; ++Column)
{
int64 Q;
int64 R;
GetHexOffsetCoordinate(Orientation, Column, Row, Q, R);
if (!Visitor(Q, R))
{
return false;
}
}
}
return true;
}
bool TryRoundHexCoordinate(const double FractionalQ, const double FractionalR, FIntPoint& Coordinate)
{
const double FractionalS = -FractionalQ - FractionalR;
if (!FMath::IsFinite(FractionalQ) || !FMath::IsFinite(FractionalR) || !FMath::IsFinite(FractionalS))
{
return false;
}
double Q = FMath::RoundHalfFromZero(FractionalQ);
double R = FMath::RoundHalfFromZero(FractionalR);
double S = FMath::RoundHalfFromZero(FractionalS);
const double QDifference = FMath::Abs(Q - FractionalQ);
const double RDifference = FMath::Abs(R - FractionalR);
const double SDifference = FMath::Abs(S - FractionalS);
if (QDifference > RDifference && QDifference > SDifference)
{
Q = -R - S;
}
else if (RDifference > SDifference)
{
R = -Q - S;
}
if (Q < MIN_int32 || Q > MAX_int32 || R < MIN_int32 || R > MAX_int32)
{
return false;
}
Coordinate = FIntPoint(static_cast<int32>(Q), static_cast<int32>(R));
return true;
}
bool TryGetHexCoordinate(const FHexLayout& Layout, const FVector& Location, FIntPoint& Coordinate)
{
if (Location.ContainsNaN())
{
return false;
}
const FVector Offset = Location - Layout.Origin;
const double X = FVector::DotProduct(Offset, Layout.AxisX) / Layout.LayoutRadius;
const double Y = FVector::DotProduct(Offset, Layout.AxisY) / Layout.LayoutRadius;
double Q;
double R;
switch (Layout.Orientation)
{
case EDirectiveUtilHexOrientation::PointyTop:
Q = UE_DOUBLE_SQRT_3 / 3.0 * X - Y / 3.0;
R = 2.0 / 3.0 * Y;
break;
case EDirectiveUtilHexOrientation::FlatTop:
Q = 2.0 / 3.0 * X;
R = -X / 3.0 + UE_DOUBLE_SQRT_3 / 3.0 * Y;
break;
default:
return false;
}
return TryRoundHexCoordinate(Q, R, Coordinate);
}
bool TryGetHexagonalPointCount(const int32 GridRadius, int32& PointCount)
{
PointCount = 0;
if (GridRadius < 0)
{
return false;
}
const int64 Radius = GridRadius;
const int64 Multiplier = 3 * (Radius + 1);
if (Radius > (MAX_int32 - 1) / Multiplier)
{
return false;
}
const int64 Count = 1 + Radius * Multiplier;
if (!IsSupportedGeneratedElementCount(Count))
{
return false;
}
PointCount = static_cast<int32>(Count);
return true;
}
bool TryGetGridPointCount(const FIntVector& Dimensions, int32& PointCount)
{
PointCount = 0;
if (Dimensions.X <= 0 || Dimensions.Y <= 0 || Dimensions.Z <= 0)
{
return false;
}
constexpr int64 MaximumPointCount = UDirectiveUtilMathFunctionLibrary::MaximumGeneratedElementCount;
int64 Count = Dimensions.X;
if (Count > MaximumPointCount / Dimensions.Y)
{
return false;
}
Count *= Dimensions.Y;
if (Count > MaximumPointCount / Dimensions.Z)
{
return false;
}
PointCount = static_cast<int32>(Count * Dimensions.Z);
return true;
}
struct FGridLattice
{
int32 DimX = 0;
int32 DimY = 0;
int32 DimZ = 0;
int32 PointCount = 0;
FVector FirstPoint = FVector::ZeroVector;
FVector StepX = FVector::ZeroVector;
FVector StepY = FVector::ZeroVector;
FVector StepZ = FVector::ZeroVector;
};
bool TryMakeGridLattice(const FVector& Origin, const FRotator& Rotation,
const FIntVector& Dimensions, const FVector& Spacing, const bool bCentered, FGridLattice& Lattice)
{
Lattice = {};
FVector AxisX;
FVector AxisY;
FVector AxisZ;
if (!IsFiniteVector(Origin) || !IsFiniteVector(Spacing)
|| !TryGetGridPointCount(Dimensions, Lattice.PointCount)
|| !TryGetRotatedAxes(Rotation, AxisX, AxisY, AxisZ))
{
return false;
}
Lattice.DimX = Dimensions.X;
Lattice.DimY = Dimensions.Y;
Lattice.DimZ = Dimensions.Z;
Lattice.StepX = AxisX * Spacing.X;
Lattice.StepY = AxisY * Spacing.Y;
Lattice.StepZ = AxisZ * Spacing.Z;
Lattice.FirstPoint = Origin;
if (bCentered)
{
Lattice.FirstPoint -= (Lattice.StepX * (Lattice.DimX - 1)
+ Lattice.StepY * (Lattice.DimY - 1)
+ Lattice.StepZ * (Lattice.DimZ - 1)) * 0.5;
}
// Reject if any lattice point can overflow: |p| <= |First| + Σ |Step|*(Dim-1).
const FVector CoordinateBound = Lattice.FirstPoint.GetAbs()
+ Lattice.StepX.GetAbs() * (Lattice.DimX - 1)
+ Lattice.StepY.GetAbs() * (Lattice.DimY - 1)
+ Lattice.StepZ.GetAbs() * (Lattice.DimZ - 1);
return IsFiniteVector(Lattice.FirstPoint)
&& IsFiniteVector(Lattice.StepX)
&& IsFiniteVector(Lattice.StepY)
&& IsFiniteVector(Lattice.StepZ)
&& IsFiniteVector(CoordinateBound);
}
TArray<FVector> GenerateGridPoints(const FVector& Origin, const FRotator& Rotation,
const FIntVector& Dimensions, const FVector& Spacing, const bool bCentered)
{
FGridLattice Lattice;
if (!TryMakeGridLattice(Origin, Rotation, Dimensions, Spacing, bCentered, Lattice))
{
return {};
}
TArray<FVector> Points;
Points.SetNumUninitialized(Lattice.PointCount);
FVector* RESTRICT Dest = Points.GetData();
for (int32 Z = 0; Z < Lattice.DimZ; ++Z)
{
const FVector LayerStart = Lattice.FirstPoint + Lattice.StepZ * Z;
for (int32 Y = 0; Y < Lattice.DimY; ++Y)
{
const FVector RowStart = LayerStart + Lattice.StepY * Y;
for (int32 X = 0; X < Lattice.DimX; ++X)
{
*Dest++ = RowStart + Lattice.StepX * X;
}
}
}
return Points;
}
TArray<FTransform> GenerateGridTransforms(const FVector& Origin, const FRotator& Rotation,
const FIntVector& Dimensions, const FVector& Spacing, const bool bCentered,
const FRotator& InstanceRotation, const FVector& Scale)
{
FGridLattice Lattice;
FQuat InstanceQuaternion;
if (!IsFiniteVector(Scale)
|| !TryGetRotationQuaternion(InstanceRotation, InstanceQuaternion)
|| !TryMakeGridLattice(Origin, Rotation, Dimensions, Spacing, bCentered, Lattice))
{
return {};
}
TArray<FTransform> Transforms;
Transforms.SetNumUninitialized(Lattice.PointCount);
FTransform* RESTRICT Dest = Transforms.GetData();
for (int32 Z = 0; Z < Lattice.DimZ; ++Z)
{
const FVector LayerStart = Lattice.FirstPoint + Lattice.StepZ * Z;
for (int32 Y = 0; Y < Lattice.DimY; ++Y)
{
const FVector RowStart = LayerStart + Lattice.StepY * Y;
for (int32 X = 0; X < Lattice.DimX; ++X)
{
*Dest++ = FTransform(InstanceQuaternion, RowStart + Lattice.StepX * X, Scale);
}
}
}
return Transforms;
}
TArray<FVector> GenerateLinearPoints(const FVector& Origin, const FVector& Step,
const int32 Count, const double FirstStep)
{
if (!IsSupportedGeneratedElementCount(Count)
|| !FMath::IsFinite(FirstStep) || Origin.ContainsNaN() || Step.ContainsNaN())
{
return {};
}
TArray<FVector> Points;
Points.SetNumUninitialized(Count);
for (int32 Index = 0; Index < Count; ++Index)
{
const FVector Point = Origin + Step * (FirstStep + Index);
if (Point.ContainsNaN())
{
return {};
}
Points[Index] = Point;
}
return Points;
}
TArray<FVector> MakeSinglePoint(const FVector& Point)
{
return Point.ContainsNaN() ? TArray<FVector>() : TArray<FVector>({ Point });
}
struct FAngleStepper
{
explicit FAngleStepper(const double InStartAngle, const double InStepAngle)
: StartAngle(InStartAngle), StepAngle(InStepAngle)
{
FMath::SinCos(&Sine, &Cosine, StartAngle);
FMath::SinCos(&StepSine, &StepCosine, StepAngle);
}
void Advance()
{
++Index;
if ((Index & 255) == 0)
{
FMath::SinCos(&Sine, &Cosine, StartAngle + StepAngle * Index);
return;
}
const double NextSine = Sine * StepCosine + Cosine * StepSine;
Cosine = Cosine * StepCosine - Sine * StepSine;
Sine = NextSine;
}
double Sine = 0.0;
double Cosine = 1.0;
private:
double StartAngle;
double StepAngle;
double StepSine = 0.0;
double StepCosine = 1.0;
int32 Index = 0;
};
template <typename RadiusFunction>
TArray<FVector> GeneratePlanarRadialPoints(const FVector& Center, const FRotator& Rotation,
const int32 Count, const double StartAngle, const double StepAngle, RadiusFunction&& GetRadius)
{
FVector AxisX;
FVector AxisY;
FVector AxisZ;
if (!IsSupportedGeneratedElementCount(Count)
|| Center.ContainsNaN() || !FMath::IsFinite(StartAngle) || !FMath::IsFinite(StepAngle)
|| !TryGetRotatedAxes(Rotation, AxisX, AxisY, AxisZ))
{
return {};
}
TArray<FVector> Points;
Points.SetNumUninitialized(Count);
FAngleStepper Angle(StartAngle, StepAngle);
for (int32 Index = 0; Index < Count; ++Index)
{
const double Radius = GetRadius(Index);
const FVector Point = Center + AxisX * (Angle.Cosine * Radius) + AxisY * (Angle.Sine * Radius);
if (!FMath::IsFinite(Radius) || Point.ContainsNaN())
{
return {};
}
Points[Index] = Point;
Angle.Advance();
}
return Points;
}
bool TryMakeFacingRotationFromNormalized(const FVector& Forward, const FVector& UpDirection,
const FQuat& RotationOffset, FQuat& Rotation)
{
// Forward and RotationOffset are already unit/validated; MakeFromXZ yields a unit quat.
Rotation = FRotationMatrix::MakeFromXZ(Forward, UpDirection).ToQuat() * RotationOffset;
return !Rotation.ContainsNaN();
}
bool IsValidRadialOrientation(const EDirectiveUtilRadialOrientation Orientation)
{
switch (Orientation)
{
case EDirectiveUtilRadialOrientation::Fixed:
case EDirectiveUtilRadialOrientation::FaceCenter:
case EDirectiveUtilRadialOrientation::FaceAwayFromCenter:
case EDirectiveUtilRadialOrientation::FollowPath:
case EDirectiveUtilRadialOrientation::FaceAgainstPath:
return true;
default:
return false;
}
}
bool TryGetRadialRotation(const EDirectiveUtilRadialOrientation Orientation,
const FVector& AxisX, const FVector& AxisY, const FVector& AxisZ, const FQuat& FixedRotation,
const FQuat& RotationOffset, const double Sine, const double Cosine, const double PathDirection,
FQuat& Rotation)
{
switch (Orientation)
{
case EDirectiveUtilRadialOrientation::Fixed:
Rotation = FixedRotation;
return true;
case EDirectiveUtilRadialOrientation::FaceCenter:
return TryMakeFacingRotationFromNormalized(
-AxisX * Cosine - AxisY * Sine, AxisZ, RotationOffset, Rotation);
case EDirectiveUtilRadialOrientation::FaceAwayFromCenter:
return TryMakeFacingRotationFromNormalized(
AxisX * Cosine + AxisY * Sine, AxisZ, RotationOffset, Rotation);
case EDirectiveUtilRadialOrientation::FollowPath:
return TryMakeFacingRotationFromNormalized(
(AxisX * -Sine + AxisY * Cosine) * PathDirection, AxisZ, RotationOffset, Rotation);
case EDirectiveUtilRadialOrientation::FaceAgainstPath:
return TryMakeFacingRotationFromNormalized(
(AxisX * Sine - AxisY * Cosine) * PathDirection, AxisZ, RotationOffset, Rotation);
default:
return false;
}
}
TArray<FTransform> GeneratePlanarRadialTransforms(const FVector& Center, const FRotator& PlaneRotator,
const double Radius, const int32 Count, const double StartAngle, const double StepAngle,
const double PathDirection, const EDirectiveUtilRadialOrientation Orientation,
const FRotator& RotationOffset, const FVector& Scale)
{
FQuat PlaneRotation;
FQuat RotationOffsetQuaternion;
if (!IsSupportedGeneratedElementCount(Count) || Center.ContainsNaN() || Scale.ContainsNaN()
|| !FMath::IsFinite(Radius) || !FMath::IsFinite(StartAngle) || !FMath::IsFinite(StepAngle)
|| !FMath::IsFinite(PathDirection) || !IsValidRadialOrientation(Orientation)
|| !TryGetRotationQuaternion(PlaneRotator, PlaneRotation)
|| !TryGetRotationQuaternion(RotationOffset, RotationOffsetQuaternion))
{
return {};
}
const FVector AxisX = PlaneRotation.GetAxisX();
const FVector AxisY = PlaneRotation.GetAxisY();
const FVector AxisZ = PlaneRotation.GetAxisZ();
const double DirectionSign = PathDirection < 0.0 ? -1.0 : 1.0;
const double AbsoluteRadius = FMath::Abs(Radius);
FQuat FixedRotation = PlaneRotation * RotationOffsetQuaternion;
FixedRotation.Normalize();
TArray<FTransform> Transforms;
Transforms.SetNumUninitialized(Count);
FAngleStepper Angle(StartAngle, StepAngle);
for (int32 Index = 0; Index < Count; ++Index)
{
const FVector Location = Center
+ AxisX * (Angle.Cosine * AbsoluteRadius) + AxisY * (Angle.Sine * AbsoluteRadius);
FQuat TransformRotation;
if (!TryGetRadialRotation(Orientation, AxisX, AxisY, AxisZ, FixedRotation,
RotationOffsetQuaternion, Angle.Sine, Angle.Cosine, DirectionSign, TransformRotation))
{
return {};
}
Transforms[Index] = FTransform(TransformRotation, Location, Scale);
Angle.Advance();
}
return Transforms;
}
struct FSplineSamplePlan
{
double StartDistance = 0.0;
double EndDistance = 0.0;
double Spacing = 0.0;
float SampleStart = 0.0f;
float SampleSpacing = 0.0f;
float SampleEnd = 0.0f;
int32 RegularSampleCount = 0;
bool bAppendEndpoint = false;
int32 Num() const
{
return RegularSampleCount + (bAppendEndpoint ? 1 : 0);
}
void FinalizeSampleDistances()
{
SampleStart = static_cast<float>(StartDistance);
SampleSpacing = static_cast<float>(Spacing);
SampleEnd = static_cast<float>(EndDistance);
}
float GetDistance(const int32 Index) const
{
return bAppendEndpoint && Index == RegularSampleCount
? SampleEnd
: SampleStart + static_cast<float>(Index) * SampleSpacing;
}
};
bool IsValidSplineCoordinateSpace(const ESplineCoordinateSpace::Type CoordinateSpace)
{
return CoordinateSpace == ESplineCoordinateSpace::Local
|| CoordinateSpace == ESplineCoordinateSpace::World;
}
bool TryResolveSplineSampleRange(const USplineComponent* Spline, const double StartDistance,
const double EndDistance, FSplineSamplePlan& Plan, bool& bOutFullClosedLoop)
{
Plan = {};
bOutFullClosedLoop = false;
if (!IsValid(Spline) || Spline->GetNumberOfSplinePoints() <= 0
|| !FMath::IsFinite(StartDistance) || !FMath::IsFinite(EndDistance))
{
return false;
}
const double SplineLength = Spline->GetSplineLength();
if (!FMath::IsFinite(SplineLength) || SplineLength < 0.0)
{
return false;
}
Plan.StartDistance = FMath::Clamp(StartDistance, 0.0, SplineLength);
Plan.EndDistance = EndDistance < 0.0 ? SplineLength : FMath::Clamp(EndDistance, 0.0, SplineLength);
if (Plan.EndDistance < Plan.StartDistance)
{
return false;
}
bOutFullClosedLoop = Spline->IsClosedLoop()
&& Plan.StartDistance == 0.0 && Plan.EndDistance == SplineLength;
return true;
}
bool TryMakeSplineSpacingPlan(const USplineComponent* Spline, const double Spacing,
const EDirectiveUtilSplineSpacingMode SpacingMode, const bool bIncludeEndpoint,
const double StartDistance, const double EndDistance, FSplineSamplePlan& Plan)
{
bool bFullClosedLoop = false;
if (!FMath::IsFinite(Spacing) || Spacing <= 0.0
|| (SpacingMode != EDirectiveUtilSplineSpacingMode::Fixed
&& SpacingMode != EDirectiveUtilSplineSpacingMode::Even)
|| !TryResolveSplineSampleRange(Spline, StartDistance, EndDistance, Plan, bFullClosedLoop))
{
return false;
}
const double RangeLength = Plan.EndDistance - Plan.StartDistance;
if (RangeLength == 0.0)
{
Plan.RegularSampleCount = 1;
Plan.FinalizeSampleDistances();
return true;
}
const double SampleCountValue = FMath::CeilToDouble(RangeLength / Spacing);
if (!FMath::IsFinite(SampleCountValue) || SampleCountValue < 1.0
|| SampleCountValue > UDirectiveUtilMathFunctionLibrary::MaximumGeneratedElementCount)
{
return false;
}
Plan.RegularSampleCount = static_cast<int32>(SampleCountValue);
Plan.Spacing = SpacingMode == EDirectiveUtilSplineSpacingMode::Even
? RangeLength / SampleCountValue
: Spacing;
Plan.bAppendEndpoint = bIncludeEndpoint && !bFullClosedLoop;
if (Plan.bAppendEndpoint)
{
// Drop a final regular sample that float noise placed within a hair of the endpoint.
const double LastRegularOffset = static_cast<double>(Plan.RegularSampleCount - 1) * Plan.Spacing;
if (RangeLength - LastRegularOffset <= FMath::Max(UE_DOUBLE_KINDA_SMALL_NUMBER, RangeLength * 1.e-9))
{
--Plan.RegularSampleCount;
}
}
if (!IsSupportedGeneratedElementCount(Plan.Num()))
{
return false;
}
Plan.FinalizeSampleDistances();
return true;
}
bool TryMakeSplineCountPlan(const USplineComponent* Spline, const int32 Count,
const bool bIncludeEndpoints, const double StartDistance, const double EndDistance,
FSplineSamplePlan& Plan)
{
bool bFullClosedLoop = false;
if (!IsSupportedGeneratedElementCount(Count)
|| !TryResolveSplineSampleRange(Spline, StartDistance, EndDistance, Plan, bFullClosedLoop))
{
return false;
}
Plan.RegularSampleCount = Count;
const double RangeLength = Plan.EndDistance - Plan.StartDistance;
if (RangeLength == 0.0 || (bFullClosedLoop && Count == 1))
{
Plan.FinalizeSampleDistances();
return true;
}
if (bFullClosedLoop)
{
Plan.Spacing = RangeLength / static_cast<double>(Count);
}
else if (!bIncludeEndpoints)
{
Plan.Spacing = RangeLength / (static_cast<double>(Count) + 1.0);
Plan.StartDistance += Plan.Spacing;
}
else if (Count == 1)
{
Plan.StartDistance += RangeLength * 0.5;
}
else
{
Plan.Spacing = RangeLength / static_cast<double>(Count - 1);
Plan.RegularSampleCount = Count - 1;
Plan.bAppendEndpoint = true;
}
Plan.FinalizeSampleDistances();
return true;
}
TArray<FVector> SampleSplineLocations(const USplineComponent* Spline, const FSplineSamplePlan& Plan,
const ESplineCoordinateSpace::Type CoordinateSpace)
{
TArray<FVector> Points;
Points.SetNumUninitialized(Plan.Num());
for (int32 Index = 0; Index < Points.Num(); ++Index)
{
const FVector Point = Spline->GetLocationAtDistanceAlongSpline(
Plan.GetDistance(Index), CoordinateSpace);
if (Point.ContainsNaN())
{
return {};
}
Points[Index] = Point;
}
return Points;
}
TArray<FTransform> SampleSplineTransforms(const USplineComponent* Spline, const FSplineSamplePlan& Plan,
const ESplineCoordinateSpace::Type CoordinateSpace, const bool bUseSplineScale,
const FQuat& RotationOffsetQuaternion, const FVector& ScaleMultiplier)
{
TArray<FTransform> Transforms;
Transforms.SetNumUninitialized(Plan.Num());
for (int32 Index = 0; Index < Transforms.Num(); ++Index)
{
FTransform Transform = Spline->GetTransformAtDistanceAlongSpline(
Plan.GetDistance(Index), CoordinateSpace, bUseSplineScale);
FQuat Rotation = Transform.GetRotation() * RotationOffsetQuaternion;
Rotation.Normalize();
Transform.SetRotation(Rotation);
Transform.SetScale3D(Transform.GetScale3D() * ScaleMultiplier);
if (Transform.ContainsNaN())
{
return {};
}
Transforms[Index] = Transform;
}
return Transforms;
}
FTransform BlendTransforms(const FTransform& A, const FTransform& B, const double Alpha)
{
FQuat Rotation = FQuat::Slerp(A.GetRotation(), B.GetRotation(), Alpha);
Rotation.Normalize();
return FTransform(Rotation,
FMath::Lerp(A.GetLocation(), B.GetLocation(), Alpha),
FMath::Lerp(A.GetScale3D(), B.GetScale3D(), Alpha));
}
template <typename PositionType>
bool TryFindArraySample(const int32 Count, const bool bClosedLoop, const float Alpha,
PositionType Position, int32& IndexA, int32& IndexB, double& SegmentAlpha)
{
IndexA = 0;
IndexB = 0;
SegmentAlpha = 0.0;
if (Count <= 0 || !FMath::IsFinite(Alpha))
{
return false;
}
if (Count == 1)
{
return true;
}
const int32 SegmentCount = bClosedLoop ? Count : Count - 1;
// Reuse capacity across samples on this thread; inline storage covers typical Blueprint paths.
static thread_local TArray<double, TInlineAllocator<128>> SegmentLengths;
SegmentLengths.SetNumUninitialized(SegmentCount, EAllowShrinking::No);
double TotalLength = 0.0;
for (int32 Index = 0; Index < SegmentCount; ++Index)
{
const int32 NextIndex = (!bClosedLoop || Index + 1 < Count) ? Index + 1 : 0;
const double Length = FVector::Distance(Position(Index), Position(NextIndex));
if (!FMath::IsFinite(Length))
{
return false;
}
SegmentLengths[Index] = Length;
TotalLength += Length;
}
if (TotalLength <= 0.0)
{
return true;
}
const double TargetAlpha = bClosedLoop
? static_cast<double>(Alpha) - FMath::FloorToDouble(Alpha)
: FMath::Clamp(static_cast<double>(Alpha), 0.0, 1.0);
double TargetDistance = TargetAlpha * TotalLength;
for (int32 Index = 0; Index < SegmentCount; ++Index)
{
const double Length = SegmentLengths[Index];
if (TargetDistance <= Length || Index == SegmentCount - 1)
{
IndexA = Index;
IndexB = (!bClosedLoop || Index + 1 < Count) ? Index + 1 : 0;
SegmentAlpha = Length > 0.0
? FMath::Clamp(TargetDistance / Length, 0.0, 1.0)
: 0.0;
return true;
}
TargetDistance -= Length;
}
return true;
}
// Perlin noise is exactly zero at every integer lattice point, so bias samples off the lattice.
constexpr double NoiseSampleBias = 0.6180339887498949;
bool TryOffsetLocationByNoise(const FVector& Location, const FVector& NormalizedDirection,
const double NoiseScale, const double Amplitude, const FVector& NoiseOffset, FVector& OffsetLocation)
{
if (Location.ContainsNaN())
{
return false;
}
const FVector SamplePosition = (Location + NoiseOffset) / NoiseScale + FVector(NoiseSampleBias);
const double Noise = FMath::PerlinNoise3D(SamplePosition);
OffsetLocation = Location + NormalizedDirection * (Noise * Amplitude);
return !OffsetLocation.ContainsNaN();
}
bool TryGetNoiseOffsetInputs(const double NoiseScale, const double Amplitude, const FVector& Direction,
const FVector& NoiseOffset, FVector& NormalizedDirection)
{
return FMath::IsFinite(NoiseScale) && NoiseScale > 0.0 && FMath::IsFinite(Amplitude)
&& !NoiseOffset.ContainsNaN() && TryGetNormalizedVector(Direction, NormalizedDirection);
}
double WrapDegreesAsRadians(const double AngleDegrees)
{
return FMath::DegreesToRadians(FMath::Fmod(AngleDegrees, 360.0));
}
double FindWrappedDeltaDegrees(const double From, const double To)
{
double Delta = FMath::Fmod(To - From, 360.0);
if (Delta < 0.0)
{
Delta += 360.0;
}
if (Delta > 180.0)
{
Delta -= 360.0;
}
return Delta;
}
}
float UDirectiveUtilMathFunctionLibrary::PerlinNoise2D(const FVector2D Position)
{
return FMath::PerlinNoise2D(Position);
}
float UDirectiveUtilMathFunctionLibrary::PerlinNoise3D(const FVector& Position)
{
return FMath::PerlinNoise3D(Position);
}
float UDirectiveUtilMathFunctionLibrary::AngleBetweenVectors(const FVector& A, const FVector& B)
{
FVector NormalizedA;
FVector NormalizedB;
if (!TryGetNormalizedVector(A, NormalizedA) || !TryGetNormalizedVector(B, NormalizedB))
{
return 0.0f;
}
return FMath::RadiansToDegrees(FMath::Acos(FMath::Clamp(FVector::DotProduct(NormalizedA, NormalizedB), -1.0, 1.0)));
}
float UDirectiveUtilMathFunctionLibrary::SignedAngleBetweenVectors(const FVector& From, const FVector& To, const FVector& Axis)
{
FVector NormalizedAxis;
if (!TryGetNormalizedVector(Axis, NormalizedAxis))
{
return 0.0f;
}
FVector ProjectedFrom;
FVector ProjectedTo;
if (!TryGetProjectedDirection(From, NormalizedAxis, ProjectedFrom)
|| !TryGetProjectedDirection(To, NormalizedAxis, ProjectedTo))
{
return 0.0f;
}
const double Sine = FVector::DotProduct(NormalizedAxis, FVector::CrossProduct(ProjectedFrom, ProjectedTo));
const double Cosine = FMath::Clamp(FVector::DotProduct(ProjectedFrom, ProjectedTo), -1.0, 1.0);
return static_cast<float>(FMath::RadiansToDegrees(FMath::Atan2(Sine, Cosine)));
}
float UDirectiveUtilMathFunctionLibrary::DeltaAngle(const float From, const float To)
{
if (!FMath::IsFinite(From) || !FMath::IsFinite(To))
{
return 0.0f;
}
return static_cast<float>(FindWrappedDeltaDegrees(From, To));
}
float UDirectiveUtilMathFunctionLibrary::LerpAngle(const float A, const float B, const float Alpha)
{
if (!FMath::IsFinite(A) || !FMath::IsFinite(B) || !FMath::IsFinite(Alpha))
{
return 0.0f;
}
const double Result = static_cast<double>(A)
+ FindWrappedDeltaDegrees(A, B) * static_cast<double>(Alpha);
return static_cast<float>(Result);
}
float UDirectiveUtilMathFunctionLibrary::PingPong(const float Value, const float Minimum, const float Maximum)
{
if (!FMath::IsFinite(Value) || !FMath::IsFinite(Minimum) || !FMath::IsFinite(Maximum))
{
return 0.0f;
}
const double LowerBound = FMath::Min(static_cast<double>(Minimum), static_cast<double>(Maximum));
const double UpperBound = FMath::Max(static_cast<double>(Minimum), static_cast<double>(Maximum));
const double Range = UpperBound - LowerBound;
if (Range == 0.0)
{
return static_cast<float>(LowerBound);
}
const double Period = Range * 2.0;
double Offset = FMath::Fmod(static_cast<double>(Value) - LowerBound, Period);
if (Offset < 0.0)
{
Offset += Period;
}
const double DistanceFromLowerBound = Offset <= Range ? Offset : Period - Offset;
return static_cast<float>(LowerBound + DistanceFromLowerBound);
}
float UDirectiveUtilMathFunctionLibrary::SmoothStep(const float Value, const float Minimum, const float Maximum)
{
if (!FMath::IsFinite(Value) || !FMath::IsFinite(Minimum) || !FMath::IsFinite(Maximum))
{
return 0.0f;
}
const double LowerBound = FMath::Min(static_cast<double>(Minimum), static_cast<double>(Maximum));
const double UpperBound = FMath::Max(static_cast<double>(Minimum), static_cast<double>(Maximum));
if (LowerBound == UpperBound)
{
return Value < LowerBound ? 0.0f : 1.0f;
}
const double Alpha = FMath::Clamp((static_cast<double>(Value) - LowerBound) / (UpperBound - LowerBound), 0.0, 1.0);
return static_cast<float>(Alpha * Alpha * (3.0 - 2.0 * Alpha));
}
float UDirectiveUtilMathFunctionLibrary::SmootherStep(const float Value, const float Minimum, const float Maximum)
{
if (!FMath::IsFinite(Value) || !FMath::IsFinite(Minimum) || !FMath::IsFinite(Maximum))
{
return 0.0f;
}
const double LowerBound = FMath::Min(static_cast<double>(Minimum), static_cast<double>(Maximum));
const double UpperBound = FMath::Max(static_cast<double>(Minimum), static_cast<double>(Maximum));
if (LowerBound == UpperBound)
{
return Value < LowerBound ? 0.0f : 1.0f;
}
const double Alpha = FMath::Clamp((static_cast<double>(Value) - LowerBound) / (UpperBound - LowerBound), 0.0, 1.0);
return static_cast<float>(Alpha * Alpha * Alpha * (Alpha * (Alpha * 6.0 - 15.0) + 10.0));
}
float UDirectiveUtilMathFunctionLibrary::RangeFalloff(const float Distance, const float InnerRadius,
const float OuterRadius, const float FalloffExponent)
{
if (!FMath::IsFinite(Distance) || !FMath::IsFinite(InnerRadius)
|| !FMath::IsFinite(OuterRadius) || !FMath::IsFinite(FalloffExponent))
{
return 0.0f;
}
const double FirstRadius = FMath::Max(0.0, static_cast<double>(InnerRadius));
const double SecondRadius = FMath::Max(0.0, static_cast<double>(OuterRadius));
const double Inner = FMath::Min(FirstRadius, SecondRadius);
const double Outer = FMath::Max(FirstRadius, SecondRadius);
const double ClampedDistance = FMath::Max(0.0, static_cast<double>(Distance));
if (Inner == Outer)
{
return ClampedDistance <= Inner ? 1.0f : 0.0f;
}
if (ClampedDistance >= Outer)
{
return 0.0f;
}
if (ClampedDistance <= Inner || FalloffExponent <= 0.0f)
{
return 1.0f;
}
const double Alpha = 1.0 - (ClampedDistance - Inner) / (Outer - Inner);
if (FalloffExponent == 1.0f)
{
return static_cast<float>(Alpha);
}
if (FalloffExponent == 2.0f)
{
return static_cast<float>(Alpha * Alpha);
}
return static_cast<float>(FMath::Pow(Alpha, static_cast<double>(FalloffExponent)));
}
bool UDirectiveUtilMathFunctionLibrary::IsDirectionWithinCone(const FVector& Direction, const FVector& ConeDirection, const float ConeHalfAngleDegrees)
{
FVector NormalizedDirection;
if (!TryGetNormalizedVector(Direction, NormalizedDirection))
{
return false;
}
return IsNormalizedDirectionWithinCone(NormalizedDirection, ConeDirection, ConeHalfAngleDegrees);
}
bool UDirectiveUtilMathFunctionLibrary::GetDirectionAndDistance(const FVector& From, const FVector& To,
FVector& Direction, double& Distance)
{
Direction = FVector::ZeroVector;
Distance = 0.0;
if (From.ContainsNaN() || To.ContainsNaN())
{
return false;
}
const FVector Delta = To - From;
if (Delta.ContainsNaN())
{
return false;
}
if (!TryGetNormalizedVector(Delta, Direction, &Distance))
{
Distance = 0.0;
return false;
}
return true;
}
FVector2D UDirectiveUtilMathFunctionLibrary::RotatePointAroundPivot2D(const FVector2D& Point,
const FVector2D& Pivot, const float AngleDegrees)
{
if (!IsFiniteVector2D(Point) || !IsFiniteVector2D(Pivot) || !FMath::IsFinite(AngleDegrees))
{
return FVector2D::ZeroVector;
}
const FVector2D Offset = Point - Pivot;
const double AngleRadians = FMath::DegreesToRadians(static_cast<double>(AngleDegrees));
const double Sine = FMath::Sin(AngleRadians);
const double Cosine = FMath::Cos(AngleRadians);
const FVector2D RotatedPoint = Pivot + FVector2D(
Offset.X * Cosine - Offset.Y * Sine,
Offset.X * Sine + Offset.Y * Cosine);
return IsFiniteVector2D(RotatedPoint) ? RotatedPoint : FVector2D::ZeroVector;
}
double UDirectiveUtilMathFunctionLibrary::SignedDistanceToPlane(const FVector& Point,
const FVector& PlanePoint, const FVector& PlaneNormal)
{
FVector NormalizedPlaneNormal;
if (Point.ContainsNaN() || PlanePoint.ContainsNaN()
|| !TryGetNormalizedVector(PlaneNormal, NormalizedPlaneNormal))
{
return 0.0;
}
const FVector Offset = Point - PlanePoint;
if (Offset.ContainsNaN())
{
return 0.0;
}
const double MaximumComponent = Offset.GetAbsMax();
if (MaximumComponent == 0.0)
{
return 0.0;
}
const double ScaledDistance = FVector::DotProduct(Offset / MaximumComponent, NormalizedPlaneNormal);
if (!FMath::IsFinite(ScaledDistance)
|| FMath::Abs(ScaledDistance) > TNumericLimits<double>::Max() / MaximumComponent)
{
return 0.0;
}
return ScaledDistance * MaximumComponent;
}
bool UDirectiveUtilMathFunctionLibrary::IsPointWithinCone(const FVector& Point, const FVector& ConeOrigin,
const FVector& ConeDirection, const float ConeHalfAngleDegrees, const double MaximumDistance)
{
if (Point.ContainsNaN() || ConeOrigin.ContainsNaN() || ConeDirection.ContainsNaN()
|| !FMath::IsFinite(ConeHalfAngleDegrees) || !FMath::IsFinite(MaximumDistance))
{
return false;
}
const FVector PointDirection = Point - ConeOrigin;
FVector NormalizedPointDirection;
double Distance = 0.0;
if (PointDirection.ContainsNaN()
|| !TryGetNormalizedVector(PointDirection, NormalizedPointDirection,
MaximumDistance > 0.0 ? &Distance : nullptr))
{
return false;
}
if (MaximumDistance > 0.0 && Distance > MaximumDistance)
{
return false;
}
return IsNormalizedDirectionWithinCone(NormalizedPointDirection, ConeDirection, ConeHalfAngleDegrees);
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::LocationsToTransforms(const TArray<FVector>& Locations,
const FRotator Rotation, const FVector Scale)
{
if (Locations.IsEmpty())
{
return {};
}
FQuat RotationQuaternion;
if (Scale.ContainsNaN() || !TryGetRotationQuaternion(Rotation, RotationQuaternion))
{
return {};
}
TArray<FTransform> Transforms;
Transforms.Reserve(Locations.Num());
for (const FVector& Location : Locations)
{
if (Location.ContainsNaN())
{
return {};
}
Transforms.Emplace(RotationQuaternion, Location, Scale);
}
return Transforms;
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::LocationsToFacingTransforms(
const TArray<FVector>& Locations, const FVector Target, const FVector UpDirection,
const FRotator RotationOffset, const FVector Scale, const bool bFaceAway)
{
if (Locations.IsEmpty())
{
return {};
}
FVector NormalizedUpDirection;
FQuat RotationOffsetQuaternion;
if (Target.ContainsNaN() || Scale.ContainsNaN()
|| !TryGetNormalizedVector(UpDirection, NormalizedUpDirection)
|| !TryGetRotationQuaternion(RotationOffset, RotationOffsetQuaternion))
{
return {};
}
TArray<FTransform> Transforms;
Transforms.SetNumUninitialized(Locations.Num());
for (int32 Index = 0; Index < Locations.Num(); ++Index)
{
const FVector& Location = Locations[Index];
if (Location.ContainsNaN())
{
return {};
}
const FVector Direction = bFaceAway ? Location - Target : Target - Location;
FQuat Rotation = RotationOffsetQuaternion;
if (Direction.SizeSquared() > UE_DOUBLE_SMALL_NUMBER)
{
FVector Forward;
if (!TryGetNormalizedVector(Direction, Forward)
|| !TryMakeFacingRotationFromNormalized(
Forward, NormalizedUpDirection, RotationOffsetQuaternion, Rotation))
{
return {};
}
}
Transforms[Index] = FTransform(Rotation, Location, Scale);
}
return Transforms;
}
bool UDirectiveUtilMathFunctionLibrary::MakeTransformsFromArrays(const TArray<FVector>& Locations,
const TArray<FRotator>& Rotations, const TArray<FVector>& Scales, TArray<FTransform>& Transforms)
{
Transforms.Reset();
const int32 TransformCount = Locations.Num();
if ((Rotations.Num() != 0 && Rotations.Num() != 1 && Rotations.Num() != TransformCount)
|| (Scales.Num() != 0 && Scales.Num() != 1 && Scales.Num() != TransformCount))
{
return false;
}
if (TransformCount == 0)
{
return true;
}
const bool bUsePerTransformRotations = Rotations.Num() == TransformCount && TransformCount > 1;
const bool bUsePerTransformScales = Scales.Num() == TransformCount && TransformCount > 1;
FQuat SharedRotation = FQuat::Identity;
if (!bUsePerTransformRotations && !Rotations.IsEmpty()
&& !TryGetRotationQuaternion(Rotations[0], SharedRotation))
{
return false;
}
const FVector SharedScale = Scales.IsEmpty() ? FVector::OneVector : Scales[0];
if (!bUsePerTransformScales && SharedScale.ContainsNaN())
{
return false;
}
Transforms.Reserve(TransformCount);
for (int32 Index = 0; Index < TransformCount; ++Index)
{
const FVector& Location = Locations[Index];
if (Location.ContainsNaN())
{
Transforms.Reset();
return false;
}
FQuat ItemRotation;
const FQuat* RotationQuaternion = &SharedRotation;
if (bUsePerTransformRotations)
{
if (!TryGetRotationQuaternion(Rotations[Index], ItemRotation))
{
Transforms.Reset();
return false;
}
RotationQuaternion = &ItemRotation;
}
const FVector& Scale = bUsePerTransformScales ? Scales[Index] : SharedScale;
if (bUsePerTransformScales && Scale.ContainsNaN())
{
Transforms.Reset();
return false;
}
Transforms.Emplace(*RotationQuaternion, Location, Scale);
}
return true;
}
FVector UDirectiveUtilMathFunctionLibrary::SampleLocationArray(const TArray<FVector>& Locations,
const float Alpha, const bool bClosedLoop)
{
int32 IndexA;
int32 IndexB;
double SegmentAlpha;
if (!TryFindArraySample(Locations.Num(), bClosedLoop, Alpha,
[&Locations](const int32 Index) { return Locations[Index]; }, IndexA, IndexB, SegmentAlpha))
{
return FVector::ZeroVector;
}
const FVector Result = FMath::Lerp(Locations[IndexA], Locations[IndexB], SegmentAlpha);
return Result.ContainsNaN() ? FVector::ZeroVector : Result;
}
FTransform UDirectiveUtilMathFunctionLibrary::SampleTransformArray(const TArray<FTransform>& Transforms,
const float Alpha, const bool bClosedLoop)
{
int32 IndexA;
int32 IndexB;
double SegmentAlpha;
if (!TryFindArraySample(Transforms.Num(), bClosedLoop, Alpha,
[&Transforms](const int32 Index) { return Transforms[Index].GetLocation(); },
IndexA, IndexB, SegmentAlpha))
{
return FTransform::Identity;
}
const FTransform Result = BlendTransforms(Transforms[IndexA], Transforms[IndexB], SegmentAlpha);
return Result.ContainsNaN() ? FTransform::Identity : Result;
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GenerateGridPoints2D(const FVector& Origin,
const FRotator& Rotation, const FIntPoint Dimensions, const FVector2D& Spacing, const bool bCentered)
{
if (!IsFiniteVector2D(Spacing))
{
return {};
}
return GenerateGridPoints(Origin, Rotation, FIntVector(Dimensions.X, Dimensions.Y, 1),
FVector(Spacing.X, Spacing.Y, 0.0), bCentered);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GenerateGridPoints3D(const FVector& Origin,
const FRotator& Rotation, const FIntVector Dimensions, const FVector& Spacing, const bool bCentered)
{
return GenerateGridPoints(Origin, Rotation, Dimensions, Spacing, bCentered);
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateGridTransforms2D(const FVector& Origin,
const FRotator& Rotation, const FIntPoint Dimensions, const FVector2D& Spacing, const bool bCentered,
const FRotator InstanceRotation, const FVector Scale)
{
if (!IsFiniteVector2D(Spacing))
{
return {};
}
return GenerateGridTransforms(Origin, Rotation, FIntVector(Dimensions.X, Dimensions.Y, 1),
FVector(Spacing.X, Spacing.Y, 0.0), bCentered, InstanceRotation, Scale);
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateGridTransforms3D(const FVector& Origin,
const FRotator& Rotation, const FIntVector Dimensions, const FVector& Spacing, const bool bCentered,
const FRotator InstanceRotation, const FVector Scale)
{
return GenerateGridTransforms(Origin, Rotation, Dimensions, Spacing, bCentered, InstanceRotation, Scale);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GenerateRectangularHexGrid(const FVector& Origin,
const FRotator& Rotation, const FIntPoint Dimensions, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap, const bool bCentered)
{
int32 PointCount;
FHexLayout Layout;
if (!TryGetGridPointCount(FIntVector(Dimensions.X, Dimensions.Y, 1), PointCount)
|| !TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout)
|| !TryApplyRectangularHexCentering(Layout, Dimensions, bCentered)
|| !TryValidateHexLayoutSpan(Layout, GetRectangularHexSpan(Dimensions), GetRectangularHexSpan(Dimensions)))
{
return {};
}
TArray<FVector> Points;
Points.SetNumUninitialized(PointCount);
int32 PointIndex = 0;
if (!VisitRectangularHexCoordinates(Dimensions, Orientation,
[&Layout, &Points, &PointIndex](const int64 Q, const int64 R)
{
WriteHexLocationUnchecked(Layout, Q, R, Points[PointIndex++]);
return true;
}))
{
return {};
}
return Points;
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateRectangularHexGridTransforms(const FVector& Origin,
const FRotator& Rotation, const FIntPoint Dimensions, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap, const bool bCentered,
const FRotator InstanceRotation, const FVector Scale)
{
int32 PointCount;
FHexLayout Layout;
FQuat InstanceQuaternion;
if (!IsFiniteVector(Scale)
|| !TryGetRotationQuaternion(InstanceRotation, InstanceQuaternion)
|| !TryGetGridPointCount(FIntVector(Dimensions.X, Dimensions.Y, 1), PointCount)
|| !TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout)
|| !TryApplyRectangularHexCentering(Layout, Dimensions, bCentered)
|| !TryValidateHexLayoutSpan(Layout, GetRectangularHexSpan(Dimensions), GetRectangularHexSpan(Dimensions)))
{
return {};
}
TArray<FTransform> Transforms;
Transforms.SetNumUninitialized(PointCount);
int32 PointIndex = 0;
if (!VisitRectangularHexCoordinates(Dimensions, Orientation,
[&Layout, &Transforms, &PointIndex, &InstanceQuaternion, &Scale](const int64 Q, const int64 R)
{
FVector Location;
WriteHexLocationUnchecked(Layout, Q, R, Location);
Transforms[PointIndex++] = FTransform(InstanceQuaternion, Location, Scale);
return true;
}))
{
return {};
}
return Transforms;
}
TArray<FIntPoint> UDirectiveUtilMathFunctionLibrary::GetRectangularHexGridCoordinates(const FIntPoint Dimensions,
const EDirectiveUtilHexOrientation Orientation)
{
int32 PointCount;
if ((Orientation != EDirectiveUtilHexOrientation::PointyTop
&& Orientation != EDirectiveUtilHexOrientation::FlatTop)
|| !TryGetGridPointCount(FIntVector(Dimensions.X, Dimensions.Y, 1), PointCount))
{
return {};
}
TArray<FIntPoint> Coordinates;
Coordinates.SetNumUninitialized(PointCount);
int32 PointIndex = 0;
if (!VisitRectangularHexCoordinates(Dimensions, Orientation,
[&Coordinates, &PointIndex](const int64 Q, const int64 R)
{
return TryMakeHexCoordinate(Q, R, Coordinates[PointIndex++]);
}))
{
return {};
}
return Coordinates;
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GenerateHexagonalHexGrid(const FVector& Origin,
const FRotator& Rotation, const int32 GridRadius, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap)
{
int32 PointCount;
FHexLayout Layout;
if (!TryGetHexagonalPointCount(GridRadius, PointCount)
|| !TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout)
|| !TryValidateHexLayoutSpan(Layout, GridRadius, GridRadius))
{
return {};
}
TArray<FVector> Points;
Points.SetNumUninitialized(PointCount);
int32 PointIndex = 0;
if (!VisitHexagonalCoordinates(GridRadius,
[&Layout, &Points, &PointIndex](const int64 Q, const int64 R)
{
WriteHexLocationUnchecked(Layout, Q, R, Points[PointIndex++]);
return true;
}))
{
return {};
}
return Points;
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateHexagonalHexGridTransforms(const FVector& Origin,
const FRotator& Rotation, const int32 GridRadius, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap,
const FRotator InstanceRotation, const FVector Scale)
{
int32 PointCount;
FHexLayout Layout;
FQuat InstanceQuaternion;
if (!IsFiniteVector(Scale)
|| !TryGetRotationQuaternion(InstanceRotation, InstanceQuaternion)
|| !TryGetHexagonalPointCount(GridRadius, PointCount)
|| !TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout)
|| !TryValidateHexLayoutSpan(Layout, GridRadius, GridRadius))
{
return {};
}
TArray<FTransform> Transforms;
Transforms.SetNumUninitialized(PointCount);
int32 PointIndex = 0;
if (!VisitHexagonalCoordinates(GridRadius,
[&Layout, &Transforms, &PointIndex, &InstanceQuaternion, &Scale](const int64 Q, const int64 R)
{
FVector Location;
WriteHexLocationUnchecked(Layout, Q, R, Location);
Transforms[PointIndex++] = FTransform(InstanceQuaternion, Location, Scale);
return true;
}))
{
return {};
}
return Transforms;
}
FVector UDirectiveUtilMathFunctionLibrary::HexCoordinateToLocation(const FIntPoint Coordinate,
const FVector& Origin, const FRotator& Rotation, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap)
{
FHexLayout Layout;
if (!TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout))
{
return FVector::ZeroVector;
}
const FVector Location = Layout.GetLocation(Coordinate.X, Coordinate.Y);
return Location.ContainsNaN() ? FVector::ZeroVector : Location;
}
FIntPoint UDirectiveUtilMathFunctionLibrary::LocationToHexCoordinate(const FVector& Location,
const FVector& Origin, const FRotator& Rotation, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap)
{
FHexLayout Layout;
FIntPoint Coordinate = FIntPoint::ZeroValue;
if (!TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout)
|| !TryGetHexCoordinate(Layout, Location, Coordinate))
{
return FIntPoint::ZeroValue;
}
return Coordinate;
}
TArray<FIntPoint> UDirectiveUtilMathFunctionLibrary::GetHexNeighbors(const FIntPoint Coordinate)
{
TArray<FIntPoint> Neighbors;
Neighbors.SetNumUninitialized(6);
for (int32 Index = 0; Index < 6; ++Index)
{
const int64 Q = static_cast<int64>(Coordinate.X) + HexDirections[Index][0];
const int64 R = static_cast<int64>(Coordinate.Y) + HexDirections[Index][1];
if (!TryMakeHexCoordinate(Q, R, Neighbors[Index]))
{
return {};
}
}
return Neighbors;
}
int64 UDirectiveUtilMathFunctionLibrary::GetHexDistance(const FIntPoint A, const FIntPoint B)
{
const int64 DeltaQ = static_cast<int64>(A.X) - B.X;
const int64 DeltaR = static_cast<int64>(A.Y) - B.Y;
return FMath::Max3(FMath::Abs(DeltaQ), FMath::Abs(DeltaR), FMath::Abs(DeltaQ + DeltaR));
}
TArray<FIntPoint> UDirectiveUtilMathFunctionLibrary::GetHexesInRange(const FIntPoint Center, const int32 Range)
{
int32 PointCount;
if (!TryGetHexagonalPointCount(Range, PointCount))
{
return {};
}
TArray<FIntPoint> Hexes;
Hexes.SetNumUninitialized(PointCount);
int32 PointIndex = 0;
if (!VisitHexagonalCoordinates(Range,
[&Center, &Hexes, &PointIndex](const int64 DeltaQ, const int64 DeltaR)
{
return TryMakeHexCoordinate(Center.X + DeltaQ, Center.Y + DeltaR, Hexes[PointIndex++]);
}))
{
return {};
}
return Hexes;
}
TArray<FIntPoint> UDirectiveUtilMathFunctionLibrary::GetHexRing(const FIntPoint Center, const int32 Radius)
{
const int64 PointCount = static_cast<int64>(Radius) * 6;
if (Radius < 0 || (Radius > 0 && !IsSupportedGeneratedElementCount(PointCount)))
{
return {};
}
if (Radius == 0)
{
return { Center };
}
TArray<FIntPoint> Ring;
Ring.SetNumUninitialized(static_cast<int32>(PointCount));
int32 PointIndex = 0;
int64 Q = static_cast<int64>(Center.X) + static_cast<int64>(HexDirections[4][0]) * Radius;
int64 R = static_cast<int64>(Center.Y) + static_cast<int64>(HexDirections[4][1]) * Radius;
for (int32 Side = 0; Side < 6; ++Side)
{
for (int32 Step = 0; Step < Radius; ++Step)
{
if (!TryMakeHexCoordinate(Q, R, Ring[PointIndex++]))
{
return {};
}
Q += HexDirections[Side][0];
R += HexDirections[Side][1];
}
}
return Ring;
}
TArray<FIntPoint> UDirectiveUtilMathFunctionLibrary::GetHexLine(const FIntPoint Start, const FIntPoint End)
{
const int64 Distance = GetHexDistance(Start, End);
if (!IsSupportedGeneratedElementCount(Distance + 1))
{
return {};
}
TArray<FIntPoint> Line;
Line.SetNumUninitialized(static_cast<int32>(Distance) + 1);
Line[0] = Start;
if (Distance == 0)
{
return Line;
}
Line.Last() = End;
// Nudging both endpoints off the cell edges makes ties round to a consistent side.
constexpr double Nudge = 1.e-6;
const double StartQ = static_cast<double>(Start.X) + Nudge;
const double StartR = static_cast<double>(Start.Y) + Nudge;
const double EndQ = static_cast<double>(End.X) + Nudge;
const double EndR = static_cast<double>(End.Y) + Nudge;
for (int64 Step = 1; Step < Distance; ++Step)
{
const double Alpha = static_cast<double>(Step) / static_cast<double>(Distance);
FIntPoint Coordinate;
if (!TryRoundHexCoordinate(
FMath::Lerp(StartQ, EndQ, Alpha), FMath::Lerp(StartR, EndR, Alpha), Coordinate))
{
return {};
}
Line[static_cast<int32>(Step)] = Coordinate;
}
return Line;
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GetHexCellCorners(const FIntPoint Coordinate,
const FVector& Origin, const FRotator& Rotation, const double CellRadius,
const EDirectiveUtilHexOrientation Orientation, const double Gap)
{
FHexLayout Layout;
if (!TryMakeHexLayout(Origin, Rotation, CellRadius, Gap, Orientation, Layout))
{
return {};
}
const FVector Center = Layout.GetLocation(Coordinate.X, Coordinate.Y);
if (Center.ContainsNaN())
{
return {};
}
const double StartAngle = Orientation == EDirectiveUtilHexOrientation::PointyTop
? UE_DOUBLE_PI / 6.0
: 0.0;
TArray<FVector> Corners;
Corners.SetNumUninitialized(6);
for (int32 Index = 0; Index < 6; ++Index)
{
const double Angle = StartAngle + Index * (UE_DOUBLE_PI / 3.0);
const FVector Corner = Center
+ (Layout.AxisX * FMath::Cos(Angle) + Layout.AxisY * FMath::Sin(Angle)) * CellRadius;
if (Corner.ContainsNaN())
{
return {};
}
Corners[Index] = Corner;
}
return Corners;
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsAlongDirection(const FVector& Origin,
const FVector& Direction, const int32 Count, const double Spacing, const bool bCentered)
{
FVector NormalizedDirection;
if (Count <= 0 || !FMath::IsFinite(Spacing) || Origin.ContainsNaN()
|| !TryGetNormalizedVector(Direction, NormalizedDirection))
{
return {};
}
const FVector Step = NormalizedDirection * Spacing;
const double FirstStep = bCentered ? -0.5 * static_cast<double>(Count - 1) : 0.0;
return GenerateLinearPoints(Origin, Step, Count, FirstStep);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsBetweenLocations(const FVector& Start,
const FVector& End, const int32 Count, const bool bIncludeEndpoints)
{
if (Count <= 0 || Start.ContainsNaN() || End.ContainsNaN())
{
return {};
}
if (Count == 1)
{
return MakeSinglePoint(Start * 0.5 + End * 0.5);
}
const FVector Delta = End - Start;
if (Delta.ContainsNaN())
{
return {};
}
const double Divisor = bIncludeEndpoints ? static_cast<double>(Count - 1) : static_cast<double>(Count) + 1.0;
TArray<FVector> Points = GenerateLinearPoints(Start, Delta / Divisor, Count, bIncludeEndpoints ? 0.0 : 1.0);
if (bIncludeEndpoints && Points.Num() == Count)
{
Points[0] = Start;
Points.Last() = End;
}
return Points;
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsAlongSpline(const USplineComponent* Spline,
const double Spacing, const bool bIncludeEndpoint, const EDirectiveUtilSplineSpacingMode SpacingMode,
const ESplineCoordinateSpace::Type CoordinateSpace, const double StartDistance, const double EndDistance)
{
FSplineSamplePlan Plan;
if (!IsValidSplineCoordinateSpace(CoordinateSpace)
|| !TryMakeSplineSpacingPlan(Spline, Spacing, SpacingMode, bIncludeEndpoint,
StartDistance, EndDistance, Plan))
{
return {};
}
return SampleSplineLocations(Spline, Plan, CoordinateSpace);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsAlongSplineByCount(
const USplineComponent* Spline, const int32 Count, const bool bIncludeEndpoints,
const ESplineCoordinateSpace::Type CoordinateSpace, const double StartDistance, const double EndDistance)
{
FSplineSamplePlan Plan;
if (!IsValidSplineCoordinateSpace(CoordinateSpace)
|| !TryMakeSplineCountPlan(Spline, Count, bIncludeEndpoints, StartDistance, EndDistance, Plan))
{
return {};
}
return SampleSplineLocations(Spline, Plan, CoordinateSpace);
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateTransformsAlongSpline(
const USplineComponent* Spline, const double Spacing, const bool bIncludeEndpoint,
const EDirectiveUtilSplineSpacingMode SpacingMode, const ESplineCoordinateSpace::Type CoordinateSpace,
const bool bUseSplineScale, const FRotator RotationOffset, const FVector ScaleMultiplier,
const double StartDistance, const double EndDistance)
{
FSplineSamplePlan Plan;
FQuat RotationOffsetQuaternion;
if (ScaleMultiplier.ContainsNaN()
|| !IsValidSplineCoordinateSpace(CoordinateSpace)
|| !TryGetRotationQuaternion(RotationOffset, RotationOffsetQuaternion)
|| !TryMakeSplineSpacingPlan(Spline, Spacing, SpacingMode, bIncludeEndpoint,
StartDistance, EndDistance, Plan))
{
return {};
}
return SampleSplineTransforms(Spline, Plan, CoordinateSpace, bUseSplineScale,
RotationOffsetQuaternion, ScaleMultiplier);
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateTransformsAlongSplineByCount(
const USplineComponent* Spline, const int32 Count, const bool bIncludeEndpoints,
const ESplineCoordinateSpace::Type CoordinateSpace, const bool bUseSplineScale,
const FRotator RotationOffset, const FVector ScaleMultiplier,
const double StartDistance, const double EndDistance)
{
FSplineSamplePlan Plan;
FQuat RotationOffsetQuaternion;
if (ScaleMultiplier.ContainsNaN()
|| !IsValidSplineCoordinateSpace(CoordinateSpace)
|| !TryGetRotationQuaternion(RotationOffset, RotationOffsetQuaternion)
|| !TryMakeSplineCountPlan(Spline, Count, bIncludeEndpoints, StartDistance, EndDistance, Plan))
{
return {};
}
return SampleSplineTransforms(Spline, Plan, CoordinateSpace, bUseSplineScale,
RotationOffsetQuaternion, ScaleMultiplier);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsOnCircle(const FVector& Center,
const FRotator& Rotation, const double Radius, const int32 Count, const double StartAngleDegrees)
{
if (Count <= 0 || !FMath::IsFinite(Radius) || !FMath::IsFinite(StartAngleDegrees))
{
return {};
}
const double AbsoluteRadius = FMath::Abs(Radius);
return GeneratePlanarRadialPoints(Center, Rotation, Count, WrapDegreesAsRadians(StartAngleDegrees),
UE_DOUBLE_TWO_PI / Count, [AbsoluteRadius](const int32) { return AbsoluteRadius; });
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateTransformsOnCircle(const FVector& Center,
const FRotator& Rotation, const double Radius, const int32 Count, const double StartAngleDegrees,
const EDirectiveUtilRadialOrientation Orientation, const FRotator RotationOffset, const FVector Scale)
{
if (Count <= 0 || !FMath::IsFinite(StartAngleDegrees))
{
return {};
}
return GeneratePlanarRadialTransforms(Center, Rotation, Radius, Count,
WrapDegreesAsRadians(StartAngleDegrees), UE_DOUBLE_TWO_PI / Count, 1.0,
Orientation, RotationOffset, Scale);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsOnArc(const FVector& Center,
const FRotator& Rotation, const double Radius, const int32 Count, const double StartAngleDegrees,
const double ArcAngleDegrees, const bool bIncludeEndpoint)
{
if (Count <= 0 || !FMath::IsFinite(Radius) || !FMath::IsFinite(StartAngleDegrees)
|| !FMath::IsFinite(ArcAngleDegrees))
{
return {};
}
const double Divisor = bIncludeEndpoint && Count > 1 ? Count - 1.0 : static_cast<double>(Count);
const double StepAngle = Count == 1 ? 0.0 : WrapDegreesAsRadians(ArcAngleDegrees / Divisor);
const double AbsoluteRadius = FMath::Abs(Radius);
return GeneratePlanarRadialPoints(Center, Rotation, Count, WrapDegreesAsRadians(StartAngleDegrees),
StepAngle, [AbsoluteRadius](const int32) { return AbsoluteRadius; });
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::GenerateTransformsOnArc(const FVector& Center,
const FRotator& Rotation, const double Radius, const int32 Count, const double StartAngleDegrees,
const double ArcAngleDegrees, const bool bIncludeEndpoint,
const EDirectiveUtilRadialOrientation Orientation, const FRotator RotationOffset, const FVector Scale)
{
if (Count <= 0 || !FMath::IsFinite(StartAngleDegrees) || !FMath::IsFinite(ArcAngleDegrees))
{
return {};
}
const double Divisor = bIncludeEndpoint && Count > 1 ? Count - 1.0 : static_cast<double>(Count);
const double StepAngle = Count == 1 ? 0.0 : WrapDegreesAsRadians(ArcAngleDegrees / Divisor);
return GeneratePlanarRadialTransforms(Center, Rotation, Radius, Count,
WrapDegreesAsRadians(StartAngleDegrees), StepAngle, ArcAngleDegrees,
Orientation, RotationOffset, Scale);
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsOnDisc(const FVector& Center,
const FRotator& Rotation, const double Radius, const int32 Count, const double AngleOffsetDegrees)
{
if (Count <= 0 || !FMath::IsFinite(Radius) || !FMath::IsFinite(AngleOffsetDegrees))
{
return {};
}
if (Count == 1)
{
return MakeSinglePoint(Center);
}
const double AbsoluteRadius = FMath::Abs(Radius);
const double InverseCount = 1.0 / Count;
const double GoldenAngle = UE_DOUBLE_PI * (3.0 - FMath::Sqrt(5.0));
return GeneratePlanarRadialPoints(Center, Rotation, Count, WrapDegreesAsRadians(AngleOffsetDegrees),
GoldenAngle, [AbsoluteRadius, InverseCount](const int32 Index)
{
return AbsoluteRadius * FMath::Sqrt((Index + 0.5) * InverseCount);
});
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::GeneratePointsOnSphere(const FVector& Center,
const FRotator& Rotation, const double Radius, const int32 Count, const double AngleOffsetDegrees)
{
if (!IsSupportedGeneratedElementCount(Count)
|| Center.ContainsNaN() || !FMath::IsFinite(Radius) || !FMath::IsFinite(AngleOffsetDegrees))
{
return {};
}
FVector AxisX;
FVector AxisY;
FVector AxisZ;
if (!TryGetRotatedAxes(Rotation, AxisX, AxisY, AxisZ))
{
return {};
}
const double AbsoluteRadius = FMath::Abs(Radius);
if (Count == 1)
{
return MakeSinglePoint(Center + AxisZ * AbsoluteRadius);
}
TArray<FVector> Points;
Points.SetNumUninitialized(Count);
const double InverseCount = 1.0 / Count;
const double GoldenAngle = UE_DOUBLE_PI * (3.0 - FMath::Sqrt(5.0));
FAngleStepper Angle(WrapDegreesAsRadians(AngleOffsetDegrees), GoldenAngle);
for (int32 Index = 0; Index < Count; ++Index)
{
const double Z = 1.0 - 2.0 * (Index + 0.5) * InverseCount;
const double RadialScale = FMath::Sqrt(FMath::Max(0.0, 1.0 - Z * Z));
const FVector Point = Center + (AxisX * (Angle.Cosine * RadialScale)
+ AxisY * (Angle.Sine * RadialScale) + AxisZ * Z) * AbsoluteRadius;
if (Point.ContainsNaN())
{
return {};
}
Points[Index] = Point;
Angle.Advance();
}
return Points;
}
TArray<FVector> UDirectiveUtilMathFunctionLibrary::OffsetLocationsByNoise(const TArray<FVector>& Locations,
const double NoiseScale, const double Amplitude, const FVector Direction, const FVector NoiseOffset)
{
FVector NormalizedDirection;
if (!TryGetNoiseOffsetInputs(NoiseScale, Amplitude, Direction, NoiseOffset, NormalizedDirection))
{
return {};
}
TArray<FVector> Result;
Result.SetNumUninitialized(Locations.Num());
for (int32 Index = 0; Index < Locations.Num(); ++Index)
{
if (!TryOffsetLocationByNoise(Locations[Index], NormalizedDirection, NoiseScale, Amplitude,
NoiseOffset, Result[Index]))
{
return {};
}
}
return Result;
}
TArray<FTransform> UDirectiveUtilMathFunctionLibrary::OffsetTransformsByNoise(
const TArray<FTransform>& Transforms, const double NoiseScale, const double Amplitude,
const FVector Direction, const FVector NoiseOffset)
{
FVector NormalizedDirection;
if (!TryGetNoiseOffsetInputs(NoiseScale, Amplitude, Direction, NoiseOffset, NormalizedDirection))
{
return {};
}
TArray<FTransform> Result;
Result.SetNumUninitialized(Transforms.Num());
for (int32 Index = 0; Index < Transforms.Num(); ++Index)
{
FVector OffsetLocation;
if (!TryOffsetLocationByNoise(Transforms[Index].GetLocation(), NormalizedDirection, NoiseScale,
Amplitude, NoiseOffset, OffsetLocation))
{
return {};
}
Result[Index] = Transforms[Index];
Result[Index].SetLocation(OffsetLocation);
}
return Result;
}