Initial public commit.
This commit is contained in:
parent
5defe65535
commit
ad48ebabe5
3 changed files with 524 additions and 32 deletions
35
README.md
35
README.md
|
|
@ -61,11 +61,14 @@ Example:
|
||||||
| keys | does |
|
| keys | does |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `W` / `S` | throttle: accelerate / decelerate forward speed (holds when released; `S` past zero goes into reverse) |
|
| `W` / `S` | throttle: accelerate / decelerate forward speed (holds when released; `S` past zero goes into reverse) |
|
||||||
|
| `X` | full stop (zero the camera's velocity) |
|
||||||
| `A` / `D` | strafe left / right |
|
| `A` / `D` | strafe left / right |
|
||||||
| `Page Up` / `Page Down` | raise / lower camera altitude |
|
| `Page Up` / `Page Down` | raise / lower camera altitude |
|
||||||
| `←` / `→` | pan view left / right |
|
| `←` / `→` | pan view left / right |
|
||||||
| `↑` / `↓` | pan view up / down |
|
| `↑` / `↓` | pan view up / down |
|
||||||
| `[` / `]` | decrease / increase rendering distance |
|
| `[` / `]` | decrease / increase rendering distance |
|
||||||
|
| `G` | toggle the floor-altitude indicator |
|
||||||
|
| `Space` | drop a bomb |
|
||||||
|
|
||||||
Velocity is held in world space, so panning the view with the arrow keys
|
Velocity is held in world space, so panning the view with the arrow keys
|
||||||
only changes where you look — it does not change the direction you are
|
only changes where you look — it does not change the direction you are
|
||||||
|
|
@ -97,13 +100,43 @@ on-screen heads-up display (drawn in the same vector style) lists every
|
||||||
setting next to the key(s) that change it. The HUD fades out after 10
|
setting next to the key(s) that change it. The HUD fades out after 10
|
||||||
seconds with no keypresses and snaps back as soon as you press a key.
|
seconds with no keypresses and snaps back as soon as you press a key.
|
||||||
|
|
||||||
|
All settings (and the rendering distance) are saved to `~/.vectordesert.cfg`
|
||||||
|
on exit and reloaded at startup, so the program resumes with the values from
|
||||||
|
your last session. Command-line options still override the saved values for
|
||||||
|
that run.
|
||||||
|
|
||||||
|
## Bombs
|
||||||
|
|
||||||
|
Press `Space` to drop a bomb. It falls from the sky onto a random spot in
|
||||||
|
your field of view, between half the rendering distance and the full
|
||||||
|
rendering distance away. When it strikes the ground it detonates into an
|
||||||
|
animated, all-vector mushroom cloud twenty times the height of the tallest
|
||||||
|
mountains, in an extremely bright random hue (additively blended so it
|
||||||
|
glows):
|
||||||
|
|
||||||
|
- an initial blinding fireball,
|
||||||
|
- a rising stem with climbing vortex rings,
|
||||||
|
- a billowing cap that stays joined to the stem — a rolling vortex torus
|
||||||
|
whose outer edge curls up and over — and keeps animating as the whole
|
||||||
|
cloud fades away.
|
||||||
|
|
||||||
|
The blast **obliterates every mountain and cactus within the cloud's total
|
||||||
|
radius** and **gouges a deep crater** — a steep-walled bowl with a raised
|
||||||
|
rim thrown up above the surrounding surface — at the impact point. Every
|
||||||
|
bomb leaves its own permanent crater, and craters never erase one another:
|
||||||
|
repeated blasts in the same place **accumulate**, deepening and reshaping
|
||||||
|
the crater. Drop as many as you like; the clouds animate and dissipate
|
||||||
|
independently.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- **Arms scale with size:** the number of arms is derived from a cactus's
|
- **Arms scale with size:** the number of arms is derived from a cactus's
|
||||||
normalized size, so the smallest cacti always have the fewest arms (down
|
normalized size, so the smallest cacti always have the fewest arms (down
|
||||||
to a bare trunk) and the largest always reach `max-arms`.
|
to a bare trunk) and the largest always reach `max-arms`.
|
||||||
- The world is generated procedurally from a hash-based value-noise height
|
- The world is generated procedurally from a hash-based value-noise height
|
||||||
field, so it is effectively infinite — the camera flies forever.
|
field, so it is effectively infinite — the camera flies forever. As well
|
||||||
|
as mountain ranges rising above the plain, the terrain carves valleys and
|
||||||
|
basins below it (and bomb craters), so it has real depth, not just relief.
|
||||||
- Distance fog fades distant geometry into the dusk sky for depth.
|
- Distance fog fades distant geometry into the dusk sky for depth.
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
|
||||||
521
main.c
521
main.c
|
|
@ -125,54 +125,107 @@ static Settings S = {
|
||||||
|
|
||||||
#define ARM_LIMIT 12
|
#define ARM_LIMIT 12
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* blast craters: bombs flatten the terrain (and clear cacti) inside a */
|
||||||
|
/* radius. Stored here so terrainHeight() can carve them out. */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
typedef struct { float x, z, r, bowlR, depth; } Crater;
|
||||||
|
static Crater *g_craters = NULL; /* append-only: every bomb keeps a crater */
|
||||||
|
static int g_nCraters = 0;
|
||||||
|
static int g_craterCap = 0;
|
||||||
|
|
||||||
|
static void addCrater(float x, float z, float size){
|
||||||
|
if(g_nCraters == g_craterCap){
|
||||||
|
g_craterCap = g_craterCap ? g_craterCap*2 : 32;
|
||||||
|
g_craters = (Crater*)realloc(g_craters, (size_t)g_craterCap*sizeof(Crater));
|
||||||
|
}
|
||||||
|
Crater *c = &g_craters[g_nCraters++];
|
||||||
|
c->x = x; c->z = z;
|
||||||
|
c->r = 0.5f * size; /* blast / clear radius */
|
||||||
|
c->bowlR = 0.16f * size; /* crater radius */
|
||||||
|
c->depth = clampf(size*0.12f, 20.0f, 130.0f); /* deep, well-defined */
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool inCrater(float x, float z){
|
||||||
|
for(int i=0;i<g_nCraters;i++){
|
||||||
|
float dx=x-g_craters[i].x, dz=z-g_craters[i].z;
|
||||||
|
if(dx*dx + dz*dz < g_craters[i].r*g_craters[i].r) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* terrain */
|
/* terrain */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
static float terrainHeight(float x, float z){
|
|
||||||
/* The default state of the world is a near-flat desert plain with only
|
|
||||||
* very gentle undulation. */
|
|
||||||
float plains = (fbm(x*0.02f, z*0.02f, 3) - 0.5f) * 2.0f;
|
|
||||||
|
|
||||||
/* Mountain ranges are coherent low-frequency blobs at a *fixed* spatial
|
/* The shape of a range/basin in 0..1: a ridged multifractal that roughness
|
||||||
* scale (so a range stays a sensibly sized range). The user's
|
* morphs from a flat-topped mesa (0) to jagged crests (1). Used for both the
|
||||||
* mountainFreq controls how much of the plain those ranges cover, i.e.
|
* mountains (added) and the valleys (subtracted). */
|
||||||
* how frequently the plain is interrupted: higher freq -> lower
|
static float rangeRidge(float x, float z){
|
||||||
* threshold -> ranges appear more often. */
|
|
||||||
float mask = valnoise(x*0.006f, z*0.006f);
|
|
||||||
float thr = clampf(0.80f - 0.11f*S.mountainFreq, 0.35f, 0.86f);
|
|
||||||
float mountainMask = smoothstepf(thr, thr+0.10f, mask);
|
|
||||||
if(mountainMask <= 0.0f) return plains;
|
|
||||||
|
|
||||||
/* Base frequency scaled by range height keeps slopes proportional for
|
|
||||||
* short and tall ranges alike (no-op at the default max height). */
|
|
||||||
float bf = 0.05f * (26.0f / fmaxf(S.mountainMaxH, 1.0f));
|
float bf = 0.05f * (26.0f / fmaxf(S.mountainMaxH, 1.0f));
|
||||||
|
|
||||||
/* Accumulate a ridged multifractal. The first octave is the broad mass;
|
|
||||||
* the remaining octaves add higher-frequency jagged detail riding on the
|
|
||||||
* existing crests. */
|
|
||||||
float freq=bf, amp=0.5f, sum=0.0f, norm=0.0f, weight=1.0f, mass=0.0f;
|
float freq=bf, amp=0.5f, sum=0.0f, norm=0.0f, weight=1.0f, mass=0.0f;
|
||||||
for(int i=0;i<6;i++){
|
for(int i=0;i<6;i++){
|
||||||
float n = 1.0f - fabsf(2.0f*valnoise(x*freq, z*freq) - 1.0f);
|
float n = 1.0f - fabsf(2.0f*valnoise(x*freq, z*freq) - 1.0f);
|
||||||
n *= n;
|
n *= n;
|
||||||
if(i==0) mass = n; /* broad base shape */
|
if(i==0) mass = n;
|
||||||
n *= weight; /* detail only rides on existing ridge*/
|
n *= weight;
|
||||||
weight = clampf(n*2.5f, 0.0f, 1.0f);
|
weight = clampf(n*2.5f, 0.0f, 1.0f);
|
||||||
sum += n*amp;
|
sum += n*amp;
|
||||||
norm += amp;
|
norm += amp;
|
||||||
freq *= 2.0f; amp *= 0.5f;
|
freq *= 2.0f; amp *= 0.5f;
|
||||||
}
|
}
|
||||||
float jagged = clampf(sum / norm, 0.0f, 1.0f);
|
float jagged = clampf(sum / norm, 0.0f, 1.0f);
|
||||||
|
|
||||||
/* A mesa: saturate the broad mass so its top clamps to a flat plateau
|
|
||||||
* with steeper sides. */
|
|
||||||
float mesa = clampf((mass - 0.25f) * 3.2f, 0.0f, 1.0f);
|
float mesa = clampf((mass - 0.25f) * 3.2f, 0.0f, 1.0f);
|
||||||
|
return lerpf(mesa, jagged, S.mountainRough);
|
||||||
|
}
|
||||||
|
|
||||||
/* mountainRough morphs flat-topped mesas (0) into jagged peaks (1) */
|
static float terrainHeight(float x, float z){
|
||||||
float ridge = lerpf(mesa, jagged, S.mountainRough);
|
/* The default state of the world is a near-flat desert plain with only
|
||||||
|
* very gentle undulation. */
|
||||||
|
float plains = (fbm(x*0.02f, z*0.02f, 3) - 0.5f) * 2.0f;
|
||||||
|
float h = plains;
|
||||||
|
|
||||||
/* ridge (0..1) maps the range between the user's min and max heights */
|
/* Coherent low-frequency blobs decide where ranges/basins occur; the
|
||||||
float body = lerpf(S.mountainMinH, S.mountainMaxH, ridge);
|
* user's mountainFreq sets how much of the plain they cover. */
|
||||||
return plains + mountainMask * body;
|
float thr = clampf(0.80f - 0.11f*S.mountainFreq, 0.35f, 0.86f);
|
||||||
|
|
||||||
|
/* mountain ranges rise above the plain */
|
||||||
|
float mMask = smoothstepf(thr, thr+0.10f, valnoise(x*0.006f, z*0.006f));
|
||||||
|
if(mMask > 0.0f)
|
||||||
|
h += mMask * lerpf(S.mountainMinH, S.mountainMaxH, rangeRidge(x, z));
|
||||||
|
|
||||||
|
/* valleys / basins carve below the plain (a differently-phased mask so
|
||||||
|
* they fall in different places than the mountains). Roughness shapes
|
||||||
|
* them too: flat-bottomed basins -> rugged canyons. */
|
||||||
|
float vMask = smoothstepf(thr, thr+0.10f, valnoise(x*0.006f+19.3f, z*0.006f-23.1f));
|
||||||
|
if(vMask > 0.0f)
|
||||||
|
h -= vMask * lerpf(S.mountainMinH, S.mountainMaxH, rangeRidge(x+57.0f, z-91.0f)) * 0.85f;
|
||||||
|
|
||||||
|
/* blast craters. Two separable effects so that overlapping blasts
|
||||||
|
* accumulate rather than wipe each other out:
|
||||||
|
* - flatten: erase the procedural mountains/valleys back to the plain
|
||||||
|
* across the blast radius (combined as a max, applied once);
|
||||||
|
* - carve: a deep bowl + raised rim, summed over every crater so that
|
||||||
|
* repeated blasts in one place deepen and reshape the crater. */
|
||||||
|
float flat = 0.0f, carve = 0.0f;
|
||||||
|
for(int i=0;i<g_nCraters;i++){
|
||||||
|
Crater *c = &g_craters[i];
|
||||||
|
float dx=x-c->x, dz=z-c->z;
|
||||||
|
float d2 = dx*dx + dz*dz;
|
||||||
|
if(d2 >= c->r*c->r) continue;
|
||||||
|
float d = sqrtf(d2);
|
||||||
|
float f = smoothstepf(c->r, c->r*0.65f, d); /* 1 inside, 0 at rim */
|
||||||
|
if(f > flat) flat = f;
|
||||||
|
/* bowl: flat floor inside, steep wall up to the lip */
|
||||||
|
float wall = smoothstepf(c->bowlR, c->bowlR*0.55f, d);
|
||||||
|
carve -= c->depth * wall;
|
||||||
|
/* raised rim: a bump straddling the lip, rising above the surface */
|
||||||
|
float rim = (d - c->bowlR)/(c->bowlR*0.25f);
|
||||||
|
carve += c->depth*0.35f*expf(-rim*rim);
|
||||||
|
}
|
||||||
|
if(flat > 0.0f) h = lerpf(h, plains, flat);
|
||||||
|
h += carve;
|
||||||
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -223,6 +276,7 @@ static void lookAtGL(V3 eye, V3 center, V3 up){
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
static V3 camPos;
|
static V3 camPos;
|
||||||
static float g_viewRadius = 128.0f; /* rendering distance (runtime) */
|
static float g_viewRadius = 128.0f; /* rendering distance (runtime) */
|
||||||
|
static bool g_showAlt = false; /* altitude indicator toggle */
|
||||||
#define FOG_NEAR 18.0f
|
#define FOG_NEAR 18.0f
|
||||||
|
|
||||||
/* fog reaches full opacity a little inside the draw radius so the mesh
|
/* fog reaches full opacity a little inside the draw radius so the mesh
|
||||||
|
|
@ -232,6 +286,57 @@ static float fogFarDist(void){
|
||||||
return f < FOG_NEAR + 5.0f ? FOG_NEAR + 5.0f : f;
|
return f < FOG_NEAR + 5.0f ? FOG_NEAR + 5.0f : f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* settings persistence: every user setting is saved on exit and */
|
||||||
|
/* reloaded at startup, so the program resumes with the last values. */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
static const char *configPath(void){
|
||||||
|
static char path[512];
|
||||||
|
const char *home = getenv("HOME");
|
||||||
|
if(home && *home) snprintf(path, sizeof path, "%s/.vectordesert.cfg", home);
|
||||||
|
else snprintf(path, sizeof path, "vectordesert.cfg");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void saveSettings(void){
|
||||||
|
FILE *f = fopen(configPath(), "w");
|
||||||
|
if(!f) return;
|
||||||
|
fprintf(f, "terrainHue %.5f\n", S.terrainHue);
|
||||||
|
fprintf(f, "mountainFreq %.5f\n", S.mountainFreq);
|
||||||
|
fprintf(f, "mountainRough %.5f\n", S.mountainRough);
|
||||||
|
fprintf(f, "mountainMinH %.5f\n", S.mountainMinH);
|
||||||
|
fprintf(f, "mountainMaxH %.5f\n", S.mountainMaxH);
|
||||||
|
fprintf(f, "cactusFreq %.5f\n", S.cactusFreq);
|
||||||
|
fprintf(f, "cactusSizeVar %.5f\n", S.cactusSizeVar);
|
||||||
|
fprintf(f, "cactusMinSize %.5f\n", S.cactusMinSize);
|
||||||
|
fprintf(f, "cactusMaxSize %.5f\n", S.cactusMaxSize);
|
||||||
|
fprintf(f, "cactusHue %.5f\n", S.cactusHue);
|
||||||
|
fprintf(f, "maxArms %d\n", S.maxArms);
|
||||||
|
fprintf(f, "viewRadius %.3f\n", g_viewRadius);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadSettings(void){
|
||||||
|
FILE *f = fopen(configPath(), "r");
|
||||||
|
if(!f) return;
|
||||||
|
char key[64]; float v;
|
||||||
|
while(fscanf(f, "%63s %f", key, &v) == 2){
|
||||||
|
if (!strcmp(key,"terrainHue")) S.terrainHue = v;
|
||||||
|
else if (!strcmp(key,"mountainFreq")) S.mountainFreq = v;
|
||||||
|
else if (!strcmp(key,"mountainRough")) S.mountainRough = v;
|
||||||
|
else if (!strcmp(key,"mountainMinH")) S.mountainMinH = v;
|
||||||
|
else if (!strcmp(key,"mountainMaxH")) S.mountainMaxH = v;
|
||||||
|
else if (!strcmp(key,"cactusFreq")) S.cactusFreq = v;
|
||||||
|
else if (!strcmp(key,"cactusSizeVar")) S.cactusSizeVar = v;
|
||||||
|
else if (!strcmp(key,"cactusMinSize")) S.cactusMinSize = v;
|
||||||
|
else if (!strcmp(key,"cactusMaxSize")) S.cactusMaxSize = v;
|
||||||
|
else if (!strcmp(key,"cactusHue")) S.cactusHue = v;
|
||||||
|
else if (!strcmp(key,"maxArms")) S.maxArms = (int)v;
|
||||||
|
else if (!strcmp(key,"viewRadius")) g_viewRadius = v;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* line batch: collect every wireframe segment into one vertex+colour */
|
/* line batch: collect every wireframe segment into one vertex+colour */
|
||||||
/* array so a whole frame's geometry ships in a single glDrawArrays. */
|
/* array so a whole frame's geometry ships in a single glDrawArrays. */
|
||||||
|
|
@ -399,12 +504,66 @@ static void drawHUD(int fbw, int fbh, float alpha){
|
||||||
LINE("9/0 CACTUS HUE %.2f", S.cactusHue);
|
LINE("9/0 CACTUS HUE %.2f", S.cactusHue);
|
||||||
LINE("-/= MAX ARMS %d", S.maxArms);
|
LINE("-/= MAX ARMS %d", S.maxArms);
|
||||||
LINE("[/] RENDER DIST %.0f", g_viewRadius);
|
LINE("[/] RENDER DIST %.0f", g_viewRadius);
|
||||||
|
LINE("X FULL STOP");
|
||||||
|
LINE("G FLOOR ALT %s", g_showAlt ? "ON" : "OFF");
|
||||||
#undef LINE
|
#undef LINE
|
||||||
|
|
||||||
/* camera + window controls footer */
|
/* camera + window controls footer */
|
||||||
y += lineH*0.5f;
|
y += lineH*0.5f;
|
||||||
glColor4f(0.45f, 0.70f, 0.85f, alpha);
|
glColor4f(0.45f, 0.70f, 0.85f, alpha);
|
||||||
drawText("WASD MOVE ARROWS PAN PGUP/DN ALT F FULL", x, y, sc*0.85f);
|
drawText("WASD MOVE ARROWS PAN PGUP/DN ALT SPACE BOMB F FULL", x, y, sc*0.85f);
|
||||||
|
|
||||||
|
glEnable(GL_FOG);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glMatrixMode(GL_PROJECTION); glPopMatrix();
|
||||||
|
glMatrixMode(GL_MODELVIEW); glPopMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* on-screen altitude indicator: the terrain floor directly beneath the
|
||||||
|
* camera, the camera's own altitude, and the clearance between them, plus a
|
||||||
|
* vertical clearance gauge. Drawn top-right when toggled on. */
|
||||||
|
static void drawAltIndicator(int fbw, int fbh){
|
||||||
|
float floor = terrainHeight(camPos.x, camPos.z);
|
||||||
|
float camAlt = camPos.y;
|
||||||
|
float clear = camAlt - floor;
|
||||||
|
|
||||||
|
float sc = fbh/240.0f; if(sc < 2.0f) sc = 2.0f;
|
||||||
|
float lineH = 9.0f*sc, pad = 5.0f*sc;
|
||||||
|
float gaugeW = 6.0f*sc, gaugeH = 56.0f*sc;
|
||||||
|
float gx1 = fbw - pad, gx0 = gx1 - gaugeW;
|
||||||
|
float textW = 12.0f*5.0f*sc;
|
||||||
|
float tx = gx0 - 6.0f*sc - textW;
|
||||||
|
float ty = pad;
|
||||||
|
|
||||||
|
glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity();
|
||||||
|
glOrtho(0.0, fbw, fbh, 0.0, -1.0, 1.0);
|
||||||
|
glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity();
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glDisable(GL_FOG);
|
||||||
|
|
||||||
|
char buf[48];
|
||||||
|
glColor4f(0.95f, 0.85f, 0.35f, 1.0f);
|
||||||
|
drawText("ALTITUDE", tx, ty, sc); ty += lineH*1.4f;
|
||||||
|
glColor4f(0.55f, 1.0f, 0.75f, 1.0f);
|
||||||
|
snprintf(buf,sizeof buf,"FLOOR %.1f", floor); drawText(buf,tx,ty,sc); ty+=lineH;
|
||||||
|
snprintf(buf,sizeof buf,"CAM %.1f", camAlt); drawText(buf,tx,ty,sc); ty+=lineH;
|
||||||
|
snprintf(buf,sizeof buf,"CLEAR %.1f", clear); drawText(buf,tx,ty,sc); ty+=lineH;
|
||||||
|
|
||||||
|
/* vertical clearance gauge (0..120) */
|
||||||
|
float gTop = pad, gBot = pad + gaugeH;
|
||||||
|
glColor4f(0.45f, 0.70f, 0.85f, 1.0f);
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
glVertex2f(gx0, gTop); glVertex2f(gx1, gTop);
|
||||||
|
glVertex2f(gx1, gBot); glVertex2f(gx0, gBot);
|
||||||
|
glEnd();
|
||||||
|
float cl = clampf(clear/120.0f, 0.0f, 1.0f);
|
||||||
|
float fillY = gBot - cl*(gBot-gTop);
|
||||||
|
glColor4f(0.55f, 1.0f, 0.75f, 1.0f);
|
||||||
|
glBegin(GL_LINES);
|
||||||
|
for(float yy=gBot-1.5f*sc; yy>fillY; yy-=2.5f*sc){
|
||||||
|
glVertex2f(gx0+1.0f, yy); glVertex2f(gx1-1.0f, yy);
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
|
||||||
glEnable(GL_FOG);
|
glEnable(GL_FOG);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
|
@ -693,6 +852,7 @@ static void buildCacti(float cx, float cz, float margin){
|
||||||
float dx = wx-cx, dz = wz-cz;
|
float dx = wx-cx, dz = wz-cz;
|
||||||
float d2 = dx*dx + dz*dz;
|
float d2 = dx*dx + dz*dz;
|
||||||
if(d2 > reach2) continue;
|
if(d2 > reach2) continue;
|
||||||
|
if(inCrater(wx, wz)) continue; /* obliterated by a blast */
|
||||||
buildCactus(&gCacti, wx, wz, sqrtf(d2));
|
buildCactus(&gCacti, wx, wz, sqrtf(d2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -751,6 +911,294 @@ static float camPitch = -0.18f; /* slightly downward */
|
||||||
static V3 camVel; /* persistent world-space velocity (W/S) */
|
static V3 camVel; /* persistent world-space velocity (W/S) */
|
||||||
static double g_lastInput = 0.0; /* time of last keypress (for HUD fade) */
|
static double g_lastInput = 0.0; /* time of last keypress (for HUD fade) */
|
||||||
|
|
||||||
|
/* ================================================================== */
|
||||||
|
/* bombs + animated vector mushroom clouds */
|
||||||
|
/* ================================================================== */
|
||||||
|
#define MAX_BOMBS 48
|
||||||
|
#define BOMB_GRAV 120.0f /* fall acceleration */
|
||||||
|
#define EXPLO_DUR 12.0f /* explosion lifetime (seconds) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool active;
|
||||||
|
bool exploding; /* false = falling, true = mushroom */
|
||||||
|
float gx, gz; /* ground impact x,z */
|
||||||
|
float y; /* current altitude while falling */
|
||||||
|
float vy; /* fall speed */
|
||||||
|
float groundY; /* terrain height at impact */
|
||||||
|
float t; /* time since detonation */
|
||||||
|
float size; /* cloud size (20 * max mountain height)*/
|
||||||
|
V3 col; /* cloud hue */
|
||||||
|
} Bomb;
|
||||||
|
static Bomb bombs[MAX_BOMBS];
|
||||||
|
|
||||||
|
static uint32_t g_rng = 0x2545F491u;
|
||||||
|
static float frand(void){
|
||||||
|
g_rng = g_rng*1664525u + 1013904223u;
|
||||||
|
return ((g_rng >> 8) & 0xffffff) / (float)0xffffff;
|
||||||
|
}
|
||||||
|
static V3 vlerp(V3 a, V3 b, float t){
|
||||||
|
return v3(lerpf(a.x,b.x,t), lerpf(a.y,b.y,t), lerpf(a.z,b.z,t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a wobbly horizontal ring -- the basic cloud-billow primitive */
|
||||||
|
static void drawWobRing(float cx,float cy,float cz,float r,int nA,
|
||||||
|
float wob,int lobes,float phase,V3 col,float a){
|
||||||
|
if(r <= 0.0f || a <= 0.0f) return;
|
||||||
|
glColor4f(col.x,col.y,col.z,a);
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
for(int k=0;k<nA;k++){
|
||||||
|
float ang = 2.0f*(float)M_PI*k/nA;
|
||||||
|
float rr = r*(1.0f + wob*sinf(lobes*ang + phase));
|
||||||
|
glVertex3f(cx+cosf(ang)*rr, cy, cz+sinf(ang)*rr);
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a wireframe surface of revolution (stem / cap): stacked rings plus
|
||||||
|
* longitudinal strands, with an animated billow wobble */
|
||||||
|
static void drawLathe(float cx,float cz,const float*ys,const float*rs,
|
||||||
|
const V3*cols,int nP,float wob,int lobes,
|
||||||
|
float phase,float alpha){
|
||||||
|
const int nA=48, stride=4; /* 12 longitudinal strands */
|
||||||
|
for(int i=0;i<nP;i++){
|
||||||
|
glColor4f(cols[i].x,cols[i].y,cols[i].z,alpha);
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
for(int k=0;k<nA;k++){
|
||||||
|
float ang = 2.0f*(float)M_PI*k/nA;
|
||||||
|
float rr = rs[i]*(1.0f + wob*sinf(lobes*ang + phase + i*0.7f));
|
||||||
|
glVertex3f(cx+cosf(ang)*rr, ys[i], cz+sinf(ang)*rr);
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
for(int k=0;k<nA;k+=stride){
|
||||||
|
float ang = 2.0f*(float)M_PI*k/nA;
|
||||||
|
glBegin(GL_LINE_STRIP);
|
||||||
|
for(int i=0;i<nP;i++){
|
||||||
|
glColor4f(cols[i].x,cols[i].y,cols[i].z,alpha);
|
||||||
|
float rr = rs[i]*(1.0f + wob*sinf(lobes*ang + phase + i*0.7f));
|
||||||
|
glVertex3f(cx+cosf(ang)*rr, ys[i], cz+sinf(ang)*rr);
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* wireframe sphere for the initial fireball */
|
||||||
|
static void drawSphereWire(float cx,float cy,float cz,float r,
|
||||||
|
V3 col,float alpha,float phase){
|
||||||
|
for(int j=1;j<6;j++){
|
||||||
|
float th = (float)M_PI*j/6;
|
||||||
|
drawWobRing(cx, cy+cosf(th)*r, cz, sinf(th)*r, 32,
|
||||||
|
0.08f, 6, phase+j, col, alpha);
|
||||||
|
}
|
||||||
|
for(int m=0;m<6;m++){
|
||||||
|
float a = (float)M_PI*m/6;
|
||||||
|
glColor4f(col.x,col.y,col.z,alpha);
|
||||||
|
glBegin(GL_LINE_STRIP);
|
||||||
|
for(int j=0;j<=16;j++){
|
||||||
|
float th = (float)M_PI*j/16;
|
||||||
|
float rr = sinf(th)*r;
|
||||||
|
glVertex3f(cx+cosf(a)*rr, cy+cosf(th)*r, cz+sinf(a)*rr);
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a vortex torus -- the rolling cap whose outer edge curls up and over.
|
||||||
|
* Rmaj is the ring radius, rmin the tube radius; the tube cross-section is
|
||||||
|
* drawn as latitude rings plus meridional strands so the roll reads clearly */
|
||||||
|
static void drawTorusCloud(float cx,float cz,float cy,float Rmaj,float rmin,
|
||||||
|
V3 col,V3 hot,float alpha,float roil){
|
||||||
|
if(alpha <= 0.01f) return;
|
||||||
|
const int nPhi=10, nTh=40, nStrand=14;
|
||||||
|
/* latitude rings around the tube */
|
||||||
|
for(int p=0;p<nPhi;p++){
|
||||||
|
float phi = 2.0f*(float)M_PI*p/nPhi;
|
||||||
|
float Rr = Rmaj + rmin*cosf(phi);
|
||||||
|
float yy = cy + rmin*sinf(phi);
|
||||||
|
float ct = 0.5f + 0.5f*cosf(phi); /* 1 outer .. 0 inner */
|
||||||
|
V3 c = vlerp(hot, col, ct);
|
||||||
|
float wob = 0.12f + 0.05f*sinf(roil + phi);
|
||||||
|
drawWobRing(cx, yy, cz, Rr, nTh, wob, 7, roil*1.1f + phi*2.0f, c, alpha);
|
||||||
|
}
|
||||||
|
/* meridional strands showing the rolling tube cross-section */
|
||||||
|
for(int m=0;m<nStrand;m++){
|
||||||
|
float th = 2.0f*(float)M_PI*m/nStrand;
|
||||||
|
glBegin(GL_LINE_STRIP);
|
||||||
|
for(int p=0;p<=nPhi;p++){
|
||||||
|
float phi = 2.0f*(float)M_PI*p/nPhi;
|
||||||
|
float Rr = Rmaj + rmin*cosf(phi);
|
||||||
|
float yy = cy + rmin*sinf(phi);
|
||||||
|
float ct = 0.5f + 0.5f*cosf(phi);
|
||||||
|
V3 c = vlerp(hot, col, ct);
|
||||||
|
glColor4f(c.x,c.y,c.z, alpha);
|
||||||
|
glVertex3f(cx+cosf(th)*Rr, yy, cz+sinf(th)*Rr);
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the full animated mushroom cloud */
|
||||||
|
static void drawMushroom(const Bomb *b){
|
||||||
|
float t=b->t, H=b->size, gY=b->groundY, cx=b->gx, cz=b->gz;
|
||||||
|
float eg = smoothstepf(0.0f, 3.0f, t); /* main growth */
|
||||||
|
float late = clampf((t-3.0f)/6.0f, 0.0f, 1.0f); /* slow billow */
|
||||||
|
float fade = (t < EXPLO_DUR-2.5f) ? 1.0f
|
||||||
|
: clampf((EXPLO_DUR-t)/2.5f,0.0f,1.0f);
|
||||||
|
float roil = t*1.6f; /* roll phase */
|
||||||
|
V3 hue = b->col;
|
||||||
|
V3 hot = vlerp(hue, v3(1.0f,0.95f,0.7f), 0.75f); /* incandescent*/
|
||||||
|
float alpha = fade;
|
||||||
|
|
||||||
|
float top = H*(0.22f + 0.78f*eg);
|
||||||
|
float stemTopY = gY + top*0.50f;
|
||||||
|
float capR = H*0.30f*(0.35f + 0.65f*eg)*(1.0f + 0.4f*late);
|
||||||
|
float stemR = H*0.055f*(0.5f + 0.5f*eg);
|
||||||
|
/* the head always sits on top of the stem -- it never detaches. It just
|
||||||
|
* keeps billowing and rolling (the roil phase advances with time) as the
|
||||||
|
* whole cloud fades away. */
|
||||||
|
float capCenterY = stemTopY + capR*0.45f;
|
||||||
|
float capH = top*0.50f;
|
||||||
|
|
||||||
|
/* initial blinding fireball, fading into the rising column */
|
||||||
|
if(t < 1.0f){
|
||||||
|
float ff = clampf(t/0.6f, 0.0f, 1.0f);
|
||||||
|
float fr = H*0.14f*ff + H*0.02f;
|
||||||
|
float fy = gY + fr*0.9f + (stemTopY-gY)*0.2f*ff;
|
||||||
|
float fa = alpha*clampf(1.0f-(t-0.4f)/0.6f, 0.0f, 1.0f);
|
||||||
|
if(fa > 0.01f) drawSphereWire(cx,fy,cz,fr, hot, fa, roil*2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ground shock + dust skirt */
|
||||||
|
float shockR = H*0.5f*smoothstepf(0.0f,2.0f,t);
|
||||||
|
float shockA = alpha*clampf(1.0f - t/4.0f, 0.0f,1.0f)*0.7f;
|
||||||
|
if(shockA > 0.01f){
|
||||||
|
drawWobRing(cx, gY+H*0.005f, cz, shockR, 48, 0.05f, 8, roil, hue, shockA);
|
||||||
|
drawWobRing(cx, gY+H*0.02f, cz, shockR*0.7f, 44, 0.10f, 6, roil*1.3f, hue, shockA*0.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rising vortex rings climbing the stem */
|
||||||
|
for(int k=0;k<3;k++){
|
||||||
|
float rp = fmodf(t*0.5f + k*0.34f, 1.0f);
|
||||||
|
float ry = gY + rp*(stemTopY-gY);
|
||||||
|
float rr = stemR*(1.0f + rp*2.2f);
|
||||||
|
drawWobRing(cx, ry, cz, rr, 32, 0.14f, 5, roil+k, vlerp(hot,hue,rp), alpha*(1.0f-rp)*0.7f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stem -- always joined to the cap */
|
||||||
|
{
|
||||||
|
const int nP=8; float ys[8], rs[8]; V3 cs[8];
|
||||||
|
for(int i=0;i<nP;i++){
|
||||||
|
float f=(float)i/(nP-1);
|
||||||
|
ys[i]=gY + f*(stemTopY-gY);
|
||||||
|
rs[i]=stemR*(0.8f + 0.35f*sinf(f*(float)M_PI));
|
||||||
|
cs[i]=vlerp(hot,hue, clampf(f*1.2f,0.0f,1.0f));
|
||||||
|
}
|
||||||
|
drawLathe(cx,cz, ys,rs,cs, nP, 0.08f, 5, roil, alpha*0.9f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* domed top of the cap */
|
||||||
|
{
|
||||||
|
const int nP=8; float ys[8], rs[8]; V3 cs[8];
|
||||||
|
for(int i=0;i<nP;i++){
|
||||||
|
float f=(float)i/(nP-1);
|
||||||
|
float dome=sqrtf(clampf(1.0f-f*f,0.0f,1.0f));
|
||||||
|
ys[i]=capCenterY + f*(capH*0.5f);
|
||||||
|
rs[i]=capR*dome;
|
||||||
|
cs[i]=vlerp(hot,hue, clampf(0.3f+f,0.0f,1.0f));
|
||||||
|
}
|
||||||
|
float wob=0.12f + 0.05f*sinf(roil);
|
||||||
|
drawLathe(cx,cz, ys,rs,cs, nP, wob, 7, roil*1.2f, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the billowing cap as a rolling vortex torus whose outer edge curls
|
||||||
|
* over -- its underside meets the top of the stem */
|
||||||
|
drawTorusCloud(cx,cz,capCenterY, capR*0.78f, capR*0.5f, hue, hot, alpha*0.85f, roil);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the falling bomb: a small finned body trailing a bright tracer */
|
||||||
|
static void drawBomb(const Bomb *b){
|
||||||
|
float x=b->gx, y=b->y, z=b->gz, s=1.8f;
|
||||||
|
glColor4f(1.0f,0.75f,0.35f,0.85f);
|
||||||
|
glBegin(GL_LINES);
|
||||||
|
glVertex3f(x,y,z); glVertex3f(x,y+10.0f,z);
|
||||||
|
glEnd();
|
||||||
|
glColor4f(0.9f,0.9f,0.95f,1.0f);
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
glVertex3f(x,y+s*2,z); glVertex3f(x+s,y,z);
|
||||||
|
glVertex3f(x,y-s*2,z); glVertex3f(x-s,y,z);
|
||||||
|
glEnd();
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
glVertex3f(x,y+s*2,z); glVertex3f(x,y,z+s);
|
||||||
|
glVertex3f(x,y-s*2,z); glVertex3f(x,y,z-s);
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* drop a new bomb into a random spot in the field of view, between half
|
||||||
|
* the rendering distance and the full rendering distance */
|
||||||
|
static void spawnBomb(void){
|
||||||
|
int slot=-1;
|
||||||
|
for(int i=0;i<MAX_BOMBS;i++) if(!bombs[i].active){ slot=i; break; }
|
||||||
|
if(slot<0){ /* all busy: reuse the oldest */
|
||||||
|
float best=-1.0f;
|
||||||
|
for(int i=0;i<MAX_BOMBS;i++) if(bombs[i].t>best){ best=bombs[i].t; slot=i; }
|
||||||
|
}
|
||||||
|
Bomb *b=&bombs[slot];
|
||||||
|
float R = g_viewRadius;
|
||||||
|
float a = camYaw + (frand()-0.5f)*1.1f; /* within the FOV */
|
||||||
|
float dist = lerpf(0.5f*R, R, frand()); /* half..full distance*/
|
||||||
|
b->gx = camPos.x + sinf(a)*dist;
|
||||||
|
b->gz = camPos.z - cosf(a)*dist;
|
||||||
|
b->groundY = terrainHeight(b->gx, b->gz);
|
||||||
|
b->size = 20.0f * S.mountainMaxH;
|
||||||
|
b->y = b->groundY + fmaxf(160.0f, b->size*0.55f);
|
||||||
|
b->vy = 0.0f;
|
||||||
|
b->t = 0.0f;
|
||||||
|
b->exploding = false;
|
||||||
|
b->active = true;
|
||||||
|
b->col = hsv2rgb(frand(), 0.85f, 1.0f); /* vivid, extremely bright */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateBombs(float dt){
|
||||||
|
for(int i=0;i<MAX_BOMBS;i++){
|
||||||
|
Bomb *b=&bombs[i];
|
||||||
|
if(!b->active) continue;
|
||||||
|
if(!b->exploding){
|
||||||
|
b->vy += BOMB_GRAV*dt;
|
||||||
|
b->y -= b->vy*dt;
|
||||||
|
if(b->y <= b->groundY){ /* impact -> detonate */
|
||||||
|
b->y = b->groundY;
|
||||||
|
b->exploding = true;
|
||||||
|
b->t = 0.0f;
|
||||||
|
/* obliterate mountains + cacti within the cloud's total
|
||||||
|
* radius, and gouge a real crater bowl at the impact point */
|
||||||
|
addCrater(b->gx, b->gz, b->size);
|
||||||
|
tk.ok = false; ck.ok = false; /* force terrain+cacti rebuild */
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b->t += dt;
|
||||||
|
if(b->t >= EXPLO_DUR) b->active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawBombs(void){
|
||||||
|
bool any=false;
|
||||||
|
for(int i=0;i<MAX_BOMBS;i++) if(bombs[i].active){ any=true; break; }
|
||||||
|
if(!any) return;
|
||||||
|
|
||||||
|
/* additive blending + no fog makes the clouds glow at full, extreme
|
||||||
|
* brightness regardless of distance */
|
||||||
|
glDisable(GL_FOG);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||||
|
for(int i=0;i<MAX_BOMBS;i++){
|
||||||
|
if(!bombs[i].active) continue;
|
||||||
|
if(bombs[i].exploding) drawMushroom(&bombs[i]);
|
||||||
|
else drawBomb(&bombs[i]);
|
||||||
|
}
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glEnable(GL_FOG);
|
||||||
|
}
|
||||||
|
|
||||||
static void toggleFullscreen(GLFWwindow *w){
|
static void toggleFullscreen(GLFWwindow *w){
|
||||||
g_fullscreen = !g_fullscreen;
|
g_fullscreen = !g_fullscreen;
|
||||||
if(g_fullscreen){
|
if(g_fullscreen){
|
||||||
|
|
@ -771,6 +1219,9 @@ static void keyCB(GLFWwindow *w, int key, int sc, int action, int mods){
|
||||||
switch(key){
|
switch(key){
|
||||||
case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(w, GLFW_TRUE); break;
|
case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(w, GLFW_TRUE); break;
|
||||||
case GLFW_KEY_F: if(action==GLFW_PRESS) toggleFullscreen(w); break;
|
case GLFW_KEY_F: if(action==GLFW_PRESS) toggleFullscreen(w); break;
|
||||||
|
case GLFW_KEY_SPACE: if(action==GLFW_PRESS) spawnBomb(); break;
|
||||||
|
case GLFW_KEY_X: if(action==GLFW_PRESS) camVel = v3(0,0,0); break; /* full stop */
|
||||||
|
case GLFW_KEY_G: if(action==GLFW_PRESS) g_showAlt = !g_showAlt; break;
|
||||||
|
|
||||||
case GLFW_KEY_1: S.terrainHue = fmodf(S.terrainHue-0.01f+1.0f,1.0f); printSettings(); break;
|
case GLFW_KEY_1: S.terrainHue = fmodf(S.terrainHue-0.01f+1.0f,1.0f); printSettings(); break;
|
||||||
case GLFW_KEY_2: S.terrainHue = fmodf(S.terrainHue+0.01f,1.0f); printSettings(); break;
|
case GLFW_KEY_2: S.terrainHue = fmodf(S.terrainHue+0.01f,1.0f); printSettings(); break;
|
||||||
|
|
@ -831,6 +1282,7 @@ static void usage(const char *prog){
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv){
|
||||||
bool startFull = false;
|
bool startFull = false;
|
||||||
float fv;
|
float fv;
|
||||||
|
loadSettings(); /* resume last session's settings; CLI args below still override */
|
||||||
for(int i=1;i<argc;i++){
|
for(int i=1;i<argc;i++){
|
||||||
if(strcmp(argv[i],"--fullscreen")==0) startFull = true;
|
if(strcmp(argv[i],"--fullscreen")==0) startFull = true;
|
||||||
else if(strcmp(argv[i],"--help")==0 || strcmp(argv[i],"-h")==0){ usage(argv[0]); return 0; }
|
else if(strcmp(argv[i],"--help")==0 || strcmp(argv[i],"-h")==0){ usage(argv[0]); return 0; }
|
||||||
|
|
@ -874,6 +1326,7 @@ int main(int argc, char **argv){
|
||||||
|
|
||||||
printf("vectordesert -- wire-mesh vector desert flythrough\n");
|
printf("vectordesert -- wire-mesh vector desert flythrough\n");
|
||||||
printf("move: W/S throttle A/D strafe PageUp/Dn altitude arrows pan\n");
|
printf("move: W/S throttle A/D strafe PageUp/Dn altitude arrows pan\n");
|
||||||
|
printf(" X full stop SPACE drops a bomb G floor-altitude indicator\n");
|
||||||
printf("keys: 1/2 terrainHue 3/4 mtnFreq ,/. mtnRough T/Y U/I mtnH\n");
|
printf("keys: 1/2 terrainHue 3/4 mtnFreq ,/. mtnRough T/Y U/I mtnH\n");
|
||||||
printf(" 5/6 cactusFreq 7/8 sizeVar J/K minSize N/M maxSize\n");
|
printf(" 5/6 cactusFreq 7/8 sizeVar J/K minSize N/M maxSize\n");
|
||||||
printf(" 9/0 cactusHue -/= maxArms [ ] renderDist F full ESC quit\n\n");
|
printf(" 9/0 cactusHue -/= maxArms [ ] renderDist F full ESC quit\n\n");
|
||||||
|
|
@ -898,6 +1351,7 @@ int main(int argc, char **argv){
|
||||||
|
|
||||||
double t0 = glfwGetTime();
|
double t0 = glfwGetTime();
|
||||||
g_lastInput = t0; /* HUD starts visible, then fades */
|
g_lastInput = t0; /* HUD starts visible, then fades */
|
||||||
|
g_rng ^= (uint32_t)(t0*1000.0) * 2654435761u; /* seed the bomb RNG */
|
||||||
camPos = v3(0.0f, 14.0f, 0.0f);
|
camPos = v3(0.0f, 14.0f, 0.0f);
|
||||||
const float moveSpeed = 28.0f; /* WASD / strafe units per second */
|
const float moveSpeed = 28.0f; /* WASD / strafe units per second */
|
||||||
const float climbSpeed= 22.0f; /* PageUp / PageDown */
|
const float climbSpeed= 22.0f; /* PageUp / PageDown */
|
||||||
|
|
@ -943,6 +1397,8 @@ int main(int argc, char **argv){
|
||||||
float floorY = terrainHeight(camPos.x, camPos.z) + 2.0f;
|
float floorY = terrainHeight(camPos.x, camPos.z) + 2.0f;
|
||||||
if(camPos.y < floorY) camPos.y = floorY;
|
if(camPos.y < floorY) camPos.y = floorY;
|
||||||
|
|
||||||
|
updateBombs(dt);
|
||||||
|
|
||||||
V3 target = add(camPos, fwd);
|
V3 target = add(camPos, fwd);
|
||||||
|
|
||||||
int fbw, fbh;
|
int fbw, fbh;
|
||||||
|
|
@ -967,6 +1423,7 @@ int main(int argc, char **argv){
|
||||||
ensureCaches();
|
ensureCaches();
|
||||||
batchDraw(&gTerr);
|
batchDraw(&gTerr);
|
||||||
batchDraw(&gCacti);
|
batchDraw(&gCacti);
|
||||||
|
drawBombs();
|
||||||
|
|
||||||
/* HUD: fully visible, then fades after 10s without a keypress */
|
/* HUD: fully visible, then fades after 10s without a keypress */
|
||||||
double idle = glfwGetTime() - g_lastInput;
|
double idle = glfwGetTime() - g_lastInput;
|
||||||
|
|
@ -974,12 +1431,14 @@ int main(int argc, char **argv){
|
||||||
? 1.0f
|
? 1.0f
|
||||||
: clampf(1.0f - (float)(idle-10.0)/1.5f, 0.0f, 1.0f);
|
: clampf(1.0f - (float)(idle-10.0)/1.5f, 0.0f, 1.0f);
|
||||||
if(hudAlpha > 0.001f) drawHUD(fbw, fbh, hudAlpha);
|
if(hudAlpha > 0.001f) drawHUD(fbw, fbh, hudAlpha);
|
||||||
|
if(g_showAlt) drawAltIndicator(fbw, fbh);
|
||||||
|
|
||||||
glfwSwapBuffers(win);
|
glfwSwapBuffers(win);
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
saveSettings(); /* persist settings for next launch */
|
||||||
glfwDestroyWindow(win);
|
glfwDestroyWindow(win);
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
BIN
vectordesert
BIN
vectordesert
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue