diff --git a/QuadLightsV3.ino b/QuadLightsV3.ino index f702599..4c1294c 100644 --- a/QuadLightsV3.ino +++ b/QuadLightsV3.ino @@ -78,8 +78,8 @@ enum Mode { MODE_COUNT, }; -// Mode activeMode = MODE_HEAD; -Mode activeMode = MODE_KITT; +Mode activeMode = MODE_HEAD; +// Mode activeMode = MODE_KITT; uint32_t colorHSV(float hue, float sat = 1.0, float val = 1.0) { return Adafruit_NeoPixel::ColorHSV( @@ -98,6 +98,7 @@ const uint32_t COLOR_RED = ledsTail.Color(255, 0, 0, 0); const uint32_t COLOR_AMBER = colorHSV(10); // easier value fade const uint32_t COLOR_POLICE_BLUE = ledsTail.Color(0, 0, 255, 0); const uint32_t COLOR_POLICE_RED = ledsTail.Color(255, 0, 0, 0); +const uint32_t COLOR_GREEN = ledsTail.Color(0, 255, 0, 0); unsigned long animationFrame = 0; unsigned long stableAnimationFrame = 0; @@ -186,7 +187,7 @@ void animateLeftTailSide(uint32_t color, bool withMargin = true) { } void animateDash() { - ledsDash.clear(); + ledsDash.fill(COLOR_GREEN); } void animateReverse() { @@ -349,8 +350,97 @@ void animateRainbow() { ledsDash.setPixelColor(0, colorHSV(stableAnimationFrame * RAINBOW_SPEED + 90)); } +constexpr float KITT_SPEED = 0.5; +constexpr int KITT_TRAIL = 4; +constexpr float KITT_TAIL_SPEED = 3.0f; + +int kittScanPos(int vPos) { + if (vPos < LEDS_RING_LENGTH) { + // first ring — reversed so it flows naturally into the edge + return LEDS_EDGE_LENGTH + (LEDS_RING_LENGTH - 1 - vPos); + } else if (vPos < LEDS_RING_LENGTH + LEDS_EDGE_LENGTH) { + // edge forward + return vPos - LEDS_RING_LENGTH; + } else if (vPos < 2 * LEDS_RING_LENGTH + LEDS_EDGE_LENGTH) { + // second ring + return LEDS_EDGE_LENGTH + (vPos - LEDS_RING_LENGTH - LEDS_EDGE_LENGTH); + } else { + // edge backward + return LEDS_EDGE_LENGTH - 1 - (vPos - 2 * LEDS_RING_LENGTH - LEDS_EDGE_LENGTH); + } +} + void animateKitt() { - // not implemented + const long kittFrame = lround(stableAnimationFrame * KITT_SPEED); + const int cycleLength = 2 * LEDS_EDGE_LENGTH + 2 * LEDS_RING_LENGTH; + const int vPos = kittFrame % cycleLength; + + ledsHeadLeft.fill(COLOR_OFF); + ledsHeadRight.fill(COLOR_OFF); + + // trail behind + for (int t = 0; t < KITT_TRAIL; t++) { + const int pastVPos = ((vPos - t) % cycleLength + cycleLength) % cycleLength; + const int scanPos = kittScanPos(pastVPos); + + const uint32_t color = colorHSV(0, 1, 1.0f - (float)t / KITT_TRAIL); + ledsHeadLeft.setPixelColor(scanPos, color); + ledsHeadRight.setPixelColor(scanPos, color); + } + + // lead ahead — shorter and dimmer than trail + constexpr int KITT_LEAD = 2; + for (int t = 1; t <= KITT_LEAD; t++) { + const int futureVPos = ((vPos + t) % cycleLength + cycleLength) % cycleLength; + const int scanPos = kittScanPos(futureVPos); + + const uint32_t color = colorHSV(0, 1, 0.3f * (1.0f - (float)t / KITT_LEAD)); + ledsHeadLeft.setPixelColor(scanPos, color); + ledsHeadRight.setPixelColor(scanPos, color); + } + + const int scanPos = kittScanPos(vPos); + + + // normalized cycle (0–1), sped up + float tailCycle = (float)vPos * KITT_TAIL_SPEED / cycleLength; + tailCycle -= (int)tailCycle; // wrap to 0–1 + + // triangle wave: 0 → 1 → 0 (KITT bounce) + float tailT = tailCycle * 2.0f; + if (tailT > 1.0f) tailT = 2.0f - tailT; + + // optional smoothing (gives that analog feel) + tailT = tailT * tailT * (3.0f - 2.0f * tailT); // smoothstep + + // map to LED index + const int tailPos = lround(tailT * (LEDS_TAIL_LENGTH - 1)); + + // draw tail with falloff + for (int i = 0; i < LEDS_TAIL_LENGTH; i++) { + const int dist = abs(i - tailPos); + float brightness = 0.0f; + + if (dist < KITT_TRAIL) { + brightness = 1.0f - (float)dist / KITT_TRAIL; + } + + ledsTail.setPixelColor(i, colorHSV(0, 1, brightness)); + } + + ledsDash.fill(COLOR_OFF); + + float speed = 3.0; + + float dashCycle = (float)vPos * speed / cycleLength; + dashCycle -= (int)dashCycle; // keep it in 0–1 + + // triangle wave: 0→1→0 over the cycle + float t = dashCycle * 2.0; + if (t > 1.0) t = 2.0 - t; + + ledsDash.setPixelColor(0, colorHSV(0, 1, t)); + ledsDash.setPixelColor(1, colorHSV(0, 1, 1.0 - t)); } void animate(long now) {