/* =========================================================================== * VECTORGONS -- Commodore 64 edition * --------------------------------------------------------------------------- * A tumbling-wireframe vector demo for the C64, a tiny cousin of the desktop / * web Vectorgons. The 1 MHz 6510 has no FPU, so everything here is integer * fixed-point: a 256-entry signed sine table (scale 128) drives the 3-D * rotation, points are perspective-projected with integer division, and edges * are drawn into the VIC-II hi-res bitmap with a Bresenham line routine. * * Per the brief the glass / metal / mirror objects are dropped (no shading on * a 1-bit bitmap) -- what remains is the heart of Vectorgons: rotating * polytopes, cycling through a small library of 3-D shapes plus a 4-D * tesseract shadow, in classic monochrome vector-graphics style. * * Display : 320x200 hi-res bitmap at $2000, colour cells in screen RAM $0400. * Build : cc65 (cl65 -t c64 -C c64-vg.cfg). See the Makefile in this dir. * Controls: press any key to quit back to BASIC. * =========================================================================== */ #include #include /* ---- VIC-II / memory layout ------------------------------------------- */ #define BITMAP ((unsigned char *)0x2000) /* 8 KB hi-res bitmap */ #define SCREEN ((unsigned char *)0x0400) /* colour cells (1000 bytes) */ #define VIC_D011 0xD011 #define VIC_D016 0xD016 #define VIC_D018 0xD018 #define VIC_D020 0xD020 /* border colour */ #define VIC_D021 0xD021 /* background colour */ #define KBD_NDX 0x00C6 /* keyboard buffer count */ /* ---- projection constants (model units) ------------------------------- */ #define PROJ 224 /* focal length */ #define DIST 140 /* camera distance */ #define SCRW 320 #define SCRH 200 #define MAXV 16 /* tesseract has the most vertices */ /* round(127 * sin(i * 2pi / 256)) -- fixed-point scale 128 (>>7) */ static const signed char sintab[256] = { 0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 51, 54, 57, 60, 63, 65, 68, 71, 73, 76, 78, 81, 83, 85, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 107, 109, 111, 112, 113, 115, 116, 117, 118, 120, 121, 122, 122, 123, 124, 125, 125, 126, 126, 126, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 125, 125, 124, 123, 122, 122, 121, 120, 118, 117, 116, 115, 113, 112, 111, 109, 107, 106, 104, 102, 100, 98, 96, 94, 92, 90, 88, 85, 83, 81, 78, 76, 73, 71, 68, 65, 63, 60, 57, 54, 51, 49, 46, 43, 40, 37, 34, 31, 28, 25, 22, 19, 16, 12, 9, 6, 3, 0, -3, -6, -9, -12, -16, -19, -22, -25, -28, -31, -34, -37, -40, -43, -46, -49, -51, -54, -57, -60, -63, -65, -68, -71, -73, -76, -78, -81, -83, -85, -88, -90, -92, -94, -96, -98, -100, -102, -104, -106, -107, -109, -111, -112, -113, -115, -116, -117, -118, -120, -121, -122, -122, -123, -124, -125, -125, -126, -126, -126, -127, -127, -127, -127, -127, -127, -127, -126, -126, -126, -125, -125, -124, -123, -122, -122, -121, -120, -118, -117, -116, -115, -113, -112, -111, -109, -107, -106, -104, -102, -100, -98, -96, -94, -92, -90, -88, -85, -83, -81, -78, -76, -73, -71, -68, -65, -63, -60, -57, -54, -51, -49, -46, -43, -40, -37, -34, -31, -28, -25, -22, -19, -16, -12, -9, -6, -3, }; /* =========================================================================== * Shape library. Vertices are signed-char model coords (~ +/-45); edges are * vertex-index pairs. Skip metal/glass/mirror -- pure wireframe polytopes. * =========================================================================== */ static const signed char tetra_v[4][3] = { { 45, 45, 45}, { 45,-45,-45}, {-45, 45,-45}, {-45,-45, 45} }; static const unsigned char tetra_e[6][2] = { {0,1},{0,2},{0,3},{1,2},{1,3},{2,3} }; static const signed char cube_v[8][3] = { {-40,-40,-40},{ 40,-40,-40},{ 40, 40,-40},{-40, 40,-40}, {-40,-40, 40},{ 40,-40, 40},{ 40, 40, 40},{-40, 40, 40} }; static const unsigned char cube_e[12][2] = { {0,1},{1,2},{2,3},{3,0}, {4,5},{5,6},{6,7},{7,4}, {0,4},{1,5},{2,6},{3,7} }; static const signed char octa_v[6][3] = { { 50,0,0},{-50,0,0},{0, 50,0},{0,-50,0},{0,0, 50},{0,0,-50} }; static const unsigned char octa_e[12][2] = { {0,2},{0,3},{0,4},{0,5},{1,2},{1,3},{1,4},{1,5},{2,4},{2,5},{3,4},{3,5} }; static const signed char pyra_v[5][3] = { {-40,-30,-40},{ 40,-30,-40},{ 40,-30, 40},{-40,-30, 40},{ 0, 48, 0} }; static const unsigned char pyra_e[8][2] = { {0,1},{1,2},{2,3},{3,0},{4,0},{4,1},{4,2},{4,3} }; /* Stellated "star": two interpenetrating tetrahedra (Stella octangula). */ static const signed char star_v[8][3] = { { 45, 45, 45},{ 45,-45,-45},{-45, 45,-45},{-45,-45, 45}, {-45,-45,-45},{-45, 45, 45},{ 45,-45, 45},{ 45, 45,-45} }; static const unsigned char star_e[12][2] = { {0,1},{0,2},{0,3},{1,2},{1,3},{2,3}, {4,5},{4,6},{4,7},{5,6},{5,7},{6,7} }; /* Tesseract shadow: an inner cube nested in an outer cube, corners linked. */ static const signed char tess_v[16][3] = { {-20,-20,-20},{ 20,-20,-20},{ 20, 20,-20},{-20, 20,-20}, {-20,-20, 20},{ 20,-20, 20},{ 20, 20, 20},{-20, 20, 20}, {-46,-46,-46},{ 46,-46,-46},{ 46, 46,-46},{-46, 46,-46}, {-46,-46, 46},{ 46,-46, 46},{ 46, 46, 46},{-46, 46, 46} }; static const unsigned char tess_e[32][2] = { {0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7}, {8,9},{9,10},{10,11},{11,8},{12,13},{13,14},{14,15},{15,12}, {8,12},{9,13},{10,14},{11,15}, {0,8},{1,9},{2,10},{3,11},{4,12},{5,13},{6,14},{7,15} }; typedef struct { const signed char (*v)[3]; unsigned char nv; const unsigned char (*e)[2]; unsigned char ne; } Shape; static const Shape shapes[] = { { cube_v, 8, cube_e, 12 }, { octa_v, 6, octa_e, 12 }, { tetra_v, 4, tetra_e, 6 }, { star_v, 8, star_e, 12 }, { pyra_v, 5, pyra_e, 8 }, { tess_v, 16, tess_e, 32 } }; #define NSHAPES (sizeof(shapes) / sizeof(shapes[0])) /* Foreground colours cycled per shape (upper nibble; background black). */ static const unsigned char palette[] = { 3, 14, 1, 7, 13, 5, 4 }; #define NCOL (sizeof(palette) / sizeof(palette[0])) /* ---- precomputed bitmap address tables (built at startup) ------------- */ static unsigned int ytab[SCRH]; /* (y>>3)*320 + (y&7) */ static unsigned int xtab[SCRW]; /* (x>>3)*8 */ static unsigned char mtab[SCRW]; /* 0x80 >> (x&7) */ /* ---- per-frame projected coordinates ---------------------------------- */ static int sx[MAXV], sy[MAXV]; /* current frame */ static int psx[MAXV], psy[MAXV]; /* previous frame */ static void build_tables(void) { unsigned int y, x; for (y = 0; y < SCRH; ++y) ytab[y] = (unsigned int)((y >> 3) * 320 + (y & 7)); for (x = 0; x < SCRW; ++x) { xtab[x] = (unsigned int)((x >> 3) * 8); mtab[x] = (unsigned char)(0x80 >> (x & 7)); } } /* Set bitmap mode, bitmap @ $2000, colour cells @ $0400, black border. */ static void gfx_on(unsigned char colour) { memset(BITMAP, 0, 8000); /* clear bitmap */ memset(SCREEN, colour, 1000); /* fg/bg per cell */ POKE(VIC_D020, 0); /* black border */ POKE(VIC_D021, 0); /* black background */ POKE(VIC_D018, 0x18); /* screen $0400, bmp $2000 */ POKE(VIC_D016, PEEK(VIC_D016) & 0xEF);/* hi-res (MCM off) */ POKE(VIC_D011, PEEK(VIC_D011) | 0x20);/* bitmap mode (BMM on) */ } /* Restore the normal text screen on the way out. */ static void gfx_off(void) { POKE(VIC_D011, PEEK(VIC_D011) & 0xDF);/* BMM off */ POKE(VIC_D018, 0x15); /* default char/screen */ POKE(VIC_D020, 14); /* default border */ POKE(VIC_D021, 6); /* default background */ memset(SCREEN, 0x20, 1000); /* blank text screen */ memset((unsigned char *)0xD800, 14, 1000); /* light-blue text */ } /* Plot one pixel. set != 0 turns the pixel on, otherwise clears it. * Off-screen points are silently clipped. */ static void plot(int x, int y, unsigned char set) { unsigned char *p; if (x < 0 || x >= SCRW || y < 0 || y >= SCRH) return; p = BITMAP + ytab[y] + xtab[x]; if (set) *p |= mtab[x]; else *p &= (unsigned char)~mtab[x]; } /* Integer Bresenham line from (x0,y0) to (x1,y1). */ static void line(int x0, int y0, int x1, int y1, unsigned char set) { int dx = (x1 > x0) ? (x1 - x0) : (x0 - x1); int dy = -((y1 > y0) ? (y1 - y0) : (y0 - y1)); int sx0 = (x0 < x1) ? 1 : -1; int sy0 = (y0 < y1) ? 1 : -1; int err = dx + dy; int e2; for (;;) { plot(x0, y0, set); if (x0 == x1 && y0 == y1) break; e2 = err << 1; if (e2 >= dy) { err += dy; x0 += sx0; } if (e2 <= dx) { err += dx; y0 += sy0; } } } /* Rotate the current shape's vertices by (ax,ay,az) and perspective-project * into sx[]/sy[]. All maths integer; sin/cos read from sintab (>>7). */ static void project(const Shape *sh, unsigned char ax, unsigned char ay, unsigned char az) { int sinx = sintab[ax]; int cosx = sintab[(unsigned char)(ax + 64)]; int siny = sintab[ay]; int cosy = sintab[(unsigned char)(ay + 64)]; int sinz = sintab[az]; int cosz = sintab[(unsigned char)(az + 64)]; unsigned char i; for (i = 0; i < sh->nv; ++i) { int x = sh->v[i][0]; int y = sh->v[i][1]; int z = sh->v[i][2]; int t; int denom; /* rotate about X */ t = (y * cosx - z * sinx) >> 7; z = (y * sinx + z * cosx) >> 7; y = t; /* rotate about Y */ t = (x * cosy + z * siny) >> 7; z = (z * cosy - x * siny) >> 7; x = t; /* rotate about Z */ t = (x * cosz - y * sinz) >> 7; y = (x * sinz + y * cosz) >> 7; x = t; denom = z + DIST; /* always positive given coord ranges */ sx[i] = 160 + (x * PROJ) / denom; sy[i] = 100 - (y * PROJ) / denom; } } void main(void) { unsigned char shape = 0; unsigned char colidx = 0; unsigned char ax = 0, ay = 0, az = 0; unsigned int frame = 0; unsigned char first = 1; const Shape *sh; unsigned char i; build_tables(); POKE(KBD_NDX, 0); /* flush keyboard buffer */ gfx_on(palette[0] << 4); sh = &shapes[0]; for (;;) { /* --- advance rotation (three different rates => tumble) --- */ ax += 2; ay += 3; az += 1; project(sh, ax, ay, az); /* --- erase previous frame's edges, then draw the new ones --- */ if (!first) { for (i = 0; i < sh->ne; ++i) { unsigned char a = sh->e[i][0], b = sh->e[i][1]; line(psx[a], psy[a], psx[b], psy[b], 0); } } for (i = 0; i < sh->ne; ++i) { unsigned char a = sh->e[i][0], b = sh->e[i][1]; line(sx[a], sy[a], sx[b], sy[b], 1); } for (i = 0; i < sh->nv; ++i) { psx[i] = sx[i]; psy[i] = sy[i]; } first = 0; /* --- cycle to the next shape every ~200 frames --- */ if (++frame >= 200) { frame = 0; shape = (unsigned char)((shape + 1) % NSHAPES); colidx = (unsigned char)((colidx + 1) % NCOL); sh = &shapes[shape]; first = 1; memset(BITMAP, 0, 8000); /* fresh canvas */ memset(SCREEN, palette[colidx] << 4, 1000); } /* --- quit on any keypress --- */ if (PEEK(KBD_NDX) != 0) break; } gfx_off(); }