Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/world/weather.c raw
#include "world/weather.h"
#include "world/wetness.h"
#include "render/shapes.h"
#include <stdlib.h>
#define FIRST_DRY (3.0f * 3600.0f) /* game s: dry at first (you arrive, the tents go up) */
#define DRY_MIN (2.0f * 3600.0f) /* a dry spell: this long ... */
#define DRY_MORE (6.0f * 3600.0f) /* ... up to this much longer */
#define SHOWER_MIN (12.0f * 60.0f) /* a shower: this long ... */
#define SHOWER_MORE (40.0f * 60.0f)
#define RAIN_EASE 150.0f /* game s: it sets in / eases off over about this */
#define SOAK_TIME (25.0f * 60.0f) /* game s of hard rain: everything soaked */
#define DRY_TIME (3.0f * 3600.0f) /* ... and dry again (in the day; slower at night) */
#define RAIN_MM_HOUR 9.0f /* mm an hour in hard rain */
#define POUR_RADIUS 16.0f /* m round you the ground gets the water (puddles, mud) */
#define POURS 8 /* ... in this many splashes a frame */
#define PEOPLE_RADIUS 6.0f /* m round anybody else out in it ... */
#define PEOPLE_POURS 2 /* ... in this many */
#define DROP_RADIUS 9.0f /* m round the camera the drops fall */
#define DROP_SPEED 8.5f
#define DROP_LEN 0.6f
#define FIRE_DOUSE 0.004f /* kg of a fire's wood a game second, in hard rain */
#define STORM_RAIN 0.7f /* rain this hard: a thunderstorm ... */
#define FLASH_EVERY 75.0f /* ... a flash about every this many game seconds */
#define FLASH_FADE 4.0f /* a flash gone in a quarter second (real) */
#define WIND_EASE 240.0f /* game s: the wind picks up / dies down over about this */
#define WIND_SPELL (20.0f * 60.0f) /* game s: it changes after this ... */
#define WIND_SPELL_MORE (60.0f * 60.0f) /* ... up to this much later */
#define GUST_RATE 0.45f /* gusts come and go (real s, a slow beat of two sines) */
static float dice(unsigned *rng) {
*rng = *rng * 1103515245u + 12345u;
return (float)((*rng >> 8) & 0xFFFFu) / 65536.0f;
}
void Weather_init(World *world) {
world->rain = world->rain_goal = 0.0f;
world->rain_left = FIRST_DRY;
world->wet = 0.0f;
world->weather = WEATHER_AUTO;
world->wind_kind = WIND_AUTO;
world->wind = vec3_zero();
world->wind_goal = world->wind_level = 0.08f;
world->wind_angle = 0.6f;
world->wind_left = WIND_SPELL;
world->gust = world->gust_t = 0.0f;
world->flash = world->thunder_in = 0.0f;
world->thunder = false;
world->drops = calloc(1, sizeof(*world->drops));
if (world->drops) world->drops->rng = 0x5eed1234u;
}
const char *Weather_windName(WindKind kind) {
static const char *const NAME[WIND_KINDS] = { "on its own", "calm", "a breeze", "a storm" };
return NAME[(unsigned)kind < WIND_KINDS ? kind : 0];
}
float Weather_windSpeed(const World *world) {
return hypotf(world->wind.x, world->wind.z);
}
/* The wind: calm spells and windy ones (windier with rain coming), turning now
and then; gusts on top of it. */
static void blow(World *world, float dt, float game_dt) {
unsigned *rng = world->drops ? &world->drops->rng : &world->rng;
if (world->wind_kind == WIND_CALM) world->wind_goal = 0.03f;
else if (world->wind_kind == WIND_BREEZE) world->wind_goal = 0.3f;
else if (world->wind_kind == WIND_STORM) world->wind_goal = 0.85f;
else if ((world->wind_left -= game_dt) <= 0.0f) {
world->wind_left = WIND_SPELL + WIND_SPELL_MORE * dice(rng);
float d = dice(rng);
world->wind_goal = 0.04f + 0.35f * d * d + 0.45f * world->rain_goal * dice(rng);
world->wind_angle += (dice(rng) - 0.5f) * 1.6f; /* (turning a bit) */
}
if (world->wind_kind == WIND_AUTO && world->rain > STORM_RAIN) world->wind_goal = fmaxf(world->wind_goal, 0.55f); /* (a thunderstorm: windy) */
world->wind_level += (world->wind_goal - world->wind_level) * fminf(1.0f, game_dt / WIND_EASE);
world->gust_t += dt * GUST_RATE;
float beat = 0.5f + 0.5f * sinf(world->gust_t * 2.3f) * sinf(world->gust_t * 0.71f + 1.3f);
world->gust = clampf(beat * beat * 1.6f - 0.25f, 0.0f, 1.0f) * world->wind_level;
float speed = WIND_MAX * world->wind_level * (0.7f + 0.3f * sinf(world->gust_t * 3.1f)) + 0.6f * WIND_MAX * world->gust;
float a = world->wind_angle + 0.15f * sinf(world->gust_t * 1.7f);
world->wind = vec3(cosf(a) * speed, 0.0f, sinf(a) * speed);
}
const char *Weather_name(WeatherKind kind) {
static const char *const NAME[WEATHER_KINDS] = { "on its own", "dry", "a shower", "pouring" };
return NAME[(unsigned)kind < WEATHER_KINDS ? kind : 0];
}
bool Weather_covered(const World *world, Vec3 p) {
return World_rayCast(world, vec3_add(p, vec3(0.0f, 0.3f, 0.0f)), vec3(0.0f, 1.0f, 0.0f), 14.0f, NULL, NULL, NULL, NULL) >= 0.0f;
}
static void respawn(World *world, int k, bool anywhere) {
RainDrops *d = world->drops;
float a = dice(&d->rng) * 2.0f * PI_F, r = DROP_RADIUS * sqrtf(dice(&d->rng));
Vec3 p = vec3_add(world->eye, vec3(cosf(a) * r, 0.0f, sinf(a) * r));
float ground = world->has_terrain ? Terrain_height(&world->terrain, p.x, p.z) : 0.0f;
p.y = anywhere ? ground + 7.0f * dice(&d->rng) : fmaxf(world->eye.y, ground) + 4.0f + 3.0f * dice(&d->rng);
d->hidden[k] = Weather_covered(world, vec3(p.x, fminf(world->eye.y, ground + 1.0f), p.z));
d->p[k] = p;
}
void Weather_update(World *world, float dt, float game_dt) {
/* where it's going: forced (dev), or showers and dry spells taking turns */
if (world->weather == WEATHER_DRY) world->rain_goal = 0.0f;
else if (world->weather == WEATHER_SHOWER) world->rain_goal = 0.4f;
else if (world->weather == WEATHER_RAIN) world->rain_goal = 1.0f;
else if ((world->rain_left -= game_dt) <= 0.0f) {
unsigned *rng = world->drops ? &world->drops->rng : &world->rng;
if (world->rain_goal > 0.0f) {
world->rain_goal = 0.0f;
world->rain_left = DRY_MIN + DRY_MORE * dice(rng);
} else {
world->rain_goal = 0.25f + 0.75f * dice(rng);
world->rain_left = SHOWER_MIN + SHOWER_MORE * dice(rng);
}
}
world->rain += (world->rain_goal - world->rain) * fminf(1.0f, game_dt / RAIN_EASE);
if (world->rain < 0.002f && world->rain_goal <= 0.0f) world->rain = 0.0f;
bool day = world->hour >= 7.0f && world->hour < 20.0f;
world->wet = clampf(world->wet + world->rain * game_dt / SOAK_TIME - (1.0f - world->rain) * game_dt / (DRY_TIME * (day ? 1.0f : 3.0f)), 0.0f, 1.0f);
blow(world, dt, game_dt);
/* a thunderstorm: lightning, the thunder a few seconds after it */
world->thunder = false;
world->flash = fmaxf(0.0f, world->flash - FLASH_FADE * dt);
if (world->thunder_in > 0.0f && (world->thunder_in -= dt) <= 0.0f) { world->thunder_in = 0.0f; world->thunder = true; }
if (world->rain > STORM_RAIN && world->drops && world->thunder_in <= 0.0f && dice(&world->drops->rng) < game_dt / FLASH_EVERY) {
world->flash = 1.0f;
world->thunder_in = 1.0f + 5.0f * dice(&world->drops->rng); /* (near: right after; further: later) */
}
if (world->rain <= 0.0f || !world->drops) return;
/* the ground near you gets the water: puddles, mud (not under a roof) */
RainDrops *d = world->drops;
float liters = RAIN_MM_HOUR * world->rain / 3600.0f * game_dt * PI_F * POUR_RADIUS * POUR_RADIUS / (float)POURS;
for (int k = 0; k < POURS && world->has_terrain; k++) {
float a = dice(&d->rng) * 2.0f * PI_F, r = POUR_RADIUS * sqrtf(dice(&d->rng));
Vec3 p = vec3_add(world->eye, vec3(cosf(a) * r, 0.0f, sinf(a) * r));
p.y = Terrain_height(&world->terrain, p.x, p.z);
if (!Weather_covered(world, p)) Wetness_pour(&world->terrain, p.x, p.z, liters, 2.2f, 0.0f);
}
/* ... and round everybody else out in it: where they walk (the paths) gets soaked too, and turns to mud */
float near_liters = RAIN_MM_HOUR * world->rain / 3600.0f * game_dt * PI_F * PEOPLE_RADIUS * PEOPLE_RADIUS / (float)PEOPLE_POURS;
for (int i = 0; i < world->count && world->has_terrain; i++) {
const Object *o = world->objects[i];
if (!o->is_character || o->indoors > 0.0f || o->in_tent || o->riding) continue;
float ex = o->position.x - world->eye.x, ez = o->position.z - world->eye.z;
if (ex * ex + ez * ez < (POUR_RADIUS - PEOPLE_RADIUS) * (POUR_RADIUS - PEOPLE_RADIUS)) continue; /* (in the camera's patch already) */
for (int k = 0; k < PEOPLE_POURS; k++) {
float a = dice(&d->rng) * 2.0f * PI_F, r = PEOPLE_RADIUS * sqrtf(dice(&d->rng));
Vec3 p = vec3_add(o->position, vec3(cosf(a) * r, 0.0f, sinf(a) * r));
p.y = Terrain_height(&world->terrain, p.x, p.z);
if (!Weather_covered(world, p)) Wetness_pour(&world->terrain, p.x, p.z, near_liters, 2.2f, 0.0f);
}
}
/* a fire burns down faster in it */
for (int i = 0; i < world->count; i++) {
Object *f = world->objects[i];
if (f->type == OBJ_FIREPLACE && f->burning) f->fuel = fmaxf(0.0f, f->fuel - FIRE_DOUSE * world->rain * game_dt);
}
/* the drops round the camera */
int n = (int)(RAIN_DROPS * world->rain);
for (int k = 0; k < n; k++) {
Vec3 *p = &d->p[k];
float dx = p->x - world->eye.x, dz = p->z - world->eye.z;
if (p->y == 0.0f && p->x == 0.0f) { respawn(world, k, true); continue; }
p->y -= DROP_SPEED * dt;
p->x += world->wind.x * dt; /* (blown along) */
p->z += world->wind.z * dt;
float ground = world->has_terrain ? Terrain_height(&world->terrain, p->x, p->z) : 0.0f;
if (p->y < ground || dx * dx + dz * dz > DROP_RADIUS * DROP_RADIUS * 1.3f) respawn(world, k, p->y >= ground);
}
}
void Weather_draw(World *world, Renderer *renderer) {
if (world->rain <= 0.01f || !world->drops || world->eye.x > WORLD_BERLIN_FROM) return; /* (Berlin: indoors, another sky) */
Vec3 shown[RAIN_DROPS];
int n = 0, live = (int)(RAIN_DROPS * world->rain);
for (int k = 0; k < live; k++)
if (!world->drops->hidden[k]) { /* (not right in front of the eye: a fat bar) */
Vec3 p = world->drops->p[k], e = vec3_sub(p, world->eye);
if (e.x * e.x + e.z * e.z > 1.5f * 1.5f || fabsf(e.y) > 1.5f) shown[n++] = p;
}
if (n == 0) return;
Vec3 lean = vec3(-world->wind.x / DROP_SPEED, 0.0f, -world->wind.z / DROP_SPEED); /* (slanting into where it comes from) */
Mesh mesh = Shapes_rain(shown, n, DROP_LEN, lean);
Renderer_drawSmoke(renderer, &mesh, mat4_identity(), vec3(1.0f, 1.0f, 1.0f));
Mesh_destroy(&mesh);
}