imported from "final" folder
This commit is contained in:
607
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/Chromancer.ino
Normal file
607
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/Chromancer.ino
Normal file
@@ -0,0 +1,607 @@
|
||||
/// @file Chromancer.ino
|
||||
/// @brief Hexagonal LED display visualization
|
||||
/// @example Chromancer.ino
|
||||
///
|
||||
/// This sketch is fully compatible with the FastLED web compiler. To use it do the following:
|
||||
/// 1. Install Fastled: `pip install fastled`
|
||||
/// 2. cd into this examples page.
|
||||
/// 3. Run the FastLED web compiler at root: `fastled`
|
||||
/// 4. When the compiler is done a web page will open.
|
||||
|
||||
/*
|
||||
Original Source: https://github.com/ZackFreedman/Chromance
|
||||
GaryWoo's Video: https://www.youtube.com/watch?v=-nSCtxa2Kp0
|
||||
GaryWoo's LedMap: https://gist.github.com/Garywoo/b6cd1ea90cb5e17cc60b01ae68a2b770
|
||||
GaryWoo's presets: https://gist.github.com/Garywoo/82fa67c6e1f9529dc16a01dd97d05d58
|
||||
Chromance wall hexagon source (emotion controlled w/ EmotiBit)
|
||||
Partially cribbed from the DotStar example
|
||||
I smooshed in the ESP32 BasicOTA sketch, too
|
||||
|
||||
(C) Voidstar Lab 2021
|
||||
*/
|
||||
|
||||
#include "fl/sketch_macros.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
#if !SKETCH_HAS_LOTS_OF_MEMORY
|
||||
// Platform does not have enough memory
|
||||
// Other platforms have weird issues. Will revisit this later.
|
||||
#include <Arduino.h>
|
||||
|
||||
void setup() {
|
||||
// Use Serial.println instead of FL_WARN to prevent optimization away
|
||||
Serial.begin(115200);
|
||||
Serial.println("Chromancer.ino: setup() - Platform has insufficient memory for full demo");
|
||||
}
|
||||
void loop() {
|
||||
// Use Serial.println instead of FL_WARN to prevent optimization away
|
||||
Serial.println("Chromancer.ino: loop() - Platform has insufficient memory for full demo");
|
||||
delay(1000); // Prevent rapid printing
|
||||
}
|
||||
#else
|
||||
|
||||
|
||||
#include <FastLED.h>
|
||||
|
||||
#include "fl/screenmap.h"
|
||||
#include "fl/math_macros.h"
|
||||
#include "fl/json.h"
|
||||
#include "fl/ui.h"
|
||||
#include "fl/map.h"
|
||||
|
||||
#include "fl/str.h"
|
||||
|
||||
#include "./screenmap.json.h"
|
||||
#include "./mapping.h"
|
||||
#include "./ripple.h"
|
||||
#include "./detail.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
enum {
|
||||
BlackStrip = 0,
|
||||
GreenStrip = 1,
|
||||
RedStrip = 2,
|
||||
BlueStrip = 3,
|
||||
};
|
||||
|
||||
|
||||
// Strips are different lengths because I am a dumb
|
||||
|
||||
constexpr int lengths[] = {
|
||||
154, // Black strip
|
||||
168, // Green strip
|
||||
84, // Red strip
|
||||
154 // Blue strip
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// non emscripten uses separate arrays for each strip. Eventually emscripten
|
||||
// should support this as well but right now we don't
|
||||
CRGB leds0[lengths[BlackStrip]] = {};
|
||||
CRGB leds1[lengths[GreenStrip]] = {};
|
||||
CRGB leds2[lengths[RedStrip]] = {}; // Red
|
||||
CRGB leds3[lengths[BlueStrip]] = {};
|
||||
CRGB *leds[] = {leds0, leds1, leds2, leds3};
|
||||
|
||||
|
||||
byte ledColors[40][14][3]; // LED buffer - each ripple writes to this, then we
|
||||
// write this to the strips
|
||||
//float decay = 0.97; // Multiply all LED's by this amount each tick to create
|
||||
// fancy fading tails
|
||||
|
||||
UISlider sliderDecay("decay", .97f, .8, 1.0, .01);
|
||||
|
||||
// These ripples are endlessly reused so we don't need to do any memory
|
||||
// management
|
||||
#define numberOfRipples 30
|
||||
Ripple ripples[numberOfRipples] = {
|
||||
Ripple(0), Ripple(1), Ripple(2), Ripple(3), Ripple(4), Ripple(5),
|
||||
Ripple(6), Ripple(7), Ripple(8), Ripple(9), Ripple(10), Ripple(11),
|
||||
Ripple(12), Ripple(13), Ripple(14), Ripple(15), Ripple(16), Ripple(17),
|
||||
Ripple(18), Ripple(19), Ripple(20), Ripple(21), Ripple(22), Ripple(23),
|
||||
Ripple(24), Ripple(25), Ripple(26), Ripple(27), Ripple(28), Ripple(29),
|
||||
};
|
||||
|
||||
// Biometric detection and interpretation
|
||||
// IR (heartbeat) is used to fire outward ripples
|
||||
float lastIrReading; // When our heart pumps, reflected IR drops sharply
|
||||
float highestIrReading; // These vars let us detect this drop
|
||||
unsigned long
|
||||
lastHeartbeat; // Track last heartbeat so we can detect noise/disconnections
|
||||
#define heartbeatLockout \
|
||||
500 // Heartbeats that happen within this many milliseconds are ignored
|
||||
#define heartbeatDelta 300 // Drop in reflected IR that constitutes a heartbeat
|
||||
|
||||
// Heartbeat color ripples are proportional to skin temperature
|
||||
#define lowTemperature 33.0 // Resting temperature
|
||||
#define highTemperature 37.0 // Really fired up
|
||||
float lastKnownTemperature =
|
||||
(lowTemperature + highTemperature) /
|
||||
2.0; // Carries skin temperature from temperature callback to IR callback
|
||||
|
||||
// EDA code was too unreliable and was cut.
|
||||
// TODO: Rebuild EDA code
|
||||
|
||||
// Gyroscope is used to reject data if you're moving too much
|
||||
#define gyroAlpha 0.9 // Exponential smoothing constant
|
||||
#define gyroThreshold \
|
||||
300 // Minimum angular velocity total (X+Y+Z) that disqualifies readings
|
||||
float gyroX, gyroY, gyroZ;
|
||||
|
||||
// If you don't have an EmotiBit or don't feel like wearing it, that's OK
|
||||
// We'll fire automatic pulses
|
||||
#define randomPulsesEnabled true // Fire random rainbow pulses from random nodes
|
||||
#define cubePulsesEnabled true // Draw cubes at random nodes
|
||||
UICheckbox starburstPulsesEnabled("Starburst Pulses", true);
|
||||
UICheckbox simulatedBiometricsEnabled("Simulated Biometrics", true);
|
||||
|
||||
#define autoPulseTimeout \
|
||||
5000 // If no heartbeat is received in this many ms, begin firing
|
||||
// random/simulated pulses
|
||||
#define randomPulseTime 2000 // Fire a random pulse every (this many) ms
|
||||
unsigned long lastRandomPulse;
|
||||
byte lastAutoPulseNode = 255;
|
||||
|
||||
byte numberOfAutoPulseTypes =
|
||||
randomPulsesEnabled + cubePulsesEnabled + int(starburstPulsesEnabled);
|
||||
byte currentAutoPulseType = 255;
|
||||
#define autoPulseChangeTime 30000
|
||||
unsigned long lastAutoPulseChange;
|
||||
|
||||
#define simulatedHeartbeatBaseTime \
|
||||
600 // Fire a simulated heartbeat pulse after at least this many ms
|
||||
#define simulatedHeartbeatVariance \
|
||||
200 // Add random jitter to simulated heartbeat
|
||||
#define simulatedEdaBaseTime 1000 // Same, but for inward EDA pulses
|
||||
#define simulatedEdaVariance 10000
|
||||
unsigned long nextSimulatedHeartbeat;
|
||||
unsigned long nextSimulatedEda;
|
||||
|
||||
// Helper function to check if a node is on the border
|
||||
bool isNodeOnBorder(byte node) {
|
||||
for (int i = 0; i < numberOfBorderNodes; i++) {
|
||||
if (node == borderNodes[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UITitle title("Chromancer");
|
||||
UIDescription description("Take 6 seconds to boot up. Chromancer is a wall-mounted hexagonal LED display that originally reacted to biometric data from an EmotiBit sensor. It visualizes your heartbeat, skin temperature, and movement in real-time. Chromancer also has a few built-in effects that can be triggered with the push of a button. Enjoy!");
|
||||
UICheckbox allWhite("All White", false);
|
||||
|
||||
UIButton simulatedHeartbeat("Simulated Heartbeat");
|
||||
UIButton triggerStarburst("Trigger Starburst");
|
||||
UIButton triggerRainbowCube("Rainbow Cube");
|
||||
UIButton triggerBorderWave("Border Wave");
|
||||
UIButton triggerSpiral("Spiral Wave");
|
||||
bool wasHeartbeatClicked = false;
|
||||
bool wasStarburstClicked = false;
|
||||
bool wasRainbowCubeClicked = false;
|
||||
bool wasBorderWaveClicked = false;
|
||||
bool wasSpiralClicked = false;
|
||||
|
||||
// Group related UI elements using UIGroup template multi-argument constructor
|
||||
UIGroup effectTriggers("Effect Triggers", simulatedHeartbeat, triggerStarburst, triggerRainbowCube, triggerBorderWave, triggerSpiral);
|
||||
UIGroup automationControls("Automation", starburstPulsesEnabled, simulatedBiometricsEnabled);
|
||||
UIGroup displayControls("Display", sliderDecay, allWhite);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
Serial.println("*** LET'S GOOOOO ***");
|
||||
|
||||
Serial.println("JSON SCREENMAP");
|
||||
Serial.println(JSON_SCREEN_MAP);
|
||||
|
||||
fl::fl_map<fl::string, ScreenMap> segmentMaps;
|
||||
ScreenMap::ParseJson(JSON_SCREEN_MAP, &segmentMaps);
|
||||
|
||||
printf("Parsed %d segment maps\n", int(segmentMaps.size()));
|
||||
for (auto kv : segmentMaps) {
|
||||
Serial.print(kv.first.c_str());
|
||||
Serial.print(" ");
|
||||
Serial.println(kv.second.getLength());
|
||||
}
|
||||
|
||||
|
||||
// ScreenMap screenmaps[4];
|
||||
ScreenMap red, black, green, blue;
|
||||
bool ok = true;
|
||||
|
||||
auto red_it = segmentMaps.find("red_segment");
|
||||
ok = (red_it != segmentMaps.end()) && ok;
|
||||
if (red_it != segmentMaps.end()) red = red_it->second;
|
||||
|
||||
auto black_it = segmentMaps.find("back_segment");
|
||||
ok = (black_it != segmentMaps.end()) && ok;
|
||||
if (black_it != segmentMaps.end()) black = black_it->second;
|
||||
|
||||
auto green_it = segmentMaps.find("green_segment");
|
||||
ok = (green_it != segmentMaps.end()) && ok;
|
||||
if (green_it != segmentMaps.end()) green = green_it->second;
|
||||
|
||||
auto blue_it = segmentMaps.find("blue_segment");
|
||||
ok = (blue_it != segmentMaps.end()) && ok;
|
||||
if (blue_it != segmentMaps.end()) blue = blue_it->second;
|
||||
if (!ok) {
|
||||
Serial.println("Failed to get all segment maps");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
CRGB* red_leds = leds[RedStrip];
|
||||
CRGB* black_leds = leds[BlackStrip];
|
||||
CRGB* green_leds = leds[GreenStrip];
|
||||
CRGB* blue_leds = leds[BlueStrip];
|
||||
|
||||
FastLED.addLeds<WS2812, 2>(black_leds, lengths[BlackStrip]).setScreenMap(black);
|
||||
FastLED.addLeds<WS2812, 3>(green_leds, lengths[GreenStrip]).setScreenMap(green);
|
||||
FastLED.addLeds<WS2812, 1>(red_leds, lengths[RedStrip]).setScreenMap(red);
|
||||
FastLED.addLeds<WS2812, 4>(blue_leds, lengths[BlueStrip]).setScreenMap(blue);
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
unsigned long benchmark = millis();
|
||||
FL_UNUSED(benchmark);
|
||||
|
||||
// Fade all dots to create trails
|
||||
for (int strip = 0; strip < 40; strip++) {
|
||||
for (int led = 0; led < 14; led++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ledColors[strip][led][i] *= sliderDecay.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberOfRipples; i++) {
|
||||
ripples[i].advance(ledColors);
|
||||
}
|
||||
|
||||
for (int segment = 0; segment < 40; segment++) {
|
||||
for (int fromBottom = 0; fromBottom < 14; fromBottom++) {
|
||||
int strip = ledAssignments[segment][0];
|
||||
int led = round(fmap(fromBottom, 0, 13, ledAssignments[segment][2],
|
||||
ledAssignments[segment][1]));
|
||||
leds[strip][led] = CRGB(ledColors[segment][fromBottom][0],
|
||||
ledColors[segment][fromBottom][1],
|
||||
ledColors[segment][fromBottom][2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (allWhite) {
|
||||
// for all strips
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < lengths[i]; j++) {
|
||||
leds[i][j] = CRGB::White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FastLED.show();
|
||||
|
||||
|
||||
// Check if buttons were clicked
|
||||
wasHeartbeatClicked = bool(simulatedHeartbeat);
|
||||
wasStarburstClicked = bool(triggerStarburst);
|
||||
wasRainbowCubeClicked = bool(triggerRainbowCube);
|
||||
wasBorderWaveClicked = bool(triggerBorderWave);
|
||||
wasSpiralClicked = bool(triggerSpiral);
|
||||
|
||||
if (wasSpiralClicked) {
|
||||
// Trigger spiral wave effect from center
|
||||
unsigned int baseColor = random(0xFFFF);
|
||||
byte centerNode = 15; // Center node
|
||||
|
||||
// Create 6 ripples in a spiral pattern
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (nodeConnections[centerNode][i] >= 0) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
centerNode, i,
|
||||
Adafruit_DotStar_ColorHSV(
|
||||
baseColor + (0xFFFF / 6) * i, 255, 255),
|
||||
0.3 + (i * 0.1), // Varying speeds creates spiral effect
|
||||
2000,
|
||||
i % 2 ? alwaysTurnsLeft : alwaysTurnsRight); // Alternating turn directions
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHeartbeat = millis();
|
||||
}
|
||||
|
||||
if (wasBorderWaveClicked) {
|
||||
// Trigger immediate border wave effect
|
||||
unsigned int baseColor = random(0xFFFF);
|
||||
|
||||
// Start ripples from each border node in sequence
|
||||
for (int i = 0; i < numberOfBorderNodes; i++) {
|
||||
byte node = borderNodes[i];
|
||||
// Find an inward direction
|
||||
for (int dir = 0; dir < 6; dir++) {
|
||||
if (nodeConnections[node][dir] >= 0 &&
|
||||
!isNodeOnBorder(nodeConnections[node][dir])) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
node, dir,
|
||||
Adafruit_DotStar_ColorHSV(
|
||||
baseColor + (0xFFFF / numberOfBorderNodes) * i,
|
||||
255, 255),
|
||||
.4, 2000, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHeartbeat = millis();
|
||||
}
|
||||
|
||||
if (wasRainbowCubeClicked) {
|
||||
// Trigger immediate rainbow cube effect
|
||||
int node = cubeNodes[random(numberOfCubeNodes)];
|
||||
unsigned int baseColor = random(0xFFFF);
|
||||
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (nodeConnections[node][i] >= 0) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
node, i,
|
||||
Adafruit_DotStar_ColorHSV(
|
||||
baseColor + (0xFFFF / 6) * i, 255, 255),
|
||||
.5, 2000, behavior);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHeartbeat = millis();
|
||||
}
|
||||
|
||||
if (wasStarburstClicked) {
|
||||
// Trigger immediate starburst effect
|
||||
unsigned int baseColor = random(0xFFFF);
|
||||
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
starburstNode, i,
|
||||
Adafruit_DotStar_ColorHSV(
|
||||
baseColor + (0xFFFF / 6) * i, 255, 255),
|
||||
.65, 1500, behavior);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHeartbeat = millis();
|
||||
}
|
||||
|
||||
if (wasHeartbeatClicked) {
|
||||
// Trigger immediate heartbeat effect
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(15, i, 0xEE1111,
|
||||
float(random(100)) / 100.0 * .1 + .4, 1000, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHeartbeat = millis();
|
||||
}
|
||||
|
||||
if (millis() - lastHeartbeat >= autoPulseTimeout) {
|
||||
// When biometric data is unavailable, visualize at random
|
||||
if (numberOfAutoPulseTypes &&
|
||||
millis() - lastRandomPulse >= randomPulseTime) {
|
||||
unsigned int baseColor = random(0xFFFF);
|
||||
|
||||
if (currentAutoPulseType == 255 ||
|
||||
(numberOfAutoPulseTypes > 1 &&
|
||||
millis() - lastAutoPulseChange >= autoPulseChangeTime)) {
|
||||
byte possiblePulse = 255;
|
||||
while (true) {
|
||||
possiblePulse = random(3);
|
||||
|
||||
if (possiblePulse == currentAutoPulseType)
|
||||
continue;
|
||||
|
||||
switch (possiblePulse) {
|
||||
case 0:
|
||||
if (!randomPulsesEnabled)
|
||||
continue;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (!cubePulsesEnabled)
|
||||
continue;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (!starburstPulsesEnabled)
|
||||
continue;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
currentAutoPulseType = possiblePulse;
|
||||
lastAutoPulseChange = millis();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (currentAutoPulseType) {
|
||||
case 0: {
|
||||
int node = 0;
|
||||
bool foundStartingNode = false;
|
||||
while (!foundStartingNode) {
|
||||
node = random(25);
|
||||
foundStartingNode = true;
|
||||
for (int i = 0; i < numberOfBorderNodes; i++) {
|
||||
// Don't fire a pulse on one of the outer nodes - it
|
||||
// looks boring
|
||||
if (node == borderNodes[i])
|
||||
foundStartingNode = false;
|
||||
}
|
||||
|
||||
if (node == lastAutoPulseNode)
|
||||
foundStartingNode = false;
|
||||
}
|
||||
|
||||
lastAutoPulseNode = node;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (nodeConnections[node][i] >= 0) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
node, i,
|
||||
// strip0.ColorHSV(baseColor
|
||||
// + (0xFFFF / 6) * i,
|
||||
// 255, 255),
|
||||
Adafruit_DotStar_ColorHSV(baseColor, 255,
|
||||
255),
|
||||
float(random(100)) / 100.0 * .2 + .5, 3000,
|
||||
1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
int node = cubeNodes[random(numberOfCubeNodes)];
|
||||
|
||||
while (node == lastAutoPulseNode)
|
||||
node = cubeNodes[random(numberOfCubeNodes)];
|
||||
|
||||
lastAutoPulseNode = node;
|
||||
|
||||
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (nodeConnections[node][i] >= 0) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
node, i,
|
||||
// strip0.ColorHSV(baseColor
|
||||
// + (0xFFFF / 6) * i,
|
||||
// 255, 255),
|
||||
Adafruit_DotStar_ColorHSV(baseColor, 255,
|
||||
255),
|
||||
.5, 2000, behavior);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
|
||||
|
||||
lastAutoPulseNode = starburstNode;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
starburstNode, i,
|
||||
Adafruit_DotStar_ColorHSV(
|
||||
baseColor + (0xFFFF / 6) * i, 255, 255),
|
||||
.65, 1500, behavior);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
lastRandomPulse = millis();
|
||||
}
|
||||
|
||||
if (simulatedBiometricsEnabled) {
|
||||
// Simulated heartbeat
|
||||
if (millis() >= nextSimulatedHeartbeat) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
ripples[j].start(
|
||||
15, i, 0xEE1111,
|
||||
float(random(100)) / 100.0 * .1 + .4, 1000, 0);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextSimulatedHeartbeat = millis() + simulatedHeartbeatBaseTime +
|
||||
random(simulatedHeartbeatVariance);
|
||||
}
|
||||
|
||||
// Simulated EDA ripples
|
||||
if (millis() >= nextSimulatedEda) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < numberOfRipples; j++) {
|
||||
if (ripples[j].state == dead) {
|
||||
byte targetNode =
|
||||
borderNodes[random(numberOfBorderNodes)];
|
||||
byte direction = 255;
|
||||
|
||||
while (direction == 255) {
|
||||
direction = random(6);
|
||||
if (nodeConnections[targetNode][direction] < 0)
|
||||
direction = 255;
|
||||
}
|
||||
|
||||
ripples[j].start(
|
||||
targetNode, direction, 0x1111EE,
|
||||
float(random(100)) / 100.0 * .5 + 2, 300, 2);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextSimulatedEda = millis() + simulatedEdaBaseTime +
|
||||
random(simulatedEdaVariance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serial.print("Benchmark: ");
|
||||
// Serial.println(millis() - benchmark);
|
||||
}
|
||||
|
||||
#endif // __AVR__
|
||||
80
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/detail.h
Normal file
80
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/detail.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "fl/stdint.h"
|
||||
|
||||
|
||||
inline uint32_t Adafruit_DotStar_ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {
|
||||
|
||||
uint8_t r, g, b;
|
||||
|
||||
// Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
|
||||
// 0 is not the start of pure red, but the midpoint...a few values above
|
||||
// zero and a few below 65536 all yield pure red (similarly, 32768 is the
|
||||
// midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
|
||||
// each for red, green, blue) really only allows for 1530 distinct hues
|
||||
// (not 1536, more on that below), but the full unsigned 16-bit type was
|
||||
// chosen for hue so that one's code can easily handle a contiguous color
|
||||
// wheel by allowing hue to roll over in either direction.
|
||||
hue = (hue * 1530L + 32768) / 65536;
|
||||
// Because red is centered on the rollover point (the +32768 above,
|
||||
// essentially a fixed-point +0.5), the above actually yields 0 to 1530,
|
||||
// where 0 and 1530 would yield the same thing. Rather than apply a
|
||||
// costly modulo operator, 1530 is handled as a special case below.
|
||||
|
||||
// So you'd think that the color "hexcone" (the thing that ramps from
|
||||
// pure red, to pure yellow, to pure green and so forth back to red,
|
||||
// yielding six slices), and with each color component having 256
|
||||
// possible values (0-255), might have 1536 possible items (6*256),
|
||||
// but in reality there's 1530. This is because the last element in
|
||||
// each 256-element slice is equal to the first element of the next
|
||||
// slice, and keeping those in there this would create small
|
||||
// discontinuities in the color wheel. So the last element of each
|
||||
// slice is dropped...we regard only elements 0-254, with item 255
|
||||
// being picked up as element 0 of the next slice. Like this:
|
||||
// Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0
|
||||
// Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0
|
||||
// Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254
|
||||
// and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
|
||||
// the constants below are not the multiples of 256 you might expect.
|
||||
|
||||
// Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
|
||||
if (hue < 510) { // Red to Green-1
|
||||
b = 0;
|
||||
if (hue < 255) { // Red to Yellow-1
|
||||
r = 255;
|
||||
g = hue; // g = 0 to 254
|
||||
} else { // Yellow to Green-1
|
||||
r = 510 - hue; // r = 255 to 1
|
||||
g = 255;
|
||||
}
|
||||
} else if (hue < 1020) { // Green to Blue-1
|
||||
r = 0;
|
||||
if (hue < 765) { // Green to Cyan-1
|
||||
g = 255;
|
||||
b = hue - 510; // b = 0 to 254
|
||||
} else { // Cyan to Blue-1
|
||||
g = 1020 - hue; // g = 255 to 1
|
||||
b = 255;
|
||||
}
|
||||
} else if (hue < 1530) { // Blue to Red-1
|
||||
g = 0;
|
||||
if (hue < 1275) { // Blue to Magenta-1
|
||||
r = hue - 1020; // r = 0 to 254
|
||||
b = 255;
|
||||
} else { // Magenta to Red-1
|
||||
r = 255;
|
||||
b = 1530 - hue; // b = 255 to 1
|
||||
}
|
||||
} else { // Last 0.5 Red (quicker than % operator)
|
||||
r = 255;
|
||||
g = b = 0;
|
||||
}
|
||||
|
||||
// Apply saturation and value to R,G,B, pack into 32-bit result:
|
||||
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
|
||||
uint16_t s1 = 1 + sat; // 1 to 256; same reason
|
||||
uint8_t s2 = 255 - sat; // 255 to 0
|
||||
return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
|
||||
(((((g * s1) >> 8) + s2) * v1) & 0xff00) |
|
||||
(((((b * s1) >> 8) + s2) * v1) >> 8);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
const char JSON_MAP[] = R"({"map": [
|
||||
|
||||
406,407,408,409,410,411,412,413,414,415,416,417,418,419,
|
||||
420,421,422,423,424,425,426,427,428,429,430,431,432,433,
|
||||
434,435,436,437,438,439,440,441,442,443,444,445,446,447,
|
||||
532,533,534,535,536,537,538,539,540,541,542,543,544,545,
|
||||
546,547,548,549,550,551,552,553,554,555,556,557,558,559,
|
||||
377,376,375,374,373,372,371,370,369,368,367,366,365,364,
|
||||
363,362,361,360,359,358,357,356,355,354,353,352,351,350,
|
||||
392,393,394,395,396,397,398,399,400,401,402,403,404,405,
|
||||
223,222,221,220,219,218,217,216,215,214,213,212,211,210,
|
||||
125,124,123,122,121,120,119,118,117,116,115,114,113,112,
|
||||
111,110,109,108,107,106,105,104,103,102,101,100,99,98,
|
||||
97,96,95,94,93,92,91,90,89,88,87,86,85,84,
|
||||
168,169,170,171,172,173,174,175,176,177,178,179,180,181,
|
||||
182,183,184,185,186,187,188,189,190,191,192,193,194,195,
|
||||
196,197,198,199,200,201,202,203,204,205,206,207,208,209,
|
||||
126,127,128,129,130,131,132,133,134,135,136,137,138,139,
|
||||
307,306,305,304,303,302,301,300,299,298,297,296,295,294,
|
||||
349,348,347,346,345,344,343,342,341,340,339,338,337,336,
|
||||
391,390,389,388,387,386,385,384,383,382,381,380,379,378,
|
||||
13,12,11,10,9,8,7,6,5,4,3,2,1,0,
|
||||
461,460,459,458,457,456,455,454,453,452,451,450,449,448,
|
||||
531,530,529,528,527,526,525,524,523,522,521,520,519,518,
|
||||
517,516,515,514,513,512,511,510,509,508,507,506,505,504,
|
||||
503,502,501,500,499,498,497,496,495,494,493,492,491,490,
|
||||
476,477,478,479,480,481,482,483,484,485,486,487,488,489,
|
||||
321,320,319,318,317,316,315,314,313,312,311,310,309,308,
|
||||
153,152,151,150,149,148,147,146,145,144,143,142,141,140,
|
||||
322,323,324,325,326,327,328,329,330,331,332,333,334,335,
|
||||
475,474,473,472,471,470,469,468,467,466,465,464,463,462,
|
||||
154,155,156,157,158,159,160,161,162,163,164,165,166,167,
|
||||
14,15,16,17,18,19,20,21,22,23,24,25,26,27,
|
||||
28,29,30,31,32,33,34,35,36,37,38,39,40,41,
|
||||
42,43,44,45,46,47,48,49,50,51,52,53,54,55,
|
||||
56,57,58,59,60,61,62,63,64,65,66,67,68,69,
|
||||
70,71,72,73,74,75,76,77,78,79,80,81,82,83,
|
||||
237,236,235,234,233,232,231,230,229,228,227,226,225,224,
|
||||
238,239,240,241,242,243,244,245,246,247,248,249,250,251,
|
||||
252,253,254,255,256,257,258,259,260,261,262,263,264,265,
|
||||
266,267,268,269,270,271,272,273,274,275,276,277,278,279,
|
||||
280,281,282,283,284,285,286,287,288,289,290,291,292,293
|
||||
]}
|
||||
)";
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
270
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gen.py
Normal file
270
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/gen.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
Generates the hexegon using math.
|
||||
"""
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import json
|
||||
|
||||
|
||||
from math import pi, cos, sin
|
||||
|
||||
LED_PER_STRIP = 14
|
||||
SPACE_PER_LED = 30.0 # Increased for better visibility
|
||||
LED_DIAMETER = SPACE_PER_LED / 4
|
||||
MIRROR_X = True # Diagramed from the reverse side. Reverse the x-axis
|
||||
|
||||
SMALLEST_ANGLE = 360 / 6
|
||||
|
||||
|
||||
class HexagonAngle(Enum):
|
||||
UP = 90
|
||||
DOWN = 270
|
||||
RIGHT_UP = 30
|
||||
RIGHT_DOWN = 360 - 30
|
||||
LEFT_UP = 150 # (RIGHT_DOWN + 180) % 360
|
||||
LEFT_DOWN = 210 # (RIGHT_UP + 180) % 360
|
||||
|
||||
|
||||
def toRads(angle: float) -> float:
|
||||
return angle * (pi / 180)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: float
|
||||
y: float
|
||||
|
||||
@staticmethod
|
||||
def toJson(points: list["Point"]) -> list[dict]:
|
||||
x_values = [p.x for p in points]
|
||||
y_values = [p.y for p in points]
|
||||
# round
|
||||
x_values = [round(x, 4) for x in x_values]
|
||||
y_values = [round(y, 4) for y in y_values]
|
||||
if MIRROR_X:
|
||||
x_values = [-x for x in x_values]
|
||||
|
||||
return {"x": x_values, "y": y_values, "diameter": LED_DIAMETER}
|
||||
|
||||
def copy(self) -> "Point":
|
||||
return Point(self.x, self.y)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
x_rounded = round(self.x, 2)
|
||||
y_rounded = round(self.y, 2)
|
||||
return f"({x_rounded}, {y_rounded})"
|
||||
|
||||
|
||||
def next_point(pos: Point, angle: HexagonAngle, space: float) -> Point:
|
||||
degrees = angle.value
|
||||
angle_rad = toRads(degrees)
|
||||
x = pos.x + space * cos(angle_rad)
|
||||
y = pos.y + space * sin(angle_rad)
|
||||
return Point(x, y)
|
||||
|
||||
|
||||
def gen_points(
|
||||
input: list[HexagonAngle], leds_per_strip: int, startPos: Point,
|
||||
exclude: list[int] | None = None,
|
||||
add_last: bool = False
|
||||
) -> list[Point]:
|
||||
points: list[Point] = []
|
||||
if (not input) or (not leds_per_strip):
|
||||
return points
|
||||
exclude = exclude or []
|
||||
# Start FSM. Start pointer get's put into the accumulator.
|
||||
curr_point: Point = Point(startPos.x, startPos.y)
|
||||
# points.append(curr_point)
|
||||
last_angle = input[0]
|
||||
for i,angle in enumerate(input):
|
||||
excluded = i in exclude
|
||||
values = list(range(leds_per_strip))
|
||||
last_angle = angle
|
||||
for v in values:
|
||||
last_angle = angle
|
||||
curr_point = next_point(curr_point, angle, SPACE_PER_LED)
|
||||
if not excluded:
|
||||
points.append(curr_point)
|
||||
#if i == len(input) - 1:
|
||||
# break
|
||||
# Next starting point
|
||||
curr_point = next_point(curr_point, last_angle, SPACE_PER_LED)
|
||||
#if not excluded:
|
||||
# points.append(curr_point)
|
||||
if add_last:
|
||||
points.append(curr_point)
|
||||
return points
|
||||
|
||||
|
||||
|
||||
def main() -> None:
|
||||
startPos = Point(0, 0)
|
||||
hexagon_angles = [
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.RIGHT_DOWN,
|
||||
HexagonAngle.DOWN,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.LEFT_UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
|
||||
|
||||
print(points)
|
||||
|
||||
|
||||
|
||||
def simple_test() -> None:
|
||||
startPos = Point(0, 0)
|
||||
hexagon_angles = [
|
||||
HexagonAngle.UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
|
||||
print(points)
|
||||
# assert len(points) == LED_PER_STRIP + 1
|
||||
|
||||
def two_angle_test() -> None:
|
||||
startPos = Point(0, 0)
|
||||
hexagon_angles = [
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
|
||||
print(points)
|
||||
# assert len(points) == LED_PER_STRIP * 2, f"Expected {LED_PER_STRIP * 2} points, got {len(points)} points"
|
||||
|
||||
|
||||
|
||||
def two_angle_test2() -> None:
|
||||
print("two_angle_test2")
|
||||
startPos = Point(0, 0)
|
||||
hexagon_angles = [
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.DOWN,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, startPos)
|
||||
print(points)
|
||||
# assert len(points) == LED_PER_STRIP * 2, f"Expected {LED_PER_STRIP * 2} points, got {len(points)} points"
|
||||
|
||||
# Red is defined by this instruction tutorial: https://voidstar.dozuki.com/Guide/Chromance+Assembly+Instructions/6
|
||||
def find_red_anchor_point() -> list[Point]:
|
||||
hexagon_angles = [
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, Point(0, 0), add_last=True)
|
||||
return points
|
||||
|
||||
def find_green_anchore_point() -> list[Point]:
|
||||
hexagon_angles = [
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, Point(0, 0), add_last=True)
|
||||
return points
|
||||
|
||||
|
||||
RED_ANCHOR_POINT = find_red_anchor_point()[-1]
|
||||
BLACK_ANCHOR_POINT = Point(0,0) # Black
|
||||
GREEN_ANCHOR_POINT = find_green_anchore_point()[-1]
|
||||
BLUE_ANCHOR_POINT = Point(0, 0)
|
||||
|
||||
|
||||
def generate_red_points() -> list[Point]:
|
||||
starting_point = RED_ANCHOR_POINT.copy()
|
||||
hexagon_angles = [
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.DOWN,
|
||||
HexagonAngle.RIGHT_DOWN,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.LEFT_UP
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[5])
|
||||
return points
|
||||
|
||||
|
||||
def generate_black_points() -> list[Point]:
|
||||
starting_point = BLACK_ANCHOR_POINT.copy()
|
||||
hexagon_angles = [
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.RIGHT_DOWN,
|
||||
HexagonAngle.DOWN,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point)
|
||||
return points
|
||||
|
||||
|
||||
def generate_green_points() -> list[Point]:
|
||||
starting_point = GREEN_ANCHOR_POINT.copy()
|
||||
hexagon_angles = [
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.DOWN,
|
||||
HexagonAngle.RIGHT_DOWN, # skip
|
||||
HexagonAngle.LEFT_DOWN, # skip
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.RIGHT_DOWN,
|
||||
HexagonAngle.RIGHT_UP, # skip
|
||||
HexagonAngle.RIGHT_DOWN,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[5,6,13])
|
||||
return points
|
||||
|
||||
def generate_blue_points() -> list[Point]:
|
||||
starting_point = BLUE_ANCHOR_POINT.copy()
|
||||
hexagon_angles = [
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.LEFT_UP,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.LEFT_DOWN,
|
||||
HexagonAngle.RIGHT_DOWN, # skip
|
||||
HexagonAngle.RIGHT_DOWN,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
HexagonAngle.UP,
|
||||
HexagonAngle.RIGHT_UP,
|
||||
]
|
||||
points = gen_points(hexagon_angles, LED_PER_STRIP, starting_point, exclude=[6])
|
||||
return points
|
||||
|
||||
def unit_test() -> None:
|
||||
#simple_test()
|
||||
#two_angle_test()
|
||||
out = {}
|
||||
map = out.setdefault("map", {})
|
||||
map.update({
|
||||
"red_segment": Point.toJson(generate_red_points()),
|
||||
"back_segment": Point.toJson(generate_black_points()),
|
||||
"green_segment": Point.toJson(generate_green_points()),
|
||||
"blue_segment": Point.toJson(generate_blue_points()),
|
||||
})
|
||||
print(json.dumps(out))
|
||||
# write it out to a file
|
||||
with open("output.json", "w") as f:
|
||||
f.write(json.dumps(out))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unit_test()
|
||||
158
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/mapping.h
Normal file
158
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/mapping.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Maps hex topology onto LED's
|
||||
* (C) Voidstar Lab LLC 2021
|
||||
*/
|
||||
|
||||
#ifndef MAPPING_H_
|
||||
#define MAPPING_H_
|
||||
|
||||
// I accidentally noted these down 1-indexed and I'm too tired to adjust them
|
||||
#define headof(S) ((S - 1) * 14)
|
||||
#define tailof(S) (headof(S) + 13)
|
||||
|
||||
// Beam 0 is at 12:00 and advance clockwise
|
||||
// -1 means nothing connected on that side
|
||||
int nodeConnections[25][6] = {
|
||||
{-1, -1, 1, -1, 0, -1},
|
||||
{-1, -1, 3, -1, 2, -1},
|
||||
{-1, -1, 5, -1, 4, -1},
|
||||
{-1, 0, 6, 12, -1, -1},
|
||||
{-1, 2, 8, 14, 7, 1},
|
||||
|
||||
{-1, 4, 10, 16, 9, 3},
|
||||
{-1, -1, -1, 18, 11, 5},
|
||||
{-1, 7, -1, 13, -1, 6},
|
||||
{-1, 9, -1, 15, -1, 8},
|
||||
{-1, 11, -1, 17, -1, 10},
|
||||
|
||||
{12, -1, 19, -1, -1, -1},
|
||||
{14, -1, 21, -1, 20, -1},
|
||||
{16, -1, 23, -1, 22, -1},
|
||||
{18, -1, -1, -1, 24, -1},
|
||||
{13, 20, 25, 29, -1, -1},
|
||||
|
||||
{15, 22, 27, 31, 26, 21},
|
||||
{17, 24, -1, 33, 28, 23},
|
||||
{-1, 26, -1, 30, -1, 25},
|
||||
{-1, 28, -1, 32, -1, 27},
|
||||
{29, -1, 34, -1, -1, -1},
|
||||
|
||||
{31, -1, 36, -1, 35, -1},
|
||||
{33, -1, -1, -1, 37, -1},
|
||||
{30, 35, 38, -1, -1, 34},
|
||||
{32, 37, -1, -1, 39, 36},
|
||||
{-1, 39, -1, -1, -1, 38}
|
||||
};
|
||||
|
||||
// First member: Node closer to ceiling
|
||||
// Second: Node closer to floor
|
||||
int segmentConnections[40][2] = {
|
||||
{0, 3},
|
||||
{0, 4},
|
||||
{1, 4},
|
||||
{1, 5},
|
||||
{2, 5},
|
||||
{2, 6},
|
||||
{3, 7},
|
||||
{4, 7},
|
||||
{4, 8},
|
||||
{5, 8},
|
||||
{5, 9},
|
||||
{6, 9}, // ayy
|
||||
{3, 10},
|
||||
{7, 14},
|
||||
{4, 11},
|
||||
{8, 15},
|
||||
{5, 12},
|
||||
{9, 16},
|
||||
{6, 13},
|
||||
{10, 14},
|
||||
{11, 14},
|
||||
{11, 15},
|
||||
{12, 15},
|
||||
{12, 16},
|
||||
{13, 16},
|
||||
{14, 17},
|
||||
{15, 17},
|
||||
{15, 18},
|
||||
{16, 18},
|
||||
{14, 19},
|
||||
{17, 22},
|
||||
{15, 20},
|
||||
{18, 23},
|
||||
{16, 21},
|
||||
{19, 22},
|
||||
{20, 22},
|
||||
{20, 23},
|
||||
{21, 23},
|
||||
{22, 24},
|
||||
{23, 24}
|
||||
};
|
||||
|
||||
// First member: Strip number
|
||||
// Second: LED index closer to ceiling
|
||||
// Third: LED index closer to floor
|
||||
int ledAssignments[40][3] = {
|
||||
{2, headof(3), tailof(3)},
|
||||
{2, tailof(2), headof(2)},
|
||||
{1, headof(10), tailof(10)},
|
||||
{1, tailof(9), headof(9)},
|
||||
{1, headof(4), tailof(4)},
|
||||
{1, tailof(3), headof(3)},
|
||||
|
||||
{2, tailof(6), headof(6)},
|
||||
{3, tailof(11), headof(11)},
|
||||
{1, headof(11), tailof(11)},
|
||||
{1, tailof(8), headof(8)},
|
||||
{1, headof(12), tailof(12)},
|
||||
{0, tailof(11), headof(11)},
|
||||
|
||||
{2, headof(4), tailof(4)},
|
||||
{3, tailof(10), headof(10)},
|
||||
{2, tailof(1), headof(1)},
|
||||
{1, tailof(7), headof(7)},
|
||||
{1, headof(5), tailof(5)},
|
||||
{0, tailof(10), headof(10)},
|
||||
{1, tailof(2), headof(2)},
|
||||
|
||||
{2, headof(5), tailof(5)},
|
||||
{3, tailof(4), headof(4)},
|
||||
{3, headof(5), tailof(5)},
|
||||
{0, headof(5), tailof(5)},
|
||||
{0, tailof(4), headof(4)},
|
||||
{1, tailof(1), headof(1)},
|
||||
|
||||
{3, tailof(9), headof(9)},
|
||||
{0, headof(6), tailof(6)},
|
||||
{1, tailof(6), headof(6)},
|
||||
{0, tailof(9), headof(9)},
|
||||
|
||||
{3, tailof(3), headof(3)},
|
||||
{3, tailof(8), headof(8)},
|
||||
{3, headof(6), tailof(6)},
|
||||
{0, tailof(8), headof(8)},
|
||||
{0, tailof(3), headof(3)},
|
||||
|
||||
{3, tailof(2), headof(2)},
|
||||
{3, headof(7), tailof(7)},
|
||||
{0, headof(7), tailof(7)},
|
||||
{0, tailof(2), headof(2)},
|
||||
|
||||
{3, tailof(1), headof(1)},
|
||||
{0, tailof(1), headof(1)}
|
||||
};
|
||||
|
||||
// Border nodes are on the very edge of the network.
|
||||
// Ripples fired here don't look very impressive.
|
||||
int numberOfBorderNodes = 10;
|
||||
int borderNodes[] = {0, 1, 2, 3, 6, 10, 13, 19, 21, 24};
|
||||
|
||||
// Cube nodes link three equiangular segments
|
||||
// Firing ripples that always turn in one direction will draw a cube
|
||||
int numberOfCubeNodes = 8;
|
||||
int cubeNodes[] = {7, 8, 9, 11, 12, 17, 18};
|
||||
|
||||
// Firing ripples that always turn in one direction will draw a starburst
|
||||
int starburstNode = 15;
|
||||
|
||||
#endif
|
||||
File diff suppressed because one or more lines are too long
426
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/ripple.h
Normal file
426
.pio/libdeps/esp01_1m/FastLED/examples/Chromancer/ripple.h
Normal file
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
A dot animation that travels along rails
|
||||
(C) Voidstar Lab LLC 2021
|
||||
*/
|
||||
|
||||
#ifndef RIPPLE_H_
|
||||
#define RIPPLE_H_
|
||||
|
||||
// WARNING: These slow things down enough to affect performance. Don't turn on unless you need them!
|
||||
//#define DEBUG_ADVANCEMENT // Print debug messages about ripples' movement
|
||||
//#define DEBUG_RENDERING // Print debug messages about translating logical to actual position
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "mapping.h"
|
||||
|
||||
enum rippleState {
|
||||
dead,
|
||||
withinNode, // Ripple isn't drawn as it passes through a node to keep the speed consistent
|
||||
travelingUpwards,
|
||||
travelingDownwards
|
||||
};
|
||||
|
||||
enum rippleBehavior {
|
||||
weaksauce = 0,
|
||||
feisty = 1,
|
||||
angry = 2,
|
||||
alwaysTurnsRight = 3,
|
||||
alwaysTurnsLeft = 4
|
||||
};
|
||||
|
||||
float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
class Ripple {
|
||||
public:
|
||||
Ripple(int id) : rippleId(id) {
|
||||
Serial.print("Instanced ripple #");
|
||||
Serial.println(rippleId);
|
||||
}
|
||||
|
||||
rippleState state = dead;
|
||||
unsigned long color;
|
||||
|
||||
/*
|
||||
If within a node: 0 is node, 1 is direction
|
||||
If traveling, 0 is segment, 1 is LED position from bottom
|
||||
*/
|
||||
int position[2];
|
||||
|
||||
// Place the Ripple in a node
|
||||
void start(byte n, byte d, unsigned long c, float s, unsigned long l, byte b) {
|
||||
color = c;
|
||||
speed = s;
|
||||
lifespan = l;
|
||||
behavior = b;
|
||||
|
||||
birthday = millis();
|
||||
pressure = 0;
|
||||
state = withinNode;
|
||||
|
||||
position[0] = n;
|
||||
position[1] = d;
|
||||
|
||||
justStarted = true;
|
||||
|
||||
Serial.print("Ripple ");
|
||||
Serial.print(rippleId);
|
||||
Serial.print(" starting at node ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" direction ");
|
||||
Serial.println(position[1]);
|
||||
}
|
||||
|
||||
void advance(byte ledColors[40][14][3]) {
|
||||
unsigned long age = millis() - birthday;
|
||||
|
||||
if (state == dead)
|
||||
return;
|
||||
|
||||
pressure += fmap(float(age), 0.0, float(lifespan), speed, 0.0); // Ripple slows down as it ages
|
||||
// TODO: Motion of ripple is severely affected by loop speed. Make it time invariant
|
||||
|
||||
if (pressure < 1 && (state == travelingUpwards || state == travelingDownwards)) {
|
||||
// Ripple is visible but hasn't moved - render it to avoid flickering
|
||||
renderLed(ledColors, age);
|
||||
}
|
||||
|
||||
while (pressure >= 1) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print("Ripple ");
|
||||
Serial.print(rippleId);
|
||||
Serial.println(" advancing:");
|
||||
#endif
|
||||
|
||||
switch (state) {
|
||||
case withinNode: {
|
||||
if (justStarted) {
|
||||
justStarted = false;
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Picking direction out of node ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" with agr. ");
|
||||
Serial.println(behavior);
|
||||
#endif
|
||||
|
||||
int newDirection = -1;
|
||||
|
||||
int sharpLeft = (position[1] + 1) % 6;
|
||||
int wideLeft = (position[1] + 2) % 6;
|
||||
int forward = (position[1] + 3) % 6;
|
||||
int wideRight = (position[1] + 4) % 6;
|
||||
int sharpRight = (position[1] + 5) % 6;
|
||||
|
||||
if (behavior <= 2) { // Semi-random aggressive turn mode
|
||||
// The more aggressive a ripple, the tighter turns it wants to make.
|
||||
// If there aren't any segments it can turn to, we need to adjust its behavior.
|
||||
byte anger = behavior;
|
||||
|
||||
while (newDirection < 0) {
|
||||
if (anger == 0) {
|
||||
int forwardConnection = nodeConnections[position[0]][forward];
|
||||
|
||||
if (forwardConnection < 0) {
|
||||
// We can't go straight ahead - we need to take a more aggressive angle
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can't go straight - picking more agr. path");
|
||||
#endif
|
||||
anger++;
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Going forward");
|
||||
#endif
|
||||
newDirection = forward;
|
||||
}
|
||||
}
|
||||
|
||||
if (anger == 1) {
|
||||
int leftConnection = nodeConnections[position[0]][wideLeft];
|
||||
int rightConnection = nodeConnections[position[0]][wideRight];
|
||||
|
||||
if (leftConnection >= 0 && rightConnection >= 0) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Turning left or right at random");
|
||||
#endif
|
||||
newDirection = random(2) ? wideLeft : wideRight;
|
||||
}
|
||||
else if (leftConnection >= 0) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can only turn left");
|
||||
#endif
|
||||
newDirection = wideLeft;
|
||||
}
|
||||
else if (rightConnection >= 0) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can only turn right");
|
||||
#endif
|
||||
newDirection = wideRight;
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can't make wide turn - picking more agr. path");
|
||||
#endif
|
||||
anger++; // Can't take shallow turn - must become more aggressive
|
||||
}
|
||||
}
|
||||
|
||||
if (anger == 2) {
|
||||
int leftConnection = nodeConnections[position[0]][sharpLeft];
|
||||
int rightConnection = nodeConnections[position[0]][sharpRight];
|
||||
|
||||
if (leftConnection >= 0 && rightConnection >= 0) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Turning left or right at random");
|
||||
#endif
|
||||
newDirection = random(2) ? sharpLeft : sharpRight;
|
||||
}
|
||||
else if (leftConnection >= 0) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can only turn left");
|
||||
#endif
|
||||
newDirection = sharpLeft;
|
||||
}
|
||||
else if (rightConnection >= 0) {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can only turn right");
|
||||
#endif
|
||||
newDirection = sharpRight;
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Can't make tight turn - picking less agr. path");
|
||||
#endif
|
||||
anger--; // Can't take tight turn - must become less aggressive
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this can't handle some circumstances,
|
||||
// like a node with segments in nothing but the 0 and 3 positions.
|
||||
// Good thing we don't have any of those!
|
||||
}
|
||||
}
|
||||
else if (behavior == alwaysTurnsRight) {
|
||||
for (int i = 1; i < 6; i++) {
|
||||
int possibleDirection = (position[1] + i) % 6;
|
||||
|
||||
if (nodeConnections[position[0]][possibleDirection] >= 0) {
|
||||
newDirection = possibleDirection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Turning as rightward as possible");
|
||||
#endif
|
||||
}
|
||||
else if (behavior == alwaysTurnsLeft) {
|
||||
for (int i = 5; i >= 1; i--) {
|
||||
int possibleDirection = (position[1] + i) % 6;
|
||||
|
||||
if (nodeConnections[position[0]][possibleDirection] >= 0) {
|
||||
newDirection = possibleDirection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Turning as leftward as possible");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Leaving node ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" in direction ");
|
||||
Serial.println(newDirection);
|
||||
#endif
|
||||
|
||||
position[1] = newDirection;
|
||||
}
|
||||
|
||||
position[0] = nodeConnections[position[0]][position[1]]; // Look up which segment we're on
|
||||
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" and entering segment ");
|
||||
Serial.println(position[0]);
|
||||
#endif
|
||||
|
||||
if (position[1] == 5 || position[1] == 0 || position[1] == 1) { // Top half of the node
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" (starting at bottom)");
|
||||
#endif
|
||||
state = travelingUpwards;
|
||||
position[1] = 0; // Starting at bottom of segment
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" (starting at top)");
|
||||
#endif
|
||||
state = travelingDownwards;
|
||||
position[1] = 13; // Starting at top of 14-LED-long strip
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case travelingUpwards: {
|
||||
position[1]++;
|
||||
|
||||
if (position[1] >= 14) {
|
||||
// We've reached the top!
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Reached top of seg. ");
|
||||
Serial.println(position[0]);
|
||||
#endif
|
||||
// Enter the new node.
|
||||
int segment = position[0];
|
||||
position[0] = segmentConnections[position[0]][0];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// Figure out from which direction the ripple is entering the node.
|
||||
// Allows us to exit in an appropriately aggressive direction.
|
||||
int incomingConnection = nodeConnections[position[0]][i];
|
||||
if (incomingConnection == segment)
|
||||
position[1] = i;
|
||||
}
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Entering node ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" from direction ");
|
||||
Serial.println(position[1]);
|
||||
#endif
|
||||
state = withinNode;
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Moved up to seg. ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" LED ");
|
||||
Serial.println(position[1]);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case travelingDownwards: {
|
||||
position[1]--;
|
||||
if (position[1] < 0) {
|
||||
// We've reached the bottom!
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Reached bottom of seg. ");
|
||||
Serial.println(position[0]);
|
||||
#endif
|
||||
// Enter the new node.
|
||||
int segment = position[0];
|
||||
position[0] = segmentConnections[position[0]][1];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// Figure out from which direction the ripple is entering the node.
|
||||
// Allows us to exit in an appropriately aggressive direction.
|
||||
int incomingConnection = nodeConnections[position[0]][i];
|
||||
if (incomingConnection == segment)
|
||||
position[1] = i;
|
||||
}
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Entering node ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" from direction ");
|
||||
Serial.println(position[1]);
|
||||
#endif
|
||||
state = withinNode;
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Moved down to seg. ");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(" LED ");
|
||||
Serial.println(position[1]);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pressure -= 1;
|
||||
|
||||
if (state == travelingUpwards || state == travelingDownwards) {
|
||||
// Ripple is visible - render it
|
||||
renderLed(ledColors, age);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.print(" Age is now ");
|
||||
Serial.print(age);
|
||||
Serial.print('/');
|
||||
Serial.println(lifespan);
|
||||
#endif
|
||||
|
||||
if (lifespan && age >= lifespan) {
|
||||
// We dead
|
||||
#ifdef DEBUG_ADVANCEMENT
|
||||
Serial.println(" Lifespan is up! Ripple is dead.");
|
||||
#endif
|
||||
state = dead;
|
||||
position[0] = position[1] = pressure = age = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float speed; // Each loop, ripples move this many LED's.
|
||||
unsigned long lifespan; // The ripple stops after this many milliseconds
|
||||
|
||||
/*
|
||||
0: Always goes straight ahead if possible
|
||||
1: Can take 60-degree turns
|
||||
2: Can take 120-degree turns
|
||||
*/
|
||||
byte behavior;
|
||||
|
||||
bool justStarted = false;
|
||||
|
||||
float pressure; // When Pressure reaches 1, ripple will move
|
||||
unsigned long birthday; // Used to track age of ripple
|
||||
|
||||
static byte rippleCount; // Used to give them unique ID's
|
||||
byte rippleId; // Used to identify this ripple in debug output
|
||||
|
||||
void renderLed(byte ledColors[40][14][3], unsigned long age) {
|
||||
int strip = ledAssignments[position[0]][0];
|
||||
int led = ledAssignments[position[0]][2] + position[1];
|
||||
FL_UNUSED(strip);
|
||||
FL_UNUSED(led);
|
||||
|
||||
int red = ledColors[position[0]][position[1]][0];
|
||||
int green = ledColors[position[0]][position[1]][1];
|
||||
int blue = ledColors[position[0]][position[1]][2];
|
||||
|
||||
ledColors[position[0]][position[1]][0] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 8) & 0xFF, 0.0)) + red)));
|
||||
ledColors[position[0]][position[1]][1] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 16) & 0xFF, 0.0)) + green)));
|
||||
ledColors[position[0]][position[1]][2] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), color & 0xFF, 0.0)) + blue)));
|
||||
|
||||
#ifdef DEBUG_RENDERING
|
||||
Serial.print("Rendering ripple position (");
|
||||
Serial.print(position[0]);
|
||||
Serial.print(',');
|
||||
Serial.print(position[1]);
|
||||
Serial.print(") at Strip ");
|
||||
Serial.print(strip);
|
||||
Serial.print(", LED ");
|
||||
Serial.print(led);
|
||||
Serial.print(", color 0x");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (ledColors[position[0]][position[1]][i] <= 0x0F)
|
||||
Serial.print('0');
|
||||
Serial.print(ledColors[position[0]][position[1]][i], HEX);
|
||||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user