taisei/src/laser.c
2019-07-03 21:00:56 +03:00

664 lines
18 KiB
C

/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "taisei.h"
#include "laser.h"
#include "global.h"
#include "list.h"
#include "stageobjects.h"
#include "stagedraw.h"
#include "renderer/api.h"
#include "resource/model.h"
static struct {
VertexArray *varr;
VertexBuffer *vbuf;
ShaderProgram *shader_generic;
Model quad_generic;
Framebuffer *saved_fb;
Framebuffer *render_fb;
} lasers;
typedef struct LaserInstancedAttribs {
float pos[2];
float delta[2];
} LaserInstancedAttribs;
static void lasers_ent_predraw_hook(EntityInterface *ent, void *arg);
static void lasers_ent_postdraw_hook(EntityInterface *ent, void *arg);
void lasers_preload(void) {
preload_resources(RES_SHADER_PROGRAM, RESF_DEFAULT,
"blur25",
"blur5",
"laser_generic",
NULL);
size_t sz_vert = sizeof(GenericModelVertex);
size_t sz_attr = sizeof(LaserInstancedAttribs);
#define VERTEX_OFS(attr) offsetof(GenericModelVertex, attr)
#define INSTANCE_OFS(attr) offsetof(LaserInstancedAttribs, attr)
VertexAttribFormat fmt[] = {
// Per-vertex attributes (for the static models buffer, bound at 0)
{ { 3, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(position), 0 },
{ { 3, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(normal), 0 },
{ { 2, VA_FLOAT, VA_CONVERT_FLOAT, 0 }, sz_vert, VERTEX_OFS(uv), 0 },
// Per-instance attributes (for our own buffer, bound at 1)
// pos and delta packed into a single attribute
{ { 4, VA_FLOAT, VA_CONVERT_FLOAT, 1 }, sz_attr, INSTANCE_OFS(pos), 1 },
};
#undef VERTEX_OFS
#undef INSTANCE_OFS
lasers.vbuf = r_vertex_buffer_create(sizeof(LaserInstancedAttribs) * 4096, NULL);
r_vertex_buffer_set_debug_label(lasers.vbuf, "Lasers vertex buffer");
lasers.varr = r_vertex_array_create();
r_vertex_array_set_debug_label(lasers.varr, "Lasers vertex array");
r_vertex_array_layout(lasers.varr, sizeof(fmt)/sizeof(VertexAttribFormat), fmt);
r_vertex_array_attach_vertex_buffer(lasers.varr, r_vertex_buffer_static_models(), 0);
r_vertex_array_attach_vertex_buffer(lasers.varr, lasers.vbuf, 1);
r_vertex_array_layout(lasers.varr, sizeof(fmt)/sizeof(VertexAttribFormat), fmt);
FBAttachmentConfig cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.attachment = FRAMEBUFFER_ATTACH_COLOR0;
cfg.tex_params.type = TEX_TYPE_RGBA;
cfg.tex_params.filter.min = TEX_FILTER_LINEAR;
cfg.tex_params.filter.mag = TEX_FILTER_LINEAR;
cfg.tex_params.wrap.s = TEX_WRAP_MIRROR;
cfg.tex_params.wrap.t = TEX_WRAP_MIRROR;
lasers.render_fb = stage_add_foreground_framebuffer("Lasers FB", 0.5, 1, 1, &cfg);
ent_hook_pre_draw(lasers_ent_predraw_hook, NULL);
ent_hook_post_draw(lasers_ent_postdraw_hook, NULL);
lasers.quad_generic.indexed = false;
lasers.quad_generic.num_vertices = 4;
lasers.quad_generic.offset = 0;
lasers.quad_generic.primitive = PRIM_TRIANGLE_STRIP;
lasers.quad_generic.vertex_array = lasers.varr;
lasers.shader_generic = r_shader_get("laser_generic");
}
void lasers_free(void) {
r_vertex_array_destroy(lasers.varr);
r_vertex_buffer_destroy(lasers.vbuf);
ent_unhook_pre_draw(lasers_ent_predraw_hook);
ent_unhook_post_draw(lasers_ent_postdraw_hook);
}
static void ent_draw_laser(EntityInterface *ent);
Laser *create_laser(complex pos, float time, float deathtime, const Color *color, LaserPosRule prule, LaserLogicRule lrule, complex a0, complex a1, complex a2, complex a3) {
Laser *l = alist_push(&global.lasers, (Laser*)objpool_acquire(stage_object_pools.lasers));
l->birthtime = global.frames;
l->timespan = time;
l->deathtime = deathtime;
l->pos = pos;
l->color = *color;
l->args[0] = a0;
l->args[1] = a1;
l->args[2] = a2;
l->args[3] = a3;
l->prule = prule;
l->lrule = lrule;
l->shader = NULL;
l->collision_step = 1;
l->width = 10;
l->width_exponent = 1.0;
l->speed = 1;
l->timeshift = 0;
l->next_graze = 0;
l->clear_flags = 0;
l->unclearable = false;
l->ent.draw_layer = LAYER_LASER_HIGH;
l->ent.draw_func = ent_draw_laser;
ent_register(&l->ent, ENT_LASER);
if(l->lrule)
l->lrule(l, EVENT_BIRTH);
l->prule(l, EVENT_BIRTH);
return l;
}
Laser *create_laserline(complex pos, complex dir, float charge, float dur, const Color *clr) {
return create_laserline_ab(pos, (pos)+(dir)*VIEWPORT_H*1.4/cabs(dir), cabs(dir), charge, dur, clr);
}
Laser *create_laserline_ab(complex a, complex b, float width, float charge, float dur, const Color *clr) {
complex m = (b-a)*0.005;
return create_laser(a, 200, dur, clr, las_linear, static_laser, m, charge + I*width, 0, 0);
}
static double laser_graze_width(Laser *l, double *exponent) {
*exponent = 0; // l->width_exponent * 0.5;
return 5 * sqrt(l->width) + 15;
}
static bool draw_laser_instanced_prepare(Laser *l, uint *out_instances, float *out_timeshift) {
float t;
int c;
c = l->timespan;
t = (global.frames - l->birthtime)*l->speed - l->timespan + l->timeshift;
if(t + l->timespan > l->deathtime + l->timeshift)
c += l->deathtime + l->timeshift - (t + l->timespan);
if(t < 0) {
c += t;
t = 0;
}
if(c <= 0) {
return false;
}
*out_instances = c * 2;
*out_timeshift = t;
return true;
}
static void draw_laser_curve_specialized(Laser *l) {
float timeshift;
uint instances;
if(!draw_laser_instanced_prepare(l, &instances, &timeshift)) {
return;
}
r_shader_ptr(l->shader);
r_color(&l->color);
r_uniform_sampler("tex", "part/lasercurve");
r_uniform_vec2_complex("origin", l->pos);
r_uniform_vec2_array_complex("args[0]", 0, 4, l->args);
r_uniform_float("timeshift", timeshift);
r_uniform_float("width", l->width);
r_uniform_float("width_exponent", l->width_exponent);
r_uniform_int("span", instances);
#if 1
r_draw_quad_instanced(instances);
#else
double graze_exp;
r_color(RGBA(0, 0, 0.2, 0.2));
r_uniform_sampler("tex", "part/lasercurve");
r_uniform_float("width", 2 * laser_graze_width(l, &graze_exp));
r_uniform_float("width_exponent", graze_exp);
r_draw_quad_instanced(instances);
r_color(&l->color);
r_uniform_sampler("tex", "part/lasercurve");
r_uniform_float("width", l->width);
r_uniform_float("width_exponent", l->width_exponent);
r_draw_quad_instanced(instances);
#endif
}
static void draw_laser_curve_generic(Laser *l) {
float timeshift;
uint instances;
if(!draw_laser_instanced_prepare(l, &instances, &timeshift)) {
return;
}
r_shader_ptr(lasers.shader_generic);
r_color(&l->color);
r_uniform_sampler("tex", "part/lasercurve");
r_uniform_float("timeshift", timeshift);
r_uniform_float("width", l->width);
r_uniform_float("width_exponent", l->width_exponent);
r_uniform_int("span", instances);
SDL_RWops *stream = r_vertex_buffer_get_stream(lasers.vbuf);
r_vertex_buffer_invalidate(lasers.vbuf);
for(uint i = 0; i < instances; ++i) {
complex pos = l->prule(l, i * 0.5 + timeshift);
complex delta = pos - l->prule(l, i * 0.5 + timeshift - 0.1);
LaserInstancedAttribs attr;
attr.pos[0] = creal(pos);
attr.pos[1] = cimag(pos);
attr.delta[0] = creal(delta);
attr.delta[1] = cimag(delta);
SDL_RWwrite(stream, &attr, sizeof(attr), 1);
}
r_draw_model_ptr(&lasers.quad_generic, instances, 0);
}
static void ent_draw_laser(EntityInterface *ent) {
Laser *laser = ENT_CAST(ent, Laser);
if(laser->shader) {
draw_laser_curve_specialized(laser);
} else {
draw_laser_curve_generic(laser);
}
}
static void lasers_ent_predraw_hook(EntityInterface *ent, void *arg) {
int pp_quality = config_get_int(CONFIG_POSTPROCESS);
if(pp_quality < 1) {
return;
}
if(ent && ent->type == ENT_LASER) {
if(lasers.saved_fb != NULL) {
return;
}
lasers.saved_fb = r_framebuffer_current();
r_framebuffer(lasers.render_fb);
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
} else {
if(lasers.saved_fb == NULL) {
return;
}
FBPair *fbpair = stage_get_fbpair(FBPAIR_FG_AUX);
stage_draw_begin_noshake();
r_framebuffer(lasers.saved_fb);
r_state_push();
if(pp_quality > 1) {
// Ambient glow pass (large kernel)
r_framebuffer(fbpair->back);
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
r_shader("blur25");
r_uniform_vec2("blur_resolution", VIEWPORT_W, VIEWPORT_H);
r_uniform_vec2("blur_direction", 1, 0);
draw_framebuffer_tex(lasers.render_fb, VIEWPORT_W, VIEWPORT_H);
fbpair_swap(fbpair);
r_framebuffer(lasers.saved_fb);
r_uniform_vec2("blur_direction", 0, 1);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
}
// Smoothed laser curves pass (small kernel)
r_framebuffer(fbpair->back);
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
r_shader("blur5");
r_uniform_vec2("blur_resolution", VIEWPORT_W, VIEWPORT_H);
r_uniform_vec2("blur_direction", 1, 0);
draw_framebuffer_tex(lasers.render_fb, VIEWPORT_W, VIEWPORT_H);
fbpair_swap(fbpair);
r_uniform_vec2("blur_direction", 0, 1);
if(pp_quality < 2) {
// Since the aux framebuffers are smaller in this case, it's actually
// a bit faster to finish the blur pass there, and then blend it onto
// the main Framebuffer.
r_framebuffer(fbpair->back);
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
fbpair_swap(fbpair);
r_shader_standard();
}
r_framebuffer(lasers.saved_fb);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
r_state_pop();
stage_draw_end_noshake();
lasers.saved_fb = NULL;
}
}
static void lasers_ent_postdraw_hook(EntityInterface *ent, void *arg) {
if(ent == NULL) {
// Edge case when the last entity drawn is a laser
lasers_ent_predraw_hook(NULL, arg);
}
}
static void* _delete_laser(ListAnchor *lasers, List *laser, void *arg) {
Laser *l = (Laser*)laser;
if(l->lrule)
l->lrule(l, EVENT_DEATH);
ent_unregister(&l->ent);
objpool_release(stage_object_pools.lasers, alist_unlink(lasers, laser));
return NULL;
}
static void delete_laser(LaserList *lasers, Laser *laser) {
_delete_laser((ListAnchor*)lasers, (List*)laser, NULL);
}
void delete_lasers(void) {
alist_foreach(&global.lasers, _delete_laser, NULL);
}
bool laser_is_active(Laser *l) {
return l->width > 3.0;
}
bool laser_is_clearable(Laser *l) {
return !l->unclearable && laser_is_active(l);
}
bool clear_laser(Laser *l, uint flags) {
if(!(flags & CLEAR_HAZARDS_FORCE) && !laser_is_clearable(l)) {
return false;
}
l->clear_flags |= flags;
return true;
}
static bool laser_collision(Laser *l);
void process_lasers(void) {
Laser *laser = global.lasers.first, *del = NULL;
bool stage_cleared = stage_is_cleared();
while(laser != NULL) {
if(stage_cleared) {
clear_laser(laser, CLEAR_HAZARDS_LASERS | CLEAR_HAZARDS_FORCE);
}
if(laser->clear_flags & CLEAR_HAZARDS_LASERS) {
// TODO: implement CLEAR_HAZARDS_NOW
laser->timespan *= 0.9;
bool kill_now = laser->timespan < 5;
if(!((global.frames - laser->birthtime) % 2) || kill_now) {
double t = max(0, (global.frames - laser->birthtime)*laser->speed - laser->timespan + laser->timeshift);
complex p = laser->prule(laser, t);
double x = creal(p);
double y = cimag(p);
if(x > 0 && x < VIEWPORT_W && y > 0 && y < VIEWPORT_H) {
create_clear_item(p, laser->clear_flags);
}
if(kill_now) {
PARTICLE(
.sprite = "flare",
.pos = p,
.timeout = 20,
.draw_rule = GrowFade
);
laser->deathtime = 0;
}
}
} else {
if(laser_collision(laser)) {
ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_SHOT });
}
if(laser->lrule) {
laser->lrule(laser, global.frames - laser->birthtime);
}
}
if(global.frames - laser->birthtime > laser->deathtime + laser->timespan*laser->speed) {
del = laser;
laser = laser->next;
delete_laser(&global.lasers, del);
} else {
laser = laser->next;
}
}
}
static inline bool laser_collision_segment(Laser *l, LineSegment *segment, Circle *collision_area, float t, float width_tail_factor, float tail) {
float t1 = t - l->timespan / 2;
float widthfac_orig = width_tail_factor * (t1 - tail) * (t1 + tail);
float widthfac = max(0.25, pow(widthfac_orig, l->width_exponent));
segment->b = l->prule(l, t);
collision_area->radius = widthfac * l->width * 0.5 + 1;
if(lineseg_circle_intersect(*segment, *collision_area) >= 0) {
return true;
}
if(global.frames >= l->next_graze && global.frames - abs(global.plr.recovery) > 0) {
double exponent;
collision_area->radius = laser_graze_width(l, &exponent) * max(0.25, pow(widthfac_orig, exponent));
assert(collision_area->radius > 0);
float f = lineseg_circle_intersect(*segment, *collision_area);
if(f >= 0) {
player_graze(&global.plr, segment->a + f * (segment->b - segment->a), 7, 5, &l->color);
l->next_graze = global.frames + 4;
}
}
segment->a = segment->b;
return false;
}
static bool laser_collision(Laser *l) {
if(!laser_is_active(l)) {
return false;
}
float t_end_len = (global.frames - l->birthtime) * l->speed + l->timeshift; // end of the laser based on length
float t_end_lifetime = l->deathtime * l->speed + l->timeshift; // end of the laser based on lifetime
float t = t_end_len - l->timespan;
float t_end = fmin(t_end_len, t_end_lifetime);
if(t < 0) {
t = 0;
}
LineSegment segment = { .a = l->prule(l,t) };
Circle collision_area = { .origin = global.plr.pos };
float tail = l->timespan / 1.9;
float width_tail_factor = -0.75 / (tail * tail);
for(t += l->collision_step; t <= t_end; t += l->collision_step) {
if(laser_collision_segment(l, &segment, &collision_area, t, width_tail_factor, tail)) {
return true;
}
}
return laser_collision_segment(l, &segment, &collision_area, t_end, width_tail_factor, tail);
}
bool laser_intersects_ellipse(Laser *l, Ellipse ellipse) {
// NOTE: this function does not take laser width into account
float t_end_len = (global.frames - l->birthtime) * l->speed + l->timeshift; // end of the laser based on length
float t_end_lifetime = l->deathtime * l->speed + l->timeshift; // end of the laser based on lifetime
float t = t_end_len - l->timespan;
float t_end = fmin(t_end_len, t_end_lifetime);
if(t < 0) {
t = 0;
}
LineSegment segment = { .a = l->prule(l, t) };
for(t += l->collision_step; t <= t_end; t += l->collision_step) {
segment.b = l->prule(l, t);
if(lineseg_ellipse_intersect(segment, ellipse)) {
return true;
}
segment.a = segment.b;
}
segment.b = l->prule(l, t_end);
return lineseg_ellipse_intersect(segment, ellipse);
}
bool laser_intersects_circle(Laser *l, Circle circle) {
Ellipse ellipse = {
.origin = circle.origin,
.axes = circle.radius * 2 * (1 + I),
};
return laser_intersects_ellipse(l, ellipse);
}
complex las_linear(Laser *l, float t) {
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/linear");
l->collision_step = max(3, l->timespan/10);
return 0;
}
return l->pos + l->args[0]*t;
}
complex las_accel(Laser *l, float t) {
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/accelerated");
l->collision_step = max(3, l->timespan/10);
return 0;
}
return l->pos + l->args[0]*t + 0.5*l->args[1]*t*t;
}
complex las_weird_sine(Laser *l, float t) { // [0] = velocity; [1] = sine amplitude; [2] = sine frequency; [3] = sine phase
// XXX: this used to be called "las_sine", but it's actually not a proper sine wave
// do we even still need this?
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/weird_sine");
return 0;
}
double s = (l->args[2] * t + l->args[3]);
return l->pos + cexp(I * (carg(l->args[0]) + l->args[1] * sin(s) / s)) * t * cabs(l->args[0]);
}
complex las_sine(Laser *l, float t) { // [0] = velocity; [1] = sine amplitude; [2] = sine frequency; [3] = sine phase
// this is actually shaped like a sine wave
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/sine");
return 0;
}
complex line_vel = l->args[0];
complex line_dir = line_vel / cabs(line_vel);
complex line_normal = cimag(line_dir) - I*creal(line_dir);
complex sine_amp = l->args[1];
complex sine_freq = l->args[2];
complex sine_phase = l->args[3];
complex sine_ofs = line_normal * sine_amp * sin(sine_freq * t + sine_phase);
return l->pos + t * line_vel + sine_ofs;
}
complex las_sine_expanding(Laser *l, float t) { // [0] = velocity; [1] = sine amplitude; [2] = sine frequency; [3] = sine phase
// XXX: this is also a "weird" one
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/sine_expanding");
return 0;
}
complex velocity = l->args[0];
double amplitude = creal(l->args[1]);
double frequency = creal(l->args[2]);
double phase = creal(l->args[3]);
double angle = carg(velocity);
double speed = cabs(velocity);
double s = (frequency * t + phase);
return l->pos + cexp(I * (angle + amplitude * sin(s))) * t * speed;
}
complex las_turning(Laser *l, float t) { // [0] = vel0; [1] = vel1; [2] r: turn begin time, i: turn end time
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/turning");
return 0;
}
complex v0 = l->args[0];
complex v1 = l->args[1];
float begin = creal(l->args[2]);
float end = cimag(l->args[2]);
float a = clamp((t - begin) / (end - begin), 0, 1);
a = 1.0 - (0.5 + 0.5 * cos(a * M_PI));
a = 1.0 - pow(1.0 - a, 2);
complex v = v1 * a + v0 * (1 - a);
return l->pos + v * t;
}
complex las_circle(Laser *l, float t) {
if(t == EVENT_BIRTH) {
l->shader = r_shader_get_optional("lasers/circle");
return 0;
}
// XXX: should turn speed be in rad/sec or rad/frame? currently rad/sec.
double turn_speed = creal(l->args[0]) / 60;
double time_ofs = cimag(l->args[0]);
double radius = creal(l->args[1]);
return l->pos + radius * cexp(I * (t + time_ofs) * turn_speed);
}
float laser_charge(Laser *l, int t, float charge, float width) {
if(t < charge - 10) {
return min(2, 2 * t / min(30, charge - 10));
}
if(t >= charge - 10 && t < l->deathtime - 20) {
float w = 1.7 + width/20*(t-charge+10);
return w < width ? w : width;
}
if(t >= l->deathtime - 20) {
float w = width - width/20*(t-l->deathtime+20);
return w > 0 ? w : 0;
}
return width;
}
void static_laser(Laser *l, int t) {
if(t == EVENT_BIRTH) {
l->width = 0;
l->speed = 0;
l->timeshift = l->timespan;
return;
}
l->width = laser_charge(l, t, creal(l->args[1]), cimag(l->args[1]));
}