LEDaliClock matrix pattern

Cleaned up and optimized. In particular the neat thing here is:
function WuLine(pfn, x0, y0, x1, y1, h, s, v) {

… where pfn is a function, and I use two different blending functions in the clock!

// This pattern creates a rectangular framebuffer
// and does its own integer coordinate mapping
// and draws an analog clock
// with sweeping hands
// using Xiaolin Wu's antialiased line algorithm.

var blur = 0
export function sliderMotion_Blur(v) {
  blur = v * 0.95
}

// export var hour, minute, second, hx, hy, mx, my, sx, sy

// Duplicate PixelBlaze 'Mapper' functionality without normalizing

var width = 16
var height = width
var mid = width/2 - 0.5
var midm1 = mid - 1
var midm2 = mid - 2
var hlen = midm2 * 2/3

var coordmap = array(pixelCount)

for (index=0; index<pixelCount; index++) {
  x = floor(index / height)
  y = index % height
  y = x % 2 == 1 ? height - 1 - y : y // I have a zigzag LED matrix
  coords = array(2)
  coords[0] = x
  coords[1] = y
  coordmap[index] = coords
}

// HSV to RGB using global variables

var h, s, v // filled by HSVtoRGB

function HSVtoRGB(h, s, v) {
    var i, f, p, q, t;
    i = floor(h * 6); 
    f = h * 6 - i;
    p = v * (1 - s); 
    q = v * (1 - f * s); 
    t = v * (1 - (1 - f) * s); 
    im6 = i % 6 
    if (im6 == 0) {
      r = v; g = t; b = p;
    } else { if (im6 == 1) {
      r = q; g = v; b = p;
    } else { if (im6 == 2) {
      r = p; g = v; b = t;
    } else { if (im6 == 3) {
      r = p; g = q; b = v;
    } else { if (im6 == 4) {
      r = t; g = p; b = v;
    } else {if (im6 == 5) {
      r = v; g = p; b = q;
    }}}}}}
}

// RGB framebuffer
// ... Storage 

var Pr = array(width); for (i=0; i<width; i++) Pr[i] = array(height)
var Pg = array(width); for (i=0; i<width; i++) Pg[i] = array(height)
var Pb = array(width); for (i=0; i<width; i++) Pb[i] = array(height)

// ... Render a pixel
//  - Only used by WuLine, so we do 'alpha' here to keep WuLine readable
//  - Uses max() so overlapping line pixels don't make bright spots

function PixelMax(x,y,a,h,s,v) {
  if (x >= 0 && x < width && y >= 0 && y < height) {
    HSVtoRGB(h,s,v*a*a*a)
    Pr[x][y] = max(Pr[x][y], r)
    Pg[x][y] = max(Pg[x][y], g)
    Pb[x][y] = max(Pb[x][y], b)
  }
}

// just attenuate the existing pixel data by a
function PixelMultiply(x,y,a,h,s,v) {
  if (x >= 0 && x < width && y >= 0 && y < height) {
    Pr[x][y] = clamp(Pr[x][y] * a, 0, 1)
    Pg[x][y] = clamp(Pg[x][y] * a, 0, 1)
    Pb[x][y] = clamp(Pb[x][y] * a, 0, 1)
  }
}


// Xiaolin Wu's antialiased line algorithm
// Adapted from https://gist.github.com/polyamide/3f33cb4dc69e22fbf8b66cee39b78d60
// (replaced utility functions with PixelBlaze built-ins)

function WuLine(pfn, x0, y0, x1, y1, h, s, v) {
  if (x0 == x1 && y0 == y1) return

  steep = abs(y1 - y0) > abs(x1 - x0);

  if (steep) {
    tmp = y0; y0 = x0; x0 = tmp;
    tmp = y1; y1 = x1; x1 = tmp;
  }

  if (x0 > x1) {
    tmp = x0; x0 = x1; x1 = tmp;
    tmp = y0; y0 = y1; y1 = tmp;
  }

  dx = x1 - x0;
  dy = y1 - y0;
  gradient = dy / dx;

  xEnd = round(x0);
  yEnd = y0 + gradient * (xEnd - x0);
  xGap = 1 - frac(x0 + 0.5);
  xPx1 = xEnd;
  yPx1 = trunc(yEnd);

  if (steep) {
    pfn(yPx1, xPx1, 1 - frac(yEnd) * xGap, h, s, v )
    pfn(yPx1 + 1, xPx1, frac(yEnd) * xGap, h, s, v )
  } else {
    pfn(xPx1, yPx1, 1 - frac(yEnd) * xGap, h, s, v )
    pfn(xPx1, yPx1 + 1, frac(yEnd) * xGap, h, s, v )
  }

  intery = yEnd + gradient;

  xEnd = round(x1);
  yEnd = y1 + gradient * (xEnd - x1);
  xGap = frac(x1 + 0.5);

  xPx2 = xEnd;
  yPx2 = trunc(yEnd);

  if (steep) {
    pfn(yPx2, xPx2, 1 - frac(yEnd) * xGap, h, s, v )
    pfn(yPx2 + 1, xPx2, frac(yEnd) * xGap, h, s, v )
  } else {
    pfn(xPx2, yPx2, 1 - frac(yEnd) * xGap, h, s, v )
    pfn(xPx2, yPx2 + 1, frac(yEnd) * xGap, h, s, v )
  }

  if (steep) {
    for (x = xPx1 + 1; x <= xPx2 - 1; x++) {
      pfn(trunc(intery), x, 1 - frac(intery), h, s, v )
      pfn(trunc(intery) + 1, x, frac(intery), h, s, v )
      intery = intery + gradient;
    }
  } else {
    for (x = xPx1 + 1; x <= xPx2 - 1; x++) {
      pfn(x, trunc(intery), 1 - frac(intery), h, s, v )
      pfn(x, trunc(intery) + 1, frac(intery), h, s, v )
      intery = intery + gradient
    }
  }
}

// Render-related variables persistent between frames
export var lastsecond = -1
export var ms = 0

// Clear (or fade if blur enabled) framebuffer
// and render the current time

export function beforeRender(delta) {
  for (i=0; i<width; i++) {
    for (j=0; j<height; j++) {
      Pr[i][j] = blur * Pr[i][j]
      Pg[i][j] = blur * Pg[i][j]
      Pb[i][j] = blur * Pb[i][j]
    }
  }
  
  // get the time
  second = clockSecond()

  if (second == lastsecond) {
    ms = clamp(ms + delta, 0, 1000)
  } else {
    ms = 0
    lastsecond = second
  }
  
  second = (second + ms/1000) % 60
  minute = (clockMinute() + second/60) % 60
  hour = (clockHour() + minute/60) % 12
  
  hangle = PI * (hour/6)
  mangle = PI * (minute/30)
  sangle = PI * (second/30)
  uangle = PI * (ms/500)
  
  hx = clamp(sin(hangle), -1,1) * hlen
  hy = clamp(-cos(hangle),-1,1) * hlen
  mx = clamp(sin(mangle), -1,1) * midm2
  my = clamp(-cos(mangle),-1,1) * midm2
  sx = clamp(sin(sangle), -1,1) * midm1
  sy = clamp(-cos(sangle),-1,1) * midm1
  su = sin(uangle)
  cu = -cos(uangle)
  ux = clamp(su, -1,1) * mid
  uy = clamp(cu,-1,1) * mid
  vx = clamp(su, -1,1) * midm1
  vy = clamp(cu,-1,1) * midm1

  WuLine(PixelMax, mid, mid, mid + hx, mid + hy, 0, 0, 1)
  WuLine(PixelMax, mid, mid, mid + mx, mid + my, 1/3, 0.9, 1)
  WuLine(PixelMax, mid, mid, mid + sx, mid + sy, 0, 0.9, 1)
  WuLine(PixelMax, mid + vx, mid + vy, mid + ux, mid + uy, 2/3, 2/3, 1/4)
  
  for (h = 0; h < 12; h++) {
    ha = PI * (h/6)
    sh = sin(ha)
    ch = -cos(ha)
    x1 = mid + clamp(sh, -1,1) * midm1
    y1 = mid + clamp(ch,-1,1) * midm1
    x2 = mid + clamp(sh, -1,1) * mid
    y2 = mid + clamp(ch,-1,1) * mid
    
    WuLine(PixelMultiply, x1, y1, x2, y2, 1, 1, 1)
  }
}

// Draw from the framebuffer

export function render(index) {
  coords = coordmap[index]
  x = coords[0]
  y = coords[1]

  rgb(Pr[x][y], Pg[x][y], Pb[x][y])
}
1 Like