/* * lbrender.c - render Lite Blue's 3D board and pieces on the GXT120P (offline). * * lbrender OUT.bin OUT.idx everything (needs the card: X stopped) * lbrender -p OUT.bin OUT.idx a preview: one 640x480 frame of a * position, no supersampling * lbrender -b OUT.bin OUT.idx the page banner (BAN_W x BAN_H, low * camera, a few pieces), as B tiles * lbrender -bp OUT.bin OUT.idx a preview of the banner (one frame) * * The camera looks at the board from White's side. The final image is * IMG_W x IMG_H; the card renders at SS times that, in windows of at most * 640x480 placed anywhere on the big image (geometry outside a window is cut * by the card's clip registers). Every job is drawn twice, cleared to magenta * and to green: pixels equal in both are the object, the rest background. * pack.py turns the result into the atlas play.cgi composites from. * * Pieces are lathe profiles (the knight's head is an extruded, bevelled * outline), lit per vertex on the CPU (two lights, Blinn specular, a rim term * for the glass) and textured with procedural marble through TMODULATE. * * Index lines: * C ex ey ez rx ry rz ux uy uz fx fy fz focal cx cy SS IMG_W IMG_H * B tx ty x0 y0 w h offset board tile (supersampled coords) * S colour type facing square x0 y0 w h offset piece sprite * Each job is two w*h big-endian RGB565 frames at offset (magenta, green). * * Claude Opus 5.5, October 2026. Card library: ../../xshare/tex3d.c. */ #include #include #include #include #include "tex3d.h" #define IMG_W 560 #define IMG_H 420 #define SS 3 #define WIN_W 640 #define WIN_H 480 #define SEG 40 #define ZNEAR 4.0 #define CLEAR_A 0xF81F #define CLEAR_B 0x07E0 #define PI 3.14159265358979 typedef struct { double x, y, z; } V3; typedef struct { double r, g, b, amb, kd, ks, shin, rim; } Mat; static V3 v3(double x, double y, double z) { V3 v; v.x = x; v.y = y; v.z = z; return v; } static V3 add(V3 a, V3 b) { return v3(a.x + b.x, a.y + b.y, a.z + b.z); } static V3 sub(V3 a, V3 b) { return v3(a.x - b.x, a.y - b.y, a.z - b.z); } static V3 scl(V3 a, double s) { return v3(a.x * s, a.y * s, a.z * s); } static double dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } static V3 cross(V3 a, V3 b) { return v3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); } static V3 norm(V3 a) { double l = sqrt(dot(a, a)); return l > 0 ? scl(a, 1 / l) : a; } /* ------------------------------------------------------------------ */ /* Camera, lights, materials */ static V3 eye, cr, cu, cf, lkey, lfill; static double focal = 1, ccx, ccy; static int win_x, win_y, measuring; static double bx0, by0, bx1, by1; /* bbox while measuring */ static const Mat M_GLASS = { 150, 192, 236, 0.32, 0.78, 0.60, 38, 0.35 }; static const Mat M_OBSIDIAN = { 30, 32, 42, 0.55, 0.55, 0.90, 70, 0.22 }; static const Mat M_LIGHT = { 224, 233, 245, 0.58, 0.48, 0.12, 24, 0.0 }; static const Mat M_DARK = { 86, 124, 176, 0.58, 0.48, 0.18, 24, 0.0 }; static const Mat M_FRAME = { 44, 48, 60, 0.60, 0.50, 0.40, 40, 0.0 }; static void project_raw(V3 p, double *x, double *y, double *zc) { V3 q = sub(p, eye); *zc = dot(q, cf); *x = ccx + focal * dot(q, cr) / *zc; *y = ccy - focal * dot(q, cu) / *zc; } static void camera_setup(void) { V3 target = v3(4.0, 0.0, 3.55), up = v3(0, 1, 0); double mnx = 1e9, mny = 1e9, mxx = -1e9, mxy = -1e9, x, y, z, s; int i, j, k; double xs[2], ys[3], zs[2]; eye = v3(4.0, 8.3, -6.2); cf = norm(sub(target, eye)); cr = norm(cross(up, cf)); cu = cross(cf, cr); lkey = norm(add(add(scl(cr, -0.55), scl(cu, 0.85)), scl(cf, -0.45))); lfill = norm(add(add(scl(cr, 0.7), scl(cu, 0.25)), scl(cf, -0.55))); /* fit the frame and the tallest piece on the back rank into the image */ focal = 1; ccx = ccy = 0; xs[0] = -0.65; xs[1] = 8.65; zs[0] = -0.65; zs[1] = 8.65; ys[0] = -0.30; ys[1] = 0.0; ys[2] = 1.50; for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) for (k = 0; k < 2; k++) { if (j == 2 && k == 0) continue; project_raw(v3(xs[i], ys[j], zs[k] - (j == 2 ? 1.1 : 0)), &x, &y, &z); if (x < mnx) mnx = x; if (x > mxx) mxx = x; if (y < mny) mny = y; if (y > mxy) mxy = y; } s = (IMG_W - 16) / (mxx - mnx); if ((IMG_H - 12) / (mxy - mny) < s) s = (IMG_H - 12) / (mxy - mny); focal = s; ccx = IMG_W / 2.0 - s * (mnx + mxx) / 2; ccy = IMG_H / 2.0 - s * (mny + mxy) / 2; } static double clamp255(double v) { return v < 0 ? 0 : v > 255 ? 255 : v; } static void shade(V3 p, V3 n, const Mat *m, double *rgb) { V3 v = norm(sub(eye, p)), h; double d1 = dot(n, lkey), d2 = dot(n, lfill), dif, sp, nv, rim; if (d1 < 0) d1 = 0; if (d2 < 0) d2 = 0; dif = 0.85 * d1 + 0.35 * d2; h = norm(add(lkey, v)); sp = dot(n, h); sp = sp > 0 ? m->ks * pow(sp, m->shin) : 0; nv = dot(n, v); rim = nv < 1 ? m->rim * pow(1 - (nv > 0 ? nv : 0), 3) : 0; rgb[0] = clamp255(m->r * (m->amb + m->kd * dif) + 255 * sp + 150 * rim); rgb[1] = clamp255(m->g * (m->amb + m->kd * dif) + 255 * sp + 175 * rim); rgb[2] = clamp255(m->b * (m->amb + m->kd * dif) + 255 * sp + 215 * rim); } /* ------------------------------------------------------------------ */ /* Emitting triangles */ typedef struct { V3 p, n; double s, t; } Vert; static t3_vtx to_vtx(const Vert *v, const Mat *m) { t3_vtx o; double x, y, zc, rgb[3], w; project_raw(v->p, &x, &y, &zc); x = x * SS - win_x; y = y * SS - win_y; if (measuring) { if (x < bx0) bx0 = x; if (x > bx1) bx1 = x; if (y < by0) by0 = y; if (y > by1) by1 = y; } shade(v->p, v->n, m, rgb); w = 1 / zc; o.x = floor(x + 0.5); o.y = floor(y + 0.5); o.z = 65000 * (1 - ZNEAR / zc); o.r = rgb[0]; o.g = rgb[1]; o.b = rgb[2]; o.s = v->s * w; o.t = v->t * w; o.q = w; return o; } static void tri(const Vert *a, const Vert *b, const Vert *c, const Mat *m) { t3_vtx va = to_vtx(a, m), vb = to_vtx(b, m), vc = to_vtx(c, m); if (!measuring) t3_tri(va, vb, vc); } static void quad4(const Vert *a, const Vert *b, const Vert *c, const Vert *d, const Mat *m) { tri(a, b, c, m); tri(a, c, d, m); } /* ------------------------------------------------------------------ */ /* Lathe: profile of (radius, height) pairs, bottom to top. */ #define MAXP 128 static double prof[MAXP][2]; static int np; #define PR 1.08 /* piece radius and height scales */ #define PH 1.30 static void P(double r, double y) { if (np < MAXP) { prof[np][0] = r * PR; prof[np][1] = y * PH; np++; } } static void arc(double cr0, double cy0, double rad, double a0, double a1, int n) { int i; for (i = 0; i <= n; i++) { double a = (a0 + (a1 - a0) * i / n) * PI / 180; double r = cr0 + rad * cos(a); P(r < 0 ? 0 : r, cy0 + rad * sin(a)); } } static void lathe(V3 c, const Mat *m, double spin) { static double sn[MAXP][2]; /* segment normals in (r, y) */ double nr0, ny0, nr1, ny1; int j, k; for (j = 0; j + 1 < np; j++) { double dr = prof[j + 1][0] - prof[j][0], dy = prof[j + 1][1] - prof[j][1]; double l = sqrt(dr * dr + dy * dy); sn[j][0] = l > 0 ? dy / l : 1; sn[j][1] = l > 0 ? -dr / l : 0; } for (j = 0; j + 1 < np; j++) { if (prof[j][0] == 0 && prof[j + 1][0] == 0) continue; if (prof[j][1] == prof[j + 1][1] && prof[j][0] == prof[j + 1][0]) continue; /* smooth across a joint unless it is a crease (> ~35 degrees) */ nr0 = sn[j][0]; ny0 = sn[j][1]; if (j > 0 && sn[j - 1][0] * sn[j][0] + sn[j - 1][1] * sn[j][1] > 0.82) { nr0 = sn[j - 1][0] + sn[j][0]; ny0 = sn[j - 1][1] + sn[j][1]; } nr1 = sn[j][0]; ny1 = sn[j][1]; if (j + 2 < np && sn[j + 1][0] * sn[j][0] + sn[j + 1][1] * sn[j][1] > 0.82) { nr1 = sn[j + 1][0] + sn[j][0]; ny1 = sn[j + 1][1] + sn[j][1]; } for (k = 0; k < SEG; k++) { Vert q[4]; int i; for (i = 0; i < 4; i++) { int jj = j + (i == 1 || i == 2), kk = k + (i >= 2); double a = 2 * PI * kk / SEG + spin, ca = cos(a), sa = sin(a); double nr = jj == j ? nr0 : nr1, ny = jj == j ? ny0 : ny1; q[i].p = v3(c.x + prof[jj][0] * ca, c.y + prof[jj][1], c.z + prof[jj][0] * sa); q[i].n = norm(v3(nr * ca, ny, nr * sa)); q[i].s = 2.0 * kk / SEG; q[i].t = prof[jj][1] * 1.3 + 0.37 * (c.x + c.z); } quad4(&q[0], &q[1], &q[2], &q[3], m); } } } static void box(V3 c, double bxh, double byh, double bzh, const Mat *m) { static const int f[6][4][3] = { { {1,-1,-1},{1,1,-1},{1,1,1},{1,-1,1} }, { {-1,-1,1},{-1,1,1},{-1,1,-1},{-1,-1,-1} }, { {-1,1,-1},{-1,1,1},{1,1,1},{1,1,-1} }, { {-1,-1,1},{-1,-1,-1},{1,-1,-1},{1,-1,1} }, { {-1,-1,1},{1,-1,1},{1,1,1},{-1,1,1} }, { {1,-1,-1},{-1,-1,-1},{-1,1,-1},{1,1,-1} } }; static const int nn[6][3] = { {1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1} }; int i, j; for (i = 0; i < 6; i++) { Vert q[4]; for (j = 0; j < 4; j++) { q[j].p = v3(c.x + f[i][j][0] * bxh, c.y + f[i][j][1] * byh, c.z + f[i][j][2] * bzh); q[j].n = v3(nn[i][0], nn[i][1], nn[i][2]); q[j].s = (j == 1 || j == 2) * 0.3; q[j].t = (j >= 2) * 0.3 + c.y; } quad4(&q[0], &q[1], &q[2], &q[3], m); } } /* ------------------------------------------------------------------ */ /* The knight's head: an outline in (forward, up), extruded sideways with a * bevel. Ear clipping triangulates the caps. */ static const double HEAD[][2] = { {-0.21, 0.17}, { 0.17, 0.17}, { 0.20, 0.27}, { 0.17, 0.37}, { 0.14, 0.43}, { 0.22, 0.49}, { 0.31, 0.53}, { 0.36, 0.58}, { 0.36, 0.63}, { 0.31, 0.67}, { 0.20, 0.74}, { 0.12, 0.79}, { 0.09, 0.90}, { 0.03, 0.84}, {-0.02, 0.80}, {-0.10, 0.75}, {-0.17, 0.66}, {-0.21, 0.55}, {-0.24, 0.42}, {-0.25, 0.30}, {-0.24, 0.21} }; #define NH ((int)(sizeof(HEAD) / sizeof(HEAD[0]))) static double area2(const double *a, const double *b, const double *c) { return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); } static int inside(const double *p, const double *a, const double *b, const double *c) { return area2(a, b, p) >= 0 && area2(b, c, p) >= 0 && area2(c, a, p) >= 0; } static void knight_head(V3 c, int facing, const Mat *m) { static double inset[NH][2]; static int tris[NH][3]; int idx[NH], n = NH, ntri = 0, i, j, side, guard = 0; double half = 0.115, bev = 0.03, dir = facing ? 1 : -1; /* the head looks along +-x on the board; outline (fwd, up) -> world */ #define HP(f, u, s) v3(c.x + dir * (f) * PR, c.y + (u) * PH, c.z + (s) * PR) /* inset outline for the caps (offset along the averaged edge normals) */ for (i = 0; i < NH; i++) { const double *a = HEAD[(i + NH - 1) % NH], *b = HEAD[i], *d = HEAD[(i + 1) % NH]; double e1x = b[0] - a[0], e1y = b[1] - a[1], e2x = d[0] - b[0], e2y = d[1] - b[1]; double l1 = sqrt(e1x * e1x + e1y * e1y), l2 = sqrt(e2x * e2x + e2y * e2y); double nx = e1y / l1 + e2y / l2, ny = -e1x / l1 - e2x / l2, l = sqrt(nx * nx + ny * ny); inset[i][0] = b[0] - bev * 0.8 * nx / l; inset[i][1] = b[1] - bev * 0.8 * ny / l; } /* ear clipping on the inset outline (counter-clockwise) */ for (i = 0; i < NH; i++) idx[i] = i; while (n > 3 && guard++ < 1000) { for (i = 0; i < n; i++) { int a = idx[(i + n - 1) % n], b = idx[i], d = idx[(i + 1) % n], k, ok = 1; if (area2(inset[a], inset[b], inset[d]) <= 0) continue; for (k = 0; k < n && ok; k++) { int q = idx[k]; if (q != a && q != b && q != d && inside(inset[q], inset[a], inset[b], inset[d])) ok = 0; } if (!ok) continue; tris[ntri][0] = a; tris[ntri][1] = b; tris[ntri][2] = d; ntri++; for (k = i; k + 1 < n; k++) idx[k] = idx[k + 1]; n--; break; } } if (n == 3) { tris[ntri][0] = idx[0]; tris[ntri][1] = idx[1]; tris[ntri][2] = idx[2]; ntri++; } for (side = -1; side <= 1; side += 2) { /* cap */ for (i = 0; i < ntri; i++) { Vert q[3]; for (j = 0; j < 3; j++) { const double *o = inset[tris[i][j]]; q[j].p = HP(o[0], o[1], side * half); q[j].n = v3(0, 0, side); q[j].s = o[0] * 1.5; q[j].t = o[1] * 1.5; } tri(&q[0], &q[1], &q[2], m); } } /* walls and bevels */ for (i = 0; i < NH; i++) { const double *a = HEAD[i], *b = HEAD[(i + 1) % NH]; const double *ia = inset[i], *ib = inset[(i + 1) % NH]; double ex = b[0] - a[0], ey = b[1] - a[1], l = sqrt(ex * ex + ey * ey); V3 wn = norm(v3(dir * ey / l, -ex / l, 0)); Vert q[4]; double hw = half - bev; q[0].p = HP(a[0], a[1], -hw); q[1].p = HP(b[0], b[1], -hw); q[2].p = HP(b[0], b[1], hw); q[3].p = HP(a[0], a[1], hw); for (j = 0; j < 4; j++) { q[j].n = wn; q[j].s = (j == 1 || j == 2) * 0.2 + a[1]; q[j].t = (j >= 2) * 0.2; } quad4(&q[0], &q[1], &q[2], &q[3], m); for (side = -1; side <= 1; side += 2) { V3 bn = norm(add(wn, v3(0, 0, side * 1.0))); q[0].p = HP(a[0], a[1], side * hw); q[1].p = HP(b[0], b[1], side * hw); q[2].p = HP(ib[0], ib[1], side * half); q[3].p = HP(ia[0], ia[1], side * half); for (j = 0; j < 4; j++) q[j].n = bn; quad4(&q[0], &q[1], &q[2], &q[3], m); } } /* an eye on each side */ for (side = -1; side <= 1; side += 2) box(HP(0.17, 0.665, side * (half + 0.004)), 0.025, 0.022, 0.006, &M_OBSIDIAN); #undef HP } /* ------------------------------------------------------------------ */ /* Pieces */ static void base(double r) { np = 0; P(0, 0); P(r, 0); P(r + 0.01, 0.025); P(r, 0.05); P(r - 0.04, 0.06); P(r - 0.035, 0.085); P(r - 0.08, 0.10); } static void piece(int type, int colour, int facing, int sq) { const Mat *m = colour ? &M_OBSIDIAN : &M_GLASS; V3 c = v3((sq & 7) + 0.5, 0, (sq >> 3) + 0.5); double spin = 0.37 * sq; switch (type) { case 1: /* pawn */ base(0.30); P(0.15, 0.30); P(0.19, 0.32); P(0.19, 0.345); P(0.11, 0.36); arc(0, 0.47, 0.135, -55, 90, 10); lathe(c, m, spin); break; case 2: /* knight */ base(0.32); P(0.24, 0.13); P(0.24, 0.17); P(0.0, 0.17); lathe(c, m, spin); knight_head(c, facing, m); break; case 3: /* bishop */ base(0.31); P(0.15, 0.40); P(0.21, 0.43); P(0.21, 0.46); P(0.13, 0.48); arc(0, 0.64, 0.165, -70, 0, 6); P(0.14, 0.73); P(0.10, 0.80); P(0.05, 0.84); P(0.035, 0.85); arc(0, 0.885, 0.04, -60, 90, 6); lathe(c, m, spin); break; case 4: /* rook */ base(0.33); P(0.235, 0.14); P(0.20, 0.50); P(0.24, 0.53); P(0.27, 0.57); P(0.27, 0.70); P(0.20, 0.70); P(0.20, 0.64); P(0.0, 0.64); lathe(c, m, spin); break; case 5: /* queen */ base(0.34); P(0.15, 0.50); P(0.22, 0.54); P(0.22, 0.57); P(0.14, 0.60); P(0.15, 0.70); P(0.25, 0.86); P(0.22, 0.88); P(0.15, 0.86); P(0.10, 0.90); arc(0, 0.95, 0.06, -55, 90, 6); lathe(c, m, spin); break; case 6: /* king */ base(0.35); P(0.16, 0.55); P(0.23, 0.59); P(0.23, 0.62); P(0.15, 0.65); P(0.16, 0.75); P(0.24, 0.90); P(0.20, 0.93); P(0.0, 0.94); lathe(c, m, spin); box(v3(c.x, 1.03 * PH, c.z), 0.035 * PR, 0.10 * PH, 0.035 * PR, m); box(v3(c.x, 1.05 * PH, c.z), 0.085 * PR, 0.032 * PH, 0.035 * PR, m); break; } } /* ------------------------------------------------------------------ */ /* Board: 64 squares (4x4 cells each, for the specular), the frame, and the * frame's front and side faces. */ static void board(void) { int f, r, i, j; for (r = 0; r < 8; r++) for (f = 0; f < 8; f++) { const Mat *m = ((f + r) & 1) ? &M_LIGHT : &M_DARK; for (j = 0; j < 4; j++) for (i = 0; i < 4; i++) { Vert q[4]; int k; for (k = 0; k < 4; k++) { double x = f + (i + (k == 1 || k == 2)) / 4.0, z = r + (j + (k >= 2)) / 4.0; q[k].p = v3(x, 0, z); q[k].n = v3(0, 1, 0); q[k].s = x / 4.0 + ((f + r) & 1) * 0.5; q[k].t = z / 4.0; } quad4(&q[0], &q[3], &q[2], &q[1], m); } } { static const double e[5] = { -0.6, 0.0, 4.0, 8.0, 8.6 }; int a, b; /* frame top: the ring between the board and the outer edge, in strips */ for (a = 0; a < 4; a++) for (b = 0; b < 4; b++) { Vert q[4]; int k; if (a >= 1 && a <= 2 && b >= 1 && b <= 2) continue; for (k = 0; k < 4; k++) { double x = e[a + (k == 1 || k == 2)], z = e[b + (k >= 2)]; q[k].p = v3(x, -0.002, z); q[k].n = v3(0, 1, 0); q[k].s = x / 3.0; q[k].t = z / 3.0; } quad4(&q[0], &q[3], &q[2], &q[1], &M_FRAME); } /* front and side faces */ for (a = 0; a < 3; a++) { Vert q[4]; int k; for (k = 0; k < 4; k++) { double t = (k == 1 || k == 2) ? 1 : 0, y = k >= 2 ? -0.3 : -0.002; if (a == 0) { q[k].p = v3(-0.6 + 9.2 * t, y, -0.6); q[k].n = v3(0, 0, -1); } else if (a == 1) { q[k].p = v3(-0.6, y, -0.6 + 9.2 * t); q[k].n = v3(-1, 0, 0); } else { q[k].p = v3(8.6, y, -0.6 + 9.2 * t); q[k].n = v3(1, 0, 0); } q[k].s = t * 3; q[k].t = y; } quad4(&q[0], &q[1], &q[2], &q[3], &M_FRAME); } } } /* ------------------------------------------------------------------ */ /* Textures: procedural marble, luminance only (the colour is in the vertices). */ static unsigned int seed = 12345; static double rnd(void) { seed = seed * 1103515245u + 12345u; return ((seed >> 8) & 0xffff) / 65536.0; } static double lattice[64][64]; /* value noise with period per (<= 64) in both directions */ static double vnoise(double x, double y, int per) { int xi = (int) floor(x), yi = (int) floor(y), x0, x1, y0, y1; double fx = x - xi, fy = y - yi, a, b, c, d; fx = fx * fx * (3 - 2 * fx); fy = fy * fy * (3 - 2 * fy); x0 = ((xi % per) + per) % per; x1 = (x0 + 1) % per; y0 = ((yi % per) + per) % per; y1 = (y0 + 1) % per; a = lattice[y0][x0]; b = lattice[y0][x1]; c = lattice[y1][x0]; d = lattice[y1][x1]; return a + (b - a) * fx + (c - a) * fy + (a - b - c + d) * fx * fy; } /* 256x256, seamless: noise periods divide 256, the veins repeat every 128 */ static void marble(unsigned short *t, double lo, double sharp, double turb) { int x, y; for (y = 0; y < 256; y++) for (x = 0; x < 256; x++) { double f = 0, amp = 1, l, vein; int o, per = 8; for (o = 0; o < 4; o++) { f += amp * vnoise(x * per / 256.0, y * per / 256.0, per); amp *= 0.5; per *= 2; } vein = fabs(sin((x + y) * 2 * PI / 128 + turb * f)); l = 1 - (1 - lo) * pow(1 - vein, sharp) - 0.05 * f; if (l < 0) l = 0; if (l > 1) l = 1; { int r5 = (int)(l * 31 + 0.5), g6 = (int)(l * 63 + 0.5); t[y * 256 + x] = (unsigned short)((r5 << 11) | (g6 << 5) | r5); } } } /* ------------------------------------------------------------------ */ static unsigned int frame[WIN_W * WIN_H / 2]; static FILE *fbin, *fidx; static long offset; static void write_crop(int x0, int y0, int w, int h) { const unsigned short *px = (const unsigned short *) frame; int y; for (y = 0; y < h; y++) fwrite(px + (y0 + y) * WIN_W + x0, 2, (size_t) w, fbin); offset += 2L * w * h; } /* Draw something twice (two clear colours) in the window at (wx, wy) of the * supersampled image; write the crop (cx, cy, w, h) of the window. */ static void job(void (*draw)(int, int, int, int), int a, int b, int c, int d, int wx, int wy, int cx, int cy, int w, int h) { int pass; for (pass = 0; pass < 2; pass++) { win_x = wx; win_y = wy; t3_clear(pass ? CLEAR_B : CLEAR_A); draw(a, b, c, d); if (t3_finish()) { fprintf(stderr, "engine stuck\n"); t3_close(); exit(1); } t3_read(frame); write_crop(cx, cy, w, h); } } static void draw_board(int a, int b, int c, int d) { (void) a; (void) b; (void) c; (void) d; board(); } static void draw_piece(int t, int col, int fac, int sq) { piece(t, col, fac, sq); } /* ------------------------------------------------------------------ */ /* The banner: a low, close camera over the right-hand corner of the board. */ #define BAN_W 880 #define BAN_H 150 static int tex_piece, tex_board; static const int BANNER[][3] = { /* type, colour, square */ { 1, 1, 19 }, { 2, 0, 5 }, { 6, 1, 14 }, { 5, 0, 28 }, { 4, 0, 7 }, { 1, 0, 12 } }; static void camera_banner(void) { V3 target = v3(5.0, 0.55, 2.0), up = v3(0, 1, 0); eye = v3(3.4, 2.1, -4.6); cf = norm(sub(target, eye)); cr = norm(cross(up, cf)); cu = cross(cf, cr); lkey = norm(add(add(scl(cr, -0.55), scl(cu, 0.85)), scl(cf, -0.45))); lfill = norm(add(add(scl(cr, 0.7), scl(cu, 0.25)), scl(cf, -0.55))); focal = BAN_W * 0.60; ccx = BAN_W * 0.56; ccy = BAN_H * 0.47; } static void draw_banner(int a, int b, int c, int d) { int i; (void) a; (void) b; (void) c; (void) d; t3_bind(tex_board, T3_PERSP | T3_MODUL); board(); t3_bind(tex_piece, T3_PERSP | T3_MODUL); for (i = 0; i < (int)(sizeof(BANNER) / sizeof(BANNER[0])); i++) piece(BANNER[i][0], BANNER[i][1], 0, BANNER[i][2]); } static void preview(void) { static const char *pos[8] = { "rnbqkbnr", "pppppppp", "........", "........", "....P...", ".....N..", "PPPP.PPP", "RNBQKB.R" }; int r, f; for (r = 7; r >= 0; r--) for (f = 0; f < 8; f++) { char ch = pos[7 - r][f]; const char *t = strchr("pnbrqk", ch >= 'a' ? ch : ch + 32); if (ch == '.') continue; piece((int)(t - "pnbrqk") + 1, ch >= 'a', 0, r * 8 + f); } } int main(int argc, char **argv) { static unsigned short tex[256 * 256]; int pv = 0, ban = 0, ti, tb, i, j, type, col, fac, sq, nj = 0; if (argc > 1 && strcmp(argv[1], "-p") == 0) { pv = 1; argv++; argc--; } else if (argc > 1 && strcmp(argv[1], "-b") == 0) { ban = 1; argv++; argc--; } else if (argc > 1 && strcmp(argv[1], "-bp") == 0) { ban = 2; argv++; argc--; } if (argc != 3) { fprintf(stderr, "usage: lbrender [-p|-b|-bp] OUT.bin OUT.idx\n"); return 2; } fbin = fopen(argv[1], "wb"); fidx = fopen(argv[2], "w"); if (!fbin || !fidx) { perror("open"); return 1; } camera_setup(); for (i = 0; i < 64; i++) for (j = 0; j < 64; j++) lattice[i][j] = rnd(); if (t3_open(WIN_W, WIN_H)) { fprintf(stderr, "t3_open failed (X running?)\n"); return 1; } marble(tex, 0.86, 10, 3.0); ti = t3_texture(tex, 256); marble(tex, 0.74, 9, 3.5); tb = t3_texture(tex, 256); tex_piece = ti; tex_board = tb; if (ban) { int bw = BAN_W * SS, bh = BAN_H * SS; camera_banner(); fprintf(fidx, "C %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %d %d %d\n", eye.x, eye.y, eye.z, cr.x, cr.y, cr.z, cu.x, cu.y, cu.z, cf.x, cf.y, cf.z, focal, ccx, ccy, SS, BAN_W, BAN_H); if (ban == 2) { /* preview: the whole banner squeezed into 640 wide */ focal *= 640.0 / BAN_W / SS; ccx *= 640.0 / BAN_W / SS; ccy *= 640.0 / BAN_W / SS; win_x = win_y = 0; t3_clear(0xFFFF); draw_banner(0, 0, 0, 0); t3_finish(); t3_read(frame); write_crop(0, 0, WIN_W, WIN_H); fprintf(fidx, "P 0 0 %d %d 0\n", WIN_W, WIN_H); t3_close(); return 0; } for (j = 0; j * WIN_H < bh; j++) for (i = 0; i * WIN_W < bw; i++) { int w = bw - i * WIN_W, h = bh - j * WIN_H; if (w > WIN_W) w = WIN_W; if (h > WIN_H) h = WIN_H; fprintf(fidx, "B %d %d %d %d %d %d %ld\n", i, j, i * WIN_W, j * WIN_H, w, h, offset); job(draw_banner, 0, 0, 0, 0, i * WIN_W, j * WIN_H, 0, 0, w, h); } t3_close(); fclose(fbin); fclose(fidx); return 0; } fprintf(fidx, "C %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %d %d %d\n", eye.x, eye.y, eye.z, cr.x, cr.y, cr.z, cu.x, cu.y, cu.z, cf.x, cf.y, cf.z, focal, ccx, ccy, SS, IMG_W, IMG_H); if (pv) { /* one frame at 640/IMG_W scale, centred: a quick look at the scene */ focal *= 640.0 / IMG_W / SS; ccx *= 640.0 / IMG_W / SS; ccy *= 640.0 / IMG_W / SS; win_x = 0; win_y = 0; t3_clear(0xFFFF); t3_bind(tb, T3_PERSP | T3_MODUL); board(); t3_bind(ti, T3_PERSP | T3_MODUL); preview(); t3_finish(); t3_read(frame); offset = 0; write_crop(0, 0, WIN_W, WIN_H); fprintf(fidx, "P 0 0 %d %d 0\n", WIN_W, WIN_H); t3_close(); return 0; } /* the board, in tiles of the supersampled image */ t3_bind(tb, T3_PERSP | T3_MODUL); for (j = 0; j * WIN_H < IMG_H * SS; j++) for (i = 0; i * WIN_W < IMG_W * SS; i++) { int w = IMG_W * SS - i * WIN_W, h = IMG_H * SS - j * WIN_H; if (w > WIN_W) w = WIN_W; if (h > WIN_H) h = WIN_H; fprintf(fidx, "B %d %d %d %d %d %d %ld\n", i, j, i * WIN_W, j * WIN_H, w, h, offset); job(draw_board, 0, 0, 0, 0, i * WIN_W, j * WIN_H, 0, 0, w, h); nj++; } /* every piece on every square; knights facing both ways */ t3_bind(ti, T3_PERSP | T3_MODUL); for (col = 0; col < 2; col++) for (type = 1; type <= 6; type++) for (fac = 0; fac < (type == 2 ? 2 : 1); fac++) for (sq = 0; sq < 64; sq++) { int x0, y0, w, h; measuring = 1; bx0 = by0 = 1e9; bx1 = by1 = -1e9; win_x = win_y = 0; piece(type, col, fac, sq); measuring = 0; x0 = (int) floor(bx0) - 2; y0 = (int) floor(by0) - 2; w = (int) ceil(bx1) - x0 + 3; h = (int) ceil(by1) - y0 + 3; if (w > WIN_W || h > WIN_H) { fprintf(stderr, "sprite too big %d %d\n", w, h); t3_close(); return 1; } fprintf(fidx, "S %d %d %d %d %d %d %d %d %ld\n", col, type, fac, sq, x0, y0, w, h, offset); job(draw_piece, type, col, fac, sq, x0, y0, 0, 0, w, h); nj++; } t3_close(); fclose(fbin); fclose(fidx); fprintf(stderr, "%d jobs, %ld bytes\n", nj, offset); return 0; }