“Lewpen's pattern generator” is the window title shared by a family of small Windows programs: Pattern, New Stylee and PatternsMK2. Each one opens a window and, as fast as the machine allows, works out a new picture and copies it to the screen.
The pictures are built from functions that work on whole images. A plasma fills an image with moving sine waves, a lookup reads one image at the positions given by two others, and a blur or a difference combines images pixel by pixel. Chain a few of them together, feed the time in at the start, and the result animates. PatternsMK2 v1 and v2 split the maths into small functions wired together by hand, which is already a network of functions, just written into the code.
From PatternsMK2 v4 on, the network is the program. That's ZGo, “a network of functions which generate a piece of graphics”, where every value and every function is an object, a frame is worked out by pulling on the final image, and a function can have a whole network inside it. It starts in Java, fading between two pictures, and carries on in C++ with 3D objects, cameras and a z-buffered renderer among its functions. Let's see how it works.
The window
Every version up to PatternsMK2 v2 opens the same window: a 256x256 popup at (128, 128) on the screen, with a black background and no title bar or border at all, just the picture.
hMainWindow = CreateWindowEx(
0,
szAppName,
szAppName,
WS_POPUP,
128,
128,
256,
256,
NULL,
NULL,
hInstance,
NULL
);
With no title bar there's nothing to drag it by, so the window procedure answers WM_NCHITTEST with HTCAPTION. That tells Windows every point of the window is title bar, and you can drag the picture around by grabbing it anywhere.
Drawing happens in Redraw(), which is called from three places: a 10ms timer, WM_PAINT, and the main loop, which checks for messages and then redraws, over and over, as fast as it can:
case WM_CREATE:
Startup();
SetTimer(hwnd, 1, 10, NULL);
break;
case WM_TIMER:
dc = GetDC(hwnd);
Redraw(dc);
ReleaseDC(hwnd, dc);
return 0L;
...
case WM_NCHITTEST:
return HTCAPTION;
while(TRUE) {
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) return TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
HDC dc = GetDC(hMainWindow);
Redraw(dc);
ReleaseDC(hMainWindow, dc);
}
The loop alone would keep it busy, so the timer only adds the odd extra frame. Most versions take the time from timeGetTime(), in milliseconds, so the animation runs at the same speed however many frames the machine manages. Pattern v1 has no keys; from v2 on, Escape closes the window.
Images
An image is a plain block of 256x256 bytes. There are two kinds, a single channel and a colour image with three bytes per pixel:
typedef struct _PATTERN_A {
unsigned char buf[256][256];
} PATTERN_A;
typedef struct _PATTERN_RGB {
unsigned char buf[256][256][3];
} PATTERN_RGB;
Pattern_Create_A() and Pattern_Create_RGB() just malloc() one, and Startup() makes five of each. Every function in Pattern.cpp is named for what it takes: Pattern_Fn_A_Plasma1 fills an A image, Pattern_Fn_RGB2_A2_Lookup works on two RGB images and two A images. Indexing is buf[y][x], so an image is stored a row at a time.
Getting an RGB image on the screen is a single StretchDIBits(), with a BITMAPINFO describing the buffer as a 24-bit bitmap. A negative height means the rows run from the top down, the same as the buffer:
bmi.bmiHeader.biWidth = 256;
bmi.bmiHeader.biHeight = -256;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
...
int w = 1280, h = 1024, x, y;
for(y = 0; y < h; y += 256)
for(x = 0; x < w; x += 256)
StretchDIBits(
dc,
x, y, 256, 256,
0, 0, 256, 256,
(LPVOID)rgb->buf,
(LPBITMAPINFO)&bmi,
DIB_RGB_COLORS, SRCCOPY
);
The two loops tile the picture across a 1280x1024 screen, but the window is only 256x256, so only the first copy ever shows. In a 24-bit bitmap the bytes of a pixel are blue, green, red, so buf[y][x][0] is blue. For the grey pictures, where all three are the same, it makes no difference.
The sine table
Everything that moves is made of sine waves, and they come from a table of 256 entries filled once at start-up by Pattern_Startup():
for(i=0; i<256; i++) pattern_sin[i] = (int)(128+127*sin(i*3.1415926/128));
One cycle fits in 256 entries, so an angle is a byte: any phase can be wrapped into the table with & 255, and adding 256 brings you back to where you started.
| i | 0 | 32 | 64 | 96 | 128 | 160 | 192 | 224 |
|---|---|---|---|---|---|---|---|---|
| pattern_sin[i] | 128 | 217 | 254 | 217 | 128 | 38 | 1 | 38 |
The cast rounds down, and 3.1415926 is a touch short of π, so the peak at entry 64 comes out at 254.99999999999994, which becomes 254. The values run from 1 to 254, never 0 or 255, and that matters later: anything averaged from the table also stays between 1 and 254.
Plasma
A plasma adds up four sine waves running across the image at different angles and speeds. Here's the first one:
void Pattern_Fn_A_Plasma1(PATTERN_A *a, int time)
{
int i, j;
for(j=0; j<256; j++) {
for(i=0; i<256; i++) {
a->buf[j][i] = (
pattern_sin[(time*3 + i*5 + j*4)&255] +
pattern_sin[(time*6 + i*2 + j*5)&255] +
pattern_sin[(time*5 + i*1 + j*3)&255] +
pattern_sin[(time*2 + i*3 + j*1)&255]
) >> 2;
}
}
}
Take the first wave, time*3 + i*5 + j*4. Moving one pixel to the right adds 5 to the phase, and moving one pixel down adds 4, so the wave's crests are straight lines across the image, tilted by the ratio of the two. Its wavelength is 256 divided by the length of (5, 4), or 40 pixels. The time*3 slides the whole wave along as the time goes up. The other three waves do the same with their own numbers, and >> 2 divides the sum by four to bring it back into a byte.
Where the crests of several waves meet you get bright blobs, and where troughs meet, dark ones. Since each wave moves at its own speed, the blobs drift, merge and split. There are three plasmas, each with its own twelve numbers:
| Plasma | Wave | time | x | y | Wavelength |
|---|---|---|---|---|---|
| Plasma1 | 1 | 3 | 5 | 4 | 40.0 pixels |
| 2 | 6 | 2 | 5 | 47.5 | |
| 3 | 5 | 1 | 3 | 81.0 | |
| 4 | 2 | 3 | 1 | 81.0 | |
| Plasma2 | 1 | 4 | 1 | 1 | 181.0 |
| 2 | 3 | -1 | -3 | 81.0 | |
| 3 | 1 | -3 | 2 | 71.0 | |
| 4 | 2 | -2 | -1 | 114.5 | |
| Plasma3 | 1 | 1 | -7 | -3 | 33.6 |
| 2 | 1 | -6 | 6 | 30.2 | |
| 3 | 1 | 4 | 3 | 51.2 | |
| 4 | 1 | 5 | -2 | 47.5 |
Plasma2 has the longest waves, so it's the smoothest. Plasma3's waves are short and its time numbers are all 1, so it's busy but slow. The caller decides how fast time goes by dividing the clock before passing it in. Pattern v1 gives Plasma1 timeGetTime()/17, so its first wave moves 3 table entries every 17ms and goes round a whole cycle in about 1.45 seconds.
Looking one image up in another
A plasma on its own is a soft grey cloud. The trick that makes the patterns is to use two plasmas as coordinates, and read another image at those positions:
void Pattern_Fn_RGB2_A2_Lookup(PATTERN_RGB *rgb1, PATTERN_RGB *rgb2, PATTERN_A *a1, PATTERN_A *a2)
{
int i, j;
int x, y;
for(j=0; j<256; j++) {
for(i=0; i<256; i++) {
x = a1->buf[j][i];
y = a2->buf[j][i];
rgb1->buf[j][i][0] = rgb2->buf[y][x][0];
rgb1->buf[j][i][1] = rgb2->buf[y][x][1];
rgb1->buf[j][i][2] = rgb2->buf[y][x][2];
}
}
}
Since a plasma's values are bytes, they're already in range as coordinates, and since they change smoothly from pixel to pixel, the lookup bends the second image smoothly: a stretch here, a fold there, and wherever a plasma has a peak, the image is mirrored around it. As the plasmas move, the bends move.
The image being bent is Pattern_Fn_RGB_Checkers(). Despite the name, it makes four ramps of grey. Two other versions are left in as comments: an XOR of x and y, and the larger or smaller of the two in alternate quarters.
void Pattern_Fn_RGB_Checkers(PATTERN_RGB *rgb)
{
int i, j;
int x, y;
int c;
for(j=0; j<256; j++) {
for(i=0; i<256; i++) {
// c=i^j;
// if((i<128 && j<128) || (i>=128 && j>=128)) c = 255-max(i, j); else c = min(i, j);
if(i<128) c = j; else c = j-128;
if(j<128) c -= i;
rgb->buf[j][i][0] = c;
rgb->buf[j][i][1] = c;
rgb->buf[j][i][2] = c;
}
}
}
In the bottom half c is just the height, less 128 on the right, so those quarters are smooth ramps. In the top half x is taken off as well, and c goes negative. Storing a negative number in an unsigned char keeps its bottom 8 bits, so -1 becomes 255 and -20 becomes 236. That puts a hard edge from black to white along each diagonal where c crosses zero:
Pattern v1's Redraw() puts it together:
void Redraw(HDC dc)
{
int time = timeGetTime();
Pattern_Fn_A_Plasma1(a1, time/17);
Pattern_Fn_A_Plasma2(a2, time/36);
Pattern_Fn_A_Plasma3(a3, time/41);
// Pattern_Fn_RGB_A3(rgb2, a1, a1, a1);
Pattern_Fn_RGB_Checkers(rgb2);
Pattern_Fn_RGB2_A2_Lookup(rgb1, rgb2, a1, a2);
Pattern_Display(dc, rgb1);
}
Plasma3 is worked out too, at time/41, but nothing reads it. The checkers never change either, yet they're made again every frame.
Let's follow one pixel through. Say timeGetTime() returns 58293, and we want the pixel at x = 100, y = 50. Plasma1 gets 58293/17 = 3429 as its time. Its first wave's phase is 3429×3 + 100×5 + 50×4 = 10987, which wraps to 235, and pattern_sin[235] is 65. The other three come to 217, 88 and 233, and the four add up to 603, so Plasma1 is 150 at this pixel. Plasma2 gets 58293/36 = 1619 and comes to 68 in the same way.
So the pixel takes the checkers' value at (150, 68). That's in the top right quarter, where c = 68 - 128 - 150 = -210, and in a byte that's 46: a dark grey.
The result has smooth grey gradients, where the plasmas carry a ramp of the checkers across the window, broken by sharp black and white edges wherever they cross one of the wrap lines:

Main.cpp also has a Redraw2(), which nothing calls. It makes a single-channel XOR pattern, looks that up through two pairs of plasmas to get two new images, and then looks the checkers up through those: a lookup of lookups.
Pattern v1 to v4
The first four versions all use the same Pattern.cpp, and differ in Redraw():
| Version | Picture | What changed |
|---|---|---|
| Pattern v1 | The checkers through Plasma1 (time/17) and Plasma2 (time/36) | |
| Pattern v2 | The same | Escape closes the window, and the display function moves into Main.cpp |
| Pattern v3 | Plasma3 (time/75) through Plasma2 (time/36) and Plasma1 (time/51), in grey | script.txt appears, and the checkers function becomes an XOR pattern |
| Pattern v4 | The same | Only blank lines |
Version 3 keeps v1's chain in a comment and tries something new. Instead of looking up the checkers, it looks up a plasma:
Pattern_Fn_A_Plasma1(a1, time/51);
Pattern_Fn_A_Plasma2(a2, time/36);
Pattern_Fn_A_Plasma3(a3, time/75 );
Pattern_Fn_A2_A2_Lookup(a4, a3, a2, a1);
Pattern_Fn_A2_A2_Lookup(a5, a4, a3, a2);
Pattern_Fn_RGB_Checkers(rgb2);
Pattern_Fn_RGB_A3(rgb1, a4, a4, a4);
Pattern_Fn_A2_A2_Lookup() is the single-channel lookup, and its arguments are the result, the image to read, then the x and y. So a4 is Plasma3 read at (Plasma2, Plasma1). Pattern_Fn_RGB_A3() copies one image into each of red, green and blue, which makes it grey.
Plasma3 has the shortest waves, and bending it through two smooth plasmas packs them into tight contour lines that wind round the blobs, like rings in wood. a5 and the checkers are still worked out, but neither is shown, so the XOR change to the checkers makes no difference.
The script
Pattern v3 and v4 and both New Stylees come with a script.txt. It's v1's Redraw(), written out as a list of functions:
/*
ZGo pattern script
*/
A_Plasma1 (time/17) -> (a1)
A_Plasma2 (time/36) -> (a2)
A_Plasma3 (time/41) -> (a3)
RGB_Checkers () -> (rgb2)
RGB2_A2_Lookup (rgb2, a1, a2) -> (buf)
Each line is a function without its Pattern_Fn_ prefix, its inputs in brackets and its outputs after the arrow, and the images become named values joining one function to the next. Nothing in the programs reads it, so it's a sketch of how a pattern could be written down instead of compiled in, and its heading calls it a ZGo pattern script.
New Stylee
New Stylee keeps the window and Pattern.cpp but draws something quite different, all in Main.cpp: a field of circles drifting up and down, each lit from inside and with a yellow glow round its edge.
It changes the pixel format to suit. A single-channel image is now unsigned int buf[256][256], and a new PATTERN_RGBA holds a 32-bit word per pixel, laid out as 0xAARRGGBB, which a 32-bit bitmap shows directly, ignoring the top byte. A set of macros takes the channels apart and puts them back together:
#define GET_A( c ) (((c)>>24)&255)
#define GET_R( c ) (((c)>>16)&255)
#define GET_G( c ) (((c)>>8)&255)
#define GET_B( c ) (((c))&255)
#define SET_A( c, a ) ( ( (a) <<24)+((c)&0x00FFFFFF) )
#define SET_R( c, r ) ( (((r)&255)<<24)+((c)&0xFF00FFFF) )
#define SET_G( c, g ) ( (((g)&255)<<24)+((c)&0xFFFF00FF) )
#define SET_B( c, b ) ( (((b)&255)<<24)+((c)&0xFFFFFF00) )
...
#define OVERLAY( under, over ) \
ADD_RGBA_RGBA \
( \
MUL_RGBA_BYTE( under, 255 - GET_A(over) ), \
MUL_RGBA_BYTE( over, GET_A(over) ) \
)
SET_R, SET_G and SET_B all shift their value into the top byte, the alpha, rather than into their own channel, but nothing uses them. MUL_RGBA_BYTE scales each colour channel by a byte over 256, ADD_RGBA_RGBA adds two colours and caps each channel at 255, and OVERLAY puts one colour over another by its alpha.
Random circles
The circles are placed with a random number generator of its own: three integers that are stirred together on every call.
int random( int min, int max )
{
if( min == max ) return min;
if( min > max ) { int t=min; min = max; max = t; }
int dif = max-min;
static int r1 = 0x8939823;
static int r2 = 0x9219812;
static int r3 = 0x112891F;
r1 += 19198;
r2 += r1 * 0x982191 - 328923;
r3 <<= 1;
r3 ^= r2;
return min + ( ( ( r3*7 + r2*33 + r1*4 ) / 3 ) % dif );
}
The arithmetic overflows all the time, which is the point, but the numbers are signed ints. ( r3*7 + r2*33 + r1*4 ) / 3 is negative about half the time, and in C % keeps the sign, so random( 0, 65536 ) gives anything from -65535 to 65535 rather than 0 to 65535. The seeds are fixed, so the circles come out the same every time. Each circle takes five numbers:
for( i=0; i<MAX_CIRCLES; i++ )
{
circle[i].x = (random( 0, 65536 )+0.0) / 256.0;
circle[i].y = (random( 0, 65536 )+0.0) / 256.0;
circle[i].r = 0.01 * random( MIN_RADIUS, MAX_RADIUS );
circle[i].dy = -0.01 * random( 0, 100 );
circle[i].col = 0xFF000000 | 0x010102 * random( 60, 120 );
}
The colour multiplies 0x010102 by a number n, giving red and green of n and blue of 2n, a greyish blue, with the alpha byte set to 255. Here are the first three circles:
| Circle | x | y | Radius | Speed | n |
|---|---|---|---|---|---|
| 0 | -152.57 | 222.97 | 6.28 | 0 | 19 |
| 1 | -62.69 | -98.47 | 21.81 | 0.52 down | 104 |
| 2 | 179.05 | 59.84 | 24.64 | 0.34 down | 7 |
Because the numbers can be negative, positions run from -256 to 256, radii from -9.99 to 29.99, speeds from 0.99 up to 0.99 down, and n from 1 to 119. The first two circles start off the left of the window, and since circles only ever move up and down, they never come into view. 102 of the 200 are like that. A negative radius stops a circle being drawn too, as we'll see, and 54 have one, so only 80 of the 200 circles ever put a pixel on the screen.
Drawing a frame
Each frame clears an RGBA image to 0, transparent black, draws every circle into it, then moves each one by its speed, wrapping round from the top to the bottom and back:
void draw_circle( PATTERN_RGBA *dest, double x, double y, double r, RGBA col )
{
int x1, y1, x2, y2;
x1 = x - r - 1;
y1 = y - r - 1;
x2 = x + r + 1;
y2 = y + r + 1;
if( x1 < 0 ) x1 = 0;
if( y1 < 0 ) y1 = 0;
if( x2 > 255 ) x2 = 255;
if( y2 < 255 ) y2 = 255;
int i, j;
for( j=y1; j<=y2; j++ )
{
for( i=x1; i<=x2; i++ )
{
if( (x-i)*(x-i) + (y-j)*(y-j) < r*r ) dest->buf[j][i] = col;
}
}
}
With a negative radius, x1 nearly always comes out bigger than x2 and the loop never runs. The last clip test is written y2 < 255 where it should be y2 > 255, so every circle is scanned all the way down to the bottom row, and one hanging over the bottom edge is drawn on past the end of the buffer.
The movement is a fixed amount per frame, not per millisecond, so the circles drift faster on a faster machine.
Then comes the glow. The alpha channel of the circle image, 255 inside a circle and 0 outside, is copied out with get_alpha_channel() and blurred by blur_channel(), which adds up the 11x11 square of pixels around each one, wrapping round the edges, and multiplies by 1/121. Then each pixel of the picture is worked out from the circle image a and the blur c:
RGBA d = MUL_RGBA_BYTE( OVERLAY( 0xFFFF00, a.buf[j][i] ), c.buf[j][i] );
...
dest.buf[j][i] = d;
Inside a circle its own colour covers the yellow. Outside, the yellow shows. Either way it's then scaled by the blur, which is 255 well inside a big circle, 0 well away from any, and somewhere between near an edge. So a circle is dimmer round its rim and brightens towards its middle, and just outside it there's a fading yellow halo. Across a straight stretch of edge it works out like this, for a circle with n = 100:
One pixel outside, the 11x11 square takes in 5 columns of circle, 55 pixels at 255, and 55 × 255 / 121 is 115.9, so c is 115. The yellow is 254 there (255 scaled by 255/256), and 254 × 115 / 256 gives 114 for red and green: a dim yellow. Six pixels out, the square no longer reaches the circle and it's black. A small circle never fills the square, so it never reaches full brightness, and the smallest stay dark.
The loop also works out four other colours, g, dd, f and e: a shifted shadow, a masked inverse and their combinations. Only d is shown.
New Stylee v2
Version 2 cuts the circles from 200 to 70 and puts each one on a whole pixel, wrapped into the window:
circle[i].x = (int)((random( 0, 65536 )+0.0) / 256.0)&255;
circle[i].y = (int)((random( 0, 65536 )+0.0) / 256.0)&255;
The & 255 brings the negative positions round to the other side, so the first circle's -152 becomes 104, and all 70 start somewhere in the window. That's why 70 circles look about as busy as 200 did. It also adds blur_image(), which nothing calls, and greyscale_and_blur(), which is called on the previous frame's picture with the comment “why does this not work??” and whose result isn't used.
PatternsMK2 v1
PatternsMK2 goes back to the plasmas and rebuilds Pattern.cpp around one function, Pattern_Main(), that takes the time and fills the output image. Main.cpp's Redraw() just calls it:
void Redraw(HDC dc)
{
int time = timeGetTime();
pattern_var(PATTERN_RGB, Pattern_Create_RGB, rgb_output);
Pattern_Main(rgb_output, time);
Pattern_Display(dc, rgb_output);
}
The images no longer have to be made in Startup(). pattern_var declares a static pointer and creates the image the first time the line runs, and pattern_const also fills it, once, with a given function:
#define pattern_var(type, fn, name) static type *name = NULL; if(!name) name = fn();
#define pattern_const(type, fn, name, init) static type *name = NULL; if(!name) { name = fn(); init(name); }
So a function can keep its own working images just by naming them. Pattern.cpp gains a set of new functions to chain together, among them a copy, a greyscale, an emboss, an XOR, a difference, a fade, a box blur and a ripple, which is a lookup through Plasma1 at time/35 and Plasma2 at time/36. Pattern_Main() chains a few of them:
void Pattern_Main(PATTERN_RGB *result, int time)
{
static int lasttime = 0, timediff = 0;
Pattern_Fn_RGB_BWRipples(result, time);
...
pattern_const(PATTERN_RGB, Pattern_Create_RGB, checkers, Pattern_Fn_RGB_Checkers);
pattern_var(PATTERN_RGB, Pattern_Create_RGB, result1);
pattern_var(PATTERN_RGB, Pattern_Create_RGB, result2);
Pattern_Fn_RGB_BoxBlur(result2, checkers, 7);
Pattern_Fn_RGB_RippleDistort(result1, result2, time);
Pattern_Fn_RGB_BoxBlur(result, result1, 6);
Pattern_Fn_RGB3_Difference(result, result, result1);
The checkers are blurred, so their wrap edges turn into short soft slopes, and rippled through the two plasmas, as before. Then that is blurred again and the difference taken between the blurred and the sharp copy, channel by channel. A box blur doesn't change a straight ramp, so wherever the rippled image is a smooth gradient the difference is close to 0. It's only bright where the image bends or changes slope sharply. What's left is a dark picture with glowing outlines that trace the folds of the ripple:
The box blur
Pattern_Fn_RGB_BoxBlur() blurs across each row into a temporary image, then down each column. It doesn't add up the whole box for every pixel. It keeps a running sum of the 2 × radius + 1 pixels under the box, and for each step along it takes off the pixel leaving at one end and adds the one arriving at the other:
for(j=0; j<256; j++)
{
b = g = r = 0;
for(i=-radius; i<=radius; i++)
{
b += src->buf[j][i&255][0];
g += src->buf[j][i&255][1];
r += src->buf[j][i&255][2];
}
for(i=0; i<255; i++)
{
temp->buf[j][i][0] = b/diameter;
temp->buf[j][i][1] = g/diameter;
temp->buf[j][i][2] = r/diameter;
b -= src->buf[j][(i-radius)&255][0];
g -= src->buf[j][(i-radius)&255][1];
r -= src->buf[j][(i-radius)&255][2];
b += src->buf[j][(i+1+radius)&255][0];
g += src->buf[j][(i+1+radius)&255][1];
r += src->buf[j][(i+1+radius)&255][2];
}
}
That's one subtraction and one addition per channel per pixel in each pass, whatever the radius, where adding up the whole 15x15 box for radius 7 would take 225 additions.
Both passes stop at i<255 and j<255, so the last column of the temporary image and the last row of the result are never written. The temporary image is a pattern_var, made with malloc() and never filled, so the blurred image's right-hand column is worked out from whatever that memory held. If it held zeros, as fresh memory usually does, the difference leaves the rippled image itself showing down the right-hand edge. The bottom row keeps what Pattern_Fn_RGB_BWRipples() put there at the start: the same ripple of the checkers without any blur, which is overwritten everywhere else. The ripple's coordinates come from the plasmas, so they never reach 0 or 255, and the lookups never land on the missing row or column.
Main.cpp still has v1's two redraws, as Redraw2() and Redraw3(), and Pattern_Main() has a longer chain commented out: eight plasmas mixed into six colour images and run through five differences.
PatternsMK2 v2
Version 2 turns the functions into objects. Each one is a class with sixteen input and sixteen output pointers, set before it runs, and a process() that reads the inputs and writes the outputs. Pattern2.h has macros for writing them:
#define _PATTERN_CLASS_STD \
void *input[16]; \
void *output[16]; \
void init() { for(int i=0; i<16; i++) input[i] = output[i] = NULL; } \
void setinput(int i, void* p) { input[i] = p; } \
void setoutput(int i, void* p) { output[i] = p; } \
void addinput(void* p) { for(int i=0; i<16; i++) if(!input[i]) { input[i] = p; return; } } \
void addoutput(void* p) { for(int i=0; i<16; i++) if(!output[i]) { output[i] = p; return; } }
...
#define pattern_def(name) class name : Pattern_Fn { public: _PATTERN_CLASS_STD; name() { init(); } void process(); }; void name::process()
...
#define pattern_input(type, name, index) type *name = (type *)input[index]
#define pattern_output(type, name, index) type *name = (type *)output[index]
Values are objects too, so a real number is a PATTERN_REAL that can be wired from one function's output to another's input. Time is one of them, now in seconds since the first frame. The top function is Pattern_Fn_Main, which wires the time into a single function and runs it:
pattern_def( Pattern_Fn_Main )
{
// Inputs and outputs
pattern_input(PATTERN_REAL, time, 0);
pattern_output(PATTERN_RGB, result, 0);
// Internal functions
pattern_fn(Pattern_Fn_SwirlyThing1, swirly);
// Make tree
pattern_addinput(swirly, time);
pattern_addoutput(swirly, result);
// Process
pattern_process(swirly);
}
The functions are split into headers by what they work on: reals (add, subtract, multiply, divide, difference, absolute value and a range between two numbers), single-channel images, RGB images, drums, a visualiser that draws a number as a bar chart, and simple 3D. The drum is the space bar, read through key state that Main.cpp now keeps from WM_KEYDOWN and WM_KEYUP. A larger network in Pattern2.h, Pattern_Fn_HitTest1, uses it. Pressing space restarts a timer, a value falls from 0.3 to 0 over the next 0.375 seconds, and that value fades two plasmas in over plain gradients before they're used to look up the checkers: a ripple that dies away. Nothing runs it.
What does run is Pattern_Fn_SwirlyThing1, from the simple 3D header. It fills the image with vertical stripes of blue, one value per column from four sine waves of x and time, then draws 4096 white points along a curve in 3D:
for(i=0; i<4096; i++)
{
obj.point[i][0] = 0.7*sin(3.14159 * i / 128 + 1.1422 * t + 0.2873)
+ 0.3*sin(3.14159 * i / 341 + 1.2412 * t + 0.2422);
obj.point[i][1] = 0.7*sin(3.14159 * i / 293 + 1.0897 * t + 0.7426)
+ 0.3*sin(3.14159 * i / 134 + 1.4211 * t + 0.3523);
obj.point[i][2] = 0.7*sin(4.14159 * i / 207 + 1.1097 * t + 0.5243)
+ 0.3*sin(3.14159 * i / 491 + 2.2511 * t + 0.7627);
}
ZGo_Simple3D_Rotate(&obj, t, &obj2);
ZGo_Simple3D_Screen(&obj2, &obj3);
for(i=0; i<4096; i++)
{
int x = obj3.point[i][0];
int y = obj3.point[i][1];
int z = obj3.point[i][2];
result->buf[y&255][x&255][0] = 255;
result->buf[y&255][x&255][1] = 255;
result->buf[y&255][x&255][2] = 255;
result->buf[y&255][x&255][3] = 255;
}
Each coordinate is a pair of sine waves of the point's number, so neighbouring points sit close together and the 4096 of them join up into a long tangled line. The time in each wave makes the loop change shape. The z coordinate has 4.14159 where the others have 3.14159.
ZGo_Simple3D_Rotate() turns the whole thing about x, y and z, at angles of tπ/4.2376, tπ/5.7457 and tπ/6.3251, so it goes round each axis every 8.5, 11.5 and 12.7 seconds. ZGo_Simple3D_Screen() then puts the eye 3 units back and projects with 128 + 128x / (z + 3). At the first frame there's no rotation yet, and point 0 is at (0.270, 0.577, 0.558), which lands on pixel (137, 148). A point that goes off the edge comes back on the other side, because of the & 255.
The output image is now 4 bytes per pixel, and the display function shows it as a 32-bit bitmap. The transform also has a small slip: it multiplies by a 4x4 matrix, so it reads a fourth coordinate from each point, which is really the next point's x. The matrix only rotates, so that coordinate is always multiplied by 0. Pattern2.h includes type_simple3d.h, which is empty, and the 3D types are declared in functions_simple3d.h instead.
PatternsMK2 v3
Version 3 starts again, with a 640x480 window and a new header, zn.h. Its Image class can be any size and can blit one image onto another, with an alpha fade, and ZN holds the screen image and a 320x160 logo. Each frame is this:
void recalc(double time)
{
//- Constants
static double ZNLOGO_STARTFADE = 5.000;
static double ZNLOGO_ENDFADE = 7.000;
static double BOXES_START = 4.500;
//- Clear buffer
int i;
for(i=0; i<w*h; i++) im->buf[i] = 0x336699;
// Draw 3d things
// Draw ZN logo
double alpha = (time - ZNLOGO_STARTFADE) / (ZNLOGO_ENDFADE - ZNLOGO_STARTFADE);
im->blit(znlogo, (w - znlogo->w)/2, (h - znlogo->h) / 2, alpha);
}
The logo isn't a picture yet. The Image constructor fills every new image with one colour made from GetTickCount() and the image's own address, so it's a flat box of an unpredictable colour, solid until 5 seconds, gone at 7. There's no code under “Draw 3d things”, and BOXES_START isn't used. With no pattern in it, it isn't in the live version.
ZGo
Put the script's one line per function next to v2's function objects, with their numbered inputs and outputs, and you have the idea of ZGo. Its design notes are four short text files, and the first one says it all:
ZGO
---
The basis for ZGo is a network of functions which generate a piece of graphics.
By modifying the initial parameters to the network over time the graphics will animate.
The second note lists what flows through a network. “With ZGo 1 I'm going to stick to 256x256 images”:
| Type | What it is |
|---|---|
| Integer | A 32-bit integer |
| Real | A floating-point value |
| Channel | 256x256 array of unsigned bytes |
| Image | Up to 4 channels: greyscale; greyscale and alpha; red, green and blue; or red, green, blue and alpha |
Each value was to carry a name and a type in a common structure:
typedef struct
{
char name[64];
int type;
void *item;
}
ZGO_DATAITEM;
A function takes some values and gives back some more. The third note's example is the first network that got built:
A function takes a set of data items, performs some operation on them and returns a set of data items.
eg.
Fade (IMAGE im1, IMAGE im2, REAL alpha) -> (IMAGE im3)
There are two types of function - native functions and user functions.
Native functions are stored in DLLs which have a defined interface (see below)
User functions are networks of functions (both native and user) which have an input and output interface
All will be made clear
Native functions were to go one per DLL, called by name through ZGo_Function_Call(char* fname, ZGO_DATASET* in), and the fourth note defines a network as “a set of connected functions. The outputs of functions are connected to the inputs of others.” It stops after the names of two wrapper functions, ZGo_Network_Create() and ZGo_Network_Delete().
None of the programs uses the C structures, data sets or DLLs. What they keep is the shape: values and functions are objects, each function is wired to named inputs and outputs, and a user function is a function with a network inside.
ZEffect
ZEffect is a 320x240 Java applet with the start of a different idea, objects whose positions come from other objects. ZObjectGroup2D gives a position for each index and time, a Bubbler is a group of points moving round on sines and cosines, and a RingPairs draws pairs of rings joined by a line, at points it borrows from another group. init() makes a Bubbler, a RingPairs on top of it and another RingPairs on top of that, but the three Redraw() calls are commented out.
What does run is a grid of 128x128 numbers. Each repaint goes through it in reading order and replaces every cell with the average of its four neighbours plus one, wrapped to a byte:
for(int j=0; j<128; j++) {
for(int i=0; i<128; i++) {
buf[j][i] = (((
(int)buf[(j-1)&127][i] +
(int)buf[(j+1)&127][i] +
(int)buf[j][(i-1)&127] +
(int)buf[j][(i+1)&127]
)>>2) + 1)&255;
if(buf[j][i] < 240 || buf[i][j] > 248) img.fillRect(i, j, 1, 1);
}
}
The cells are updated in place, so each one already sees the new values above it and to its left. A cell is drawn black when it's under 240, or when the cell at the swapped position, buf[i][j], is over 248. The grid starts from (((i*117+j)%(172+j/18+i/39))+145)&255, which jumps about from one cell to the next. The averaging smooths it out and the extra one lifts it a step each time, so patches turn white as they climb past 240 and black again when they wrap round to 0, every 256 steps or so. The thread repaints, sleeps 20ms and goes round again, so that's about every five seconds.
ZGo in Java
PatternsMK2 v4 is the first ZGo program: ZGoTest, a 512x240 Java applet that builds the note's fade as a network, with a sine wave of the time driving a fader between two pictures.
A value is a ZGoDATA. It knows which function writes it, output_of, and when it's asked to process(), it asks that function to run first, but only once per frame:
public void process()
{
if(frame == zgo.frame) return;
frame = zgo.frame;
if(output_of != null) output_of.process();
}
A function is a ZGoFn, made with two ZGoPARAMS, lists of up to 16 named values. Its constructor tells its inputs it reads them and its outputs it writes them:
public ZGoFn(ZGo zgo, ZGoPARAMS input, ZGoPARAMS output)
{
this.zgo = zgo;
this.input = input.setInput(this);
this.output = output.setOutput(this);
}
So building a network is just making the values and then the functions between them. ZGoMain does it in its constructor, and its own process() is empty:
// Create internal data
im1 = new ZGoIMAGE(zgo, "image1.jpg", 512, 240);
im2 = new ZGoIMAGE(zgo, "image2.jpg", 512, 240);
alpha = new ZGoREAL(zgo);
// Create internal functions
sine = new ZGoSine(zgo,
new ZGoPARAMS(zgo).add("time", input.get("time")),
new ZGoPARAMS(zgo).add("result", alpha)
);
fader = new ZGoFader(zgo,
new ZGoPARAMS(zgo).add("image1", im1).add("image2", im2).add("alpha", alpha),
new ZGoPARAMS(zgo).add("result", output.get("result"))
);
The result belongs to ZGo, which hands it to ZGoMain as its output. ZGoMain then hands the same value to the fader as the fader's output, and the last function to claim a value is the one that writes it. So ZGoMain never runs at all. It only builds the network, which is what the notes meant by a user function. Each frame, ZGo moves the clock on and pulls the result:
public int[] render(double t)
{
frame ++;
time.value = t;
result.process();
return result.buf;
}
The fader asks for alpha, which runs the sine, which asks for the time. Nothing writes the time or the two images, so asking for them just marks them as up to date. Values that nothing asks for are never worked out at all.
The sine is 0.5 + 0.4*Math.sin(time.value*3.14159/1.4), so alpha swings between 0.1 and 0.9 and back every 2.8 seconds. The fader turns it into a whole number and mixes each channel:
int a = (int)(255*alpha.value), aa = 255-a;
...
c = buf1[o1++]; a1 = (c>>24)&255; r1 = (c>>16)&255; g1 = (c>>8)&255; b1 = c&255;
c = buf2[o2++]; a2 = (c>>24)&255; r2 = (c>>16)&255; g2 = (c>>8)&255; b2 = c&255;
a1 = (a1*aa + a2*a)>>8;
r1 = (r1*aa + r2*a)>>8;
g1 = (g1*aa + g2*a)>>8;
b1 = (b1*aa + b2*a)>>8;
rbuf[o3++] = (a1<<24) + (r1<<16) + (g1<<8) + (b1);
When the applet starts, alpha is 0.5, so a is 127 and aa is 128. A pixel that's 200 in the first picture and 100 in the second comes out as (200 × 128 + 100 × 127) / 256 = 149. Alpha never goes below 0.1 or above 0.9, so a stays between 25 and 229 and you never see either picture on its own. The two weights add up to 255 and the sum is divided by 256, so everything is a shade darker: white comes out as 254.
Fading also clips the size to the smaller image, and one line of that sets the width where it means the height, if(h > im2.h) w = im2.h;. All three images are 240 high, so it never happens. The two pictures are greyscale JPEGs:


ZGoIMAGE loads each one with the applet's getImage() and copies its pixels out with a PixelGrabber, which waits until it's loaded. The applet itself is built on TinyPTC, a small library by Gaffer that runs your main() in its own thread and copies an array of 32-bit pixels to the applet whenever you call update(). ZGoTest's main() just renders frames as fast as it can, with the time in seconds since the first one. It also keeps track of mouse drags, but nothing reads them.
ZGo in C++
PatternsMK2 v5 to v8 bring ZGo back to C++: the same ZGoDATA, ZGoFn and ZGoPARAMS, a class for every type and function in a .cpp and .h of its own, and ZGo.h including the lot. The window is a 512x240 popup, still titled “Lewpen's pattern generator”, and ZGo::display() copies the result to it as a 32-bit bitmap.
Every ZGoMain.cpp keeps the Java network in a comment at the top, and ZGoFader and ZGoSine come across, the sine now 0.5 + 0.5 sin. Loading a picture doesn't, yet:
ZGoIMAGE::ZGoIMAGE(ZGo *zgo, char *fname, int w, int h)
{
this->zgo = zgo;
this->w = w;
this->h = h;
size = w*h;
buf = (int *)malloc(size*4);
//- TODO: Load image
int k = GetTickCount()*836782 + ((int)fname)*82619311;
for(int j=0; j<h; j++) for(int i=0; i<w; i++) buf[i+w*j] = ((i^j)+k)*0x01010101;
}
Instead the C++ versions go into 3D. These are the types in v8:
| Type | Holds |
|---|---|
| ZGoINT, ZGoBOOL, ZGoREAL | An int, a bool and a float (a double in v5 and v6) |
| ZGoIMAGE | w x h ints, 0xAARRGGBB |
| ZGoARRAY2D, ZGoRANDOMARRAY2D | A float for any (x, y); the random one stirs x and y through a shift register |
| ZGoVECTOR | x, y and z, with dot, cross, subtract and unit |
| ZGoMATRIX | A 4x4 matrix: shifts, scales, rotations, products and an inverse |
| ZGoCAMERA | A matrix from the world into the camera, and the projection |
| ZGo3DOBJECT | Points, triangles with a colour each, up to 256 sub-objects and a matrix |
| ZGoDISTORTION3D | A function from a point to a point: two sine waves along x, moving with time |
And these are the functions, with the first version whose ZGo.h includes each one:
| Function | Takes | Gives | From |
|---|---|---|---|
| ZGoKeyPressed | key | whether it's held down | v5 |
| ZGoSine | time | 0.5 + 0.5 sin(π time / 1.4) | v5 |
| ZGoFader | two images, alpha | the two mixed | v5 |
| ZGoTile | an image, x and y offsets | the image repeated across the result | v5 |
| ZGoChecker | two images, x and y offsets | the two in a chequerboard | v5 |
| ZGoMatrixMul | two matrices | their product | v5 |
| ZGoTransform | an object, a matrix | the object, moved | v5 |
| ZGoRender | an object, a camera | an image | v5 |
| AnimatedRotationMatrix | time | a turn about y of π time / 20 | v5 |
| Subdivide3DObject | an object | each triangle split in four | v6 |
| ZGoDistort3D | an object, a distortion | every point distorted | v6 |
| ZGoPointCamera | a position and a point to look at | a camera | v6 |
| ZGoMerge3D | two objects | one object holding both | v7 |
| ZGoRealAdd, Sub, Mul, Div, Mod, Sqrt, Floor | one or two reals | a real | v7 |
| ZGoRandom | min and max | a random real between them | v7 |
| ZGoIsRealIncreasing | a real | whether it's gone up since last time | v7 |
| ZGoRealFreezer | a real, a flag | the real, held while the flag is false | v7 |
| ZGoMakeVector, ZGoMakeShiftMatrix | x, y and z | a vector, a shift | v7 |
| Scene1, MakeScene1, MakeCamera1 | time | an image, a scene, a camera | v7, v8 |
Each function also carries its type name in fnType, but most of the real-number functions were started from a copy of ZGoSine and still call themselves "ZGoSine". Subdivide3DObject puts each new point half way between a corner and itself, 0.5 * (result->point[p1][0] + result->point[p1][0]), so it doesn't move anything yet.
Each version's ZGoMain draws something different:
| Version | What ZGoMain draws |
|---|---|
| v5 | A tube bent round a ring, pqtorus(1, 1, 0), turning, environment-mapped into a 128x128 image that's tiled across the window and scrolled |
| v6 | A 10x10 plane seen from (-1, 2, -4), looking at the middle |
| v7 | Scene1: a floor and eight cubes, with the camera gliding forward, all moved by code in Scene1::process() |
| v8 | Scene1 again, a floor and a cube, built entirely as a network |
Version 5's ZGo3DOBJECT.cpp doesn't compile as it stands: it puts new int[4096][3] into its double point array, and fills tpoint, tnormal and spoint arrays that aren't declared anywhere. Versions 6 and 7 colour every triangle from its number and the object's address in memory, i*0x89387278 + 0x17863267*(int)this, so the colours depend on where new happened to put each object. Version 8 gives every face a colour of its own, which is why it's the one in the live version.
Networks inside functions
Version 7's Scene1 is written the old way. Its constructor wires a camera and a render, but its process() does the work in code: it keeps its own clock, moves the camera, moves the cubes, and then marks the values it changed as up to date with a new updated():
//- Calculate time difference
static float oldt = 0;
float dt = time->value - oldt;
oldt = time->value;
float dz = dt * 1.1;
zz += dz;
...
for(i=0; i<8; i++)
{
while(z[i] < zz-5)
{
z[i] += 10;
x[i] = (rand()%8) - 3.5;
}
scene->object[i]->trans->shiftMatrix(x[i], 1.5, z[i]-zz);
}
...
position->updated();
point_at->updated();
scene->updated();
Version 8 does the same kind of thing with no code at all. Scene1 becomes three functions wired together in its constructor, and its process() is empty:
ZGo3DOBJECT *scene = new ZGo3DOBJECT(zgo);
new MakeScene1(zgo,
(new ZGoPARAMS(zgo))->add("time", time),
(new ZGoPARAMS(zgo))->add("scene", scene)
);
ZGoCAMERA *camera = new ZGoCAMERA(zgo);
new MakeCamera1(zgo,
(new ZGoPARAMS(zgo))->add("time", time),
(new ZGoPARAMS(zgo))->add("camera", camera)
);
new ZGoRender(zgo,
(new ZGoPARAMS(zgo))->add("object", scene)->add("camera", camera),
(new ZGoPARAMS(zgo))->add("result", result)
);
MakeScene1 and MakeCamera1 are user functions too, each with an empty process() and a network inside. Even the numbers are values: new ZGoREAL(zgo, 2) is a constant that nothing writes. Here's the whole thing, with the ZGo left off the front of each function's name:
The camera's path is the simplest part. It looks at (0, 0, t) from 1.25 units behind, 4.375 up and 0.625 to the side, so it glides forward one unit a second, looking steeply down. The floor is a plane(1), 10 by 10 black and white squares, lowered half a unit and moved forward to (int)(t / 2) × 2. Every 2 seconds it jumps 2 units, a whole pair of squares, so the pattern lines up again and it looks like a floor that never ends.
The cube is the clever part. Every 10 seconds it jumps 10 units ahead to a new random x, and stays put while the camera passes it:
new ZGoRealFloor(zgo,
(new ZGoPARAMS(zgo))->add("a", zz)->add("b", new ZGoREAL(zgo, 10)),
(new ZGoPARAMS(zgo))->add("result", z)
);
ZGoBOOL *inc = new ZGoBOOL(zgo);
new ZGoIsRealIncreasing(zgo,
(new ZGoPARAMS(zgo))->add("a", z)->add("init_a", new ZGoREAL(zgo, -9999)),
(new ZGoPARAMS(zgo))->add("result", inc)
);
ZGoREAL *x = new ZGoREAL(zgo);
new ZGoRealFreezer(zgo,
(new ZGoPARAMS(zgo))->add("in", intrand)->add("flag", inc),
(new ZGoPARAMS(zgo))->add("out", x)
);
z is (int)((t + 4) / 10) × 10, so it's 0 until 6 seconds, then 10 until 16, and so on. ZGoIsRealIncreasing is true on the frames where z has just gone up, and true on the first frame too, since it starts from -9999. The freezer only pulls its input on those frames, and that's the only time anything asks the random number for a value, so rand() is called once per cube. The random number is between -5 and 5, and ZGoRealFloor with no divisor rounds it towards zero. rand() is never seeded, so the cubes always land in the same places:
| From | rand() | & 4095 | -5 + 10 × that / 4096 | x | z |
|---|---|---|---|---|---|
| 0s | 41 | 41 | -4.90 | -4 | 0 |
| 6s | 18467 | 2083 | 0.09 | 0 | 10 |
| 16s | 6334 | 2238 | 0.46 | 0 | 20 |
| 26s | 26500 | 1924 | -0.30 | 0 | 30 |
| 36s | 19169 | 2785 | 1.80 | 1 | 40 |
Anything from -0.99 to 0.99 rounds to 0, so 0 comes up twice as often as any other x. Both of these functions keep their last value in a static inside process(), so every copy of them would share it. There's only one of each here.
ZGoMerge3D joins the floor and the cube as the two sub-objects of the scene. Every frame it clears the scene and adds them again, and since the deletes in clear() are commented out, the old list of 256 pointers is left behind each time.
The renderer
ZGoPointCamera builds the camera from its two vectors. Forward is from the position to the point it looks at; right is up, (0, 1, 0), crossed with forward; and the camera's own up is right crossed with forward. Those three and the position make a matrix from the camera into the world, and inverse(), a 4x4 inverse written out term by term, turns it round. At the first frame forward is (-0.136, -0.953, 0.272): nearly straight down.
ZGoRender clears the picture and a z-buffer to 0 and renders the scene. Each object's points go through its own matrix, then its parent's, then the camera's, and then ZGoCAMERA::screenSpace() puts them on the screen:
float d = 1.0 / sqrt
(
tpoint[i][0]*tpoint[i][0] +
tpoint[i][1]*tpoint[i][1] +
tpoint[i][2]*tpoint[i][2]
);
tpoint[i][2] = 1.0 / tpoint[i][2];
// d = tpoint[i][2];
tpoint[i][0] = cx + scale * tpoint[i][0] * d;
tpoint[i][1] = cy + scale * tpoint[i][1] * d;
An ordinary perspective divides by the depth, z. This divides by the distance, and the line that would make it the depth is commented out. scale is half the window height, 120. Let's see what that does to the near corner of the floor at the first frame, (4.5, -0.5, -5). In the camera's space it's at (1.79, 6.33, 3.10), 7.27 units away and 64.8° off the view axis. Divided by its depth, it would go at (256 + 120 × 1.79 / 3.10, 120 + 120 × 6.33 / 3.10) = (325, 365), well below the bottom of the window. Divided by its distance, it goes at (285, 224), just inside.
Dividing by the distance puts every point on a sphere around the eye and then looks at it straight on, so a point's distance from the centre is 120 times the sine of its angle off the axis, 108.6 pixels for the corner, and nothing can land more than 120 pixels away. However wide the view, everything in front of the camera fits in a disc 240 pixels across in the middle of the window, like a fish-eye lens. The triangles between the points are still drawn with straight edges.
A triangle is only drawn if all three corners are in front of the eye, with 1/z greater than 0. zbufTri() sorts the corners by height, steps down the two edges in 16.16 fixed point and fills each row, keeping 1/z × 65536 in the z-buffer, so a bigger number is nearer:
for(j=y2; j<y3; j++)
{
int xai = (fxa+0xFFFF) >> 16; // int xai = (int)xa; if(xai < xa) xai++;
int xbi = (fxb+0xFFFF) >> 16; // int xbi = (int)xb; if(xbi < xb) xbi++;
...
float z = zl + xai*dzx;
int *buf = this->buf + j*w + xai;
int *zbuf = zbuffer->buf + j*w + xai;
for(i=xai; i<xbi; i++)
{
int tempz = z*0x10000;
if(tempz > *zbuf)
{
*buf = col;
*zbuf = tempz;
}
buf++;
zbuf++;
z += dzx;
}
fxa += fdxa; fxb += fdxb;
zl += dzy;
}
Adding 0xFFFF before the shift rounds each edge up to the next whole pixel, as the comments say. There's no back-face test in this one, so all six sides of the cube are drawn and the z-buffer sorts them out. The floor's black squares are 0, the same as the background, so only the white ones show.
The clock is smoothed too. Redraw() keeps the last ten frame times and passes ZGo their average:
for(i=0; i<9; i++) last_t[i] = last_t[i+1];
last_t[9] = t;
for(t=0, i=0; i<10; i++) t += last_t[i];
zgo->render(t*0.1*0.001);
That keeps the speed even when frames take different times, at the cost of running four and a half frames behind, and the first ten frames count from zeros, so it starts slowly.
ZGo1 and ZGoBuilder
ZGo1 tries out the plan in the functions note, one function per DLL. It's a Visual C++ application with a DLL called plugin, which exports one function, fnPlugin(), that returns 42. The program loads the DLL when its window opens, finds the function by its C++ decorated name, and paints the DLL's handle, the function's address and what it returns:
case WM_CREATE:
hPlugin = LoadLibrary("plugin.dll");
pProc = GetProcAddress(hPlugin, "?fnPlugin@@YAHXZ");
break;
...
sprintf(msg, "%08X %08X %d", hPlugin, pProc, pProc());
DrawText(hdc, msg, strlen(msg), &rt, DT_CENTER);
ZGoBuilder is a Java applet, on a page titled “LKS Site3D v0.01” with the comment “Zero-Now 3D Web site v0.01”. It has a Gadget class, a box that can hold other boxes, find which one is under the mouse and be dragged, and it puts four 40x40 DragRect boxes in red, green, blue and white inside the applet. It wouldn't compile as it is: hitTest() returns false where it should return a Gadget, DragRect calls super.Gadget() and declares render() with no return type, and Gadget.render() calls render() on the Graphics it was given. The applet's paint() doesn't draw the gadgets anyway: it fills the applet with black and draws a 10x10 square, blue and yellow in turn.
The live version
The live version at the top of the page runs the same functions on the same buffers, in JavaScript, and puts each frame in a 256x256 canvas, scaled up by a whole number of screen pixels so every original pixel stays sharp and square. The sine table, the plasmas, the lookups, New Stylee's random(), circles and blur, and PatternsMK2's box blur with its unwritten column all work out the same whole numbers as the C, and the 3D curve rounds its floats to 32 bits as the original's do. The colour channels are kept in the original byte order and swapped round only when the frame goes on the canvas.
The C relies on int arithmetic wrapping at 32 bits. JavaScript's numbers don't wrap, so the port's random() uses Math.imul() and | 0 to get the same overflow:
r1 = ( r1 + 19198 ) | 0;
r2 = ( r2 + ( ( Math.imul( r1, 0x982191 ) - 328923 ) | 0 ) ) | 0;
r3 = r3 << 1;
r3 ^= r2;
var v = ( Math.imul( r3, 7 ) + Math.imul( r2, 33 ) + Math.imul( r1, 4 ) ) | 0;
return min + ( ( ( v / 3 ) | 0 ) % dif );
The buttons switch between the six different pictures. Pattern v1 and v2 draw the same thing and share a button, as do v3 and v4. A few things can't be the same:
- There's no window to drag around, and Escape can't close a browser tab, so there are no controls other than the buttons.
timeGetTime()counts from when Windows started, so where the plasmas were when a program opened depended on how long the machine had been on. The port counts from when you press a button, and starts Pattern v1 and v2 at 11,747 milliseconds, which is the frame in the picture above.- New Stylee moves its circles once per frame, as fast as the machine would go. The port moves them 60 times a second.
- Circles that hang over the bottom edge aren't drawn on past the end of the buffer, and PatternsMK2 v1's never-written column of the blur's temporary image is taken to be 0, as fresh memory from
malloc()usually is. - PatternsMK2 v3 is left out, since it's a 640x480 window with no pattern in it.
The ZGo player in the middle of the page runs ZGoTest, version 8 and ZEffect in the same way. It builds the same objects as the originals, a ZGoDATA for every value and an object for every function, wired up in the same order, so each frame is pulled through the network just as it was. Version 8 keeps its floats as 32-bit floats and uses MSVC's rand(), never seeded, so the cubes land where they did. The differences:
- The two JPEGs are decoded by the browser, which can differ from Java's decoder by a level here and there.
- ZEffect steps 50 times a second, which is what its 20ms sleep allows when painting takes no time.
- Version 8 did its sums in the x87's 80-bit registers before storing floats, and the port does them in doubles, so the last bit can come out differently.
- The scene's old lists of sub-objects are cleared up by the browser instead of piling up.
- Versions 5 to 7 aren't there: version 5 doesn't compile, and 6 and 7 take their colours from memory addresses.
Source
Pattern
- pattern-v1/Main.cpp C++ · 203 lines · the window and Redraw()
- pattern-v1/Pattern.cpp C++ · 267 lines · the sine table, plasmas, lookups and display
- pattern-v1/Pattern.h C header · 32 lines
- pattern-v2/Main.cpp C++ · 216 lines · adds Escape
- pattern-v2/Pattern.cpp C++ · 267 lines
- pattern-v2/Pattern.h C header · 32 lines
- pattern-v3/Main.cpp C++ · 235 lines · Plasma3 through the other two
- pattern-v3/Pattern.cpp C++ · 273 lines
- pattern-v3/Pattern.h C header · 32 lines
- pattern-v3/script.txt Text · 19 lines · the ZGo pattern script
- pattern-v4/Main.cpp C++ · 232 lines
- pattern-v4/Pattern.cpp C++ · 273 lines
- pattern-v4/Pattern.h C header · 32 lines
- pattern-v4/script.txt Text · 19 lines
New Stylee
- new-stylee-v1/Main.cpp C++ · 508 lines · random(), the circles, the blur and the glow
- new-stylee-v1/Pattern.cpp C++ · 273 lines
- new-stylee-v1/Pattern.h C header · 36 lines · 32-bit images
- new-stylee-v1/script.txt Text · 19 lines
- new-stylee-v2/Main.cpp C++ · 587 lines · 70 circles on whole pixels
- new-stylee-v2/Pattern.cpp C++ · 273 lines
- new-stylee-v2/Pattern.h C header · 36 lines
- new-stylee-v2/script.txt Text · 19 lines
PatternsMK2
- patterns-mk2-v1/Main.cpp C++ · 209 lines
- patterns-mk2-v1/Pattern.cpp C++ · 638 lines · Pattern_Main(), the box blur and the ripple
- patterns-mk2-v1/Pattern.h C header · 51 lines · pattern_var and pattern_const
- patterns-mk2-v2/Main.cpp C++ · 281 lines · time in seconds, and the key state
- patterns-mk2-v2/Pattern.cpp C++ · 661 lines
- patterns-mk2-v2/Pattern.h C header · 59 lines
- patterns-mk2-v2/Pattern2.h C header · 380 lines · the function classes and the networks
- patterns-mk2-v2/Pattern2.cpp C++ · 11 lines
- patterns-mk2-v2/functions_real.h C header · 102 lines · arithmetic on reals
- patterns-mk2-v2/functions_alpha.h C header · 115 lines · gradients, plasmas and fades
- patterns-mk2-v2/functions_rgb.h C header · 243 lines · checkers, lookups, stretches and the box blur
- patterns-mk2-v2/functions_drums.h C header · 11 lines · the space bar
- patterns-mk2-v2/functions_vis.h C header · 44 lines · a number as a bar chart
- patterns-mk2-v2/functions_simple3d.h C header · 408 lines · matrices, a triangle filler and SwirlyThing1
- patterns-mk2-v2/type_simple3d.h C header · empty
- patterns-mk2-v3/Main.cpp C++ · 174 lines · a 640x480 window
- patterns-mk2-v3/zn.h C header · 241 lines · the Image and ZN classes
- patterns-mk2-v3/zn.cpp C++ · 1 line
ZGo design notes
- zgo/1. Introduction.txt Text · 6 lines · a network of functions
- zgo/2. Data Types.txt Text · 47 lines · integers, reals, channels and images
- zgo/3. Functions.txt Text · 52 lines · native and user functions, DLLs and wrappers
- zgo/4. Networks.txt Text · 13 lines
ZEffect
- zgo/old/ZEffect.java Java · 228 lines · the averaging grid and the ZObject groups
- zgo/old/z.htm HTML · 10 lines · the applet's page
PatternsMK2 v4 (Java)
- patterns-mk2-v4/ZGoTest.java Java · 458 lines · ZGoDATA, ZGoFn, the sine, the fader and the network
- patterns-mk2-v4/tinyptc.java Java · 136 lines · TinyPTC by Gaffer
- patterns-mk2-v4/index.html HTML · 18 lines · the applet's page
- patterns-mk2-v4/image1.jpg · image2.jpg JPEG · 512x240 · the two pictures
PatternsMK2 v5
- patterns-mk2-v5/Main.cpp C++ · 187 lines · the 512x240 window
- patterns-mk2-v5/ZGoMain.cpp C++ · 85 lines · the torus, tiled
- patterns-mk2-v5/ZGo3DOBJECT.cpp C++ · 257 lines · the cube and the torus; doesn't compile as it stands
- patterns-mk2-v5/ZGoIMAGE.cpp C++ · 561 lines · the environment map and the triangle fillers
- Also: ZGo.h · ZGo.cpp · ZGoMain.h · ZGo3DOBJECT.h · ZGoIMAGE.h · AnimatedRotationMatrix.cpp · .h · Subdivide3DObject.cpp · .h · ZGoBOOL.cpp · .h · ZGoCAMERA.cpp · .h · ZGoChecker.cpp · .h · ZGoDistort3D.cpp · .h · ZGoDISTORTION3D.cpp · .h · ZGoFader.cpp · .h · ZGoINT.cpp · .h · ZGoKeyPressed.cpp · .h · ZGoMATRIX.cpp · .h · ZGoMatrixMul.cpp · .h · ZGoNAME.cpp · .h · ZGoPARAMS.cpp · .h · ZGoREAL.cpp · .h · ZGoRender.cpp · .h · ZGoSine.cpp · .h · ZGoTile.cpp · .h · ZGoTransform.cpp · .h
PatternsMK2 v6
- patterns-mk2-v6/ZGoMain.cpp C++ · 60 lines · the plane from (-1, 2, -4)
- patterns-mk2-v6/ZGoPointCamera.cpp C++ · 77 lines · a camera from a position and a point to look at
- patterns-mk2-v6/ZGo3DOBJECT.cpp C++ · 295 lines · colours from the object's address
- Also: Main.cpp · ZGo.h · ZGo.cpp · ZGoMain.h · ZGoPointCamera.h · ZGo3DOBJECT.h · AnimatedRotationMatrix.cpp · .h · Subdivide3DObject.cpp · .h · ZGoBOOL.cpp · .h · ZGoCAMERA.cpp · .h · ZGoChecker.cpp · .h · ZGoDistort3D.cpp · .h · ZGoDISTORTION3D.cpp · .h · ZGoFader.cpp · .h · ZGoIMAGE.cpp · .h · ZGoINT.cpp · .h · ZGoKeyPressed.cpp · .h · ZGoMATRIX.cpp · .h · ZGoMatrixMul.cpp · .h · ZGoMerge3D.cpp · .h · ZGoNAME.cpp · .h · ZGoPARAMS.cpp · .h · ZGoREAL.cpp · .h · ZGoRender.cpp · .h · ZGoSine.cpp · .h · ZGoTile.cpp · .h · ZGoTransform.cpp · .h · ZGoVECTOR.cpp · .h
PatternsMK2 v7
- patterns-mk2-v7/Scene1.cpp C++ · 135 lines · the scene moved by hand
- patterns-mk2-v7/ZGo.cpp C++ · 173 lines · adds updated()
- patterns-mk2-v7/ZGoRealFreezer.cpp C++ · 49 lines · one of the new real-number functions
- Also: Main.cpp · ZGo.h · Scene1.h · ZGoRealFreezer.h · AnimatedRotationMatrix.cpp · .h · Subdivide3DObject.cpp · .h · ZGo3DOBJECT.cpp · .h · ZGoARRAY2D.cpp · .h · ZGoBOOL.cpp · .h · ZGoCAMERA.cpp · .h · ZGoChecker.cpp · .h · ZGoDistort3D.cpp · .h · ZGoDISTORTION3D.cpp · .h · ZGoFader.cpp · .h · ZGoIMAGE.cpp · .h · ZGoINT.cpp · .h · ZGoIsRealIncreasing.cpp · .h · ZGoKeyPressed.cpp · .h · ZGoMain.cpp · .h · ZGoMakeShiftMatrix.cpp · .h · ZGoMakeVector.cpp · .h · ZGoMATRIX.cpp · .h · ZGoMatrixMul.cpp · .h · ZGoMerge3D.cpp · .h · ZGoNAME.cpp · .h · ZGoPARAMS.cpp · .h · ZGoPointCamera.cpp · .h · ZGoRandom.cpp · .h · ZGoRANDOMARRAY2D.cpp · .h · ZGoREAL.cpp · .h · ZGoRealAdd.cpp · .h · ZGoRealDiv.cpp · .h · ZGoRealFloor.cpp · .h · ZGoRealMod.cpp · .h · ZGoRealMul.cpp · .h · ZGoRealSqrt.cpp · .h · ZGoRealSub.cpp · .h · ZGoRender.cpp · .h · ZGoSine.cpp · .h · ZGoTile.cpp · .h · ZGoTransform.cpp · .h · ZGoVECTOR.cpp · .h
PatternsMK2 v8
- patterns-mk2-v8/Main.cpp C++ · 190 lines · the window and the averaged clock
- patterns-mk2-v8/Scene1.cpp C++ · 54 lines · scene, camera and render
- patterns-mk2-v8/MakeScene1.cpp C++ · 136 lines · the floor and the cube
- patterns-mk2-v8/MakeCamera1.cpp C++ · 69 lines · the camera's path
- patterns-mk2-v8/MakeObject1.cpp C++ · 35 lines · another MakeScene1, with just the floor
- patterns-mk2-v8/ZGo.h C header · 182 lines · ZGoDATA, ZGoFn, and every type and function
- patterns-mk2-v8/ZGo.cpp C++ · 173 lines · process(), render() and display()
- patterns-mk2-v8/ZGo3DOBJECT.cpp C++ · 368 lines · plane, cube and render()
- patterns-mk2-v8/ZGoCAMERA.cpp C++ · 55 lines · the projection
- patterns-mk2-v8/ZGoPointCamera.cpp C++ · 77 lines
- patterns-mk2-v8/ZGoIMAGE.cpp C++ · 561 lines · zbufTri()
- patterns-mk2-v8/ZGoMATRIX.cpp C++ · 244 lines · transform() and inverse()
- Also: Scene1.h · MakeScene1.h · MakeCamera1.h · MakeObject1.h · ZGo3DOBJECT.h · ZGoCAMERA.h · ZGoPointCamera.h · ZGoIMAGE.h · ZGoMATRIX.h · AnimatedRotationMatrix.cpp · .h · Subdivide3DObject.cpp · .h · ZGoARRAY2D.cpp · .h · ZGoBOOL.cpp · .h · ZGoChecker.cpp · .h · ZGoDistort3D.cpp · .h · ZGoDISTORTION3D.cpp · .h · ZGoFader.cpp · .h · ZGoINT.cpp · .h · ZGoIsRealIncreasing.cpp · .h · ZGoKeyPressed.cpp · .h · ZGoMain.cpp · .h · ZGoMakeShiftMatrix.cpp · .h · ZGoMakeVector.cpp · .h · ZGoMatrixMul.cpp · .h · ZGoMerge3D.cpp · .h · ZGoNAME.cpp · .h · ZGoPARAMS.cpp · .h · ZGoRandom.cpp · .h · ZGoRANDOMARRAY2D.cpp · .h · ZGoREAL.cpp · .h · ZGoRealAdd.cpp · .h · ZGoRealDiv.cpp · .h · ZGoRealFloor.cpp · .h · ZGoRealFreezer.cpp · .h · ZGoRealMod.cpp · .h · ZGoRealMul.cpp · .h · ZGoRealSqrt.cpp · .h · ZGoRealSub.cpp · .h · ZGoRender.cpp · .h · ZGoSine.cpp · .h · ZGoTile.cpp · .h · ZGoTransform.cpp · .h · ZGoVECTOR.cpp · .h
ZGo1
- zgo/ZGo1/code/main/main.cpp C++ · 216 lines · loads plugin.dll and calls fnPlugin()
- zgo/ZGo1/code/plugin/plugin.cpp C++ · 69 lines · fnPlugin() returns 42
- Also: main.h · main.rc · resource.h · StdAfx.cpp · StdAfx.h · plugin.h · plugin/StdAfx.cpp · plugin/StdAfx.h
ZGoBuilder
- zgo/ZGoBuilder/ZGoBuilder.java Java · 287 lines · Gadget and DragRect
- zgo/ZGoBuilder/ZGoBuilder.html HTML · 24 lines · “LKS Site3D v0.01”
Live version
- live/demo.js JavaScript · 1908 lines · the canvas port of all six pictures and the three ZGo programs
- live/index.html HTML · 38 lines · the pattern player
- live/zgo.html HTML · 35 lines · the ZGo player