Different ways to do the same thing

One thing I’m playing with, as I explore the many things a PB can do, is try different ways to do the same thing…

For example, drawing a line.

(Yes, yes, this means having a canvas, which itself can be done different ways)

I just implemented a version of this algo: https://jsfiddle.net/6x7t4q1o/5
It draws a continuous line (so it doesn’t do diagonal movement), from point A to point B.

Compare that to the Xiaolin Wu algorithm that @sorceror did. Different approach, similar but different. Pros and Cons to each.

Doing the Randogram pattern I just added, which implements 6 different algos for random, and really could do many more, some of which are different enough to warrant using in particular use cases, some of which aren’t worth using, and some of which are ‘good enough for most cases’ (built in random for example), makes me realize that part of making a good library of libraries, and even a set of tutorials that build up skills, is that you need to have a good firm ‘best practice’, but then again, the best practice might just be another way to do it… and the best way to figure out the best practice is implement a handful of ways and see what works well and is fast/easy/cheap (or whatever the 3 legged tripod is in the case of code)

I’ll likely do a pile of these ‘same thing done multiple ways’ patterns, just to flesh out my learning, and hopefully others will find them helpful.

You don’t have to have a canvas; look at “Animated Asterisks 2D” in the library which calculates an array of line endpoints in the preRender() function and in the render2D() function tests to see if the current point is contained within any of the lines in the array.

It’s not as fast as a good Bresenham, though…

1 Like

True, but doing an ‘on the fly’ like that during render only works for some limited cases.
There are plenty of canvas-less patterns, but the array based ones are basically a pseudo-canvas (1D, usually).

The whole ‘canvas’ discussion is another can of worms to be opened really, likely in a dedicated thread on it.

One of the things we ought to do is make a giant pile of clocks, since we already have several. Here’s my contribution – it draws antialiased lines using a kinda-optimized distance function - no backing buffer - and runs at roughly the same speed as the Wu clock.

// variables that describe the clock hands
var hourX,hourY,hourLen = 0.4, hourHue = 0.333;
var minuteX,minuteY,minuteLen = 0.5, minuteHue = 0;
var secondX,secondY, secondLen = 0.56, secondHue = 0.6667
var milliX, milliY, milliLen = 0.22, milliHue = 0.22;

var hour, minute, second, milli;
var lastSecond = -1;
var hue;
export var tolerance = 0.055;

// move origin to center and rotate so 0 radians is at the top of
// the display.
translate(-0.5,-0.5);
rotate(PI/2);

//export function sliderWidth(v) {
  //tolerance = v * v;
//}

// rotate 2d vector around origin,
var outX,outY;  // icky hack to return two values
function rotateVector2D(px,py, angle) {
    var c1,s1;
    c1 = cos(angle); s1 = sin(angle);
    outX = (c1 * px) - (s1 * py);
    outY = (s1 * px) + (c1 * py);
}

// distance from point to specified radial hand (really a line segment
// defined by the origin and one point)
function pointOnHand(x1,y1,px,py,plen,pcol) {
  
  // see if point is inside this hand's radius, then   
  // compare normalized vectors and make sure they're headed in the 
  // same direction
  if (r > plen) return 0;
  if ((x1 * px + y1 * py) < 0) return 0;
  
  // calculate distance between point and line defined by segment
  z = abs(px * (py - y1) - (px - x1) * py) / plen;
  if (z > tolerance) return 0;
  
  hue = pcol;
  return 1.1-(z/tolerance)
}

export function beforeRender(delta) {
  
  // retrieve the system time
  hour = clockHour();
  minute = clockMinute();
  second = clockSecond();

  // do the continuous second hand movement thing...    
  if (second != lastSecond) {
    milli = 0;
    lastSecond = second;
  } else {
    milli = (milli + delta) % 1000;    
  }

  // set up hand lengths and rotate hands into proper position
  rotateVector2D(hourLen,0,-PI2 * ((hour + minute/60) % 12 ) / 12)
  hourX = outX; hourY = outY;
  
  rotateVector2D(minuteLen,0,-PI2 * ((minute + second/60) % 60) / 60);;
  minuteX = outX; minuteY = outY;
  
  rotateVector2D(secondLen,0,-PI2 * ((second + milli/1000) % 60) / 60);
  secondX = outX; secondY = outY;
  
  rotateVector2D(milliLen,0,-PI2 * milli / 1000);
  milliX = outX; milliY = outY;
}

var r,nx,ny;
export function render2D(index,x,y) {
  // precalculate distance from center for current pixel since we
  // use it for each hand
  r = hypot(x,y); 

  v = pointOnHand(x,y,milliX,milliY,milliLen,milliHue);
  v += pointOnHand(x,y,secondX,secondY,secondLen,secondHue);  
  v += pointOnHand(x,y,minuteX,minuteY,minuteLen,minuteHue);    
  v += pointOnHand(x,y,hourX,hourY,hourLen,hourHue);  

  hsv(hue, 1, v*v*v)   
}
2 Likes

Not only are there some neat new tricks in your clock code (the translate/rotate is a great example)… But I’m kinda tempted to say we need a monthly contest (weekly is too short) for things like this… “Clock”… Or “Sound”, or “Fish”… You get the idea. One word theme, and then people can submit whatever…

But yeah, so many ways to do clocks… Good example.

Added: re the ‘icky’ “global 2 results” hack above… What about making the returned values an array of two [x,y]?

With the array functions, you can also do

src.mapTo(dest, fn)

If src is an array of [x,y,angle], you could call a rotateVector fn, which gets passed (value, index, array), you could read the array, calc and push result into dest [x,y]. But that’s uglier than just returning an array, I think.

Short answer: Performance. I originally wrote it using arrays to hold data for the hands, but array access is slow. I gained about 8 fps by using variables instead.

This is something I’ve been meaning to benchmark, characterize and write up - it just hasn’t reached the top of the list yet. Guess we should have a performance & optimization thead somewhere too.

For me, this pattern is right on the performance vs. convenience/clarity line – if there had been any more data to manage, it would have been worth the overhead of using arrays.

1 Like