diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 55cb29c..2364a07 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -23,7 +23,10 @@ "Bash(/tmp/sim)", "Bash(cc -O2 -o /tmp/st2 /tmp/st2.c -lm)", "Bash(/tmp/st2)", - "Bash(rm -f /tmp/st2 /tmp/st2.c)" + "Bash(rm -f /tmp/st2 /tmp/st2.c)", + "Bash(convert /tmp/vg_frame.ppm -crop 760x360+340+60 +repage /tmp/vg_faces.png)", + "Bash(convert /tmp/vg_frame.ppm -crop 760x300+340+430 +repage /tmp/vg_googie.png)", + "Bash(convert /tmp/vg_frame.ppm -crop 360x340+150+60 +repage /tmp/vg_angry.png)" ] } } diff --git a/README.md b/README.md index 28c6f59..7baa02d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A starfield simulator — except instead of stars, colorful **vector-drawn platonic solids** tumble through space toward the camera. -**~110 shape types** are rendered as glowing wireframes — each spawns far away +**~165 shape types** are rendered as glowing wireframes — each spawns far away at a random size, tumbles on its own random axis, and streams past the camera before being recycled at the render-distance sphere. Solids never overlap. @@ -20,15 +20,23 @@ The shape set includes: each lands on exactly the requested number of faces - **Star polygons:** {5/2}, {6/2}, {7/2}, {7/3}, {8/3}, {9/2}, {9/4}, {12/5}, plus the **unicursal hexagram** -- **Googie / atomic-age shapes**, most in both flat **2-D** and **3-D** forms: - jet-age bowling-alley twinkles (4- and 8-point), a layered double starburst, - an atomic burst with electron caps, a ray sunburst, boomerangs, kidney/amoeba - blobs, orbital atoms, concentric orbit rings, and a dense ray-star — plus the - naturally-3-D **Sputnik** satellite, a three-ring **gyroscope** cage, and a - spike-orb sea-urchin -- **Symbols & signs**, each in a flat **2-D** and an extruded **3-D** form: - smiley face, biohazard, peace sign, cross, question mark, exclamation point, - hash/pound (`#`), dollar sign (`$`), and pound sterling (`£`) +- **Googie / atomic-age shapes** — a large set in the jet-age / space-age idiom: + bowling-alley twinkles (4- and 8-point), a layered double starburst, atomic + bursts with electron caps, ray sunbursts, boomerangs, kidney/amoeba blobs, + concentric orbit rings, plus **30+ more truly-3-D objects**: radial + **starbursts** and **sea-urchins** (6/8/12/20/32-spike, some ball-tipped), + **gyroscope** orbit cages (2–6 rings), **orbital atoms** (2–6 orbits), + ringed **Saturn** planets, ball-and-stick **molecules**, **diabolo** + hourglasses, ray-stars, and a **Sputnik** satellite +- **Symbols, signs & faces**, each in a flat **2-D** and an extruded **3-D** + form: smiley, **frowny**, and **angry** faces, biohazard, peace sign, cross, + question mark, exclamation point, hash/pound (`#`), dollar sign (`$`), pound + sterling (`£`), and the **at sign (`@`)** +- **Object iconography**, each in **2-D** and **3-D**: space invaders, UFOs, + pac-man, alien heads, smoking pipes, umbrellas, and hands +- **Live clocks** (analog and digital, 2-D and 3-D) that show the **actual + current time** — their geometry is rebuilt every frame, so the analog hands + sweep and the seven-segment digital display ticks along in real time - **Random 3-D asteroids:** lumpy convex-hull rocks generated fresh each run, with random vertex counts (≈7–26) giving each a different number of sides and level of complexity diff --git a/vectorgons b/vectorgons index 15383f5..c91cfc4 100755 Binary files a/vectorgons and b/vectorgons differ diff --git a/vectorgons.c b/vectorgons.c index 11fa30a..91abd44 100644 --- a/vectorgons.c +++ b/vectorgons.c @@ -6,13 +6,17 @@ * Windowing & input via GLFW3, perspective via GLU. * * Features: - * - 100+ shape types streaming at the camera: the 5 platonic solids, + * - 160+ shape types streaming at the camera: the 5 platonic solids, * archimedeans, prisms, antiprisms, bipyramids, trapezohedra, the * many-faced solids (tri/tetra/.../hecato-hedron), star polygons and the - * unicursal hexagram, googie / atomic-age starbursts, 2-D and 3-D symbols - * (smiley, biohazard, peace, cross, ? ! # $ £), 4/5/6-D polytopes - * (tesseract, penteract, 24-cell, ...) that morph as they tumble, plus - * randomly generated 3-D convex-hull asteroids + * unicursal hexagram, a big set of googie / atomic-age 3-D objects + * (starbursts, sea-urchins, gyroscopes, orbital atoms, ringed planets, + * molecules, diabolos, sputniks), 2-D and 3-D symbols (smiley, frowny, + * angry, biohazard, peace, cross, ? ! # $ £ @), object iconography + * (space invaders, UFOs, pac-man, alien heads, pipes, umbrellas, hands) + * and live analog/digital clocks showing the real current time, 4/5/6-D + * polytopes (tesseract, penteract, 24-cell, ...) that morph as they + * tumble, plus randomly generated 3-D convex-hull asteroids * - WASD pans the camera and the arrow keys rotate it * - User-settable render distance (how far away solids spawn) * - Random per-body sizes (user-settable min / max) @@ -57,7 +61,7 @@ #define MAXD 6 #define MAX_VERTS 128 #define MAX_EDGES 512 -#define MAX_SHAPES 160 +#define MAX_SHAPES 220 #define TAU 6.283185307179586 typedef struct { @@ -70,6 +74,7 @@ typedef struct { static Solid solids[MAX_SHAPES]; static int num_shapes = 0; +static int clock_idx[4] = {-1,-1,-1,-1}; /* analog2d, analog3d, digital2d, digital3d */ static float frand(void); /* uniform [0,1); defined below */ @@ -347,9 +352,11 @@ static void sym_poly(SymB *b, const double *xy, int n, int closed) { } static double sym_deg(double d) { return d * TAU / 360.0; } -/* Turn the accumulated strokes into a Solid: flat if depth<=0, else extruded. */ -static void sym_finish(SymB *b, float depth) { - Solid *s = new_solid(3); +/* Fill an existing Solid from the strokes: flat if depth<=0, else extruded. + * (Used both for one-shot shapes and for the live clocks, which rewrite their + * solid in place every frame.) */ +static void sym_fill_solid(Solid *s, SymB *b, float depth) { + s->dim = 3; s->nv = 0; s->ne = 0; if (depth <= 0.0f) { for (int k = 0; k < b->np; k++) PV(s, b->px[k], b->py[k], 0, 0,0,0); for (int k = 0; k < b->ne; k++) AE(s, b->ea[k], b->eb[k]); @@ -365,6 +372,7 @@ static void sym_finish(SymB *b, float depth) { } center_normalize(s); } +static void sym_finish(SymB *b, float depth) { sym_fill_solid(new_solid(3), b, depth); } /* ---- individual symbol strokes (drawn roughly within [-1,1]) ------- */ @@ -636,6 +644,342 @@ static void gen_spikeorb3d(void) { center_normalize(s); } +/* ================================================================== */ +/* More googie / atomic-age 3-D objects, plus faces and the '@' sign. */ +/* ================================================================== */ + +/* Symmetric unit-direction sets for radial bursts. Returns the count. + * 0:octahedron(6) 1:cube(8) 2:icosahedron(12) 3:dodecahedron(20) + * 4:icosa+dodeca(32). */ +static int dir_set(int which, double out[][3]) { + const double P = 1.6180339887, Q = 0.6180339887; + double t[40][3]; int n = 0; + if (which == 0) { + double d[6][3] = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}}; + for (int i=0;i<6;i++){ t[n][0]=d[i][0];t[n][1]=d[i][1];t[n][2]=d[i][2];n++; } + } else if (which == 1) { + for (int x=-1;x<=1;x+=2) for (int y=-1;y<=1;y+=2) for (int z=-1;z<=1;z+=2) + { t[n][0]=x;t[n][1]=y;t[n][2]=z;n++; } + } else { + if (which == 2 || which == 4) { + double d[12][3]={{0,1,P},{0,1,-P},{0,-1,P},{0,-1,-P},{1,P,0},{1,-P,0}, + {-1,P,0},{-1,-P,0},{P,0,1},{P,0,-1},{-P,0,1},{-P,0,-1}}; + for (int i=0;i<12;i++){ t[n][0]=d[i][0];t[n][1]=d[i][1];t[n][2]=d[i][2];n++; } + } + if (which == 3 || which == 4) { + for (int x=-1;x<=1;x+=2) for (int y=-1;y<=1;y+=2) for (int z=-1;z<=1;z+=2) + { t[n][0]=x;t[n][1]=y;t[n][2]=z;n++; } + double e[12][3]={{0,Q,P},{0,Q,-P},{0,-Q,P},{0,-Q,-P},{Q,P,0},{Q,-P,0}, + {-Q,P,0},{-Q,-P,0},{P,0,Q},{P,0,-Q},{-P,0,Q},{-P,0,-Q}}; + for (int i=0;i<12;i++){ t[n][0]=e[i][0];t[n][1]=e[i][1];t[n][2]=e[i][2];n++; } + } + } + for (int i=0;inv; + PV(s, px+rad*(cos(a)*rx+sin(a)*sx), py+rad*(cos(a)*ry+sin(a)*sy), + pz+rad*(cos(a)*rz+sin(a)*sz), 0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; + } + if(first>=0&&prev>=0) AE(s,prev,first); +} + +/* radial spike burst (starburst): a spoke to each direction, optionally + * alternating long/short, optionally capped with a little ball */ +static void gen_burst3d(int which, double longr, double shortr, int balls) { + Solid *s=new_solid(3); + double d[40][3]; int nd=dir_set(which,d); + int c=s->nv; PV(s,0,0,0,0,0,0); + for (int i=0;i0 && (i&1))?shortr:longr; + double tx=d[i][0]*r, ty=d[i][1]*r, tz=d[i][2]*r; + int tip=s->nv; PV(s,tx,ty,tz,0,0,0); AE(s,c,tip); + if (balls) add_ball(s,tx,ty,tz,d[i][0],d[i][1],d[i][2],0.12,5); + } + center_normalize(s); +} + +/* great circles at spread orientations (gyroscope / orbit cage) */ +static void gen_rings3d(int nrings, int seg) { + Solid *s=new_solid(3); + for (int r=0;rnv; PV(s,x,y,z,0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; + } + AE(s,prev,first); + } + center_normalize(s); +} + +/* atomic orbital cluster: tilted elliptical orbits around an octahedral nucleus */ +static void gen_atomorbits(int norbits, int seg) { + Solid *s=new_solid(3); + for (int o=0;onv; PV(s,x,y,z,0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; + } + AE(s,prev,first); + } + int b0=s->nv; double r=0.12; + PV(s,r,0,0,0,0,0);PV(s,-r,0,0,0,0,0);PV(s,0,r,0,0,0,0); + PV(s,0,-r,0,0,0,0);PV(s,0,0,r,0,0,0);PV(s,0,0,-r,0,0,0); + int oe[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}}; + for(int e=0;e<12;e++) AE(s,b0+oe[e][0],b0+oe[e][1]); + center_normalize(s); +} + +/* ringed planet (Saturn): a wireframe sphere with tilted equatorial ring(s) */ +static void gen_saturn3d(int nrings, double tilt) { + Solid *s=new_solid(3); + const double pi=TAU*0.5; int lats=3, seg=14; + for (int la=1;la<=lats;la++){ + double phi=pi*la/(lats+1)-pi*0.5, y=sin(phi)*0.6, rr=cos(phi)*0.6; + int first=-1, prev=-1; + for (int i=0;inv; + PV(s,rr*cos(a),y,rr*sin(a),0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; } + AE(s,prev,first); + } + for (int m=0;m<2;m++){ + double mo=TAU*m/4; int first=-1, prev=-1; + for (int i=0;inv; PV(s,x1,y,z1,0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; } + AE(s,prev,first); + } + for (int rg=0;rgnv; PV(s,x,y1,z1,0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; } + AE(s,prev,first); + } + center_normalize(s); +} + +/* ball-and-stick molecule: a central ball bonded to balls in each direction */ +static void gen_molecule3d(int which) { + Solid *s=new_solid(3); + double d[40][3]; int nd=dir_set(which,d); + int c=s->nv; PV(s,0,0,0,0,0,0); + add_ball(s,0,0,0, 0,0,1, 0.2, 6); + for (int i=0;inv; PV(s,tx,ty,tz,0,0,0); AE(s,c,tip); + add_ball(s,tx,ty,tz,d[i][0],d[i][1],d[i][2],0.17,6); + } + center_normalize(s); +} + +/* diabolo / hourglass burst: two cones tip-to-tip with a waisted lattice */ +static void gen_diabolo3d(int nspokes) { + Solid *s=new_solid(3); + int top=s->nv; PV(s,0,0,1.0,0,0,0); + int bot=s->nv; PV(s,0,0,-1.0,0,0,0); + int rt=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0,0,0,0,0); + for (int i=0;inv; PV(s,r*cos(a),r*sin(a),0,0,0,0); AE(s,c,tip); } + int z1=s->nv; PV(s,0,0,0.7,0,0,0); AE(s,c,z1); + int z2=s->nv; PV(s,0,0,-0.7,0,0,0); AE(s,c,z2); + center_normalize(s); +} + +/* ---- expressive faces and the '@' sign (flat + extruded) ---------- */ + +static void build_frowny(SymB *b) { + sym_ring(b, 0,0, 1.0, 18); + sym_ring(b, -0.35, 0.32, 0.12, 6); + sym_ring(b, 0.35, 0.32, 0.12, 6); + sym_arc (b, 0.0, -0.55, 0.5, sym_deg(20), sym_deg(160), 8); /* downturned mouth */ +} +static void build_angry(SymB *b) { + sym_ring(b, 0,0, 1.0, 18); + sym_ring(b, -0.33, 0.26, 0.11, 6); + sym_ring(b, 0.33, 0.26, 0.11, 6); + sym_line(b, -0.58, 0.62, -0.14, 0.42); /* angry V brows: inner ends low */ + sym_line(b, 0.58, 0.62, 0.14, 0.42); + sym_arc (b, 0.0, -0.58, 0.48, sym_deg(20), sym_deg(160), 8); /* frown */ +} +static void build_at(SymB *b) { + sym_arc (b, 0.0, 0.0, 1.0, sym_deg(-25), sym_deg(295), 20); /* outer ring, open at right */ + sym_ring(b, -0.05, 0.0, 0.40, 12); /* inner 'a' bowl */ + sym_line(b, 0.35, 0.42, 0.35, -0.55); /* 'a' stem / tail */ +} + +/* ---- pop-culture / object iconography (flat 2-D + extruded 3-D) ---- */ + +static void build_invader(SymB *b) { /* blocky pixel-alien (Space Invader) */ + const double body[] = { + -0.7, 0.2, -0.7,-0.2, -0.5,-0.2, -0.5,-0.5, -0.25,-0.5, -0.25,-0.25, + 0.25,-0.25, 0.25,-0.5, 0.5,-0.5, 0.5,-0.2, 0.7,-0.2, 0.7, 0.2, + 0.45,0.5, 0.2,0.5, 0.2,0.78, 0.05,0.78, 0.05,0.5, -0.05,0.5, + -0.05,0.78, -0.2,0.78, -0.2,0.5, -0.45,0.5 }; + sym_poly(b, body, 22, 1); + sym_ring(b, -0.25, 0.12, 0.08, 5); + sym_ring(b, 0.25, 0.12, 0.08, 5); +} +static void build_ufo(SymB *b) { /* flying saucer */ + sym_ellipse(b, 0,0, 1.0, 0.30, 0, 22); /* saucer disc */ + sym_arc(b, 0.0, 0.06, 0.46, sym_deg(0), sym_deg(180), 11); /* dome */ + for (int i=-2;i<=2;i++) sym_ring(b, i*0.32, -0.22, 0.05, 4); /* lights */ +} +static void build_pacman(SymB *b) { /* wedge-mouth + pellet */ + sym_arc(b, 0,0, 1.0, sym_deg(36), sym_deg(324), 24); + int last = b->np - 1, c = sym_pt(b, 0, 0); + sym_edge(b, last, c); sym_edge(b, c, 0); /* close the mouth wedge */ + sym_ring(b, 0.05, 0.45, 0.09, 5); /* eye */ + sym_ring(b, 1.35, 0.0, 0.12, 6); /* pellet */ +} +static void build_alien(SymB *b) { /* grey alien head */ + int seg=22, first=-1, prev=-1; + for (int i=0;i0 ? y*0.95 : y*1.28); + if(first<0)first=idx; + if(prev>=0)sym_edge(b,prev,idx); + prev=idx; + } + sym_edge(b, prev, first); + sym_ellipse(b, -0.26, 0.05, 0.22, 0.10, sym_deg( 28), 9); /* almond eyes */ + sym_ellipse(b, 0.26, 0.05, 0.22, 0.10, sym_deg(-28), 9); +} +static void build_pipe(SymB *b) { /* smoking pipe */ + sym_arc(b, -0.55, 0.0, 0.34, sym_deg(18), sym_deg(342), 14); /* bowl, open at top */ + const double stem[] = { -0.28,-0.22, 0.2,-0.32, 0.7,-0.3, 0.98,-0.18 }; + sym_poly(b, stem, 4, 0); + sym_line(b, 0.98,-0.18, 1.04,-0.04); /* mouthpiece */ +} +static void build_umbrella(SymB *b) { /* canopy + ribs + pole + J-handle */ + double R = 0.95; int n = 4; + sym_arc(b, 0, 0.0, R, sym_deg(0), sym_deg(180), 14); /* canopy dome */ + for (int i=0;itm_hour%12, m=lt->tm_min, s=lt->tm_sec; + double ha=TAU*0.25 - TAU*((h + m/60.0)/12.0); /* 12 at top, clockwise */ + double ma=TAU*0.25 - TAU*(m/60.0); + double sa=TAU*0.25 - TAU*(s/60.0); + int c=sym_pt(b,0,0); + sym_edge(b, c, sym_pt(b, 0.50*cos(ha), 0.50*sin(ha))); /* hour */ + sym_edge(b, c, sym_pt(b, 0.74*cos(ma), 0.74*sin(ma))); /* minute */ + sym_edge(b, c, sym_pt(b, 0.84*cos(sa), 0.84*sin(sa))); /* second */ +} +static void build_digital(SymB *b, const struct tm *lt) { + int hh=lt->tm_hour, mm=lt->tm_min, d[4]={hh/10,hh%10,mm/10,mm%10}; + double w=0.30, h=0.30, cw=0.42, oy=-0.3; + double x[4]={-0.92,-0.92+cw, 0.18, 0.18+cw}; + for (int i=0;i<4;i++) sym_7seg(b, d[i], x[i], oy, w, h); + sym_ring(b, -0.02, oy+0.42*h, 0.035, 4); /* colon dots */ + sym_ring(b, -0.02, oy+1.4*h, 0.035, 4); + const double fr[]={-1.12,-0.55, 1.12,-0.55, 1.12,0.55, -1.12,0.55}; + sym_poly(b, fr, 4, 1); /* bezel keeps size constant */ +} +/* Rewrite the four clock solids in place from the current local time. */ +static void update_clocks(void) { + if (clock_idx[0] < 0) return; + time_t t = time(NULL); + struct tm *lt = localtime(&t); + SymB b; + sym_init(&b); build_analog (&b, lt); sym_fill_solid(&solids[clock_idx[0]], &b, 0.0f); + sym_init(&b); build_analog (&b, lt); sym_fill_solid(&solids[clock_idx[1]], &b, 0.5f); + sym_init(&b); build_digital(&b, lt); sym_fill_solid(&solids[clock_idx[2]], &b, 0.0f); + sym_init(&b); build_digital(&b, lt); sym_fill_solid(&solids[clock_idx[3]], &b, 0.5f); +} + /* Build a symbol in both its flat and extruded-3-D forms. */ static void add_symbol(void (*build)(SymB *)) { SymB b; @@ -764,6 +1108,59 @@ static void init_solids(void) { add_symbol(build_dollar); /* dollar sign */ add_symbol(build_sterling); /* pound sterling (£) */ + /* --- 30+ more googie / atomic-age 3-D objects --- */ + gen_burst3d(0, 1.0, 0.0, 0); /* 6-spike asterisk star */ + gen_burst3d(1, 1.0, 0.0, 0); /* 8-spike cube burst */ + gen_burst3d(2, 1.0, 0.55, 0); /* 12-spike spiky star */ + gen_burst3d(3, 1.0, 0.0, 0); /* 20-spike sea urchin */ + gen_burst3d(4, 1.0, 0.6, 0); /* 32-spike dense urchin */ + gen_burst3d(4, 1.0, 0.0, 0); /* 32-spike uniform urchin */ + gen_burst3d(2, 1.0, 0.0, 1); /* 12 ball-tipped sputnik */ + gen_burst3d(0, 1.0, 0.0, 1); /* 6 ball-tipped jacks */ + gen_burst3d(1, 1.0, 0.0, 1); /* 8 ball-tipped burst */ + gen_burst3d(3, 1.0, 0.0, 1); /* 20 ball-tipped burst */ + gen_burst3d(2, 1.0, 0.4, 1); /* 12 alt ball-tipped burst */ + gen_rings3d(2, 24); /* crossed rings */ + gen_rings3d(3, 22); /* gyroscope */ + gen_rings3d(4, 20); /* 4-ring cage */ + gen_rings3d(5, 20); /* 5-ring cage */ + gen_rings3d(6, 20); /* 6-ring orb cage */ + gen_atomorbits(2, 24); /* 2-orbit atom */ + gen_atomorbits(4, 20); /* 4-orbit atom */ + gen_atomorbits(5, 18); /* 5-orbit atom */ + gen_atomorbits(6, 16); /* 6-orbit atom */ + gen_saturn3d(1, 0.0); /* ringed planet */ + gen_saturn3d(2, 0.35); /* double-ringed planet */ + gen_saturn3d(1, 0.6); /* tilted-ring planet */ + gen_molecule3d(0); /* octahedral molecule */ + gen_molecule3d(1); /* cubic molecule */ + gen_molecule3d(2); /* icosahedral molecule */ + gen_diabolo3d(6); /* hex diabolo */ + gen_diabolo3d(10); /* diabolo / hourglass */ + gen_diabolo3d(16); /* fine diabolo */ + gen_raystar3d(12); /* 12-ray starburst */ + gen_raystar3d(16); /* 16-ray starburst */ + gen_raystar3d(24); /* 24-ray sunburst */ + + /* --- expressive faces + the @ sign (flat 2-D and extruded 3-D) --- */ + add_symbol(build_angry); /* angry face */ + add_symbol(build_frowny); /* frowny face */ + add_symbol(build_at); /* at sign (@) */ + + /* --- object iconography (flat 2-D and extruded 3-D each) --- */ + add_symbol(build_invader); /* space invader */ + add_symbol(build_ufo); /* flying saucer */ + add_symbol(build_pacman); /* pac-man */ + add_symbol(build_alien); /* alien head */ + add_symbol(build_pipe); /* smoking pipe */ + add_symbol(build_umbrella); /* umbrella */ + add_symbol(build_hand); /* hand */ + + /* --- live clocks: reserve 4 slots, then fill from the current time + * (refreshed every frame by update_clocks() in the main loop) --- */ + for (int i = 0; i < 4; i++) { clock_idx[i] = num_shapes; new_solid(3); } + update_clocks(); + /* --- 4/5/6-dimensional polytopes (projected & morphing) --- */ gen_simplex(5); /* 5-cell (4-simplex) */ gen_hypercube(4); /* tesseract / 8-cell */ @@ -1498,6 +1895,7 @@ int main(void) { int active = active_count(); ensure_filled(active); + update_clocks(); /* keep the clock shapes showing the current time */ for (int i = 0; i < active; i++) { Body *b = &bodies[i];