Merge pull request #10 from Block-Party-VR/5-create-more-eye-catching-idle-animations
Refactoring the board tracking code and adding new functionality to make it easy to make animations for the board
This commit is contained in:
@@ -4,32 +4,67 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include "PINOUT.h"
|
#include "PINOUT.h"
|
||||||
#include "CubeStack.h"
|
#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
|
// 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};
|
||||||
|
|
||||||
|
static constexpr V3D<uint32_t> BOARD_DIMENSIONS{BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT};
|
||||||
|
|
||||||
// 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 BOARD_TYPES::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 BOARD_TYPES::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 BOARD_TYPES::CubeStack stack3{
|
||||||
|
.adcPin=STACK3_ADC_PIN,
|
||||||
// define the array of stacks
|
.ledPin=STACK3_LED_PIN
|
||||||
CubeStack * stacks[] = {
|
};
|
||||||
&stack1, &stack2, &stack3,
|
static BOARD_TYPES::CubeStack stack4{
|
||||||
&stack4, &stack5, &stack6,
|
.adcPin=STACK4_ADC_PIN,
|
||||||
&stack7, &stack8, &stack9
|
.ledPin=STACK4_LED_PIN
|
||||||
|
};
|
||||||
|
static BOARD_TYPES::CubeStack stack5{
|
||||||
|
.adcPin=STACK5_ADC_PIN,
|
||||||
|
.ledPin=STACK5_LED_PIN
|
||||||
|
};
|
||||||
|
static BOARD_TYPES::CubeStack stack6{
|
||||||
|
.adcPin=STACK6_ADC_PIN,
|
||||||
|
.ledPin=STACK6_LED_PIN
|
||||||
|
};
|
||||||
|
static BOARD_TYPES::CubeStack stack7{
|
||||||
|
.adcPin=STACK7_ADC_PIN,
|
||||||
|
.ledPin=STACK7_LED_PIN
|
||||||
|
};
|
||||||
|
static BOARD_TYPES::CubeStack stack8{
|
||||||
|
.adcPin=STACK8_ADC_PIN,
|
||||||
|
.ledPin=STACK8_LED_PIN
|
||||||
|
};
|
||||||
|
static BOARD_TYPES::CubeStack stack9{
|
||||||
|
.adcPin=STACK9_ADC_PIN,
|
||||||
|
.ledPin=STACK9_LED_PIN
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::array<BOARD_TYPES::CubeStack, NUMBER_STACKS> stacks{
|
||||||
|
stack1, stack2, stack3,
|
||||||
|
stack4, stack5, stack6,
|
||||||
|
stack7, stack8, stack9
|
||||||
};
|
};
|
||||||
@@ -5,45 +5,47 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
// Stack pins
|
// Stack pins
|
||||||
// Stack 1 pins
|
// Stack 1 pins
|
||||||
#define STACK1_ADC_PIN 6
|
static constexpr uint8_t STACK1_ADC_PIN{6};
|
||||||
#define STACK1_LED_PIN 11
|
static constexpr uint8_t STACK1_LED_PIN{11};
|
||||||
|
|
||||||
// Stack 2 pins
|
// Stack 2 pins
|
||||||
#define STACK2_ADC_PIN 16
|
static constexpr uint8_t STACK2_ADC_PIN{16};
|
||||||
#define STACK2_LED_PIN 14
|
static constexpr uint8_t STACK2_LED_PIN{14};
|
||||||
|
|
||||||
// Stack 3 pins
|
// Stack 3 pins
|
||||||
#define STACK3_ADC_PIN 8
|
static constexpr uint8_t STACK3_ADC_PIN{8};
|
||||||
#define STACK3_LED_PIN 45
|
static constexpr uint8_t STACK3_LED_PIN{45};
|
||||||
|
|
||||||
// Stack 4 pins
|
// Stack 4 pins
|
||||||
#define STACK4_ADC_PIN 5
|
static constexpr uint8_t STACK4_ADC_PIN{5};
|
||||||
#define STACK4_LED_PIN 10
|
static constexpr uint8_t STACK4_LED_PIN{10};
|
||||||
|
|
||||||
// Stack 5 pins
|
// Stack 5 pins
|
||||||
#define STACK5_ADC_PIN 15
|
static constexpr uint8_t STACK5_ADC_PIN{15};
|
||||||
#define STACK5_LED_PIN 13
|
static constexpr uint8_t STACK5_LED_PIN{13};
|
||||||
|
|
||||||
// Stack 6 pins
|
// Stack 6 pins
|
||||||
#define STACK6_ADC_PIN 18
|
static constexpr uint8_t STACK6_ADC_PIN{18};
|
||||||
#define STACK6_LED_PIN 38
|
static constexpr uint8_t STACK6_LED_PIN{38};
|
||||||
|
|
||||||
// Stack 7 pins
|
// Stack 7 pins
|
||||||
#define STACK7_ADC_PIN 4
|
static constexpr uint8_t STACK7_ADC_PIN{4};
|
||||||
#define STACK7_LED_PIN 9
|
static constexpr uint8_t STACK7_LED_PIN{9};
|
||||||
|
|
||||||
// Stack 8 pins
|
// Stack 8 pins
|
||||||
#define STACK8_ADC_PIN 7
|
static constexpr uint8_t STACK8_ADC_PIN{7};
|
||||||
#define STACK8_LED_PIN 12
|
static constexpr uint8_t STACK8_LED_PIN{12};
|
||||||
|
|
||||||
|
|
||||||
// Stack 9 pins
|
// Stack 9 pins
|
||||||
#define STACK9_ADC_PIN 17
|
static constexpr uint8_t STACK9_ADC_PIN{17};
|
||||||
#define STACK9_LED_PIN 37
|
static constexpr uint8_t STACK9_LED_PIN{37};
|
||||||
|
|
||||||
// Bluetooth module configuration pins
|
// Bluetooth module configuration pins
|
||||||
#define BT_STATE_PIN 2
|
static constexpr uint8_t BT_STATE_PIN{2};
|
||||||
#define BT_EN_PIN 3
|
static constexpr uint8_t BT_EN_PIN{3};
|
||||||
|
|
||||||
|
|||||||
80
include/Vector3D.h
Normal file
80
include/Vector3D.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
class V3D{
|
||||||
|
public:
|
||||||
|
constexpr V3D(const V3D& other):
|
||||||
|
x(other.x),
|
||||||
|
y(other.y),
|
||||||
|
z(other.z){
|
||||||
|
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr V3D(Type x=0, Type y=0, Type z=0):
|
||||||
|
x(x),
|
||||||
|
y(y),
|
||||||
|
z(z){
|
||||||
|
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OtherType>
|
||||||
|
constexpr V3D(const V3D<OtherType> other):
|
||||||
|
x(static_cast<Type>(other.x)),
|
||||||
|
y(static_cast<Type>(other.y)),
|
||||||
|
z(static_cast<Type>(other.z)){
|
||||||
|
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
|
||||||
|
static_assert(std::is_arithmetic<OtherType>::value, "OtherType must be a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
V3D& operator=(const V3D &other){
|
||||||
|
this->x = other.x;
|
||||||
|
this->y = other.y;
|
||||||
|
this->z = other.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
V3D& operator+=(const V3D &other){
|
||||||
|
this->x += other.x;
|
||||||
|
this->y += other.y;
|
||||||
|
this->z += other.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
V3D& operator-=(const V3D &other){
|
||||||
|
this->x -= other.x;
|
||||||
|
this->y -= other.y;
|
||||||
|
this->z -= other.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
V3D& operator/=(const Type scalar){
|
||||||
|
if(scalar == 0){
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
this->x /= scalar;
|
||||||
|
this->y /= scalar;
|
||||||
|
this->z /= scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
V3D& operator*=(const Type scalar){
|
||||||
|
this->x *= scalar;
|
||||||
|
this->y *= scalar;
|
||||||
|
this->z *= scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const V3D &other){
|
||||||
|
return this->x == other.x && this->y == other.y && this->z == other.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
float magnitude(){
|
||||||
|
return std::sqrt(static_cast<float>(this->x * this->x + this->y * this->y + this->z * this->z));
|
||||||
|
}
|
||||||
|
Type x;
|
||||||
|
Type y;
|
||||||
|
Type z;
|
||||||
|
};
|
||||||
221
lib/Animator/Animation.h
Normal file
221
lib/Animator/Animation.h
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AnimationTypes.h"
|
||||||
|
#include "Vector3D.h"
|
||||||
|
#include "Animator.h"
|
||||||
|
|
||||||
|
using namespace ANIMATION_TYPES;
|
||||||
|
|
||||||
|
namespace AnimationHelpers{
|
||||||
|
V3D<uint8_t> red{255,0,0};
|
||||||
|
V3D<uint8_t> green{0,255,0};
|
||||||
|
V3D<uint8_t> blue{0,0,255};
|
||||||
|
V3D<uint8_t> cyan{0,255,255};
|
||||||
|
V3D<uint8_t> magenta{255,0,255};
|
||||||
|
|
||||||
|
Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D<uint8_t> &color){
|
||||||
|
float continuousMaxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
|
||||||
|
Cell cell{
|
||||||
|
.position = V3D<uint32_t>{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<AnimationFrame> 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<AnimationFrame> rising{
|
||||||
|
frame1, // 0
|
||||||
|
frame2, // 1
|
||||||
|
frame3, // 2
|
||||||
|
frame4, // 3
|
||||||
|
frame5
|
||||||
|
};
|
||||||
|
}
|
||||||
33
lib/Animator/AnimationTypes.h
Normal file
33
lib/Animator/AnimationTypes.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Vector3D.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
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<uint32_t> position;
|
||||||
|
V3D<uint32_t> 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
293
lib/Animator/Animator.h
Normal file
293
lib/Animator/Animator.h
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cmath>
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
#include "Vector3D.h"
|
||||||
|
#include "AnimationTypes.h"
|
||||||
|
|
||||||
|
using namespace ANIMATION_TYPES;
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
class Animator{
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef std::array<std::array<std::array<V3D<uint32_t>, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x> Frame;
|
||||||
|
|
||||||
|
|
||||||
|
void StartAnimation(const std::vector<AnimationFrame> *animationSequence);
|
||||||
|
|
||||||
|
void RunAnimation(const std::chrono::milliseconds& timePassed);
|
||||||
|
|
||||||
|
void SetLoop(bool isLooping);
|
||||||
|
|
||||||
|
Frame &GetInterpolatedFrame(){return this->interpolatedFrame;}
|
||||||
|
|
||||||
|
bool isEnabled{true};
|
||||||
|
bool interpolatedFrameHasChanged{false};
|
||||||
|
|
||||||
|
private:
|
||||||
|
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 interpolatedFrame;
|
||||||
|
Frame endFrame;
|
||||||
|
std::chrono::milliseconds timeElapsed;
|
||||||
|
|
||||||
|
const std::vector<AnimationFrame> *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<uint32_t> getInterpolatedColor(const AnimationFrame &keyFrame, V3D<uint32_t> position);
|
||||||
|
|
||||||
|
V3D<uint32_t> keyFrame2BoardCoords(const V3D<uint32_t> &keyFramePosition);
|
||||||
|
|
||||||
|
V3D<uint32_t> noFillInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
|
||||||
|
|
||||||
|
V3D<uint32_t> closestColorInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
|
||||||
|
|
||||||
|
V3D<uint32_t> linearInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
|
||||||
|
|
||||||
|
V3D<uint32_t> squareInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Animator<BOARD_DIMS>::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){
|
||||||
|
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((*this->animationSequence)[0], this->startFrame);
|
||||||
|
this->copyFrame(this->startFrame, this->interpolatedFrame);
|
||||||
|
this->uncompressFrame((*this->animationSequence)[1], this->endFrame);
|
||||||
|
}
|
||||||
|
this->interpolatedFrameHasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Animator<BOARD_DIMS>::RunAnimation(const std::chrono::milliseconds& timePassed){
|
||||||
|
if(!(this->isEnabled)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto delayTime = (*this->animationSequence)[this->animationIndex].delay;
|
||||||
|
this->timeElapsed += timePassed;
|
||||||
|
|
||||||
|
// 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 < 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<float> startColor{this->startFrame[x][y][z]};
|
||||||
|
V3D<float> endColor{this->endFrame[x][y][z]};
|
||||||
|
V3D<float> difference{endColor};
|
||||||
|
difference -= startColor;
|
||||||
|
|
||||||
|
V3D<float> interpolatedColor{difference};
|
||||||
|
interpolatedColor *= this->timeElapsed.count();
|
||||||
|
interpolatedColor /= delayTime.count();
|
||||||
|
interpolatedColor += startColor;
|
||||||
|
|
||||||
|
this->interpolatedFrame[x][y][z] = interpolatedColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->interpolatedFrameHasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Animator<BOARD_DIMS>::SetLoop(bool isLooping){
|
||||||
|
this->isLooping = isLooping;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Animator<BOARD_DIMS>::incrimentAnimationIndex(){
|
||||||
|
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{
|
||||||
|
this->StartAnimation(this->animationSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->interpolatedFrameHasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Animator<BOARD_DIMS>::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<uint32_t>(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
V3D<uint32_t> Animator<BOARD_DIMS>::getInterpolatedColor(const AnimationFrame &keyFrame, V3D<uint32_t> 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:
|
||||||
|
V3D<uint32_t> black{};
|
||||||
|
return black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
V3D<uint32_t> Animator<BOARD_DIMS>::keyFrame2BoardCoords(const V3D<uint32_t> &keyFramePosition){
|
||||||
|
V3D<uint32_t> 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>(BOARD_DIMS.x - 1) * static_cast<float>(keyFramePosition.x) / maxValue;
|
||||||
|
float keyFrame_Y = static_cast<float>(BOARD_DIMS.y - 1) * static_cast<float>(keyFramePosition.y) / maxValue;
|
||||||
|
float keyFrame_Z = static_cast<float>(BOARD_DIMS.z - 1) * 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
V3D<uint32_t> Animator<BOARD_DIMS>::noFillInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position){
|
||||||
|
V3D<uint32_t> returnColor{};
|
||||||
|
for(Cell cell : keyFrame.frame){
|
||||||
|
if(keyFrame2BoardCoords(cell.position) == position){
|
||||||
|
returnColor = cell.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
V3D<uint32_t> Animator<BOARD_DIMS>::closestColorInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> cubePosition){
|
||||||
|
V3D<uint32_t> returnColor{keyFrame.frame[0].color};
|
||||||
|
V3D<uint32_t> distance{keyFrame.frame[0].position};
|
||||||
|
distance -= cubePosition;
|
||||||
|
float closestDistance = distance.magnitude();
|
||||||
|
|
||||||
|
for(Cell cell : keyFrame.frame){
|
||||||
|
distance = keyFrame2BoardCoords(cell.position);
|
||||||
|
distance -= cubePosition;
|
||||||
|
float euclidDistance = distance.magnitude();
|
||||||
|
if(euclidDistance < closestDistance){
|
||||||
|
returnColor = cell.color;
|
||||||
|
closestDistance = euclidDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
V3D<uint32_t> Animator<BOARD_DIMS>::linearInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position){
|
||||||
|
V3D<uint32_t> returnColor{};
|
||||||
|
|
||||||
|
for(Cell cell : keyFrame.frame){
|
||||||
|
V3D<uint32_t> vectorDistance{keyFrame2BoardCoords(cell.position)};
|
||||||
|
vectorDistance -= position;
|
||||||
|
float distance = vectorDistance.magnitude();
|
||||||
|
if(distance == 0) return cell.color;
|
||||||
|
returnColor += cell.color;
|
||||||
|
returnColor /= distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnColor /= keyFrame.frame.size();
|
||||||
|
|
||||||
|
return returnColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
V3D<uint32_t> Animator<BOARD_DIMS>::squareInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position){
|
||||||
|
V3D<uint32_t> returnColor{};
|
||||||
|
|
||||||
|
for(Cell cell : keyFrame.frame){
|
||||||
|
V3D<uint32_t> vectorDistance{keyFrame2BoardCoords(cell.position)};
|
||||||
|
vectorDistance -= position;
|
||||||
|
uint32_t distance = static_cast<uint32_t>(vectorDistance.magnitude());
|
||||||
|
distance *= distance;
|
||||||
|
if(distance == 0) return cell.color;
|
||||||
|
returnColor += cell.color;
|
||||||
|
returnColor /= distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnColor /= keyFrame.frame.size();
|
||||||
|
|
||||||
|
return returnColor;
|
||||||
|
}
|
||||||
169
lib/Animator/TestFrames.h
Normal file
169
lib/Animator/TestFrames.h
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AnimationTypes.h"
|
||||||
|
#include "Vector3D.h"
|
||||||
|
#include "Animator.h"
|
||||||
|
|
||||||
|
using namespace ANIMATION_TYPES;
|
||||||
|
|
||||||
|
namespace TestFrames{
|
||||||
|
|
||||||
|
V3D<uint32_t> red{255,0,0};
|
||||||
|
V3D<uint32_t> green{0,255,0};
|
||||||
|
V3D<uint32_t> blue{0,0,255};
|
||||||
|
uint32_t maxValue{std::numeric_limits<uint32_t>::max()};
|
||||||
|
|
||||||
|
Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D<uint32_t> &color){
|
||||||
|
float continuousMaxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
|
||||||
|
Cell cell{
|
||||||
|
.position = V3D<uint32_t>{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<AnimationFrame> testAnimationSequence2{
|
||||||
|
noFillFrame, // 0
|
||||||
|
closestColorFrame, // 1
|
||||||
|
linearFillFrame, // 2
|
||||||
|
squareFillFrame, // 3
|
||||||
|
noFillFadeFrame, // 4
|
||||||
|
closestColorFadeFrame, // 5
|
||||||
|
linearFillFadeFrame, // 6
|
||||||
|
squareFillFadeFrame, // 7
|
||||||
|
noFillFrame // 8
|
||||||
|
};
|
||||||
|
|
||||||
|
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 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::FADE,
|
||||||
|
.delay = std::chrono::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
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::FADE,
|
||||||
|
.delay = std::chrono::milliseconds(10000)
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<AnimationFrame> testAnimationSequence1{
|
||||||
|
testFrame1,
|
||||||
|
testFrame2,
|
||||||
|
testFrame3,
|
||||||
|
testFrame1
|
||||||
|
};
|
||||||
|
}
|
||||||
205
lib/Board/Board.h
Normal file
205
lib/Board/Board.h
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* @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 <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include <WString.h>
|
||||||
|
|
||||||
|
#include "BoardTypes.h"
|
||||||
|
#include "Vector3D.h"
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
class Board{
|
||||||
|
public:
|
||||||
|
Board() = default;
|
||||||
|
~Board() = default;
|
||||||
|
|
||||||
|
constexpr const V3D<uint32_t> &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;
|
||||||
|
* 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 fill the entire board with the given color
|
||||||
|
* @param color the color to fill the board with
|
||||||
|
*/
|
||||||
|
void FillColor(const V3D<uint32_t> &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<uint32_t> &position, const V3D<uint32_t> &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<uint32_t> &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;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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<uint32_t>(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<uint32_t> &column, BOARD_TYPES::Cube ** sliceBuffer);
|
||||||
|
|
||||||
|
void PrintEntireBoard() const;
|
||||||
|
|
||||||
|
void UpdateAllColors(const std::array<std::array<std::array<V3D<uint32_t>, 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++){
|
||||||
|
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
|
||||||
|
/* _____________
|
||||||
|
/ /|
|
||||||
|
/ / |
|
||||||
|
/ / |
|
||||||
|
/____________/ |
|
||||||
|
| | |
|
||||||
|
| | /
|
||||||
|
| | /
|
||||||
|
|____________|/
|
||||||
|
*/
|
||||||
|
std::array<std::array<std::array<BOARD_TYPES::Cube, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x> cubes;
|
||||||
|
|
||||||
|
bool boardStateHasChanged;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Board<BOARD_DIMS>::ToStackString(String &stringBuffer) const{
|
||||||
|
std::array<uint32_t, BOARD_DIMS.x * BOARD_DIMS.y> 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 = 1; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){
|
||||||
|
stringBuffer += "," + String(linearizedBoard[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Board<BOARD_DIMS>::FillColor(const V3D<uint32_t> &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++){
|
||||||
|
this->cubes[x][y][z].color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Board<BOARD_DIMS>::SetCubeColor(const V3D<uint32_t> &position, const V3D<uint32_t> &color){
|
||||||
|
this->cubes[position.x][position.y][position.z].color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Board<BOARD_DIMS>::SetCubeOccupation(const V3D<uint32_t> &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 <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
uint32_t Board<BOARD_DIMS>::SliceBoard(const V3D<uint32_t> &column, BOARD_TYPES::Cube ** sliceBuffer){
|
||||||
|
uint32_t columnLength{0};
|
||||||
|
V3D<uint32_t> indexIncrimentVector{};
|
||||||
|
V3D<uint32_t> indexVector{};
|
||||||
|
|
||||||
|
switch(column.z){
|
||||||
|
case BOARD_TYPES::PLANE_NORMAL::X:
|
||||||
|
columnLength = BOARD_DIMS.x;
|
||||||
|
indexIncrimentVector.x = 1;
|
||||||
|
indexVector.z = column.x;
|
||||||
|
indexVector.y = column.y;
|
||||||
|
break;
|
||||||
|
case BOARD_TYPES::PLANE_NORMAL::Y:
|
||||||
|
columnLength = BOARD_DIMS.y;
|
||||||
|
indexIncrimentVector.y = 1;
|
||||||
|
indexVector.x = column.x;
|
||||||
|
indexVector.z = column.y;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
case BOARD_TYPES::PLANE_NORMAL::Z:
|
||||||
|
columnLength = BOARD_DIMS.z;
|
||||||
|
indexIncrimentVector.z = 1;
|
||||||
|
indexVector.x = column.x;
|
||||||
|
indexVector.y = column.y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < columnLength; i++){
|
||||||
|
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 <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void Board<BOARD_DIMS>::PrintEntireBoard() const{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
187
lib/Board/BoardDriver.h
Normal file
187
lib/Board/BoardDriver.h
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* @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 <cstdint>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "BoardTypes.h"
|
||||||
|
#include "BoardTypes.h"
|
||||||
|
|
||||||
|
template <uint32_t NUM_STACKS>
|
||||||
|
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<BOARD_TYPES::CubeStack, NUM_STACKS> &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,
|
||||||
|
BOARD_TYPES::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,
|
||||||
|
BOARD_TYPES::Cube* cubes[],
|
||||||
|
uint32_t numCubes
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<BOARD_TYPES::CubeStack, NUM_STACKS> &stacks;
|
||||||
|
Adafruit_NeoPixel &pixelController;
|
||||||
|
std::array<uint16_t, NUM_STACKS> filteredReadings;
|
||||||
|
|
||||||
|
uint32_t xy2StackIndex(uint32_t x_coord, uint32_t y_coord, uint32_t numXStacks){
|
||||||
|
return x_coord + y_coord*numXStacks;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
filteredReadings[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// begin doesn't really do anything besides setting the pinmode
|
||||||
|
this->pixelController.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<uint32_t NUM_STACKS>
|
||||||
|
BoardDriver<NUM_STACKS>::BoardDriver(
|
||||||
|
std::array<BOARD_TYPES::CubeStack, NUM_STACKS> &stacks,
|
||||||
|
Adafruit_NeoPixel &pixelController
|
||||||
|
):
|
||||||
|
stacks(stacks),
|
||||||
|
pixelController(pixelController)
|
||||||
|
{
|
||||||
|
for(uint32_t i = 0; i < NUM_STACKS; i++){
|
||||||
|
this->filteredReadings[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<uint32_t NUM_STACKS>
|
||||||
|
void BoardDriver<NUM_STACKS>::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<uint32_t NUM_STACKS>
|
||||||
|
void BoardDriver<NUM_STACKS>::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<uint32_t> 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 NUM_STACKS>
|
||||||
|
uint32_t BoardDriver<NUM_STACKS>::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 NUM_STACKS>
|
||||||
|
uint32_t BoardDriver<NUM_STACKS>::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(this->stacks[stackIndex].adcPin);
|
||||||
|
uint16_t lowPassADCRead =
|
||||||
|
static_cast<uint16_t>(
|
||||||
|
(static_cast<float>(this->filteredReadings[stackIndex]) * 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->filteredReadings[stackIndex] = lowPassADCRead;
|
||||||
|
return stackHeight;
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
};
|
|
||||||
197
lib/Board/BoardManager.h
Normal file
197
lib/Board/BoardManager.h
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* @file BoardManager.h
|
||||||
|
* @brief Provides an easy way to manage a board
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Board.h"
|
||||||
|
#include "BoardDriver.h"
|
||||||
|
#include "Vector3D.h"
|
||||||
|
#include "Animator.h"
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
class BoardManager{
|
||||||
|
public:
|
||||||
|
BoardManager(BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &boardDriver, Animator<BOARD_DIMS> &animator);
|
||||||
|
|
||||||
|
~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<uint32_t> &position, const V3D<uint32_t> &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<uint32_t> &column, const V3D<uint32_t> *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<uint32_t>(0,2,PLANE_NORMAL::Z)
|
||||||
|
* @param color the color you want to fill the column with
|
||||||
|
*/
|
||||||
|
void FillColumnColor(const V3D<uint32_t> &column, const V3D<uint32_t> &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....
|
||||||
|
*/
|
||||||
|
void Board2StackString(String& messageBuffer);
|
||||||
|
|
||||||
|
void FillColor(const V3D<uint32_t> &color){this->board.FillColor(color);}
|
||||||
|
void PrintColorState(){this->board.PrintEntireBoard();}
|
||||||
|
|
||||||
|
private:
|
||||||
|
BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &driver;
|
||||||
|
Board<BOARD_DIMS> board{};
|
||||||
|
Animator<BOARD_DIMS> &animator;
|
||||||
|
|
||||||
|
void updateStackColors(const V3D<uint32_t> &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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateColorsFromAnimator();
|
||||||
|
|
||||||
|
};
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::updateColorsFromAnimator(){
|
||||||
|
if(this->animator.interpolatedFrameHasChanged){
|
||||||
|
this->board.UpdateAllColors(this->animator.GetInterpolatedFrame());
|
||||||
|
this->animator.interpolatedFrameHasChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
BoardManager<BOARD_DIMS>::BoardManager(BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &boardDriver, Animator<BOARD_DIMS> &animator):
|
||||||
|
driver(boardDriver),
|
||||||
|
animator(animator){}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::Init(){
|
||||||
|
this->driver.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::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++){
|
||||||
|
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<uint32_t> cubePosition{x, y, z};
|
||||||
|
// update the cube's occupation
|
||||||
|
this->board.SetCubeOccupation(cubePosition, z < numCubes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the column vector for the slice direction
|
||||||
|
V3D<uint32_t> 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 <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::updateStackColors(const V3D<uint32_t> &column){
|
||||||
|
// the only column type allowed here is z.
|
||||||
|
V3D<uint32_t> 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<BOARD_TYPES::PLANE_NORMAL>(column.z))};
|
||||||
|
this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::SetCubeColor(const V3D<uint32_t> &position, const V3D<uint32_t> &color){
|
||||||
|
this->board.SetCubeColor(position, color);
|
||||||
|
V3D<uint32_t> slice{position.x, position.y, BOARD_TYPES::PLANE_NORMAL::Z};
|
||||||
|
this->updateStackColors(slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::SetColumnColors(const V3D<uint32_t> &column, const V3D<uint32_t> *color, uint32_t numColors){
|
||||||
|
uint32_t columnHeight{this->getColumnHeight(static_cast<BOARD_TYPES::PLANE_NORMAL>(column.z))};
|
||||||
|
|
||||||
|
// create a cube pointer buffer and store a board slice into it
|
||||||
|
BOARD_TYPES::Cube * slicedBoard[columnHeight];
|
||||||
|
uint32_t sliceLength{this->board.SliceBoard(column, slicedBoard)};
|
||||||
|
|
||||||
|
uint32_t maxIndex{std::min(numColors, columnHeight)};
|
||||||
|
for(uint32_t i = 0; i < maxIndex; i++){
|
||||||
|
slicedBoard[i]->color = color[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::FillColumnColor(const V3D<uint32_t> &column, const V3D<uint32_t> &color){
|
||||||
|
uint32_t columnHeight{this->getColumnHeight(column.z)};
|
||||||
|
|
||||||
|
V3D<uint32_t> colors[columnHeight];
|
||||||
|
for(uint32_t i = 0; i < columnHeight; i++){
|
||||||
|
colors[i] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->SetColumnColors(column, colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
bool BoardManager<BOARD_DIMS>::HasBoardChanged(){return this->board.BoardStateChanged();}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::ClearBoardChanged(){this->board.SetStateChanged(false);}
|
||||||
|
|
||||||
|
template <const V3D<uint32_t> &BOARD_DIMS>
|
||||||
|
void BoardManager<BOARD_DIMS>::Board2StackString(String& messageBuffer){
|
||||||
|
this->board.ToStackString(messageBuffer);
|
||||||
|
}
|
||||||
26
lib/Board/BoardTypes.h
Normal file
26
lib/Board/BoardTypes.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* @file BoardTypes.h
|
||||||
|
* @brief Some types to that you'll need to define and control the board
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include "Vector3D.h"
|
||||||
|
|
||||||
|
namespace BOARD_TYPES{
|
||||||
|
struct CubeStack{
|
||||||
|
uint8_t adcPin;
|
||||||
|
uint8_t ledPin;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PLANE_NORMAL : uint32_t{
|
||||||
|
X = 0,
|
||||||
|
Y,
|
||||||
|
Z
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cube{
|
||||||
|
V3D<uint32_t> color;
|
||||||
|
bool isOccupied{false};
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Color.h
|
|
||||||
* @brief This file contains the color struct
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
// store a color
|
|
||||||
struct Color{
|
|
||||||
// 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};
|
|
||||||
};
|
|
||||||
@@ -1,79 +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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t CubeStack::GetNumberCubes(){
|
|
||||||
// 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(this->ADCPin);
|
|
||||||
this->lowPassADCRead = static_cast<uint16_t>((static_cast<float>(this->lowPassADCRead) * 0.9) + (static_cast<float>(value) * 0.1));
|
|
||||||
if(this->lowPassADCRead < 2500 && false){
|
|
||||||
Serial.println("ADC Pin:" + String(this->ADCPin) + " Value: " + String(value) + " Low Pass: " + String(this->lowPassADCRead));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(this->lowPassADCRead >= zeroCubesLow && this->lowPassADCRead <= zeroCubesHigh){
|
|
||||||
stackHeight = 0;
|
|
||||||
}
|
|
||||||
else if(this->lowPassADCRead >= oneCubeLow){
|
|
||||||
stackHeight = 1;
|
|
||||||
}
|
|
||||||
else if(this->lowPassADCRead >= twoCubesLow){
|
|
||||||
stackHeight = 2;
|
|
||||||
}
|
|
||||||
else if(this->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
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
@@ -1,52 +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"
|
|
||||||
|
|
||||||
|
|
||||||
class CubeStack{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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};
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
133
src/main.cpp
133
src/main.cpp
@@ -2,6 +2,9 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <BluetoothSerial.h>
|
#include <BluetoothSerial.h>
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
// Static Defines
|
// Static Defines
|
||||||
#include "PINOUT.h"
|
#include "PINOUT.h"
|
||||||
@@ -11,29 +14,36 @@
|
|||||||
// 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 "ColorManager.h"
|
|
||||||
#include "GlobalPrint.h"
|
#include "GlobalPrint.h"
|
||||||
|
|
||||||
|
#include "BoardManager.h"
|
||||||
|
#include "BoardDriver.h"
|
||||||
|
#include "BoardTypes.h"
|
||||||
|
|
||||||
|
#include "Animator.h"
|
||||||
|
#include "TestFrames.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// ----------------- VARIABLES ----------------------
|
// ----------------- VARIABLES ----------------------
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
TaskHandle_t updateCommunicaitonTask;
|
TaskHandle_t updateCommunicaitonTask;
|
||||||
TaskHandle_t updateBoardTask;
|
TaskHandle_t updateBoardTask;
|
||||||
|
|
||||||
uint32_t boardStateTimer{0};
|
std::array<std::vector<AnimationFrame>*, 2> animations = {
|
||||||
bool boardStateHasChanged{false};
|
&RisingCubes::rising,
|
||||||
uint32_t boardStateMaxUpdatePeriod{34}; // this is a little slower than 30fps
|
&RotatingCubes::rotating,
|
||||||
|
};
|
||||||
|
|
||||||
// BluetoothSerial SerialBT;
|
// BluetoothSerial SerialBT;
|
||||||
// BluetoothSerialMessage serialMessageBT(&SerialBT);
|
// BluetoothSerialMessage serialMessageBT(&SerialBT);
|
||||||
SerialMessage<500, 10> serialMessage(&Serial);
|
SerialMessage<SERIAL_CHAR_LENGTH, SERIAL_ARG_LENGTH> serialMessage(&Serial);
|
||||||
BoardLayout board(BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT, stacks);
|
|
||||||
|
|
||||||
// Temporary thing until we can get bluetooth color management working on the quest
|
Adafruit_NeoPixel pixelController{BOARD_HEIGHT*2, STACK1_LED_PIN, NEO_GRB + NEO_KHZ800};
|
||||||
ColorManager colorManager(&board);
|
|
||||||
|
|
||||||
|
Animator<BOARD_DIMENSIONS> animator{};
|
||||||
|
BoardDriver<BOARD_WIDTH*BOARD_LENGTH> boardDriver{stacks, pixelController};
|
||||||
|
BoardManager<BOARD_DIMENSIONS> boardManager{boardDriver, animator};
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// ----------------- FUNCTIONS ----------------------
|
// ----------------- FUNCTIONS ----------------------
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@@ -64,63 +74,64 @@ void SetupBluetoothModule(){
|
|||||||
|
|
||||||
|
|
||||||
void printBoardState(){
|
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,");
|
GlobalPrint::Print("!0,");
|
||||||
|
String boardString;
|
||||||
for(int i = 0; i < (BOARD_WIDTH * BOARD_LENGTH); i++){
|
boardManager.Board2StackString(boardString);
|
||||||
GlobalPrint::Print(String(boardState[i]));
|
GlobalPrint::Print(boardString);
|
||||||
if(i == (BOARD_WIDTH * BOARD_LENGTH) - 1){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
GlobalPrint::Print(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalPrint::Println(";");
|
GlobalPrint::Println(";");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetStackColor(uint32_t * args, int argsLength){
|
void SetStackColor(uint32_t * args, int argsLength){
|
||||||
uint32_t stackNum = args[1];
|
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;
|
uint32_t numColors = (argsLength - 2) / 3;
|
||||||
Color colors[numColors];
|
V3D<uint32_t> colors[numColors];
|
||||||
|
|
||||||
for(int i = 0; i < numColors; i++){
|
for(int i = 0; i < numColors; i++){
|
||||||
int red = args[2 + (i * 3)];
|
uint32_t red = args[2 + (i * 3)];
|
||||||
int green = args[3 + (i * 3)];
|
uint32_t green = args[3 + (i * 3)];
|
||||||
int blue = args[4 + (i * 3)];
|
uint32_t blue = args[4 + (i * 3)];
|
||||||
colors[i] = Color(red, green, blue);
|
colors[i] = V3D<uint32_t>{red, green, blue};
|
||||||
|
}
|
||||||
|
boardManager.SetColumnColors(V3D<uint32_t>{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors, numColors);
|
||||||
}
|
}
|
||||||
|
|
||||||
board.SetStackColors(stackNum, colors);
|
void parseData(Message<SERIAL_CHAR_LENGTH, SERIAL_ARG_LENGTH> &message){
|
||||||
}
|
|
||||||
|
|
||||||
void parseData(Message<500, 10> &message){
|
|
||||||
int32_t * args{message.GetArgs()};
|
int32_t * args{message.GetArgs()};
|
||||||
uint32_t argsLength{message.GetArgsLength()};
|
uint32_t argsLength{message.GetPopulatedArgs()};
|
||||||
uint32_t command = args[0];
|
uint32_t command = args[0];
|
||||||
switch(command){
|
switch(command){
|
||||||
case Commands::BoardState:
|
case Commands::BoardState:{
|
||||||
printBoardState();
|
printBoardState();
|
||||||
break;
|
break;
|
||||||
case Commands::PING:
|
}
|
||||||
|
case Commands::PING:{
|
||||||
GlobalPrint::Println("!" + String(Commands::PING) + ";");
|
GlobalPrint::Println("!" + String(Commands::PING) + ";");
|
||||||
break;
|
break;
|
||||||
case Commands::SetStackColors:
|
}
|
||||||
|
case Commands::SetStackColors:{
|
||||||
GlobalPrint::Println("!2;");
|
GlobalPrint::Println("!2;");
|
||||||
colorManager.Enable(false);
|
animator.isEnabled = false;
|
||||||
|
V3D<uint32_t> black{};
|
||||||
|
boardManager.FillColor(black);
|
||||||
SetStackColor(reinterpret_cast<uint32_t *>(args), argsLength);
|
SetStackColor(reinterpret_cast<uint32_t *>(args), argsLength);
|
||||||
break;
|
break;
|
||||||
case Commands::GoToIdle:
|
}
|
||||||
|
case Commands::GoToIdle:{
|
||||||
GlobalPrint::Println("!3;");
|
GlobalPrint::Println("!3;");
|
||||||
colorManager.Enable(true);
|
animator.isEnabled = true;
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
|
default:{
|
||||||
GlobalPrint::Println("INVALID COMMAND");
|
GlobalPrint::Println("INVALID COMMAND");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// now that we have run the command we can clear the data for the next command.
|
// now that we have run the command we can clear the data for the next command.
|
||||||
serialMessage.ClearNewData();
|
serialMessage.ClearNewData();
|
||||||
@@ -150,20 +161,38 @@ void UpdateCommunication(void * params){
|
|||||||
|
|
||||||
void UpdateBoard(void * params){
|
void UpdateBoard(void * params){
|
||||||
Serial.println("Spawning UpdateBoard task");
|
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
|
||||||
|
unsigned long accurateTimer{millis()};
|
||||||
|
auto changeAnimationTimer{std::chrono::milliseconds(0)};
|
||||||
|
uint8_t currentAnimation{0};
|
||||||
|
|
||||||
for(;;){
|
for(;;){
|
||||||
if(board.BoardStateHasChanged()){
|
auto actualTimePassed{std::chrono::milliseconds(millis() - accurateTimer)};
|
||||||
boardStateHasChanged = true;
|
accurateTimer = millis();
|
||||||
}
|
|
||||||
|
|
||||||
if(millis() - boardStateTimer > boardStateMaxUpdatePeriod && boardStateHasChanged){
|
if(boardStateTimer >= boardStateMaxUpdatePeriod && boardManager.HasBoardChanged()){
|
||||||
boardStateTimer = millis();
|
|
||||||
printBoardState();
|
printBoardState();
|
||||||
boardStateHasChanged = false;
|
boardManager.ClearBoardChanged();
|
||||||
|
boardStateTimer = std::chrono::milliseconds(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
colorManager.Update();
|
if(changeAnimationTimer >= std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::minutes(1))){
|
||||||
|
changeAnimationTimer = std::chrono::milliseconds(0);
|
||||||
|
currentAnimation++;
|
||||||
|
if(currentAnimation >= animations.size()){
|
||||||
|
currentAnimation = 0;
|
||||||
|
}
|
||||||
|
animator.StartAnimation(animations[currentAnimation]);
|
||||||
|
}
|
||||||
|
|
||||||
vTaskDelay(5);
|
animator.RunAnimation(actualTimePassed);
|
||||||
|
boardManager.Update();
|
||||||
|
|
||||||
|
boardStateTimer += actualTimePassed;
|
||||||
|
changeAnimationTimer += actualTimePassed;
|
||||||
|
vTaskDelay(updateTickRate.count());
|
||||||
}
|
}
|
||||||
Serial.println("UpdateBoard task has ended unexpectedly!");
|
Serial.println("UpdateBoard task has ended unexpectedly!");
|
||||||
}
|
}
|
||||||
@@ -189,10 +218,10 @@ void setup() {
|
|||||||
xTaskCreate(UpdateCommunication, "UpdateCommunication", 10000, NULL, 0, &updateCommunicaitonTask);
|
xTaskCreate(UpdateCommunication, "UpdateCommunication", 10000, NULL, 0, &updateCommunicaitonTask);
|
||||||
|
|
||||||
Serial.println("Beginning Board Initializaiton");
|
Serial.println("Beginning Board Initializaiton");
|
||||||
|
boardManager.Init();
|
||||||
|
animator.SetLoop(true);
|
||||||
|
animator.StartAnimation(animations[0]);
|
||||||
xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask);
|
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");
|
Serial.println("Setup Complete");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user