Compare commits
44 Commits
2-electric
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3954624413 | ||
|
|
5c3f2730ec | ||
|
|
ff9bb57665 | ||
|
|
17af6e4faf | ||
| 331cbe4f82 | |||
| a5652102a0 | |||
|
|
1df45a2d2b | ||
| 59bafb2549 | |||
| 495330dbf4 | |||
|
|
e9a8ae1a22 | ||
| f3c9aea558 | |||
| f471e9ab56 | |||
|
|
a632e91b1c | ||
| 8b5cd5323b | |||
|
|
37fc5e4dcf | ||
| 2f8469ef6c | |||
| ecd516bc6f | |||
| 12193dc560 | |||
| 5cb8101495 | |||
| 9d6c19cfd5 | |||
| e4506bd9f0 | |||
| 7a454edc46 | |||
| 7a7494beb2 | |||
| 77ddc5c7fd | |||
|
|
5ca4ea2410 | ||
|
|
4196fdef23 | ||
| 5237de0539 | |||
|
|
35a94f7b5a | ||
| 67c5cc7c19 | |||
| 14ac96988a | |||
| 240d4866aa | |||
| 61ff0bbbfd | |||
| 4d47b68600 | |||
| a59a6657e8 | |||
| 5caa0a8a61 | |||
| 5ba5924a29 | |||
| 3a49761b66 | |||
| 3e4f0124db | |||
| 48f83eee38 | |||
|
|
a29ccbd2c8 | ||
| 2d3b1392d0 | |||
| bf852ffc81 | |||
| 64e84ad531 | |||
|
|
2074fd6b73 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ Schematics/Block-Party/Block-Party-Cube-Bottom-Board/production
|
|||||||
Schematics/Block-Party/Block-Party-Main-Board/Block-Party-Main-Board-backups/
|
Schematics/Block-Party/Block-Party-Main-Board/Block-Party-Main-Board-backups/
|
||||||
Schematics/Block-Party/Block-Party-Cube-Bottom-Board/Block-Party-Bottom-Board-backups/
|
Schematics/Block-Party/Block-Party-Cube-Bottom-Board/Block-Party-Bottom-Board-backups/
|
||||||
Schematics/Block-Party/Block-Party-Cube-Top-Board/Block-Party-Cube-Top-Board-backups/
|
Schematics/Block-Party/Block-Party-Cube-Top-Board/Block-Party-Cube-Top-Board-backups/
|
||||||
|
|
||||||
|
platformio-local.ini
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Quinn Henthorne, Y. Jenny Wang, Stanton Nash
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
56
README.md
56
README.md
@@ -15,6 +15,11 @@ When the physical user removes a block from the board, the corresponding digital
|
|||||||
When the VR user hovers over or picks up a digital cube, the corresponding physical cube changes color to show the physical user what the VR user is doing.
|
When the VR user hovers over or picks up a digital cube, the corresponding physical cube changes color to show the physical user what the VR user is doing.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
### Repository Setup
|
||||||
|
When the repository is first cloned you will need to initialize all of its submodules.
|
||||||
|
|
||||||
|
In the terminal type: `git submodule update --init --recursive` which will initalize all of the submodules.
|
||||||
|
|
||||||
### Board Assemble
|
### Board Assemble
|
||||||
#### Parts List
|
#### Parts List
|
||||||
- Foam core (scavenged)
|
- Foam core (scavenged)
|
||||||
@@ -53,7 +58,7 @@ wiring diagram for the cube is in the `documentation` directory.
|
|||||||
- Arduino libraries
|
- Arduino libraries
|
||||||
|
|
||||||
### Bluetooth Module
|
### Bluetooth Module
|
||||||
On v0.1 of the baord, a seperate bluetooth module (HC-05) is being used for bluetooth. This module needs to be programmed when first plugged in. To program the module, disconnect it from power and hold the "EN" button on the module. (The button should be the only button on the HC-05 module). While still holding down the button, reconnect the module to the ESP32 and press the ESP32's reset button. Wait 5 seconds while still holding down the "EN" button on the module, then release the button, power cycle the module, and you're done.
|
On v0.1 of the board, a seperate bluetooth module (HC-05) is being used for bluetooth. This module needs to be programmed when first plugged in. To program the module, disconnect it from power and hold the "EN" button on the module. (The button should be the only button on the HC-05 module). While still holding down the button, reconnect the module to the ESP32. Let go of the button and then press the ESP32's reset button. Wait 5 seconds while still holding down the "EN" button on the module, then release the button, power cycle the module, and you're done.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
- Power up the board and pair it with the headset over bluetooth.
|
- Power up the board and pair it with the headset over bluetooth.
|
||||||
@@ -122,55 +127,6 @@ Format Example `!3;`
|
|||||||
|
|
||||||
Description: This command will tell the ESP32 to re-enable its idle animation. It will respond with a `!3;` when it recieves this command.
|
Description: This command will tell the ESP32 to re-enable its idle animation. It will respond with a `!3;` when it recieves this command.
|
||||||
|
|
||||||
**HERE IS A GUIDE TO GET YOU STARTED ON YOUR [MAYBE] FIRST REPOSITORY**:
|
|
||||||
|
|
||||||
<https://docs.codeberg.org/getting-started/first-repository/#2.-clone-the-repository>
|
|
||||||
|
|
||||||
Obviously, this is not what your README should say on-submission. Change it. Yes, the whole thing.
|
|
||||||
|
|
||||||
This is where a description of your project will go. README.md files support [Markdown syntax](https://www.markdownguide.org/basic-syntax/).
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
**Oh yeah, by the way, don't forget to set up `git lfs` on your machine.**
|
|
||||||
|
|
||||||
Tell everyone what your project needs in order to run. This might involve many components or software. Maybe some cool libraries?
|
|
||||||
|
|
||||||
### Hardware Required
|
|
||||||
|
|
||||||
- Some polyphonic headset on OS version `3.14159265358979323846233832795028841971` with room-temperature hyperconductors
|
|
||||||
- Some macrocontroller with specific hardware revision `4.2`
|
|
||||||
- With tank wheels
|
|
||||||
- Some computer with a holoported underdisplay cap
|
|
||||||
- Some fancy smart device that is worn and knows your favorite toothpaste
|
|
||||||
|
|
||||||
### Software Dependencies
|
|
||||||
|
|
||||||
- Division Game Engine version `2024.1.26`
|
|
||||||
- Michaelsoft Binbows `XD`
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
1. Open the thing
|
|
||||||
- You know, that thing over there
|
|
||||||
- No, not that one
|
|
||||||
- Go back
|
|
||||||
- Yes, that one
|
|
||||||
2. Click the button and type the following text:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# install the sotware
|
|
||||||
sudo apt install -y cmatrix
|
|
||||||
# run the trap
|
|
||||||
cmatrix
|
|
||||||
```
|
|
||||||
|
|
||||||
3. After the process completes and you don't even see the code, anymore, you are ready. Here is what it looks like:
|
|
||||||
|
|
||||||
```js
|
|
||||||
"b" + "a" + +"a" + "a"; // 'baNaNa'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shout-Outs
|
## Shout-Outs
|
||||||
- Thank you to all the organizers at Reality Hack and the Hardware track.
|
- Thank you to all the organizers at Reality Hack and the Hardware track.
|
||||||
- Lucas De Bonet for creating `the Singularity` which allowed us to connect the board to Quest headsets.
|
- Lucas De Bonet for creating `the Singularity` which allowed us to connect the board to Quest headsets.
|
||||||
|
|||||||
25
THIRD-PARTY-LICENSES.md
Normal file
25
THIRD-PARTY-LICENSES.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Open Source Software Used in the Project
|
||||||
|
|
||||||
|
[BlockParty](https://codeberg.org/reality-hack-2024/BlockParty)
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Reality-Hack-Inc
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
13
include/COMMANDS.h
Normal file
13
include/COMMANDS.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief These are the serial commands that the board supports
|
||||||
|
*/
|
||||||
|
enum Commands : uint8_t{
|
||||||
|
BoardState = 0,
|
||||||
|
PING = 1,
|
||||||
|
SetStackColors = 2,
|
||||||
|
GoToIdle = 3
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
252
lib/Animator/Animation.h
Normal file
252
lib/Animator/Animation.h
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#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, const 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)
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimationFrame frame00{
|
||||||
|
.frame = {
|
||||||
|
CreateCell(0.0,0.0,1.0,V3D<uint8_t>(128.0,128.0,255.0)),
|
||||||
|
CreateCell(0.5,0.0,1.0,V3D<uint8_t>(128.0,128.0,255.0)),
|
||||||
|
CreateCell(1.0,0.0,0.0,V3D<uint8_t>(255.0,118.0,205.0)),
|
||||||
|
CreateCell(1.0,0.0,0.5,V3D<uint8_t>(255.0,118.0,205.0)),
|
||||||
|
CreateCell(1.0,0.0,1.0,V3D<uint8_t>(255.0,116.0,0.0)),
|
||||||
|
CreateCell(1.0,0.5,1.0,V3D<uint8_t>(183.0,0.0,255.0)),
|
||||||
|
CreateCell(1.0,1.0,1.0,V3D<uint8_t>(183.0,0.0,255.0))
|
||||||
|
},
|
||||||
|
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
|
||||||
|
.frameInterpolation = FrameInterpolation::FADE,
|
||||||
|
.delay = std::chrono::milliseconds(1000)
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimationFrame frame01{
|
||||||
|
.frame = {
|
||||||
|
CreateCell(0.0,0.0,1.0,V3D<uint8_t>(255.0,255.0,171.0)),
|
||||||
|
CreateCell(0.5,0.0,1.0,V3D<uint8_t>(255.0,255.0,171.0)),
|
||||||
|
CreateCell(1.0,0.0,0.0,V3D<uint8_t>(0.0,195.0,88.0)),
|
||||||
|
CreateCell(1.0,0.0,0.5,V3D<uint8_t>(0.0,195.0,88.0)),
|
||||||
|
CreateCell(1.0,0.0,1.0,V3D<uint8_t>(0.0,195.0,88.0)),
|
||||||
|
CreateCell(1.0,0.5,1.0,V3D<uint8_t>(112.0,222.0,255.0)),
|
||||||
|
CreateCell(1.0,1.0,1.0,V3D<uint8_t>(112.0,222.0,255.0))
|
||||||
|
},
|
||||||
|
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
|
||||||
|
.frameInterpolation = FrameInterpolation::FADE,
|
||||||
|
.delay = std::chrono::milliseconds(1000)
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<AnimationFrame> rising{
|
||||||
|
frame00, frame01, frame00
|
||||||
|
// 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;
|
|
||||||
|
|
||||||
};
|
|
||||||
210
lib/Board/BoardManager.h
Normal file
210
lib/Board/BoardManager.h
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
|
uint32_t sliceSize = this->board.SliceBoard(sliceVector, sliceBuffer);
|
||||||
|
if(sliceSize < BOARD_DIMS.z){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 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];
|
||||||
|
uint32_t sliceSize = this->board.SliceBoard(column, cubeSlice);
|
||||||
|
|
||||||
|
uint32_t numCubes{this->getColumnHeight(static_cast<BOARD_TYPES::PLANE_NORMAL>(column.z))};
|
||||||
|
|
||||||
|
if(sliceSize < numCubes){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)};
|
||||||
|
|
||||||
|
if(sliceLength < maxIndex){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
47
lib/CommandHandler/CommandHandler.cpp
Normal file
47
lib/CommandHandler/CommandHandler.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "CommandHandler.h"
|
||||||
|
|
||||||
|
CommandHandler::CommandHandler(){
|
||||||
|
for(uint32_t i = 0; i < MAX_COMMAND_SIZE; i++){
|
||||||
|
commandCallbacks[i] = nullptr;
|
||||||
|
commandCallbackIDs[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandHandler::RegisterCommand(uint32_t commandID, CommandCallback callback){
|
||||||
|
for(uint32_t i = 0; i < MAX_COMMAND_SIZE; i++){
|
||||||
|
if(commandCallbacks[i] == nullptr){
|
||||||
|
commandCallbacks[i] = callback;
|
||||||
|
commandCallbackIDs[i] = commandID;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandHandler::RemoveCommand(uint32_t commandID){
|
||||||
|
for(uint32_t i = 0; i < MAX_COMMAND_SIZE; i++){
|
||||||
|
if(commandCallbackIDs[i] == commandID){
|
||||||
|
commandCallbacks[i] = nullptr;
|
||||||
|
commandCallbackIDs[i] = -1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandHandler::CommandStatus CommandHandler::ProcessCommand(uint32_t * command, uint32_t commandSize){
|
||||||
|
if(commandSize == 0){
|
||||||
|
return CommandStatus::INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t commandID{command[0]};
|
||||||
|
// get a pointer to the second element in the array because the first element is the command ID
|
||||||
|
uint32_t * args{&(command[1])};
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < MAX_COMMAND_SIZE; i++){
|
||||||
|
if(commandCallbacks[i] != nullptr && commandCallbackIDs[i] == commandID){
|
||||||
|
return commandCallbacks[i](args, commandSize-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CommandStatus::INVALID;
|
||||||
|
}
|
||||||
47
lib/CommandHandler/CommandHandler.h
Normal file
47
lib/CommandHandler/CommandHandler.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class CommandHandler{
|
||||||
|
public:
|
||||||
|
// create an enum for command return values
|
||||||
|
enum CommandStatus{
|
||||||
|
SUCCESS,
|
||||||
|
FAILURE,
|
||||||
|
INVALID
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a typedef for a command callback function which takes an array pointer and an array size as an argument and returns a CommandStatus
|
||||||
|
typedef CommandStatus (*CommandCallback)(uint32_t * command, uint32_t commandSize);
|
||||||
|
|
||||||
|
CommandHandler();
|
||||||
|
~CommandHandler() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a command callback function to a command ID
|
||||||
|
* @param commandID The command ID to register the callback to
|
||||||
|
* @param callback The callback function to register
|
||||||
|
* @return true if the callback was registered successfully, false otherwise
|
||||||
|
*/
|
||||||
|
bool RegisterCommand(uint32_t commandID, CommandCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a command callback function from a command ID
|
||||||
|
* @param commandID The command ID to remove the callback from
|
||||||
|
* @return true if the callback was removed successfully, false otherwise
|
||||||
|
*/
|
||||||
|
bool RemoveCommand(uint32_t commandID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process a command
|
||||||
|
* @param command The command to process
|
||||||
|
*/
|
||||||
|
CommandStatus ProcessCommand(uint32_t * command, uint32_t commandSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint32_t MAX_COMMAND_SIZE{10};
|
||||||
|
|
||||||
|
// an array of command callbacks
|
||||||
|
CommandCallback commandCallbacks[MAX_COMMAND_SIZE];
|
||||||
|
// an array of command callback IDs
|
||||||
|
uint32_t commandCallbackIDs[MAX_COMMAND_SIZE];
|
||||||
|
};
|
||||||
28
lib/GlobalPrint/GlobalPrint.h
Normal file
28
lib/GlobalPrint/GlobalPrint.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @file GlobalPrint.h
|
||||||
|
* @brief a method of printing serial data to all outgoing communication methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
namespace GlobalPrint{
|
||||||
|
static void Print(const char * s){
|
||||||
|
Serial.print(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Print(const String &s){
|
||||||
|
GlobalPrint::Print(s.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Println(const char * s){
|
||||||
|
GlobalPrint::Print(s);
|
||||||
|
GlobalPrint::Print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Println(const String &s){
|
||||||
|
GlobalPrint::Println(s.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Submodule lib/SerialMessage updated: 7cd43742b6...cf854b6c2c
@@ -7,22 +7,23 @@
|
|||||||
;
|
;
|
||||||
; Please visit documentation for the other options and examples
|
; Please visit documentation for the other options and examples
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
default_envs = esp32s3_release ; this ensures that only this environment is built for anything but the debug build
|
default_envs = esp32s3_release
|
||||||
|
extra_configs = platformio-local.ini
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
; platform = espressif32
|
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
platform = https://github.com/platformio/platform-espressif32.git
|
platform = https://github.com/platformio/platform-espressif32.git
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
framework = arduino
|
framework = arduino
|
||||||
build_flags = -Iinclude
|
build_flags = -Iinclude
|
||||||
; lib_ldf_mode = chain+
|
|
||||||
|
|
||||||
monitor_speed = 9600
|
monitor_speed = 9600
|
||||||
monitor_filters = esp32_exception_decoder, colorize, send_on_enter
|
monitor_filters = colorize, send_on_enter
|
||||||
upload_speed = 2000000 ;ESP32S3 USB-Serial Converter maximum 2000000bps
|
upload_speed = 2000000
|
||||||
lib_deps = adafruit/Adafruit NeoPixel@^1.12.0
|
lib_deps =
|
||||||
|
adafruit/Adafruit NeoPixel@^1.12.3
|
||||||
|
fastled/FastLED@^3.7.3
|
||||||
|
|
||||||
|
|
||||||
[env:esp32s3_release]
|
[env:esp32s3_release]
|
||||||
@@ -33,6 +34,6 @@ debug_init_break = tbreak setup
|
|||||||
debug_tool = esp-builtin
|
debug_tool = esp-builtin
|
||||||
build_type = debug
|
build_type = debug
|
||||||
debug_speed = 20000
|
debug_speed = 20000
|
||||||
; debug_port = COM7
|
build_flags =
|
||||||
; monitor_port = COM14
|
-D DEBUG = 1
|
||||||
build_flags = -O1 -Iinclude
|
-I include
|
||||||
|
|||||||
283
src/main.cpp
283
src/main.cpp
@@ -1,43 +1,61 @@
|
|||||||
// Other peoples libraries
|
// Other peoples libraries
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <BluetoothSerial.h>
|
#include <BluetoothSerial.h>
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
// Static Defines
|
||||||
|
#include "PINOUT.h"
|
||||||
|
#include "BOARD-DEFINITIONS.h"
|
||||||
|
#include "COMMANDS.h"
|
||||||
|
|
||||||
// project specific libraries
|
// project specific libraries
|
||||||
#include "BluetoothSerial.h"
|
#include "BluetoothSerial.h"
|
||||||
#include "SerialMessage.h"
|
#include "SerialMessage.h"
|
||||||
#include "BoardLayout.h"
|
#include "GlobalPrint.h"
|
||||||
#include "BOARD-DEFINITIONS.h"
|
#include "CommandHandler.h"
|
||||||
#include "Color.h"
|
|
||||||
#include "ColorManager.h"
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
#include "BoardManager.h"
|
||||||
// ----------------- Types ----------------------
|
#include "BoardDriver.h"
|
||||||
// --------------------------------------------------
|
#include "BoardTypes.h"
|
||||||
enum Commands : uint8_t{
|
|
||||||
BoardState = 0,
|
#include "Animator.h"
|
||||||
PING = 1,
|
#include "TestFrames.h"
|
||||||
SetStackColors = 2,
|
#include "Animation.h"
|
||||||
GoToIdle = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// ----------------- VARIABLES ----------------------
|
// ----------------- VARIABLES ----------------------
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
uint32_t boardStateTimer{0};
|
TaskHandle_t updateCommunicaitonTask;
|
||||||
bool boardStateHasChanged{false};
|
TaskHandle_t updateBoardTask;
|
||||||
uint32_t boardStateMaxUpdatePeriod{34}; // this is a little slower than 30fps
|
|
||||||
|
// WARNING! This array size should always be equal to the number of entries in it!!
|
||||||
|
std::array<std::vector<AnimationFrame>*, 2> animations = {
|
||||||
|
&RisingCubes::rising,
|
||||||
|
&RotatingCubes::rotating,
|
||||||
|
};
|
||||||
|
|
||||||
|
CommandHandler commandHandler{};
|
||||||
|
|
||||||
// BluetoothSerial SerialBT;
|
// BluetoothSerial SerialBT;
|
||||||
// BluetoothSerialMessage serialMessageBT(&SerialBT);
|
// BluetoothSerialMessage serialMessageBT(&SerialBT);
|
||||||
SerialMessage<500> 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 ----------------------
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Send programming commands to the serial to bluetooth adapter so
|
||||||
|
* it is set up as expected for the VR headset
|
||||||
|
* @post the serial baud rate will be set to 9600
|
||||||
|
*/
|
||||||
void SetupBluetoothModule(){
|
void SetupBluetoothModule(){
|
||||||
Serial.begin(38400);
|
Serial.begin(38400);
|
||||||
Serial.print("AT+UART=9600,0,0\r\n"); // set baud rate to 9600
|
Serial.print("AT+UART=9600,0,0\r\n"); // set baud rate to 9600
|
||||||
@@ -52,123 +70,176 @@ void SetupBluetoothModule(){
|
|||||||
Serial.print("AT+ROLE=0\r\n"); // set to slave
|
Serial.print("AT+ROLE=0\r\n"); // set to slave
|
||||||
delay(100);
|
delay(100);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// exit at mode and go into pairing mode
|
// exit at mode and go into pairing mode
|
||||||
Serial.print("AT+INIT\r\n");
|
Serial.print("AT+INIT\r\n");
|
||||||
Serial.begin(9600);
|
Serial.begin(9600);
|
||||||
delay(100);
|
delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
void printBoardState(){
|
void printBoardState(){
|
||||||
// create a buffer to hold the board state
|
GlobalPrint::Print("!0,");
|
||||||
uint16_t boardState[BOARD_WIDTH * BOARD_LENGTH];
|
String boardString;
|
||||||
// read in the board state
|
boardManager.Board2StackString(boardString);
|
||||||
board.GetBoardState(boardState);
|
GlobalPrint::Print(boardString);
|
||||||
|
GlobalPrint::Println(";");
|
||||||
Serial.print("!0,");
|
|
||||||
// SerialBT.print("!0,");
|
|
||||||
|
|
||||||
for(int i = 0; i < (BOARD_WIDTH * BOARD_LENGTH); i++){
|
|
||||||
Serial.print(boardState[i]);
|
|
||||||
// SerialBT.print(boardState[i]);
|
|
||||||
if(i == (BOARD_WIDTH * BOARD_LENGTH) - 1){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Serial.print(",");
|
|
||||||
// SerialBT.print(",");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.println(";");
|
void SetStackColor(uint32_t * args, uint32_t argsLength){
|
||||||
// SerialBT.println(";");
|
uint32_t stackNum = args[0];
|
||||||
|
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};
|
||||||
|
Serial.println("StackNum: " + String(stackNum));
|
||||||
|
Serial.println("X: " + String(X_COORD) + " Y: " + String(Y_COORD));
|
||||||
|
uint32_t numColors = (argsLength - 1) / 3;
|
||||||
|
|
||||||
|
// nothing to do if no colors were given
|
||||||
|
if(numColors == 0){
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStackColor(uint32_t * args, int argsLength){
|
Serial.println("Num Colors: " + String(numColors));
|
||||||
uint32_t stackNum = args[1];
|
V3D<uint32_t> colors[numColors];
|
||||||
uint32_t numColors = (argsLength - 2) / 3;
|
|
||||||
Color 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[1 + (i * 3)];
|
||||||
int green = args[3 + (i * 3)];
|
uint32_t green = args[2 + (i * 3)];
|
||||||
int blue = args[4 + (i * 3)];
|
uint32_t blue = args[3 + (i * 3)];
|
||||||
colors[i] = Color(red, green, blue);
|
colors[i] = V3D<uint32_t>{red, green, blue};
|
||||||
|
Serial.println("Color: " + String(red) + "," + String(green) + "," + String(blue));
|
||||||
|
}
|
||||||
|
boardManager.SetColumnColors(V3D<uint32_t>{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors, numColors);
|
||||||
}
|
}
|
||||||
|
|
||||||
board.SetStackColors(stackNum, colors);
|
// command handling functions
|
||||||
|
CommandHandler::CommandStatus BoardStateCommandHandler(uint32_t * /*args*/, uint32_t /*argsLength*/){
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseData(uint32_t * args, int argsLength){
|
|
||||||
uint32_t command = args[0];
|
|
||||||
switch(command){
|
|
||||||
case Commands::BoardState:
|
|
||||||
printBoardState();
|
printBoardState();
|
||||||
break;
|
return CommandHandler::CommandStatus::SUCCESS;
|
||||||
case Commands::PING:
|
|
||||||
Serial.print("!");
|
|
||||||
// SerialBT.print("!");
|
|
||||||
Serial.print(Commands::PING);
|
|
||||||
// SerialBT.print(Commands::PING);
|
|
||||||
Serial.println(";");
|
|
||||||
// SerialBT.println(";");
|
|
||||||
break;
|
|
||||||
case Commands::SetStackColors:
|
|
||||||
Serial.println("!2;");
|
|
||||||
// SerialBT.println("!2;");
|
|
||||||
colorManager.Enable(false);
|
|
||||||
setStackColor(args, argsLength);
|
|
||||||
break;
|
|
||||||
case Commands::GoToIdle:
|
|
||||||
Serial.println("!3;");
|
|
||||||
// SerialBT.println("!3;");
|
|
||||||
colorManager.Enable(true);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Serial.println("INVALID COMMAND");
|
|
||||||
// SerialBT.println("INVALID COMMAND");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandHandler::CommandStatus PingCommandHandler(uint32_t * /*args*/, uint32_t /*argsLength*/){
|
||||||
|
GlobalPrint::Println("!" + String(Commands::PING) + ";");
|
||||||
|
return CommandHandler::CommandStatus::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandHandler::CommandStatus SetColorCommandHandler(uint32_t * args, uint32_t argsLength){
|
||||||
|
GlobalPrint::Println("!2;");
|
||||||
|
animator.isEnabled = false;
|
||||||
|
V3D<uint32_t> black{};
|
||||||
|
boardManager.FillColor(black);
|
||||||
|
SetStackColor(args, argsLength);
|
||||||
|
return CommandHandler::CommandStatus::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandHandler::CommandStatus GoToIdleCommandHandler(uint32_t * /*args*/, uint32_t /*argsLength*/){
|
||||||
|
GlobalPrint::Println("!3;");
|
||||||
|
animator.isEnabled = true;
|
||||||
|
return CommandHandler::CommandStatus::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// ----------------- FREERTOS TASKS -----------------
|
||||||
|
// --------------------------------------------------
|
||||||
|
void UpdateCommunication(void * params){
|
||||||
|
Serial.println("Spawning UpdateCommunication task");
|
||||||
|
for(;;){
|
||||||
|
// DO serial processing
|
||||||
|
serialMessage.Update();
|
||||||
|
if(serialMessage.IsNewData()){
|
||||||
|
// We reinterpret cast the args to a uint32_t pointer because we know that the args will always be positive
|
||||||
|
commandHandler.ProcessCommand(reinterpret_cast<uint32_t *>(serialMessage.GetArgs()), serialMessage.GetPopulatedArgs());
|
||||||
|
serialMessage.ClearNewData();
|
||||||
|
}
|
||||||
|
// serialMessageBT.Update();
|
||||||
|
// if(serialMessageBT.IsNewData()){
|
||||||
|
// commandHandler.ProcessCommand(reinterpret_cast<uint32_t *>(serialMessage.GetArgs()), serialMessage.GetPopulatedArgs());
|
||||||
|
// serialMessage.ClearNewData();
|
||||||
|
// }
|
||||||
|
vTaskDelay(3);
|
||||||
|
}
|
||||||
|
Serial.println("UpdateCommunication task has ended unexpectedly!");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateBoard(void * params){
|
||||||
|
Serial.println("Spawning UpdateBoard task");
|
||||||
|
auto updateTickRate{std::chrono::milliseconds(8)};
|
||||||
|
auto boardStateTimer{std::chrono::milliseconds(0)};
|
||||||
|
auto boardStateMaxUpdatePeriod{std::chrono::milliseconds(34)}; // this is a little slower than 30fps
|
||||||
|
unsigned long accurateTimer{millis()};
|
||||||
|
auto changeAnimationTimer{std::chrono::milliseconds(0)};
|
||||||
|
uint8_t currentAnimation{0};
|
||||||
|
|
||||||
|
for(;;){
|
||||||
|
auto actualTimePassed{std::chrono::milliseconds(millis() - accurateTimer)};
|
||||||
|
accurateTimer = millis();
|
||||||
|
|
||||||
|
if(boardStateTimer >= boardStateMaxUpdatePeriod && boardManager.HasBoardChanged()){
|
||||||
|
printBoardState();
|
||||||
|
boardManager.ClearBoardChanged();
|
||||||
|
boardStateTimer = std::chrono::milliseconds(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
animator.RunAnimation(actualTimePassed);
|
||||||
|
boardManager.Update();
|
||||||
|
|
||||||
|
boardStateTimer += actualTimePassed;
|
||||||
|
changeAnimationTimer += actualTimePassed;
|
||||||
|
vTaskDelay(updateTickRate.count());
|
||||||
|
}
|
||||||
|
Serial.println("UpdateBoard task has ended unexpectedly!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// ----------------- SETUP AND LOOP -----------------
|
// ----------------- SETUP AND LOOP -----------------
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
// delay a little bit to get the serial monitor a chance to capture the next log messages.
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
||||||
|
Serial.begin(9600);
|
||||||
|
Serial.println("Beginning Setup");
|
||||||
|
Serial.println("Configuring Bluetooth Adapter");
|
||||||
SetupBluetoothModule();
|
SetupBluetoothModule();
|
||||||
Serial.begin(9600);
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
// Register all of our commands with the command handler
|
||||||
|
commandHandler.RegisterCommand(Commands::BoardState, BoardStateCommandHandler);
|
||||||
|
commandHandler.RegisterCommand(Commands::PING, PingCommandHandler);
|
||||||
|
commandHandler.RegisterCommand(Commands::SetStackColors, SetColorCommandHandler);
|
||||||
|
commandHandler.RegisterCommand(Commands::GoToIdle, GoToIdleCommandHandler);
|
||||||
|
|
||||||
|
Serial.println("Configuring communication methods");
|
||||||
|
serialMessage.Init(9600);
|
||||||
// SerialBT.begin("blockPartyBT");
|
// SerialBT.begin("blockPartyBT");
|
||||||
Color colors[] = {Color(255, 0, 0), Color(0, 0, 0), Color(0, 0, 0)};
|
xTaskCreate(UpdateCommunication, "UpdateCommunication", 10000, NULL, 0, &updateCommunicaitonTask);
|
||||||
board.SetStackColors(2, colors);
|
|
||||||
|
|
||||||
boardStateTimer = millis();
|
Serial.println("Beginning Board Initializaiton");
|
||||||
|
boardManager.Init();
|
||||||
|
animator.SetLoop(true);
|
||||||
|
animator.StartAnimation(animations[0]);
|
||||||
|
xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask);
|
||||||
|
|
||||||
|
Serial.println("Setup Complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if(board.BoardStateHasChanged()){
|
// delete the loop task because we don't use it
|
||||||
boardStateHasChanged = true;
|
vTaskDelete(NULL);
|
||||||
}
|
|
||||||
|
|
||||||
if(millis() - boardStateTimer > boardStateMaxUpdatePeriod && boardStateHasChanged){
|
|
||||||
boardStateTimer = millis();
|
|
||||||
printBoardState();
|
|
||||||
boardStateHasChanged = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DO serial processing
|
|
||||||
serialMessage.Update();
|
|
||||||
if(serialMessage.IsNewData()){
|
|
||||||
parseData(serialMessage.GetArgs(), serialMessage.GetArgsLength());
|
|
||||||
serialMessage.ClearNewData();
|
|
||||||
}
|
|
||||||
// serialMessageBT.Update();
|
|
||||||
// if(serialMessageBT.IsNewData()){
|
|
||||||
// parseData(serialMessageBT.GetArgs(), serialMessageBT.GetArgsLength());
|
|
||||||
// serialMessageBT.ClearNewData();
|
|
||||||
// }
|
|
||||||
colorManager.Update();
|
|
||||||
}
|
}
|
||||||
23
template-platformio-local.ini
Normal file
23
template-platformio-local.ini
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
; This is a template that will allow you to add additional configuration
|
||||||
|
; options to the platformio.ini file without changing anything in git.
|
||||||
|
; options like upload/monitor ports are great things to put here
|
||||||
|
; because those will vary from computer to computer.
|
||||||
|
; To use this configuration, make a copy of this file and rename it to "platformio-local.ini"
|
||||||
|
; and then uncomment some of the following code or add your own options
|
||||||
|
; Feel free to modify the code as needed
|
||||||
|
|
||||||
|
; this will add additional configuration options to esp32s3_release environment.
|
||||||
|
; This environment is the default for building/sending code to the baord
|
||||||
|
; so this should be the first thing you change/modify
|
||||||
|
; Uncomment the following code as needed:
|
||||||
|
; [env:esp32s3_release]
|
||||||
|
; monitor_port = COM10
|
||||||
|
; upload_port = COM11
|
||||||
|
; monitor_speed = 115200
|
||||||
|
|
||||||
|
; this is the debug configuration which you probably don't need
|
||||||
|
; unless you're doing some complicated firmware development.
|
||||||
|
; [env:esp32s3_debug]
|
||||||
|
; debug_port = COM7
|
||||||
|
; monitor_port = COM14
|
||||||
|
; upload_port = COM12
|
||||||
3
tools/animation-tools/.gitignore
vendored
Normal file
3
tools/animation-tools/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
output.txt
|
||||||
78
tools/animation-tools/AnimationExporter.py
Normal file
78
tools/animation-tools/AnimationExporter.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from Scene import Scene
|
||||||
|
from enum import StrEnum
|
||||||
|
from pygame.math import Vector3
|
||||||
|
from Shapes import Mesh
|
||||||
|
|
||||||
|
class FillInterpolation(StrEnum):
|
||||||
|
NO_FILL = "NO_FILL"
|
||||||
|
CLOSEST_COLOR = "CLOSEST_COLOR"
|
||||||
|
LINEAR_WEIGHTED_DISTANCE = "LINEAR_WEIGHTED_DISTANCE"
|
||||||
|
SQUARE_WEIGHTED_DISTANCE = "SQUARE_WEIGHTED_DISTANCE"
|
||||||
|
# options = [NO_FILL, CLOSEST_COLOR, LINEAR_WEIGHTED_DISTANCE, SQUARE_WEIGHTED_DISTANCE]
|
||||||
|
|
||||||
|
class FrameInterpolation(StrEnum):
|
||||||
|
SNAP = "SNAP"
|
||||||
|
FADE = "FADE"
|
||||||
|
# options = [SNAP, FADE]
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, position: Vector3, color: Vector3):
|
||||||
|
self.position: Vector3 = position
|
||||||
|
self.color: Vector3 = color
|
||||||
|
|
||||||
|
def to_string(self):
|
||||||
|
message = f"CreateCell({self.position.x},{self.position.y},{self.position.z}"
|
||||||
|
message += f",V3D<uint8_t>({self.color.x},{self.color.y},{self.color.z})),"
|
||||||
|
return message
|
||||||
|
|
||||||
|
class AnimationFrame:
|
||||||
|
def __init__(self, cells: list[Cell], fill_interpolation: FillInterpolation, frame_interpolation: FrameInterpolation, delay: int):
|
||||||
|
self.cells = cells
|
||||||
|
self.fill = fill_interpolation
|
||||||
|
self.frame_interp = frame_interpolation
|
||||||
|
self.delay = delay
|
||||||
|
|
||||||
|
def to_string(self, frame_number: int):
|
||||||
|
cell_str = ""
|
||||||
|
for i, cell in enumerate(self.cells):
|
||||||
|
if cell.color.x != 0 or cell.color.y != 0 or cell.color.z != 0:
|
||||||
|
if i != 0:
|
||||||
|
cell_str += "\t\t"
|
||||||
|
cell_str += cell.to_string() + "\n\t"
|
||||||
|
cell_str = cell_str[:-3]
|
||||||
|
message = f"""
|
||||||
|
AnimationFrame frame{frame_number}{{
|
||||||
|
.frame = {{
|
||||||
|
{cell_str}
|
||||||
|
}},
|
||||||
|
.fillInterpolation = FillInterpolation::{self.fill},
|
||||||
|
.frameInterpolation = FrameInterpolation::{self.frame_interp},
|
||||||
|
.delay = std::chrono::milliseconds({self.delay})
|
||||||
|
}};
|
||||||
|
"""
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
#TODO: Impliment this
|
||||||
|
def generate_animation_frame_struct(animation_types_path: str):
|
||||||
|
with open(animation_types_path, 'r') as AnimationTypes:
|
||||||
|
with open("AnimationTypes.py", 'w') as output:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mesh_to_cell(mesh: Mesh) -> Cell:
|
||||||
|
if mesh.face_color[0] != 0 or mesh.face_color[1] != 0 or mesh.face_color[2] != 0:
|
||||||
|
pass
|
||||||
|
pos = (mesh.get_average_position() + Vector3(1,1,1)) / 2
|
||||||
|
# need to swap z and y for the coordinate system
|
||||||
|
z = pos.y
|
||||||
|
pos.y = pos.z
|
||||||
|
pos.z = z
|
||||||
|
testVector = Vector3(0.1,0.5,0.9)
|
||||||
|
|
||||||
|
cell = Cell(pos, Vector3(mesh.face_color))
|
||||||
|
return cell
|
||||||
|
|
||||||
|
def scene_to_frame(scene: Scene, fill_interpolation: FillInterpolation, frame_interpolation: FrameInterpolation, delay: int, scene_number: int) -> str:
|
||||||
|
cells = [mesh_to_cell(cube) for cube in scene.meshes]
|
||||||
|
frame = AnimationFrame(cells, fill_interpolation, frame_interpolation, delay)
|
||||||
|
return frame.to_string(scene_number)
|
||||||
17
tools/animation-tools/MatrixMath.py
Normal file
17
tools/animation-tools/MatrixMath.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import math
|
||||||
|
from pygame.math import Vector3
|
||||||
|
|
||||||
|
def project(vector, w, h, fov, distance):
|
||||||
|
factor = math.atan(fov / 2 * math.pi / 180) / (distance + vector.z)
|
||||||
|
x = vector.x * factor * w + w / 2
|
||||||
|
y = -vector.y * factor * w + h / 2
|
||||||
|
return Vector3(x, y, vector.z)
|
||||||
|
|
||||||
|
def rotate_vertices(vertices, angle, axis):
|
||||||
|
return [v.rotate(angle, axis) for v in vertices]
|
||||||
|
def scale_vertices(vertices, s):
|
||||||
|
return [Vector3(v[0]*s[0], v[1]*s[1], v[2]*s[2]) for v in vertices]
|
||||||
|
def translate_vertices(vertices, t):
|
||||||
|
return [v + Vector3(t) for v in vertices]
|
||||||
|
def project_vertices(vertices, w, h, fov, distance):
|
||||||
|
return [project(v, w, h, fov, distance) for v in vertices]
|
||||||
80
tools/animation-tools/README.md
Normal file
80
tools/animation-tools/README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Setup
|
||||||
|
In order to run this tool follow these steps:
|
||||||
|
|
||||||
|
1. ensure you're using python 3.12+. You can verify this by running
|
||||||
|
```
|
||||||
|
python --version
|
||||||
|
```
|
||||||
|
in a terminal.
|
||||||
|
|
||||||
|
2. Inside of a terminal in the
|
||||||
|
```
|
||||||
|
tools/animation-tools
|
||||||
|
```
|
||||||
|
folder, run:
|
||||||
|
```
|
||||||
|
python -m venv venv
|
||||||
|
```
|
||||||
|
to create your virtual environment.
|
||||||
|
|
||||||
|
3. Now activate the virtual environment. In powershell run
|
||||||
|
```
|
||||||
|
venv\Scripts\activate
|
||||||
|
```
|
||||||
|
You may need to fix permission issues if this is your first time using a virtual environment. For git bash just run:
|
||||||
|
```
|
||||||
|
source venv/Scripts/activate
|
||||||
|
```
|
||||||
|
4. Run
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
# How to Use
|
||||||
|
The animation tool was made to allow anyone to easily create animaitons without much practice. You can create a sequence of frames in the studio, export them and then copy them over to the firmware
|
||||||
|
|
||||||
|
## Creating a Frame
|
||||||
|
When you first run the program the window will look like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Rotating the view
|
||||||
|
Press and hold the middle mouse button and move around the mouse to rotate the view.
|
||||||
|
|
||||||
|
### Changing Cube Colors
|
||||||
|
Click on a cube to select it. Selected cubes will be highlighted in red. All selected cubes will be set to the current color picker color. You can deselect a block by clicking on it again.
|
||||||
|
|
||||||
|
The color picker consists of 3 sliders on the left of the screen. A preview of the color is in a small box underneath the sliders. Move the sliders around to change colors.
|
||||||
|
|
||||||
|
### Fill Options
|
||||||
|
Fill options tell the board how to color cubes which are set to black in the animator. There are four options:
|
||||||
|
|
||||||
|
#### No Fill
|
||||||
|
Cubes that are set to black will be off
|
||||||
|
|
||||||
|
#### Closest Color
|
||||||
|
Cubes that are set to black will become the same color as their closest colored neighbor
|
||||||
|
|
||||||
|
#### Linear
|
||||||
|
Cubes that are set to black will become a distance weighted average of all of the colored cubes
|
||||||
|
|
||||||
|
#### Square
|
||||||
|
Same as linear, but the distance weighting is squared. This means a cube that's twice as far away will have only 1/4 the effect.
|
||||||
|
|
||||||
|
### Fade options
|
||||||
|
The fade options tell the board how to transition between frames
|
||||||
|
|
||||||
|
#### Snap
|
||||||
|
The colors will remain constant until the delay time for the current frame has passed. At that point, the frame will immediatly switch to the next frame.
|
||||||
|
|
||||||
|
#### Fade
|
||||||
|
The colors will linearly transition from one frame to the next. This often results in very interesting colors and patterns emerging.
|
||||||
|
|
||||||
|
### Creating more than one frame
|
||||||
|
An animation isn't really an animation with only one frame. You can add more frames to your animation with the "Next Frame" button. This will create a new frame for you to edit. You'll also notice the frame counter above the next frame button will go up. That shows you the current frame number that you're editing. You can view previous frames but clicking on the last frame button.
|
||||||
|
|
||||||
|
### Exporting your Animation
|
||||||
|
Now that you're done creating your lovely animation you can export it! Click the save button and C++ code will automatically be generated which describes all of the frames you just created. The code is saved in output.txt in the animation-tools folder. How to use that code is beyond the scope of this README. Reach out to Quinn for more instruction.
|
||||||
|
|
||||||
|
# Future features
|
||||||
|
Eventually I would like to add the ability to send the animations directly to the board without having to mess with the firmware at all. This will take a pretty big rework though.
|
||||||
70
tools/animation-tools/Scene.py
Normal file
70
tools/animation-tools/Scene.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from Shapes import Mesh
|
||||||
|
from MatrixMath import *
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
class Scene:
|
||||||
|
def __init__(self, meshes, fov, distance):
|
||||||
|
self.meshes: list[Mesh] = meshes
|
||||||
|
self.fov = fov
|
||||||
|
self.distance = distance
|
||||||
|
self.euler_angles = [0, 0, 0]
|
||||||
|
|
||||||
|
def transform_vertices(self, vertices, width, height):
|
||||||
|
transformed_vertices = vertices
|
||||||
|
axis_list = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
|
||||||
|
for angle, axis in reversed(list(zip(list(self.euler_angles), axis_list))):
|
||||||
|
transformed_vertices = rotate_vertices(transformed_vertices, angle, axis)
|
||||||
|
transformed_vertices = project_vertices(transformed_vertices, width, height, self.fov, self.distance)
|
||||||
|
return transformed_vertices
|
||||||
|
|
||||||
|
def point_in_polygon(self, point, polygon):
|
||||||
|
""" Determine if the point (x, y) is inside the polygon """
|
||||||
|
x, y = point
|
||||||
|
n = len(polygon)
|
||||||
|
inside = False
|
||||||
|
p1x, p1y = polygon[0]
|
||||||
|
for i in range(n + 1):
|
||||||
|
p2x, p2y = polygon[i % n]
|
||||||
|
if y > min(p1y, p2y):
|
||||||
|
if y <= max(p1y, p2y):
|
||||||
|
if x <= max(p1x, p2x):
|
||||||
|
if p1y != p2y:
|
||||||
|
xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
||||||
|
if p1x == p2x or x <= xints:
|
||||||
|
inside = not inside
|
||||||
|
p1x, p1y = p2x, p2y
|
||||||
|
return inside
|
||||||
|
|
||||||
|
def get_mesh_from_xy(self, pos: tuple[int]) -> Mesh:
|
||||||
|
x, y = pos
|
||||||
|
closest_mesh = None
|
||||||
|
closest_z = float('inf')
|
||||||
|
|
||||||
|
for mesh in self.meshes:
|
||||||
|
transformed_vertices = self.transform_vertices(mesh.get_vertices(), *pygame.display.get_surface().get_size())
|
||||||
|
avg_z = mesh.calculate_average_z(transformed_vertices)
|
||||||
|
for i, avg_z_value in avg_z:
|
||||||
|
polygon = mesh.create_polygon(mesh.get_face(i), transformed_vertices)
|
||||||
|
if self.point_in_polygon((x, y), polygon):
|
||||||
|
if avg_z_value < closest_z:
|
||||||
|
closest_z = avg_z_value
|
||||||
|
closest_mesh = mesh
|
||||||
|
|
||||||
|
return closest_mesh
|
||||||
|
|
||||||
|
def draw(self, surface):
|
||||||
|
|
||||||
|
polygons = []
|
||||||
|
for mesh in self.meshes:
|
||||||
|
transformed_vertices = self.transform_vertices(mesh.get_vertices(), *surface.get_size())
|
||||||
|
avg_z = mesh.calculate_average_z(transformed_vertices)
|
||||||
|
for z in avg_z:
|
||||||
|
#for z in sorted(avg_z, key=lambda x: x[1], reverse=True):
|
||||||
|
pointlist = mesh.create_polygon(mesh.get_face(z[0]), transformed_vertices)
|
||||||
|
polygons.append((pointlist, z[1], mesh.face_color, mesh.edge_color))
|
||||||
|
#pygame.draw.polygon(surface, (128, 128, 192), pointlist)
|
||||||
|
#pygame.draw.polygon(surface, (0, 0, 0), pointlist, 3)
|
||||||
|
|
||||||
|
for poly in sorted(polygons, key=lambda x: x[1], reverse=True):
|
||||||
|
pygame.draw.polygon(surface, poly[2], poly[0])
|
||||||
|
pygame.draw.polygon(surface, poly[3], poly[0], 3)
|
||||||
48
tools/animation-tools/Shapes.py
Normal file
48
tools/animation-tools/Shapes.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from MatrixMath import *
|
||||||
|
from pygame.math import Vector3
|
||||||
|
|
||||||
|
class Mesh():
|
||||||
|
def __init__(self, vertices, faces):
|
||||||
|
self.__vertices = [Vector3(v) for v in vertices]
|
||||||
|
self.__faces = faces
|
||||||
|
self.face_color = (0, 0, 0)
|
||||||
|
self.edge_color = (0,0,0)
|
||||||
|
|
||||||
|
def rotate(self, angle, axis):
|
||||||
|
self.__vertices = rotate_vertices(self.__vertices, angle, axis)
|
||||||
|
def scale(self, s):
|
||||||
|
self.__vertices = scale_vertices(self.__vertices, s)
|
||||||
|
def translate(self, t):
|
||||||
|
self.__vertices = translate_vertices(self.__vertices, t)
|
||||||
|
|
||||||
|
def calculate_average_z(self, vertices):
|
||||||
|
return [(i, sum([vertices[j].z for j in f]) / len(f)) for i, f in enumerate(self.__faces)]
|
||||||
|
|
||||||
|
def get_average_position(self):
|
||||||
|
vertex_sum: Vector3 = Vector3()
|
||||||
|
for vertex in self.__vertices:
|
||||||
|
vertex_sum += vertex
|
||||||
|
return vertex_sum / len(self.__vertices)
|
||||||
|
|
||||||
|
def get_face(self, index):
|
||||||
|
return self.__faces[index]
|
||||||
|
def get_vertices(self):
|
||||||
|
return self.__vertices
|
||||||
|
|
||||||
|
def create_polygon(self, face, vertices):
|
||||||
|
return [(vertices[i].x, vertices[i].y) for i in [*face, face[0]]]
|
||||||
|
|
||||||
|
def set_face_color(self, color : tuple[int]):
|
||||||
|
self.face_color = color
|
||||||
|
|
||||||
|
def set_edge_color(self, color : tuple[int]):
|
||||||
|
self.edge_color = color
|
||||||
|
|
||||||
|
class Cube(Mesh):
|
||||||
|
def __init__(self):
|
||||||
|
vertices = [(-1,-1,1), (1,-1,1), (1,1,1), (-1,1,1), (-1,-1,-1), (1,-1,-1), (1,1,-1), (-1,1,-1)]
|
||||||
|
faces = [(0,1,2,3), (1,5,6,2), (5,4,7,6), (4,0,3,7), (3,2,6,7), (1,0,4,5)]
|
||||||
|
super().__init__(vertices, faces)
|
||||||
|
|
||||||
|
def set_position(self, position : tuple):
|
||||||
|
super().translate(position)
|
||||||
210
tools/animation-tools/UI.py
Normal file
210
tools/animation-tools/UI.py
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
from itertools import product
|
||||||
|
from pygame_widgets.button import Button
|
||||||
|
from pygame_widgets.slider import Slider
|
||||||
|
from pygame_widgets.dropdown import Dropdown
|
||||||
|
from pygame_widgets.textbox import TextBox
|
||||||
|
from Scene import Scene
|
||||||
|
from Shapes import *
|
||||||
|
import pygame
|
||||||
|
from AnimationExporter import *
|
||||||
|
|
||||||
|
class ColorPicker:
|
||||||
|
def __init__(self, screen, x_pos: int, y_pos: int, width: int, height: int):
|
||||||
|
width = max(50, width)
|
||||||
|
slider_colors = ((255,0,0),(0,255,0),(0,0,255))
|
||||||
|
self.sliders: list[Slider] = [
|
||||||
|
Slider(screen, int(x_pos + i*(width/3)), y_pos, int((width-50)/3), height, min=0, max=255, step=1, vertical=True, handleColour=slider_colors[i]) for i in range(3)
|
||||||
|
]
|
||||||
|
for slider in self.sliders:
|
||||||
|
slider.enable()
|
||||||
|
|
||||||
|
def get_color(self) -> tuple[int]:
|
||||||
|
return tuple([slider.getValue() for slider in self.sliders])
|
||||||
|
|
||||||
|
def set_color(self, color: tuple[int]):
|
||||||
|
for i, slider in enumerate(self.sliders):
|
||||||
|
slider.setValue(color[i])
|
||||||
|
|
||||||
|
class SceneStore:
|
||||||
|
def __init__(self, scene: Scene, fill: FillInterpolation, fade: FrameInterpolation, delay: int):
|
||||||
|
self.scene: Scene = scene
|
||||||
|
self.fill: FillInterpolation = fill
|
||||||
|
self.fade: FrameInterpolation = fade
|
||||||
|
self.delay: int = delay
|
||||||
|
|
||||||
|
class SceneManager:
|
||||||
|
def __init__(self, window, color_picker: ColorPicker):
|
||||||
|
self.file_data: str = ""
|
||||||
|
self._scenes: list[SceneStore] = [SceneStore(self.new_scene(), FillInterpolation.NO_FILL, FrameInterpolation.FADE, 1000)]
|
||||||
|
self._current_scene_idx: int = 0
|
||||||
|
self.window = window
|
||||||
|
self.color_picker = color_picker
|
||||||
|
self._selected_meshes: list[Mesh] = []
|
||||||
|
|
||||||
|
def save_frame_to_file(self):
|
||||||
|
with open("tools/animation-tools/output.txt", 'w') as file:
|
||||||
|
for i, scene in enumerate(self._scenes):
|
||||||
|
file.write(scene_to_frame(scene.scene, scene.fill, scene.fade, scene.delay, i))
|
||||||
|
|
||||||
|
def generate_meshes(self) -> list[Mesh]:
|
||||||
|
# generate a list of cubes
|
||||||
|
meshes: list[Mesh] = []
|
||||||
|
for origin in product([-1, 0, 1],[-1, 0, 1],[-1, 0, 1]):
|
||||||
|
cube = Cube()
|
||||||
|
cube.scale((0.35, 0.35, 0.35))
|
||||||
|
cube.set_position(origin)
|
||||||
|
cube.set_face_color((0, 0, 0))
|
||||||
|
meshes.append(cube)
|
||||||
|
return meshes
|
||||||
|
|
||||||
|
def new_scene(self) -> Scene:
|
||||||
|
return Scene(self.generate_meshes(), 90, 5)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
for mesh in self._selected_meshes:
|
||||||
|
mesh.set_face_color(self.color_picker.get_color())
|
||||||
|
self.get_current_scene().scene.draw(self.window)
|
||||||
|
|
||||||
|
def get_current_scene(self) -> SceneStore:
|
||||||
|
return self._scenes[self._current_scene_idx]
|
||||||
|
|
||||||
|
def click_mesh(self, coordinates: tuple[int, int]):
|
||||||
|
mesh = self.get_current_scene().scene.get_mesh_from_xy(coordinates)
|
||||||
|
|
||||||
|
if mesh == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if mesh in self._selected_meshes:
|
||||||
|
mesh.set_edge_color((0,0,0))
|
||||||
|
self._selected_meshes.remove(mesh)
|
||||||
|
else:
|
||||||
|
mesh.set_edge_color((255,0,0))
|
||||||
|
self._selected_meshes.append(mesh)
|
||||||
|
|
||||||
|
def deselect_all_mesh(self):
|
||||||
|
for mesh in self._selected_meshes:
|
||||||
|
mesh.set_edge_color((0,0,0))
|
||||||
|
self._selected_meshes = []
|
||||||
|
|
||||||
|
def next_scene(self):
|
||||||
|
self.deselect_all_mesh()
|
||||||
|
current_angles = self.get_current_scene().scene.euler_angles
|
||||||
|
if len(self._scenes)-1 == self._current_scene_idx:
|
||||||
|
self._scenes.append(SceneStore(self.new_scene(), FillInterpolation.NO_FILL, FrameInterpolation.FADE, 1000))
|
||||||
|
|
||||||
|
self._current_scene_idx += 1
|
||||||
|
|
||||||
|
self.get_current_scene().scene.euler_angles = [angle for angle in current_angles]
|
||||||
|
|
||||||
|
def last_scene(self):
|
||||||
|
if self._current_scene_idx > 0:
|
||||||
|
current_angles = self.get_current_scene().scene.euler_angles
|
||||||
|
self._current_scene_idx -= 1
|
||||||
|
self.get_current_scene().scene.euler_angles = [angle for angle in current_angles]
|
||||||
|
self.deselect_all_mesh()
|
||||||
|
|
||||||
|
def update_scene_options(self, fill: FillInterpolation, fade: FrameInterpolation, delay: int):
|
||||||
|
cur_scene: SceneStore = self.get_current_scene()
|
||||||
|
cur_scene.fill = fill
|
||||||
|
cur_scene.fade = fade
|
||||||
|
cur_scene.delay = delay
|
||||||
|
|
||||||
|
class AnimatorUI:
|
||||||
|
def __init__(self, window):
|
||||||
|
scr_wdt, scr_hgt = window.get_size()
|
||||||
|
|
||||||
|
self.window = window
|
||||||
|
self.colorPicker: ColorPicker = ColorPicker(self.window, *self.rel2abs(5, 5, 20, 60))
|
||||||
|
self.sceneManager: SceneManager = SceneManager(window, self.colorPicker)
|
||||||
|
|
||||||
|
self.save_button: Button = Button(self.window, *self.rel2abs(5, 90, 10, 10), text="Save",onClick=self.sceneManager.save_frame_to_file)
|
||||||
|
self.next_frame_button: Button = Button(self.window, *self.rel2abs(75, 95, 25, 5), text="Next Frame",onClick=self._next_scene)
|
||||||
|
self.last_frame_button: Button = Button(self.window, *self.rel2abs(50, 95, 25, 5), text="last Frame",onClick=self._last_scene)
|
||||||
|
self.trackMouseMotion: bool = False
|
||||||
|
|
||||||
|
self.fill_dropdown: Dropdown = Dropdown(self.window, *self.rel2abs(30, 0, 40, 5), name="Fill Type",
|
||||||
|
choices=[option.value for option in FillInterpolation], onRelease=self.set_update_options_flag)
|
||||||
|
|
||||||
|
self.fade_dropdown: Dropdown = Dropdown(self.window, *self.rel2abs(70, 0, 20, 5), name="Fade Type",
|
||||||
|
choices=[option.value for option in FrameInterpolation], onRelease=self.set_update_options_flag)
|
||||||
|
|
||||||
|
self.updateOptions = False
|
||||||
|
self._set_dropdown_options()
|
||||||
|
|
||||||
|
self.delay_text_entry: TextBox = TextBox(self.window, *self.rel2abs(30, 5, 60, 7), placeholderText="Transition Time (ms)", onSubmit=self.set_update_options_flag)
|
||||||
|
|
||||||
|
# Make a frame counter as a button but make it not look like a button
|
||||||
|
default_color=(150,150,150)
|
||||||
|
self.frame_counter_text: Button = Button(self.window, *self.rel2abs(70, 85, 10, 10), text="0", inactiveColour=default_color, hoverColour=default_color, pressedColour=default_color)
|
||||||
|
|
||||||
|
def rel2abs(self, x: float, y: float, width: float, height: float) -> tuple[int,int,int,int]:
|
||||||
|
scr_wdt, scr_hgt = self.window.get_size()
|
||||||
|
x_abs = int(x*scr_wdt/100)
|
||||||
|
y_abs = int(y*scr_hgt/100)
|
||||||
|
w_abs = int(width*scr_wdt/100)
|
||||||
|
h_abs = int(height*scr_hgt/100)
|
||||||
|
return (x_abs, y_abs, w_abs, h_abs)
|
||||||
|
|
||||||
|
|
||||||
|
def update_interaction(self, game_event):
|
||||||
|
if not self.trackMouseMotion:
|
||||||
|
pygame.mouse.get_rel()
|
||||||
|
|
||||||
|
if game_event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
if game_event.button == 2: # middle mouse click
|
||||||
|
self.trackMouseMotion = True
|
||||||
|
elif game_event.button == 1: # left click
|
||||||
|
self.sceneManager.click_mesh(pygame.mouse.get_pos())
|
||||||
|
elif game_event.type == pygame.MOUSEBUTTONUP:
|
||||||
|
if game_event.button == 2: # middle mouse release
|
||||||
|
self.trackMouseMotion = False
|
||||||
|
elif self.trackMouseMotion and game_event.type == pygame.MOUSEMOTION:
|
||||||
|
mouseMovement = pygame.mouse.get_rel()
|
||||||
|
current_scene = self.sceneManager.get_current_scene()
|
||||||
|
current_scene.scene.euler_angles[0] -= mouseMovement[1]
|
||||||
|
current_scene.scene.euler_angles[1] -= mouseMovement[0]
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
if self.updateOptions:
|
||||||
|
self.set_scene_options()
|
||||||
|
# make the preview window for the color picker
|
||||||
|
pygame.draw.rect(self.window, self.colorPicker.get_color(), self.rel2abs(11, 70, 5, 5))
|
||||||
|
pygame.draw.rect(self.window, (0,0,0), self.rel2abs(11, 70, 5, 5), 2)
|
||||||
|
|
||||||
|
self.sceneManager.draw()
|
||||||
|
|
||||||
|
def _next_scene(self):
|
||||||
|
self.sceneManager.next_scene()
|
||||||
|
self._set_dropdown_options()
|
||||||
|
self.frame_counter_text.setText(str(self.sceneManager._current_scene_idx))
|
||||||
|
|
||||||
|
|
||||||
|
def _last_scene(self):
|
||||||
|
self.sceneManager.last_scene()
|
||||||
|
self._set_dropdown_options()
|
||||||
|
self.frame_counter_text.setText(str(self.sceneManager._current_scene_idx))
|
||||||
|
|
||||||
|
def _set_dropdown_options(self):
|
||||||
|
# cursed method to set dropdown options because for some reason pygame_widgets dropdown doesn't allow you to manually set the dropdown option
|
||||||
|
scene = self.sceneManager.get_current_scene()
|
||||||
|
for choice in self.fill_dropdown._Dropdown__choices:
|
||||||
|
if choice.text.find(scene.fill) != -1:
|
||||||
|
self.fill_dropdown.chosen = choice
|
||||||
|
break
|
||||||
|
|
||||||
|
for choice in self.fade_dropdown._Dropdown__choices:
|
||||||
|
if choice.text.find(scene.fade) != -1:
|
||||||
|
self.fade_dropdown.chosen = choice
|
||||||
|
break
|
||||||
|
|
||||||
|
def set_update_options_flag(self):
|
||||||
|
self.updateOptions = True
|
||||||
|
|
||||||
|
def set_scene_options(self):
|
||||||
|
self.updateOptions = False
|
||||||
|
delay_time = 1000
|
||||||
|
try:
|
||||||
|
delay_time = int(self.delay_text_entry.getText())
|
||||||
|
except ValueError:
|
||||||
|
self.delay_text_entry.setText(str(delay_time))
|
||||||
|
self.sceneManager.update_scene_options(self.fill_dropdown.getSelected(), self.fade_dropdown.getSelected(), delay_time)
|
||||||
37
tools/animation-tools/animation-tool.py
Normal file
37
tools/animation-tools/animation-tool.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import pygame
|
||||||
|
import pygame_widgets
|
||||||
|
from Shapes import *
|
||||||
|
from UI import AnimatorUI
|
||||||
|
|
||||||
|
WINDOW_W = 500
|
||||||
|
WINDOW_H = 500
|
||||||
|
|
||||||
|
|
||||||
|
file_data = ""
|
||||||
|
|
||||||
|
pygame.init()
|
||||||
|
window = pygame.display.set_mode((WINDOW_W, WINDOW_H))
|
||||||
|
|
||||||
|
ui = AnimatorUI(window)
|
||||||
|
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
run = True
|
||||||
|
|
||||||
|
while run:
|
||||||
|
clock.tick(60)
|
||||||
|
window.fill((255, 255, 255))
|
||||||
|
|
||||||
|
events = pygame.event.get()
|
||||||
|
for event in events:
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
run = False
|
||||||
|
# if the event isn't handled above as a global event, let the ui handle it
|
||||||
|
else:
|
||||||
|
ui.update_interaction(event)
|
||||||
|
|
||||||
|
pygame_widgets.update(events)
|
||||||
|
|
||||||
|
ui.draw()
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
pygame.quit()
|
||||||
BIN
tools/animation-tools/images/open-animator.png
Normal file
BIN
tools/animation-tools/images/open-animator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
2
tools/animation-tools/requirements.txt
Normal file
2
tools/animation-tools/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pygame==2.6.0
|
||||||
|
pygame-widgets==1.1.5
|
||||||
Reference in New Issue
Block a user