From e669140e2cda0b7745ac4d7cf43b2d09c2e86b79 Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 9 May 2026 03:16:41 -0400 Subject: [PATCH] Layout code for radial menu complete. --- Source/Integration/RadialMenu.cpp | 85 ++++++++++++++++++------------- Source/Integration/RadialMenu.h | 49 ++++++++++++------ 2 files changed, 84 insertions(+), 50 deletions(-) diff --git a/Source/Integration/RadialMenu.cpp b/Source/Integration/RadialMenu.cpp index e1b55cbd..8d03464c 100644 --- a/Source/Integration/RadialMenu.cpp +++ b/Source/Integration/RadialMenu.cpp @@ -3,28 +3,30 @@ void URadialMenu::Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread) { - CNItems = NItems; - CItemHeight = ItemHeight; - CInnerRadius = InnerRadius; - CMinSpoke = MinSpoke; - CSpread = Spread; - - CNumRight = (NItems / 2); - CNumLeft = NItems - CNumRight; + NumRight = (NItems / 2); + NumLeft = NItems - NumRight; Items.SetNum(NItems); - LeftItems = View(Items.GetData(), CNumLeft); - RightItems = View(Items.GetData() + CNumLeft, CNumRight); + 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); - CalculateSide(LeftItems); - CalculateSide(RightItems); FlipHorizontal(LeftItems); } -FVector2D URadialMenu::PieSliceToVector(double Slice, double Slices) +FVector2D URadialMenu::SpokeVector(int32 I, int32 NSide, int32 NTotal) { - double HalfRevolutions = (Slice + 0.5) / Slices; - double Radians = (HalfRevolutions * UE_PI); + 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)); } @@ -39,48 +41,63 @@ void URadialMenu::FlipHorizontal(View V) } } -void URadialMenu::CalculateSide(View V) +double URadialMenu::WidestSpoke(View V) { - // Point1 is simple. RightSide is always initialized to - // true, it may get reversed in FlipHorizontal. + 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 = PieSliceToVector(I, V.Num()) * CInnerRadius; + V[I].Point1 = SpokeVector(I, V.Num(), Items.Num()) * InnerRadius; } // Calculate point2 for all spokes. - double NextLineMin = CItemHeight * 0.5; + double NextLineMin = ItemHeight * 0.5; int32 Mid = (V.Num() / 2); if (V.Num() & 1) { - V[Mid].Point2 = FVector2D(CInnerRadius + CMinSpoke, 0.0); - NextLineMin = CItemHeight; + V[Mid].Point2 = FVector2D(InnerRadius + MinSpoke, 0.0); + NextLineMin = ItemHeight; Mid += 1; } for (int32 I = Mid; I < V.Num(); I++) { - FVector2D UnitVec = PieSliceToVector(I, V.Num()); - double Y = (UnitVec.Y * (CInnerRadius + CMinSpoke)); + FVector2D UnitVec = SpokeVector(I, V.Num(), Items.Num()); + double Y = (UnitVec.Y * (InnerRadius + MinSpoke)); if (Y < NextLineMin) Y = NextLineMin; - NextLineMin = Y + CItemHeight; + NextLineMin = Y + ItemHeight; FVector2D Point2 = UnitVec * (Y / UnitVec.Y); V[I].Point2 = Point2; - V[V.Num() - I].Point2 = Point2 * FVector2D(1.0,-1.0); + V[V.Num() - 1 - I].Point2 = Point2 * FVector2D(1.0,-1.0); } - // The rule we use for calculating point2 may result in - // a very short horizontal spoke. If so, fix it. + // 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; } - - // Calculate Point3. - for (int32 I = 0; I < V.Num(); I++) - { - V[I].Point3 = V[I].Point2 + FVector2D(CSpread, 0.0); - } } diff --git a/Source/Integration/RadialMenu.h b/Source/Integration/RadialMenu.h index 05b86234..9673d213 100644 --- a/Source/Integration/RadialMenu.h +++ b/Source/Integration/RadialMenu.h @@ -30,31 +30,48 @@ public: void Configure(int32 NItems, float ItemHeight, float InnerRadius, float MinSpoke, float Spread); UFUNCTION(BlueprintCallable) - int32 NumItems() const { return Items.Num(); } + int32 LeftNum() const { return NumLeft; } + + UFUNCTION(BlueprintCallable) + int32 RightNum() const { return NumRight; } UFUNCTION(BlueprintCallable) const TArray GetItems() const { return Items; } - TArray Items; private: using View = TArrayView; - void CalculateSide(View V); + // Give the unit vector for the selected spoke. NSide is + // the number of spokes on the side of the wheel that + // we're calculating, and NTotal is the total number of + // spokes on both sides. The spokes on a given side are + // organized top-to-bottom, and the angle between the + // spokes is always equal to (1/NTotal) of the circle. + FVector2D SpokeVector(int32 I, int32 NSide, int32 NTotal); + + // Populate Point1 and Point2, these are the + // endpoints of the spoke segment. Spokes are + // designed to always be long enough to make room + // for MinSpoke, but also to make enough room to + // keep the menu items from overlapping. + void CalculateSpokes(View V, float ItemHeight, float InnerRadius, float MinSpoke); + + // Search for the widest spoke, and return its X coordinate. + double WidestSpoke(View V); + + // Populate Point3, this is the endpoint of the spread + // line that goes horizontal. + void CalculateSpread(View V, double Offset); + + // Flip everything in the specified view horizontally. void FlipHorizontal(View V); - // The half-circle is divided into pie slices. Returns a direction vector - // which aims directly down the center of the pie slice. - FVector2D PieSliceToVector(double Slice, double Slices); - - int32 CNItems = 0; - float CItemHeight = 0; - float CInnerRadius = 0; - float CMinSpoke = 0; - float CSpread = 0; - int32 CNumLeft = 0; - int32 CNumRight = 0; - View LeftItems; - View RightItems; + // The array of items. + TArray Items; + + // Number of items on the left, and on the right. + int32 NumLeft = 0; + int32 NumRight = 0; };