775 lines
18 KiB
C
775 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 "internal.h"
|
|
#include "draw.h"
|
|
|
|
#include "global.h"
|
|
#include "list.h"
|
|
#include "stageobjects.h"
|
|
#include "stagedraw.h"
|
|
#include "renderer/api.h"
|
|
#include "resource/model.h"
|
|
#include "util/fbmgr.h"
|
|
#include "util/glm.h"
|
|
#include "video.h"
|
|
|
|
typedef struct LaserSamplingParams {
|
|
uint num_samples;
|
|
float time_shift;
|
|
float time_step;
|
|
} LaserSamplingParams;
|
|
|
|
void lasers_init(void) {
|
|
laserintern_init();
|
|
laserdraw_init();
|
|
}
|
|
|
|
void lasers_shutdown(void) {
|
|
laserdraw_shutdown();
|
|
laserintern_shutdown();
|
|
}
|
|
|
|
Laser *create_laser(
|
|
cmplx pos, float time, float deathtime, const Color *color,
|
|
LaserPosRule prule,
|
|
cmplx a0, cmplx a1, cmplx a2, cmplx a3
|
|
) {
|
|
Laser *l = objpool_acquire(&stage_object_pools.lasers);
|
|
alist_push(&global.lasers, l);
|
|
|
|
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->width = 10;
|
|
l->width_exponent = 1.0;
|
|
l->speed = 1;
|
|
l->collision_active = true;
|
|
|
|
l->ent.draw_layer = LAYER_LASER_HIGH;
|
|
l->ent.draw_func = laserdraw_ent_drawfunc;
|
|
ent_register(&l->ent, ENT_TYPE_ID(Laser));
|
|
|
|
l->prule(l, EVENT_BIRTH);
|
|
|
|
return l;
|
|
}
|
|
|
|
Laser *create_laserline(cmplx pos, cmplx 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(cmplx a, cmplx b, float width, float charge, float dur, const Color *clr) {
|
|
float lt = 200;
|
|
cmplx m = (b - a) / lt;
|
|
Laser *l = create_laser(a, lt, dur, clr, las_linear, m, 0, 0, 0);
|
|
INVOKE_TASK(laser_charge,
|
|
.laser = ENT_BOX(l),
|
|
.charge_delay = charge,
|
|
.target_width = width,
|
|
);
|
|
return l;
|
|
}
|
|
|
|
void laserline_set_ab(Laser *l, cmplx a, cmplx b) {
|
|
l->pos = a;
|
|
l->args[0] = (b - a) / l->timespan;
|
|
}
|
|
|
|
void laserline_set_posdir(Laser *l, cmplx pos, cmplx dir) {
|
|
laserline_set_ab(l, pos, pos + VIEWPORT_H * cnormalize(dir));
|
|
}
|
|
|
|
static void *_delete_laser(ListAnchor *lasers, List *laser, void *arg) {
|
|
Laser *l = (Laser*)laser;
|
|
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;
|
|
return l->collision_active;
|
|
}
|
|
|
|
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_prepare_sampling_params(Laser *l, float step, LaserSamplingParams *out_params) {
|
|
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_params->num_samples = c / step;
|
|
out_params->time_shift = t;
|
|
out_params->time_step = step;
|
|
|
|
return true;
|
|
}
|
|
|
|
static float calc_sample_width(
|
|
Laser *l, float sample, float half_samples, float width_factor, float tail
|
|
) {
|
|
float mid_ofs = sample - half_samples;
|
|
float w = width_factor * (mid_ofs - tail) * (mid_ofs + tail);
|
|
|
|
if(l->width_exponent != 1.0) {
|
|
w = powf(w, l->width_exponent);
|
|
}
|
|
|
|
return 0.75f * l->width * w;
|
|
}
|
|
|
|
static void laserseg_flip(LaserSegment *s) {
|
|
SWAP(s->pos.a, s->pos.b);
|
|
SWAP(s->time.a, s->time.b);
|
|
SWAP(s->width.a, s->width.b);
|
|
}
|
|
|
|
attr_hot
|
|
static int quantize_laser(Laser *l) {
|
|
// Break the laser curve into small line segments, simplify and cull them,
|
|
// compute the bounding box.
|
|
|
|
l->_internal.segments_ofs = lintern.segments.num_elements;
|
|
l->_internal.num_segments = 0;
|
|
|
|
LaserSamplingParams sp;
|
|
|
|
if(!laser_prepare_sampling_params(l, 0.5f, &sp)) {
|
|
l->_internal.bbox.top_left.as_cmplx = 0;
|
|
l->_internal.bbox.bottom_right.as_cmplx = 0;
|
|
return 0;
|
|
}
|
|
|
|
// Precomputed magic parameters for width calculation
|
|
float half_samples = sp.num_samples * 0.5;
|
|
float tail = sp.num_samples / 1.6;
|
|
float width_factor = -1 / (tail * tail);
|
|
|
|
// Maximum value of `1 - cos(angle)` between two curve segments to reduce to straight lines
|
|
const float thres_angular = 1e-4;
|
|
// Maximum laser-time sample difference between two segment points (for width interpolation)
|
|
const float thres_temporal = sp.num_samples / 16.0;
|
|
// These values should be kept as high as possible without introducing artifacts.
|
|
|
|
// Time value of current sample
|
|
float t = sp.time_shift;
|
|
|
|
// Time value of last included sample
|
|
float t0 = t;
|
|
|
|
// Points of the current line segment
|
|
// Begin constructing at t0
|
|
// WARNING: these must be double precision to prevent cross-platform replay desync
|
|
cmplx a, b;
|
|
a = l->prule(l, t0);
|
|
|
|
// Width value of the last included sample
|
|
// Initialized to the width at t0
|
|
float w0 = calc_sample_width(l, 0, half_samples, width_factor, tail);
|
|
|
|
// Already sampled the first point, so shift
|
|
t += sp.time_step;
|
|
|
|
// Vector from A to B of the last included segment, and its squared length.
|
|
cmplxf v0 = a - l->prule(l, t0 - sp.time_step);
|
|
float v0_abs2 = cabs2f(v0);
|
|
|
|
float viewmargin = l->width * 0.5f;
|
|
FloatRect viewbounds = { .extent = VIEWPORT_SIZE };
|
|
viewbounds.w += viewmargin * 2.0f;
|
|
viewbounds.h += viewmargin * 2.0f;
|
|
viewbounds.x -= viewmargin;
|
|
viewbounds.y -= viewmargin;
|
|
|
|
FloatOffset top_left, bottom_right;
|
|
top_left.as_cmplx = a;
|
|
bottom_right.as_cmplx = a;
|
|
|
|
for(uint i = 1; i < sp.num_samples; ++i, t += sp.time_step) {
|
|
b = l->prule(l, t);
|
|
|
|
if(i < sp.num_samples - 1 && (t - t0) < thres_temporal) {
|
|
cmplxf v1 = b - a;
|
|
|
|
// dot(a, b) == |a|*|b|*cos(theta)
|
|
float dot = cdotf(v0, v1);
|
|
float norm2 = v0_abs2 * cabs2f(v1);
|
|
|
|
if(norm2 == 0.0f) {
|
|
// degenerate case
|
|
continue;
|
|
}
|
|
|
|
float norm = sqrtf(norm2);
|
|
float cosTheta = dot / norm;
|
|
float d = 1.0f - fabsf(cosTheta);
|
|
|
|
if(d < thres_angular) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
float w = calc_sample_width(l, i, half_samples, width_factor, tail);
|
|
|
|
float xa = re(a);
|
|
float ya = im(a);
|
|
float xb = re(b);
|
|
float yb = im(b);
|
|
|
|
bool visible =
|
|
(xa > viewbounds.x && xa < viewbounds.w && ya > viewbounds.y && ya < viewbounds.h) ||
|
|
(xb > viewbounds.x && xb < viewbounds.w && yb > viewbounds.y && yb < viewbounds.h);
|
|
|
|
if(visible) {
|
|
LaserSegment *seg = dynarray_append(&lintern.segments, {
|
|
.pos = { a, b },
|
|
.width = { w0, w },
|
|
.time = { -t0, -t },
|
|
});
|
|
|
|
if(w < w0) {
|
|
// NOTE: the uneven capsule distance function may not work correctly in cases where
|
|
// radius(A) > radius(B) and circle A contains circle B.
|
|
laserseg_flip(seg);
|
|
}
|
|
|
|
assert(seg->width.a <= seg->width.b);
|
|
|
|
top_left.x = min( top_left.x, min(xa, xb));
|
|
top_left.y = min( top_left.y, min(ya, yb));
|
|
bottom_right.x = max(bottom_right.x, max(xa, xb));
|
|
bottom_right.y = max(bottom_right.y, max(ya, yb));
|
|
}
|
|
|
|
t0 = t;
|
|
w0 = w;
|
|
v0 = b - a;
|
|
v0_abs2 = cabs2f(v0);
|
|
a = b;
|
|
}
|
|
|
|
float aabb_margin = LASER_SDF_RANGE + l->width * 0.5f;
|
|
top_left.as_cmplx -= aabb_margin * (1.0f + I);
|
|
bottom_right.as_cmplx += aabb_margin * (1.0f + I);
|
|
|
|
l->_internal.bbox.top_left = top_left;
|
|
l->_internal.bbox.bottom_right = bottom_right;
|
|
|
|
l->_internal.num_segments = lintern.segments.num_elements - l->_internal.segments_ofs;
|
|
return l->_internal.num_segments;
|
|
}
|
|
|
|
static bool laser_collision(Laser *l, Player *plr);
|
|
|
|
typedef struct LaserTraceState {
|
|
Laser *l;
|
|
LaserTraceFunc func;
|
|
void *userdata;
|
|
LaserSegment *seg;
|
|
LaserTraceSample sample;
|
|
cmplx p;
|
|
real step;
|
|
real accum;
|
|
real inverse_seglen;
|
|
} LaserTraceState;
|
|
|
|
static void *laser_trace_dispatch(LaserTraceState *st) {
|
|
return st->func(st->l, &st->sample, st->userdata);
|
|
}
|
|
|
|
static void *laser_trace_advance(LaserTraceState *st, cmplx v, real l) {
|
|
real full = l;
|
|
l = min(l, st->step - st->accum);
|
|
|
|
st->accum += l;
|
|
st->sample.segment_param += l * st->inverse_seglen;
|
|
st->p += v * l;
|
|
|
|
if(st->accum >= st->step) {
|
|
st->accum -= st->step;
|
|
st->sample.pos = st->p;
|
|
|
|
void *result = laser_trace_dispatch(st);
|
|
|
|
if(result) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if(full - l > 0) {
|
|
return laser_trace_advance(st, v, full - l);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void *laser_trace(Laser *l, real step, LaserTraceFunc trace, void *userdata) {
|
|
if(l->_internal.num_segments < 1) {
|
|
return NULL;
|
|
}
|
|
|
|
int first_seg = l->_internal.segments_ofs;
|
|
int last_seg = first_seg + l->_internal.num_segments - 1;
|
|
|
|
LaserTraceState st = {
|
|
.l = l,
|
|
.step = step,
|
|
.func = trace,
|
|
.userdata = userdata,
|
|
.p = dynarray_get(&lintern.segments, first_seg).pos.a,
|
|
};
|
|
|
|
void *result;
|
|
|
|
cmplx prev_endpos = INFINITY;
|
|
|
|
for(int seg = first_seg; seg <= last_seg; ++seg) {
|
|
// NOTE: deliberate copy
|
|
LaserSegment s = dynarray_get(&lintern.segments, seg);
|
|
|
|
if(prev_endpos != s.pos.a && prev_endpos == s.pos.b) {
|
|
// Segment was flipped (see quantize_laser); undo it
|
|
laserseg_flip(&s);
|
|
}
|
|
|
|
cmplx v = s.pos.b - s.pos.a;
|
|
real len = cabs(v);
|
|
st.inverse_seglen = 1 / len;
|
|
v *= st.inverse_seglen;
|
|
|
|
st.sample.segment = &s;
|
|
st.sample.segment_param = 0;
|
|
|
|
if(prev_endpos != s.pos.a) {
|
|
// discontinuity, or first segment.
|
|
st.p = s.pos.a;
|
|
st.accum = 0;
|
|
st.sample.discontinuous = true;
|
|
st.sample.pos = st.p;
|
|
result = laser_trace_dispatch(&st);
|
|
|
|
if(result) {
|
|
return result;
|
|
}
|
|
|
|
st.sample.discontinuous = false;
|
|
}
|
|
|
|
real pstep = step * 1;
|
|
while(len >= pstep) {
|
|
result = laser_trace_advance(&st, v, pstep);
|
|
|
|
if(result) {
|
|
return result;
|
|
}
|
|
|
|
len -= pstep;
|
|
}
|
|
|
|
laser_trace_advance(&st, v, len);
|
|
prev_endpos = s.pos.b;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void laser_clear_effect(Sprite *spr, cmplx p, cmplxf scale, const Color *clr) {
|
|
int timeout = rng_irange(18, 24);
|
|
cmplx v = rng_dir();
|
|
v *= rng_range(0.4, 1.2);
|
|
PARTICLE(
|
|
.sprite_ptr = spr,
|
|
.pos = p,
|
|
.color = clr,
|
|
.timeout = timeout,
|
|
.move = move_linear(v),
|
|
.draw_rule = pdraw_timeout_scalefade(1+I, 0.25+0.5i, 1, 0),
|
|
.flags = PFLAG_NOREFLECT,
|
|
.scale = scale,
|
|
);
|
|
}
|
|
|
|
#define CLEAR_STEP 16
|
|
|
|
typedef struct LaserClearTraceCtx {
|
|
struct {
|
|
Sprite *spr;
|
|
Color clr;
|
|
} particle;
|
|
|
|
struct {
|
|
cmplx pos;
|
|
float width;
|
|
} prev;
|
|
} LaserClearTraceCtx;
|
|
|
|
static void *laser_clear_now_tracefunc(Laser *l, const LaserTraceSample *sample, void *userdata) {
|
|
LaserClearTraceCtx *ctx = userdata;
|
|
cmplx pos = sample->pos;
|
|
float width = lerpf(sample->segment->width.a, sample->segment->width.b, sample->segment_param);
|
|
create_clear_item(pos, l->clear_flags);
|
|
|
|
if(!sample->discontinuous) {
|
|
for(float f = 0.33; f < 0.9; f += 0.33) {
|
|
cmplx ipos = clerp(ctx->prev.pos, pos, f);
|
|
float iwidth = lerpf(ctx->prev.width, width, f);
|
|
laser_clear_effect(
|
|
ctx->particle.spr, ipos, iwidth / ctx->particle.spr->w, &ctx->particle.clr);
|
|
}
|
|
}
|
|
|
|
laser_clear_effect(ctx->particle.spr, pos, width / ctx->particle.spr->w, &ctx->particle.clr);
|
|
ctx->prev.pos = pos;
|
|
ctx->prev.width = width;
|
|
return NULL;
|
|
}
|
|
|
|
static void laser_clear_now(Laser *l) {
|
|
LaserClearTraceCtx ctx;
|
|
ctx.particle.spr = res_sprite("part/flare");
|
|
ctx.particle.clr = l->color;
|
|
color_mul(&ctx.particle.clr, RGBA(2, 2, 2, 0));
|
|
color_add(&ctx.particle.clr, RGBA(0.1, 0.1, 0.1, 0));
|
|
laser_trace(l, CLEAR_STEP, laser_clear_now_tracefunc, &ctx);
|
|
}
|
|
|
|
void process_lasers(void) {
|
|
bool stage_cleared = stage_is_cleared();
|
|
Player *plr = &global.plr;
|
|
|
|
lintern.segments.num_elements = 0;
|
|
|
|
/*
|
|
* NOTE: it's important to have two loops here, because something triggered from ent_damage()
|
|
* may try poking laser segment data before it's initialized by quantize_laser().
|
|
* For example, dying to a laser while having a surge field active will immediately trigger a
|
|
* discharge and try to cancel all lasers in a circle.
|
|
*/
|
|
|
|
for(Laser *laser = global.lasers.first, *next; laser; laser = next) {
|
|
next = laser->next;
|
|
|
|
if(global.frames - laser->birthtime > laser->deathtime + laser->timespan * laser->speed) {
|
|
delete_laser(&global.lasers, laser);
|
|
continue;
|
|
}
|
|
|
|
quantize_laser(laser);
|
|
|
|
if(stage_cleared) {
|
|
clear_laser(laser, CLEAR_HAZARDS_LASERS | CLEAR_HAZARDS_FORCE);
|
|
}
|
|
}
|
|
|
|
for(Laser *laser = global.lasers.first, *next; laser; laser = next) {
|
|
next = laser->next;
|
|
|
|
if(laser->clear_flags & CLEAR_HAZARDS_LASERS) {
|
|
laser_clear_now(laser);
|
|
laser->deathtime = 0;
|
|
} else if(laser_collision(laser, plr)) {
|
|
ent_damage(&plr->ent, &(DamageInfo) { .type = DMG_ENEMY_SHOT });
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline Rect laser_bbox_rect(Laser *l) {
|
|
return (Rect) {
|
|
l->_internal.bbox.top_left.as_cmplx,
|
|
l->_internal.bbox.bottom_right.as_cmplx
|
|
};
|
|
}
|
|
|
|
static bool laser_collision(Laser *l, Player *plr) {
|
|
if(!laser_is_active(l)) {
|
|
return false;
|
|
}
|
|
|
|
int num_segs = l->_internal.num_segments;
|
|
|
|
if(num_segs < 1) {
|
|
return false;
|
|
}
|
|
|
|
bool graze = global.frames >= l->next_graze;
|
|
|
|
double graze_maxdist = 42;
|
|
double graze_dist = graze_maxdist;
|
|
cmplx graze_pos = 0;
|
|
|
|
Rect bbox = laser_bbox_rect(l);
|
|
|
|
if(graze) {
|
|
cmplx graze_bbox_ofs = graze_dist * (1 + I);
|
|
bbox.top_left -= graze_bbox_ofs;
|
|
bbox.bottom_right += graze_bbox_ofs;
|
|
}
|
|
|
|
if(!point_in_rect(plr->pos, bbox)) {
|
|
return false;
|
|
}
|
|
|
|
LaserSegment *segs = dynarray_get_ptr(&lintern.segments, l->_internal.segments_ofs);
|
|
|
|
LineSegment plrmotion;
|
|
cmplx plrpos = plr->pos;
|
|
bool player_moved = false;
|
|
|
|
if(plr->velocity != 0) {
|
|
player_moved = true;
|
|
plrmotion.a = plrpos - plr->velocity;
|
|
plrmotion.b = plrpos;
|
|
}
|
|
|
|
for(int i = 0; i < num_segs; ++i) {
|
|
LaserSegment *lseg = segs + i;
|
|
LineSegment s = { lseg->pos.a, lseg->pos.b };
|
|
|
|
if(player_moved && lineseg_lineseg_intersection(plrmotion, s, NULL)) {
|
|
// Prevent phasing through laser beams
|
|
return true;
|
|
}
|
|
|
|
UnevenCapsule c = {
|
|
.pos = s,
|
|
.radius.a = max(lseg->width.a * 0.5 - 4, 2),
|
|
.radius.b = max(lseg->width.b * 0.5 - 4, 2),
|
|
};
|
|
|
|
double d = ucapsule_dist_from_point(plrpos, c);
|
|
|
|
if(d < 0) {
|
|
return true;
|
|
}
|
|
|
|
if(graze && d < graze_dist) {
|
|
double f = lineseg_closest_factor(c.pos, plrpos);
|
|
graze_pos = clerp(c.pos.a, c.pos.b, f);
|
|
cmplx v = cnormalize(plrpos - graze_pos);
|
|
graze_pos += 0.5 * clerp(lseg->width.a, lseg->width.b, f) * v;
|
|
graze_dist = d;
|
|
}
|
|
|
|
}
|
|
|
|
if(graze_dist < graze_maxdist) {
|
|
player_graze(plr, graze_pos, 7, 5, &l->color);
|
|
l->next_graze = global.frames + 4;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool laser_intersects_ellipse(Laser *l, Ellipse ellipse) {
|
|
// NOTE: This function does not take laser width into account.
|
|
// It also can't test culled parts of the laser, because culling
|
|
// is done at the quantization stage.
|
|
// But surely this won't ever be a problem, right…?
|
|
|
|
int num_segs = l->_internal.num_segments;
|
|
|
|
if(num_segs < 1) {
|
|
return false;
|
|
}
|
|
|
|
Rect e_bbox = ellipse_bbox(ellipse);
|
|
Rect l_bbox = laser_bbox_rect(l);
|
|
|
|
if(!rect_rect_intersect(e_bbox, l_bbox, true, true)) {
|
|
return false;
|
|
}
|
|
|
|
LaserSegment *segs = dynarray_get_ptr(&lintern.segments, l->_internal.segments_ofs);
|
|
|
|
for(int i = 0; i < num_segs; ++i) {
|
|
LaserSegment *lseg = segs + i;
|
|
LineSegment s = { lseg->pos.a, lseg->pos.b };
|
|
|
|
if(lineseg_ellipse_intersect(s, ellipse)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
cmplx las_linear(Laser *l, float t) {
|
|
if(t == EVENT_BIRTH) {
|
|
return 0;
|
|
}
|
|
|
|
return l->pos + l->args[0]*t;
|
|
}
|
|
|
|
cmplx las_accel(Laser *l, float t) {
|
|
if(t == EVENT_BIRTH) {
|
|
return 0;
|
|
}
|
|
|
|
return l->pos + l->args[0]*t + 0.5*l->args[1]*t*t;
|
|
}
|
|
|
|
cmplx 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) {
|
|
return 0;
|
|
}
|
|
|
|
cmplx line_vel = l->args[0];
|
|
cmplx line_dir = line_vel / cabs(line_vel);
|
|
cmplx line_normal = im(line_dir) - I*re(line_dir);
|
|
cmplx sine_amp = l->args[1];
|
|
real sine_freq = re(l->args[2]);
|
|
real sine_phase = re(l->args[3]);
|
|
|
|
cmplx sine_ofs = line_normal * sine_amp * sin(sine_freq * t + sine_phase);
|
|
return l->pos + t * line_vel + sine_ofs;
|
|
}
|
|
|
|
cmplx las_sine_expanding(Laser *l, float t) { // [0] = velocity; [1] = sine amplitude; [2] = sine frequency; [3] = sine phase
|
|
|
|
if(t == EVENT_BIRTH) {
|
|
return 0;
|
|
}
|
|
|
|
cmplx velocity = l->args[0];
|
|
real amplitude = re(l->args[1]);
|
|
real frequency = re(l->args[2]);
|
|
real phase = re(l->args[3]);
|
|
|
|
real angle = carg(velocity);
|
|
real speed = cabs(velocity);
|
|
|
|
real s = (frequency * t + phase);
|
|
return l->pos + cdir(angle + amplitude * sin(s)) * t * speed;
|
|
}
|
|
|
|
cmplx las_turning(Laser *l, float t) { // [0] = vel0; [1] = vel1; [2] r: turn begin time, i: turn end time
|
|
if(t == EVENT_BIRTH) {
|
|
return 0;
|
|
}
|
|
|
|
cmplx v0 = l->args[0];
|
|
cmplx v1 = l->args[1];
|
|
float begin = re(l->args[2]);
|
|
float end = im(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);
|
|
|
|
cmplx v = v1 * a + v0 * (1 - a);
|
|
|
|
return l->pos + v * t;
|
|
}
|
|
|
|
cmplx las_circle(Laser *l, float t) {
|
|
if(t == EVENT_BIRTH) {
|
|
return 0;
|
|
}
|
|
|
|
real turn_speed = re(l->args[0]);
|
|
real time_ofs = im(l->args[0]);
|
|
real radius = re(l->args[1]);
|
|
|
|
return l->pos + radius * cdir(turn_speed * (t + time_ofs));
|
|
}
|
|
|
|
void laser_charge(Laser *l, int t, float charge, float width) {
|
|
float new_width;
|
|
|
|
if(t < charge - 10) {
|
|
new_width = min(2.0f, 2.0f * t / min(30.0f, charge - 10.0f));
|
|
} else if(t >= charge - 10.0f && t < l->deathtime - 20.0f) {
|
|
new_width = min(width, 1.7f + width / 20.0f * (t - charge + 10.0f));
|
|
} else if(t >= l->deathtime - 20.0f) {
|
|
new_width = max(0.0f, width - width / 20.0f * (t - l->deathtime + 20.0f));
|
|
} else {
|
|
new_width = width;
|
|
}
|
|
|
|
l->width = new_width;
|
|
l->collision_active = (new_width > width * 0.6f);
|
|
}
|
|
|
|
void laser_make_static(Laser *l) {
|
|
l->speed = 0;
|
|
l->timeshift = l->timespan;
|
|
}
|
|
|
|
DEFINE_EXTERN_TASK(laser_charge) {
|
|
Laser *l = TASK_BIND(ARGS.laser);
|
|
|
|
l->width = 0;
|
|
l->collision_active = false;
|
|
laser_make_static(l);
|
|
|
|
float target_width = ARGS.target_width;
|
|
float charge_delay = ARGS.charge_delay;
|
|
|
|
// TODO: stop when done charging
|
|
for(int t = 0;; ++t) {
|
|
laser_charge(l, t, charge_delay, target_width);
|
|
YIELD;
|
|
}
|
|
}
|