The 16x16x16 cube is done....ish Now time to map. Photos, video, and mapping questions

So this will be the last cube post cause it’s functionally done. Here’s a bit of info behind it. Total hours are somewhere between 300-400. Who knows where in between. Total cost was 1800$ and would have been more if I hadn’t had a few friends that could give me a friend hook up on Acrylic, custom wood work, and 3D Printing. MaltWhiskey joked about not recommending making one of these and I have to agree with him. It was a second full time job from June until Mid-Oct. If I had kids and most adult obligations this probably would have taken a full year.

Anyways,I’m basing the mapping off of the one Ben nicely wrote for me last year, linked below, and I’m gonna attempt to map it myself but it’s MUCH more involved. Instead of 4 channels there’s 2 per tower and I’ve swapped the direction of the data wiring once it leaves the 1st plane around a bit but I’m at least semi-confident I can figure that portion of it out.

8x8x8 cube finished

One question I DO have that I keep getting confused about is that I have 4 PBs running it and I’m going to have to get Firestorm to sync them but separating each one to run a different portion of the pattern. So I need to modify the mapping for each PB and modify the code for each pattern on every Pixelblaze? I’ve got some decent practice in with getting familiar with code on the PB and adjusting values but this is way more advanced than what I’ve been dealing with.

I’ve done a bit of research through the forums and I came across this thread and this is good information

Configuring multiple PBs
I see Jeffs code in there and that seems like the correct choice, modified for my own purposes of course but it’s still a little murky how to implement (to me, it’s actually explained really well but code is still a struggle with me and will be for a while) Anyways, rambled on enough and not sure what other questions to ask without more info. Thanks for keeping me company on the way!



6 Likes

@Shakes999 - What an incredible accomplishment!!

Can’t wait to see it fully mapped.

I would think you can go with the “ghost pixel” approach for most volumetric patterns and get pretty great results without needing the custom code approach I wrote out in that thread.

You’ll still be able to clone most 3D patterns to all 4 PBs and not need to make any pattern code tweaks to the individual copies.

If I understand a “tower” correctly, it’s two upright 16x16 planes, and one Pixelblaze drives one tower using two output expander channels.

Let’s say you have a cube map that generates [x,y,z] from [0-15, 0-15, 0-1]. Even though each Pixelblaze will be configured for 512 LEDs, you’ll add one or two extra points to your map on each board. The extra points you add will be different for each board. Let’s assume Cartesian coordinates where as you look at the front of the cube, [0,0,0] is in the left-bottom-rear (farthest plane from the viewer).

The Pixelblaze driving the two furthest towers of [x, y, 0] and [x, y, 1] will have a map that you append one phantom pixel position to: [0,0,15].

The next Pixelblaze driving towers of [x, y, 2] and [x, y, 3] need two phantom pixels of [-2,0,0] and [14,0,0].

Does this make sense?

I think you got the gist of it! Just to clarify that we’re on the same page. A PB controls 4 different towers sectioned off in to essentially 4 different 8x8x8 cubes (well 8x8x16 technically, I tried to draw it badly on the top picture) and each tower has 2 channels and each channel controls half of a tower so each output expander (each PB has one) is full up 8 channels.

So I kinda what you’re saying! So with the each tower, I’m gonna have to adjust a phantom pixel twice. And that the phantom pixel is going increase over each tower? So that makes sense but implementing it, well, lets see how it goes.

And dumb question, but Firestorm will be what syncronizes the separate PBs just as long as all 4 are mapped the same? I guess I’m a little confused just what portion of this process of finishing mapping and firestorm will coordinate the PBs to work together? So for context, I had this exchange a while back.

I guess this is throwing me for the biggest loop, I don’t really understand where or how in the process things get split across each one.

Just bare with me, definitely out of my depth here but gonna figure it out.

OK - I really did a lot of mental war with the diagram and I think I have it. Can you tell me if these annotations are correct?




Understanding Firestorm's role

Firestorm doesn’t know anything about:

  1. How many LEDs are driven by each pixelblaze
  2. The map loaded on each
  3. The code that makes up any given patterns on any given Pixelblaze

Firestorm just knows:

  1. What Pixelblazes it can see on a WiFi network
  2. The list of patterns (by name, not code) on each of those Pixelblazes

And it can:

  1. Launch all patterns of a given name at once, across all the Pixelblazes it discovers
  2. Arrange those pattern names into a playlist and launch them automatically in sequence
  3. Make sure that whatever code is running in each Pixelblaze, the output of time(K) will be synchronized between them all.

So with that as background, to answer your questions:

Firestorm will synchronize which pattern is selected as active on all of the Pixelblazes, as well as the value that time() returns in those patterns. The maps can be different, and probably should be different, as each Pixelblaze will have different coordinates for the phantom pixels that nudge it’s towers into the 4 corners (as viewed from above).

Firestorm will only select one pattern for all four to run.

For most volumetric patterns, the code will be identical on each of the 4 Pixelblazes, but because the map specifies the LEDs for each Pixelblaze as having different coordinates, they will be crunching different pixel positions with the same code. By “volumetric” I mean patterns which use 3D renderers (they have a render3D function).

Depending on what you’re trying to achieve, the pattern code might need to be slightly modified on each of the four Pixelblaze. For patterns that only have a 1D renderer (render(index) in the examples), you’ll see four identical copies running. For something like Blinkfade, maybe that symmetry is distracting so you might modify each copy of the pattern code to use a slightly different random seed.

Given my refined understanding of your layout / routing, it’s more like you’ll need to add one phantom pixels to each Pixelblaze’s map.

As I understand it, each of the four Pixelblaze will be driving four towers. The map on one Pixelblaze will generate the pixel positions for all 512 LEDs in the four towers it’s driving.

I’m assuming you’ve read the mapping page and understand the global 0-to-1 normalized space that comes from a map and is fed into patterns’ renderers.

You’ll add one phantom pixels to a map that essentially squeezes and shifts that set of four towers into one quarter of the global (x, y, z) / (0-1, 0-1, 0-1) space.

Using the coordinates I defined in the annotations above, consider the map for PB1. You’ll add a phantom pixel around (1, 0, 1) (in normalized 0-1 global space), and that will shove the entire resulting co-ordinates for all PB1’s LEDs into the top-left quadrant as viewed from above. I know you might need to read that a few times. This shifted/squeezed map will result in normalized LED coordinates crunched by that Pixelblaze that only fall within (0-.5, 0-1. 0-.5). PB2 will only process it’s LED coordinates where (x, y, z) is within (.5-1, 0-1, 0-.5).

Phew, that’s a lot. I hope this helps!

2 Likes

Thank you! This helps a TON. I have read the mapping page and between that, this thread, and also the using the completed code from my 8x8x8, I’m pretty sure I got it handled now or at least eventually figure it out! The relationship between the PBs and the Firestorm was definitely what was giving me the most trouble. I had read the page which helped but in regards to mapping it was just a jumble in my brain.

Thank you for all the help Jeff! Will post some videos hopefully next post!

@jeff Hello! I’m working with Shakes on the mapping and programming side of this project IRL. I’m a bit new to mapping but not to programming, and I could use some guidance at some point in the relatively near future. I know the holidays are a rotten time for that, if/when you’ve got the bandwidth I would love to pick your brain somewhat. I’m testing parts of the 4-column system this week and weekend so that I can get my brain wrapped around what’s already been done.

Sure! I prefer to help in public if possible so that the other forum members can learn something and we build this up as a searchable support resource, so after you’ve read the stuff above and mapping docs, as well as tried it out a bit in the mapper tab, post here when you get stuck! I’ll try my best (holiday schedule allowing).

1 Like

Copy that, thank you! Wasn’t sure if maintaining the thread with new questions was appropriate, appreciate the prompt response :smiley:

@jeff SO! Back again! And was able to find someone to get this successfully mapped individually! So now I have the 4 of them working but now comes the part of getting them working together. So the person who mapped it used this code

function (pixelCount) {
  var mapx1=[0,1,2,3,3,2,1,0,0,1,2,3,3,2,1,0]
  var mapx2=[0,4,0,4,8,12,8,12,0,4,0,4,8,12,8,12]
  var mapy1=[3,3,3,3,2,2,2,2,1,1,1,1,0,0,0,0]
  var mapy2=[0,0,4,4,0,0,4,4,8,8,12,12,8,8,12,12]
  var mapz=[]
  for(var z=0;z<16;z++)  mapz.push(z/15)
  for(var z=15;z>=0;z--)  mapz.push(z/15)
  var map = []
  for (i = 0; i < pixelCount; i++) {
    n=Math.floor(i/16)+0  // pb1:0 pb2:64 pb3:128 pb4:192
    t1=n%16
    t2=Math.floor(n/16)
    z=mapz[i%32]
    y=mapz[mapy1[t1]+mapy2[t2]];
    x=mapz[mapx1[t1]+mapx2[t2]];
    map.push([x, y, z])
  }
  return map
} '

So the comment added in there was in theory supposed to offset them for that effect but it’s not producing any results (I did apply it to each individual PB with it’s respective number). So looking at his particular mapping, how would I go about changing this? I know Phantom PIxels are a thing but I’m not particularly sure how I would apply it under this setup.

Gettin closer!

Nevermind! And it’s done! This thread is officially finished and I can quit bothering everyone! Here’s the completed code with how the phantom pixel was applied!

function (pixelCount) {
  var mapx1=[0,1,2,3,3,2,1,0,0,1,2,3,3,2,1,0]
  var mapy1=[3,3,3,3,2,2,2,2,1,1,1,1,0,0,0,0]
  var mapx2=[0,4,0,4,8,12,8,12,0,4,0,4,8,12,8,12]
  var mapy2=[0,0,4,4,0,0,4,4,8,8,12,12,8,8,12,12]
  var mapz=[]
  for(var z=0;z<16;z++)  mapz.push(z/15)
  for(var z=15;z>=0;z--)  mapz.push(z/15)
  var map = []
  for (i = 0; i < (pixelCount-2); i++) {
    n=Math.floor(i/16)+0  // pb1:0 pb2:64 pb3:128 pb4:192
    t1=n%16
    t2=Math.floor(n/16)
    x=mapz[mapx1[t1]+mapx2[t2]];
    y=mapz[mapy1[t1]+mapy2[t2]];
    z=mapz[i%32]
    map.push([x, y, z])
  }
  map.push([0,0,0])
  map.push([1,1,1])
  return map
}
5 Likes

Wow! Congratulations! It’s totally gorgeous. I can’t imagine how you feel after SO many hours invested of soldering and construction.

1 Like

@Shakes999, amazing. Simply amazing. So much work into that, and what gorgeous results! I want to experiment with all kinds of 3D patterns on your cube now.

1 Like

You and me both!!! The 3D patterns are incredible on it! Infact, the only patterns I’ve found that don’t work are Coronal Mass inJection and Perlin fire which is weird but everything else looks great (They both work on the 1st section of the cube but crashes the other 3. It’s weird but I’ll figure it out after the burn)

I plan on finding a better video solution so I can get some decent videos of it. Everything just looks like a blob of light on camera phones. Example

1 Like

@Shakes999, if you get time, give this one a try and see if it works with all the mapping. It draws a spinning cube on your cube - just what you need, right? I’m really curious about how this sort of thing performs on your setup. (Which is completely epic, btw!)

Spinning Wireframe Cube Pattern
// Volumetric 3D wire-frame box!
//
// For volumetric displays only. Will probably not
// do the right thing on other display types!
//
// MIT License
// ZRanger1 1/2023


export var boxSize = 0.3;
export var edgeWidth = 0.0375;


// UI
export function sliderSize(v) {
  boxSize = 0.1+(v*v/4);
} 

export function sliderEdgeWidth(v) {
  edgeWidth = mix(0.025,0.08,v*v); 
}


// vector utilities
function max3(x,y,z) {
  return max(max(x,y),z);
}

function min3(x,y,z) {
  return min(min(x,y),z); 
}

// from https://iquilezles.org/articles/distfunctions/
// Returns signed distance to the edges of a box of size sz,
// with edge width e.
function wireframeBox( x,y,z,sz,e){
       px = abs(x)-sz;
       py = abs(y)-sz;
       pz = abs(z)-sz;
       
       qx = abs(px+e) - e;
       qy = abs(py+e) - e;
       qz = abs(pz+e) - e;   
       
       k1 = max3(px,qy,qz);  
       k2 = max3(qx,py,qz);
       k3 = max3(qx,qy,pz);
            
  return min(min(
      hypot(k1,0.0)+min(k1,0.0), 
      hypot(k2,0.0)+min(k2,0.0)),
      hypot(k3,0.0)+min(k3,0.0));
}

// tumble the box around a little
timebase = 0;
export function beforeRender(delta) {
  timebase = (timebase + delta /1000) % 3600;
  
  resetTransform();
  translate3D(-0.5,-0.5,-0.5);  
  rotateZ(timebase * 2);
  rotateX(timebase / 2); 
}

// I'm slightly cheesing the edge calculation here to gain a tiny
// bit of performance.Better math would have better anti-aliasing, but
// the resolution is so low that I think it looked better. YMMV.
export function render3D(index,x,y,z) {
  h = timebase + index/pixelCount
  hsv(h, 1, 1-wireframeBox(x,y,z,boxSize,edgeWidth)/edgeWidth)  
}

3D is easier to think about on a volumetric display like this than it is on a 2D screen - no ray projection to find the geometry, no perspective transform, no clipping planes, just, “If this pixel is part of the thing I’m drawing, light it up!”

Anyway, here’s a video of what it ought to look like, in this case using a single PB to drive a 10x10x10 cube.

2 Likes

Oh hell I’m gonna load that one up on land! We at least got hot spot out there. I got around 5-6 3D patterns but more are always welcome! I’ll shoot a video over tomorrow night when I get it all setup!

1 Like

I’m gonna think about how this could be antialiased!

1 Like

So i finally got the cube repaired after 2, 3 hour car rides, a well meaning friend “booping” it and frying 3 planes, and other assorted repairs.

And now im trouble shooting patterns! So thats zrangers pattern! Works fine and looks great!

However im running in to issues with 2 patterns and only these 2. Perlin fire, Coronal Mass Ejection. They work only on the first PB and it freezes PB 2-4. 2-4 are giving me a undefined symbol error despite it being the exact same code for PB 1 where it works.


Any thoughts?

Also will post some more videos!

Sinpulse 3D, Spiral twirls 3D, and Xorcery 3D!



2 Likes

setPerlinWrap wasn’t added to the language until version 3.30, so PBs 2 through 4 probably need to be updated to the latest firmware. Look for the “Updates” section on the Settings tab:

3 Likes

Omg DUH!!! Thank you!!!