Slow Refresh & infrequent Freezing

The FPS has increased to 4.94 but I shouldn’t be so focused on 1 pattern to make a global decision. I’m seeing higher FPS on other cool patterns so, once I get my final 260 strips, I’ll reassess and determine the project’s fate. Having said that, if there’s a simple fix…I do love that pattern…was that your creation?

Not mine. But I’m a math/code geek who thinks in patterns which is why I gravitate towards this stuff and see how to work with it.

If there was a way to switch maps easily, or if we had a map with 4 dimensions (X,Y,R,A)… (Nudge to @wizard)

I’ll flesh out things later. I find Polar math quite cool. @jeff just did a neat Xmas topper using it, video and all.

2 Likes

I saw that…very cool!

So I grabbed my 576 pixel panel, and did some quick testing. Running the Angle pattern ran at consistently under 12FPS. Then I essentially neutered the math heavy bits (I removed all of the XY->RA(aka polar) function calls) so it would behave as if it was given RA values instead of XY values. And the FPS ran at over 16FPS consistently.

That’s for 576 pixels, which is already a heavy load. I’m sure for 2000+, it’s worse.

The slow speed is the XY->RA conversion math.
Especially because built in atan2 is broken so we have to use code to do it correctly.

If you want to do polar looking patterns, use a polar map. Especially with lots of pixels.

I’ll do up a polar->XY function and compare how the speed to do that conversion is, but as I said it’s just a sin or cos conversion so likely quite fast. It might worth doing any mapping in polar and using that for your XY pattern(s) rather than using XY mapping and converting to polar for polar patterns, if you have lots of pixels. For small numbers of pixels, it’s probably fine, but it’s a scale problem: the esp8266 just isn’t fast enough to do the math over and over and keep up a good framerate.

If you have the pattern memory for it, precompute angle into an array. With the bit shifting tricks you can store a few 8 bit values per element.

1 Like

Well you just flew past me like an F-16 on a critical mission (haha). Unfortunately higher level math is not my strong point so I’m struggling to translate in my brain. I’m using the map code below you created and am getting close to 9 FPS with OE and dividing the staff into 2 (4 zig zag strips of 260 each). Are you suggesting a change to the map code or a change to the pattern code? Thanks for taking the time to look at this and I need to find a way to bolster my programming skills which should be fun.

function (pixelCount) {
  var map = [];
  var strands = 8;
  var pixPerRing = 260;
    for (strand = 0; strand < strands; strand++) {
      for (i = 0; i < pixPerRing; i++) {
        c = strand / strands * Math.PI * 2
        if (strand % 2) {
          map.push([Math.cos(c), Math.sin(c), i ])
        } else {
          map.push([Math.cos(c), Math.sin(c), pixPerRing -1 - i ])
        }
      }
    }
  return map
}

Heh, so what your mapping code does is this (in English):

For each strand
Figure out the angle of the strand (value C)
Use angle to calculate X and Y
If it’s a even or odd strand, we calculate the height (Z) up or down from i (which is the pixel #)

So you have Angle, and by default, you are using a fixed radius (call it one)

Doing it as a 3d map, it gets more complex, but let’s do 2D for a sec…

  • x = r × cos( θ )
  • y = r × sin( θ )

If r is 1, then X is cos(angle), and Y is sin(Angle), and behold that’s exactly what you are calculating and adding into the map: X, Y

So you already have Angle and Radius and you are throwing them out, as you map for X+Y
And then in the Angle pattern, you are spending a majority of the time figuring out Angle and Radius again… From X and Y via square roots and tangent math. (Again, see the link above)

Z complicates it because in polar, Z is another angle (think up/down angle) but it also changes the radius (you need a bigger radius to reach higher or lower)

More in a bit. On my phone, typing sucks.

Is that clear why it’s slow?

I was talking about @Scruffynerf’s suggestion that the angle + radius calculations take a lot of time, true it’s extra work, and you can put arbitrary data into the pixel map. The map doesn’t have to be cartesian x,z,y coordinates, could be any value you want, like angle, radius, and height.

My suggestion was a compromise, letting you keep your map as-is, but a way to speed things up in the pattern by doing the math and storing pre-computed values in an array.

I will try to make an example later unless someone beats me to it :slight_smile:

1 Like

To be clear, @wizard is suggesting a change to the pattern code (precompute the angles so it’ll do it once, and stash it all into an array, so you don’t keep doing repetitive math over and over).
That’s a good solution. The other part of his suggestion is to smush it tightly, to save space, given limited memory.

I’m suggesting a different solution, by figuring out if a polar map is better for your use case than a XYZ map. They are very different maps…

For example: If you want to do a bouncing ring where it goes up and down the staff, an XYZ pattern is great: light up all of the same Z pixels and it’s a ring… Change what Z is and the ring moves up and down.

That’s much harder to do with polar: it’s where all of the radius is the same (but that also lights up second ring, think top and bottom…), And as you reduce radius, it’ll move the ring(s) toward each other… But at the middle, radius is 1… If you reduce radius lower than one, nothing lights up.
If you want just one ring, you need to only light up the ones with an “Z” angle above the horizon (vs the ones below it)

See how doing it is WAY more complex than the XYZ solution?

Different things are easier or harder depending on your mapping.

You just happened to find a slow pattern where by doing the math, you are essentially remapping by hand on every refresh and that’s The Slow Part

With 20 or even 200 pixels, that’s probably ok
With 2000, it’s not.

An ability to do a 6 element array mapping would be great: X,Y,Z,R,A,H
(Where H is the second angle off the Horizon, ie up/down)
But right now with 2D and 3D as the limit, you have to pick and choose. For most patterns out there, XY, for less XYZ, for a handful RA, for a tiny number RAH.

Given that it’s normalized to 0…1, I’m not sure how (or if) we could push 6 dimensions into 3 values. Sounds like advanced physics to me. :laughing:

One of the nicest things about this math is that many have trod these paths:

That discusses 3 systems

  • Cartesian aka XYZ
  • Cylindrical (which is actually what your staff is)
  • Spherical aka RAH (radius plus 2 angles)

Cylindrical is RAZ, it’s a hybrid of the other two.
And your mapping could easily be this. Just stop converting to XYZ and store 1,C,Z (radius,angle,height)

That might be the best way to go, IF you have some helper functions you have to add to most patterns to give you the XY back (which as I showed above are pretty trivial, x = r*cos( a ) )

1 Like

Here’s a modification of your map that is 2D with angle and height. I don’t add radius since that is constant. The key part is that instead of passing angle to Math.sin/cos to calculate the x and y, we just use the angle, which is based on the strand number. Since the value is normalized to 0-1 anyway, we can just use the strand number itself. This ends up looking like a 2D matrix if you unwrapped and laid the strands flat.

There is one small issue, the angle will be normalized to 0-1 as I mentioned, but in terms of a circle’s angle 0 and 360 are the same location, so strand 0 and strand 7 would be effectively overlapping at 0 degrees and 360 degrees. You could correct for this by applying a scale factor. You really want each strand to be 1/8th of a circle apart, not 1/7th. So you could take angle and multiply by 7/8 in your pattern before using it, but I’ll show you another hack magic trick.

The pixel map can contain pixels that don’t exist, and these are still taken into account when it normalizes the coordinates. So if we provide an extra point where the 0th strand would be if it was repeating, it will space it out for you and save a little work in the pattern.

function (pixelCount) {
  var map = [];
  var strands = 8;
  var pixPerRing = 260;
    for (strand = 0; strand < strands; strand++) {
      for (i = 0; i < pixPerRing; i++) {
        angle = strand
        if (strand % 2) {
          map.push([angle, i ])
        } else {
          map.push([angle, pixPerRing -1 - i ])
        }
      }
    }
    
  //magic trick to prevent normalization from overlapping strand 7 and 0
  //this adds an undrawn pixel to the map
  map.push([strands, 0]) 
  return map
}

With this, you can use render2D(angle, height) (you can name the render2D parameters anything you like).

2 Likes

Yeah, I was mulling over how the 0…1 normalization means that some of these alternatives end up mapping similarly. I’ll brain dump later. But a Cylindrical map where R is constant (an even cylinder, not a cone), then Y(A) and Z are almost equal to just doing X and Y for all practical purposes.

I’m ready everything very slow so that my human memory doesn’t overload…I’ll get there…just doing a bit of simplification soldering…I’m not bad at that!

Thanks for the revised map @wizard and thanks to both of you for the very educational information above. I’m starting to wrap my brain around this and it’s actually making sense. Still a long way to go but it’s progress and that’s a good thing!

3 Likes