I wanted to see how well antialiasing and the persistence of vision on HDR SK9822s would give the impression of a higher-resolution thingy. So, here’s a spinning zooming wobbling Sierpinski gasket!
See if you can spot the 1-character change between the video and the code below.
Sierpinski gasket drawn with Wu antialiasing
// Made for fun. Share and enjoy.
// This pattern creates a rectangular framebuffer
// and does its own integer coordinate mapping
// and draws a moving Sierpinski triangle
// using Xiaolin Wu's antialiased line algorithm.
var blur = 0
export function sliderMotion_Blur(v) {
blur = v * 3/5
}
// 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 r, g, b // 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
}
}
}
function Triangle(pfn,value,hrot,ax,ay,bx,by,cx,cy) {
WuLine(pfn,ax,ay,bx,by,hrot,0.75,value)
WuLine(pfn,bx,by,cx,cy,hrot+1/3,0.75,value)
WuLine(pfn,cx,cy,ax,ay,hrot+2/3,0.75,value)
}
function Sierpinski(pfn,depth,value,hrot,ax,ay,bx,by,cx,cy) {
Triangle(pfn,value,hrot,ax,ay,bx,by,cx,cy)
if(depth > 0) {
Triangle(pfn,value,hrot+1/6,(ax+bx)/2,(ay+by)/2,(ax+cx)/2,(ay+cy)/2,(bx+cx)/2,(by+cy)/2)
if(depth > 1) {
nd = depth-1
nv = value/2
nr = hrot+1/3
Sierpinski(pfn,nd,nv,nr,
(3*ax+cx)/4,
(3*ay+cy)/4,
(3*ax+bx)/4,
(3*ay+by)/4,
(2*ax+bx+cx)/4,
(2*ay+by+cy)/4,
)
Sierpinski(pfn,nd,nv,nr,
(3*cx+ax)/4,
(3*cy+ay)/4,
(3*cx+bx)/4,
(3*cy+by)/4,
(2*cx+bx+ax)/4,
(2*cy+by+ay)/4,
)
Sierpinski(pfn,nd,nv,nr,
(3*bx+ax)/4,
(3*by+ay)/4,
(3*bx+cx)/4,
(3*by+cy)/4,
(2*bx+cx+ax)/4,
(2*by+cy+ay)/4,
)
}
}
}
// constants
var pi23 = PI2/3
var pi43 = 2 * PI2/3
var midp = 7.5
var radi = 7
// 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]
}
}
rotation = time(0.05) * PI2
hrot = time(0.025)
midx = midp + 2.5 * sin(time(0.087) * PI2)
midy = midp + 2 * cos(time(0.043) * PI2)
radz = radi + 6.5 * cos(time(0.067) * PI2)
ax = midx + radz*sin(rotation)
ay = midy + radz*cos(rotation)
bx = midy + radz*sin(rotation+pi23)
by = midp + radz*cos(rotation+pi23)
cx = midp + radz*sin(rotation+pi43)
cy = midx + radz*cos(rotation+pi43)
Sierpinski(PixelMax,2,1,hrot,ax,ay,bx,by,cx,cy)
}
// 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])
}
[details="Summary"]
This text will be hidden
[/details]