├── examples ├── fireflies_example │ ├── middlerpath.h │ ├── fireflies_example.ino │ ├── ziggypath.h │ └── pongpath.h └── fire_example │ └── fire_example.ino ├── particlegfx.h ├── particle.h ├── particle.cpp ├── README.md ├── particlegfx.cpp ├── particlefx.h └── particlefx.cpp /examples/fireflies_example/middlerpath.h: -------------------------------------------------------------------------------- 1 | int16_t middlerloopstart = 6; 2 | 3 | const int16_t middlerpath[][2] = { 4 | {32,-6},{32,-5},{32,-4},{32,-3},{32,-2},{32,-1}, 5 | 6 | // begin of loop 7 | 8 | // draw line from (32,0) to (21,21) 9 | 10 | // deltax: -11, deltay = 21 11 | 12 | {32,0},{32,1},{31,2},{31,3}, 13 | {30,4},{30,5},{29,6},{29,7}, 14 | {28,8},{28,9},{27,10},{27,11}, 15 | {26,12},{26,13},{25,14},{25,15}, 16 | {24,16},{24,17},{23,18},{23,19}, 17 | {22,20},{21,21}, 18 | 19 | // wait at (21,21) for 5 cycles 20 | 21 | {21,21},{21,21},{21,21},{21,21}, 22 | {21,21}, 23 | 24 | // draw line from (21,21) to (32,43) 25 | 26 | // deltax: 11, deltay = 22 27 | 28 | {21,21},{21,22},{22,23},{22,24}, 29 | {23,25},{23,26},{24,27},{24,28}, 30 | {25,29},{25,30},{26,31},{26,32}, 31 | {27,33},{27,34},{28,35},{28,36}, 32 | {29,37},{29,38},{30,39},{30,40}, 33 | {31,41},{31,42},{32,43}, 34 | 35 | // draw line from (32,43) to (43,21) 36 | 37 | // deltax: 11, deltay = -22 38 | 39 | {32,43},{32,42},{33,41},{33,40}, 40 | {34,39},{34,38},{35,37},{35,36}, 41 | {36,35},{36,34},{37,33},{37,32}, 42 | {38,31},{38,30},{39,29},{39,28}, 43 | {40,27},{40,26},{41,25},{41,24}, 44 | {42,23},{42,22},{43,21}, 45 | 46 | // wait at (43,21) for 10 cycles 47 | 48 | {43,21},{43,21},{43,21},{43,21}, 49 | {43,21},{43,21},{43,21},{43,21}, 50 | {43,21},{43,21}, 51 | 52 | // draw line from (43,21) to (32,0) 53 | 54 | // deltax: -11, deltay = -21 55 | 56 | {43,21},{43,20},{42,19},{42,18}, 57 | {41,17},{41,16},{40,15},{40,14}, 58 | {39,13},{39,12},{38,11},{38,10}, 59 | {37,9},{37,8},{36,7},{36,6}, 60 | {35,5},{35,4},{34,3},{34,2}, 61 | {33,1},{32,0}, 62 | }; 63 | 64 | // count: 105 65 | -------------------------------------------------------------------------------- /examples/fireflies_example/fireflies_example.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "middlerpath.h" 10 | #include "pongpath.h" 11 | #include "ziggypath.h" 12 | 13 | particle *fireflies[3]; 14 | 15 | // Matrix pin 16 | #define MPIN 6 17 | 18 | Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(8, 8, MPIN, 19 | NEO_MATRIX_TOP + NEO_MATRIX_RIGHT + 20 | //NEO_MATRIX_BOTTOM + NEO_MATRIX_LEFT + 21 | NEO_MATRIX_COLUMNS + NEO_MATRIX_PROGRESSIVE, 22 | NEO_GRB + NEO_KHZ800); 23 | 24 | #define FIREFLYFRAMERATE 100 25 | particlegfx ffmatrix = particlegfx(); 26 | 27 | void setup() { 28 | // Each firefly has its own path 29 | 30 | // middlerpath keeps the firefly in the middle 31 | fireflies[0] = new particle((int16_t *) middlerpath, sizeof(middlerpath), middlerloopstart, 0, 0xff, 0, false); 32 | 33 | // pongpath moves the firefly left to right and back again 34 | fireflies[1] = new particle((int16_t *) pongpath, sizeof(pongpath), pongloopstart, 0, 0xff, 0, false); 35 | 36 | // ziggypath moves the firefly in a zig-zag fashion 37 | fireflies[2] = new particle((int16_t *) ziggypath, sizeof(ziggypath), ziggyloopstart, 0, 0xff, 0, false); 38 | 39 | for(int i = 0; i < 3; i++) 40 | { 41 | fireflies[i]->resetPoint(); 42 | fireflies[i]->setEnabled(true); 43 | } 44 | } 45 | 46 | void loop() { 47 | static unsigned long timer; 48 | if(millis() - timer >= FIREFLYFRAMERATE) 49 | { 50 | timer = millis(); 51 | matrix.fillScreen(0); 52 | for(int i = 0; i < 3; i++) 53 | { 54 | ffmatrix.drawParticle(matrix, fireflies[i], true); 55 | fireflies[i]->nextPoint(); 56 | } 57 | matrix.show(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /particlegfx.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARTICLEGFX_H 2 | #define _PARTICLEGFX_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "particle.h" 8 | 9 | //#define PARTICLE_MATRIX 64 10 | //define DPP 8 // dots per pixel 11 | 12 | 13 | class particlegfx : public GFXcanvas16 { 14 | public: 15 | particlegfx(uint8_t gridx = 8, uint8_t gridy = 8, uint8_t dpp = 8); 16 | void drawPixelRGB(Adafruit_NeoMatrix &_matrix, int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b, bool gammacorrect = false); 17 | void drawPixel(int16_t x, int16_t y, uint16_t color); 18 | void resizeBitmap(Adafruit_NeoMatrix &_matrix); 19 | void drawParticle(Adafruit_NeoMatrix &_matrix, particle *ff, bool distancedim = true); 20 | private: 21 | uint8_t _gridx, _gridy, _dpp; 22 | public: 23 | const uint8_t gamma8[256] = { 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 26 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 27 | 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 28 | 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 29 | 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 30 | 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 31 | 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 32 | 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 33 | 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 34 | 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 35 | 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, 36 | 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, 37 | 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, 38 | 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, 39 | 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 40 | }; 41 | 42 | }; 43 | 44 | #endif -------------------------------------------------------------------------------- /particle.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARTICLE_H 2 | #define _PARTICLE_H 3 | 4 | #include 5 | 6 | #define SPARKLEMAX 10 7 | #define SPARKLECYCLE 400 8 | 9 | class particle { 10 | public: 11 | particle(){_path = 0; }; 12 | particle(int16_t *path, uint16_t pathlength, uint16_t loopstart, uint8_t red = 0, uint8_t green = 0xff, uint8_t blue = 0, bool gamma = false); 13 | uint16_t getPathLength(); 14 | uint16_t nextPoint(); 15 | bool getGamma(){return _gamma;}; 16 | void setGamma(bool gamma){_gamma = gamma;}; 17 | void resetPoint(){ _pointpos = 0;}; 18 | void setColor(uint8_t r, uint8_t g, uint8_t b){ _r = r; _g = g; _b = b; _sr = r; _sg = g; _sb = b;}; 19 | int16_t getPointX(); 20 | int16_t getPointY(); 21 | void setPointX(int16_t x){ _x = x;}; 22 | void setPointY(int16_t y){ _y = y;}; 23 | void setPoint(int16_t x, int16_t y){ _x = x; _y = y;}; 24 | uint32_t getColor(){return (_r << 16) + (_g << 8) + _b;}; 25 | uint8_t getRed(){return _r;}; 26 | uint8_t getGreen(){return _g;}; 27 | uint8_t getBlue(){return _b;}; 28 | uint8_t getSparkleRed(){return _sr;}; 29 | uint8_t getSparkleGreen(){return _sg;}; 30 | uint8_t getSparkleBlue(){return _sb;}; 31 | float getBrightness(){return _brightness;}; 32 | void setBrightness(float brightness){_brightness = _brightness_r = _brightness_g = _brightness_b = brightness;}; 33 | float getRedBrightness(){return _brightness_r;}; 34 | float getGreenBrightness(){return _brightness_g;}; 35 | float getBlueBrightness(){return _brightness_b;}; 36 | void setRedBrightness(float brightness){_brightness_r = brightness;}; 37 | void setGreenBrightness(float brightness){_brightness_g = brightness;}; 38 | void setBlueBrightness(float brightness){_brightness_b = brightness;}; 39 | bool isEnabled(){return _enabled;}; 40 | void setEnabled(bool enabled){_enabled = enabled;}; 41 | void setSparkleColor(uint8_t r, uint8_t g, uint8_t b){ _sr = r; _sg = g; _sb = b;}; 42 | void addSparkle(); 43 | void clearSparkles(); 44 | uint16_t *getSparkle(int s){return _sparkles[s];}; 45 | 46 | 47 | private: 48 | float _brightness, _brightness_r, _brightness_g, _brightness_b; 49 | bool _enabled, _gamma; 50 | uint16_t _pointpos; 51 | uint16_t _loopstart; 52 | int16_t *_path; 53 | uint16_t _pathlength; 54 | int16_t _x, _y; 55 | uint8_t _r, _g, _b; 56 | uint8_t _sr, _sg, _sb; 57 | uint8_t _sparklecount, _sparkleptr; 58 | uint16_t _sparkles[SPARKLEMAX][3]; 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /particle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | particle::particle(int16_t *path, uint16_t pathlength, uint16_t loopstart, uint8_t red, uint8_t green, uint8_t blue, bool gamma) 4 | { 5 | _pointpos = 0; 6 | _loopstart = loopstart; 7 | _path = path; 8 | _pathlength = pathlength / 4; 9 | _sr = _r = red; 10 | _sg =_g = green; 11 | _sb = _b = blue; 12 | //_fx = NULL; 13 | _enabled = false; 14 | _gamma = gamma; 15 | _brightness = _brightness_r = _brightness_g = _brightness_b = 1.; 16 | _x = -999; _y = -999; 17 | 18 | for(int i = 0; i < SPARKLEMAX; i++) 19 | { 20 | for(int j = 0; j < 3; j++) 21 | { 22 | _sparkles[i][j] = 0; 23 | } 24 | } 25 | _sparkleptr = 0; 26 | 27 | } 28 | 29 | int16_t particle::getPointX() 30 | { 31 | if(_x != -999) 32 | { 33 | return _x; 34 | } 35 | else if(_path != NULL) 36 | { 37 | return *(_path + _pointpos*2); 38 | } 39 | return 0; 40 | } 41 | 42 | int16_t particle::getPointY() 43 | { 44 | if(_y != -999) 45 | { 46 | return _y; 47 | } 48 | else if(_path != NULL) 49 | { 50 | return *(_path + _pointpos*2 + 1); 51 | } 52 | return 0; 53 | } 54 | 55 | uint16_t particle::getPathLength() 56 | { 57 | return _pathlength; 58 | } 59 | 60 | uint16_t particle::nextPoint() 61 | { 62 | _pointpos++; 63 | if(_pointpos >= _pathlength) 64 | { 65 | _pointpos = _loopstart; 66 | } 67 | return _pointpos; 68 | } 69 | 70 | #define SPARKLEDELTA 8 71 | 72 | void particle::addSparkle() 73 | { 74 | int rnum = random(8,16); 75 | uint16_t dx, dy; 76 | 77 | switch(rnum) 78 | { 79 | case 0: 80 | dx = 0 - SPARKLEDELTA; dy = SPARKLEDELTA; 81 | break; 82 | case 1: 83 | dx = 0; dy = SPARKLEDELTA; 84 | break; 85 | case 2: 86 | dx = SPARKLEDELTA; dy = SPARKLEDELTA; 87 | break; 88 | case 3: 89 | dx = SPARKLEDELTA; dy = 0; 90 | break; 91 | case 4: 92 | dx = SPARKLEDELTA; dy = 0 - SPARKLEDELTA; 93 | break; 94 | case 5: 95 | dx = 0; dy = 0 - SPARKLEDELTA; 96 | break; 97 | case 6: 98 | dx = 0 - SPARKLEDELTA; dy = 0 - SPARKLEDELTA; 99 | break; 100 | case 7: 101 | dx = 0 - SPARKLEDELTA; dy = 0; 102 | break; 103 | 104 | // in-between sparkles 105 | case 8: 106 | dx = 0 - SPARKLEDELTA/2; dy = SPARKLEDELTA; 107 | break; 108 | case 9: 109 | dx = SPARKLEDELTA/2; dy = SPARKLEDELTA; 110 | break; 111 | case 10: 112 | dx = SPARKLEDELTA/2; dy = 0 - SPARKLEDELTA; 113 | break; 114 | case 11: 115 | dx = 0 - SPARKLEDELTA/2; dy = 0 - SPARKLEDELTA; 116 | break; 117 | case 12: 118 | dx = 0 - SPARKLEDELTA; dy = SPARKLEDELTA/2; 119 | break; 120 | case 13: 121 | dx = SPARKLEDELTA; dy = SPARKLEDELTA/2; 122 | break; 123 | case 14: 124 | dx = SPARKLEDELTA; dy = 0 - SPARKLEDELTA/2; 125 | break; 126 | case 15: 127 | dx = 0 - SPARKLEDELTA; dy = 0 - SPARKLEDELTA/2; 128 | break; 129 | } 130 | _sparkles[_sparkleptr][0] = getPointX() + dx; 131 | _sparkles[_sparkleptr][1] = getPointY() + dy; 132 | _sparkles[_sparkleptr][2] = SPARKLECYCLE; 133 | //Serial.print("set sparkle ");Serial.print(_sparkleptr);Serial.print(": ");Serial.print(_sparkles[_sparkleptr][0]); 134 | // Serial.print(" ");Serial.print(_sparkles[_sparkleptr][1]);Serial.print(" ");Serial.println(_sparkles[_sparkleptr][2]); 135 | _sparkleptr = (_sparkleptr+1)%SPARKLEMAX; 136 | } 137 | 138 | void particle::clearSparkles() 139 | { 140 | for(int i = 0; i < SPARKLEMAX; i++) 141 | { 142 | _sparkles[i][2] = 0; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZContent_ParticleFX 2 | Point of light effects with an Adafruit NeoPixel matrix 3 | 4 | This Arduino library uses an Adafruit NeoPixel matrix to create animated 5 | special effects for points of light. These "particles" can be used singlely 6 | or chained together to create complex animations like fireworks, fairies 7 | (i.e. "Tinker Bell"), fireflies, and fire. 8 | 9 | When effects are chained together, they can run in series or in parallel 10 | using the wait option. 11 | For example, take a simple animation using the Move effect to move a particle 12 | from point A to point B. Add to this The Dim effect in parallel, which 13 | creates the animation of a particle moving from point A to point B while the 14 | brightness decreases so the particle is invisible by the time it reaches 15 | point B. Add to this the Pause effect to delay the start of the dim until 16 | the point is perhaps half way between point A and Point B. The Pause and 17 | Dim effects would run in series while the Move effect would run in parallel 18 | with the Pause and Dim effects. 19 | This is similar to the way a shooting star appears in the sky. 20 | 21 | ## Classes 22 | 23 | These are main classes to the ParticleFX library: 24 | 25 | particle - This is the "point of light" that is controlled by the effects 26 | 27 | particleEffect - The main class where all effects are derived from (see Effects 28 | section below) 29 | 30 | particleEffectList - This class contains the list of effects that are applied 31 | to the particle 32 | 33 | particlegfx - This class draws the particles onto the pixel matrix 34 | 35 | ## Effects 36 | 37 | Here is a list of the effects that are available in the libray: 38 | 39 | effectDim(particle *particle, bool wait, uint16_t brightness, int16_t speed) 40 | 41 | effectPause(particle *particle, bool wait, uint16_t speed) 42 | 43 | effectsetColor(particle *particle, bool wait, uint8_t red, uint8_t green, uint8_t blue) 44 | 45 | effectMove(particle *particle, bool wait, int16_t movetox, int16_t movetoy, uint16_t speed = 1, bool moveback = false) 46 | 47 | effectFloat(particle *particle, bool wait, uint16_t speed = 1, uint16_t amplitude = 4) 48 | 49 | effectMotionBlur(particle *particle, bool wait, particlegfx *particlegfx, Adafruit_NeoMatrix *matrix, uint16_t framecount, float dimvalue = .7) 50 | 51 | effectTwinkle(particle *particle, bool wait, uint16_t speed = 10, float dimvalue = .5 ) 52 | 53 | ## Library Requirements 54 | 55 | In addition to this library, the following GitHub libraries are needed in 56 | order to use this library. 57 | 58 | Adafruit_GFX - https://github.com/adafruit/Adafruit-GFX-Library 59 | 60 | Adafruit_NeoMatrix - https://github.com/adafruit/Adafruit_NeoMatrix 61 | 62 | Adafruit_NeoPixel - https://github.com/adafruit/Adafruit_NeoPixel 63 | 64 | ## Hardware Requirements 65 | 66 | This library was tested using the Adafruit NeoPixel NeoMatrix 8x8 board. 67 | Other matrix boards may work but they have not been tested. 68 | 69 | The M0 and NRF52480 based Feathers from Adafruit have been tested using 70 | this library. Other Feathers may work but have not been tested. 71 | 72 | ## Examples 73 | 74 | Examples are written based on an 8x8 matrix, however it is programmed as a 64x64 matrix 75 | and then reduced to the lower resolution to create a smooth, anti-aliasing 76 | type effect with the animations. 77 | 78 | Examples can be found in the examples folder and a are a great way to see 79 | how to use the library. This project and documentation 80 | is under development and more examples are expected to be added in the future. 81 | 82 | Animation videos and project status can also be found on the author's 83 | twitter account @cogliano. 84 | 85 | -------------------------------------------------------------------------------- /examples/fireflies_example/ziggypath.h: -------------------------------------------------------------------------------- 1 | int16_t ziggyloopstart = 30; 2 | 3 | const int16_t ziggypath[][2] = { 4 | {16,-6},{16,-5},{16,-4},{16,-3},{16,-2},{16,-1}, 5 | {16,0},{16,0},{16,0},{16,0},{16,0},{16,0}, 6 | {16,0},{16,0},{16,0},{16,0},{16,0},{16,0}, 7 | {16,0},{16,0},{16,0},{16,0},{16,0},{16,0}, 8 | {16,0},{16,0},{16,0},{16,0},{16,0},{16,0}, 9 | // begin of loop 10 | 11 | // draw line from (16,0) to (32,56) 12 | 13 | // deltax: 16, deltay = 56 14 | 15 | {16,0},{16,1},{16,2},{16,3}, 16 | {17,4},{17,5},{17,6},{18,7}, 17 | {18,8},{18,9},{18,10},{19,11}, 18 | {19,12},{19,13},{20,14},{20,15}, 19 | {20,16},{20,17},{21,18},{21,19}, 20 | {21,20},{22,21},{22,22},{22,23}, 21 | {22,24},{23,25},{23,26},{23,27}, 22 | {24,28},{24,29},{24,30},{24,31}, 23 | {25,32},{25,33},{25,34},{26,35}, 24 | {26,36},{26,37},{26,38},{27,39}, 25 | {27,40},{27,41},{28,42},{28,43}, 26 | {28,44},{28,45},{29,46},{29,47}, 27 | {29,48},{30,49},{30,50},{30,51}, 28 | {30,52},{31,53},{31,54},{31,55}, 29 | {32,56}, 30 | 31 | // wait at (32,56) for 5 cycles 32 | 33 | {32,56},{32,56},{32,56},{32,56}, 34 | {32,56}, 35 | 36 | // draw line from (32,56) to (56,32) 37 | 38 | // deltax: 24, deltay = -24 39 | 40 | {32,56},{33,55},{34,54},{35,53}, 41 | {36,52},{37,51},{38,50},{39,49}, 42 | {40,48},{41,47},{42,46},{43,45}, 43 | {44,44},{45,43},{46,42},{47,41}, 44 | {48,40},{49,39},{50,38},{51,37}, 45 | {52,36},{53,35},{54,34},{55,33}, 46 | {56,32}, 47 | 48 | // wait at (56,32) for 10 cycles 49 | 50 | {56,32},{56,32},{56,32},{56,32}, 51 | {56,32},{56,32},{56,32},{56,32}, 52 | {56,32},{56,32}, 53 | 54 | // draw line from (56,32) to (8,35) 55 | 56 | // deltax: -48, deltay = 3 57 | 58 | {56,32},{55,32},{54,32},{53,32}, 59 | {52,32},{51,32},{50,32},{49,32}, 60 | {48,32},{47,32},{46,32},{45,32}, 61 | {44,32},{43,32},{42,32},{41,32}, 62 | {40,33},{39,33},{38,33},{37,33}, 63 | {36,33},{35,33},{34,33},{33,33}, 64 | {32,33},{31,33},{30,33},{29,33}, 65 | {28,33},{27,33},{26,33},{25,33}, 66 | {24,34},{23,34},{22,34},{21,34}, 67 | {20,34},{19,34},{18,34},{17,34}, 68 | {16,34},{15,34},{14,34},{13,34}, 69 | {12,34},{11,34},{10,34},{9,34}, 70 | {8,35}, 71 | 72 | // draw line from (8,35) to (16,56) 73 | 74 | // deltax: 8, deltay = 21 75 | 76 | {8,35},{8,36},{8,37},{9,38}, 77 | {9,39},{9,40},{10,41},{10,42}, 78 | {11,43},{11,44},{11,45},{12,46}, 79 | {12,47},{12,48},{13,49},{13,50}, 80 | {14,51},{14,52},{14,53},{15,54}, 81 | {15,55},{16,56}, 82 | 83 | // wait at (16,56) for 10 cycles 84 | 85 | {16,56},{16,56},{16,56},{16,56}, 86 | {16,56},{16,56},{16,56},{16,56}, 87 | {16,56},{16,56}, 88 | 89 | // draw line from (16,56) to (56,16) 90 | 91 | // deltax: 40, deltay = -40 92 | 93 | {16,56},{17,55},{18,54},{19,53}, 94 | {20,52},{21,51},{22,50},{23,49}, 95 | {24,48},{25,47},{26,46},{27,45}, 96 | {28,44},{29,43},{30,42},{31,41}, 97 | {32,40},{33,39},{34,38},{35,37}, 98 | {36,36},{37,35},{38,34},{39,33}, 99 | {40,32},{41,31},{42,30},{43,29}, 100 | {44,28},{45,27},{46,26},{47,25}, 101 | {48,24},{49,23},{50,22},{51,21}, 102 | {52,20},{53,19},{54,18},{55,17}, 103 | {56,16}, 104 | 105 | // draw line from (56,16) to (16,0) 106 | 107 | // deltax: -40, deltay = -16 108 | 109 | {56,16},{55,16},{54,16},{53,15}, 110 | {52,15},{51,14},{50,14},{49,14}, 111 | {48,13},{47,13},{46,12},{45,12}, 112 | {44,12},{43,11},{42,11},{41,10}, 113 | {40,10},{39,10},{38,9},{37,9}, 114 | {36,8},{35,8},{34,8},{33,7}, 115 | {32,7},{31,6},{30,6},{29,6}, 116 | {28,5},{27,5},{26,4},{25,4}, 117 | {24,4},{23,3},{22,3},{21,2}, 118 | {20,2},{19,2},{18,1},{17,1}, 119 | {16,0}, 120 | }; 121 | 122 | // count: 260 123 | -------------------------------------------------------------------------------- /examples/fire_example/fire_example.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Matrix pin 11 | #define MPIN 6 12 | 13 | Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(8, 8, MPIN, 14 | NEO_MATRIX_TOP + NEO_MATRIX_RIGHT + 15 | //NEO_MATRIX_BOTTOM + NEO_MATRIX_LEFT + 16 | NEO_MATRIX_COLUMNS + NEO_MATRIX_PROGRESSIVE, 17 | NEO_GRB + NEO_KHZ800); 18 | 19 | #define FIREFRAMERATE 40 20 | #define MAXFIREPARTICLES 8 21 | 22 | // comment out line below to see animation without motion-blur 23 | #define MOTIONBLUR 24 | 25 | #define COLOR_R 0xff 26 | #define COLOR_G 0xd0 27 | #define COLOR_B 0x60 28 | 29 | particle *fire[MAXFIREPARTICLES]; 30 | particleEffectList *fireEffects[MAXFIREPARTICLES]; 31 | effectMove *fireMoveEffect[MAXFIREPARTICLES]; 32 | effectMotionBlur *fireMotionBlurEffect[MAXFIREPARTICLES]; 33 | effectPause *firePauseEffect[MAXFIREPARTICLES]; 34 | effectDim *fireDimEffect[MAXFIREPARTICLES]; 35 | particlegfx firematrix = particlegfx(); 36 | 37 | void setup() { 38 | // initialize fire 39 | for(int i =0; i < MAXFIREPARTICLES; i++) 40 | { 41 | fire[i] = new particle(NULL, 0, 0, COLOR_R, COLOR_G, COLOR_B, true); 42 | fire[i]->setGamma(true); 43 | fire[i]->setPointX(0); 44 | fire[i]->setPointY(78); 45 | fire[i]->setEnabled(false); 46 | fire[i]->setBrightness(1.0); 47 | 48 | fireEffects[i] = new particleEffectList(); 49 | #ifdef MOTIONBLUR 50 | fireEffects[i]->appendEffect(fireMotionBlurEffect[i] = new effectMotionBlur(fire[i], false, &firematrix, &matrix, 6, 1.0)); 51 | #endif 52 | // random wait to start flame 53 | fireEffects[i]->appendEffect(firePauseEffect[i] = new effectPause(fire[i], true, random(0,8))); 54 | // move flame up 55 | fireEffects[i]->appendEffect(fireMoveEffect[i] = new effectMove(fire[i], true, 8, 78, 6)); 56 | // dim flame 57 | fireEffects[i]->appendEffect(fireDimEffect[i] = new effectDim(fire[i], true, 0., 5)); 58 | } 59 | } 60 | 61 | void drawFlame(bool flameon) 62 | { 63 | matrix.fillScreen(0); 64 | 65 | for(int i = 0; i < MAXFIREPARTICLES; i++) 66 | { 67 | fire[i]->setEnabled(true); 68 | if(fireEffects[i]->getNext() != NULL) 69 | { 70 | particleEffectList *listitem = fireEffects[i]->getNext(); 71 | bool done = false; 72 | int listcount = 0; 73 | do 74 | { 75 | //Serial.println(String(i) + ": doEffect " + String(listcount) + ": " + String(listitem->getItem()->effectName())); 76 | if(!listitem->getItem()->doEffect()) 77 | { 78 | listcount++; 79 | listitem = listitem->getNext(); 80 | if(listitem == NULL) 81 | done = true; 82 | } 83 | else 84 | done = true; 85 | } 86 | while(!done); 87 | if(fireDimEffect[i]->isDone()) 88 | { 89 | 90 | // reset fire effects 91 | int startx = 0; 92 | int test = random(0,10); 93 | if(test < 9) 94 | { 95 | // inner flame (top of bell curve) 96 | startx = 24 + random(0,2)*8; 97 | } 98 | else 99 | { 100 | // outer flame (lower bell curve) 101 | startx = 16 + random(0,2)*24; 102 | } 103 | fire[i]->setPointX(startx); 104 | fire[i]->setPointY(64); 105 | fireMoveEffect[i]->setPointX(startx); 106 | fireMoveEffect[i]->setPointY(random(0,24)); 107 | firePauseEffect[i]->setSpeed(random(0,6)); 108 | // 3-5 looks good 109 | fireDimEffect[i]->setSpeed(4); 110 | #ifdef MOTIONBLUR 111 | fireMotionBlurEffect[i]->reset(); 112 | #endif 113 | fire[i]->setBrightness(1.0); 114 | if(!flameon) 115 | { 116 | fire[i]->setPointY(78); 117 | fireMoveEffect[i]->setPointY(78); 118 | } 119 | } 120 | } 121 | firematrix.drawParticle(matrix, fire[i]); 122 | } 123 | matrix.show(); 124 | } 125 | 126 | void loop() { 127 | static unsigned long timer; 128 | if(millis() - timer >= FIREFRAMERATE ) 129 | { 130 | drawFlame(true); 131 | timer = millis(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /examples/fireflies_example/pongpath.h: -------------------------------------------------------------------------------- 1 | int16_t pongloopstart = 18; 2 | 3 | const int16_t pongpath[][2] = { 4 | {0,-6},{0,-5},{0,-4},{0,-3},{0,-2},{0,-1}, 5 | {0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 6 | {0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, 7 | 8 | // begin of loop 9 | 10 | // draw line from (0,0) to (21,60) 11 | 12 | // deltax: 21, deltay = 60 13 | 14 | {0,0},{0,1},{0,2},{1,3}, 15 | {1,4},{1,5},{2,6},{2,7}, 16 | {2,8},{3,9},{3,10},{3,11}, 17 | {4,12},{4,13},{4,14},{5,15}, 18 | {5,16},{5,17},{6,18},{6,19}, 19 | {7,20},{7,21},{7,22},{8,23}, 20 | {8,24},{8,25},{9,26},{9,27}, 21 | {9,28},{10,29},{10,30},{10,31}, 22 | {11,32},{11,33},{11,34},{12,35}, 23 | {12,36},{12,37},{13,38},{13,39}, 24 | {14,40},{14,41},{14,42},{15,43}, 25 | {15,44},{15,45},{16,46},{16,47}, 26 | {16,48},{17,49},{17,50},{17,51}, 27 | {18,52},{18,53},{18,54},{19,55}, 28 | {19,56},{19,57},{20,58},{20,59}, 29 | {21,60}, 30 | 31 | // wait at (21,60) for 10 cycles 32 | 33 | {21,60},{21,60},{21,60},{21,60}, 34 | {21,60},{21,60},{21,60},{21,60}, 35 | {21,60},{21,60}, 36 | 37 | // draw line from (21,60) to (32,0) 38 | 39 | // deltax: 11, deltay = -60 40 | 41 | {21,60},{21,59},{21,58},{21,57}, 42 | {21,56},{21,55},{22,54},{22,53}, 43 | {22,52},{22,51},{22,50},{23,49}, 44 | {23,48},{23,47},{23,46},{23,45}, 45 | {23,44},{24,43},{24,42},{24,41}, 46 | {24,40},{24,39},{25,38},{25,37}, 47 | {25,36},{25,35},{25,34},{25,33}, 48 | {26,32},{26,31},{26,30},{26,29}, 49 | {26,28},{27,27},{27,26},{27,25}, 50 | {27,24},{27,23},{27,22},{28,21}, 51 | {28,20},{28,19},{28,18},{28,17}, 52 | {29,16},{29,15},{29,14},{29,13}, 53 | {29,12},{29,11},{30,10},{30,9}, 54 | {30,8},{30,7},{30,6},{31,5}, 55 | {31,4},{31,3},{31,2},{31,1}, 56 | {32,0}, 57 | 58 | // wait at (32,0) for 5 cycles 59 | 60 | {32,0},{32,0},{32,0},{32,0}, 61 | {32,0}, 62 | 63 | // draw line from (32,0) to (43,60) 64 | 65 | // deltax: 11, deltay = 60 66 | 67 | {32,0},{32,1},{32,2},{32,3}, 68 | {32,4},{32,5},{33,6},{33,7}, 69 | {33,8},{33,9},{33,10},{34,11}, 70 | {34,12},{34,13},{34,14},{34,15}, 71 | {34,16},{35,17},{35,18},{35,19}, 72 | {35,20},{35,21},{36,22},{36,23}, 73 | {36,24},{36,25},{36,26},{36,27}, 74 | {37,28},{37,29},{37,30},{37,31}, 75 | {37,32},{38,33},{38,34},{38,35}, 76 | {38,36},{38,37},{38,38},{39,39}, 77 | {39,40},{39,41},{39,42},{39,43}, 78 | {40,44},{40,45},{40,46},{40,47}, 79 | {40,48},{40,49},{41,50},{41,51}, 80 | {41,52},{41,53},{41,54},{42,55}, 81 | {42,56},{42,57},{42,58},{42,59}, 82 | {43,60}, 83 | 84 | // wait at (43,60) for 10 cycles 85 | 86 | {43,60},{43,60},{43,60},{43,60}, 87 | {43,60},{43,60},{43,60},{43,60}, 88 | {43,60},{43,60}, 89 | 90 | // draw line from (43,60) to (32,0) 91 | 92 | // deltax: -11, deltay = -60 93 | 94 | {43,60},{43,59},{43,58},{43,57}, 95 | {43,56},{43,55},{42,54},{42,53}, 96 | {42,52},{42,51},{42,50},{41,49}, 97 | {41,48},{41,47},{41,46},{41,45}, 98 | {41,44},{40,43},{40,42},{40,41}, 99 | {40,40},{40,39},{39,38},{39,37}, 100 | {39,36},{39,35},{39,34},{39,33}, 101 | {38,32},{38,31},{38,30},{38,29}, 102 | {38,28},{37,27},{37,26},{37,25}, 103 | {37,24},{37,23},{37,22},{36,21}, 104 | {36,20},{36,19},{36,18},{36,17}, 105 | {35,16},{35,15},{35,14},{35,13}, 106 | {35,12},{35,11},{34,10},{34,9}, 107 | {34,8},{34,7},{34,6},{33,5}, 108 | {33,4},{33,3},{33,2},{33,1}, 109 | {32,0}, 110 | 111 | // draw line from (32,0) to (21,60) 112 | 113 | // deltax: -11, deltay = 60 114 | 115 | {32,0},{32,1},{32,2},{32,3}, 116 | {32,4},{32,5},{31,6},{31,7}, 117 | {31,8},{31,9},{31,10},{30,11}, 118 | {30,12},{30,13},{30,14},{30,15}, 119 | {30,16},{29,17},{29,18},{29,19}, 120 | {29,20},{29,21},{28,22},{28,23}, 121 | {28,24},{28,25},{28,26},{28,27}, 122 | {27,28},{27,29},{27,30},{27,31}, 123 | {27,32},{26,33},{26,34},{26,35}, 124 | {26,36},{26,37},{26,38},{25,39}, 125 | {25,40},{25,41},{25,42},{25,43}, 126 | {24,44},{24,45},{24,46},{24,47}, 127 | {24,48},{24,49},{23,50},{23,51}, 128 | {23,52},{23,53},{23,54},{22,55}, 129 | {22,56},{22,57},{22,58},{22,59}, 130 | {21,60}, 131 | 132 | // wait at (21,60) for 15 cycles 133 | 134 | {21,60},{21,60},{21,60},{21,60}, 135 | {21,60},{21,60},{21,60},{21,60}, 136 | {21,60},{21,60},{21,60},{21,60}, 137 | {21,60},{21,60},{21,60}, 138 | 139 | // draw line from (21,60) to (0,0) 140 | 141 | // deltax: -21, deltay = -60 142 | 143 | {21,60},{21,59},{21,58},{20,57}, 144 | {20,56},{20,55},{19,54},{19,53}, 145 | {19,52},{18,51},{18,50},{18,49}, 146 | {17,48},{17,47},{17,46},{16,45}, 147 | {16,44},{16,43},{15,42},{15,41}, 148 | {14,40},{14,39},{14,38},{13,37}, 149 | {13,36},{13,35},{12,34},{12,33}, 150 | {12,32},{11,31},{11,30},{11,29}, 151 | {10,28},{10,27},{10,26},{9,25}, 152 | {9,24},{9,23},{8,22},{8,21}, 153 | {7,20},{7,19},{7,18},{6,17}, 154 | {6,16},{6,15},{5,14},{5,13}, 155 | {5,12},{4,11},{4,10},{4,9}, 156 | {3,8},{3,7},{3,6},{2,5}, 157 | {2,4},{2,3},{1,2},{1,1}, 158 | {0,0}, 159 | }; 160 | 161 | // count: 406 162 | -------------------------------------------------------------------------------- /particlegfx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | particlegfx::particlegfx(uint8_t gridx, uint8_t gridy, uint8_t dpp): GFXcanvas16(gridx*dpp, gridy*dpp) 4 | { 5 | _gridx = gridx; 6 | _gridy = gridy; 7 | _dpp = dpp; 8 | }; 9 | 10 | void particlegfx::drawPixel(int16_t x, int16_t y, uint16_t color) { 11 | 12 | if((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return; 13 | 14 | int16_t t; 15 | switch(rotation) { 16 | case 1: 17 | t = x; 18 | x = WIDTH - 1 - y; 19 | y = t; 20 | break; 21 | case 2: 22 | x = WIDTH - 1 - x; 23 | y = HEIGHT - 1 - y; 24 | break; 25 | case 3: 26 | t = x; 27 | x = y; 28 | y = HEIGHT - 1 - t; 29 | break; 30 | } 31 | uint16_t *buffer = getBuffer(); 32 | buffer[y*_gridy*_dpp + x] = color; 33 | } 34 | 35 | void particlegfx::drawPixelRGB(Adafruit_NeoMatrix &_matrix, int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b, bool gammacorrect) 36 | { 37 | uint8_t newr, newg, newb; 38 | newr = r; 39 | newg = g; 40 | newb = b; 41 | if(gammacorrect) 42 | { 43 | newr = gamma8[newr]; 44 | newg = gamma8[newg]; 45 | newb = gamma8[newb]; 46 | } 47 | drawPixel(x, y, _matrix.Color(newr, newg, newb)); 48 | } 49 | 50 | 51 | void particlegfx::resizeBitmap(Adafruit_NeoMatrix &_matrix) { 52 | uint16_t *buffer = getBuffer(); 53 | uint16_t pixelbuffer[64]; 54 | for(int t = 0; t < 64; t++) 55 | { 56 | pixelbuffer[t] = 0; 57 | } 58 | for(int i = 0; i < _gridy*_dpp; i++) 59 | { 60 | for(int j = 0; j < _gridx*_dpp; j++) 61 | { 62 | int16_t x = (j * 8 / _gridx*_dpp); 63 | int16_t y = (i * 8 / _gridy*_dpp); 64 | uint16_t pixel = buffer[i * _gridx*_dpp + j]; 65 | uint16_t gcolor = (pixelbuffer[y*_dpp + x] & 0xFC) >> 3; 66 | uint16_t pgcolor = (pixel & 0xFC) >>3; 67 | if(pixel != 0 && pgcolor > gcolor) 68 | { 69 | //Serial.println(String(pixelbuffer[y*8 + x]) + ":" + String(pgcolor) + " < " + String(pixel) + ":" + String(gcolor) + "?"); 70 | _matrix.drawPixel(x,y,pixel); 71 | pixelbuffer[y*_dpp + x] = pixel; 72 | } 73 | //Serial.print(String("(") + String(x) + String(",") + String(y) + String(") ") + String(pixel) + String(" ")); 74 | } 75 | 76 | } 77 | } 78 | 79 | void particlegfx::drawParticle(Adafruit_NeoMatrix &_matrix, particle *ff, bool distancedim) 80 | { 81 | int16_t px, py, newx, newy, distbrightness; 82 | uint8_t newr, newg, newb; 83 | int16_t x = ff->getPointX(); 84 | int16_t y = ff->getPointY(); 85 | uint8_t r = ff->getRed(); 86 | uint8_t g = ff->getGreen(); 87 | uint8_t b = ff->getBlue(); 88 | if(ff->isEnabled()) 89 | { 90 | for(int i = -1; i < 2; i++) 91 | for(int j = -1; j < 2; j++) 92 | { 93 | px = (int)(x * 1. / _dpp + .5) + j; 94 | py = (int)(y * 1. / _dpp + .5) + i; 95 | newx = px * _dpp; 96 | newy = py * _dpp; 97 | int16_t dist = (x - newx)*(x - newx) + (y - newy)*(y - newy); 98 | distbrightness = 0; 99 | if(dist < (_dpp*_dpp)) 100 | { 101 | //Serial.println("dist: " + String(dist)); 102 | 103 | if(distancedim) 104 | distbrightness = 100 - dist * 100 / (_dpp*_dpp); 105 | else 106 | distbrightness = 100; 107 | //Serial.println(String(j) + ", " + String(i) + ": " + String(x) + "/" + String(newx) + ", " + String(y) + "/" + String(newy) + " distance; " + String(dist) + ", brightness: " + String(distbrightness)); 108 | /* 109 | if(distancedim) 110 | { 111 | //distbrightness = 100 - dist * 100 / DPP*DPP; 112 | 113 | if(dist < (_dpp*_dpp)) 114 | { 115 | distbrightness = 100; 116 | //Serial.println("brightness: " + String(distbrightness)); 117 | } 118 | else 119 | { 120 | distbrightness = 100 - (dist - _dpp*_dpp/2) * 100 / (_dpp * _dpp / 2); 121 | //distbrightness = 100 - (dist - (_dpp*_dpp/2)) * 100 / (_dpp*_dpp/2); 122 | //Serial.println("brightness: " + String(distbrightness)); 123 | } 124 | 125 | } 126 | else 127 | distbrightness = 100; 128 | */ 129 | } 130 | if(distbrightness > 0) 131 | { 132 | //Serial.println("point " + String(px) + "," + String(py) + ": old RGB " + String(r) + " " + String(g) + " " + String(b) + " brightness: " + String(distbrightness)); 133 | newr = r * distbrightness / 100. * ff->getRedBrightness(); // * _brightness; 134 | newg = g * distbrightness / 100. * ff->getGreenBrightness(); // * _brightness; 135 | newb = b * distbrightness / 100. * ff->getBlueBrightness(); // * _brightness; 136 | if(ff->getGreenBrightness() < 1. && (newr > 0 || newb > 0)) 137 | { 138 | // compensate for green, which is too bright when multiple LEDs enabled 139 | if(newg < .2) 140 | newg = 0; 141 | else 142 | newg = newg * max(0,ff->getGreenBrightness() - .1); 143 | /* 144 | if(ff->getGreenBrightness() < .2) 145 | newg = newg * 0; 146 | else if(ff->getGreenBrightness() < .4) 147 | newg = newg * .3; 148 | else if(ff->getGreenBrightness() < .7) 149 | newg = newg * .6; 150 | else 151 | newg = newg * .8; 152 | */ 153 | } 154 | if(ff->getGamma()) 155 | { 156 | newr = gamma8[newr]; 157 | newg = gamma8[newg]; 158 | newb = gamma8[newb]; 159 | } 160 | _matrix.drawPixel(px, py, _matrix.Color(newr,newg,newb)); 161 | //Serial.println("dpp: " + String(_dpp)); 162 | //Serial.println("point: " + String(newx) + "," + String(newy) + ": new RGB " + String(newr) + "," + String(newg) + "," + String(newb) + " brightness: " + String(ff->getBrightness(),2)); 163 | } 164 | } 165 | // add sparkles 166 | for(int i = 0; i < SPARKLEMAX; i++) 167 | { 168 | uint16_t *sparkle = ff->getSparkle(i); 169 | //Serial.print("sparkle[2]: ");Serial.println(sparkle[2]); 170 | if(sparkle[2] > 0) 171 | { 172 | sparkle[2] = sparkle[2] - 1; 173 | //Serial.print("sparkle brightness: "); Serial.println(ff->getBrightness(),2); 174 | if(ff->getBrightness() > .9) 175 | { 176 | //Serial.print("sparkle ");Serial.print(i);Serial.print(": ");Serial.print(sparkle[0]);Serial.print(" ");Serial.print(sparkle[1]);Serial.print(" ");Serial.println(sparkle[2]); 177 | px = (int)(sparkle[0] * 1. / _dpp + .5); 178 | py = (int)(sparkle[1] * 1. / _dpp + .5); 179 | // btype: 0: increase brightness, 1: decrease brightness 180 | int btype = (int)((float)sparkle[2] / (SPARKLECYCLE / 2)) % 2; 181 | int bmode = sparkle[2] % (SPARKLECYCLE / 2); 182 | if(btype == 1) 183 | { 184 | bmode = (SPARKLECYCLE / 2) - bmode; 185 | } 186 | newr = ff->getSparkleRed() * ff->getRedBrightness() * bmode/(SPARKLECYCLE/2); 187 | newg = ff->getSparkleGreen() * ff->getGreenBrightness() * bmode/(SPARKLECYCLE/2); 188 | newb = ff->getSparkleBlue() * ff->getBlueBrightness() * bmode/(SPARKLECYCLE/2); 189 | if(ff->getGamma()) 190 | { 191 | newr = gamma8[newr]; 192 | newg = gamma8[newg]; 193 | newg = gamma8[newb]; 194 | } 195 | _matrix.drawPixel(px,py,_matrix.Color(newr,newg,newb)); 196 | } 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /particlefx.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARTICLEFX_H 2 | #define _PARTICLEFX_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class particleEffect; // forward declaration 9 | 10 | class particleEffectList { 11 | private: 12 | particleEffect *_item; 13 | particleEffectList *_next; 14 | particleEffectList *_previous; 15 | public: 16 | particleEffectList(particleEffect *ff = NULL){_item = ff; _next = _previous = NULL;}; 17 | particleEffectList* getNext(); 18 | particleEffectList* getPrevious(); 19 | particleEffect* getItem(){return _item;}; 20 | void appendEffect(particleEffect *effect); 21 | void removeEffects(); 22 | particleEffectList* popLast(); 23 | }; 24 | 25 | class particleEffect { 26 | public: 27 | particleEffect(){_wait = false; _done = false; _enabled = true; _debug = 0;}; 28 | virtual bool doEffect() = 0; 29 | virtual char *effectName(){return "generic";}; 30 | void setWait(bool wait){_wait = wait;}; 31 | bool getWait(){return _wait;}; 32 | bool isDone(){return _done;}; 33 | void setDone(bool done){_done = done;}; 34 | bool isEnabled(){return _enabled;}; 35 | void setEnabled(bool e){_enabled = e;}; 36 | void setDebug(int debug){_debug = debug;}; 37 | protected: 38 | particle *_particle; 39 | bool _wait; 40 | bool _done; 41 | bool _enabled; 42 | uint8_t _debug; 43 | }; 44 | 45 | // dim to a specified brightness and speed 46 | class effectDim : public particleEffect { 47 | public: 48 | char *effectName(){return "effectDim";}; 49 | bool doEffect(); 50 | effectDim(particle *particle, bool wait, uint16_t brightness, int16_t speed); 51 | effectDim(particle *particle, bool wait, uint16_t startb, uint16_t endb, int16_t speed); 52 | void setBrightness(float value){_counter = _speed; _end_brightness = value; _start_brightness = _particle->getBrightness(); setDone(false);}; 53 | void setBrightness(float startb, float endb){_counter = _speed; _end_brightness = endb; _start_brightness = startb; setDone(false);Serial.print("debug ");Serial.print(_start_brightness); 54 | Serial.print(" ");Serial.println(_end_brightness);}; 55 | void setSpeed(int16_t speed){_speed = _counter = speed; setDone(false);}; 56 | 57 | private: 58 | float _start_brightness; 59 | float _end_brightness; 60 | int16_t _speed; 61 | int16_t _counter; 62 | }; 63 | 64 | // pause before next effect 65 | class effectPause : public particleEffect { 66 | public: 67 | char *effectName(){return "effectPause";}; 68 | effectPause(particle *particle, bool wait, uint16_t speed){_particle = particle; setWait(wait); _speed = _counter = speed; setDone(false);}; 69 | void setSpeed(int16_t speed){_speed = _counter = speed;setDone(false);}; 70 | bool doEffect(); 71 | 72 | private: 73 | int16_t _speed; 74 | int16_t _counter; 75 | }; 76 | 77 | // set color of light 78 | class effectsetColor : public particleEffect { 79 | public: 80 | char *effectName(){return "effectsetColor";}; 81 | effectsetColor(particle *particle, bool wait, uint8_t red, uint8_t green, uint8_t blue){_particle = particle; setWait(wait); _r = red; _g = green; _b = blue;}; 82 | bool doEffect(){_particle->setColor(_r, _g, _b); return getWait();;}; 83 | 84 | private: 85 | uint8_t _r, _g, _b; 86 | }; 87 | 88 | // move to a new point 89 | class effectMove : public particleEffect { 90 | public: 91 | char *effectName(){return "effectMove";}; 92 | effectMove(particle *particle, bool wait, int16_t movetox, int16_t movetoy, uint16_t speed = 1, bool moveback = false); 93 | bool doEffect(); 94 | void setPoint(int16_t movetox, int16_t movetoy, bool moveback = false){_finalx = movetox; _finaly = movetoy; _startx = _particle->getPointX(); _starty = _particle->getPointY(); setDone(false); _moveback = moveback; _movemode = 0; _count = 0;}; 95 | void setPointX(int16_t movetox, bool moveback = false){_finalx = movetox; _startx = _particle->getPointX(); _starty = _particle->getPointY(); setDone(false); _moveback = moveback; _movemode = 0; _count =0;}; 96 | void setPointY(int16_t movetoy, bool moveback = false){_finaly = movetoy; _startx = _particle->getPointX(); _starty = _particle->getPointY(); setDone(false); _moveback = moveback; _movemode = 0; _count = 0;}; 97 | void setSpeed(uint16_t speed){_speed = speed; _count = 0; setDone(false);}; 98 | void setPause(uint16_t pause){_pause = pause;}; 99 | void setMoveBack(bool moveback){_moveback = moveback; _movemode = 0;}; 100 | 101 | private: 102 | int16_t _startx, _starty; 103 | int16_t _finalx, _finaly; 104 | uint16_t _speed, _count; 105 | uint16_t _pause; 106 | int8_t _movemode; 107 | bool _moveback = false; 108 | }; 109 | 110 | // simulate floating 111 | class effectFloat : public particleEffect { 112 | public: 113 | char *effectName(){return "effectFloat";}; 114 | effectFloat(particle *particle, bool wait, uint16_t speed = 1, uint16_t amplitude = 4); 115 | bool doEffect(); 116 | void setY(int16_t y){_originaly = y;}; 117 | void setSpeed(uint16_t speed){_speed = speed;}; 118 | 119 | private: 120 | int16_t _counter = 0; 121 | int16_t _originaly; 122 | uint16_t _speed = 1; 123 | uint16_t _amplitude = 4; 124 | 125 | // see https://daycounter.com/Calculators/Sine-Generator-Calculator.phtml 126 | 127 | const int16_t _sinedata320[320] = { 128 | 128,130,133,135,138,140,142,145, 129 | 147,150,152,155,157,160,162,165, 130 | 167,169,172,174,176,179,181,183, 131 | 185,188,190,192,194,196,198,200, 132 | 202,204,206,208,210,212,214,216, 133 | 218,219,221,223,224,226,228,229, 134 | 231,232,234,235,236,237,239,240, 135 | 241,242,243,244,245,246,247,248, 136 | 249,250,250,251,251,252,253,253, 137 | 253,254,254,254,255,255,255,255, 138 | 255,255,255,255,255,254,254,254, 139 | 253,253,253,252,251,251,250,250, 140 | 249,248,247,246,245,244,243,242, 141 | 241,240,239,237,236,235,234,232, 142 | 231,229,228,226,224,223,221,219, 143 | 218,216,214,212,210,208,206,204, 144 | 202,200,198,196,194,192,190,188, 145 | 185,183,181,179,176,174,172,169, 146 | 167,165,162,160,157,155,152,150, 147 | 147,145,142,140,138,135,133,130, 148 | 128,125,122,120,117,115,113,110, 149 | 108,105,103,100,98,95,93,90, 150 | 88,86,83,81,79,76,74,72, 151 | 70,67,65,63,61,59,57,55, 152 | 53,51,49,47,45,43,41,39, 153 | 37,36,34,32,31,29,27,26, 154 | 24,23,21,20,19,18,16,15, 155 | 14,13,12,11,10,9,8,7, 156 | 6,5,5,4,4,3,2,2, 157 | 2,1,1,1,0,0,0,0, 158 | 0,0,0,0,0,1,1,1, 159 | 2,2,2,3,4,4,5,5, 160 | 6,7,8,9,10,11,12,13, 161 | 14,15,16,18,19,20,21,23, 162 | 24,26,27,29,31,32,34,36, 163 | 37,39,41,43,45,47,49,51, 164 | 53,55,57,59,61,63,65,67, 165 | 70,72,74,76,79,81,83,86, 166 | 88,90,93,95,98,100,103,105, 167 | 108,110,113,115,117,120,122,125 168 | }; 169 | 170 | }; 171 | 172 | class effectMotionBlur : public particleEffect { 173 | public: 174 | char *effectName(){return "effectMotionBlur";}; 175 | effectMotionBlur(particle *particle, bool wait, particlegfx *particlegfx, Adafruit_NeoMatrix *matrix, uint16_t framecount, float dimvalue = .7); 176 | bool doEffect(); 177 | void reset(); 178 | 179 | private: 180 | Adafruit_NeoMatrix* _matrix; 181 | particlegfx* _particlegfx; 182 | uint16_t _framecount; 183 | uint16_t _frameptr; 184 | int16_t _frame[100][2]; 185 | float _dimvalue; 186 | }; 187 | 188 | class effectTwinkle : public particleEffect { 189 | public: 190 | char *effectName(){return "effectTwinkle";}; 191 | effectTwinkle(particle *particle, bool wait, uint16_t speed = 10, float dimvalue = .5 ); 192 | bool doEffect(); 193 | void setSpeed(uint16_t speed){_speed = min(speed,2); _counter = 0;}; 194 | void setBrightness(float dimvalue){_dimvalue = dimvalue;}; 195 | private: 196 | float _dimvalue; 197 | uint16_t _speed; 198 | uint16_t _counter; 199 | }; 200 | 201 | #endif 202 | -------------------------------------------------------------------------------- /particlefx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | particleEffectList* particleEffectList::getNext() 5 | { 6 | particleEffectList* next; 7 | next = _next; 8 | while(next != NULL && !next->_item->isEnabled()) 9 | { 10 | next = next->_next; 11 | } 12 | return next; 13 | }; 14 | 15 | particleEffectList* particleEffectList::getPrevious() 16 | { 17 | particleEffectList* previous; 18 | previous = _previous; 19 | while(previous != NULL && !previous->_item->isEnabled()) 20 | { 21 | previous = previous->_previous; 22 | } 23 | return _previous; 24 | }; 25 | 26 | void particleEffectList::appendEffect(particleEffect *effect) 27 | { 28 | particleEffectList *list = this; 29 | //Serial.println("in appendEffect()"); 30 | int effectcount = 0; 31 | while(list->_next != NULL) 32 | { 33 | list = list->_next; 34 | effectcount++; 35 | //Serial.print("count:"); Serial.print(effectcount); Serial.print(" / "); Serial.println((uint32_t)list); 36 | } 37 | //Serial.print("appending effect "); Serial.println(effectcount); 38 | particleEffectList *item = new particleEffectList(effect); 39 | list->_next = item; 40 | item->_previous = list; 41 | //Serial.println("done appendEffect()"); 42 | } 43 | 44 | void particleEffectList::removeEffects() 45 | { 46 | particleEffectList* listitem; 47 | //Serial.println("removeEffects()"); 48 | //effect completed, remove effects from list 49 | while(listitem = this->popLast()) 50 | { 51 | if(listitem != NULL) 52 | { 53 | //Serial.println("removing effect"); 54 | if(listitem != this) 55 | free(listitem->getItem()); 56 | free(listitem); 57 | } 58 | } 59 | } 60 | 61 | particleEffectList* particleEffectList::popLast() 62 | { 63 | uint16_t counter = 0; 64 | particleEffectList *list, *last; 65 | list = last = this; 66 | while(list->_next != NULL) 67 | { 68 | last = list; 69 | list = list->_next; 70 | counter++; 71 | } 72 | last->_next = NULL; 73 | if(list == this) 74 | { 75 | return NULL; 76 | } 77 | //Serial.print("popLast(): removing item ");Serial.println(counter); 78 | return list; 79 | } 80 | 81 | // higher speed: slower 82 | effectDim::effectDim(particle *particle, bool wait, uint16_t startb, uint16_t endb, int16_t speed) 83 | { 84 | _particle = particle; 85 | setWait(wait); 86 | _start_brightness = startb; 87 | _end_brightness = endb; 88 | _speed = _counter = speed; 89 | }; 90 | 91 | effectDim::effectDim(particle *particle, bool wait, uint16_t brightness, int16_t speed) 92 | { 93 | _particle = particle; 94 | setWait(wait); 95 | _start_brightness = _particle->getBrightness(); 96 | _end_brightness = brightness; 97 | _speed = _counter = speed; 98 | }; 99 | 100 | bool effectDim::doEffect() 101 | { 102 | if(_debug > 0) 103 | Serial.println("effectDim:doEffect()"); 104 | if(_particle->isEnabled()) 105 | { 106 | if(_counter > 0) 107 | { 108 | _counter--; 109 | if(_debug > 1) 110 | { 111 | Serial.print("counter/speed: ");Serial.print(_counter);Serial.print("/");Serial.println(_speed); 112 | Serial.print("dim value: ");Serial.println(_end_brightness + (_start_brightness - _end_brightness)*((float)_counter / (float)_speed)); 113 | 114 | } 115 | float tb = _end_brightness + (_start_brightness - _end_brightness)*((float)_counter / (float)_speed); 116 | _particle->setBrightness(tb); 117 | if(tb < .2) 118 | _particle->setGreenBrightness(0); 119 | return getWait(); 120 | } 121 | else 122 | { 123 | _counter = 0; 124 | _particle->setBrightness(_end_brightness); 125 | if(!isDone()) 126 | { 127 | if(_debug > 1 || true) 128 | Serial.println("dim effect completed"); 129 | setDone(true); 130 | } 131 | } 132 | } 133 | return false; 134 | } 135 | 136 | bool effectPause::doEffect() 137 | { 138 | if(_debug > 0) 139 | Serial.println("effectPause:doEffect()"); 140 | if(_particle->isEnabled()) 141 | { 142 | if(_debug > 1) 143 | Serial.print("Pause countdown: ");Serial.println(_counter); 144 | if(_counter > 0) 145 | { 146 | _counter--; 147 | setDone(false); 148 | return getWait(); 149 | } 150 | if(_debug > 1) 151 | Serial.println("effectPause completed"); 152 | setDone(true); 153 | return false; 154 | } 155 | return false; 156 | } 157 | 158 | effectMove::effectMove(particle *particle, bool wait, int16_t movetox, int16_t movetoy, uint16_t speed, bool moveback) 159 | { 160 | _particle = particle; 161 | setWait(wait); 162 | _finalx = movetox; 163 | _finaly = movetoy; 164 | _startx = particle->getPointX(); 165 | _starty = particle->getPointY(); 166 | _speed = speed; 167 | _count = 0; 168 | _pause = 0; 169 | _moveback = moveback; 170 | _movemode = 0; 171 | setDone(false); 172 | } 173 | 174 | bool effectMove::doEffect() 175 | { 176 | // higher speed = slower movement 177 | if(_debug > 0) 178 | Serial.println("effectMove:doEffect()"); 179 | if(_particle->isEnabled()) 180 | { 181 | if(_debug > 1) 182 | { 183 | if(_movemode == 0) 184 | Serial.println("moving from " + String(_particle->getPointX()) + ", " + String(_particle->getPointY()) + " to " + String(_finalx) + ", " + String(_finaly)); 185 | else 186 | Serial.println("moving from " + String(_particle->getPointX()) + ", " + String(_particle->getPointY()) + " back to " + String(_startx) + ", " + String(_starty)); 187 | } 188 | 189 | } 190 | if(_particle->isEnabled() && !isDone() && 191 | !(!_moveback && (_particle->getPointX() == _finalx) && (_particle->getPointY() == _finaly)) && 192 | !(_moveback && (_movemode == 1) && (_particle->getPointX() == _startx) && (_particle->getPointY() == _starty)) 193 | ) 194 | { 195 | int16_t diffx = _finalx - _particle->getPointX(); 196 | int16_t diffy = _finaly - _particle->getPointY(); 197 | if(_count >= _speed && _movemode == 0) 198 | { 199 | _particle->setPointX(_finalx); 200 | _particle->setPointY(_finaly); 201 | _movemode = 1; 202 | //setDone(true); 203 | //return false; 204 | } 205 | else 206 | { 207 | if(_movemode == 0) 208 | { 209 | // move to point 210 | _count++; 211 | int16_t newx = _startx + ((float) _count / _speed)*(_finalx - _startx); 212 | int16_t newy = _starty + ((float) _count / _speed)*(_finaly - _starty); 213 | _particle->setPointX(newx); 214 | _particle->setPointY(newy); 215 | if(_debug > 1) 216 | { 217 | Serial.print("move point: ");Serial.print(_particle->getPointX());Serial.print(",");Serial.print(_particle->getPointY());Serial.print(" speed:");Serial.print(_count);Serial.print("/");Serial.println(_speed); 218 | } 219 | } 220 | else 221 | { 222 | if(_pause > 0) 223 | { 224 | // wait here a bit 225 | _pause--; 226 | //Serial.println("waiting during move"); 227 | } 228 | else 229 | { 230 | // move back to start 231 | _count--; 232 | int16_t newx = _startx + ((float) _count / _speed)*(_finalx - _startx); 233 | int16_t newy = _starty + ((float) _count / _speed)*(_finaly - _starty); 234 | _particle->setPointX(newx); 235 | _particle->setPointY(newy); 236 | if(_debug > 1) 237 | { 238 | Serial.print("move back point: ");Serial.print(_particle->getPointX());Serial.print(",");Serial.print(_particle->getPointY());Serial.print(" speed:");Serial.println(_speed); 239 | } 240 | if(_count <= 0) 241 | { 242 | _particle->setPointX(_startx); 243 | _particle->setPointY(_starty); 244 | setDone(true); 245 | } 246 | } 247 | } 248 | } 249 | } 250 | if(isDone() || (!_moveback && (_particle->getPointX() == _finalx) && (_particle->getPointY() == _finaly)) 251 | || (_moveback && (_movemode == 1) && (_particle->getPointX() == _startx) && (_particle->getPointY() == _starty))) 252 | { 253 | // we are done 254 | if(_debug > 1) 255 | Serial.println("Move completed"); 256 | setDone(true); 257 | _movemode = 0; 258 | _moveback = false; 259 | return false; 260 | } 261 | return getWait(); 262 | } 263 | 264 | effectFloat::effectFloat(particle *particle, bool wait, uint16_t speed, uint16_t amplitude) 265 | { 266 | _particle = particle; 267 | setWait(wait); 268 | _originaly = _particle->getPointY(); 269 | _speed = speed; 270 | _amplitude = amplitude; 271 | } 272 | 273 | bool effectFloat::doEffect() 274 | { 275 | if(_debug > 0) 276 | Serial.println("effectFloat:doEffect()"); 277 | if(_particle->isEnabled()) 278 | { 279 | int16_t floatdelta = _sinedata320[_counter] - 128; 280 | _particle->setPointY(_originaly + (int)(floatdelta * _amplitude / 128)); 281 | if(_debug > 1) 282 | { 283 | Serial.print("effectFloat point: ");Serial.print(_particle->getPointX());Serial.print(",");Serial.println(_particle->getPointY()); 284 | } 285 | _counter = (_counter + _speed)%320; 286 | } 287 | return getWait(); 288 | } 289 | 290 | effectMotionBlur::effectMotionBlur(particle *particle, bool wait, particlegfx *particlegfx, Adafruit_NeoMatrix *matrix, uint16_t framecount, float dimvalue) 291 | { 292 | _particle = particle; 293 | setWait(wait); 294 | _particlegfx = particlegfx; 295 | _matrix = matrix; 296 | _framecount = max(min(framecount,100),1); 297 | _frameptr = 0; 298 | _dimvalue = dimvalue; 299 | reset(); 300 | } 301 | 302 | void effectMotionBlur::reset() 303 | { 304 | for(int i = 0; i < _framecount; i++) 305 | { 306 | _frame[i][0] = -999; 307 | _frame[i][1] = -999; 308 | } 309 | setDone(false); 310 | } 311 | 312 | bool effectMotionBlur::doEffect() 313 | { 314 | if(this->isEnabled()) 315 | { 316 | int dpp = 8; // dots per pixel (64 / 8) 317 | if(_debug > 0) 318 | Serial.println("effectDoMotionBlur::doEffect(): brightness = " + String(_particle->getBrightness(),2)); 319 | float brightness = _particle->getBrightness(); 320 | int16_t x = _particle->getPointX(); 321 | int16_t y = _particle->getPointY(); 322 | _particlegfx->drawParticle(*_matrix, _particle, true); 323 | //Serial.print(_frame[_frameptr][0]);Serial.print(",");Serial.println(_frame[_frameptr][1]); 324 | if(!((_frame[_frameptr][0]) && (_frame[_frameptr][1] < 0))) 325 | { 326 | if(_debug > 1) 327 | Serial.println("motion blur from " + String(_frame[_frameptr][0]) + "," + String(_frame[_frameptr][1]) + " to " + String(x) + "," + String(y)); 328 | int16_t lastframe = -1; 329 | int16_t lastx = 1, lasty = -1; 330 | for(int i = 0; i < _framecount; i++) 331 | { 332 | int16_t newx = _frame[(_frameptr + i) % _framecount][0]; 333 | int16_t newy = _frame[(_frameptr + i) % _framecount][1]; 334 | if(newx != lastx || newy != lasty) 335 | { 336 | //Serial.println("drawing frame " + String(i) + " @ " + String(newx) + ", " + String(newy)); 337 | _particle->setPointX(newx); 338 | _particle->setPointY(newy); 339 | // reduce brightness for motion blur from dimvalue 340 | float tbrightness = brightness * _dimvalue + brightness * (1.0 - _dimvalue) * (i + 1) / _framecount; 341 | _particle->setBrightness(tbrightness); 342 | //if(tbrightness < .25) 343 | // _particle->setGreenBrightness(0); // special case for green 344 | //_particle->setBrightness(1.0); 345 | //_particle->setBrightness(brightness * i / _framecount); 346 | _particlegfx->drawParticle(*_matrix, _particle, true); 347 | lastx = newx; 348 | lasty = newy; 349 | } 350 | } 351 | // reset back to original values 352 | _particle->setBrightness(brightness); 353 | _particle->setPointX(x); 354 | _particle->setPointY(y); 355 | } 356 | 357 | // store frame for next time 358 | _frame[_frameptr][0] = x; 359 | _frame[_frameptr][1] = y; 360 | _frameptr = (_frameptr + 1 ) % _framecount; 361 | return getWait(); 362 | } 363 | return false; 364 | } 365 | 366 | //pulsating light 367 | effectTwinkle::effectTwinkle(particle *particle, bool wait, uint16_t speed, float dimvalue) 368 | { 369 | _particle = particle; 370 | setWait(wait); 371 | _speed = max(speed, 4); 372 | _counter = 0; 373 | _dimvalue = dimvalue; 374 | } 375 | 376 | bool effectTwinkle::doEffect() 377 | { 378 | if(_debug > 0) 379 | Serial.println("effectTwinkle::doEffect()"); 380 | // btype: 0: increase brightness, 1: decrease brightness 381 | int btype = (int)((float)_counter / (_speed / 2)) % 2; 382 | int bmode = _counter % (_speed / 2); 383 | if(btype == 1) 384 | { 385 | bmode = (_counter / 2) - bmode; 386 | } 387 | _particle->setBrightness(_dimvalue + bmode/(_speed/2.)*(1.0 - _dimvalue)); 388 | if(_debug > 1) 389 | Serial.println("Twinkle brightness: " + String(_particle->getBrightness(),2)); 390 | 391 | _counter = (_counter+1)%_speed; 392 | 393 | return false; // always on 394 | } 395 | --------------------------------------------------------------------------------