From 212b99a4c52f7d61adfa4663c3a017e46d2e5206 Mon Sep 17 00:00:00 2001 From: Michael Votaw Date: Sun, 8 Sep 2024 10:40:21 -0500 Subject: [PATCH] Changed StageLayout into a struct and consolidated layout math functions into it. Added convenience methods for determining if a given column is an up, down, or side arrow. Added explicit tech counts for up footswitches and down footswitches --- src/StepParityCost.cpp | 141 ++++++---------------------------- src/StepParityCost.h | 12 +-- src/StepParityDatastructs.cpp | 94 +++++++++++++++++++++++ src/StepParityDatastructs.h | 39 ++++++++-- src/StepParityGenerator.cpp | 47 +----------- src/StepParityGenerator.h | 25 +++++- src/Steps.cpp | 13 ++-- src/TechCounts.cpp | 18 ++++- src/TechCounts.h | 6 +- 9 files changed, 203 insertions(+), 192 deletions(-) diff --git a/src/StepParityCost.cpp b/src/StepParityCost.cpp index 6c17627088..9c7e2d330e 100644 --- a/src/StepParityCost.cpp +++ b/src/StepParityCost.cpp @@ -7,30 +7,6 @@ using namespace StepParity; -template -bool vectorIncludes(const std::vector& vec, const T& value, int columnCount) { - for (int i = 0; i < columnCount; i++) - { - if(vec[i] == value) - { - return true; - } - } - return false; -} - -template -int indexOf(const std::vector& vec, const T& value, int columnCount) { - for (int i = 0; i < columnCount; i++) - { - if(vec[i] == value) - { - return i; - } - } - return -1; -} - template bool isEmpty(const std::vector & vec, int columnCount) { for (int i = 0; i < columnCount; i++) @@ -241,7 +217,7 @@ float StepParityCost::calcHoldSwitchCost(State * initialState, State * resultSta (previousFoot == -1 ? 1 : sqrt( - getDistanceSq(layout[c], layout[previousFoot]) + layout.getDistanceSq(c, previousFoot) )); } } @@ -427,12 +403,12 @@ float StepParityCost::calcTwistedFootCost(State * resultState) int rightHeel = resultState->whereTheFeetAre[RIGHT_HEEL]; int rightToe = resultState->whereTheFeetAre[RIGHT_TOE]; - StagePoint leftPos = averagePoint(leftHeel, leftToe); - StagePoint rightPos = averagePoint(rightHeel, rightToe); + StagePoint leftPos = layout.averagePoint(leftHeel, leftToe); + StagePoint rightPos = layout.averagePoint(rightHeel, rightToe); bool crossedOver = rightPos.x < leftPos.x; - bool rightBackwards = rightHeel != -1 && rightToe != -1 ? layout[rightToe].y < layout[rightHeel].y : false; - bool leftBackwards = leftHeel != -1 && leftToe != -1 ? layout[leftToe].y < layout[leftHeel].y : false; + bool rightBackwards = rightHeel != -1 && rightToe != -1 ? layout.columns[rightToe].y < layout.columns[rightHeel].y : false; + bool leftBackwards = leftHeel != -1 && leftToe != -1 ? layout.columns[leftToe].y < layout.columns[leftHeel].y : false; if(!crossedOver && (rightBackwards || leftBackwards)) { @@ -493,19 +469,19 @@ float StepParityCost::calcFacingCosts(State * initialState, State * resultState, // facing backwards gives a bit of bad weight (scaled heavily the further back you angle, so crossovers aren't Too bad; less bad than doublesteps) float heelFacing = endLeftHeel != -1 && endRightHeel != -1 - ? getXDifference(endLeftHeel, endRightHeel) + ? layout.getXDifference(endLeftHeel, endRightHeel) : 0; float toeFacing = endLeftToe != -1 && endRightToe != -1 - ? getXDifference(endLeftToe, endRightToe) + ? layout.getXDifference(endLeftToe, endRightToe) : 0; float leftFacing = endLeftHeel != -1 && endLeftToe != -1 - ? getYDifference(endLeftHeel, endLeftToe) + ? layout.getYDifference(endLeftHeel, endLeftToe) : 0; float rightFacing = endRightHeel != -1 && endRightToe != -1 - ? getYDifference(endRightHeel, endRightToe) + ? layout.getYDifference(endRightHeel, endRightToe) : 0; @@ -560,16 +536,16 @@ float StepParityCost::calcSpinCosts(State * initialState, State * resultState, s if (endRightToe == -1) endRightToe = endRightHeel; // spin - StagePoint previousLeftPos = averagePoint( + StagePoint previousLeftPos = layout.averagePoint( initialState->whereTheFeetAre[LEFT_HEEL], initialState->whereTheFeetAre[LEFT_TOE] ); - StagePoint previousRightPos = averagePoint( + StagePoint previousRightPos = layout.averagePoint( initialState->whereTheFeetAre[RIGHT_HEEL], initialState->whereTheFeetAre[RIGHT_TOE] ); - StagePoint leftPos = averagePoint(endLeftHeel, endLeftToe); - StagePoint rightPos = averagePoint(endRightHeel, endRightToe); + StagePoint leftPos = layout.averagePoint(endLeftHeel, endLeftToe); + StagePoint rightPos = layout.averagePoint(endRightHeel, endRightToe); if ( rightPos.x < leftPos.x && @@ -626,26 +602,19 @@ float StepParityCost::caclFootswitchCost(State * initialState, State * resultSta return cost; } -// TODO: This doesn't work for doubles, since it's only checking P1 left and P1 right float StepParityCost::calcSideswitchCost(State * initialState, State * resultState, int columnCount) { float cost = 0; - if ( - initialState->columns[0] != resultState->columns[0] && - resultState->columns[0] != NONE && - initialState->columns[0] != NONE && - !resultState->didTheFootMove[initialState->columns[0]]) + for(auto c : layout.sideArrows) { - cost += SIDESWITCH; - } - - if ( - initialState->columns[3] != resultState->columns[3] && - resultState->columns[3] != NONE && - initialState->columns[3] != NONE && - !resultState->didTheFootMove[initialState->columns[3]] - ) { - cost += SIDESWITCH; + if ( + initialState->columns[c] != resultState->columns[c] && + resultState->columns[c] != NONE && + initialState->columns[c] != NONE && + !resultState->didTheFootMove[initialState->columns[c]]) + { + cost += SIDESWITCH; + } } return cost; } @@ -694,7 +663,7 @@ float StepParityCost::calcBigMovementsQuicklyCost(State * initialState, State * continue; } - float dist = (sqrt(getDistanceSq(layout[initialPosition], layout[resultPosition])) * DISTANCE) / elapsedTime; + float dist = (sqrt(layout.getDistanceSq(initialPosition, resultPosition)) * DISTANCE) / elapsedTime; // Otherwise if we're still bracketing, this is probably a less drastic movement if(isBracketing) { @@ -878,67 +847,3 @@ bool StepParityCost::didJackRight(State * initialState, State * resultState, int } return jackedRight; } - - -float StepParityCost::getDistanceSq(StepParity::StagePoint p1, StepParity::StagePoint p2) -{ - return (p1.y - p2.y) * (p1.y - p2.y) + (p1.x - p2.x) * (p1.x - p2.x); -} - -float StepParityCost::getPlayerAngle(StepParity::StagePoint left, StepParity::StagePoint right) -{ - float x1 = right.x - left.x; - float y1 = right.y - left.y; - float x2 = 1; - float y2 = 0; - float dot = x1 * x2 + y1 * y2; - float det = x1 * y2 - y1 * x2; - return atan2f(det, dot); -} - - - - -float StepParityCost::getXDifference(int leftIndex, int rightIndex) { - if (leftIndex == rightIndex) return 0; - float dx = layout[rightIndex].x - layout[leftIndex].x; - float dy = layout[rightIndex].y - layout[leftIndex].y; - - float distance = sqrt(dx * dx + dy * dy); - dx /= distance; - - bool negative = dx <= 0; - - dx = pow(dx, 4); - - if (negative) dx = -dx; - - return dx; - } - - float StepParityCost::getYDifference(int leftIndex, int rightIndex) { - if (leftIndex == rightIndex) return 0; - float dx = layout[rightIndex].x - layout[leftIndex].x; - float dy = layout[rightIndex].y - layout[leftIndex].y; - - float distance = sqrt(dx * dx + dy * dy); - dy /= distance; - - bool negative = dy <= 0; - - dy = pow(dy, 4); - - if (negative) dy = -dy; - - return dy; - } - -StagePoint StepParityCost::averagePoint(int leftIndex, int rightIndex) { - if (leftIndex == -1 && rightIndex == -1) return { 0,0 }; - if (leftIndex == -1) return layout[rightIndex]; - if (rightIndex == -1) return layout[leftIndex]; - return { - (layout[leftIndex].x + layout[rightIndex].x) / 2.0f, - (layout[leftIndex].y + layout[rightIndex].y) / 2.0f, - }; - } diff --git a/src/StepParityCost.h b/src/StepParityCost.h index 33725636a8..4c7e0fe7ef 100644 --- a/src/StepParityCost.h +++ b/src/StepParityCost.h @@ -45,9 +45,7 @@ namespace StepParity StageLayout layout; public: - StepParityCost(StageLayout _layout) - { - layout = _layout; + StepParityCost(const StageLayout& _layout): layout(_layout){ } /// @brief Computes and returns a cost value for the player moving from initialState to resultState. @@ -81,14 +79,6 @@ namespace StepParity bool didDoubleStep(State * initialState, State * resultState, std::vector &rows, int rowIndex, bool movedLeft, bool jackedLeft, bool movedRight, bool jackedRight, int columnCount); bool didJackLeft(State * initialState, State * resultState, int leftHeel, int leftToe, bool movedLeft, bool didJump, int columnCount); bool didJackRight(State * initialState, State * resultState, int rightHeel, int rightToe, bool movedRight, bool didJump,int columnCount); - - float getDistanceSq(StepParity::StagePoint p1, StepParity::StagePoint p2); - float getPlayerAngle(StepParity::StagePoint left, StepParity::StagePoint right); - - float getXDifference(int leftIndex, int rightIndex); - float getYDifference(int leftIndex, int rightIndex); - StagePoint averagePoint(int leftIndex, int rightIndex); - }; }; diff --git a/src/StepParityDatastructs.cpp b/src/StepParityDatastructs.cpp index c737982999..976b90c917 100644 --- a/src/StepParityDatastructs.cpp +++ b/src/StepParityDatastructs.cpp @@ -74,6 +74,100 @@ StepParityNode * StepParityGraph::addOrGetExistingNode(const State &state) return stateNodeMap[rowIndex][state]; } +// +// StageLayout +// + +bool StageLayout::bracketCheck(int column1, int column2) +{ + StagePoint p1 = columns[column1]; + StagePoint p2 = columns[column2]; + return getDistanceSq(p1, p2) <= 2; +} + +bool StageLayout::isSideArrow(int column) +{ + return std::find(sideArrows.begin(), sideArrows.end(), column) != sideArrows.end(); +} + +bool StageLayout::isUpArrow(int column) +{ + return std::find(upArrows.begin(), upArrows.end(), column) != upArrows.end(); +} + +bool StageLayout::isDownArrow(int column) +{ + return std::find(downArrows.begin(), downArrows.end(), column) != downArrows.end(); +} + +float StageLayout::getDistanceSq(int c1, int c2) +{ + return getDistanceSq(columns[c1], columns[c2]); +} + +float StageLayout::getDistanceSq(StepParity::StagePoint p1, StepParity::StagePoint p2) +{ + return (p1.y - p2.y) * (p1.y - p2.y) + (p1.x - p2.x) * (p1.x - p2.x); +} + +float StageLayout::getXDifference(int leftIndex, int rightIndex) { + if (leftIndex == rightIndex) return 0; + float dx = columns[rightIndex].x - columns[leftIndex].x; + float dy = columns[rightIndex].y - columns[leftIndex].y; + + float distance = sqrt(dx * dx + dy * dy); + dx /= distance; + + bool negative = dx <= 0; + + dx = pow(dx, 4); + + if (negative) dx = -dx; + + return dx; +} + +float StageLayout::getYDifference(int leftIndex, int rightIndex) { + if (leftIndex == rightIndex) return 0; + float dx = columns[rightIndex].x - columns[leftIndex].x; + float dy = columns[rightIndex].y - columns[leftIndex].y; + + float distance = sqrt(dx * dx + dy * dy); + dy /= distance; + + bool negative = dy <= 0; + + dy = pow(dy, 4); + + if (negative) dy = -dy; + + return dy; +} + +StagePoint StageLayout::averagePoint(int leftIndex, int rightIndex) { + if (leftIndex == -1 && rightIndex == -1) return { 0,0 }; + if (leftIndex == -1) return columns[rightIndex]; + if (rightIndex == -1) return columns[leftIndex]; + return { + (columns[leftIndex].x + columns[rightIndex].x) / 2.0f, + (columns[leftIndex].y + columns[rightIndex].y) / 2.0f, + }; +} + +float StageLayout::getPlayerAngle(int c1, int c2) +{ + return getPlayerAngle(columns[c1], columns[c2]); +} +float StageLayout::getPlayerAngle(StepParity::StagePoint left, StepParity::StagePoint right) +{ + float x1 = right.x - left.x; + float y1 = right.y - left.y; + float x2 = 1; + float y2 = 0; + float dot = x1 * x2 + y1 * y2; + float det = x1 * y2 - y1 * x2; + return atan2f(det, dot); +} // // Json methods diff --git a/src/StepParityDatastructs.h b/src/StepParityDatastructs.h index dc1dab9b6a..8442b37f61 100644 --- a/src/StepParityDatastructs.h +++ b/src/StepParityDatastructs.h @@ -74,16 +74,43 @@ namespace StepParity { "TOTAL" }; - - struct StagePoint { float x; float y; }; - - /// @brief A vector of StagePoints, which represents the - /// relative position of each arrow on the dance stage. - typedef std::vector StageLayout; + + // StageLayout represents the relative position of each panel on the dance stage, + // and provides some basic math function + struct StageLayout { + StepsType type; + int columnCount; + std::vector columns; + std::vector upArrows; + std::vector downArrows; + std::vector sideArrows; + + + StageLayout(StepsType t, + const std::vector& c, + const std::vector & u, + const std::vector & d, + const std::vector & s) :type(t), columns(c), upArrows(u), downArrows(d), sideArrows(s) { + this->columnCount = static_cast(this->columns.size()); + } + + + bool bracketCheck(int column1, int column2); + bool isSideArrow(int column); + bool isUpArrow(int column); + bool isDownArrow(int column); + float getDistanceSq(int c1, int c2); + float getDistanceSq(StagePoint p1, StagePoint p2); + float getXDifference(int leftIndex, int rightIndex); + float getYDifference(int leftIndex, int rightIndex); + StagePoint averagePoint(int leftIndex, int rightIndex); + float getPlayerAngle(int c1, int c2); + float getPlayerAngle(StepParity::StagePoint left, StepParity::StagePoint right); + }; /// @brief A vector of Foot values, which represents the player's /// foot placement on the dance stage. diff --git a/src/StepParityGenerator.cpp b/src/StepParityGenerator.cpp index cdd9be58e1..1e7604ec09 100644 --- a/src/StepParityGenerator.cpp +++ b/src/StepParityGenerator.cpp @@ -5,40 +5,13 @@ #include "TechCounts.h" #include "GameState.h" -// Generates foot parity given notedata -// Original algorithm by Jewel, polished by tillvit, then ported to C++ using namespace StepParity; -const std::map Layouts = { - {StepsType_dance_single, { - {0, 1}, // Left - {1, 0}, // Down - {1, 2}, // Up - {2, 1} // Right - }}, - {StepsType_dance_double, { - {0, 1}, // P1 Left - {1, 0}, // P1 Down - {1, 2}, // P1 Up - {2, 1}, // P1 Right - - {3, 1}, // P2 Left - {4, 0}, // P2 Down - {4, 2}, // P2 Up - {5, 1} // P2 Right - }} - }; -void StepParityGenerator::analyzeNoteData(const NoteData &in, StepsType stepsType) + +void StepParityGenerator::analyzeNoteData(const NoteData &in) { - if(Layouts.find(stepsType) == Layouts.end()) - { - LOG->Warn("Tried to call StepParityGenerator::analyze with an unsupported StepsType %s", StepsTypeToString(stepsType).c_str()); - return; - } - - layout = Layouts.at(stepsType); columnCount = in.GetNumTracks(); CreateRows(in); @@ -224,12 +197,12 @@ std::vector StepParityGenerator::PermuteFootPlacements(const Row } if (leftHeelIndex != -1 && leftToeIndex != -1) { - if (!bracketCheck(leftHeelIndex, leftToeIndex)) + if (!layout.bracketCheck(leftHeelIndex, leftToeIndex)) return std::vector(); } if (rightHeelIndex != -1 && rightToeIndex != -1) { - if (!bracketCheck(rightHeelIndex, rightToeIndex)) + if (!layout.bracketCheck(rightHeelIndex, rightToeIndex)) return std::vector(); } return {columns}; @@ -518,15 +491,3 @@ Json::Value StepParityGenerator::SMEditorParityJson() return root; } - -bool StepParityGenerator::bracketCheck(int column1, int column2) -{ - StagePoint p1 = layout[column1]; - StagePoint p2 = layout[column2]; - return getDistanceSq(p1, p2) <= 2; -} - -float StepParityGenerator::getDistanceSq(StepParity::StagePoint p1, StepParity::StagePoint p2) -{ - return (p1.y - p2.y) * (p1.y - p2.y) + (p1.x - p2.x) * (p1.x - p2.x); -} diff --git a/src/StepParityGenerator.h b/src/StepParityGenerator.h index 97fdbe4e11..2d4431701f 100644 --- a/src/StepParityGenerator.h +++ b/src/StepParityGenerator.h @@ -10,6 +10,26 @@ namespace StepParity { + const std::map Layouts = { + {StepsType_dance_single, StageLayout(StepsType_dance_single, { + {0, 1}, // Left + {1, 0}, // Down + {1, 2}, // Up + {2, 1} // Right + }, {2}, {1}, {0,3})}, + {StepsType_dance_double, StageLayout(StepsType_dance_double, { + {0, 1}, // P1 Left + {1, 0}, // P1 Down + {1, 2}, // P1 Up + {2, 1}, // P1 Right + + {3, 1}, // P2 Left + {4, 0}, // P2 Down + {4, 2}, // P2 Up + {5, 1} // P2 Right + }, {26}, {1,5}, {0,3,4,7})} + }; + /// @brief This class handles most of the work for generating step parities for a step chart. class StepParityGenerator { @@ -23,11 +43,12 @@ namespace StepParity { std::vector nodes_for_rows; int columnCount; + StepParityGenerator(const StageLayout & l) : layout(l) { + } /// @brief Analyzes the given NoteData to generate a vector of StepParity::Rows, with each step annotated with /// a foot placement. /// @param in The NoteData to analyze - /// @param stepsTypeStr StepsType, currently only supports "dance-single" - void analyzeNoteData(const NoteData &in, StepsType stepsType); + void analyzeNoteData(const NoteData &in); /// @brief Analyzes the given graph to find the least costly path from the beginnning to the end of the stepchart. /// Sets the `parity` for the relevant notes of each row in rows. diff --git a/src/Steps.cpp b/src/Steps.cpp index 9169c2ec49..dbad6868ae 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -389,16 +389,17 @@ void Steps::CalculateTechCounts() m_CachedTechCounts[pn] .Zero(); - // For now, we're only supporting dance-single and dance-double - if(this->m_StepsType != StepsType_dance_single && this->m_StepsType != StepsType_dance_double) + + // If we don't have a valid layout for this StepsType, then don't even bother + if(StepParity::Layouts.find(this->m_StepsType) == StepParity::Layouts.end()) { return; } - + StepParity::StageLayout layout = StepParity::Layouts.at(this->m_StepsType); GAMESTATE->SetProcessedTimingData(this->GetTimingData()); - StepParity::StepParityGenerator gen; - gen.analyzeNoteData(tempNoteData, this->m_StepsType); - TechCounts::CalculateTechCountsFromRows(gen.rows, m_CachedTechCounts[0]); + StepParity::StepParityGenerator gen = StepParity::StepParityGenerator(layout); + gen.analyzeNoteData(tempNoteData); + TechCounts::CalculateTechCountsFromRows(gen.rows, layout, m_CachedTechCounts[0]); std::fill_n( m_CachedTechCounts + 1, NUM_PLAYERS-1, m_CachedTechCounts[0] ); GAMESTATE->SetProcessedTimingData(nullptr); diff --git a/src/TechCounts.cpp b/src/TechCounts.cpp index 4e3d528c3c..b386bf0d64 100644 --- a/src/TechCounts.cpp +++ b/src/TechCounts.cpp @@ -11,7 +11,9 @@ static const char *TechCountsCategoryNames[] = { "Crossovers", - "Footswitches", + "Total Footswitches", + "Up Footswitches", + "Down Footswitches", "Sideswitches", "Jacks", "Brackets", @@ -89,7 +91,7 @@ void TechCounts::FromString( RString sTechCounts ) } -void TechCounts::CalculateTechCountsFromRows(const std::vector &rows, TechCounts &out) +void TechCounts::CalculateTechCountsFromRows(const std::vector &rows, StepParity::StageLayout & layout, TechCounts &out) { // arrays to hold the column for each Foot enum. // A value of -1 means that Foot is not on any column @@ -200,13 +202,21 @@ void TechCounts::CalculateTechCountsFromRows(const std::vector ) { // this is assuming only 4-panel single - if(c == 0 || c == 3) + if(layout.isSideArrow(c)) { out[TechCountsCategory_Sideswitches] += 1; } else { - out[TechCountsCategory_Footswitches] += 1; + out[TechCountsCategory_TotalFootswitches] += 1; + if(layout.isUpArrow(c)) + { + out[TechCountsCategory_UpFootswitches] += 1; + } + else if(layout.isDownArrow(c)) + { + out[TechCountsCategory_DownFootswitches] += 1; + } } } // if the right foot is pressing the left arrow, or the left foot is pressing the right ==> crossover diff --git a/src/TechCounts.h b/src/TechCounts.h index 93d4067718..52e25d1520 100644 --- a/src/TechCounts.h +++ b/src/TechCounts.h @@ -12,7 +12,9 @@ class NoteData; enum TechCountsCategory { TechCountsCategory_Crossovers = 0, - TechCountsCategory_Footswitches, + TechCountsCategory_TotalFootswitches, + TechCountsCategory_UpFootswitches, + TechCountsCategory_DownFootswitches, TechCountsCategory_Sideswitches, TechCountsCategory_Jacks, TechCountsCategory_Brackets, @@ -77,7 +79,7 @@ public: void FromString( RString sValues ); void PushSelf( lua_State *L ); - static void CalculateTechCountsFromRows(const std::vector &rows, TechCounts &out); + static void CalculateTechCountsFromRows(const std::vector &rows, StepParity::StageLayout & layout, TechCounts &out); }; #endif