Multimap multi-pattern - it's alive!

I realize this definitely needs some documentation. I could see someone saying “multimap? What’s that?” for example.

I’m defining a map as “a set of points”.

You’d still need to build a single mapping in PB, whether by explicit points, math, or whatever, and let it get normalized into 0…1 on all axes. (Axies? Axis?)

Then you define a submap, which in most of my example is either static math (all points where X is less than some number, for example), OR math involving variables, which can be changing, so for example, maps that shift in time. You could also use a set of values, like a array, but I didn’t feel like adding one, as it’s just a hardcoding of matching math values.

I’ll document this more, do some clean up, and post it as an example. I need to populate my GitHub with this stuff anyway. Plus I’ll stick not quite so horrifically busy example into the pattern library.

I did come up with a neat idea to use this, using @ChrisNZ 's spirals… Imagine multiple spirals, coming and going, and sliding around. So instead of one small spiral in the corner like the above, a handful of spirals changing sizes and origins.

1 Like

From looking at the code, it almost feels like painting layers with masks.

1 Like

That’s an excellent analogy.

Awesome proof-of-concept! I particularly like the idea of layering and having multiple maps. It’s a great toolbox for wall or room-sized installations with odd shapes, multiple panels and whatever else one might want to build.

(Also, I think this satisfies the “wall art” project requirement – I could see a big version hanging a modern art museum. Yeah, it’s a little over-the-top, but you can’t help reacting to it, and that’s pretty much what art is for!)

This is brilliant. Some day when I grow up I may catch up with y’all. But I doubt it… not it this life time anyway!.Thank you so much for taking the lead and providing great inspiration!

1 Like

To clarify, I think this is brilliant because it speaks to great potential, layering and juxtaposing patterns… that fuels creativity and imagination.

And that’s a very good thing.

2 Likes

Ok, so two months later and I haven’t found the time to make this more “friendly” yet. Now on my shorter Todo list, so hopefully next week.

1 Like

Also posted to pattern library. The version above uses more features, but is way more complex. This is a cleaned up, basic and simple version, for new folks to learn from.

/*
  Multiple SubMaps with multiple patterns v1.0 - by ScruffyNerf
  inspired by various code bits by zranger, Jeff Vyduna and Ben Hencke  

  Very Simple demo version, a truly minimal example (2 ranges, 2 patterns)
  There are lot of ways to do what this pattern does without using multiple maps, it's for building on.

  Requirements: Matrix with a 2D map
*/

//  these functions can take index,x,y and modify a set of globals to use instead
//     isInMap is set to true, if the pixel is in this submap
//     NewIndex is the "new" index value, if any [NOT USED IN EXAMPLE]
//     NewPixelCount is the pixelcount of the new map, if any [NOT USED IN EXAMPLE]
//     NewX is the new value of x for that pixel, might be renormalized, might not, still in range of 0..1
//     NewY is the new value of y for that pixel, might be renormalized, might not, still in range of 0..1
var isInMap
var NewIndex = 0;
var NewPixelCount = 0;
var NewX,NewY

// These are the map functions it will consider.
// Order matters, it will consider only the FIRST return of true
// This allows overlapping maps, with the first (0) being "on top", 
// and potentially this also allows a background/default map (matching none of the submaps listed)
var mapCount = 2 // number of maps+patterns
var maps = array(mapCount)
maps[0] = inmap0
maps[1] = inmap1
// maps[2] = your map functions go here

function inmap0(index, x, y) {
  centerx = x - 0.5;
  centery = y - 0.5;
	// circle from center of matrix
  if (centerx * centerx + centery * centery < 0.04) { 
    isInMap = true;
    NewX = centerx;
    NewY = centery;
  }
}

function inmap1(index, x, y) {
  if (x < .5 && y < .5) {
    isInMap = true;
  }
}

// if you are learning how this works, I recommend adding a new map first, and experimenting...
// then add a new pattern  (You'll want to just copy a pattern for the new map.  Map # matches Pattern # for simplicity )

var patterns = array(mapCount)
patterns[0] = pulseBlue
patterns[1] = pulseGreen
// patterns[2] = your pattern function(s) goes here

function pulseBlue(index,x,y){
  hsv(.6, 1, wave(time(.02)))
}

function pulseGreen(index,x,y) {
  hsv(.3, 1, wave(time(x*y+.03)))
}

export function render2D(index, x, y) {
  matched = false;
  NewPixelCount = pixelCount;
  NewIndex = index;
	NewX = x;
	NewY = y;
  for (var p = 0; p < mapCount; p++) {
    isInMap = false;
    maps[p](index, x, y);
    if (isInMap) {
      patterns[p](NewIndex,NewX,NewY);
      matched = true; // to avoid turning light off
      p = mapCount;  // stop the for loop
      }
  }
  if (matched == false) {
		// no match, no light
		hsv(0,0,0)
  }
}

// just so it'll render in 1D
export function render(index) {
    render2D(index, index / pixelCount, 0)
}
2 Likes

How can i display different patterns on different sections of my map…ie split the y into several defined segments and have patterns play only on those segments… with a pattern that plays over all ???

It’s all math, so decide where the Y breaks are, maybe Y < .25, or abs(Y-.5)<.1 (ie between .4 and .6), etc.

The examples try to make it clear… Play with it.
As for a background pattern, instead of the HSV(0,0,0), call a pattern there.

Post your code, working or not!

1 Like

where can i find out hat these symbols mean for example…< and abs ect… i know their math functions but i dont know what alot of them mean and im sure this would help :relaxed:

It’s all basically JavaScript. So any good tutorial on JavaScript will help you.

https://www.w3schools.com/js/js_comparisons.asp

1 Like

well this seems to work, 5 patterns running in different zones of the Y. im not sure how to implement more complex patterns or the audio reactivity… but this is good progress. also the last pattern doesnt show up…im wandering how to allow the last pattern thru in the dark moments of the previous patterns.

/*
  Multiple SubMaps with multiple patterns v1.0 - by ScruffyNerf
  inspired by various code bits by zranger, Jeff Vyduna and Ben Hencke  

  Very Simple demo version, a truly minimal example (2 ranges, 2 patterns)
  There are lot of ways to do what this pattern does without using multiple maps, it's for building on.

  Requirements: Matrix with a 2D map
*/

//  these functions can take index,x,y and modify a set of globals to use instead
//     isInMap is set to true, if the pixel is in this submap
//     NewIndex is the "new" index value, if any [NOT USED IN EXAMPLE]
//     NewPixelCount is the pixelcount of the new map, if any [NOT USED IN EXAMPLE]
//     NewX is the new value of x for that pixel, might be renormalized, might not, still in range of 0..1
//     NewY is the new value of y for that pixel, might be renormalized, might not, still in range of 0..1
var isInMap
var NewIndex = 0;
var NewPixelCount = 0;
var NewX,NewY
var t1
// These are the map functions it will consider.
// Order matters, it will consider only the FIRST return of true
// This allows overlapping maps, with the first (0) being "on top", 
// and potentially this also allows a background/default map (matching none of the submaps listed)
var mapCount = 5 // number of maps+patterns
var maps = array(mapCount)
maps[0] = inmap0
maps[1] = inmap1
maps[2] = inmap2
maps[3] = inmap3
maps[4] = inmap4

export function beforeRender(delta) {
  t1 = time((.5) / 65.536) // From 0…1 every 4 seconds

}
// maps[2] = your map functions go here

function inmap0(index, x, y) {
  if (x < 1 && y > .99) {
    isInMap = true;
  }
}

function inmap1(index, x, y) {
  if (x < 1 && y > .88 ) {
    isInMap = true;
  }
}
function inmap2(index, x, y) {
  if (x < 1 && y > .8) {
    isInMap = true;
  }
}
function inmap3(index, x, y) {
  if (x < 1 && y > .12) {
    isInMap = true;
  }
}
function inmap4(index, x, y) {
  if (x < 1 && y > .00) {
    isInMap = true;
  }
}

// if you are learning how this works, I recommend adding a new map first, and experimenting...
// then add a new pattern  (You'll want to just copy a pattern for the new map.  Map # matches Pattern # for simplicity )

var patterns = array(mapCount)
patterns[0] = pulseBlue
patterns[1] = pulseGreen
patterns[2] = pulseRed
patterns[3] = pulseviolet
patterns[4] = pulsecf
// patterns[2] = your pattern function(s) goes here

function pulseBlue(index,x,y){
  hsv(.6, 1, wave(time(.02)))
}

function pulseGreen(index,x,y) {
  hsv(.3, 1, triangle(time(.04)))
}
function pulseRed(index,x,y) {
  hsv(.01, 1, wave(time(.008)))
}
function pulseviolet(index,x,y) {
  hsv(.8, 1, wave(time(.0008)))
}
function pulsecf(index,x,y) {
  hsv(.2, 1, wave(time(.001)))
}

export function render2D(index, x, y) {
  matched = false;
  NewPixelCount = pixelCount;
  NewIndex = index;
	NewX = x;
	NewY = y;
  for (var p = 0; p < mapCount; p++) {
    isInMap = false;
    maps[p](index, x, y);
    if (isInMap) {
      patterns[p](NewIndex,NewX,NewY);
      matched = true; // to avoid turning light off
      p = mapCount;  // stop the for loop
      }
  }
  if (matched == false) {
		// no match, no light
	



  v = clamp((triangle((y/0.5) - t1 + 0.5) -.5) * .5, 0, 1)
  
  hsv((t1 / 0.3)* 0.2, 1, v * v)
}
}

// just so it'll render in 1D
export function render(index) {
    render2D(index, index / pixelCount, 0)
}

So the X part isn’t needed (it’s always true, as you’ve written it…), And the Y part is always going to match that last one, since it’ll always be greater than zero. (I don’t believe map value go to an actual 0 or 1, just between those values)

If you want the “none of the above” option to work, you need to change things so that at times, none of those tests are true.

For example, make the X value check check to see if it’s .5 or less… Then half of that section will be “background”. Make another test check for X is greater than .5, and now you’ve got the background on the other side.

To make it run behind the “dark moments” is more complex:

You’d need to NOT match when the light wouldn’t be on as well… For example
wave(time(.0008) is used to decide the brightness of the violet section…
But if you add into inmap3() a check to see if wave(time(.0008) is currently greater than .25, so it fails, meaning the brightness would be between 0 (off) and .25 (pretty dim still) then it wouldn’t match that map, then…

However, inmap4 would match as true then…
Because you need to add a check to inmap4 for y to not be greater than .12 (so it won’t match inmap3’s area.

Hope this makes sense… Keep playing with it!

mmmm i think it makes sense but wouldnt this just turn off the map…? rather than allow another pattern to be visable thru the dark areas… so if i have a triangle wave running the black would effectively become see thru…a blending option

So the basic example isn’t meant to have multiple things on the same pixel, it’s set so that first to say “Mine!” gets the pixel.

My suggestion is to make the map check logic say “oh, even though the pixel is in my map, it’s not going to be lit, so NOT mine this time…”

You could rewrite this to have multiple maps share a pixel and blend colors/brightness…
But then you’d have to rewrite all of the patterns to not write directly (HSV) and instead return values as to what they’d like the pixel to be, merge these together into one result and then finally light the pixel. Very doable, but not done.

If we had a way (nudge nudge to @wizard) to read the current pixel’s info (and then merge new color data, perhaps), or some other method, more of the current way the pattern is set could be left intact. Or we could use a canvas array…

Added You could cheat it with 3 globals H,S,V and then set those in each pattern, merging existing values, and finally at the end, only then light the pixel via hsv(H,S,V). I might implement this.

But…

I wrote this to minimize changing pattern code.
Basically you can (as my more complex demo shows) do pretty fancy patterns all with a single pattern render function for each. Which could be a direct copy from an existing pattern you like.

I’ll mull over the best way to make this handle overlapping patterns, and probably write something up.

Right now, my suggestion is the best way approach this: make your logic smarter about when the map “gives up” the pixel to something else.

Keep in mind: this is an advanced technique, and you really need to understand how your pattern(s) work, and the mapping is thrown on top of all of that. My “kitchen sink” original example uses some map logic that change over time, which leads to a look like patterns are overlapping, but in reality the map itself is shifting so for those pixels, which map says “Mine!” Is really what changes.

1 Like

so im trying to figure out if i can basically point to the v in the hsv so that when the value falls below a certain point that the map match becomes false… i thought maybe i could do it in the inmap section…using something like:
function inmap4(index, x, y) {
if (x < 1 && y > .00) {
isInMap = true;
}
if (x < 1 && y > .00) {
isInMap = true;
}
else if (v <0.04){
isInMap = false;
}
but this comes up with the error that v is undefined…mmm not sure how or where to define it… or if this is even the right approach, command…i kinda feel like im fishing in the dark…and sooooooo much to learn

So there is no connection between your map and your pattern, and v is decided in the pattern…

Easy fix though… Copy the code that decides what v is into your map. For example

wave(time(.0008)) in Violet… So check that to see how bright it is…

  function inmap4(index, x, y) {
  v = clamp((wave((y/-0.1) - t1 + 0.5) -.5) * .5, 0, 1)
  if (x < 1 && y > .00) {
    isInMap = true;
     
  }
  
  if (v <0.02){
      isInMap = false;
    }

not the effect im looking to achieve but it is turning the map on and off… kinda creating an interesting pattern…effect…i should upload some video of this thing im playing with… :relaxed: :smiley: