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
183
vectorgons.c
183
vectorgons.c
|
|
@ -67,8 +67,18 @@
|
|||
#define NOMINMAX
|
||||
#include <windows.h> /* provides APIENTRY / CALLBACK that the GL headers need */
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Browser build: WebGL via Emscripten's legacy-GL emulation. GLU is not
|
||||
* provided, so the three GLU helpers used here are shimmed below. */
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GL/gl.h>
|
||||
#else
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GL/glu.h>
|
||||
#endif
|
||||
|
||||
/* Legacy GL constants that the Windows (MinGW) OpenGL 1.1 headers may omit.
|
||||
* The values are standard; the GPU driver provides the functionality at run
|
||||
|
|
@ -88,6 +98,50 @@
|
|||
#include <time.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) --------------
|
||||
* The hot wireframe geometry is uploaded to VBOs and drawn with glDrawElements
|
||||
* 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 int g_vsync = 1; /* F2 toggles glfwSwapInterval(0/1) */
|
||||
static int g_show_hud = 1; /* F1 toggles the perf HUD */
|
||||
static GLFWwindow *g_win = NULL; /* the window/canvas, shared with the frame fn */
|
||||
|
||||
/* --- retained-mode geometry buffers (one per shape) --------------------------
|
||||
* g_shape_vbomode[si]: 0 = immediate mode (clocks/dynamics whose topology is
|
||||
|
|
@ -3207,6 +3262,9 @@ static void settings_path(char *buf, size_t n) {
|
|||
}
|
||||
|
||||
static void save_settings(void) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return; /* no persistent filesystem in the browser */
|
||||
#else
|
||||
char path[1024];
|
||||
settings_path(path, sizeof path);
|
||||
FILE *f = fopen(path, "w");
|
||||
|
|
@ -3230,9 +3288,13 @@ static void save_settings(void) {
|
|||
fprintf(f, "mirror_count=%d\n", cfg.mirror_count);
|
||||
fprintf(f, "fullscreen=%d\n", cfg.fullscreen);
|
||||
fclose(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void load_settings(void) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return; /* no persistent filesystem in the browser */
|
||||
#else
|
||||
char path[1024];
|
||||
settings_path(path, sizeof path);
|
||||
FILE *f = fopen(path, "r");
|
||||
|
|
@ -3282,11 +3344,33 @@ static void load_settings(void) {
|
|||
cfg.multicolor = cfg.multicolor ? 1 : 0;
|
||||
cfg.cycle_shapes = cfg.cycle_shapes ? 1 : 0;
|
||||
cfg.fullscreen = cfg.fullscreen ? 1 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Keep cfg.fullscreen in sync with the browser for *any* fullscreen change
|
||||
* (e.g. the user pressing Esc, which doesn't go through toggle_fullscreen). The
|
||||
* actual canvas/viewport sizing is handled per-frame in render_frame(), which
|
||||
* tracks the canvas's real displayed size. */
|
||||
static EM_BOOL on_fullscreen_change(int type,
|
||||
const EmscriptenFullscreenChangeEvent *e,
|
||||
void *user) {
|
||||
(void)type; (void)user;
|
||||
cfg.fullscreen = e->isFullscreen ? 1 : 0;
|
||||
return EM_FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void toggle_fullscreen(GLFWwindow *win) {
|
||||
static int wx = 100, wy = 100, ww = 1100, wh = 760;
|
||||
cfg.fullscreen = !cfg.fullscreen;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
(void)win; /* browser: request canvas fullscreen */
|
||||
/* Just enter/exit fullscreen here; on_fullscreen_change() does the canvas
|
||||
* resize so the render fills the screen at the correct (screen) aspect. */
|
||||
if (cfg.fullscreen) emscripten_request_fullscreen("#canvas", 1);
|
||||
else emscripten_exit_fullscreen();
|
||||
#else
|
||||
static int wx = 100, wy = 100, ww = 1100, wh = 760;
|
||||
if (cfg.fullscreen) {
|
||||
glfwGetWindowPos(win, &wx, &wy);
|
||||
glfwGetWindowSize(win, &ww, &wh);
|
||||
|
|
@ -3296,6 +3380,7 @@ static void toggle_fullscreen(GLFWwindow *win) {
|
|||
} else {
|
||||
glfwSetWindowMonitor(win, NULL, wx, wy, ww, wh, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
/* Immediate-mode path (clocks / dynamics, whose topology is rebuilt each
|
||||
* frame). Detach the vertex buffer / client array first: WebGL's legacy-GL
|
||||
* immediate-mode emulation otherwise folds the stale array state into the
|
||||
* glVertex stream and draws garbage long lines. (No-op on desktop, where
|
||||
* immediate mode already ignores client arrays.) glVertex3f is also better
|
||||
* supported by the emulation than the 3fv vector form. */
|
||||
if (g_have_vbo) {
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
vgBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
glBegin(GL_LINES);
|
||||
for (int e = 0; e < s->ne; e++) {
|
||||
glVertex3fv(p[s->e[e][0]]);
|
||||
glVertex3fv(p[s->e[e][1]]);
|
||||
const float *a = p[s->e[e][0]], *b = p[s->e[e][1]];
|
||||
glVertex3f(a[0], a[1], a[2]);
|
||||
glVertex3f(b[0], b[1], b[2]);
|
||||
}
|
||||
glEnd();
|
||||
if (g_have_vbo) glEnableClientState(GL_VERTEX_ARRAY);
|
||||
}
|
||||
|
||||
/* Build the 6 world-space view-frustum planes from the current modelview *
|
||||
|
|
@ -3607,7 +3704,9 @@ static void ensure_scene_tex(int w, int h) {
|
|||
static void grab_scene(int fbw, int fbh) {
|
||||
ensure_scene_tex(fbw, fbh);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -3625,7 +3724,7 @@ static int ensure_bloom_targets(int fbw, int fbh) {
|
|||
h = h > 1 ? h / 2 : 1;
|
||||
g_bl_w[i] = w; g_bl_h[i] = h;
|
||||
glBindTexture(GL_TEXTURE_2D, g_bl_tex[i]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
|
|
@ -3940,6 +4039,8 @@ static void draw_mirrorballs(const int *list, int n, int fbw, int fbh) {
|
|||
glMatrixMode(GL_MODELVIEW); glPopMatrix();
|
||||
}
|
||||
|
||||
static void render_frame(void); /* one frame; defined after main() */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#ifdef SCREENSAVER
|
||||
{
|
||||
|
|
@ -3965,6 +4066,18 @@ int main(int argc, char **argv) {
|
|||
srand((unsigned)time(NULL));
|
||||
init_solids();
|
||||
load_settings(); /* user's last-used settings become this run's defaults */
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* The browser build has no persisted settings, so start from a chosen look.
|
||||
* density 205 * (1520/140) ~= 2226 on-screen bodies. */
|
||||
cfg.speed = 100; cfg.tumble = 65; cfg.tumble_var = 100;
|
||||
cfg.render_dist = 1520; cfg.density = 205;
|
||||
cfg.size_min = 1.4f; cfg.size_max = 50.2f;
|
||||
cfg.hue_cycle = 60; cfg.multicolor = 1;
|
||||
cfg.glow = 75; cfg.flicker = 28;
|
||||
cfg.mag_count = 25; cfg.glass_count = 25; cfg.mirror_count = 25;
|
||||
cfg.cycle_shapes = 0; /* random shapes */
|
||||
g_bloom = 0; /* legacy glow mode */
|
||||
#endif
|
||||
if (!g_screensaver)
|
||||
printf("Vectorgons: %d shape types loaded.\n", num_shapes);
|
||||
|
||||
|
|
@ -4004,14 +4117,36 @@ int main(int argc, char **argv) {
|
|||
|
||||
glEnable(GL_BLEND);
|
||||
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);
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
#endif
|
||||
|
||||
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();
|
||||
float raw = (float)(now - last); /* true frame time (for FPS) */
|
||||
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;
|
||||
|
||||
int fbw, fbh;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Drive the viewport from the canvas's *actual displayed* size rather
|
||||
* than GLFW's cached window size (which doesn't follow CSS/fullscreen
|
||||
* reliably). Size the drawing buffer to match the displayed CSS pixels,
|
||||
* so fullscreen (CSS 100vw x 100vh => the whole screen) renders at the
|
||||
* screen's aspect ratio and fills it edge-to-edge — no black bars, no
|
||||
* stretching. Windowed, the CSS size is 1100x760, unchanged. */
|
||||
{
|
||||
double cssw = 0, cssh = 0;
|
||||
emscripten_get_element_css_size("#canvas", &cssw, &cssh);
|
||||
fbw = (int)(cssw + 0.5);
|
||||
fbh = (int)(cssh + 0.5);
|
||||
if (fbw < 1) fbw = 1;
|
||||
if (fbh < 1) fbh = 1;
|
||||
int bw = 0, bh = 0;
|
||||
emscripten_get_canvas_element_size("#canvas", &bw, &bh);
|
||||
if (bw != fbw || bh != fbh)
|
||||
emscripten_set_canvas_element_size("#canvas", fbw, fbh);
|
||||
}
|
||||
#else
|
||||
glfwGetFramebufferSize(win, &fbw, &fbh);
|
||||
#endif
|
||||
if (fbh < 1) fbh = 1;
|
||||
glViewport(0, 0, fbw, fbh);
|
||||
|
||||
|
|
@ -4065,7 +4221,11 @@ int main(int argc, char **argv) {
|
|||
glTranslatef(-cam_x, -cam_y, 0.0f); /* apply camera pan (WASD) */
|
||||
build_frustum(); /* for CPU culling of off-screen bodies */
|
||||
|
||||
glClearColor(0.006f, 0.010f, 0.035f, 1.0f);
|
||||
#ifdef __EMSCRIPTEN__
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); /* pure black in the browser */
|
||||
#else
|
||||
glClearColor(0.006f, 0.010f, 0.035f, 1.0f); /* faint blue-black ambiance */
|
||||
#endif
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
/* continuous hue cycling (advances even while paused) */
|
||||
|
|
@ -4259,9 +4419,4 @@ int main(int argc, char **argv) {
|
|||
glfwSwapBuffers(win);
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
if (!g_screensaver) save_settings(); /* persist settings (not from the saver) */
|
||||
glfwDestroyWindow(win);
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue