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
This commit is contained in:
Michael Votaw
2025-02-11 19:39:03 -08:00
committed by teejusb
parent 36c4223a38
commit 212b99a4c5
9 changed files with 203 additions and 192 deletions
+23 -118
View File
@@ -7,30 +7,6 @@
using namespace StepParity;
template <typename T>
bool vectorIncludes(const std::vector<T>& vec, const T& value, int columnCount) {
for (int i = 0; i < columnCount; i++)
{
if(vec[i] == value)
{
return true;
}
}
return false;
}
template <typename T>
int indexOf(const std::vector<T>& vec, const T& value, int columnCount) {
for (int i = 0; i < columnCount; i++)
{
if(vec[i] == value)
{
return i;
}
}
return -1;
}
template <typename T>
bool isEmpty(const std::vector<T> & 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,
};
}
+1 -11
View File
@@ -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<Row> &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);
};
};
+94
View File
@@ -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
+33 -6
View File
@@ -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<StagePoint> 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<StagePoint> columns;
std::vector<int> upArrows;
std::vector<int> downArrows;
std::vector<int> sideArrows;
StageLayout(StepsType t,
const std::vector<StagePoint>& c,
const std::vector<int> & u,
const std::vector<int> & d,
const std::vector<int> & s) :type(t), columns(c), upArrows(u), downArrows(d), sideArrows(s) {
this->columnCount = static_cast<int>(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.
+4 -43
View File
@@ -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<StepsType, StageLayout> 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<FootPlacement> StepParityGenerator::PermuteFootPlacements(const Row
}
if (leftHeelIndex != -1 && leftToeIndex != -1)
{
if (!bracketCheck(leftHeelIndex, leftToeIndex))
if (!layout.bracketCheck(leftHeelIndex, leftToeIndex))
return std::vector<FootPlacement>();
}
if (rightHeelIndex != -1 && rightToeIndex != -1)
{
if (!bracketCheck(rightHeelIndex, rightToeIndex))
if (!layout.bracketCheck(rightHeelIndex, rightToeIndex))
return std::vector<FootPlacement>();
}
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);
}
+23 -2
View File
@@ -10,6 +10,26 @@
namespace StepParity {
const std::map<StepsType, StageLayout> 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<int> 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.
+7 -6
View File
@@ -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);
+14 -4
View File
@@ -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<StepParity::Row> &rows, TechCounts &out)
void TechCounts::CalculateTechCountsFromRows(const std::vector<StepParity::Row> &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<StepParity::Row>
)
{
// 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
+4 -2
View File
@@ -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<StepParity::Row> &rows, TechCounts &out);
static void CalculateTechCountsFromRows(const std::vector<StepParity::Row> &rows, StepParity::StageLayout & layout, TechCounts &out);
};
#endif