diff --git a/examples/AuroraDemo/PatternFireworks.hpp b/examples/AuroraDemo/PatternFireworks.hpp new file mode 100644 index 0000000..8d9c13b --- /dev/null +++ b/examples/AuroraDemo/PatternFireworks.hpp @@ -0,0 +1,235 @@ +#ifndef FireWork_H +#define FireWork_H + +/**************************************************************** + * Fireworks Class + ****************************************************************/ +// Ripped from: https://github.com/lmirel/MorphingClockRemix + +const int FIREWORKS = 4; // Number of fireworks +const int FIREWORK_PARTICLES = 32; // Number of particles per firework + +const float GRAVITY = 0.03f; +const float baselineSpeed = -1.2f; +const float maxSpeed = -2.0f; + +class Firework +{ + public: + float x[FIREWORK_PARTICLES]; + float y[FIREWORK_PARTICLES]; + char lx[FIREWORK_PARTICLES], ly[FIREWORK_PARTICLES]; + float xSpeed[FIREWORK_PARTICLES]; + float ySpeed[FIREWORK_PARTICLES]; + + char red; + char blue; + char green; + char alpha; + + int framesUntilLaunch; + + char particleSize; + bool hasExploded; + + Firework(); // Constructor declaration + void initialise(); + void move(); + void explode(); +}; + +// Constructor implementation +Firework::Firework() +{ + initialise(); + for (int loop = 0; loop < FIREWORK_PARTICLES; loop++) + { + lx[loop] = 0; + ly[loop] = VPANEL_H + 1; // Push the particle location down off the bottom of the screen + } +} + +void Firework::initialise() +{ + // Pick an initial x location and random x/y speeds + float xLoc = (rand() % VPANEL_W); + float xSpeedVal = baselineSpeed + (rand() % (int)maxSpeed); + float ySpeedVal = baselineSpeed + (rand() % (int)maxSpeed); + + // Set initial x/y location and speeds + for (int loop = 0; loop < FIREWORK_PARTICLES; loop++) + { + x[loop] = xLoc; + y[loop] = VPANEL_H + 1; // Push the particle location down off the bottom of the screen + xSpeed[loop] = xSpeedVal; + ySpeed[loop] = ySpeedVal; + //don't reset these otherwise particles won't be removed + //lx[loop] = 0; + //ly[loop] = LAYER_HEIGHT + 1; // Push the particle location down off the bottom of the screen + } + + // Assign a random colour and full alpha (i.e. particle is completely opaque) + red = (rand() % 255);/// (float)RAND_MAX); + green = (rand() % 255); /// (float)RAND_MAX); + blue = (rand() % 255); /// (float)RAND_MAX); + alpha = 50;//max particle frames + + // Firework will launch after a random amount of frames between 0 and 400 + framesUntilLaunch = ((int)rand() % (VPANEL_H)); + + // Size of the particle (as thrown to glPointSize) - range is 1.0f to 4.0f + particleSize = 1.0f + ((float)rand() / (float)RAND_MAX) * 3.0f; + + // Flag to keep trackof whether the firework has exploded or not + hasExploded = false; + + //cout << "Initialised a firework." << endl; +} + +void Firework::move() +{ + for (int loop = 0; loop < FIREWORK_PARTICLES; loop++) + { + // Once the firework is ready to launch start moving the particles + if (framesUntilLaunch <= 0) + { + //draw black on last known position + //tl->drawPixel (x[loop], y[loop], cc_blk); + lx[loop] = x[loop]; + ly[loop] = y[loop]; + // + x[loop] += xSpeed[loop]; + + y[loop] += ySpeed[loop]; + + ySpeed[loop] += GRAVITY; + } + } + framesUntilLaunch--; + + // Once a fireworks speed turns positive (i.e. at top of arc) - blow it up! + if (ySpeed[0] > 0.0f) + { + for (int loop2 = 0; loop2 < FIREWORK_PARTICLES; loop2++) + { + // Set a random x and y speed beteen -4 and + 4 + xSpeed[loop2] = -2 + (rand() / (float)RAND_MAX) * 4; + ySpeed[loop2] = -2 + (rand() / (float)RAND_MAX) * 4; + } + + //cout << "Boom!" << endl; + hasExploded = true; + } +} + +void Firework::explode() +{ + for (int loop = 0; loop < FIREWORK_PARTICLES; loop++) + { + // Dampen the horizontal speed by 1% per frame + xSpeed[loop] *= 0.99; + + //draw black on last known position (NO LONGER USED tl->dim used instead) + //tl->drawPixel (x[loop], y[loop], cc_blk); + lx[loop] = x[loop]; + ly[loop] = y[loop]; + // + // Move the particle + x[loop] += xSpeed[loop]; + y[loop] += ySpeed[loop]; + + // Apply gravity to the particle's speed + ySpeed[loop] += GRAVITY; + } + + // Fade out the particles (alpha is stored per firework, not per particle) + if (alpha > 0) + { + alpha -= 1; + } + else // Once the alpha hits zero reset the firework + { + initialise(); + } +} + +/*********************** */ + + + +class PatternFirework : public Drawable { + + public: + PatternFirework() { + name = (char *)"PatternFirework"; + + } + + void start(); + unsigned int drawFrame(); + void stop(); + + private: + // Create our array of fireworks + Firework fw[FIREWORKS]; + +}; + + +void PatternFirework::start() { } + +void PatternFirework::stop() { } + +unsigned int PatternFirework::drawFrame() +{ + + effects.DimAll(250); + + CRGB cc_frw; + //display.fillScreen (0); + // Draw fireworks + //cout << "Firework count is: " << Firework::fireworkCount << endl; + for (int loop = 0; loop < FIREWORKS; loop++) + { + for (int particleLoop = 0; particleLoop < FIREWORK_PARTICLES; particleLoop++) + { + + // Set colour to yellow on way up, then whatever colour firework should be when exploded + if (fw[loop].hasExploded == false) + { + //glColor4f(1.0f, 1.0f, 0.0f, 1.0f); + cc_frw = CRGB (255, 255, 0); + } + else + { + //glColor4f(fw[loop].red, fw[loop].green, fw[loop].blue, fw[loop].alpha); + //glVertex2f(fw[loop].x[particleLoop], fw[loop].y[particleLoop]); + cc_frw = CRGB (fw[loop].red, fw[loop].green, fw[loop].blue); + } + + // Draw the point + //glVertex2f(fw[loop].x[particleLoop], fw[loop].y[particleLoop]); + effects.setPixel (fw[loop].x[particleLoop], fw[loop].y[particleLoop], cc_frw); + // effects.setPixel (fw[loop].lx[particleLoop], fw[loop].ly[particleLoop], 0); + } + // Move the firework appropriately depending on its explosion state + if (fw[loop].hasExploded == false) + { + fw[loop].move(); + } + else + { + fw[loop].explode(); + } + // + //delay (10); + } // end loop + + effects.ShowFrame(); + + return 20; + +} // end drawframe + + +#endif \ No newline at end of file diff --git a/examples/AuroraDemo/PatternTheMatrix.hpp b/examples/AuroraDemo/PatternGreenScroll.hpp similarity index 98% rename from examples/AuroraDemo/PatternTheMatrix.hpp rename to examples/AuroraDemo/PatternGreenScroll.hpp index ee6aa06..32d38bc 100644 --- a/examples/AuroraDemo/PatternTheMatrix.hpp +++ b/examples/AuroraDemo/PatternGreenScroll.hpp @@ -1,5 +1,6 @@ // Codetastic 2024 // ChatGPT was used to create this. +// It sucks. #ifndef PatternTheMatrix_H #define PatternTheMatrix_H diff --git a/examples/AuroraDemo/PatternRain.hpp b/examples/AuroraDemo/PatternRain.hpp new file mode 100644 index 0000000..19c34ef --- /dev/null +++ b/examples/AuroraDemo/PatternRain.hpp @@ -0,0 +1,93 @@ +#ifndef PatternRain_H +#define PatternRain_H + +// Codetastic 2024 + +struct rainDrop { + uint8_t x; + uint8_t y; + CRGB colour; +}; + +#define MAX_RAINDROPS 128 + +class PatternRain : public Drawable { + + public: + PatternRain() + { + name = (char *)"PatternRain"; + } + + void start() { + + buffer = (uint16_t *) malloc(((VPANEL_W*VPANEL_H)+1)*sizeof(uint16_t)); // always alloc an extra amount for XY + } + + void stop() { + + free(buffer); + + } + + + unsigned int drawFrame() + { + rain(32, 255, 224, 240, CRGB::Green); + + effects.ShowFrame(); + + return 45; // 1000/45 frames per secton + + } + + + + private: + + struct rainDrop rainDrops[MAX_RAINDROPS]; + int rainDropPos = 0; + + uint16_t* buffer = NULL; // buffer of number + + void rain(byte backgroundDepth, byte maxBrightness, byte spawnFreq, byte tailLength, CRGB rainColor) + { + CRGBPalette16 rain_p( CRGB::Black, rainColor ); + + // Dim routine + for (int16_t i = 0; i < VPANEL_W; i++) { + for (int16_t j = 0; j < VPANEL_H; j++) { + uint16_t xy = XY16(i, j); + effects.leds[xy].nscale8(tailLength); + } + } + + // Genrate a new raindrop if the randomness says we should + if (random(255) < spawnFreq) { + + // Find a spare raindrop slot + for (int d = 0; d < MAX_RAINDROPS; d++) { + + // This raindrop is done with, it has... dropped + if (rainDrops[d].y >= VPANEL_H ) // not currently in use + { + rainDrops[d].colour = ColorFromPalette(rain_p, random(backgroundDepth, maxBrightness)); + rainDrops[d].x = random(VPANEL_W-1); + rainDrops[d].y = 0; + + break; // exit until next time. + } + } + } // end random spawn + + // Iterate through all the rainDrops, draw the drop pixel on the layer + for (int d = 0; d < MAX_RAINDROPS; d++) { + effects.setPixel(rainDrops[d].x, rainDrops[d].y++, rainDrops[d].colour); + } + + } + + +}; + +#endif \ No newline at end of file diff --git a/examples/AuroraDemo/PatternSphereSpin.hpp b/examples/AuroraDemo/PatternSphereSpin.hpp deleted file mode 100644 index 7da2537..0000000 --- a/examples/AuroraDemo/PatternSphereSpin.hpp +++ /dev/null @@ -1,103 +0,0 @@ -// Codetastic 2024 -// ChatGPT was used to create this. - -#ifndef PatternSphereSpin_H -#define PatternSphereSpin_H - -#include -#include - -struct Point3D { - float x, y, z; -}; - -struct Point2D { - int x, y; -}; - -class PatternSphereSpin : public Drawable { - - private: - - std::vector points; - - float angleX = 0.0f; - float angleY = 0.0f; - float distance = 3.0f; - - - Point2D project(Point3D point, float distance) { - Point2D projected; - projected.x = static_cast((point.x / (distance - point.z)) * VPANEL_W / 2 + VPANEL_W / 2); - projected.y = static_cast((point.y / (distance - point.z)) * VPANEL_H / 2 + VPANEL_H / 2); - return projected; - } - - std::vector generateSpherePoints(int numPoints) { - std::vector points; - for (int i = 0; i < numPoints; ++i) { - float theta = 2 * PI * (i / static_cast(numPoints)); - for (int j = 0; j < numPoints / 2; ++j) { - float phi = PI * (j / static_cast(numPoints / 2)); - points.push_back({ cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi) }); - } - } - return points; - } - - void rotatePoints(std::vector& points, float angleX, float angleY) { - for (auto& point : points) { - // Rotate around the X axis - float y = point.y * cos(angleX) - point.z * sin(angleX); - float z = point.y * sin(angleX) + point.z * cos(angleX); - point.y = y; - point.z = z; - - // Rotate around the Y axis - float x = point.x * cos(angleY) + point.z * sin(angleY); - z = -point.x * sin(angleY) + point.z * cos(angleY); - point.x = x; - point.z = z; - } - } - - - public: - PatternSphereSpin() { - name = (char *)"Sphere Spin"; - } - - void start() - { - points = generateSpherePoints(30); - angleX = 0.0f; - angleY = 0.0f; - distance = 3.0f; - }; - - unsigned int drawFrame() { - - effects.ClearFrame(); - - // Rotate points - rotatePoints(points, angleX, angleY); - - // Project and draw lines between points - for (size_t i = 0; i < points.size(); ++i) { - Point2D p1 = project(points[i], distance); - for (size_t j = i + 1; j < points.size(); ++j) { - Point2D p2 = project(points[j], distance); - effects.drawLine(p1.x, p1.y, p2.x, p2.y, CRGB(255,255,255)); - } - } - - // Update angles for rotation - angleX += 0.05f; - angleY += 0.05f; - - effects.ShowFrame(); - return 0; - } -}; - -#endif diff --git a/examples/AuroraDemo/PatternStarfield.hpp b/examples/AuroraDemo/PatternStarfield.hpp index 2c637fb..52253fb 100644 --- a/examples/AuroraDemo/PatternStarfield.hpp +++ b/examples/AuroraDemo/PatternStarfield.hpp @@ -1,77 +1,133 @@ -// Codetastic 2024 -// ChatGPT was used to create this. - #ifndef PatternStarfield_H #define PatternStarfield_H -#include +struct Star +{ + Star() { + x = y = z = 0; + colour = CRGB::White; + } -#define STAR_COUNT 128 - -// Star structure -struct Star { - float x, y, z; - CRGB colour; + float x; + float y; + float z; + CRGB colour; }; - +// Based on https://github.com/sinoia/oled-starfield/blob/master/src/starfield.cpp class PatternStarfield : public Drawable { private: - std::vector stars; - float speed = 0.5f; + const int starCount = 100; // number of stars in the star field + const int maxDepth = 32; // maximum distance away for a star - public: - PatternStarfield() { - name = (char *)"Starfield"; - } + // the star field - starCount stars represented as x, y and z co-ordinates + // https://www.cplusplus.com/doc/tutorial/dynamic/ + Star * stars; + //CRGBPalette16 currentPalette; - void start() - { + unsigned int drawFrame() { // aka drawStars - stars.resize(STAR_COUNT); + // Dim routine + + for (int16_t i = 0; i < VPANEL_W; i++) { + for (int16_t j = 0; j < VPANEL_H; j++) { - for (int i = 0; i < STAR_COUNT; ++i) { - stars[i] = { static_cast(rand() % VPANEL_W - VPANEL_W / 2), - static_cast(rand() % VPANEL_H - VPANEL_H / 2), - static_cast(rand() % VPANEL_W), - effects.ColorFromCurrentPalette(rand()*255) - }; - } // random positions - }; + uint16_t xy = XY16(i, j); + effects.leds[xy].nscale8(250); + } + } - unsigned int drawFrame() { + int origin_x = VPANEL_W / 2; + int origin_y = VPANEL_H / 2; - effects.DimAll(250); + // Iterate through the stars reducing the z co-ordinate in order to move the + // star closer. + for (int i = 0; i < starCount; ++i) { + stars[i].z -= 0.1; + // if the star has moved past the screen (z < 0) reposition it far away + // with random x and y positions. + if (stars[i].z <= 0) + { + stars[i].x = getRandom(-25, 25); + stars[i].y = getRandom(-25, 25); + stars[i].z = maxDepth; + } - // Update star positions - for (auto& star : stars) { - star.z -= speed; - if (star.z <= 0) { - star.z = VPANEL_W; - star.x = static_cast(rand() % VPANEL_W - VPANEL_W / 2); - star.y = static_cast(rand() % VPANEL_H - VPANEL_H / 2); + // Convert the 3D coordinates to 2D using perspective projection. + float k = VPANEL_W / stars[i].z; + int x = static_cast(stars[i].x * k + origin_x); + int y = static_cast(stars[i].y * k + origin_y); + + // Draw the star (if it is visible in the screen). + // Distant stars are smaller than closer stars. + if ((0 <= x and x < VPANEL_H) + and (0 <= y and y < VPANEL_H)) { + + CRGB tmp = stars[i].colour; + //CRGB tmp = CRGB::White; + byte scale = 255 -(stars[i].z*7); + tmp.nscale8(scale); + + effects.setPixel(x,y, CRGB(tmp.r,tmp.g,tmp.b)); + } + else + { + stars[i].z = -1; // set to -1 so it gets re-popualted } } - // draw position - for (const auto& star : stars) { - float k = 128.0f / star.z; - int x = static_cast(star.x * k + VPANEL_W / 2); - int y = static_cast(star.y * k + VPANEL_H / 2); + effects.ShowFrame(); - if (x >= 0 && x < VPANEL_W && y >= 0 && y < VPANEL_H) { - - // TODO: Get brighter as we get closer to edges? - effects.setPixel(x, y, star.colour); - } - } - - effects.ShowFrame(); - return 0; + return 5; + } + + int getRandom(int lower, int upper) { + /* Generate and return a random number between lower and upper bound */ + return lower + static_cast(rand() % (upper - lower + 1)); } + +/* + CRGB ColorFromCurrentPalette(uint8_t index = 0, uint8_t brightness = 255, TBlendType blendType = LINEARBLEND) { + return ColorFromPalette(currentPalette, index, brightness, blendType); + } +*/ + + public: + PatternStarfield() + { + name = (char *)"PatternStarfield"; + } + + void start() { + + //currentPalette = RainbowColors_p; + //currentPalette = CloudColors_p; + + // Allocate memory + stars = new Star[starCount]; + + // Initialise the star field with random stars + for (int i = 0; i < starCount; i++) { + stars[i].x = getRandom(-25, 25); + stars[i].y = getRandom(-25, 25); + stars[i].z = getRandom(0, maxDepth); + //stars[i].colour = ColorFromCurrentPalette(random(0, 128)); + stars[i].colour = effects.ColorFromCurrentPalette(random(0, 128)); + } + } // end start + + void stop() { + + delete[] stars; + delete stars; + + } + + + }; -#endif +#endif \ No newline at end of file diff --git a/examples/AuroraDemo/PatternTunnel.hpp b/examples/AuroraDemo/PatternTunnel.hpp deleted file mode 100644 index 89859ae..0000000 --- a/examples/AuroraDemo/PatternTunnel.hpp +++ /dev/null @@ -1,66 +0,0 @@ -// Codetastic 2024 -// ChatGPT was used to create this. - -#ifndef PatternTunnel_H -#define PatternTunnel_H - -class PatternTunnel : public Drawable { - - private: - - uint8_t circlePositions[5] = {0, 6, 12, 18, 24}; // Initial positions of circles - - - public: - PatternTunnel() { - name = (char *)"Tunnel"; - } - - - - // Function to draw a circle on the matrix - void drawCircle(int centerX, int centerY, int radius, CRGB color) { - for (int y = -radius; y <= radius; y++) { - for (int x = -radius; x <= radius; x++) { - if (x*x + y*y <= radius*radius) { - int drawX = centerX + x; - int drawY = centerY + y; - if (drawX >= 0 && drawX < VPANEL_W && drawY >= 0 && drawY < VPANEL_H) { - effects.leds[XY(drawX, drawY)] = color; - } - } - } - } - } - - void start() - { - - }; - - unsigned int drawFrame() { - - effects.DimAll(250); - - - // Draw circles - for (int i = 0; i < 5; i++) { - int radius = circlePositions[i] % 32; - CRGB color = CHSV(map(radius, 0, 31, 0, 255), 255, 255); - drawCircle(VPANEL_W / 2, VPANEL_H / 2, radius, color); - - // Move circles forward - circlePositions[i]++; - - // Reset the position if the circle is out of bounds - if (circlePositions[i] >= 32) { - circlePositions[i] = 0; - } - } - - effects.ShowFrame(); - return 0; - } -}; - -#endif diff --git a/examples/AuroraDemo/Patterns.hpp b/examples/AuroraDemo/Patterns.hpp index a93d998..cdd8bd5 100644 --- a/examples/AuroraDemo/Patterns.hpp +++ b/examples/AuroraDemo/Patterns.hpp @@ -43,17 +43,17 @@ #include "PatternSpiral.hpp" #include "PatternSpiro.hpp" #include "PatternWave.hpp" -#include "PatternTheMatrix.hpp" +#include "PatternRain.hpp" #include "PatternJuliaSetFractal.hpp" -//#include "PatternTunnel.hpp" // fail -//#include "PatternSphereSpin.hpp" // fail +#include "PatternRain.hpp" +#include "PatternFireworks.hpp" + class Patterns { private: PatternStarfield starfield; - // PatternSphereSpin sspin; PatternAttract attract; PatternBounce bounce; PatternCube cube; @@ -74,16 +74,15 @@ class Patterns { PatternSpiral spiral; PatternSpiro spiro; PatternWave wave; - PatternTheMatrix matrix; - // PatternTunnel tunnel; PatternJuliaSet juliaSet; + + PatternRain rain; + PatternFirework fireworks; + std::vector availablePatterns = { - // &tunnel, &juliaSet, - &matrix, &starfield, - // &sspin, &attract, &bounce, &cube, @@ -102,10 +101,10 @@ class Patterns { &plasma, &radar, &simpnoise, - //&snake, // a bit crap - //&spiral, // a bit crap &spiro, &wave, + &rain, + &fireworks }; int currentIndex = 0;