#include <FastLED.h>
#include "shapes.h"
////////////////////////////////////////////////////////////
// Pin and LED definitions
////////////////////////////////////////////////////////////
#define LED_PIN_STRIP_1 16
#define LED_PIN_STRIP_2 17
#define LED_PIN_STRIP_3 18
#define LED_PIN_STRIP_4 19
#define LED_PIN_STRIP_5 21
#define LED_PIN_STRIP_6 22
#define LED_PIN_STRIP_7 23
#define SMALL_RING_LEDS 28
#define BASE_RING_LEDS 42
#define LARGE_RING_LEDS 51
#define LED_GROUPS 7
const uint16_t ledsInGroup = SMALL_RING_LEDS + BASE_RING_LEDS + LARGE_RING_LEDS;
CRGB leds_1[121];
CRGB leds_2[121];
CRGB leds_3[121];
CRGB leds_4[121];
CRGB leds_5[121];
CRGB leds_6[121];
CRGB leds_7[121];
CRGB* ledGroups[LED_GROUPS] = { leds_1, leds_2, leds_3, leds_4, leds_5, leds_6, leds_7 };
////////////////////////////////////////////////////////////
// Global Animation State
////////////////////////////////////////////////////////////
const Animation* currentAnimSet = nullptr;
uint8_t currentAnimIndex = 0;
uint8_t currentFrame = 0;
uint8_t nextFrame = 0;
uint8_t framesInCurrentAnim = 0;
const uint8_t MORPH_STEPS = 30; // how many steps in each frame blend
uint8_t morphStep = 0;
bool morphInProgress = false;
uint16_t morphIntervalMs;
bool reverseAnimation = false;
////////////////////////////////////////////////////////////
// getColorInAnimationFrame(animIndex, frameIndex, group, led)
////////////////////////////////////////////////////////////
CRGB getColorInAnimationFrame(const Animation& anim, uint8_t frameIndex, uint8_t group, uint16_t led)
{
if (frameIndex >= anim.numFrames) return CRGB::Black;
const Shape &shape = anim.frames[frameIndex];
for (uint8_t i = 0; i < shape.numSegments; i++) {
const LedSegment &seg = shape.segments[i];
if (seg.group == group && led >= seg.startIndex && led <= seg.endIndex) {
CRGB rgb;
hsv2rgb_rainbow(seg.color, rgb);
return rgb;
}
}
return CRGB::Black;
}
////////////////////////////////////////////////////////////
// blendAnimationFrames(...)
// Blends from frameFrom to frameTo by "alpha" (0.0-1.0)
////////////////////////////////////////////////////////////
/* void blendAnimationFrames(const Animation& anim, uint8_t frameFrom, uint8_t frameTo, float alpha)
{
for (uint8_t g = 0; g < LED_GROUPS; g++) {
for (uint16_t led = 0; led < ledsInGroup; led++) {
CRGB colorA = getColorInAnimationFrame(anim, frameFrom, g, led);
CRGB colorB = getColorInAnimationFrame(anim, frameTo, g, led);
// Randomly decide which frame's color to use based on alpha
if (random8() < (uint8_t)(alpha * 255)) {
ledGroups[g][led] = colorB; // Use color from frameTo
} else {
ledGroups[g][led] = colorA; // Use color from frameFrom
}
}
}
} */
void blendAnimationFrames(const Animation& anim, uint8_t frameFrom, uint8_t frameTo, float alpha)
{
for (uint8_t g = 0; g < LED_GROUPS; g++) {
for (uint16_t led = 0; led < ledsInGroup; led++) {
CRGB colorA = getColorInAnimationFrame(anim, frameFrom, g, led);
CRGB colorB = getColorInAnimationFrame(anim, frameTo, g, led);
uint8_t blendAmount = (uint8_t)(alpha * 255);
CRGB crossFaded = blend(colorA, colorB, blendAmount);
ledGroups[g][led] = crossFaded;
}
}
}
////////////////////////////////////////////////////////////
// startMorph(anim, fromFrame, toFrame)
// Initializes a new morph process
////////////////////////////////////////////////////////////
void startMorph(const Animation& anim, uint8_t fromFrame, uint8_t toFrame, uint16_t durationMs)
{
morphStep = 0;
morphInProgress = true;
morphIntervalMs = durationMs / MORPH_STEPS;
blendAnimationFrames(anim, fromFrame, toFrame, 0.0f);
FastLED.show();
}
////////////////////////////////////////////////////////////
// updateMorph()
// Call this frequently; does 1 step if it's time
////////////////////////////////////////////////////////////
void updateMorph()
{
static uint32_t lastMorphMs = 0;
uint32_t now = millis();
if (!morphInProgress) return;
if (now - lastMorphMs >= morphIntervalMs) {
lastMorphMs = now;
morphStep++;
if (morphStep > MORPH_STEPS) {
morphInProgress = false;
} else {
float alpha = (float)morphStep / (float)MORPH_STEPS;
const Animation& anim = currentAnimSet[currentAnimIndex];
blendAnimationFrames(anim, currentFrame, nextFrame, alpha);
FastLED.show();
}
}
}
////////////////////////////////////////////////////////////
// pickNextFrame()
// Decide nextFrame based on reverseAnimation or not
////////////////////////////////////////////////////////////
uint8_t pickNextFrame(uint8_t current, uint8_t totalFrames, bool reverseFlag)
{
if (!reverseFlag) {
return (current + 1) % totalFrames;
}
static int8_t direction = 1;
int next = (int)current + direction;
if (next >= totalFrames) {
next = totalFrames - 2;
direction = -1;
} else if (next < 0) {
next = 1;
direction = 1;
}
return (uint8_t)next;
}
////////////////////////////////////////////////////////////
// setAnimationSet(newSet)
// Switch to idle, partly, or active if needed
////////////////////////////////////////////////////////////
void setAnimationSet(const Animation* newSet, uint8_t index = 0)
{
if (newSet == currentAnimSet) {
return;
}
currentAnimSet = newSet;
currentAnimIndex = index;
currentFrame = 0;
nextFrame = 0;
morphInProgress = false;
morphStep = 0;
framesInCurrentAnim = currentAnimSet[currentAnimIndex].numFrames;
blendAnimationFrames(currentAnimSet[currentAnimIndex], 0, 0, 0.0f);
FastLED.show();
}
////////////////////////////////////////////////////////////
// Setup
////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
FastLED.addLeds<WS2812, LED_PIN_STRIP_1, GRB>(leds_1, ledsInGroup);
FastLED.addLeds<WS2812, LED_PIN_STRIP_2, GRB>(leds_2, ledsInGroup);
FastLED.addLeds<WS2812, LED_PIN_STRIP_3, GRB>(leds_3, ledsInGroup);
FastLED.addLeds<WS2812, LED_PIN_STRIP_4, GRB>(leds_4, ledsInGroup);
FastLED.addLeds<WS2812, LED_PIN_STRIP_5, GRB>(leds_5, ledsInGroup);
FastLED.addLeds<WS2812, LED_PIN_STRIP_6, GRB>(leds_6, ledsInGroup);
FastLED.addLeds<WS2812, LED_PIN_STRIP_7, GRB>(leds_7, ledsInGroup);
setAnimationSet(idleAnimations, 0);
}
////////////////////////////////////////////////////////////
// Loop
////////////////////////////////////////////////////////////
void loop() {
static uint32_t lastReadTime = 0;
static int sensor1 = 0, sensor2 = 0;
if (millis() - lastReadTime >= 200) {
sensor1 = analogRead(12);
sensor2 = analogRead(14);
lastReadTime = millis();
}
int diff = abs(sensor1 - sensor2);
int newMode = -1;
if (sensor1 < 50 && sensor2 < 50) {
newMode = 0; // idle
} else if (sensor1 > 50 && sensor2 > 50 && diff < 25) {
newMode = 2; // active
} else {
newMode = 1; // partly active
}
static int currentMode = -1;
if (newMode != currentMode) {
currentMode = newMode;
switch (currentMode) {
case 0:
setAnimationSet(idleAnimations, 0);
break;
case 1:
setAnimationSet(partlyAnimations, 0);
break;
case 2:
setAnimationSet(activeAnimations, 0);
break;
}
}
// morph logic
if (!morphInProgress) {
const Animation& anim = currentAnimSet[currentAnimIndex];
nextFrame = pickNextFrame(currentFrame, anim.numFrames, anim.reverse);
startMorph(anim, currentFrame, nextFrame, 2000);
}
updateMorph();
if (!morphInProgress) {
currentFrame = nextFrame;
}
}