vpyfx — sparks and debris
vpyfx draws effects with one stroke per piece. There are two kinds of piece:
- A spark is a point drawn as a streak along its velocity.
- A stick is a rigid segment that moves and spins.
The main calls:
vpyfx_shatter()turns every edge of a mesh into a stick, thrown out from the point of the blow. This is the classic vector explosion, and it costs exactly the edges the object had.vpyfx_disintegrate()is the finer version. Every edge is cut into short pieces that wait in place until a wave from the hit reaches them, so the object comes apart from where it was struck.vpyfx_assemble()runs that in reverse: pieces fly in and settle exactly on the edges, one edge after another.
To use it, add $(VPY_C_SDK)/vpyfx.c to UVM2_SRCS. It also needs vpy3d, because it reads each mesh's edges with vpy3d_mesh_edge.
void vpyfx_reset(void); void vpyfx_seed(uint32_t s);
void vpyfx_set_gravity(int32_t gx,int32_t gy,int32_t gz); /* units/s² */
void vpyfx_set_floor(int on,int32_t y,int bounce_q8);
void vpyfx_set_budget(int strokes_per_frame); /* default 160 */
int vpyfx_burst(int32_t x,int32_t y,int32_t z, int32_t vx,int32_t vy,int32_t vz,
int count,int32_t speed,int life,int br);
int vpyfx_shatter(const vpy_mesh *m,const vpy_xf *place, int32_t vx,int32_t vy,int32_t vz,
int32_t cx,int32_t cy,int32_t cz, int32_t speed,int32_t spin,int life,int br);
int vpyfx_disintegrate(const vpy_mesh *m,const vpy_xf *place, int32_t cx,int32_t cy,int32_t cz,
int per_edge,int32_t speed,int32_t spin,int32_t wave,int life,int br);
int vpyfx_assemble(const vpy_mesh *m,const vpy_xf *place, int per_edge,int32_t scatter,
int frames,int stagger,int br); /* returns a group id */
int vpyfx_assembled(int group); void vpyfx_release(int group);
int vpyfx_ring(int32_t cx,int32_t cy,int32_t cz, int32_t nx,int32_t ny,int32_t nz,
int32_t r0,int32_t speed,int segments,int life,int br); /* a visible shockwave */
int vpyfx_line(int32_t ax,int32_t ay,int32_t az, int32_t bx,int32_t by,int32_t bz,
int life,int br); /* stays and fades: a trail */
void vpyfx_step(void);
void vpyfx_draw(int occlude); void vpyfx_draw2d(void);
const vpyfx_stats_t *vpyfx_stats(void); /* alive, drawn, shed, recycled */The budget
Effects draw at VPY_PRI_LOW and within their own stroke budget. That makes them the first strokes shed when a frame is full, never the scenery. Whatever is left out is counted.
A disintegration or an assembly costs per_edge strokes per edge: a cube at 8 per edge is 96 strokes. When the effect is the point of the scene, raise the budget. Otherwise the far side of the object is what gets shed.
Handing an assembly over to the mesh
When vpyfx_assembled(g) reports that every piece is in place, draw the real mesh and call vpyfx_release(g) in the same frame. The pieces sit exactly on the edges, so the switch is invisible.
What it does not do
- Pieces pass through solid bodies.
- To shove the objects around an explosion, use
vpyp_blast(Physics).vpyfx_ringis the shockwave you see.
vpyfx is deterministic, with a seeded random generator. sdk/vpy-c/tools/fx_check.c checks it against its header. fx_demo is the worked example.
vpycam — a camera that follows, shakes and stops
void vpycam_reset(int32_t fx,int32_t fy,int32_t fz); /* focus there, no shake, no stop */
void vpycam_seed(uint32_t seed);
void vpycam_follow_config(int32_t dead_x,int32_t dead_y,int32_t dead_z,int lead_frames,int smooth);
void vpycam_follow(int32_t tx,int32_t ty,int32_t tz, int32_t vx,int32_t vy,int32_t vz);
void vpycam_focus(int32_t *x,int32_t *y,int32_t *z);
void vpycam_shake(int32_t amount,int frames); /* the larger shake wins */
void vpycam_shake_offset(int32_t *dx,int32_t *dy,int32_t *dz);
void vpycam_hitstop(int frames); /* the longer stop wins */
int vpycam_stopped(void); /* skip the simulation while 1 */
void vpycam_step(void); /* once a frame */
int vpycam_look_at(int32_t ox,int32_t oy,int32_t oz); /* the vpy3d camera, shaken */The focus follows a target with three settings:
- a dead zone, inside which the camera doesn't move;
- a lead, which looks ahead by the target's velocity;
- smoothing that snaps the last few units instead of creeping towards the target.
The snap matters on a Vectrex. A camera that keeps moving one unit per frame shifts the whole picture every frame, so the beam is re-zeroed somewhere new each time.
Two more effects:
- Shake.
vpycam_look_atapplies the shake to the vpy3d camera for you. - Hit-stop freezes the game for a few frames on a big hit. While
vpycam_stopped()returns 1, skip the simulation step and keep drawing.
vpyease — curves and tweens
int32_t vpy_ease_linear(int32_t t);
int32_t vpy_ease_in_quad(int32_t t), vpy_ease_out_quad(int32_t t), vpy_ease_in_out_quad(int32_t t);
int32_t vpy_ease_in_cubic(int32_t t), vpy_ease_out_cubic(int32_t t), vpy_ease_in_out_cubic(int32_t t);
int32_t vpy_ease_smoothstep(int32_t t); /* 3t² - 2t³ */
int32_t vpy_ease_out_back(int32_t t); /* overshoots by about 10%, then comes back */
int32_t vpy_ease_out_bounce(int32_t t); /* a ball dropped onto the end: 3 bounces */
int32_t vpy_ease_out_elastic(int32_t t); /* a spring settling on the end */
int32_t vpy_lerp_ease(int32_t from,int32_t to,int32_t t_q14,vpy_ease_fn curve);
int32_t vpy_tween(int32_t from,int32_t to,int frame,int frames,vpy_ease_fn curve);Time is Q14 (16384 = 1.0). Every curve is exactly 0 at the start and exactly 1 at the end, so a tween always lands on its target. sdk/vpy-c/tools/motion_check.c checks both the camera and the curves.