Files
WizardingHub/Source/WizardingCentral/Utils/OneDollar/UniStrokePoint.cpp
T
2025-05-15 01:34:38 -04:00

399 lines
9.0 KiB
C++

#include "UniStrokePoint.h"
FUniStrokePoint::FUniStrokePoint()
{
this->X = 0.0f;
this->Y = 0.0f;
}
FUniStrokePoint::FUniStrokePoint(const float& X, const float& Y)
{
this->X = X;
this->Y = Y;
}
float
FUniStrokePoint::Distance(const FUniStrokePoint& PointA,
const FUniStrokePoint& PointB)
{
return FMath::Sqrt(
// delta X
FMath::Square(PointB.X - PointA.X) +
/// delta Y
FMath::Square(PointB.Y - PointA.Y)
);
}
FUniStrokePoint
FUniStrokePoint::Centroid(const TArray<FUniStrokePoint>& Points)
{
float SumX = 0.0f;
float SumY = 0.0f;
for (const FUniStrokePoint& Point : Points)
{
SumX += Point.X;
SumY += Point.Y;
}
return FUniStrokePoint(
SumX / Points.Num(),
SumY / Points.Num()
);
}
FUniStrokeRectangle
FUniStrokePoint::BoundingBox(const TArray<FUniStrokePoint>& Points)
{
// Edge case
if (Points.Num() == 0)
return FUniStrokeRectangle();
float MinX = Points[0].X;
float MinY = Points[0].Y;
float MaxX = Points[0].X;
float MaxY = Points[0].Y;
for (const FUniStrokePoint& Point : Points)
{
MinX = FMath::Min(MinX, Point.X);
MinY = FMath::Min(MinY, Point.Y);
MaxX = FMath::Max(MaxX, Point.X);
MaxY = FMath::Max(MaxY, Point.Y);
}
return FUniStrokeRectangle(MinX, MinY, MaxX - MinX, MaxY - MinY);
}
// TODO: Proofread this function
TArray<float>
FUniStrokePoint::Vectorize(const TArray<FUniStrokePoint>& Points)
{
float Sum = 0.0f;
TArray<float> Vector;
for (const FUniStrokePoint& Point : Points)
{
Vector.Add(Point.X);
Vector.Add(Point.Y);
Sum += FMath::Square(Point.X) + FMath::Square(Point.Y);
}
const float Magnitude = FMath::Sqrt(Sum);
for (float& V : Vector)
V /= Magnitude;
return Vector;
}
FString FUniStrokePoint::ToString() const
{
return FString::Printf(TEXT("X=%.3f Y=%.3f"), X, Y);
}
float
FUniStrokePoint::OptimalCosineDistance(const TArray<float>& VectorA,
const TArray<float>& VectorB)
{
float A = 0.0f;
float B = 0.0f;
for (int i = 0; i < VectorA.Num(); i += 2)
{
A += VectorA[i] * VectorB[i] + VectorA[i + 1] * VectorB[i + 1];
B += VectorA[i] * VectorB[i + 1] - VectorA[i + 1] * VectorB[i];
}
const float Angle = FMath::Atan(B / A);
return FMath::Acos(A * FMath::Cos(Angle) + B * FMath::Sin(Angle));
}
TArray<FUniStrokePoint>
FUniStrokePoint::From(const TArray<FVector2D>& Points)
{
TArray<FUniStrokePoint> NewPoints;
NewPoints.Reserve(Points.Num());
Algo::Transform(
Points,
NewPoints,
[](const FVector2D& Point)
{
return FUniStrokePoint(Point.X, Point.Y);
}
);
return NewPoints;
}
void
FUniStrokePoint::Resample(TArray<FUniStrokePoint>& Points,
const int& Num)
{
const float I = PathLength(Points) / (Num - 1);
// D <- 0
float D = 0.0f;
// newPoints <- points[0]
TArray<FUniStrokePoint> OldPoints = Points;
Points.SetNum(1);
// foreach point p[i] for i >= 1 in points do
for (int i = 1; i < OldPoints.Num(); ++i)
{
// d <- Distance(p[i - 1], p[i])
const float d = Distance(OldPoints[i - 1], OldPoints[i]);
// if (D + d) >= I then
if (D + d >= I)
{
// q.X <- p[i - 1].X + ((I - D) / d) * (p[i].X - p[i - 1].X)
const float qx = OldPoints[i - 1].X + (I - D) / d * (OldPoints[i].X - OldPoints[i - 1].X);
// q.Y <- p[i - 1].Y + ((I - D) / d) * (p[i].Y - p[i - 1].Y)
const float qy = OldPoints[i - 1].Y + (I - D) / d * (OldPoints[i].Y - OldPoints[i - 1].Y);
// Append(newPoints, q)
FUniStrokePoint NewPoint = FUniStrokePoint(qx, qy);
Points.Add(NewPoint);
// Insert(points, i, q)
OldPoints.Insert(NewPoint, i);
// D <- 0
D = 0.0f;
}
else
{
// D <- D + d
D += d;
}
}
// Edge case
if (Points.Num() == Num - 1)
Points.Add(OldPoints.Last());
}
float
FUniStrokePoint::PathLength(const TArray<FUniStrokePoint>& Points)
{
// d <- 0
float d = 0;
// for i from 1 to |A| step 1 do
for (auto i = 1; i < Points.Num(); ++i)
{
// d <- d + Distance(A[i - 1], A[i])
d += Distance(Points[i - 1], Points[i]);
}
// return d
return d;
}
void
FUniStrokePoint::RotateToZero(TArray<FUniStrokePoint>& Points)
{
// c <- Centroid(points)
FUniStrokePoint c = Centroid(Points);
// theta <- Atan(c.Y - points[0].Y, c.X - points[0].X)
const float Theta = FMath::Atan2(c.Y - Points[0].Y, c.X - Points[0].X);
// newPoints <- RotateBy(points, -theta)
RotateBy(Points, -Theta);
// return newPoints
}
void
FUniStrokePoint::RotateBy(TArray<FUniStrokePoint>& Points,
const float& Theta)
{
TArray<FUniStrokePoint> OldPoints = Points;
Points.Empty();
// c <- Centroid(points)
const FUniStrokePoint c = Centroid(OldPoints);
const float SinTheta = FMath::Sin(Theta);
const float CosTheta = FMath::Cos(Theta);
// foreach point p in points do
for (const FUniStrokePoint& p : OldPoints)
{
FUniStrokePoint q = FUniStrokePoint(
// q.x <- (p.x - c.x) * Cos(theta) - (p.y - c.y) * Sin(theta) + c.x
(p.X - c.X) * CosTheta - (p.Y - c.Y) * SinTheta + c.X,
// q.y <- (p.x - c.x) * Sin(theta) + (p.y - c.y) * Cos(theta) + c.y
(p.X - c.X) * SinTheta + (p.Y - c.Y) * CosTheta + c.Y
);
// Append(newPoints, q)
Points.Add(q);
}
// return newPoints
}
void
FUniStrokePoint::ScaleToSquare(TArray<FUniStrokePoint>& Points,
const float& Size)
{
TArray<FUniStrokePoint> OldPoints = Points;
Points.Empty();
// B <- BoundingRect(points)
const FUniStrokeRectangle B = BoundingBox(OldPoints);
// foreach point p in points do
for (const FUniStrokePoint& p : OldPoints)
{
const FUniStrokePoint q = FUniStrokePoint(
// q.x <- p.x * (size / B.width)
p.X * (Size / B.Width),
// q.y <- p.y * (size / B.height)
p.Y * (Size / B.Height)
);
// Append(newPoints, q)
Points.Add(q);
}
}
void
FUniStrokePoint::TranslateTo(TArray<FUniStrokePoint>& Points,
const FUniStrokePoint& Point)
{
TArray<FUniStrokePoint> OldPoints = Points;
Points.Empty();
// c <- Centroid(points)
const FUniStrokePoint c = Centroid(OldPoints);
// foreach point p in points do
for (; const FUniStrokePoint& p : OldPoints)
{
const FUniStrokePoint q = FUniStrokePoint(
// q.x <- p.x + point.x - c.x
p.X + Point.X - c.X,
// q.y <- p.y + point.y - c.y.
p.Y + Point.Y - c.Y
);
// Append(newPoints, q)
Points.Add(q);
}
// return newPoints
}
void
FUniStrokePoint::TranslateToOrigin(TArray<FUniStrokePoint>& Points)
{
TArray<FUniStrokePoint> OldPoints = Points;
Points.Empty();
// c <- Centroid(points)
const FUniStrokePoint c = Centroid(OldPoints);
// foreach point p in points do
for (const FUniStrokePoint& p : OldPoints)
{
const FUniStrokePoint q = FUniStrokePoint(
// q.x <- p.x - c.x
p.X - c.X,
// q.y <- p.y - c.y.
p.Y - c.Y
);
// Append(newPoints, q)
Points.Add(q);
}
// return newPoints
}
float
FUniStrokePoint::DistanceAtBestAngle(const TArray<FUniStrokePoint>& Points,
const TArray<FUniStrokePoint>& T,
const float& ThetaFrom,
const float& ThetaTo,
const float& ThetaThreshold)
{
float ThetaA = ThetaFrom;
float ThetaB = ThetaTo;
// x1 <- phi theta_a + (1 - phi) theta_b
float x1 = Phi * ThetaA + (1 - Phi) * ThetaB;
// f1 <- DistanceAtAngle(points, T, x1)
float f1 = DistanceAtAngle(Points, T, x1);
// x2 <- (1 - phi) theta_a + phi theta_b
float x2 = (1 - Phi) * ThetaA + Phi * ThetaB;
// f2 <- DistanceAtAngle(points, T, x2)
float f2 = DistanceAtAngle(Points, T, x2);
// while |theta_b - theta_a| > threshold do
while (FMath::Abs(ThetaB - ThetaA) > ThetaThreshold)
{
// if f1 < f2 then
if (f1 < f2)
{
// theta_b <- x2
ThetaB = x2;
// x2 <- x1
x2 = x1;
// f2 <- f1
f2 = f1;
// x1 <- phi theta_a + (1 - phi) theta_b
x1 = Phi * ThetaA + (1 - Phi) * ThetaB;
// f1 <- DistanceAtAngle(points, T, x1)
f1 = DistanceAtAngle(Points, T, x1);
}
// else
else
{
// theta_a <- x1
ThetaA = x1;
// x1 <- x2
x1 = x2;
// f1 <- f2
f1 = f2;
// x2 <- (1 - phi) theta_a + phi theta_b
x2 = (1 - Phi) * ThetaA + Phi * ThetaB;
// f2 <- DistanceAtAngle(points, T, x2)
f2 = DistanceAtAngle(Points, T, x2);
}
}
// return min(f1, f2)
return FMath::Min(f1, f2);
}
float
FUniStrokePoint::DistanceAtAngle(const TArray<FUniStrokePoint>& Points,
const TArray<FUniStrokePoint>& T,
const float& Theta)
{
// newPoints <- RotateBy(points, theta)
TArray<FUniStrokePoint> NewPoints = Points;
RotateBy(NewPoints, Theta);
// d <- PathDistance(newPoints, T.points)
// return d
return PathDistance(NewPoints, T);
}
float
FUniStrokePoint::PathDistance(const TArray<FUniStrokePoint>& PathA,
const TArray<FUniStrokePoint>& PathB)
{
// Edge case: Different number of points
if (PathA.Num() != PathB.Num())
return TNumericLimits<float>::Max();
// d <- 0
float d = 0;
// for i from 0 to |A| step 1 do
for (int i = 0; i < PathA.Num(); ++i)
{
// d <- d + Distance(A[i], B[i])
d += Distance(PathA[i], PathB[i]);
}
// return d / |A|
return d / PathA.Num();
}