4267 lines
230 KiB
C
4267 lines
230 KiB
C
/*
|
|
* 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 <windows.h> /* provides APIENTRY / CALLBACK that the GL headers need */
|
|
#endif
|
|
#include <GLFW/glfw3.h>
|
|
#include <GL/glu.h>
|
|
|
|
/* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <stddef.h>
|
|
|
|
/* --- 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;i<n;i++){
|
|
double L=sqrt(t[i][0]*t[i][0]+t[i][1]*t[i][1]+t[i][2]*t[i][2]); if(L<1e-9)L=1;
|
|
out[i][0]=t[i][0]/L; out[i][1]=t[i][1]/L; out[i][2]=t[i][2]/L;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* small ring "ball" of `seg` points at p, in the plane perpendicular to u */
|
|
static void add_ball(Solid *s, double px,double py,double pz,
|
|
double ux,double uy,double uz, double rad, int seg) {
|
|
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;
|
|
int first=-1, prev=-1;
|
|
for (int k=0;k<seg;k++){
|
|
double a=TAU*k/seg; int idx=s->nv;
|
|
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;i<nd;i++){
|
|
double r=(shortr>0 && (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;r<nrings;r++){
|
|
double ax=TAU*r/(2.0*nrings), ay=TAU*r/(3.0*nrings)+0.3*r;
|
|
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 y1=y*cos(ax)-z*sin(ax), z1=y*sin(ax)+z*cos(ax); y=y1; z=z1;
|
|
double x1=x*cos(ay)+z*sin(ay), z2=-x*sin(ay)+z*cos(ay); x=x1; z=z2;
|
|
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);
|
|
}
|
|
|
|
/* 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;o<norbits;o++){
|
|
double sp=TAU*o/norbits, tx=TAU*o/(norbits*2.0)+0.4;
|
|
int first=-1, prev=-1;
|
|
for (int i=0;i<seg;i++){
|
|
double a=TAU*i/seg, x=cos(a), y=sin(a)*0.42, z=0;
|
|
double x1=x*cos(sp)-y*sin(sp), y1=x*sin(sp)+y*cos(sp); x=x1; y=y1;
|
|
double y2=y*cos(tx)-z*sin(tx), z2=y*sin(tx)+z*cos(tx); y=y2; z=z2;
|
|
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);
|
|
}
|
|
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;i<seg;i++){ double a=TAU*i/seg; int idx=s->nv;
|
|
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;i<seg;i++){ double a=TAU*i/seg, x=cos(a)*0.6, y=sin(a)*0.6, z=0;
|
|
double x1=x*cos(mo)+z*sin(mo), z1=-x*sin(mo)+z*cos(mo);
|
|
int idx=s->nv; 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<nrings;rg++){
|
|
double rad=1.0+rg*0.2; int first=-1, prev=-1, seg2=22;
|
|
for (int i=0;i<seg2;i++){ double a=TAU*i/seg2, x=rad*cos(a), y=0, z=rad*sin(a);
|
|
double y1=y*cos(tilt)-z*sin(tilt), z1=y*sin(tilt)+z*cos(tilt);
|
|
int idx=s->nv; 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;i<nd;i++){
|
|
double tx=d[i][0], ty=d[i][1], tz=d[i][2];
|
|
int tip=s->nv; 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;i<nspokes;i++){ double a=TAU*i/nspokes; PV(s,0.6*cos(a),0.6*sin(a),0.35,0,0,0); }
|
|
int rb=s->nv; for(int i=0;i<nspokes;i++){ double a=TAU*i/nspokes; PV(s,0.6*cos(a),0.6*sin(a),-0.35,0,0,0); }
|
|
for (int i=0;i<nspokes;i++){
|
|
AE(s,top,rt+i); AE(s,bot,rb+i);
|
|
AE(s,rt+i,rt+(i+1)%nspokes); AE(s,rb+i,rb+(i+1)%nspokes);
|
|
AE(s,rt+i,rb+i);
|
|
}
|
|
center_normalize(s);
|
|
}
|
|
|
|
/* planar ray starburst with a pair of axial spikes for 3-D pop */
|
|
static void gen_raystar3d(int nrays) {
|
|
Solid *s=new_solid(3);
|
|
int c=s->nv; PV(s,0,0,0,0,0,0);
|
|
for (int i=0;i<nrays;i++){ double a=TAU*i/nrays, r=(i&1)?0.6:1.0;
|
|
int tip=s->nv; 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;i<seg;i++){
|
|
double a=TAU*i/seg, x=0.62*cos(a), y=sin(a);
|
|
if (y<0) x *= (1.0 + y*0.42); /* taper to a chin */
|
|
int idx=sym_pt(b, x, y>0 ? 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;i<n;i++){ /* scalloped hem */
|
|
double x0=-R+2*R*i/n, x1=-R+2*R*(i+1)/n, xm=(x0+x1)/2;
|
|
sym_arc(b, xm, 0.0, (x1-x0)/2, sym_deg(180), sym_deg(360), 4);
|
|
}
|
|
for (int i=0;i<=n;i++) sym_line(b, 0, R, -R+2*R*i/n, 0.0); /* ribs */
|
|
sym_line(b, 0, 0.0, 0, -0.85); /* pole */
|
|
sym_arc (b, -0.12, -0.85, 0.12, sym_deg(0), sym_deg(-180), 6); /* handle */
|
|
}
|
|
static void build_hand(SymB *b) { /* open hand silhouette */
|
|
const double hand[] = {
|
|
-0.45,-0.9, -0.5,-0.2, -0.85,0.1, -0.78,0.38, -0.45,0.08,
|
|
-0.4,0.72, -0.29,0.98, -0.18,0.66,
|
|
-0.12,0.82, 0.0,1.06, 0.09,0.74,
|
|
0.14,0.8, 0.27,1.0, 0.35,0.68,
|
|
0.41,0.62, 0.52,0.82, 0.56,0.5,
|
|
0.46,-0.9 };
|
|
sym_poly(b, hand, 18, 1);
|
|
}
|
|
|
|
/* ---- live clocks (rebuilt every frame from the current local time) ---- */
|
|
|
|
/* one seven-segment digit in a cell of width w and height 2h at (ox,oy) */
|
|
static void sym_7seg(SymB *b, int dg, double ox,double oy,double w,double h) {
|
|
int TL=sym_pt(b,ox,oy+2*h), TR=sym_pt(b,ox+w,oy+2*h);
|
|
int ML=sym_pt(b,ox,oy+h), MR=sym_pt(b,ox+w,oy+h);
|
|
int BL=sym_pt(b,ox,oy), BR=sym_pt(b,ox+w,oy);
|
|
static const unsigned char seg[10] =
|
|
{ 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F };
|
|
int m = seg[dg % 10];
|
|
if(m&0x01) sym_edge(b,TL,TR); /* a top */
|
|
if(m&0x02) sym_edge(b,TR,MR); /* b up-right */
|
|
if(m&0x04) sym_edge(b,MR,BR); /* c lo-right */
|
|
if(m&0x08) sym_edge(b,BL,BR); /* d bottom */
|
|
if(m&0x10) sym_edge(b,ML,BL); /* e lo-left */
|
|
if(m&0x20) sym_edge(b,TL,ML); /* f up-left */
|
|
if(m&0x40) sym_edge(b,ML,MR); /* g middle */
|
|
}
|
|
static void build_analog(SymB *b, const struct tm *lt) {
|
|
sym_ring(b, 0,0, 1.0, 24); /* face */
|
|
for (int i=0;i<12;i++){ /* hour ticks */
|
|
double a=TAU*i/12;
|
|
sym_line(b, 0.86*cos(a),0.86*sin(a), 0.97*cos(a),0.97*sin(a));
|
|
}
|
|
int h=lt->tm_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<n;i++){ int idx=sym_pt(b, rp[i][0], rp[i][1]);
|
|
if(first<0)first=idx;
|
|
if(prev>=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<seg;i++){ double a=TAU*i/seg, r=1.0+0.05*sin(2*a);
|
|
int idx=sym_pt(b, r*cos(a), 0.6*r*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<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<n;i++){ int idx=sym_pt(b,rp[i][0],rp[i][1]);
|
|
if(first<0)first=idx;
|
|
if(prev>=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;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);
|
|
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;i<seg;i++){ double a=TAU*i/seg; int idx=s->nv;
|
|
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;i<seg;i++){ double a=TAU*i/seg, x=cos(a), y=sin(a), z=0;
|
|
double x1=x*cos(mo)+z*sin(mo), z1=-x*sin(mo)+z*cos(mo);
|
|
int idx=s->nv; 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;j<seg;j++) AE(s, rs[i], rs[i+1]+j);
|
|
else if (isp[i+1]) for (int j=0;j<seg;j++) AE(s, rs[i]+j, rs[i+1]);
|
|
else for (int j=0;j<seg;j++) AE(s, rs[i]+j, rs[i+1]+j);
|
|
}
|
|
}
|
|
static void gen_lathe(const double (*prof)[2], int n, int seg) {
|
|
Solid *s = new_solid(3); lathe_into(s, prof, n, seg); center_normalize(s);
|
|
}
|
|
/* add a small flat ring at (cx,cy) in the X-Y plane (for handles, flames, etc.) */
|
|
static void add_xy_ring(Solid *s, double cx,double cy,double r,int seg) {
|
|
int first=-1, prev=-1;
|
|
for (int k=0;k<seg;k++){ double a=TAU*k/seg; int idx=s->nv;
|
|
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;i<nu;i++){ double u=TAU*i/nu;
|
|
for(int j=0;j<nv;j++){ double v=TAU*j/nv;
|
|
PV(s,(R+rr*cos(v))*cos(u), rr*sin(v), (R+rr*cos(v))*sin(u), 0,0,0); } }
|
|
for(int i=0;i<nu;i++) for(int j=0;j<nv;j++){ int a=i*nv+j;
|
|
AE(s,a,i*nv+(j+1)%nv); AE(s,a,((i+1)%nu)*nv+j); }
|
|
center_normalize(s);
|
|
}
|
|
|
|
/* ---- extruded silhouettes (built flat, extruded to 3-D) ------------ */
|
|
static void build_knight(SymB *b){ /* chess knight (horse head) */
|
|
const double p[][2]={ {-0.45,-1.0},{0.45,-1.0},{0.45,-0.82},{0.22,-0.78},
|
|
{0.30,-0.45},{0.34,-0.05},{0.30,0.22},{0.17,0.5},{0.04,0.72},{-0.12,0.82},
|
|
{-0.10,0.6},{-0.27,0.64},{-0.5,0.42},{-0.62,0.2},{-0.5,0.12},{-0.55,-0.04},
|
|
{-0.32,-0.08},{-0.22,-0.45},{-0.3,-0.78} };
|
|
sym_poly(b,&p[0][0],19,1);
|
|
sym_ring(b,-0.06,0.46,0.045,4); /* eye */
|
|
}
|
|
static void build_padlock(SymB *b){
|
|
const double body[]={-0.55,-0.7, 0.55,-0.7, 0.55,0.22, -0.55,0.22};
|
|
sym_poly(b,body,4,1);
|
|
sym_arc(b,0,0.22,0.34,sym_deg(0),sym_deg(180),10); /* shackle */
|
|
sym_line(b,-0.34,0.22,-0.34,0.05); sym_line(b,0.34,0.22,0.34,0.05);
|
|
sym_ring(b,0,-0.12,0.1,6); sym_line(b,0,-0.12,0,-0.45);/* keyhole */
|
|
}
|
|
static void build_key(SymB *b){ /* antique key */
|
|
sym_ring(b,-0.7,0,0.27,12); sym_ring(b,-0.7,0,0.11,6); /* ornate bow */
|
|
sym_line(b,-0.43,0,0.9,0); /* shaft */
|
|
const double bit[]={0.9,0, 0.9,-0.26, 0.74,-0.26, 0.74,-0.12, 0.62,-0.12, 0.62,0};
|
|
sym_poly(b,bit,6,0);
|
|
}
|
|
static void build_banana(SymB *b){
|
|
sym_arc(b,0,-0.45,1.05,sym_deg(33),sym_deg(147),11); /* outer edge */
|
|
int oR=0, oL=b->np-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<n;i++){ double a=TAU*i/n; sym_edge(b,c,sym_pt(b,0.8*cos(a),0.8*sin(a)));
|
|
sym_ring(b,0.92*cos(a),0.92*sin(a),0.1,5);} }
|
|
static void build_gyroscope2d(SymB *b){
|
|
for(int k=0;k<3;k++) sym_ellipse(b,0,0,1.0,0.34,sym_deg(k*60),18);
|
|
sym_ring(b,0,0,0.12,6);
|
|
}
|
|
|
|
/* ---- computer chips (IC package styles) ---------------------------- */
|
|
static void build_chip_dip(SymB *b){ /* DIP (e.g. 6502 / Z80 / 8-bit CPU) */
|
|
const double body[]={-0.9,-0.45, 0.9,-0.45, 0.9,0.45, -0.9,0.45};
|
|
sym_poly(b,body,4,1);
|
|
sym_arc(b,-0.9,0,0.12,sym_deg(-90),sym_deg(90),4); /* end notch */
|
|
for(int i=0;i<8;i++){ double x=-0.72+i*1.44/7;
|
|
sym_line(b,x,0.45,x,0.62); sym_line(b,x,-0.45,x,-0.62); }
|
|
sym_ring(b,-0.7,0.3,0.05,4); /* pin-1 dot */
|
|
}
|
|
static void build_chip_dip8(SymB *b){ /* small DIP-8 */
|
|
const double body[]={-0.5,-0.5, 0.5,-0.5, 0.5,0.5, -0.5,0.5};
|
|
sym_poly(b,body,4,1);
|
|
sym_arc(b,0,0.5,0.12,sym_deg(180),sym_deg(360),4);
|
|
for(int i=0;i<4;i++){ double y=-0.3+i*0.2; sym_line(b,0.5,y,0.7,y); sym_line(b,-0.5,y,-0.7,y); }
|
|
sym_ring(b,-0.32,0.3,0.05,4);
|
|
}
|
|
static void build_chip_qfp(SymB *b){ /* QFP (e.g. 486 era), pins on 4 sides */
|
|
const double body[]={-0.6,-0.6, 0.6,-0.6, 0.6,0.6, -0.6,0.6};
|
|
sym_poly(b,body,4,1);
|
|
sym_line(b,-0.6,0.45,-0.45,0.6); /* chamfered pin-1 corner */
|
|
for(int i=0;i<5;i++){ double t=-0.4+i*0.2;
|
|
sym_line(b,t,0.6,t,0.76); sym_line(b,t,-0.6,t,-0.76);
|
|
sym_line(b,0.6,t,0.76,t); sym_line(b,-0.6,t,-0.76,t); }
|
|
}
|
|
static void build_chip_pga(SymB *b){ /* PGA (e.g. 386/486), grid of pins */
|
|
const double body[]={-0.7,-0.7, 0.7,-0.7, 0.7,0.7, -0.7,0.7};
|
|
const double die[]={-0.4,-0.4, 0.4,-0.4, 0.4,0.4, -0.4,0.4};
|
|
sym_poly(b,body,4,1); sym_poly(b,die,4,1);
|
|
for(int r=0;r<3;r++) for(int c=0;c<3;c++) sym_ring(b,-0.5+c*0.5,-0.5+r*0.5,0.05,3);
|
|
sym_line(b,-0.7,0.55,-0.55,0.7);
|
|
}
|
|
static void build_chip_cpu(SymB *b){ /* generic processor + heat-spreader */
|
|
const double body[]={-0.8,-0.55, 0.8,-0.55, 0.8,0.55, -0.8,0.55};
|
|
const double die[]={-0.35,-0.25, 0.35,-0.25, 0.35,0.25, -0.35,0.25};
|
|
sym_poly(b,body,4,1); sym_poly(b,die,4,1);
|
|
sym_line(b,-0.35,0,0.35,0); sym_line(b,0,-0.25,0,0.25);
|
|
for(int i=0;i<6;i++){ double x=-0.6+i*0.24; sym_line(b,x,0.55,x,0.68); sym_line(b,x,-0.55,x,-0.68); }
|
|
sym_ring(b,-0.65,0.4,0.05,4);
|
|
}
|
|
|
|
/* ---- food, tools and other objects (extruded silhouettes) ---------- */
|
|
static void build_hotdog(SymB *b){
|
|
sym_arc(b,-0.6,0,0.4,sym_deg(90),sym_deg(270),5);
|
|
sym_arc(b, 0.6,0,0.4,sym_deg(-90),sym_deg(90),5);
|
|
sym_line(b,-0.6,0.4,0.6,0.4); sym_line(b,-0.6,-0.4,0.6,-0.4); /* bun */
|
|
sym_arc(b,-0.55,0,0.2,sym_deg(90),sym_deg(270),4);
|
|
sym_arc(b, 0.55,0,0.2,sym_deg(-90),sym_deg(90),4);
|
|
sym_line(b,-0.55,0.2,0.55,0.2); sym_line(b,-0.55,-0.2,0.55,-0.2); /* sausage */
|
|
int prev=-1; for(int i=0;i<=6;i++){ double x=-0.5+i*1.0/6, y=0.06*((i%2)?1:-1);
|
|
int idx=sym_pt(b,x,y); if(prev>=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;i<nu;i++){ double u=TAU*i/nu; for(int j=0;j<nv;j++){ double v=TAU*j/nv;
|
|
PV(s,(R+r*cos(v))*cos(u),r*sin(v),(R+r*cos(v))*sin(u),0,0,0); } }
|
|
for(int i=0;i<nu;i++)for(int j=0;j<nv;j++){ int a=i*nv+j; AE(s,a,i*nv+(j+1)%nv); AE(s,a,((i+1)%nu)*nv+j); }
|
|
center_normalize(s); }
|
|
static void build_ringedplanet2d(SymB *b){ sym_ring(b,0,0,0.55,16);
|
|
sym_ellipse(b,0,0,1.1,0.28,sym_deg(-15),20); sym_ellipse(b,0,0,0.9,0.23,sym_deg(-15),18); }
|
|
static void gen_ringedplanet3d(void){ Solid*s=new_solid(3); int lats=3,seg=14; double tilt=sym_deg(22);
|
|
for(int la=1;la<=lats;la++){ double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25,y=sin(phi)*0.55,rr=cos(phi)*0.55;
|
|
int first=-1,prev=-1; for(int i=0;i<seg;i++){ double a=TAU*i/seg; int idx=s->nv;
|
|
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;i<seg;i++){ double a=TAU*i/seg,x=cos(a)*0.55,y=sin(a)*0.55,z=0;
|
|
double x1=x*cos(mo)+z*sin(mo),z1=-x*sin(mo)+z*cos(mo); int idx=s->nv; 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;i<seg2;i++){ double a=TAU*i/seg2,x=rad*cos(a),y=0,z=rad*sin(a);
|
|
double y1=y*cos(tilt)-z*sin(tilt),z1=y*sin(tilt)+z*cos(tilt); int idx=s->nv; 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;i<seg;i++){ double a=TAU*i/seg; PV(s,0.55*cos(a),0.55*sin(a),-0.08,0,0,0); } for(int i=0;i<seg;i++)AE(s,r0+i,r0+(i+1)%seg);
|
|
int r1=s->nv; for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,0.55*cos(a),0.55*sin(a),0.08,0,0,0); } for(int i=0;i<seg;i++)AE(s,r1+i,r1+(i+1)%seg);
|
|
for(int i=0;i<seg;i+=2)AE(s,r0+i,r1+i);
|
|
int hub=s->nv; PV(s,0,0,0,0,0,0); for(int i=0;i<seg;i+=4)AE(s,hub,r0+i);
|
|
int ax=s->nv; 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;i<seg;i++){ double a=TAU*i/seg; PV(s,cx+r*cos(a),cy+r*sin(a),0,0,0,0); } for(int i=0;i<seg;i++)AE(s,a0+i,a0+(i+1)%seg);
|
|
int b0=s->nv; for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,cx+r*cos(a),cy,r*sin(a),0,0,0); } for(int i=0;i<seg;i++)AE(s,b0+i,b0+(i+1)%seg); }
|
|
int st=s->nv; 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<seg;i++){ double a=TAU*i/seg;
|
|
double r=0.85+0.13*sin(5*a)+0.06*sin(8*a); int idx=sym_pt(b,r*cos(a)*1.05,r*sin(a)*0.85);
|
|
if(first<0)first=idx;
|
|
if(prev>=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<n;i++){ int idx=sym_pt(b,p[i][0],p[i][1]); if(first<0)first=idx; if(prev>=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<n;i++){int idx=sym_pt(b,p[i][0],p[i][1]); if(first<0)first=idx; if(prev>=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;i<s->nv;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<n;k++){ double ox=off[k][0]*sc,oy=off[k][1]*sc;
|
|
add_ball(s, cx+ox*ux+oy*vx, cy+ox*uy+oy*vy, cz+ox*uz+oy*vz, ux*vy-uy*vx,uy*vz-uz*vy+0.001,uz*vx-ux*vz, 0.1, 4); } }
|
|
static void gen_die6(void){ Solid*s=new_solid(3); double X=0.7;
|
|
for(int i=0;i<8;i++) PV(s,(i&1?X:-X),(i&2?X:-X),(i&4?X:-X),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]);
|
|
cube_pip(s,0,0,X, X,0,0, 0,X,0, 1); /* +z face: 1 */
|
|
cube_pip(s,0,0,-X, X,0,0, 0,X,0, 5); /* -z face: 5 */
|
|
cube_pip(s,X,0,0, 0,0,X, 0,X,0, 3); /* +x: 3 */
|
|
cube_pip(s,-X,0,0, 0,0,X, 0,X,0, 4); /* -x: 4 */
|
|
center_normalize(s); }
|
|
static void build_die2d(SymB *b){ build_dice(b); }
|
|
static void gen_die20(void){ Solid*s=new_solid(3); const double P=1.6180339887;
|
|
double v[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++) PV(s,v[i][0],v[i][1],v[i][2],0,0,0);
|
|
center_normalize(s); derive_edges(s); }
|
|
static void build_die20_2d(SymB *b){ int first=-1,prev=-1; for(int i=0;i<6;i++){ double a=sym_deg(90+i*60);
|
|
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<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||d<el2)el2=d;}
|
|
for(int i=0;i<12;i++)for(int j=i+1;j<12;j++){ double dx=v[i][0]-v[j][0],dy=v[i][1]-v[j][1],dz=v[i][2]-v[j][2]; if(dx*dx+dy*dy+dz*dz<=el2*1.1) AE(s,i,j);}
|
|
int c=s->nv; 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;i<s->nv;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;i<seg;i++){double a=TAU*i/seg; PV(s,0.55*cos(a),-0.1,0.55*sin(a),0,0,0);} for(int i=0;i<seg;i++){AE(s,bp,r0+i);AE(s,r0+i,r0+(i+1)%seg);}
|
|
int r1=s->nv; for(int i=0;i<seg;i++){double a=TAU*i/seg; PV(s,0.3*cos(a),0.35,0.3*sin(a),0,0,0);} for(int i=0;i<seg;i++){AE(s,r0+i,r1+i);AE(s,r1+i,r1+(i+1)%seg);}
|
|
int st=s->nv; 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;i<seg;i++){double a=TAU*i/seg+TAU*0.125; PV(s,0.5*cos(a),-0.2,0.5*sin(a),0,0,0);} for(int i=0;i<seg;i++){AE(s,bp,r0+i);AE(s,r0+i,r0+(i+1)%seg);}
|
|
int r1=s->nv; for(int i=0;i<seg;i++){double a=TAU*i/seg+TAU*0.125; PV(s,0.5*cos(a),0.45,0.5*sin(a),0,0,0);} for(int i=0;i<seg;i++){AE(s,r0+i,r1+i);AE(s,r1+i,r1+(i+1)%seg);}
|
|
int st=s->nv; 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;i<seg;i++){double a=TAU*i/seg; PV(s,0.32*cos(a),-1.0,0.32*sin(a),0,0,0);}
|
|
int r1=s->nv; for(int i=0;i<seg;i++){double a=TAU*i/seg; PV(s,0.16*cos(a),0.35,0.16*sin(a),0,0,0);}
|
|
for(int i=0;i<seg;i++){AE(s,r0+i,r0+(i+1)%seg);AE(s,r1+i,r1+(i+1)%seg);AE(s,r0+i,r1+i);}
|
|
int lr=s->nv; for(int i=0;i<seg;i++){double a=TAU*i/seg; PV(s,0.22*cos(a),0.55,0.22*sin(a),0,0,0);} for(int i=0;i<seg;i++){AE(s,r1+i,lr+i);AE(s,lr+i,lr+(i+1)%seg);}
|
|
int top=s->nv; PV(s,0,0.7,0,0,0,0); for(int i=0;i<seg;i+=2)AE(s,lr+i,top);
|
|
double ba=t*1.6; int L=s->nv; 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;i<seg;i++){double a=TAU*i/seg; PV(s,rr*cos(a),y,rr*sin(a),0,0,0);} for(int i=0;i<seg;i++)AE(s,f+i,f+(i+1)%seg);}
|
|
for(int m=0;m<2;m++){double mo=TAU*m/4; int f=s->nv; for(int i=0;i<seg;i++){double a=TAU*i/seg,x=cos(a)*0.9,y=sin(a)*0.9,z=0,x1=x*cos(mo)+z*sin(mo),z1=-x*sin(mo)+z*cos(mo); PV(s,x1,y,z1,0,0,0);} for(int i=0;i<seg;i++)AE(s,f+i,f+(i+1)%seg);}
|
|
int ir=s->nv; for(int i=0;i<seg;i++){double a=TAU*i/seg; PV(s,0.32*cos(a),0.32*sin(a),0.85,0,0,0);} for(int i=0;i<seg;i++)AE(s,ir+i,ir+(i+1)%seg);
|
|
int pp=s->nv; 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;i<seg;i++){double a=TAU*i/seg; PV(s,rr*cos(a),y,rr*sin(a),0,0,0);} for(int i=0;i<seg;i++)AE(s,f+i,f+(i+1)%seg);}
|
|
add_ball(s,-0.25,0.25,0.55, 0,0,1, 0.13,6); add_ball(s,0.25,0.25,0.55, 0,0,1, 0.13,6);
|
|
/* jaw: a small ring that drops by gap, with a couple of teeth */
|
|
int j=s->nv; 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;i<seg;i++){double a=TAU*i/seg; PV(s,0.45*cos(a),0.45+0.0,0.45*sin(a),0,0,0);} for(int i=0;i<seg;i++)AE(s,f+i,f+(i+1)%seg);
|
|
int top=s->nv; PV(s,0,0.9,0,0,0,0); for(int i=0;i<seg;i+=2)AE(s,f+i,top);
|
|
add_ball(s,-0.2,0.55,0.4,0,0,1,0.08,5); add_ball(s,0.2,0.55,0.4,0,0,1,0.08,5);
|
|
for(int arm=0;arm<8;arm++){ double base=TAU*arm/8; double bx=0.4*cos(base),bz=0.4*sin(base); int prev=-1;
|
|
for(int k=0;k<=6;k++){ double kk=k/6.0; double wob=0.25*sin(t*4.0+arm+kk*4); double rr=0.4+kk*0.6;
|
|
double x=(rr)*cos(base+wob), z=(rr)*sin(base+wob), y=0.45-kk*1.4; int idx=s->nv; 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;i<seg;i++){double a=TAU*i/seg; PV(s,rr*cos(a),y,rr*sin(a),0,0,0);} for(int i=0;i<seg;i++)AE(s,f+i,f+(i+1)%seg); if(la==0){int tp=s->nv; PV(s,0,0.75,0,0,0,0);for(int i=0;i<seg;i+=2)AE(s,f+i,tp);} }
|
|
for(int tnt=0;tnt<10;tnt++){ double a=TAU*tnt/10; int prev=-1; for(int k=0;k<=6;k++){ double kk=k/6.0; double wob=0.2*sin(t*3.5+tnt+kk*5);
|
|
double x=(0.5+0.1*kk)*cos(a)+wob*0.2, z=(0.5+0.1*kk)*sin(a), y=0.05-kk*1.3; int idx=s->nv; 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_count<MAX_DYN){ dyn_solid[dyn_count]=num_shapes; dyn_func[dyn_count]=f; new_solid(3); dyn_count++; } }
|
|
static void update_dynamics(float t){ for(int i=0;i<dyn_count;i++){ Solid*s=&solids[dyn_solid[i]]; s->dim=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;k<nsec;k++){ double kk=(double)k/(nsec-1), x=-0.75+kk*1.2, rr=0.42*sin(kk*3.14159)+0.03; rings[k]=s->nv;
|
|
for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,x,rr*cos(a),rr*sin(a),0,0,0); } for(int i=0;i<seg;i++)AE(s,rings[k]+i,rings[k]+(i+1)%seg);
|
|
if(k>0) for(int i=0;i<seg;i++)AE(s,rings[k-1]+i,rings[k]+i); }
|
|
int tf=s->nv; 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;i<n;i++){ double a=ang+TAU*i/n, r=(i&1)?R*0.82:R; PV(s,cx+r*cos(a),cy+r*sin(a),0,0,0,0); } for(int i=0;i<n;i++)AE(s,f+i,f+(i+1)%n);
|
|
int h=s->nv; PV(s,cx,cy,0,0,0,0); for(int i=0;i<n;i+=3)AE(s,h,f+i);
|
|
int hr=s->nv; 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<n;i++){ double a=TAU*i/n,r=(i&1)?R*0.82:R; int idx=sym_pt(b,cx+r*cos(a),r*sin(a)); if(first<0)first=idx; if(prev>=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;i<seg;i++){ double a=TAU*i/seg; PV(s,rr*cos(a),y,rr*sin(a),0,0,0); }
|
|
for(int i=0;i<seg;i++) AE(s,f+i,f+(i+1)%seg); }
|
|
int m0=s->nv; for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,0.9*cos(a),0.9*sin(a),0,0,0,0); }
|
|
for(int i=0;i<seg;i++) AE(s,m0+i,m0+(i+1)%seg); }
|
|
static void gen_glassball2d(void){ Solid*s=new_solid(3); int seg=22;
|
|
for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,0.95*cos(a),0.95*sin(a),0,0,0,0); }
|
|
for(int i=0;i<seg;i++) AE(s,i,(i+1)%seg); }
|
|
/* Mirror sphere. Rendered as a reflection billboard (draw_mirrorballs); like the
|
|
* glass sphere the solid is only a coarse wireframe ball / ring for bounding. */
|
|
static void gen_mirror3d(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;i<seg;i++){ double a=TAU*i/seg; PV(s,rr*cos(a),y,rr*sin(a),0,0,0); }
|
|
for(int i=0;i<seg;i++) AE(s,f+i,f+(i+1)%seg); }
|
|
int m0=s->nv; for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,0.9*cos(a),0.9*sin(a),0,0,0,0); }
|
|
for(int i=0;i<seg;i++) AE(s,m0+i,m0+(i+1)%seg); }
|
|
static void gen_mirror2d(void){ Solid*s=new_solid(3); int seg=22;
|
|
for(int i=0;i<seg;i++){ double a=TAU*i/seg; PV(s,0.95*cos(a),0.95*sin(a),0,0,0,0); }
|
|
for(int i=0;i<seg;i++) AE(s,i,(i+1)%seg); }
|
|
/* Draw the refraction: a white beam into one face, a rainbow fan out the other.
|
|
* Called inside the body's model transform, so coordinates are object-space. */
|
|
static void draw_prism_beams(float bright, float alpha){
|
|
double ex=-0.3, ey=0.135, ox=0.3, oy=-0.05; /* entry / exit points */
|
|
static const float rb[7][3]={{1,0.15,0.15},{1,0.5,0.1},{1,0.95,0.2},{0.3,0.95,0.35},
|
|
{0.2,0.7,1},{0.35,0.35,1},{0.75,0.25,1}};
|
|
glLineWidth(2.2f);
|
|
glBegin(GL_LINES);
|
|
glColor4f(0.95f*bright,0.95f*bright,0.95f*bright,alpha);
|
|
glVertex3d(-2.4,0.5,0.0); glVertex3d(ex,ey,0.0); /* incoming white */
|
|
glVertex3d(ex,ey,0.0); glVertex3d(ox,oy,0.0); /* internal ray */
|
|
for(int c=0;c<7;c++){ double yend = oy + (0.18 - c*0.22);
|
|
glColor4f(rb[c][0]*bright, rb[c][1]*bright, rb[c][2]*bright, alpha);
|
|
glVertex3d(ox,oy,0.0); glVertex3d(2.5,yend,0.0); } /* rainbow fan out */
|
|
glEnd();
|
|
}
|
|
|
|
/* Build only the flat 2-D form of a SymB shape. */
|
|
static void add_flat2d(void (*build)(SymB *)) {
|
|
SymB b; sym_init(&b); build(&b); sym_finish(&b, 0.0f);
|
|
}
|
|
|
|
/* Build a symbol in both its flat and extruded-3-D forms. */
|
|
static void add_symbol(void (*build)(SymB *)) {
|
|
SymB b;
|
|
sym_init(&b); build(&b); sym_finish(&b, 0.0f); /* 2-D */
|
|
sym_init(&b); build(&b); sym_finish(&b, 0.5f); /* 3-D */
|
|
}
|
|
|
|
/* Random lumpy 3-D asteroid: n points scattered over a sphere at jittered
|
|
* radii, wired up as the convex hull (brute-force face enumeration). The
|
|
* vertex count `n` sets the number of sides / overall complexity, and the
|
|
* radial jitter gives each rock its irregular, faceted silhouette. */
|
|
static void gen_asteroid(int n) {
|
|
if (n > 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 */
|
|
|
|
/* --- 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 ? "MULTICOLOR" : "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) {
|
|
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);
|
|
}
|
|
|
|
static void load_settings(void) {
|
|
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;
|
|
}
|
|
|
|
static void toggle_fullscreen(GLFWwindow *win) {
|
|
static int wx = 100, wy = 100, ww = 1100, wh = 760;
|
|
cfg.fullscreen = !cfg.fullscreen;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
glBegin(GL_LINES);
|
|
for (int e = 0; e < s->ne; e++) {
|
|
glVertex3fv(p[s->e[e][0]]);
|
|
glVertex3fv(p[s->e[e][1]]);
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
/* 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<x) p<<=1; return p; }
|
|
|
|
static void ensure_scene_tex(int w, int h) {
|
|
int tw = next_pot(w), th = next_pot(h);
|
|
if (!g_scene_tex) glGenTextures(1, &g_scene_tex);
|
|
if (tw != g_tex_w || th != g_tex_h) {
|
|
glBindTexture(GL_TEXTURE_2D, g_scene_tex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, 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);
|
|
g_tex_w = tw; g_tex_h = th;
|
|
}
|
|
}
|
|
|
|
/* Copy the rendered back buffer into the scratch texture so lenses can sample it. */
|
|
static void grab_scene(int fbw, int fbh) {
|
|
ensure_scene_tex(fbw, fbh);
|
|
glBindTexture(GL_TEXTURE_2D, g_scene_tex);
|
|
glReadBuffer(GL_BACK);
|
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, 0,0, fbw, fbh);
|
|
}
|
|
|
|
/* (Re)create the bloom pyramid render targets for the current framebuffer size.
|
|
* Each level is half the previous; an exact-size (NPOT) colour texture wrapped
|
|
* in an FBO. Returns 0 and disables bloom if any FBO is incomplete. */
|
|
static int ensure_bloom_targets(int fbw, int fbh) {
|
|
if (fbw == g_bl_fbw && fbh == g_bl_fbh && g_bl_fbo[0]) return 1;
|
|
if (g_bl_fbo[0]) { vgDeleteFramebuffers(BLOOM_LEVELS, g_bl_fbo); glDeleteTextures(BLOOM_LEVELS, g_bl_tex); }
|
|
vgGenFramebuffers(BLOOM_LEVELS, g_bl_fbo);
|
|
glGenTextures(BLOOM_LEVELS, g_bl_tex);
|
|
int w = fbw, h = fbh;
|
|
for (int i = 0; i < BLOOM_LEVELS; i++) {
|
|
w = w > 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_RGB, w, h, 0, GL_RGB, 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();
|
|
}
|
|
|
|
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 */
|
|
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 */
|
|
glEnable(GL_LINE_SMOOTH);
|
|
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
|
glEnable(GL_MULTISAMPLE);
|
|
|
|
last_input_time = glfwGetTime();
|
|
double last = glfwGetTime();
|
|
|
|
while (!glfwWindowShouldClose(win) && !(g_screensaver && g_quit)) {
|
|
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;
|
|
glfwGetFramebufferSize(win, &fbw, &fbh);
|
|
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 */
|
|
|
|
glClearColor(0.006f, 0.010f, 0.035f, 1.0f);
|
|
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();
|
|
}
|
|
|
|
if (!g_screensaver) save_settings(); /* persist settings (not from the saver) */
|
|
glfwDestroyWindow(win);
|
|
glfwTerminate();
|
|
return 0;
|
|
}
|