From 48f83eee38796b32b066c9a80497a23935155adc Mon Sep 17 00:00:00 2001 From: Quinn Date: Thu, 22 Aug 2024 18:17:35 -0400 Subject: [PATCH 01/12] Refactoring how board control works --- lib/Board/Board.h | 109 ++++++++++++++++++++++++++++++++ lib/Board/Color.h | 1 + lib/Board/CubeStack.cpp | 32 +++------- lib/Board/CubeStack.h | 27 +------- lib/BoardReader/BoardDriver.h | 115 ++++++++++++++++++++++++++++++++++ lib/Cube/Cube.cpp | 22 +++++++ lib/Cube/Cube.h | 21 +++++++ 7 files changed, 278 insertions(+), 49 deletions(-) create mode 100644 lib/Board/Board.h create mode 100644 lib/BoardReader/BoardDriver.h create mode 100644 lib/Cube/Cube.cpp create mode 100644 lib/Cube/Cube.h diff --git a/lib/Board/Board.h b/lib/Board/Board.h new file mode 100644 index 0000000..8ad0a65 --- /dev/null +++ b/lib/Board/Board.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +#include "Cube.h" +#include "Color.h" + +template +class Board{ + public: + Board(); + ~Board() = default; + + constexpr uint32_t GetXSize() const{return X_SIZE;} + constexpr uint32_t GetYSize() const{return Y_SIZE;} + constexpr uint32_t GetZSize() const {return Z_SIZE;} + constexpr uint32_t GetNumberCubes() const{return X_SIZE * Y_SIZE * Z_SIZE;} + + /** + * @brief Returns a string in the format: + * !a,b,c,d,e,f,g,h,i; + * Where each letter is the number of cubes in a given position's stack + * @param stringBuffer the buffer to write the string into + */ + void ToStackString(String& stringBuffer) const; + + /** + * @brief Returns a bool array representing the board + */ + std::array &LinearizeBoard() const; + + void Update(); + + void FillColor(const Color &color); + + void SetCubeColor(uint32_t x_coord, uint32_t y_cord, uint32_t z_cord, const Color &color); + + private: + // this is a 3d array of cubes to represent the board. Good luck visualizing it + /* _____________ + / /| + / / | + / / | + /____________/ | + | | | + | | / + | | / + |____________|/ + */ + std::array, Y_SIZE>, X_SIZE> cubes; + + bool boardStateHasChanged; +}; + +template +Board::Board(){ + this->FillColor(Color(0,0,0)); +} + +template +void Board::ToStackString(String &stringBuffer) const{ + std::array linearizedBoard = this->LinearizeBoard(); + + stringBuffer += "!" + String(linearizedBoard[0]); + + for(uint32_t i = 0; i < X_SIZE * Y_SIZE; i++){ + stringBuffer += "," + String(linearizedBoard[i]); + } + + stringBuffer += ";"; +} + +template +std::array & Board::LinearizeBoard() const{ + // convert the board into one array where each entry represents the height of one stack + std::array linearizedBoard; + for(uint32_t x{0}; x < X_SIZE; x++){ + for(uint32_t y{0}; y < Y_SIZE; y++){ + for(uint32_t z{0}; z < Z_SIZE; z++){ + bool isOccupied{this->cubes[x][y][z].isOccupied}; + linearizedBoard[x + y*3] += static_cast(isOccupied); + } + } + } + return linearizedBoard; +} + +template +void Board::Update(){ + +} + +template +void Board::FillColor(const Color &color){ + for(uint32_t x{0}; x < X_SIZE; x++){ + for(uint32_t y{0}; y < Y_SIZE; y++){ + for(uint32_t z{0}; z < Z_SIZE; z++){ + this->cubes[x][y][z].SetColor(color); + } + } + } +} + +template +void Board::SetCubeColor(uint32_t x_coord, uint32_t y_cord, uint32_t z_cord, const Color &color){ + this->cubes[x][y][z].SetColor(color); +} + diff --git a/lib/Board/Color.h b/lib/Board/Color.h index 015bbfb..434b105 100644 --- a/lib/Board/Color.h +++ b/lib/Board/Color.h @@ -8,6 +8,7 @@ // store a color struct Color{ + public: // create a constructor for this struct Color(uint8_t red, uint8_t green, uint8_t blue) : red(red), diff --git a/lib/Board/CubeStack.cpp b/lib/Board/CubeStack.cpp index e8954c1..7af12ab 100644 --- a/lib/Board/CubeStack.cpp +++ b/lib/Board/CubeStack.cpp @@ -12,7 +12,7 @@ CubeStack::CubeStack(uint16_t ADCPin, uint16_t ledPin, uint8_t numLEDs){ } }; -uint8_t CubeStack::GetNumberCubes(){ +uint32_t CubeStack::GetNumberCubes(Cube &cube){ // read the ADC and return the number of cubes /* 0 cubes: 1 : 4095-3400 @@ -20,11 +20,8 @@ uint8_t CubeStack::GetNumberCubes(){ 2 cubes: 1/3 2500-1850 3 cubes: 1/4 1850-0 */ - uint16_t value = analogRead(this->ADCPin); - this->lowPassADCRead = static_cast((static_cast(this->lowPassADCRead) * 0.9) + (static_cast(value) * 0.1)); - if(this->lowPassADCRead < 2500 && false){ - Serial.println("ADC Pin:" + String(this->ADCPin) + " Value: " + String(value) + " Low Pass: " + String(this->lowPassADCRead)); - } + uint16_t value = analogRead(cube.ADCPin); + uint16_t lowPassADCRead = static_cast((static_cast(cube.lastADCReading) * 0.9) + (static_cast(value) * 0.1)); // temporary definitions to define value ranges: uint16_t zeroCubesHigh = 4095; @@ -35,37 +32,22 @@ uint8_t CubeStack::GetNumberCubes(){ uint8_t stackHeight = 0; - if(this->lowPassADCRead >= zeroCubesLow && this->lowPassADCRead <= zeroCubesHigh){ + if(lowPassADCRead >= zeroCubesLow && lowPassADCRead <= zeroCubesHigh){ stackHeight = 0; } - else if(this->lowPassADCRead >= oneCubeLow){ + else if(lowPassADCRead >= oneCubeLow){ stackHeight = 1; } - else if(this->lowPassADCRead >= twoCubesLow){ + else if(lowPassADCRead >= twoCubesLow){ stackHeight = 2; } - else if(this->lowPassADCRead >= threeCubesLow){ + else if(lowPassADCRead >= threeCubesLow){ stackHeight = 3; } - if(this->lastStackHeight != stackHeight){ - this->lastStackHeight = stackHeight; - this->SendLEDData(); - } return stackHeight; } -void CubeStack::SetLEDColors(Color * colors, uint8_t numColors){ - // copy the colors into the ledColors array - for(int i = 0; i < numColors; i++){ - this->ledColors[i].red = colors[i].red; - this->ledColors[i].green = colors[i].green; - this->ledColors[i].blue = colors[i].blue; - } - - this->SendLEDData(); -} - void CubeStack::SendLEDData(){ // we always initialize before we do anything because other CubeStacks could be hogging the hardware // between our writes diff --git a/lib/Board/CubeStack.h b/lib/Board/CubeStack.h index 634ef50..8a343e4 100644 --- a/lib/Board/CubeStack.h +++ b/lib/Board/CubeStack.h @@ -7,10 +7,10 @@ #include #include #include "Color.h" +#include "Cube.h" -class CubeStack{ - public: +namespace CubeStack{ /** * @brief Construct a new Cube Stack object * @param ADCPin the pin that the ADC is connected to @@ -22,31 +22,10 @@ class CubeStack{ * @brief Returns the number of cubes in the stack * @return the number of cubes in the stack */ - uint8_t GetNumberCubes(); - - /** - * @brief Set the led color array and then send the data to the LED strip - * @param colors the array of colors to set the LEDs to - * @param numColors the number of colors in the array - */ - void SetLEDColors(Color * colors, uint8_t numColors); + uint32_t GetNumberCubes(Cube &cube); /** * @brief sends the LED data to the LED strip */ void SendLEDData(); - - private: - - - uint8_t ADCPin; - // we will probably need a pointer to a fastled object here - Adafruit_NeoPixel blockLights; - - uint16_t lowPassADCRead{0}; - - // store the Color of each LED - Color * ledColors; - uint8_t numLEDs; - uint8_t lastStackHeight{0}; }; \ No newline at end of file diff --git a/lib/BoardReader/BoardDriver.h b/lib/BoardReader/BoardDriver.h new file mode 100644 index 0000000..5ff3d09 --- /dev/null +++ b/lib/BoardReader/BoardDriver.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include + +#include + +#include "Cube.h" + +namespace BoardDriverTypes{ + struct CubeStack{ + uint8_t adcPin; + uint8_t ledPin; + } +}; + +template +class BoardDriver{ +public: + + BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController); + ~BoardDriver() = default; + + uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD); + uint32_t GetNumberCubes(uint32_t stackIndex); + + void UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube cubes[], uint32_t numCubes); + void UpdateStackLEDs(uint32_t stackIndex, Cube cubes[], uint32_t numCubes); + + +private: + std::array &stacks; + Adafruit_NeoPixel &pixelController; + std::array filteredReadings; + + uint32_t xy2StackIndex(uint32_t x_coord, uint32_t y_coord, uint32_t numXStacks){ + return X_COORD + Y_COORD*numXStacks; + } +}; + +template +BoardDriver::BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController): +stacks(stacks), +pixelController(pixelController) +{ + for(uint32_t i = 0; i < NUM_STACKS; i++){ + this->filteredReadings[i] = 0; + pinMode(this->stacks[i].ledPin, OUTPUT); + } + + // begin doesn't really do anything besides setting the pinmode + this->pixelController.begin(); +} + +template +void BoardDriver::UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube cubes[], uint32_t numCubes){ + this->UpdateStackLEDs(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks), cubes, numCubes); +} + +template +void BoardDriver::UpdateStackLEDs(uint32_t stackIndex, Cube cubes[], uint32_t numCubes){ + this->pixelController.setPin(this->stacks[stackIndex].ledPin); + for(int i = 0; i < numCubes; i++){ + Color color{cubes[i].color}; + this->pixelController.setPixelColor(i*2, this->pixelController.Color(color.red, color.green, color.blue)); + this->pixelController.setPixelColor((i*2 + 1), this->pixelController.Color(color.red, color.green, color.blue)); + } + this->pixelController.show(); +} + +template +uint32_t BoardDriver::GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD){ + return this->GetNumberCubes(this->xy2StackIndex(x_coord, y_coord, numXStacks)); +} + +template +uint32_t BoardDriver::GetNumberCubes(uint32_t stackIndex){ + // read the ADC and return the number of cubes + /* + 0 cubes: 1 : 4095-3400 + 1 cube: 1/2 3400-2500 + 2 cubes: 1/3 2500-1850 + 3 cubes: 1/4 1850-0 + */ + uint16_t value = analogRead(cube.ADCPin); + uint16_t lowPassADCRead = + static_cast( + (static_cast(this->filteredReadings[stackIndex]) * 0.9) + + (static_cast(value) * 0.1) + ); + + // temporary definitions to define value ranges: + uint16_t zeroCubesHigh = 4095; + uint16_t zeroCubesLow = 3400; + uint16_t oneCubeLow = 2500; + uint16_t twoCubesLow = 1850; + uint16_t threeCubesLow = 0; + + uint8_t stackHeight = 0; + + if(lowPassADCRead >= zeroCubesLow && lowPassADCRead <= zeroCubesHigh){ + stackHeight = 0; + } + else if(lowPassADCRead >= oneCubeLow){ + stackHeight = 1; + } + else if(lowPassADCRead >= twoCubesLow){ + stackHeight = 2; + } + else if(lowPassADCRead >= threeCubesLow){ + stackHeight = 3; + } + + return stackHeight; +} \ No newline at end of file diff --git a/lib/Cube/Cube.cpp b/lib/Cube/Cube.cpp new file mode 100644 index 0000000..7a271ba --- /dev/null +++ b/lib/Cube/Cube.cpp @@ -0,0 +1,22 @@ +#include "Cube.h" + +Cube::Cube(uint8_t ADCPin, uint8_t ledPin) + : ADCPin(ADCPin), + ledPin(ledPin) +{ + Color black(0,0,0); + this->SetColor(black); +} + +Cube::Cube(uint8_t ADCPin, uint8_t ledPin, const Color &color) + : ADCPin(ADCPin), + ledPin(ledPin) +{ + this->SetColor(color); +} + +void Cube::SetColor(const Color &color){ + this->color.red = color.red; + this->color.green = color.green; + this->color.blue = color.blue; +} \ No newline at end of file diff --git a/lib/Cube/Cube.h b/lib/Cube/Cube.h new file mode 100644 index 0000000..516ac77 --- /dev/null +++ b/lib/Cube/Cube.h @@ -0,0 +1,21 @@ +/** + * @file Cube.h + * @brief An object to store the data of one cube + */ + +#pragma once + +#include "Color.h" + +class Cube{ + public: + Cube(); + Cube(); + + void SetColor(const Color &color); + + uint16_t lastADCReading{0}; + + Color color; + bool isOccupied{false}; +}; \ No newline at end of file From 3e4f0124db3f65cabe68acbffb4dd1789235a767 Mon Sep 17 00:00:00 2001 From: Quinn Date: Thu, 22 Aug 2024 21:07:32 -0400 Subject: [PATCH 02/12] Completed the board manager rewrite --- include/BOARD-DEFINITIONS.h | 66 +++- include/Vector3D.h | 54 +++ lib/Animator/Animator.h | 353 ++++++++++++++++++ lib/Board/Board.h | 112 ++++-- lib/Board/BoardLayout.cpp | 38 -- lib/Board/BoardLayout.h | 69 ---- lib/Board/Color.h | 22 -- lib/Board/CubeStack.cpp | 61 --- lib/Board/CubeStack.h | 31 -- .../BoardDriver.h | 25 +- lib/BoardDriver/BoardDriverTypes.h | 10 + lib/BoardManager/BoardManager.h | 102 +++++ lib/ColorManager/ColorManager.cpp | 48 --- lib/ColorManager/ColorManager.h | 44 --- lib/Cube/Cube.cpp | 22 -- lib/Cube/Cube.h | 11 +- src/main.cpp | 6 - 17 files changed, 666 insertions(+), 408 deletions(-) create mode 100644 include/Vector3D.h create mode 100644 lib/Animator/Animator.h delete mode 100644 lib/Board/BoardLayout.cpp delete mode 100644 lib/Board/BoardLayout.h delete mode 100644 lib/Board/Color.h delete mode 100644 lib/Board/CubeStack.cpp delete mode 100644 lib/Board/CubeStack.h rename lib/{BoardReader => BoardDriver}/BoardDriver.h (95%) create mode 100644 lib/BoardDriver/BoardDriverTypes.h create mode 100644 lib/BoardManager/BoardManager.h delete mode 100644 lib/ColorManager/ColorManager.cpp delete mode 100644 lib/ColorManager/ColorManager.h delete mode 100644 lib/Cube/Cube.cpp diff --git a/include/BOARD-DEFINITIONS.h b/include/BOARD-DEFINITIONS.h index 0c80af9..d112779 100644 --- a/include/BOARD-DEFINITIONS.h +++ b/include/BOARD-DEFINITIONS.h @@ -4,32 +4,60 @@ */ #pragma once +#include +#include #include "PINOUT.h" -#include "CubeStack.h" +#include "BoardDriverTypes.h" // define the physical dimensions of the board -#define BOARD_WIDTH 3 -#define BOARD_LENGTH 3 -#define BOARD_HEIGHT 3 +static constexpr uint32_t BOARD_WIDTH{3}; +static constexpr uint32_t BOARD_LENGTH{3}; +static constexpr uint32_t BOARD_HEIGHT{3}; // define the number of stacks -#define NUMBER_STACKS BOARD_WIDTH * BOARD_LENGTH +static constexpr uint32_t NUMBER_STACKS{BOARD_WIDTH * BOARD_LENGTH}; // define the CubeStacks -CubeStack stack1(STACK1_ADC_PIN, STACK1_LED_PIN, BOARD_HEIGHT); -CubeStack stack2(STACK2_ADC_PIN, STACK2_LED_PIN, BOARD_HEIGHT); -CubeStack stack3(STACK3_ADC_PIN, STACK3_LED_PIN, BOARD_HEIGHT); -CubeStack stack4(STACK4_ADC_PIN, STACK4_LED_PIN, BOARD_HEIGHT); -CubeStack stack5(STACK5_ADC_PIN, STACK5_LED_PIN, BOARD_HEIGHT); -CubeStack stack6(STACK6_ADC_PIN, STACK6_LED_PIN, BOARD_HEIGHT); -CubeStack stack7(STACK7_ADC_PIN, STACK7_LED_PIN, BOARD_HEIGHT); -CubeStack stack8(STACK8_ADC_PIN, STACK8_LED_PIN, BOARD_HEIGHT); -CubeStack stack9(STACK9_ADC_PIN, STACK9_LED_PIN, BOARD_HEIGHT); +static BoardDriverTypes::CubeStack stack1{ + .adcPin=STACK1_ADC_PIN, + .ledPin=STACK1_LED_PIN +}; +static BoardDriverTypes::CubeStack stack2{ + .adcPin=STACK2_ADC_PIN, + .ledPin=STACK2_LED_PIN +}; +static BoardDriverTypes::CubeStack stack3{ + .adcPin=STACK3_ADC_PIN, + .ledPin=STACK3_LED_PIN +}; +static BoardDriverTypes::CubeStack stack4{ + .adcPin=STACK4_ADC_PIN, + .ledPin=STACK4_LED_PIN +}; +static BoardDriverTypes::CubeStack stack5{ + .adcPin=STACK5_ADC_PIN, + .ledPin=STACK5_LED_PIN +}; +static BoardDriverTypes::CubeStack stack6{ + .adcPin=STACK6_ADC_PIN, + .ledPin=STACK6_LED_PIN +}; +static BoardDriverTypes::CubeStack stack7{ + .adcPin=STACK7_ADC_PIN, + .ledPin=STACK7_LED_PIN +}; +static BoardDriverTypes::CubeStack stack8{ + .adcPin=STACK8_ADC_PIN, + .ledPin=STACK8_LED_PIN +}; +static BoardDriverTypes::CubeStack stack9{ + .adcPin=STACK9_ADC_PIN, + .ledPin=STACK9_LED_PIN +}; -// define the array of stacks -CubeStack * stacks[] = { - &stack1, &stack2, &stack3, - &stack4, &stack5, &stack6, - &stack7, &stack8, &stack9 +static std::array stacks{ + stack1, stack2, stack3, + stack4, stack5, stack6, + stack7, stack8, stack9 }; \ No newline at end of file diff --git a/include/Vector3D.h b/include/Vector3D.h new file mode 100644 index 0000000..1858f3b --- /dev/null +++ b/include/Vector3D.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +class V3D{ + public: + V3D(uint32_t x=0, uint32_t y=0, uint32_t z=0): + x(x), + y(y), + z(z){} + + V3D& operator=(const V3D &other){ + this->x = other.x; + this->y = other.y; + this->z = other.z; + return *this; + } + + V3D& operator+(const V3D &other){ + V3D vector{}; + vector.x = this->x + other.x; + vector.y = this->y + other.y; + vector.z = this->z + other.z; + return vector; + } + + V3D& operator-(const V3D &other){ + V3D vector{}; + vector.x = this->x - other.x; + vector.y = this->y - other.y; + vector.z = this->z - other.z; + return vector; + } + + V3D operator/(const uint32_t scalar){ + V3D vector{}; + vector.x = this->x / scalar; + vector.y = this->y / scalar; + vector.z = this->z / scalar; + return vector; + } + + bool operator==(const V3D &other){ + return this->x == other.x && this->y == other.y && this->z == other.z; + } + + float magnitude(){ + return std::sqrt(this->x * this->x + this->y * this->y + this->z * this-> z); + } + uint32_t x; + uint32_t y; + uint32_t z; +}; \ No newline at end of file diff --git a/lib/Animator/Animator.h b/lib/Animator/Animator.h new file mode 100644 index 0000000..37f384f --- /dev/null +++ b/lib/Animator/Animator.h @@ -0,0 +1,353 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Vector3D.h" + + +class Animation{ +public: +// for cube spots which aren't defined in a key frame, +// you can have the controller automatically interpolate a color +enum FillInterpolation{ + NO_FILL, // if not specified, the cube color will be black + CLOSEST_COLOR, // The cube color will be the same color as the cube closest to it + LINEAR_WEIGHTED_DISTANCE, // the cube color will be an average of all specified cube colors weighted by the linear distance to this cube + SQUARE_WEIGHTED_DISTANCE // same as linear, but further colors have exponentially less impact on the color +}; + +enum FrameInterpolation{ + SNAP, // After the delay, snap to the next key frame + FADE // over the course of the delay, fade to the next frame +}; + +struct Cell{ + V3D position; + V3D color; +}; + +// this contains all of the information to specify exactly how a single frame should look and fade to the next frame +struct AnimationFrame{ + std::vector frame; + FillInterpolation fillInterpolation; + FrameInterpolation frameInterpolation; + std::chrono::milliseconds delay; +}; + +typedef std::array, Y_SIZE>, X_SIZE> Frame; + + +void StartAnimation(const std::vector &animationSequence){ + this->animationSequence = animationSequence; + this->animationIndex = 0; + this->timeElapsed = std::chrono::milliseconds(0); + + if(animationSequence.size() == 0){ + return; + } + else if(animationSequence.size() == 1){ + this->uncompressFrame(animationSequence[0], this->startFrame); + this->uncompressFrame(animationSequence[0], this->endFrame); + } + else{ + this->uncompressFrame(animationSequence[0], this->startFrame); + this->uncompressFrame(animationSequence[1], this->endFrame); + } +} + +Frame &RunAnimation(std::chrono::milliseconds timePassed){ + auto delayTime = this->animationSequence[this->animationIndex].delay; + this->timeElapsed += timePassed; + Frame interpolatedFrame; + + // load in the next frame if we're done with this transition + if(this->timeElapsed >= delayTime){ + this->incrimentAnimationIndex(); + } + + // don't do frame interpolations if we're doing snap fades + if(this->animationSequence[this->animationIndex].frameInterpolation == FrameInterpolation::SNAP){ + return; + } + + // linearly interpolate between the two uncompressed frames + for(uint32_t x = 0; x < this->X_SIZE; x++){ + for(uint32_t y = 0; y < this->Y_SIZE; y++){ + for(uint32_t z = 0; z < this->Z_SIZE; z++){ + V3D startColor{this->startFrame[x][y][z]}; + V3D endColor{this->endFrame[x][y][z]}; + V3D difference{endColor - startColor}; + V3D interpolatedColor = this->timeElapsed.count() * difference / delayTime.count() + startColor; + interpolatedFrame[x][y][z] = interpolatedColor; + } + } + } +} + +void SetLoop(bool isLooping){ + this->isLooping = isLooping; +} + +private: +uint32_t X_SIZE{3}; +uint32_t Y_SIZE{3}; +uint32_t Z_SIZE{3}; +bool isLooping{true}; +// these are the uncompressed frames you get by following the key colors and interpolation instructions of an animation frame +Frame startFrame; +Frame endFrame; +std::chrono::milliseconds timeElapsed; + +const std::vector & animationSequence; +uint32_t animationIndex{0}; + +void incrimentAnimationIndex(){ + if(this->animationIndex < animationSequence.size() - 1){ + this->animationIndex++; + this->timeElapsed = std::chrono::millis(0); + this->uncompressFrame(this->animationSequence[this->animationIndex], this->startFrame); + this->uncompressFrame(this->animationSequence[this->animationIndex + 1], this->endFrame); + } +} + +void uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer){ + for(uint32_t x = 0; x < X_SIZE; x++){ + for(uint32_t y = 0; y < Y_SIZE; y++){ + for(uint32_t z = 0; z < Z_SIZE; z++){ + frameBuffer[x][y][z] = getInterpolatedColor(keyFrame, V3D(x, y, z)); + } + } + } +} + +V3D &getInterpolatedColor(const AnimationFrame &keyFrame, V3D position){ + switch(keyFrame.fillInterpolation){ + case FillInterpolation::NO_FILL: + return noFillInterpolate(keyFrame, position); + case FillInterpolation::CLOSEST_COLOR: + return closestColorInterpolate(keyFrame, position); + case FillInterpolation::LINEAR_WEIGHTED_DISTANCE: + return linearInterpolate(keyFrame, position); + case FillInterpolation::SQUARE_WEIGHTED_DISTANCE: + return squareInterpolate(keyFrame, position); + default: + return V3D{}; + } +} + +V3D &keyFrame2BoardCoords(V3D &keyFramePosition){ + V3D returnValue{}; + float maxValue{static_cast(std::numeric_limits::max())}; + // scale the key frame values down to be within board coordinates + float keyFrame_X = static_cast(this->X_SIZE) * static_cast(keyFramePosition.x) / maxValue; + float keyFrame_Y = static_cast(this->Y_SIZE) * static_cast(keyFramePosition.y) / maxValue; + float keyFrame_Z = static_cast(this->Z_SIZE) * static_cast(keyFramePosition.z) / maxValue; + + // carefully quantize the float values back into ints with a precise rounding operation + if(keyFrame_X - std::floor(keyFrame_X) < 0.5f){ + returnValue.x = static_cast(keyFrame_X); + } + else{ + returnValue.x = static_cast(keyFrame_X) + 1; + } + + if(keyFrame_Y - std::floor(keyFrame_Y) < 0.5f){ + returnValue.y = static_cast(keyFrame_Y); + } + else{ + returnValue.y = static_cast(keyFrame_Y) + 1; + } + + if(keyFrame_Z - std::floor(keyFrame_Z) < 0.5f){ + returnValue.z = static_cast(keyFrame_Z); + } + else{ + returnValue.z = static_cast(keyFrame_Z) + 1; + } + + return returnValue; +} + +V3D &noFillInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; + for(Cell cell : keyFrame.frame){ + if(keyFrame2BoardCoords(cell.position) == position){ + returnColor = cell.color; + } + } + + return returnColor; +} + +V3D &closestColorInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; + float closestDistance = (keyframe.frame[0].position - position).mag(); + for(Cell cell : keyFrame.frame){ + float distance = (keyFrame2BoardCoords(cell.position) - position).mag(); + if(distance < closestDistance){ + returnColor = cell.color; + closestDistance = distance; + } + } + + return returnColor; +} + +V3D &linearInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; + + for(Cell cell : keyFrame.frame){ + uint32_t distance = static_cast((keyFrame2BoardCoords(cell.position) - position).mag()); + if(distance == 0) distance = 1; + returnColor = returnColor + cell.color / distance; + } + + returnColor = returnColor / keyFrame.frame.size(); + + return returnColor; +} + +V3D &squareInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; + + for(Cell cell : keyFrame.frame){ + uint32_t distance = static_cast((keyFrame2BoardCoords(cell.position) - position).mag()); + distance *= distance; + if(distance == 0) distance = 1; + returnColor = returnColor + cell.color / distance; + } + + returnColor = returnColor / keyFrame.frame.size(); + + return returnColor; +} +}; + +// let's make some test animation frames + +namespace TestFrames{ + V3D red{255,0,0}; + V3D green{0,255,0}; + V3D blue{0,0,255}; + uint32_t maxValue{std::numeric_limits::max()}; + + Animation::Cell &CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){ + float continuousMaxValue{static_cast(std::numeric_limits::max())}; + Animation::Cell cell{ + .position = V3D{ + static_cast(continuousMaxValue*x_percent), + static_cast(continuousMaxValue*y_percent), + static_cast(continuousMaxValue*z_percent) + }, + .color = color + }; + + return cell; + } + + Animation::AnimationFrame noFillFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::NO_FILL; + .frameInterpolation = FrameInterpolation::SNAP; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame closestColorFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::CLOSEST_COLOR; + .frameInterpolation = FrameInterpolation::SNAP; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame linearFillFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE; + .frameInterpolation = FrameInterpolation::SNAP; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame squareFillFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE; + .frameInterpolation = FrameInterpolation::SNAP; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame noFillFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::NO_FILL; + .frameInterpolation = FrameInterpolation::FADE; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame closestColorFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::CLOSEST_COLOR; + .frameInterpolation = FrameInterpolation::FADE; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame linearFillFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE; + .frameInterpolation = FrameInterpolation::FADE; + .delay = std::chrono::millis(10000); + } + + Animation::AnimationFrame squareFillFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) + }; + .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE; + .frameInterpolation = FrameInterpolation::FADE; + .delay = std::chrono::millis(10000); + } + + std::vector testAnimationSequence{ + noFillFrame, + closestColorFrame, + linearFillFrame, + squareFillFrame, + noFillFadeFrame, + closestFillFadeFrame, + linearFillFadeFrame, + squareFillFadeFrame + }; +} + + diff --git a/lib/Board/Board.h b/lib/Board/Board.h index 8ad0a65..4856f96 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -2,20 +2,25 @@ #include #include +#include #include "Cube.h" -#include "Color.h" +#include "Vector3D.h" -template +template class Board{ public: + enum PLANE_NORMAL : uint32_t{ + X = 0, + Y, + Z + }; + Board(); ~Board() = default; - constexpr uint32_t GetXSize() const{return X_SIZE;} - constexpr uint32_t GetYSize() const{return Y_SIZE;} - constexpr uint32_t GetZSize() const {return Z_SIZE;} - constexpr uint32_t GetNumberCubes() const{return X_SIZE * Y_SIZE * Z_SIZE;} + constexpr V3D &GetSize() const{return BOARD_DIMS;} + constexpr uint32_t GetNumberCubes() const{return BOARD_DIMS.x * BOARD_DIMS.y * BOARD_DIMS.z;} /** * @brief Returns a string in the format: @@ -28,13 +33,25 @@ class Board{ /** * @brief Returns a bool array representing the board */ - std::array &LinearizeBoard() const; + std::array &LinearizeBoard() const; - void Update(); + void FillColor(const V3D &color); - void FillColor(const Color &color); + void SetCubeColor(const V3D &position, const V3D &color); - void SetCubeColor(uint32_t x_coord, uint32_t y_cord, uint32_t z_cord, const Color &color); + void SetCubeOccupation(const V3D &position, bool occupation); + + bool BoardStateChanged(){return this->boardStateHasChanged;} + void ClearBoardStateChanged(){this->boardStateHasChanged = false;} + + /** + * @brief Get a column along any axis + * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and + * the x,y values specify the location of the column in that plane + * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) + * @returns an array of cubes along that column + */ + Cube * SliceBoard(const V3D &column); private: // this is a 3d array of cubes to represent the board. Good luck visualizing it @@ -53,13 +70,13 @@ class Board{ bool boardStateHasChanged; }; -template -Board::Board(){ +template +Board::Board(){ this->FillColor(Color(0,0,0)); } -template -void Board::ToStackString(String &stringBuffer) const{ +template +void Board::ToStackString(String &stringBuffer) const{ std::array linearizedBoard = this->LinearizeBoard(); stringBuffer += "!" + String(linearizedBoard[0]); @@ -71,10 +88,10 @@ void Board::ToStackString(String &stringBuffer) const{ stringBuffer += ";"; } -template -std::array & Board::LinearizeBoard() const{ +template +std::array & Board::LinearizeBoard() const{ // convert the board into one array where each entry represents the height of one stack - std::array linearizedBoard; + std::array linearizedBoard; for(uint32_t x{0}; x < X_SIZE; x++){ for(uint32_t y{0}; y < Y_SIZE; y++){ for(uint32_t z{0}; z < Z_SIZE; z++){ @@ -86,24 +103,63 @@ std::array & Board::Linearize return linearizedBoard; } -template -void Board::Update(){ - -} - -template -void Board::FillColor(const Color &color){ +template +void Board::FillColor(const V3D &color){ for(uint32_t x{0}; x < X_SIZE; x++){ for(uint32_t y{0}; y < Y_SIZE; y++){ for(uint32_t z{0}; z < Z_SIZE; z++){ - this->cubes[x][y][z].SetColor(color); + this->cubes[x][y][z].color = color; } } } } -template -void Board::SetCubeColor(uint32_t x_coord, uint32_t y_cord, uint32_t z_cord, const Color &color){ - this->cubes[x][y][z].SetColor(color); +template +void Board::SetCubeColor(const V3D &position, const V3D &color){ + this->cubes[position.x][position.y][position.z].color = color; +} + +template +void Board::SetCubeOccupation(const V3D &position, bool occupation){ + bool oldOccupation{this->cubes[position.x][position.y][position.z].occupied}; + this->cubes[position.x][position.y][position.z].occupied = occupation; + if(occupation != oldOccupation) this->boardStateHasChanged = true; +} + +template +Cube * Board::SliceBoard(const V3D &column){ + uint32_t columnLength{0}; + V3D indexIncriment{}; + V3D position{}; + switch(column.z){ + case Board::PLANE_NORMAL::X: + columnLength = BOARD_DIMS.x; + indexIncriment.x = 1; + position.z = column.x; + position.y = column.y; + break; + case Board::PLANE_NORMAL::Y: + columnLength = BOARD_DIMS.Y; + indexIncriment.y = 1; + position.x = column.x; + position.z = column.y; + break; + + default: + case Board::PLANE_NORMAL::Z: + columnLength = BOARD_DIMS.Z; + indexIncriment.z = 1; + position.x = column.x; + position.y = column.y; + break; + } + + std::array columnSlice; + for(uint32_t i = 0; i < columnLength; i++){ + V3D cubePosition = indexIncriment * i + position; + columnSlice[i] = &(this->cubes[cubePosition.x][cubePosition.y][cubePosition.z]); + } + + return columnSlice.data(); } diff --git a/lib/Board/BoardLayout.cpp b/lib/Board/BoardLayout.cpp deleted file mode 100644 index b4fee9e..0000000 --- a/lib/Board/BoardLayout.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "BoardLayout.h" - -uint8_t BoardLayout::GetNumberStacks(){ - return this->boardWidth * this->boardHeight; -} - -void BoardLayout::SetStackColors(uint8_t stackNum, Color * colors){ - CubeStack * stack = this->stacks[stackNum]; - stack->SetLEDColors(colors, this->boardHeight); -} - -bool BoardLayout::BoardStateHasChanged(){ - uint16_t boardState[this->boardWidth * this->boardLength]; - this->GetBoardState(boardState); - - // compare the board state to the last board state - for(int i = 0; i < (this->boardWidth * this->boardLength); i++){ - uint16_t stackState = boardState[i]; - uint16_t lastStackState = (this->lastBoardState)[i]; - if(stackState != lastStackState){ - // copy the board state into the last board state - for(int k = 0; k < (this->boardWidth * this->boardLength); k++){ - this->lastBoardState[k] = boardState[k]; - } - return true; - } - } - return false; -} - -void BoardLayout::GetBoardState(uint16_t * boardStateBuffer){ - for(int i = 0; i < (this->boardLength * this->boardWidth); i++){ - CubeStack * stack = this->stacks[i]; - stack->SendLEDData(); // Enable this if you want to constantly stream LED data - boardStateBuffer[i] = stack->GetNumberCubes(); - } -} - diff --git a/lib/Board/BoardLayout.h b/lib/Board/BoardLayout.h deleted file mode 100644 index d8684de..0000000 --- a/lib/Board/BoardLayout.h +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @brief This is the full board manager which handles the state of every stack on the board -*/ - -#pragma once - -#include "CubeStack.h" -#include "Color.h" - -class BoardLayout{ - public: - /** - * @brief BoardLayout COnstructor - */ - BoardLayout(uint8_t boardWidth, uint8_t boardLength, uint8_t boardHeight, CubeStack ** stacks) : - boardWidth(boardWidth), - boardLength(boardLength), - boardHeight(boardHeight), - stacks(stacks) - { - this->lastBoardState = new uint16_t[boardWidth * boardLength]; - } - - /** - * @brief Check if our board state has changed - * @return true if the board state has changed, false otherwise - */ - bool BoardStateHasChanged(); - - /** - * @brief Get the Number of Stacks - * @return the number of stacks - */ - uint8_t GetNumberStacks(); - - /** - * @brief Set the LED Colors - * @param stackNum the stack index you would like to address. - * From top left to bottom right, the stack numbers are as follows: - * | 0 1 2 | - * | 3 4 5 | - * | 6 7 8 | - * @param Colors the array of colors to set the LEDs in a stack to - */ - void SetStackColors(uint8_t stackNum, Color * colors); - - /** - * @brief Get the board population state - * @param boardStateBuffer the buffer to write the board state to. It must be at least boardWidth * boardLength in length - */ - void GetBoardState(uint16_t * boardStateBuffer); - - private: - uint8_t boardWidth; - uint8_t boardLength; - uint8_t boardHeight; - /* - An array of arrays of stacks - [ [stack1, stack2, stack3], - [stack4, stack5, stack6], - [stack7, stack8, stack9] ] - etc - */ - CubeStack ** stacks; - - // records the last known board state - uint16_t * lastBoardState; - -}; \ No newline at end of file diff --git a/lib/Board/Color.h b/lib/Board/Color.h deleted file mode 100644 index 434b105..0000000 --- a/lib/Board/Color.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @file Color.h - * @brief This file contains the color struct -*/ - -#pragma once -#include - -// store a color -struct Color{ - public: - // create a constructor for this struct - Color(uint8_t red, uint8_t green, uint8_t blue) : - red(red), - green(green), - blue(blue) - {} - Color() = default; - uint8_t red{0}; - uint8_t green{0}; - uint8_t blue{0}; -}; \ No newline at end of file diff --git a/lib/Board/CubeStack.cpp b/lib/Board/CubeStack.cpp deleted file mode 100644 index 7af12ab..0000000 --- a/lib/Board/CubeStack.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "CubeStack.h" - -CubeStack::CubeStack(uint16_t ADCPin, uint16_t ledPin, uint8_t numLEDs){ - this->ADCPin = ADCPin; - this->blockLights = *(new Adafruit_NeoPixel(numLEDs*2, ledPin, NEO_GRB + NEO_KHZ800)); - this->ledColors = new Color[numLEDs]; - this->numLEDs = numLEDs; - - // initialize the LED colors to off - for(int i = 0; i < numLEDs; i++){ - this->ledColors[i] = *(new Color(0, 0, 0)); - } -}; - -uint32_t CubeStack::GetNumberCubes(Cube &cube){ - // read the ADC and return the number of cubes - /* - 0 cubes: 1 : 4095-3400 - 1 cube: 1/2 3400-2500 - 2 cubes: 1/3 2500-1850 - 3 cubes: 1/4 1850-0 - */ - uint16_t value = analogRead(cube.ADCPin); - uint16_t lowPassADCRead = static_cast((static_cast(cube.lastADCReading) * 0.9) + (static_cast(value) * 0.1)); - - // temporary definitions to define value ranges: - uint16_t zeroCubesHigh = 4095; - uint16_t zeroCubesLow = 3400; - uint16_t oneCubeLow = 2500; - uint16_t twoCubesLow = 1850; - uint16_t threeCubesLow = 0; - - uint8_t stackHeight = 0; - - if(lowPassADCRead >= zeroCubesLow && lowPassADCRead <= zeroCubesHigh){ - stackHeight = 0; - } - else if(lowPassADCRead >= oneCubeLow){ - stackHeight = 1; - } - else if(lowPassADCRead >= twoCubesLow){ - stackHeight = 2; - } - else if(lowPassADCRead >= threeCubesLow){ - stackHeight = 3; - } - - return stackHeight; -} - -void CubeStack::SendLEDData(){ - // we always initialize before we do anything because other CubeStacks could be hogging the hardware - // between our writes - this->blockLights.begin(); - // set the LED colors - for(int i = 0; i < this->numLEDs; i++){ - this->blockLights.setPixelColor(i*2, this->blockLights.Color(this->ledColors[i].red, this->ledColors[i].green, this->ledColors[i].blue)); - this->blockLights.setPixelColor((i*2 + 1), this->blockLights.Color(this->ledColors[i].red, this->ledColors[i].green, this->ledColors[i].blue)); - } - this->blockLights.show(); -} \ No newline at end of file diff --git a/lib/Board/CubeStack.h b/lib/Board/CubeStack.h deleted file mode 100644 index 8a343e4..0000000 --- a/lib/Board/CubeStack.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @brief this manages a single cube stack and the lighting / detecting of how many cubes -*/ - -#pragma once - -#include -#include -#include "Color.h" -#include "Cube.h" - - -namespace CubeStack{ - /** - * @brief Construct a new Cube Stack object - * @param ADCPin the pin that the ADC is connected to - * @param ledPin the pin that the LED is connected to - */ - CubeStack(uint16_t ADCPin, uint16_t ledPin, uint8_t numLEDs); - - /** - * @brief Returns the number of cubes in the stack - * @return the number of cubes in the stack - */ - uint32_t GetNumberCubes(Cube &cube); - - /** - * @brief sends the LED data to the LED strip - */ - void SendLEDData(); -}; \ No newline at end of file diff --git a/lib/BoardReader/BoardDriver.h b/lib/BoardDriver/BoardDriver.h similarity index 95% rename from lib/BoardReader/BoardDriver.h rename to lib/BoardDriver/BoardDriver.h index 5ff3d09..649b5c9 100644 --- a/lib/BoardReader/BoardDriver.h +++ b/lib/BoardDriver/BoardDriver.h @@ -4,15 +4,10 @@ #include #include +#include #include "Cube.h" - -namespace BoardDriverTypes{ - struct CubeStack{ - uint8_t adcPin; - uint8_t ledPin; - } -}; +#include "BoardDriverTypes.h" template class BoardDriver{ @@ -21,6 +16,8 @@ public: BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController); ~BoardDriver() = default; + void Init(); + uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD); uint32_t GetNumberCubes(uint32_t stackIndex); @@ -38,6 +35,16 @@ private: } }; +template +void BoardDriver::Init(){ + for(uint32_t i = 0; i < NUM_STACKS; i++){ + pinMode(this->stacks[i].ledPin, OUTPUT); + } + + // begin doesn't really do anything besides setting the pinmode + this->pixelController.begin(); +} + template BoardDriver::BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController): stacks(stacks), @@ -45,11 +52,7 @@ pixelController(pixelController) { for(uint32_t i = 0; i < NUM_STACKS; i++){ this->filteredReadings[i] = 0; - pinMode(this->stacks[i].ledPin, OUTPUT); } - - // begin doesn't really do anything besides setting the pinmode - this->pixelController.begin(); } template diff --git a/lib/BoardDriver/BoardDriverTypes.h b/lib/BoardDriver/BoardDriverTypes.h new file mode 100644 index 0000000..89382fb --- /dev/null +++ b/lib/BoardDriver/BoardDriverTypes.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace BoardDriverTypes{ + struct CubeStack{ + uint8_t adcPin; + uint8_t ledPin; + }; +}; \ No newline at end of file diff --git a/lib/BoardManager/BoardManager.h b/lib/BoardManager/BoardManager.h new file mode 100644 index 0000000..0639557 --- /dev/null +++ b/lib/BoardManager/BoardManager.h @@ -0,0 +1,102 @@ +#pragma once + +#include "Board.h" +#include "BoardDriver.h" +#include "Vector3D.h" + +template +class BoardManager{ + public: + BoardManager(BoardDriver &boardDriver): + driver(boardDriver){}; + + ~BoardManager() = default; + + void Init(); + + void Update(); + + void SetCubeColor(const V3D &position, const V3D &color); + + void SetColumnColor(const V3D &position, const V3D &color); + + + /** + * @brief Fill a column along any axis with a color + * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and + * the x,y values specify the location of the column in that plane + * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) + * @param color the color you want to fill the column with + */ + void FillColumnColor(const V3D &column, const V3D &color); + + bool HasBoardChanged(){return this->hasBoardChanged;} + + private: + BoardDriver &driver; + Board board{}; + + bool hasBoardChanged{false}; + + void updateBoardColors(const V3D &column); + +}; + +template +void BoardManager::Init(){ + this->driver.Init(); +} + +template +void BoardManager::Update(){ + // update the occupied cubes on the board and the cube colors + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + uint32_t numCubes{this->driver.GetNumberCubes(i)}; + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ + V3D cubePosition{x, y, z}; + bool isOccupied{z < numCubes}; + if(this->board.SetCubeOccupation(cubePosition, i < numCubes) != isOccupied){ + this->board.boardStateHasChanged = true; + } + cubePosition.z = Board::PLANE_NORMAL::Z; + this->driver.UpdateStackLEDs(i, this->board.SliceBoard(cubePosition), BOARD_DIMS.z); + } + } + } + + // update the colors + for(uint32_t i = 0; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ + + } + +} + +template +void BoardManager::updateBoardColors(const V3D &column){ + Cube * cubeSlice{this->board.SliceBoard(column)}; + uint32_t numCubes{0}; + + switch(column.z){ + case Board::PLANE_NORMAL::X: + numCubes = BOARD_DIMS.x; + break; + case Board::PLANE_NORMAL::Y: + numCubes = BOARD_DIMS.y; + break; + case Board::PLANE_NORMAL::Z: + numCubes = BOARD_DIMS.z; + break; + default: + break; + } + + this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes); +} + +template +void BoardManager::SetCubeColor(const V3D &position, const V3D &color){ + this->board.SetCubeColor(position, color); + V3D slice{position.x, position.y, Board::PLANE_NORMAL::Z}; + this->updateBoardColors(slice); +} \ No newline at end of file diff --git a/lib/ColorManager/ColorManager.cpp b/lib/ColorManager/ColorManager.cpp deleted file mode 100644 index 95f4294..0000000 --- a/lib/ColorManager/ColorManager.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "ColorManager.h" - -void ColorManager::Update(){ - if(!(this->enabled)){ - return; - } - // go through our colors and have them fade from r->g->b->r - for(uint8_t i = 0; i < 9; i++){ - for(uint8_t j = 0; j < 3; j++){ - Color * color = this->colors[i][j]; - // fade from red to green - if(color->red > 0 && color->green >= 0 && color->blue == 0){ - color->red--; - color->green++; - } - // fade from green to blue - else if(color->green > 0 && color->blue >= 0 && color->red == 0){ - color->green--; - color->blue++; - } - // fade from blue to red - else if(color->blue > 0 && color->red >= 0 && color->green == 0){ - color->blue--; - color->red++; - } - } - } - - // set the colors - for(uint8_t i = 0; i < 9; i++){ - Color temp_colors[3] = {*(this->colors[i][0]), *(this->colors[i][1]), *(this->colors[i][2])}; - this->board->SetStackColors(i, temp_colors); - } -} - -void ColorManager::Enable(bool enable){ - this->enabled = enable; - - if(this->enabled == false){ - // set all the colors to black - Color black(0, 0, 0); - Color temp_colors[3] = {black, black, black}; - // set the colors - for(uint8_t i = 0; i < 9; i++){ - this->board->SetStackColors(i, temp_colors); - } - } -} \ No newline at end of file diff --git a/lib/ColorManager/ColorManager.h b/lib/ColorManager/ColorManager.h deleted file mode 100644 index 375c4bf..0000000 --- a/lib/ColorManager/ColorManager.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file ColorManager.h - * @brief Generate pretty colors for the board and make it do something when unity isn't controlling it -*/ - -#pragma once - -#include "BoardLayout.h" -#include "Color.h" - -class ColorManager{ - public: - ColorManager(BoardLayout * board) : - board(board) - {} - - /** - * @brief Allows the color manager to update the board colors - */ - void Update(); - - /** - * @brief Enables or disables the color manager - * @param enable true to enable, false to disable - */ - void Enable(bool enable); - - - private: - BoardLayout * board; - bool enabled{true}; - - Color * colors[9][3] = { - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}, - {new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)} - }; -}; \ No newline at end of file diff --git a/lib/Cube/Cube.cpp b/lib/Cube/Cube.cpp deleted file mode 100644 index 7a271ba..0000000 --- a/lib/Cube/Cube.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Cube.h" - -Cube::Cube(uint8_t ADCPin, uint8_t ledPin) - : ADCPin(ADCPin), - ledPin(ledPin) -{ - Color black(0,0,0); - this->SetColor(black); -} - -Cube::Cube(uint8_t ADCPin, uint8_t ledPin, const Color &color) - : ADCPin(ADCPin), - ledPin(ledPin) -{ - this->SetColor(color); -} - -void Cube::SetColor(const Color &color){ - this->color.red = color.red; - this->color.green = color.green; - this->color.blue = color.blue; -} \ No newline at end of file diff --git a/lib/Cube/Cube.h b/lib/Cube/Cube.h index 516ac77..8e2bc57 100644 --- a/lib/Cube/Cube.h +++ b/lib/Cube/Cube.h @@ -5,17 +5,10 @@ #pragma once -#include "Color.h" +#include "Vector3D.h" class Cube{ public: - Cube(); - Cube(); - - void SetColor(const Color &color); - - uint16_t lastADCReading{0}; - - Color color; + V3D color; bool isOccupied{false}; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index dd49238..dbdb8bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,7 @@ // project specific libraries #include "BluetoothSerial.h" #include "SerialMessage.h" -#include "BoardLayout.h" #include "Color.h" -#include "ColorManager.h" #include "GlobalPrint.h" // -------------------------------------------------- @@ -29,10 +27,6 @@ uint32_t boardStateMaxUpdatePeriod{34}; // this is a little slower than 30fps // BluetoothSerial SerialBT; // BluetoothSerialMessage serialMessageBT(&SerialBT); SerialMessage<500, 10> serialMessage(&Serial); -BoardLayout board(BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT, stacks); - -// Temporary thing until we can get bluetooth color management working on the quest -ColorManager colorManager(&board); // -------------------------------------------------- // ----------------- FUNCTIONS ---------------------- From 3a49761b663cf861a9c79e31ab539920f1a1853f Mon Sep 17 00:00:00 2001 From: Quinn Date: Thu, 22 Aug 2024 23:00:55 -0400 Subject: [PATCH 03/12] Got the refactor building --- include/BOARD-DEFINITIONS.h | 25 +-- include/PINOUT.h | 42 +++--- include/Vector3D.h | 10 +- lib/Board/Board.h | 71 ++++----- lib/{BoardDriver => Board}/BoardDriver.h | 28 ++-- lib/Board/BoardManager.h | 142 ++++++++++++++++++ .../BoardDriverTypes.h => Board/BoardTypes.h} | 8 +- lib/{Cube => Board}/Cube.h | 0 lib/BoardManager/BoardManager.h | 102 ------------- src/main.cpp | 70 +++++---- 10 files changed, 274 insertions(+), 224 deletions(-) rename lib/{BoardDriver => Board}/BoardDriver.h (76%) create mode 100644 lib/Board/BoardManager.h rename lib/{BoardDriver/BoardDriverTypes.h => Board/BoardTypes.h} (52%) rename lib/{Cube => Board}/Cube.h (100%) delete mode 100644 lib/BoardManager/BoardManager.h diff --git a/include/BOARD-DEFINITIONS.h b/include/BOARD-DEFINITIONS.h index d112779..db1aa63 100644 --- a/include/BOARD-DEFINITIONS.h +++ b/include/BOARD-DEFINITIONS.h @@ -8,55 +8,58 @@ #include #include "PINOUT.h" -#include "BoardDriverTypes.h" +#include "BoardTypes.h" +#include "Vector3D.h" // define the physical dimensions of the board static constexpr uint32_t BOARD_WIDTH{3}; static constexpr uint32_t BOARD_LENGTH{3}; static constexpr uint32_t BOARD_HEIGHT{3}; +static constexpr V3D BOARD_DIMENSIONS{BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT}; + // define the number of stacks static constexpr uint32_t NUMBER_STACKS{BOARD_WIDTH * BOARD_LENGTH}; // define the CubeStacks -static BoardDriverTypes::CubeStack stack1{ +static BOARD_TYPES::CubeStack stack1{ .adcPin=STACK1_ADC_PIN, .ledPin=STACK1_LED_PIN }; -static BoardDriverTypes::CubeStack stack2{ +static BOARD_TYPES::CubeStack stack2{ .adcPin=STACK2_ADC_PIN, .ledPin=STACK2_LED_PIN }; -static BoardDriverTypes::CubeStack stack3{ +static BOARD_TYPES::CubeStack stack3{ .adcPin=STACK3_ADC_PIN, .ledPin=STACK3_LED_PIN }; -static BoardDriverTypes::CubeStack stack4{ +static BOARD_TYPES::CubeStack stack4{ .adcPin=STACK4_ADC_PIN, .ledPin=STACK4_LED_PIN }; -static BoardDriverTypes::CubeStack stack5{ +static BOARD_TYPES::CubeStack stack5{ .adcPin=STACK5_ADC_PIN, .ledPin=STACK5_LED_PIN }; -static BoardDriverTypes::CubeStack stack6{ +static BOARD_TYPES::CubeStack stack6{ .adcPin=STACK6_ADC_PIN, .ledPin=STACK6_LED_PIN }; -static BoardDriverTypes::CubeStack stack7{ +static BOARD_TYPES::CubeStack stack7{ .adcPin=STACK7_ADC_PIN, .ledPin=STACK7_LED_PIN }; -static BoardDriverTypes::CubeStack stack8{ +static BOARD_TYPES::CubeStack stack8{ .adcPin=STACK8_ADC_PIN, .ledPin=STACK8_LED_PIN }; -static BoardDriverTypes::CubeStack stack9{ +static BOARD_TYPES::CubeStack stack9{ .adcPin=STACK9_ADC_PIN, .ledPin=STACK9_LED_PIN }; -static std::array stacks{ +static std::array stacks{ stack1, stack2, stack3, stack4, stack5, stack6, stack7, stack8, stack9 diff --git a/include/PINOUT.h b/include/PINOUT.h index e6764b4..1a7a974 100644 --- a/include/PINOUT.h +++ b/include/PINOUT.h @@ -5,45 +5,47 @@ #pragma once +#include + // Stack pins // Stack 1 pins -#define STACK1_ADC_PIN 6 -#define STACK1_LED_PIN 11 +static constexpr uint8_t STACK1_ADC_PIN{6}; +static constexpr uint8_t STACK1_LED_PIN{11}; // Stack 2 pins -#define STACK2_ADC_PIN 16 -#define STACK2_LED_PIN 14 +static constexpr uint8_t STACK2_ADC_PIN{16}; +static constexpr uint8_t STACK2_LED_PIN{14}; // Stack 3 pins -#define STACK3_ADC_PIN 8 -#define STACK3_LED_PIN 45 +static constexpr uint8_t STACK3_ADC_PIN{8}; +static constexpr uint8_t STACK3_LED_PIN{45}; // Stack 4 pins -#define STACK4_ADC_PIN 5 -#define STACK4_LED_PIN 10 +static constexpr uint8_t STACK4_ADC_PIN{5}; +static constexpr uint8_t STACK4_LED_PIN{10}; // Stack 5 pins -#define STACK5_ADC_PIN 15 -#define STACK5_LED_PIN 13 +static constexpr uint8_t STACK5_ADC_PIN{15}; +static constexpr uint8_t STACK5_LED_PIN{13}; // Stack 6 pins -#define STACK6_ADC_PIN 18 -#define STACK6_LED_PIN 38 +static constexpr uint8_t STACK6_ADC_PIN{18}; +static constexpr uint8_t STACK6_LED_PIN{38}; // Stack 7 pins -#define STACK7_ADC_PIN 4 -#define STACK7_LED_PIN 9 +static constexpr uint8_t STACK7_ADC_PIN{4}; +static constexpr uint8_t STACK7_LED_PIN{9}; // Stack 8 pins -#define STACK8_ADC_PIN 7 -#define STACK8_LED_PIN 12 +static constexpr uint8_t STACK8_ADC_PIN{7}; +static constexpr uint8_t STACK8_LED_PIN{12}; // Stack 9 pins -#define STACK9_ADC_PIN 17 -#define STACK9_LED_PIN 37 +static constexpr uint8_t STACK9_ADC_PIN{17}; +static constexpr uint8_t STACK9_LED_PIN{37}; // Bluetooth module configuration pins -#define BT_STATE_PIN 2 -#define BT_EN_PIN 3 +static constexpr uint8_t BT_STATE_PIN{2}; +static constexpr uint8_t BT_EN_PIN{3}; diff --git a/include/Vector3D.h b/include/Vector3D.h index 1858f3b..6f84399 100644 --- a/include/Vector3D.h +++ b/include/Vector3D.h @@ -5,7 +5,7 @@ class V3D{ public: - V3D(uint32_t x=0, uint32_t y=0, uint32_t z=0): + constexpr V3D(uint32_t x=0, uint32_t y=0, uint32_t z=0): x(x), y(y), z(z){} @@ -41,6 +41,14 @@ class V3D{ return vector; } + V3D operator*(const uint32_t scalar){ + V3D vector{}; + vector.x = this->x * scalar; + vector.y = this->y * scalar; + vector.z = this->z * scalar; + return vector; + } + bool operator==(const V3D &other){ return this->x == other.x && this->y == other.y && this->z == other.z; } diff --git a/lib/Board/Board.h b/lib/Board/Board.h index 4856f96..b16165c 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -7,19 +7,14 @@ #include "Cube.h" #include "Vector3D.h" -template +template class Board{ public: - enum PLANE_NORMAL : uint32_t{ - X = 0, - Y, - Z - }; Board(); ~Board() = default; - constexpr V3D &GetSize() const{return BOARD_DIMS;} + constexpr const V3D &GetSize() const{return BOARD_DIMS;} constexpr uint32_t GetNumberCubes() const{return BOARD_DIMS.x * BOARD_DIMS.y * BOARD_DIMS.z;} /** @@ -42,7 +37,7 @@ class Board{ void SetCubeOccupation(const V3D &position, bool occupation); bool BoardStateChanged(){return this->boardStateHasChanged;} - void ClearBoardStateChanged(){this->boardStateHasChanged = false;} + void SetStateChanged(bool boardState){this->boardStateHasChanged = boardState;} /** * @brief Get a column along any axis @@ -51,7 +46,7 @@ class Board{ * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) * @returns an array of cubes along that column */ - Cube * SliceBoard(const V3D &column); + Cube ** SliceBoard(const V3D &column); private: // this is a 3d array of cubes to represent the board. Good luck visualizing it @@ -65,36 +60,34 @@ class Board{ | | / |____________|/ */ - std::array, Y_SIZE>, X_SIZE> cubes; + std::array, BOARD_DIMS.y>, BOARD_DIMS.x> cubes; bool boardStateHasChanged; }; -template +template Board::Board(){ - this->FillColor(Color(0,0,0)); + this->FillColor(V3D{}); } -template +template void Board::ToStackString(String &stringBuffer) const{ - std::array linearizedBoard = this->LinearizeBoard(); + std::array linearizedBoard = this->LinearizeBoard(); - stringBuffer += "!" + String(linearizedBoard[0]); + stringBuffer += String(linearizedBoard[0]); - for(uint32_t i = 0; i < X_SIZE * Y_SIZE; i++){ + for(uint32_t i = 0; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ stringBuffer += "," + String(linearizedBoard[i]); } - - stringBuffer += ";"; } -template +template std::array & Board::LinearizeBoard() const{ // convert the board into one array where each entry represents the height of one stack std::array linearizedBoard; - for(uint32_t x{0}; x < X_SIZE; x++){ - for(uint32_t y{0}; y < Y_SIZE; y++){ - for(uint32_t z{0}; z < Z_SIZE; z++){ + for(uint32_t x{0}; x < BOARD_DIMS.x; x++){ + for(uint32_t y{0}; y < BOARD_DIMS.y; y++){ + for(uint32_t z{0}; z < BOARD_DIMS.z; z++){ bool isOccupied{this->cubes[x][y][z].isOccupied}; linearizedBoard[x + y*3] += static_cast(isOccupied); } @@ -103,63 +96,63 @@ std::array & Board::Linearize return linearizedBoard; } -template +template void Board::FillColor(const V3D &color){ - for(uint32_t x{0}; x < X_SIZE; x++){ - for(uint32_t y{0}; y < Y_SIZE; y++){ - for(uint32_t z{0}; z < Z_SIZE; z++){ + for(uint32_t x{0}; x < BOARD_DIMS.x; x++){ + for(uint32_t y{0}; y < BOARD_DIMS.y; y++){ + for(uint32_t z{0}; z < BOARD_DIMS.z; z++){ this->cubes[x][y][z].color = color; } } } } -template +template void Board::SetCubeColor(const V3D &position, const V3D &color){ this->cubes[position.x][position.y][position.z].color = color; } -template +template void Board::SetCubeOccupation(const V3D &position, bool occupation){ - bool oldOccupation{this->cubes[position.x][position.y][position.z].occupied}; - this->cubes[position.x][position.y][position.z].occupied = occupation; + bool oldOccupation{this->cubes[position.x][position.y][position.z].isOccupied}; + this->cubes[position.x][position.y][position.z].isOccupied = occupation; if(occupation != oldOccupation) this->boardStateHasChanged = true; } -template -Cube * Board::SliceBoard(const V3D &column){ +template +Cube ** Board::SliceBoard(const V3D &column){ uint32_t columnLength{0}; V3D indexIncriment{}; V3D position{}; switch(column.z){ - case Board::PLANE_NORMAL::X: + case BOARD_TYPES::PLANE_NORMAL::X: columnLength = BOARD_DIMS.x; indexIncriment.x = 1; position.z = column.x; position.y = column.y; break; - case Board::PLANE_NORMAL::Y: - columnLength = BOARD_DIMS.Y; + case BOARD_TYPES::PLANE_NORMAL::Y: + columnLength = BOARD_DIMS.y; indexIncriment.y = 1; position.x = column.x; position.z = column.y; break; default: - case Board::PLANE_NORMAL::Z: - columnLength = BOARD_DIMS.Z; + case BOARD_TYPES::PLANE_NORMAL::Z: + columnLength = BOARD_DIMS.z; indexIncriment.z = 1; position.x = column.x; position.y = column.y; break; } - std::array columnSlice; + Cube* columnSlice[columnLength]; for(uint32_t i = 0; i < columnLength; i++){ V3D cubePosition = indexIncriment * i + position; columnSlice[i] = &(this->cubes[cubePosition.x][cubePosition.y][cubePosition.z]); } - return columnSlice.data(); + return columnSlice; } diff --git a/lib/BoardDriver/BoardDriver.h b/lib/Board/BoardDriver.h similarity index 76% rename from lib/BoardDriver/BoardDriver.h rename to lib/Board/BoardDriver.h index 649b5c9..c502f8f 100644 --- a/lib/BoardDriver/BoardDriver.h +++ b/lib/Board/BoardDriver.h @@ -7,13 +7,13 @@ #include #include "Cube.h" -#include "BoardDriverTypes.h" +#include "BoardTypes.h" template class BoardDriver{ public: - BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController); + BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController); ~BoardDriver() = default; void Init(); @@ -21,17 +21,17 @@ public: uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD); uint32_t GetNumberCubes(uint32_t stackIndex); - void UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube cubes[], uint32_t numCubes); - void UpdateStackLEDs(uint32_t stackIndex, Cube cubes[], uint32_t numCubes); + void UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube* cubes[], uint32_t numCubes); + void UpdateStackLEDs(uint32_t stackIndex, Cube* cubes[], uint32_t numCubes); private: - std::array &stacks; + std::array &stacks; Adafruit_NeoPixel &pixelController; std::array filteredReadings; uint32_t xy2StackIndex(uint32_t x_coord, uint32_t y_coord, uint32_t numXStacks){ - return X_COORD + Y_COORD*numXStacks; + return x_coord + y_coord*numXStacks; } }; @@ -46,7 +46,7 @@ void BoardDriver::Init(){ } template -BoardDriver::BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController): +BoardDriver::BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController): stacks(stacks), pixelController(pixelController) { @@ -56,24 +56,24 @@ pixelController(pixelController) } template -void BoardDriver::UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube cubes[], uint32_t numCubes){ +void BoardDriver::UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube* cubes[], uint32_t numCubes){ this->UpdateStackLEDs(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks), cubes, numCubes); } template -void BoardDriver::UpdateStackLEDs(uint32_t stackIndex, Cube cubes[], uint32_t numCubes){ +void BoardDriver::UpdateStackLEDs(uint32_t stackIndex, Cube* cubes[], uint32_t numCubes){ this->pixelController.setPin(this->stacks[stackIndex].ledPin); for(int i = 0; i < numCubes; i++){ - Color color{cubes[i].color}; - this->pixelController.setPixelColor(i*2, this->pixelController.Color(color.red, color.green, color.blue)); - this->pixelController.setPixelColor((i*2 + 1), this->pixelController.Color(color.red, color.green, color.blue)); + V3D color{cubes[i]->color}; + this->pixelController.setPixelColor(i*2, this->pixelController.Color(color.x, color.y, color.z)); + this->pixelController.setPixelColor((i*2 + 1), this->pixelController.Color(color.x, color.y, color.z)); } this->pixelController.show(); } template uint32_t BoardDriver::GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD){ - return this->GetNumberCubes(this->xy2StackIndex(x_coord, y_coord, numXStacks)); + return this->GetNumberCubes(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks)); } template @@ -85,7 +85,7 @@ uint32_t BoardDriver::GetNumberCubes(uint32_t stackIndex){ 2 cubes: 1/3 2500-1850 3 cubes: 1/4 1850-0 */ - uint16_t value = analogRead(cube.ADCPin); + uint16_t value = analogRead(this->stacks[stackIndex].adcPin); uint16_t lowPassADCRead = static_cast( (static_cast(this->filteredReadings[stackIndex]) * 0.9) diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h new file mode 100644 index 0000000..9a0d78a --- /dev/null +++ b/lib/Board/BoardManager.h @@ -0,0 +1,142 @@ +#pragma once + +#include "Board.h" +#include "BoardDriver.h" +#include "Vector3D.h" + +template +class BoardManager{ + public: + BoardManager(BoardDriver &boardDriver); + + ~BoardManager() = default; + + void Init(); + + void Update(); + + void SetCubeColor(const V3D &position, const V3D &color); + + void SetColumnColors(const V3D &column, const V3D *color); + + + /** + * @brief Fill a column along any axis with a color + * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and + * the x,y values specify the location of the column in that plane + * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) + * @param color the color you want to fill the column with + */ + void FillColumnColor(const V3D &column, const V3D &color); + + bool HasBoardChanged(); + + void ClearBoardChanged(); + + String &Board2StackString(); + + private: + BoardDriver &driver; + Board board{}; + + bool hasBoardChanged{false}; + + void updateBoardColors(const V3D &column); + + uint32_t getColumnHeight(BOARD_TYPES::PLANE_NORMAL normal){ + switch(normal){ + case BOARD_TYPES::PLANE_NORMAL::X: + return BOARD_DIMS.x; + break; + case BOARD_TYPES::PLANE_NORMAL::Y: + return BOARD_DIMS.y; + break; + case BOARD_TYPES::PLANE_NORMAL::Z: + return BOARD_DIMS.z; + break; + default: + return 0; + } + } + +}; + +template +BoardManager::BoardManager(BoardDriver &boardDriver): + driver(boardDriver){} + +template +void BoardManager::Init(){ + this->driver.Init(); +} + +template +void BoardManager::Update(){ + // update the occupied cubes on the board and the cube colors + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + uint32_t stackIndex{y * BOARD_DIMS.x + x}; + uint32_t numCubes{this->driver.GetNumberCubes(stackIndex)}; + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ + V3D cubePosition{x, y, z}; + this->board.SetCubeOccupation(cubePosition, z < numCubes); + cubePosition.z = BOARD_TYPES::PLANE_NORMAL::Z; + this->driver.UpdateStackLEDs(stackIndex, this->board.SliceBoard(cubePosition), BOARD_DIMS.z); + } + } + } +} + +template +void BoardManager::updateBoardColors(const V3D &column){ + Cube ** cubeSlice{this->board.SliceBoard(column)}; + uint32_t numCubes{this->getColumnHeight(static_cast(column.z))}; + this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes); +} + +template +void BoardManager::SetCubeColor(const V3D &position, const V3D &color){ + this->board.SetCubeColor(position, color); + V3D slice{position.x, position.y, BOARD_TYPES::PLANE_NORMAL::Z}; + this->updateBoardColors(slice); +} + +template +void BoardManager::SetColumnColors(const V3D &column, const V3D *color){ + uint32_t columnHeight{this->getColumnHeight(static_cast(column.z))}; + + V3D position = column; + for(uint32_t z = 0; z < columnHeight; z++){ + position.z = z; + this->board.SetCubeColor(position, color[z]); + + } + + this->updateBoardColors(column); +} + +template +void BoardManager::FillColumnColor(const V3D &column, const V3D &color){ + uint32_t columnHeight{this->getColumnHeight(column.z)}; + + V3D position = column; + for(uint32_t z = 0; z < columnHeight; z++){ + position.z = z; + this->board.SetCubeColor(position, color); + } + + this->updateBoardColors(column); +} + +template +bool BoardManager::HasBoardChanged(){return this->hasBoardChanged;} + +template +void BoardManager::ClearBoardChanged(){this->hasBoardChanged = false;} + +template +String &BoardManager::Board2StackString(){ + String message{}; + this->board.ToStackString(message); + return message; +} diff --git a/lib/BoardDriver/BoardDriverTypes.h b/lib/Board/BoardTypes.h similarity index 52% rename from lib/BoardDriver/BoardDriverTypes.h rename to lib/Board/BoardTypes.h index 89382fb..e83e1a1 100644 --- a/lib/BoardDriver/BoardDriverTypes.h +++ b/lib/Board/BoardTypes.h @@ -2,9 +2,15 @@ #include -namespace BoardDriverTypes{ +namespace BOARD_TYPES{ struct CubeStack{ uint8_t adcPin; uint8_t ledPin; }; + + enum PLANE_NORMAL : uint32_t{ + X = 0, + Y, + Z + }; }; \ No newline at end of file diff --git a/lib/Cube/Cube.h b/lib/Board/Cube.h similarity index 100% rename from lib/Cube/Cube.h rename to lib/Board/Cube.h diff --git a/lib/BoardManager/BoardManager.h b/lib/BoardManager/BoardManager.h deleted file mode 100644 index 0639557..0000000 --- a/lib/BoardManager/BoardManager.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#include "Board.h" -#include "BoardDriver.h" -#include "Vector3D.h" - -template -class BoardManager{ - public: - BoardManager(BoardDriver &boardDriver): - driver(boardDriver){}; - - ~BoardManager() = default; - - void Init(); - - void Update(); - - void SetCubeColor(const V3D &position, const V3D &color); - - void SetColumnColor(const V3D &position, const V3D &color); - - - /** - * @brief Fill a column along any axis with a color - * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and - * the x,y values specify the location of the column in that plane - * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) - * @param color the color you want to fill the column with - */ - void FillColumnColor(const V3D &column, const V3D &color); - - bool HasBoardChanged(){return this->hasBoardChanged;} - - private: - BoardDriver &driver; - Board board{}; - - bool hasBoardChanged{false}; - - void updateBoardColors(const V3D &column); - -}; - -template -void BoardManager::Init(){ - this->driver.Init(); -} - -template -void BoardManager::Update(){ - // update the occupied cubes on the board and the cube colors - for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ - for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ - uint32_t numCubes{this->driver.GetNumberCubes(i)}; - for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ - V3D cubePosition{x, y, z}; - bool isOccupied{z < numCubes}; - if(this->board.SetCubeOccupation(cubePosition, i < numCubes) != isOccupied){ - this->board.boardStateHasChanged = true; - } - cubePosition.z = Board::PLANE_NORMAL::Z; - this->driver.UpdateStackLEDs(i, this->board.SliceBoard(cubePosition), BOARD_DIMS.z); - } - } - } - - // update the colors - for(uint32_t i = 0; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ - - } - -} - -template -void BoardManager::updateBoardColors(const V3D &column){ - Cube * cubeSlice{this->board.SliceBoard(column)}; - uint32_t numCubes{0}; - - switch(column.z){ - case Board::PLANE_NORMAL::X: - numCubes = BOARD_DIMS.x; - break; - case Board::PLANE_NORMAL::Y: - numCubes = BOARD_DIMS.y; - break; - case Board::PLANE_NORMAL::Z: - numCubes = BOARD_DIMS.z; - break; - default: - break; - } - - this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes); -} - -template -void BoardManager::SetCubeColor(const V3D &position, const V3D &color){ - this->board.SetCubeColor(position, color); - V3D slice{position.x, position.y, Board::PLANE_NORMAL::Z}; - this->updateBoardColors(slice); -} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index dbdb8bc..294b4be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include // Static Defines #include "PINOUT.h" @@ -11,23 +13,26 @@ // project specific libraries #include "BluetoothSerial.h" #include "SerialMessage.h" -#include "Color.h" #include "GlobalPrint.h" +#include "BoardManager.h" +#include "BoardDriver.h" +#include "BoardTypes.h" + // -------------------------------------------------- // ----------------- VARIABLES ---------------------- // -------------------------------------------------- TaskHandle_t updateCommunicaitonTask; TaskHandle_t updateBoardTask; -uint32_t boardStateTimer{0}; -bool boardStateHasChanged{false}; -uint32_t boardStateMaxUpdatePeriod{34}; // this is a little slower than 30fps - // BluetoothSerial SerialBT; // BluetoothSerialMessage serialMessageBT(&SerialBT); SerialMessage<500, 10> serialMessage(&Serial); +Adafruit_NeoPixel pixelController{BOARD_HEIGHT*2, STACK1_LED_PIN, NEO_GRB + NEO_KHZ800}; + +BoardDriver boardDriver{stacks, pixelController}; +BoardManager boardManager{boardDriver}; // -------------------------------------------------- // ----------------- FUNCTIONS ---------------------- // -------------------------------------------------- @@ -58,37 +63,30 @@ void SetupBluetoothModule(){ void printBoardState(){ - // create a buffer to hold the board state - uint16_t boardState[BOARD_WIDTH * BOARD_LENGTH]; - // read in the board state - board.GetBoardState(boardState); - GlobalPrint::Print("!0,"); - - for(int i = 0; i < (BOARD_WIDTH * BOARD_LENGTH); i++){ - GlobalPrint::Print(String(boardState[i])); - if(i == (BOARD_WIDTH * BOARD_LENGTH) - 1){ - break; - } - GlobalPrint::Print(","); - } - + GlobalPrint::Print(boardManager.Board2StackString()); GlobalPrint::Println(";"); } void SetStackColor(uint32_t * args, int argsLength){ uint32_t stackNum = args[1]; + uint32_t X_COORD{stackNum}; + while(X_COORD > BOARD_DIMENSIONS.x - 1){ + X_COORD -= BOARD_DIMENSIONS.x; + } + uint32_t Y_COORD{(stackNum - X_COORD) / BOARD_DIMENSIONS.y}; + uint32_t numColors = (argsLength - 2) / 3; - Color colors[numColors]; + V3D colors[numColors]; for(int i = 0; i < numColors; i++){ int red = args[2 + (i * 3)]; int green = args[3 + (i * 3)]; int blue = args[4 + (i * 3)]; - colors[i] = Color(red, green, blue); + colors[i] = V3D{red, green, blue}; } - board.SetStackColors(stackNum, colors); + boardManager.SetColumnColors(V3D{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors); } void parseData(Message<500, 10> &message){ @@ -104,12 +102,14 @@ void parseData(Message<500, 10> &message){ break; case Commands::SetStackColors: GlobalPrint::Println("!2;"); - colorManager.Enable(false); + // TODO: replace this with the animator + // colorManager.Enable(false); SetStackColor(reinterpret_cast(args), argsLength); break; case Commands::GoToIdle: GlobalPrint::Println("!3;"); - colorManager.Enable(true); + // TODO: replace this with the animator + // colorManager.Enable(true); break; default: GlobalPrint::Println("INVALID COMMAND"); @@ -144,20 +144,20 @@ void UpdateCommunication(void * params){ void UpdateBoard(void * params){ Serial.println("Spawning UpdateBoard task"); + auto updateTickRate{std::chrono::milliseconds(8)}; + auto boardStateTimer{std::chrono::milliseconds(0)}; + auto boardStateMaxUpdatePeriod{std::chrono::milliseconds(34)}; // this is a little slower than 30fps + for(;;){ - if(board.BoardStateHasChanged()){ - boardStateHasChanged = true; - } - - if(millis() - boardStateTimer > boardStateMaxUpdatePeriod && boardStateHasChanged){ - boardStateTimer = millis(); + if(boardStateTimer >= boardStateMaxUpdatePeriod && boardManager.HasBoardChanged()){ printBoardState(); - boardStateHasChanged = false; + boardManager.ClearBoardChanged(); } - colorManager.Update(); + boardManager.Update(); - vTaskDelay(5); + boardStateTimer += updateTickRate; + vTaskDelay(updateTickRate.count()); } Serial.println("UpdateBoard task has ended unexpectedly!"); } @@ -183,10 +183,8 @@ void setup() { xTaskCreate(UpdateCommunication, "UpdateCommunication", 10000, NULL, 0, &updateCommunicaitonTask); Serial.println("Beginning Board Initializaiton"); + boardManager.Init(); xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask); - Color colors[] = {Color(255, 0, 0), Color(0, 0, 0), Color(0, 0, 0)}; - board.SetStackColors(2, colors); - boardStateTimer = millis(); Serial.println("Setup Complete"); } From 5ba5924a29a3a601babab4bf79fa9e5220c00924 Mon Sep 17 00:00:00 2001 From: Quinn Date: Fri, 23 Aug 2024 17:37:30 -0400 Subject: [PATCH 04/12] Added comments to the functions in board.h --- lib/Board/Board.h | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/Board/Board.h b/lib/Board/Board.h index b16165c..b98e914 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -10,8 +10,7 @@ template class Board{ public: - - Board(); + Board() = default; ~Board() = default; constexpr const V3D &GetSize() const{return BOARD_DIMS;} @@ -26,17 +25,43 @@ class Board{ void ToStackString(String& stringBuffer) const; /** - * @brief Returns a bool array representing the board + * @returns Returns an array which contains how many cubes are in each z column on the board */ std::array &LinearizeBoard() const; + /** + * @brief fill the entire board with the given color + * @param color the color to fill the board with + */ void FillColor(const V3D &color); + /** + * @brief Set the color of the cube at the given position. + * Even if the cube is not occupied, it will remember this color + * and turn that color if it becomes occupied. + * @param position the position of the cube. + * @param color the color you want the cube to be + */ void SetCubeColor(const V3D &position, const V3D &color); + /** + * @brief Set the occupation status of the cube at a given position + * @param position the position of the cube + * @param the occupation status of the cube + * @post if the new occupation status of the cube is different than + * the old occupation status, this will enable boardStateHasChanged. + */ void SetCubeOccupation(const V3D &position, bool occupation); + /** + * @returns true if the board state has changed since this flag was last set to false + */ bool BoardStateChanged(){return this->boardStateHasChanged;} + + /** + * @brief set the board state status. + * @note you should probably only use this to clear the baordState flag + */ void SetStateChanged(bool boardState){this->boardStateHasChanged = boardState;} /** @@ -65,11 +90,6 @@ class Board{ bool boardStateHasChanged; }; -template -Board::Board(){ - this->FillColor(V3D{}); -} - template void Board::ToStackString(String &stringBuffer) const{ std::array linearizedBoard = this->LinearizeBoard(); From 5caa0a8a61174e50ad2fe79d25810056ddd1920c Mon Sep 17 00:00:00 2001 From: Quinn Date: Fri, 23 Aug 2024 17:48:54 -0400 Subject: [PATCH 05/12] Added comments to boarddriver.h --- lib/Board/Board.h | 5 +++++ lib/Board/BoardDriver.h | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/Board/Board.h b/lib/Board/Board.h index b98e914..d705b58 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -1,3 +1,8 @@ +/** + * @file Board.h + * @brief The Board class is meant to store data about each cube on the board and + * provide an easy way to organize and access that data in a 3-dimensional context. + */ #pragma once #include diff --git a/lib/Board/BoardDriver.h b/lib/Board/BoardDriver.h index c502f8f..4b200a7 100644 --- a/lib/Board/BoardDriver.h +++ b/lib/Board/BoardDriver.h @@ -1,3 +1,8 @@ +/** + * @file BoardDriver.h + * @brief the board driver provides helpful methods to read the physical board's state, + * filter those results, and send color data to the board + */ #pragma once #include @@ -13,15 +18,48 @@ template class BoardDriver{ public: + /** + * @param stacks a reference to an array which contains information about the pins which control each cube stack. + * @param pixelController a reference to a pre-constructed neopixel controller which will allow this driver to control neopixels + */ BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController); ~BoardDriver() = default; + /** + * @brief Initializes all of the pins necessary to read/write to the board + */ void Init(); + /** + * @param numXStacks the width of the board along the x dimension. + * @param X_COORD the x coordinate of the stack you want information about + * @param Y_COORD the y coordinate of the stack you want information about + * @returns Get the number of cubes present in a stack + */ uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD); + + /** + * @param stackIndex the index of the stack that you want to know the height of + * @returns Get the number of cubes present in a stack + */ uint32_t GetNumberCubes(uint32_t stackIndex); + /** + * @brief stream data to a single cube stack on the board + * @param numXStacks the width of the board along the x dimension. + * @param X_COORD the x coordinate of the stack you want information about + * @param Y_COORD the y coordinate of the stack you want information about + * @param cubes an array of cube pointers which contain the color data you want to stream + * @param numCubes the number of cubes in the cubes array + */ void UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube* cubes[], uint32_t numCubes); + + /** + * @brief stream data to a single cube stack on the board + * @param stackIndex the index of the stack you want to update the color of + * @param cubes an array of cube pointers which contain the color data you want to stream + * @param numCubes the number of cubes in the cubes array + */ void UpdateStackLEDs(uint32_t stackIndex, Cube* cubes[], uint32_t numCubes); From a59a6657e8cee952978c794c65cb3a3174154832 Mon Sep 17 00:00:00 2001 From: Quinn Date: Sat, 24 Aug 2024 14:07:12 -0400 Subject: [PATCH 06/12] Finished commenting code changes --- lib/Board/Board.h | 10 ++++----- lib/Board/BoardDriver.h | 46 +++++++++++++++++++++++++++++++++------- lib/Board/BoardManager.h | 33 +++++++++++++++++++++++++++- lib/Board/BoardTypes.h | 10 +++++++++ lib/Board/Cube.h | 14 ------------ 5 files changed, 85 insertions(+), 28 deletions(-) delete mode 100644 lib/Board/Cube.h diff --git a/lib/Board/Board.h b/lib/Board/Board.h index d705b58..f3b51d8 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -9,7 +9,7 @@ #include #include -#include "Cube.h" +#include "BoardTypes.h" #include "Vector3D.h" template @@ -76,7 +76,7 @@ class Board{ * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) * @returns an array of cubes along that column */ - Cube ** SliceBoard(const V3D &column); + BOARD_TYPES::Cube ** SliceBoard(const V3D &column); private: // this is a 3d array of cubes to represent the board. Good luck visualizing it @@ -90,7 +90,7 @@ class Board{ | | / |____________|/ */ - std::array, BOARD_DIMS.y>, BOARD_DIMS.x> cubes; + std::array, BOARD_DIMS.y>, BOARD_DIMS.x> cubes; bool boardStateHasChanged; }; @@ -145,7 +145,7 @@ void Board::SetCubeOccupation(const V3D &position, bool occupation){ } template -Cube ** Board::SliceBoard(const V3D &column){ +BOARD_TYPES::Cube ** Board::SliceBoard(const V3D &column){ uint32_t columnLength{0}; V3D indexIncriment{}; V3D position{}; @@ -172,7 +172,7 @@ Cube ** Board::SliceBoard(const V3D &column){ break; } - Cube* columnSlice[columnLength]; + BOARD_TYPES::Cube* columnSlice[columnLength]; for(uint32_t i = 0; i < columnLength; i++){ V3D cubePosition = indexIncriment * i + position; columnSlice[i] = &(this->cubes[cubePosition.x][cubePosition.y][cubePosition.z]); diff --git a/lib/Board/BoardDriver.h b/lib/Board/BoardDriver.h index 4b200a7..6180620 100644 --- a/lib/Board/BoardDriver.h +++ b/lib/Board/BoardDriver.h @@ -11,7 +11,7 @@ #include #include -#include "Cube.h" +#include "BoardTypes.h" #include "BoardTypes.h" template @@ -22,7 +22,10 @@ public: * @param stacks a reference to an array which contains information about the pins which control each cube stack. * @param pixelController a reference to a pre-constructed neopixel controller which will allow this driver to control neopixels */ - BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController); + BoardDriver( + std::array &stacks, + Adafruit_NeoPixel &pixelController + ); ~BoardDriver() = default; /** @@ -52,7 +55,13 @@ public: * @param cubes an array of cube pointers which contain the color data you want to stream * @param numCubes the number of cubes in the cubes array */ - void UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube* cubes[], uint32_t numCubes); + void UpdateStackLEDs( + uint32_t numXStacks, + uint32_t X_COORD, + uint32_t Y_COORD, + BOARD_TYPES::Cube* cubes[], + uint32_t numCubes + ); /** * @brief stream data to a single cube stack on the board @@ -60,7 +69,11 @@ public: * @param cubes an array of cube pointers which contain the color data you want to stream * @param numCubes the number of cubes in the cubes array */ - void UpdateStackLEDs(uint32_t stackIndex, Cube* cubes[], uint32_t numCubes); + void UpdateStackLEDs( + uint32_t stackIndex, + BOARD_TYPES::Cube* cubes[], + uint32_t numCubes + ); private: @@ -84,7 +97,10 @@ void BoardDriver::Init(){ } template -BoardDriver::BoardDriver(std::array &stacks, Adafruit_NeoPixel &pixelController): +BoardDriver::BoardDriver( + std::array &stacks, + Adafruit_NeoPixel &pixelController + ): stacks(stacks), pixelController(pixelController) { @@ -94,12 +110,22 @@ pixelController(pixelController) } template -void BoardDriver::UpdateStackLEDs(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD, Cube* cubes[], uint32_t numCubes){ +void BoardDriver::UpdateStackLEDs( + uint32_t numXStacks, + uint32_t X_COORD, + uint32_t Y_COORD, + BOARD_TYPES::Cube* cubes[], + uint32_t numCubes + ){ this->UpdateStackLEDs(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks), cubes, numCubes); } template -void BoardDriver::UpdateStackLEDs(uint32_t stackIndex, Cube* cubes[], uint32_t numCubes){ +void BoardDriver::UpdateStackLEDs( + uint32_t stackIndex, + BOARD_TYPES::Cube* cubes[], + uint32_t numCubes + ){ this->pixelController.setPin(this->stacks[stackIndex].ledPin); for(int i = 0; i < numCubes; i++){ V3D color{cubes[i]->color}; @@ -110,7 +136,11 @@ void BoardDriver::UpdateStackLEDs(uint32_t stackIndex, Cube* cubes[] } template -uint32_t BoardDriver::GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD){ +uint32_t BoardDriver::GetNumberCubes( + uint32_t numXStacks, + uint32_t X_COORD, + uint32_t Y_COORD + ){ return this->GetNumberCubes(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks)); } diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h index 9a0d78a..2527057 100644 --- a/lib/Board/BoardManager.h +++ b/lib/Board/BoardManager.h @@ -1,3 +1,7 @@ +/** + * @file BoardManager.h + * @brief Provides an easy way to manage a board + */ #pragma once #include "Board.h" @@ -11,12 +15,30 @@ class BoardManager{ ~BoardManager() = default; + /** + * @brief Initialize the board control. This hsould be called during setup + */ void Init(); + /** + * @brief Read the board state and update the LED colors + */ void Update(); + /** + * @brief Set the color of one cube + * @param position the position of the cube + * @param color the oclor you want the cube to be + */ void SetCubeColor(const V3D &position, const V3D &color); + /** + * @brief Set the color of one column of cubes. + * A column is defined as x,y,z where z is the normal vector direction to a plane. + * x,y is the column coordinate on the plane. + * @param column the column vector + * @param color the color you want the column to be + */ void SetColumnColors(const V3D &column, const V3D *color); @@ -29,10 +51,19 @@ class BoardManager{ */ void FillColumnColor(const V3D &column, const V3D &color); + /** + * @returns true if the board has changed state + */ bool HasBoardChanged(); + /** + * @brief Clear the board state changed flag + */ void ClearBoardChanged(); + /** + * @brief Get the board occupation state returned in the format a,b,c,d.... + */ String &Board2StackString(); private: @@ -89,7 +120,7 @@ void BoardManager::Update(){ template void BoardManager::updateBoardColors(const V3D &column){ - Cube ** cubeSlice{this->board.SliceBoard(column)}; + BOARD_TYPES::Cube ** cubeSlice{this->board.SliceBoard(column)}; uint32_t numCubes{this->getColumnHeight(static_cast(column.z))}; this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes); } diff --git a/lib/Board/BoardTypes.h b/lib/Board/BoardTypes.h index e83e1a1..812c341 100644 --- a/lib/Board/BoardTypes.h +++ b/lib/Board/BoardTypes.h @@ -1,6 +1,11 @@ +/** + * @file BoardTypes.h + * @brief Some types to that you'll need to define and control the board + */ #pragma once #include +#include "Vector3D.h" namespace BOARD_TYPES{ struct CubeStack{ @@ -13,4 +18,9 @@ namespace BOARD_TYPES{ Y, Z }; + + struct Cube{ + V3D color; + bool isOccupied{false}; + }; }; \ No newline at end of file diff --git a/lib/Board/Cube.h b/lib/Board/Cube.h deleted file mode 100644 index 8e2bc57..0000000 --- a/lib/Board/Cube.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file Cube.h - * @brief An object to store the data of one cube - */ - -#pragma once - -#include "Vector3D.h" - -class Cube{ - public: - V3D color; - bool isOccupied{false}; -}; \ No newline at end of file From 4d47b68600e31801286b87ed48264d04b25b38db Mon Sep 17 00:00:00 2001 From: Quinn Date: Sun, 25 Aug 2024 12:06:52 -0400 Subject: [PATCH 07/12] Fixed several buffer overflow issues --- include/BOARD-DEFINITIONS.h | 4 ++ include/Vector3D.h | 52 +++++++++++--------- lib/Board/Board.h | 97 ++++++++++++++++++++++--------------- lib/Board/BoardDriver.h | 3 +- lib/Board/BoardManager.h | 67 ++++++++++++++----------- src/main.cpp | 22 +++++---- 6 files changed, 142 insertions(+), 103 deletions(-) diff --git a/include/BOARD-DEFINITIONS.h b/include/BOARD-DEFINITIONS.h index db1aa63..4dd56da 100644 --- a/include/BOARD-DEFINITIONS.h +++ b/include/BOARD-DEFINITIONS.h @@ -11,6 +11,10 @@ #include "BoardTypes.h" #include "Vector3D.h" +// define some important buffer sizes +static constexpr uint32_t SERIAL_ARG_LENGTH{15}; +static constexpr uint32_t SERIAL_CHAR_LENGTH{SERIAL_ARG_LENGTH*10}; + // define the physical dimensions of the board static constexpr uint32_t BOARD_WIDTH{3}; static constexpr uint32_t BOARD_LENGTH{3}; diff --git a/include/Vector3D.h b/include/Vector3D.h index 6f84399..fdcb83d 100644 --- a/include/Vector3D.h +++ b/include/Vector3D.h @@ -5,6 +5,11 @@ class V3D{ public: + constexpr V3D(const V3D& other): + x(other.x), + y(other.y), + z(other.z){} + constexpr V3D(uint32_t x=0, uint32_t y=0, uint32_t z=0): x(x), y(y), @@ -17,36 +22,35 @@ class V3D{ return *this; } - V3D& operator+(const V3D &other){ - V3D vector{}; - vector.x = this->x + other.x; - vector.y = this->y + other.y; - vector.z = this->z + other.z; - return vector; + V3D& operator+=(const V3D &other){ + this->x += other.x; + this->y += other.y; + this->z += other.z; + return *this; } - V3D& operator-(const V3D &other){ - V3D vector{}; - vector.x = this->x - other.x; - vector.y = this->y - other.y; - vector.z = this->z - other.z; - return vector; + V3D& operator-=(const V3D &other){ + this->x -= other.x; + this->y -= other.y; + this->z -= other.z; + return *this; } - V3D operator/(const uint32_t scalar){ - V3D vector{}; - vector.x = this->x / scalar; - vector.y = this->y / scalar; - vector.z = this->z / scalar; - return vector; + V3D& operator/=(const uint32_t scalar){ + if(scalar == 0){ + return *this; + } + this->x /= scalar; + this->y /= scalar; + this->z /= scalar; + return *this; } - V3D operator*(const uint32_t scalar){ - V3D vector{}; - vector.x = this->x * scalar; - vector.y = this->y * scalar; - vector.z = this->z * scalar; - return vector; + V3D& operator*=(const uint32_t scalar){ + this->x *= scalar; + this->y *= scalar; + this->z *= scalar; + return *this; } bool operator==(const V3D &other){ diff --git a/lib/Board/Board.h b/lib/Board/Board.h index f3b51d8..4133f1d 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -21,6 +21,7 @@ class Board{ constexpr const V3D &GetSize() const{return BOARD_DIMS;} constexpr uint32_t GetNumberCubes() const{return BOARD_DIMS.x * BOARD_DIMS.y * BOARD_DIMS.z;} + constexpr uint32_t GetMaxDimension(){return std::max(std::max(BOARD_DIMS.x, BOARD_DIMS.y), BOARD_DIMS.z);} /** * @brief Returns a string in the format: * !a,b,c,d,e,f,g,h,i; @@ -29,11 +30,6 @@ class Board{ */ void ToStackString(String& stringBuffer) const; - /** - * @returns Returns an array which contains how many cubes are in each z column on the board - */ - std::array &LinearizeBoard() const; - /** * @brief fill the entire board with the given color * @param color the color to fill the board with @@ -70,14 +66,19 @@ class Board{ void SetStateChanged(bool boardState){this->boardStateHasChanged = boardState;} /** - * @brief Get a column along any axis + * @brief Get a column along any axis read into the sliceBuffer * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and * the x,y values specify the location of the column in that plane * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) - * @returns an array of cubes along that column + * @param sliceBuffer an array of pointers to the cubes along that column + * @returns the number of elements written into the slice buffer + * @note That array is stored locally and will be overwritten everytime this function is called. + * Also, any unused spots at the end of the array will be nullptrs + * @warning allocate the size of the slice buffer using GetMaxDimension if you don't know what you're doing! */ - BOARD_TYPES::Cube ** SliceBoard(const V3D &column); + uint32_t SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer); + void PrintEntireBoard() const; private: // this is a 3d array of cubes to represent the board. Good luck visualizing it /* _____________ @@ -97,28 +98,25 @@ class Board{ template void Board::ToStackString(String &stringBuffer) const{ - std::array linearizedBoard = this->LinearizeBoard(); + std::array linearizedBoard; + for(uint32_t x{0}; x < BOARD_DIMS.x; x++){ + for(uint32_t y{0}; y < BOARD_DIMS.y; y++){ + uint32_t boardIndex{x + y*3}; + linearizedBoard[boardIndex] = 0; + for(uint32_t z{0}; z < BOARD_DIMS.z; z++){ + linearizedBoard[boardIndex] += this->cubes[x][y][z].isOccupied; + } + } + } stringBuffer += String(linearizedBoard[0]); for(uint32_t i = 0; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ stringBuffer += "," + String(linearizedBoard[i]); } -} + // TODO: Delete this before merging into develop + this->PrintEntireBoard(); -template -std::array & Board::LinearizeBoard() const{ - // convert the board into one array where each entry represents the height of one stack - std::array linearizedBoard; - for(uint32_t x{0}; x < BOARD_DIMS.x; x++){ - for(uint32_t y{0}; y < BOARD_DIMS.y; y++){ - for(uint32_t z{0}; z < BOARD_DIMS.z; z++){ - bool isOccupied{this->cubes[x][y][z].isOccupied}; - linearizedBoard[x + y*3] += static_cast(isOccupied); - } - } - } - return linearizedBoard; } template @@ -145,39 +143,58 @@ void Board::SetCubeOccupation(const V3D &position, bool occupation){ } template -BOARD_TYPES::Cube ** Board::SliceBoard(const V3D &column){ +uint32_t Board::SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer){ uint32_t columnLength{0}; - V3D indexIncriment{}; - V3D position{}; + V3D indexIncrimentVector{}; + V3D indexVector{}; + switch(column.z){ case BOARD_TYPES::PLANE_NORMAL::X: columnLength = BOARD_DIMS.x; - indexIncriment.x = 1; - position.z = column.x; - position.y = column.y; + indexIncrimentVector.x = 1; + indexVector.z = column.x; + indexVector.y = column.y; break; case BOARD_TYPES::PLANE_NORMAL::Y: columnLength = BOARD_DIMS.y; - indexIncriment.y = 1; - position.x = column.x; - position.z = column.y; + indexIncrimentVector.y = 1; + indexVector.x = column.x; + indexVector.z = column.y; break; default: case BOARD_TYPES::PLANE_NORMAL::Z: columnLength = BOARD_DIMS.z; - indexIncriment.z = 1; - position.x = column.x; - position.y = column.y; + indexIncrimentVector.z = 1; + indexVector.x = column.x; + indexVector.y = column.y; break; } - BOARD_TYPES::Cube* columnSlice[columnLength]; for(uint32_t i = 0; i < columnLength; i++){ - V3D cubePosition = indexIncriment * i + position; - columnSlice[i] = &(this->cubes[cubePosition.x][cubePosition.y][cubePosition.z]); + if(indexVector.x >= BOARD_DIMS.x || indexVector.y >= BOARD_DIMS.y || indexVector.z >= BOARD_DIMS.z){ + Serial.println("Board::SliceBoard: Index Out of Bounds:" + String(indexVector.x) + "," + String(indexVector.y) + "," + String(indexVector.z)); + return 0; + } + sliceBuffer[i] = &(this->cubes[indexVector.x][indexVector.y][indexVector.z]); + indexVector += indexIncrimentVector; + } + return columnLength; +} + +template +void Board::PrintEntireBoard() const{ + Serial.println("begin"); + + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ + const BOARD_TYPES::Cube &cube = this->cubes[x][y][z]; + Serial.print("Cube X:" + String(x) + ",Y:" + String(y) + ",Z:" + String(z)); + Serial.print("\tColor R:" + String(cube.color.x) + ",G:" + String(cube.color.y) + ",B:" + String(cube.color.z)); + Serial.println("\tOccupied? " + String(cube.isOccupied)); + } + } } - - return columnSlice; } diff --git a/lib/Board/BoardDriver.h b/lib/Board/BoardDriver.h index 6180620..7be2a35 100644 --- a/lib/Board/BoardDriver.h +++ b/lib/Board/BoardDriver.h @@ -90,6 +90,7 @@ template void BoardDriver::Init(){ for(uint32_t i = 0; i < NUM_STACKS; i++){ pinMode(this->stacks[i].ledPin, OUTPUT); + filteredReadings[i] = 0; } // begin doesn't really do anything besides setting the pinmode @@ -159,7 +160,6 @@ uint32_t BoardDriver::GetNumberCubes(uint32_t stackIndex){ (static_cast(this->filteredReadings[stackIndex]) * 0.9) + (static_cast(value) * 0.1) ); - // temporary definitions to define value ranges: uint16_t zeroCubesHigh = 4095; uint16_t zeroCubesLow = 3400; @@ -182,5 +182,6 @@ uint32_t BoardDriver::GetNumberCubes(uint32_t stackIndex){ stackHeight = 3; } + this->filteredReadings[stackIndex] = lowPassADCRead; return stackHeight; } \ No newline at end of file diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h index 2527057..5dff2c5 100644 --- a/lib/Board/BoardManager.h +++ b/lib/Board/BoardManager.h @@ -39,7 +39,7 @@ class BoardManager{ * @param column the column vector * @param color the color you want the column to be */ - void SetColumnColors(const V3D &column, const V3D *color); + void SetColumnColors(const V3D &column, const V3D *color, uint32_t numColors); /** @@ -64,15 +64,13 @@ class BoardManager{ /** * @brief Get the board occupation state returned in the format a,b,c,d.... */ - String &Board2StackString(); + void Board2StackString(String& messageBuffer); private: BoardDriver &driver; Board board{}; - bool hasBoardChanged{false}; - - void updateBoardColors(const V3D &column); + void updateStackColors(const V3D &column); uint32_t getColumnHeight(BOARD_TYPES::PLANE_NORMAL normal){ switch(normal){ @@ -110,17 +108,30 @@ void BoardManager::Update(){ uint32_t numCubes{this->driver.GetNumberCubes(stackIndex)}; for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ V3D cubePosition{x, y, z}; + // update the cube's occupation this->board.SetCubeOccupation(cubePosition, z < numCubes); - cubePosition.z = BOARD_TYPES::PLANE_NORMAL::Z; - this->driver.UpdateStackLEDs(stackIndex, this->board.SliceBoard(cubePosition), BOARD_DIMS.z); } + + // create the column vector for the slice direction + V3D sliceVector{x,y,BOARD_TYPES::PLANE_NORMAL::Z}; + // create a cube slice array buffer + BOARD_TYPES::Cube* sliceBuffer[BOARD_DIMS.z]; + // have the board slice get read into our buffer + this->board.SliceBoard(sliceVector, sliceBuffer); + // send the board slice to the driver to update its LED colors + this->driver.UpdateStackLEDs(stackIndex, sliceBuffer, BOARD_DIMS.z); } } } template -void BoardManager::updateBoardColors(const V3D &column){ - BOARD_TYPES::Cube ** cubeSlice{this->board.SliceBoard(column)}; +void BoardManager::updateStackColors(const V3D &column){ + // the only column type allowed here is z. + V3D sliceVector{column.x, column.y, BOARD_TYPES::Z}; + // create a buffer for slice board to write the cube slice into + BOARD_TYPES::Cube * cubeSlice[BOARD_DIMS.z]; + this->board.SliceBoard(column, cubeSlice); + uint32_t numCubes{this->getColumnHeight(static_cast(column.z))}; this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes); } @@ -129,45 +140,45 @@ template void BoardManager::SetCubeColor(const V3D &position, const V3D &color){ this->board.SetCubeColor(position, color); V3D slice{position.x, position.y, BOARD_TYPES::PLANE_NORMAL::Z}; - this->updateBoardColors(slice); + this->updateStackColors(slice); } template -void BoardManager::SetColumnColors(const V3D &column, const V3D *color){ +void BoardManager::SetColumnColors(const V3D &column, const V3D *color, uint32_t numColors){ uint32_t columnHeight{this->getColumnHeight(static_cast(column.z))}; - V3D position = column; - for(uint32_t z = 0; z < columnHeight; z++){ - position.z = z; - this->board.SetCubeColor(position, color[z]); + // create a cube pointer buffer and store a board slice into it + BOARD_TYPES::Cube * slicedBoard[columnHeight]; + Serial.println("moments before slicing"); + uint32_t sliceLength{this->board.SliceBoard(column, slicedBoard)}; + Serial.println("setting colors"); + uint32_t maxIndex{std::min(numColors, columnHeight)}; + for(uint32_t i = 0; i < columnHeight; i++){ + slicedBoard[i]->color = color[i]; } - - this->updateBoardColors(column); + Serial.println("End of SetColumnColors"); } template void BoardManager::FillColumnColor(const V3D &column, const V3D &color){ uint32_t columnHeight{this->getColumnHeight(column.z)}; - V3D position = column; - for(uint32_t z = 0; z < columnHeight; z++){ - position.z = z; - this->board.SetCubeColor(position, color); + V3D colors[columnHeight]; + for(uint32_t i = 0; i < columnHeight; i++){ + colors[i] = color; } - this->updateBoardColors(column); + this->SetColumnColors(column, colors); } template -bool BoardManager::HasBoardChanged(){return this->hasBoardChanged;} +bool BoardManager::HasBoardChanged(){return this->board.BoardStateChanged();} template -void BoardManager::ClearBoardChanged(){this->hasBoardChanged = false;} +void BoardManager::ClearBoardChanged(){this->board.SetStateChanged(false);} template -String &BoardManager::Board2StackString(){ - String message{}; - this->board.ToStackString(message); - return message; +void BoardManager::Board2StackString(String& messageBuffer){ + this->board.ToStackString(messageBuffer); } diff --git a/src/main.cpp b/src/main.cpp index 294b4be..924f45f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,7 @@ TaskHandle_t updateBoardTask; // BluetoothSerial SerialBT; // BluetoothSerialMessage serialMessageBT(&SerialBT); -SerialMessage<500, 10> serialMessage(&Serial); +SerialMessage serialMessage(&Serial); Adafruit_NeoPixel pixelController{BOARD_HEIGHT*2, STACK1_LED_PIN, NEO_GRB + NEO_KHZ800}; @@ -64,7 +64,9 @@ void SetupBluetoothModule(){ void printBoardState(){ GlobalPrint::Print("!0,"); - GlobalPrint::Print(boardManager.Board2StackString()); + String boardString; + boardManager.Board2StackString(boardString); + GlobalPrint::Print(boardString); GlobalPrint::Println(";"); } @@ -80,21 +82,21 @@ void SetStackColor(uint32_t * args, int argsLength){ V3D colors[numColors]; for(int i = 0; i < numColors; i++){ - int red = args[2 + (i * 3)]; - int green = args[3 + (i * 3)]; - int blue = args[4 + (i * 3)]; + uint32_t red = args[2 + (i * 3)]; + uint32_t green = args[3 + (i * 3)]; + uint32_t blue = args[4 + (i * 3)]; colors[i] = V3D{red, green, blue}; } - - boardManager.SetColumnColors(V3D{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors); + boardManager.SetColumnColors(V3D{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors, numColors); } -void parseData(Message<500, 10> &message){ +void parseData(Message &message){ int32_t * args{message.GetArgs()}; - uint32_t argsLength{message.GetArgsLength()}; + uint32_t argsLength{message.GetPopulatedArgs()}; uint32_t command = args[0]; switch(command){ case Commands::BoardState: + GlobalPrint::Println("Test"); printBoardState(); break; case Commands::PING: @@ -154,7 +156,7 @@ void UpdateBoard(void * params){ boardManager.ClearBoardChanged(); } - boardManager.Update(); + // boardManager.Update(); boardStateTimer += updateTickRate; vTaskDelay(updateTickRate.count()); From 61ff0bbbfdebe3a04e8b39c8abcd102dd567bffe Mon Sep 17 00:00:00 2001 From: Quinn Date: Sun, 25 Aug 2024 12:12:46 -0400 Subject: [PATCH 08/12] The board now works as expected but without idle animations --- lib/Board/Board.h | 5 ----- lib/Board/BoardManager.h | 3 --- src/main.cpp | 3 +-- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/Board/Board.h b/lib/Board/Board.h index 4133f1d..16ba9bc 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -114,9 +114,6 @@ void Board::ToStackString(String &stringBuffer) const{ for(uint32_t i = 0; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ stringBuffer += "," + String(linearizedBoard[i]); } - // TODO: Delete this before merging into develop - this->PrintEntireBoard(); - } template @@ -184,8 +181,6 @@ uint32_t Board::SliceBoard(const V3D &column, BOARD_TYPES::Cube ** s template void Board::PrintEntireBoard() const{ - Serial.println("begin"); - for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h index 5dff2c5..6f273eb 100644 --- a/lib/Board/BoardManager.h +++ b/lib/Board/BoardManager.h @@ -149,15 +149,12 @@ void BoardManager::SetColumnColors(const V3D &column, const V3D *col // create a cube pointer buffer and store a board slice into it BOARD_TYPES::Cube * slicedBoard[columnHeight]; - Serial.println("moments before slicing"); uint32_t sliceLength{this->board.SliceBoard(column, slicedBoard)}; - Serial.println("setting colors"); uint32_t maxIndex{std::min(numColors, columnHeight)}; for(uint32_t i = 0; i < columnHeight; i++){ slicedBoard[i]->color = color[i]; } - Serial.println("End of SetColumnColors"); } template diff --git a/src/main.cpp b/src/main.cpp index 924f45f..e297b81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,7 +96,6 @@ void parseData(Message &message){ uint32_t command = args[0]; switch(command){ case Commands::BoardState: - GlobalPrint::Println("Test"); printBoardState(); break; case Commands::PING: @@ -156,7 +155,7 @@ void UpdateBoard(void * params){ boardManager.ClearBoardChanged(); } - // boardManager.Update(); + boardManager.Update(); boardStateTimer += updateTickRate; vTaskDelay(updateTickRate.count()); From 240d4866aabf35dec1dc8f1ff2430d51491b8854 Mon Sep 17 00:00:00 2001 From: Quinn Date: Sun, 25 Aug 2024 14:30:08 -0400 Subject: [PATCH 09/12] Got the animator running --- lib/Animator/AnimationTypes.h | 33 +++ lib/Animator/Animator.h | 372 ++++++++++++++-------------------- lib/Animator/TestFrames.h | 158 +++++++++++++++ lib/Board/Board.h | 10 + lib/Board/BoardManager.h | 20 +- src/main.cpp | 32 ++- 6 files changed, 397 insertions(+), 228 deletions(-) create mode 100644 lib/Animator/AnimationTypes.h create mode 100644 lib/Animator/TestFrames.h diff --git a/lib/Animator/AnimationTypes.h b/lib/Animator/AnimationTypes.h new file mode 100644 index 0000000..482ceeb --- /dev/null +++ b/lib/Animator/AnimationTypes.h @@ -0,0 +1,33 @@ +#pragma once +#include "Vector3D.h" +#include +#include + +namespace ANIMATION_TYPES{ + // for cube spots which aren't defined in a key frame, + // you can have the controller automatically interpolate a color + enum FillInterpolation{ + NO_FILL, // if not specified, the cube color will be black + CLOSEST_COLOR, // The cube color will be the same color as the cube closest to it + LINEAR_WEIGHTED_DISTANCE, // the cube color will be an average of all specified cube colors weighted by the linear distance to this cube + SQUARE_WEIGHTED_DISTANCE // same as linear, but further colors have exponentially less impact on the color + }; + + enum FrameInterpolation{ + SNAP, // After the delay, snap to the next key frame + FADE // over the course of the delay, fade to the next frame + }; + + struct Cell{ + V3D position; + V3D color; + }; + + // this contains all of the information to specify exactly how a single frame should look and fade to the next frame + struct AnimationFrame{ + std::vector frame; + FillInterpolation fillInterpolation; + FrameInterpolation frameInterpolation; + std::chrono::milliseconds delay; + }; +}; \ No newline at end of file diff --git a/lib/Animator/Animator.h b/lib/Animator/Animator.h index 37f384f..0ee4d98 100644 --- a/lib/Animator/Animator.h +++ b/lib/Animator/Animator.h @@ -2,68 +2,114 @@ #include #include +#include #include #include #include #include #include "Vector3D.h" +#include "AnimationTypes.h" +using namespace ANIMATION_TYPES; + +template +class Animator{ -class Animation{ public: -// for cube spots which aren't defined in a key frame, -// you can have the controller automatically interpolate a color -enum FillInterpolation{ - NO_FILL, // if not specified, the cube color will be black - CLOSEST_COLOR, // The cube color will be the same color as the cube closest to it - LINEAR_WEIGHTED_DISTANCE, // the cube color will be an average of all specified cube colors weighted by the linear distance to this cube - SQUARE_WEIGHTED_DISTANCE // same as linear, but further colors have exponentially less impact on the color +typedef std::array, BOARD_DIMS.y>, BOARD_DIMS.x> Frame; + + +void StartAnimation(const std::vector *animationSequence); + +void RunAnimation(const std::chrono::milliseconds& timePassed); + +void SetLoop(bool isLooping); + +bool HasInterpolatedFrameChanged(){return this->interpolatedFrameHasChanged;} + +Frame &GetInterpolatedFrame(){return this->interpolatedFrame;} + +bool isEnabled{true}; + +private: +bool isLooping{true}; +bool interpolatedFrameHasChanged{false}; +// these are the uncompressed frames you get by following the key colors and interpolation instructions of an animation frame +Frame startFrame; +Frame interpolatedFrame; +Frame endFrame; +std::chrono::milliseconds timeElapsed; + +const std::vector *animationSequence; +uint32_t animationIndex{0}; + +void incrimentAnimationIndex(); + +void uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer); + +void copyFrame(Frame ©From, Frame ©To){ + std::memcpy(©To, ©From, sizeof(Frame)); +} + +V3D getInterpolatedColor(const AnimationFrame &keyFrame, V3D position); + +V3D keyFrame2BoardCoords(const V3D &keyFramePosition); + +V3D noFillInterpolate(const AnimationFrame &keyFrame, V3D position); + +V3D closestColorInterpolate(const AnimationFrame &keyFrame, V3D position); + +V3D linearInterpolate(const AnimationFrame &keyFrame, V3D position); + +V3D squareInterpolate(const AnimationFrame &keyFrame, V3D position); + +void PrintUncompressedFrame(){ + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ + auto color = this->startFrame[x][y][z]; + Serial.print("Cube X:" + String(x) + ",Y:" + String(y) + ",Z:" + String(z)); + Serial.println("\tColor R:" + String(color.x) + ",G:" + String(color.y) + ",B:" + String(color.z)); + } + } + } +} + }; -enum FrameInterpolation{ - SNAP, // After the delay, snap to the next key frame - FADE // over the course of the delay, fade to the next frame -}; +template +void Animator::StartAnimation(const std::vector *animationSequence){ -struct Cell{ - V3D position; - V3D color; -}; - -// this contains all of the information to specify exactly how a single frame should look and fade to the next frame -struct AnimationFrame{ - std::vector frame; - FillInterpolation fillInterpolation; - FrameInterpolation frameInterpolation; - std::chrono::milliseconds delay; -}; - -typedef std::array, Y_SIZE>, X_SIZE> Frame; - - -void StartAnimation(const std::vector &animationSequence){ this->animationSequence = animationSequence; this->animationIndex = 0; this->timeElapsed = std::chrono::milliseconds(0); - if(animationSequence.size() == 0){ + if(animationSequence->size() == 0){ return; } - else if(animationSequence.size() == 1){ - this->uncompressFrame(animationSequence[0], this->startFrame); - this->uncompressFrame(animationSequence[0], this->endFrame); + else if(animationSequence->size() == 1){ + AnimationFrame frame{((*this->animationSequence)[0])}; + this->uncompressFrame(frame, this->startFrame); + this->copyFrame(this->startFrame, this->interpolatedFrame); + this->copyFrame(this->startFrame, this->endFrame); } else{ - this->uncompressFrame(animationSequence[0], this->startFrame); - this->uncompressFrame(animationSequence[1], this->endFrame); + this->uncompressFrame((*this->animationSequence)[0], this->startFrame); + this->copyFrame(this->startFrame, this->interpolatedFrame); + this->uncompressFrame((*this->animationSequence)[1], this->endFrame); } + this->PrintUncompressedFrame(); } -Frame &RunAnimation(std::chrono::milliseconds timePassed){ - auto delayTime = this->animationSequence[this->animationIndex].delay; +template +void Animator::RunAnimation(const std::chrono::milliseconds& timePassed){ + if(!(this->isEnabled)){ + this->interpolatedFrameHasChanged = false; + } + + auto delayTime = (*this->animationSequence)[this->animationIndex].delay; this->timeElapsed += timePassed; - Frame interpolatedFrame; // load in the next frame if we're done with this transition if(this->timeElapsed >= delayTime){ @@ -71,61 +117,66 @@ Frame &RunAnimation(std::chrono::milliseconds timePassed){ } // don't do frame interpolations if we're doing snap fades - if(this->animationSequence[this->animationIndex].frameInterpolation == FrameInterpolation::SNAP){ - return; + if((*this->animationSequence)[this->animationIndex].frameInterpolation == FrameInterpolation::SNAP){ + this->interpolatedFrameHasChanged = false; } // linearly interpolate between the two uncompressed frames - for(uint32_t x = 0; x < this->X_SIZE; x++){ - for(uint32_t y = 0; y < this->Y_SIZE; y++){ - for(uint32_t z = 0; z < this->Z_SIZE; z++){ + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ V3D startColor{this->startFrame[x][y][z]}; V3D endColor{this->endFrame[x][y][z]}; - V3D difference{endColor - startColor}; - V3D interpolatedColor = this->timeElapsed.count() * difference / delayTime.count() + startColor; - interpolatedFrame[x][y][z] = interpolatedColor; + V3D difference{endColor}; + difference -= startColor; + + V3D interpolatedColor{difference}; + interpolatedColor *= this->timeElapsed.count(); + interpolatedColor /= delayTime.count(); + interpolatedColor += startColor; + + this->interpolatedFrame[x][y][z] = interpolatedColor; } } } + + this->interpolatedFrameHasChanged = true; } -void SetLoop(bool isLooping){ +template +void Animator::SetLoop(bool isLooping){ this->isLooping = isLooping; } -private: -uint32_t X_SIZE{3}; -uint32_t Y_SIZE{3}; -uint32_t Z_SIZE{3}; -bool isLooping{true}; -// these are the uncompressed frames you get by following the key colors and interpolation instructions of an animation frame -Frame startFrame; -Frame endFrame; -std::chrono::milliseconds timeElapsed; - -const std::vector & animationSequence; -uint32_t animationIndex{0}; - -void incrimentAnimationIndex(){ - if(this->animationIndex < animationSequence.size() - 1){ +template +void Animator::incrimentAnimationIndex(){ + if(this->animationIndex < this->animationSequence->size() - 1){ this->animationIndex++; - this->timeElapsed = std::chrono::millis(0); - this->uncompressFrame(this->animationSequence[this->animationIndex], this->startFrame); - this->uncompressFrame(this->animationSequence[this->animationIndex + 1], this->endFrame); + this->timeElapsed = std::chrono::milliseconds(0); + this->uncompressFrame((*this->animationSequence)[this->animationIndex], this->startFrame); + this->uncompressFrame((*this->animationSequence)[this->animationIndex + 1], this->endFrame); } + else if(isLooping){ + this->StartAnimation(this->animationSequence); + } + + Serial.println("Animator::incrimentAnimationIndex" + String(animationIndex)); + this->PrintUncompressedFrame(); } -void uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer){ - for(uint32_t x = 0; x < X_SIZE; x++){ - for(uint32_t y = 0; y < Y_SIZE; y++){ - for(uint32_t z = 0; z < Z_SIZE; z++){ +template +void Animator::uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer){ + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ frameBuffer[x][y][z] = getInterpolatedColor(keyFrame, V3D(x, y, z)); } } } } -V3D &getInterpolatedColor(const AnimationFrame &keyFrame, V3D position){ +template +V3D Animator::getInterpolatedColor(const AnimationFrame &keyFrame, V3D position){ switch(keyFrame.fillInterpolation){ case FillInterpolation::NO_FILL: return noFillInterpolate(keyFrame, position); @@ -136,17 +187,19 @@ V3D &getInterpolatedColor(const AnimationFrame &keyFrame, V3D position){ case FillInterpolation::SQUARE_WEIGHTED_DISTANCE: return squareInterpolate(keyFrame, position); default: - return V3D{}; + V3D black{}; + return black; } } -V3D &keyFrame2BoardCoords(V3D &keyFramePosition){ +template +V3D Animator::keyFrame2BoardCoords(const V3D &keyFramePosition){ V3D returnValue{}; float maxValue{static_cast(std::numeric_limits::max())}; // scale the key frame values down to be within board coordinates - float keyFrame_X = static_cast(this->X_SIZE) * static_cast(keyFramePosition.x) / maxValue; - float keyFrame_Y = static_cast(this->Y_SIZE) * static_cast(keyFramePosition.y) / maxValue; - float keyFrame_Z = static_cast(this->Z_SIZE) * static_cast(keyFramePosition.z) / maxValue; + float keyFrame_X = static_cast(BOARD_DIMS.x) * static_cast(keyFramePosition.x) / maxValue; + float keyFrame_Y = static_cast(BOARD_DIMS.y) * static_cast(keyFramePosition.y) / maxValue; + float keyFrame_Z = static_cast(BOARD_DIMS.z) * static_cast(keyFramePosition.z) / maxValue; // carefully quantize the float values back into ints with a precise rounding operation if(keyFrame_X - std::floor(keyFrame_X) < 0.5f){ @@ -173,7 +226,8 @@ V3D &keyFrame2BoardCoords(V3D &keyFramePosition){ return returnValue; } -V3D &noFillInterpolate(const AnimationFrame &keyFrame, V3D position){ +template +V3D Animator::noFillInterpolate(const AnimationFrame &keyFrame, V3D position){ V3D returnColor{}; for(Cell cell : keyFrame.frame){ if(keyFrame2BoardCoords(cell.position) == position){ @@ -184,170 +238,58 @@ V3D &noFillInterpolate(const AnimationFrame &keyFrame, V3D position){ return returnColor; } -V3D &closestColorInterpolate(const AnimationFrame &keyFrame, V3D position){ +template +V3D Animator::closestColorInterpolate(const AnimationFrame &keyFrame, V3D position){ V3D returnColor{}; - float closestDistance = (keyframe.frame[0].position - position).mag(); + V3D distance{keyFrame.frame[0].position}; + distance -= position; + float closestDistance = distance.magnitude(); for(Cell cell : keyFrame.frame){ - float distance = (keyFrame2BoardCoords(cell.position) - position).mag(); - if(distance < closestDistance){ + distance = keyFrame2BoardCoords(cell.position); + distance -= position; + float euclidDistance = distance.magnitude(); + if(euclidDistance < closestDistance){ returnColor = cell.color; - closestDistance = distance; + closestDistance = euclidDistance; } } return returnColor; } -V3D &linearInterpolate(const AnimationFrame &keyFrame, V3D position){ +template +V3D Animator::linearInterpolate(const AnimationFrame &keyFrame, V3D position){ V3D returnColor{}; for(Cell cell : keyFrame.frame){ - uint32_t distance = static_cast((keyFrame2BoardCoords(cell.position) - position).mag()); + V3D vectorDistance{keyFrame2BoardCoords(cell.position)}; + vectorDistance -= position; + uint32_t distance = static_cast(vectorDistance.magnitude()); if(distance == 0) distance = 1; - returnColor = returnColor + cell.color / distance; + returnColor += cell.color; + returnColor /= distance; } - returnColor = returnColor / keyFrame.frame.size(); + returnColor /= keyFrame.frame.size(); return returnColor; } -V3D &squareInterpolate(const AnimationFrame &keyFrame, V3D position){ +template +V3D Animator::squareInterpolate(const AnimationFrame &keyFrame, V3D position){ V3D returnColor{}; for(Cell cell : keyFrame.frame){ - uint32_t distance = static_cast((keyFrame2BoardCoords(cell.position) - position).mag()); + V3D vectorDistance{keyFrame2BoardCoords(cell.position)}; + vectorDistance -= position; + uint32_t distance = static_cast(vectorDistance.magnitude()); distance *= distance; if(distance == 0) distance = 1; - returnColor = returnColor + cell.color / distance; + returnColor += cell.color; + returnColor /= distance; } - returnColor = returnColor / keyFrame.frame.size(); + returnColor /= keyFrame.frame.size(); return returnColor; -} -}; - -// let's make some test animation frames - -namespace TestFrames{ - V3D red{255,0,0}; - V3D green{0,255,0}; - V3D blue{0,0,255}; - uint32_t maxValue{std::numeric_limits::max()}; - - Animation::Cell &CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){ - float continuousMaxValue{static_cast(std::numeric_limits::max())}; - Animation::Cell cell{ - .position = V3D{ - static_cast(continuousMaxValue*x_percent), - static_cast(continuousMaxValue*y_percent), - static_cast(continuousMaxValue*z_percent) - }, - .color = color - }; - - return cell; - } - - Animation::AnimationFrame noFillFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::NO_FILL; - .frameInterpolation = FrameInterpolation::SNAP; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame closestColorFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::CLOSEST_COLOR; - .frameInterpolation = FrameInterpolation::SNAP; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame linearFillFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE; - .frameInterpolation = FrameInterpolation::SNAP; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame squareFillFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE; - .frameInterpolation = FrameInterpolation::SNAP; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame noFillFadeFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::NO_FILL; - .frameInterpolation = FrameInterpolation::FADE; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame closestColorFadeFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::CLOSEST_COLOR; - .frameInterpolation = FrameInterpolation::FADE; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame linearFillFadeFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE; - .frameInterpolation = FrameInterpolation::FADE; - .delay = std::chrono::millis(10000); - } - - Animation::AnimationFrame squareFillFadeFrame{ - .frame = { - CreateCell(0,0,0,red), - CreateCell(0.5,0.5,0.5,green), - CreateCell(1,1,1,blue) - }; - .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE; - .frameInterpolation = FrameInterpolation::FADE; - .delay = std::chrono::millis(10000); - } - - std::vector testAnimationSequence{ - noFillFrame, - closestColorFrame, - linearFillFrame, - squareFillFrame, - noFillFadeFrame, - closestFillFadeFrame, - linearFillFadeFrame, - squareFillFadeFrame - }; -} - - +} \ No newline at end of file diff --git a/lib/Animator/TestFrames.h b/lib/Animator/TestFrames.h new file mode 100644 index 0000000..673955c --- /dev/null +++ b/lib/Animator/TestFrames.h @@ -0,0 +1,158 @@ +#pragma once + +#include "AnimationTypes.h" +#include "Vector3D.h" +#include "Animator.h" + +using namespace ANIMATION_TYPES; + +namespace TestFrames{ + + V3D red{255,0,0}; + V3D green{0,255,0}; + V3D blue{0,0,255}; + uint32_t maxValue{std::numeric_limits::max()}; + + Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){ + float continuousMaxValue{static_cast(std::numeric_limits::max())}; + Cell cell{ + .position = V3D{ + static_cast(continuousMaxValue*x_percent), + static_cast(continuousMaxValue*y_percent), + static_cast(continuousMaxValue*z_percent) + }, + .color = color + }; + + return cell; + } + + AnimationFrame noFillFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::NO_FILL, + .frameInterpolation = FrameInterpolation::SNAP, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame closestColorFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::SNAP, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame linearFillFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::SNAP, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame squareFillFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::SNAP, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame noFillFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::NO_FILL, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame closestColorFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame linearFillFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame squareFillFadeFrame{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(10000) + }; + + std::vector testAnimationSequence2{ + noFillFrame, // 0 + closestColorFrame, // 1 + linearFillFrame, // 2 + squareFillFrame, // 3 + noFillFadeFrame, // 4 + closestColorFadeFrame, // 5 + linearFillFadeFrame, // 6 + squareFillFadeFrame // 7 + }; + + + AnimationFrame noFillFrame2{ + .frame = { + CreateCell(0,1,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,0,0,green) + }, + .fillInterpolation = FillInterpolation::NO_FILL, + .frameInterpolation = FrameInterpolation::SNAP, + .delay = std::chrono::milliseconds(10000) + }; + + AnimationFrame noFillFrame3{ + .frame = { + CreateCell(0.5,0.5,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(0,1,0,blue) + }, + .fillInterpolation = FillInterpolation::NO_FILL, + .frameInterpolation = FrameInterpolation::SNAP, + .delay = std::chrono::milliseconds(10000) + }; + + std::vector testAnimationSequence1{ + noFillFrame, + noFillFrame2, + noFillFrame3, + noFillFrame3 + }; +} \ No newline at end of file diff --git a/lib/Board/Board.h b/lib/Board/Board.h index 16ba9bc..c5496e3 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -79,6 +79,16 @@ class Board{ uint32_t SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer); void PrintEntireBoard() const; + + void UpdateAllColors(const std::array, BOARD_DIMS.y>, BOARD_DIMS.x>& colorFrame){ + for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ + for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ + for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ + this->cubes[x][y][z].color = colorFrame[x][y][z]; + } + } + } + } private: // this is a 3d array of cubes to represent the board. Good luck visualizing it /* _____________ diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h index 6f273eb..417117b 100644 --- a/lib/Board/BoardManager.h +++ b/lib/Board/BoardManager.h @@ -7,11 +7,12 @@ #include "Board.h" #include "BoardDriver.h" #include "Vector3D.h" +#include "Animator.h" template class BoardManager{ public: - BoardManager(BoardDriver &boardDriver); + BoardManager(BoardDriver &boardDriver, Animator &animator); ~BoardManager() = default; @@ -66,9 +67,12 @@ class BoardManager{ */ void Board2StackString(String& messageBuffer); + void FillColor(const V3D &color){this->board.FillColor(color);} + private: BoardDriver &driver; Board board{}; + Animator &animator; void updateStackColors(const V3D &column); @@ -88,11 +92,20 @@ class BoardManager{ } } + void updateColorsFromAnimator(); + }; +template +void BoardManager::updateColorsFromAnimator(){ + if(this->animator.HasInterpolatedFrameChanged()){ + this->board.UpdateAllColors(this->animator.GetInterpolatedFrame()); + } +} template -BoardManager::BoardManager(BoardDriver &boardDriver): - driver(boardDriver){} +BoardManager::BoardManager(BoardDriver &boardDriver, Animator &animator): + driver(boardDriver), + animator(animator){} template void BoardManager::Init(){ @@ -101,6 +114,7 @@ void BoardManager::Init(){ template void BoardManager::Update(){ + this->updateColorsFromAnimator(); // update the occupied cubes on the board and the cube colors for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ diff --git a/src/main.cpp b/src/main.cpp index e297b81..ccb7ede 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,9 @@ #include "BoardDriver.h" #include "BoardTypes.h" +#include "Animator.h" +#include "TestFrames.h" + // -------------------------------------------------- // ----------------- VARIABLES ---------------------- // -------------------------------------------------- @@ -31,8 +34,9 @@ SerialMessage serialMessage(&Serial); Adafruit_NeoPixel pixelController{BOARD_HEIGHT*2, STACK1_LED_PIN, NEO_GRB + NEO_KHZ800}; +Animator animator{}; BoardDriver boardDriver{stacks, pixelController}; -BoardManager boardManager{boardDriver}; +BoardManager boardManager{boardDriver, animator}; // -------------------------------------------------- // ----------------- FUNCTIONS ---------------------- // -------------------------------------------------- @@ -95,26 +99,31 @@ void parseData(Message &message){ uint32_t argsLength{message.GetPopulatedArgs()}; uint32_t command = args[0]; switch(command){ - case Commands::BoardState: + case Commands::BoardState:{ printBoardState(); break; - case Commands::PING: + } + case Commands::PING:{ GlobalPrint::Println("!" + String(Commands::PING) + ";"); break; - case Commands::SetStackColors: + } + case Commands::SetStackColors:{ GlobalPrint::Println("!2;"); - // TODO: replace this with the animator - // colorManager.Enable(false); + animator.isEnabled = false; + V3D black{}; + boardManager.FillColor(black); SetStackColor(reinterpret_cast(args), argsLength); break; - case Commands::GoToIdle: + } + case Commands::GoToIdle:{ GlobalPrint::Println("!3;"); - // TODO: replace this with the animator - // colorManager.Enable(true); + animator.isEnabled = true; break; - default: + } + default:{ GlobalPrint::Println("INVALID COMMAND"); break; + } } // now that we have run the command we can clear the data for the next command. @@ -155,6 +164,7 @@ void UpdateBoard(void * params){ boardManager.ClearBoardChanged(); } + animator.RunAnimation(updateTickRate); boardManager.Update(); boardStateTimer += updateTickRate; @@ -185,6 +195,8 @@ void setup() { Serial.println("Beginning Board Initializaiton"); boardManager.Init(); + animator.SetLoop(true); + animator.StartAnimation(&(TestFrames::testAnimationSequence1)); xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask); Serial.println("Setup Complete"); From 14ac96988aa7dcd932fc399fa61bc421937e0e49 Mon Sep 17 00:00:00 2001 From: Quinn Date: Sun, 25 Aug 2024 16:11:06 -0400 Subject: [PATCH 10/12] Got the animator fades working --- include/BOARD-DEFINITIONS.h | 2 +- include/Vector3D.h | 32 +++++++--- lib/Animator/AnimationTypes.h | 4 +- lib/Animator/Animator.h | 115 +++++++++++++++++----------------- lib/Animator/TestFrames.h | 36 +++++++---- lib/Board/Board.h | 40 ++++++------ lib/Board/BoardDriver.h | 2 +- lib/Board/BoardManager.h | 57 ++++++++--------- lib/Board/BoardTypes.h | 2 +- src/main.cpp | 13 ++-- 10 files changed, 164 insertions(+), 139 deletions(-) diff --git a/include/BOARD-DEFINITIONS.h b/include/BOARD-DEFINITIONS.h index 4dd56da..4ded666 100644 --- a/include/BOARD-DEFINITIONS.h +++ b/include/BOARD-DEFINITIONS.h @@ -20,7 +20,7 @@ static constexpr uint32_t BOARD_WIDTH{3}; static constexpr uint32_t BOARD_LENGTH{3}; static constexpr uint32_t BOARD_HEIGHT{3}; -static constexpr V3D BOARD_DIMENSIONS{BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT}; +static constexpr V3D BOARD_DIMENSIONS{BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT}; // define the number of stacks static constexpr uint32_t NUMBER_STACKS{BOARD_WIDTH * BOARD_LENGTH}; diff --git a/include/Vector3D.h b/include/Vector3D.h index fdcb83d..cb60300 100644 --- a/include/Vector3D.h +++ b/include/Vector3D.h @@ -3,17 +3,31 @@ #include #include +template class V3D{ public: constexpr V3D(const V3D& other): x(other.x), y(other.y), - z(other.z){} + z(other.z){ + static_assert(std::is_arithmetic::value, "Type must be a number"); + } - constexpr V3D(uint32_t x=0, uint32_t y=0, uint32_t z=0): + constexpr V3D(Type x=0, Type y=0, Type z=0): x(x), y(y), - z(z){} + z(z){ + static_assert(std::is_arithmetic::value, "Type must be a number"); + } + + template + constexpr V3D(const V3D other): + x(static_cast(other.x)), + y(static_cast(other.y)), + z(static_cast(other.z)){ + static_assert(std::is_arithmetic::value, "Type must be a number"); + static_assert(std::is_arithmetic::value, "OtherType must be a number"); + } V3D& operator=(const V3D &other){ this->x = other.x; @@ -36,7 +50,7 @@ class V3D{ return *this; } - V3D& operator/=(const uint32_t scalar){ + V3D& operator/=(const Type scalar){ if(scalar == 0){ return *this; } @@ -46,7 +60,7 @@ class V3D{ return *this; } - V3D& operator*=(const uint32_t scalar){ + V3D& operator*=(const Type scalar){ this->x *= scalar; this->y *= scalar; this->z *= scalar; @@ -58,9 +72,9 @@ class V3D{ } float magnitude(){ - return std::sqrt(this->x * this->x + this->y * this->y + this->z * this-> z); + return std::sqrt(static_cast(this->x * this->x + this->y * this->y + this->z * this->z)); } - uint32_t x; - uint32_t y; - uint32_t z; + Type x; + Type y; + Type z; }; \ No newline at end of file diff --git a/lib/Animator/AnimationTypes.h b/lib/Animator/AnimationTypes.h index 482ceeb..d3b29c5 100644 --- a/lib/Animator/AnimationTypes.h +++ b/lib/Animator/AnimationTypes.h @@ -19,8 +19,8 @@ namespace ANIMATION_TYPES{ }; struct Cell{ - V3D position; - V3D color; + V3D position; + V3D color; }; // this contains all of the information to specify exactly how a single frame should look and fade to the next frame diff --git a/lib/Animator/Animator.h b/lib/Animator/Animator.h index 0ee4d98..9c5af47 100644 --- a/lib/Animator/Animator.h +++ b/lib/Animator/Animator.h @@ -13,11 +13,11 @@ using namespace ANIMATION_TYPES; -template +template &BOARD_DIMS> class Animator{ public: -typedef std::array, BOARD_DIMS.y>, BOARD_DIMS.x> Frame; +typedef std::array, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x> Frame; void StartAnimation(const std::vector *animationSequence); @@ -26,15 +26,13 @@ void RunAnimation(const std::chrono::milliseconds& timePassed); void SetLoop(bool isLooping); -bool HasInterpolatedFrameChanged(){return this->interpolatedFrameHasChanged;} - Frame &GetInterpolatedFrame(){return this->interpolatedFrame;} bool isEnabled{true}; +bool interpolatedFrameHasChanged{false}; private: bool isLooping{true}; -bool interpolatedFrameHasChanged{false}; // these are the uncompressed frames you get by following the key colors and interpolation instructions of an animation frame Frame startFrame; Frame interpolatedFrame; @@ -52,17 +50,17 @@ void copyFrame(Frame ©From, Frame ©To){ std::memcpy(©To, ©From, sizeof(Frame)); } -V3D getInterpolatedColor(const AnimationFrame &keyFrame, V3D position); +V3D getInterpolatedColor(const AnimationFrame &keyFrame, V3D position); -V3D keyFrame2BoardCoords(const V3D &keyFramePosition); +V3D keyFrame2BoardCoords(const V3D &keyFramePosition); -V3D noFillInterpolate(const AnimationFrame &keyFrame, V3D position); +V3D noFillInterpolate(const AnimationFrame &keyFrame, V3D position); -V3D closestColorInterpolate(const AnimationFrame &keyFrame, V3D position); +V3D closestColorInterpolate(const AnimationFrame &keyFrame, V3D position); -V3D linearInterpolate(const AnimationFrame &keyFrame, V3D position); +V3D linearInterpolate(const AnimationFrame &keyFrame, V3D position); -V3D squareInterpolate(const AnimationFrame &keyFrame, V3D position); +V3D squareInterpolate(const AnimationFrame &keyFrame, V3D position); void PrintUncompressedFrame(){ for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ @@ -78,7 +76,7 @@ void PrintUncompressedFrame(){ }; -template +template &BOARD_DIMS> void Animator::StartAnimation(const std::vector *animationSequence){ this->animationSequence = animationSequence; @@ -99,13 +97,13 @@ void Animator::StartAnimation(const std::vector *ani this->copyFrame(this->startFrame, this->interpolatedFrame); this->uncompressFrame((*this->animationSequence)[1], this->endFrame); } - this->PrintUncompressedFrame(); + this->interpolatedFrameHasChanged = true; } -template +template &BOARD_DIMS> void Animator::RunAnimation(const std::chrono::milliseconds& timePassed){ if(!(this->isEnabled)){ - this->interpolatedFrameHasChanged = false; + return; } auto delayTime = (*this->animationSequence)[this->animationIndex].delay; @@ -118,19 +116,19 @@ void Animator::RunAnimation(const std::chrono::milliseconds& timePas // don't do frame interpolations if we're doing snap fades if((*this->animationSequence)[this->animationIndex].frameInterpolation == FrameInterpolation::SNAP){ - this->interpolatedFrameHasChanged = false; + return; } // linearly interpolate between the two uncompressed frames for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ - V3D startColor{this->startFrame[x][y][z]}; - V3D endColor{this->endFrame[x][y][z]}; - V3D difference{endColor}; + V3D startColor{this->startFrame[x][y][z]}; + V3D endColor{this->endFrame[x][y][z]}; + V3D difference{endColor}; difference -= startColor; - V3D interpolatedColor{difference}; + V3D interpolatedColor{difference}; interpolatedColor *= this->timeElapsed.count(); interpolatedColor /= delayTime.count(); interpolatedColor += startColor; @@ -143,40 +141,40 @@ void Animator::RunAnimation(const std::chrono::milliseconds& timePas this->interpolatedFrameHasChanged = true; } -template +template &BOARD_DIMS> void Animator::SetLoop(bool isLooping){ this->isLooping = isLooping; } -template +template &BOARD_DIMS> void Animator::incrimentAnimationIndex(){ - if(this->animationIndex < this->animationSequence->size() - 1){ + if(this->animationIndex < this->animationSequence->size() - 2){ this->animationIndex++; this->timeElapsed = std::chrono::milliseconds(0); this->uncompressFrame((*this->animationSequence)[this->animationIndex], this->startFrame); + this->copyFrame(this->startFrame, this->interpolatedFrame); this->uncompressFrame((*this->animationSequence)[this->animationIndex + 1], this->endFrame); } - else if(isLooping){ + else{ this->StartAnimation(this->animationSequence); } - Serial.println("Animator::incrimentAnimationIndex" + String(animationIndex)); - this->PrintUncompressedFrame(); + this->interpolatedFrameHasChanged = true; } -template +template &BOARD_DIMS> void Animator::uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer){ for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ - frameBuffer[x][y][z] = getInterpolatedColor(keyFrame, V3D(x, y, z)); + frameBuffer[x][y][z] = getInterpolatedColor(keyFrame, V3D(x, y, z)); } } } } -template -V3D Animator::getInterpolatedColor(const AnimationFrame &keyFrame, V3D position){ +template &BOARD_DIMS> +V3D Animator::getInterpolatedColor(const AnimationFrame &keyFrame, V3D position){ switch(keyFrame.fillInterpolation){ case FillInterpolation::NO_FILL: return noFillInterpolate(keyFrame, position); @@ -187,19 +185,19 @@ V3D Animator::getInterpolatedColor(const AnimationFrame &keyFrame, V case FillInterpolation::SQUARE_WEIGHTED_DISTANCE: return squareInterpolate(keyFrame, position); default: - V3D black{}; + V3D black{}; return black; } } -template -V3D Animator::keyFrame2BoardCoords(const V3D &keyFramePosition){ - V3D returnValue{}; +template &BOARD_DIMS> +V3D Animator::keyFrame2BoardCoords(const V3D &keyFramePosition){ + V3D returnValue{}; float maxValue{static_cast(std::numeric_limits::max())}; // scale the key frame values down to be within board coordinates - float keyFrame_X = static_cast(BOARD_DIMS.x) * static_cast(keyFramePosition.x) / maxValue; - float keyFrame_Y = static_cast(BOARD_DIMS.y) * static_cast(keyFramePosition.y) / maxValue; - float keyFrame_Z = static_cast(BOARD_DIMS.z) * static_cast(keyFramePosition.z) / maxValue; + float keyFrame_X = static_cast(BOARD_DIMS.x - 1) * static_cast(keyFramePosition.x) / maxValue; + float keyFrame_Y = static_cast(BOARD_DIMS.y - 1) * static_cast(keyFramePosition.y) / maxValue; + float keyFrame_Z = static_cast(BOARD_DIMS.z - 1) * static_cast(keyFramePosition.z) / maxValue; // carefully quantize the float values back into ints with a precise rounding operation if(keyFrame_X - std::floor(keyFrame_X) < 0.5f){ @@ -226,9 +224,9 @@ V3D Animator::keyFrame2BoardCoords(const V3D &keyFramePosition){ return returnValue; } -template -V3D Animator::noFillInterpolate(const AnimationFrame &keyFrame, V3D position){ - V3D returnColor{}; +template &BOARD_DIMS> +V3D Animator::noFillInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; for(Cell cell : keyFrame.frame){ if(keyFrame2BoardCoords(cell.position) == position){ returnColor = cell.color; @@ -238,34 +236,35 @@ V3D Animator::noFillInterpolate(const AnimationFrame &keyFrame, V3D return returnColor; } -template -V3D Animator::closestColorInterpolate(const AnimationFrame &keyFrame, V3D position){ - V3D returnColor{}; - V3D distance{keyFrame.frame[0].position}; - distance -= position; +template &BOARD_DIMS> +V3D Animator::closestColorInterpolate(const AnimationFrame &keyFrame, V3D cubePosition){ + Serial.print("X:" + String(cubePosition.x) + ",Y:" + String(cubePosition.y) + ",Z:" + String(cubePosition.z)); + V3D returnColor{keyFrame.frame[0].color}; + V3D distance{keyFrame.frame[0].position}; + distance -= cubePosition; float closestDistance = distance.magnitude(); + for(Cell cell : keyFrame.frame){ distance = keyFrame2BoardCoords(cell.position); - distance -= position; + distance -= cubePosition; float euclidDistance = distance.magnitude(); if(euclidDistance < closestDistance){ returnColor = cell.color; closestDistance = euclidDistance; } } - return returnColor; } -template -V3D Animator::linearInterpolate(const AnimationFrame &keyFrame, V3D position){ - V3D returnColor{}; +template &BOARD_DIMS> +V3D Animator::linearInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; for(Cell cell : keyFrame.frame){ - V3D vectorDistance{keyFrame2BoardCoords(cell.position)}; + V3D vectorDistance{keyFrame2BoardCoords(cell.position)}; vectorDistance -= position; - uint32_t distance = static_cast(vectorDistance.magnitude()); - if(distance == 0) distance = 1; + float distance = vectorDistance.magnitude(); + if(distance == 0) return cell.color; returnColor += cell.color; returnColor /= distance; } @@ -275,16 +274,16 @@ V3D Animator::linearInterpolate(const AnimationFrame &keyFrame, V3D return returnColor; } -template -V3D Animator::squareInterpolate(const AnimationFrame &keyFrame, V3D position){ - V3D returnColor{}; +template &BOARD_DIMS> +V3D Animator::squareInterpolate(const AnimationFrame &keyFrame, V3D position){ + V3D returnColor{}; for(Cell cell : keyFrame.frame){ - V3D vectorDistance{keyFrame2BoardCoords(cell.position)}; + V3D vectorDistance{keyFrame2BoardCoords(cell.position)}; vectorDistance -= position; uint32_t distance = static_cast(vectorDistance.magnitude()); distance *= distance; - if(distance == 0) distance = 1; + if(distance == 0) return cell.color; returnColor += cell.color; returnColor /= distance; } diff --git a/lib/Animator/TestFrames.h b/lib/Animator/TestFrames.h index 673955c..5312dd7 100644 --- a/lib/Animator/TestFrames.h +++ b/lib/Animator/TestFrames.h @@ -8,15 +8,15 @@ using namespace ANIMATION_TYPES; namespace TestFrames{ - V3D red{255,0,0}; - V3D green{0,255,0}; - V3D blue{0,0,255}; + V3D red{255,0,0}; + V3D green{0,255,0}; + V3D blue{0,0,255}; uint32_t maxValue{std::numeric_limits::max()}; - Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){ + Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){ float continuousMaxValue{static_cast(std::numeric_limits::max())}; Cell cell{ - .position = V3D{ + .position = V3D{ static_cast(continuousMaxValue*x_percent), static_cast(continuousMaxValue*y_percent), static_cast(continuousMaxValue*z_percent) @@ -126,33 +126,43 @@ namespace TestFrames{ squareFillFadeFrame // 7 }; + AnimationFrame testFrame1{ + .frame = { + CreateCell(0,0,0,red), + // CreateCell(0.5,0.5,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::NO_FILL, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(10000) + }; - AnimationFrame noFillFrame2{ + AnimationFrame testFrame2{ .frame = { CreateCell(0,1,0,red), // CreateCell(0.5,0.5,0,green), CreateCell(1,0,0,green) }, .fillInterpolation = FillInterpolation::NO_FILL, - .frameInterpolation = FrameInterpolation::SNAP, + .frameInterpolation = FrameInterpolation::FADE, .delay = std::chrono::milliseconds(10000) }; - AnimationFrame noFillFrame3{ + AnimationFrame testFrame3{ .frame = { CreateCell(0.5,0.5,0,red), // CreateCell(0.5,0.5,0,green), CreateCell(0,1,0,blue) }, .fillInterpolation = FillInterpolation::NO_FILL, - .frameInterpolation = FrameInterpolation::SNAP, + .frameInterpolation = FrameInterpolation::FADE, .delay = std::chrono::milliseconds(10000) }; std::vector testAnimationSequence1{ - noFillFrame, - noFillFrame2, - noFillFrame3, - noFillFrame3 + testFrame1, + testFrame2, + testFrame3, + testFrame1 }; } \ No newline at end of file diff --git a/lib/Board/Board.h b/lib/Board/Board.h index c5496e3..00be54c 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -12,13 +12,13 @@ #include "BoardTypes.h" #include "Vector3D.h" -template +template &BOARD_DIMS> class Board{ public: Board() = default; ~Board() = default; - constexpr const V3D &GetSize() const{return BOARD_DIMS;} + constexpr const V3D &GetSize() const{return BOARD_DIMS;} constexpr uint32_t GetNumberCubes() const{return BOARD_DIMS.x * BOARD_DIMS.y * BOARD_DIMS.z;} constexpr uint32_t GetMaxDimension(){return std::max(std::max(BOARD_DIMS.x, BOARD_DIMS.y), BOARD_DIMS.z);} @@ -34,7 +34,7 @@ class Board{ * @brief fill the entire board with the given color * @param color the color to fill the board with */ - void FillColor(const V3D &color); + void FillColor(const V3D &color); /** * @brief Set the color of the cube at the given position. @@ -43,7 +43,7 @@ class Board{ * @param position the position of the cube. * @param color the color you want the cube to be */ - void SetCubeColor(const V3D &position, const V3D &color); + void SetCubeColor(const V3D &position, const V3D &color); /** * @brief Set the occupation status of the cube at a given position @@ -52,7 +52,7 @@ class Board{ * @post if the new occupation status of the cube is different than * the old occupation status, this will enable boardStateHasChanged. */ - void SetCubeOccupation(const V3D &position, bool occupation); + void SetCubeOccupation(const V3D &position, bool occupation); /** * @returns true if the board state has changed since this flag was last set to false @@ -69,18 +69,18 @@ class Board{ * @brief Get a column along any axis read into the sliceBuffer * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and * the x,y values specify the location of the column in that plane - * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) + * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) * @param sliceBuffer an array of pointers to the cubes along that column * @returns the number of elements written into the slice buffer * @note That array is stored locally and will be overwritten everytime this function is called. * Also, any unused spots at the end of the array will be nullptrs * @warning allocate the size of the slice buffer using GetMaxDimension if you don't know what you're doing! */ - uint32_t SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer); + uint32_t SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer); void PrintEntireBoard() const; - void UpdateAllColors(const std::array, BOARD_DIMS.y>, BOARD_DIMS.x>& colorFrame){ + void UpdateAllColors(const std::array, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x>& colorFrame){ for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ @@ -106,7 +106,7 @@ class Board{ bool boardStateHasChanged; }; -template +template &BOARD_DIMS> void Board::ToStackString(String &stringBuffer) const{ std::array linearizedBoard; for(uint32_t x{0}; x < BOARD_DIMS.x; x++){ @@ -126,8 +126,8 @@ void Board::ToStackString(String &stringBuffer) const{ } } -template -void Board::FillColor(const V3D &color){ +template &BOARD_DIMS> +void Board::FillColor(const V3D &color){ for(uint32_t x{0}; x < BOARD_DIMS.x; x++){ for(uint32_t y{0}; y < BOARD_DIMS.y; y++){ for(uint32_t z{0}; z < BOARD_DIMS.z; z++){ @@ -137,23 +137,23 @@ void Board::FillColor(const V3D &color){ } } -template -void Board::SetCubeColor(const V3D &position, const V3D &color){ +template &BOARD_DIMS> +void Board::SetCubeColor(const V3D &position, const V3D &color){ this->cubes[position.x][position.y][position.z].color = color; } -template -void Board::SetCubeOccupation(const V3D &position, bool occupation){ +template &BOARD_DIMS> +void Board::SetCubeOccupation(const V3D &position, bool occupation){ bool oldOccupation{this->cubes[position.x][position.y][position.z].isOccupied}; this->cubes[position.x][position.y][position.z].isOccupied = occupation; if(occupation != oldOccupation) this->boardStateHasChanged = true; } -template -uint32_t Board::SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer){ +template &BOARD_DIMS> +uint32_t Board::SliceBoard(const V3D &column, BOARD_TYPES::Cube ** sliceBuffer){ uint32_t columnLength{0}; - V3D indexIncrimentVector{}; - V3D indexVector{}; + V3D indexIncrimentVector{}; + V3D indexVector{}; switch(column.z){ case BOARD_TYPES::PLANE_NORMAL::X: @@ -189,7 +189,7 @@ uint32_t Board::SliceBoard(const V3D &column, BOARD_TYPES::Cube ** s return columnLength; } -template +template &BOARD_DIMS> void Board::PrintEntireBoard() const{ for(uint32_t x = 0; x < BOARD_DIMS.x; x++){ for(uint32_t y = 0; y < BOARD_DIMS.y; y++){ diff --git a/lib/Board/BoardDriver.h b/lib/Board/BoardDriver.h index 7be2a35..fa49550 100644 --- a/lib/Board/BoardDriver.h +++ b/lib/Board/BoardDriver.h @@ -129,7 +129,7 @@ void BoardDriver::UpdateStackLEDs( ){ this->pixelController.setPin(this->stacks[stackIndex].ledPin); for(int i = 0; i < numCubes; i++){ - V3D color{cubes[i]->color}; + V3D color{cubes[i]->color}; this->pixelController.setPixelColor(i*2, this->pixelController.Color(color.x, color.y, color.z)); this->pixelController.setPixelColor((i*2 + 1), this->pixelController.Color(color.x, color.y, color.z)); } diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h index 417117b..4710e1b 100644 --- a/lib/Board/BoardManager.h +++ b/lib/Board/BoardManager.h @@ -9,7 +9,7 @@ #include "Vector3D.h" #include "Animator.h" -template +template &BOARD_DIMS> class BoardManager{ public: BoardManager(BoardDriver &boardDriver, Animator &animator); @@ -31,7 +31,7 @@ class BoardManager{ * @param position the position of the cube * @param color the oclor you want the cube to be */ - void SetCubeColor(const V3D &position, const V3D &color); + void SetCubeColor(const V3D &position, const V3D &color); /** * @brief Set the color of one column of cubes. @@ -40,17 +40,17 @@ class BoardManager{ * @param column the column vector * @param color the color you want the column to be */ - void SetColumnColors(const V3D &column, const V3D *color, uint32_t numColors); + void SetColumnColors(const V3D &column, const V3D *color, uint32_t numColors); /** * @brief Fill a column along any axis with a color * @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and * the x,y values specify the location of the column in that plane - * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) + * to fill. IE To fill one stack at 0,2 I would say give V3D(0,2,PLANE_NORMAL::Z) * @param color the color you want to fill the column with */ - void FillColumnColor(const V3D &column, const V3D &color); + void FillColumnColor(const V3D &column, const V3D &color); /** * @returns true if the board has changed state @@ -67,14 +67,14 @@ class BoardManager{ */ void Board2StackString(String& messageBuffer); - void FillColor(const V3D &color){this->board.FillColor(color);} + void FillColor(const V3D &color){this->board.FillColor(color);} private: BoardDriver &driver; Board board{}; Animator &animator; - void updateStackColors(const V3D &column); + void updateStackColors(const V3D &column); uint32_t getColumnHeight(BOARD_TYPES::PLANE_NORMAL normal){ switch(normal){ @@ -95,24 +95,25 @@ class BoardManager{ void updateColorsFromAnimator(); }; -template +template &BOARD_DIMS> void BoardManager::updateColorsFromAnimator(){ - if(this->animator.HasInterpolatedFrameChanged()){ + if(this->animator.interpolatedFrameHasChanged){ this->board.UpdateAllColors(this->animator.GetInterpolatedFrame()); + this->animator.interpolatedFrameHasChanged = false; } } -template +template &BOARD_DIMS> BoardManager::BoardManager(BoardDriver &boardDriver, Animator &animator): driver(boardDriver), animator(animator){} -template +template &BOARD_DIMS> void BoardManager::Init(){ this->driver.Init(); } -template +template &BOARD_DIMS> void BoardManager::Update(){ this->updateColorsFromAnimator(); // update the occupied cubes on the board and the cube colors @@ -121,13 +122,13 @@ void BoardManager::Update(){ uint32_t stackIndex{y * BOARD_DIMS.x + x}; uint32_t numCubes{this->driver.GetNumberCubes(stackIndex)}; for(uint32_t z = 0; z < BOARD_DIMS.z; z++){ - V3D cubePosition{x, y, z}; + V3D cubePosition{x, y, z}; // update the cube's occupation this->board.SetCubeOccupation(cubePosition, z < numCubes); } // create the column vector for the slice direction - V3D sliceVector{x,y,BOARD_TYPES::PLANE_NORMAL::Z}; + V3D sliceVector{x,y,BOARD_TYPES::PLANE_NORMAL::Z}; // create a cube slice array buffer BOARD_TYPES::Cube* sliceBuffer[BOARD_DIMS.z]; // have the board slice get read into our buffer @@ -138,10 +139,10 @@ void BoardManager::Update(){ } } -template -void BoardManager::updateStackColors(const V3D &column){ +template &BOARD_DIMS> +void BoardManager::updateStackColors(const V3D &column){ // the only column type allowed here is z. - V3D sliceVector{column.x, column.y, BOARD_TYPES::Z}; + V3D sliceVector{column.x, column.y, BOARD_TYPES::Z}; // create a buffer for slice board to write the cube slice into BOARD_TYPES::Cube * cubeSlice[BOARD_DIMS.z]; this->board.SliceBoard(column, cubeSlice); @@ -150,15 +151,15 @@ void BoardManager::updateStackColors(const V3D &column){ this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes); } -template -void BoardManager::SetCubeColor(const V3D &position, const V3D &color){ +template &BOARD_DIMS> +void BoardManager::SetCubeColor(const V3D &position, const V3D &color){ this->board.SetCubeColor(position, color); - V3D slice{position.x, position.y, BOARD_TYPES::PLANE_NORMAL::Z}; + V3D slice{position.x, position.y, BOARD_TYPES::PLANE_NORMAL::Z}; this->updateStackColors(slice); } -template -void BoardManager::SetColumnColors(const V3D &column, const V3D *color, uint32_t numColors){ +template &BOARD_DIMS> +void BoardManager::SetColumnColors(const V3D &column, const V3D *color, uint32_t numColors){ uint32_t columnHeight{this->getColumnHeight(static_cast(column.z))}; // create a cube pointer buffer and store a board slice into it @@ -171,11 +172,11 @@ void BoardManager::SetColumnColors(const V3D &column, const V3D *col } } -template -void BoardManager::FillColumnColor(const V3D &column, const V3D &color){ +template &BOARD_DIMS> +void BoardManager::FillColumnColor(const V3D &column, const V3D &color){ uint32_t columnHeight{this->getColumnHeight(column.z)}; - V3D colors[columnHeight]; + V3D colors[columnHeight]; for(uint32_t i = 0; i < columnHeight; i++){ colors[i] = color; } @@ -183,13 +184,13 @@ void BoardManager::FillColumnColor(const V3D &column, const V3D &col this->SetColumnColors(column, colors); } -template +template &BOARD_DIMS> bool BoardManager::HasBoardChanged(){return this->board.BoardStateChanged();} -template +template &BOARD_DIMS> void BoardManager::ClearBoardChanged(){this->board.SetStateChanged(false);} -template +template &BOARD_DIMS> void BoardManager::Board2StackString(String& messageBuffer){ this->board.ToStackString(messageBuffer); } diff --git a/lib/Board/BoardTypes.h b/lib/Board/BoardTypes.h index 812c341..4eba9a5 100644 --- a/lib/Board/BoardTypes.h +++ b/lib/Board/BoardTypes.h @@ -20,7 +20,7 @@ namespace BOARD_TYPES{ }; struct Cube{ - V3D color; + V3D color; bool isOccupied{false}; }; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ccb7ede..c27de7d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,15 +83,15 @@ void SetStackColor(uint32_t * args, int argsLength){ uint32_t Y_COORD{(stackNum - X_COORD) / BOARD_DIMENSIONS.y}; uint32_t numColors = (argsLength - 2) / 3; - V3D colors[numColors]; + V3D colors[numColors]; for(int i = 0; i < numColors; i++){ uint32_t red = args[2 + (i * 3)]; uint32_t green = args[3 + (i * 3)]; uint32_t blue = args[4 + (i * 3)]; - colors[i] = V3D{red, green, blue}; + colors[i] = V3D{red, green, blue}; } - boardManager.SetColumnColors(V3D{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors, numColors); + boardManager.SetColumnColors(V3D{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors, numColors); } void parseData(Message &message){ @@ -110,7 +110,7 @@ void parseData(Message &message){ case Commands::SetStackColors:{ GlobalPrint::Println("!2;"); animator.isEnabled = false; - V3D black{}; + V3D black{}; boardManager.FillColor(black); SetStackColor(reinterpret_cast(args), argsLength); break; @@ -157,14 +157,15 @@ void UpdateBoard(void * params){ auto updateTickRate{std::chrono::milliseconds(8)}; auto boardStateTimer{std::chrono::milliseconds(0)}; auto boardStateMaxUpdatePeriod{std::chrono::milliseconds(34)}; // this is a little slower than 30fps - + unsigned long accurateTimer{millis()}; for(;;){ if(boardStateTimer >= boardStateMaxUpdatePeriod && boardManager.HasBoardChanged()){ printBoardState(); boardManager.ClearBoardChanged(); } - animator.RunAnimation(updateTickRate); + animator.RunAnimation(std::chrono::milliseconds(millis() - accurateTimer)); + accurateTimer = millis(); boardManager.Update(); boardStateTimer += updateTickRate; From 67c5cc7c19c85b7e543da9212273bf57d8ca40f4 Mon Sep 17 00:00:00 2001 From: Quinn Date: Sun, 25 Aug 2024 16:25:59 -0400 Subject: [PATCH 11/12] Got the animator completed tested and working --- lib/Animator/Animator.h | 1 - lib/Animator/TestFrames.h | 35 ++++++++++++++++++----------------- src/main.cpp | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/Animator/Animator.h b/lib/Animator/Animator.h index 9c5af47..2499150 100644 --- a/lib/Animator/Animator.h +++ b/lib/Animator/Animator.h @@ -238,7 +238,6 @@ V3D Animator::noFillInterpolate(const AnimationFrame &keyF template &BOARD_DIMS> V3D Animator::closestColorInterpolate(const AnimationFrame &keyFrame, V3D cubePosition){ - Serial.print("X:" + String(cubePosition.x) + ",Y:" + String(cubePosition.y) + ",Z:" + String(cubePosition.z)); V3D returnColor{keyFrame.frame[0].color}; V3D distance{keyFrame.frame[0].position}; distance -= cubePosition; diff --git a/lib/Animator/TestFrames.h b/lib/Animator/TestFrames.h index 5312dd7..4a3e2b4 100644 --- a/lib/Animator/TestFrames.h +++ b/lib/Animator/TestFrames.h @@ -30,8 +30,8 @@ namespace TestFrames{ AnimationFrame noFillFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::NO_FILL, .frameInterpolation = FrameInterpolation::SNAP, @@ -41,8 +41,8 @@ namespace TestFrames{ AnimationFrame closestColorFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::CLOSEST_COLOR, .frameInterpolation = FrameInterpolation::SNAP, @@ -52,8 +52,8 @@ namespace TestFrames{ AnimationFrame linearFillFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, .frameInterpolation = FrameInterpolation::SNAP, @@ -63,8 +63,8 @@ namespace TestFrames{ AnimationFrame squareFillFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE, .frameInterpolation = FrameInterpolation::SNAP, @@ -74,8 +74,8 @@ namespace TestFrames{ AnimationFrame noFillFadeFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::NO_FILL, .frameInterpolation = FrameInterpolation::FADE, @@ -85,8 +85,8 @@ namespace TestFrames{ AnimationFrame closestColorFadeFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::CLOSEST_COLOR, .frameInterpolation = FrameInterpolation::FADE, @@ -96,8 +96,8 @@ namespace TestFrames{ AnimationFrame linearFillFadeFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, .frameInterpolation = FrameInterpolation::FADE, @@ -107,8 +107,8 @@ namespace TestFrames{ AnimationFrame squareFillFadeFrame{ .frame = { CreateCell(0,0,0,red), - // CreateCell(0.5,0.5,0,green), - CreateCell(1,1,0,blue) + CreateCell(0.5,0.5,0.5,green), + CreateCell(1,1,1,blue) }, .fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE, .frameInterpolation = FrameInterpolation::FADE, @@ -123,7 +123,8 @@ namespace TestFrames{ noFillFadeFrame, // 4 closestColorFadeFrame, // 5 linearFillFadeFrame, // 6 - squareFillFadeFrame // 7 + squareFillFadeFrame, // 7 + noFillFrame // 8 }; AnimationFrame testFrame1{ diff --git a/src/main.cpp b/src/main.cpp index c27de7d..52fe41e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -197,7 +197,7 @@ void setup() { Serial.println("Beginning Board Initializaiton"); boardManager.Init(); animator.SetLoop(true); - animator.StartAnimation(&(TestFrames::testAnimationSequence1)); + animator.StartAnimation(&(TestFrames::testAnimationSequence2)); xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask); Serial.println("Setup Complete"); From 5237de0539f264db3f4a33efc1bc3f85cd63ff00 Mon Sep 17 00:00:00 2001 From: Quinn Date: Mon, 26 Aug 2024 18:49:24 -0400 Subject: [PATCH 12/12] minor bug fixes and a new animation --- lib/Animator/Animation.h | 221 +++++++++++++++++++++++++++++++++++++++ lib/Board/Board.h | 2 +- lib/Board/BoardManager.h | 3 +- src/main.cpp | 31 +++++- 4 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 lib/Animator/Animation.h diff --git a/lib/Animator/Animation.h b/lib/Animator/Animation.h new file mode 100644 index 0000000..4adfa02 --- /dev/null +++ b/lib/Animator/Animation.h @@ -0,0 +1,221 @@ +#pragma once + +#include "AnimationTypes.h" +#include "Vector3D.h" +#include "Animator.h" + +using namespace ANIMATION_TYPES; + +namespace AnimationHelpers{ + V3D red{255,0,0}; + V3D green{0,255,0}; + V3D blue{0,0,255}; + V3D cyan{0,255,255}; + V3D magenta{255,0,255}; + + Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){ + float continuousMaxValue{static_cast(std::numeric_limits::max())}; + Cell cell{ + .position = V3D{ + static_cast(continuousMaxValue*x_percent), + static_cast(continuousMaxValue*y_percent), + static_cast(continuousMaxValue*z_percent) + }, + .color = color + }; + + return cell; + } +} + +namespace RotatingCubes{ + using namespace AnimationHelpers; + + AnimationFrame frame1{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(1,0.5,0,green), + CreateCell(0,1,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(1000) + }; + + AnimationFrame frame2{ + .frame = { + CreateCell(0,0.5,0,red), + CreateCell(1,0,0,green), + CreateCell(0.5,1,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(500) + }; + + AnimationFrame frame3{ + .frame = { + CreateCell(0,1,0,red), + CreateCell(0.5,0,0,green), + CreateCell(1,1,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(1000) + }; + + AnimationFrame frame4{ + .frame = { + CreateCell(0.5,1,0,red), + CreateCell(0,0,0,green), + CreateCell(1,0.5,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(500) + }; + + AnimationFrame frame5{ + .frame = { + CreateCell(1,1,0,red), + CreateCell(0,0.5,0,green), + CreateCell(1,0,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(1000) + }; + + AnimationFrame frame6{ + .frame = { + CreateCell(1,0.5,0,red), + CreateCell(0,1,0,green), + CreateCell(0.5,0,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(500) + }; + + AnimationFrame frame7{ + .frame = { + CreateCell(1,0,0,red), + CreateCell(0.5,1,0,green), + CreateCell(0,0,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(1000) + }; + + AnimationFrame frame8{ + .frame = { + CreateCell(0.5,0,0,red), + CreateCell(1,1,0,green), + CreateCell(0,0.5,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(500) + }; + + AnimationFrame frame9{ + .frame = { + CreateCell(0,0,0,red), + CreateCell(1,0.5,0,green), + CreateCell(0,1,0,blue) + }, + .fillInterpolation = FillInterpolation::CLOSEST_COLOR, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(1) + }; + + std::vector rotating{ + frame1, // 0 + frame2, // 1 + frame3, // 2 + frame4, // 3 + frame5, // 4 + frame6, // 5 + frame7, // 6 + frame8, // 7 + frame9, // 8 + }; +} + +namespace RisingCubes{ + using namespace AnimationHelpers; + + AnimationFrame frame1{ + .frame = { + CreateCell(0,0,0,cyan), + CreateCell(0,1,0.5,green), + CreateCell(1,0,1,blue), + CreateCell(0.5,0.5,0.5,red), + CreateCell(1,1,0,magenta) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(800) + }; + + AnimationFrame frame2{ + .frame = { + CreateCell(0,0,0.5,cyan), + CreateCell(0,1,1,green), + CreateCell(1,0,0.5,blue), + CreateCell(0.5,0.5,0,red), + CreateCell(1,1,0.5,magenta) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(800) + }; + + AnimationFrame frame3{ + .frame = { + CreateCell(0,0,1,cyan), + CreateCell(0,1,0.5,green), + CreateCell(1,0,0,blue), + CreateCell(0.5,0.5,0.5,red), + CreateCell(1,1,1,magenta) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(800) + }; + + AnimationFrame frame4{ + .frame = { + CreateCell(0,0,0.5,cyan), + CreateCell(0,1,0,green), + CreateCell(1,0,0.5,blue), + CreateCell(0.5,0.5,1,red), + CreateCell(1,1,0.5,magenta) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(800) + }; + + AnimationFrame frame5{ + .frame = { + CreateCell(0,0,0,cyan), + CreateCell(0,1,0.5,green), + CreateCell(1,0,1,blue), + CreateCell(0.5,0.5,0.5,red), + CreateCell(1,1,0,magenta) + }, + .fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE, + .frameInterpolation = FrameInterpolation::FADE, + .delay = std::chrono::milliseconds(1) + }; + + std::vector rising{ + frame1, // 0 + frame2, // 1 + frame3, // 2 + frame4, // 3 + frame5 + }; +} \ No newline at end of file diff --git a/lib/Board/Board.h b/lib/Board/Board.h index 00be54c..b64774d 100644 --- a/lib/Board/Board.h +++ b/lib/Board/Board.h @@ -121,7 +121,7 @@ void Board::ToStackString(String &stringBuffer) const{ stringBuffer += String(linearizedBoard[0]); - for(uint32_t i = 0; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ + for(uint32_t i = 1; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){ stringBuffer += "," + String(linearizedBoard[i]); } } diff --git a/lib/Board/BoardManager.h b/lib/Board/BoardManager.h index 4710e1b..54624d3 100644 --- a/lib/Board/BoardManager.h +++ b/lib/Board/BoardManager.h @@ -68,6 +68,7 @@ class BoardManager{ void Board2StackString(String& messageBuffer); void FillColor(const V3D &color){this->board.FillColor(color);} + void PrintColorState(){this->board.PrintEntireBoard();} private: BoardDriver &driver; @@ -167,7 +168,7 @@ void BoardManager::SetColumnColors(const V3D &column, cons uint32_t sliceLength{this->board.SliceBoard(column, slicedBoard)}; uint32_t maxIndex{std::min(numColors, columnHeight)}; - for(uint32_t i = 0; i < columnHeight; i++){ + for(uint32_t i = 0; i < maxIndex; i++){ slicedBoard[i]->color = color[i]; } } diff --git a/src/main.cpp b/src/main.cpp index 52fe41e..737824f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // Static Defines #include "PINOUT.h" @@ -21,6 +22,7 @@ #include "Animator.h" #include "TestFrames.h" +#include "Animation.h" // -------------------------------------------------- // ----------------- VARIABLES ---------------------- @@ -28,6 +30,11 @@ TaskHandle_t updateCommunicaitonTask; TaskHandle_t updateBoardTask; +std::array*, 2> animations = { + &RisingCubes::rising, + &RotatingCubes::rotating, +}; + // BluetoothSerial SerialBT; // BluetoothSerialMessage serialMessageBT(&SerialBT); SerialMessage serialMessage(&Serial); @@ -158,17 +165,33 @@ void UpdateBoard(void * params){ auto boardStateTimer{std::chrono::milliseconds(0)}; auto boardStateMaxUpdatePeriod{std::chrono::milliseconds(34)}; // this is a little slower than 30fps unsigned long accurateTimer{millis()}; + auto changeAnimationTimer{std::chrono::milliseconds(0)}; + uint8_t currentAnimation{0}; + for(;;){ + auto actualTimePassed{std::chrono::milliseconds(millis() - accurateTimer)}; + accurateTimer = millis(); + if(boardStateTimer >= boardStateMaxUpdatePeriod && boardManager.HasBoardChanged()){ printBoardState(); boardManager.ClearBoardChanged(); + boardStateTimer = std::chrono::milliseconds(0); } - animator.RunAnimation(std::chrono::milliseconds(millis() - accurateTimer)); - accurateTimer = millis(); + if(changeAnimationTimer >= std::chrono::duration_cast(std::chrono::minutes(1))){ + changeAnimationTimer = std::chrono::milliseconds(0); + currentAnimation++; + if(currentAnimation >= animations.size()){ + currentAnimation = 0; + } + animator.StartAnimation(animations[currentAnimation]); + } + + animator.RunAnimation(actualTimePassed); boardManager.Update(); - boardStateTimer += updateTickRate; + boardStateTimer += actualTimePassed; + changeAnimationTimer += actualTimePassed; vTaskDelay(updateTickRate.count()); } Serial.println("UpdateBoard task has ended unexpectedly!"); @@ -197,7 +220,7 @@ void setup() { Serial.println("Beginning Board Initializaiton"); boardManager.Init(); animator.SetLoop(true); - animator.StartAnimation(&(TestFrames::testAnimationSequence2)); + animator.StartAnimation(animations[0]); xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask); Serial.println("Setup Complete");