/* * Vectorgons — a starfield simulator of colorful vector-drawn platonic * solids tumbling through space toward the camera. * * C + OpenGL (legacy immediate mode for true wireframe "vector" rendering) * Windowing & input via GLFW3, perspective via GLU. * * Features: * - 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, 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, more * iconography (ghosts, ice cream, bulbs, mitochondria, windows, bowling * balls/pins, bats, bottles, glasses, phones, booths, radio towers, atoms), * lathed solids of revolution (a full chess set, candlesticks, cups, * lanterns, crystals, pineapples) and a torus, extruded silhouettes * (keys, padlocks, guitars, tools, cutlery, pens, element symbols, hats, * hearts, keyboards, floppies, bells, books, IC chip packages, food, * telescopes, microscopes, palm trees, cacti, batteries, ...) -- every * object has both a flat 2-D and a 3-D form; many more objects (cards, * dice, vehicles, sea life, bucky balls, ...) plus animated ones whose * 3-D form is rebuilt each frame (Hoberman sphere folds, Rubik's cube * turns, lighthouse beam sweeps, eyeball blinks, jaws chatter, octopus * and jellyfish wave, a fish swims, scissors snip, flames flicker, gears * and a fan spin); media (cassettes, records, CDs), traditional alchemical * and occult symbols, springs/slinkies, and assorted objects, * 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) * - Magnifying-glass shapes are real lenses: the field is grabbed into a * texture and resampled with a radial zoom, so they magnify what's behind * - A refracting prism splits a white beam into a rainbow fan * - A glass sphere (crystal ball) refracts the field behind it inverted * and fish-eye-distorted * - An opaque polished mirror sphere reflects the surrounding field in a wide * upright (convex-mirror) fish-eye, with metallic shading * - The magnifier / glass-sphere / mirror-sphere counts are user-settable * (0..1000 each), added on top of the density field without reducing it * - Random per-body sizes (user-settable min / max) * - Guaranteed no overlap between solids (bounding-sphere rejection) * - Adjustable tumble rate AND tumble-speed variance * - Adjustable CRT-style glow / light bleed (post-process bloom; F3 toggles * the legacy per-vector glow). Geometry is drawn from VBOs (glDrawElements), * not immediate mode, and off-screen bodies are frustum-culled before draw, * so the CPU stays fed at high body counts; GL 1.5/3.0 entry points are * loaded at run time via glfwGetProcAddress. F1 = perf HUD, F2 = VSync * toggle. Both retained mode and bloom fall back gracefully. * - Adjustable vector flicker * - On-screen display (self-contained vector font) that fades after idle * - Single-hue and multicolor modes; adjustable hue + continuous hue cycling * - Fullscreen toggle * - Settings auto-saved to ~/.vectorgons on exit, reloaded as defaults * * All controls are listed in the on-screen display (and printed at startup). */ #define GL_SILENCE_DEPRECATION #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include /* provides APIENTRY / CALLBACK that the GL headers need */ #endif #ifdef __EMSCRIPTEN__ /* Browser build: WebGL via Emscripten's legacy-GL emulation. GLU is not * provided, so the three GLU helpers used here are shimmed below. */ #include #include #define GLFW_INCLUDE_NONE #include #include #else #include #include #endif /* Legacy GL constants that the Windows (MinGW) OpenGL 1.1 headers may omit. * The values are standard; the GPU driver provides the functionality at run * time. Guarded so platforms whose headers already define them are unaffected. */ #ifndef GL_ALIASED_LINE_WIDTH_RANGE #define GL_ALIASED_LINE_WIDTH_RANGE 0x846E #endif #ifndef GL_MULTISAMPLE #define GL_MULTISAMPLE 0x809D #endif #include #include #include #include #include #include #include #ifdef __EMSCRIPTEN__ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Minimal GLU replacements (Emscripten has no GLU). gluOrtho2D/gluPerspective * post-multiply onto the current fixed-function matrix; gluProject is pure math. */ static void gluOrtho2D(double l, double r, double b, double t) { float m[16] = { 2.0f/(float)(r-l),0,0,0, 0,2.0f/(float)(t-b),0,0, 0,0,-1,0, -(float)((r+l)/(r-l)), -(float)((t+b)/(t-b)), 0, 1 }; glMultMatrixf(m); } static void gluPerspective(double fovy, double aspect, double zn, double zf) { double f = 1.0 / tan(fovy * (M_PI / 360.0)); float m[16] = { (float)(f/aspect),0,0,0, 0,(float)f,0,0, 0,0,(float)((zf+zn)/(zn-zf)),-1, 0,0,(float)((2*zf*zn)/(zn-zf)),0 }; glMultMatrixf(m); } static int gluProject(double ox, double oy, double oz, const double m[16], const double p[16], const int vp[4], double *wx, double *wy, double *wz) { double a0=m[0]*ox+m[4]*oy+m[8]*oz+m[12], a1=m[1]*ox+m[5]*oy+m[9]*oz+m[13]; double a2=m[2]*ox+m[6]*oy+m[10]*oz+m[14], a3=m[3]*ox+m[7]*oy+m[11]*oz+m[15]; double c0=p[0]*a0+p[4]*a1+p[8]*a2+p[12]*a3, c1=p[1]*a0+p[5]*a1+p[9]*a2+p[13]*a3; double c2=p[2]*a0+p[6]*a1+p[10]*a2+p[14]*a3, c3=p[3]*a0+p[7]*a1+p[11]*a2+p[15]*a3; if (c3 == 0.0) return 0; c0/=c3; c1/=c3; c2/=c3; *wx = vp[0] + (c0*0.5+0.5)*vp[2]; *wy = vp[1] + (c1*0.5+0.5)*vp[3]; *wz = c2*0.5 + 0.5; return 1; } /* The legacy-GL emulation exposes only the float entry points; map the handful * of double-precision calls used here onto them (all glGetDoublev calls query a * 4x4 matrix). */ static void vg_get_doublev(GLenum pname, double *out) { float f[16]; glGetFloatv(pname, f); for (int i = 0; i < 16; i++) out[i] = f[i]; } #define glGetDoublev(pname, out) vg_get_doublev((pname), (out)) #define glVertex2d(x, y) glVertex2f((float)(x), (float)(y)) #define glVertex3d(x, y, z) glVertex3f((float)(x), (float)(y), (float)(z)) #endif /* __EMSCRIPTEN__ */ /* --- OpenGL 1.5 buffer objects (Step 1: retained-mode geometry) -------------- * The hot wireframe geometry is uploaded to VBOs and drawn with glDrawElements * instead of per-vertex glBegin/glVertex3fv, which removes the millions of * driver calls/frame that bottlenecked the immediate-mode path. opengl32.dll on * Windows only exports GL 1.1, so these four entry points are resolved at run * time via glfwGetProcAddress (cross-platform). Values/signatures are standard. */ #ifndef GL_ARRAY_BUFFER #define GL_ARRAY_BUFFER 0x8892 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_STATIC_DRAW 0x88E4 #define GL_STREAM_DRAW 0x88E0 #endif #ifndef APIENTRY #define APIENTRY #endif typedef ptrdiff_t VGsizeiptr; typedef void (APIENTRY *VG_GENBUFFERS)(GLsizei, GLuint *); typedef void (APIENTRY *VG_BINDBUFFER)(GLenum, GLuint); typedef void (APIENTRY *VG_BUFFERDATA)(GLenum, VGsizeiptr, const void *, GLenum); static VG_GENBUFFERS vgGenBuffers; static VG_BINDBUFFER vgBindBuffer; static VG_BUFFERDATA vgBufferData; /* Framebuffer objects (GL 3.0 / EXT_framebuffer_object), for the Step 2 bloom * post-process: render the field core once, then build a soft multi-scale glow * off-screen instead of redrawing every body 9x. Same runtime-load story. */ #ifndef GL_FRAMEBUFFER #define GL_FRAMEBUFFER 0x8D40 #define GL_COLOR_ATTACHMENT0 0x8CE0 #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 #endif typedef void (APIENTRY *VG_GENFRAMEBUFFERS)(GLsizei, GLuint *); typedef void (APIENTRY *VG_BINDFRAMEBUFFER)(GLenum, GLuint); typedef void (APIENTRY *VG_FRAMEBUFFERTEXTURE2D)(GLenum, GLenum, GLenum, GLuint, GLint); typedef void (APIENTRY *VG_DELETEFRAMEBUFFERS)(GLsizei, const GLuint *); typedef GLenum (APIENTRY *VG_CHECKFRAMEBUFFERSTATUS)(GLenum); static VG_GENFRAMEBUFFERS vgGenFramebuffers; static VG_BINDFRAMEBUFFER vgBindFramebuffer; static VG_FRAMEBUFFERTEXTURE2D vgFramebufferTexture2D; static VG_DELETEFRAMEBUFFERS vgDeleteFramebuffers; static VG_CHECKFRAMEBUFFERSTATUS vgCheckFramebufferStatus; /* ================================================================== */ /* Geometry: an N-dimensional polytope engine (3..6 dimensions). */ /* */ /* Every shape stores vertices in up to MAXD dimensions plus an edge */ /* list. 3-D shapes (platonic solids, archimedeans, prisms, stars) */ /* are drawn directly; 4/5/6-D polytopes (tesseract, 24-cell, ...) */ /* are rotated in their own dimension and perspective-projected down */ /* to 3-D every frame, so they morph like true hyperobjects while */ /* also tumbling in 3-D. */ /* */ /* For regular figures, edges are derived automatically: connect */ /* every vertex pair at the shared minimum distance. Families with */ /* non-uniform edges (prisms, stars, ...) set edges explicitly. */ /* ================================================================== */ #define MAXD 6 #define MAX_VERTS 128 #define MAX_EDGES 512 #define MAX_SHAPES 720 #define TAU 6.283185307179586 typedef struct { int dim; /* 3..6 */ int nv; float v[MAX_VERTS][MAXD]; int ne; int e[MAX_EDGES][2]; } Solid; static Solid solids[MAX_SHAPES]; static int num_shapes = 0; static int clock_idx[4] = {-1,-1,-1,-1}; /* analog2d, analog3d, digital2d, digital3d */ static int g_mag_idx = -2; /* magnifying-glass shape (g_mag_idx & +1) */ static int g_prism_idx = -2; /* refracting prism shape (g_prism_idx & +1) */ static int g_glass_idx = -2; /* glass-sphere shape (g_glass_idx & +1) */ static int g_mirror_idx = -2; /* mirror-sphere shape (g_mirror_idx & +1) */ /* dynamic shapes: a shared solid rebuilt every frame from a time value */ #define MAX_DYN 24 typedef void (*DynFn)(Solid *, float); static int dyn_solid[MAX_DYN]; static DynFn dyn_func[MAX_DYN]; static int dyn_count = 0; static GLuint g_scene_tex = 0; /* framebuffer grab, magnified through the lens */ static int g_tex_w = 0, g_tex_h = 0; #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif static float frand(void); /* uniform [0,1); defined below */ static Solid *new_solid(int dim) { Solid *s = &solids[num_shapes++]; s->dim = dim; s->nv = 0; s->ne = 0; return s; } static void PV(Solid *s, double a,double b,double c,double d,double e,double f) { if (s->nv >= MAX_VERTS) return; float *p = s->v[s->nv++]; p[0]=(float)a; p[1]=(float)b; p[2]=(float)c; p[3]=(float)d; p[4]=(float)e; p[5]=(float)f; } static void AE(Solid *s, int i, int j) { if (s->ne >= MAX_EDGES) return; s->e[s->ne][0]=i; s->e[s->ne][1]=j; s->ne++; } static void AE_unique(Solid *s, int i, int j) { if (i > j) { int t = i; i = j; j = t; } for (int k = 0; k < s->ne; k++) if (s->e[k][0] == i && s->e[k][1] == j) return; AE(s, i, j); } /* Center on the centroid and scale so the farthest vertex sits on the * unit sphere (radius == 1, in `dim` dimensions). */ static void center_normalize(Solid *s) { double cen[MAXD] = {0,0,0,0,0,0}; for (int i = 0; i < s->nv; i++) for (int k = 0; k < s->dim; k++) cen[k] += s->v[i][k]; for (int k = 0; k < s->dim; k++) cen[k] /= (s->nv > 0 ? s->nv : 1); double maxr2 = 0; for (int i = 0; i < s->nv; i++) { double r2 = 0; for (int k = 0; k < s->dim; k++) { s->v[i][k] -= cen[k]; r2 += (double)s->v[i][k]*s->v[i][k]; } if (r2 > maxr2) maxr2 = r2; } double inv = maxr2 > 1e-12 ? 1.0/sqrt(maxr2) : 1.0; for (int i = 0; i < s->nv; i++) for (int k = 0; k < s->dim; k++) s->v[i][k] *= inv; } /* Derive edges from the minimum pairwise distance (regular figures). */ static void derive_edges(Solid *s) { s->ne = 0; double mind2 = 1e18; for (int i = 0; i < s->nv; i++) for (int j = i+1; j < s->nv; j++) { double d2 = 0; for (int k = 0; k < s->dim; k++) { double dd = s->v[i][k]-s->v[j][k]; d2 += dd*dd; } if (d2 < mind2) mind2 = d2; } double tol2 = mind2 * 1.04; /* ~2% slack on length */ for (int i = 0; i < s->nv; i++) for (int j = i+1; j < s->nv; j++) { double d2 = 0; for (int k = 0; k < s->dim; k++) { double dd = s->v[i][k]-s->v[j][k]; d2 += dd*dd; } if (d2 <= tol2) AE(s, i, j); } } /* ---- generic N-dimensional regular polytopes ---------------------- */ static void gen_hypercube(int d) { /* {4,3..} measure polytope */ Solid *s = new_solid(d); int N = 1 << d; for (int m = 0; m < N; m++) { double c[MAXD] = {0,0,0,0,0,0}; for (int k = 0; k < d; k++) c[k] = (m >> k & 1) ? 1 : -1; PV(s, c[0],c[1],c[2],c[3],c[4],c[5]); } center_normalize(s); derive_edges(s); } static void gen_orthoplex(int d) { /* cross-polytope */ Solid *s = new_solid(d); for (int k = 0; k < d; k++) { double c[MAXD] = {0,0,0,0,0,0}; c[k] = 1; PV(s,c[0],c[1],c[2],c[3],c[4],c[5]); c[k] = -1; PV(s,c[0],c[1],c[2],c[3],c[4],c[5]); } center_normalize(s); derive_edges(s); } static void gen_simplex(int nv) { /* regular (nv-1)-simplex */ Solid *s = new_solid(nv); /* basis e_i in R^nv */ for (int i = 0; i < nv; i++) { double c[MAXD] = {0,0,0,0,0,0}; c[i] = 1; PV(s,c[0],c[1],c[2],c[3],c[4],c[5]); } center_normalize(s); derive_edges(s); /* -> complete graph */ } static void gen_24cell(void) { /* perms of (+-1,+-1,0,0) */ Solid *s = new_solid(4); for (int a = 0; a < 4; a++) for (int b = a+1; b < 4; b++) for (int sa = -1; sa <= 1; sa += 2) for (int sb = -1; sb <= 1; sb += 2) { double c[MAXD] = {0,0,0,0,0,0}; c[a]=sa; c[b]=sb; PV(s,c[0],c[1],c[2],c[3],c[4],c[5]); } center_normalize(s); derive_edges(s); } /* ---- curated 3-D solids ------------------------------------------- */ static void gen_cuboctahedron(void) { /* perms of (+-1,+-1,0) */ Solid *s = new_solid(3); for (int a = 0; a < 3; a++) for (int b = a+1; b < 3; b++) for (int sa = -1; sa <= 1; sa += 2) for (int sb = -1; sb <= 1; sb += 2) { double c[3] = {0,0,0}; c[a]=sa; c[b]=sb; PV(s,c[0],c[1],c[2],0,0,0); } center_normalize(s); derive_edges(s); } static void gen_truncated_octahedron(void) { /* perms of (0,+-1,+-2) */ Solid *s = new_solid(3); int perm[6][3] = {{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}}; for (int p = 0; p < 6; p++) for (int s1 = -1; s1 <= 1; s1 += 2) for (int s2 = -1; s2 <= 1; s2 += 2) { double c[3] = {0,0,0}; c[perm[p][1]] = s1 * 1.0; /* axis carrying value 1 */ c[perm[p][2]] = s2 * 2.0; /* axis carrying value 2 */ PV(s, c[0],c[1],c[2],0,0,0); } center_normalize(s); derive_edges(s); } static void gen_stella_octangula(void) { /* two interlocked tetrahedra */ Solid *s = new_solid(3); for (int m = 0; m < 8; m++) PV(s, (m&1)?1:-1, (m&2)?1:-1, (m&4)?1:-1, 0,0,0); center_normalize(s); /* connect vertices whose minus-bit parity matches (each tetra) */ for (int i = 0; i < 8; i++) for (int j = i+1; j < 8; j++) { int pi = __builtin_popcount(i & 7) & 1; int pj = __builtin_popcount(j & 7) & 1; if (pi == pj) AE(s, i, j); } } /* ---- parametric 3-D families -------------------------------------- */ static void gen_prism(int n) { Solid *s = new_solid(3); for (int i = 0; i < n; i++) { double a = TAU*i/n; PV(s, cos(a), sin(a), 1, 0,0,0); } for (int i = 0; i < n; i++) { double a = TAU*i/n; PV(s, cos(a), sin(a), -1, 0,0,0); } for (int i = 0; i < n; i++) { AE(s, i, (i+1)%n); AE(s, n+i, n+(i+1)%n); AE(s, i, n+i); } center_normalize(s); } static void gen_antiprism(int n) { Solid *s = new_solid(3); for (int i = 0; i < n; i++) { double a = TAU*i/n; PV(s, cos(a), sin(a), 1, 0,0,0); } for (int i = 0; i < n; i++) { double a = TAU*(i+0.5)/n; PV(s, cos(a), sin(a), -1, 0,0,0); } for (int i = 0; i < n; i++) { AE(s, i, (i+1)%n); AE(s, n+i, n+(i+1)%n); AE(s, i, n+i); AE(s, i, n+(i+n-1)%n); } center_normalize(s); } static void gen_bipyramid(int n) { Solid *s = new_solid(3); for (int i = 0; i < n; i++) { double a = TAU*i/n; PV(s, cos(a), sin(a), 0, 0,0,0); } PV(s, 0,0, 1.25, 0,0,0); /* apex top = index n */ PV(s, 0,0,-1.25, 0,0,0); /* apex bottom = index n+1 */ for (int i = 0; i < n; i++) { AE(s, i, (i+1)%n); AE(s, n, i); AE(s, n+1, i); } center_normalize(s); } static void gen_star(int n, int k) { /* star polygon {n/k} */ Solid *s = new_solid(3); for (int i = 0; i < n; i++) { double a = TAU*i/n; PV(s, cos(a), sin(a), 0, 0,0,0); } for (int i = 0; i < n; i++) AE(s, i, (i+k)%n); center_normalize(s); } /* n-gonal trapezohedron (dual of an antiprism): two staggered rings wired * in a zig-zag equator and capped by two apexes -> 2n kite faces. Used for * the larger many-faced solids (24-, 30-, 60-hedra). */ static void gen_trapezohedron(int n) { Solid *s = new_solid(3); for (int i = 0; i < n; i++) { double a = TAU*i/n; PV(s, cos(a), sin(a), 0.45, 0,0,0); } for (int i = 0; i < n; i++) { double a = TAU*(i+0.5)/n; PV(s, cos(a), sin(a), -0.45, 0,0,0); } int top = 2*n, bot = 2*n + 1; PV(s, 0,0, 1.5, 0,0,0); /* top apex = 2n */ PV(s, 0,0, -1.5, 0,0,0); /* bottom apex = 2n+1 */ for (int i = 0; i < n; i++) { AE(s, i, n + i); /* upper ring -> staggered lower ring */ AE(s, n + i, (i + 1) % n); /* lower ring -> next upper-ring vertex */ AE(s, top, i); /* top apex -> upper ring */ AE(s, bot, n + i); /* bottom apex-> lower ring */ } center_normalize(s); } /* Unicursal hexagram: the six-pointed star drawn as a single closed stroke, * the way the occult symbol actually is. Unlike the Star of David ({6/2}, two * separate triangles), a regular hexagram cannot be traced in one pen-stroke, * so this visits the six tips in the order 90 -> 210 -> 330 -> 270 -> 30 -> 150 * degrees: the long 120-degree chords cross through the interior and weave the * star, leaving the figure 2-fold symmetric like the genuine emblem. */ static void gen_unicursal_hexagram(void) { Solid *s = new_solid(3); const int order[6] = { 90, 210, 330, 270, 30, 150 }; /* tip angles, path order */ for (int i = 0; i < 6; i++) { double a = order[i] * TAU / 360.0; PV(s, cos(a), sin(a), 0, 0,0,0); } for (int i = 0; i < 6; i++) AE(s, i, (i + 1) % 6); /* one closed loop */ center_normalize(s); } /* ================================================================== */ /* Symbols & signs: a small 2-D stroke builder that finalizes either */ /* flat (depth 0) or extruded into a 3-D "wireframe prism" (depth > 0): */ /* a front layer at +z, a back layer at -z, and rungs joining them. */ /* ================================================================== */ typedef struct { float px[MAX_VERTS], py[MAX_VERTS]; int ea[MAX_EDGES], eb[MAX_EDGES]; int np, ne; } SymB; static void sym_init(SymB *b) { b->np = 0; b->ne = 0; } static int sym_pt(SymB *b, double x, double y) { if (b->np >= MAX_VERTS) return b->np - 1; b->px[b->np] = (float)x; b->py[b->np] = (float)y; return b->np++; } static void sym_edge(SymB *b, int i, int j) { if (b->ne >= MAX_EDGES || i < 0 || j < 0 || i == j) return; b->ea[b->ne] = i; b->eb[b->ne] = j; b->ne++; } static void sym_line(SymB *b, double x0,double y0,double x1,double y1) { sym_edge(b, sym_pt(b,x0,y0), sym_pt(b,x1,y1)); } static void sym_ring(SymB *b, double cx,double cy,double r,int seg) { /* closed circle */ int first = -1, prev = -1; for (int i = 0; i < seg; i++) { double a = TAU * i / seg; int idx = sym_pt(b, cx + r*cos(a), cy + r*sin(a)); if (first < 0) first = idx; sym_edge(b, prev, idx); prev = idx; } sym_edge(b, prev, first); } static void sym_arc(SymB *b, double cx,double cy,double r,double a0,double a1,int seg) { int prev = -1; /* open arc */ for (int i = 0; i <= seg; i++) { double a = a0 + (a1 - a0) * i / seg; int idx = sym_pt(b, cx + r*cos(a), cy + r*sin(a)); sym_edge(b, prev, idx); prev = idx; } } static void sym_poly(SymB *b, const double *xy, int n, int closed) { int first = -1, prev = -1; for (int i = 0; i < n; i++) { int idx = sym_pt(b, xy[2*i], xy[2*i+1]); if (first < 0) first = idx; sym_edge(b, prev, idx); prev = idx; } if (closed) sym_edge(b, prev, first); } static double sym_deg(double d) { return d * TAU / 360.0; } /* 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]); } else { int n = b->np; for (int k = 0; k < n; k++) PV(s, b->px[k], b->py[k], depth, 0,0,0); /* front */ for (int k = 0; k < n; k++) PV(s, b->px[k], b->py[k], -depth, 0,0,0); /* back */ for (int k = 0; k < b->ne; k++) { AE(s, b->ea[k], b->eb[k]); AE(s, n + b->ea[k], n + b->eb[k]); } for (int k = 0; k < n; k++) AE(s, k, n + k); /* rungs */ } 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]) ------- */ static void build_smiley(SymB *b) { sym_ring(b, 0.0, 0.0, 1.0, 18); /* face */ sym_ring(b, -0.35, 0.32, 0.13, 6); /* l eye */ sym_ring(b, 0.35, 0.32, 0.13, 6); /* r eye */ sym_arc (b, 0.0, 0.10, 0.55, sym_deg(200), sym_deg(340), 8); /* smile */ } static void build_biohazard(SymB *b) { sym_ring(b, 0.0, 0.0, 0.24, 8); /* central circle */ for (int k = 0; k < 3; k++) { /* three broken rings, gaps facing center */ double th = sym_deg(90 + 120*k); double cx = 0.58*cos(th), cy = 0.58*sin(th); double g = sym_deg(90 + 120*k + 180); /* gap direction (toward center) */ sym_arc(b, cx, cy, 0.44, g + sym_deg(38), g + sym_deg(322), 9); } } static void build_peace(SymB *b) { sym_ring(b, 0.0, 0.0, 1.0, 18); int c = sym_pt(b, 0.0, 0.0); const double ang[4] = {90, 270, 225, 315}; /* up, down, lower-left, lower-right */ for (int i = 0; i < 4; i++) { double a = sym_deg(ang[i]); sym_edge(b, c, sym_pt(b, cos(a), sin(a))); } } static void build_cross(SymB *b) { /* Latin cross outline */ const double p[] = { -0.22, 1.00, 0.22, 1.00, 0.22, 0.45, 0.60, 0.45, 0.60, 0.00, 0.22, 0.00, 0.22,-1.00, -0.22,-1.00, -0.22, 0.00, -0.60, 0.00, -0.60, 0.45, -0.22, 0.45 }; sym_poly(b, p, 12, 1); } static void build_question(SymB *b) { sym_arc(b, 0.0, 0.42, 0.42, sym_deg(200), sym_deg(-30), 10); /* top hook */ int last = b->np - 1; int t1 = sym_pt(b, 0.0, -0.05); sym_edge(b, last, t1); /* tail curls in */ int t2 = sym_pt(b, 0.0, -0.35); sym_edge(b, t1, t2); sym_ring(b, 0.0, -0.72, 0.12, 6); /* dot */ } static void build_exclaim(SymB *b) { const double bar[] = { -0.12, 1.0, 0.12, 1.0, 0.06, -0.20, -0.06, -0.20 }; sym_poly(b, bar, 4, 1); /* tapered stroke */ sym_ring(b, 0.0, -0.62, 0.13, 6); /* dot */ } static void build_hash(SymB *b) { /* # — two verticals, two horizontals */ sym_line(b, -0.32, 0.85, -0.20, -0.85); sym_line(b, 0.20, 0.85, 0.32, -0.85); sym_line(b, -0.85, 0.32, 0.85, 0.32); sym_line(b, -0.85, -0.32, 0.85, -0.32); } static void build_dollar(SymB *b) { /* $ — S-curve with a vertical bar through it */ const double S[] = { 0.80, 0.64, 0.40, 0.96, -0.40, 0.96, -0.80, 0.64, -0.40, 0.00, 0.40, 0.00, 0.80,-0.32, 0.40,-0.96, -0.40,-0.96, -0.80,-0.64 }; sym_poly(b, S, 10, 0); sym_line(b, 0.0, 1.18, 0.0, -1.18); } static void build_sterling(SymB *b) { /* £ — hooked top, stem, crossbar, base */ sym_arc(b, 0.05, 0.45, 0.42, sym_deg(8), sym_deg(200), 9); int last = b->np - 1; int s1 = sym_pt(b, -0.33, -0.58); sym_edge(b, last, s1); /* stem down */ int base0 = sym_pt(b, -0.52, -0.62); int base1 = sym_pt(b, 0.52, -0.62); sym_edge(b, s1, base0); sym_edge(b, base0, base1); /* base */ sym_line(b, -0.33, -0.05, 0.42, -0.05); /* crossbar */ } /* ---- googie / atomic-age starbursts -------------------------------- */ /* Flat multi-point sparkle (the bowling-alley twinkle): sharp tips at radius * r1 (or alternating r1/r2) joined through deep notches at radius `inner`. */ static void build_sparkle(SymB *b, int tips, double r1, double r2, double inner) { int first = -1, prev = -1; for (int i = 0; i < tips; i++) { double ta = TAU*0.25 + TAU*i/tips; /* first tip points up */ double na = ta + TAU/(2*tips); double tr = (r2 > 0 && (i & 1)) ? r2 : r1; int t = sym_pt(b, tr*cos(ta), tr*sin(ta)); if (first < 0) first = t; sym_edge(b, prev, t); prev = t; int n = sym_pt(b, inner*cos(na), inner*sin(na)); sym_edge(b, prev, n); prev = n; } sym_edge(b, prev, first); } /* Flat atomic burst: spokes of alternating length from a hub, with little * "electron" rings capping the long ones. */ static void build_atomic(SymB *b) { int c = sym_pt(b, 0.0, 0.0); int n = 12; for (int i = 0; i < n; i++) { double a = TAU*i/n; double r = (i & 1) ? 0.55 : 1.0; sym_edge(b, c, sym_pt(b, r*cos(a), r*sin(a))); if (!(i & 1)) sym_ring(b, cos(a), sin(a), 0.08, 5); } } /* Flat sunburst: a dense fan of alternating-length rays from a hub. */ static void build_sunburst(SymB *b) { int c = sym_pt(b, 0.0, 0.0); int n = 20; for (int i = 0; i < n; i++) { double a = TAU*i/n; double r = (i & 1) ? 0.78 : 1.0; sym_edge(b, c, sym_pt(b, r*cos(a), r*sin(a))); } } /* True 3-D Sputnik: rods to the 12 icosahedral directions, each tipped with a * small ring "ball" lying perpendicular to the rod. */ static void gen_sputnik(void) { Solid *s = new_solid(3); const double P = 1.6180339887; const double dir[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} }; int c = s->nv; PV(s, 0,0,0, 0,0,0); for (int i = 0; i < 12; i++) { double L = sqrt(dir[i][0]*dir[i][0] + dir[i][1]*dir[i][1] + dir[i][2]*dir[i][2]); double ux = dir[i][0]/L, uy = dir[i][1]/L, uz = dir[i][2]/L; int tip = s->nv; PV(s, ux, uy, uz, 0,0,0); AE(s, c, tip); double rx = uy, ry = -ux, rz = 0, rl = sqrt(rx*rx + ry*ry + rz*rz); if (rl < 1e-6) { rx = 1; ry = 0; rz = 0; rl = 1; } rx /= rl; ry /= rl; rz /= rl; double sx = uy*rz - uz*ry, sy = uz*rx - ux*rz, sz = ux*ry - uy*rx; /* u x r */ int ring0 = -1, prev = -1; double rr = 0.14; for (int k = 0; k < 5; k++) { double a = TAU*k/5; double bx = ux + rr*(cos(a)*rx + sin(a)*sx); double by = uy + rr*(cos(a)*ry + sin(a)*sy); double bz = uz + rr*(cos(a)*rz + sin(a)*sz); int idx = s->nv; PV(s, bx, by, bz, 0,0,0); if (ring0 < 0) ring0 = idx; if (prev >= 0) AE(s, prev, idx); prev = idx; } AE(s, prev, ring0); } center_normalize(s); } /* ---- more googie shapes: flat builders + naturally-3-D generators --- */ static void build_sparkle4(SymB *b) { build_sparkle(b, 4, 1.0, 0.0, 0.13); } static void build_sparkle8(SymB *b) { build_sparkle(b, 8, 1.0, 0.62, 0.12); } static void sym_ellipse(SymB *b, double cx,double cy,double ra,double rb,double rot,int seg) { int first = -1, prev = -1; for (int i = 0; i < seg; i++) { double a = TAU*i/seg, ex = ra*cos(a), ey = rb*sin(a); int idx = sym_pt(b, cx + ex*cos(rot) - ey*sin(rot), cy + ex*sin(rot) + ey*cos(rot)); if (first < 0) first = idx; sym_edge(b, prev, idx); prev = idx; } sym_edge(b, prev, first); } static void build_atom2d(SymB *b) { /* flat atom: 3 ellipses + nucleus */ sym_ring(b, 0, 0, 0.16, 7); sym_ellipse(b, 0, 0, 1.0, 0.34, sym_deg(0), 16); sym_ellipse(b, 0, 0, 1.0, 0.34, sym_deg(60), 16); sym_ellipse(b, 0, 0, 1.0, 0.34, sym_deg(120), 16); } static void build_boomerang(SymB *b) { /* crescent / boomerang band */ double Ro = 1.0, Ri = 0.6, a0 = sym_deg(30), a1 = sym_deg(150); int seg = 8; int first = -1, prev = -1; for (int i = 0; i <= seg; i++) { double a = a0 + (a1-a0)*i/seg; int idx = sym_pt(b, Ro*cos(a), Ro*sin(a)); if (first<0) first=idx; sym_edge(b, prev, idx); prev = idx; } for (int i = 0; i <= seg; i++) { double a = a1 + (a0-a1)*i/seg; int idx = sym_pt(b, Ri*cos(a), Ri*sin(a)); sym_edge(b, prev, idx); prev = idx; } sym_edge(b, prev, first); } static void build_amoeba(SymB *b) { /* googie kidney / amoeba blob */ int seg = 18, first = -1, prev = -1; for (int i = 0; i < seg; i++) { double a = TAU*i/seg; double r = 0.72 + 0.26*sin(3*a + 0.6) + 0.12*sin(2*a - 0.4); int idx = sym_pt(b, r*cos(a), 0.82*r*sin(a)); if (first < 0) first = idx; sym_edge(b, prev, idx); prev = idx; } sym_edge(b, prev, first); } static void build_doublestar(SymB *b) { /* big 4-point twinkle + inner one at 45 */ build_sparkle(b, 4, 1.0, 0.0, 0.12); int first = -1, prev = -1, tips = 4; double R = 0.5, inner = 0.07, off = TAU/8; for (int i = 0; i < tips; i++) { double ta = TAU*0.25 + off + TAU*i/tips, na = ta + TAU/(2*tips); int t = sym_pt(b, R*cos(ta), R*sin(ta)); if (first<0) first=t; sym_edge(b, prev, t); prev = t; int n = sym_pt(b, inner*cos(na), inner*sin(na)); sym_edge(b, prev, n); prev = n; } sym_edge(b, prev, first); } static void build_orbit2d(SymB *b) { /* concentric orbit rings + ticks */ sym_ring(b, 0, 0, 1.00, 18); sym_ring(b, 0, 0, 0.64, 14); sym_ring(b, 0, 0, 0.30, 9); for (int i = 0; i < 4; i++) { double a = sym_deg(45 + 90*i); sym_line(b, 0.30*cos(a), 0.30*sin(a), cos(a), sin(a)); } } static void build_starorb2d(SymB *b) { /* dense ray star with a core ring */ int c = sym_pt(b, 0, 0), n = 24; for (int i = 0; i < n; i++) { double a = TAU*i/n; sym_edge(b, c, sym_pt(b, cos(a), sin(a))); } sym_ring(b, 0, 0, 0.30, 10); } /* 3-D atom: three circular orbits in tilted planes around an octahedral core. */ static void gen_atom3d(void) { Solid *s = new_solid(3); int seg = 20; const double tilt[3] = {0, 60, 120}; for (int o = 0; o < 3; o++) { double t = sym_deg(tilt[o]); int first = -1, prev = -1; for (int i = 0; i < seg; i++) { double a = TAU*i/seg, x = cos(a), y = sin(a), z = 0; double y2 = y*cos(t) - z*sin(t), z2 = y*sin(t) + z*cos(t); int idx = s->nv; PV(s, x, y2, z2, 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.13; 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); const 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); } /* 3-D gyroscope: three great circles in the coordinate planes. */ static void gen_gyroscope3d(void) { Solid *s = new_solid(3); int seg = 22; for (int p = 0; p < 3; p++) { int first = -1, prev = -1; for (int i = 0; i < seg; i++) { double a = TAU*i/seg, c = cos(a), sn = sin(a), x, y, z; if (p == 0) { x = c; y = sn; z = 0; } else if (p == 1) { x = c; y = 0; z = sn; } else { x = 0; y = c; z = sn; } int idx = s->nv; 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); } /* 3-D spike orb / sea-urchin: rods to 20 symmetric directions from a hub. */ static void gen_spikeorb3d(void) { Solid *s = new_solid(3); const double P = 1.6180339887; const double d[20][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}, {1,1,1},{1,1,-1},{1,-1,1},{1,-1,-1},{-1,1,1},{-1,1,-1},{-1,-1,1},{-1,-1,-1} }; int c = s->nv; PV(s, 0,0,0, 0,0,0); for (int i = 0; i < 20; i++) { double L = sqrt(d[i][0]*d[i][0] + d[i][1]*d[i][1] + d[i][2]*d[i][2]); int tip = s->nv; PV(s, d[i][0]/L, d[i][1]/L, d[i][2]/L, 0,0,0); AE(s, c, tip); } 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); } /* ---- more object iconography (flat 2-D + extruded 3-D) -------------- */ /* helper: closed outline from a right-half profile, mirrored across x=0; * skips duplicating the shared top/bottom centre points (those have x==0). */ static void sym_profile(SymB *b, const double (*rp)[2], int n) { int first=-1, prev=-1; for (int i=0;i=0)sym_edge(b,prev,idx); prev=idx; } for (int i=n-1;i>=0;i--){ if (rp[i][0]==0.0) continue; /* don't repeat centre points */ int idx=sym_pt(b, -rp[i][0], rp[i][1]); sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); } static void build_ghost(SymB *b) { /* pac-man ghost */ double R=0.78, top=0.18, bot=-0.7; int first=sym_pt(b,-R,bot), prev=first; for (int i=0;i<=12;i++){ double a=TAU*0.5 - TAU*0.5*i/12; /* dome 180->0 */ int idx=sym_pt(b, R*cos(a), top + R*sin(a)); sym_edge(b,prev,idx); prev=idx; } int rb=sym_pt(b, R, bot); sym_edge(b,prev,rb); prev=rb; for (int i=0;i<4;i++){ /* wavy skirt, points down */ double x1=R-2*R*(i+1)/4, xm=R-2*R*(i+0.5)/4; int v=sym_pt(b,xm,bot+0.2); sym_edge(b,prev,v); prev=v; int p=sym_pt(b,x1,bot); sym_edge(b,prev,p); prev=p; } sym_edge(b,prev,first); sym_ring(b,-0.27,0.18,0.19,8); sym_ring(b, 0.27,0.18,0.19,8); /* eyes */ sym_ring(b,-0.19,0.18,0.07,4); sym_ring(b, 0.35,0.18,0.07,4); /* pupils */ } static void build_icecream(SymB *b) { /* ice cream cone */ int apex=sym_pt(b,0,-1.05), tl=sym_pt(b,-0.42,0.05), tr=sym_pt(b,0.42,0.05); sym_edge(b,apex,tl); sym_edge(b,apex,tr); sym_line(b,-0.28,-0.28, 0.02,-0.02); /* waffle hatch */ sym_line(b, 0.0,-0.5, 0.30,-0.2); sym_line(b,-0.42,0.05, 0.42,0.05); /* scoop base */ sym_arc(b,-0.21,0.18,0.23, sym_deg(0), sym_deg(180), 6); /* three scoop bumps */ sym_arc(b, 0.21,0.18,0.23, sym_deg(0), sym_deg(180), 6); sym_arc(b, 0.0, 0.34,0.24, sym_deg(0), sym_deg(180), 6); } static void build_bulb(SymB *b) { /* light bulb */ double cy=0.32, R=0.58, bx=0.24; sym_arc(b, 0,cy, R, sym_deg(-55), sym_deg(235), 16); /* glass, open at neck */ int start=0, last=b->np-1; int br=sym_pt(b, bx,-0.18); sym_edge(b,start,br); /* start = lower-right */ int bl=sym_pt(b,-bx,-0.18); sym_edge(b,last,bl); /* last = lower-left */ int br2=sym_pt(b, bx,-0.42), bl2=sym_pt(b,-bx,-0.42); sym_edge(b,br,br2); sym_edge(b,bl,bl2); sym_edge(b,bl2,br2); /* screw base */ sym_line(b,-bx,-0.26, bx,-0.26); sym_line(b,-bx,-0.34, bx,-0.34); /* threads */ sym_line(b,-0.15,0.18,-0.05,0.42); /* filament W */ sym_line(b,-0.05,0.42, 0.05,0.18); sym_line(b, 0.05,0.18, 0.15,0.42); } static void build_mito(SymB *b) { /* mitochondrion */ int seg=18, first=-1, prev=-1; for (int i=0;i=0)sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); for (int i=0;i<4;i++) sym_arc(b,-0.6+0.4*i, 0.24,0.17,sym_deg(0),sym_deg(-180),4); /* cristae */ for (int i=0;i<3;i++) sym_arc(b,-0.4+0.4*i,-0.24,0.17,sym_deg(0),sym_deg( 180),4); } static void build_window(SymB *b) { /* windowpane */ const double fr[]={-0.8,-1.0, 0.8,-1.0, 0.8,1.0, -0.8,1.0}; const double in[]={-0.7,-0.9, 0.7,-0.9, 0.7,0.9, -0.7,0.9}; sym_poly(b,fr,4,1); sym_poly(b,in,4,1); sym_line(b,0,-0.9,0,0.9); sym_line(b,-0.7,0,0.7,0); /* mullions */ sym_line(b,-0.92,-1.0,0.92,-1.0); /* sill */ } static void build_bowlingpin(SymB *b) { /* bowling pin */ const double rp[][2]={ {0,1.0},{0.12,0.85},{0.14,0.68},{0.30,0.42},{0.34,0.15}, {0.24,-0.2},{0.16,-0.5},{0.26,-0.82},{0.21,-1.0} }; sym_profile(b,rp,9); sym_line(b,-0.16,0.6,0.16,0.6); sym_line(b,-0.18,0.52,0.18,0.52); /* neck stripes */ } static void build_bat(SymB *b) { /* baseball bat */ const double rp[][2]={ {0,-1.0},{0.13,-0.95},{0.13,-0.86},{0.06,-0.78}, {0.05,-0.2},{0.10,0.35},{0.17,0.82},{0.15,0.96},{0,1.0} }; sym_profile(b,rp,9); sym_line(b,-0.10,-0.7,0.10,-0.7); /* grip line */ } static void build_beerbottle(SymB *b) { /* beer bottle */ const double rp[][2]={ {0,-1.0},{0.32,-0.97},{0.34,-0.2},{0.30,0.05}, {0.12,0.35},{0.11,0.82},{0.15,0.9},{0.15,1.0},{0,1.0} }; sym_profile(b,rp,9); sym_line(b,-0.34,-0.45,0.34,-0.45); sym_line(b,-0.34,-0.05,0.34,-0.05); /* label */ } static void build_nonic(SymB *b) { /* nonic pint glass (bulge near rim) */ const double rp[][2]={ {0.30,-1.0},{0.34,-0.5},{0.38,0.45},{0.47,0.62}, {0.40,0.74},{0.42,0.96} }; int n=6, first=-1, prev=-1; for (int i=0;i=0)sym_edge(b,prev,idx); prev=idx; } for (int i=n-1;i>=0;i--){ int idx=sym_pt(b,-rp[i][0],rp[i][1]); sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); /* closes base + rim */ } static void build_whiskey(SymB *b) { /* whiskey bottle */ const double rp[][2]={ {0,-1.0},{0.40,-0.95},{0.42,-0.1},{0.40,0.2}, {0.20,0.45},{0.18,0.7},{0.22,0.78},{0.22,0.92},{0,0.92} }; sym_profile(b,rp,9); sym_line(b,-0.40,-0.5,0.40,-0.5); sym_line(b,-0.40,0.05,0.40,0.05); /* label box */ sym_line(b,-0.40,-0.5,-0.40,0.05); sym_line(b,0.40,-0.5,0.40,0.05); } static void build_phone(SymB *b) { /* desk telephone */ const double base[]={-0.7,-0.55, 0.7,-0.55, 0.62,-0.05, -0.62,-0.05}; sym_poly(b,base,4,1); sym_ring(b,0,-0.3,0.17,8); /* rotary dial */ sym_arc(b,0,0.78,0.66, sym_deg(205), sym_deg(335), 10); /* handset handle */ sym_ring(b,-0.6,0.5,0.15,7); sym_ring(b, 0.6,0.5,0.15,7); /* ear / mouth piece */ } static void build_booth(SymB *b) { /* red telephone box */ const double box[]={-0.5,-1.0, 0.5,-1.0, 0.5,0.7, -0.5,0.7}; const double crown[]={-0.5,0.7, -0.42,0.86, 0.42,0.86, 0.5,0.7}; const double win[]={-0.4,-0.2, 0.4,-0.2, 0.4,0.55, -0.4,0.55}; sym_poly(b,box,4,1); sym_poly(b,crown,4,0); sym_poly(b,win,4,1); sym_line(b,-0.45,0.62,0.45,0.62); /* TELEPHONE sign band */ sym_line(b,-0.4,0.12,0.4,0.12); sym_line(b,-0.4,0.33,0.4,0.33); /* pane grid */ sym_line(b,0,-0.2,0,0.55); } static void build_radiotower(SymB *b) { /* lattice radio tower */ sym_line(b,-0.4,-1.0,-0.08,0.7); sym_line(b,0.4,-1.0,0.08,0.7); /* legs */ for (int i=0;i<5;i++){ double t0=i/5.0, t1=(i+1)/5.0; double lx0=-0.4+0.32*t0, rx0=0.4-0.32*t0, y0=-1.0+1.7*t0; double lx1=-0.4+0.32*t1, rx1=0.4-0.32*t1, y1=-1.0+1.7*t1; sym_line(b,lx0,y0,rx1,y1); sym_line(b,rx0,y0,lx1,y1); /* cross-brace X */ sym_line(b,lx1,y1,rx1,y1); /* horizontal */ } sym_line(b,0,0.7,0,1.0); /* mast */ sym_arc(b,0,1.0,0.18,sym_deg(20),sym_deg(160),5); /* signal waves */ sym_arc(b,0,1.0,0.30,sym_deg(25),sym_deg(155),6); } static void build_atom_e2d(SymB *b) { /* flat atom with electrons */ sym_ring(b,0,0,0.14,6); /* nucleus */ const double rots[3]={0,60,120}; for (int k=0;k<3;k++) sym_ellipse(b,0,0,1.0,0.36,sym_deg(rots[k]),12); for (int k=0;k<3;k++){ /* one electron per orbit */ double rot=sym_deg(rots[k]), a=sym_deg(30+k*45); double ex=cos(a), ey=0.36*sin(a); sym_ring(b, ex*cos(rot)-ey*sin(rot), ex*sin(rot)+ey*cos(rot), 0.08, 5); } } /* ---- volumetric versions (dedicated 3-D generators) ---------------- */ /* 3-D atom: three tilted orbits, an octahedral nucleus, and an electron * ball riding each orbit. (The whole body tumbles, so the electrons sweep.) */ static void gen_atom_electrons3d(void) { Solid *s=new_solid(3); int seg=20; const double tilt[3]={0,60,120}; for (int o=0;o<3;o++){ double t=sym_deg(tilt[o]); int first=-1, prev=-1; for (int i=0;inv; PV(s,x,y2,z2,0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } AE(s,prev,first); double a=sym_deg(25+o*47), x=cos(a), y=sin(a), z=0; /* electron */ double y2=y*cos(t)-z*sin(t), z2=y*sin(t)+z*cos(t); add_ball(s, x,y2,z2, x,y2,z2, 0.11, 5); } int b0=s->nv; double r=0.13; 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); } /* 3-D bowling ball: a wireframe sphere with three finger holes up front. */ static void gen_bowlingball3d(void) { Solid *s=new_solid(3); int lats=4, seg=16; for (int la=1;la<=lats;la++){ double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25, y=sin(phi), rr=cos(phi); 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); } const double hole[3][2]={{-0.2,0.25},{0.2,0.25},{0.0,-0.05}}; for (int h=0;h<3;h++){ double hx=hole[h][0], hy=hole[h][1], hz=sqrt(fmax(0.0,1.0-hx*hx-hy*hy)); add_ball(s, hx,hy,hz, hx,hy,hz, 0.1, 6); } center_normalize(s); } /* ================================================================== */ /* A larger batch of 3-D objects: solids of revolution (lathe), a */ /* parametric torus, and extruded silhouettes for the flatter items. */ /* ================================================================== */ /* Revolve a profile of (radius,height) points around the Y axis into a * wireframe surface of revolution. r<~0 makes an on-axis point (apex/pole). * Adds to `s` without normalizing, so callers can append details first. */ static void lathe_into(Solid *s, const double (*prof)[2], int n, int seg) { int rs[40], isp[40]; for (int i = 0; i < n && i < 40; i++) { double r = prof[i][0], y = prof[i][1]; rs[i] = s->nv; if (r < 1e-4) { PV(s, 0, y, 0, 0,0,0); isp[i] = 1; } else { isp[i] = 0; for (int j = 0; j < seg; j++) { double a = TAU*j/seg; PV(s, r*cos(a), y, r*sin(a), 0,0,0); } for (int j = 0; j < seg; j++) AE(s, rs[i]+j, rs[i]+(j+1)%seg); } } for (int i = 0; i < n-1; i++) { if (isp[i] && isp[i+1]) AE(s, rs[i], rs[i+1]); else if (isp[i]) for (int j=0;jnv; PV(s, cx+r*cos(a), cy+r*sin(a), 0, 0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } if(first>=0) AE(s,prev,first); } /* ---- chess pieces (lathe profiles, bottom -> top) ------------------ */ static void gen_pawn(void){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86},{0.15,-0.76}, {0.12,-0.45},{0.22,-0.36},{0.11,-0.28},{0.19,-0.12},{0.25,0.0},{0.22,0.13},{0.10,0.27},{0,0.33}}; gen_lathe(p,12,8); } static void gen_rook(void){ const double p[][2]={{0,-1.0},{0.40,-1.0},{0.34,-0.84},{0.19,-0.68}, {0.19,0.35},{0.33,0.5},{0.33,0.82},{0.24,0.82},{0.24,0.6},{0,0.6}}; Solid*s=new_solid(3); lathe_into(s,p,10,8); for(int k=0;k<4;k++){ double a=TAU*k/4; double c=cos(a),sn=sin(a); /* crenellation notches */ int v0=s->nv; PV(s,0.30*c,0.82,0.30*sn,0,0,0); PV(s,0.30*c,0.95,0.30*sn,0,0,0); AE(s,v0,v0+1);} center_normalize(s); } static void gen_bishop(void){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86},{0.16,-0.74}, {0.13,-0.4},{0.24,-0.3},{0.12,-0.22},{0.20,0.0},{0.16,0.3},{0.09,0.55},{0.06,0.66},{0,0.72}, {0.05,0.78},{0,0.86}}; gen_lathe(p,14,8); } static void gen_king(void){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85},{0.17,-0.72}, {0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.20,0.05},{0.27,0.28},{0.18,0.5},{0.16,0.6},{0,0.6}}; Solid*s=new_solid(3); lathe_into(s,p,12,8); int a=s->nv; PV(s,0,0.6,0,0,0,0); PV(s,0,1.0,0,0,0,0); AE(s,a,a+1); /* cross */ int c=s->nv; PV(s,-0.13,0.84,0,0,0,0); PV(s,0.13,0.84,0,0,0,0); AE(s,c,c+1); center_normalize(s); } static void gen_queen(void){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85},{0.17,-0.72}, {0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.22,0.1},{0.29,0.38},{0.30,0.52}}; Solid*s=new_solid(3); lathe_into(s,p,10,8); for(int k=0;k<7;k++){ double a=TAU*k/7; double c=cos(a),sn=sin(a); /* coronet spikes */ int v0=s->nv; PV(s,0.28*c,0.52,0.28*sn,0,0,0); PV(s,0.30*c,0.74,0.30*sn,0,0,0); AE(s,v0,v0+1); add_ball(s,0.30*c,0.74,0.30*sn, c,0.3,sn, 0.05,4); } center_normalize(s); } /* ---- other solids of revolution ------------------------------------ */ static void gen_candlestick(void){ const double p[][2]={{0,-1.0},{0.42,-1.0},{0.36,-0.85},{0.12,-0.7}, {0.10,-0.2},{0.20,-0.05},{0.13,0.02},{0.11,0.08},{0.11,0.62},{0,0.62}}; Solid*s=new_solid(3); lathe_into(s,p,10,8); /* flame teardrop above the wick */ int f=s->nv; PV(s,0,0.62,0,0,0,0); PV(s,0.07,0.75,0,0,0,0); PV(s,0,0.92,0,0,0,0); PV(s,-0.07,0.75,0,0,0,0); AE(s,f,f+1); AE(s,f+1,f+2); AE(s,f+2,f+3); AE(s,f+3,f); center_normalize(s); } static void gen_coffeecup(void){ const double p[][2]={{0,-0.6},{0.46,-0.6},{0.5,-0.45},{0.46,0.5},{0.40,0.5}}; Solid*s=new_solid(3); lathe_into(s,p,5,10); /* handle: a C-loop on the +X side, in the X-Y plane */ int h=s->nv; PV(s,0.5,0.25,0,0,0,0); PV(s,0.85,0.2,0,0,0,0); PV(s,0.88,-0.1,0,0,0,0); PV(s,0.55,-0.3,0,0,0,0); AE(s,h,h+1); AE(s,h+1,h+2); AE(s,h+2,h+3); center_normalize(s); } static void gen_lantern(void){ const double p[][2]={{0,-1.0},{0.35,-0.95},{0.40,-0.75},{0.34,-0.6}, {0.36,0.45},{0.30,0.6},{0.34,0.72},{0.18,0.82},{0.12,0.9},{0,0.9}}; Solid*s=new_solid(3); lathe_into(s,p,10,8); add_xy_ring(s,0,1.05,0.13,8); /* hanging ring */ int v=s->nv; PV(s,0,0.9,0,0,0,0); PV(s,0,0.92,0,0,0,0); AE(s,v,v+1); center_normalize(s); } static void gen_hurricane(void){ const double p[][2]={{0,-1.0},{0.34,-0.95},{0.40,-0.78},{0.30,-0.6}, {0.14,-0.5},{0.30,-0.4},{0.30,-0.28},{0.20,-0.18},{0.26,0.55},{0.34,0.85},{0.30,1.0}}; gen_lathe(p,11,8); } /* oil base + glass chimney */ static void gen_quartz(void){ const double p[][2]={{0,-1.05},{0.30,-0.7},{0.45,-0.5},{0.45,0.5}, {0.30,0.7},{0,1.05}}; gen_lathe(p,6,6); } /* hexagonal crystal */ static void gen_pineapple(void){ const double p[][2]={{0,-1.0},{0.25,-0.85},{0.42,-0.5},{0.45,0.1}, {0.34,0.4},{0.18,0.55},{0,0.55}}; Solid*s=new_solid(3); lathe_into(s,p,7,10); for(int k=0;k<6;k++){ double a=TAU*k/6; double c=cos(a),sn=sin(a); /* crown leaves */ int v=s->nv; PV(s,0.12*c,0.55,0.12*sn,0,0,0); PV(s,0.22*c,0.95,0.22*sn,0,0,0); AE(s,v,v+1); } int t=s->nv; PV(s,0,0.55,0,0,0,0); PV(s,0,1.05,0,0,0,0); AE(s,t,t+1); center_normalize(s); } static void gen_torus3d(void){ Solid *s=new_solid(3); int nu=16, nv=8; double R=0.72, rr=0.27; for(int i=0;inp-1; /* right / left ends */ sym_arc(b,0,-0.45,0.72,sym_deg(40),sym_deg(140),9); /* inner edge */ int iR=oL+1, iL=b->np-1; sym_edge(b,oR,iR); sym_edge(b,oL,iL); /* close the two tips */ sym_line(b,-0.86,0.12,-0.97,0.28); /* stem */ } static void build_guitar(SymB *b){ /* electric guitar */ sym_ellipse(b,0,-0.5,0.55,0.42,0,16); /* body */ const double neck[]={-0.11,-0.15, 0.11,-0.15, 0.09,0.82, -0.09,0.82}; const double head[]={-0.13,0.82, 0.13,0.82, 0.15,1.04, -0.15,1.04}; sym_poly(b,neck,4,1); sym_poly(b,head,4,1); for(int i=0;i<4;i++){ double y=0.05+i*0.18; sym_line(b,-0.10,y,0.10,y); } /* frets */ sym_line(b,-0.26,-0.5,0.26,-0.5); sym_line(b,-0.26,-0.3,0.26,-0.3); /* pickups */ sym_line(b,-0.2,-0.72,0.2,-0.72); /* bridge */ } static void build_taco(SymB *b){ sym_arc(b,0,0.28,1.0,sym_deg(200),sym_deg(340),14); /* shell */ for(int i=0;i<5;i++){ double x=-0.7+0.35*i; sym_arc(b,x,0.28,0.12,sym_deg(0),sym_deg(180),4);} /* fillings */ sym_line(b,-0.94,0.0,0.94,0.0); } static void build_pizza(SymB *b){ const double tri[]={0,-1.0, -0.62,0.7, 0.62,0.7}; sym_poly(b,tri,3,1); sym_arc(b,0,0.0,1.07,sym_deg(65),sym_deg(115),8); /* crust */ sym_ring(b,-0.12,0.2,0.1,5); sym_ring(b,0.18,0.0,0.1,5); sym_ring(b,-0.02,-0.4,0.09,5); } static void build_hammer(SymB *b){ /* claw hammer */ const double handle[]={-0.08,-1.0, 0.08,-1.0, 0.10,0.45, -0.06,0.45}; sym_poly(b,handle,4,1); const double head[]={-0.45,0.45, 0.5,0.45, 0.5,0.72, 0.18,0.72, 0.10,0.95, -0.10,0.95, -0.2,0.72, -0.45,0.72}; sym_poly(b,head,8,1); sym_line(b,-0.45,0.58,-0.62,0.5); /* claw tip */ } static void build_wrench(SymB *b){ /* open/box wrench */ const double handle[]={-0.10,-0.7, 0.10,-0.7, 0.12,0.45, -0.12,0.45}; sym_poly(b,handle,4,1); sym_arc(b,0,0.62,0.28,sym_deg(-40),sym_deg(220),12); /* open jaw (C) */ sym_ring(b,0,-0.78,0.22,10); sym_ring(b,0,-0.78,0.12,6); /* box end */ } static void build_compass_dir(SymB *b){ /* directional compass */ sym_ring(b,0,0,1.0,22); sym_ring(b,0,0,0.85,18); const double star[]={0,0.8, 0.12,0.12, 0.8,0, 0.12,-0.12, 0,-0.8, -0.12,-0.12, -0.8,0, -0.12,0.12}; sym_poly(b,star,8,1); /* compass rose */ sym_line(b,0,0.8,0,-0.8); sym_line(b,-0.8,0,0.8,0); } static void build_compass_draft(SymB *b){ /* drafting compass (dividers) */ sym_line(b,0,0.95,-0.45,-0.95); sym_line(b,0,0.95,0.45,-0.95); /* two legs */ sym_ring(b,0,0.95,0.1,6); /* hinge */ sym_line(b,0.45,-0.95,0.45,-0.7); /* pencil stub */ sym_line(b,-0.45,-0.95,-0.5,-1.05); /* needle point */ } static void build_square(SymB *b){ /* drafting set-square (45 deg) */ const double tri[]={-0.85,-0.7, 0.85,-0.7, -0.85,0.85}; sym_poly(b,tri,3,1); const double tri2[]={-0.6,-0.55, 0.45,-0.55, -0.6,0.4}; /* inner cut-out */ sym_poly(b,tri2,3,1); } static void build_atari(SymB *b){ /* stylized 'fuji' mark (3 prongs) */ sym_line(b,-0.7,-0.7,0.7,-0.7); /* base bar */ sym_line(b,-0.08,-0.7,-0.08,0.8); sym_line(b,0.08,-0.7,0.08,0.8); /* centre prong */ sym_arc(b,-0.95,-0.7,0.85,sym_deg(0),sym_deg(80),8); /* left prong curves out */ sym_arc(b, 0.95,-0.7,0.85,sym_deg(100),sym_deg(180),8);/* right prong curves out */ } static void build_joystick(SymB *b){ /* retro joystick */ const double base[]={-0.7,-0.8, 0.7,-0.8, 0.55,-0.2, -0.55,-0.2}; sym_poly(b,base,4,1); sym_line(b,0,-0.2,0,0.55); /* stick */ sym_ring(b,0,0.62,0.22,10); /* ball top */ sym_ring(b,-0.35,-0.5,0.1,6); /* button */ } static void build_debian(SymB *b){ /* stylized swirl */ int seg=22, prev=-1; for(int i=0;i<=seg;i++){ double t=(double)i/seg, a=t*TAU*0.95 + sym_deg(40); double r=0.25+0.7*t; int idx=sym_pt(b,r*cos(a),r*sin(a)); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } sym_arc(b,0,0,0.95,sym_deg(60),sym_deg(330),14); /* open ring around the swirl */ } static void build_glasses(SymB *b){ /* spectacles */ sym_ring(b,-0.5,0,0.36,12); sym_ring(b,0.5,0,0.36,12); sym_arc(b,0,0.0,0.18,sym_deg(20),sym_deg(160),5); /* bridge */ sym_line(b,-0.86,0.1,-1.0,0.25); sym_line(b,0.86,0.1,1.0,0.25); /* temples */ } static void build_fork(SymB *b){ const double handle[]={-0.08,-1.0, 0.08,-1.0, 0.10,0.3, -0.10,0.3}; sym_poly(b,handle,4,1); for(int i=0;i<4;i++){ double x=-0.21+0.14*i; sym_line(b,x,0.3,x,0.92); } /* tines */ sym_line(b,-0.21,0.3,0.21,0.3); } static void build_spoon(SymB *b){ const double handle[]={-0.08,-1.0, 0.08,-1.0, 0.10,0.25, -0.10,0.25}; sym_poly(b,handle,4,1); sym_ellipse(b,0,0.55,0.26,0.42,0,12); /* bowl */ } static void build_pen(SymB *b){ /* fountain pen */ const double barrel[]={-0.12,-1.0, 0.12,-1.0, 0.12,0.55, -0.12,0.55}; sym_poly(b,barrel,4,1); const double nib[]={-0.12,0.55, 0.12,0.55, 0,1.0}; /* nib */ sym_poly(b,nib,3,1); sym_line(b,0,0.62,0,0.95); /* nib slit */ sym_line(b,0.12,0.1,0.20,0.1); sym_line(b,0.20,0.1,0.20,0.4); /* clip */ } /* ---- classical element symbols (alchemical triangles) -------------- */ static void build_fire(SymB *b){ const double t[]={-0.8,-0.6,0.8,-0.6,0,0.85}; sym_poly(b,t,3,1); } static void build_water(SymB *b){ const double t[]={-0.8,0.6,0.8,0.6,0,-0.85}; sym_poly(b,t,3,1); } static void build_air(SymB *b){ const double t[]={-0.8,-0.6,0.8,-0.6,0,0.85}; sym_poly(b,t,3,1); sym_line(b,-0.33,0.3,0.33,0.3); } static void build_earth(SymB *b){ const double t[]={-0.8,0.6,0.8,0.6,0,-0.85}; sym_poly(b,t,3,1); sym_line(b,-0.33,-0.3,0.33,-0.3); } static void build_philstone(SymB *b){ /* squared circle */ sym_ring(b,0,0,1.0,22); const double sq[]={-0.72,-0.72,0.72,-0.72,0.72,0.72,-0.72,0.72}; sym_poly(b,sq,4,1); const double tri[]={0,0.66,-0.6,-0.5,0.6,-0.5}; sym_poly(b,tri,3,1); sym_ring(b,0,-0.16,0.32,12); } static void build_starship(SymB *b){ /* generic starship (saucer + nacelles) */ sym_ellipse(b,0,0.5,0.85,0.30,0,18); /* saucer */ sym_line(b,-0.1,0.3,-0.05,-0.1); sym_line(b,0.1,0.3,0.05,-0.1); /* neck */ const double hull[]={-0.28,-0.1, 0.28,-0.1, 0.22,-0.45, -0.22,-0.45}; sym_poly(b,hull,4,1); /* engineering hull */ /* two nacelles on struts */ sym_line(b,-0.22,-0.25,-0.62,-0.55); sym_line(b,0.22,-0.25,0.62,-0.55); const double nl[]={-0.85,-0.5,-0.4,-0.5,-0.4,-0.66,-0.85,-0.66}; const double nr[]={ 0.4,-0.5, 0.85,-0.5, 0.85,-0.66, 0.4,-0.66}; sym_poly(b,nl,4,1); sym_poly(b,nr,4,1); } /* ---- still more iconography (flat 2-D + extruded 3-D) -------------- */ static void build_tophat(SymB *b) { const double crown[]={-0.4,-0.2, 0.4,-0.2, 0.44,0.9, -0.44,0.9}; sym_poly(b,crown,4,1); sym_ellipse(b,0,-0.2,0.86,0.18,0,18); /* brim */ sym_line(b,-0.4,0.02,0.4,0.02); sym_line(b,-0.42,0.16,0.42,0.16); /* band */ } static void build_cap(SymB *b) { /* baseball cap, side view */ sym_arc(b,-0.05,-0.1,0.72,sym_deg(5),sym_deg(175),12); /* crown */ sym_line(b,-0.77,-0.1,0.66,-0.1); const double bill[]={0.65,-0.06, 1.22,-0.2, 1.2,-0.32, 0.6,-0.18}; sym_poly(b,bill,4,1); /* bill */ sym_ring(b,-0.05,0.6,0.05,4); /* top button */ } static void build_nose(SymB *b) { /* schnozz, profile facing right */ const double p[]={ -0.35,0.95, 0.05,0.45, 0.38,0.0, 0.5,-0.4, 0.32,-0.6, -0.02,-0.55, -0.18,-0.38 }; sym_poly(b,p,7,0); sym_arc(b,0.08,-0.46,0.12,sym_deg(170),sym_deg(360),5); /* nostril */ } static void build_heart(SymB *b) { /* parametric heart curve */ int first=-1, prev=-1; for (int i=0;i<=24;i++){ double t=TAU*i/24; double x=16*pow(sin(t),3)/17.0; double y=(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t))/15.0; int idx=sym_pt(b,x,y); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); } static void build_keyboard(SymB *b) { const double body[]={-1.0,-0.45, 1.0,-0.45, 1.0,0.45, -1.0,0.45}; sym_poly(b,body,4,1); for (int r=1;r<4;r++){ double y=-0.45+r*0.225; sym_line(b,-1.0,y,1.0,y); } for (int c=1;c<8;c++){ double x=-1.0+c*0.25; sym_line(b,x,-0.225,x,0.45); } sym_line(b,-0.5,-0.45,-0.5,-0.225); sym_line(b,0.5,-0.45,0.5,-0.225); /* spacebar */ } static void build_floppy(SymB *b) { /* 3.5" floppy disk */ const double body[]={-0.85,-0.9, 0.85,-0.9, 0.85,0.72, 0.55,0.9, -0.85,0.9}; sym_poly(b,body,5,1); const double sh[]={-0.2,0.42, 0.5,0.42, 0.5,0.9, -0.2,0.9}; sym_poly(b,sh,4,1); sym_line(b,0.18,0.42,0.18,0.9); /* metal shutter */ const double lab[]={-0.6,-0.85, 0.6,-0.85, 0.6,-0.05, -0.6,-0.05}; sym_poly(b,lab,4,1); /* label */ } static void build_bell(SymB *b) { const double p[][2]={ {0,1.0},{0.12,0.92},{0.18,0.7},{0.30,0.35},{0.45,-0.1}, {0.6,-0.45},{0.63,-0.6} }; sym_profile(b,p,7); sym_ring(b,0,-0.78,0.12,6); /* clapper */ sym_ring(b,0,1.05,0.08,5); /* top loop */ } static void build_book(SymB *b) { /* open book */ const double left[]={-1.0,0.2, -0.05,0.35, -0.05,-0.45, -1.0,-0.3}; const double right[]={1.0,0.2, 0.05,0.35, 0.05,-0.45, 1.0,-0.3}; sym_poly(b,left,4,1); sym_poly(b,right,4,1); sym_line(b,-0.9,0.05,-0.15,0.18); sym_line(b,-0.9,-0.1,-0.15,0.03); sym_line(b, 0.9,0.05, 0.15,0.18); sym_line(b, 0.9,-0.1, 0.15,0.03); } static void build_commodore(SymB *b) { /* stylized 'C' mark */ sym_arc(b,0,0,1.0,sym_deg(38),sym_deg(322),12); sym_arc(b,0,0,0.55,sym_deg(44),sym_deg(316),10); sym_line(b, cos(sym_deg(38)), sin(sym_deg(38)), 0.55*cos(sym_deg(44)), 0.55*sin(sym_deg(44))); sym_line(b, cos(sym_deg(322)),sin(sym_deg(322)),0.55*cos(sym_deg(316)),0.55*sin(sym_deg(316))); } static void build_ti(SymB *b) { /* stylized framed 'TI' */ const double fr[]={-0.9,-0.7, 0.9,-0.7, 0.9,0.7, -0.9,0.7}; sym_poly(b,fr,4,1); sym_line(b,-0.72,0.42,-0.1,0.42); sym_line(b,-0.41,0.42,-0.41,-0.42); /* T */ sym_line(b, 0.1,0.42, 0.62,0.42); sym_line(b,0.36,0.42,0.36,-0.42); sym_line(b, 0.1,-0.42,0.62,-0.42); /* I */ } /* ---- 2-D silhouettes for the lathe solids of revolution ------------ */ static void build_pawn2d(SymB *b){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86}, {0.15,-0.76},{0.12,-0.45},{0.22,-0.36},{0.11,-0.28},{0.19,-0.12},{0.25,0.0},{0.22,0.13}, {0.10,0.27},{0,0.33}}; sym_profile(b,p,12); } static void build_rook2d(SymB *b){ const double p[][2]={{0,-1.0},{0.40,-1.0},{0.34,-0.84}, {0.19,-0.68},{0.19,0.35},{0.33,0.5},{0.33,0.82},{0,0.82}}; sym_profile(b,p,8); for(int i=-1;i<=1;i++){ double x=i*0.22; sym_line(b,x-0.07,0.82,x-0.07,0.95); sym_line(b,x+0.07,0.82,x+0.07,0.95); sym_line(b,x-0.07,0.95,x+0.07,0.95);} } static void build_bishop2d(SymB *b){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86}, {0.16,-0.74},{0.13,-0.4},{0.24,-0.3},{0.12,-0.22},{0.20,0.0},{0.16,0.3},{0.09,0.55}, {0.06,0.66},{0,0.72},{0.05,0.78},{0,0.86}}; sym_profile(b,p,14); sym_line(b,-0.1,0.35,0.12,0.5); } static void build_king2d(SymB *b){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85}, {0.17,-0.72},{0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.20,0.05},{0.27,0.28},{0.18,0.5}, {0.16,0.6},{0,0.6}}; sym_profile(b,p,12); sym_line(b,0,0.6,0,1.0); sym_line(b,-0.13,0.84,0.13,0.84); } static void build_queen2d(SymB *b){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85}, {0.17,-0.72},{0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.22,0.1},{0.29,0.38},{0.30,0.52}}; sym_profile(b,p,10); for(int i=-2;i<=2;i++){ double x=i*0.13; sym_line(b,x,0.52,x*1.2,0.74); sym_ring(b,x*1.2,0.76,0.04,4);} } static void build_candlestick2d(SymB *b){ const double p[][2]={{0,-1.0},{0.42,-1.0},{0.36,-0.85}, {0.12,-0.7},{0.10,-0.2},{0.20,-0.05},{0.13,0.02},{0.11,0.08},{0.11,0.62},{0,0.62}}; sym_profile(b,p,10); sym_line(b,0,0.62,0.06,0.74); sym_line(b,0.06,0.74,0,0.92); sym_line(b,0,0.92,-0.06,0.74); sym_line(b,-0.06,0.74,0,0.62); } static void build_coffeecup2d(SymB *b){ const double p[][2]={{0,-0.6},{0.46,-0.6},{0.5,-0.45}, {0.46,0.5},{0.40,0.5}}; sym_profile(b,p,5); sym_arc(b,0.55,0.05,0.32,sym_deg(-75),sym_deg(75),6); } static void build_lantern2d(SymB *b){ const double p[][2]={{0,-1.0},{0.35,-0.95},{0.40,-0.75}, {0.34,-0.6},{0.36,0.45},{0.30,0.6},{0.34,0.72},{0.18,0.82},{0.12,0.9},{0,0.9}}; sym_profile(b,p,10); sym_ring(b,0,1.0,0.12,8); } static void build_hurricane2d(SymB *b){ const double p[][2]={{0,-1.0},{0.34,-0.95},{0.40,-0.78}, {0.30,-0.6},{0.14,-0.5},{0.30,-0.4},{0.30,-0.28},{0.20,-0.18},{0.26,0.55},{0.34,0.85}, {0.30,1.0}}; sym_profile(b,p,11); } static void build_quartz2d(SymB *b){ const double p[][2]={{0,-1.05},{0.30,-0.7},{0.45,-0.5}, {0.45,0.5},{0.30,0.7},{0,1.05}}; sym_profile(b,p,6); sym_line(b,-0.45,-0.5,-0.45,0.5); sym_line(b,0.45,-0.5,0.45,0.5); } static void build_pineapple2d(SymB *b){ const double p[][2]={{0,-1.0},{0.25,-0.85},{0.42,-0.5}, {0.45,0.1},{0.34,0.4},{0.18,0.55}}; sym_profile(b,p,6); for(int i=-2;i<=2;i++){ double x=i*0.1; sym_line(b,x,0.55,x*1.6,1.0); } for(int i=0;i<3;i++){ double y=-0.4+i*0.3; sym_line(b,-0.4,y,0.4,y+0.18); sym_line(b,-0.4,y+0.18,0.4,y);} } static void build_torus2d(SymB *b){ sym_ring(b,0,0,1.0,22); sym_ring(b,0,0,0.46,16); } /* ---- 2-D versions of the 3-D-only googie objects ------------------- */ static void build_saturn2d(SymB *b){ sym_ring(b,0,0,0.58,16); sym_ellipse(b,0,0,1.05,0.26,sym_deg(-12),20); } static void build_molecule2d(SymB *b){ sym_ring(b,0,0,0.22,8); const double a[3]={90,210,330}; for(int k=0;k<3;k++){ double th=sym_deg(a[k]); sym_line(b,0.22*cos(th),0.22*sin(th),0.7*cos(th),0.7*sin(th)); sym_ring(b,0.85*cos(th),0.85*sin(th),0.18,7);} } static void build_diabolo2d(SymB *b){ const double p[]={-0.5,-1.0, 0.5,-1.0, -0.5,1.0, 0.5,1.0}; sym_poly(b,p,4,1); sym_line(b,-0.5,-1.0,-0.18,0.0); sym_line(b,0.5,1.0,0.18,0.0); } static void build_sputnik2d(SymB *b){ int c=sym_pt(b,0,0); int n=10; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx; } /* mustard */ } static void build_hamburger(SymB *b){ sym_arc(b,0,0.12,0.9,sym_deg(0),sym_deg(180),12); sym_line(b,-0.9,0.12,0.9,0.12); /* top bun */ sym_ring(b,-0.3,0.45,0.04,3); sym_ring(b,0.1,0.55,0.04,3); sym_ring(b,0.42,0.4,0.04,3); /* seeds */ int prev=-1; for(int i=0;i<=8;i++){ double x=-0.9+i*1.8/8, y=-0.02+0.06*((i%2)?1:-1); int idx=sym_pt(b,x,y); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } /* lettuce */ const double patty[]={-0.85,-0.07, 0.85,-0.07, 0.85,-0.27, -0.85,-0.27}; sym_poly(b,patty,4,1); sym_arc(b,0,-0.3,0.85,sym_deg(180),sym_deg(360),10); sym_line(b,-0.85,-0.3,0.85,-0.3); /* bottom bun */ } static void build_unicycle(SymB *b){ sym_ring(b,0,-0.4,0.5,16); sym_ring(b,0,-0.4,0.08,5); for(int i=0;i<8;i++){ double a=TAU*i/8; sym_line(b,0.08*cos(a),-0.4+0.08*sin(a),0.5*cos(a),-0.4+0.5*sin(a)); } sym_line(b,0,-0.32,0,0.6); sym_line(b,-0.25,0.62,0.25,0.62); /* post + seat */ sym_line(b,0,-0.4,0.26,-0.4); sym_line(b,0,-0.4,-0.26,-0.4); /* cranks */ sym_line(b,0.26,-0.4,0.34,-0.46); sym_line(b,-0.26,-0.4,-0.34,-0.34); } static void build_microscope(SymB *b){ const double base[]={-0.5,-1.0, 0.5,-1.0, 0.4,-0.78, -0.4,-0.78}; sym_poly(b,base,4,1); sym_line(b,-0.22,-0.78,-0.22,0.25); /* arm pillar */ const double tube[]={-0.06,0.25, 0.22,0.3, 0.34,0.85, 0.06,0.8}; sym_poly(b,tube,4,1); sym_line(b,0.2,0.85,0.4,0.95); /* eyepiece */ sym_line(b,-0.4,-0.12,0.35,-0.12); sym_line(b,-0.4,-0.24,0.35,-0.24); /* stage */ sym_line(b,-0.06,0.25,-0.02,0.0); sym_line(b,0.1,0.27,0.06,0.0); /* objective */ } static void build_telescope(SymB *b){ const double tube[]={-0.78,-0.18, 0.72,0.55, 0.62,0.72, -0.88,-0.0}; sym_poly(b,tube,4,1); sym_line(b,-0.78,-0.18,-0.95,-0.28); /* eyepiece */ sym_line(b,-0.08,0.18,-0.32,-1.0); sym_line(b,-0.08,0.18,0.3,-1.0); sym_line(b,-0.08,0.18,0.0,-1.0); /* tripod */ sym_line(b,-0.18,0.1,0.05,0.25); /* mount */ } static void build_magnifier(SymB *b){ sym_ring(b,0.18,0.28,0.56,18); sym_ring(b,0.18,0.28,0.46,15); /* lens */ sym_line(b,-0.18,-0.18,-0.72,-0.72); sym_line(b,-0.06,-0.3,-0.6,-0.84); sym_line(b,-0.72,-0.72,-0.6,-0.84); /* handle */ } static void build_thermometer(SymB *b){ sym_ring(b,0,-0.8,0.22,12); /* bulb */ const double tube[]={-0.1,-0.72, 0.1,-0.72, 0.1,0.82, -0.1,0.82}; sym_poly(b,tube,4,1); sym_arc(b,0,0.82,0.1,sym_deg(0),sym_deg(180),4); sym_line(b,0,-0.8,0,0.3); /* mercury */ for(int i=0;i<6;i++){ double y=-0.5+i*0.22; sym_line(b,0.1,y,0.22,y); } } static void build_calculator(SymB *b){ const double body[]={-0.6,-0.95, 0.6,-0.95, 0.6,0.95, -0.6,0.95}; const double scr[]={-0.45,0.45, 0.45,0.45, 0.45,0.8, -0.45,0.8}; sym_poly(b,body,4,1); sym_poly(b,scr,4,1); for(int i=1;i<4;i++){ double x=-0.6+i*0.3; sym_line(b,x,-0.9,x,0.3); } for(int i=1;i<5;i++){ double y=-0.9+i*0.3; sym_line(b,-0.6,y,0.6,y); } } static void build_stapler(SymB *b){ const double base[]={-0.9,-0.4, 0.9,-0.4, 0.9,-0.12, -0.9,-0.12}; const double top[]={-0.85,0.0, 0.7,0.06, 0.92,0.3, 0.85,0.42, -0.8,0.36, -0.9,0.12}; sym_poly(b,base,4,1); sym_poly(b,top,6,1); sym_ring(b,-0.78,0.06,0.08,5); /* hinge */ } static void build_paperclip(SymB *b){ /* nested gem-clip loops */ sym_arc(b,-0.18,0.55,0.32,sym_deg(0),sym_deg(180),6); sym_line(b,-0.5,0.55,-0.5,-0.5); sym_arc(b,-0.3,-0.5,0.2,sym_deg(180),sym_deg(360),5); sym_line(b,-0.1,-0.5,-0.1,0.35); sym_arc(b,0.06,0.35,0.16,sym_deg(0),sym_deg(180),5); sym_line(b,0.22,0.35,0.22,-0.35); sym_line(b,0.14,0.55,0.14,0.78); } static void build_palm(SymB *b){ sym_line(b,-0.1,-1.0,0.0,0.42); sym_line(b,0.12,-1.0,0.16,0.42); sym_line(b,-0.1,-1.0,0.12,-1.0); for(int i=0;i<4;i++){ double y=-0.8+i*0.3; sym_line(b,-0.07+i*0.04,y,0.12+i*0.03,y); } double cx=0.08, cy=0.42; for(int k=0;k<6;k++){ double a=sym_deg(20+k*28); double mx=cx+0.3*cos(a), my=cy+0.34*sin(a); sym_line(b,cx,cy,mx,my); sym_line(b,mx,my,cx+0.55*cos(a),cy+0.42*sin(a)-0.16); } } static void build_saguaro(SymB *b){ const double trunk[]={-0.2,-1.0, 0.2,-1.0, 0.2,0.7, 0.14,0.86, -0.14,0.86, -0.2,0.7}; sym_poly(b,trunk,6,1); sym_line(b,-0.2,-0.1,-0.5,-0.1); sym_line(b,-0.5,-0.1,-0.5,0.45); /* left arm */ sym_arc(b,-0.4,-0.1,0.1,sym_deg(180),sym_deg(270),3); sym_arc(b,-0.4,0.45,0.1,sym_deg(90),sym_deg(180),3); sym_line(b,0.2,0.15,0.48,0.15); sym_line(b,0.48,0.15,0.48,0.6); /* right arm */ } static void build_sushi(SymB *b){ sym_ring(b,0,0,0.95,18); sym_ring(b,0,0,0.74,16); /* nori + rice */ sym_ring(b,0,0,0.32,8); sym_ring(b,0,0,0.14,5); /* filling */ } static void build_bottleopener(SymB *b){ const double h[]={-0.9,-0.18, 0.5,-0.18, 0.5,0.18, -0.9,0.18}; sym_poly(b,h,4,1); sym_arc(b,0.6,0,0.4,sym_deg(-90),sym_deg(90),8); sym_line(b,0.5,0.18,0.6,0.4); sym_line(b,0.5,-0.18,0.6,-0.4); sym_arc(b,0.72,0,0.16,sym_deg(95),sym_deg(265),4); /* tooth notch */ sym_ring(b,-0.7,0,0.1,6); /* hole */ } static void build_lightning(SymB *b){ const double p[]={ 0.28,1.0, -0.36,0.12, 0.0,0.12, -0.3,-1.0, 0.42,-0.02, 0.08,-0.02 }; sym_poly(b,p,6,1); } /* ---- objects with a genuine volumetric 3-D form (gen) + 2-D -------- */ static void build_monolith2d(SymB *b){ const double p[]={-0.22,-1.0,0.22,-1.0,0.22,1.0,-0.22,1.0}; sym_poly(b,p,4,1); } static void gen_monolith3d(void){ Solid*s=new_solid(3); double X=0.22,Y=1.0,Z=0.06; for(int i=0;i<8;i++) PV(s,(i&1?X:-X),(i&2?Y:-Y),(i&4?Z:-Z),0,0,0); int e[12][2]={{0,1},{2,3},{4,5},{6,7},{0,2},{1,3},{4,6},{5,7},{0,4},{1,5},{2,6},{3,7}}; for(int k=0;k<12;k++)AE(s,e[k][0],e[k][1]); center_normalize(s); } static void build_hulahoop2d(SymB *b){ sym_ellipse(b,0,0,1.0,0.32,0,24); sym_ellipse(b,0,0,0.9,0.27,0,22); } static void gen_hulahoop3d(void){ Solid*s=new_solid(3); int nu=20,nv=6; double R=0.85,r=0.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;rg<2;rg++){ double rad=1.0+rg*0.16; int first=-1,prev=-1,seg2=22; for(int i=0;inv; 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); } static void build_gyromech2d(SymB *b){ sym_ring(b,0,0,1.0,18); sym_ring(b,0,0,0.5,12); sym_line(b,-1.0,0,1.0,0); for(int k=0;k<6;k++){ double a=TAU*k/6; sym_line(b,0,0,0.5*cos(a),0.5*sin(a)); } sym_ring(b,0,0,0.08,5); } static void gen_gyromech3d(void){ Solid*s=new_solid(3); int seg=16; int r0=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,0,0,-0.78,0,0,0); PV(s,0,0,0.78,0,0,0); AE(s,ax,ax+1); int g=s->nv; for(int i=0;i<20;i++){ double a=TAU*i/20; PV(s,0.78*cos(a),0,0.78*sin(a),0,0,0); } for(int i=0;i<20;i++)AE(s,g+i,g+(i+1)%20); center_normalize(s); } static const double GRAPE_POS[6][2]={{-0.42,0.35},{0,0.4},{0.42,0.35},{-0.24,0.0},{0.24,0.0},{0,-0.34}}; static void build_grapes2d(SymB *b){ for(int k=0;k<6;k++) sym_ring(b,GRAPE_POS[k][0],GRAPE_POS[k][1]-0.1,0.26,9); sym_line(b,0,0.6,0.0,0.78); sym_line(b,0,0.78,0.18,0.92); } static void gen_grapes3d(void){ Solid*s=new_solid(3); int seg=6; double r=0.26; for(int k=0;k<6;k++){ double cx=GRAPE_POS[k][0],cy=GRAPE_POS[k][1]-0.1; int a0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.5,0,0,0,0); PV(s,0,0.8,0,0,0,0); AE(s,st,st+1); center_normalize(s); } static void build_corkscrew2d(SymB *b){ sym_line(b,-0.4,0.85,0.4,0.85); sym_line(b,0,0.85,0,0.5); int prev=sym_pt(b,0,0.5); for(int i=1;i<=10;i++){ double y=0.5-i*0.13, x=(i%2?0.18:-0.18); int idx=sym_pt(b,x,y); sym_edge(b,prev,idx); prev=idx; } } static void gen_corkscrew3d(void){ Solid*s=new_solid(3); int n=40; double turns=4; int prev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, a=t*TAU*turns, y=0.55-t*1.3; int idx=s->nv; PV(s,0.2*cos(a),y,0.2*sin(a),0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } int sh=s->nv; PV(s,0,0.55,0,0,0,0); PV(s,0,0.88,0,0,0,0); AE(s,sh,sh+1); int h=s->nv; PV(s,-0.45,0.88,0,0,0,0); PV(s,0.45,0.88,0,0,0,0); AE(s,h,h+1); center_normalize(s); } static void build_battery2d(SymB *b){ const double body[]={-0.4,-1.0, 0.4,-1.0, 0.4,0.85, -0.4,0.85}; const double nub[]={-0.15,0.85, 0.15,0.85, 0.15,1.0, -0.15,1.0}; sym_poly(b,body,4,1); sym_poly(b,nub,4,1); sym_line(b,-0.4,0.45,0.4,0.45); sym_line(b,0.02,0.62,0.22,0.62); sym_line(b,0.12,0.52,0.12,0.72); /* + */ sym_line(b,-0.22,-0.7,-0.02,-0.7); /* - */ } static void gen_battery3d(void){ const double p[][2]={{0,-1.0},{0.4,-1.0},{0.4,0.85}, {0.15,0.85},{0.15,1.0},{0,1.0}}; gen_lathe(p,6,12); } /* ================================================================== */ /* A very large batch of objects (static), plus animated dynamic ones. */ /* ================================================================== */ static void build_gobstopper(SymB *b){ for(int i=0;i<4;i++) sym_ring(b,0,0,1.0-i*0.24,16); } static void build_pants(SymB *b){ const double p[]={-0.6,0.95, 0.6,0.95, 0.6,0.2, 0.18,0.2, 0.14,-1.0, -0.14,-1.0, -0.14,-0.1, -0.14,-1.0,/*dup*/ -0.6,0.2 }; const double q[]={-0.6,0.95,0.6,0.95,0.6,0.2,0.18,0.2,0.16,-1.0,0.0,-0.1,-0.16,-1.0,-0.6,0.2}; (void)p; sym_poly(b,q,8,1); sym_line(b,0,-0.1,0,0.95); } static void build_shirt(SymB *b){ const double p[]={-0.35,0.85,-0.7,0.55,-0.9,0.7,-0.55,1.0, 0.55,1.0,0.9,0.7,0.7,0.55,0.35,0.85,0.4,-0.95,-0.4,-0.95}; sym_poly(b,p,10,1); sym_arc(b,0,0.95,0.2,sym_deg(200),sym_deg(340),5); } static void build_dice(SymB *b){ const double f[]={-0.85,-0.85,0.85,-0.85,0.85,0.85,-0.85,0.85}; sym_poly(b,f,4,1); double pp[5][2]={{-0.45,0.45},{0.45,0.45},{0,0},{-0.45,-0.45},{0.45,-0.45}}; for(int i=0;i<5;i++) sym_ring(b,pp[i][0],pp[i][1],0.13,6); } static void card_corner(SymB *b, int which){ double ox=-0.5, oy=0.66, w=0.22, h=0.34; if(which==0){ sym_line(b,ox,oy-h,ox,oy); sym_line(b,ox,oy-h*0.5,ox+w,oy); sym_line(b,ox,oy-h*0.5,ox+w,oy-h); } /*K*/ else if(which==1){ sym_ring(b,ox+w*0.5,oy-h*0.5,h*0.45,8); sym_line(b,ox+w*0.6,oy-h*0.7,ox+w,oy-h); } /*Q*/ else if(which==2){ sym_line(b,ox+w,oy,ox+w,oy-h*0.7); sym_arc(b,ox+w*0.5,oy-h*0.7,w*0.5,sym_deg(0),sym_deg(200),4); } /*J*/ else { sym_line(b,ox,oy-h,ox+w*0.5,oy); sym_line(b,ox+w*0.5,oy,ox+w,oy-h); sym_line(b,ox+w*0.2,oy-h*0.5,ox+w*0.8,oy-h*0.5); } /*A*/ } static void build_pip_heart(SymB *b,double cx,double cy,double sc){ int first=-1,prev=-1; for(int i=0;i<=14;i++){ double t=TAU*i/14, x=cx+sc*16*pow(sin(t),3)/17.0, y=cy+sc*(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t))/15.0; int idx=sym_pt(b,x,y); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); } static void card_base(SymB *b,int w){ const double fr[]={-0.62,-0.92,0.62,-0.92,0.62,0.92,-0.62,0.92}; sym_poly(b,fr,4,1); card_corner(b,w); build_pip_heart(b,0,-0.05,0.55); } static void build_card_k(SymB *b){ card_base(b,0); } static void build_card_q(SymB *b){ card_base(b,1); } static void build_card_j(SymB *b){ card_base(b,2); } static void build_card_a(SymB *b){ card_base(b,3); } static void build_roulette(SymB *b){ sym_ring(b,0,0,1.0,24); sym_ring(b,0,0,0.78,20); sym_ring(b,0,0,0.35,12); for(int i=0;i<16;i++){ double a=TAU*i/16; sym_line(b,0.35*cos(a),0.35*sin(a),0.78*cos(a),0.78*sin(a)); } sym_ring(b,0,0,0.1,6); } static void build_slot(SymB *b){ const double box[]={-0.7,-1.0,0.7,-1.0,0.7,0.7,-0.7,0.7}; sym_poly(b,box,4,1); const double top[]={-0.7,0.7,-0.55,0.95,0.55,0.95,0.7,0.7}; sym_poly(b,top,4,0); for(int i=0;i<3;i++){ double x=-0.4+i*0.4; const double r[]={x-0.16,-0.1,x+0.16,-0.1,x+0.16,0.45,x-0.16,0.45}; sym_poly(b,r,4,1); } sym_line(b,0.7,0.3,0.95,0.3); sym_ring(b,1.0,0.3,0.1,6); /*lever*/ const double pay[]={-0.45,-0.85,0.45,-0.85,0.45,-0.45,-0.45,-0.45}; sym_poly(b,pay,4,1); } static void build_hydrant(SymB *b){ const double body[]={-0.32,-1.0,0.32,-1.0,0.34,0.45,-0.34,0.45}; sym_poly(b,body,4,1); sym_arc(b,0,0.45,0.34,sym_deg(0),sym_deg(180),8); sym_ring(b,0,0.62,0.16,8);/*cap*/ sym_line(b,-0.34,0.0,-0.55,0.0); sym_ring(b,-0.55,0,0.12,6); sym_line(b,0.34,0.0,0.55,0.0); sym_ring(b,0.55,0,0.12,6); sym_line(b,-0.4,-1.0,0.4,-1.0); sym_line(b,-0.4,-0.85,0.4,-0.85); } static void build_brain(SymB *b){ int seg=20,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); sym_line(b,0,0.7,0,-0.7); for(int k=0;k<4;k++){ double y=0.45-k*0.3; sym_arc(b,-0.4,y,0.2,sym_deg(-60),sym_deg(180),3); sym_arc(b,0.4,y,0.2,sym_deg(0),sym_deg(240),3);} } static void build_thimble(SymB *b){ const double p[][2]={{0.42,-1.0},{0.42,0.4},{0.34,0.75},{0,0.92}}; int n=4,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx;} for(int i=n-1;i>=0;i--){ if(p[i][0]==0)continue; int idx=sym_pt(b,-p[i][0],p[i][1]); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); for(int r=0;r<4;r++){ double y=-0.7+r*0.35; sym_line(b,-0.4,y,0.4,y);} sym_line(b,-0.42,-1.0,0.42,-1.0); } static void build_automobile(SymB *b){ const double body[]={-1.0,-0.3,-0.8,-0.3,-0.65,0.05,-0.4,0.25, 0.35,0.25,0.6,0.05,0.85,0.0,1.0,-0.05,1.0,-0.3,0.8,-0.3}; sym_poly(b,body,10,1); sym_ring(b,-0.6,-0.35,0.22,10); sym_ring(b,0.6,-0.35,0.22,10); sym_line(b,-0.35,0.25,-0.25,-0.0); sym_line(b,0.05,0.25,0.15,0.0); } static void build_couch(SymB *b){ const double back[]={-1.0,0.5,1.0,0.5,1.0,-0.1,-1.0,-0.1}; sym_poly(b,back,4,1); const double seat[]={-1.0,-0.1,1.0,-0.1,0.95,-0.5,-0.95,-0.5}; sym_poly(b,seat,4,1); const double la[]={-1.0,0.35,-1.2,0.35,-1.2,-0.55,-1.0,-0.5}; sym_poly(b,la,4,1); const double ra[]={1.0,0.35,1.2,0.35,1.2,-0.55,1.0,-0.5}; sym_poly(b,ra,4,1); sym_line(b,0,0.5,0,-0.1); sym_line(b,-1.2,-0.55,-1.1,-0.9); sym_line(b,1.2,-0.55,1.1,-0.9); } static void build_stopsign(SymB *b){ int first=-1,prev=-1; for(int i=0;i<8;i++){ double a=sym_deg(22.5+i*45); int idx=sym_pt(b,cos(a),sin(a)); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); for(int i=0;i<8;i++){ double a=sym_deg(22.5+i*45); sym_pt(b,0.82*cos(a),0.82*sin(a)); } /* "STOP" as a bar */ sym_line(b,-0.45,0.12,0.45,0.12); sym_line(b,-0.45,-0.12,0.45,-0.12); } static void build_officechair(SymB *b){ const double seat[]={-0.5,0.0,0.5,0.0,0.45,-0.18,-0.45,-0.18}; sym_poly(b,seat,4,1); const double back[]={-0.42,0.0,-0.38,0.85,0.38,0.85,0.42,0.0}; sym_poly(b,back,4,1); sym_line(b,0,-0.18,0,-0.55); for(int i=0;i<5;i++){ double a=TAU*i/5; sym_line(b,0,-0.55,0.55*cos(a),-0.85+0.0*0); sym_ring(b,0.55*cos(a),-0.88,0.06,4);} } static void build_crossbones(SymB *b){ sym_ring(b,0,0.3,0.5,14);/*skull*/ sym_ring(b,-0.18,0.32,0.1,5); sym_ring(b,0.18,0.32,0.1,5); sym_line(b,-0.12,0.05,0.12,0.05); sym_line(b,0,0.05,0,-0.05); sym_line(b,-0.7,-0.7,0.7,-0.3); sym_line(b,-0.7,-0.3,0.7,-0.7); /*bones X*/ for(double s=-1;s<=1;s+=2){ sym_ring(b,0.7*s,-0.5+0.2*s,0.12,5); sym_ring(b,-0.7*s,-0.5-0.2*s,0.12,5);} } static void build_goldfish(SymB *b){ sym_ellipse(b,0.0,0,0.7,0.45,0,16);/*body*/ const double tail[]={0.6,0,1.0,0.4,0.85,0,1.0,-0.4}; sym_poly(b,tail,4,0); sym_ring(b,-0.35,0.1,0.08,5); /*eye*/ sym_arc(b,0.2,0.45,0.2,sym_deg(200),sym_deg(340),4);/*fin*/ } static void build_squid(SymB *b){ sym_ellipse(b,0,0.45,0.4,0.55,0,14);/*mantle*/ sym_arc(b,0,0.95,0.4,sym_deg(0),sym_deg(180),6); sym_ring(b,-0.16,0.4,0.07,4); sym_ring(b,0.16,0.4,0.07,4); for(int i=0;i<6;i++){ double x=-0.35+i*0.14; sym_line(b,x,-0.05,x*1.3,-0.95);} sym_line(b,-0.5,-0.1,-0.85,-0.7); sym_line(b,0.5,-0.1,0.85,-0.7); } static void build_pinecone(SymB *b){ const double p[][2]={{0,-1.0},{0.3,-0.6},{0.42,-0.1},{0.34,0.4},{0,0.7}}; int n=5,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx;} for(int i=n-1;i>=0;i--){ if(p[i][0]==0)continue; int idx=sym_pt(b,-p[i][0],p[i][1]); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); for(int r=0;r<4;r++){ double y=-0.7+r*0.35; sym_line(b,-0.35,y,0,y+0.12); sym_line(b,0,y+0.12,0.35,y);} sym_line(b,0,0.7,0,0.95); } static void build_mapleleaf(SymB *b){ const double p[]={0,1.0, 0.12,0.55, 0.5,0.62, 0.36,0.36, 0.78,0.3, 0.5,0.05, 0.62,-0.2, 0.25,-0.1, 0.28,-0.55, 0.0,-0.35, -0.28,-0.55, -0.25,-0.1, -0.62,-0.2, -0.5,0.05, -0.78,0.3, -0.36,0.36, -0.5,0.62, -0.12,0.55}; sym_poly(b,p,18,1); sym_line(b,0,-0.35,0,-0.9); } static void build_anemone(SymB *b){ sym_arc(b,0,-0.2,0.5,sym_deg(180),sym_deg(360),8);/*base*/ for(int i=0;i<11;i++){ double a=sym_deg(15+i*15); double bx=0.45*cos(a)-0.0, by=-0.2+0.45*sin(a); sym_line(b,bx*0.6,by*0.6-0.0,cos(a),sin(a)*0.9+0.1);} } static void build_wagonwheel(SymB *b){ sym_ring(b,0,0,1.0,20); sym_ring(b,0,0,0.85,18); sym_ring(b,0,0,0.18,8); for(int i=0;i<10;i++){ double a=TAU*i/10; sym_line(b,0.18*cos(a),0.18*sin(a),0.85*cos(a),0.85*sin(a)); } } static void build_submarine(SymB *b){ sym_arc(b,-0.55,0,0.4,sym_deg(90),sym_deg(270),6); sym_arc(b,0.55,0,0.4,sym_deg(-90),sym_deg(90),6); sym_line(b,-0.55,0.4,0.55,0.4); sym_line(b,-0.55,-0.4,0.55,-0.4); const double tower[]={-0.18,0.4,-0.14,0.78,0.14,0.78,0.18,0.4}; sym_poly(b,tower,4,0); sym_line(b,0,0.78,0,1.0);/*periscope*/ sym_ring(b,-0.2,0,0.1,5); sym_ring(b,0.2,0,0.1,5); } static void build_sailship(SymB *b){ const double hull[]={-0.85,-0.4,0.85,-0.4,0.6,-0.75,-0.6,-0.75}; sym_poly(b,hull,4,1); sym_line(b,0,-0.4,0,0.95);/*mast*/ sym_line(b,-0.45,-0.4,-0.45,0.6); sym_line(b,0.45,-0.4,0.45,0.55); const double s1[]={0.05,0.85,0.5,0.0,0.05,0.0}; sym_poly(b,s1,3,1); const double s2[]={-0.05,0.7,-0.5,-0.1,-0.05,-0.1}; sym_poly(b,s2,3,1); sym_line(b,-0.85,-0.4,-0.6,-0.4); } static void build_anchor(SymB *b){ sym_ring(b,0,0.85,0.16,8);/*ring*/ sym_line(b,0,0.7,0,-0.7);/*shaft*/ sym_line(b,-0.35,0.45,0.35,0.45);/*stock*/ sym_arc(b,0,-0.4,0.6,sym_deg(200),sym_deg(340),8);/*arms*/ sym_line(b,-0.56,-0.55,-0.7,-0.3); sym_line(b,0.56,-0.55,0.7,-0.3); } static void build_swiss(SymB *b){ const double w[]={-0.9,-0.5,0.9,-0.7,0.85,0.5,-0.9,0.3}; sym_poly(b,w,4,1); sym_ring(b,-0.3,-0.1,0.16,7); sym_ring(b,0.3,0.05,0.2,8); sym_ring(b,0.4,-0.45,0.12,6); sym_ring(b,-0.55,0.1,0.1,5); sym_ring(b,0.0,-0.45,0.1,5); } static void build_anarchy(SymB *b){ sym_ring(b,0,0,1.0,22); /*A*/ sym_line(b,-0.55,-0.55,0,0.65); sym_line(b,0,0.65,0.6,-0.55); sym_line(b,-0.3,-0.0,0.7,-0.0); } static void build_yoyo(SymB *b){ sym_ring(b,0,0,1.0,18); sym_ring(b,0,0,0.85,16); sym_ring(b,0,0,0.12,6); sym_line(b,0,0,0.0,1.0); /*string*/ } /* ---- dedicated 3-D solids + flat 2-D companions -------------------- */ static int near_v(Solid*s,double x,double y,double z){ for(int i=0;inv;i++){ double dx=s->v[i][0]-x,dy=s->v[i][1]-y,dz=s->v[i][2]-z; if(dx*dx+dy*dy+dz*dz<1e-4) return 1;} return 0; } static void gen_buckyball(void){ Solid*s=new_solid(3); const double P=1.6180339887; double base[3][3]={{0,1,3*P},{1,2+P,2*P},{2,1+2*P,P}}; for(int t=0;t<3;t++){ double v[3]={base[t][0],base[t][1],base[t][2]}; double pr[3][3]={{v[0],v[1],v[2]},{v[1],v[2],v[0]},{v[2],v[0],v[1]}}; for(int p=0;p<3;p++) for(int sx=-1;sx<=1;sx+=2) for(int sy=-1;sy<=1;sy+=2) for(int sz=-1;sz<=1;sz+=2){ double x=sx*pr[p][0],y=sy*pr[p][1],z=sz*pr[p][2]; if(!near_v(s,x,y,z)) PV(s,x,y,z,0,0,0); } } center_normalize(s); derive_edges(s); } static void build_buckyball2d(SymB *b){ sym_ring(b,0,0,1.0,18); for(int i=0;i<6;i++){ double a=sym_deg(i*60); sym_line(b,0.5*cos(a),0.5*sin(a),0.5*cos(a+sym_deg(60)),0.5*sin(a+sym_deg(60))); } sym_ring(b,0,0,0.5,6); } static void cube_pip(Solid*s,double cx,double cy,double cz,double ux,double uy,double uz,double vx,double vy,double vz,int n){ /* n pips on a face centred at c, face axes u,v */ double off[7][2]={{0,0},{-1,1},{1,-1},{1,1},{-1,-1},{-1,0},{1,0}}; double sc=0.4; for(int k=0;k=0)sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); for(int i=0;i<6;i++){ double a=sym_deg(90+i*60); sym_line(b,0,0,cos(a),sin(a)); } sym_line(b,-0.2,0.1,0.2,0.1); } static void gen_jacks(void){ Solid*s=new_solid(3); double d[6][3]={{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}}; int c=s->nv; PV(s,0,0,0,0,0,0); for(int i=0;i<6;i++){ int t=s->nv; PV(s,d[i][0],d[i][1],d[i][2],0,0,0); AE(s,c,t); add_ball(s,d[i][0],d[i][1],d[i][2],d[i][0],d[i][1],d[i][2],0.18,5);} center_normalize(s); } static void build_jacks2d(SymB *b){ for(int i=0;i<3;i++){ double a=sym_deg(i*60); sym_line(b,-cos(a),-sin(a),cos(a),sin(a)); sym_ring(b,cos(a),sin(a),0.16,5); sym_ring(b,-cos(a),-sin(a),0.16,5);} } static void gen_dna(void){ Solid*s=new_solid(3); int n=28; double turns=3, H=2.0; int aprev=-1,bprev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, ang=t*TAU*turns, y=H*(t-0.5); int a=s->nv; PV(s,0.45*cos(ang),y,0.45*sin(ang),0,0,0); int bb=s->nv; PV(s,0.45*cos(ang+TAU*0.5),y,0.45*sin(ang+TAU*0.5),0,0,0); if(aprev>=0){AE(s,aprev,a);AE(s,bprev,bb);} if(i%2==0)AE(s,a,bb); aprev=a;bprev=bb;} center_normalize(s); } static void build_dna2d(SymB *b){ int n=20; double turns=3,H=2.0,aprev=-1,bprev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, ang=t*TAU*turns, y=H*(t-0.5)*0.5; double x=0.5*cos(ang); int a=sym_pt(b,x,y); int bb=sym_pt(b,-x,y); if(aprev>=0){sym_edge(b,(int)aprev,a);sym_edge(b,(int)bprev,bb);} if(i%2==0)sym_edge(b,a,bb); aprev=a;bprev=bb;} } /* ---- animated (dynamic) 3-D builders + static 2-D companions ------- */ static void icos_verts(double v[12][3],double r){ const double P=1.6180339887; double s=r/sqrt(1+P*P); double t[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++){v[i][0]=t[i][0]*s;v[i][1]=t[i][1]*s;v[i][2]=t[i][2]*s;} } static void gen_hoberman_dyn(Solid*s,float t){ double rad=0.55+0.42*(0.5+0.5*sin(t*1.3)); double v[12][3]; icos_verts(v,rad); for(int i=0;i<12;i++) PV(s,v[i][0],v[i][1],v[i][2],0,0,0); double el2=0; for(int j=1;j<12;j++){double dx=v[0][0]-v[j][0],dy=v[0][1]-v[j][1],dz=v[0][2]-v[j][2],d=dx*dx+dy*dy+dz*dz; if(el2==0||dnv; PV(s,0,0,0,0,0,0); for(int i=0;i<12;i++) AE(s,c,i); } static void build_hoberman2d(SymB *b){ sym_ring(b,0,0,1.0,16); sym_ring(b,0,0,0.55,12); for(int i=0;i<8;i++){double a=TAU*i/8; sym_line(b,0.55*cos(a),0.55*sin(a),cos(a),sin(a));} } static void gen_rubik_dyn(Solid*s,float t){ float ang=(float)(TAU/4)*0.5f*(1-cosf(t*1.0f)); double pts[27][3]; int n=0; for(int yi=0;yi<3;yi++)for(int zi=0;zi<3;zi++)for(int xi=0;xi<3;xi++){ double x=(xi-1)*0.6,y=(yi-1)*0.6,z=(zi-1)*0.6; if(yi==2){double c=cos(ang),s2=sin(ang),nx=x*c+z*s2,nz=-x*s2+z*c; x=nx;z=nz;} PV(s,x,y,z,0,0,0); pts[n][0]=x;pts[n][1]=y;pts[n][2]=z; n++; } for(int i=0;i<27;i++)for(int j=i+1;j<27;j++){ double dx=pts[i][0]-pts[j][0],dy=pts[i][1]-pts[j][1],dz=pts[i][2]-pts[j][2],d=dx*dx+dy*dy+dz*dz; if(d<0.62*0.62&&d>0.1) AE(s,i,j);} } static void build_rubik2d(SymB *b){ const double sq[]={-0.9,-0.9,0.9,-0.9,0.9,0.9,-0.9,0.9}; sym_poly(b,sq,4,1); for(int i=1;i<3;i++){double x=-0.9+i*0.6; sym_line(b,x,-0.9,x,0.9); sym_line(b,-0.9,x,0.9,x);} } static void tilt_spin(Solid*s,double tlt,double pa){ for(int i=0;inv;i++){ double x=s->v[i][0],y=s->v[i][1],z=s->v[i][2]; double y1=y*cos(tlt)-z*sin(tlt),z1=y*sin(tlt)+z*cos(tlt); y=y1;z=z1; double x1=x*cos(pa)+z*sin(pa),z2=-x*sin(pa)+z*cos(pa); s->v[i][0]=(float)x1;s->v[i][1]=(float)y;s->v[i][2]=(float)z2; } } static void gen_top_dyn(Solid*s,float t){ int seg=12; int bp=s->nv; PV(s,0,-1.0,0,0,0,0); int r0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.35,0,0,0,0); PV(s,0,0.7,0,0,0,0); AE(s,st,st+1); tilt_spin(s,0.18,t*4.5); } static void build_top2d(SymB *b){ const double p[]={0,-1.0,0.55,-0.1,0.35,0.35,0,0.7,-0.35,0.35,-0.55,-0.1}; sym_poly(b,p,6,1); sym_line(b,0,0.35,0,0.7); } static void gen_dreidel_dyn(Solid*s,float t){ int seg=4; int bp=s->nv; PV(s,0,-1.0,0,0,0,0); int r0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.45,0,0,0,0); PV(s,0,0.8,0,0,0,0); AE(s,st,st+1); tilt_spin(s,0.2,t*5.0); } static void build_dreidel2d(SymB *b){ const double p[]={0,-1.0,0.5,-0.2,0.5,0.45,-0.5,0.45,-0.5,-0.2}; sym_poly(b,p,5,1); sym_line(b,0,0.45,0,0.8); sym_line(b,-0.18,0.0,0.18,0.0); } static void gen_lighthouse_dyn(Solid*s,float t){ int seg=8; int r0=s->nv; for(int i=0;inv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.7,0,0,0,0); for(int i=0;inv; PV(s,0,0.55,0,0,0,0); PV(s,2.4*cos(ba-0.12),0.55,2.4*sin(ba-0.12),0,0,0); PV(s,2.4*cos(ba+0.12),0.55,2.4*sin(ba+0.12),0,0,0); AE(s,L,L+1); AE(s,L,L+2); AE(s,L+1,L+2); } static void build_lighthouse2d(SymB *b){ const double p[]={-0.32,-1.0,0.32,-1.0,0.16,0.35,-0.16,0.35}; sym_poly(b,p,4,1); const double lamp[]={-0.22,0.35,0.22,0.35,0.22,0.6,-0.22,0.6}; sym_poly(b,lamp,4,1); sym_arc(b,0,0.6,0.22,sym_deg(0),sym_deg(180),4); for(int r=0;r<3;r++){double y=-0.7+r*0.35; sym_line(b,-0.28+r*0.05,y,0.28-r*0.05,y);} sym_line(b,0.2,0.48,0.9,0.62); sym_line(b,0.2,0.48,0.9,0.34); } static void gen_eyeball_dyn(Solid*s,float t){ float ph=fmodf(t,3.2f); float op=1.0f; if(ph<0.3f) op=fabsf(ph-0.15f)/0.15f; int lats=3,seg=12; for(int la=1;la<=lats;la++){ double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25,y=sin(phi)*0.9,rr=cos(phi)*0.9; int f=s->nv; for(int i=0;inv; for(int i=0;inv; for(int i=0;inv; for(int i=0;i<6;i++){double a=TAU*i/6; PV(s,0.13*cos(a),0.13*sin(a),0.92,0,0,0);} for(int i=0;i<6;i++)AE(s,pp+i,pp+(i+1)%6); double lid=(1.0-op)*0.9; if(lid>0.02){ int u=s->nv; for(int i=0;i<=8;i++){double a=TAU*0.5*i/8; PV(s,0.9*cos(a),0.9-lid+0.0,0.0,0,0,0);} (void)u; for(int sgn=0;sgn<2;sgn++){ int f=s->nv; for(int i=0;i<=8;i++){double a=sym_deg(180)*i/8; double x=0.9*cos(a); double yy=(sgn?-(0.9-lid):(0.9-lid)); PV(s,x,yy,0.4,0,0,0);} for(int i=0;i<8;i++)AE(s,f+i,f+i+1);} } } static void build_eyeball2d(SymB *b){ sym_ring(b,0,0,1.0,18); sym_ring(b,0,0,0.4,12); sym_ring(b,0,0,0.16,7); } static void gen_skulljaw_dyn(Solid*s,float t){ float gap=0.12f+0.12f*(0.5f+0.5f*sinf(t*7.0f)); int seg=12; for(int la=1;la<=2;la++){ double phi=(TAU*0.5)*la/3-TAU*0.25,y=0.2+sin(phi)*0.6,rr=cos(phi)*0.6; int f=s->nv; for(int i=0;inv; double jy=-0.2-gap; for(int i=0;i<8;i++){ double a=TAU*i/8; PV(s,0.45*cos(a),jy,0.3*sin(a),0,0,0); } for(int i=0;i<8;i++) AE(s,j+i,j+(i+1)%8); for(int i=0;i<8;i+=2) AE(s,j+i, j+(i+4)%8); } static void build_skull2d(SymB *b){ sym_arc(b,0,0.1,0.7,sym_deg(0),sym_deg(180),12); sym_line(b,-0.7,0.1,-0.4,-0.5); sym_line(b,0.7,0.1,0.4,-0.5); sym_line(b,-0.4,-0.5,0.4,-0.5); sym_ring(b,-0.28,0.2,0.16,7); sym_ring(b,0.28,0.2,0.16,7); for(int i=-2;i<=2;i++) sym_line(b,i*0.16,-0.5,i*0.16,-0.35); } static void gen_teeth_dyn(Solid*s,float t){ float gap=0.18f+0.18f*(0.5f+0.5f*sinf(t*8.0f)); for(int row=0;row<2;row++){ double y=(row?gap:-gap); double w=0.7; const double bs[8]={0,0,0,0,0,0,0,0}; (void)bs; int f=s->nv; for(int i=0;i<6;i++){double x=-w+i*2*w/5; PV(s,x,y,0.15,0,0,0);} for(int i=0;i<6;i++){double x=-w+i*2*w/5; PV(s,x,y+(row?0.3:-0.3),0.15,0,0,0);} for(int i=0;i<6;i++){AE(s,f+i,f+6+i); if(i<5)AE(s,f+i,f+i+1);} AE(s,f,f+6); AE(s,f+5,f+11); int g=s->nv; for(int i=0;i<6;i++){double x=-w+i*2*w/5; PV(s,x,y,-0.15,0,0,0);} for(int i=0;i<6;i++)AE(s,f+i,g+i); } } static void build_teeth2d(SymB *b){ for(int row=0;row<2;row++){ double y=(row?0.18:-0.18); const double q[]={-0.7,0,0.7,0,0.7,0,-0.7,0}; (void)q; double yy=y+(row?0.35:-0.35); sym_line(b,-0.7,y,0.7,y); sym_line(b,-0.7,yy,0.7,yy); sym_line(b,-0.7,y,-0.7,yy); sym_line(b,0.7,y,0.7,yy); for(int i=1;i<6;i++){double x=-0.7+i*0.28; sym_line(b,x,y,x,yy);} } } static void gen_octopus_dyn(Solid*s,float t){ int seg=10; int f=s->nv; for(int i=0;inv; PV(s,0,0.9,0,0,0,0); for(int i=0;inv; PV(s,x,y,z,0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; (void)bx;(void)bz; } } } static void build_octopus2d(SymB *b){ sym_arc(b,0,0.3,0.55,sym_deg(0),sym_deg(180),8); sym_ring(b,-0.2,0.4,0.08,4); sym_ring(b,0.2,0.4,0.08,4); for(int i=0;i<6;i++){ double x=-0.5+i*0.2; sym_line(b,x,0.3,x*1.3,-0.9);} } static void gen_jelly_dyn(Solid*s,float t){ int seg=12; for(int la=0;la<3;la++){ double y=0.5-la*0.25, rr=0.6*cos(la*0.5); int f=s->nv; for(int i=0;inv; PV(s,0,0.75,0,0,0,0);for(int i=0;inv; PV(s,x,y,z,0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } } } static void build_jelly2d(SymB *b){ sym_arc(b,0,0.3,0.6,sym_deg(0),sym_deg(180),10); sym_line(b,-0.6,0.3,0.6,0.3); for(int i=0;i<7;i++){ double x=-0.5+i*0.166; int prev=sym_pt(b,x,0.3); for(int k=1;k<=4;k++){ int idx=sym_pt(b,x+0.08*((k%2)?1:-1),0.3-k*0.3); sym_edge(b,prev,idx); prev=idx;} } } /* register a dynamic shape (rebuilt every frame) + return nothing */ static void add_dynamic(DynFn f){ if(dyn_countdim=3; s->nv=0; s->ne=0; dyn_func[i](s,t); } } /* ================================================================== */ /* Yet another batch: media, symbols, tools, and more animated objects. */ /* ================================================================== */ static void build_cassette(SymB *b){ const double bd[]={-0.9,-0.55,0.9,-0.55,0.9,0.55,-0.9,0.55}; sym_poly(b,bd,4,1); sym_ring(b,-0.35,0.12,0.22,10); sym_ring(b,0.35,0.12,0.22,10); sym_ring(b,-0.35,0.12,0.08,5); sym_ring(b,0.35,0.12,0.08,5); const double w[]={-0.5,-0.04,0.5,-0.04,0.5,0.3,-0.5,0.3}; sym_poly(b,w,4,1); const double l[]={-0.75,-0.5,0.75,-0.5,0.75,-0.14,-0.75,-0.14}; sym_poly(b,l,4,1); for(int i=0;i<5;i++){ double x=-0.6+i*0.3; sym_line(b,x,-0.55,x,-0.62); } } static void build_record(SymB *b){ sym_ring(b,0,0,1.0,24); for(int i=1;i<=3;i++) sym_ring(b,0,0,1.0-i*0.16,20); sym_ring(b,0,0,0.32,12); sym_ring(b,0,0,0.05,5); } static void build_cd(SymB *b){ sym_ring(b,0,0,1.0,24); sym_ring(b,0,0,0.85,20); sym_ring(b,0,0,0.22,10); sym_ring(b,0,0,0.1,6); sym_line(b,0.32,0.32,0.7,0.7); } static void build_quarter(SymB *b){ sym_ring(b,0,0,1.0,24); sym_ring(b,0,0,0.85,22); for(int i=0;i<28;i++){ double a=TAU*i/28; sym_line(b,1.0*cos(a),1.0*sin(a),0.91*cos(a),0.91*sin(a)); } sym_arc(b,0,-0.15,0.4,sym_deg(20),sym_deg(160),6); sym_line(b,-0.3,-0.15,0.3,-0.15); sym_ring(b,0,0.05,0.18,7); } static void build_kite(SymB *b){ const double d[]={0,1.0,0.55,0.1,0,-0.6,-0.55,0.1}; sym_poly(b,d,4,1); sym_line(b,0,1.0,0,-0.6); sym_line(b,-0.55,0.1,0.55,0.1); int prev=sym_pt(b,0,-0.6); for(int i=1;i<=4;i++){ double y=-0.6-i*0.16, x=0.12*((i%2)?1:-1); int idx=sym_pt(b,x,y); sym_edge(b,prev,idx); prev=idx; sym_ring(b,x,y,0.05,4); } } static void build_baseball(SymB *b){ sym_ring(b,0,0,1.0,22); sym_arc(b,-1.35,0,0.95,sym_deg(-42),sym_deg(42),7); sym_arc(b,1.35,0,0.95,sym_deg(138),sym_deg(222),7); for(int i=0;i<5;i++){ double y=-0.45+i*0.22; sym_line(b,-0.72,y,-0.62,y+0.06); sym_line(b,0.62,y,0.72,y+0.06); } } static void build_tardis(SymB *b){ const double bd[]={-0.6,-1.0,0.6,-1.0,0.6,0.7,-0.6,0.7}; sym_poly(b,bd,4,1); const double rf[]={-0.66,0.7,0.66,0.7,0.55,0.85,-0.55,0.85}; sym_poly(b,rf,4,0); sym_line(b,-0.66,0.7,0.66,0.7); sym_ring(b,0,0.95,0.07,6); sym_line(b,0,-1.0,0,0.5); for(double sx=-1;sx<=1;sx+=2){ double cx=sx*0.3; const double wnd[]={cx-0.13,0.28,cx+0.13,0.28,cx+0.13,0.55,cx-0.13,0.55}; sym_poly(b,wnd,4,1); } sym_line(b,-0.6,0.62,0.6,0.62); } static void build_rose(SymB *b){ double cy=0.32; int prev=-1; for(int i=0;i<=24;i++){ double t=(double)i/24, a=t*TAU*2.0, r=0.1+t*0.45; int idx=sym_pt(b,r*cos(a),cy+r*sin(a)); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } for(int k=0;k<5;k++){ double a=TAU*k/5+0.3; sym_arc(b,0.55*cos(a),cy+0.55*sin(a),0.32,sym_deg(0),sym_deg(200),5); } sym_line(b,0,cy-0.55,0,-1.0); sym_arc(b,-0.22,-0.5,0.22,sym_deg(-20),sym_deg(140),4); sym_arc(b,0.22,-0.72,0.22,sym_deg(40),sym_deg(200),4); } static void build_shoe(SymB *b){ const double p[]={-1.0,-0.45,-0.95,-0.65,0.85,-0.65,1.0,-0.35,0.92,-0.08,0.4,0.05,0.0,0.3,-0.7,0.3,-0.95,-0.05}; sym_poly(b,p,9,1); sym_line(b,-0.95,-0.45,1.0,-0.45); sym_arc(b,-0.2,0.18,0.4,sym_deg(0),sym_deg(180),4); for(int i=0;i<3;i++){ double x=-0.3+i*0.2; sym_line(b,x-0.05,0.0,x+0.1,0.2); } } static void build_boot(SymB *b){ const double p[]={-0.5,-0.65,-0.45,0.9,0.0,0.9,0.05,-0.25,1.0,-0.35,1.0,-0.65}; sym_poly(b,p,6,1); sym_line(b,-0.5,-0.5,1.0,-0.5); sym_line(b,-0.45,0.5,0.0,0.5); } static void build_piano(SymB *b){ const double bd[]={-0.9,-1.0,0.9,-1.0,0.9,0.9,-0.9,0.9}; sym_poly(b,bd,4,1); const double kb[]={-0.85,-0.2,0.85,-0.2,0.85,0.06,-0.85,0.06}; sym_poly(b,kb,4,1); for(int i=1;i<10;i++){ double x=-0.85+i*0.17; sym_line(b,x,-0.2,x,0.06); } for(int i=0;i<9;i++){ if(i%7==2||i%7==6) continue; double x=-0.7+i*0.17; const double bk[]={x-0.04,-0.04,x+0.04,-0.04,x+0.04,0.06,x-0.04,0.06}; sym_poly(b,bk,4,1); } sym_line(b,-0.9,0.5,0.9,0.5); } static void build_cloud(SymB *b){ sym_arc(b,-0.55,-0.1,0.4,sym_deg(90),sym_deg(270),5); sym_arc(b,-0.2,0.2,0.45,sym_deg(20),sym_deg(180),6); sym_arc(b,0.35,0.18,0.42,sym_deg(0),sym_deg(160),5); sym_arc(b,0.65,-0.1,0.35,sym_deg(-90),sym_deg(90),5); sym_line(b,-0.55,-0.5,0.65,-0.45); } static void build_soupcan(SymB *b){ const double bd[]={-0.55,-0.95,0.55,-0.95,0.55,0.8,-0.55,0.8}; sym_poly(b,bd,4,1); sym_ellipse(b,0,0.8,0.55,0.16,0,16); sym_line(b,-0.55,0.45,0.55,0.45); sym_line(b,-0.55,-0.6,0.55,-0.6); sym_ring(b,0,-0.07,0.3,12); } static void build_iceberg(SymB *b){ const double p[]={-0.5,0.05,-0.2,0.78,0.15,0.45,0.45,0.95,0.62,0.3,0.55,0.05,0.9,-0.2,0.6,-0.82,0.0,-1.0,-0.7,-0.7,-0.85,-0.1}; sym_poly(b,p,11,1); sym_line(b,-0.95,0.05,0.95,0.05); } static void build_bomb(SymB *b){ sym_ring(b,0,-0.2,0.7,18); const double nk[]={-0.15,0.45,0.15,0.45,0.13,0.6,-0.13,0.6}; sym_poly(b,nk,4,1); int prev=sym_pt(b,0,0.6); for(int i=1;i<=4;i++){ double y=0.6+i*0.12, x=0.13*((i%2)?1:-1); int idx=sym_pt(b,x,y); sym_edge(b,prev,idx); prev=idx; } sym_ring(b,0.13,1.12,0.08,5); } static void build_comb(SymB *b){ const double sp[]={-0.95,0.3,0.95,0.3,0.95,0.6,-0.95,0.6}; sym_poly(b,sp,4,1); for(int i=0;i<22;i++){ double x=-0.9+i*1.8/21; sym_line(b,x,0.3,x,-0.8); } } static void build_traincross(SymB *b){ sym_line(b,-0.8,0.85,0.8,-0.15); sym_line(b,-0.78,0.8,0.82,-0.2); sym_line(b,-0.8,-0.15,0.8,0.85); sym_line(b,-0.78,-0.2,0.82,0.8); sym_line(b,0,-0.2,0,-1.0); sym_ring(b,-0.5,-0.42,0.12,6); sym_ring(b,0.5,-0.42,0.12,6); } static void build_mailbox(SymB *b){ sym_arc(b,0,0.1,0.5,sym_deg(0),sym_deg(180),8); sym_line(b,-0.5,0.1,-0.5,-0.4); sym_line(b,0.5,0.1,0.5,-0.4); sym_line(b,-0.5,-0.4,0.5,-0.4); sym_line(b,0.5,-0.1,0.5,0.3); sym_line(b,0.5,0.3,0.72,0.3); sym_line(b,0.72,0.3,0.72,0.0); sym_line(b,0,-0.4,0,-1.0); sym_arc(b,-0.25,0.1,0.25,sym_deg(0),sym_deg(180),3); } static void build_shipwheel(SymB *b){ sym_ring(b,0,0,0.7,18); sym_ring(b,0,0,0.55,16); sym_ring(b,0,0,0.12,6); for(int i=0;i<8;i++){ double a=TAU*i/8; sym_line(b,0.12*cos(a),0.12*sin(a),0.7*cos(a),0.7*sin(a)); sym_line(b,0.7*cos(a),0.7*sin(a),0.96*cos(a),0.96*sin(a)); sym_ring(b,0.88*cos(a),0.88*sin(a),0.06,4); } } static void build_axe(SymB *b){ sym_line(b,-0.16,-1.0,0.08,0.6); sym_line(b,-0.04,-1.0,0.2,0.6); sym_line(b,-0.16,-1.0,-0.04,-1.0); const double hd[]={0.08,0.35,0.6,0.55,0.78,0.32,0.78,0.05,0.6,-0.12,0.14,0.12}; sym_poly(b,hd,6,1); } static void build_popcorn(SymB *b){ const double bx[]={-0.55,-0.95,0.55,-0.95,0.45,0.15,-0.45,0.15}; sym_poly(b,bx,4,1); for(int i=0;i<4;i++){ double x=-0.4+i*0.27; sym_line(b,x,-0.9,x,0.13); } double pp[6][2]={{-0.35,0.3},{0.0,0.45},{0.35,0.3},{-0.18,0.55},{0.18,0.55},{0.0,0.2}}; for(int k=0;k<6;k++) sym_ring(b,pp[k][0],pp[k][1],0.16,5); } static void build_teapot(SymB *b){ sym_ellipse(b,0,-0.1,0.65,0.5,0,18); sym_line(b,-0.6,0.05,-1.05,0.35); sym_line(b,-0.55,-0.18,-0.98,0.12); sym_line(b,-1.05,0.35,-0.98,0.12); sym_arc(b,0.72,-0.1,0.3,sym_deg(-80),sym_deg(80),6); sym_arc(b,0,0.38,0.32,sym_deg(0),sym_deg(180),6); sym_line(b,-0.32,0.38,0.32,0.38); sym_ring(b,0,0.58,0.08,5); } static void build_churchbell(SymB *b){ const double p[][2]={{0,0.7},{0.15,0.6},{0.26,0.3},{0.46,-0.2},{0.6,-0.5},{0.63,-0.62}}; sym_profile(b,p,6); sym_line(b,-0.63,-0.62,0.63,-0.62); sym_ring(b,0,-0.45,0.1,6); sym_line(b,-0.42,0.78,0.42,0.78); sym_line(b,0,0.7,0,0.78); sym_arc(b,0.5,0.78,0.18,sym_deg(-90),sym_deg(200),5); } static void build_anvil(SymB *b){ const double p[]={-0.9,0.5,0.55,0.5,0.95,0.28,0.55,0.18,0.45,0.1,0.45,-0.12,0.28,-0.12,0.28,-0.35,0.5,-0.5,-0.55,-0.5,-0.38,-0.35,-0.38,-0.12,-0.7,-0.12,-0.7,0.22,-0.9,0.3}; sym_poly(b,p,15,1); } static void build_bookstack(SymB *b){ for(int k=0;k<3;k++){ double y=-0.65+k*0.42, w=0.85-k*0.12, off=(k%2)*0.12-0.06; const double bk[]={-w+off,y,w+off,y,w+off,y+0.32,-w+off,y+0.32}; sym_poly(b,bk,4,1); sym_line(b,-w+off+0.06,y,-w+off+0.06,y+0.32); } } /* alchemical symbols */ static void build_sulfur(SymB *b){ const double t[]={-0.5,0.3,0.5,0.3,0,1.0}; sym_poly(b,t,3,1); sym_line(b,0,0.3,0,-0.6); sym_line(b,-0.32,-0.15,0.32,-0.15); } static void build_mercury(SymB *b){ sym_ring(b,0,0.18,0.3,12); sym_arc(b,0,0.55,0.3,sym_deg(15),sym_deg(165),5); sym_line(b,0,-0.12,0,-0.7); sym_line(b,-0.26,-0.42,0.26,-0.42); } static void build_salt(SymB *b){ sym_ring(b,0,0,0.8,18); sym_line(b,-0.8,0,0.8,0); } static void build_antimony(SymB *b){ sym_ring(b,0,-0.2,0.6,16); sym_line(b,0,0.4,0,1.0); sym_line(b,-0.3,0.7,0.3,0.7); } /* occult symbols */ static void build_pentagram(SymB *b){ sym_ring(b,0,0,1.0,20); int first=-1,prev=-1; for(int i=0;i<5;i++){ double a=sym_deg(90+i*144); int idx=sym_pt(b,0.95*cos(a),0.95*sin(a)); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); } static void build_ankh(SymB *b){ sym_ring(b,0,0.55,0.3,12); sym_line(b,0,0.25,0,-1.0); sym_line(b,-0.4,-0.1,0.4,-0.1); } static void build_eyeprovidence(SymB *b){ const double t[]={-0.85,-0.6,0.85,-0.6,0,0.85}; sym_poly(b,t,3,1); sym_arc(b,0,-0.1,0.32,sym_deg(0),sym_deg(180),5); sym_arc(b,0,-0.1,0.32,sym_deg(180),sym_deg(360),5); sym_ring(b,0,-0.1,0.1,5); for(int i=0;i<5;i++){ double a=sym_deg(50+i*20); sym_line(b,0,0.86,0.32*cos(a),0.86+0.3*sin(a)); } } static void build_triplemoon(SymB *b){ sym_ring(b,0,0,0.5,14); sym_arc(b,-0.6,0,0.45,sym_deg(-80),sym_deg(80),6); sym_arc(b,0.6,0,0.45,sym_deg(100),sym_deg(260),6); } /* more old keys */ static void build_key2(SymB *b){ sym_ring(b,-0.75,0,0.25,12); sym_line(b,-0.5,0,0.9,0); sym_line(b,0.9,0,0.9,-0.3); sym_line(b,0.68,0,0.68,-0.25); sym_line(b,0.5,0,0.5,-0.15); } static void build_key3(SymB *b){ const double bw[]={-0.95,0,-0.7,0.32,-0.4,0.32,-0.5,0,-0.4,-0.32,-0.7,-0.32}; sym_poly(b,bw,6,1); sym_line(b,-0.4,0,0.95,0); sym_ring(b,0.6,0,0.12,6); sym_line(b,0.9,0,0.9,-0.28); sym_line(b,0.74,0,0.74,-0.2); } /* dedicated 3-D: helices, pyramid */ static void gen_spring3d(void){ Solid*s=new_solid(3); int coils=6,per=10,n=coils*per; double r=0.42,H=1.5; int prev=-1; for(int i=0;i<=n;i++){ double a=TAU*i/per, y=-H/2+H*i/n; int idx=s->nv; PV(s,r*cos(a),y,r*sin(a),0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } center_normalize(s); } static void gen_slinky3d(void){ Solid*s=new_solid(3); int coils=13,per=8,n=coils*per; double r=0.5,H=1.7; int prev=-1; for(int i=0;i<=n;i++){ double a=TAU*i/per, y=-H/2+H*i/n; int idx=s->nv; PV(s,r*cos(a),y,r*sin(a),0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } center_normalize(s); } static void build_coil2d(SymB *b,int loops){ int n=loops*8, prev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, y=-1.0+2.0*t, x=0.5*sin(t*TAU*loops); int idx=sym_pt(b,x,y); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } } static void build_spring2d(SymB *b){ build_coil2d(b,6); } static void build_slinky2d(SymB *b){ build_coil2d(b,13); } static void gen_pyramid(void){ Solid*s=new_solid(3); PV(s,-0.8,-0.6,-0.8,0,0,0);PV(s,0.8,-0.6,-0.8,0,0,0);PV(s,0.8,-0.6,0.8,0,0,0);PV(s,-0.8,-0.6,0.8,0,0,0);PV(s,0,0.9,0,0,0,0); int e[8][2]={{0,1},{1,2},{2,3},{3,0},{0,4},{1,4},{2,4},{3,4}}; for(int k=0;k<8;k++)AE(s,e[k][0],e[k][1]); center_normalize(s); } static void build_pyramid2d(SymB *b){ const double t[]={-0.9,-0.6,0.9,-0.6,0,0.9}; sym_poly(b,t,3,1); sym_line(b,0,0.9,0,-0.6); } /* dynamic (animated) */ static void gen_fish_dyn(Solid*s,float t){ float flap=0.45f*sinf(t*5.0f); int seg=8,nsec=6,rings[6]; for(int k=0;knv; for(int i=0;i0) for(int i=0;inv; PV(s,0.45,0,0,0,0,0); PV(s,0.95,0.42+flap,0,0,0,0); PV(s,0.88,flap,0,0,0,0); PV(s,0.95,-0.42+flap,0,0,0,0); AE(s,tf,tf+1);AE(s,tf+1,tf+2);AE(s,tf+2,tf+3);AE(s,tf+3,tf); add_ball(s,-0.55,0.12,0.22,0,0,1,0.06,4); } static void build_fish2d(SymB *b){ sym_ellipse(b,0,0,0.7,0.42,0,16); const double tl[]={0.6,0,1.0,0.38,0.85,0,1.0,-0.38}; sym_poly(b,tl,4,0); sym_ring(b,-0.4,0.1,0.07,4); } static void gen_scissors_dyn(Solid*s,float t){ float op=0.28f+0.28f*(0.5f+0.5f*sinf(t*4.0f)); int piv=s->nv; PV(s,0,0,0,0,0,0); for(int sd=0;sd<2;sd++){ double sg=sd?1:-1; double bx=0.95*sin(op)*sg, by=0.95*cos(op); int tip=s->nv; PV(s,bx,by,0,0,0,0); int mid=s->nv; PV(s,0.8*sin(op)*sg+0.06*sg,0.85*cos(op),0,0,0,0); AE(s,piv,tip); AE(s,tip,mid); AE(s,mid,piv); /* handle loop below pivot, opposite side */ double hx=-0.45*sg, hy=-0.7; add_xy_ring(s,hx,hy,0.22,8); int hb=s->nv; PV(s,hx,hy+0.22,0,0,0,0); AE(s,piv,hb); } } static void build_scissors2d(SymB *b){ sym_line(b,0,0,0.55,0.9); sym_line(b,0.05,0.05,0.5,0.85); sym_line(b,0,0,-0.55,0.9); sym_line(b,-0.05,0.05,-0.5,0.85); sym_ring(b,-0.4,-0.6,0.25,8); sym_ring(b,0.4,-0.6,0.25,8); sym_line(b,0,0,-0.4,-0.4); sym_line(b,0,0,0.4,-0.4); } static void gen_flame_dyn(Solid*s,float t){ int n=8; for(int pl=0;pl<2;pl++){ double cr=cos(pl*1.5708), sr=sin(pl*1.5708); int prev=-1,first=-1; for(int i=0;i<=n;i++){ double kk=(double)i/n, y=-0.85+kk*1.7, w=0.42*sin(kk*3.14159)*(1.3-0.6*kk), jit=0.07*sinf(t*11+i+pl*3)*kk, x=w+jit; int idx=s->nv; PV(s,x*cr,y,x*sr,0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } for(int i=n;i>=0;i--){ double kk=(double)i/n, y=-0.85+kk*1.7, w=0.42*sin(kk*3.14159)*(1.3-0.6*kk), jit=0.07*sinf(t*11+i+pl*3+1.5)*kk, x=-(w+jit); int idx=s->nv; PV(s,x*cr,y,x*sr,0,0,0); AE(s,prev,idx); prev=idx; } AE(s,prev,first); } } static void build_flame2d(SymB *b){ int n=10,first=-1,prev=-1; for(int i=0;i<=n;i++){ double kk=(double)i/n,y=-0.85+kk*1.7,w=0.42*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,w,y); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx;} for(int i=n;i>=0;i--){ double kk=(double)i/n,y=-0.85+kk*1.7,w=0.42*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,-w,y); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); } static void gen_zippo_dyn(Solid*s,float t){ double X=0.32,Y0=-0.9,Y1=0.05,Z=0.18; double c[8][3]={{-X,Y0,-Z},{X,Y0,-Z},{X,Y1,-Z},{-X,Y1,-Z},{-X,Y0,Z},{X,Y0,Z},{X,Y1,Z},{-X,Y1,Z}}; int b0=s->nv; for(int i=0;i<8;i++)PV(s,c[i][0],c[i][1],c[i][2],0,0,0); int 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}}; for(int k=0;k<12;k++)AE(s,b0+e[k][0],b0+e[k][1]); /* open lid */ int li=s->nv; PV(s,-X,Y1,-Z,0,0,0);PV(s,X,Y1,-Z,0,0,0);PV(s,X,Y1+0.4,-Z-0.2,0,0,0);PV(s,-X,Y1+0.4,-Z-0.2,0,0,0); AE(s,li,li+1);AE(s,li+1,li+2);AE(s,li+2,li+3);AE(s,li+3,li); /* flickering flame above front */ int fl=s->nv,prev=-1; for(int i=0;i<=6;i++){ double kk=(double)i/6,y=Y1+0.05+kk*0.7,w=0.18*sin(kk*3.14159)*(1.3-0.6*kk)+0.05*sinf(t*13+i)*kk; int idx=s->nv; PV(s,w,y,Z*0.3,0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; (void)fl; } for(int i=6;i>=0;i--){ double kk=(double)i/6,y=Y1+0.05+kk*0.7,w=0.18*sin(kk*3.14159)*(1.3-0.6*kk)+0.05*sinf(t*13+i+1)*kk; int idx=s->nv; PV(s,-w,y,Z*0.3,0,0,0); AE(s,prev,idx); prev=idx; } } static void build_zippo2d(SymB *b){ const double bx[]={-0.32,-0.9,0.32,-0.9,0.32,0.05,-0.32,0.05}; sym_poly(b,bx,4,1); const double lid[]={-0.32,0.05,0.32,0.05,0.28,0.4,-0.28,0.4}; sym_poly(b,lid,4,0); int n=6,first=-1,prev=-1; for(int i=0;i<=n;i++){ double kk=(double)i/n,y=0.1+kk*0.6,w=0.16*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,w,y); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx;} for(int i=n;i>=0;i--){ double kk=(double)i/n,y=0.1+kk*0.6,w=0.16*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,-w,y); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); } static void add_gear3d(Solid*s,double cx,double cy,double R,int nt,double ang){ int n=nt*2,f=s->nv; for(int i=0;inv; PV(s,cx,cy,0,0,0,0); for(int i=0;inv; for(int i=0;i<6;i++){ double a=TAU*i/6; PV(s,cx+R*0.2*cos(a),cy+R*0.2*sin(a),0,0,0,0);} for(int i=0;i<6;i++)AE(s,hr+i,hr+(i+1)%6); } static void gen_gears_dyn(Solid*s,float t){ add_gear3d(s,-0.5,0,0.5,9,t*1.4); add_gear3d(s,0.58,0,0.42,7,-t*1.8+0.3); } static void build_gears2d(SymB *b){ for(int g=0;g<2;g++){ double cx=g?0.55:-0.5,R=g?0.42:0.5; int nt=g?7:9,n=nt*2,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); sym_ring(b,cx,0,R*0.2,6); } } static void gen_fan_dyn(Solid*s,float t){ double a0=t*8.0; int cg=s->nv; for(int i=0;i<20;i++){ double a=TAU*i/20; PV(s,cos(a),sin(a),0.15,0,0,0); } for(int i=0;i<20;i++)AE(s,cg+i,cg+(i+1)%20); int hub=s->nv; PV(s,0,0,0,0,0,0); for(int bl=0;bl<4;bl++){ double a=a0+TAU*bl/4; double c=cos(a),sn=sin(a); int p0=s->nv; PV(s,0.12*c,0.12*sn,0,0,0,0); double tx=0.9*c,ty=0.9*sn; double px=-sn,py=c; PV(s,tx+px*0.25,ty+py*0.25,0,0,0,0); PV(s,tx-px*0.18,ty-py*0.18,0,0,0,0); AE(s,p0,p0+1); AE(s,p0+1,p0+2); AE(s,p0+2,p0); AE(s,hub,p0); } } static void build_fan2d(SymB *b){ sym_ring(b,0,0,1.0,20); sym_ring(b,0,0,0.12,6); for(int bl=0;bl<4;bl++){ double a=TAU*bl/4; double c=cos(a),sn=sin(a),px=-sn,py=c; double tx=0.9*c,ty=0.9*sn; const double q[]={0.12*c,0.12*sn,tx+px*0.25,ty+py*0.25,tx-px*0.18,ty-py*0.18}; sym_poly(b,q,3,1); } } /* Refracting prism. The triangle lives at these object-space coords (kept * within the unit sphere, so no normalisation), and the render loop draws a * white beam into one face and a rainbow fan out the other (draw_prism_beams). */ static void gen_prism3d(void){ Solid*s=new_solid(3); const double tri[3][2]={{-0.6,-0.45},{0.6,-0.45},{0,0.72}}; double z=0.4; for(int zz=0;zz<2;zz++){ double zv=zz?z:-z; for(int i=0;i<3;i++) PV(s,tri[i][0],tri[i][1],zv,0,0,0); } for(int zz=0;zz<2;zz++){ int bb=zz*3; AE(s,bb,bb+1); AE(s,bb+1,bb+2); AE(s,bb+2,bb); } for(int i=0;i<3;i++) AE(s,i,i+3); } static void gen_prism2d(void){ Solid*s=new_solid(3); /* flat, un-normalised so beams line up */ const double tri[3][2]={{-0.6,-0.45},{0.6,-0.45},{0,0.72}}; for(int i=0;i<3;i++) PV(s,tri[i][0],tri[i][1],0,0,0,0); AE(s,0,1); AE(s,1,2); AE(s,2,0); } /* Glass sphere. Rendered as a refraction billboard (draw_glassballs); the solid * itself is only used for bounding/collision, so a coarse wireframe ball / ring. */ static void gen_glassball3d(void){ Solid*s=new_solid(3); int lats=3, seg=14; for(int la=1;la<=lats;la++){ double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25, y=sin(phi)*0.9, rr=cos(phi)*0.9; int f=s->nv; for(int i=0;inv; for(int i=0;inv; for(int i=0;inv; for(int i=0;i MAX_VERTS) n = MAX_VERTS; if (n < 6) n = 6; Solid *s = new_solid(3); for (int i = 0; i < n; i++) { double z = 2.0*frand() - 1.0; /* uniform on the sphere */ double t = TAU*frand(); double rr = sqrt(1.0 - z*z); double rad = 0.74 + 0.26*frand(); /* lumpy radius */ PV(s, rr*cos(t)*rad, rr*sin(t)*rad, z*rad, 0,0,0); } /* a triangle (a,b,c) is a hull face iff every other vertex lies on one * side of its plane; collect the edges of all such faces. */ for (int a = 0; a < n; a++) for (int b = a+1; b < n; b++) for (int c = b+1; c < n; c++) { double ux = s->v[b][0]-s->v[a][0], uy = s->v[b][1]-s->v[a][1], uz = s->v[b][2]-s->v[a][2]; double vx = s->v[c][0]-s->v[a][0], vy = s->v[c][1]-s->v[a][1], vz = s->v[c][2]-s->v[a][2]; double nx = uy*vz - uz*vy, ny = uz*vx - ux*vz, nz = ux*vy - uy*vx; double nl = sqrt(nx*nx + ny*ny + nz*nz); if (nl < 1e-9) continue; /* degenerate triple */ int pos = 0, neg = 0; for (int p = 0; p < n; p++) { if (p == a || p == b || p == c) continue; double dx = s->v[p][0]-s->v[a][0], dy = s->v[p][1]-s->v[a][1], dz = s->v[p][2]-s->v[a][2]; double d = (nx*dx + ny*dy + nz*dz) / nl; if (d > 1e-5) pos++; else if (d < -1e-5) neg++; } if (pos == 0 || neg == 0) { AE_unique(s, a, b); AE_unique(s, b, c); AE_unique(s, c, a); } } center_normalize(s); } static void init_solids(void) { const double P = 1.6180339887, I = 1.0/1.6180339887; Solid *s; /* --- the five Platonic solids --- */ s = new_solid(3); PV(s,1,1,1,0,0,0); PV(s,1,-1,-1,0,0,0); PV(s,-1,1,-1,0,0,0); PV(s,-1,-1,1,0,0,0); center_normalize(s); derive_edges(s); /* tetrahedron */ gen_hypercube(3); /* cube */ gen_orthoplex(3); /* octahedron */ s = new_solid(3); /* icosahedron */ PV(s,0,1,P,0,0,0); PV(s,0,1,-P,0,0,0); PV(s,0,-1,P,0,0,0); PV(s,0,-1,-P,0,0,0); PV(s,1,P,0,0,0,0); PV(s,1,-P,0,0,0,0); PV(s,-1,P,0,0,0,0); PV(s,-1,-P,0,0,0,0); PV(s,P,0,1,0,0,0); PV(s,P,0,-1,0,0,0); PV(s,-P,0,1,0,0,0); PV(s,-P,0,-1,0,0,0); center_normalize(s); derive_edges(s); s = new_solid(3); /* dodecahedron */ PV(s,1,1,1,0,0,0); PV(s,1,1,-1,0,0,0); PV(s,1,-1,1,0,0,0); PV(s,1,-1,-1,0,0,0); PV(s,-1,1,1,0,0,0); PV(s,-1,1,-1,0,0,0); PV(s,-1,-1,1,0,0,0); PV(s,-1,-1,-1,0,0,0); PV(s,0,I,P,0,0,0); PV(s,0,I,-P,0,0,0); PV(s,0,-I,P,0,0,0); PV(s,0,-I,-P,0,0,0); PV(s,I,P,0,0,0,0); PV(s,I,-P,0,0,0,0); PV(s,-I,P,0,0,0,0); PV(s,-I,-P,0,0,0,0); PV(s,P,0,I,0,0,0); PV(s,P,0,-I,0,0,0); PV(s,-P,0,I,0,0,0); PV(s,-P,0,-I,0,0,0); center_normalize(s); derive_edges(s); /* --- other 3-D solids --- */ gen_cuboctahedron(); gen_truncated_octahedron(); gen_stella_octangula(); /* --- prisms, antiprisms, bipyramids --- */ gen_prism(3); gen_prism(5); gen_prism(6); gen_prism(7); gen_prism(8); gen_antiprism(3); gen_antiprism(4); gen_antiprism(5); gen_antiprism(6); gen_antiprism(8); gen_bipyramid(3); gen_bipyramid(4); gen_bipyramid(5); gen_bipyramid(6); gen_bipyramid(8); /* --- star polygons --- */ gen_star(5,2); gen_star(6,2); gen_star(7,2); gen_star(7,3); gen_star(8,3); gen_star(9,2); gen_star(9,4); gen_star(12,5); /* --- many-faced solids (named for their face count) + a hexagram --- * Built from prism / bipyramid / trapezohedron families so each lands on * exactly the requested number of faces. (The 20-face icosahedron here is * a decagonal bipyramid, distinct from the platonic icosahedron above.) */ gen_prism(11); /* tridecahedron (13 faces) */ gen_bipyramid(7); /* tetradecahedron (14 faces) */ gen_prism(13); /* pentadecahedron (15 faces) */ gen_prism(15); /* heptadecahedron (17 faces) */ gen_bipyramid(9); /* octadecahedron (18 faces) */ gen_prism(17); /* enneadecahedron (19 faces) */ gen_bipyramid(10); /* icosahedron (20 faces) */ gen_trapezohedron(12); /* icositetrahedron (24 faces) */ gen_trapezohedron(15); /* triacontahedron (30 faces) */ gen_trapezohedron(30); /* hexacontahedron (60 faces) */ gen_bipyramid(50); /* hecatohedron (100 faces) */ gen_unicursal_hexagram(); /* unicursal hexagram */ /* --- googie / atomic-age starbursts, each in 2-D and extruded 3-D --- */ add_symbol(build_sparkle4); /* 4-point bowling-alley twinkle */ add_symbol(build_sparkle8); /* 8-point twinkle */ add_symbol(build_atomic); /* atomic burst with electrons */ add_symbol(build_sunburst); /* ray sunburst */ add_symbol(build_doublestar); /* layered double starburst */ add_symbol(build_boomerang); /* boomerang / crescent */ add_symbol(build_amoeba); /* kidney / amoeba blob */ /* flat motifs paired with a naturally-3-D counterpart below */ { SymB b; sym_init(&b); build_atom2d(&b); sym_finish(&b, 0.0f); } /* flat atom */ { SymB b; sym_init(&b); build_orbit2d(&b); sym_finish(&b, 0.0f); } /* orbit rings */ { SymB b; sym_init(&b); build_starorb2d(&b); sym_finish(&b, 0.0f); } /* dense star */ gen_sputnik(); /* 3-D sputnik satellite */ gen_atom3d(); /* 3-D orbital atom */ gen_gyroscope3d(); /* 3-D gyroscope / orbit cage */ gen_spikeorb3d(); /* 3-D spike orb / sea-urchin */ /* --- symbols & signs, each in a flat (2-D) and an extruded (3-D) form --- */ add_symbol(build_smiley); /* smiley face */ add_symbol(build_biohazard); /* biohazard */ add_symbol(build_peace); /* peace sign */ add_symbol(build_cross); /* cross */ add_symbol(build_question); /* question mark */ add_symbol(build_exclaim); /* exclamation point */ add_symbol(build_hash); /* hash / pound (#) */ 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(); /* --- more object iconography (flat 2-D and extruded 3-D each) --- */ add_symbol(build_ghost); /* pac-man ghost */ add_symbol(build_icecream); /* ice cream cone */ add_symbol(build_bulb); /* light bulb */ add_symbol(build_mito); /* mitochondrion */ add_symbol(build_window); /* windowpane */ add_symbol(build_bowlingpin); /* bowling pin */ add_symbol(build_bat); /* baseball bat */ add_symbol(build_beerbottle); /* beer bottle */ add_symbol(build_nonic); /* nonic pint glass */ add_symbol(build_whiskey); /* whiskey bottle */ add_symbol(build_phone); /* telephone */ add_symbol(build_booth); /* telephone booth */ add_symbol(build_radiotower); /* radio tower */ /* atom-with-electrons and bowling ball: flat 2-D + a true volumetric 3-D */ { SymB b; sym_init(&b); build_atom_e2d(&b); sym_finish(&b, 0.0f); } /* flat atom */ gen_atom_electrons3d(); /* 3-D atom */ { SymB b; sym_init(&b); /* flat ball */ sym_ring(&b,0,0,1.0,22); sym_ring(&b,-0.2,0.25,0.12,6); sym_ring(&b,0.2,0.25,0.12,6); sym_ring(&b,0.0,-0.05,0.12,6); sym_ellipse(&b,0,0,1.0,0.34,0,18); sym_finish(&b, 0.0f); } gen_bowlingball3d(); /* 3-D ball */ /* --- lathe solids of revolution + torus: 3-D plus a 2-D silhouette --- */ gen_pawn(); gen_rook(); gen_bishop(); gen_king(); gen_queen(); /* chess set */ gen_candlestick(); gen_coffeecup(); gen_lantern(); gen_hurricane(); gen_quartz(); gen_pineapple(); gen_torus3d(); add_flat2d(build_pawn2d); add_flat2d(build_rook2d); add_flat2d(build_bishop2d); add_flat2d(build_king2d); add_flat2d(build_queen2d); add_flat2d(build_candlestick2d); add_flat2d(build_coffeecup2d); add_flat2d(build_lantern2d); add_flat2d(build_hurricane2d); add_flat2d(build_quartz2d); add_flat2d(build_pineapple2d); add_flat2d(build_torus2d); /* --- the extruded objects, now in BOTH 2-D and 3-D --- */ add_symbol(build_knight); add_symbol(build_padlock); add_symbol(build_key); add_symbol(build_banana); add_symbol(build_guitar); add_symbol(build_taco); add_symbol(build_pizza); add_symbol(build_hammer); add_symbol(build_wrench); add_symbol(build_compass_dir); add_symbol(build_compass_draft); add_symbol(build_square); add_symbol(build_atari); add_symbol(build_joystick); add_symbol(build_debian); add_symbol(build_glasses); add_symbol(build_fork); add_symbol(build_spoon); add_symbol(build_pen); add_symbol(build_fire); add_symbol(build_water); add_symbol(build_air); add_symbol(build_earth); add_symbol(build_philstone); add_symbol(build_starship); /* --- 2-D companions for the 3-D-only googie objects --- */ add_flat2d(build_saturn2d); add_flat2d(build_molecule2d); add_flat2d(build_diabolo2d); add_flat2d(build_sputnik2d); add_flat2d(build_gyroscope2d); /* --- ten more objects, each in 2-D and 3-D --- */ add_symbol(build_tophat); add_symbol(build_cap); add_symbol(build_nose); add_symbol(build_heart); add_symbol(build_keyboard); add_symbol(build_floppy); add_symbol(build_bell); add_symbol(build_book); add_symbol(build_commodore); add_symbol(build_ti); /* --- computer chips (IC package styles), 2-D and 3-D --- */ add_symbol(build_chip_dip); add_symbol(build_chip_dip8); add_symbol(build_chip_qfp); add_symbol(build_chip_pga); add_symbol(build_chip_cpu); /* --- food, tools and assorted objects, 2-D and 3-D --- */ add_symbol(build_hotdog); add_symbol(build_hamburger); add_symbol(build_unicycle); add_symbol(build_microscope); add_symbol(build_telescope); g_mag_idx = num_shapes; add_symbol(build_magnifier); /* 2-D at g_mag_idx, 3-D at +1 */ add_symbol(build_thermometer);add_symbol(build_calculator);add_symbol(build_stapler); add_symbol(build_paperclip); add_symbol(build_palm); add_symbol(build_saguaro); add_symbol(build_sushi); add_symbol(build_bottleopener); add_symbol(build_lightning); /* --- objects with a true volumetric 3-D form + a 2-D companion --- */ gen_monolith3d(); add_flat2d(build_monolith2d); gen_hulahoop3d(); add_flat2d(build_hulahoop2d); gen_ringedplanet3d(); add_flat2d(build_ringedplanet2d); gen_gyromech3d(); add_flat2d(build_gyromech2d); gen_grapes3d(); add_flat2d(build_grapes2d); gen_corkscrew3d(); add_flat2d(build_corkscrew2d); gen_battery3d(); add_flat2d(build_battery2d); /* --- a big batch of objects (2-D and 3-D) --- */ add_symbol(build_gobstopper); add_symbol(build_pants); add_symbol(build_shirt); add_symbol(build_dice); add_symbol(build_card_k); add_symbol(build_card_q); add_symbol(build_card_j); add_symbol(build_card_a); add_symbol(build_roulette); add_symbol(build_slot); add_symbol(build_hydrant); add_symbol(build_brain); add_symbol(build_thimble); add_symbol(build_automobile);add_symbol(build_couch); add_symbol(build_stopsign); add_symbol(build_officechair);add_symbol(build_crossbones); add_symbol(build_goldfish); add_symbol(build_squid); add_symbol(build_pinecone); add_symbol(build_mapleleaf); add_symbol(build_anemone); add_symbol(build_wagonwheel); add_symbol(build_submarine); add_symbol(build_sailship); add_symbol(build_anchor); add_symbol(build_swiss); add_symbol(build_anarchy); add_symbol(build_yoyo); /* dedicated volumetric solids + 2-D companion */ gen_buckyball(); add_flat2d(build_buckyball2d); gen_die6(); add_flat2d(build_die2d); gen_die20(); add_flat2d(build_die20_2d); gen_jacks(); add_flat2d(build_jacks2d); gen_dna(); add_flat2d(build_dna2d); /* animated 3-D (rebuilt each frame) + a static 2-D companion */ add_dynamic(gen_hoberman_dyn); add_flat2d(build_hoberman2d); add_dynamic(gen_rubik_dyn); add_flat2d(build_rubik2d); add_dynamic(gen_top_dyn); add_flat2d(build_top2d); add_dynamic(gen_dreidel_dyn); add_flat2d(build_dreidel2d); add_dynamic(gen_lighthouse_dyn); add_flat2d(build_lighthouse2d); add_dynamic(gen_eyeball_dyn); add_flat2d(build_eyeball2d); add_dynamic(gen_skulljaw_dyn); add_flat2d(build_skull2d); add_dynamic(gen_teeth_dyn); add_flat2d(build_teeth2d); add_dynamic(gen_octopus_dyn); add_flat2d(build_octopus2d); add_dynamic(gen_jelly_dyn); add_flat2d(build_jelly2d); /* --- media, symbols, tools and more (2-D and 3-D) --- */ add_symbol(build_cassette); add_symbol(build_record); add_symbol(build_cd); add_symbol(build_quarter); add_symbol(build_kite); add_symbol(build_baseball); add_symbol(build_tardis); add_symbol(build_rose); add_symbol(build_shoe); add_symbol(build_boot); add_symbol(build_piano); add_symbol(build_cloud); add_symbol(build_soupcan); add_symbol(build_iceberg); add_symbol(build_bomb); add_symbol(build_comb); add_symbol(build_traincross);add_symbol(build_mailbox); add_symbol(build_shipwheel); add_symbol(build_axe); add_symbol(build_popcorn); add_symbol(build_teapot); add_symbol(build_churchbell);add_symbol(build_anvil); add_symbol(build_bookstack); add_symbol(build_sulfur); add_symbol(build_mercury); add_symbol(build_salt); add_symbol(build_antimony); add_symbol(build_pentagram);add_symbol(build_ankh); add_symbol(build_eyeprovidence); add_symbol(build_triplemoon); add_symbol(build_key2); add_symbol(build_key3); gen_spring3d(); add_flat2d(build_spring2d); gen_slinky3d(); add_flat2d(build_slinky2d); gen_pyramid(); add_flat2d(build_pyramid2d); g_prism_idx = num_shapes; gen_prism3d(); gen_prism2d(); /* 3-D at idx, 2-D at +1 */ g_glass_idx = num_shapes; gen_glassball3d(); gen_glassball2d(); /* refracting glass sphere */ g_mirror_idx = num_shapes; gen_mirror3d(); gen_mirror2d(); /* reflecting mirror sphere */ add_dynamic(gen_fish_dyn); add_flat2d(build_fish2d); add_dynamic(gen_scissors_dyn); add_flat2d(build_scissors2d); add_dynamic(gen_flame_dyn); add_flat2d(build_flame2d); add_dynamic(gen_zippo_dyn); add_flat2d(build_zippo2d); add_dynamic(gen_gears_dyn); add_flat2d(build_gears2d); add_dynamic(gen_fan_dyn); add_flat2d(build_fan2d); update_dynamics(0.0f); /* fill the dynamic slots initially */ /* --- 4/5/6-dimensional polytopes (projected & morphing) --- */ gen_simplex(5); /* 5-cell (4-simplex) */ gen_hypercube(4); /* tesseract / 8-cell */ gen_hypercube(5); /* penteract / 5-cube */ gen_hypercube(6); /* 6-cube */ gen_orthoplex(4); /* 16-cell (4-orthoplex) */ gen_orthoplex(5); /* 5-orthoplex */ gen_orthoplex(6); /* 6-orthoplex */ gen_24cell(); /* 24-cell */ gen_simplex(6); /* 5-simplex */ /* --- random vector asteroids (each session generates a fresh set) --- */ for (int k = 0; k < 18; k++) gen_asteroid(7 + (rand() % 20)); /* 7..26 verts */ } /* ================================================================== */ /* The field of tumbling solids. */ /* ================================================================== */ #define MAX_BODIES 7200 #define RENDER_REF 140.0f /* render distance at which density == body count */ /* The field is a sphere around the camera whose radius is the user-settable * render distance (cfg.render_dist). Bodies stream along -Z (forward) or +Z * (when speed is negative) and, once they leave the sphere, are recycled back * to the render-distance shell on the incoming side -- so objects always appear * far away and approach, never popping in close. The active body count scales * with render distance, so a deeper field simply holds proportionally more * shapes. The camera can rotate a full 360 degrees around it. */ typedef struct { int shape; float x, y, z; /* world position; camera at origin, looking -Z */ float size; /* == bounding-sphere radius (unit solid scaled) */ float axis[3]; /* tumble axis (normalized) */ float spin_seed; /* in [-1,1]; tumble spread applied live */ float angle; /* current tumble angle (radians) */ float hue_offset; /* used in multicolor mode */ float spawn_fade; /* 0->1 ease-in after (re)spawn, kills pop-in */ int forced; /* -1 = random field body; else a forced special shape * base index (magnifier / glass / mirror), so the body * keeps its role across recycles */ } Body; static Body bodies[MAX_BODIES]; /* ================================================================== */ /* Settings (driven by keyboard). */ /* ================================================================== */ typedef struct { float speed; /* approach speed 0..100 */ float tumble; /* tumble rate 0..100 */ float tumble_var; /* tumble variance 0..100 */ float render_dist; /* field sphere radius 40..1520 */ int density; /* active body count */ float size_min; /* min solid size */ float size_max; /* max solid size */ float hue; /* base hue 0..360 */ float hue_cycle; /* auto hue-cycle rate 0..100 (0 = off) */ int multicolor; /* 0 = single, 1 = multi */ int cycle_shapes; /* 0 = random, 1 = cycle */ float glow; /* CRT glow / bleed 0..100 */ float flicker; /* vector flicker 0..100 */ int mag_count; /* max magnifying glasses on screen 0..1000 (extra) */ int glass_count; /* max glass spheres on screen 0..1000 (extra) */ int mirror_count; /* max mirror spheres on screen 0..1000 (extra) */ int fullscreen; int paused; } Settings; static Settings cfg = { .speed = 35, .tumble = 40, .tumble_var = 50, .render_dist = 140, .density = 50, .size_min = 1.4f, .size_max = 4.6f, .hue = 200, .hue_cycle = 0, .multicolor = 0, .cycle_shapes = 0, .mag_count = 0, .glass_count = 0, .mirror_count = 0, .glow = 45, .flicker = 18, .fullscreen = 0, .paused = 0 }; static int spawn_counter = 0; static float max_line_width = 10.0f; /* queried at runtime */ static float cam_x = 0.0f, cam_y = 0.0f; /* camera pan (WASD) */ static float cam_yaw = 0.0f, cam_pitch = 0.0f; /* camera rotate (arrow keys) */ static float frand(void) { return (float)rand() / (float)RAND_MAX; } /* Bounding-sphere overlap test against the first `check_count` bodies. */ static int collides(const Body *c, int check_count, int self) { for (int i = 0; i < check_count; i++) { if (i == self) continue; const Body *o = &bodies[i]; float dx = c->x - o->x, dy = c->y - o->y, dz = c->z - o->z; float rr = c->size + o->size + 0.6f; /* +margin so they never touch */ if (dx*dx + dy*dy + dz*dz < rr*rr) return 1; } return 0; } /* (Re)initialize one body. reset=0 (recycle) re-enters it ONLY at the render- * distance shell, on the incoming side (which flips with the travel direction), * so objects always appear far away and approach -- never popping in close. * reset=1 (initial fill) spreads bodies through the volume instead, as if they * had each spawned at the shell at a different past time, so the field starts * populated rather than blank. Position is rejection-sampled to avoid overlap. */ static void spawn_body(Body *b, int reset, int check_count, int self) { if (b->forced >= 0) /* magnifier / glass / mirror body */ b->shape = (rand() & 1) ? b->forced : b->forced + 1; else b->shape = cfg.cycle_shapes ? (spawn_counter++ % num_shapes) : (rand() % num_shapes); float smin = cfg.size_min, smax = cfg.size_max; if (smax < smin) smax = smin; b->size = smin + frand() * (smax - smin); float ax = frand()*2-1, ay = frand()*2-1, az = frand()*2-1; float len = sqrtf(ax*ax + ay*ay + az*az); if (len < 1e-4f) { ax = 1; ay = 0; az = 0; len = 1; } b->axis[0] = ax/len; b->axis[1] = ay/len; b->axis[2] = az/len; b->spin_seed = frand()*2 - 1; b->angle = frand() * 6.2831853f; b->hue_offset = frand() * 360.0f; b->spawn_fade = reset ? 1.0f : 0.0f; float R = cfg.render_dist; float side = (cfg.speed >= 0.0f) ? 1.0f : -1.0f; /* +Z if flying forward */ for (int t = 0; t < 80; t++) { if (reset) { /* initial fill: uniform through the volume of the sphere */ float zc = 2.0f*frand() - 1.0f; float th = 6.2831853f*frand(); float rr = sqrtf(1.0f - zc*zc); float rad = R * cbrtf(frand()); b->x = rr*cosf(th)*rad; b->y = rr*sinf(th)*rad; b->z = zc*rad; } else { /* recycle: enter at the shell, on the incoming-side cap */ float rho = 0.9f * R * sqrtf(frand()); float phi = 6.2831853f*frand(); b->x = rho*cosf(phi); b->y = rho*sinf(phi); b->z = side * sqrtf(R*R - rho*rho) * 0.999f; } if (!collides(b, check_count, self)) break; } } /* How many bodies are active right now: the density knob, scaled up with render * distance so a larger sphere holds proportionally more shapes (constant near- * field density), clamped to the hard array limit. */ static int active_count(void) { int n = (int)(cfg.density * (cfg.render_dist / RENDER_REF) + 0.5f); if (n < 1) n = 1; if (n > MAX_BODIES) n = MAX_BODIES; return n; } static int clampi(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); } #define MAX_SPECIAL 1000 /* per-type cap of magnifier / glass / mirror bodies */ /* The body array is laid out as [ specials | density field ]. The magnifier, * glass-sphere and mirror-sphere counts are EXTRA bodies (each 0..MAX_SPECIAL) * that sit ahead of the normal field, so they never displace it -- the density * knob still controls exactly as many random shapes as before. A body is only * (re)spawned when its role actually changes or it becomes newly active, so * changing density or render distance grows/shrinks the field tail without * reshuffling everything. */ static int g_filled = 0; /* high-water mark of ever-initialized indices */ static int g_total = 0; /* current active body count */ /* --- perf instrumentation (Step 0: measure before optimizing) ---------------- * g_batches / g_verts count the immediate-mode line submissions per frame -- the * exact CPU/driver load the retained-mode refactor targets. Reset each frame in * the main loop, incremented in draw_edges, shown in the F1 perf HUD. */ static unsigned g_batches = 0; /* glBegin/glEnd line batches this frame */ static unsigned long g_verts = 0; /* vertices submitted this frame */ static unsigned g_drawn = 0; /* bodies that passed frustum cull */ static double g_frustum[6][4]; /* world-space frustum planes (a,b,c,d) */ static int g_vsync = 1; /* F2 toggles glfwSwapInterval(0/1) */ static int g_show_hud = 1; /* F1 toggles the perf HUD */ static GLFWwindow *g_win = NULL; /* the window/canvas, shared with the frame fn */ /* --- retained-mode geometry buffers (one per shape) -------------------------- * g_shape_vbomode[si]: 0 = immediate mode (clocks/dynamics whose topology is * rebuilt each frame), 1 = static VBO+IBO (rigid 3-D shapes), 2 = morphing * 4/5/6-D shape (static topology IBO + per-frame vertex upload to g_dyn_vbo). */ static int g_have_vbo = 0; static GLuint g_dyn_vbo = 0; /* shared streaming vertex buffer */ static GLuint *g_shape_vbo = NULL; /* per static shape (0 otherwise) */ static GLuint *g_shape_ibo = NULL; /* per shape edge-index buffer */ static unsigned char *g_shape_vbomode = NULL; /* --- bloom post-process (Step 2) -------------------------------------------- * A pyramid of half-scaling render targets: the field core is downsampled with * bilinear filtering through BLOOM_LEVELS levels, then the levels are summed * back additively over the sharp scene -> a soft, wide CRT glow for one pass per * body instead of nine. F3 toggles it against the legacy per-body glow. */ #define BLOOM_LEVELS 6 #define BLOOM_GAIN 1.5f #define BLOOM_FOLD 0.78f /* <1 attenuates each wider scale -> halo fades out */ static int g_have_fbo = 0; static int g_bloom = 1; /* F3 toggles bloom vs legacy glow */ static GLuint g_bl_tex[BLOOM_LEVELS]; static GLuint g_bl_fbo[BLOOM_LEVELS]; static int g_bl_w[BLOOM_LEVELS], g_bl_h[BLOOM_LEVELS]; static int g_bl_fbw = 0, g_bl_fbh = 0; /* framebuffer size targets are built for */ static void recompose_field(void) { int mag = (g_mag_idx >= 0) ? clampi(cfg.mag_count, 0, MAX_SPECIAL) : 0; int glass = (g_glass_idx >= 0) ? clampi(cfg.glass_count, 0, MAX_SPECIAL) : 0; int mirror = (g_mirror_idx >= 0) ? clampi(cfg.mirror_count, 0, MAX_SPECIAL) : 0; int special = mag + glass + mirror; if (special > MAX_BODIES) special = MAX_BODIES; int norm = active_count(); if (norm > MAX_BODIES - special) norm = MAX_BODIES - special; if (norm < 0) norm = 0; int total = special + norm; for (int i = 0; i < total; i++) { int role; if (i < mag) role = g_mag_idx; else if (i < mag + glass) role = g_glass_idx; else if (i < special) role = g_mirror_idx; else role = -1; if (i >= g_filled || bodies[i].forced != role) { bodies[i].forced = role; spawn_body(&bodies[i], 1, i, i); } } if (total > g_filled) g_filled = total; g_total = total; } static void rebuild_field(void) { if (cfg.density > MAX_BODIES) cfg.density = MAX_BODIES; if (cfg.density < 1) cfg.density = 1; recompose_field(); } /* ================================================================== */ /* Color. */ /* ================================================================== */ static void hsv_to_rgb(float h, float s, float v, float *r, float *g, float *b) { h = fmodf(h, 360.0f); if (h < 0) h += 360.0f; float c = v * s; float x = c * (1 - fabsf(fmodf(h / 60.0f, 2) - 1)); float m = v - c, rr, gg, bb; if (h < 60) { rr=c; gg=x; bb=0; } else if (h < 120) { rr=x; gg=c; bb=0; } else if (h < 180) { rr=0; gg=c; bb=x; } else if (h < 240) { rr=0; gg=x; bb=c; } else if (h < 300) { rr=x; gg=0; bb=c; } else { rr=c; gg=0; bb=x; } *r = rr + m; *g = gg + m; *b = bb + m; } /* ================================================================== */ /* Self-contained vector stroke font (for the on-screen display). */ /* Each glyph is a polyline list on a 0..4 (x) by 0..6 (y) grid. */ /* PU lifts the pen (start a new stroke); EN ends the glyph. */ /* ================================================================== */ #define PU 127 #define EN (-128) #define G_ static const signed char G_ g_A[] = {0,0, 2,6, 4,0, PU, 1,2, 3,2, EN}; G_ g_B[] = {0,0, 0,6, 3,6, 4,5, 3,3, 0,3, PU, 3,3, 4,1, 3,0, 0,0, EN}; G_ g_C[] = {4,5, 3,6, 1,6, 0,5, 0,1, 1,0, 3,0, 4,1, EN}; G_ g_D[] = {0,0, 0,6, 2,6, 4,4, 4,2, 2,0, 0,0, EN}; G_ g_E[] = {4,6, 0,6, 0,0, 4,0, PU, 0,3, 3,3, EN}; G_ g_F[] = {4,6, 0,6, 0,0, PU, 0,3, 3,3, EN}; G_ g_G[] = {4,5, 3,6, 1,6, 0,5, 0,1, 1,0, 3,0, 4,1, 4,3, 2,3, EN}; G_ g_H[] = {0,0, 0,6, PU, 4,0, 4,6, PU, 0,3, 4,3, EN}; G_ g_I[] = {1,0, 3,0, PU, 2,0, 2,6, PU, 1,6, 3,6, EN}; G_ g_J[] = {3,6, 3,1, 2,0, 1,0, 0,1, EN}; G_ g_K[] = {0,0, 0,6, PU, 4,6, 0,3, 4,0, EN}; G_ g_L[] = {0,6, 0,0, 4,0, EN}; G_ g_M[] = {0,0, 0,6, 2,3, 4,6, 4,0, EN}; G_ g_N[] = {0,0, 0,6, 4,0, 4,6, EN}; G_ g_O[] = {1,0, 3,0, 4,1, 4,5, 3,6, 1,6, 0,5, 0,1, 1,0, EN}; G_ g_P[] = {0,0, 0,6, 3,6, 4,5, 4,4, 3,3, 0,3, EN}; G_ g_Q[] = {1,0, 3,0, 4,1, 4,5, 3,6, 1,6, 0,5, 0,1, 1,0, PU, 2,2, 4,0, EN}; G_ g_R[] = {0,0, 0,6, 3,6, 4,5, 4,4, 3,3, 0,3, PU, 2,3, 4,0, EN}; G_ g_S[] = {4,5, 3,6, 1,6, 0,5, 1,3, 3,3, 4,2, 3,0, 1,0, 0,1, EN}; G_ g_T[] = {0,6, 4,6, PU, 2,6, 2,0, EN}; G_ g_U[] = {0,6, 0,1, 1,0, 3,0, 4,1, 4,6, EN}; G_ g_V[] = {0,6, 2,0, 4,6, EN}; G_ g_W[] = {0,6, 1,0, 2,3, 3,0, 4,6, EN}; G_ g_X[] = {0,0, 4,6, PU, 0,6, 4,0, EN}; G_ g_Y[] = {0,6, 2,3, 4,6, PU, 2,3, 2,0, EN}; G_ g_Z[] = {0,6, 4,6, 0,0, 4,0, EN}; G_ g_0[] = {1,0, 3,0, 4,1, 4,5, 3,6, 1,6, 0,5, 0,1, 1,0, PU, 1,1, 3,5, EN}; G_ g_1[] = {1,5, 2,6, 2,0, PU, 1,0, 3,0, EN}; G_ g_2[] = {0,5, 1,6, 3,6, 4,5, 4,4, 0,0, 4,0, EN}; G_ g_3[] = {0,6, 4,6, 2,3, PU, 2,3, 4,2, 4,1, 3,0, 1,0, 0,1, EN}; G_ g_4[] = {3,0, 3,6, 0,2, 4,2, EN}; G_ g_5[] = {4,6, 0,6, 0,3, 3,3, 4,2, 4,1, 3,0, 1,0, 0,1, EN}; G_ g_6[] = {4,5, 3,6, 1,6, 0,5, 0,1, 1,0, 3,0, 4,1, 4,2, 3,3, 0,3, EN}; G_ g_7[] = {0,6, 4,6, 2,0, EN}; G_ g_8[] = {1,3, 0,4, 0,5, 1,6, 3,6, 4,5, 4,4, 3,3, 1,3, 0,2, 0,1, 1,0, 3,0, 4,1, 4,2, 3,3, EN}; G_ g_9[] = {0,1, 1,0, 3,0, 4,1, 4,5, 3,6, 1,6, 0,5, 0,4, 1,3, 4,3, EN}; G_ g_pct[] = {0,0, 4,6, PU, 0,5,1,5,1,6,0,6,0,5, PU, 3,0,4,0,4,1,3,1,3,0, EN}; G_ g_slash[] = {0,0, 4,6, EN}; G_ g_plus[] = {2,1, 2,5, PU, 0,3, 4,3, EN}; G_ g_minus[] = {0,3, 4,3, EN}; G_ g_dot[] = {2,0, 2,1, EN}; G_ g_lbr[] = {3,6, 1,6, 1,0, 3,0, EN}; G_ g_rbr[] = {1,6, 3,6, 3,0, 1,0, EN}; G_ g_lpar[] = {3,6, 1,4, 1,2, 3,0, EN}; G_ g_rpar[] = {1,6, 3,4, 3,2, 1,0, EN}; G_ g_dash[] = {0,3, 4,3, EN}; /* same as minus, used by '/'-fallback */ static const signed char *glyph(char c) { switch (toupper((unsigned char)c)) { case 'A': return g_A; case 'B': return g_B; case 'C': return g_C; case 'D': return g_D; case 'E': return g_E; case 'F': return g_F; case 'G': return g_G; case 'H': return g_H; case 'I': return g_I; case 'J': return g_J; case 'K': return g_K; case 'L': return g_L; case 'M': return g_M; case 'N': return g_N; case 'O': return g_O; case 'P': return g_P; case 'Q': return g_Q; case 'R': return g_R; case 'S': return g_S; case 'T': return g_T; case 'U': return g_U; case 'V': return g_V; case 'W': return g_W; case 'X': return g_X; case 'Y': return g_Y; case 'Z': return g_Z; case '0': return g_0; case '1': return g_1; case '2': return g_2; case '3': return g_3; case '4': return g_4; case '5': return g_5; case '6': return g_6; case '7': return g_7; case '8': return g_8; case '9': return g_9; case '%': return g_pct; case '/': return g_slash; case '+': return g_plus; case '-': return g_minus; case '.': return g_dot; case '[': return g_lbr; case ']': return g_rbr; case '(': return g_lpar; case ')': return g_rpar; case '_': return g_dash; default: return NULL; /* space and unknowns advance only */ } } /* Draw a string with its bottom-left at (x,y); glyph height = h px. */ static float stroke_text(const char *str, float x, float y, float h) { float sx = h / 6.0f, sy = h / 6.0f; float advance = sx * 6.0f; /* 4 wide + 2 spacing */ float cx = x; for (const char *p = str; *p; p++) { const signed char *g = glyph(*p); if (g) { glBegin(GL_LINE_STRIP); for (int i = 0;;) { signed char a = g[i++]; if (a == EN) break; if (a == PU) { glEnd(); glBegin(GL_LINE_STRIP); continue; } signed char b = g[i++]; glVertex2f(cx + a * sx, y + b * sy); } glEnd(); } cx += advance; } return cx; } /* ================================================================== */ /* On-screen display. */ /* ================================================================== */ static void render_osd(int fbw, int fbh, float alpha) { if (alpha <= 0.001f) return; glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, fbw, 0, fbh); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); const float h = 13.0f; const float left = 22.0f; const float lineStep = h + 9.0f; const int nlines = 25; const float panelW = 384.0f; float panelTop = fbh - 18.0f; float panelH = nlines * lineStep + 16.0f; /* dim backdrop for readability (normal alpha blend) */ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.02f, 0.03f, 0.06f, 0.55f * alpha); glBegin(GL_QUADS); glVertex2f(8, panelTop + 10); glVertex2f(panelW, panelTop + 10); glVertex2f(panelW, panelTop + 10 - panelH); glVertex2f(8, panelTop + 10 - panelH); glEnd(); /* subtle border */ glColor4f(0.3f, 0.6f, 0.9f, 0.35f * alpha); glBegin(GL_LINE_LOOP); glVertex2f(8, panelTop + 10); glVertex2f(panelW, panelTop + 10); glVertex2f(panelW, panelTop + 10 - panelH); glVertex2f(8, panelTop + 10 - panelH); glEnd(); /* additive, theme-tinted text with a faint glow pass */ glBlendFunc(GL_SRC_ALPHA, GL_ONE); float tr, tg, tb; hsv_to_rgb(cfg.hue, 0.35f, 1.0f, &tr, &tg, &tb); char buf[80]; float y = panelTop - h; /* title */ glColor4f(tr*alpha, tg*alpha, tb*alpha, alpha); glLineWidth(2.4f); stroke_text("- VECTORGONS -", left, y, h + 3); glLineWidth(1.6f); y -= lineStep + 6; #define LINE(...) do { snprintf(buf,sizeof buf,__VA_ARGS__); \ /* glow pass */ \ glColor4f(tr*alpha*0.6f,tg*alpha*0.6f,tb*alpha*0.6f, alpha*0.5f); \ glLineWidth(3.0f); stroke_text(buf,left,y,h); \ /* crisp pass */ \ glColor4f(tr*alpha,tg*alpha,tb*alpha,alpha); \ glLineWidth(1.5f); stroke_text(buf,left,y,h); \ y -= lineStep; } while(0) LINE("SPEED PGUP/DN %.0f%%%s", cfg.speed, cfg.speed < 0 ? " REV" : ""); LINE("TUMBLE Q/E %.0f%%", cfg.tumble); LINE("TUMBLE VAR T/Y %.0f%%", cfg.tumble_var); LINE("RENDER DST Z/X %.0f", cfg.render_dist); LINE("DENSITY +/- %d", active_count()); LINE("SIZE MIN U/J %.1f", cfg.size_min); LINE("SIZE MAX I/K %.1f", cfg.size_max); LINE("HUE [ / ] %.0f", cfg.hue); if (cfg.hue_cycle > 0.001f) LINE("HUE CYCLE C/V %.0f%%", cfg.hue_cycle); else LINE("%s", "HUE CYCLE C/V OFF"); LINE("COLOR M %s", cfg.multicolor ? "MULTI" : "SINGLE"); LINE("GLOW O/L %.0f%%", cfg.glow); LINE("FLICKER G/H %.0f%%", cfg.flicker); LINE("MAGNIFY B/P %d", cfg.mag_count); LINE("GLASS BALL 1/2 %d", cfg.glass_count); LINE("MIRROR 3/4 %d", cfg.mirror_count); LINE("SHAPES N %s", cfg.cycle_shapes ? "CYCLE" : "RANDOM"); LINE("MOVE CAM WASD %+.0f %+.0f", cam_x, cam_y); LINE("ROTATE CAM ARROWS %+.0f %+.0f", cam_yaw, cam_pitch); LINE("FULLSCREEN F/F11 %s", cfg.fullscreen ? "ON" : "OFF"); LINE("PERF HUD F1 %s", g_show_hud ? "ON" : "OFF"); LINE("VSYNC F2 %s", g_vsync ? "ON" : "OFF"); LINE("GLOW MODE F3 %s", (g_have_fbo && g_bloom) ? "BLOOM" : "LEGACY"); LINE("PAUSE SPACE %s", cfg.paused ? "PAUSED" : "RUNNING"); LINE("%s", "QUIT ESC"); #undef LINE glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); /* leave additive blending on for the next 3-D frame */ } /* Always-on performance HUD (top-right). The measurement tool for the refactor: * FPS / frame time, active body count, and the per-frame immediate-mode line * batch + vertex submission counts -- i.e. exactly the CPU/driver work that the * retained-mode (VBO/glDrawElements) conversion is meant to collapse. */ static void render_perf_hud(int fbw, int fbh, float fps, float ms) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, fbw, 0, fbh); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glBlendFunc(GL_SRC_ALPHA, GL_ONE); const float h = 13.0f, step = h + 8.0f, x = (float)fbw - 220.0f; float y = (float)fbh - 26.0f; char buf[64]; glLineWidth(1.8f); glColor4f(fps < 55.0f ? 1.0f : 0.4f, fps < 55.0f ? 0.55f : 1.0f, 0.35f, 1.0f); snprintf(buf, sizeof buf, "FPS %.0f %.2f MS", fps, ms); stroke_text(buf, x, y, h); y -= step; glColor4f(0.5f, 0.8f, 1.0f, 1.0f); glLineWidth(1.5f); snprintf(buf, sizeof buf, "BODIES %d", g_total); stroke_text(buf, x, y, h); y -= step; snprintf(buf, sizeof buf, "DRAWN %u", g_drawn); stroke_text(buf, x, y, h); y -= step; snprintf(buf, sizeof buf, "BATCHES %u", g_batches); stroke_text(buf, x, y, h); y -= step; snprintf(buf, sizeof buf, "VERTS %luK", g_verts / 1000UL); stroke_text(buf, x, y, h); y -= step; snprintf(buf, sizeof buf, "VSYNC %s", g_vsync ? "ON" : "OFF"); stroke_text(buf, x, y, h); y -= step; snprintf(buf, sizeof buf, "GLOW %s", (g_have_fbo && g_bloom) ? "BLOOM" : "LEGACY"); stroke_text(buf, x, y, h); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } /* ================================================================== */ /* Input. */ /* ================================================================== */ static double last_input_time = 0.0; static void clampf(float *v, float lo, float hi) { if (*v < lo) *v = lo; if (*v > hi) *v = hi; } static void normalize_sizes(void) { if (cfg.size_min < 0.4f) cfg.size_min = 0.4f; if (cfg.size_max < cfg.size_min) cfg.size_max = cfg.size_min; if (cfg.size_max > 54.0f) cfg.size_max = 54.0f; } /* ================================================================== */ /* Settings persistence: a plain key=value file under $HOME so the */ /* user's last-used settings become the defaults next launch. */ /* ================================================================== */ static void settings_path(char *buf, size_t n) { const char *home = getenv("HOME"); /* Unix / macOS */ if (!home || !*home) home = getenv("USERPROFILE"); /* Windows */ if (home && *home) snprintf(buf, n, "%s/.vectorgons", home); else snprintf(buf, n, ".vectorgons"); } static void save_settings(void) { #ifdef __EMSCRIPTEN__ return; /* no persistent filesystem in the browser */ #else char path[1024]; settings_path(path, sizeof path); FILE *f = fopen(path, "w"); if (!f) return; fprintf(f, "# Vectorgons settings (auto-saved on exit)\n"); fprintf(f, "speed=%g\n", cfg.speed); fprintf(f, "tumble=%g\n", cfg.tumble); fprintf(f, "tumble_var=%g\n", cfg.tumble_var); fprintf(f, "render_dist=%g\n", cfg.render_dist); fprintf(f, "density=%d\n", cfg.density); fprintf(f, "size_min=%g\n", cfg.size_min); fprintf(f, "size_max=%g\n", cfg.size_max); fprintf(f, "hue=%g\n", cfg.hue); fprintf(f, "hue_cycle=%g\n", cfg.hue_cycle); fprintf(f, "multicolor=%d\n", cfg.multicolor); fprintf(f, "cycle_shapes=%d\n", cfg.cycle_shapes); fprintf(f, "glow=%g\n", cfg.glow); fprintf(f, "flicker=%g\n", cfg.flicker); fprintf(f, "mag_count=%d\n", cfg.mag_count); fprintf(f, "glass_count=%d\n", cfg.glass_count); fprintf(f, "mirror_count=%d\n", cfg.mirror_count); fprintf(f, "fullscreen=%d\n", cfg.fullscreen); fclose(f); #endif } static void load_settings(void) { #ifdef __EMSCRIPTEN__ return; /* no persistent filesystem in the browser */ #else char path[1024]; settings_path(path, sizeof path); FILE *f = fopen(path, "r"); if (!f) return; /* first run: keep built-in defaults */ char line[256]; while (fgets(line, sizeof line, f)) { if (line[0] == '#' || line[0] == '\n') continue; char *eq = strchr(line, '='); if (!eq) continue; *eq = '\0'; const char *key = line, *val = eq + 1; double d = atof(val); if (!strcmp(key, "speed")) cfg.speed = (float)d; else if (!strcmp(key, "tumble")) cfg.tumble = (float)d; else if (!strcmp(key, "tumble_var")) cfg.tumble_var = (float)d; else if (!strcmp(key, "render_dist")) cfg.render_dist = (float)d; else if (!strcmp(key, "density")) cfg.density = (int)d; else if (!strcmp(key, "size_min")) cfg.size_min = (float)d; else if (!strcmp(key, "size_max")) cfg.size_max = (float)d; else if (!strcmp(key, "hue")) cfg.hue = (float)d; else if (!strcmp(key, "hue_cycle")) cfg.hue_cycle = (float)d; else if (!strcmp(key, "multicolor")) cfg.multicolor = (int)d; else if (!strcmp(key, "cycle_shapes")) cfg.cycle_shapes = (int)d; else if (!strcmp(key, "glow")) cfg.glow = (float)d; else if (!strcmp(key, "flicker")) cfg.flicker = (float)d; else if (!strcmp(key, "mag_count")) cfg.mag_count = (int)d; else if (!strcmp(key, "glass_count")) cfg.glass_count = (int)d; else if (!strcmp(key, "mirror_count")) cfg.mirror_count = (int)d; else if (!strcmp(key, "fullscreen")) cfg.fullscreen = (int)d; } fclose(f); /* defend against a hand-edited or stale file by clamping into range */ clampf(&cfg.speed, -100, 100); clampf(&cfg.tumble, 0, 100); clampf(&cfg.tumble_var, 0, 100); clampf(&cfg.render_dist, 40, 1520); cfg.mag_count = clampi(cfg.mag_count, 0, MAX_SPECIAL); cfg.glass_count = clampi(cfg.glass_count, 0, MAX_SPECIAL); cfg.mirror_count = clampi(cfg.mirror_count, 0, MAX_SPECIAL); clampf(&cfg.hue_cycle, 0, 100); clampf(&cfg.glow, 0, 100); clampf(&cfg.flicker, 0, 100); cfg.hue = fmodf(cfg.hue, 360.0f); if (cfg.hue < 0) cfg.hue += 360.0f; if (cfg.density < 1) cfg.density = 1; if (cfg.density > MAX_BODIES) cfg.density = MAX_BODIES; normalize_sizes(); cfg.multicolor = cfg.multicolor ? 1 : 0; cfg.cycle_shapes = cfg.cycle_shapes ? 1 : 0; cfg.fullscreen = cfg.fullscreen ? 1 : 0; #endif } #ifdef __EMSCRIPTEN__ /* Keep cfg.fullscreen in sync with the browser for *any* fullscreen change * (e.g. the user pressing Esc, which doesn't go through toggle_fullscreen). The * actual canvas/viewport sizing is handled per-frame in render_frame(), which * tracks the canvas's real displayed size. */ static EM_BOOL on_fullscreen_change(int type, const EmscriptenFullscreenChangeEvent *e, void *user) { (void)type; (void)user; cfg.fullscreen = e->isFullscreen ? 1 : 0; return EM_FALSE; } #endif static void toggle_fullscreen(GLFWwindow *win) { cfg.fullscreen = !cfg.fullscreen; #ifdef __EMSCRIPTEN__ (void)win; /* browser: request canvas fullscreen */ /* Just enter/exit fullscreen here; on_fullscreen_change() does the canvas * resize so the render fills the screen at the correct (screen) aspect. */ if (cfg.fullscreen) emscripten_request_fullscreen("#canvas", 1); else emscripten_exit_fullscreen(); #else static int wx = 100, wy = 100, ww = 1100, wh = 760; if (cfg.fullscreen) { glfwGetWindowPos(win, &wx, &wy); glfwGetWindowSize(win, &ww, &wh); GLFWmonitor *mon = glfwGetPrimaryMonitor(); const GLFWvidmode *m = glfwGetVideoMode(mon); glfwSetWindowMonitor(win, mon, 0, 0, m->width, m->height, m->refreshRate); } else { glfwSetWindowMonitor(win, NULL, wx, wy, ww, wh, 0); } #endif } static void key_cb(GLFWwindow *win, int key, int sc, int action, int mods) { (void)sc; (void)mods; if (action != GLFW_PRESS && action != GLFW_REPEAT) return; last_input_time = glfwGetTime(); /* keeps the OSD awake */ switch (key) { case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(win, 1); break; /* WASD pans the camera and the arrow keys rotate it; both are polled * per-frame in the main loop for smooth, frame-rate-independent motion. */ case GLFW_KEY_PAGE_UP: cfg.speed += 2.5f; clampf(&cfg.speed, -100,100); break; case GLFW_KEY_PAGE_DOWN: cfg.speed -= 2.5f; clampf(&cfg.speed, -100,100); break; case GLFW_KEY_E: cfg.tumble += 2.5f; clampf(&cfg.tumble,0,100); break; case GLFW_KEY_Q: cfg.tumble -= 2.5f; clampf(&cfg.tumble,0,100); break; case GLFW_KEY_T: cfg.tumble_var += 5; clampf(&cfg.tumble_var,0,100); break; case GLFW_KEY_Y: cfg.tumble_var -= 5; clampf(&cfg.tumble_var,0,100); break; case GLFW_KEY_X: cfg.render_dist += 10; clampf(&cfg.render_dist,40,1520); break; case GLFW_KEY_Z: cfg.render_dist -= 10; clampf(&cfg.render_dist,40,1520); break; case GLFW_KEY_U: cfg.size_min += 0.2f; normalize_sizes(); break; case GLFW_KEY_J: cfg.size_min -= 0.2f; normalize_sizes(); break; case GLFW_KEY_I: cfg.size_max += 0.2f; normalize_sizes(); break; case GLFW_KEY_K: cfg.size_max -= 0.2f; normalize_sizes(); break; case GLFW_KEY_LEFT_BRACKET: cfg.hue -= 6; if (cfg.hue < 0) cfg.hue += 360; break; case GLFW_KEY_RIGHT_BRACKET: cfg.hue += 6; if (cfg.hue >= 360) cfg.hue -= 360; break; case GLFW_KEY_C: cfg.hue_cycle += 5; clampf(&cfg.hue_cycle,0,100); break; case GLFW_KEY_V: cfg.hue_cycle -= 5; clampf(&cfg.hue_cycle,0,100); break; case GLFW_KEY_M: cfg.multicolor = !cfg.multicolor; break; case GLFW_KEY_N: cfg.cycle_shapes = !cfg.cycle_shapes; spawn_counter = 0; break; case GLFW_KEY_O: cfg.glow += 5; clampf(&cfg.glow,0,100); break; case GLFW_KEY_L: cfg.glow -= 5; clampf(&cfg.glow,0,100); break; case GLFW_KEY_G: cfg.flicker += 5; clampf(&cfg.flicker,0,100); break; case GLFW_KEY_H: cfg.flicker -= 5; clampf(&cfg.flicker,0,100); break; case GLFW_KEY_B: cfg.mag_count = clampi(cfg.mag_count+1,0,MAX_SPECIAL); break; /* more lenses */ case GLFW_KEY_P: cfg.mag_count = clampi(cfg.mag_count-1,0,MAX_SPECIAL); break; case GLFW_KEY_1: cfg.glass_count = clampi(cfg.glass_count+1,0,MAX_SPECIAL); break; /* more glass balls */ case GLFW_KEY_2: cfg.glass_count = clampi(cfg.glass_count-1,0,MAX_SPECIAL); break; case GLFW_KEY_3: cfg.mirror_count = clampi(cfg.mirror_count+1,0,MAX_SPECIAL); break; /* more mirror balls */ case GLFW_KEY_4: cfg.mirror_count = clampi(cfg.mirror_count-1,0,MAX_SPECIAL); break; /* +/- adjust the live on-screen count and back-solve the baseline * density, so a press always moves the count even when render-distance * scaling has the active total pinned at MAX_BODIES. */ case GLFW_KEY_EQUAL: { int a = active_count() + 10; if (a > MAX_BODIES) a = MAX_BODIES; cfg.density = (int)(a * (RENDER_REF / cfg.render_dist) + 0.5f); if (cfg.density < 1) cfg.density = 1; if (cfg.density > MAX_BODIES) cfg.density = MAX_BODIES; rebuild_field(); break; } case GLFW_KEY_MINUS: { int a = active_count() - 10; if (a < 1) a = 1; cfg.density = (int)(a * (RENDER_REF / cfg.render_dist) + 0.5f); if (cfg.density < 1) cfg.density = 1; if (cfg.density > MAX_BODIES) cfg.density = MAX_BODIES; break; } case GLFW_KEY_F: case GLFW_KEY_F11: toggle_fullscreen(win); break; case GLFW_KEY_F1: g_show_hud = !g_show_hud; break; /* perf HUD */ case GLFW_KEY_F2: g_vsync = !g_vsync; glfwSwapInterval(g_vsync); break; /* VSync */ case GLFW_KEY_F3: g_bloom = !g_bloom; break; /* bloom vs legacy glow */ case GLFW_KEY_SPACE: cfg.paused = !cfg.paused; break; default: break; } } /* ================================================================== */ /* Projection + body wireframe (modelview transform must be set). */ /* ================================================================== */ static void rot2(double *c, int a, int b, double th) { double ca = cos(th), sa = sin(th), x = c[a], y = c[b]; c[a] = x*ca - y*sa; c[b] = x*sa + y*ca; } /* Produce 3-D vertices for a body. 3-D shapes are copied through; * higher-D shapes are rotated in their own dimension (driven by the * tumble angle + a per-body phase) and perspective-projected down to * 3-D, then renormalized to the unit sphere so the bounding radius * stays <= 1 (which keeps the no-overlap guarantee intact). */ static void project_body(const Solid *s, float angf, float phasef, float out[][3]) { int d = s->dim; if (d == 3) { for (int i = 0; i < s->nv; i++) { out[i][0] = s->v[i][0]; out[i][1] = s->v[i][1]; out[i][2] = s->v[i][2]; } return; } double ang = angf, ph = phasef; for (int i = 0; i < s->nv; i++) { double c[MAXD]; for (int k = 0; k < MAXD; k++) c[k] = (k < d) ? s->v[i][k] : 0.0; rot2(c, 0, 1, ang*0.5); if (d >= 4) { rot2(c, 2, 3, ang*0.9 + ph); rot2(c, 0, 3, ang*0.6); } if (d >= 5) { rot2(c, 1, 4, ang*0.7 + ph*0.5); rot2(c, 3, 4, ang*0.45); } if (d >= 6) { rot2(c, 2, 5, ang*0.8); rot2(c, 4, 5, ang*0.4 + ph*0.3); } for (int k = d-1; k >= 3; k--) { /* project k-D -> (k-1)-D */ double den = 4.0 - c[k]; if (den < 0.5) den = 0.5; double f = 4.0 / den; for (int j = 0; j < k; j++) c[j] *= f; } out[i][0] = (float)c[0]; out[i][1] = (float)c[1]; out[i][2] = (float)c[2]; } double mr2 = 0; for (int i = 0; i < s->nv; i++) { double r2 = (double)out[i][0]*out[i][0] + (double)out[i][1]*out[i][1] + (double)out[i][2]*out[i][2]; if (r2 > mr2) mr2 = r2; } if (mr2 > 1e-9) { float inv = (float)(1.0/sqrt(mr2)); for (int i = 0; i < s->nv; i++) { out[i][0]*=inv; out[i][1]*=inv; out[i][2]*=inv; } } } /* Build a VBO+IBO for every shape, classifying each into one of the three draw * modes. Called once after the GL context exists and all shapes are built. */ static void init_gl_buffers(void) { /* framebuffer-object entry points (bloom) -- independent of VBO support */ vgGenFramebuffers = (VG_GENFRAMEBUFFERS) glfwGetProcAddress("glGenFramebuffers"); vgBindFramebuffer = (VG_BINDFRAMEBUFFER) glfwGetProcAddress("glBindFramebuffer"); vgFramebufferTexture2D = (VG_FRAMEBUFFERTEXTURE2D) glfwGetProcAddress("glFramebufferTexture2D"); vgDeleteFramebuffers = (VG_DELETEFRAMEBUFFERS) glfwGetProcAddress("glDeleteFramebuffers"); vgCheckFramebufferStatus = (VG_CHECKFRAMEBUFFERSTATUS) glfwGetProcAddress("glCheckFramebufferStatus"); if (vgGenFramebuffers && vgBindFramebuffer && vgFramebufferTexture2D && vgDeleteFramebuffers) g_have_fbo = 1; vgGenBuffers = (VG_GENBUFFERS) glfwGetProcAddress("glGenBuffers"); vgBindBuffer = (VG_BINDBUFFER) glfwGetProcAddress("glBindBuffer"); vgBufferData = (VG_BUFFERDATA) glfwGetProcAddress("glBufferData"); if (!vgGenBuffers || !vgBindBuffer || !vgBufferData) return; /* keep immediate mode */ g_shape_vbo = (GLuint *)calloc((size_t)num_shapes, sizeof(GLuint)); g_shape_ibo = (GLuint *)calloc((size_t)num_shapes, sizeof(GLuint)); g_shape_vbomode = (unsigned char *)calloc((size_t)num_shapes, 1); if (!g_shape_vbo || !g_shape_ibo || !g_shape_vbomode) return; vgGenBuffers(1, &g_dyn_vbo); unsigned short idx[MAX_EDGES * 2]; float vbuf[MAX_VERTS * 3]; for (int si = 0; si < num_shapes; si++) { Solid *s = &solids[si]; int mode; if (s->dim != 3) { mode = 2; /* morph: positions only */ } else { mode = 1; /* rigid 3-D: fully static */ for (int k = 0; k < dyn_count; k++) if (dyn_solid[k] == si) mode = 0; for (int k = 0; k < 4; k++) if (clock_idx[k] == si) mode = 0; } g_shape_vbomode[si] = (unsigned char)mode; if (mode == 0) continue; /* topology changes -> immediate */ for (int e = 0; e < s->ne; e++) { /* static topology index buffer */ idx[e*2] = (unsigned short)s->e[e][0]; idx[e*2+1] = (unsigned short)s->e[e][1]; } vgGenBuffers(1, &g_shape_ibo[si]); vgBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_shape_ibo[si]); vgBufferData(GL_ELEMENT_ARRAY_BUFFER, (VGsizeiptr)((size_t)s->ne * 2 * sizeof(unsigned short)), idx, GL_STATIC_DRAW); if (mode == 1) { /* rigid: upload vertices once */ for (int i = 0; i < s->nv; i++) { vbuf[i*3] = s->v[i][0]; vbuf[i*3+1] = s->v[i][1]; vbuf[i*3+2] = s->v[i][2]; } vgGenBuffers(1, &g_shape_vbo[si]); vgBindBuffer(GL_ARRAY_BUFFER, g_shape_vbo[si]); vgBufferData(GL_ARRAY_BUFFER, (VGsizeiptr)((size_t)s->nv * 3 * sizeof(float)), vbuf, GL_STATIC_DRAW); } } vgBindBuffer(GL_ARRAY_BUFFER, 0); vgBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); g_have_vbo = 1; } /* Draw one solid's edges. For VBO-backed shapes the vertex source + index buffer * are bound by the caller (per body) and this is a single glDrawElements; clocks * and dynamics fall back to immediate mode (their topology is rebuilt each frame * so a static index buffer would be stale). */ static void draw_edges(const Solid *s, float p[][3]) { g_batches++; g_verts += (unsigned long)s->ne * 2u; if (g_have_vbo && g_shape_vbomode[s - solids]) { glDrawElements(GL_LINES, s->ne * 2, GL_UNSIGNED_SHORT, (const void *)0); return; } /* Immediate-mode path (clocks / dynamics, whose topology is rebuilt each * frame). Detach the vertex buffer / client array first: WebGL's legacy-GL * immediate-mode emulation otherwise folds the stale array state into the * glVertex stream and draws garbage long lines. (No-op on desktop, where * immediate mode already ignores client arrays.) glVertex3f is also better * supported by the emulation than the 3fv vector form. */ if (g_have_vbo) { glDisableClientState(GL_VERTEX_ARRAY); vgBindBuffer(GL_ARRAY_BUFFER, 0); } glBegin(GL_LINES); for (int e = 0; e < s->ne; e++) { const float *a = p[s->e[e][0]], *b = p[s->e[e][1]]; glVertex3f(a[0], a[1], a[2]); glVertex3f(b[0], b[1], b[2]); } glEnd(); if (g_have_vbo) glEnableClientState(GL_VERTEX_ARRAY); } /* Build the 6 world-space view-frustum planes from the current modelview * * projection (Gribb-Hartmann), normalized so plane[i]·(x,y,z,1) gives signed * world distance. Used to cull bodies that can't be on screen before the * (relatively expensive) projection + draw -- the field is a full sphere around * the camera, so most bodies are behind or beside the view. */ static void build_frustum(void) { double mv[16], pj[16], m[16]; glGetDoublev(GL_MODELVIEW_MATRIX, mv); glGetDoublev(GL_PROJECTION_MATRIX, pj); for (int c = 0; c < 4; c++) for (int r = 0; r < 4; r++) { double s = 0; for (int k = 0; k < 4; k++) s += pj[k*4 + r] * mv[c*4 + k]; m[c*4 + r] = s; } for (int i = 0; i < 6; i++) { /* L,R,B,T,N,F = row4 +/- row{x,y,z} */ int sign = (i & 1) ? -1 : 1, row = i / 2; for (int j = 0; j < 4; j++) g_frustum[i][j] = m[j*4 + 3] + sign * m[j*4 + row]; double a = g_frustum[i][0], b = g_frustum[i][1], c = g_frustum[i][2]; double len = sqrt(a*a + b*b + c*c); if (len > 1e-12) for (int j = 0; j < 4; j++) g_frustum[i][j] /= len; } } static int sphere_visible(double x, double y, double z, double rad) { for (int i = 0; i < 6; i++) if (g_frustum[i][0]*x + g_frustum[i][1]*y + g_frustum[i][2]*z + g_frustum[i][3] < -rad) return 0; return 1; } /* ================================================================== */ /* Help / main. */ /* ================================================================== */ static void print_help(void) { printf( "\nVECTORGONS — vector solids & asteroids tumbling through space\n" "------------------------------------------------------------\n" " W/A/S/D move (pan) camera Arrows rotate camera\n" " PgUp/PgDn approach speed (PgDn past 0 = reverse)\n" " Q / E tumble rate\n" " T / Y tumble variance Z / X render distance\n" " U / J size min I / K size max\n" " [ / ] hue C / V hue-cycle rate\n" " O / L glow G / H flicker\n" " B / P magnifier count 1 / 2 glass-ball count\n" " 3 / 4 mirror-ball count (each 0..1000, extra of the density)\n" " + / - density M color N shapes\n" " F / F11 fullscreen Space pause Esc quit\n" " F1 perf HUD F2 VSync on/off (uncap FPS)\n" " F3 bloom / legacy glow\n\n" " (all settings are also shown in the on-screen display)\n\n"); fflush(stdout); } /* ---- Windows screensaver support (compile with -DSCREENSAVER -> .scr) ---- * A .scr is just a GUI exe Windows launches with: /s (run full-screen), * /p HWND (preview) or /c (configure). We run full-screen for /s and exit on * any input; preview is skipped; /c shows a short info dialog. The saver uses * whatever settings the main app last saved to ~/.vectorgons. */ static int g_screensaver = 0; static int g_quit = 0; static double ss_mx = 0, ss_my = 0; static int ss_have = 0; static void ss_key_cb(GLFWwindow *w,int k,int sc,int a,int m){(void)w;(void)k;(void)sc;(void)a;(void)m; g_quit=1;} static void ss_btn_cb(GLFWwindow *w,int b,int a,int m){(void)w;(void)b;(void)a;(void)m; g_quit=1;} static void ss_pos_cb(GLFWwindow *w,double x,double y){(void)w; if(!ss_have){ ss_mx=x; ss_my=y; ss_have=1; return; } if(fabs(x-ss_mx)>6.0 || fabs(y-ss_my)>6.0) g_quit=1; /* a real move ends it */ } /* ================================================================== */ /* Magnifying glass: grab the rendered field into a texture and draw */ /* each lens body as a screen-space disc that samples that texture */ /* with a radial zoom -- so it really magnifies whatever is behind it. */ /* ================================================================== */ static int next_pot(int x){ int p=1; while(p 1 ? w / 2 : 1; h = h > 1 ? h / 2 : 1; g_bl_w[i] = w; g_bl_h[i] = h; glBindTexture(GL_TEXTURE_2D, g_bl_tex[i]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); vgBindFramebuffer(GL_FRAMEBUFFER, g_bl_fbo[i]); vgFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_bl_tex[i], 0); if (vgCheckFramebufferStatus && vgCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { vgBindFramebuffer(GL_FRAMEBUFFER, 0); g_have_fbo = 0; /* fall back to legacy glow for good */ return 0; } } vgBindFramebuffer(GL_FRAMEBUFFER, 0); g_bl_fbw = fbw; g_bl_fbh = fbh; return 1; } /* Draw a unit quad in ortho [0,1] with the given source texcoord rectangle. */ static void bloom_quad(float u0, float v0, float u1, float v1) { glBegin(GL_QUADS); glTexCoord2f(u0, v0); glVertex2f(0, 0); glTexCoord2f(u1, v0); glVertex2f(1, 0); glTexCoord2f(u1, v1); glVertex2f(1, 1); glTexCoord2f(u0, v1); glVertex2f(0, 1); glEnd(); } /* 3x3 tent-filter upsample of the currently bound source texture, drawn * additively over a unit quad. (tx,ty) = one source texel; the nine weighted, * texel-offset taps blend smoothly, so a small level expands into a wide soft * gradient with no blocky bilinear cells. Weights sum to 1 (energy-preserving). */ static void bloom_tent(float tx, float ty) { static const float ox[9] = {-1, 0, 1, -1, 0, 1, -1, 0, 1}; static const float oy[9] = {-1,-1,-1, 0, 0, 0, 1, 1, 1}; static const float wt[9] = { 1, 2, 1, 2, 4, 2, 1, 2, 1}; for (int k = 0; k < 9; k++) { float c = wt[k] / 16.0f * BLOOM_FOLD, dx = ox[k]*tx, dy = oy[k]*ty; glColor3f(c, c, c); bloom_quad(dx, dy, 1 + dx, 1 + dy); } } /* Dual-filter bloom: downsample the field core through the pyramid, then tent- * upsample each coarse level additively into the next finer one, accumulating * every scale into level 0. Summing the scales this way (rather than blitting * each straight to full screen) yields a smooth, ever-widening, fading halo * around the sharp vectors -- the old-CRT look. `amt` is cfg.glow/100. */ static void build_bloom(int fbw, int fbh, float amt) { if (!ensure_bloom_targets(fbw, fbh)) return; grab_scene(fbw, fbh); /* g_scene_tex = field core */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, 1, 0, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glEnable(GL_TEXTURE_2D); /* downsample (overwrite): level 0 from the POT grab's valid sub-rect */ glDisable(GL_BLEND); glColor3f(1, 1, 1); for (int i = 0; i < BLOOM_LEVELS; i++) { vgBindFramebuffer(GL_FRAMEBUFFER, g_bl_fbo[i]); glViewport(0, 0, g_bl_w[i], g_bl_h[i]); if (i == 0) { glBindTexture(GL_TEXTURE_2D, g_scene_tex); bloom_quad(0, 0, (float)fbw / g_tex_w, (float)fbh / g_tex_h); } else { glBindTexture(GL_TEXTURE_2D, g_bl_tex[i-1]); bloom_quad(0, 0, 1, 1); } } /* upsample (additive tent), folding coarse scales down into level 0 */ glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); for (int i = BLOOM_LEVELS - 1; i > 0; i--) { vgBindFramebuffer(GL_FRAMEBUFFER, g_bl_fbo[i-1]); glViewport(0, 0, g_bl_w[i-1], g_bl_h[i-1]); glBindTexture(GL_TEXTURE_2D, g_bl_tex[i]); bloom_tent(1.0f / g_bl_w[i], 1.0f / g_bl_h[i]); } /* composite the accumulated glow over the sharp scene */ vgBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, fbw, fbh); glBindTexture(GL_TEXTURE_2D, g_bl_tex[0]); float c = amt * BLOOM_GAIN; glColor3f(c, c, c); bloom_quad(0, 0, 1, 1); glBlendFunc(GL_SRC_ALPHA, GL_ONE); /* restore additive-alpha */ glDisable(GL_TEXTURE_2D); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } /* Draw the collected magnifier bodies as lenses over the grabbed field. */ static void draw_magnifiers(const int *list, int n, int fbw, int fbh) { double mvm[16], pjm[16]; int vp[4]; glGetDoublev(GL_MODELVIEW_MATRIX, mvm); /* still the camera view here */ glGetDoublev(GL_PROJECTION_MATRIX, pjm); glGetIntegerv(GL_VIEWPORT, vp); glBindTexture(GL_TEXTURE_2D, g_scene_tex); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, fbw, 0, fbh); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); const double tw = g_tex_w, th = g_tex_h; const double M = 2.3, TAN30 = 0.5773502692; /* magnification, tan(fov/2) */ const float LENS_FRAC = 0.82f; for (int m = 0; m < n; m++) { const Body *b = &bodies[list[m]]; double wx = b->x, wy = b->y, wz = -(double)b->z; double ez = mvm[2]*wx + mvm[6]*wy + mvm[10]*wz + mvm[14]; /* eye-space z */ double d = -ez; if (d < 0.4) continue; double sx, sy, sz; if (!gluProject(wx, wy, wz, mvm, pjm, vp, &sx, &sy, &sz)) continue; if (sz < 0.0 || sz > 1.0) continue; double rad = (b->size * LENS_FRAC) * (fbh * 0.5) / (d * TAN30); if (rad < 5) continue; if (rad > fbh * 1.6) rad = fbh * 1.6; /* (1) the magnified field: an opaque textured disc, scene zoomed by M */ glDisable(GL_BLEND); glEnable(GL_TEXTURE_2D); glColor3f(1, 1, 1); glBegin(GL_TRIANGLE_FAN); glTexCoord2f((float)(sx/tw), (float)(sy/th)); glVertex2d(sx, sy); for (int k = 0; k <= 40; k++) { double a = TAU*k/40, c = cos(a), s2 = sin(a); glTexCoord2f((float)((sx + (rad/M)*c)/tw), (float)((sy + (rad/M)*s2)/th)); glVertex2d(sx + rad*c, sy + rad*s2); } glEnd(); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); /* (2) the lens rim + handle, additive and coloured like any other body */ double depth = sqrt(wx*wx + wy*wy + (double)b->z*b->z) / cfg.render_dist; if (depth < 0) depth = 0; if (depth > 1) depth = 1; float bright = 0.25f + (1.0f - (float)depth) * 0.75f; float alpha = b->spawn_fade; if (depth > 0.85) alpha *= (1.0f - (float)depth) / 0.15f; if (alpha < 0) alpha = 0; float hue = cfg.multicolor ? (cfg.hue + b->hue_offset) : cfg.hue, r, g, bl; hsv_to_rgb(hue, 0.9f, 1.0f, &r, &g, &bl); glColor4f(r*bright, g*bright, bl*bright, alpha); glLineWidth(2.5f); glBegin(GL_LINE_LOOP); for (int k = 0; k < 72; k++) { double a = TAU*k/72; glVertex2d(sx + rad*cos(a), sy + rad*sin(a)); } glEnd(); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } /* Draw the collected glass-sphere bodies. A solid glass ball acts as a strong * ball lens: it shows the world behind it inverted and fish-eye-distorted, with * a bright fresnel rim and a specular highlight (a faint reflection). */ static void draw_glassballs(const int *list, int n, int fbw, int fbh) { double mvm[16], pjm[16]; int vp[4]; glGetDoublev(GL_MODELVIEW_MATRIX, mvm); glGetDoublev(GL_PROJECTION_MATRIX, pjm); glGetIntegerv(GL_VIEWPORT, vp); glBindTexture(GL_TEXTURE_2D, g_scene_tex); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, fbw, 0, fbh); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); const double tw = g_tex_w, th = g_tex_h, TAN30 = 0.5773502692; const float BALL_FRAC = 0.9f; const double MAG = 1.55; /* how much of the world the ball pulls in */ const int NR = 9, NS = 40; /* radial rings x angular segments */ for (int m = 0; m < n; m++) { const Body *b = &bodies[list[m]]; double wx = b->x, wy = b->y, wz = -(double)b->z; double ez = mvm[2]*wx + mvm[6]*wy + mvm[10]*wz + mvm[14]; double d = -ez; if (d < 0.4) continue; double sx, sy, sz; if (!gluProject(wx, wy, wz, mvm, pjm, vp, &sx, &sy, &sz)) continue; if (sz < 0.0 || sz > 1.0) continue; double rad = (b->size * BALL_FRAC) * (fbh * 0.5) / (d * TAN30); if (rad < 5) continue; if (rad > fbh * 1.7) rad = fbh * 1.7; /* (1) refracted, inverted, fish-eye image of the field behind the ball */ glDisable(GL_BLEND); glEnable(GL_TEXTURE_2D); glColor3f(1, 1, 1); for (int i = 0; i < NR; i++) { double t0 = (double)i/NR, t1 = (double)(i+1)/NR; /* inverted sample radius (pixels): grows non-linearly -> fish-eye */ double s0 = -MAG * pow(t0, 1.7) * rad, s1 = -MAG * pow(t1, 1.7) * rad; glBegin(GL_TRIANGLE_STRIP); for (int j = 0; j <= NS; j++) { double a = TAU*j/NS, c = cos(a), s2 = sin(a); glTexCoord2f((float)((sx + s0*c)/tw), (float)((sy + s0*s2)/th)); glVertex2d(sx + t0*rad*c, sy + t0*rad*s2); glTexCoord2f((float)((sx + s1*c)/tw), (float)((sy + s1*s2)/th)); glVertex2d(sx + t1*rad*c, sy + t1*rad*s2); } glEnd(); } glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); /* (2) glassy rim + a specular reflection highlight (additive) */ double depth = sqrt(wx*wx + wy*wy + (double)b->z*b->z) / cfg.render_dist; if (depth < 0) depth = 0; if (depth > 1) depth = 1; float bright = 0.25f + (1.0f - (float)depth) * 0.75f; float alpha = b->spawn_fade; if (depth > 0.85) alpha *= (1.0f - (float)depth) / 0.15f; if (alpha < 0) alpha = 0; float hue = cfg.multicolor ? (cfg.hue + b->hue_offset) : cfg.hue, r, g, bl; hsv_to_rgb(hue, 0.6f, 1.0f, &r, &g, &bl); /* paler -> glassy */ glLineWidth(2.0f); /* single clean fresnel edge */ glColor4f((0.3f+0.7f*r)*bright, (0.3f+0.7f*g)*bright, (0.3f+0.7f*bl)*bright, alpha); glBegin(GL_LINE_LOOP); for (int k = 0; k < 80; k++) { double a = TAU*k/80; glVertex2d(sx + rad*cos(a), sy + rad*sin(a)); } glEnd(); /* specular highlight: a small bright filled spot, upper-left */ double hx = sx - 0.42*rad, hy = sy + 0.42*rad, hr = 0.14*rad; glColor4f(bright, bright, bright, alpha*0.9f); glBegin(GL_TRIANGLE_FAN); glVertex2d(hx, hy); for (int k = 0; k <= 12; k++){ double a=TAU*k/12; glVertex2d(hx+hr*cos(a), hy+hr*sin(a)); } glEnd(); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } /* Draw the collected mirror-sphere bodies. An opaque polished metal ball is a * convex mirror: it reflects the surroundings in a wide, upright (non-inverted) * fish-eye, with a sky/ground metallic shading gradient and a sharp specular * highlight. It samples the same grabbed field texture as the lenses. */ static void draw_mirrorballs(const int *list, int n, int fbw, int fbh) { double mvm[16], pjm[16]; int vp[4]; glGetDoublev(GL_MODELVIEW_MATRIX, mvm); glGetDoublev(GL_PROJECTION_MATRIX, pjm); glGetIntegerv(GL_VIEWPORT, vp); glBindTexture(GL_TEXTURE_2D, g_scene_tex); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, fbw, 0, fbh); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); const double tw = g_tex_w, th = g_tex_h, TAN30 = 0.5773502692; const float BALL_FRAC = 0.9f; const double BASE = 0.25, SPREAD = 2.0; /* convex-mirror fish-eye spread */ const int NR = 9, NS = 40; for (int m = 0; m < n; m++) { const Body *b = &bodies[list[m]]; double wx = b->x, wy = b->y, wz = -(double)b->z; double ez = mvm[2]*wx + mvm[6]*wy + mvm[10]*wz + mvm[14]; double d = -ez; if (d < 0.4) continue; double sx, sy, sz; if (!gluProject(wx, wy, wz, mvm, pjm, vp, &sx, &sy, &sz)) continue; if (sz < 0.0 || sz > 1.0) continue; double rad = (b->size * BALL_FRAC) * (fbh * 0.5) / (d * TAN30); if (rad < 5) continue; if (rad > fbh * 1.7) rad = fbh * 1.7; float depth = (float)(sqrt(wx*wx + wy*wy + (double)b->z*b->z) / cfg.render_dist); if (depth < 0) depth = 0; if (depth > 1) depth = 1; float bright = 0.25f + (1.0f - depth) * 0.75f; float alpha = b->spawn_fade; if (depth > 0.85f) alpha *= (1.0f - depth) / 0.15f; if (alpha < 0) alpha = 0; /* (1) opaque reflection: upright (non-inverted) convex fish-eye, metal-tinted */ glDisable(GL_BLEND); glEnable(GL_TEXTURE_2D); for (int i = 0; i < NR; i++) { double t0 = (double)i/NR, t1 = (double)(i+1)/NR; double s0 = (BASE + SPREAD*pow(t0,1.6)) * rad; /* +offset -> upright reflection */ double s1 = (BASE + SPREAD*pow(t1,1.6)) * rad; glBegin(GL_TRIANGLE_STRIP); for (int j = 0; j <= NS; j++) { double a = TAU*j/NS, c = cos(a), s2 = sin(a); float sh0 = 0.55f + 0.4f*(float)(t0*s2); if (sh0 < 0.18f) sh0 = 0.18f; float sh1 = 0.55f + 0.4f*(float)(t1*s2); if (sh1 < 0.18f) sh1 = 0.18f; glColor3f(sh0*0.95f*bright, sh0*0.98f*bright, sh0*1.08f*bright); glTexCoord2f((float)((sx + s0*c)/tw), (float)((sy + s0*s2)/th)); glVertex2d(sx + t0*rad*c, sy + t0*rad*s2); glColor3f(sh1*0.95f*bright, sh1*0.98f*bright, sh1*1.08f*bright); glTexCoord2f((float)((sx + s1*c)/tw), (float)((sy + s1*s2)/th)); glVertex2d(sx + t1*rad*c, sy + t1*rad*s2); } glEnd(); } glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); /* (2) faint metal body so the sphere still reads over empty space, but * kept dim so the polished reflection dominates (less gray haze) */ glBegin(GL_TRIANGLE_FAN); glColor4f(0.07f*bright, 0.08f*bright, 0.1f*bright, alpha); glVertex2d(sx, sy); for (int k = 0; k <= NS; k++) { double a = TAU*k/NS; float ny = (float)sin(a); float sh = 0.04f + 0.15f*(ny*0.5f + 0.5f); glColor4f(sh*0.85f*bright, sh*0.9f*bright, sh*1.0f*bright, alpha); glVertex2d(sx + rad*cos(a), sy + rad*sin(a)); } glEnd(); /* (3) sharp specular highlight + a thin smooth rim */ double hx = sx - 0.4*rad, hy = sy + 0.42*rad, hr = 0.13*rad; glColor4f(bright, bright, bright, alpha); glBegin(GL_TRIANGLE_FAN); glVertex2d(hx, hy); for (int k = 0; k <= 16; k++){ double a=TAU*k/16; glVertex2d(hx+hr*cos(a), hy+hr*sin(a)); } glEnd(); glColor4f(0.42f*bright, 0.45f*bright, 0.52f*bright, alpha); glLineWidth(1.8f); glBegin(GL_LINE_LOOP); for (int k = 0; k < 80; k++){ double a=TAU*k/80; glVertex2d(sx+rad*cos(a), sy+rad*sin(a)); } glEnd(); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } static void render_frame(void); /* one frame; defined after main() */ int main(int argc, char **argv) { #ifdef SCREENSAVER { const char *mode = (argc > 1) ? argv[1] : "/s"; char m = (mode[0]=='/'||mode[0]=='-') ? (char)tolower((unsigned char)mode[1]) : 's'; if (m == 'c') { /* configuration dialog */ #ifdef _WIN32 MessageBoxA(NULL, "Vectorgons screensaver.\n\n" "It uses the settings saved by the Vectorgons app, stored in\n" "%USERPROFILE%\\.vectorgons -- run vectorgons.exe to change the\n" "speed, density, colors, glow, render distance, and so on.", "Vectorgons", MB_OK | MB_ICONINFORMATION); #endif return 0; } if (m == 'p') return 0; /* preview not supported; exit cleanly */ g_screensaver = 1; /* '/s' (or no arg): run full-screen */ } #else (void)argc; (void)argv; #endif srand((unsigned)time(NULL)); init_solids(); load_settings(); /* user's last-used settings become this run's defaults */ #ifdef __EMSCRIPTEN__ /* The browser build has no persisted settings, so start from a chosen look. * density 205 * (1520/140) ~= 2226 on-screen bodies. */ cfg.speed = 100; cfg.tumble = 65; cfg.tumble_var = 100; cfg.render_dist = 1520; cfg.density = 205; cfg.size_min = 1.4f; cfg.size_max = 50.2f; cfg.hue_cycle = 60; cfg.multicolor = 1; cfg.glow = 75; cfg.flicker = 28; cfg.mag_count = 25; cfg.glass_count = 25; cfg.mirror_count = 25; cfg.cycle_shapes = 0; /* random shapes */ g_bloom = 0; /* legacy glow mode */ #endif if (!g_screensaver) printf("Vectorgons: %d shape types loaded.\n", num_shapes); if (!glfwInit()) { fprintf(stderr, "Failed to init GLFW\n"); return 1; } glfwWindowHint(GLFW_SAMPLES, 4); GLFWwindow *win; if (g_screensaver) { /* full-screen on the primary monitor */ GLFWmonitor *mon = glfwGetPrimaryMonitor(); const GLFWvidmode *vm = glfwGetVideoMode(mon); win = glfwCreateWindow(vm->width, vm->height, "Vectorgons", mon, NULL); } else { win = glfwCreateWindow(1100, 760, "Vectorgons", NULL, NULL); } if (!win) { fprintf(stderr, "Failed to create window\n"); glfwTerminate(); return 1; } glfwMakeContextCurrent(win); glfwSwapInterval(g_vsync); if (g_screensaver) { /* any input ends the screensaver */ glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); glfwSetKeyCallback(win, ss_key_cb); glfwSetMouseButtonCallback(win, ss_btn_cb); glfwSetCursorPosCallback(win, ss_pos_cb); } else { glfwSetKeyCallback(win, key_cb); } GLfloat lwr[2] = {1, 10}; glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lwr); max_line_width = lwr[1] > 1 ? lwr[1] : 10.0f; init_gl_buffers(); /* retained-mode geometry; falls back to immediate on failure */ if (!g_screensaver && cfg.fullscreen) { cfg.fullscreen = 0; toggle_fullscreen(win); } rebuild_field(); if (!g_screensaver) print_help(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); /* additive glow */ #ifndef __EMSCRIPTEN__ glEnable(GL_LINE_SMOOTH); /* not available in WebGL */ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_MULTISAMPLE); #endif last_input_time = glfwGetTime(); g_win = win; #ifdef __EMSCRIPTEN__ /* Resize the canvas to fill the screen (at screen aspect) on fullscreen. */ emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_TRUE, on_fullscreen_change); /* The browser drives the frame loop via requestAnimationFrame; main() returns. */ emscripten_set_main_loop(render_frame, 0, 1); #else while (!glfwWindowShouldClose(win) && !(g_screensaver && g_quit)) render_frame(); if (!g_screensaver) save_settings(); /* persist settings (not from the saver) */ glfwDestroyWindow(win); glfwTerminate(); #endif return 0; } /* One simulation + render frame. Driven by the native while-loop on desktop, or * by emscripten_set_main_loop() in the browser. */ static void render_frame(void) { GLFWwindow *win = g_win; static double last = 0.0; if (last == 0.0) last = glfwGetTime(); { double now = glfwGetTime(); float raw = (float)(now - last); /* true frame time (for FPS) */ float dt = raw > 0.05f ? 0.05f : raw; /* clamped for the simulation */ last = now; /* rolling FPS / frame time over ~0.25 s, plus reset the per-frame counters */ static double fps_acc = 0.0; static int fps_n = 0; static float fps_val = 0.0f, ms_val = 0.0f; fps_acc += raw; fps_n++; if (fps_acc >= 0.25) { fps_val = (float)(fps_n / fps_acc); ms_val = (float)(1000.0 * fps_acc / fps_n); fps_acc = 0.0; fps_n = 0; } g_batches = 0; g_verts = 0; g_drawn = 0; int fbw, fbh; #ifdef __EMSCRIPTEN__ /* Drive the viewport from the canvas's *actual displayed* size rather * than GLFW's cached window size (which doesn't follow CSS/fullscreen * reliably). Size the drawing buffer to match the displayed CSS pixels, * so fullscreen (CSS 100vw x 100vh => the whole screen) renders at the * screen's aspect ratio and fills it edge-to-edge — no black bars, no * stretching. Windowed, the CSS size is 1100x760, unchanged. */ { double cssw = 0, cssh = 0; emscripten_get_element_css_size("#canvas", &cssw, &cssh); fbw = (int)(cssw + 0.5); fbh = (int)(cssh + 0.5); if (fbw < 1) fbw = 1; if (fbh < 1) fbh = 1; int bw = 0, bh = 0; emscripten_get_canvas_element_size("#canvas", &bw, &bh); if (bw != fbw || bh != fbh) emscripten_set_canvas_element_size("#canvas", fbw, fbh); } #else glfwGetFramebufferSize(win, &fbw, &fbh); #endif if (fbh < 1) fbh = 1; glViewport(0, 0, fbw, fbh); /* --- WASD pan + arrow-key rotate (held keys, frame-rate independent) --- */ if (!g_screensaver) { float pan = 42.0f * dt; float rot = 60.0f * dt; /* degrees / second */ int moved = 0; if (glfwGetKey(win, GLFW_KEY_W) == GLFW_PRESS) { cam_y += pan; moved = 1; } if (glfwGetKey(win, GLFW_KEY_S) == GLFW_PRESS) { cam_y -= pan; moved = 1; } if (glfwGetKey(win, GLFW_KEY_D) == GLFW_PRESS) { cam_x += pan; moved = 1; } if (glfwGetKey(win, GLFW_KEY_A) == GLFW_PRESS) { cam_x -= pan; moved = 1; } if (glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS) { cam_yaw -= rot; moved = 1; } if (glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS) { cam_yaw += rot; moved = 1; } if (glfwGetKey(win, GLFW_KEY_UP) == GLFW_PRESS) { cam_pitch += rot; moved = 1; } if (glfwGetKey(win, GLFW_KEY_DOWN) == GLFW_PRESS) { cam_pitch -= rot; moved = 1; } if (cam_x < -180) cam_x = -180; if (cam_x > 180) cam_x = 180; if (cam_y < -180) cam_y = -180; if (cam_y > 180) cam_y = 180; cam_yaw = fmodf(cam_yaw, 360.0f); /* free 360 rotation, both axes */ cam_pitch = fmodf(cam_pitch, 360.0f); if (moved) last_input_time = now; /* keep the OSD awake */ } glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (double)fbw / (double)fbh, 0.5, cfg.render_dist + 80.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(cam_pitch, 1.0f, 0.0f, 0.0f); /* apply camera rotation (arrows) */ glRotatef(cam_yaw, 0.0f, 1.0f, 0.0f); glTranslatef(-cam_x, -cam_y, 0.0f); /* apply camera pan (WASD) */ build_frustum(); /* for CPU culling of off-screen bodies */ #ifdef __EMSCRIPTEN__ glClearColor(0.0f, 0.0f, 0.0f, 1.0f); /* pure black in the browser */ #else glClearColor(0.006f, 0.010f, 0.035f, 1.0f); /* faint blue-black ambiance */ #endif glClear(GL_COLOR_BUFFER_BIT); /* continuous hue cycling (advances even while paused) */ if (cfg.hue_cycle > 0.001f) { cfg.hue += (cfg.hue_cycle / 100.0f) * 120.0f * dt; /* up to 120 deg/s */ cfg.hue = fmodf(cfg.hue, 360.0f); if (cfg.hue < 0) cfg.hue += 360.0f; } /* signed approach: negative speed flies the field backward (Page Down) */ float sp = cfg.speed / 100.0f; float approach = copysignf(powf(fabsf(sp), 1.6f), sp) * 120.0f; float tumble_rate = powf(cfg.tumble / 100.0f, 1.4f) * 3.2f; float var = cfg.tumble_var / 100.0f; float glow = cfg.glow / 100.0f; float fl = cfg.flicker / 100.0f; int bloom_on = g_have_fbo && g_bloom && glow > 0.001f; /* post-process glow */ recompose_field(); int active = g_total; update_clocks(); /* keep the clock shapes showing the current time */ update_dynamics((float)now); /* animate the moving objects */ static int mag_list[MAX_BODIES], glass_list[MAX_BODIES], mirror_list[MAX_BODIES]; int mag_n = 0, glass_n = 0, mirror_n = 0; if (g_have_vbo) glEnableClientState(GL_VERTEX_ARRAY); for (int i = 0; i < active; i++) { Body *b = &bodies[i]; if (!cfg.paused) { float spin = powf(4.0f, b->spin_seed * var); /* live variance */ b->z -= approach * dt; b->angle += tumble_rate * spin * dt; } if (b->spawn_fade < 1.0f) { /* ease in after spawn */ b->spawn_fade += dt * 2.2f; if (b->spawn_fade > 1.0f) b->spawn_fade = 1.0f; } /* recycle once the body leaves the render-distance sphere (in any * direction), so culling is purely radial and rotation-agnostic */ float dist2 = b->x*b->x + b->y*b->y + b->z*b->z; float R = cfg.render_dist; if (dist2 > R*R) { spawn_body(b, 0, active, i); continue; } /* magnifier bodies are deferred: drawn as lenses after the field * has been grabbed into a texture (see draw_magnifiers) */ if (b->shape == g_mag_idx || b->shape == g_mag_idx + 1) { if (mag_n < MAX_BODIES) mag_list[mag_n++] = i; continue; } if (b->shape == g_glass_idx || b->shape == g_glass_idx + 1) { if (glass_n < MAX_BODIES) glass_list[glass_n++] = i; continue; } if (b->shape == g_mirror_idx || b->shape == g_mirror_idx + 1) { if (mirror_n < MAX_BODIES) mirror_list[mirror_n++] = i; continue; } /* frustum cull: skip the projection + draw for bodies that can't be * on screen. The margin covers the glow, not just the body: the * legacy ghosts scale to ~1.8x, and the screen-space halo (a fixed * pixel width) maps to a world size that grows with distance -- so a * distance term keeps far-edge shapes' glow from popping. Motion and * recycle above already ran, so culled bodies still advance. */ double cmargin = b->size * 1.9 + sqrt((double)dist2) * 0.022 + 3.0; if (!sphere_visible(b->x, b->y, -(double)b->z, cmargin)) continue; g_drawn++; float depth = sqrtf(dist2) / R; /* 0 at camera .. 1 at far sphere */ if (depth < 0) depth = 0; if (depth > 1) depth = 1; float bright = 0.25f + (1.0f - depth) * 0.75f; if (fl > 0.001f) bright *= 1.0f - fl * 0.6f * frand(); /* flicker */ float alpha = b->spawn_fade; if (depth > 0.85f) alpha *= (1.0f - depth) / 0.15f; /* fade in far */ if (alpha < 0) alpha = 0; float hue = cfg.multicolor ? (cfg.hue + b->hue_offset) : cfg.hue; float r, g, bl; hsv_to_rgb(hue, 0.9f, 1.0f, &r, &g, &bl); float lw = 1.0f + (1.0f - depth) * 2.0f; const Solid *s = &solids[b->shape]; /* Select the vertex source for this body. mode 1 (rigid) reads a * prebuilt static VBO and needs no per-frame projection; mode 2 * (morph) and mode 0 (immediate clocks/dynamics) project into p3, * and mode 2 streams it to the shared dynamic VBO. */ static float p3[MAX_VERTS][3]; int mode = g_have_vbo ? g_shape_vbomode[b->shape] : 0; if (mode != 1) project_body(s, b->angle, b->hue_offset * 0.01745329f, p3); if (mode) { vgBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_shape_ibo[b->shape]); if (mode == 1) { vgBindBuffer(GL_ARRAY_BUFFER, g_shape_vbo[b->shape]); } else { vgBindBuffer(GL_ARRAY_BUFFER, g_dyn_vbo); vgBufferData(GL_ARRAY_BUFFER, (VGsizeiptr)((size_t)s->nv * 3 * sizeof(float)), p3, GL_STREAM_DRAW); } glVertexPointer(3, GL_FLOAT, 0, (const void *)0); } glPushMatrix(); glTranslatef(b->x, b->y, -b->z); glRotatef(b->angle * 57.2957795f, b->axis[0], b->axis[1], b->axis[2]); glScalef(b->size, b->size, b->size); /* CRT phosphor glow: a sharp core wrapped in a soft, dim mist. * Hardware caps line width (~10px), so the near halo is built from * many faint antialiased width layers fading outward (a smooth * gradient, not one fat blurry line), and a few faint scaled-up * ghost copies bloom the glow into a larger volume than the line * width alone could ever reach. (Skipped when the post-process bloom * is active -- it produces the same look in one pass per body.) */ if (!bloom_on && glow > 0.001f) { float maxw = max_line_width; /* (a) soft halo hugging each vector, fading out into mist */ for (int p = 1; p <= 5; p++) { float t = p / 5.0f; /* 0..1 outward */ float w = lw + (maxw - lw) * t; if (w < 1.0f) w = 1.0f; float fade = (1.0f - t) * (1.0f - t); /* dim, fades out */ glColor4f(r*bright, g*bright, bl*bright, alpha * glow * 0.16f * fade); glLineWidth(w); draw_edges(s, p3); } /* (b) volumetric bloom: a few faint, widely-spaced enlarged * ghosts drawn at the maximum (blurriest) width, so they smear * into an outer haze that fills a larger volume than the line- * width-capped halo can reach -- without reading as crisp rings */ glLineWidth(maxw); for (int p = 1; p <= 3; p++) { float t = p / 3.0f; float sc = 1.0f + glow * (0.22f + 0.55f * t); glColor4f(r*bright, g*bright, bl*bright, alpha * glow * 0.035f * (1.0f - 0.6f * t)); glPushMatrix(); glScalef(sc, sc, sc); draw_edges(s, p3); glPopMatrix(); } } /* crisp core, drawn on top so the vector stays sharp */ glColor4f(r*bright, g*bright, bl*bright, alpha); glLineWidth(lw); draw_edges(s, p3); /* refracting prism: white beam in, rainbow fan out */ if (b->shape == g_prism_idx || b->shape == g_prism_idx + 1) draw_prism_beams(bright, alpha); glPopMatrix(); } if (g_have_vbo) { /* restore state for fixed-function passes */ glDisableClientState(GL_VERTEX_ARRAY); vgBindBuffer(GL_ARRAY_BUFFER, 0); vgBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } /* bloom: synthesize the CRT glow from the sharp core in one post pass */ if (bloom_on) build_bloom(fbw, fbh, glow); /* magnifiers, glass + mirror spheres: grab the field, draw lenses over it */ if (mag_n > 0 || glass_n > 0 || mirror_n > 0) { grab_scene(fbw, fbh); if (mag_n > 0) draw_magnifiers(mag_list, mag_n, fbw, fbh); if (glass_n > 0) draw_glassballs(glass_list, glass_n, fbw, fbh); if (mirror_n > 0) draw_mirrorballs(mirror_list, mirror_n, fbw, fbh); } /* OSD: full for 10 s after last keypress, then fades over 4 s. * Suppressed in the screensaver (it has no interactive controls). */ if (!g_screensaver) { float idle = (float)(now - last_input_time); float osd_alpha = 1.0f; if (idle > 10.0f) osd_alpha = 1.0f - (idle - 10.0f) / 4.0f; if (osd_alpha < 0) osd_alpha = 0; render_osd(fbw, fbh, osd_alpha); if (g_show_hud) render_perf_hud(fbw, fbh, fps_val, ms_val); } glfwSwapBuffers(win); glfwPollEvents(); } }