#include "RadialMenu.h" void URadialMenu::Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread) { NumRight = (NItems / 2); NumLeft = NItems - NumRight; Items.SetNum(NItems); View LeftItems(Items.GetData(), NumLeft); View RightItems(Items.GetData() + NumLeft, NumRight); CalculateSpokes(LeftItems, ItemHeight, InnerRadius, MinSpoke); CalculateSpokes(RightItems, ItemHeight, InnerRadius, MinSpoke); double LeftWidth = WidestSpoke(LeftItems); double RightWidth = WidestSpoke(RightItems); double HalfWidth = FMath::Max(LeftWidth, RightWidth) + Spread; CalculateSpread(LeftItems, HalfWidth - LeftWidth); CalculateSpread(RightItems, HalfWidth - RightWidth); FlipHorizontal(LeftItems); } FVector2D URadialMenu::SpokeVector(int32 I, int32 NSide, int32 NTotal) { double SpokeAngle = 1.0 / NTotal; double OffsetAngle = 0.5 * (0.5 - ((NSide - 1) * SpokeAngle)); double Revolutions = (I * SpokeAngle) + OffsetAngle; double Radians = (Revolutions * 2.0 * UE_PI); return FVector2D(FMath::Sin(Radians), -FMath::Cos(Radians)); } void URadialMenu::FlipHorizontal(View V) { for (FRadialMenuItem& Item : V) { Item.Point1.X = -Item.Point1.X; Item.Point2.X = -Item.Point2.X; Item.Point3.X = -Item.Point3.X; Item.RightSide = !Item.RightSide; } } double URadialMenu::WidestSpoke(View V) { double Result = 0.0; for (const FRadialMenuItem &Item : V) { Result = FMath::Max(Item.Point2.X, Result); } return Result; } void URadialMenu::CalculateSpread(View V, double Offset) { for (FRadialMenuItem &Item : V) { Item.Point3 = Item.Point2 + FVector2D(Offset, 0.0); } } void URadialMenu::CalculateSpokes(View V, float ItemHeight, float InnerRadius, float MinSpoke) { if (V.Num() == 0) return; // RightSide is always initialized to true, it may get // reversed by FlipHorizontal. for (int32 I = 0; I < V.Num(); I++) { V[I].RightSide = true; V[I].Point1 = SpokeVector(I, V.Num(), Items.Num()) * InnerRadius; } // Calculate point2 for all spokes. double NextLineMin = ItemHeight * 0.5; int32 Mid = (V.Num() / 2); if (V.Num() & 1) { V[Mid].Point2 = FVector2D(InnerRadius + MinSpoke, 0.0); NextLineMin = ItemHeight; Mid += 1; } for (int32 I = Mid; I < V.Num(); I++) { FVector2D UnitVec = SpokeVector(I, V.Num(), Items.Num()); double Y = (UnitVec.Y * (InnerRadius + MinSpoke)); if (Y < NextLineMin) Y = NextLineMin; NextLineMin = Y + ItemHeight; FVector2D Point2 = UnitVec * (Y / UnitVec.Y); V[I].Point2 = Point2; V[V.Num() - 1 - I].Point2 = Point2 * FVector2D(1.0,-1.0); } // The middle spoke is calculated using a different formula, // which may result in a short spoke. If so, fix it to make // it at least as long as the adjacent spoke. if ((V.Num() & 1) && (V.Num() >= 3)) { Mid = V.Num() / 2; if (V[Mid].Point2.X < V[Mid + 1].Point2.X) V[Mid].Point2.X = V[Mid + 1].Point2.X; } }