Single moving pixel across a 2d map?

So I’m just starting to learn the coding for patterns, and I had an idea but I just don’t grok how to do it.
I have 3 8x8 panels in a horizontal row, and I wrote a quickie Python script to spit out the JSON array which I pasted into the mapper. So far, so good.
My idea was to plot a sine wave across the matrix with just a lone pixel illuminated for the current xy coordinate (ignoring for the moment the matrix y is 0 → 1 and a sin(x) is -1 → 1). When everything is floating-point numbers how do you do the equivalent of if ((x == t1) && (y == sin(x)) { pixelOn } else { pixelOff }?

I have a pattern very similar to this! Would you like a working example or are you in the mood for clues?

One thing I’ve found repeatably useful for things like this is a function that just returns “how close” two numbers are.

var halfwidthDefault = 0.125

// Returns 1 when a & b are proximate, 0 when they are more than `halfwidth`
// apart, and a gamma-corrected brightness for distances within `halfwidth`
function near(a, b, halfwidth) {
  if (halfwidth == 0) halfwidth = halfwidthDefault
  var v = clamp(1 - abs(a - b) / halfwidth, 0, 1)
  return v * v
}

Also, remember that wave() outputs a sinusoid from 0…1 given a 0…1 input, so at least you don’t even need to scale sin().

I don’t need to be annoyingly socratic - if you want some working code I’ll get it to you quick.

1 Like

Jeff is replying and I know his answer will be awesome.

The “PB” way is to do that test during the Render, as X changes from 0 to 1… (And with scaling and shifting, you can make -1 to 1 span across 0 to 1… (Example: multiply X (or Y, in your case) by 2, and now it’s 0 to 2… And then subtract 1 and it’s -1 to 1…)

That said you can also look at my post Waves upon waves for a different way to do it.

1 Like

Scruffy! So funny, I had never tried your waves upon waves, but the example I was going to offer is really similar - I love your sliders though, especially useful for different aspect ratio matrices.

This is mine:

basePeriod = 1.2 / 65.535
export function beforeRender(delta) {
  t1 = time(basePeriod)
  t2 = time(basePeriod * 1.05)
  t3 = time(basePeriod * 1.1)
}

export function render2D(index, x, y) {
  x /= 2
  y = 1.4 * y - .2
  r = near(y, wave(x - t1))
  g = near(y, wave(x - t2))
  b = near(y, wave(x - t3))
  rgb(r, g, b)
}

var halfwidthDefault = 0.4

// Returns 1 when a & b are proximate, 0 when they are more than `halfwidth`
// apart, and a gamma-corrected brightness for distances within `halfwidth`
function near(a, b, halfwidth) {
  if (halfwidth == 0) halfwidth = halfwidthDefault
  var v = clamp(1 - abs(a - b) / halfwidth, 0, 1)
  return v * v * v
}
2 Likes

The advantage of a “near” in this case, when it comes to line/pixel drawing, is that the closer you get to the desired value, the brighter the value. (I’m doing something similar with line thickness in my wave pattern…)

The secret sauce is taking the difference between your actual value and desired value (using abs() removes the minus sign if any) and then using that difference, which if it’s zero (exact match) brightness should be 1 (or greater), and then the larger the difference, the dimmer the brightness should go … Toward zero (or lower). So subtracting the difference from 1 works well here.

Clamping and other neat tricks help to keep that value consistent, in the above near(), while my waves freely allows values to go higher or lower than 0…1 intentionally so that I can decide which wave to keep in front (the brighter one, but it’s set to only check half the brightness value, to have it switch a bit more)

1 Like

Thanks for all the great answers, guys. I will take a look at all the various examples you gave me & see what I can come up with. Tomorrow, though. The pico driving my nanoleafs is set to turn off at 1 am as a reminder to go to bed and that’s coming up real soon. Messing around with Scruffy’s wavers upon waves makes me want to put connectors on the rest of the panels I have so I can make it bigger. :slight_smile:

1 Like

That’s awesome. What are you using for pixels here?

I’m using a 10x30 net of WS2811s. It’s $220 shipped and IMO worth it for the labor savings of assembling one myself!

https://m.aliexpress.com/item/32631430388.html?trace=wwwdetail2mobilesitedetail&spm=a2g0o.store_pc_allProduct.8148356.15.71cb6a3cvcd59O

1 Like

Ah ha, I was wondering how these work… Up close picture shows:

So basically take a few sets of bullets, add zipties. Yes, labor intensive but… Zipties are like $6-10 for 1000, and bullet strands are $12 for 50leds in bulk… So 300 led net is $12x6 = $72 + $8 in zipties… Or $80 in materials. So it comes down to your labor costs to zip up 300 zipties.

The $12-13 bullets (10 strands) might be 5V, though 12v aren’t much more. They have 3 inches of wire between them which seems low compared to the above… But it’ll make a tighter mesh then… Looking to see if longer wire lengths exist. I see some 4 inch lengths too

I’ve been trying to figure out how to make nearly anything you’d made work on my array for quite some time, but I cracked the code on this one!

I’m not entirely sure how to show this nicely on here, but this is the pattern I’m running. I just turned your render 2D into a render3D and played around a little bit in there and have gotten a pretty reliable interpretation of your pattern.

This is the first time with a PB that I tried to make something happen and the exact thing I wanted to happen happened! Very exciting.

basePeriod = 1.2 / 65.535
export function beforeRender(delta) {
t1 = time(basePeriod)
t2 = time(basePeriod * 1.05)
t3 = time(basePeriod * 1.1)
}

export function render3D(index, x, y,z ) {
x /= 1.5
y = z * z
z = x*y
r = near(y, wave(x - t1))
g = near(y, wave(x - t2))
b = near(y, wave(x - t3))
rgb(r, g, b)
}

var halfwidthDefault = .4

// Returns 1 when a & b are proximate, 0 when they are more than halfwidth
// apart, and a gamma-corrected brightness for distances within halfwidth
function near(a, b, halfwidth) {
if (halfwidth == 0) halfwidth = halfwidthDefault
var v = clamp(1 - abs(a - b) / halfwidth, 0, 1)
return v * v * v
}

3 Likes

I think it’s important to mention that wave() is literally sin() and not cos().

I expected it to be: wave(0) = 0 = wave(1) and wave(1/2) = 1

But no, it’s off by 1/4!

Maybe it just feels important because I spent hours trying to debug something, and the bug was my incorrect expectation. :relaxed:

[Edit: nope! See Zranger’s next post for the answer]

I’m away from a PB right now, but I could swear wave() has the same phase as cos(), no? wave(0)==wave(1)==1; wave(.5)==0

1 Like

just checked my PB3:
wave(0) == wave(1) == 0.5
wave(0.25) == 1
wave(0.75) = 0

Yes, @wizard confirmed that in the discussion on easing functions. It’s off from what you’d expect.

@jeff good to know I’m not the only one who expected that.

1 Like

If it wouldn’t break existing patterns, I’d even argue that changing wave() to behave “as expected” should happen, but since you can just make function waveasexpected(x) call wave (x-.25), it’s probably not worth “fixing” except in the documentation so it’s visible up front to newbies. (I don’t recommend doing the above of course, just subtract .25 directly, in most patterns)