diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2364a07..9cb6b6f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -26,7 +26,53 @@ "Bash(rm -f /tmp/st2 /tmp/st2.c)", "Bash(convert /tmp/vg_frame.ppm -crop 760x360+340+60 +repage /tmp/vg_faces.png)", "Bash(convert /tmp/vg_frame.ppm -crop 760x300+340+430 +repage /tmp/vg_googie.png)", - "Bash(convert /tmp/vg_frame.ppm -crop 360x340+150+60 +repage /tmp/vg_angry.png)" + "Bash(convert /tmp/vg_frame.ppm -crop 360x340+150+60 +repage /tmp/vg_angry.png)", + "Read(//usr/bin/**)", + "Read(//usr/x86_64-w64-mingw32/**)", + "Read(//usr/x86_64-w64-mingw32/lib/**)", + "Bash(dpkg -l)", + "Bash(timeout 12 curl -sI https://github.com)", + "Bash(timeout 12 curl -sI https://deb.debian.org)", + "Bash(timeout 60 apt-get download gcc-mingw-w64-x86-64-posix)", + "Bash(timeout 90 curl -sL -o glfw-win.zip https://github.com/glfw/glfw/releases/download/3.4/glfw-3.4.bin.WIN64.zip)", + "Bash(unzip -o -q glfw-win.zip -d /tmp/)", + "Bash(identify /tmp/vg_win.png)", + "Bash(awk '{print \"screenshot size:\", $3}')", + "Bash(convert /tmp/vg_win.png -crop 300x300+0+0 +repage -resize 200% /tmp/vg_win_crop.png)", + "Bash(convert /tmp/vg_win.png -crop 1140x800+0+20 +repage /tmp/vg_win_crop.png)", + "Bash(export WINEDEBUG=-all DISPLAY=:0)", + "Bash(echo \"=== /p preview: should exit cleanly and fast ===\")", + "Bash(timeout 15 wine ./vectorgons.scr /p 12345)", + "Bash(echo \"exit=$?\")", + "Bash(echo)", + "Bash(convert /tmp/scr.png -resize 60% /tmp/scr_small.png)", + "Bash(grep -n \"static void add_ball\\\\|static void update_clocks\\\\|static void add_symbol\\\\|#define MAX_SHAPES\\\\|Vectorgons: %d\\\\|add_symbol\\(build_hand\\)\\\\|update_clocks\\(\\);$\" vectorgons.c)", + "Bash(convert /tmp/vg_frame.ppm -crop 1100x380+0+40 +repage /tmp/vg_sil_top.png)", + "Bash(convert /tmp/vg_frame.ppm -crop 1100x340+0+400 +repage /tmp/vg_sil_bot.png)", + "Bash(grep -n \"int active = active_count\\(\\);\\\\|for \\(int i = 0; i < active; i++\\)\\\\|if \\(!g_screensaver\\) {\\\\s*$\\\\|render_osd\\(fbw, fbh, osd_alpha\\);\" vectorgons.c)", + "Bash(echo \"=== exe launches & loads shapes? ===\")", + "Bash(timeout 12 wine ./vectorgons.exe)", + "Bash(timeout 12 wine ./vectorgons.scr /p 0)", + "Bash(echo \"scr /p exit=$?\")", + "Bash(timeout 8 wine ./vectorgons.scr /s)", + "Bash(python3 verify_mirror.py)", + "Bash(sed 's#-lglfw -lGLU -lGL#X#' /dev/null)", + "Bash(DISPLAY=:0 timeout 6 wine vectorgons.exe)", + "Bash(tail -3 /tmp/vgtest/wine.log)", + "Bash(python3 count3.py)", + "Bash(cc -O2 -std=c11 -o cnt3 cnt3.c -lglfw -lGLU -lGL -lm)", + "Bash(DISPLAY=:0 VG_COUNT=1 timeout 8 ./cnt3)", + "Bash(python3 verify_counts.py)", + "Bash(python3 verify_visual.py)", + "Bash(echo \"exit=$? \\(no matches expected\\)\")", + "Bash(DISPLAY=:0 timeout 3 ./vectorgons)", + "Bash(echo \"normal-config exit=$? \\(124=ran ok\\)\")", + "Bash(cp ~/.vectorgons /tmp/vgtest/cfg.bak)", + "Bash(printf 'mag_count=1000\\\\nglass_count=1000\\\\nmirror_count=1000\\\\ndensity=200\\\\nrender_dist=400\\\\nspeed=20\\\\n')", + "Bash(DISPLAY=:0 timeout 4 ./vectorgons)", + "Bash(python3 verify_smooth.py)", + "Bash(convert smooth.ppm -crop 760x500+330+150 +repage -resize 200% smooth_zoom.png)", + "Bash(python3 verify_gm.py)" ] } } diff --git a/Makefile b/Makefile index 669a6be..82ad284 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,29 @@ $(TARGET): $(SRC) run: $(TARGET) ./$(TARGET) -clean: - rm -f $(TARGET) +# --- Windows x64 cross-compile (MinGW-w64) --------------------------------- +# Produces a self-contained vectorgons.exe (statically links GLFW and the +# GCC/threads runtime; at run time it needs only Windows system DLLs: +# opengl32, glu32, gdi32, user32, shell32, kernel32). Override WINCC / GLFWDIR +# for your toolchain and GLFW (https://www.glfw.org/download, WIN64 binaries): +# make windows WINCC=x86_64-w64-mingw32-gcc GLFWDIR=/path/to/glfw-3.4.bin.WIN64 +WINCC ?= x86_64-w64-mingw32-gcc +GLFWDIR ?= /tmp/glfw-3.4.bin.WIN64 +WINFLAGS = -O2 -std=c11 -Wall -Wextra -mwindows -static -s +WINLIBS = -lglfw3 -lglu32 -lopengl32 -lgdi32 -luser32 -lshell32 -.PHONY: run clean +windows: $(SRC) + $(WINCC) $(WINFLAGS) -I$(GLFWDIR)/include -o $(TARGET).exe $(SRC) \ + -L$(GLFWDIR)/lib-mingw-w64 $(WINLIBS) + +# Windows screensaver: same code with -DSCREENSAVER, output named .scr. +# Install by right-clicking the .scr in Windows -> Install (or drop it in +# C:\Windows\System32 and pick it in Settings > Lock screen > Screen saver). +screensaver: $(SRC) + $(WINCC) $(WINFLAGS) -DSCREENSAVER -I$(GLFWDIR)/include -o $(TARGET).scr $(SRC) \ + -L$(GLFWDIR)/lib-mingw-w64 $(WINLIBS) + +clean: + rm -f $(TARGET) $(TARGET).exe $(TARGET).scr + +.PHONY: run windows screensaver clean diff --git a/README.md b/README.md index 7baa02d..dc539f9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A starfield simulator — except instead of stars, colorful **vector-drawn platonic solids** tumble through space toward the camera. -**~165 shape types** are rendered as glowing wireframes — each spawns far away +**~530 shape types** are rendered as glowing wireframes — each spawns far away at a random size, tumbles on its own random axis, and streams past the camera before being recycled at the render-distance sphere. Solids never overlap. @@ -33,7 +33,69 @@ The shape set includes: question mark, exclamation point, hash/pound (`#`), dollar sign (`$`), pound sterling (`£`), and the **at sign (`@`)** - **Object iconography**, each in **2-D** and **3-D**: space invaders, UFOs, - pac-man, alien heads, smoking pipes, umbrellas, and hands + pac-man and pac-man ghosts, alien heads, smoking pipes, umbrellas, hands, + ice cream cones, light bulbs, mitochondria, windowpanes, bowling balls and + pins, baseball bats, beer bottles, nonic pint glasses, whiskey bottles, + telephones, telephone booths, radio towers, and atoms with orbiting electrons + (the volumetric bowling ball is a true wireframe sphere, and the 3-D atom has + real orbits + nucleus + electron balls rather than a flat extrusion) +- **More 3-D objects** — many built as true **solids of revolution** (a profile + lathed around an axis): a full **chess set** (pawn, rook, bishop, knight, + queen, king), candlesticks with a flame, coffee cups, lanterns, hurricane + lamps, quartz crystals, pineapples, and a parametric **torus** — plus extruded + silhouettes of padlocks, antique keys, bananas, electric guitars, tacos, pizza + slices, hammers, wrenches, directional and drafting compasses, set-squares, + eyeglasses, forks, spoons, fountain pens, the four classical element symbols + (fire/water/air/earth), the squared-circle "philosopher's stone", a couple of + retro stylized gaming/OS marks, a retro joystick, and a generic starship +- **Even more objects** in 2-D and 3-D: top hats, baseball caps, noses, hearts, + computer keyboards, floppy disks, bells, books, and two more stylized retro + computer marks. Every representational *object* now has both a flat 2-D and a + 3-D form — the once-3-D-only chess pieces, lathed solids, torus, and atomic + starbursts all gained matching 2-D silhouettes. +- **Yet more**, all in 2-D and 3-D: IC chip packages (DIP, QFP, PGA, and a + generic CPU — the styles behind the 6502/Z80/386/486 and friends), hot dogs, + hamburgers, monoliths, ringed planets, unicycles, hula hoops, mechanical + gyroscopes, microscopes, telescopes, **magnifying glasses that actually + magnify the field behind them**, thermometers, calculators, staplers, paper + clips, palm trees, saguaro cacti, grape bunches, sushi rolls, corkscrews (a + real 3-D helix), bottle openers, lightning bolts, and batteries +- **An even bigger batch** in 2-D and 3-D: everlasting gobstoppers, pants, + shirts, jacks, dice, K/Q/J/A playing cards, roulette wheels, slot machines, + fire hydrants, bucky balls (a real truncated icosahedron), yo-yos, brains, + d6 and d20 dice, thimbles, automobiles, couches, stop signs, office chairs, + DNA double helices, skull-and-crossbones, goldfish, squid, pine cones, maple + leaves, sea anemones, wagon wheels, submarines, sailing ships, anchors, swiss + cheese wedges, and the anarchy symbol +- **Animated objects** (the 3-D form is rebuilt every frame): a **Hoberman + sphere** that unfolds and folds, a **Rubik's cube** whose top layer turns, a + **spinning top** and **dreidel** that precess, a **lighthouse** with a beam + that sweeps around, a **blinking eyeball**, a **skull with a chattering jaw**, + **chattering teeth**, an **octopus** with waving arms, a **jellyfish** with + drifting tentacles, a **fish** swimming with a flapping tail, **scissors** + that scissor, **flickering flames** (free and on a zippo lighter), and + **spinning gears** and a **spinning fan** +- A **3-D refracting prism**: a triangular glass prism that takes a white beam + in one face and splits it into a full rainbow spectrum fanning out the other — + the colours are drawn straight into the shape, independent of the field hue +- A **3-D glass sphere** (crystal ball): a real ball lens that grabs the field + behind it and shows it **inverted and fish-eye-distorted**, with a bright + fresnel rim and a specular reflection highlight. Like the magnifier it samples + the grabbed framebuffer, but with an inverting non-linear radial warp +- An **opaque polished mirror sphere** (chrome ball): a convex mirror that + **reflects the surrounding field upright** in a wide fish-eye — the optical + opposite of the glass ball (which inverts what's behind it). It has metallic + sky/ground shading so it reads as a solid metal ball even over empty space, + plus a sharp specular highlight. The magnifier, glass-ball and mirror-ball + **counts are each user-settable (0–1000)** — these are *extra* bodies added on + top of the density field, so dialling them up never thins out the normal shapes +- **Still more** in 2-D and 3-D: cassette tapes, vinyl records, CDs, quarters, + kites, baseballs, a police box, roses, shoes, boots, pianos, clouds, soup + cans, icebergs, pyramids (a real square pyramid in 3-D), bombs, combs, train + crossing signs, mailboxes, ship's wheels, axes, popcorn, teapots, church + bells, anvils, book stacks, springs and slinkies (real 3-D helices), extra + old keys, and a set of traditional alchemical and occult symbols (sulfur, + mercury, salt, antimony, pentagram, ankh, all-seeing eye, triple moon) - **Live clocks** (analog and digital, 2-D and 3-D) that show the **actual current time** — their geometry is rebuilt every frame, so the analog hands sweep and the seven-segment digital display ticks along in real time @@ -55,6 +117,46 @@ make ./vectorgons # or: make run ``` +## Windows 11 (cross-compiled from Linux) + +A self-contained `vectorgons.exe` can be cross-compiled with the +**MinGW-w64** toolchain. It statically links GLFW and the GCC/threads runtime, +so on Windows it needs **only system DLLs** (`opengl32`, `glu32`, `gdi32`, +`user32`, `shell32`, `kernel32`) — just copy `vectorgons.exe` to a Windows 11 +machine and double-click it. No console window, no extra DLLs. + +```sh +# 1. toolchain: sudo apt install gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-x86-64-dev +# 2. GLFW: download the WIN64 binaries from https://www.glfw.org/download +# 3. build: +make windows WINCC=x86_64-w64-mingw32-gcc GLFWDIR=/path/to/glfw-3.4.bin.WIN64 +make screensaver WINCC=x86_64-w64-mingw32-gcc GLFWDIR=/path/to/glfw-3.4.bin.WIN64 +``` + +The legacy immediate-mode OpenGL it uses (and the `glu32` perspective helpers) +are provided by the standard Windows OpenGL driver, so it runs on any Windows 11 +box with working GPU drivers. + +### Screensaver (`vectorgons.scr`) + +`make screensaver` builds the **same program as a Windows screensaver** (it's +the same source with `-DSCREENSAVER`, output named `.scr`). It handles the +standard screensaver command line: + +- **`/s`** (or no argument) — run full-screen on the primary monitor; **any key, + mouse click, or real mouse movement exits**. The on-screen control panel is + hidden. +- **`/c`** — show a short configuration dialog. There are no in-saver controls; + the saver simply uses whatever settings the main app last saved (see below), + so run `vectorgons.exe`, dial in the look you want, quit, and the saver picks + it up. +- **`/p`** — preview (the little monitor in Windows' settings); not supported, so + it exits cleanly and the preview stays blank. + +To install on Windows: right-click `vectorgons.scr` → **Install** (or copy it to +`C:\Windows\System32` and choose it under *Settings → Personalization → Lock +screen → Screen saver*). Like the `.exe`, it needs only Windows system DLLs. + ## Settings persistence Your settings are saved automatically on exit to `~/.vectorgons` (a plain @@ -87,6 +189,9 @@ out over a few seconds. Press any key to bring it back. | Color mode | `M` | Toggle **single-hue** ⇄ **multicolor** | | Glow | `O` / `L` | CRT glow / light bleed around vectors | | Flicker | `G` / `H` | Vector flicker intensity | +| Magnifier count | `B` / `P` | How many magnifying glasses are on screen (0–1000, ±1 per press; extra, on top of the density field) | +| Glass-ball count | `1` / `2` | How many refracting glass spheres are on screen (0–1000) | +| Mirror-ball count | `3` / `4` | How many reflecting mirror spheres are on screen (0–1000) | | Shapes | `N` | Toggle random ⇄ cycling shape spawns | | Fullscreen | `F` or `F11` | Toggle fullscreen | | Pause | `Space` | | @@ -141,6 +246,17 @@ out over a few seconds. Press any key to bring it back. ghost copies bloom that glow into a larger volume than the hardware line-width cap (~10px) could reach on its own. The crisp core is drawn last so the vector stays sharp; the glow setting scales the mist's spread and brightness. +- **Magnifying glasses** are real lenses. After the field is drawn, the back + buffer is grabbed into a texture (`glCopyTexSubImage2D`); each lens body is + then projected to its screen position and drawn as a disc that samples that + texture with a radial zoom — so the wireframes behind the glass appear + genuinely enlarged, with the rim and handle drawn on top. (Pure legacy GL, so + it works on the Linux, Windows, and screensaver builds alike.) +- **Glass and mirror spheres** reuse the same grabbed-framebuffer trick. The + glass ball resamples it with an *inverting* fish-eye warp (refraction). The + mirror ball is a convex mirror: it samples *outward* from its centre with an + *upright* fish-eye, so the surrounding shapes wrap onto its surface the right + way up, over an opaque metallic sky/ground gradient with a specular highlight. - **Flicker** randomly dips each solid's brightness per frame, scaled by the flicker setting. - **Rendering** uses legacy OpenGL immediate mode (`GL_LINES`) with additive diff --git a/vectorgons b/vectorgons index c91cfc4..38a139b 100755 Binary files a/vectorgons and b/vectorgons differ diff --git a/vectorgons.c b/vectorgons.c index 91abd44..073df35 100644 --- a/vectorgons.c +++ b/vectorgons.c @@ -14,11 +14,35 @@ * molecules, diabolos, sputniks), 2-D and 3-D symbols (smiley, frowny, * angry, biohazard, peace, cross, ? ! # $ £ @), object iconography * (space invaders, UFOs, pac-man, alien heads, pipes, umbrellas, hands) - * and live analog/digital clocks showing the real current time, 4/5/6-D + * 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 @@ -33,9 +57,24 @@ */ #define GL_SILENCE_DEPRECATION +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include /* provides APIENTRY / CALLBACK that the GL headers need */ +#endif #include #include +/* Legacy GL constants that the Windows (MinGW) OpenGL 1.1 headers may omit. + * The values are standard; the GPU driver provides the functionality at run + * time. Guarded so platforms whose headers already define them are unaffected. */ +#ifndef GL_ALIASED_LINE_WIDTH_RANGE +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#endif +#ifndef GL_MULTISAMPLE +#define GL_MULTISAMPLE 0x809D +#endif + #include #include #include @@ -61,7 +100,7 @@ #define MAXD 6 #define MAX_VERTS 128 #define MAX_EDGES 512 -#define MAX_SHAPES 220 +#define MAX_SHAPES 720 #define TAU 6.283185307179586 typedef struct { @@ -75,6 +114,22 @@ typedef struct { static Solid solids[MAX_SHAPES]; static int num_shapes = 0; static int clock_idx[4] = {-1,-1,-1,-1}; /* analog2d, analog3d, digital2d, digital3d */ +static 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 */ @@ -980,6 +1035,1292 @@ static void update_clocks(void) { sym_init(&b); build_digital(&b, lt); sym_fill_solid(&solids[clock_idx[3]], &b, 0.5f); } +/* ---- more object iconography (flat 2-D + extruded 3-D) -------------- */ + +/* helper: closed outline from a right-half profile, mirrored across x=0; + * skips duplicating the shared top/bottom centre points (those have x==0). */ +static void sym_profile(SymB *b, const double (*rp)[2], int n) { + int first=-1, prev=-1; + for (int i=0;i=0)sym_edge(b,prev,idx); + prev=idx; } + for (int i=n-1;i>=0;i--){ + if (rp[i][0]==0.0) continue; /* don't repeat centre points */ + int idx=sym_pt(b, -rp[i][0], rp[i][1]); + sym_edge(b,prev,idx); prev=idx; } + sym_edge(b,prev,first); +} + +static void build_ghost(SymB *b) { /* pac-man ghost */ + double R=0.78, top=0.18, bot=-0.7; + int first=sym_pt(b,-R,bot), prev=first; + for (int i=0;i<=12;i++){ double a=TAU*0.5 - TAU*0.5*i/12; /* dome 180->0 */ + int idx=sym_pt(b, R*cos(a), top + R*sin(a)); sym_edge(b,prev,idx); prev=idx; } + int rb=sym_pt(b, R, bot); sym_edge(b,prev,rb); prev=rb; + for (int i=0;i<4;i++){ /* wavy skirt, points down */ + double x1=R-2*R*(i+1)/4, xm=R-2*R*(i+0.5)/4; + int v=sym_pt(b,xm,bot+0.2); sym_edge(b,prev,v); prev=v; + int p=sym_pt(b,x1,bot); sym_edge(b,prev,p); prev=p; } + sym_edge(b,prev,first); + sym_ring(b,-0.27,0.18,0.19,8); sym_ring(b, 0.27,0.18,0.19,8); /* eyes */ + sym_ring(b,-0.19,0.18,0.07,4); sym_ring(b, 0.35,0.18,0.07,4); /* pupils */ +} +static void build_icecream(SymB *b) { /* ice cream cone */ + int apex=sym_pt(b,0,-1.05), tl=sym_pt(b,-0.42,0.05), tr=sym_pt(b,0.42,0.05); + sym_edge(b,apex,tl); sym_edge(b,apex,tr); + sym_line(b,-0.28,-0.28, 0.02,-0.02); /* waffle hatch */ + sym_line(b, 0.0,-0.5, 0.30,-0.2); + sym_line(b,-0.42,0.05, 0.42,0.05); /* scoop base */ + sym_arc(b,-0.21,0.18,0.23, sym_deg(0), sym_deg(180), 6); /* three scoop bumps */ + sym_arc(b, 0.21,0.18,0.23, sym_deg(0), sym_deg(180), 6); + sym_arc(b, 0.0, 0.34,0.24, sym_deg(0), sym_deg(180), 6); +} +static void build_bulb(SymB *b) { /* light bulb */ + double cy=0.32, R=0.58, bx=0.24; + sym_arc(b, 0,cy, R, sym_deg(-55), sym_deg(235), 16); /* glass, open at neck */ + int start=0, last=b->np-1; + int br=sym_pt(b, bx,-0.18); sym_edge(b,start,br); /* start = lower-right */ + int bl=sym_pt(b,-bx,-0.18); sym_edge(b,last,bl); /* last = lower-left */ + int br2=sym_pt(b, bx,-0.42), bl2=sym_pt(b,-bx,-0.42); + sym_edge(b,br,br2); sym_edge(b,bl,bl2); sym_edge(b,bl2,br2); /* screw base */ + sym_line(b,-bx,-0.26, bx,-0.26); sym_line(b,-bx,-0.34, bx,-0.34); /* threads */ + sym_line(b,-0.15,0.18,-0.05,0.42); /* filament W */ + sym_line(b,-0.05,0.42, 0.05,0.18); + sym_line(b, 0.05,0.18, 0.15,0.42); +} +static void build_mito(SymB *b) { /* mitochondrion */ + int seg=18, first=-1, prev=-1; + for (int i=0;i=0)sym_edge(b,prev,idx); + prev=idx; } + sym_edge(b,prev,first); + for (int i=0;i<4;i++) sym_arc(b,-0.6+0.4*i, 0.24,0.17,sym_deg(0),sym_deg(-180),4); /* cristae */ + for (int i=0;i<3;i++) sym_arc(b,-0.4+0.4*i,-0.24,0.17,sym_deg(0),sym_deg( 180),4); +} +static void build_window(SymB *b) { /* windowpane */ + const double fr[]={-0.8,-1.0, 0.8,-1.0, 0.8,1.0, -0.8,1.0}; + const double in[]={-0.7,-0.9, 0.7,-0.9, 0.7,0.9, -0.7,0.9}; + sym_poly(b,fr,4,1); sym_poly(b,in,4,1); + sym_line(b,0,-0.9,0,0.9); sym_line(b,-0.7,0,0.7,0); /* mullions */ + sym_line(b,-0.92,-1.0,0.92,-1.0); /* sill */ +} +static void build_bowlingpin(SymB *b) { /* bowling pin */ + const double rp[][2]={ {0,1.0},{0.12,0.85},{0.14,0.68},{0.30,0.42},{0.34,0.15}, + {0.24,-0.2},{0.16,-0.5},{0.26,-0.82},{0.21,-1.0} }; + sym_profile(b,rp,9); + sym_line(b,-0.16,0.6,0.16,0.6); sym_line(b,-0.18,0.52,0.18,0.52); /* neck stripes */ +} +static void build_bat(SymB *b) { /* baseball bat */ + const double rp[][2]={ {0,-1.0},{0.13,-0.95},{0.13,-0.86},{0.06,-0.78}, + {0.05,-0.2},{0.10,0.35},{0.17,0.82},{0.15,0.96},{0,1.0} }; + sym_profile(b,rp,9); + sym_line(b,-0.10,-0.7,0.10,-0.7); /* grip line */ +} +static void build_beerbottle(SymB *b) { /* beer bottle */ + const double rp[][2]={ {0,-1.0},{0.32,-0.97},{0.34,-0.2},{0.30,0.05}, + {0.12,0.35},{0.11,0.82},{0.15,0.9},{0.15,1.0},{0,1.0} }; + sym_profile(b,rp,9); + sym_line(b,-0.34,-0.45,0.34,-0.45); sym_line(b,-0.34,-0.05,0.34,-0.05); /* label */ +} +static void build_nonic(SymB *b) { /* nonic pint glass (bulge near rim) */ + const double rp[][2]={ {0.30,-1.0},{0.34,-0.5},{0.38,0.45},{0.47,0.62}, + {0.40,0.74},{0.42,0.96} }; + int n=6, first=-1, prev=-1; + for (int i=0;i=0)sym_edge(b,prev,idx); + prev=idx; } + for (int i=n-1;i>=0;i--){ int idx=sym_pt(b,-rp[i][0],rp[i][1]); + sym_edge(b,prev,idx); prev=idx; } + sym_edge(b,prev,first); /* closes base + rim */ +} +static void build_whiskey(SymB *b) { /* whiskey bottle */ + const double rp[][2]={ {0,-1.0},{0.40,-0.95},{0.42,-0.1},{0.40,0.2}, + {0.20,0.45},{0.18,0.7},{0.22,0.78},{0.22,0.92},{0,0.92} }; + sym_profile(b,rp,9); + sym_line(b,-0.40,-0.5,0.40,-0.5); sym_line(b,-0.40,0.05,0.40,0.05); /* label box */ + sym_line(b,-0.40,-0.5,-0.40,0.05); sym_line(b,0.40,-0.5,0.40,0.05); +} +static void build_phone(SymB *b) { /* desk telephone */ + const double base[]={-0.7,-0.55, 0.7,-0.55, 0.62,-0.05, -0.62,-0.05}; + sym_poly(b,base,4,1); + sym_ring(b,0,-0.3,0.17,8); /* rotary dial */ + sym_arc(b,0,0.78,0.66, sym_deg(205), sym_deg(335), 10); /* handset handle */ + sym_ring(b,-0.6,0.5,0.15,7); sym_ring(b, 0.6,0.5,0.15,7); /* ear / mouth piece */ +} +static void build_booth(SymB *b) { /* red telephone box */ + const double box[]={-0.5,-1.0, 0.5,-1.0, 0.5,0.7, -0.5,0.7}; + const double crown[]={-0.5,0.7, -0.42,0.86, 0.42,0.86, 0.5,0.7}; + const double win[]={-0.4,-0.2, 0.4,-0.2, 0.4,0.55, -0.4,0.55}; + sym_poly(b,box,4,1); sym_poly(b,crown,4,0); sym_poly(b,win,4,1); + sym_line(b,-0.45,0.62,0.45,0.62); /* TELEPHONE sign band */ + sym_line(b,-0.4,0.12,0.4,0.12); sym_line(b,-0.4,0.33,0.4,0.33); /* pane grid */ + sym_line(b,0,-0.2,0,0.55); +} +static void build_radiotower(SymB *b) { /* lattice radio tower */ + sym_line(b,-0.4,-1.0,-0.08,0.7); sym_line(b,0.4,-1.0,0.08,0.7); /* legs */ + for (int i=0;i<5;i++){ + double t0=i/5.0, t1=(i+1)/5.0; + double lx0=-0.4+0.32*t0, rx0=0.4-0.32*t0, y0=-1.0+1.7*t0; + double lx1=-0.4+0.32*t1, rx1=0.4-0.32*t1, y1=-1.0+1.7*t1; + sym_line(b,lx0,y0,rx1,y1); sym_line(b,rx0,y0,lx1,y1); /* cross-brace X */ + sym_line(b,lx1,y1,rx1,y1); /* horizontal */ + } + sym_line(b,0,0.7,0,1.0); /* mast */ + sym_arc(b,0,1.0,0.18,sym_deg(20),sym_deg(160),5); /* signal waves */ + sym_arc(b,0,1.0,0.30,sym_deg(25),sym_deg(155),6); +} +static void build_atom_e2d(SymB *b) { /* flat atom with electrons */ + sym_ring(b,0,0,0.14,6); /* nucleus */ + const double rots[3]={0,60,120}; + for (int k=0;k<3;k++) sym_ellipse(b,0,0,1.0,0.36,sym_deg(rots[k]),12); + for (int k=0;k<3;k++){ /* one electron per orbit */ + double rot=sym_deg(rots[k]), a=sym_deg(30+k*45); + double ex=cos(a), ey=0.36*sin(a); + sym_ring(b, ex*cos(rot)-ey*sin(rot), ex*sin(rot)+ey*cos(rot), 0.08, 5); + } +} + +/* ---- volumetric versions (dedicated 3-D generators) ---------------- */ + +/* 3-D atom: three tilted orbits, an octahedral nucleus, and an electron + * ball riding each orbit. (The whole body tumbles, so the electrons sweep.) */ +static void gen_atom_electrons3d(void) { + Solid *s=new_solid(3); int seg=20; + const double tilt[3]={0,60,120}; + for (int o=0;o<3;o++){ + double t=sym_deg(tilt[o]); int first=-1, prev=-1; + for (int i=0;inv; PV(s,x,y2,z2,0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; + } + AE(s,prev,first); + double a=sym_deg(25+o*47), x=cos(a), y=sin(a), z=0; /* electron */ + double y2=y*cos(t)-z*sin(t), z2=y*sin(t)+z*cos(t); + add_ball(s, x,y2,z2, x,y2,z2, 0.11, 5); + } + int b0=s->nv; double r=0.13; + PV(s,r,0,0,0,0,0);PV(s,-r,0,0,0,0,0);PV(s,0,r,0,0,0,0); + PV(s,0,-r,0,0,0,0);PV(s,0,0,r,0,0,0);PV(s,0,0,-r,0,0,0); + int oe[12][2]={{0,2},{0,3},{0,4},{0,5},{1,2},{1,3},{1,4},{1,5},{2,4},{2,5},{3,4},{3,5}}; + for(int e=0;e<12;e++) AE(s,b0+oe[e][0],b0+oe[e][1]); + center_normalize(s); +} +/* 3-D bowling ball: a wireframe sphere with three finger holes up front. */ +static void gen_bowlingball3d(void) { + Solid *s=new_solid(3); int lats=4, seg=16; + for (int la=1;la<=lats;la++){ + double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25, y=sin(phi), rr=cos(phi); + int first=-1, prev=-1; + for (int i=0;inv; + PV(s,rr*cos(a),y,rr*sin(a),0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; } + AE(s,prev,first); + } + for (int m=0;m<2;m++){ + double mo=TAU*m/4; int first=-1, prev=-1; + for (int i=0;inv; PV(s,x1,y,z1,0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; } + AE(s,prev,first); + } + const double hole[3][2]={{-0.2,0.25},{0.2,0.25},{0.0,-0.05}}; + for (int h=0;h<3;h++){ + double hx=hole[h][0], hy=hole[h][1], hz=sqrt(fmax(0.0,1.0-hx*hx-hy*hy)); + add_ball(s, hx,hy,hz, hx,hy,hz, 0.1, 6); + } + center_normalize(s); +} + +/* ================================================================== */ +/* A larger batch of 3-D objects: solids of revolution (lathe), a */ +/* parametric torus, and extruded silhouettes for the flatter items. */ +/* ================================================================== */ + +/* Revolve a profile of (radius,height) points around the Y axis into a + * wireframe surface of revolution. r<~0 makes an on-axis point (apex/pole). + * Adds to `s` without normalizing, so callers can append details first. */ +static void lathe_into(Solid *s, const double (*prof)[2], int n, int seg) { + int rs[40], isp[40]; + for (int i = 0; i < n && i < 40; i++) { + double r = prof[i][0], y = prof[i][1]; + rs[i] = s->nv; + if (r < 1e-4) { PV(s, 0, y, 0, 0,0,0); isp[i] = 1; } + else { + isp[i] = 0; + for (int j = 0; j < seg; j++) { double a = TAU*j/seg; PV(s, r*cos(a), y, r*sin(a), 0,0,0); } + for (int j = 0; j < seg; j++) AE(s, rs[i]+j, rs[i]+(j+1)%seg); + } + } + for (int i = 0; i < n-1; i++) { + if (isp[i] && isp[i+1]) AE(s, rs[i], rs[i+1]); + else if (isp[i]) for (int j=0;jnv; + PV(s, cx+r*cos(a), cy+r*sin(a), 0, 0,0,0); + if(first<0)first=idx; + if(prev>=0)AE(s,prev,idx); + prev=idx; } + if(first>=0) AE(s,prev,first); +} + +/* ---- chess pieces (lathe profiles, bottom -> top) ------------------ */ +static void gen_pawn(void){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86},{0.15,-0.76}, + {0.12,-0.45},{0.22,-0.36},{0.11,-0.28},{0.19,-0.12},{0.25,0.0},{0.22,0.13},{0.10,0.27},{0,0.33}}; + gen_lathe(p,12,8); } +static void gen_rook(void){ const double p[][2]={{0,-1.0},{0.40,-1.0},{0.34,-0.84},{0.19,-0.68}, + {0.19,0.35},{0.33,0.5},{0.33,0.82},{0.24,0.82},{0.24,0.6},{0,0.6}}; + Solid*s=new_solid(3); lathe_into(s,p,10,8); + for(int k=0;k<4;k++){ double a=TAU*k/4; double c=cos(a),sn=sin(a); /* crenellation notches */ + int v0=s->nv; PV(s,0.30*c,0.82,0.30*sn,0,0,0); PV(s,0.30*c,0.95,0.30*sn,0,0,0); AE(s,v0,v0+1);} + center_normalize(s); } +static void gen_bishop(void){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86},{0.16,-0.74}, + {0.13,-0.4},{0.24,-0.3},{0.12,-0.22},{0.20,0.0},{0.16,0.3},{0.09,0.55},{0.06,0.66},{0,0.72}, + {0.05,0.78},{0,0.86}}; + gen_lathe(p,14,8); } +static void gen_king(void){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85},{0.17,-0.72}, + {0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.20,0.05},{0.27,0.28},{0.18,0.5},{0.16,0.6},{0,0.6}}; + Solid*s=new_solid(3); lathe_into(s,p,12,8); + int a=s->nv; PV(s,0,0.6,0,0,0,0); PV(s,0,1.0,0,0,0,0); AE(s,a,a+1); /* cross */ + int c=s->nv; PV(s,-0.13,0.84,0,0,0,0); PV(s,0.13,0.84,0,0,0,0); AE(s,c,c+1); + center_normalize(s); } +static void gen_queen(void){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85},{0.17,-0.72}, + {0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.22,0.1},{0.29,0.38},{0.30,0.52}}; + Solid*s=new_solid(3); lathe_into(s,p,10,8); + for(int k=0;k<7;k++){ double a=TAU*k/7; double c=cos(a),sn=sin(a); /* coronet spikes */ + int v0=s->nv; PV(s,0.28*c,0.52,0.28*sn,0,0,0); PV(s,0.30*c,0.74,0.30*sn,0,0,0); AE(s,v0,v0+1); + add_ball(s,0.30*c,0.74,0.30*sn, c,0.3,sn, 0.05,4); } + center_normalize(s); } + +/* ---- other solids of revolution ------------------------------------ */ +static void gen_candlestick(void){ const double p[][2]={{0,-1.0},{0.42,-1.0},{0.36,-0.85},{0.12,-0.7}, + {0.10,-0.2},{0.20,-0.05},{0.13,0.02},{0.11,0.08},{0.11,0.62},{0,0.62}}; + Solid*s=new_solid(3); lathe_into(s,p,10,8); + /* flame teardrop above the wick */ + int f=s->nv; PV(s,0,0.62,0,0,0,0); PV(s,0.07,0.75,0,0,0,0); PV(s,0,0.92,0,0,0,0); + PV(s,-0.07,0.75,0,0,0,0); AE(s,f,f+1); AE(s,f+1,f+2); AE(s,f+2,f+3); AE(s,f+3,f); + center_normalize(s); } +static void gen_coffeecup(void){ const double p[][2]={{0,-0.6},{0.46,-0.6},{0.5,-0.45},{0.46,0.5},{0.40,0.5}}; + Solid*s=new_solid(3); lathe_into(s,p,5,10); + /* handle: a C-loop on the +X side, in the X-Y plane */ + int h=s->nv; PV(s,0.5,0.25,0,0,0,0); PV(s,0.85,0.2,0,0,0,0); PV(s,0.88,-0.1,0,0,0,0); + PV(s,0.55,-0.3,0,0,0,0); AE(s,h,h+1); AE(s,h+1,h+2); AE(s,h+2,h+3); + center_normalize(s); } +static void gen_lantern(void){ const double p[][2]={{0,-1.0},{0.35,-0.95},{0.40,-0.75},{0.34,-0.6}, + {0.36,0.45},{0.30,0.6},{0.34,0.72},{0.18,0.82},{0.12,0.9},{0,0.9}}; + Solid*s=new_solid(3); lathe_into(s,p,10,8); + add_xy_ring(s,0,1.05,0.13,8); /* hanging ring */ + int v=s->nv; PV(s,0,0.9,0,0,0,0); PV(s,0,0.92,0,0,0,0); AE(s,v,v+1); + center_normalize(s); } +static void gen_hurricane(void){ const double p[][2]={{0,-1.0},{0.34,-0.95},{0.40,-0.78},{0.30,-0.6}, + {0.14,-0.5},{0.30,-0.4},{0.30,-0.28},{0.20,-0.18},{0.26,0.55},{0.34,0.85},{0.30,1.0}}; + gen_lathe(p,11,8); } /* oil base + glass chimney */ +static void gen_quartz(void){ const double p[][2]={{0,-1.05},{0.30,-0.7},{0.45,-0.5},{0.45,0.5}, + {0.30,0.7},{0,1.05}}; + gen_lathe(p,6,6); } /* hexagonal crystal */ +static void gen_pineapple(void){ const double p[][2]={{0,-1.0},{0.25,-0.85},{0.42,-0.5},{0.45,0.1}, + {0.34,0.4},{0.18,0.55},{0,0.55}}; + Solid*s=new_solid(3); lathe_into(s,p,7,10); + for(int k=0;k<6;k++){ double a=TAU*k/6; double c=cos(a),sn=sin(a); /* crown leaves */ + int v=s->nv; PV(s,0.12*c,0.55,0.12*sn,0,0,0); PV(s,0.22*c,0.95,0.22*sn,0,0,0); AE(s,v,v+1); } + int t=s->nv; PV(s,0,0.55,0,0,0,0); PV(s,0,1.05,0,0,0,0); AE(s,t,t+1); + center_normalize(s); } +static void gen_torus3d(void){ + Solid *s=new_solid(3); int nu=16, nv=8; double R=0.72, rr=0.27; + for(int i=0;inp-1; /* right / left ends */ + sym_arc(b,0,-0.45,0.72,sym_deg(40),sym_deg(140),9); /* inner edge */ + int iR=oL+1, iL=b->np-1; + sym_edge(b,oR,iR); sym_edge(b,oL,iL); /* close the two tips */ + sym_line(b,-0.86,0.12,-0.97,0.28); /* stem */ +} +static void build_guitar(SymB *b){ /* electric guitar */ + sym_ellipse(b,0,-0.5,0.55,0.42,0,16); /* body */ + const double neck[]={-0.11,-0.15, 0.11,-0.15, 0.09,0.82, -0.09,0.82}; + const double head[]={-0.13,0.82, 0.13,0.82, 0.15,1.04, -0.15,1.04}; + sym_poly(b,neck,4,1); sym_poly(b,head,4,1); + for(int i=0;i<4;i++){ double y=0.05+i*0.18; sym_line(b,-0.10,y,0.10,y); } /* frets */ + sym_line(b,-0.26,-0.5,0.26,-0.5); sym_line(b,-0.26,-0.3,0.26,-0.3); /* pickups */ + sym_line(b,-0.2,-0.72,0.2,-0.72); /* bridge */ +} +static void build_taco(SymB *b){ + sym_arc(b,0,0.28,1.0,sym_deg(200),sym_deg(340),14); /* shell */ + for(int i=0;i<5;i++){ double x=-0.7+0.35*i; sym_arc(b,x,0.28,0.12,sym_deg(0),sym_deg(180),4);} /* fillings */ + sym_line(b,-0.94,0.0,0.94,0.0); +} +static void build_pizza(SymB *b){ + const double tri[]={0,-1.0, -0.62,0.7, 0.62,0.7}; + sym_poly(b,tri,3,1); + sym_arc(b,0,0.0,1.07,sym_deg(65),sym_deg(115),8); /* crust */ + sym_ring(b,-0.12,0.2,0.1,5); sym_ring(b,0.18,0.0,0.1,5); sym_ring(b,-0.02,-0.4,0.09,5); +} +static void build_hammer(SymB *b){ /* claw hammer */ + const double handle[]={-0.08,-1.0, 0.08,-1.0, 0.10,0.45, -0.06,0.45}; + sym_poly(b,handle,4,1); + const double head[]={-0.45,0.45, 0.5,0.45, 0.5,0.72, 0.18,0.72, 0.10,0.95, -0.10,0.95, + -0.2,0.72, -0.45,0.72}; + sym_poly(b,head,8,1); + sym_line(b,-0.45,0.58,-0.62,0.5); /* claw tip */ +} +static void build_wrench(SymB *b){ /* open/box wrench */ + const double handle[]={-0.10,-0.7, 0.10,-0.7, 0.12,0.45, -0.12,0.45}; + sym_poly(b,handle,4,1); + sym_arc(b,0,0.62,0.28,sym_deg(-40),sym_deg(220),12); /* open jaw (C) */ + sym_ring(b,0,-0.78,0.22,10); sym_ring(b,0,-0.78,0.12,6); /* box end */ +} +static void build_compass_dir(SymB *b){ /* directional compass */ + sym_ring(b,0,0,1.0,22); sym_ring(b,0,0,0.85,18); + const double star[]={0,0.8, 0.12,0.12, 0.8,0, 0.12,-0.12, 0,-0.8, -0.12,-0.12, -0.8,0, -0.12,0.12}; + sym_poly(b,star,8,1); /* compass rose */ + sym_line(b,0,0.8,0,-0.8); sym_line(b,-0.8,0,0.8,0); +} +static void build_compass_draft(SymB *b){ /* drafting compass (dividers) */ + sym_line(b,0,0.95,-0.45,-0.95); sym_line(b,0,0.95,0.45,-0.95); /* two legs */ + sym_ring(b,0,0.95,0.1,6); /* hinge */ + sym_line(b,0.45,-0.95,0.45,-0.7); /* pencil stub */ + sym_line(b,-0.45,-0.95,-0.5,-1.05); /* needle point */ +} +static void build_square(SymB *b){ /* drafting set-square (45 deg) */ + const double tri[]={-0.85,-0.7, 0.85,-0.7, -0.85,0.85}; + sym_poly(b,tri,3,1); + const double tri2[]={-0.6,-0.55, 0.45,-0.55, -0.6,0.4}; /* inner cut-out */ + sym_poly(b,tri2,3,1); +} +static void build_atari(SymB *b){ /* stylized 'fuji' mark (3 prongs) */ + sym_line(b,-0.7,-0.7,0.7,-0.7); /* base bar */ + sym_line(b,-0.08,-0.7,-0.08,0.8); sym_line(b,0.08,-0.7,0.08,0.8); /* centre prong */ + sym_arc(b,-0.95,-0.7,0.85,sym_deg(0),sym_deg(80),8); /* left prong curves out */ + sym_arc(b, 0.95,-0.7,0.85,sym_deg(100),sym_deg(180),8);/* right prong curves out */ +} +static void build_joystick(SymB *b){ /* retro joystick */ + const double base[]={-0.7,-0.8, 0.7,-0.8, 0.55,-0.2, -0.55,-0.2}; + sym_poly(b,base,4,1); + sym_line(b,0,-0.2,0,0.55); /* stick */ + sym_ring(b,0,0.62,0.22,10); /* ball top */ + sym_ring(b,-0.35,-0.5,0.1,6); /* button */ +} +static void build_debian(SymB *b){ /* stylized swirl */ + int seg=22, prev=-1; + for(int i=0;i<=seg;i++){ double t=(double)i/seg, a=t*TAU*0.95 + sym_deg(40); + double r=0.25+0.7*t; int idx=sym_pt(b,r*cos(a),r*sin(a)); + if(prev>=0)sym_edge(b,prev,idx); + prev=idx; } + sym_arc(b,0,0,0.95,sym_deg(60),sym_deg(330),14); /* open ring around the swirl */ +} +static void build_glasses(SymB *b){ /* spectacles */ + sym_ring(b,-0.5,0,0.36,12); sym_ring(b,0.5,0,0.36,12); + sym_arc(b,0,0.0,0.18,sym_deg(20),sym_deg(160),5); /* bridge */ + sym_line(b,-0.86,0.1,-1.0,0.25); sym_line(b,0.86,0.1,1.0,0.25); /* temples */ +} +static void build_fork(SymB *b){ + const double handle[]={-0.08,-1.0, 0.08,-1.0, 0.10,0.3, -0.10,0.3}; + sym_poly(b,handle,4,1); + for(int i=0;i<4;i++){ double x=-0.21+0.14*i; sym_line(b,x,0.3,x,0.92); } /* tines */ + sym_line(b,-0.21,0.3,0.21,0.3); +} +static void build_spoon(SymB *b){ + const double handle[]={-0.08,-1.0, 0.08,-1.0, 0.10,0.25, -0.10,0.25}; + sym_poly(b,handle,4,1); + sym_ellipse(b,0,0.55,0.26,0.42,0,12); /* bowl */ +} +static void build_pen(SymB *b){ /* fountain pen */ + const double barrel[]={-0.12,-1.0, 0.12,-1.0, 0.12,0.55, -0.12,0.55}; + sym_poly(b,barrel,4,1); + const double nib[]={-0.12,0.55, 0.12,0.55, 0,1.0}; /* nib */ + sym_poly(b,nib,3,1); + sym_line(b,0,0.62,0,0.95); /* nib slit */ + sym_line(b,0.12,0.1,0.20,0.1); sym_line(b,0.20,0.1,0.20,0.4); /* clip */ +} +/* ---- classical element symbols (alchemical triangles) -------------- */ +static void build_fire(SymB *b){ const double t[]={-0.8,-0.6,0.8,-0.6,0,0.85}; sym_poly(b,t,3,1); } +static void build_water(SymB *b){ const double t[]={-0.8,0.6,0.8,0.6,0,-0.85}; sym_poly(b,t,3,1); } +static void build_air(SymB *b){ const double t[]={-0.8,-0.6,0.8,-0.6,0,0.85}; sym_poly(b,t,3,1); + sym_line(b,-0.33,0.3,0.33,0.3); } +static void build_earth(SymB *b){ const double t[]={-0.8,0.6,0.8,0.6,0,-0.85}; sym_poly(b,t,3,1); + sym_line(b,-0.33,-0.3,0.33,-0.3); } +static void build_philstone(SymB *b){ /* squared circle */ + sym_ring(b,0,0,1.0,22); + const double sq[]={-0.72,-0.72,0.72,-0.72,0.72,0.72,-0.72,0.72}; + sym_poly(b,sq,4,1); + const double tri[]={0,0.66,-0.6,-0.5,0.6,-0.5}; + sym_poly(b,tri,3,1); + sym_ring(b,0,-0.16,0.32,12); +} +static void build_starship(SymB *b){ /* generic starship (saucer + nacelles) */ + sym_ellipse(b,0,0.5,0.85,0.30,0,18); /* saucer */ + sym_line(b,-0.1,0.3,-0.05,-0.1); sym_line(b,0.1,0.3,0.05,-0.1); /* neck */ + const double hull[]={-0.28,-0.1, 0.28,-0.1, 0.22,-0.45, -0.22,-0.45}; + sym_poly(b,hull,4,1); /* engineering hull */ + /* two nacelles on struts */ + sym_line(b,-0.22,-0.25,-0.62,-0.55); sym_line(b,0.22,-0.25,0.62,-0.55); + const double nl[]={-0.85,-0.5,-0.4,-0.5,-0.4,-0.66,-0.85,-0.66}; + const double nr[]={ 0.4,-0.5, 0.85,-0.5, 0.85,-0.66, 0.4,-0.66}; + sym_poly(b,nl,4,1); sym_poly(b,nr,4,1); +} + +/* ---- still more iconography (flat 2-D + extruded 3-D) -------------- */ + +static void build_tophat(SymB *b) { + const double crown[]={-0.4,-0.2, 0.4,-0.2, 0.44,0.9, -0.44,0.9}; + sym_poly(b,crown,4,1); + sym_ellipse(b,0,-0.2,0.86,0.18,0,18); /* brim */ + sym_line(b,-0.4,0.02,0.4,0.02); sym_line(b,-0.42,0.16,0.42,0.16); /* band */ +} +static void build_cap(SymB *b) { /* baseball cap, side view */ + sym_arc(b,-0.05,-0.1,0.72,sym_deg(5),sym_deg(175),12); /* crown */ + sym_line(b,-0.77,-0.1,0.66,-0.1); + const double bill[]={0.65,-0.06, 1.22,-0.2, 1.2,-0.32, 0.6,-0.18}; + sym_poly(b,bill,4,1); /* bill */ + sym_ring(b,-0.05,0.6,0.05,4); /* top button */ +} +static void build_nose(SymB *b) { /* schnozz, profile facing right */ + const double p[]={ -0.35,0.95, 0.05,0.45, 0.38,0.0, 0.5,-0.4, 0.32,-0.6, + -0.02,-0.55, -0.18,-0.38 }; + sym_poly(b,p,7,0); + sym_arc(b,0.08,-0.46,0.12,sym_deg(170),sym_deg(360),5); /* nostril */ +} +static void build_heart(SymB *b) { /* parametric heart curve */ + int first=-1, prev=-1; + for (int i=0;i<=24;i++){ double t=TAU*i/24; + double x=16*pow(sin(t),3)/17.0; + double y=(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t))/15.0; + int idx=sym_pt(b,x,y); + if(first<0)first=idx; + if(prev>=0)sym_edge(b,prev,idx); + prev=idx; } + sym_edge(b,prev,first); +} +static void build_keyboard(SymB *b) { + const double body[]={-1.0,-0.45, 1.0,-0.45, 1.0,0.45, -1.0,0.45}; + sym_poly(b,body,4,1); + for (int r=1;r<4;r++){ double y=-0.45+r*0.225; sym_line(b,-1.0,y,1.0,y); } + for (int c=1;c<8;c++){ double x=-1.0+c*0.25; sym_line(b,x,-0.225,x,0.45); } + sym_line(b,-0.5,-0.45,-0.5,-0.225); sym_line(b,0.5,-0.45,0.5,-0.225); /* spacebar */ +} +static void build_floppy(SymB *b) { /* 3.5" floppy disk */ + const double body[]={-0.85,-0.9, 0.85,-0.9, 0.85,0.72, 0.55,0.9, -0.85,0.9}; + sym_poly(b,body,5,1); + const double sh[]={-0.2,0.42, 0.5,0.42, 0.5,0.9, -0.2,0.9}; + sym_poly(b,sh,4,1); sym_line(b,0.18,0.42,0.18,0.9); /* metal shutter */ + const double lab[]={-0.6,-0.85, 0.6,-0.85, 0.6,-0.05, -0.6,-0.05}; + sym_poly(b,lab,4,1); /* label */ +} +static void build_bell(SymB *b) { + const double p[][2]={ {0,1.0},{0.12,0.92},{0.18,0.7},{0.30,0.35},{0.45,-0.1}, + {0.6,-0.45},{0.63,-0.6} }; + sym_profile(b,p,7); + sym_ring(b,0,-0.78,0.12,6); /* clapper */ + sym_ring(b,0,1.05,0.08,5); /* top loop */ +} +static void build_book(SymB *b) { /* open book */ + const double left[]={-1.0,0.2, -0.05,0.35, -0.05,-0.45, -1.0,-0.3}; + const double right[]={1.0,0.2, 0.05,0.35, 0.05,-0.45, 1.0,-0.3}; + sym_poly(b,left,4,1); sym_poly(b,right,4,1); + sym_line(b,-0.9,0.05,-0.15,0.18); sym_line(b,-0.9,-0.1,-0.15,0.03); + sym_line(b, 0.9,0.05, 0.15,0.18); sym_line(b, 0.9,-0.1, 0.15,0.03); +} +static void build_commodore(SymB *b) { /* stylized 'C' mark */ + sym_arc(b,0,0,1.0,sym_deg(38),sym_deg(322),12); + sym_arc(b,0,0,0.55,sym_deg(44),sym_deg(316),10); + sym_line(b, cos(sym_deg(38)), sin(sym_deg(38)), 0.55*cos(sym_deg(44)), 0.55*sin(sym_deg(44))); + sym_line(b, cos(sym_deg(322)),sin(sym_deg(322)),0.55*cos(sym_deg(316)),0.55*sin(sym_deg(316))); +} +static void build_ti(SymB *b) { /* stylized framed 'TI' */ + const double fr[]={-0.9,-0.7, 0.9,-0.7, 0.9,0.7, -0.9,0.7}; + sym_poly(b,fr,4,1); + sym_line(b,-0.72,0.42,-0.1,0.42); sym_line(b,-0.41,0.42,-0.41,-0.42); /* T */ + sym_line(b, 0.1,0.42, 0.62,0.42); sym_line(b,0.36,0.42,0.36,-0.42); + sym_line(b, 0.1,-0.42,0.62,-0.42); /* I */ +} + +/* ---- 2-D silhouettes for the lathe solids of revolution ------------ */ +static void build_pawn2d(SymB *b){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86}, + {0.15,-0.76},{0.12,-0.45},{0.22,-0.36},{0.11,-0.28},{0.19,-0.12},{0.25,0.0},{0.22,0.13}, + {0.10,0.27},{0,0.33}}; sym_profile(b,p,12); } +static void build_rook2d(SymB *b){ const double p[][2]={{0,-1.0},{0.40,-1.0},{0.34,-0.84}, + {0.19,-0.68},{0.19,0.35},{0.33,0.5},{0.33,0.82},{0,0.82}}; sym_profile(b,p,8); + for(int i=-1;i<=1;i++){ double x=i*0.22; sym_line(b,x-0.07,0.82,x-0.07,0.95); + sym_line(b,x+0.07,0.82,x+0.07,0.95); sym_line(b,x-0.07,0.95,x+0.07,0.95);} } +static void build_bishop2d(SymB *b){ const double p[][2]={{0,-1.0},{0.36,-1.0},{0.32,-0.86}, + {0.16,-0.74},{0.13,-0.4},{0.24,-0.3},{0.12,-0.22},{0.20,0.0},{0.16,0.3},{0.09,0.55}, + {0.06,0.66},{0,0.72},{0.05,0.78},{0,0.86}}; sym_profile(b,p,14); + sym_line(b,-0.1,0.35,0.12,0.5); } +static void build_king2d(SymB *b){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85}, + {0.17,-0.72},{0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.20,0.05},{0.27,0.28},{0.18,0.5}, + {0.16,0.6},{0,0.6}}; sym_profile(b,p,12); + sym_line(b,0,0.6,0,1.0); sym_line(b,-0.13,0.84,0.13,0.84); } +static void build_queen2d(SymB *b){ const double p[][2]={{0,-1.0},{0.38,-1.0},{0.34,-0.85}, + {0.17,-0.72},{0.14,-0.35},{0.25,-0.25},{0.12,-0.18},{0.22,0.1},{0.29,0.38},{0.30,0.52}}; + sym_profile(b,p,10); + for(int i=-2;i<=2;i++){ double x=i*0.13; sym_line(b,x,0.52,x*1.2,0.74); + sym_ring(b,x*1.2,0.76,0.04,4);} } +static void build_candlestick2d(SymB *b){ const double p[][2]={{0,-1.0},{0.42,-1.0},{0.36,-0.85}, + {0.12,-0.7},{0.10,-0.2},{0.20,-0.05},{0.13,0.02},{0.11,0.08},{0.11,0.62},{0,0.62}}; + sym_profile(b,p,10); + sym_line(b,0,0.62,0.06,0.74); sym_line(b,0.06,0.74,0,0.92); + sym_line(b,0,0.92,-0.06,0.74); sym_line(b,-0.06,0.74,0,0.62); } +static void build_coffeecup2d(SymB *b){ const double p[][2]={{0,-0.6},{0.46,-0.6},{0.5,-0.45}, + {0.46,0.5},{0.40,0.5}}; sym_profile(b,p,5); + sym_arc(b,0.55,0.05,0.32,sym_deg(-75),sym_deg(75),6); } +static void build_lantern2d(SymB *b){ const double p[][2]={{0,-1.0},{0.35,-0.95},{0.40,-0.75}, + {0.34,-0.6},{0.36,0.45},{0.30,0.6},{0.34,0.72},{0.18,0.82},{0.12,0.9},{0,0.9}}; + sym_profile(b,p,10); sym_ring(b,0,1.0,0.12,8); } +static void build_hurricane2d(SymB *b){ const double p[][2]={{0,-1.0},{0.34,-0.95},{0.40,-0.78}, + {0.30,-0.6},{0.14,-0.5},{0.30,-0.4},{0.30,-0.28},{0.20,-0.18},{0.26,0.55},{0.34,0.85}, + {0.30,1.0}}; sym_profile(b,p,11); } +static void build_quartz2d(SymB *b){ const double p[][2]={{0,-1.05},{0.30,-0.7},{0.45,-0.5}, + {0.45,0.5},{0.30,0.7},{0,1.05}}; sym_profile(b,p,6); + sym_line(b,-0.45,-0.5,-0.45,0.5); sym_line(b,0.45,-0.5,0.45,0.5); } +static void build_pineapple2d(SymB *b){ const double p[][2]={{0,-1.0},{0.25,-0.85},{0.42,-0.5}, + {0.45,0.1},{0.34,0.4},{0.18,0.55}}; sym_profile(b,p,6); + for(int i=-2;i<=2;i++){ double x=i*0.1; sym_line(b,x,0.55,x*1.6,1.0); } + for(int i=0;i<3;i++){ double y=-0.4+i*0.3; sym_line(b,-0.4,y,0.4,y+0.18); sym_line(b,-0.4,y+0.18,0.4,y);} } +static void build_torus2d(SymB *b){ sym_ring(b,0,0,1.0,22); sym_ring(b,0,0,0.46,16); } + +/* ---- 2-D versions of the 3-D-only googie objects ------------------- */ +static void build_saturn2d(SymB *b){ sym_ring(b,0,0,0.58,16); + sym_ellipse(b,0,0,1.05,0.26,sym_deg(-12),20); } +static void build_molecule2d(SymB *b){ sym_ring(b,0,0,0.22,8); + const double a[3]={90,210,330}; + for(int k=0;k<3;k++){ double th=sym_deg(a[k]); + sym_line(b,0.22*cos(th),0.22*sin(th),0.7*cos(th),0.7*sin(th)); + sym_ring(b,0.85*cos(th),0.85*sin(th),0.18,7);} } +static void build_diabolo2d(SymB *b){ const double p[]={-0.5,-1.0, 0.5,-1.0, -0.5,1.0, 0.5,1.0}; + sym_poly(b,p,4,1); sym_line(b,-0.5,-1.0,-0.18,0.0); sym_line(b,0.5,1.0,0.18,0.0); } +static void build_sputnik2d(SymB *b){ int c=sym_pt(b,0,0); int n=10; + for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx; } /* mustard */ +} +static void build_hamburger(SymB *b){ + sym_arc(b,0,0.12,0.9,sym_deg(0),sym_deg(180),12); sym_line(b,-0.9,0.12,0.9,0.12); /* top bun */ + sym_ring(b,-0.3,0.45,0.04,3); sym_ring(b,0.1,0.55,0.04,3); sym_ring(b,0.42,0.4,0.04,3); /* seeds */ + int prev=-1; for(int i=0;i<=8;i++){ double x=-0.9+i*1.8/8, y=-0.02+0.06*((i%2)?1:-1); + int idx=sym_pt(b,x,y); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } /* lettuce */ + const double patty[]={-0.85,-0.07, 0.85,-0.07, 0.85,-0.27, -0.85,-0.27}; + sym_poly(b,patty,4,1); + sym_arc(b,0,-0.3,0.85,sym_deg(180),sym_deg(360),10); sym_line(b,-0.85,-0.3,0.85,-0.3); /* bottom bun */ +} +static void build_unicycle(SymB *b){ + sym_ring(b,0,-0.4,0.5,16); sym_ring(b,0,-0.4,0.08,5); + for(int i=0;i<8;i++){ double a=TAU*i/8; sym_line(b,0.08*cos(a),-0.4+0.08*sin(a),0.5*cos(a),-0.4+0.5*sin(a)); } + sym_line(b,0,-0.32,0,0.6); sym_line(b,-0.25,0.62,0.25,0.62); /* post + seat */ + sym_line(b,0,-0.4,0.26,-0.4); sym_line(b,0,-0.4,-0.26,-0.4); /* cranks */ + sym_line(b,0.26,-0.4,0.34,-0.46); sym_line(b,-0.26,-0.4,-0.34,-0.34); +} +static void build_microscope(SymB *b){ + const double base[]={-0.5,-1.0, 0.5,-1.0, 0.4,-0.78, -0.4,-0.78}; + sym_poly(b,base,4,1); + sym_line(b,-0.22,-0.78,-0.22,0.25); /* arm pillar */ + const double tube[]={-0.06,0.25, 0.22,0.3, 0.34,0.85, 0.06,0.8}; + sym_poly(b,tube,4,1); + sym_line(b,0.2,0.85,0.4,0.95); /* eyepiece */ + sym_line(b,-0.4,-0.12,0.35,-0.12); sym_line(b,-0.4,-0.24,0.35,-0.24); /* stage */ + sym_line(b,-0.06,0.25,-0.02,0.0); sym_line(b,0.1,0.27,0.06,0.0); /* objective */ +} +static void build_telescope(SymB *b){ + const double tube[]={-0.78,-0.18, 0.72,0.55, 0.62,0.72, -0.88,-0.0}; + sym_poly(b,tube,4,1); + sym_line(b,-0.78,-0.18,-0.95,-0.28); /* eyepiece */ + sym_line(b,-0.08,0.18,-0.32,-1.0); sym_line(b,-0.08,0.18,0.3,-1.0); + sym_line(b,-0.08,0.18,0.0,-1.0); /* tripod */ + sym_line(b,-0.18,0.1,0.05,0.25); /* mount */ +} +static void build_magnifier(SymB *b){ + sym_ring(b,0.18,0.28,0.56,18); sym_ring(b,0.18,0.28,0.46,15); /* lens */ + sym_line(b,-0.18,-0.18,-0.72,-0.72); sym_line(b,-0.06,-0.3,-0.6,-0.84); + sym_line(b,-0.72,-0.72,-0.6,-0.84); /* handle */ +} +static void build_thermometer(SymB *b){ + sym_ring(b,0,-0.8,0.22,12); /* bulb */ + const double tube[]={-0.1,-0.72, 0.1,-0.72, 0.1,0.82, -0.1,0.82}; + sym_poly(b,tube,4,1); sym_arc(b,0,0.82,0.1,sym_deg(0),sym_deg(180),4); + sym_line(b,0,-0.8,0,0.3); /* mercury */ + for(int i=0;i<6;i++){ double y=-0.5+i*0.22; sym_line(b,0.1,y,0.22,y); } +} +static void build_calculator(SymB *b){ + const double body[]={-0.6,-0.95, 0.6,-0.95, 0.6,0.95, -0.6,0.95}; + const double scr[]={-0.45,0.45, 0.45,0.45, 0.45,0.8, -0.45,0.8}; + sym_poly(b,body,4,1); sym_poly(b,scr,4,1); + for(int i=1;i<4;i++){ double x=-0.6+i*0.3; sym_line(b,x,-0.9,x,0.3); } + for(int i=1;i<5;i++){ double y=-0.9+i*0.3; sym_line(b,-0.6,y,0.6,y); } +} +static void build_stapler(SymB *b){ + const double base[]={-0.9,-0.4, 0.9,-0.4, 0.9,-0.12, -0.9,-0.12}; + const double top[]={-0.85,0.0, 0.7,0.06, 0.92,0.3, 0.85,0.42, -0.8,0.36, -0.9,0.12}; + sym_poly(b,base,4,1); sym_poly(b,top,6,1); + sym_ring(b,-0.78,0.06,0.08,5); /* hinge */ +} +static void build_paperclip(SymB *b){ /* nested gem-clip loops */ + sym_arc(b,-0.18,0.55,0.32,sym_deg(0),sym_deg(180),6); + sym_line(b,-0.5,0.55,-0.5,-0.5); + sym_arc(b,-0.3,-0.5,0.2,sym_deg(180),sym_deg(360),5); + sym_line(b,-0.1,-0.5,-0.1,0.35); + sym_arc(b,0.06,0.35,0.16,sym_deg(0),sym_deg(180),5); + sym_line(b,0.22,0.35,0.22,-0.35); + sym_line(b,0.14,0.55,0.14,0.78); +} +static void build_palm(SymB *b){ + sym_line(b,-0.1,-1.0,0.0,0.42); sym_line(b,0.12,-1.0,0.16,0.42); sym_line(b,-0.1,-1.0,0.12,-1.0); + for(int i=0;i<4;i++){ double y=-0.8+i*0.3; sym_line(b,-0.07+i*0.04,y,0.12+i*0.03,y); } + double cx=0.08, cy=0.42; + for(int k=0;k<6;k++){ double a=sym_deg(20+k*28); + double mx=cx+0.3*cos(a), my=cy+0.34*sin(a); + sym_line(b,cx,cy,mx,my); sym_line(b,mx,my,cx+0.55*cos(a),cy+0.42*sin(a)-0.16); } +} +static void build_saguaro(SymB *b){ + const double trunk[]={-0.2,-1.0, 0.2,-1.0, 0.2,0.7, 0.14,0.86, -0.14,0.86, -0.2,0.7}; + sym_poly(b,trunk,6,1); + sym_line(b,-0.2,-0.1,-0.5,-0.1); sym_line(b,-0.5,-0.1,-0.5,0.45); /* left arm */ + sym_arc(b,-0.4,-0.1,0.1,sym_deg(180),sym_deg(270),3); sym_arc(b,-0.4,0.45,0.1,sym_deg(90),sym_deg(180),3); + sym_line(b,0.2,0.15,0.48,0.15); sym_line(b,0.48,0.15,0.48,0.6); /* right arm */ +} +static void build_sushi(SymB *b){ + sym_ring(b,0,0,0.95,18); sym_ring(b,0,0,0.74,16); /* nori + rice */ + sym_ring(b,0,0,0.32,8); sym_ring(b,0,0,0.14,5); /* filling */ +} +static void build_bottleopener(SymB *b){ + const double h[]={-0.9,-0.18, 0.5,-0.18, 0.5,0.18, -0.9,0.18}; + sym_poly(b,h,4,1); + sym_arc(b,0.6,0,0.4,sym_deg(-90),sym_deg(90),8); + sym_line(b,0.5,0.18,0.6,0.4); sym_line(b,0.5,-0.18,0.6,-0.4); + sym_arc(b,0.72,0,0.16,sym_deg(95),sym_deg(265),4); /* tooth notch */ + sym_ring(b,-0.7,0,0.1,6); /* hole */ +} +static void build_lightning(SymB *b){ + const double p[]={ 0.28,1.0, -0.36,0.12, 0.0,0.12, -0.3,-1.0, 0.42,-0.02, 0.08,-0.02 }; + sym_poly(b,p,6,1); +} + +/* ---- objects with a genuine volumetric 3-D form (gen) + 2-D -------- */ +static void build_monolith2d(SymB *b){ const double p[]={-0.22,-1.0,0.22,-1.0,0.22,1.0,-0.22,1.0}; sym_poly(b,p,4,1); } +static void gen_monolith3d(void){ Solid*s=new_solid(3); double X=0.22,Y=1.0,Z=0.06; + for(int i=0;i<8;i++) PV(s,(i&1?X:-X),(i&2?Y:-Y),(i&4?Z:-Z),0,0,0); + int e[12][2]={{0,1},{2,3},{4,5},{6,7},{0,2},{1,3},{4,6},{5,7},{0,4},{1,5},{2,6},{3,7}}; + for(int k=0;k<12;k++)AE(s,e[k][0],e[k][1]); + center_normalize(s); } +static void build_hulahoop2d(SymB *b){ sym_ellipse(b,0,0,1.0,0.32,0,24); sym_ellipse(b,0,0,0.9,0.27,0,22); } +static void gen_hulahoop3d(void){ Solid*s=new_solid(3); int nu=20,nv=6; double R=0.85,r=0.1; + for(int i=0;inv; + PV(s,rr*cos(a),y,rr*sin(a),0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } AE(s,prev,first); } + for(int m=0;m<2;m++){ double mo=TAU*m/4; int first=-1,prev=-1; for(int i=0;inv; PV(s,x1,y,z1,0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } AE(s,prev,first); } + for(int rg=0;rg<2;rg++){ double rad=1.0+rg*0.16; int first=-1,prev=-1,seg2=22; for(int i=0;inv; PV(s,x,y1,z1,0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } AE(s,prev,first); } + center_normalize(s); } +static void build_gyromech2d(SymB *b){ sym_ring(b,0,0,1.0,18); sym_ring(b,0,0,0.5,12); + sym_line(b,-1.0,0,1.0,0); for(int k=0;k<6;k++){ double a=TAU*k/6; sym_line(b,0,0,0.5*cos(a),0.5*sin(a)); } + sym_ring(b,0,0,0.08,5); } +static void gen_gyromech3d(void){ Solid*s=new_solid(3); int seg=16; + int r0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0,0,0,0,0); for(int i=0;inv; PV(s,0,0,-0.78,0,0,0); PV(s,0,0,0.78,0,0,0); AE(s,ax,ax+1); + int g=s->nv; for(int i=0;i<20;i++){ double a=TAU*i/20; PV(s,0.78*cos(a),0,0.78*sin(a),0,0,0); } for(int i=0;i<20;i++)AE(s,g+i,g+(i+1)%20); + center_normalize(s); } +static const double GRAPE_POS[6][2]={{-0.42,0.35},{0,0.4},{0.42,0.35},{-0.24,0.0},{0.24,0.0},{0,-0.34}}; +static void build_grapes2d(SymB *b){ for(int k=0;k<6;k++) sym_ring(b,GRAPE_POS[k][0],GRAPE_POS[k][1]-0.1,0.26,9); + sym_line(b,0,0.6,0.0,0.78); sym_line(b,0,0.78,0.18,0.92); } +static void gen_grapes3d(void){ Solid*s=new_solid(3); int seg=6; double r=0.26; + for(int k=0;k<6;k++){ double cx=GRAPE_POS[k][0],cy=GRAPE_POS[k][1]-0.1; + int a0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.5,0,0,0,0); PV(s,0,0.8,0,0,0,0); AE(s,st,st+1); center_normalize(s); } +static void build_corkscrew2d(SymB *b){ sym_line(b,-0.4,0.85,0.4,0.85); sym_line(b,0,0.85,0,0.5); + int prev=sym_pt(b,0,0.5); for(int i=1;i<=10;i++){ double y=0.5-i*0.13, x=(i%2?0.18:-0.18); + int idx=sym_pt(b,x,y); sym_edge(b,prev,idx); prev=idx; } } +static void gen_corkscrew3d(void){ Solid*s=new_solid(3); int n=40; double turns=4; + int prev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, a=t*TAU*turns, y=0.55-t*1.3; + int idx=s->nv; PV(s,0.2*cos(a),y,0.2*sin(a),0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } + int sh=s->nv; PV(s,0,0.55,0,0,0,0); PV(s,0,0.88,0,0,0,0); AE(s,sh,sh+1); + int h=s->nv; PV(s,-0.45,0.88,0,0,0,0); PV(s,0.45,0.88,0,0,0,0); AE(s,h,h+1); center_normalize(s); } +static void build_battery2d(SymB *b){ + const double body[]={-0.4,-1.0, 0.4,-1.0, 0.4,0.85, -0.4,0.85}; + const double nub[]={-0.15,0.85, 0.15,0.85, 0.15,1.0, -0.15,1.0}; + sym_poly(b,body,4,1); sym_poly(b,nub,4,1); + sym_line(b,-0.4,0.45,0.4,0.45); + sym_line(b,0.02,0.62,0.22,0.62); sym_line(b,0.12,0.52,0.12,0.72); /* + */ + sym_line(b,-0.22,-0.7,-0.02,-0.7); /* - */ +} +static void gen_battery3d(void){ const double p[][2]={{0,-1.0},{0.4,-1.0},{0.4,0.85}, + {0.15,0.85},{0.15,1.0},{0,1.0}}; gen_lathe(p,6,12); } + +/* ================================================================== */ +/* A very large batch of objects (static), plus animated dynamic ones. */ +/* ================================================================== */ + +static void build_gobstopper(SymB *b){ for(int i=0;i<4;i++) sym_ring(b,0,0,1.0-i*0.24,16); } +static void build_pants(SymB *b){ const double p[]={-0.6,0.95, 0.6,0.95, 0.6,0.2, 0.18,0.2, + 0.14,-1.0, -0.14,-1.0, -0.14,-0.1, -0.14,-1.0,/*dup*/ -0.6,0.2 }; + const double q[]={-0.6,0.95,0.6,0.95,0.6,0.2,0.18,0.2,0.16,-1.0,0.0,-0.1,-0.16,-1.0,-0.6,0.2}; + (void)p; sym_poly(b,q,8,1); sym_line(b,0,-0.1,0,0.95); } +static void build_shirt(SymB *b){ const double p[]={-0.35,0.85,-0.7,0.55,-0.9,0.7,-0.55,1.0, + 0.55,1.0,0.9,0.7,0.7,0.55,0.35,0.85,0.4,-0.95,-0.4,-0.95}; + sym_poly(b,p,10,1); sym_arc(b,0,0.95,0.2,sym_deg(200),sym_deg(340),5); } +static void build_dice(SymB *b){ const double f[]={-0.85,-0.85,0.85,-0.85,0.85,0.85,-0.85,0.85}; + sym_poly(b,f,4,1); double pp[5][2]={{-0.45,0.45},{0.45,0.45},{0,0},{-0.45,-0.45},{0.45,-0.45}}; + for(int i=0;i<5;i++) sym_ring(b,pp[i][0],pp[i][1],0.13,6); } +static void card_corner(SymB *b, int which){ + double ox=-0.5, oy=0.66, w=0.22, h=0.34; + if(which==0){ sym_line(b,ox,oy-h,ox,oy); sym_line(b,ox,oy-h*0.5,ox+w,oy); sym_line(b,ox,oy-h*0.5,ox+w,oy-h); } /*K*/ + else if(which==1){ sym_ring(b,ox+w*0.5,oy-h*0.5,h*0.45,8); sym_line(b,ox+w*0.6,oy-h*0.7,ox+w,oy-h); } /*Q*/ + else if(which==2){ sym_line(b,ox+w,oy,ox+w,oy-h*0.7); sym_arc(b,ox+w*0.5,oy-h*0.7,w*0.5,sym_deg(0),sym_deg(200),4); } /*J*/ + else { sym_line(b,ox,oy-h,ox+w*0.5,oy); sym_line(b,ox+w*0.5,oy,ox+w,oy-h); sym_line(b,ox+w*0.2,oy-h*0.5,ox+w*0.8,oy-h*0.5); } /*A*/ +} +static void build_pip_heart(SymB *b,double cx,double cy,double sc){ int first=-1,prev=-1; + for(int i=0;i<=14;i++){ double t=TAU*i/14, x=cx+sc*16*pow(sin(t),3)/17.0, + y=cy+sc*(13*cos(t)-5*cos(2*t)-2*cos(3*t)-cos(4*t))/15.0; int idx=sym_pt(b,x,y); + if(first<0)first=idx; + if(prev>=0)sym_edge(b,prev,idx); + prev=idx; } sym_edge(b,prev,first); } +static void card_base(SymB *b,int w){ const double fr[]={-0.62,-0.92,0.62,-0.92,0.62,0.92,-0.62,0.92}; + sym_poly(b,fr,4,1); card_corner(b,w); build_pip_heart(b,0,-0.05,0.55); } +static void build_card_k(SymB *b){ card_base(b,0); } +static void build_card_q(SymB *b){ card_base(b,1); } +static void build_card_j(SymB *b){ card_base(b,2); } +static void build_card_a(SymB *b){ card_base(b,3); } +static void build_roulette(SymB *b){ sym_ring(b,0,0,1.0,24); sym_ring(b,0,0,0.78,20); + sym_ring(b,0,0,0.35,12); for(int i=0;i<16;i++){ double a=TAU*i/16; + sym_line(b,0.35*cos(a),0.35*sin(a),0.78*cos(a),0.78*sin(a)); } sym_ring(b,0,0,0.1,6); } +static void build_slot(SymB *b){ const double box[]={-0.7,-1.0,0.7,-1.0,0.7,0.7,-0.7,0.7}; + sym_poly(b,box,4,1); const double top[]={-0.7,0.7,-0.55,0.95,0.55,0.95,0.7,0.7}; sym_poly(b,top,4,0); + for(int i=0;i<3;i++){ double x=-0.4+i*0.4; const double r[]={x-0.16,-0.1,x+0.16,-0.1,x+0.16,0.45,x-0.16,0.45}; + sym_poly(b,r,4,1); } sym_line(b,0.7,0.3,0.95,0.3); sym_ring(b,1.0,0.3,0.1,6); /*lever*/ + const double pay[]={-0.45,-0.85,0.45,-0.85,0.45,-0.45,-0.45,-0.45}; sym_poly(b,pay,4,1); } +static void build_hydrant(SymB *b){ const double body[]={-0.32,-1.0,0.32,-1.0,0.34,0.45,-0.34,0.45}; + sym_poly(b,body,4,1); sym_arc(b,0,0.45,0.34,sym_deg(0),sym_deg(180),8); sym_ring(b,0,0.62,0.16,8);/*cap*/ + sym_line(b,-0.34,0.0,-0.55,0.0); sym_ring(b,-0.55,0,0.12,6); sym_line(b,0.34,0.0,0.55,0.0); sym_ring(b,0.55,0,0.12,6); + sym_line(b,-0.4,-1.0,0.4,-1.0); sym_line(b,-0.4,-0.85,0.4,-0.85); } +static void build_brain(SymB *b){ int seg=20,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); + prev=idx; } sym_edge(b,prev,first); + sym_line(b,0,0.7,0,-0.7); for(int k=0;k<4;k++){ double y=0.45-k*0.3; sym_arc(b,-0.4,y,0.2,sym_deg(-60),sym_deg(180),3); sym_arc(b,0.4,y,0.2,sym_deg(0),sym_deg(240),3);} } +static void build_thimble(SymB *b){ const double p[][2]={{0.42,-1.0},{0.42,0.4},{0.34,0.75},{0,0.92}}; + int n=4,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx;} + for(int i=n-1;i>=0;i--){ if(p[i][0]==0)continue; int idx=sym_pt(b,-p[i][0],p[i][1]); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); + for(int r=0;r<4;r++){ double y=-0.7+r*0.35; sym_line(b,-0.4,y,0.4,y);} sym_line(b,-0.42,-1.0,0.42,-1.0); } +static void build_automobile(SymB *b){ const double body[]={-1.0,-0.3,-0.8,-0.3,-0.65,0.05,-0.4,0.25, + 0.35,0.25,0.6,0.05,0.85,0.0,1.0,-0.05,1.0,-0.3,0.8,-0.3}; + sym_poly(b,body,10,1); sym_ring(b,-0.6,-0.35,0.22,10); sym_ring(b,0.6,-0.35,0.22,10); + sym_line(b,-0.35,0.25,-0.25,-0.0); sym_line(b,0.05,0.25,0.15,0.0); } +static void build_couch(SymB *b){ const double back[]={-1.0,0.5,1.0,0.5,1.0,-0.1,-1.0,-0.1}; + sym_poly(b,back,4,1); const double seat[]={-1.0,-0.1,1.0,-0.1,0.95,-0.5,-0.95,-0.5}; sym_poly(b,seat,4,1); + const double la[]={-1.0,0.35,-1.2,0.35,-1.2,-0.55,-1.0,-0.5}; sym_poly(b,la,4,1); + const double ra[]={1.0,0.35,1.2,0.35,1.2,-0.55,1.0,-0.5}; sym_poly(b,ra,4,1); + sym_line(b,0,0.5,0,-0.1); sym_line(b,-1.2,-0.55,-1.1,-0.9); sym_line(b,1.2,-0.55,1.1,-0.9); } +static void build_stopsign(SymB *b){ int first=-1,prev=-1; for(int i=0;i<8;i++){ double a=sym_deg(22.5+i*45); + int idx=sym_pt(b,cos(a),sin(a)); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); + for(int i=0;i<8;i++){ double a=sym_deg(22.5+i*45); sym_pt(b,0.82*cos(a),0.82*sin(a)); } + /* "STOP" as a bar */ sym_line(b,-0.45,0.12,0.45,0.12); sym_line(b,-0.45,-0.12,0.45,-0.12); } +static void build_officechair(SymB *b){ const double seat[]={-0.5,0.0,0.5,0.0,0.45,-0.18,-0.45,-0.18}; + sym_poly(b,seat,4,1); const double back[]={-0.42,0.0,-0.38,0.85,0.38,0.85,0.42,0.0}; sym_poly(b,back,4,1); + sym_line(b,0,-0.18,0,-0.55); for(int i=0;i<5;i++){ double a=TAU*i/5; sym_line(b,0,-0.55,0.55*cos(a),-0.85+0.0*0); sym_ring(b,0.55*cos(a),-0.88,0.06,4);} } +static void build_crossbones(SymB *b){ sym_ring(b,0,0.3,0.5,14);/*skull*/ sym_ring(b,-0.18,0.32,0.1,5); sym_ring(b,0.18,0.32,0.1,5); + sym_line(b,-0.12,0.05,0.12,0.05); sym_line(b,0,0.05,0,-0.05); + sym_line(b,-0.7,-0.7,0.7,-0.3); sym_line(b,-0.7,-0.3,0.7,-0.7); /*bones X*/ + for(double s=-1;s<=1;s+=2){ sym_ring(b,0.7*s,-0.5+0.2*s,0.12,5); sym_ring(b,-0.7*s,-0.5-0.2*s,0.12,5);} } +static void build_goldfish(SymB *b){ sym_ellipse(b,0.0,0,0.7,0.45,0,16);/*body*/ + const double tail[]={0.6,0,1.0,0.4,0.85,0,1.0,-0.4}; sym_poly(b,tail,4,0); + sym_ring(b,-0.35,0.1,0.08,5); /*eye*/ sym_arc(b,0.2,0.45,0.2,sym_deg(200),sym_deg(340),4);/*fin*/ } +static void build_squid(SymB *b){ sym_ellipse(b,0,0.45,0.4,0.55,0,14);/*mantle*/ sym_arc(b,0,0.95,0.4,sym_deg(0),sym_deg(180),6); + sym_ring(b,-0.16,0.4,0.07,4); sym_ring(b,0.16,0.4,0.07,4); + for(int i=0;i<6;i++){ double x=-0.35+i*0.14; sym_line(b,x,-0.05,x*1.3,-0.95);} sym_line(b,-0.5,-0.1,-0.85,-0.7); sym_line(b,0.5,-0.1,0.85,-0.7); } +static void build_pinecone(SymB *b){ const double p[][2]={{0,-1.0},{0.3,-0.6},{0.42,-0.1},{0.34,0.4},{0,0.7}}; + int n=5,first=-1,prev=-1; for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx;} + for(int i=n-1;i>=0;i--){ if(p[i][0]==0)continue; int idx=sym_pt(b,-p[i][0],p[i][1]); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); + for(int r=0;r<4;r++){ double y=-0.7+r*0.35; sym_line(b,-0.35,y,0,y+0.12); sym_line(b,0,y+0.12,0.35,y);} sym_line(b,0,0.7,0,0.95); } +static void build_mapleleaf(SymB *b){ const double p[]={0,1.0, 0.12,0.55, 0.5,0.62, 0.36,0.36, 0.78,0.3, + 0.5,0.05, 0.62,-0.2, 0.25,-0.1, 0.28,-0.55, 0.0,-0.35, -0.28,-0.55, -0.25,-0.1, -0.62,-0.2, + -0.5,0.05, -0.78,0.3, -0.36,0.36, -0.5,0.62, -0.12,0.55}; + sym_poly(b,p,18,1); sym_line(b,0,-0.35,0,-0.9); } +static void build_anemone(SymB *b){ sym_arc(b,0,-0.2,0.5,sym_deg(180),sym_deg(360),8);/*base*/ + for(int i=0;i<11;i++){ double a=sym_deg(15+i*15); double bx=0.45*cos(a)-0.0, by=-0.2+0.45*sin(a); + sym_line(b,bx*0.6,by*0.6-0.0,cos(a),sin(a)*0.9+0.1);} } +static void build_wagonwheel(SymB *b){ sym_ring(b,0,0,1.0,20); sym_ring(b,0,0,0.85,18); sym_ring(b,0,0,0.18,8); + for(int i=0;i<10;i++){ double a=TAU*i/10; sym_line(b,0.18*cos(a),0.18*sin(a),0.85*cos(a),0.85*sin(a)); } } +static void build_submarine(SymB *b){ sym_arc(b,-0.55,0,0.4,sym_deg(90),sym_deg(270),6); + sym_arc(b,0.55,0,0.4,sym_deg(-90),sym_deg(90),6); sym_line(b,-0.55,0.4,0.55,0.4); sym_line(b,-0.55,-0.4,0.55,-0.4); + const double tower[]={-0.18,0.4,-0.14,0.78,0.14,0.78,0.18,0.4}; sym_poly(b,tower,4,0); sym_line(b,0,0.78,0,1.0);/*periscope*/ + sym_ring(b,-0.2,0,0.1,5); sym_ring(b,0.2,0,0.1,5); } +static void build_sailship(SymB *b){ const double hull[]={-0.85,-0.4,0.85,-0.4,0.6,-0.75,-0.6,-0.75}; + sym_poly(b,hull,4,1); sym_line(b,0,-0.4,0,0.95);/*mast*/ sym_line(b,-0.45,-0.4,-0.45,0.6); sym_line(b,0.45,-0.4,0.45,0.55); + const double s1[]={0.05,0.85,0.5,0.0,0.05,0.0}; sym_poly(b,s1,3,1); const double s2[]={-0.05,0.7,-0.5,-0.1,-0.05,-0.1}; sym_poly(b,s2,3,1); + sym_line(b,-0.85,-0.4,-0.6,-0.4); } +static void build_anchor(SymB *b){ sym_ring(b,0,0.85,0.16,8);/*ring*/ sym_line(b,0,0.7,0,-0.7);/*shaft*/ + sym_line(b,-0.35,0.45,0.35,0.45);/*stock*/ sym_arc(b,0,-0.4,0.6,sym_deg(200),sym_deg(340),8);/*arms*/ + sym_line(b,-0.56,-0.55,-0.7,-0.3); sym_line(b,0.56,-0.55,0.7,-0.3); } +static void build_swiss(SymB *b){ const double w[]={-0.9,-0.5,0.9,-0.7,0.85,0.5,-0.9,0.3}; + sym_poly(b,w,4,1); sym_ring(b,-0.3,-0.1,0.16,7); sym_ring(b,0.3,0.05,0.2,8); sym_ring(b,0.4,-0.45,0.12,6); sym_ring(b,-0.55,0.1,0.1,5); sym_ring(b,0.0,-0.45,0.1,5); } +static void build_anarchy(SymB *b){ sym_ring(b,0,0,1.0,22); /*A*/ sym_line(b,-0.55,-0.55,0,0.65); sym_line(b,0,0.65,0.6,-0.55); + sym_line(b,-0.3,-0.0,0.7,-0.0); } +static void build_yoyo(SymB *b){ sym_ring(b,0,0,1.0,18); sym_ring(b,0,0,0.85,16); sym_ring(b,0,0,0.12,6); + sym_line(b,0,0,0.0,1.0); /*string*/ } + +/* ---- dedicated 3-D solids + flat 2-D companions -------------------- */ +static int near_v(Solid*s,double x,double y,double z){ for(int i=0;inv;i++){ double dx=s->v[i][0]-x,dy=s->v[i][1]-y,dz=s->v[i][2]-z; if(dx*dx+dy*dy+dz*dz<1e-4) return 1;} return 0; } +static void gen_buckyball(void){ Solid*s=new_solid(3); const double P=1.6180339887; + double base[3][3]={{0,1,3*P},{1,2+P,2*P},{2,1+2*P,P}}; + for(int t=0;t<3;t++){ double v[3]={base[t][0],base[t][1],base[t][2]}; + double pr[3][3]={{v[0],v[1],v[2]},{v[1],v[2],v[0]},{v[2],v[0],v[1]}}; + for(int p=0;p<3;p++) for(int sx=-1;sx<=1;sx+=2) for(int sy=-1;sy<=1;sy+=2) for(int sz=-1;sz<=1;sz+=2){ + double x=sx*pr[p][0],y=sy*pr[p][1],z=sz*pr[p][2]; if(!near_v(s,x,y,z)) PV(s,x,y,z,0,0,0); } } + center_normalize(s); derive_edges(s); } +static void build_buckyball2d(SymB *b){ sym_ring(b,0,0,1.0,18); for(int i=0;i<6;i++){ double a=sym_deg(i*60); + sym_line(b,0.5*cos(a),0.5*sin(a),0.5*cos(a+sym_deg(60)),0.5*sin(a+sym_deg(60))); } sym_ring(b,0,0,0.5,6); } +static void cube_pip(Solid*s,double cx,double cy,double cz,double ux,double uy,double uz,double vx,double vy,double vz,int n){ + /* n pips on a face centred at c, face axes u,v */ + double off[7][2]={{0,0},{-1,1},{1,-1},{1,1},{-1,-1},{-1,0},{1,0}}; double sc=0.4; + for(int k=0;k=0)sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); + for(int i=0;i<6;i++){ double a=sym_deg(90+i*60); sym_line(b,0,0,cos(a),sin(a)); } sym_line(b,-0.2,0.1,0.2,0.1); } +static void gen_jacks(void){ Solid*s=new_solid(3); double d[6][3]={{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}}; + int c=s->nv; PV(s,0,0,0,0,0,0); for(int i=0;i<6;i++){ int t=s->nv; PV(s,d[i][0],d[i][1],d[i][2],0,0,0); AE(s,c,t); + add_ball(s,d[i][0],d[i][1],d[i][2],d[i][0],d[i][1],d[i][2],0.18,5);} center_normalize(s); } +static void build_jacks2d(SymB *b){ for(int i=0;i<3;i++){ double a=sym_deg(i*60); sym_line(b,-cos(a),-sin(a),cos(a),sin(a)); sym_ring(b,cos(a),sin(a),0.16,5); sym_ring(b,-cos(a),-sin(a),0.16,5);} } +static void gen_dna(void){ Solid*s=new_solid(3); int n=28; double turns=3, H=2.0; + int aprev=-1,bprev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, ang=t*TAU*turns, y=H*(t-0.5); + int a=s->nv; PV(s,0.45*cos(ang),y,0.45*sin(ang),0,0,0); int bb=s->nv; PV(s,0.45*cos(ang+TAU*0.5),y,0.45*sin(ang+TAU*0.5),0,0,0); + if(aprev>=0){AE(s,aprev,a);AE(s,bprev,bb);} if(i%2==0)AE(s,a,bb); aprev=a;bprev=bb;} center_normalize(s); } +static void build_dna2d(SymB *b){ int n=20; double turns=3,H=2.0,aprev=-1,bprev=-1; + for(int i=0;i<=n;i++){ double t=(double)i/n, ang=t*TAU*turns, y=H*(t-0.5)*0.5; double x=0.5*cos(ang); + int a=sym_pt(b,x,y); int bb=sym_pt(b,-x,y); if(aprev>=0){sym_edge(b,(int)aprev,a);sym_edge(b,(int)bprev,bb);} if(i%2==0)sym_edge(b,a,bb); aprev=a;bprev=bb;} } + +/* ---- animated (dynamic) 3-D builders + static 2-D companions ------- */ +static void icos_verts(double v[12][3],double r){ const double P=1.6180339887; double s=r/sqrt(1+P*P); + double t[12][3]={{0,1,P},{0,1,-P},{0,-1,P},{0,-1,-P},{1,P,0},{1,-P,0},{-1,P,0},{-1,-P,0},{P,0,1},{P,0,-1},{-P,0,1},{-P,0,-1}}; + for(int i=0;i<12;i++){v[i][0]=t[i][0]*s;v[i][1]=t[i][1]*s;v[i][2]=t[i][2]*s;} } +static void gen_hoberman_dyn(Solid*s,float t){ double rad=0.55+0.42*(0.5+0.5*sin(t*1.3)); + double v[12][3]; icos_verts(v,rad); for(int i=0;i<12;i++) PV(s,v[i][0],v[i][1],v[i][2],0,0,0); + double el2=0; for(int j=1;j<12;j++){double dx=v[0][0]-v[j][0],dy=v[0][1]-v[j][1],dz=v[0][2]-v[j][2],d=dx*dx+dy*dy+dz*dz; if(el2==0||dnv; PV(s,0,0,0,0,0,0); for(int i=0;i<12;i++) AE(s,c,i); } +static void build_hoberman2d(SymB *b){ sym_ring(b,0,0,1.0,16); sym_ring(b,0,0,0.55,12); for(int i=0;i<8;i++){double a=TAU*i/8; sym_line(b,0.55*cos(a),0.55*sin(a),cos(a),sin(a));} } +static void gen_rubik_dyn(Solid*s,float t){ float ang=(float)(TAU/4)*0.5f*(1-cosf(t*1.0f)); + double pts[27][3]; int n=0; for(int yi=0;yi<3;yi++)for(int zi=0;zi<3;zi++)for(int xi=0;xi<3;xi++){ + double x=(xi-1)*0.6,y=(yi-1)*0.6,z=(zi-1)*0.6; if(yi==2){double c=cos(ang),s2=sin(ang),nx=x*c+z*s2,nz=-x*s2+z*c; x=nx;z=nz;} + PV(s,x,y,z,0,0,0); pts[n][0]=x;pts[n][1]=y;pts[n][2]=z; n++; } + for(int i=0;i<27;i++)for(int j=i+1;j<27;j++){ double dx=pts[i][0]-pts[j][0],dy=pts[i][1]-pts[j][1],dz=pts[i][2]-pts[j][2],d=dx*dx+dy*dy+dz*dz; if(d<0.62*0.62&&d>0.1) AE(s,i,j);} } +static void build_rubik2d(SymB *b){ const double sq[]={-0.9,-0.9,0.9,-0.9,0.9,0.9,-0.9,0.9}; sym_poly(b,sq,4,1); + for(int i=1;i<3;i++){double x=-0.9+i*0.6; sym_line(b,x,-0.9,x,0.9); sym_line(b,-0.9,x,0.9,x);} } +static void tilt_spin(Solid*s,double tlt,double pa){ for(int i=0;inv;i++){ double x=s->v[i][0],y=s->v[i][1],z=s->v[i][2]; + double y1=y*cos(tlt)-z*sin(tlt),z1=y*sin(tlt)+z*cos(tlt); y=y1;z=z1; double x1=x*cos(pa)+z*sin(pa),z2=-x*sin(pa)+z*cos(pa); + s->v[i][0]=(float)x1;s->v[i][1]=(float)y;s->v[i][2]=(float)z2; } } +static void gen_top_dyn(Solid*s,float t){ int seg=12; int bp=s->nv; PV(s,0,-1.0,0,0,0,0); + int r0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.35,0,0,0,0); PV(s,0,0.7,0,0,0,0); AE(s,st,st+1); tilt_spin(s,0.18,t*4.5); } +static void build_top2d(SymB *b){ const double p[]={0,-1.0,0.55,-0.1,0.35,0.35,0,0.7,-0.35,0.35,-0.55,-0.1}; sym_poly(b,p,6,1); sym_line(b,0,0.35,0,0.7); } +static void gen_dreidel_dyn(Solid*s,float t){ int seg=4; int bp=s->nv; PV(s,0,-1.0,0,0,0,0); + int r0=s->nv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.45,0,0,0,0); PV(s,0,0.8,0,0,0,0); AE(s,st,st+1); tilt_spin(s,0.2,t*5.0); } +static void build_dreidel2d(SymB *b){ const double p[]={0,-1.0,0.5,-0.2,0.5,0.45,-0.5,0.45,-0.5,-0.2}; sym_poly(b,p,5,1); sym_line(b,0,0.45,0,0.8); sym_line(b,-0.18,0.0,0.18,0.0); } +static void gen_lighthouse_dyn(Solid*s,float t){ int seg=8; + int r0=s->nv; for(int i=0;inv; for(int i=0;inv; for(int i=0;inv; PV(s,0,0.7,0,0,0,0); for(int i=0;inv; PV(s,0,0.55,0,0,0,0); PV(s,2.4*cos(ba-0.12),0.55,2.4*sin(ba-0.12),0,0,0); PV(s,2.4*cos(ba+0.12),0.55,2.4*sin(ba+0.12),0,0,0); + AE(s,L,L+1); AE(s,L,L+2); AE(s,L+1,L+2); } +static void build_lighthouse2d(SymB *b){ const double p[]={-0.32,-1.0,0.32,-1.0,0.16,0.35,-0.16,0.35}; sym_poly(b,p,4,1); + const double lamp[]={-0.22,0.35,0.22,0.35,0.22,0.6,-0.22,0.6}; sym_poly(b,lamp,4,1); sym_arc(b,0,0.6,0.22,sym_deg(0),sym_deg(180),4); + for(int r=0;r<3;r++){double y=-0.7+r*0.35; sym_line(b,-0.28+r*0.05,y,0.28-r*0.05,y);} sym_line(b,0.2,0.48,0.9,0.62); sym_line(b,0.2,0.48,0.9,0.34); } +static void gen_eyeball_dyn(Solid*s,float t){ float ph=fmodf(t,3.2f); float op=1.0f; if(ph<0.3f) op=fabsf(ph-0.15f)/0.15f; + int lats=3,seg=12; for(int la=1;la<=lats;la++){ double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25,y=sin(phi)*0.9,rr=cos(phi)*0.9; + int f=s->nv; for(int i=0;inv; for(int i=0;inv; for(int i=0;inv; for(int i=0;i<6;i++){double a=TAU*i/6; PV(s,0.13*cos(a),0.13*sin(a),0.92,0,0,0);} for(int i=0;i<6;i++)AE(s,pp+i,pp+(i+1)%6); + double lid=(1.0-op)*0.9; if(lid>0.02){ int u=s->nv; for(int i=0;i<=8;i++){double a=TAU*0.5*i/8; PV(s,0.9*cos(a),0.9-lid+0.0,0.0,0,0,0);} (void)u; + for(int sgn=0;sgn<2;sgn++){ int f=s->nv; for(int i=0;i<=8;i++){double a=sym_deg(180)*i/8; double x=0.9*cos(a); double yy=(sgn?-(0.9-lid):(0.9-lid)); PV(s,x,yy,0.4,0,0,0);} for(int i=0;i<8;i++)AE(s,f+i,f+i+1);} } } +static void build_eyeball2d(SymB *b){ sym_ring(b,0,0,1.0,18); sym_ring(b,0,0,0.4,12); sym_ring(b,0,0,0.16,7); } +static void gen_skulljaw_dyn(Solid*s,float t){ float gap=0.12f+0.12f*(0.5f+0.5f*sinf(t*7.0f)); + int seg=12; for(int la=1;la<=2;la++){ double phi=(TAU*0.5)*la/3-TAU*0.25,y=0.2+sin(phi)*0.6,rr=cos(phi)*0.6; + int f=s->nv; for(int i=0;inv; double jy=-0.2-gap; + for(int i=0;i<8;i++){ double a=TAU*i/8; PV(s,0.45*cos(a),jy,0.3*sin(a),0,0,0); } + for(int i=0;i<8;i++) AE(s,j+i,j+(i+1)%8); + for(int i=0;i<8;i+=2) AE(s,j+i, j+(i+4)%8); } +static void build_skull2d(SymB *b){ sym_arc(b,0,0.1,0.7,sym_deg(0),sym_deg(180),12); sym_line(b,-0.7,0.1,-0.4,-0.5); sym_line(b,0.7,0.1,0.4,-0.5); + sym_line(b,-0.4,-0.5,0.4,-0.5); sym_ring(b,-0.28,0.2,0.16,7); sym_ring(b,0.28,0.2,0.16,7); for(int i=-2;i<=2;i++) sym_line(b,i*0.16,-0.5,i*0.16,-0.35); } +static void gen_teeth_dyn(Solid*s,float t){ float gap=0.18f+0.18f*(0.5f+0.5f*sinf(t*8.0f)); + for(int row=0;row<2;row++){ double y=(row?gap:-gap); double w=0.7; const double bs[8]={0,0,0,0,0,0,0,0}; (void)bs; + int f=s->nv; for(int i=0;i<6;i++){double x=-w+i*2*w/5; PV(s,x,y,0.15,0,0,0);} for(int i=0;i<6;i++){double x=-w+i*2*w/5; PV(s,x,y+(row?0.3:-0.3),0.15,0,0,0);} + for(int i=0;i<6;i++){AE(s,f+i,f+6+i); if(i<5)AE(s,f+i,f+i+1);} AE(s,f,f+6); AE(s,f+5,f+11); + int g=s->nv; for(int i=0;i<6;i++){double x=-w+i*2*w/5; PV(s,x,y,-0.15,0,0,0);} for(int i=0;i<6;i++)AE(s,f+i,g+i); } } +static void build_teeth2d(SymB *b){ for(int row=0;row<2;row++){ double y=(row?0.18:-0.18); + const double q[]={-0.7,0,0.7,0,0.7,0,-0.7,0}; (void)q; double yy=y+(row?0.35:-0.35); + sym_line(b,-0.7,y,0.7,y); sym_line(b,-0.7,yy,0.7,yy); sym_line(b,-0.7,y,-0.7,yy); sym_line(b,0.7,y,0.7,yy); + for(int i=1;i<6;i++){double x=-0.7+i*0.28; sym_line(b,x,y,x,yy);} } } +static void gen_octopus_dyn(Solid*s,float t){ int seg=10; int f=s->nv; for(int i=0;inv; PV(s,0,0.9,0,0,0,0); for(int i=0;inv; PV(s,x,y,z,0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; (void)bx;(void)bz; } } } +static void build_octopus2d(SymB *b){ sym_arc(b,0,0.3,0.55,sym_deg(0),sym_deg(180),8); sym_ring(b,-0.2,0.4,0.08,4); sym_ring(b,0.2,0.4,0.08,4); + for(int i=0;i<6;i++){ double x=-0.5+i*0.2; sym_line(b,x,0.3,x*1.3,-0.9);} } +static void gen_jelly_dyn(Solid*s,float t){ int seg=12; for(int la=0;la<3;la++){ double y=0.5-la*0.25, rr=0.6*cos(la*0.5); + int f=s->nv; for(int i=0;inv; PV(s,0,0.75,0,0,0,0);for(int i=0;inv; PV(s,x,y,z,0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } } } +static void build_jelly2d(SymB *b){ sym_arc(b,0,0.3,0.6,sym_deg(0),sym_deg(180),10); sym_line(b,-0.6,0.3,0.6,0.3); + for(int i=0;i<7;i++){ double x=-0.5+i*0.166; int prev=sym_pt(b,x,0.3); for(int k=1;k<=4;k++){ int idx=sym_pt(b,x+0.08*((k%2)?1:-1),0.3-k*0.3); sym_edge(b,prev,idx); prev=idx;} } } + +/* register a dynamic shape (rebuilt every frame) + return nothing */ +static void add_dynamic(DynFn f){ if(dyn_countdim=3; s->nv=0; s->ne=0; dyn_func[i](s,t); } } + +/* ================================================================== */ +/* Yet another batch: media, symbols, tools, and more animated objects. */ +/* ================================================================== */ +static void build_cassette(SymB *b){ const double bd[]={-0.9,-0.55,0.9,-0.55,0.9,0.55,-0.9,0.55}; sym_poly(b,bd,4,1); + sym_ring(b,-0.35,0.12,0.22,10); sym_ring(b,0.35,0.12,0.22,10); sym_ring(b,-0.35,0.12,0.08,5); sym_ring(b,0.35,0.12,0.08,5); + const double w[]={-0.5,-0.04,0.5,-0.04,0.5,0.3,-0.5,0.3}; sym_poly(b,w,4,1); + const double l[]={-0.75,-0.5,0.75,-0.5,0.75,-0.14,-0.75,-0.14}; sym_poly(b,l,4,1); + for(int i=0;i<5;i++){ double x=-0.6+i*0.3; sym_line(b,x,-0.55,x,-0.62); } } +static void build_record(SymB *b){ sym_ring(b,0,0,1.0,24); for(int i=1;i<=3;i++) sym_ring(b,0,0,1.0-i*0.16,20); sym_ring(b,0,0,0.32,12); sym_ring(b,0,0,0.05,5); } +static void build_cd(SymB *b){ sym_ring(b,0,0,1.0,24); sym_ring(b,0,0,0.85,20); sym_ring(b,0,0,0.22,10); sym_ring(b,0,0,0.1,6); sym_line(b,0.32,0.32,0.7,0.7); } +static void build_quarter(SymB *b){ sym_ring(b,0,0,1.0,24); sym_ring(b,0,0,0.85,22); + for(int i=0;i<28;i++){ double a=TAU*i/28; sym_line(b,1.0*cos(a),1.0*sin(a),0.91*cos(a),0.91*sin(a)); } + sym_arc(b,0,-0.15,0.4,sym_deg(20),sym_deg(160),6); sym_line(b,-0.3,-0.15,0.3,-0.15); sym_ring(b,0,0.05,0.18,7); } +static void build_kite(SymB *b){ const double d[]={0,1.0,0.55,0.1,0,-0.6,-0.55,0.1}; sym_poly(b,d,4,1); + sym_line(b,0,1.0,0,-0.6); sym_line(b,-0.55,0.1,0.55,0.1); + int prev=sym_pt(b,0,-0.6); for(int i=1;i<=4;i++){ double y=-0.6-i*0.16, x=0.12*((i%2)?1:-1); int idx=sym_pt(b,x,y); sym_edge(b,prev,idx); prev=idx; sym_ring(b,x,y,0.05,4); } } +static void build_baseball(SymB *b){ sym_ring(b,0,0,1.0,22); sym_arc(b,-1.35,0,0.95,sym_deg(-42),sym_deg(42),7); sym_arc(b,1.35,0,0.95,sym_deg(138),sym_deg(222),7); + for(int i=0;i<5;i++){ double y=-0.45+i*0.22; sym_line(b,-0.72,y,-0.62,y+0.06); sym_line(b,0.62,y,0.72,y+0.06); } } +static void build_tardis(SymB *b){ const double bd[]={-0.6,-1.0,0.6,-1.0,0.6,0.7,-0.6,0.7}; sym_poly(b,bd,4,1); + const double rf[]={-0.66,0.7,0.66,0.7,0.55,0.85,-0.55,0.85}; sym_poly(b,rf,4,0); sym_line(b,-0.66,0.7,0.66,0.7); + sym_ring(b,0,0.95,0.07,6); sym_line(b,0,-1.0,0,0.5); + for(double sx=-1;sx<=1;sx+=2){ double cx=sx*0.3; const double wnd[]={cx-0.13,0.28,cx+0.13,0.28,cx+0.13,0.55,cx-0.13,0.55}; sym_poly(b,wnd,4,1); } + sym_line(b,-0.6,0.62,0.6,0.62); } +static void build_rose(SymB *b){ double cy=0.32; int prev=-1; + for(int i=0;i<=24;i++){ double t=(double)i/24, a=t*TAU*2.0, r=0.1+t*0.45; int idx=sym_pt(b,r*cos(a),cy+r*sin(a)); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } + for(int k=0;k<5;k++){ double a=TAU*k/5+0.3; sym_arc(b,0.55*cos(a),cy+0.55*sin(a),0.32,sym_deg(0),sym_deg(200),5); } + sym_line(b,0,cy-0.55,0,-1.0); sym_arc(b,-0.22,-0.5,0.22,sym_deg(-20),sym_deg(140),4); sym_arc(b,0.22,-0.72,0.22,sym_deg(40),sym_deg(200),4); } +static void build_shoe(SymB *b){ const double p[]={-1.0,-0.45,-0.95,-0.65,0.85,-0.65,1.0,-0.35,0.92,-0.08,0.4,0.05,0.0,0.3,-0.7,0.3,-0.95,-0.05}; + sym_poly(b,p,9,1); sym_line(b,-0.95,-0.45,1.0,-0.45); sym_arc(b,-0.2,0.18,0.4,sym_deg(0),sym_deg(180),4); + for(int i=0;i<3;i++){ double x=-0.3+i*0.2; sym_line(b,x-0.05,0.0,x+0.1,0.2); } } +static void build_boot(SymB *b){ const double p[]={-0.5,-0.65,-0.45,0.9,0.0,0.9,0.05,-0.25,1.0,-0.35,1.0,-0.65}; + sym_poly(b,p,6,1); sym_line(b,-0.5,-0.5,1.0,-0.5); sym_line(b,-0.45,0.5,0.0,0.5); } +static void build_piano(SymB *b){ const double bd[]={-0.9,-1.0,0.9,-1.0,0.9,0.9,-0.9,0.9}; sym_poly(b,bd,4,1); + const double kb[]={-0.85,-0.2,0.85,-0.2,0.85,0.06,-0.85,0.06}; sym_poly(b,kb,4,1); + for(int i=1;i<10;i++){ double x=-0.85+i*0.17; sym_line(b,x,-0.2,x,0.06); } + for(int i=0;i<9;i++){ if(i%7==2||i%7==6) continue; double x=-0.7+i*0.17; const double bk[]={x-0.04,-0.04,x+0.04,-0.04,x+0.04,0.06,x-0.04,0.06}; sym_poly(b,bk,4,1); } + sym_line(b,-0.9,0.5,0.9,0.5); } +static void build_cloud(SymB *b){ sym_arc(b,-0.55,-0.1,0.4,sym_deg(90),sym_deg(270),5); sym_arc(b,-0.2,0.2,0.45,sym_deg(20),sym_deg(180),6); + sym_arc(b,0.35,0.18,0.42,sym_deg(0),sym_deg(160),5); sym_arc(b,0.65,-0.1,0.35,sym_deg(-90),sym_deg(90),5); sym_line(b,-0.55,-0.5,0.65,-0.45); } +static void build_soupcan(SymB *b){ const double bd[]={-0.55,-0.95,0.55,-0.95,0.55,0.8,-0.55,0.8}; sym_poly(b,bd,4,1); + sym_ellipse(b,0,0.8,0.55,0.16,0,16); sym_line(b,-0.55,0.45,0.55,0.45); sym_line(b,-0.55,-0.6,0.55,-0.6); sym_ring(b,0,-0.07,0.3,12); } +static void build_iceberg(SymB *b){ const double p[]={-0.5,0.05,-0.2,0.78,0.15,0.45,0.45,0.95,0.62,0.3,0.55,0.05,0.9,-0.2,0.6,-0.82,0.0,-1.0,-0.7,-0.7,-0.85,-0.1}; + sym_poly(b,p,11,1); sym_line(b,-0.95,0.05,0.95,0.05); } +static void build_bomb(SymB *b){ sym_ring(b,0,-0.2,0.7,18); const double nk[]={-0.15,0.45,0.15,0.45,0.13,0.6,-0.13,0.6}; sym_poly(b,nk,4,1); + int prev=sym_pt(b,0,0.6); for(int i=1;i<=4;i++){ double y=0.6+i*0.12, x=0.13*((i%2)?1:-1); int idx=sym_pt(b,x,y); sym_edge(b,prev,idx); prev=idx; } + sym_ring(b,0.13,1.12,0.08,5); } +static void build_comb(SymB *b){ const double sp[]={-0.95,0.3,0.95,0.3,0.95,0.6,-0.95,0.6}; sym_poly(b,sp,4,1); + for(int i=0;i<22;i++){ double x=-0.9+i*1.8/21; sym_line(b,x,0.3,x,-0.8); } } +static void build_traincross(SymB *b){ sym_line(b,-0.8,0.85,0.8,-0.15); sym_line(b,-0.78,0.8,0.82,-0.2); + sym_line(b,-0.8,-0.15,0.8,0.85); sym_line(b,-0.78,-0.2,0.82,0.8); + sym_line(b,0,-0.2,0,-1.0); sym_ring(b,-0.5,-0.42,0.12,6); sym_ring(b,0.5,-0.42,0.12,6); } +static void build_mailbox(SymB *b){ sym_arc(b,0,0.1,0.5,sym_deg(0),sym_deg(180),8); sym_line(b,-0.5,0.1,-0.5,-0.4); sym_line(b,0.5,0.1,0.5,-0.4); sym_line(b,-0.5,-0.4,0.5,-0.4); + sym_line(b,0.5,-0.1,0.5,0.3); sym_line(b,0.5,0.3,0.72,0.3); sym_line(b,0.72,0.3,0.72,0.0); + sym_line(b,0,-0.4,0,-1.0); sym_arc(b,-0.25,0.1,0.25,sym_deg(0),sym_deg(180),3); } +static void build_shipwheel(SymB *b){ sym_ring(b,0,0,0.7,18); sym_ring(b,0,0,0.55,16); sym_ring(b,0,0,0.12,6); + for(int i=0;i<8;i++){ double a=TAU*i/8; sym_line(b,0.12*cos(a),0.12*sin(a),0.7*cos(a),0.7*sin(a)); sym_line(b,0.7*cos(a),0.7*sin(a),0.96*cos(a),0.96*sin(a)); sym_ring(b,0.88*cos(a),0.88*sin(a),0.06,4); } } +static void build_axe(SymB *b){ sym_line(b,-0.16,-1.0,0.08,0.6); sym_line(b,-0.04,-1.0,0.2,0.6); sym_line(b,-0.16,-1.0,-0.04,-1.0); + const double hd[]={0.08,0.35,0.6,0.55,0.78,0.32,0.78,0.05,0.6,-0.12,0.14,0.12}; sym_poly(b,hd,6,1); } +static void build_popcorn(SymB *b){ const double bx[]={-0.55,-0.95,0.55,-0.95,0.45,0.15,-0.45,0.15}; sym_poly(b,bx,4,1); + for(int i=0;i<4;i++){ double x=-0.4+i*0.27; sym_line(b,x,-0.9,x,0.13); } + double pp[6][2]={{-0.35,0.3},{0.0,0.45},{0.35,0.3},{-0.18,0.55},{0.18,0.55},{0.0,0.2}}; + for(int k=0;k<6;k++) sym_ring(b,pp[k][0],pp[k][1],0.16,5); } +static void build_teapot(SymB *b){ sym_ellipse(b,0,-0.1,0.65,0.5,0,18); + sym_line(b,-0.6,0.05,-1.05,0.35); sym_line(b,-0.55,-0.18,-0.98,0.12); sym_line(b,-1.05,0.35,-0.98,0.12); + sym_arc(b,0.72,-0.1,0.3,sym_deg(-80),sym_deg(80),6); + sym_arc(b,0,0.38,0.32,sym_deg(0),sym_deg(180),6); sym_line(b,-0.32,0.38,0.32,0.38); sym_ring(b,0,0.58,0.08,5); } +static void build_churchbell(SymB *b){ const double p[][2]={{0,0.7},{0.15,0.6},{0.26,0.3},{0.46,-0.2},{0.6,-0.5},{0.63,-0.62}}; + sym_profile(b,p,6); sym_line(b,-0.63,-0.62,0.63,-0.62); sym_ring(b,0,-0.45,0.1,6); + sym_line(b,-0.42,0.78,0.42,0.78); sym_line(b,0,0.7,0,0.78); sym_arc(b,0.5,0.78,0.18,sym_deg(-90),sym_deg(200),5); } +static void build_anvil(SymB *b){ const double p[]={-0.9,0.5,0.55,0.5,0.95,0.28,0.55,0.18,0.45,0.1,0.45,-0.12,0.28,-0.12,0.28,-0.35,0.5,-0.5,-0.55,-0.5,-0.38,-0.35,-0.38,-0.12,-0.7,-0.12,-0.7,0.22,-0.9,0.3}; + sym_poly(b,p,15,1); } +static void build_bookstack(SymB *b){ for(int k=0;k<3;k++){ double y=-0.65+k*0.42, w=0.85-k*0.12, off=(k%2)*0.12-0.06; + const double bk[]={-w+off,y,w+off,y,w+off,y+0.32,-w+off,y+0.32}; sym_poly(b,bk,4,1); sym_line(b,-w+off+0.06,y,-w+off+0.06,y+0.32); } } +/* alchemical symbols */ +static void build_sulfur(SymB *b){ const double t[]={-0.5,0.3,0.5,0.3,0,1.0}; sym_poly(b,t,3,1); sym_line(b,0,0.3,0,-0.6); sym_line(b,-0.32,-0.15,0.32,-0.15); } +static void build_mercury(SymB *b){ sym_ring(b,0,0.18,0.3,12); sym_arc(b,0,0.55,0.3,sym_deg(15),sym_deg(165),5); sym_line(b,0,-0.12,0,-0.7); sym_line(b,-0.26,-0.42,0.26,-0.42); } +static void build_salt(SymB *b){ sym_ring(b,0,0,0.8,18); sym_line(b,-0.8,0,0.8,0); } +static void build_antimony(SymB *b){ sym_ring(b,0,-0.2,0.6,16); sym_line(b,0,0.4,0,1.0); sym_line(b,-0.3,0.7,0.3,0.7); } +/* occult symbols */ +static void build_pentagram(SymB *b){ sym_ring(b,0,0,1.0,20); int first=-1,prev=-1; + for(int i=0;i<5;i++){ double a=sym_deg(90+i*144); int idx=sym_pt(b,0.95*cos(a),0.95*sin(a)); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx; } sym_edge(b,prev,first); } +static void build_ankh(SymB *b){ sym_ring(b,0,0.55,0.3,12); sym_line(b,0,0.25,0,-1.0); sym_line(b,-0.4,-0.1,0.4,-0.1); } +static void build_eyeprovidence(SymB *b){ const double t[]={-0.85,-0.6,0.85,-0.6,0,0.85}; sym_poly(b,t,3,1); + sym_arc(b,0,-0.1,0.32,sym_deg(0),sym_deg(180),5); sym_arc(b,0,-0.1,0.32,sym_deg(180),sym_deg(360),5); sym_ring(b,0,-0.1,0.1,5); + for(int i=0;i<5;i++){ double a=sym_deg(50+i*20); sym_line(b,0,0.86,0.32*cos(a),0.86+0.3*sin(a)); } } +static void build_triplemoon(SymB *b){ sym_ring(b,0,0,0.5,14); sym_arc(b,-0.6,0,0.45,sym_deg(-80),sym_deg(80),6); sym_arc(b,0.6,0,0.45,sym_deg(100),sym_deg(260),6); } +/* more old keys */ +static void build_key2(SymB *b){ sym_ring(b,-0.75,0,0.25,12); sym_line(b,-0.5,0,0.9,0); sym_line(b,0.9,0,0.9,-0.3); sym_line(b,0.68,0,0.68,-0.25); sym_line(b,0.5,0,0.5,-0.15); } +static void build_key3(SymB *b){ const double bw[]={-0.95,0,-0.7,0.32,-0.4,0.32,-0.5,0,-0.4,-0.32,-0.7,-0.32}; sym_poly(b,bw,6,1); + sym_line(b,-0.4,0,0.95,0); sym_ring(b,0.6,0,0.12,6); sym_line(b,0.9,0,0.9,-0.28); sym_line(b,0.74,0,0.74,-0.2); } + +/* dedicated 3-D: helices, pyramid */ +static void gen_spring3d(void){ Solid*s=new_solid(3); int coils=6,per=10,n=coils*per; double r=0.42,H=1.5; int prev=-1; + for(int i=0;i<=n;i++){ double a=TAU*i/per, y=-H/2+H*i/n; int idx=s->nv; PV(s,r*cos(a),y,r*sin(a),0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } center_normalize(s); } +static void gen_slinky3d(void){ Solid*s=new_solid(3); int coils=13,per=8,n=coils*per; double r=0.5,H=1.7; int prev=-1; + for(int i=0;i<=n;i++){ double a=TAU*i/per, y=-H/2+H*i/n; int idx=s->nv; PV(s,r*cos(a),y,r*sin(a),0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; } center_normalize(s); } +static void build_coil2d(SymB *b,int loops){ int n=loops*8, prev=-1; for(int i=0;i<=n;i++){ double t=(double)i/n, y=-1.0+2.0*t, x=0.5*sin(t*TAU*loops); int idx=sym_pt(b,x,y); if(prev>=0)sym_edge(b,prev,idx); prev=idx; } } +static void build_spring2d(SymB *b){ build_coil2d(b,6); } +static void build_slinky2d(SymB *b){ build_coil2d(b,13); } +static void gen_pyramid(void){ Solid*s=new_solid(3); PV(s,-0.8,-0.6,-0.8,0,0,0);PV(s,0.8,-0.6,-0.8,0,0,0);PV(s,0.8,-0.6,0.8,0,0,0);PV(s,-0.8,-0.6,0.8,0,0,0);PV(s,0,0.9,0,0,0,0); + int e[8][2]={{0,1},{1,2},{2,3},{3,0},{0,4},{1,4},{2,4},{3,4}}; for(int k=0;k<8;k++)AE(s,e[k][0],e[k][1]); center_normalize(s); } +static void build_pyramid2d(SymB *b){ const double t[]={-0.9,-0.6,0.9,-0.6,0,0.9}; sym_poly(b,t,3,1); sym_line(b,0,0.9,0,-0.6); } + +/* dynamic (animated) */ +static void gen_fish_dyn(Solid*s,float t){ float flap=0.45f*sinf(t*5.0f); int seg=8,nsec=6,rings[6]; + for(int k=0;knv; + for(int i=0;i0) for(int i=0;inv; PV(s,0.45,0,0,0,0,0); PV(s,0.95,0.42+flap,0,0,0,0); PV(s,0.88,flap,0,0,0,0); PV(s,0.95,-0.42+flap,0,0,0,0); + AE(s,tf,tf+1);AE(s,tf+1,tf+2);AE(s,tf+2,tf+3);AE(s,tf+3,tf); add_ball(s,-0.55,0.12,0.22,0,0,1,0.06,4); } +static void build_fish2d(SymB *b){ sym_ellipse(b,0,0,0.7,0.42,0,16); const double tl[]={0.6,0,1.0,0.38,0.85,0,1.0,-0.38}; sym_poly(b,tl,4,0); sym_ring(b,-0.4,0.1,0.07,4); } +static void gen_scissors_dyn(Solid*s,float t){ float op=0.28f+0.28f*(0.5f+0.5f*sinf(t*4.0f)); + int piv=s->nv; PV(s,0,0,0,0,0,0); + for(int sd=0;sd<2;sd++){ double sg=sd?1:-1; double bx=0.95*sin(op)*sg, by=0.95*cos(op); + int tip=s->nv; PV(s,bx,by,0,0,0,0); int mid=s->nv; PV(s,0.8*sin(op)*sg+0.06*sg,0.85*cos(op),0,0,0,0); + AE(s,piv,tip); AE(s,tip,mid); AE(s,mid,piv); + /* handle loop below pivot, opposite side */ double hx=-0.45*sg, hy=-0.7; add_xy_ring(s,hx,hy,0.22,8); + int hb=s->nv; PV(s,hx,hy+0.22,0,0,0,0); AE(s,piv,hb); } +} +static void build_scissors2d(SymB *b){ sym_line(b,0,0,0.55,0.9); sym_line(b,0.05,0.05,0.5,0.85); sym_line(b,0,0,-0.55,0.9); sym_line(b,-0.05,0.05,-0.5,0.85); + sym_ring(b,-0.4,-0.6,0.25,8); sym_ring(b,0.4,-0.6,0.25,8); sym_line(b,0,0,-0.4,-0.4); sym_line(b,0,0,0.4,-0.4); } +static void gen_flame_dyn(Solid*s,float t){ int n=8; + for(int pl=0;pl<2;pl++){ double cr=cos(pl*1.5708), sr=sin(pl*1.5708); int prev=-1,first=-1; + for(int i=0;i<=n;i++){ double kk=(double)i/n, y=-0.85+kk*1.7, w=0.42*sin(kk*3.14159)*(1.3-0.6*kk), jit=0.07*sinf(t*11+i+pl*3)*kk, x=w+jit; + int idx=s->nv; PV(s,x*cr,y,x*sr,0,0,0); if(first<0)first=idx; if(prev>=0)AE(s,prev,idx); prev=idx; } + for(int i=n;i>=0;i--){ double kk=(double)i/n, y=-0.85+kk*1.7, w=0.42*sin(kk*3.14159)*(1.3-0.6*kk), jit=0.07*sinf(t*11+i+pl*3+1.5)*kk, x=-(w+jit); + int idx=s->nv; PV(s,x*cr,y,x*sr,0,0,0); AE(s,prev,idx); prev=idx; } AE(s,prev,first); } } +static void build_flame2d(SymB *b){ int n=10,first=-1,prev=-1; for(int i=0;i<=n;i++){ double kk=(double)i/n,y=-0.85+kk*1.7,w=0.42*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,w,y); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx;} + for(int i=n;i>=0;i--){ double kk=(double)i/n,y=-0.85+kk*1.7,w=0.42*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,-w,y); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); } +static void gen_zippo_dyn(Solid*s,float t){ double X=0.32,Y0=-0.9,Y1=0.05,Z=0.18; + double c[8][3]={{-X,Y0,-Z},{X,Y0,-Z},{X,Y1,-Z},{-X,Y1,-Z},{-X,Y0,Z},{X,Y0,Z},{X,Y1,Z},{-X,Y1,Z}}; + int b0=s->nv; for(int i=0;i<8;i++)PV(s,c[i][0],c[i][1],c[i][2],0,0,0); + int e[12][2]={{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7}}; for(int k=0;k<12;k++)AE(s,b0+e[k][0],b0+e[k][1]); + /* open lid */ int li=s->nv; PV(s,-X,Y1,-Z,0,0,0);PV(s,X,Y1,-Z,0,0,0);PV(s,X,Y1+0.4,-Z-0.2,0,0,0);PV(s,-X,Y1+0.4,-Z-0.2,0,0,0); AE(s,li,li+1);AE(s,li+1,li+2);AE(s,li+2,li+3);AE(s,li+3,li); + /* flickering flame above front */ int fl=s->nv,prev=-1; for(int i=0;i<=6;i++){ double kk=(double)i/6,y=Y1+0.05+kk*0.7,w=0.18*sin(kk*3.14159)*(1.3-0.6*kk)+0.05*sinf(t*13+i)*kk; + int idx=s->nv; PV(s,w,y,Z*0.3,0,0,0); if(prev>=0)AE(s,prev,idx); prev=idx; (void)fl; } + for(int i=6;i>=0;i--){ double kk=(double)i/6,y=Y1+0.05+kk*0.7,w=0.18*sin(kk*3.14159)*(1.3-0.6*kk)+0.05*sinf(t*13+i+1)*kk; int idx=s->nv; PV(s,-w,y,Z*0.3,0,0,0); AE(s,prev,idx); prev=idx; } } +static void build_zippo2d(SymB *b){ const double bx[]={-0.32,-0.9,0.32,-0.9,0.32,0.05,-0.32,0.05}; sym_poly(b,bx,4,1); + const double lid[]={-0.32,0.05,0.32,0.05,0.28,0.4,-0.28,0.4}; sym_poly(b,lid,4,0); + int n=6,first=-1,prev=-1; for(int i=0;i<=n;i++){ double kk=(double)i/n,y=0.1+kk*0.6,w=0.16*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,w,y); if(first<0)first=idx; if(prev>=0)sym_edge(b,prev,idx); prev=idx;} + for(int i=n;i>=0;i--){ double kk=(double)i/n,y=0.1+kk*0.6,w=0.16*sin(kk*3.14159)*(1.3-0.6*kk); int idx=sym_pt(b,-w,y); sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); } +static void add_gear3d(Solid*s,double cx,double cy,double R,int nt,double ang){ int n=nt*2,f=s->nv; + for(int i=0;inv; PV(s,cx,cy,0,0,0,0); for(int i=0;inv; for(int i=0;i<6;i++){ double a=TAU*i/6; PV(s,cx+R*0.2*cos(a),cy+R*0.2*sin(a),0,0,0,0);} for(int i=0;i<6;i++)AE(s,hr+i,hr+(i+1)%6); } +static void gen_gears_dyn(Solid*s,float t){ add_gear3d(s,-0.5,0,0.5,9,t*1.4); add_gear3d(s,0.58,0,0.42,7,-t*1.8+0.3); } +static void build_gears2d(SymB *b){ for(int g=0;g<2;g++){ double cx=g?0.55:-0.5,R=g?0.42:0.5; int nt=g?7:9,n=nt*2,first=-1,prev=-1; + for(int i=0;i=0)sym_edge(b,prev,idx); prev=idx;} sym_edge(b,prev,first); sym_ring(b,cx,0,R*0.2,6); } } +static void gen_fan_dyn(Solid*s,float t){ double a0=t*8.0; + int cg=s->nv; for(int i=0;i<20;i++){ double a=TAU*i/20; PV(s,cos(a),sin(a),0.15,0,0,0); } for(int i=0;i<20;i++)AE(s,cg+i,cg+(i+1)%20); + int hub=s->nv; PV(s,0,0,0,0,0,0); + for(int bl=0;bl<4;bl++){ double a=a0+TAU*bl/4; double c=cos(a),sn=sin(a); + int p0=s->nv; PV(s,0.12*c,0.12*sn,0,0,0,0); double tx=0.9*c,ty=0.9*sn; double px=-sn,py=c; + PV(s,tx+px*0.25,ty+py*0.25,0,0,0,0); PV(s,tx-px*0.18,ty-py*0.18,0,0,0,0); AE(s,p0,p0+1); AE(s,p0+1,p0+2); AE(s,p0+2,p0); AE(s,hub,p0); } } +static void build_fan2d(SymB *b){ sym_ring(b,0,0,1.0,20); sym_ring(b,0,0,0.12,6); + for(int bl=0;bl<4;bl++){ double a=TAU*bl/4; double c=cos(a),sn=sin(a),px=-sn,py=c; double tx=0.9*c,ty=0.9*sn; + const double q[]={0.12*c,0.12*sn,tx+px*0.25,ty+py*0.25,tx-px*0.18,ty-py*0.18}; sym_poly(b,q,3,1); } } + +/* Refracting prism. The triangle lives at these object-space coords (kept + * within the unit sphere, so no normalisation), and the render loop draws a + * white beam into one face and a rainbow fan out the other (draw_prism_beams). */ +static void gen_prism3d(void){ Solid*s=new_solid(3); + const double tri[3][2]={{-0.6,-0.45},{0.6,-0.45},{0,0.72}}; double z=0.4; + for(int zz=0;zz<2;zz++){ double zv=zz?z:-z; for(int i=0;i<3;i++) PV(s,tri[i][0],tri[i][1],zv,0,0,0); } + for(int zz=0;zz<2;zz++){ int bb=zz*3; AE(s,bb,bb+1); AE(s,bb+1,bb+2); AE(s,bb+2,bb); } + for(int i=0;i<3;i++) AE(s,i,i+3); } +static void gen_prism2d(void){ Solid*s=new_solid(3); /* flat, un-normalised so beams line up */ + const double tri[3][2]={{-0.6,-0.45},{0.6,-0.45},{0,0.72}}; + for(int i=0;i<3;i++) PV(s,tri[i][0],tri[i][1],0,0,0,0); + AE(s,0,1); AE(s,1,2); AE(s,2,0); } +/* Glass sphere. Rendered as a refraction billboard (draw_glassballs); the solid + * itself is only used for bounding/collision, so a coarse wireframe ball / ring. */ +static void gen_glassball3d(void){ Solid*s=new_solid(3); int lats=3, seg=14; + for(int la=1;la<=lats;la++){ double phi=(TAU*0.5)*la/(lats+1)-TAU*0.25, y=sin(phi)*0.9, rr=cos(phi)*0.9; + int f=s->nv; for(int i=0;inv; for(int i=0;inv; for(int i=0;inv; for(int i=0;i1 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]; @@ -1221,6 +2707,9 @@ typedef struct { 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; @@ -1228,7 +2717,7 @@ typedef struct { 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, + .multicolor = 0, .cycle_shapes = 0, .mag_count = 0, .glass_count = 0, .mirror_count = 0, .glow = 45, .flicker = 18, .fullscreen = 0, .paused = 0 }; @@ -1258,8 +2747,11 @@ static int collides(const Body *c, int check_count, int self) { * 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) { - b->shape = cfg.cycle_shapes ? (spawn_counter++ % num_shapes) - : (rand() % num_shapes); + 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; @@ -1309,19 +2801,49 @@ static int active_count(void) { return n; } -/* Make sure bodies[0..n) are initialized (spawns any not yet filled). */ -static int g_filled = 0; -static void ensure_filled(int n) { - if (n > MAX_BODIES) n = MAX_BODIES; - for (int i = g_filled; i < n; i++) - spawn_body(&bodies[i], 1, i, i); - if (n > g_filled) g_filled = 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 */ +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; - ensure_filled(active_count()); + recompose_field(); } /* ================================================================== */ @@ -1466,7 +2988,7 @@ static void render_osd(int fbw, int fbh, float alpha) { const float h = 13.0f; const float left = 22.0f; const float lineStep = h + 9.0f; - const int nlines = 19; + const int nlines = 22; const float panelW = 384.0f; float panelTop = fbh - 18.0f; float panelH = nlines * lineStep + 16.0f; @@ -1525,6 +3047,9 @@ static void render_osd(int fbw, int fbh, float alpha) { 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); @@ -1563,7 +3088,8 @@ static void normalize_sizes(void) { /* ================================================================== */ static void settings_path(char *buf, size_t n) { - const char *home = getenv("HOME"); + 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"); } @@ -1587,6 +3113,9 @@ static void save_settings(void) { 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); } @@ -1617,6 +3146,9 @@ static void load_settings(void) { 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); @@ -1625,6 +3157,9 @@ static void load_settings(void) { 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); @@ -1692,6 +3227,14 @@ static void key_cb(GLFWwindow *win, int key, int sc, int action, int mods) { 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. */ @@ -1794,35 +3337,339 @@ static void print_help(void) { " 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\n" " (all settings are also shown in the on-screen display)\n\n"); fflush(stdout); } -int main(void) { +/* ---- 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(px, 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 */ - printf("Vectorgons: %d shape types loaded.\n", num_shapes); + 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 = glfwCreateWindow(1100, 760, "Vectorgons", NULL, NULL); + 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(1); - glfwSetKeyCallback(win, key_cb); + 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; - if (cfg.fullscreen) { cfg.fullscreen = 0; toggle_fullscreen(win); } /* restore saved */ + if (!g_screensaver && cfg.fullscreen) { cfg.fullscreen = 0; toggle_fullscreen(win); } rebuild_field(); - print_help(); + if (!g_screensaver) print_help(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); /* additive glow */ @@ -1833,7 +3680,7 @@ int main(void) { last_input_time = glfwGetTime(); double last = glfwGetTime(); - while (!glfwWindowShouldClose(win)) { + while (!glfwWindowShouldClose(win) && !(g_screensaver && g_quit)) { double now = glfwGetTime(); float dt = (float)(now - last); last = now; @@ -1845,7 +3692,7 @@ int main(void) { 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; @@ -1893,9 +3740,13 @@ int main(void) { float glow = cfg.glow / 100.0f; float fl = cfg.flicker / 100.0f; - int active = active_count(); - ensure_filled(active); + 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; for (int i = 0; i < active; i++) { Body *b = &bodies[i]; @@ -1915,6 +3766,21 @@ int main(void) { 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; + } + float depth = sqrtf(dist2) / R; /* 0 at camera .. 1 at far sphere */ if (depth < 0) depth = 0; if (depth > 1) depth = 1; @@ -1984,21 +3850,36 @@ int main(void) { 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(); } - /* OSD: full for 10 s after last keypress, then fades over 4 s */ - 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); + /* 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); + } glfwSwapBuffers(win); glfwPollEvents(); } - save_settings(); /* persist current settings as next run's defaults */ + if (!g_screensaver) save_settings(); /* persist settings (not from the saver) */ glfwDestroyWindow(win); glfwTerminate(); return 0; diff --git a/vectorgons.exe b/vectorgons.exe new file mode 100755 index 0000000..c909450 Binary files /dev/null and b/vectorgons.exe differ diff --git a/vectorgons.scr b/vectorgons.scr new file mode 100755 index 0000000..505f95c Binary files /dev/null and b/vectorgons.scr differ