Web, Commodore, Windows version, Linux standalone, Windows Screensaver complete.
This commit is contained in:
parent
47730936de
commit
9257f21598
19 changed files with 794 additions and 18 deletions
1
.claude/scheduled_tasks.lock
Normal file
1
.claude/scheduled_tasks.lock
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"sessionId":"dca9796d-8a4b-4077-8999-f6dfc6f287a1","pid":12539,"procStart":"661839","acquiredAt":1780643072550}
|
||||||
|
|
@ -125,7 +125,44 @@
|
||||||
"Bash(python3 glow.py)",
|
"Bash(python3 glow.py)",
|
||||||
"Bash(python3 dense.py)",
|
"Bash(python3 dense.py)",
|
||||||
"Bash(montage g_bloom.png g_legacy.png -tile 2x1 -geometry +2+2 g_cmp125.png)",
|
"Bash(montage g_bloom.png g_legacy.png -tile 2x1 -geometry +2+2 g_cmp125.png)",
|
||||||
"Bash(montage g_bloom.png g_legacy.png -tile 2x1 -geometry +2+2 g_cmp_fold.png)"
|
"Bash(montage g_bloom.png g_legacy.png -tile 2x1 -geometry +2+2 g_cmp_fold.png)",
|
||||||
|
"Bash(emcc --version)",
|
||||||
|
"Read(//usr/lib/**)",
|
||||||
|
"Bash(command -v emcc)",
|
||||||
|
"Bash(echo \"PATH emcc: $\\(command -v emcc || echo MISSING\\)\")",
|
||||||
|
"Bash(timeout 8 bash -c 'curl -sSI https://github.com 2>&1 | head -3 || echo \"NO-CURL\"; echo \"---\"; ping -c1 -W2 github.com 2>&1 | head -2')",
|
||||||
|
"Bash(echo \"native run exit=$? \\(124=healthy\\)\")",
|
||||||
|
"Bash(curl -s -o /dev/null -w 'HTTP %{http_code} type=%{content_type} bytes=%{size_download}\\\\n' https://staging.frostwarning.com/vectorgons/__TRACKED_VAR__)",
|
||||||
|
"Bash(curl -s https://staging.frostwarning.com/vectorgons/index.html)",
|
||||||
|
"Bash(curl -sI https://staging.frostwarning.com/vectorgons/index.html)",
|
||||||
|
"Bash(curl -s -D - -o /dev/null https://staging.frostwarning.com/vectorgons/index.html)",
|
||||||
|
"Bash(convert test.png -resize 300% test_big.png)",
|
||||||
|
"Bash(cp test.png test_big.png)",
|
||||||
|
"Bash(convert test.png -crop 380x762+360+0 +repage -resize 170% col_morph.png)",
|
||||||
|
"Bash(convert test.png -crop 400x762+700+0 +repage -resize 170% col_dyn.png)",
|
||||||
|
"Bash(python3 winshot.py)",
|
||||||
|
"Bash(DISPLAY=:0 VG_WIN=1 timeout 30 wine vgtest.exe)",
|
||||||
|
"Bash(echo \"wine exit=$?\")",
|
||||||
|
"Bash(convert vgwin_out.ppm -crop 400x762+700+0 +repage -resize 170% win_dyn.png)",
|
||||||
|
"Bash(rm -rf /tmp/vgwin /tmp/vgweb/vg.c /tmp/vgweb/web /tmp/vgweb/*.png)",
|
||||||
|
"Bash(rm -f /tmp/pptr/*.png)",
|
||||||
|
"Bash(xargs -n1 basename)",
|
||||||
|
"Bash(apt-cache policy *)",
|
||||||
|
"Bash(apt-get install *)",
|
||||||
|
"Bash(xa --version)",
|
||||||
|
"Bash(c1541 -h)",
|
||||||
|
"Bash(cl65 --version)",
|
||||||
|
"Bash(dpkg-deb -x cc65_2.19-2_amd64.deb cc65root)",
|
||||||
|
"Bash(/tmp/cc65dl/cc65root/usr/bin/cl65 --version)",
|
||||||
|
"Bash(x64sc --help)",
|
||||||
|
"Bash(pkill -f x64sc)",
|
||||||
|
"Bash(grep -v '^$')",
|
||||||
|
"Bash(timeout 16 bash -c ' *)",
|
||||||
|
"Bash(export CC65_HOME=/tmp/cc65dl/cc65root/usr/share/cc65)",
|
||||||
|
"Bash(DISPLAY=:0 timeout 60 firefox --headless --window-size=1920,1080 --screenshot /tmp/fsshot.png file:///tmp/fstest.html)",
|
||||||
|
"Bash(grep -oE 'getFramebufferSize[^,;]{0,120}' web/vectorgons.js)",
|
||||||
|
"Bash(grep -oE 'onCanvasResize[^,;{]{0,60}|setWindowSize[^,;{]{0,80}' web/vectorgons.js)",
|
||||||
|
"Bash(DISPLAY=:0 timeout 60 firefox --headless --window-size=1920,1080 --screenshot /tmp/fsshot2.png file:///tmp/fstest2.html)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
Makefile
24
Makefile
|
|
@ -35,7 +35,25 @@ screensaver: $(SRC)
|
||||||
$(WINCC) $(WINFLAGS) -DSCREENSAVER -I$(GLFWDIR)/include -o $(TARGET).scr $(SRC) \
|
$(WINCC) $(WINFLAGS) -DSCREENSAVER -I$(GLFWDIR)/include -o $(TARGET).scr $(SRC) \
|
||||||
-L$(GLFWDIR)/lib-mingw-w64 $(WINLIBS)
|
-L$(GLFWDIR)/lib-mingw-w64 $(WINLIBS)
|
||||||
|
|
||||||
clean:
|
# --- WebAssembly / browser build (Emscripten) ------------------------------
|
||||||
rm -f $(TARGET) $(TARGET).exe $(TARGET).scr
|
# Produces vectorgons.js + vectorgons.wasm (load from an HTML page; see
|
||||||
|
# web/index.html). Legacy-GL emulation maps the fixed-function + immediate-mode
|
||||||
|
# rendering onto WebGL; GLFW3 is emulated; the VBO/FBO entry points are resolved
|
||||||
|
# at run time, so GL_ENABLE_GET_PROC_ADDRESS is required. Run:
|
||||||
|
# source /path/to/emsdk/emsdk_env.sh && make web
|
||||||
|
# Must be served over HTTP (WebAssembly will not load from file://).
|
||||||
|
EMCC ?= emcc
|
||||||
|
EMFLAGS = -O2 -std=c11 -sUSE_GLFW=3 -sLEGACY_GL_EMULATION=1 -sGL_UNSAFE_OPTS=0 \
|
||||||
|
-sGL_ENABLE_GET_PROC_ADDRESS=1 -sALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-sINITIAL_MEMORY=67108864 -sEXIT_RUNTIME=0
|
||||||
|
|
||||||
.PHONY: run windows screensaver clean
|
web: web/vectorgons.js
|
||||||
|
|
||||||
|
web/vectorgons.js: $(SRC)
|
||||||
|
mkdir -p web
|
||||||
|
$(EMCC) $(EMFLAGS) $(SRC) -o web/vectorgons.js
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET) $(TARGET).exe $(TARGET).scr web/vectorgons.js web/vectorgons.wasm
|
||||||
|
|
||||||
|
.PHONY: run windows screensaver web clean
|
||||||
|
|
|
||||||
93
README.md
93
README.md
|
|
@ -157,6 +157,99 @@ To install on Windows: right-click `vectorgons.scr` → **Install** (or copy it
|
||||||
`C:\Windows\System32` and choose it under *Settings → Personalization → Lock
|
`C:\Windows\System32` and choose it under *Settings → Personalization → Lock
|
||||||
screen → Screen saver*). Like the `.exe`, it needs only Windows system DLLs.
|
screen → Screen saver*). Like the `.exe`, it needs only Windows system DLLs.
|
||||||
|
|
||||||
|
## Browser (WebAssembly)
|
||||||
|
|
||||||
|
The same source compiles to WebAssembly with [Emscripten](https://emscripten.org),
|
||||||
|
which maps the GLFW3 windowing and the legacy/immediate-mode OpenGL onto WebGL
|
||||||
|
(`-sLEGACY_GL_EMULATION`). The retained-mode VBO geometry, the FBO bloom, and the
|
||||||
|
frustum culling all run unchanged; the browser-specific bits (GLU helpers, the
|
||||||
|
`requestAnimationFrame` main loop, and the WebGL-incompatible `double`/`glReadBuffer`
|
||||||
|
calls) are guarded behind `#ifdef __EMSCRIPTEN__`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
source /path/to/emsdk/emsdk_env.sh # put emcc on PATH
|
||||||
|
make web # -> web/vectorgons.js + web/vectorgons.wasm
|
||||||
|
```
|
||||||
|
|
||||||
|
Serve the `web/` directory over HTTP (WebAssembly will **not** load from a
|
||||||
|
`file://` URL) and open `index.html`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd web && python3 -m http.server 8000 # then visit http://localhost:8000/
|
||||||
|
```
|
||||||
|
|
||||||
|
`web/index.html` is a ready-made page. To embed in your own page, drop a focusable
|
||||||
|
canvas with `id="canvas"`, point Emscripten's `Module` at it, and load the script:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<canvas id="canvas" width="1100" height="760" tabindex="0"></canvas>
|
||||||
|
<script>
|
||||||
|
var Module = { canvas: document.getElementById('canvas') };
|
||||||
|
</script>
|
||||||
|
<script src="vectorgons.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Serve `vectorgons.js`, `vectorgons.wasm`, and `vectorgons-boot.js` from the same
|
||||||
|
directory. Click the canvas to give it keyboard focus; all the usual controls
|
||||||
|
then work, and `F` requests browser fullscreen. (Settings are not persisted in
|
||||||
|
the browser.)
|
||||||
|
|
||||||
|
### Content-Security-Policy
|
||||||
|
|
||||||
|
WebAssembly needs a couple of CSP allowances. If your server sends a strict CSP
|
||||||
|
(symptom: the canvas appears but nothing runs, and the console shows
|
||||||
|
`both async and sync fetching of the wasm failed`), the page's `script-src` must
|
||||||
|
include **`'wasm-unsafe-eval'`** (required to instantiate any WASM — there is no
|
||||||
|
nonce/hash alternative) and **`connect-src`** must allow fetching the `.wasm`
|
||||||
|
(e.g. `'self'`). Loading the `Module` config from `vectorgons-boot.js` (rather
|
||||||
|
than an inline `<script>`) means `'unsafe-inline'` is *not* required. A working
|
||||||
|
policy for the Vectorgons path:
|
||||||
|
|
||||||
|
```
|
||||||
|
script-src 'self' 'wasm-unsafe-eval'; connect-src 'self';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commodore 64 (`c64/`)
|
||||||
|
|
||||||
|
A bonus port for the original 1982 hardware lives in [`c64/`](c64/). The 1 MHz
|
||||||
|
6510 has no floating-point unit and a 1-bit, 320×200 hi-res bitmap, so this is a
|
||||||
|
ground-up reimplementation rather than the OpenGL engine recompiled — but it
|
||||||
|
keeps the heart of Vectorgons: **tumbling wireframe polytopes**, cycling through
|
||||||
|
a small shape library (cube, octahedron, tetrahedron, stellated star, pyramid,
|
||||||
|
and a 4-D tesseract shadow) in classic monochrome vector-graphics style.
|
||||||
|
|
||||||
|
Per the brief, the glass / metal / mirror objects are dropped — there is no
|
||||||
|
shading on a 1-bit bitmap. The compromises that make it fit a C64:
|
||||||
|
|
||||||
|
- **Integer fixed-point everywhere** — a 256-entry signed sine table (scale 128)
|
||||||
|
drives the 3-D rotation; points are perspective-projected with integer divide.
|
||||||
|
- **One object at a time**, drawn with a Bresenham line routine straight into the
|
||||||
|
VIC-II hi-res bitmap; each frame erases the previous edges and draws the new
|
||||||
|
ones (no full-screen clear) so it stays flicker-free without double buffering.
|
||||||
|
- Precomputed bitmap row/column/bit-mask address tables for fast plotting.
|
||||||
|
- A custom cc65 linker config keeps program code below `$2000`, reserves
|
||||||
|
`$2000–$3FFF` for the bitmap, and puts BSS/heap/stack above it.
|
||||||
|
|
||||||
|
Build it with [cc65](https://cc65.github.io/) and pack it onto a disk image with
|
||||||
|
VICE's `c1541`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd c64
|
||||||
|
make d64 # -> vectorgons.prg and vectorgons.d64
|
||||||
|
make run # build + launch in the VICE x64sc emulator
|
||||||
|
```
|
||||||
|
|
||||||
|
If `cl65`/`c1541` aren't on your `PATH`, point the Makefile at them, e.g.
|
||||||
|
`make d64 CL65=/path/to/cl65 CC65_HOME=/path/to/share/cc65`. On real hardware (or
|
||||||
|
an emulator) load it the usual way and it autostarts:
|
||||||
|
|
||||||
|
```
|
||||||
|
LOAD"VECTORGONS",8,1
|
||||||
|
RUN
|
||||||
|
```
|
||||||
|
|
||||||
|
Press any key to return to BASIC.
|
||||||
|
|
||||||
## Settings persistence
|
## Settings persistence
|
||||||
|
|
||||||
Your settings are saved automatically on exit to `~/.vectorgons` (a plain
|
Your settings are saved automatically on exit to `~/.vectorgons` (a plain
|
||||||
|
|
|
||||||
55
c64/Makefile
Normal file
55
c64/Makefile
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# ===========================================================================
|
||||||
|
# Vectorgons -- Commodore 64 build
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Cross-compiles the C64 edition with cc65 and packs it onto a .d64 disk
|
||||||
|
# image with VICE's c1541.
|
||||||
|
#
|
||||||
|
# make -> vectorgons.prg (raw C64 program, loads at $0801)
|
||||||
|
# make d64 -> vectorgons.d64 (bootable disk image)
|
||||||
|
# make run -> build + launch in the VICE x64sc emulator
|
||||||
|
# make clean
|
||||||
|
#
|
||||||
|
# cc65 need not be on PATH: set CC65_HOME / CL65 to point at an extracted
|
||||||
|
# install if it lives somewhere non-standard, e.g.
|
||||||
|
# make CL65=/path/to/cl65 CC65_HOME=/path/to/share/cc65
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
CL65 ?= cl65
|
||||||
|
C1541 ?= c1541
|
||||||
|
X64 ?= x64sc
|
||||||
|
|
||||||
|
TARGET = vectorgons
|
||||||
|
PRG = $(TARGET).prg
|
||||||
|
D64 = $(TARGET).d64
|
||||||
|
CFG = c64-vg.cfg
|
||||||
|
SRC = vectorgons.c
|
||||||
|
|
||||||
|
# Name shown in the C64 disk directory (PETSCII, <=16 chars).
|
||||||
|
DISKNAME = vectorgons
|
||||||
|
DISKID = vg
|
||||||
|
# Filename on the disk that the user LOADs.
|
||||||
|
DISKFILE = vectorgons
|
||||||
|
|
||||||
|
CL65FLAGS = -t c64 -O -C $(CFG)
|
||||||
|
|
||||||
|
.PHONY: all d64 run clean
|
||||||
|
|
||||||
|
all: $(PRG)
|
||||||
|
|
||||||
|
$(PRG): $(SRC) $(CFG)
|
||||||
|
$(CL65) $(CL65FLAGS) $(SRC) -o $(PRG)
|
||||||
|
|
||||||
|
# Build a fresh, formatted .d64 and write the program into it.
|
||||||
|
d64: $(D64)
|
||||||
|
|
||||||
|
$(D64): $(PRG)
|
||||||
|
$(C1541) -format "$(DISKNAME),$(DISKID)" d64 $(D64) \
|
||||||
|
-write $(PRG) $(DISKFILE)
|
||||||
|
@echo "Wrote $(PRG) -> $(D64) as \"$(DISKFILE)\""
|
||||||
|
@$(C1541) $(D64) -dir
|
||||||
|
|
||||||
|
run: $(D64)
|
||||||
|
$(X64) -autostart $(D64)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(PRG) $(D64) *.map *.o shot*.png seq_*.png f_*.png
|
||||||
51
c64/c64-vg.cfg
Normal file
51
c64/c64-vg.cfg
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Custom cc65 C64 linker config for Vectorgons.
|
||||||
|
# Code/RO/DATA live below the hi-res bitmap ($0801-$1FFF); the bitmap occupies
|
||||||
|
# $2000-$3FFF; BSS/heap/C-stack live above it ($4000-$CFFF). This guarantees
|
||||||
|
# the VIC-II bitmap region is never clobbered by program data.
|
||||||
|
FEATURES {
|
||||||
|
STARTADDRESS: default = $0801;
|
||||||
|
}
|
||||||
|
SYMBOLS {
|
||||||
|
__LOADADDR__: type = import;
|
||||||
|
__EXEHDR__: type = import;
|
||||||
|
__STACKSIZE__: type = weak, value = $0800; # 2k C stack
|
||||||
|
__HIMEM__: type = weak, value = $D000;
|
||||||
|
}
|
||||||
|
MEMORY {
|
||||||
|
ZP: file = "", define = yes, start = $0002, size = $001A;
|
||||||
|
LOADADDR: file = %O, start = %S - 2, size = $0002;
|
||||||
|
HEADER: file = %O, define = yes, start = %S, size = $000D;
|
||||||
|
# Code, read-only and initialized data: must end before the bitmap at $2000.
|
||||||
|
MAIN: file = %O, define = yes, start = __HEADER_LAST__, size = $2000 - __HEADER_LAST__;
|
||||||
|
# $2000-$3FFF is reserved for the VIC-II hi-res bitmap (not mapped here).
|
||||||
|
# BSS / heap / C-stack live above the bitmap.
|
||||||
|
BSS: file = "", start = $4000, size = __HIMEM__ - __STACKSIZE__ - $4000;
|
||||||
|
}
|
||||||
|
SEGMENTS {
|
||||||
|
ZEROPAGE: load = ZP, type = zp;
|
||||||
|
LOADADDR: load = LOADADDR, type = ro;
|
||||||
|
EXEHDR: load = HEADER, type = ro;
|
||||||
|
STARTUP: load = MAIN, type = ro;
|
||||||
|
LOWCODE: load = MAIN, type = ro, optional = yes;
|
||||||
|
CODE: load = MAIN, type = ro;
|
||||||
|
RODATA: load = MAIN, type = ro;
|
||||||
|
DATA: load = MAIN, type = rw;
|
||||||
|
INIT: load = MAIN, type = rw;
|
||||||
|
ONCE: load = MAIN, type = ro, define = yes;
|
||||||
|
BSS: load = BSS, type = bss, define = yes;
|
||||||
|
}
|
||||||
|
FEATURES {
|
||||||
|
CONDES: type = constructor,
|
||||||
|
label = __CONSTRUCTOR_TABLE__,
|
||||||
|
count = __CONSTRUCTOR_COUNT__,
|
||||||
|
segment = ONCE;
|
||||||
|
CONDES: type = destructor,
|
||||||
|
label = __DESTRUCTOR_TABLE__,
|
||||||
|
count = __DESTRUCTOR_COUNT__,
|
||||||
|
segment = RODATA;
|
||||||
|
CONDES: type = interruptor,
|
||||||
|
label = __INTERRUPTOR_TABLE__,
|
||||||
|
count = __INTERRUPTOR_COUNT__,
|
||||||
|
segment = RODATA,
|
||||||
|
import = __CALLIRQ__;
|
||||||
|
}
|
||||||
311
c64/vectorgons.c
Normal file
311
c64/vectorgons.c
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
/* ===========================================================================
|
||||||
|
* VECTORGONS -- Commodore 64 edition
|
||||||
|
* ---------------------------------------------------------------------------
|
||||||
|
* A tumbling-wireframe vector demo for the C64, a tiny cousin of the desktop /
|
||||||
|
* web Vectorgons. The 1 MHz 6510 has no FPU, so everything here is integer
|
||||||
|
* fixed-point: a 256-entry signed sine table (scale 128) drives the 3-D
|
||||||
|
* rotation, points are perspective-projected with integer division, and edges
|
||||||
|
* are drawn into the VIC-II hi-res bitmap with a Bresenham line routine.
|
||||||
|
*
|
||||||
|
* Per the brief the glass / metal / mirror objects are dropped (no shading on
|
||||||
|
* a 1-bit bitmap) -- what remains is the heart of Vectorgons: rotating
|
||||||
|
* polytopes, cycling through a small library of 3-D shapes plus a 4-D
|
||||||
|
* tesseract shadow, in classic monochrome vector-graphics style.
|
||||||
|
*
|
||||||
|
* Display : 320x200 hi-res bitmap at $2000, colour cells in screen RAM $0400.
|
||||||
|
* Build : cc65 (cl65 -t c64 -C c64-vg.cfg). See the Makefile in this dir.
|
||||||
|
* Controls: press any key to quit back to BASIC.
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <peekpoke.h>
|
||||||
|
|
||||||
|
/* ---- VIC-II / memory layout ------------------------------------------- */
|
||||||
|
#define BITMAP ((unsigned char *)0x2000) /* 8 KB hi-res bitmap */
|
||||||
|
#define SCREEN ((unsigned char *)0x0400) /* colour cells (1000 bytes) */
|
||||||
|
#define VIC_D011 0xD011
|
||||||
|
#define VIC_D016 0xD016
|
||||||
|
#define VIC_D018 0xD018
|
||||||
|
#define VIC_D020 0xD020 /* border colour */
|
||||||
|
#define VIC_D021 0xD021 /* background colour */
|
||||||
|
#define KBD_NDX 0x00C6 /* keyboard buffer count */
|
||||||
|
|
||||||
|
/* ---- projection constants (model units) ------------------------------- */
|
||||||
|
#define PROJ 224 /* focal length */
|
||||||
|
#define DIST 140 /* camera distance */
|
||||||
|
#define SCRW 320
|
||||||
|
#define SCRH 200
|
||||||
|
|
||||||
|
#define MAXV 16 /* tesseract has the most vertices */
|
||||||
|
|
||||||
|
/* round(127 * sin(i * 2pi / 256)) -- fixed-point scale 128 (>>7) */
|
||||||
|
static const signed char sintab[256] = {
|
||||||
|
0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46,
|
||||||
|
49, 51, 54, 57, 60, 63, 65, 68, 71, 73, 76, 78, 81, 83, 85, 88,
|
||||||
|
90, 92, 94, 96, 98, 100, 102, 104, 106, 107, 109, 111, 112, 113, 115, 116,
|
||||||
|
117, 118, 120, 121, 122, 122, 123, 124, 125, 125, 126, 126, 126, 127, 127, 127,
|
||||||
|
127, 127, 127, 127, 126, 126, 126, 125, 125, 124, 123, 122, 122, 121, 120, 118,
|
||||||
|
117, 116, 115, 113, 112, 111, 109, 107, 106, 104, 102, 100, 98, 96, 94, 92,
|
||||||
|
90, 88, 85, 83, 81, 78, 76, 73, 71, 68, 65, 63, 60, 57, 54, 51,
|
||||||
|
49, 46, 43, 40, 37, 34, 31, 28, 25, 22, 19, 16, 12, 9, 6, 3,
|
||||||
|
0, -3, -6, -9, -12, -16, -19, -22, -25, -28, -31, -34, -37, -40, -43, -46,
|
||||||
|
-49, -51, -54, -57, -60, -63, -65, -68, -71, -73, -76, -78, -81, -83, -85, -88,
|
||||||
|
-90, -92, -94, -96, -98, -100, -102, -104, -106, -107, -109, -111, -112, -113, -115, -116,
|
||||||
|
-117, -118, -120, -121, -122, -122, -123, -124, -125, -125, -126, -126, -126, -127, -127, -127,
|
||||||
|
-127, -127, -127, -127, -126, -126, -126, -125, -125, -124, -123, -122, -122, -121, -120, -118,
|
||||||
|
-117, -116, -115, -113, -112, -111, -109, -107, -106, -104, -102, -100, -98, -96, -94, -92,
|
||||||
|
-90, -88, -85, -83, -81, -78, -76, -73, -71, -68, -65, -63, -60, -57, -54, -51,
|
||||||
|
-49, -46, -43, -40, -37, -34, -31, -28, -25, -22, -19, -16, -12, -9, -6, -3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ===========================================================================
|
||||||
|
* Shape library. Vertices are signed-char model coords (~ +/-45); edges are
|
||||||
|
* vertex-index pairs. Skip metal/glass/mirror -- pure wireframe polytopes.
|
||||||
|
* ===========================================================================
|
||||||
|
*/
|
||||||
|
static const signed char tetra_v[4][3] = {
|
||||||
|
{ 45, 45, 45}, { 45,-45,-45}, {-45, 45,-45}, {-45,-45, 45}
|
||||||
|
};
|
||||||
|
static const unsigned char tetra_e[6][2] = {
|
||||||
|
{0,1},{0,2},{0,3},{1,2},{1,3},{2,3}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const signed char cube_v[8][3] = {
|
||||||
|
{-40,-40,-40},{ 40,-40,-40},{ 40, 40,-40},{-40, 40,-40},
|
||||||
|
{-40,-40, 40},{ 40,-40, 40},{ 40, 40, 40},{-40, 40, 40}
|
||||||
|
};
|
||||||
|
static const unsigned char cube_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}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const signed char octa_v[6][3] = {
|
||||||
|
{ 50,0,0},{-50,0,0},{0, 50,0},{0,-50,0},{0,0, 50},{0,0,-50}
|
||||||
|
};
|
||||||
|
static const unsigned char octa_e[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}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const signed char pyra_v[5][3] = {
|
||||||
|
{-40,-30,-40},{ 40,-30,-40},{ 40,-30, 40},{-40,-30, 40},{ 0, 48, 0}
|
||||||
|
};
|
||||||
|
static const unsigned char pyra_e[8][2] = {
|
||||||
|
{0,1},{1,2},{2,3},{3,0},{4,0},{4,1},{4,2},{4,3}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Stellated "star": two interpenetrating tetrahedra (Stella octangula). */
|
||||||
|
static const signed char star_v[8][3] = {
|
||||||
|
{ 45, 45, 45},{ 45,-45,-45},{-45, 45,-45},{-45,-45, 45},
|
||||||
|
{-45,-45,-45},{-45, 45, 45},{ 45,-45, 45},{ 45, 45,-45}
|
||||||
|
};
|
||||||
|
static const unsigned char star_e[12][2] = {
|
||||||
|
{0,1},{0,2},{0,3},{1,2},{1,3},{2,3},
|
||||||
|
{4,5},{4,6},{4,7},{5,6},{5,7},{6,7}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Tesseract shadow: an inner cube nested in an outer cube, corners linked. */
|
||||||
|
static const signed char tess_v[16][3] = {
|
||||||
|
{-20,-20,-20},{ 20,-20,-20},{ 20, 20,-20},{-20, 20,-20},
|
||||||
|
{-20,-20, 20},{ 20,-20, 20},{ 20, 20, 20},{-20, 20, 20},
|
||||||
|
{-46,-46,-46},{ 46,-46,-46},{ 46, 46,-46},{-46, 46,-46},
|
||||||
|
{-46,-46, 46},{ 46,-46, 46},{ 46, 46, 46},{-46, 46, 46}
|
||||||
|
};
|
||||||
|
static const unsigned char tess_e[32][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},
|
||||||
|
{8,9},{9,10},{10,11},{11,8},{12,13},{13,14},{14,15},{15,12},
|
||||||
|
{8,12},{9,13},{10,14},{11,15},
|
||||||
|
{0,8},{1,9},{2,10},{3,11},{4,12},{5,13},{6,14},{7,15}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const signed char (*v)[3];
|
||||||
|
unsigned char nv;
|
||||||
|
const unsigned char (*e)[2];
|
||||||
|
unsigned char ne;
|
||||||
|
} Shape;
|
||||||
|
|
||||||
|
static const Shape shapes[] = {
|
||||||
|
{ cube_v, 8, cube_e, 12 },
|
||||||
|
{ octa_v, 6, octa_e, 12 },
|
||||||
|
{ tetra_v, 4, tetra_e, 6 },
|
||||||
|
{ star_v, 8, star_e, 12 },
|
||||||
|
{ pyra_v, 5, pyra_e, 8 },
|
||||||
|
{ tess_v, 16, tess_e, 32 }
|
||||||
|
};
|
||||||
|
#define NSHAPES (sizeof(shapes) / sizeof(shapes[0]))
|
||||||
|
|
||||||
|
/* Foreground colours cycled per shape (upper nibble; background black). */
|
||||||
|
static const unsigned char palette[] = { 3, 14, 1, 7, 13, 5, 4 };
|
||||||
|
#define NCOL (sizeof(palette) / sizeof(palette[0]))
|
||||||
|
|
||||||
|
/* ---- precomputed bitmap address tables (built at startup) ------------- */
|
||||||
|
static unsigned int ytab[SCRH]; /* (y>>3)*320 + (y&7) */
|
||||||
|
static unsigned int xtab[SCRW]; /* (x>>3)*8 */
|
||||||
|
static unsigned char mtab[SCRW]; /* 0x80 >> (x&7) */
|
||||||
|
|
||||||
|
/* ---- per-frame projected coordinates ---------------------------------- */
|
||||||
|
static int sx[MAXV], sy[MAXV]; /* current frame */
|
||||||
|
static int psx[MAXV], psy[MAXV]; /* previous frame */
|
||||||
|
|
||||||
|
static void build_tables(void)
|
||||||
|
{
|
||||||
|
unsigned int y, x;
|
||||||
|
for (y = 0; y < SCRH; ++y)
|
||||||
|
ytab[y] = (unsigned int)((y >> 3) * 320 + (y & 7));
|
||||||
|
for (x = 0; x < SCRW; ++x) {
|
||||||
|
xtab[x] = (unsigned int)((x >> 3) * 8);
|
||||||
|
mtab[x] = (unsigned char)(0x80 >> (x & 7));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set bitmap mode, bitmap @ $2000, colour cells @ $0400, black border. */
|
||||||
|
static void gfx_on(unsigned char colour)
|
||||||
|
{
|
||||||
|
memset(BITMAP, 0, 8000); /* clear bitmap */
|
||||||
|
memset(SCREEN, colour, 1000); /* fg/bg per cell */
|
||||||
|
POKE(VIC_D020, 0); /* black border */
|
||||||
|
POKE(VIC_D021, 0); /* black background */
|
||||||
|
POKE(VIC_D018, 0x18); /* screen $0400, bmp $2000 */
|
||||||
|
POKE(VIC_D016, PEEK(VIC_D016) & 0xEF);/* hi-res (MCM off) */
|
||||||
|
POKE(VIC_D011, PEEK(VIC_D011) | 0x20);/* bitmap mode (BMM on) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Restore the normal text screen on the way out. */
|
||||||
|
static void gfx_off(void)
|
||||||
|
{
|
||||||
|
POKE(VIC_D011, PEEK(VIC_D011) & 0xDF);/* BMM off */
|
||||||
|
POKE(VIC_D018, 0x15); /* default char/screen */
|
||||||
|
POKE(VIC_D020, 14); /* default border */
|
||||||
|
POKE(VIC_D021, 6); /* default background */
|
||||||
|
memset(SCREEN, 0x20, 1000); /* blank text screen */
|
||||||
|
memset((unsigned char *)0xD800, 14, 1000); /* light-blue text */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plot one pixel. set != 0 turns the pixel on, otherwise clears it.
|
||||||
|
* Off-screen points are silently clipped. */
|
||||||
|
static void plot(int x, int y, unsigned char set)
|
||||||
|
{
|
||||||
|
unsigned char *p;
|
||||||
|
if (x < 0 || x >= SCRW || y < 0 || y >= SCRH)
|
||||||
|
return;
|
||||||
|
p = BITMAP + ytab[y] + xtab[x];
|
||||||
|
if (set)
|
||||||
|
*p |= mtab[x];
|
||||||
|
else
|
||||||
|
*p &= (unsigned char)~mtab[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Integer Bresenham line from (x0,y0) to (x1,y1). */
|
||||||
|
static void line(int x0, int y0, int x1, int y1, unsigned char set)
|
||||||
|
{
|
||||||
|
int dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
|
||||||
|
int dy = -((y1 > y0) ? (y1 - y0) : (y0 - y1));
|
||||||
|
int sx0 = (x0 < x1) ? 1 : -1;
|
||||||
|
int sy0 = (y0 < y1) ? 1 : -1;
|
||||||
|
int err = dx + dy;
|
||||||
|
int e2;
|
||||||
|
for (;;) {
|
||||||
|
plot(x0, y0, set);
|
||||||
|
if (x0 == x1 && y0 == y1)
|
||||||
|
break;
|
||||||
|
e2 = err << 1;
|
||||||
|
if (e2 >= dy) { err += dy; x0 += sx0; }
|
||||||
|
if (e2 <= dx) { err += dx; y0 += sy0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotate the current shape's vertices by (ax,ay,az) and perspective-project
|
||||||
|
* into sx[]/sy[]. All maths integer; sin/cos read from sintab (>>7). */
|
||||||
|
static void project(const Shape *sh,
|
||||||
|
unsigned char ax, unsigned char ay, unsigned char az)
|
||||||
|
{
|
||||||
|
int sinx = sintab[ax];
|
||||||
|
int cosx = sintab[(unsigned char)(ax + 64)];
|
||||||
|
int siny = sintab[ay];
|
||||||
|
int cosy = sintab[(unsigned char)(ay + 64)];
|
||||||
|
int sinz = sintab[az];
|
||||||
|
int cosz = sintab[(unsigned char)(az + 64)];
|
||||||
|
unsigned char i;
|
||||||
|
|
||||||
|
for (i = 0; i < sh->nv; ++i) {
|
||||||
|
int x = sh->v[i][0];
|
||||||
|
int y = sh->v[i][1];
|
||||||
|
int z = sh->v[i][2];
|
||||||
|
int t;
|
||||||
|
int denom;
|
||||||
|
|
||||||
|
/* rotate about X */
|
||||||
|
t = (y * cosx - z * sinx) >> 7;
|
||||||
|
z = (y * sinx + z * cosx) >> 7;
|
||||||
|
y = t;
|
||||||
|
/* rotate about Y */
|
||||||
|
t = (x * cosy + z * siny) >> 7;
|
||||||
|
z = (z * cosy - x * siny) >> 7;
|
||||||
|
x = t;
|
||||||
|
/* rotate about Z */
|
||||||
|
t = (x * cosz - y * sinz) >> 7;
|
||||||
|
y = (x * sinz + y * cosz) >> 7;
|
||||||
|
x = t;
|
||||||
|
|
||||||
|
denom = z + DIST; /* always positive given coord ranges */
|
||||||
|
sx[i] = 160 + (x * PROJ) / denom;
|
||||||
|
sy[i] = 100 - (y * PROJ) / denom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
unsigned char shape = 0;
|
||||||
|
unsigned char colidx = 0;
|
||||||
|
unsigned char ax = 0, ay = 0, az = 0;
|
||||||
|
unsigned int frame = 0;
|
||||||
|
unsigned char first = 1;
|
||||||
|
const Shape *sh;
|
||||||
|
unsigned char i;
|
||||||
|
|
||||||
|
build_tables();
|
||||||
|
POKE(KBD_NDX, 0); /* flush keyboard buffer */
|
||||||
|
gfx_on(palette[0] << 4);
|
||||||
|
|
||||||
|
sh = &shapes[0];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* --- advance rotation (three different rates => tumble) --- */
|
||||||
|
ax += 2;
|
||||||
|
ay += 3;
|
||||||
|
az += 1;
|
||||||
|
|
||||||
|
project(sh, ax, ay, az);
|
||||||
|
|
||||||
|
/* --- erase previous frame's edges, then draw the new ones --- */
|
||||||
|
if (!first) {
|
||||||
|
for (i = 0; i < sh->ne; ++i) {
|
||||||
|
unsigned char a = sh->e[i][0], b = sh->e[i][1];
|
||||||
|
line(psx[a], psy[a], psx[b], psy[b], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < sh->ne; ++i) {
|
||||||
|
unsigned char a = sh->e[i][0], b = sh->e[i][1];
|
||||||
|
line(sx[a], sy[a], sx[b], sy[b], 1);
|
||||||
|
}
|
||||||
|
for (i = 0; i < sh->nv; ++i) { psx[i] = sx[i]; psy[i] = sy[i]; }
|
||||||
|
first = 0;
|
||||||
|
|
||||||
|
/* --- cycle to the next shape every ~200 frames --- */
|
||||||
|
if (++frame >= 200) {
|
||||||
|
frame = 0;
|
||||||
|
shape = (unsigned char)((shape + 1) % NSHAPES);
|
||||||
|
colidx = (unsigned char)((colidx + 1) % NCOL);
|
||||||
|
sh = &shapes[shape];
|
||||||
|
first = 1;
|
||||||
|
memset(BITMAP, 0, 8000); /* fresh canvas */
|
||||||
|
memset(SCREEN, palette[colidx] << 4, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- quit on any keypress --- */
|
||||||
|
if (PEEK(KBD_NDX) != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx_off();
|
||||||
|
}
|
||||||
BIN
c64/vectorgons.d64
Normal file
BIN
c64/vectorgons.d64
Normal file
Binary file not shown.
BIN
c64/vectorgons.o
Normal file
BIN
c64/vectorgons.o
Normal file
Binary file not shown.
BIN
c64/vectorgons.prg
Normal file
BIN
c64/vectorgons.prg
Normal file
Binary file not shown.
1
resume.txt
Normal file
1
resume.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
claude --resume dca9796d-8a4b-4077-8999-f6dfc6f287a1
|
||||||
BIN
vectorgons
BIN
vectorgons
Binary file not shown.
183
vectorgons.c
183
vectorgons.c
|
|
@ -67,8 +67,18 @@
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#include <windows.h> /* provides APIENTRY / CALLBACK that the GL headers need */
|
#include <windows.h> /* provides APIENTRY / CALLBACK that the GL headers need */
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
/* Browser build: WebGL via Emscripten's legacy-GL emulation. GLU is not
|
||||||
|
* provided, so the three GLU helpers used here are shimmed below. */
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/html5.h>
|
||||||
|
#define GLFW_INCLUDE_NONE
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#else
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <GL/glu.h>
|
#include <GL/glu.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Legacy GL constants that the Windows (MinGW) OpenGL 1.1 headers may omit.
|
/* 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
|
* The values are standard; the GPU driver provides the functionality at run
|
||||||
|
|
@ -88,6 +98,50 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
/* Minimal GLU replacements (Emscripten has no GLU). gluOrtho2D/gluPerspective
|
||||||
|
* post-multiply onto the current fixed-function matrix; gluProject is pure math. */
|
||||||
|
static void gluOrtho2D(double l, double r, double b, double t) {
|
||||||
|
float m[16] = { 2.0f/(float)(r-l),0,0,0, 0,2.0f/(float)(t-b),0,0, 0,0,-1,0,
|
||||||
|
-(float)((r+l)/(r-l)), -(float)((t+b)/(t-b)), 0, 1 };
|
||||||
|
glMultMatrixf(m);
|
||||||
|
}
|
||||||
|
static void gluPerspective(double fovy, double aspect, double zn, double zf) {
|
||||||
|
double f = 1.0 / tan(fovy * (M_PI / 360.0));
|
||||||
|
float m[16] = { (float)(f/aspect),0,0,0, 0,(float)f,0,0,
|
||||||
|
0,0,(float)((zf+zn)/(zn-zf)),-1, 0,0,(float)((2*zf*zn)/(zn-zf)),0 };
|
||||||
|
glMultMatrixf(m);
|
||||||
|
}
|
||||||
|
static int gluProject(double ox, double oy, double oz, const double m[16],
|
||||||
|
const double p[16], const int vp[4],
|
||||||
|
double *wx, double *wy, double *wz) {
|
||||||
|
double a0=m[0]*ox+m[4]*oy+m[8]*oz+m[12], a1=m[1]*ox+m[5]*oy+m[9]*oz+m[13];
|
||||||
|
double a2=m[2]*ox+m[6]*oy+m[10]*oz+m[14], a3=m[3]*ox+m[7]*oy+m[11]*oz+m[15];
|
||||||
|
double c0=p[0]*a0+p[4]*a1+p[8]*a2+p[12]*a3, c1=p[1]*a0+p[5]*a1+p[9]*a2+p[13]*a3;
|
||||||
|
double c2=p[2]*a0+p[6]*a1+p[10]*a2+p[14]*a3, c3=p[3]*a0+p[7]*a1+p[11]*a2+p[15]*a3;
|
||||||
|
if (c3 == 0.0) return 0;
|
||||||
|
c0/=c3; c1/=c3; c2/=c3;
|
||||||
|
*wx = vp[0] + (c0*0.5+0.5)*vp[2];
|
||||||
|
*wy = vp[1] + (c1*0.5+0.5)*vp[3];
|
||||||
|
*wz = c2*0.5 + 0.5;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/* The legacy-GL emulation exposes only the float entry points; map the handful
|
||||||
|
* of double-precision calls used here onto them (all glGetDoublev calls query a
|
||||||
|
* 4x4 matrix). */
|
||||||
|
static void vg_get_doublev(GLenum pname, double *out) {
|
||||||
|
float f[16];
|
||||||
|
glGetFloatv(pname, f);
|
||||||
|
for (int i = 0; i < 16; i++) out[i] = f[i];
|
||||||
|
}
|
||||||
|
#define glGetDoublev(pname, out) vg_get_doublev((pname), (out))
|
||||||
|
#define glVertex2d(x, y) glVertex2f((float)(x), (float)(y))
|
||||||
|
#define glVertex3d(x, y, z) glVertex3f((float)(x), (float)(y), (float)(z))
|
||||||
|
#endif /* __EMSCRIPTEN__ */
|
||||||
|
|
||||||
/* --- OpenGL 1.5 buffer objects (Step 1: retained-mode geometry) --------------
|
/* --- OpenGL 1.5 buffer objects (Step 1: retained-mode geometry) --------------
|
||||||
* The hot wireframe geometry is uploaded to VBOs and drawn with glDrawElements
|
* The hot wireframe geometry is uploaded to VBOs and drawn with glDrawElements
|
||||||
* instead of per-vertex glBegin/glVertex3fv, which removes the millions of
|
* instead of per-vertex glBegin/glVertex3fv, which removes the millions of
|
||||||
|
|
@ -2873,6 +2927,7 @@ static unsigned g_drawn = 0; /* bodies that passed frustum cull
|
||||||
static double g_frustum[6][4]; /* world-space frustum planes (a,b,c,d) */
|
static double g_frustum[6][4]; /* world-space frustum planes (a,b,c,d) */
|
||||||
static int g_vsync = 1; /* F2 toggles glfwSwapInterval(0/1) */
|
static int g_vsync = 1; /* F2 toggles glfwSwapInterval(0/1) */
|
||||||
static int g_show_hud = 1; /* F1 toggles the perf HUD */
|
static int g_show_hud = 1; /* F1 toggles the perf HUD */
|
||||||
|
static GLFWwindow *g_win = NULL; /* the window/canvas, shared with the frame fn */
|
||||||
|
|
||||||
/* --- retained-mode geometry buffers (one per shape) --------------------------
|
/* --- retained-mode geometry buffers (one per shape) --------------------------
|
||||||
* g_shape_vbomode[si]: 0 = immediate mode (clocks/dynamics whose topology is
|
* g_shape_vbomode[si]: 0 = immediate mode (clocks/dynamics whose topology is
|
||||||
|
|
@ -3207,6 +3262,9 @@ static void settings_path(char *buf, size_t n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void save_settings(void) {
|
static void save_settings(void) {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
return; /* no persistent filesystem in the browser */
|
||||||
|
#else
|
||||||
char path[1024];
|
char path[1024];
|
||||||
settings_path(path, sizeof path);
|
settings_path(path, sizeof path);
|
||||||
FILE *f = fopen(path, "w");
|
FILE *f = fopen(path, "w");
|
||||||
|
|
@ -3230,9 +3288,13 @@ static void save_settings(void) {
|
||||||
fprintf(f, "mirror_count=%d\n", cfg.mirror_count);
|
fprintf(f, "mirror_count=%d\n", cfg.mirror_count);
|
||||||
fprintf(f, "fullscreen=%d\n", cfg.fullscreen);
|
fprintf(f, "fullscreen=%d\n", cfg.fullscreen);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void load_settings(void) {
|
static void load_settings(void) {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
return; /* no persistent filesystem in the browser */
|
||||||
|
#else
|
||||||
char path[1024];
|
char path[1024];
|
||||||
settings_path(path, sizeof path);
|
settings_path(path, sizeof path);
|
||||||
FILE *f = fopen(path, "r");
|
FILE *f = fopen(path, "r");
|
||||||
|
|
@ -3282,11 +3344,33 @@ static void load_settings(void) {
|
||||||
cfg.multicolor = cfg.multicolor ? 1 : 0;
|
cfg.multicolor = cfg.multicolor ? 1 : 0;
|
||||||
cfg.cycle_shapes = cfg.cycle_shapes ? 1 : 0;
|
cfg.cycle_shapes = cfg.cycle_shapes ? 1 : 0;
|
||||||
cfg.fullscreen = cfg.fullscreen ? 1 : 0;
|
cfg.fullscreen = cfg.fullscreen ? 1 : 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
/* Keep cfg.fullscreen in sync with the browser for *any* fullscreen change
|
||||||
|
* (e.g. the user pressing Esc, which doesn't go through toggle_fullscreen). The
|
||||||
|
* actual canvas/viewport sizing is handled per-frame in render_frame(), which
|
||||||
|
* tracks the canvas's real displayed size. */
|
||||||
|
static EM_BOOL on_fullscreen_change(int type,
|
||||||
|
const EmscriptenFullscreenChangeEvent *e,
|
||||||
|
void *user) {
|
||||||
|
(void)type; (void)user;
|
||||||
|
cfg.fullscreen = e->isFullscreen ? 1 : 0;
|
||||||
|
return EM_FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void toggle_fullscreen(GLFWwindow *win) {
|
static void toggle_fullscreen(GLFWwindow *win) {
|
||||||
static int wx = 100, wy = 100, ww = 1100, wh = 760;
|
|
||||||
cfg.fullscreen = !cfg.fullscreen;
|
cfg.fullscreen = !cfg.fullscreen;
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
(void)win; /* browser: request canvas fullscreen */
|
||||||
|
/* Just enter/exit fullscreen here; on_fullscreen_change() does the canvas
|
||||||
|
* resize so the render fills the screen at the correct (screen) aspect. */
|
||||||
|
if (cfg.fullscreen) emscripten_request_fullscreen("#canvas", 1);
|
||||||
|
else emscripten_exit_fullscreen();
|
||||||
|
#else
|
||||||
|
static int wx = 100, wy = 100, ww = 1100, wh = 760;
|
||||||
if (cfg.fullscreen) {
|
if (cfg.fullscreen) {
|
||||||
glfwGetWindowPos(win, &wx, &wy);
|
glfwGetWindowPos(win, &wx, &wy);
|
||||||
glfwGetWindowSize(win, &ww, &wh);
|
glfwGetWindowSize(win, &ww, &wh);
|
||||||
|
|
@ -3296,6 +3380,7 @@ static void toggle_fullscreen(GLFWwindow *win) {
|
||||||
} else {
|
} else {
|
||||||
glfwSetWindowMonitor(win, NULL, wx, wy, ww, wh, 0);
|
glfwSetWindowMonitor(win, NULL, wx, wy, ww, wh, 0);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void key_cb(GLFWwindow *win, int key, int sc, int action, int mods) {
|
static void key_cb(GLFWwindow *win, int key, int sc, int action, int mods) {
|
||||||
|
|
@ -3503,12 +3588,24 @@ static void draw_edges(const Solid *s, float p[][3]) {
|
||||||
glDrawElements(GL_LINES, s->ne * 2, GL_UNSIGNED_SHORT, (const void *)0);
|
glDrawElements(GL_LINES, s->ne * 2, GL_UNSIGNED_SHORT, (const void *)0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/* Immediate-mode path (clocks / dynamics, whose topology is rebuilt each
|
||||||
|
* frame). Detach the vertex buffer / client array first: WebGL's legacy-GL
|
||||||
|
* immediate-mode emulation otherwise folds the stale array state into the
|
||||||
|
* glVertex stream and draws garbage long lines. (No-op on desktop, where
|
||||||
|
* immediate mode already ignores client arrays.) glVertex3f is also better
|
||||||
|
* supported by the emulation than the 3fv vector form. */
|
||||||
|
if (g_have_vbo) {
|
||||||
|
glDisableClientState(GL_VERTEX_ARRAY);
|
||||||
|
vgBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
}
|
||||||
glBegin(GL_LINES);
|
glBegin(GL_LINES);
|
||||||
for (int e = 0; e < s->ne; e++) {
|
for (int e = 0; e < s->ne; e++) {
|
||||||
glVertex3fv(p[s->e[e][0]]);
|
const float *a = p[s->e[e][0]], *b = p[s->e[e][1]];
|
||||||
glVertex3fv(p[s->e[e][1]]);
|
glVertex3f(a[0], a[1], a[2]);
|
||||||
|
glVertex3f(b[0], b[1], b[2]);
|
||||||
}
|
}
|
||||||
glEnd();
|
glEnd();
|
||||||
|
if (g_have_vbo) glEnableClientState(GL_VERTEX_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Build the 6 world-space view-frustum planes from the current modelview *
|
/* Build the 6 world-space view-frustum planes from the current modelview *
|
||||||
|
|
@ -3607,7 +3704,9 @@ static void ensure_scene_tex(int w, int h) {
|
||||||
static void grab_scene(int fbw, int fbh) {
|
static void grab_scene(int fbw, int fbh) {
|
||||||
ensure_scene_tex(fbw, fbh);
|
ensure_scene_tex(fbw, fbh);
|
||||||
glBindTexture(GL_TEXTURE_2D, g_scene_tex);
|
glBindTexture(GL_TEXTURE_2D, g_scene_tex);
|
||||||
glReadBuffer(GL_BACK);
|
#ifndef __EMSCRIPTEN__
|
||||||
|
glReadBuffer(GL_BACK); /* WebGL1 has no glReadBuffer (back is default) */
|
||||||
|
#endif
|
||||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, 0,0, fbw, fbh);
|
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, 0,0, fbw, fbh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3625,7 +3724,7 @@ static int ensure_bloom_targets(int fbw, int fbh) {
|
||||||
h = h > 1 ? h / 2 : 1;
|
h = h > 1 ? h / 2 : 1;
|
||||||
g_bl_w[i] = w; g_bl_h[i] = h;
|
g_bl_w[i] = w; g_bl_h[i] = h;
|
||||||
glBindTexture(GL_TEXTURE_2D, g_bl_tex[i]);
|
glBindTexture(GL_TEXTURE_2D, g_bl_tex[i]);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
|
@ -3940,6 +4039,8 @@ static void draw_mirrorballs(const int *list, int n, int fbw, int fbh) {
|
||||||
glMatrixMode(GL_MODELVIEW); glPopMatrix();
|
glMatrixMode(GL_MODELVIEW); glPopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void render_frame(void); /* one frame; defined after main() */
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
#ifdef SCREENSAVER
|
#ifdef SCREENSAVER
|
||||||
{
|
{
|
||||||
|
|
@ -3965,6 +4066,18 @@ int main(int argc, char **argv) {
|
||||||
srand((unsigned)time(NULL));
|
srand((unsigned)time(NULL));
|
||||||
init_solids();
|
init_solids();
|
||||||
load_settings(); /* user's last-used settings become this run's defaults */
|
load_settings(); /* user's last-used settings become this run's defaults */
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
/* The browser build has no persisted settings, so start from a chosen look.
|
||||||
|
* density 205 * (1520/140) ~= 2226 on-screen bodies. */
|
||||||
|
cfg.speed = 100; cfg.tumble = 65; cfg.tumble_var = 100;
|
||||||
|
cfg.render_dist = 1520; cfg.density = 205;
|
||||||
|
cfg.size_min = 1.4f; cfg.size_max = 50.2f;
|
||||||
|
cfg.hue_cycle = 60; cfg.multicolor = 1;
|
||||||
|
cfg.glow = 75; cfg.flicker = 28;
|
||||||
|
cfg.mag_count = 25; cfg.glass_count = 25; cfg.mirror_count = 25;
|
||||||
|
cfg.cycle_shapes = 0; /* random shapes */
|
||||||
|
g_bloom = 0; /* legacy glow mode */
|
||||||
|
#endif
|
||||||
if (!g_screensaver)
|
if (!g_screensaver)
|
||||||
printf("Vectorgons: %d shape types loaded.\n", num_shapes);
|
printf("Vectorgons: %d shape types loaded.\n", num_shapes);
|
||||||
|
|
||||||
|
|
@ -4004,14 +4117,36 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE); /* additive glow */
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE); /* additive glow */
|
||||||
glEnable(GL_LINE_SMOOTH);
|
#ifndef __EMSCRIPTEN__
|
||||||
|
glEnable(GL_LINE_SMOOTH); /* not available in WebGL */
|
||||||
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
||||||
glEnable(GL_MULTISAMPLE);
|
glEnable(GL_MULTISAMPLE);
|
||||||
|
#endif
|
||||||
|
|
||||||
last_input_time = glfwGetTime();
|
last_input_time = glfwGetTime();
|
||||||
double last = glfwGetTime();
|
g_win = win;
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
/* Resize the canvas to fill the screen (at screen aspect) on fullscreen. */
|
||||||
|
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT,
|
||||||
|
NULL, EM_TRUE, on_fullscreen_change);
|
||||||
|
/* The browser drives the frame loop via requestAnimationFrame; main() returns. */
|
||||||
|
emscripten_set_main_loop(render_frame, 0, 1);
|
||||||
|
#else
|
||||||
|
while (!glfwWindowShouldClose(win) && !(g_screensaver && g_quit)) render_frame();
|
||||||
|
if (!g_screensaver) save_settings(); /* persist settings (not from the saver) */
|
||||||
|
glfwDestroyWindow(win);
|
||||||
|
glfwTerminate();
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(win) && !(g_screensaver && g_quit)) {
|
/* One simulation + render frame. Driven by the native while-loop on desktop, or
|
||||||
|
* by emscripten_set_main_loop() in the browser. */
|
||||||
|
static void render_frame(void) {
|
||||||
|
GLFWwindow *win = g_win;
|
||||||
|
static double last = 0.0;
|
||||||
|
if (last == 0.0) last = glfwGetTime();
|
||||||
|
{
|
||||||
double now = glfwGetTime();
|
double now = glfwGetTime();
|
||||||
float raw = (float)(now - last); /* true frame time (for FPS) */
|
float raw = (float)(now - last); /* true frame time (for FPS) */
|
||||||
float dt = raw > 0.05f ? 0.05f : raw; /* clamped for the simulation */
|
float dt = raw > 0.05f ? 0.05f : raw; /* clamped for the simulation */
|
||||||
|
|
@ -4029,7 +4164,28 @@ int main(int argc, char **argv) {
|
||||||
g_batches = 0; g_verts = 0; g_drawn = 0;
|
g_batches = 0; g_verts = 0; g_drawn = 0;
|
||||||
|
|
||||||
int fbw, fbh;
|
int fbw, fbh;
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
/* Drive the viewport from the canvas's *actual displayed* size rather
|
||||||
|
* than GLFW's cached window size (which doesn't follow CSS/fullscreen
|
||||||
|
* reliably). Size the drawing buffer to match the displayed CSS pixels,
|
||||||
|
* so fullscreen (CSS 100vw x 100vh => the whole screen) renders at the
|
||||||
|
* screen's aspect ratio and fills it edge-to-edge — no black bars, no
|
||||||
|
* stretching. Windowed, the CSS size is 1100x760, unchanged. */
|
||||||
|
{
|
||||||
|
double cssw = 0, cssh = 0;
|
||||||
|
emscripten_get_element_css_size("#canvas", &cssw, &cssh);
|
||||||
|
fbw = (int)(cssw + 0.5);
|
||||||
|
fbh = (int)(cssh + 0.5);
|
||||||
|
if (fbw < 1) fbw = 1;
|
||||||
|
if (fbh < 1) fbh = 1;
|
||||||
|
int bw = 0, bh = 0;
|
||||||
|
emscripten_get_canvas_element_size("#canvas", &bw, &bh);
|
||||||
|
if (bw != fbw || bh != fbh)
|
||||||
|
emscripten_set_canvas_element_size("#canvas", fbw, fbh);
|
||||||
|
}
|
||||||
|
#else
|
||||||
glfwGetFramebufferSize(win, &fbw, &fbh);
|
glfwGetFramebufferSize(win, &fbw, &fbh);
|
||||||
|
#endif
|
||||||
if (fbh < 1) fbh = 1;
|
if (fbh < 1) fbh = 1;
|
||||||
glViewport(0, 0, fbw, fbh);
|
glViewport(0, 0, fbw, fbh);
|
||||||
|
|
||||||
|
|
@ -4065,7 +4221,11 @@ int main(int argc, char **argv) {
|
||||||
glTranslatef(-cam_x, -cam_y, 0.0f); /* apply camera pan (WASD) */
|
glTranslatef(-cam_x, -cam_y, 0.0f); /* apply camera pan (WASD) */
|
||||||
build_frustum(); /* for CPU culling of off-screen bodies */
|
build_frustum(); /* for CPU culling of off-screen bodies */
|
||||||
|
|
||||||
glClearColor(0.006f, 0.010f, 0.035f, 1.0f);
|
#ifdef __EMSCRIPTEN__
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); /* pure black in the browser */
|
||||||
|
#else
|
||||||
|
glClearColor(0.006f, 0.010f, 0.035f, 1.0f); /* faint blue-black ambiance */
|
||||||
|
#endif
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
/* continuous hue cycling (advances even while paused) */
|
/* continuous hue cycling (advances even while paused) */
|
||||||
|
|
@ -4259,9 +4419,4 @@ int main(int argc, char **argv) {
|
||||||
glfwSwapBuffers(win);
|
glfwSwapBuffers(win);
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_screensaver) save_settings(); /* persist settings (not from the saver) */
|
|
||||||
glfwDestroyWindow(win);
|
|
||||||
glfwTerminate();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
vectorgons.exe
BIN
vectorgons.exe
Binary file not shown.
BIN
vectorgons.scr
BIN
vectorgons.scr
Binary file not shown.
37
web/index.html
Normal file
37
web/index.html
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Vectorgons</title>
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; height: 100%; background: #000; color: #8af;
|
||||||
|
font-family: system-ui, sans-serif; }
|
||||||
|
#wrap { display: flex; flex-direction: column; align-items: center; gap: 8px;
|
||||||
|
padding: 12px; box-sizing: border-box; }
|
||||||
|
#canvas { background: #000; border: 1px solid #234; outline: none;
|
||||||
|
width: 1100px; height: 760px; max-width: 100%; }
|
||||||
|
/* In fullscreen, fill the whole screen. The program resizes the canvas
|
||||||
|
backing buffer to the screen resolution (see on_fullscreen_change), so it
|
||||||
|
renders at the screen's aspect ratio and fills it edge-to-edge with no
|
||||||
|
black bars and no distortion. */
|
||||||
|
#canvas:fullscreen,
|
||||||
|
#canvas:-webkit-full-screen {
|
||||||
|
width: 100vw; height: 100vh; max-width: none; border: 0; background: #000;
|
||||||
|
}
|
||||||
|
#hint { font-size: 13px; opacity: .6; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrap">
|
||||||
|
<!-- The canvas must have id="canvas" and be focusable (tabindex) for keys. -->
|
||||||
|
<canvas id="canvas" width="1100" height="760" tabindex="0"></canvas>
|
||||||
|
<div id="hint">Click the canvas, then use the on-screen controls (WASD / arrows / etc.). F = fullscreen.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- External scripts only, so this page works under a strict CSP (script-src
|
||||||
|
'self'). vectorgons-boot.js must load before vectorgons.js. -->
|
||||||
|
<script src="vectorgons-boot.js"></script>
|
||||||
|
<script src="vectorgons.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
web/vectorgons-boot.js
Normal file
16
web/vectorgons-boot.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Emscripten Module config, in an external file so it loads under a strict CSP
|
||||||
|
// (script-src 'self') without needing 'unsafe-inline'. Must load BEFORE
|
||||||
|
// vectorgons.js. The canvas (id="canvas") must already exist in the DOM.
|
||||||
|
var Module = {
|
||||||
|
canvas: (function () {
|
||||||
|
var c = document.getElementById('canvas');
|
||||||
|
c.addEventListener('click', function () { c.focus(); });
|
||||||
|
// Don't let arrow keys / space scroll the page while playing.
|
||||||
|
c.addEventListener('keydown', function (e) {
|
||||||
|
if ([32, 37, 38, 39, 40].indexOf(e.keyCode) >= 0) e.preventDefault();
|
||||||
|
});
|
||||||
|
return c;
|
||||||
|
})(),
|
||||||
|
print: function (t) { console.log(t); },
|
||||||
|
printErr: function (t) { console.error(t); }
|
||||||
|
};
|
||||||
1
web/vectorgons.js
Normal file
1
web/vectorgons.js
Normal file
File diff suppressed because one or more lines are too long
BIN
web/vectorgons.wasm
Executable file
BIN
web/vectorgons.wasm
Executable file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue