#include "world/daycycle.h" #include #define SECONDS_PER_DAY 86400.0f #define SUN_TILT 0.4f /* tilts the sun path so noon shadows have a direction */ #define MIN_LIGHT_HEIGHT 0.3f /* keeps shadows from stretching to infinity */ /* Painted palette for a time of day. */ typedef struct { float hour; Vec3 sky_top; Vec3 sky_horizon; Vec3 light; /* lit areas */ Vec3 shadow; /* shaded areas */ } Mood; static const Mood MOODS[] = { { 0.0f, { 0.06f, 0.07f, 0.20f }, { 0.20f, 0.16f, 0.36f }, { 0.35f, 0.42f, 0.70f }, { 0.22f, 0.20f, 0.42f } }, /* night */ { 5.5f, { 0.16f, 0.14f, 0.38f }, { 0.55f, 0.32f, 0.48f }, { 0.45f, 0.40f, 0.70f }, { 0.28f, 0.22f, 0.45f } }, /* pre-dawn */ { 7.0f, { 0.36f, 0.42f, 0.72f }, { 1.00f, 0.58f, 0.38f }, { 1.10f, 0.72f, 0.50f }, { 0.42f, 0.30f, 0.55f } }, /* sunrise */ { 10.0f, { 0.30f, 0.56f, 0.86f }, { 0.86f, 0.84f, 0.66f }, { 1.10f, 1.00f, 0.82f }, { 0.45f, 0.45f, 0.70f } }, /* morning */ { 14.0f, { 0.26f, 0.52f, 0.84f }, { 0.92f, 0.86f, 0.62f }, { 1.15f, 1.02f, 0.78f }, { 0.48f, 0.42f, 0.68f } }, /* afternoon */ { 19.0f, { 0.36f, 0.40f, 0.70f }, { 1.00f, 0.66f, 0.36f }, { 1.20f, 0.80f, 0.46f }, { 0.50f, 0.32f, 0.55f } }, /* golden */ { 21.0f, { 0.26f, 0.20f, 0.50f }, { 0.95f, 0.42f, 0.40f }, { 0.85f, 0.48f, 0.50f }, { 0.36f, 0.24f, 0.48f } }, /* sunset */ { 22.5f, { 0.06f, 0.07f, 0.20f }, { 0.20f, 0.16f, 0.36f }, { 0.35f, 0.42f, 0.70f }, { 0.22f, 0.20f, 0.42f } }, /* night */ }; #define MOOD_COUNT ((int)(sizeof(MOODS) / sizeof(MOODS[0]))) /* Interpolates between the two moods around the given hour (wraps at midnight). */ static Mood moodAt(float hour) { int next = 0; while (next < MOOD_COUNT && MOODS[next].hour <= hour) next++; const Mood *a = &MOODS[(next + MOOD_COUNT - 1) % MOOD_COUNT]; const Mood *b = &MOODS[next % MOOD_COUNT]; float start = a->hour, end = b->hour; if (end <= start) end += 24.0f; float h = hour < start ? hour + 24.0f : hour; float t = smoothstepf(0.0f, 1.0f, (h - start) / (end - start)); return (Mood){ .hour = hour, .sky_top = vec3_lerp(a->sky_top, b->sky_top, t), .sky_horizon = vec3_lerp(a->sky_horizon, b->sky_horizon, t), .light = vec3_lerp(a->light, b->light, t), .shadow = vec3_lerp(a->shadow, b->shadow, t), }; } static void updateLighting(DayCycle *dc) { const float hour = dc->seconds / 3600.0f; const float day_length = dc->day_end - dc->day_start; const float night_length = 24.0f - day_length; /* Sun travels a half circle during the day and the other half at night. */ float angle; if (hour >= dc->day_start && hour <= dc->day_end) { angle = (hour - dc->day_start) / day_length * PI_F; } else { float since_sunset = hour < dc->day_start ? hour + 24.0f - dc->day_end : hour - dc->day_end; angle = since_sunset / night_length * PI_F + PI_F; } Lighting *l = &dc->light; l->sun_direction = vec3_normalize(vec3(cosf(angle), sinf(angle), SUN_TILT)); /* Key light: sun by day, moon (opposite the sun) by night, never too flat. */ Vec3 key = l->sun_direction.y >= 0.0f ? l->sun_direction : vec3_scale(l->sun_direction, -1.0f); if (key.y < MIN_LIGHT_HEIGHT) { float flat = sqrtf(1.0f - MIN_LIGHT_HEIGHT * MIN_LIGHT_HEIGHT); Vec3 h = vec3_normalize(vec3(key.x, 0.0f, key.z)); key = vec3(h.x * flat, MIN_LIGHT_HEIGHT, h.z * flat); } l->light_direction = key; /* Shadows fade out while sun and moon swap near the horizon. */ l->shadow_strength = smoothstepf(0.0f, 0.2f, fabsf(l->sun_direction.y)); l->day_factor = smoothstepf(-0.1f, 0.3f, l->sun_direction.y); Mood m = moodAt(hour); l->sky_top = m.sky_top; l->sky_horizon = m.sky_horizon; l->light_color = m.light; l->shadow_color = m.shadow; } void DayCycle_init(DayCycle *dc, float start_hour, float time_scale) { *dc = (DayCycle){ .seconds = start_hour * 3600.0f, .time_scale = time_scale, .day_start = 7.0f, .day_end = 21.0f, }; updateLighting(dc); } void DayCycle_update(DayCycle *dc, float dt) { dc->seconds = fmodf(dc->seconds + dt * dc->time_scale, SECONDS_PER_DAY); updateLighting(dc); } void DayCycle_format(const DayCycle *dc, char *out, size_t out_size) { int total = (int)dc->seconds; snprintf(out, out_size, "%02d:%02d:%02d", total / 3600, (total / 60) % 60, total % 60); }