Lightning Hat Halloween Costume

In 2019 I set out to create a wizard costume that achieved the illusion of lighting flashing within clouds. This page explains my design process, as well as some of the code used to control the animations.
There were several challenges to tackle, but one the larger ones was determining how to map the position of the LEDs relative to one another. Without creating this map, there would be no way to specify which pixels the lighting animation functions should illuminate. I knew I would be using an addressable RGBW LED strip, but the addresses on the strip are sequential, I needed to represent their relation to their neighbors as it would be once built into the costume. Because the only feasible way to distribute the LEDs evenly over the surface of the conical hat was to wind the strip in a spiral, I decided to approximate the spiral as a set of concentric circles at different heights.
I wanted to store the ID of each pixel's upstairs, downstairs, and left and right neighbors. So, I created a small, lightweight class as shown below.
//lightweight class to map a pixel's spatial position relative to its neighbors
//need this for realistic lightning bolts.
class PixelInfo {
public :
byte * top;
byte * bot;
PixelInfo () {}
};
The Arduino Uno has only 32kb of memory, you can easily consume all the memory on the device with just a small number of int variables. To avoid this, I used the smaller byte type along with pointers to cut down on memory consumption. I also realized that the left and right neighbors do not need to be stored, since the strip addresses are sequential. Because the top and bottom neighbors for each pixel needed to be hard coded, I stored them once in an array of byte type numbers (Starmap), then initialzed the an array of PixelInfo objects called stars by pointing their .bot and .top variables with values from the Starmap. This approach consumes eight times less memory than implementing the code with ints and without using pointers.
Next, I defined a simple function to illuminate a single pixel, lightningStrike, and five additional functions to animate different lightning patterns. The five lighting functions I then stored in array of function pointers, so the main loop can randomly call one of the five lightning types for more robust realism. Code for the "individual jagged" lightning is shown below.
void bolt () {
//Serial.println("lightning bolt! lightning bolt!");
int lrod;
for (int chain_len=0; chain_len < 10; chain_len++) {
if (chain_len==0) {
lrod = random(NUM_LEDS);
lightningStrike(lrod);
delay_show(50);
} else {
int nselect = random(3);
if(nselect==0) {
lrod = *(Stars[lrod].top);
}
if(nselect==1) {
lrod = *(Stars[lrod].bot);
}
if(nselect==2) {
lrod += 1;
}
if(nselect==3) {
lrod -= 1;
}
if(lrod >= 90 || lrod <= 0 ) {
break;
} else {
lightningStrike(lrod);
delay_show(50);
}
}
}
}
The bolt function uses the relative positions of the pixels (members of the array, Star) to randomly illuminate one LED, then randomly choose one of its neighbors to strike on the next interation of the for loop. This continues until the 10th LED in the chain, or until the chain hits the bottom or top of the hat. This is because I didn't think it would look realistic to have lightning bouncing off the borders of the hat.