diff --git a/resources/00-taisei.pkgdir/gfx/stage1/fog.webp b/resources/00-taisei.pkgdir/gfx/stage1/fog.webp index 2d14edf8..cd12c0d6 100644 Binary files a/resources/00-taisei.pkgdir/gfx/stage1/fog.webp and b/resources/00-taisei.pkgdir/gfx/stage1/fog.webp differ diff --git a/resources/00-taisei.pkgdir/gfx/stage1/horizon.tex b/resources/00-taisei.pkgdir/gfx/stage1/horizon.tex new file mode 100644 index 00000000..0de4adbf --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/stage1/horizon.tex @@ -0,0 +1,3 @@ + +wrap_s = clamp +wrap_t = clamp diff --git a/resources/00-taisei.pkgdir/gfx/stage1/horizon.webp b/resources/00-taisei.pkgdir/gfx/stage1/horizon.webp new file mode 100644 index 00000000..205f054a Binary files /dev/null and b/resources/00-taisei.pkgdir/gfx/stage1/horizon.webp differ diff --git a/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl b/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl index 9d5e9252..6c5e31a8 100644 --- a/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl +++ b/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl @@ -4,6 +4,11 @@ #include "interface/standard.glslh" UNIFORM(1) float time; +UNIFORM(2) vec4 water_color; +UNIFORM(3) float wave_offset; + +const vec4 reflection_color = vec4(0.5, 0.8, 0.8, 1.0); +const vec4 wave_color = vec4(0.8, 0.8, 1.0, 1.0); // Based on https://www.shadertoy.com/view/Xl2XWz @@ -41,7 +46,7 @@ float warpedNoise(vec2 p) { } void main(void) { - vec2 uv = flip_native_to_bottomleft(texCoord - vec2(0, 2+time * 0.2)) * rot(-pi/4); + vec2 uv = flip_native_to_bottomleft(texCoord - vec2(0, wave_offset)) * rot(-pi/4); float n = warpedNoise(uv * 4); @@ -52,11 +57,14 @@ void main(void) { bump = bump * bump * 0.5; bump2 = bump2 * bump2 * 0.5; - vec4 cmod = vec4(0.5, 0.8, 0.8, 1.0); - uv = flip_native_to_bottomleft(texCoord); uv += 0.25 * vec2(bump, bump2); uv = flip_bottomleft_to_native(uv); - fragColor = mix(cmod * texture(tex, uv), vec4(0.8, 0.8, 1.0, 1.0), (bump2 - bump * 0.25) * 0.05); + vec4 reflection = texture(tex, uv); + reflection.rgb = mix(reflection.rgb, water_color.rgb, reflection.a * 0.5); + reflection = reflection * reflection_color * 0.5; + vec4 surface = alphaCompose(water_color, reflection); + + fragColor = mix(surface, wave_color, (bump2 - bump * 0.25) * 0.05); } diff --git a/src/common_tasks.c b/src/common_tasks.c index d2ea4592..69a32575 100644 --- a/src/common_tasks.c +++ b/src/common_tasks.c @@ -46,7 +46,11 @@ DEFINE_EXTERN_TASK(common_move_ext) { common_move_loop(ARGS.pos, ARGS.move_params); } -complex common_wander(complex origin, double dist, Rect bounds) { +DEFINE_EXTERN_TASK(common_call_func) { + ARGS.func(); +} + +cmplx common_wander(cmplx origin, double dist, Rect bounds) { int attempts = 32; double angle; complex dest; diff --git a/src/common_tasks.h b/src/common_tasks.h index ae00aa09..08f70fab 100644 --- a/src/common_tasks.h +++ b/src/common_tasks.h @@ -32,7 +32,12 @@ DECLARE_EXTERN_TASK( { complex *pos; MoveParams *move_params; BoxedEntity ent; } ); -void common_move_loop(complex *restrict pos, MoveParams *restrict mp); +DECLARE_EXTERN_TASK( + common_call_func, + { void (*func)(void); } +); + +void common_move_loop(cmplx *restrict pos, MoveParams *restrict mp); INLINE Rect viewport_bounds(double margin) { return (Rect) { diff --git a/src/random.c b/src/random.c index b00e2ec4..732a7036 100644 --- a/src/random.c +++ b/src/random.c @@ -24,6 +24,13 @@ uint64_t splitmix64(uint64_t *state) { return z ^ (z >> 31); } +uint32_t splitmix32(uint32_t *state) { + uint32_t z = (*state += 0x9e3779b9); + z = (z ^ (z >> 15)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae3d; + return z ^ (z >> 16); +} + uint64_t makeseed(void) { static uint64_t s = 69; return splitmix64(&s) ^ SDL_GetPerformanceCounter(); diff --git a/src/random.h b/src/random.h index 38d52229..a8b10998 100644 --- a/src/random.h +++ b/src/random.h @@ -28,6 +28,7 @@ typedef struct rng_val { } rng_val_t; uint64_t splitmix64(uint64_t *state) attr_nonnull(1); +uint32_t splitmix32(uint32_t *state) attr_nonnull(1); uint64_t makeseed(void); void rng_init(RandomState *rng, uint64_t seed) attr_nonnull(1); diff --git a/src/stages/stage1.c b/src/stages/stage1.c index 6062fbd2..90c7e93e 100644 --- a/src/stages/stage1.c +++ b/src/stages/stage1.c @@ -16,6 +16,8 @@ #include "stageutils.h" #include "stagedraw.h" #include "resource/model.h" +#include "util/glm.h" +#include "common_tasks.h" /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. @@ -51,7 +53,20 @@ struct stage1_spells_s stage1_spells = { }, }; -static FBPair stage1_bg_fbpair; +static struct { + FBPair water_fbpair; + + struct { + float near, near_target; + float far, far_target; + } fog; + + struct { + float opacity, opacity_target; + } snow; + + float pitch_target; +} stage1_bg; #ifdef SPELL_BENCHMARK AttackInfo stage1_spell_benchmark = { @@ -109,42 +124,54 @@ static void stage1_water_draw(vec3 pos) { r_mat_mv_translate(0, stage_3d_context.cx[1] + 500, 0); r_mat_mv_rotate(M_PI, 1, 0, 0); + static const Color water_color = { 0, 0.08, 0.08, 1 }; + + r_clear(CLEAR_COLOR, &water_color, 1); + + r_mat_mv_push(); + r_mat_mv_translate(0, -9000, 0); + r_mat_mv_rotate(M_PI/2, 1, 0, 0); + r_mat_mv_scale(3640*1.4, 1456*1.4, 1); + r_mat_mv_translate(0, -0.5, 0); + r_shader_standard(); + r_uniform_sampler("tex", "stage1/horizon"); + r_draw_quad(); + r_mat_mv_pop(); + + r_state_push(); + r_enable(RCAP_DEPTH_TEST); + r_depth_func(DEPTH_ALWAYS); r_shader_standard_notex(); r_mat_mv_push(); - r_mat_mv_scale(1200, 3000, 1); - r_color4(0, 0.1, 0.1, 1); + r_mat_mv_scale(80000, 80000, 1); + r_color(&water_color); r_draw_quad(); r_color4(1, 1, 1, 1); r_mat_mv_pop(); + r_state_pop(); - r_disable(RCAP_CULL_FACE); r_disable(RCAP_DEPTH_TEST); + r_disable(RCAP_CULL_FACE); Framebuffer *bg_fb = r_framebuffer_current(); - FBPair *fbpair = &stage1_bg_fbpair; + FBPair *fbpair = &stage1_bg.water_fbpair; r_framebuffer(fbpair->back); r_mat_proj_push(); set_ortho(VIEWPORT_W, VIEWPORT_H); r_mat_mv_push_identity(); - float z = 0.75; - float zo = -0.05; + float hack = (stage_3d_context.crot[0] - 60) / 15.0; + + float z = glm_lerp(0.75, 0.8, hack); + float zo = glm_lerp(-0.05, -0.3, hack); r_mat_mv_translate(VIEWPORT_W * 0.5 * (1 - z), VIEWPORT_H * 0.5 * (1 - z), 0); r_mat_mv_scale(z, z, 1); - r_clear(CLEAR_ALL, RGBA(0, 0.08, 0.08, 1), 1); + r_clear(CLEAR_ALL, RGBA(0, 0, 0, 0), 1); r_shader("sprite_default"); ent_draw(stage1_draw_predicate); - r_mat_mv_push(); - r_shader_standard_notex(); - r_mat_mv_translate(VIEWPORT_W*0.5, VIEWPORT_H*0.5, 0); - r_mat_mv_scale(VIEWPORT_W/z, VIEWPORT_H/z, 1); - r_color4(0, 0.08, 0.08, 0.8); - r_draw_quad(); - r_mat_mv_pop(); - r_mat_mv_pop(); fbpair_swap(fbpair); @@ -152,6 +179,11 @@ static void stage1_water_draw(vec3 pos) { int pp_quality = config_get_int(CONFIG_POSTPROCESS); + ShaderProgram *water_shader = r_shader_get("stage1_water"); + r_uniform_float(r_shader_uniform(water_shader, "time"), 0.5 * global.frames / (float)FPS); + r_uniform_vec4_rgba(r_shader_uniform(water_shader, "water_color"), &water_color); + r_uniform_float(r_shader_uniform(water_shader, "wave_offset"), stage_3d_context.cx[1] / 2400.0); + if(pp_quality > 1) { r_shader("blur5"); r_uniform_vec2("blur_resolution", VIEWPORT_W, VIEWPORT_H); @@ -187,21 +219,14 @@ static void stage1_water_draw(vec3 pos) { } r_mat_mv_push(); - r_mat_mv_translate(0, 70, 0); + r_mat_mv_translate(0, 70 - 140 * hack, 0); r_mat_mv_rotate(10 * DEG2RAD, 1, 0, 0); r_mat_mv_scale(0.85 / (z + zo), -0.85 / (z + zo), 0.85); - r_mat_mv_translate(-VIEWPORT_W/2, 0, 0); + r_mat_mv_translate(-VIEWPORT_W/2, VIEWPORT_H/4 * hack, 0); r_color4(1, 1, 1, 1); draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H); r_mat_mv_pop(); - r_shader_standard_notex(); - r_mat_mv_push(); - r_mat_mv_scale(1200,3000,1); - r_color4(0, 0.08, 0.08, 0.08); - r_draw_quad(); - r_mat_mv_pop(); - r_mat_mv_pop(); r_state_pop(); } @@ -214,40 +239,62 @@ static uint stage1_bg_pos(Stage3D *s3d, vec3 p, float maxrange) { static void stage1_smoke_draw(vec3 pos) { float d = fabsf(pos[1]-stage_3d_context.cx[1]); + float o = ((d-500)*(d-500))/1.5e7; + o *= 5 * pow((5000 - d) / 5000, 3); + + if(o < 1.0f/255.0f) { + return; + } + + float spin = 0.01 * sin(pos[1]*993.11); + r_state_push(); r_shader("sprite_default"); r_disable(RCAP_DEPTH_TEST); r_cull(CULL_BACK); r_mat_mv_push(); - r_mat_mv_translate(pos[0]+200*sin(pos[1]), pos[1], pos[2]+200*sin(pos[1]/25.0)); + r_mat_mv_translate(pos[0]+600*sin(pos[1]), pos[1], pos[2]+600*sin(pos[1]/25.0)); r_mat_mv_rotate(M_PI/2, -1, 0, 0); - r_mat_mv_scale(3.5, 2, 1); - r_mat_mv_rotate(global.frames * DEG2RAD, 0, 0, 1); - float o = ((d-500)*(d-500))/1.5e7; + r_mat_mv_scale(3.5*2, 2*1.5, 1); + r_mat_mv_rotate(global.frames * spin + M_PI * 2 * sin(pos[1]*321.23), 0, 0, 1); r_draw_sprite(&(SpriteParams) { .sprite = "stage1/fog", - .color = RGBA(0.8 * o, 0.8 * o, 0.8 * o, o * 0.5), + .color = RGBA(0.6 * o, 0.7 * o, 0.8 * o, o * 0.5), }); r_mat_mv_pop(); r_state_pop(); } static uint stage1_smoke_pos(Stage3D *s3d, vec3 p, float maxrange) { - vec3 q = {0,0,-300}; - vec3 r = {0,300,0}; + vec3 q = {0,0,800}; + vec3 r = {0,200,0}; return linear3dpos(s3d, p, maxrange/2.0, q, r); } +static bool stage1_fog(Framebuffer *fb) { + r_shader("zbuf_fog"); + r_uniform_sampler("depth", r_framebuffer_get_attachment(fb, FRAMEBUFFER_ATTACH_DEPTH)); + r_uniform_vec4("fog_color", 0.78, 0.8, 0.85, 1.0); + r_uniform_float("start", stage1_bg.fog.near); + r_uniform_float("end", stage1_bg.fog.far); + r_uniform_float("exponent", 1.0); + r_uniform_float("sphereness", 0.2); + draw_framebuffer_tex(fb, VIEWPORT_W, VIEWPORT_H); + r_shader_standard(); + return true; +} + static inline uint32_t floathash(float f) { - return (union { uint32_t i; float f; }){ .f = f }.i; + uint32_t bits = float_to_bits(f); + return splitmix32(&bits); } static void stage1_waterplants_draw(vec3 pos) { r_state_push(); // stateless pseudo-random fuckery - int tile = floathash(pos[1] * 3124312) & 1; - float offs = 200 * sin(2*M_PI*remainder(3214.322211333 * floathash(pos[1]), M_E)); + int tile = floathash(pos[1] * 311.4312) & 1; + float offs = 600 * sin(2*M_PI*remainder(3214.322211333 * floathash(pos[1]), M_E)); float d = -55+50*sin(pos[1]/25.0); r_mat_mv_push(); r_mat_mv_translate(pos[0]+offs, pos[1], d); @@ -283,52 +330,136 @@ static void stage1_waterplants_draw(vec3 pos) { r_state_pop(); } -static bool stage1_fog(Framebuffer *fb) { - r_shader("zbuf_fog"); - r_uniform_sampler("depth", r_framebuffer_get_attachment(fb, FRAMEBUFFER_ATTACH_DEPTH)); - r_uniform_vec4("fog_color", 0.8, 0.8, 0.8, 1.0); - r_uniform_float("start", 0.0); - r_uniform_float("end", 0.8); - r_uniform_float("exponent", 3.0); - r_uniform_float("sphereness", 0.2); - draw_framebuffer_tex(fb, VIEWPORT_W, VIEWPORT_H); - r_shader_standard(); - return true; +static uint stage1_waterplants_pos(Stage3D *s3d, vec3 p, float maxrange) { + vec3 q = {0,0,-300}; + vec3 r = {0,150,0}; + return linear3dpos(s3d, p, maxrange/2.0, q, r); +} + +static void stage1_snow_draw(vec3 pos) { + float o = stage1_bg.snow.opacity; + // float appear_time = 2760; + + if(o < 1.0f/256.0f) { + return; + } + + float d = fabsf(pos[1] - stage_3d_context.cx[1]); + + if(fabsf(d) < 500) { + return; + } + + float x = ((d+500)*(d+500))/(5000*5000); + + float h0 = floathash(pos[1]+0) / (double)UINT32_MAX; + float h1 = floathash(pos[1]+1) / (double)UINT32_MAX; + float h2 = floathash(pos[1]+2) / (double)UINT32_MAX; + + float height = 1 + sawtooth(h0 + global.frames/240.0); + + o *= pow(1 - 1.5 * clamp(height - 1, 0, 1), 5) * x; + // o *= min(1, (global.frames - appear_time) / 180.0); + + if(o < 1.0f/256.0f) { + return; + } + + if(height > 1) { + height = 1; + } else { + height = glm_ease_quad_in(height); + } + + if(o > 1) { + o = 1; + } + + r_state_push(); + r_shader("sprite_default"); + r_disable(RCAP_DEPTH_TEST); + r_cull(CULL_BACK); + r_mat_mv_push(); + r_mat_mv_translate(pos[0] + 2200 * sawtooth(h1), pos[1] + 10 * sawtooth(h2), 1200 - 1200 * height); + r_mat_mv_rotate(M_PI/2, -1, 0, 0); + r_draw_sprite(&(SpriteParams) { + .sprite = "part/smoothdot", + .color = RGBA(o, o, o, 0), + }); + r_mat_mv_pop(); + r_state_pop(); + +} + +static uint stage1_snow_pos(Stage3D *s3d, vec3 p, float maxrange) { + vec3 q = {0,0,0}; + vec3 r = {0,15,0}; + return linear3dpos(s3d, p, maxrange, q, r); +} + +void stage1_bg_raise_camera(void) { + stage1_bg.pitch_target = 75; +} + +void stage1_bg_enable_snow(void) { + stage1_bg.snow.opacity_target = 1; +} + +void stage1_bg_disable_snow(void) { + stage1_bg.snow.opacity_target = 0; } static void stage1_draw(void) { - r_mat_proj_perspective(STAGE3D_DEFAULT_FOVY, STAGE3D_DEFAULT_ASPECT, 500, 5000); + r_mat_proj_perspective(STAGE3D_DEFAULT_FOVY, STAGE3D_DEFAULT_ASPECT, 500, 10000); Stage3DSegment segs[] = { { stage1_water_draw, stage1_bg_pos }, - { stage1_waterplants_draw, stage1_smoke_pos }, + { stage1_waterplants_draw, stage1_waterplants_pos }, + { stage1_snow_draw, stage1_snow_pos }, { stage1_smoke_draw, stage1_smoke_pos }, }; - stage3d_draw(&stage_3d_context, 7000, ARRAY_SIZE(segs), segs); + stage3d_draw(&stage_3d_context, 10000, ARRAY_SIZE(segs), segs); } static void stage1_update(void) { stage3d_update(&stage_3d_context); + + stage_3d_context.crot[1] = 2 * sin(global.frames/113.0); + stage_3d_context.crot[2] = 1 * sin(global.frames/132.0); + + fapproach_asymptotic_p(&stage_3d_context.crot[0], stage1_bg.pitch_target, 0.01, 1e-5); + fapproach_asymptotic_p(&stage1_bg.fog.near, stage1_bg.fog.near_target, 0.001, 1e-5); + fapproach_asymptotic_p(&stage1_bg.fog.far, stage1_bg.fog.far_target, 0.001, 1e-5); + fapproach_p(&stage1_bg.snow.opacity, stage1_bg.snow.opacity_target, 1.0 / 180.0); } static void stage1_start(void) { - stage3d_init(&stage_3d_context, 16); - - stage_3d_context.crot[0] = 60; - stage_3d_context.cx[2] = 700; - stage_3d_context.cv[1] = 4; + stage3d_init(&stage_3d_context, 64); FBAttachmentConfig cfg = { 0 }; cfg.attachment = FRAMEBUFFER_ATTACH_COLOR0; cfg.tex_params.filter.min = TEX_FILTER_LINEAR; cfg.tex_params.filter.mag = TEX_FILTER_LINEAR; - cfg.tex_params.type = TEX_TYPE_RGB; + cfg.tex_params.type = TEX_TYPE_RGBA; cfg.tex_params.wrap.s = TEX_WRAP_CLAMP; cfg.tex_params.wrap.t = TEX_WRAP_CLAMP; - stage1_bg_fbpair.front = stage_add_background_framebuffer("Stage 1 water FB 1", 0.2, 0.5, 1, &cfg); - stage1_bg_fbpair.back = stage_add_background_framebuffer("Stage 1 water FB 2", 0.2, 0.5, 1, &cfg); + stage1_bg.water_fbpair.front = stage_add_background_framebuffer("Stage 1 water FB 1", 0.2, 0.5, 1, &cfg); + stage1_bg.water_fbpair.back = stage_add_background_framebuffer("Stage 1 water FB 2", 0.2, 0.5, 1, &cfg); + + stage1_bg.fog.near_target = 0.5; + stage1_bg.fog.far_target = 1.5; + stage1_bg.snow.opacity_target = 0.0; + stage1_bg.pitch_target = 60; + + stage1_bg.fog.near = 0.2; // stage1_bg.fog.near_target; + stage1_bg.fog.far = 1.0; // stage1_bg.fog.far_target; + stage1_bg.snow.opacity = stage1_bg.snow.opacity_target; + + stage_3d_context.crot[0] = stage1_bg.pitch_target; + stage_3d_context.cx[2] = 700; + stage_3d_context.cv[1] = 8; } static void stage1_preload(void) { @@ -340,6 +471,9 @@ static void stage1_preload(void) { "stage1/waterplants", "dialog/cirno", NULL); + preload_resources(RES_TEXTURE, RESF_DEFAULT, + "stage1/horizon", + NULL); preload_resources(RES_SHADER_PROGRAM, RESF_DEFAULT, "blur5", "stage1_water", @@ -358,6 +492,7 @@ static void stage1_preload(void) { static void stage1_end(void) { stage3d_shutdown(&stage_3d_context); + memset(&stage1_bg, 0, sizeof(stage1_bg)); } static void stage1_spellpractice_start(void) { @@ -369,6 +504,16 @@ static void stage1_spellpractice_start(void) { global.boss = cirno; stage_start_bgm("stage1boss"); + + stage1_bg_raise_camera(); + stage1_bg_enable_snow(); + + stage1_bg.fog.near = stage1_bg.fog.near_target; + stage1_bg.fog.far = stage1_bg.fog.far_target; + stage1_bg.snow.opacity = stage1_bg.snow.opacity_target; + stage_3d_context.crot[0] = stage1_bg.pitch_target; + + INVOKE_TASK_WHEN(&cirno->events.defeated, common_call_func, stage1_bg_disable_snow); } static void stage1_spellpractice_events(void) { diff --git a/src/stages/stage1.h b/src/stages/stage1.h index c0bedd96..88d7ea40 100644 --- a/src/stages/stage1.h +++ b/src/stages/stage1.h @@ -42,6 +42,10 @@ extern struct stage1_spells_s { extern StageProcs stage1_procs; extern StageProcs stage1_spell_procs; +void stage1_bg_raise_camera(void); +void stage1_bg_enable_snow(void); +void stage1_bg_disable_snow(void); + #ifdef SPELL_BENCHMARK extern AttackInfo stage1_spell_benchmark; #endif diff --git a/src/stages/stage1_events.c b/src/stages/stage1_events.c index 78c08cd7..f1042127 100644 --- a/src/stages/stage1_events.c +++ b/src/stages/stage1_events.c @@ -1431,6 +1431,9 @@ TASK(spawn_midboss, NO_ARGS) { boss_add_attack_task(boss, AT_Move, "Introduction", 2, 0, TASK_INDIRECT(BossAttack, midboss_flee), NULL); boss_start_attack(boss, boss->attacks); + + WAIT(60); + stage1_bg_enable_snow(); } TASK(tritoss_fairy, { cmplx pos; cmplx velocity; cmplx end_velocity; }) { @@ -1526,6 +1529,9 @@ TASK(stage_timeline, NO_ARGS) { INVOKE_TASK_DELAYED(900, schedule_swirls); INVOKE_TASK_DELAYED(1500, burst_fairies_3); INVOKE_TASK_DELAYED(2200, multiburst_fairies_1); + + INVOKE_TASK_DELAYED(2200, common_call_func, stage1_bg_raise_camera); + STAGE_BOOKMARK_DELAYED(2500, pre-midboss); INVOKE_TASK_DELAYED(2700, spawn_midboss); while(!global.boss) YIELD; @@ -1597,7 +1603,10 @@ TASK(stage_timeline, NO_ARGS) { WAIT_EVENT(&global.boss->events.defeated); stage_unlock_bgm("stage1boss"); - WAIT(240); + + WAIT(120); + stage1_bg_disable_snow(); + WAIT(120); global.dialog = stage1_dialog_post_boss(); while(dialog_is_active(global.dialog)) {