Completed the board manager rewrite

This commit is contained in:
2024-08-22 21:07:32 -04:00
parent 48f83eee38
commit 3e4f0124db
17 changed files with 666 additions and 408 deletions

View File

@@ -4,32 +4,60 @@
*/ */
#pragma once #pragma once
#include <cstdint>
#include <array>
#include "PINOUT.h" #include "PINOUT.h"
#include "CubeStack.h" #include "BoardDriverTypes.h"
// define the physical dimensions of the board // define the physical dimensions of the board
#define BOARD_WIDTH 3 static constexpr uint32_t BOARD_WIDTH{3};
#define BOARD_LENGTH 3 static constexpr uint32_t BOARD_LENGTH{3};
#define BOARD_HEIGHT 3 static constexpr uint32_t BOARD_HEIGHT{3};
// define the number of stacks // 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 // define the CubeStacks
CubeStack stack1(STACK1_ADC_PIN, STACK1_LED_PIN, BOARD_HEIGHT); static BoardDriverTypes::CubeStack stack1{
CubeStack stack2(STACK2_ADC_PIN, STACK2_LED_PIN, BOARD_HEIGHT); .adcPin=STACK1_ADC_PIN,
CubeStack stack3(STACK3_ADC_PIN, STACK3_LED_PIN, BOARD_HEIGHT); .ledPin=STACK1_LED_PIN
CubeStack stack4(STACK4_ADC_PIN, STACK4_LED_PIN, BOARD_HEIGHT); };
CubeStack stack5(STACK5_ADC_PIN, STACK5_LED_PIN, BOARD_HEIGHT); static BoardDriverTypes::CubeStack stack2{
CubeStack stack6(STACK6_ADC_PIN, STACK6_LED_PIN, BOARD_HEIGHT); .adcPin=STACK2_ADC_PIN,
CubeStack stack7(STACK7_ADC_PIN, STACK7_LED_PIN, BOARD_HEIGHT); .ledPin=STACK2_LED_PIN
CubeStack stack8(STACK8_ADC_PIN, STACK8_LED_PIN, BOARD_HEIGHT); };
CubeStack stack9(STACK9_ADC_PIN, STACK9_LED_PIN, BOARD_HEIGHT); 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 static std::array<BoardDriverTypes::CubeStack, NUMBER_STACKS> stacks{
CubeStack * stacks[] = { stack1, stack2, stack3,
&stack1, &stack2, &stack3, stack4, stack5, stack6,
&stack4, &stack5, &stack6, stack7, stack8, stack9
&stack7, &stack8, &stack9
}; };

54
include/Vector3D.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include <cstdint>
#include <cmath>
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;
};

353
lib/Animator/Animator.h Normal file
View File

@@ -0,0 +1,353 @@
#pragma once
#include <cstdint>
#include <array>
#include <chrono>
#include <cstring>
#include <cmath>
#include <climits>
#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<Cell> frame;
FillInterpolation fillInterpolation;
FrameInterpolation frameInterpolation;
std::chrono::milliseconds delay;
};
typedef std::array<std::array<std::array<V3D, Z_SIZE>, Y_SIZE>, X_SIZE> Frame;
void StartAnimation(const std::vector<AnimationFrame> &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<AnimationFrame> & 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<float>(std::numeric_limits<uint32_t>::max())};
// scale the key frame values down to be within board coordinates
float keyFrame_X = static_cast<float>(this->X_SIZE) * static_cast<float>(keyFramePosition.x) / maxValue;
float keyFrame_Y = static_cast<float>(this->Y_SIZE) * static_cast<float>(keyFramePosition.y) / maxValue;
float keyFrame_Z = static_cast<float>(this->Z_SIZE) * static_cast<float>(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<uint32_t>(keyFrame_X);
}
else{
returnValue.x = static_cast<uint32_t>(keyFrame_X) + 1;
}
if(keyFrame_Y - std::floor(keyFrame_Y) < 0.5f){
returnValue.y = static_cast<uint32_t>(keyFrame_Y);
}
else{
returnValue.y = static_cast<uint32_t>(keyFrame_Y) + 1;
}
if(keyFrame_Z - std::floor(keyFrame_Z) < 0.5f){
returnValue.z = static_cast<uint32_t>(keyFrame_Z);
}
else{
returnValue.z = static_cast<uint32_t>(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<uint32_t>((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<uint32_t>((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<uint32_t>::max()};
Animation::Cell &CreateCell(float x_percent, float y_percent, float z_percent, V3D &color){
float continuousMaxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
Animation::Cell cell{
.position = V3D{
static_cast<uint32_t>(continuousMaxValue*x_percent),
static_cast<uint32_t>(continuousMaxValue*y_percent),
static_cast<uint32_t>(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<AnimationFrame> testAnimationSequence{
noFillFrame,
closestColorFrame,
linearFillFrame,
squareFillFrame,
noFillFadeFrame,
closestFillFadeFrame,
linearFillFadeFrame,
squareFillFadeFrame
};
}

View File

@@ -2,20 +2,25 @@
#include <cstdint> #include <cstdint>
#include <array> #include <array>
#include <WString.h>
#include "Cube.h" #include "Cube.h"
#include "Color.h" #include "Vector3D.h"
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE> template <V3D &BOARD_DIMS>
class Board{ class Board{
public: public:
enum PLANE_NORMAL : uint32_t{
X = 0,
Y,
Z
};
Board(); Board();
~Board() = default; ~Board() = default;
constexpr uint32_t GetXSize() const{return X_SIZE;} constexpr V3D &GetSize() const{return BOARD_DIMS;}
constexpr uint32_t GetYSize() const{return Y_SIZE;} constexpr uint32_t GetNumberCubes() const{return BOARD_DIMS.x * BOARD_DIMS.y * BOARD_DIMS.z;}
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: * @brief Returns a string in the format:
@@ -28,13 +33,25 @@ class Board{
/** /**
* @brief Returns a bool array representing the board * @brief Returns a bool array representing the board
*/ */
std::array<uint32_t, X_SIZE * Y_SIZE> &LinearizeBoard() const; std::array<uint32_t, BOARD_DIMS.x * BOARD_DIMS.y> &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: private:
// this is a 3d array of cubes to represent the board. Good luck visualizing it // this is a 3d array of cubes to represent the board. Good luck visualizing it
@@ -53,13 +70,13 @@ class Board{
bool boardStateHasChanged; bool boardStateHasChanged;
}; };
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE> template <V3D &BOARD_DIMS>
Board<X_SIZE, Y_SIZE, Z_SIZE>::Board(){ Board<BOARD_DIMS>::Board(){
this->FillColor(Color(0,0,0)); this->FillColor(Color(0,0,0));
} }
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE> template <V3D &BOARD_DIMS>
void Board<X_SIZE, Y_SIZE, Z_SIZE>::ToStackString(String &stringBuffer) const{ void Board<BOARD_DIMS>::ToStackString(String &stringBuffer) const{
std::array<uint32_t, X_SIZE*Y_SIZE> linearizedBoard = this->LinearizeBoard(); std::array<uint32_t, X_SIZE*Y_SIZE> linearizedBoard = this->LinearizeBoard();
stringBuffer += "!" + String(linearizedBoard[0]); stringBuffer += "!" + String(linearizedBoard[0]);
@@ -71,10 +88,10 @@ void Board<X_SIZE, Y_SIZE, Z_SIZE>::ToStackString(String &stringBuffer) const{
stringBuffer += ";"; stringBuffer += ";";
} }
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE> template <V3D &BOARD_DIMS>
std::array<uint32_t, X_SIZE * Y_SIZE> & Board<X_SIZE, Y_SIZE, Z_SIZE>::LinearizeBoard() const{ std::array<uint32_t, BOARD_DIMS.x * BOARD_DIMS.y> & Board<BOARD_DIMS>::LinearizeBoard() const{
// convert the board into one array where each entry represents the height of one stack // convert the board into one array where each entry represents the height of one stack
std::array<uint32_t, X_SIZE*Y_SIZE> linearizedBoard; std::array<uint32_t, BOARD_DIMS.x * BOARD_DIMS.y> linearizedBoard;
for(uint32_t x{0}; x < X_SIZE; x++){ for(uint32_t x{0}; x < X_SIZE; x++){
for(uint32_t y{0}; y < Y_SIZE; y++){ for(uint32_t y{0}; y < Y_SIZE; y++){
for(uint32_t z{0}; z < Z_SIZE; z++){ for(uint32_t z{0}; z < Z_SIZE; z++){
@@ -86,24 +103,63 @@ std::array<uint32_t, X_SIZE * Y_SIZE> & Board<X_SIZE, Y_SIZE, Z_SIZE>::Linearize
return linearizedBoard; return linearizedBoard;
} }
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE> template <V3D &BOARD_DIMS>
void Board<X_SIZE, Y_SIZE, Z_SIZE>::Update(){ void Board<BOARD_DIMS>::FillColor(const V3D &color){
}
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE>
void Board<X_SIZE, Y_SIZE, Z_SIZE>::FillColor(const Color &color){
for(uint32_t x{0}; x < X_SIZE; x++){ for(uint32_t x{0}; x < X_SIZE; x++){
for(uint32_t y{0}; y < Y_SIZE; y++){ for(uint32_t y{0}; y < Y_SIZE; y++){
for(uint32_t z{0}; z < Z_SIZE; z++){ for(uint32_t z{0}; z < Z_SIZE; z++){
this->cubes[x][y][z].SetColor(color); this->cubes[x][y][z].color = color;
} }
} }
} }
} }
template <uint32_t X_SIZE, uint32_t Y_SIZE, uint32_t Z_SIZE> template <V3D &BOARD_DIMS>
void Board<X_SIZE, Y_SIZE, Z_SIZE>::SetCubeColor(uint32_t x_coord, uint32_t y_cord, uint32_t z_cord, const Color &color){ void Board<BOARD_DIMS>::SetCubeColor(const V3D &position, const V3D &color){
this->cubes[x][y][z].SetColor(color); this->cubes[position.x][position.y][position.z].color = color;
}
template <V3D &BOARD_DIMS>
void Board<BOARD_DIMS>::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 <V3D &BOARD_DIMS>
Cube * Board<BOARD_DIMS>::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<Cube *, columnLength> 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();
} }

View File

@@ -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();
}
}

View File

@@ -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;
};

View File

@@ -1,22 +0,0 @@
/**
* @file Color.h
* @brief This file contains the color struct
*/
#pragma once
#include <Arduino.h>
// 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};
};

View File

@@ -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<uint16_t>((static_cast<float>(cube.lastADCReading) * 0.9) + (static_cast<float>(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();
}

View File

@@ -1,31 +0,0 @@
/**
* @brief this manages a single cube stack and the lighting / detecting of how many cubes
*/
#pragma once
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#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();
};

View File

@@ -4,15 +4,10 @@
#include <array> #include <array>
#include <Adafruit_NeoPixel.h> #include <Adafruit_NeoPixel.h>
#include <Arduino.h>
#include "Cube.h" #include "Cube.h"
#include "BoardDriverTypes.h"
namespace BoardDriverTypes{
struct CubeStack{
uint8_t adcPin;
uint8_t ledPin;
}
};
template <uint32_t NUM_STACKS> template <uint32_t NUM_STACKS>
class BoardDriver{ class BoardDriver{
@@ -21,6 +16,8 @@ public:
BoardDriver(std::array<BoardDriverTypes::CubeStack, NUM_STACKS> &stacks, Adafruit_NeoPixel &pixelController); BoardDriver(std::array<BoardDriverTypes::CubeStack, NUM_STACKS> &stacks, Adafruit_NeoPixel &pixelController);
~BoardDriver() = default; ~BoardDriver() = default;
void Init();
uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD); uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD);
uint32_t GetNumberCubes(uint32_t stackIndex); uint32_t GetNumberCubes(uint32_t stackIndex);
@@ -38,6 +35,16 @@ private:
} }
}; };
template<uint32_t NUM_STACKS>
void BoardDriver<NUM_STACKS>::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<uint32_t NUM_STACKS> template<uint32_t NUM_STACKS>
BoardDriver<NUM_STACKS>::BoardDriver(std::array<BoardDriverTypes::CubeStack, NUM_STACKS> &stacks, Adafruit_NeoPixel &pixelController): BoardDriver<NUM_STACKS>::BoardDriver(std::array<BoardDriverTypes::CubeStack, NUM_STACKS> &stacks, Adafruit_NeoPixel &pixelController):
stacks(stacks), stacks(stacks),
@@ -45,11 +52,7 @@ pixelController(pixelController)
{ {
for(uint32_t i = 0; i < NUM_STACKS; i++){ for(uint32_t i = 0; i < NUM_STACKS; i++){
this->filteredReadings[i] = 0; this->filteredReadings[i] = 0;
pinMode(this->stacks[i].ledPin, OUTPUT);
} }
// begin doesn't really do anything besides setting the pinmode
this->pixelController.begin();
} }
template<uint32_t NUM_STACKS> template<uint32_t NUM_STACKS>

View File

@@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
namespace BoardDriverTypes{
struct CubeStack{
uint8_t adcPin;
uint8_t ledPin;
};
};

View File

@@ -0,0 +1,102 @@
#pragma once
#include "Board.h"
#include "BoardDriver.h"
#include "Vector3D.h"
template <V3D &BOARD_DIMS>
class BoardManager{
public:
BoardManager(BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &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<BOARD_WIDTH*BOARD_LENGTH> &driver;
Board<BOARD_DIMS> board{};
bool hasBoardChanged{false};
void updateBoardColors(const V3D &column);
};
template <V3D &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::Init(){
this->driver.Init();
}
template <V3D &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::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 <V3D &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::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 <V3D &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::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);
}

View File

@@ -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);
}
}
}

View File

@@ -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)}
};
};

View File

@@ -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;
}

View File

@@ -5,17 +5,10 @@
#pragma once #pragma once
#include "Color.h" #include "Vector3D.h"
class Cube{ class Cube{
public: public:
Cube(); V3D color;
Cube();
void SetColor(const Color &color);
uint16_t lastADCReading{0};
Color color;
bool isOccupied{false}; bool isOccupied{false};
}; };

View File

@@ -11,9 +11,7 @@
// project specific libraries // project specific libraries
#include "BluetoothSerial.h" #include "BluetoothSerial.h"
#include "SerialMessage.h" #include "SerialMessage.h"
#include "BoardLayout.h"
#include "Color.h" #include "Color.h"
#include "ColorManager.h"
#include "GlobalPrint.h" #include "GlobalPrint.h"
// -------------------------------------------------- // --------------------------------------------------
@@ -29,10 +27,6 @@ uint32_t boardStateMaxUpdatePeriod{34}; // this is a little slower than 30fps
// BluetoothSerial SerialBT; // BluetoothSerial SerialBT;
// BluetoothSerialMessage serialMessageBT(&SerialBT); // BluetoothSerialMessage serialMessageBT(&SerialBT);
SerialMessage<500, 10> serialMessage(&Serial); 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 ---------------------- // ----------------- FUNCTIONS ----------------------