/* * board3d.c - Lite Blue's 3D board: composite pre-rendered sprites into a GIF. * * The atlas (render/pack.py) holds the empty board seen from each side, * every piece on every square (rendered once, offline, on obsidian's * GXT120P), a 256-colour palette and an RGB555 lookup into it. A request * only copies, blends and encodes: no 3D work, no access to the card. * * Black's view is White's camera turned 180 degrees about the board centre, * so a piece on square s seen by Black is the sprite rendered on 63 - s. * "Position" below means where a square appears in the picture, in White's * coordinates: pos = s for White, 63 - s for Black. * * Claude Opus 5.5, October 2026. */ #include #include #include #include "board3d.h" #define MAXW 640 #define MAXH 480 #define NLEV 10 #define ENTRY (16 + 6 * NLEV) #define NSPR 768 typedef struct { int colour, type, pos, x0, y0, w, h; long off; short lev[NLEV][3]; /* y, xleft, xright */ } Spr; static FILE *af; static int W, H, nspr; static unsigned char pal[768], lut[32768]; static short grid[81][2]; /* 1/8 pixel */ static long bg_off[2]; static Spr spr[NSPR]; static Spr *idx[2][7][64]; static unsigned char img[MAXW * MAXH * 3]; static int be16(const unsigned char *p) { return (p[0] << 8) | p[1]; } static int sbe16(const unsigned char *p) { int v = be16(p); return v >= 32768 ? v - 65536 : v; } static long be32(const unsigned char *p) { return ((long) p[0] << 24) | ((long) p[1] << 16) | ((long) p[2] << 8) | (long) p[3]; } int b3_open(const char *path) { unsigned char h[12], e[ENTRY], g[324]; long pos; int i, j; af = fopen(path, "rb"); if (af == NULL || fread(h, 1, 12, af) != 12 || memcmp(h, "LBA1", 4) != 0) return -1; W = be16(h + 4); H = be16(h + 6); nspr = be16(h + 8); if (W > MAXW || H > MAXH || nspr > NSPR) return -1; if (fread(pal, 1, 768, af) != 768 || fread(lut, 1, 32768, af) != 32768 || fread(g, 1, 324, af) != 324) return -1; for (i = 0; i < 81; i++) { grid[i][0] = (short) sbe16(g + 4 * i); grid[i][1] = (short) sbe16(g + 4 * i + 2); } pos = 12 + 768 + 32768 + 324; bg_off[0] = pos; bg_off[1] = pos + 3L * W * H; if (fseek(af, pos + 6L * W * H, SEEK_SET) != 0) return -1; for (i = 0; i < nspr; i++) { Spr *s = &spr[i]; if (fread(e, 1, ENTRY, af) != ENTRY) return -1; s->colour = e[0]; s->type = e[1]; s->pos = e[2]; s->x0 = sbe16(e + 4); s->y0 = sbe16(e + 6); s->w = be16(e + 8); s->h = be16(e + 10); s->off = be32(e + 12); for (j = 0; j < NLEV * 3; j++) s->lev[j / 3][j % 3] = (short) sbe16(e + 16 + 2 * j); if (s->colour > 1 || s->type < 1 || s->type > 6 || s->pos > 63 || s->x0 < 0 || s->y0 < 0 || s->x0 + s->w > W || s->y0 + s->h > H) return -1; idx[s->colour][s->type][s->pos] = s; } return 0; } int b3_width(void) { return W; } int b3_height(void) { return H; } /* ------------------------------------------------------------------ */ static void tint(int pos, const unsigned char *rgb, int a) { int f = pos & 7, r = pos >> 3, i, x, y, x0, x1, y0, y1; int px[4], py[4]; const short *c[4]; c[0] = grid[r * 9 + f]; c[1] = grid[r * 9 + f + 1]; c[2] = grid[(r + 1) * 9 + f + 1]; c[3] = grid[(r + 1) * 9 + f]; x0 = x1 = c[0][0]; y0 = y1 = c[0][1]; for (i = 0; i < 4; i++) { px[i] = c[i][0]; py[i] = c[i][1]; if (px[i] < x0) x0 = px[i]; if (px[i] > x1) x1 = px[i]; if (py[i] < y0) y0 = py[i]; if (py[i] > y1) y1 = py[i]; } x0 = x0 / 8; x1 = x1 / 8 + 1; y0 = y0 / 8; y1 = y1 / 8 + 1; if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > W - 1) x1 = W - 1; if (y1 > H - 1) y1 = H - 1; for (y = y0; y <= y1; y++) for (x = x0; x <= x1; x++) { long sx = 8L * x + 4, sy = 8L * y + 4; int in = 1; for (i = 0; i < 4 && in; i++) { int j = (i + 1) & 3; if ((long)(px[j] - px[i]) * (sy - py[i]) - (long)(py[j] - py[i]) * (sx - px[i]) > 0) in = 0; } if (in) { unsigned char *p = img + 3 * (y * W + x); for (i = 0; i < 3; i++) p[i] = (unsigned char)((p[i] * (256 - a) + rgb[i] * a) >> 8); } } } static int blit(const Spr *s) { unsigned char rh[4], row[MAXW * 4]; int y, x; if (fseek(af, s->off, SEEK_SET) != 0) return -1; for (y = 0; y < s->h; y++) { int start, len; unsigned char *d; if (fread(rh, 1, 4, af) != 4) return -1; start = be16(rh); len = be16(rh + 2); if (start + len > s->w || fread(row, 4, (size_t) len, af) != (size_t) len) return -1; d = img + 3 * ((s->y0 + y) * W + s->x0 + start); for (x = 0; x < len; x++, d += 3) { const unsigned char *p = row + 4 * x; int a = p[3], k; for (k = 0; k < 3; k++) d[k] = (unsigned char)((p[k] * a + d[k] * (255 - a) + 127) / 255); } } return 0; } /* ------------------------------------------------------------------ */ /* GIF: 8-bit LZW, codes up to 12 bits, a clear code when the table fills. */ #define HBITS 13 /* 8192 slots for at most 3838 codes */ #define HSIZE (1 << HBITS) static FILE *gout; static unsigned char blk[256]; static int blkn; static unsigned long acc; static int accbits; static void gbyte(int c) { blk[++blkn] = (unsigned char) c; if (blkn == 255) { blk[0] = 255; fwrite(blk, 1, 256, gout); blkn = 0; } } static void gcode(int code, int bits) { acc |= (unsigned long) code << accbits; accbits += bits; while (accbits >= 8) { gbyte((int)(acc & 0xff)); acc >>= 8; accbits -= 8; } } static void lzw(const unsigned char *px, long n) { static long hkey[HSIZE]; static short hcode[HSIZE]; int bits = 9, next = 258, prefix, i; long k; for (i = 0; i < HSIZE; i++) hkey[i] = -1; blkn = 0; acc = 0; accbits = 0; gcode(256, bits); prefix = px[0]; for (k = 1; k < n; k++) { int c = px[k]; long key = ((long) prefix << 8) | c; /* multiplicative hash of the 20-bit key, then linear probing */ unsigned int h = (unsigned int)((((unsigned long) key * 2654435761UL) & 0xffffffffUL) >> (32 - HBITS)); while (hkey[h] != -1 && hkey[h] != key) h = (h + 1) & (HSIZE - 1); if (hkey[h] == key) { prefix = hcode[h]; continue; } gcode(prefix, bits); if (next < 4096) { hkey[h] = key; hcode[h] = (short) next; if (next == (1 << bits) && bits < 12) bits++; next++; } else { gcode(256, bits); for (i = 0; i < HSIZE; i++) hkey[i] = -1; bits = 9; next = 258; } prefix = c; } gcode(prefix, bits); gcode(257, bits); if (accbits > 0) gbyte((int)(acc & 0xff)); if (blkn > 0) { blk[0] = (unsigned char) blkn; fwrite(blk, 1, (size_t) blkn + 1, gout); } } /* ------------------------------------------------------------------ */ static const unsigned char TINT_RGB[4][3] = { { 0, 0, 0 }, { 240, 216, 96 }, { 110, 200, 100 }, { 245, 150, 60 } }; static const int TINT_A[4] = { 0, 120, 130, 125 }; static const int BAYER[4][4] = { {0, 8, 2, 10}, {12, 4, 14, 6}, {3, 11, 1, 9}, {15, 7, 13, 5} }; static const int FILE_ORDER[8] = { 0, 7, 1, 6, 2, 5, 3, 4 }; /* outer files first */ static unsigned char ix[MAXW * MAXH]; int b3_compose(const int *pc, const int *ptype, int black, const unsigned char *tints) { int i, r, x, y; long n = (long) W * H; if (fseek(af, bg_off[black ? 1 : 0], SEEK_SET) != 0 || fread(img, 3, (size_t) n, af) != (size_t) n) return -1; for (i = 0; i < 64; i++) if (tints[i]) tint(black ? 63 - i : i, TINT_RGB[tints[i]], TINT_A[tints[i]]); for (r = 7; r >= 0; r--) for (i = 0; i < 8; i++) { int pos = r * 8 + FILE_ORDER[i], sq = black ? 63 - pos : pos; if (ptype[sq] && (idx[pc[sq]][ptype[sq]][pos] == NULL || blit(idx[pc[sq]][ptype[sq]][pos]))) return -1; } for (y = 0; y < H; y++) for (x = 0; x < W; x++) { const unsigned char *p = img + 3 * (y * W + x); int d = BAYER[y & 3][x & 3] / 2, c[3], k; for (k = 0; k < 3; k++) { c[k] = p[k] + d; if (c[k] > 255) c[k] = 255; c[k] >>= 3; } ix[y * W + x] = lut[(c[0] << 10) | (c[1] << 5) | c[2]]; } return 0; } void b3_write_gif(FILE *out) { unsigned char hd[13]; long n = (long) W * H; gout = out; fwrite("GIF87a", 1, 6, out); hd[0] = (unsigned char)(W & 255); hd[1] = (unsigned char)(W >> 8); hd[2] = (unsigned char)(H & 255); hd[3] = (unsigned char)(H >> 8); hd[4] = 0xF7; hd[5] = 0; hd[6] = 0; fwrite(hd, 1, 7, out); fwrite(pal, 1, 768, out); hd[0] = 0x2C; hd[1] = hd[2] = hd[3] = hd[4] = 0; hd[5] = (unsigned char)(W & 255); hd[6] = (unsigned char)(W >> 8); hd[7] = (unsigned char)(H & 255); hd[8] = (unsigned char)(H >> 8); hd[9] = 0; hd[10] = 8; fwrite(hd, 1, 11, out); lzw(ix, n); fputc(0, out); fputc(0x3B, out); } /* ------------------------------------------------------------------ */ /* Image-map shapes, in pixels */ void b3_square(int pos, int *xy) { int f = pos & 7, r = pos >> 3; const short *c[4]; int i; c[0] = grid[r * 9 + f]; c[1] = grid[r * 9 + f + 1]; c[2] = grid[(r + 1) * 9 + f + 1]; c[3] = grid[(r + 1) * 9 + f]; for (i = 0; i < 4; i++) { xy[2 * i] = (c[i][0] + 4) / 8; xy[2 * i + 1] = (c[i][1] + 4) / 8; } } int b3_outline(int colour, int type, int pos, int *xy) { const Spr *s = idx[colour][type][pos]; int i, n = 0; if (s == NULL) return 0; for (i = 0; i < NLEV; i++) { xy[n++] = s->lev[i][1]; xy[n++] = s->lev[i][0]; } for (i = NLEV - 1; i >= 0; i--) { xy[n++] = s->lev[i][2]; xy[n++] = s->lev[i][0]; } return n / 2; }