#include "UniStrokePoint.h" #include "WizardingCentral/Logger/LogLumiCharacter.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& 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& 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 FUniStrokePoint::Vectorize(const TArray& Points) { float Sum = 0.0f; TArray 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& VectorA, const TArray& 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::From(const TArray& Points) { TArray NewPoints; NewPoints.Reserve(Points.Num()); Algo::Transform( Points, NewPoints, [](const FVector2D& Point) { return FUniStrokePoint(Point.X, Point.Y); } ); return NewPoints; } void FUniStrokePoint::Resample(TArray& Points, const int& Num) { const float I = PathLength(Points) / (Num - 1); // D <- 0 float D = 0.0f; // newPoints <- points[0] TArray 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& 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& 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& Points, const float& Theta) { TArray 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& Points, const float& Size) { TArray 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& Points, const FUniStrokePoint& Point) { TArray 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& Points) { TArray 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& Points, const TArray& 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& Points, const TArray& T, const float& Theta) { // newPoints <- RotateBy(points, theta) TArray NewPoints = Points; RotateBy(NewPoints, Theta); // d <- PathDistance(newPoints, T.points) // return d return PathDistance(NewPoints, T); } float FUniStrokePoint::PathDistance(const TArray& PathA, const TArray& PathB) { // Edge case: Different number of points if (PathA.Num() != PathB.Num()) return TNumericLimits::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(); }