# Challenge Idea: All The Colors (Rainbow Smoke)

**URL:** <https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531>\
**Category:** Pixelblaze Academy of Wizardry and Enchantment\
**Created:** [May 7, 2024, 2:40pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531 "2024-05-07T14:40:40Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 7, 2024, 2:40pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/1 "2024-05-07T14:40:40Z")

</div>

Apologies if this isn’t the right place for this, or if it’s already been suggested/requested. I searched but didn’t find anything.

I’ve been trying to port “Rainbow Smoke” to Pixelblaze, unsuccessfully, and wondered if anyone else might like to make an attempt.

An output image example:

 ![image](https://forum.electromage.com/uploads/default/original/2X/7/76c6c703d102564d06d8dfac1eb3acd5b5b4cb8f.png)

Video of a 32x32 RGB LED matrix (SmartMatrix) implementation:

[![](https://img.youtube.com/vi/jLi_VaPeJa4/maxresdefault.jpg "Rainbow Smoke on SmartMatrix") ](https://www.youtube.com/watch?v=jLi_VaPeJa4)

It was originally a “code golf” challenge winner on stack exchange: [https://codegolf.stackexchange.com/questions/22144/images-with-all-colors/22326#22326](https://codegolf.stackexchange.com/questions/22144/images-with-all-colors/22326#22326)

The idea is to:

> make images where each pixel is a unique color (no color is used twice and no color is missing).

The algorithm used by the winner:

> put a random [color] in the middle, and then start putting random [colors] in a neighborhood that most resembles them.

My hope was to do it without mapping a virtual grid onto the actual pixels, but that may not be feasible. I’m also trying to get it to work for any arbitrary, potentially non-rectilinear 2D map (such as my Fibonacci layouts).

Obviously this would differ from the original idea, as we won’t have enough pixels to use “all colors”.

The original author’s site for the project: [http://rainbowsmoke.hu/home](http://rainbowsmoke.hu/home)

My implementation of it in C/Arduino for the SmartMatrix Aurora project: [aurora/PatternRainbowSmoke.h at master · pixelmatix/aurora · GitHub](https://github.com/pixelmatix/aurora/blob/master/PatternRainbowSmoke.h)

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 8, 2024, 2:32am UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/2 "2024-05-08T02:32:59Z")

</div>

That… is a very weird algorithm! Let me think about this for a couple of days.

Exhaustive neighborhood search and Pixelblaze are not things that occur together in nature, but there’s bound to be a sneaky way to get the same effect on non-rectangular displays.

---

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 8, 2024, 3:19am UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/3 "2024-05-08T03:19:53Z")

</div>

Thankfully the pattern doesn’t need to work quickly. I found it more interesting watching it work, filling in slowly, then pausing for a few seconds before clearing and starting over on a new random set.  
I’m going to make another attempt tomorrow, after thinking about it for a while.  
Can’t wait to see what you come up with!

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 9, 2024, 10:42pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/4 "2024-05-09T22:42:40Z")

</div>

Ok. First attempt:

This version does the color arranging incrementally, by random sampling and swapping. It makes nice patterns, and doesn’t use a lot of memory but is _!!!extremely!!!_ slow. (Of course, it works for non-rectangular pixel arrangements. 🙂)

Next up: A way to make it converge on a “good” color arrangement faster. Will post code when I get to a reasonable speed!

 ![image](https://forum.electromage.com/uploads/default/original/2X/1/1da9db9d6e230926a07895f6e3148d5318a3c281.jpeg)

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 10, 2024, 12:43am UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/5 "2024-05-10T00:43:42Z")

</div>

Aesthetically, I like things that self-organize. I feel my CPU gets bored and annoyed if I make it do exhaustive searches. And watching the original algorithm, it did look a bit organic and live – like a finite state automaton, or a [slime mold model](https://www.shadertoy.com/view/WtBcDG).

So one of the first things I tried was building a variant of the [“boids model”](https://en.wikipedia.org/wiki/Boids) in which boids are very strongly attracted to others of similar color and do their best to avoid differently colored boids.

It actually works pretty well except that, sadly, it takes even more computation than the original exhaustive search approach. So much for aesthetics!

[![](https://img.youtube.com/vi/luB9Zj_v3EY/hqdefault.jpg "Boids of a Feather: One way to get random colors to self-organize.") ](https://www.youtube.com/watch?v=luB9Zj_v3EY)

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 10, 2024, 8:30pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/6 "2024-05-10T20:30:02Z")

</div>

EDIT: Quick note - Fibonacci 256 layout seems to do best w/neighborhoodRadius of around 0.06.

 ![image](https://forum.electromage.com/uploads/default/original/2X/a/a20166f247c19f4501c40c8d8bc24b2969b80f67.jpeg)

* * *

Here’s a first cut at the basic algorithm. It is not fast, but will work on 2D displays of any shape or size

Things to note:

- Memory is the limiting factor for pixel count. Neighborhood search runs incrementally, limiting the number of comparisons per frame so you can run more pixels without getting “execution steps exhausted”.
- Uses random palettes of user specifiable size. (I chose to not do the “whole color gamut” thing, since I don’t have enough pixels to make that look good.)
- Determines neighborhood based on radial distance. You may want to play with “neighborhoodRadius” to get best results for your display.

> **Source Code**
>
> ```auto
> // Rainbow Smoke (rainbowsmoke.hu/home) for Pixelblaze
> // 2024 - ZRanger1
> 
> // Wow, what a lot of pixelCount-sized arrays!
> var palette = array(pixelCount)
> var pixels = array(pixelCount)
> var coords = array(pixelCount)
> var hasColor = array(pixelCount)
> 
> // may need to play with this to get a "good" neighborhood size for
> // your display.
> var neighborhoodRadius = 0.075;
> var numColors = pixelCount;
> var colorIndex;
> var pixelIndex;
> 
> // limit the number of neighborhood comparisons per frame so we can run
> // on displays with more pixels
> // (128 == about 8 initial fps on 256 pixel display. The algorithm speeds up considerably
> // as it goes.)
> var opsPerCycle = 128;  
> 
> // variables used to pass things to and from Pixelblaze mapPixels() functions
> var tgtIndex;
> var tgtColor;
> var tgtX, tgtY;
> var neighborCount;
> var totalDist;
> 
> // trigger button to start new pattern
> export function triggerNewCloud() {
> startPattern();
> }
> 
> // generate a random palette, one color per pixel
> function randomPalette() {
> var entriesPerColor = floor(pixelCount / numColors);
> var i = 0;
>   
> for (var i = 0;i < pixelCount; i++) {
> palette[i] = array(3);
>     
> if (0 == (i % entriesPerColor)) {
> r = random(1);
> g = random(1);
> b = random(1);
> r *= r;
> g *= g;
> b *= b;
> }
>     
> palette[i][0] = r;
> palette[i][1] = g;
> palette[i][2] = b;
> }
> }
> 
> // Stores the index of the palette color for
> // each pixel and marks the pixel as available.
> function initPixelBuffer() {
> for (i = 0; i < pixelCount; i++){
> pixels[i] = i;
> hasColor[i] = 0;
> }
> }
> 
> // get and save x/y coordinates for each pixel index so we can use them
> // in beforeRender
> function saveXYCoords(index,x,y,z) {
> coords[index] = array(2);
> coords[index][0] = x;
> coords[index][1] = y;  
> }
> 
> // shuffle color array
> // (standard Fisher-Yates-Knuth shuffle)
> function shuffle() {
> for (var i = pixelCount - 1; i > 0; i--) {
> var r = floor(random(i+1));
> var tmp = palette[i];
> palette[i] = palette[r];
> palette[r] = tmp;
> }
> }
> 
> // 50% true, 50% false
> function coinFlip() {
> return random(1) >= 0.5;
> }
> 
> function startPattern() {
> initPixelBuffer();
> randomPalette();   
> shuffle();  
>   
> colorIndex = 0;
> pixelIndex = random(pixelCount);
> searchComplete = 1;
> minDist = 9999;
> bestPoint = -1; 
> neighborhoodCenter = 0;  
> }
> 
> // initialization
> mapPixels(saveXYCoords);
> startPattern();
> 
> // calculate total color distance between specified color and the neighborhood surrounding the
> // pixel at tgtIndex.
> function colorDistance(index,x,y,z) {
> // is this pixel within our defined neighborhood, with a color to compare?
> if ((index == tgtIndex) || (!hasColor[index]) || hypot(tgtX- x,tgtY - y) > neighborhoodRadius) return;
> neighborCount++;
> 
> c1 = palette[pixels[index]];
> totalDist += hypot3(c1[0] - tgtColor[0],c1[1] - tgtColor[1],c1[2] - tgtColor[2])
> }
> 
> // returns average color distance between tgtColor and the neighborhood around the 
> // specified pixel index.
> function radialColorDistance(index) {
> neighborCount = 0;
> totalDist = 0;
> tgtIndex = index;
> tgtX = coords[tgtIndex][0]; tgtY = coords[tgtIndex][1]; 
> mapPixels(colorDistance);
>   
> // return avg distance, or very large distance if no neighbors
> return (neighborCount > 0) ? totalDist / neighborCount : 9999;
> }
> 
> // point search runs incrementally, at "opsPerCycle" neighborhood comparisons per frame.
> // (somewhere between 50 and 100 is a reasonable setting for most displays) 
> // This slows things down early in the pattern generation cycle, but it allows us to 
> // run on displays with any number of pixels (limited by available memory, of course!)
> var searchComplete;
> var minDist;
> var bestPoint;
> var neighborhoodCenter;
> function getAvailablePoint(color) {
> // find the next available point
>   
> tgtColor = color;
> 
> for (i = 0; i < opsPerCycle; i++) {
> if (neighborhoodCenter < pixelCount) {
> // find the neighborhood with the closest color match
> if (!hasColor[neighborhoodCenter]) {
> cd = radialColorDistance(neighborhoodCenter);
> if (cd < minDist) {
> bestPoint = neighborhoodCenter;
> minDist = cd;
> }
> }
> neighborhoodCenter++;
> }
> else {
> searchComplete = 1;
> break;
> }
> }
>   
> // return point index, or -1 if no points available.
> return bestPoint;
> }
> 
> export function beforeRender(delta) {
>   
> if (colorIndex < pixelCount) {
> k = (colorIndex == 0) ? random(pixelCount) : getAvailablePoint(palette[colorIndex]);
> if (!searchComplete) return;
>    
> if (k >= 0) {
> pixelIndex = k;
> pixels[pixelIndex] = colorIndex;
> hasColor[pixelIndex] = 1;
> colorIndex++;
> }
> searchComplete = 0;
> bestPoint = -1;
> neighborhoodCenter = 0;
> minDist = 9999;    
> }
> }
> 
> export function render(index) {
> c = palette[pixels[index]];
> if (hasColor[index]) rgb(c[0],c[1],c[2]);  
> }
> 
> ```

The whole, slightly slow (around a minute to complete) thing in action:

[![](https://img.youtube.com/vi/3ZdGW5_7oc0/maxresdefault.jpg "Rainbow Smoke implementation for Pixelblaze") ](https://www.youtube.com/watch?v=3ZdGW5_7oc0)

---

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 10, 2024, 10:31pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/7 "2024-05-10T22:31:39Z")

</div>

Wow, excellent work! Here it is running on a Fibonacci256:

[![](https://img.youtube.com/vi/BuN9kO6lnFY/maxresdefault.jpg "Fibonacci256 - Rainbow Smoke") ](https://www.youtube.com/watch?v=BuN9kO6lnFY)

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 10, 2024, 10:32pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/8 "2024-05-10T22:32:34Z")

</div>

Super pretty!! Just updated my post w/note on neighborhood size for fibonacci 256, too!

(EDIT: Actually, kind of hard to tell. Default neighborhood size looks pretty good too.)

---

<div class="post-metadata">

**Author:** ![wizard](https://forum.electromage.com/user_avatar/forum.electromage.com/wizard/32/4_2.png) [@wizard](https://forum.electromage.com/u/wizard)\
**Post date:** [May 12, 2024, 6:36pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/9 "2024-05-12T18:36:54Z")

</div>

UPDATE: added a timer to auto redraw after a while (controls). Added a rainbow palette and stacked the deck.

* * *

This looks super cool! I had some ideas for this but its really cool to see a working version already! I’m totally borrowing on your excellent work, @zranger1!

This variant uses palettes. Though not quite an exact match with the every color initial idea, I think it’s a really nice look, especially with limited pixels. Also happens to simplify color distance math. The target color can be a random number without needing to remember a large array of colors.

It picks a random palette (borrowing a bunch from the Lux patterns), and you can click a button to randomize it.

I wanted to speed up finding closer color matches, so I added a step that calculates a map of neighbors. I noticed that the neighbor search was favoring lower pixel indexes when the distance was otherwise equal, on regular grids things tended to grow up and left (which is cool and maybe plant-like). I wanted to pick a random one if all else was equal to make it more like a slime mold that doesn’t favor a direction.

I add a neighbor mapping stage that finds the nearest `numNeighbors` neighbors, randomizing it a bit if `numNeighbors` is low compared to the minimum `neighborhoodRadius` (which can now be fairly large without impacting speed).

It does the mapping in the render call while showing the min neighbor radius around the pixel being mapped, along with any pixels that were at one point considered a close neighbor - that converges to a tight random circle limited by the `numNeighbors` setting. I borrow the pixels array to use as list of random pixels to map instead of iterating. It’s fast enough to be mostly limited by the LED data rate, but still takes a while for large pixel counts. I’m thinking about doing a few at a time via mapPixels instead, and display something more appealing during the mapping phase.

For drawing, it uses a similar trick as the closest neighbor mapping to find a number of color matches, and will randomize them if equal.

You can toss in multiple random seed pixels if you like, and it will re-seed if it can’t find any neighbors. That helps with some cases that can happen if you set numNeighbors low (the neighbor graph can create orphans) or have separate islands of pixels in your map further away than the `neighborhoodRadius`.

> **Pattern Source Code**
>
> ```auto
> // Slime mold palette
> // 2024 - wizard
> // Based heavily on:
> // Rainbow Smoke (rainbowsmoke.hu/home) for Pixelblaze
> // 2024 - ZRanger1
> // and borrowing palettes from the Lux Lavalier
> 
> var numNeighbors = 8 //this is a key setting, smaller values create tighter patterns
> var initialSeeds = 1
> var neighborhoodRadius = 0.2; //big enough for an 8x8 grid to find adjacent pixels
> 
> // limit the number of neighborhood comparisons per frame so we can run
> // on displays with more pixels
> var opsPerCycle = 1024;
> var matches = array(8) //consider at most this many neighborhoods while drawing
> 
> //store 2 indexes per element to save space
> var neighborhoodMap = array(ceil(pixelCount * numNeighbors / 2))
> var pixels = array(pixelCount) //keep track of what color has been chosen
> 
> var neighbors = array(numNeighbors) //used during neighborhood mapping for a pixel, later copied to neighborhoodMap
> 
> var lastMatch = matches.length-1
> var lastNeighbor = numNeighbors - 1
> var emptySlot = 0x7FFF + (0x7FFF>>16) //magic number for empty neighbors / matches
> export var mapIndex, tgtIndex, tgtX, tgtY //which pixel is being mapped
> var needsMapping = true
> export var tgtColor, neighborCount, totalDist, searchComplete, neighborhoodCenter, pixelsDrawn
> var redrawTimer
> var redrawSeconds = 30
> var autoRedraw = true
> 
> export function triggerRemap() {
> neighborhoodMap.mutate(() => emptySlot)
> //borrow the pixel array during mapping stage to store a list of indexes to map
> pixels.mutate((v, i) => i)
> pixels.sortBy(() => random(1) - .5) //shuffle
> needsMapping = true
> mapIndex = -1
> }
> 
> triggerRemap()
> 
> function restartSearch() {
> searchComplete = false
> neighborhoodCenter = 0;
> tgtColor = random(1)
> matches.mutate(() => emptySlot)
> }
> 
> //start with a bunch of seeds if you like
> export function inputNumberNumSeeds(v) {
> initialSeeds = max(v,1)
> }
> 
> //you can add seeds any time too
> export function triggerSeedPixel() {
> if (pixelsDrawn >= pixelCount)
> return
> //try hard, but not forever, to find a free pixel to seed
> for (var i = 0; i < 100; i++) {
> var index = random(pixelCount)
> if (pixels[index] == -1) {
> pixels[index] = random(1)
> pixelsDrawn++
> return
> }
> }
> }
> 
> //start a new drawing, clear the display and pick a new palette
> export function triggerDraw() {
> if (needsMapping)
> return
> triggerRandomPalette()
> pixels.mutate(() => -1) //erase the pixels
> pixelsDrawn = 0
> //seed the first pixel(s)
> 
> //TODO might look cool to always start at an edge or something
> 
> for (i = 0; i < initialSeeds; i++)
> triggerSeedPixel()
> restartSearch()
> redrawTimer = 0
> }
> 
> export function toggleAutoRedraw(v) {
> autoRedraw = v
> }
> 
> export function inputNumberRedrawSeconds(v) {
> redrawSeconds = v
> }
> 
> function loadTargetCoords(index, x, y) {
> if (index == tgtIndex) {
> tgtX = x
> tgtY = y
> }
> }
> 
> //store a neighbor into the map, with 2 neighbors per array element
> function setNeighborhoodMap(pixelIndex, neighborIndex, value) {
> var offset = floor((pixelIndex * numNeighbors + neighborIndex) / 2)
> var oldValue = neighborhoodMap[offset];
> if (neighborIndex & 1)
> neighborhoodMap[offset] = trunc(oldValue) + (value >> 16)
> else
> neighborhoodMap[offset] = frac(oldValue) + trunc(value)
> }
> 
> // returns average color distance between tgtColor and the neighborhood around the
> // specified pixel index, or -1 if the neighborhood is empty
> function radialColorDistance(index) {
> neighborCount = 0;
> totalDist = 0;
> tgtIndex = index;
> 
> //look through the neighbors for this index
> var offset = floor((index * numNeighbors) / 2)
> var value;
> for (var i = 0; i < numNeighbors; i++) {
> value = neighborhoodMap[offset];
> if (i & 1) {
> value = frac(value) << 16
> offset++
> } else {
> value = trunc(value)
> }
> if (value < pixelCount) { //filter out empty entries (0x7FFF)
> var color = pixels[value]
> if (color >= 0) {
> totalDist += abs(color - tgtColor)
> neighborCount++;
> }
> }
> }
> 
> // return avg distance, or very large distance if no neighbors
> return (neighborCount > 0) ? totalDist / neighborCount : -1;
> }
> 
> // find the next available point with a neighborhood with the closest color match
> function getAvailablePoint() {
> var cd
> 
> // point search can run incrementally, at "opsPerCycle" neighborhood comparisons per frame.
> for (i = 0; i < opsPerCycle; i++) {
> if (neighborhoodCenter < pixelCount) {
> //only consider pixels that don't yet have a color
> if (pixels[neighborhoodCenter] == -1) {
> cd = radialColorDistance(neighborhoodCenter)
> if (cd >= 0) { //skip empty neighborhood
> //store match distance in integer part and index in fraction to compact and keep data pair together during sort
> //add some random bits in between to prevent the index order from impacting sort for equal distances
> cd = (round(cd << 8) << 6) + (random(100) & 0x3f) + (neighborhoodCenter >> 16)
>           
> //see if we should replace the worst match
> if (cd < matches[lastMatch]) {
> matches[lastMatch] = cd
> //sort matches from close to far
> matches.sort(); //very fast, basically a call to std::sort
> }
> }
> }
> neighborhoodCenter++;
> } else {
> searchComplete = true;
> break;
> }
> }
> 
> // return point index from the best match, or -1 if no points available.
> if (matches[0] == emptySlot)
> return -1;
> return frac(matches[0]) << 16
> }
> 
> export var render2D //dynamically switched at runtime
> 
> export function beforeRender(deltaMs) {
> var k
> if (needsMapping) {
> render2D = renderMapNeighborhood
> //save previous neighbors
> if (mapIndex != -1) {
> for (var i = 0; i < numNeighbors; i++) {
> var offset = floor(i / 2)
> setNeighborhoodMap(tgtIndex, i, frac(neighbors[i]) << 16)
> }
> }
> 
> if (++mapIndex < pixelCount) {
> //instead of going through linearly, we have borrowed the pixels array to do a random scan
> tgtIndex = pixels[mapIndex]
> mapPixels(loadTargetCoords)
> neighbors.mutate(() => emptySlot)
> } else {
> needsMapping = false
> triggerDraw()
> }
> } else {
> render2D = renderDrawColors
> 
> if (pixelsDrawn < pixelCount) {
> k = getAvailablePoint();
> if (!searchComplete) return;
> 
> if (k >= 0) {
> pixels[k] = tgtColor;
> pixelsDrawn++;
> } else {
> //special case, search was complete but didn't find anything, yet we still have pixels to draw
> //maybe an island of pixels with no neighbors
> //try to make a new seed
> triggerSeedPixel()
> }
> restartSearch()
> } else {
> if (autoRedraw) {
> redrawTimer += deltaMs / 1000
> if (redrawTimer >= redrawSeconds) {
> triggerDraw();
> }
> }
> }
> }
> }
> 
> function renderDrawColors(index, x, y) {
> var color = pixels[index]
> if (color > 0) {
> paint(color)
> }
> }
> 
> function renderMapNeighborhood(index, x, y) {
> if (index == tgtIndex) {
> rgb(0, 0, 1)
> return;
> }
> 
> var tgtDist = hypot(tgtX - x, tgtY - y)
> //show a sort of progress bar by filling the screen with gray
> hsv(0, 0, .1 * (y < mapIndex / pixelCount))
> 
> //don't consider pixels that are too far to be neighbors
> if (tgtDist < neighborhoodRadius) {
> //try to store this dist to the local neighbors
> //boot the highest distance one if this is closer
> //easy if neighbors are sorted by distance, only need to compare one value, store, and re-sort
> 
> //store as distance in integer part and index in fraction to compact and keep data pair together during sort
> //add some random bits in between to prevent the index order from impacting sort for equal distances
> tgtDist = (round(tgtDist << 8) << 6) + (random(100) & 0x3f) + (index >> 16)
> 
> //see if we should replace the furthest neighbor
> if (tgtDist < neighbors[lastNeighbor]) {
> neighbors[lastNeighbor] = tgtDist
> //sort neighbors from close to far
> neighbors.sort(); //very fast, basically a call to std::sort
> rgb(.5, .5, 0) //was closer than existing neighbors but might still get kicked out
> } else {
> rgb(.3, .0, .0) //was checked, but never closer than existing neighbors
> }
> }
> }
> 
> // From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
> 
> // Gradient palette "ib_jul01_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var ib_jul01 = [
> 0.0, 0.761, 0.004, 0.004,
> 0.369, 0.004, 0.114, 0.071,
> 0.518, 0.224, 0.514, 0.11,
> 1.0, 0.443, 0.004, 0.004,
> ];
> 
> // Gradient palette "es_vintage_57_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_57.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_vintage_57 = [
> 0.0, 0.008, 0.004, 0.004,
> 0.208, 0.071, 0.004, 0.0,
> 0.408, 0.271, 0.114, 0.004,
> 0.6, 0.655, 0.529, 0.039,
> 1.0, 0.18, 0.22, 0.016,
> ];
> 
> // Gradient palette "es_vintage_01_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_vintage_01 = [
> 0.0, 0.016, 0.004, 0.004,
> 0.2, 0.063, 0.0, 0.004,
> 0.298, 0.38, 0.408, 0.012,
> 0.396, 1.0, 0.514, 0.075,
> 0.498, 0.263, 0.035, 0.016,
> 0.6, 0.063, 0.0, 0.004,
> 0.898, 0.016, 0.004, 0.004,
> 1.0, 0.016, 0.004, 0.004,
> ];
> 
> // Gradient palette "es_rivendell_15_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_rivendell_15 = [
> 0.0, 0.004, 0.055, 0.02,
> 0.396, 0.063, 0.141, 0.055,
> 0.647, 0.22, 0.267, 0.118,
> 0.949, 0.588, 0.612, 0.388,
> 1.0, 0.588, 0.612, 0.388,
> ];
> 
> // Gradient palette "rgi_15_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var rgi_15 = [
> 0.0, 0.016, 0.004, 0.122,
> 0.122, 0.216, 0.004, 0.063,
> 0.247, 0.773, 0.012, 0.027,
> 0.373, 0.231, 0.008, 0.067,
> 0.498, 0.024, 0.008, 0.133,
> 0.624, 0.153, 0.024, 0.129,
> 0.749, 0.439, 0.051, 0.125,
> 0.875, 0.22, 0.035, 0.137,
> 1.0, 0.086, 0.024, 0.149,
> ];
> 
> // Gradient palette "retro2_16_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var retro2_16 = [
> 0.0, 0.737, 0.529, 0.004,
> 1.0, 0.18, 0.027, 0.004,
> ];
> 
> // Gradient palette "Analogous_1_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Analogous_1 = [
> 0.0, 0.012, 0.0, 1.0,
> 0.247, 0.09, 0.0, 1.0,
> 0.498, 0.263, 0.0, 1.0,
> 0.749, 0.557, 0.0, 0.176,
> 1.0, 1.0, 0.0, 0.0,
> ];
> 
> // Gradient palette "es_pinksplash_08_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_pinksplash_08 = [
> 0.0, 0.494, 0.043, 1.0,
> 0.498, 0.773, 0.004, 0.086,
> 0.686, 0.824, 0.616, 0.675,
> 0.867, 0.616, 0.012, 0.439,
> 1.0, 0.616, 0.012, 0.439,
> ];
> 
> // Gradient palette "es_pinksplash_07_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_07.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_pinksplash_07 = [
> 0.0, 0.898, 0.004, 0.004,
> 0.239, 0.949, 0.016, 0.247,
> 0.396, 1.0, 0.047, 1.0,
> 0.498, 0.976, 0.318, 0.988,
> 0.6, 1.0, 0.043, 0.922,
> 0.757, 0.957, 0.02, 0.267,
> 1.0, 0.91, 0.004, 0.02,
> ];
> 
> // Gradient palette "Coral_reef_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Coral_reef.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Coral_reef = [
> 0.0, 0.157, 0.78, 0.773,
> 0.196, 0.039, 0.596, 0.608,
> 0.376, 0.004, 0.435, 0.471,
> 0.376, 0.169, 0.498, 0.635,
> 0.545, 0.039, 0.286, 0.435,
> 1.0, 0.004, 0.133, 0.278,
> ];
> 
> // Gradient palette "es_ocean_breeze_068_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_068.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_ocean_breeze_068 = [
> 0.0, 0.392, 0.612, 0.6,
> 0.2, 0.004, 0.388, 0.537,
> 0.396, 0.004, 0.267, 0.329,
> 0.408, 0.137, 0.557, 0.659,
> 0.698, 0.0, 0.247, 0.459,
> 1.0, 0.004, 0.039, 0.039,
> ];
> 
> // Gradient palette "es_ocean_breeze_036_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_ocean_breeze_036 = [
> 0.0, 0.004, 0.024, 0.027,
> 0.349, 0.004, 0.388, 0.435,
> 0.6, 0.565, 0.82, 1.0,
> 1.0, 0.0, 0.286, 0.322,
> ];
> 
> // Gradient palette "departure_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var departure = [
> 0.0, 0.031, 0.012, 0.0,
> 0.165, 0.09, 0.027, 0.0,
> 0.247, 0.294, 0.149, 0.024,
> 0.329, 0.663, 0.388, 0.149,
> 0.416, 0.835, 0.663, 0.467,
> 0.455, 1.0, 1.0, 1.0,
> 0.541, 0.529, 1.0, 0.541,
> 0.58, 0.086, 1.0, 0.094,
> 0.667, 0.0, 1.0, 0.0,
> 0.749, 0.0, 0.533, 0.0,
> 0.831, 0.0, 0.216, 0.0,
> 1.0, 0.0, 0.216, 0.0,
> ];
> 
> // Gradient palette "es_landscape_64_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_landscape_64 = [
> 0.0, 0.0, 0.0, 0.0,
> 0.145, 0.008, 0.098, 0.004,
> 0.298, 0.059, 0.451, 0.02,
> 0.498, 0.31, 0.835, 0.004,
> 0.502, 0.494, 0.827, 0.184,
> 0.51, 0.737, 0.82, 0.969,
> 0.6, 0.565, 0.714, 0.804,
> 0.8, 0.231, 0.459, 0.98,
> 1.0, 0.004, 0.145, 0.753,
> ];
> 
> // Gradient palette "es_landscape_33_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_landscape_33 = [
> 0.0, 0.004, 0.02, 0.0,
> 0.075, 0.125, 0.09, 0.004,
> 0.149, 0.631, 0.216, 0.004,
> 0.247, 0.898, 0.565, 0.004,
> 0.259, 0.153, 0.557, 0.29,
> 1.0, 0.004, 0.016, 0.004,
> ];
> 
> // Gradient palette "rainbowsherbet_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var rainbowsherbet = [
> 0.0, 1.0, 0.129, 0.016,
> 0.169, 1.0, 0.267, 0.098,
> 0.337, 1.0, 0.027, 0.098,
> 0.498, 1.0, 0.322, 0.404,
> 0.667, 1.0, 1.0, 0.949,
> 0.82, 0.165, 1.0, 0.086,
> 1.0, 0.341, 1.0, 0.255,
> ];
> 
> // Gradient palette "gr65_hult_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var gr65_hult = [
> 0.0, 0.969, 0.69, 0.969,
> 0.188, 1.0, 0.533, 1.0,
> 0.349, 0.863, 0.114, 0.886,
> 0.627, 0.027, 0.322, 0.698,
> 0.847, 0.004, 0.486, 0.427,
> 1.0, 0.004, 0.486, 0.427,
> ];
> 
> // Gradient palette "gr64_hult_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var gr64_hult = [
> 0.0, 0.004, 0.486, 0.427,
> 0.259, 0.004, 0.365, 0.31,
> 0.408, 0.204, 0.255, 0.004,
> 0.51, 0.451, 0.498, 0.004,
> 0.588, 0.204, 0.255, 0.004,
> 0.788, 0.004, 0.337, 0.282,
> 0.937, 0.0, 0.216, 0.176,
> 1.0, 0.0, 0.216, 0.176,
> ];
> 
> // Gradient palette "GMT_drywet_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var GMT_drywet = [
> 0.0, 0.184, 0.118, 0.008,
> 0.165, 0.835, 0.576, 0.094,
> 0.329, 0.404, 0.859, 0.204,
> 0.498, 0.012, 0.859, 0.812,
> 0.667, 0.004, 0.188, 0.839,
> 0.831, 0.004, 0.004, 0.435,
> 1.0, 0.004, 0.027, 0.129,
> ];
> 
> // Gradient palette "ib15_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var ib15 = [
> 0.0, 0.443, 0.357, 0.576,
> 0.282, 0.616, 0.345, 0.306,
> 0.349, 0.816, 0.333, 0.129,
> 0.42, 1.0, 0.114, 0.043,
> 0.553, 0.537, 0.122, 0.153,
> 1.0, 0.231, 0.129, 0.349,
> ];
> 
> // Gradient palette "Fuschia_7_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/fuschia/tn/Fuschia-7.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Fuschia_7 = [
> 0.0, 0.169, 0.012, 0.6,
> 0.247, 0.392, 0.016, 0.404,
> 0.498, 0.737, 0.02, 0.259,
> 0.749, 0.631, 0.043, 0.451,
> 1.0, 0.529, 0.078, 0.714,
> ];
> 
> // Gradient palette "es_emerald_dragon_08_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_08.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_emerald_dragon_08 = [
> 0.0, 0.38, 1.0, 0.004,
> 0.396, 0.184, 0.522, 0.004,
> 0.698, 0.051, 0.169, 0.004,
> 1.0, 0.008, 0.039, 0.004,
> ];
> 
> // Gradient palette "lava_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var lava = [
> 0.0, 0.0, 0.0, 0.0,
> 0.18, 0.071, 0.0, 0.0,
> 0.376, 0.443, 0.0, 0.0,
> 0.424, 0.557, 0.012, 0.004,
> 0.467, 0.686, 0.067, 0.004,
> 0.573, 0.835, 0.173, 0.008,
> 0.682, 1.0, 0.322, 0.016,
> 0.737, 1.0, 0.451, 0.016,
> 0.792, 1.0, 0.612, 0.016,
> 0.855, 1.0, 0.796, 0.016,
> 0.918, 1.0, 1.0, 0.016,
> 0.957, 1.0, 1.0, 0.278,
> 1.0, 1.0, 1.0, 1.0,
> ];
> 
> // Gradient palette "fire_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fire.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var fire = [
> 0.0, 0.004, 0.004, 0.0,
> 0.298, 0.125, 0.02, 0.0,
> 0.573, 0.753, 0.094, 0.0,
> 0.773, 0.863, 0.412, 0.02,
> 0.941, 0.988, 1.0, 0.122,
> 0.98, 0.988, 1.0, 0.435,
> 1.0, 1.0, 1.0, 1.0,
> ];
> 
> // Gradient palette "Colorfull_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Colorfull = [
> 0.0, 0.039, 0.333, 0.02,
> 0.098, 0.114, 0.427, 0.071,
> 0.235, 0.231, 0.541, 0.165,
> 0.365, 0.325, 0.388, 0.204,
> 0.416, 0.431, 0.259, 0.251,
> 0.427, 0.482, 0.192, 0.255,
> 0.443, 0.545, 0.137, 0.259,
> 0.455, 0.753, 0.459, 0.384,
> 0.486, 1.0, 1.0, 0.537,
> 0.659, 0.392, 0.706, 0.608,
> 1.0, 0.086, 0.475, 0.682,
> ];
> 
> // Gradient palette "Magenta_Evening_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Magenta_Evening.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Magenta_Evening = [
> 0.0, 0.278, 0.106, 0.153,
> 0.122, 0.51, 0.043, 0.2,
> 0.247, 0.835, 0.008, 0.251,
> 0.275, 0.91, 0.004, 0.259,
> 0.298, 0.988, 0.004, 0.271,
> 0.424, 0.482, 0.008, 0.2,
> 1.0, 0.18, 0.035, 0.137,
> ];
> 
> // Gradient palette "Pink_Purple_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Pink_Purple = [
> 0.0, 0.075, 0.008, 0.153,
> 0.098, 0.102, 0.016, 0.176,
> 0.2, 0.129, 0.024, 0.204,
> 0.298, 0.267, 0.243, 0.49,
> 0.4, 0.463, 0.733, 0.941,
> 0.427, 0.639, 0.843, 0.969,
> 0.447, 0.851, 0.957, 1.0,
> 0.478, 0.624, 0.584, 0.867,
> 0.584, 0.443, 0.306, 0.737,
> 0.718, 0.502, 0.224, 0.608,
> 1.0, 0.573, 0.157, 0.482,
> ];
> 
> // Gradient palette "Sunset_Real_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Sunset_Real = [
> 0.0, 0.471, 0.0, 0.0,
> 0.086, 0.702, 0.086, 0.0,
> 0.2, 1.0, 0.408, 0.0,
> 0.333, 0.655, 0.086, 0.071,
> 0.529, 0.392, 0.0, 0.404,
> 0.776, 0.063, 0.0, 0.51,
> 1.0, 0.0, 0.0, 0.627,
> ];
> 
> // Gradient palette "es_autumn_19_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var es_autumn_19 = [
> 0.0, 0.102, 0.004, 0.004,
> 0.2, 0.263, 0.016, 0.004,
> 0.329, 0.463, 0.055, 0.004,
> 0.408, 0.537, 0.596, 0.204,
> 0.439, 0.443, 0.255, 0.004,
> 0.478, 0.522, 0.584, 0.231,
> 0.486, 0.537, 0.596, 0.204,
> 0.529, 0.443, 0.255, 0.004,
> 0.557, 0.545, 0.604, 0.18,
> 0.639, 0.443, 0.051, 0.004,
> 0.8, 0.216, 0.012, 0.004,
> 0.976, 0.067, 0.004, 0.004,
> 1.0, 0.067, 0.004, 0.004,
> ];
> 
> // Gradient palette "BlacK_Blue_Magenta_White_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var BlacK_Blue_Magenta_White = [
> 0.0, 0.0, 0.0, 0.0,
> 0.165, 0.0, 0.0, 0.176,
> 0.329, 0.0, 0.0, 1.0,
> 0.498, 0.165, 0.0, 1.0,
> 0.667, 1.0, 0.0, 1.0,
> 0.831, 1.0, 0.216, 1.0,
> 1.0, 1.0, 1.0, 1.0,
> ];
> 
> // Gradient palette "BlacK_Magenta_Red_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var BlacK_Magenta_Red = [
> 0.0, 0.0, 0.0, 0.0,
> 0.247, 0.165, 0.0, 0.176,
> 0.498, 1.0, 0.0, 1.0,
> 0.749, 1.0, 0.0, 0.176,
> 1.0, 1.0, 0.0, 0.0,
> ];
> 
> // Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var BlacK_Red_Magenta_Yellow = [
> 0.0, 0.0, 0.0, 0.0,
> 0.165, 0.165, 0.0, 0.0,
> 0.329, 1.0, 0.0, 0.0,
> 0.498, 1.0, 0.0, 0.176,
> 0.667, 1.0, 0.0, 1.0,
> 0.831, 1.0, 0.216, 0.176,
> 1.0, 1.0, 1.0, 0.0,
> ];
> 
> // Gradient palette "Blue_Cyan_Yellow_gp", originally from
> // http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html
> // converted for FastLED with gammas (2.6, 2.2, 2.5)
> var Blue_Cyan_Yellow = [
> 0.0, 0.0, 0.0, 1.0,
> 0.247, 0.0, 0.216, 1.0,
> 0.498, 0.0, 1.0, 1.0,
> 0.749, 0.165, 1.0, 0.176,
> 1.0, 1.0, 1.0, 0.0,
> ];
> 
> var rainbow = [
> 0, .2, 0, 0,
> .1, 1, 0, 0,
> .3, 1, 0.5, 0,
> .4, 0.5, 1, 0,
> .5, 0, 1, 0, 
> .55, 0, 1, .5,
> .6, 0, .5, 1, 
> .65, 0, 0, 1,
> .7, .5, 0, 1,
> .8, 1, 0, 1, 
> .9, .2, .1, .5,
> 1, .2, 0, 0
> ]
> 
> // Single array of defined cpt-city color palettes.
> // This will let us programmatically choose one based on
> // a number, rather than having to activate each explicitly
> // by name every time.
> //
> // This list of color palettes acts as a "playlist"; you can
> // add or delete, or re-arrange as you wish.
> // Count of how many cpt-city gradients are defined:
> var allPalettes = [
> rainbow,
> rainbow,
> rainbow,
> rainbow,
> rainbow,
> ib_jul01,
> es_vintage_57,
> es_vintage_01,
> es_rivendell_15,
> rgi_15,
> retro2_16,
> Analogous_1,
> es_pinksplash_08,
> es_pinksplash_07,
> Coral_reef,
> es_ocean_breeze_068,
> es_ocean_breeze_036,
> departure,
> es_landscape_64,
> es_landscape_33,
> rainbowsherbet,
> gr65_hult,
> gr64_hult,
> GMT_drywet,
> ib15,
> Fuschia_7,
> es_emerald_dragon_08,
> lava,
> fire,
> Colorfull,
> Magenta_Evening,
> Pink_Purple,
> Sunset_Real,
> es_autumn_19,
> BlacK_Blue_Magenta_White,
> BlacK_Magenta_Red,
> BlacK_Red_Magenta_Yellow,
> Blue_Cyan_Yellow,
> ];
> 
> var currentPalette
> 
> export function triggerRandomPalette() {
> currentPalette = floor(random(allPalettes.length))
> setPalette(allPalettes[currentPalette]);
> }
> 
> export function showNumberPalette() {
> return currentPalette
> }
> 
> ```

[/uploads/default/original/2X/1/14d1a63a07f785f157463d94e847d448cd691e50.mp4](https://forum.electromage.com/uploads/default/original/2X/1/14d1a63a07f785f157463d94e847d448cd691e50.mp4)

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 12, 2024, 10:42pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/10 "2024-05-12T22:42:44Z")

</div>

Love this! One of my favorite things about this forum has always been the way we play with and improve each other’s ideas. A few of the things I think are coolest about this:

- use of paint and palettes greatly speeds up color comparison vs hypot3(), and cuts down color storage size.
- saved neighborhood map! I thought about doing this, pretty much exactly this way with 2 neighbors per element, but was scared off by the potential storage cost. Glad to be wrong!
- dynamic renderer swapping. Great visualization during the mapping stage!

---

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 12, 2024, 11:48pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/11 "2024-05-12T23:48:09Z")

</div>

Wow, brilliant!

I’m busy with Mother’s Day activities today, but I’ll get a video of this running on the real thing tomorrow.

---

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 13, 2024, 2:44pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/12 "2024-05-13T14:44:54Z")

</div>

Here it is running on a Fibonacci256 HDR, quite quickly! 🤩

[/uploads/default/original/2X/2/2a7c1b1ec6a77474523efbae03a5d617954837e0.mp4](https://forum.electromage.com/uploads/default/original/2X/2/2a7c1b1ec6a77474523efbae03a5d617954837e0.mp4)

---

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 13, 2024, 3:13pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/13 "2024-05-13T15:13:57Z")

</div>

Lux Lavalier & Heart pendants:

[/uploads/default/original/2X/e/e15c3a30b6b63693e1e52a98dfdf6e5d23f02eb5.mp4](https://forum.electromage.com/uploads/default/original/2X/e/e15c3a30b6b63693e1e52a98dfdf6e5d23f02eb5.mp4)

---

<div class="post-metadata">

**Author:** ![JasonCoon](https://forum.electromage.com/user_avatar/forum.electromage.com/jasoncoon/32/2315_2.png) [@JasonCoon](https://forum.electromage.com/u/JasonCoon)\
**Post date:** [May 13, 2024, 3:15pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/14 "2024-05-13T15:15:20Z")

</div>

My shrine wall of Fibonacci & PixelBlaze:

[/uploads/default/original/2X/b/b5065471ed5187952505f618fb1a78f8104e4e78.mp4](https://forum.electromage.com/uploads/default/original/2X/b/b5065471ed5187952505f618fb1a78f8104e4e78.mp4)

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 13, 2024, 3:56pm UTC](https://forum.electromage.com/t/challenge-idea-all-the-colors-rainbow-smoke/3531/15 "2024-05-13T15:56:04Z")

</div>

Love the shrine wall!
