// Fire effect took from FastLED example Fire2012 // https://github.com/FastLED/FastLED/blob/master/examples/Fire2012/Fire2012.ino // Some functions were taken from FastLED library #include "KyloBlade.h" extern uint32_t getRandom(uint32_t min, uint32_t max); uint32_t update_took; KyloBlade::KyloBlade() { buffer = NULL; blade_length = quillion_length = total_length = 0; update_freq_ms = 10; state = KYLO_BLADE_OFF; initialized = false; shift = 2; blade_shade = COLOR(255,0,0); spark_color = COLOR(255, 50, 30); ignition_delay = 0; do_clash = do_lockup = quillon_on = do_retraction = false; heat_blade = heat_quillion = NULL; } bool KyloBlade::begin(uint32_t blade_count, uint32_t quillion_count, LedStripeType type) { if (initialized) return true; blade_length = blade_count; quillion_length = quillion_count; total_length = blade_count + quillion_length; buffer = new COLOR[total_length]; if (!buffer) return false; heat_blade = (uint8_t*) malloc(blade_length); if (!heat_blade) { delete [] buffer; return false; } memset(heat_blade, 0, blade_length); heat_quillion = (uint8_t*) malloc(quillion_length); if (!heat_quillion) { free(heat_blade); delete [] buffer; return false; } memset(heat_quillion, 0, quillion_length); if (!LedStripDriver::begin(total_length, type)) { free(heat_quillion); free(heat_blade); delete [] buffer; return false; } for (uint32_t i = 0; i < KYLO_MAX_SPARKLES; i++) sparkles[i].free = true; for (uint32_t i = 0; i < KYLO_MAX_SPARKLES; i++) blasters[i].free = true; clearBuffer(); updateAll(); state = KYLO_BLADE_OFF; initialized = true; return true; } void KyloBlade::end() { if (!initialized) return; if (buffer) delete [] buffer; if (heat_blade) free(heat_blade); if (heat_quillion) free(heat_quillion); buffer = NULL; heat_quillion = heat_blade = NULL; initialized = false; } void KyloBlade::setColors(const COLOR& blade, const COLOR& sparks) { blade_color = blade; spark_color = sparks; } void KyloBlade::setSpeed(uint8_t speed) { shift = speed; } void KyloBlade::poll() { static uint32_t update_time = 0; if (!initialized) return; if (millis() - update_time < update_freq_ms) return; update_time = millis(); if (state == KYLO_BLADE_ON) { shimmerFillBlade(shift); if (ignition_delay && millis() - ignition_time > ignition_delay) { ignition_delay = 0; quillon_on = true; // Quillions are full lit ? // memset(heat_quillion, 255, quillion_length); } if (quillon_on) shimmerFillQuillion(shift); drawClash(); drawBlasters(); drawLockup(); doRetraction(); } // Update updateAll(); update_took = millis() - update_time; } void KyloBlade::ignition(uint32_t quillon_delay) { if (!initialized) return; // Blade should be OFF if (state != KYLO_BLADE_OFF) return; clearBuffer(); quillon_on = false; ignition_time = millis(); ignition_delay = quillon_delay; memset(heat_blade, 0, blade_length); memset(heat_quillion, 0, quillion_length); state = KYLO_BLADE_ON; } void KyloBlade::retraction(uint32_t duration) { if (!initialized) return; // Blade should be ON if (state != KYLO_BLADE_ON) return; do_retraction = false; retraction_ticks = duration / update_freq_ms; if (!retraction_ticks) retraction_ticks = 1; retraction_leds_per_tick_blade = (float) blade_length / retraction_ticks; retraction_quillion_starts = retraction_ticks / 2; if (!retraction_quillion_starts) retraction_quillion_starts = 1; retraction_leds_per_tick_quillion = (float) quillion_length / retraction_quillion_starts; if (!retraction_quillion_starts) retraction_quillion_starts = 1; retraction_blade_count = 0; retraction_quillion_count = 0; do_retraction = true; } void KyloBlade::blaster(COLOR color, uint32_t duration, uint32_t length) { if (!initialized) return; SPARKLE* blaster = NULL; for (uint32_t i = 0; i < KYLO_MAX_SPARKLES; i++) { if (blasters[i].free) { blaster = &blasters[i]; break; } } if (!blaster) return; uint32_t ticks = duration / update_freq_ms; if (!ticks) ticks = 2; blaster->free = false; blaster->pos = quillion_length + getRandom(length + 4, blade_length - (length + 4)); blaster->lenght = length; blaster->color = color; blaster->brightness = 1.0f; blaster->decay = blaster->brightness / ticks; } void KyloBlade::clash(COLOR color, uint32_t duration) { if (!initialized) return; clash_color = color; clash_ticks = duration / update_freq_ms; if (!clash_ticks) clash_ticks = 2; clash_brightness = 1.0f; clash_decay = clash_brightness / clash_ticks; do_clash = true; } void KyloBlade::lockup(COLOR color, uint32_t freq) { if (!initialized) return; if (!freq) return; lockup_color = color; lockup_freq = freq; lockup_ticks = (1000 / freq / 2) / update_freq_ms; do_lockup = true; } void KyloBlade::stopLockup() { if (!initialized) return; do_lockup = false; } void KyloBlade::off() { clearBuffer(); updateAll(); state = KYLO_BLADE_OFF; } void KyloBlade::clearBuffer() { for (uint32_t i = 0; i < total_length; i++) { buffer[i] = 0; } } void KyloBlade::updateAll() { float brightnesss = (float) getRandom(95,100) / 100; setBrightness(brightnesss); for (uint32_t i = 0; i < total_length; i++) set(i+1, buffer[i]); update(0); } uint8_t diff(uint8_t a, uint8_t b) { int32_t s = a - b; if (s < 0) s = 0; return s; } uint8_t scale8_video( uint8_t i, int scale) { uint8_t j = (((int)i * (int)scale) >> 8) + ((i&&scale)?1:0); return j; } COLOR HeatColor( uint8_t temperature) { COLOR heatcolor; // Scale 'heat' down from 0-255 to 0-191, // which can then be easily divided into three // equal 'thirds' of 64 units each. uint8_t t192 = scale8_video( temperature, 191); // calculate a value that ramps up from // zero to 255 in each 'third' of the scale. uint8_t heatramp = t192 & 0x3F; // 0..63 heatramp <<= 2; // scale up to 0..252 // now figure out which third of the spectrum we're in: if( t192 & 0x80) { // we're in the hottest third heatcolor = RGB(255,210, heatramp); } else if( t192 & 0x40 ) { // we're in the middle third heatcolor = RGB(255, heatramp, 0); } else { // we're in the coolest third heatcolor = RGB(heatramp, 0, 0); } return heatcolor; } uint8_t add( uint8_t i, uint8_t j) { unsigned int t = i + j; if( t > 255) t = 255; return t; } void KyloBlade::shimmerFillBlade(uint32_t shift) { uint32_t speed = shift; while (speed) { for (uint32_t i = 0; i < blade_length; i++) { heat_blade[i] = diff(heat_blade[i], getRandom(0, ((35 * 10) / blade_length) + 2)); } for (uint32_t i = blade_length - 1; i >= 2; i--) { heat_blade[i] = (heat_blade[i - 1] + heat_blade[i - 2] + heat_blade[i - 2] ) / 3; } if( (uint8_t) getRandom(0, 255) < 120 ) { int y = (uint8_t) getRandom(0, 10 - 1); heat_blade[y] = add( heat_blade[y], (uint8_t) getRandom(160,255 - 1) ); } speed--; } uint32_t offs = quillion_length; for (uint32_t i = 0; i < blade_length; i++) { uint8_t val = heat_blade[i] * 100 / 255; float brightness = (float) val / 100; buffer[i+offs] = blade_color * brightness; } // Put sparkles doSpark(); drawSparkles(shift); } void KyloBlade::shimmerFillQuillion(uint32_t shift) { uint32_t speed = shift; while (speed) { for (uint32_t i = 0; i < quillion_length; i++) { heat_quillion[i] = diff(heat_quillion[i], getRandom(0, ((35 * 10) / quillion_length) + 2)); } for (uint32_t i = quillion_length - 1; i >= 2; i--) { heat_quillion[i] = (heat_quillion[i - 1] + heat_quillion[i - 2] + heat_quillion[i - 2] ) / 3; } if( (uint8_t) getRandom(0, 255) < 120 ) { int y = (uint8_t) getRandom(0, 10 - 1); heat_quillion[y] = add( heat_quillion[y], (uint8_t) getRandom(160,255 - 1) ); } speed--; } for (uint32_t i = 0; i < quillion_length; i++) { uint8_t val = heat_quillion[i] * 100 / 255; float brightness = (float) val / 100; buffer[i] = blade_color * brightness; } } void KyloBlade::doSpark() { SPARKLE* spark = NULL; if (getRandom(0, 100) < 95) return; for (uint32_t i = 0; i < KYLO_MAX_SPARKLES; i++) { if (sparkles[i].free) { spark = &sparkles[i]; break; } } if (!spark) return; spark->free = false; spark->pos = quillion_length + 2; spark->lenght = 2; spark->color = spark_color; spark->brightness = (float) getRandom(90,100) / 100; spark->decay = 0.001f * getRandom(5, 20); } void KyloBlade::drawClash() { if (do_clash) { uint32_t offs = quillion_length; for (uint32_t i = 0; i < blade_length; i++) { buffer[i+offs].blend(clash_color, clash_brightness); } clash_brightness -= clash_decay; clash_ticks--; if (!clash_ticks || clash_brightness <= 0) do_clash = false; } } void KyloBlade::drawBlasters() { for (uint32_t i = 0; i < KYLO_MAX_SPARKLES; i++) { if (!blasters[i].free) { for (uint32_t b = 0; b < blasters[i].lenght; b++) { buffer[blasters[i].pos+b].blend(blasters[i].color, blasters[i].brightness); } buffer[blasters[i].pos - 1].blend(blasters[i].color, blasters[i].brightness / 2); buffer[blasters[i].pos - 2].blend(blasters[i].color, blasters[i].brightness / 3); buffer[blasters[i].pos + blasters[i].lenght + 2].blend(blasters[i].color, blasters[i].brightness / 2); buffer[blasters[i].pos + blasters[i].lenght + 3].blend(blasters[i].color, blasters[i].brightness / 3); blasters[i].brightness -= blasters[i].decay; if (blasters[i].brightness <= 0) blasters[i].free = true; } } } void KyloBlade::drawSparkles(uint8_t shift) { for (uint32_t i = 0; i < KYLO_MAX_SPARKLES; i++) { if (!sparkles[i].free) { sparkles[i].pos += shift; if (sparkles[i].pos > blade_length) { sparkles[i].free = true; continue; } float decay = sparkles[i].decay * shift; sparkles[i].brightness -= decay; if (sparkles[i].brightness <= 0) { sparkles[i].free = true; continue; } buffer[sparkles[i].pos].blend(sparkles[i].color, sparkles[i].brightness); buffer[sparkles[i].pos+1].blend(sparkles[i].color, sparkles[i].brightness); if (sparkles[i].pos > 2) { buffer[sparkles[i].pos - 1].blend(sparkles[i].color, sparkles[i].brightness / 2); buffer[sparkles[i].pos - 2].blend(sparkles[i].color, sparkles[i].brightness / 3); } if (sparkles[i].pos < blade_length - 3) { buffer[sparkles[i].pos + 2].blend(sparkles[i].color, sparkles[i].brightness / 2); buffer[sparkles[i].pos + 3].blend(sparkles[i].color, sparkles[i].brightness / 3); } } } } void KyloBlade::drawLockup() { if (!do_lockup) return; lockup_count++; if (lockup_count > lockup_ticks) { lockup_count = 0; lockup_toggle = !lockup_toggle; if (lockup_toggle) { uint32_t offs = quillion_length; for (uint32_t i = 0; i < blade_length; i++) { uint32_t val = getRandom(80, 100); float brightness = (float) val / 100; buffer[i+offs].blend(lockup_color, brightness); } } } } void KyloBlade::doRetraction() { if (!do_retraction) return; retraction_ticks--; if (retraction_ticks == 0) { do_retraction = false; off(); return; } retraction_blade_count += retraction_leds_per_tick_blade; uint32_t count; if (retraction_blade_count > blade_length) { retraction_ticks = 0; count = blade_length; } else { count = (uint32_t) retraction_blade_count; } for (uint32_t i = 0; i < count; i++) { buffer[quillion_length + blade_length - 1 - i] = COLOR(0,0,0); } if (retraction_ticks <= retraction_quillion_starts) { retraction_quillion_count += retraction_leds_per_tick_quillion; if (retraction_quillion_count > quillion_length) { count = quillion_length; } else { count = (uint32_t) retraction_quillion_count; } for (uint32_t i = 0; i < count; i++) { buffer[quillion_length - 1 - i] = COLOR(0,0,0); } } }