Matrix animations on FastLED LED lamp with all jumbled up LED's

I built this LED lamp project where i put 126 ping pong balls with ws2812B leds inside, into a glass vase. I had no previous knowledge of fastled prior to this. The balls are all jumbled up and leds are in no apparent sequence any longer. In the code I created a 3d array where I assigned each led a position in the matrix by taping a grid on the outside of the vase and selecting each led with a built in potentiometer and reading the led number from the serial monitor.

Here is a picture and a video for better explanation:
photos.app.goo.gl/xsFiWVQpatBiHzLd8, photos.app.goo.gl/4HT4fQy6AcTcmuyp9

I now have this 3d array:

int led_zyx[3][7][13] =
{
  { {92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92},
    {92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92},
    {78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78},
    {65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65},
    {40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40},
    {24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  },
  {
    {124, 124, 124, 124, 110, 119, 119, 116, 116, 108, 108, 108, 117},
    {92, 89, 89, 107, 104, 104, 110, 116, 105, 108, 108, 106, 106},
    {85, 84, 84, 77, 98, 82, 97, 97, 75, 92, 91, 85, 85},
    {34, 73, 73, 60, 60, 82, 65, 65, 45, 62, 72, 34, 34},
    {38, 32, 50, 46, 47, 40, 51, 40, 29, 29, 38, 38, 38},
    {0, 17, 17, 46, 25, 25, 31, 31, 10, 29, 23, 23, 23},
    {2, 2, 0, 0, 0, 1, 1, 0, 0, 10, 2, 2, 2}
  },
  {
    {113, 118, 114, 109, 125, 115, 121, 120, 120, 123, 112, 112, 117},
    {95, 89, 103, 83, 83, 99, 100, 122, 111, 101, 96, 106, 102},
    {84, 94, 81, 80, 83, 88, 79, 70, 87, 86, 96, 90, 93},
    {74, 67, 55, 68, 76, 56, 69, 57, 61, 71, 58, 63, 74},
    {54, 49, 26, 37, 42, 41, 36, 43, 52, 44, 33, 59, 66},
    {18, 19, 13, 20, 25, 25, 21, 30, 22, 35, 39, 33, 27},
    {14, 8, 3, 9, 4, 4, 12, 16, 5, 11, 15, 28, 7}
  },
};

the matrix is wrapped around the center of the vase where z0 are the innermost pixels and z2 are the outermost pixels.

I already managed to create of all kinds of cool modes like that - except of using the outside of the lamp/vase (z2) as an XY matrix to draw animations on it.
It would be a comfortable 13x7 pixels to play around with, but I cant quite figure out how to address it properly to display graphics, text or pixel animations, or using the blur effect on it.

Could anyone give me a pointer how to approach this? I looked into XYmatrix and SmartMatrix, but they require the leds to be laid out in a specific order - not in that random way mine ended up.

Thank you for your help

Well done of what you have produced so far.

What you need is a look up table to translate a regular matrix into your own matrix. I am not sure if that is what you have already though.

You would define your text or graphics in therms of a regular xy grid and then apply the look up table to each point to get the pixels you need to light up..

So if I would like to use this simple example here:

#include <FastLED.h>

#define LED_PIN  3

#define COLOR_ORDER GRB
#define CHIPSET     WS2811

#define BRIGHTNESS 64

// Helper functions for an two-dimensional XY matrix of pixels.
// Simple 2-D demo code is included as well.
//
//     XY(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             No error checking is performed on the ranges of x and y.
//
//     XYsafe(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             Error checking IS performed on the ranges of x and y, and an
//             index of "-1" is returned.  Special instructions below
//             explain how to use this without having to do your own error
//             checking every time you use this function.  
//             This is a slightly more advanced technique, and 
//             it REQUIRES SPECIAL ADDITIONAL setup, described below.


// Params for width and height
const uint8_t kMatrixWidth = 16;
const uint8_t kMatrixHeight = 16;

// Param for different pixel layouts
const bool    kMatrixSerpentineLayout = true;
// Set 'kMatrixSerpentineLayout' to false if your pixels are 
// laid out all running the same way, like this:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//     .----<----<----<----'
//     |
//     5 >  6 >  7 >  8 >  9
//                         |
//     .----<----<----<----'
//     |
//    10 > 11 > 12 > 13 > 14
//                         |
//     .----<----<----<----'
//     |
//    15 > 16 > 17 > 18 > 19
//
// Set 'kMatrixSerpentineLayout' to true if your pixels are 
// laid out back-and-forth, like this:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//                         |
//     9 <  8 <  7 <  6 <  5
//     |
//     |
//    10 > 11 > 12 > 13 > 14
//                        |
//                        |
//    19 < 18 < 17 < 16 < 15
//
// Bonus vocabulary word: anything that goes one way 
// in one row, and then backwards in the next row, and so on
// is call "boustrophedon", meaning "as the ox plows."


// This function will return the right 'led index number' for 
// a given set of X and Y coordinates on your matrix.  
// IT DOES NOT CHECK THE COORDINATE BOUNDARIES.  
// That's up to you.  Don't pass it bogus values.
//
// Use the "XY" function like this:
//
//    for( uint8_t x = 0; x < kMatrixWidth; x++) {
//      for( uint8_t y = 0; y < kMatrixHeight; y++) {
//      
//        // Here's the x, y to 'led index' in action: 
//        leds[ XY( x, y) ] = CHSV( random8(), 255, 255);
//      
//      }
//    }
//
//
uint16_t XY( uint8_t x, uint8_t y)
{
  uint16_t i;
  
  if( kMatrixSerpentineLayout == false) {
    i = (y * kMatrixWidth) + x;
  }

  if( kMatrixSerpentineLayout == true) {
    if( y & 0x01) {
      // Odd rows run backwards
      uint8_t reverseX = (kMatrixWidth - 1) - x;
      i = (y * kMatrixWidth) + reverseX;
    } else {
      // Even rows run forwards
      i = (y * kMatrixWidth) + x;
    }
  }
  
  return i;
}


// Once you've gotten the basics working (AND NOT UNTIL THEN!)
// here's a helpful technique that can be tricky to set up, but 
// then helps you avoid the needs for sprinkling array-bound-checking
// throughout your code.
//
// It requires a careful attention to get it set up correctly, but
// can potentially make your code smaller and faster.
//
// Suppose you have an 8 x 5 matrix of 40 LEDs.  Normally, you'd
// delcare your leds array like this:
//    CRGB leds[40];
// But instead of that, declare an LED buffer with one extra pixel in
// it, "leds_plus_safety_pixel".  Then declare "leds" as a pointer to
// that array, but starting with the 2nd element (id=1) of that array: 
//    CRGB leds_with_safety_pixel[41];
//    CRGB* const leds( leds_plus_safety_pixel + 1);
// Then you use the "leds" array as you normally would.
// Now "leds[0..N]" are aliases for "leds_plus_safety_pixel[1..(N+1)]",
// AND leds[-1] is now a legitimate and safe alias for leds_plus_safety_pixel[0].
// leds_plus_safety_pixel[0] aka leds[-1] is now your "safety pixel".
//
// Now instead of using the XY function above, use the one below, "XYsafe".
//
// If the X and Y values are 'in bounds', this function will return an index
// into the visible led array, same as "XY" does.
// HOWEVER -- and this is the trick -- if the X or Y values
// are out of bounds, this function will return an index of -1.
// And since leds[-1] is actually just an alias for leds_plus_safety_pixel[0],
// it's a totally safe and legal place to access.  And since the 'safety pixel'
// falls 'outside' the visible part of the LED array, anything you write 
// there is hidden from view automatically.
// Thus, this line of code is totally safe, regardless of the actual size of
// your matrix:
//    leds[ XYsafe( random8(), random8() ) ] = CHSV( random8(), 255, 255);
//
// The only catch here is that while this makes it safe to read from and
// write to 'any pixel', there's really only ONE 'safety pixel'.  No matter
// what out-of-bounds coordinates you write to, you'll really be writing to
// that one safety pixel.  And if you try to READ from the safety pixel,
// you'll read whatever was written there last, reglardless of what coordinates
// were supplied.

#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
CRGB leds_plus_safety_pixel[ NUM_LEDS + 1];
CRGB* const leds( leds_plus_safety_pixel + 1);

uint16_t XYsafe( uint8_t x, uint8_t y)
{
  if( x >= kMatrixWidth) return -1;
  if( y >= kMatrixHeight) return -1;
  return XY(x,y);
}


// Demo that USES "XY" follows code below

void loop()
{
    uint32_t ms = millis();
    int32_t yHueDelta32 = ((int32_t)cos16( ms * (27/1) ) * (350 / kMatrixWidth));
    int32_t xHueDelta32 = ((int32_t)cos16( ms * (39/1) ) * (310 / kMatrixHeight));
    DrawOneFrame( ms / 65536, yHueDelta32 / 32768, xHueDelta32 / 32768);
    if( ms < 5000 ) {
      FastLED.setBrightness( scale8( BRIGHTNESS, (ms * 256) / 5000));
    } else {
      FastLED.setBrightness(BRIGHTNESS);
    }
    FastLED.show();
}

void DrawOneFrame( byte startHue8, int8_t yHueDelta8, int8_t xHueDelta8)
{
  byte lineStartHue = startHue8;
  for( byte y = 0; y < kMatrixHeight; y++) {
    lineStartHue += yHueDelta8;
    byte pixelHue = lineStartHue;      
    for( byte x = 0; x < kMatrixWidth; x++) {
      pixelHue += xHueDelta8;
      leds[ XY(x, y)]  = CHSV( pixelHue, 255, 255);
    }
  }
}


void setup() {
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
  FastLED.setBrightness( BRIGHTNESS );
}

where leds is meant to be in sequence. How would I apply that to my leds_zyx array?

where leds is meant to be in sequence. How would I apply that to my leds_zyx array?

So for each LED in the chain, the number that goes in the square braces leds[ ledNumber ] you would get from the leds_zyx[nZ][nY][nX] array.

That is the array point defined by leds_zyx[nZ][nY][nX] would contain a single value of ledNumber for that location.

So you have your pattern that you define by setting a pixel in z,y,x space and then that is looked up to give you where in the chain that is.

The only difficulty is defining the leds_zyx, but then you did it for two dimensions so it is basically the same.

I made an additional xy array for simplicity

nt leds_xy[7][13] =
{
  {113, 118, 114, 109, 125, 115, 121, 120, 120, 123, 112, 112, 117},
  {95, 89, 103, 83, 83, 99, 100, 122, 111, 101, 96, 106, 102},
  {84, 94, 81, 80, 83, 88, 79, 70, 87, 86, 96, 90, 93},
  {74, 67, 55, 68, 76, 56, 69, 57, 61, 71, 58, 63, 74},
  {54, 49, 26, 37, 42, 41, 36, 43, 52, 44, 33, 59, 66},
  {18, 19, 13, 20, 25, 25, 21, 30, 22, 35, 39, 33, 27},
  {14, 8, 3, 9, 4, 4, 12, 16, 5, 11, 15, 28, 7}
};

ThenI replaced

uint16_t XY( uint8_t x, uint8_t y)
{
  uint16_t i;

  if ( kMatrixSerpentineLayout == false) {
    i = (y * kMatrixWidth) + x;
  }

  if ( kMatrixSerpentineLayout == true) {
    if ( y & 0x01) {
      // Odd rows run backwards
      uint8_t reverseX = (kMatrixWidth - 1) - x;
      i = (y * kMatrixWidth) + reverseX;
    } else {
      // Even rows run forwards
      i = (y * kMatrixWidth) + x;
    }
  }

  return i;
}

with

uint16_t XY( uint8_t x, uint8_t y)
{
  uint16_t i;

  i = leds_xy[y][x];

  return i;
}

and look at that:

It works!

Thank you so much!
I should probably cancel everything for the weekend...

So whatever you do - don't turn the jar over! :astonished:

yea I really dont need to do this whole mapping thing ever again. That was some serious manual work.
On the other hand I could finally have a closer look at those 3-4 pixels that for some reason dont emit green... of course they all have to be at the very bottom of the pile :confused:

I had a mind earlier to suggest that you could/ should have a separate program to construct the matrix for you as you move the pixels around and print the final version to the serial monitor from where you can insert it to your working code.

It could allow you to "place" a given pixel in the matrix, "nudging" previously defined occupants aside as necessary. More coding but faster results. :grinning: