javascript based particle system

This commit is contained in:
John Alanbrook 2024-07-22 15:40:58 -05:00
parent ff71ee9db6
commit d4b057dc6f
11 changed files with 190 additions and 298 deletions

View file

@ -512,6 +512,7 @@ global.mixin("scripts/color");
global.mixin("scripts/gui");
global.mixin("scripts/tween");
global.mixin("scripts/ai");
global.mixin("scripts/particle");
var timer = {
update(dt) {

View file

@ -37,7 +37,6 @@ var entity = {
this.body = os.make_body(this.transform);
},
path_from(o) {
var p = this.toString();
var c = this.master;

105
scripts/particle.js Normal file
View file

@ -0,0 +1,105 @@
var emitter = {};
emitter.particles = {};
emitter.life = 10;
emitter.scale = 1;
emitter.grow_for = 0;
emitter.spawn_timer = 0;
emitter.pps = 0;
emitter.color = Color.white;
emitter.draw = function()
{
var amt = Object.values(this.particles).length;
if (amt === 0) return;
render.use_shader(this.shader);
render.use_mat(this);
var ts = [];
for (var p of Object.values(this.particles)) ts.push([p.transform,p.color]);
render.make_particle_ssbo(ts, this.ssbo);
render.draw(this.shape, this.ssbo, amt);
}
var std_spawn = function(par)
{
}
var std_step = function(p)
{
if (p.time < this.grow_for) {
var s = Math.lerp(0, this.scale, p.time/this.grow_for);
p.transform.scale = [s,s,s];
}
else if (p.time > (p.life - this.shrink_for)) {
var s = Math.lerp(0,this.scale,(p.life-p.time)/this.shrink_for);
p.transform.scale=[s,s,s];
} else
p.transform.scale = [this.scale,this.scale,this.scale];
}
var std_die = function(par)
{
}
emitter.spawn_hook = std_spawn;
emitter.step_hook = std_step;
emitter.die_hook = std_die;
emitter.spawn = function(t)
{
t ??= this.transform;
var par = {
transform: os.make_transform(),
life: this.life,
time: 0,
color: this.color
};
par.body = os.make_body(par.transform);
par.body.pos = t.pos;
par.transform.scale = [this.scale,this.scale,this.scale];
par.id = prosperon.guid();
this.particles[par.id] = par;
this.spawn_hook(par);
}
emitter.step = function(dt)
{
// update spawning particles
if (this.on && this.pps > 0) {
this.spawn_timer += dt;
var pp = 1/this.pps;
while (this.spawn_timer > pp) {
this.spawn_timer -= pp;
this.spawn();
}
}
// update all particles
for (var p of Object.values(this.particles)) {
p.time += dt;
this.step_hook(p);
if (p.time >= p.life) {
this.die_hook(p);
delete this.particles[p.id];
}
}
}
emitter.burst = function(count, t) {
for (var i = 0; i < count; i++) this.spawn(t);
}
var make_emitter = function()
{
var e = Object.create(emitter);
e.ssbo = render.make_textssbo();
e.shape = shape.quad;
e.shader = render.make_shader("shaders/baseparticle.cg");
return e;
}
return {make_emitter};

View file

@ -163,8 +163,12 @@ render.set_camera = function(cam)
setcam(cam);
}
var shader_cache = {};
render.make_shader = function(shader)
{
if (shader_cache[shader]) return shader_cache[shader];
var file = shader;
shader = io.slurp(shader);
if (!shader) {
@ -188,6 +192,7 @@ render.make_shader = function(shader)
var shaderobj = json.decode(io.slurp(writejson));
var obj = shaderobj[os.sys()];
obj.pipe = render.pipeline(obj);
shader_cache[shader] = obj;
return obj;
}
@ -304,6 +309,7 @@ render.make_shader = function(shader)
var obj = compiled[os.sys()];
obj.pipe = render.pipeline(obj);
shader_cache[shader] = obj;
return obj;
}
@ -640,11 +646,14 @@ render.slice9 = function(tex, pos, bb, scale = [tex.width,tex.height], color = C
render.emitter = function(emit)
{
var amt = emit.draw();
var amt = Object.values(emit.particles).length;
if (amt === 0) return;
render.use_shader(parshader);
render.use_mat(emit);
render.draw(shape.quad, emit.buffer, amt);
var ts = [];
for (var p of Object.values(emit.particles)) ts.push([p.transform,p.color]);
render.make_particle_ssbo(ts, emit.ssbo);
render.draw(shape.quad, emit.ssbo, amt);
}
var textssbo;

View file

@ -8,9 +8,7 @@ in vec2 a_uv;
uniform vec2 diffuse_size;
struct particle {
vec2 pos;
float angle;
float scale;
mat4 model;
vec4 color;
};
@ -28,21 +26,16 @@ void main()
{
particle p = par[gl_InstanceIndex];
pos = a_pos - 0.5;
vec2 rot = pos;
rot.x = pos.x*cos(p.angle) - pos.y*sin(p.angle);
rot.y = pos.x*sin(p.angle) + pos.y*cos(p.angle);
pos = rot*p.scale;
pos += p.pos;
color0 = p.color;
gl_Position = vp * vec4(pos, 0.0, 1.0);
gl_Position = vp * p.model * vec4(pos, 0.0, 1.0);
uv = a_pos;
color0 = p.color;
}
@end
@fs fs
in vec2 uv;
in vec4 color0;
in vec4 color0;
out vec4 color;
texture2D diffuse;
@ -50,7 +43,7 @@ sampler smp;
void main()
{
color = texture(sampler2D(diffuse,smp), uv);
color = texture(sampler2D(diffuse,smp),uv);
color *= color0;
}
@end

View file

@ -13,7 +13,6 @@ uniform vec4 shade;
void frag()
{
color = texture(sampler2D(diffuse,smp), uv);
if (color.a < 0.1) discard;
color *= shade;
}
@end

View file

@ -15,7 +15,6 @@
#include "window.h"
#include "spline.h"
#include "yugine.h"
#include "particle.h"
#include <assert.h>
#include "resources.h"
#include <sokol/sokol_time.h>
@ -96,7 +95,6 @@ void cpConstraint_free(cpConstraint *c)
void jsfreestr(const char *s) { JS_FreeCString(js, s); }
QJSCLASS(gameobject)
QJSCLASS(transform)
QJSCLASS(emitter)
QJSCLASS(dsp_node)
QJSCLASS(texture)
QJSCLASS(font)
@ -661,34 +659,6 @@ sg_bindings js2bind(JSValue v)
return bind;
}
JSC_GETSET(emitter, life, number)
JSC_GETSET(emitter, life_var, number)
JSC_GETSET(emitter, speed, number)
JSC_GETSET(emitter, variation, number)
JSC_GETSET(emitter, divergence, number)
JSC_GETSET(emitter, scale, number)
JSC_GETSET(emitter, scale_var, number)
JSC_GETSET(emitter, grow_for, number)
JSC_GETSET(emitter, shrink_for, number)
JSC_GETSET(emitter, color, vec4)
JSC_GETSET(emitter, max, number)
JSC_GETSET(emitter, explosiveness, number)
JSC_GETSET(emitter, bounce, number)
JSC_GETSET(emitter, collision_mask, bitmask)
JSC_GETSET(emitter, die_after_collision, boolean)
JSC_GETSET(emitter, tumble, number)
JSC_GETSET(emitter, tumble_rate, number)
JSC_GETSET(emitter, persist, number)
JSC_GETSET(emitter, persist_var, number)
JSC_GETSET(emitter, warp_mask, bitmask)
JSC_CCALL(emitter_emit, emitter_emit(js2emitter(self), js2number(argv[0]), js2transform(argv[1])))
JSC_CCALL(emitter_step, emitter_step(js2emitter(self), js2number(argv[0]), js2transform(argv[1])))
JSC_CCALL(emitter_draw,
sg_buffer *b = js2sg_buffer(js_getpropstr(self, "buffer"));
emitter_draw(js2emitter(self), b);
return number2js(arrlen(js2emitter(self)->verts));
)
JSC_CCALL(render_flushtext,
sg_buffer *buf = js2sg_buffer(argv[0]);
int amt = text_flush(buf);
@ -968,24 +938,67 @@ JSC_CCALL(render_setbind,
sg_apply_bindings(&bind);
)
typedef struct particle_ss {
HMM_Mat4 model;
HMM_Vec4 color;
} particle_ss;
JSC_CCALL(render_make_particle_ssbo,
JSValue array = argv[0];
size_t size = js_arrlen(array)*(sizeof(particle_ss));
sg_buffer *b = js2sg_buffer(argv[1]);
if (!b) return JS_UNDEFINED;
particle_ss ms[js_arrlen(array)];
if (sg_query_buffer_will_overflow(*b, size)) {
sg_destroy_buffer(*b);
*b = sg_make_buffer(&(sg_buffer_desc){
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.size = size,
.usage = SG_USAGE_STREAM,
.label = "transform buffer"
});
}
for (int i = 0; i < js_arrlen(array); i++) {
JSValue sub = js_getpropidx(array,i);
ms[i].model = transform2mat(*js2transform(js_getpropidx(sub, 0)));
ms[i].color = js2vec4(js_getpropidx(sub,1));
}
sg_append_buffer(*b, (&(sg_range){
.ptr = ms,
.size = size
}));
)
JSC_CCALL(render_make_t_ssbo,
JSValue array = argv[0];
size_t size = js_arrlen(array)*sizeof(HMM_Mat4);
sg_buffer *b = js2sg_buffer(argv[1]);
if (!b) return JS_UNDEFINED;
HMM_Mat4 ms[js_arrlen(array)];
if (sg_query_buffer_will_overflow(*b, size)) {
sg_destroy_buffer(*b);
*b = sg_make_buffer(&(sg_buffer_desc){
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.size = size,
.usage = SG_USAGE_STREAM,
.label = "transform buffer"
});
}
for (int i = 0; i < js_arrlen(array); i++)
ms[i] = transform2mat(*js2transform(js_getpropidx(array, i)));
sg_buffer *rr = malloc(sizeof(sg_buffer));
*rr = sg_make_buffer(&(sg_buffer_desc){
.data = {
sg_append_buffer(*b, (&(sg_range){
.ptr = ms,
.size = sizeof(HMM_Mat4)*js_arrlen(array),
},
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_IMMUTABLE,
.label = "transform buffer"
});
return sg_buffer2js(rr);
.size = size
}));
)
JSC_CCALL(render_spdraw,
@ -1037,7 +1050,8 @@ static const JSCFunctionListEntry js_render_funcs[] = {
MIST_FUNC_DEF(render, gfx_gui, 0),
MIST_FUNC_DEF(render, imgui_end, 0),
MIST_FUNC_DEF(render, imgui_init, 0),
MIST_FUNC_DEF(render, make_t_ssbo, 1)
MIST_FUNC_DEF(render, make_t_ssbo, 2),
MIST_FUNC_DEF(render, make_particle_ssbo, 2)
};
JSC_CCALL(gui_scissor, sg_apply_scissor_rect(js2number(argv[0]), js2number(argv[1]), js2number(argv[2]), js2number(argv[3]), 0))
@ -1484,32 +1498,6 @@ static const JSCFunctionListEntry js_physics_funcs[] = {
CGETSET_ADD(physics, collision_persistence),
};
static const JSCFunctionListEntry js_emitter_funcs[] = {
CGETSET_ADD(emitter, life),
CGETSET_ADD(emitter, life_var),
CGETSET_ADD(emitter, speed),
CGETSET_ADD(emitter, variation),
CGETSET_ADD(emitter, divergence),
CGETSET_ADD(emitter, scale),
CGETSET_ADD(emitter, scale_var),
CGETSET_ADD(emitter, grow_for),
CGETSET_ADD(emitter, shrink_for),
CGETSET_ADD(emitter, max),
CGETSET_ADD(emitter, color),
CGETSET_ADD(emitter, tumble),
CGETSET_ADD(emitter, tumble_rate),
CGETSET_ADD(emitter, explosiveness),
CGETSET_ADD(emitter, bounce),
CGETSET_ADD(emitter, collision_mask),
CGETSET_ADD(emitter, die_after_collision),
CGETSET_ADD(emitter, persist),
CGETSET_ADD(emitter, persist_var),
CGETSET_ADD(emitter, warp_mask),
MIST_FUNC_DEF(emitter, emit, 1),
MIST_FUNC_DEF(emitter, step, 1),
MIST_FUNC_DEF(emitter, draw, 1)
};
JSC_GETSET(transform, pos, vec3)
JSC_GETSET(transform, scale, vec3)
JSC_GETSET(transform, rotation, quat)
@ -1543,10 +1531,21 @@ JSC_CCALL(transform_direction,
return vec32js(HMM_QVRot(js2vec3(argv[0]), t->rotation));
)
JSC_CCALL(transform_phys2d,
transform *t = js2transform(self);
HMM_Vec2 v = js2vec2(argv[0]);
float av = js2number(argv[1]);
float dt = js2number(argv[2]);
transform_move(t, (HMM_Vec3){v.x*dt,v.y*dt,0});
HMM_Quat rot = HMM_QFromAxisAngle_LH((HMM_Vec3){0,0,1}, av*dt);
t->rotation = HMM_MulQ(t->rotation, rot);
)
static const JSCFunctionListEntry js_transform_funcs[] = {
CGETSET_ADD(transform, pos),
CGETSET_ADD(transform, scale),
CGETSET_ADD(transform, rotation),
MIST_FUNC_DEF(transform, phys2d, 3),
MIST_FUNC_DEF(transform, move, 1),
MIST_FUNC_DEF(transform, rotate, 2),
MIST_FUNC_DEF(transform, angle, 1),
@ -2399,18 +2398,6 @@ JSC_SCALL(os_make_model,
return v;
)
JSC_CCALL(os_make_emitter,
emitter *e = make_emitter();
ret = emitter2js(e);
sg_buffer *b = malloc(sizeof(*b));
*b = sg_make_buffer(&(sg_buffer_desc) {
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.size = 4,
.usage = SG_USAGE_STREAM
});
js_setpropstr(ret, "buffer", sg_buffer2js(b));
)
JSC_CCALL(os_make_buffer,
int type = js2number(argv[1]);
float *b = malloc(sizeof(float)*js_arrlen(argv[0]));
@ -2590,7 +2577,6 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_model, 1),
MIST_FUNC_DEF(os, make_transform, 0),
MIST_FUNC_DEF(os, make_emitter, 0),
MIST_FUNC_DEF(os, make_buffer, 1),
MIST_FUNC_DEF(os, make_line_prim, 4),
MIST_FUNC_DEF(os, make_cylinder, 2),
@ -2623,7 +2609,6 @@ void ffi_load() {
QJSCLASSPREP_FUNCS(gameobject);
QJSCLASSPREP_FUNCS(transform);
QJSCLASSPREP_FUNCS(dsp_node);
QJSCLASSPREP_FUNCS(emitter);
QJSCLASSPREP_FUNCS(warp_gravity);
QJSCLASSPREP_FUNCS(warp_damp);
QJSCLASSPREP_FUNCS(texture);

View file

@ -1,123 +0,0 @@
#include "particle.h"
#include "stb_ds.h"
#include "render.h"
#include "2dphysics.h"
#include "math.h"
#include "log.h"
emitter *make_emitter() {
emitter *e = calloc(sizeof(*e),1);
e->max = 20;
arrsetcap(e->particles, 10);
for (int i = 0; i < arrlen(e->particles); i++)
e->particles[i].life = 0;
e->life = 10;
e->tte = lerp(e->explosiveness, e->life/e->max, 0);
e->scale = 1;
e->speed = 20;
e->color = (HMM_Vec4){1,1,1,1};
return e;
}
void emitter_free(emitter *e)
{
arrfree(e->particles);
arrfree(e->verts);
free(e);
}
/* Variate a value around variance. Variance between 0 and 1. */
float variate(float val, float variance)
{
return val + val*(frand(variance)-(variance/2));
}
int emitter_spawn(emitter *e, transform *t)
{
if (arrlen(e->particles) == e->max) return 0;
particle p = {0};
p.life = e->life;
p.pos = (HMM_Vec4){t->pos.x,t->pos.y,t->pos.z,0};
HMM_Vec3 up = transform_direction(t, vFWD);
float newan = (frand(e->divergence)-(e->divergence/2))*HMM_TurnToRad;
HMM_Vec2 v2n = HMM_V2Rotate((HMM_Vec2){0,1}, newan);
HMM_Vec3 norm = (HMM_Vec3){v2n.x, v2n.y,0};
p.v = HMM_MulV4F((HMM_Vec4){norm.x,norm.y,norm.z,0}, variate(e->speed, e->variation));
p.angle = e->tumble;
p.av = e->tumble_rate;
p.scale = variate(e->scale*t->scale.x, e->scale_var);
arrput(e->particles,p);
return 1;
}
void emitter_emit(emitter *e, int count, transform *t)
{
for (int i = 0; i < count; i++)
emitter_spawn(e, t);
}
void emitter_draw(emitter *e, sg_buffer *b)
{
if (arrlen(e->particles) == 0) return;
arrsetlen(e->verts, arrlen(e->particles));
for (int i = 0; i < arrlen(e->particles); i++) {
if (e->particles[i].time >= e->particles[i].life) continue;
particle *p = e->particles+i;
e->verts[i].pos = p->pos.xy;
e->verts[i].angle = p->angle;
e->verts[i].scale = p->scale;
if (p->time < e->grow_for)
e->verts[i].scale = lerp(p->time/e->grow_for, 0, p->scale);
else if (p->time > (p->life - e->shrink_for))
e->verts[i].scale = lerp((p->time-(p->life-e->shrink_for))/e->shrink_for, p->scale, 0);
e->verts[i].color = p->color;
}
sg_range verts;
verts.ptr = e->verts;
verts.size = sizeof(*e->verts)*arrlen(e->verts);
if (sg_query_buffer_will_overflow(*b, verts.size)) {
sg_destroy_buffer(*b);
*b = sg_make_buffer(&(sg_buffer_desc){
.size = verts.size,
.type = SG_BUFFERTYPE_STORAGEBUFFER,
.usage = SG_USAGE_STREAM
});
}
sg_append_buffer(*b, &verts);
}
void emitter_step(emitter *e, double dt, transform *t) {
HMM_Vec4 g_accel = HMM_MulV4F((HMM_Vec4){cpSpaceGetGravity(space).x, cpSpaceGetGravity(space).y, 0, 0}, dt);
for (int i = 0; i < arrlen(e->particles); i++) {
if (e->particles[i].time >= e->particles[i].life) continue;
if (e->warp_mask & gravmask)
e->particles[i].v = HMM_AddV4(e->particles[i].v, g_accel);
e->particles[i].pos = HMM_AddV4(e->particles[i].pos, HMM_MulV4F(e->particles[i].v, dt));
e->particles[i].angle += e->particles[i].av*dt;
e->particles[i].time += dt;
e->particles[i].color = e->color;
//e->particles[i].color = sample_sampler(&e->color, e->particles[i].time/e->particles[i].life);
e->particles[i].scale = e->scale;
if (e->particles[i].time >= e->particles[i].life)
arrdelswap(e->particles, i);
// else if (query_point(e->particles[i].pos.xy))
// arrdelswap(e->particles,i);
}
e->tte-=dt;
float step = lerp(e->explosiveness, e->life/e->max,0);
while (e->tte <= 0) {
e->tte += step;
if (!emitter_spawn(e, t)) break;
}
}

View file

@ -1,74 +0,0 @@
#ifndef PARTICLE_H
#define PARTICLE_H
#include "HandmadeMath.h"
#include "warp.h"
#include "transform.h"
#include "texture.h"
#include "anim.h"
#include "gameobject.h"
#include "render.h"
typedef struct particle {
HMM_Vec4 pos;
HMM_Vec4 v; /* velocity */
float angle;
float av; /* angular velocity */
float scale;
double time;
double life;
HMM_Vec4 color;
} particle;
#define SPRAY 0
#define CLOUD 1
#define MESH 2
typedef struct par_vert {
HMM_Vec2 pos;
float angle;
float scale;
HMM_Vec4 color;
} par_vert;
typedef struct emitter {
struct particle *particles;
par_vert *verts;
HMM_Vec3 *mesh; /* list of points to optionally spawn from */
HMM_Vec3 *norm; /* norm at each point */
int type; /* spray, cloud, or mesh */
float explosiveness; /* 0 for a stream, 1 for all at once. Range of values allowed. */
int max; /* number of particles */
double life; /* how long a particle lasts */
double life_var;
/* SPRAY PARTICLE GEN */
float speed; /* initial speed of particle */
float variation; /* variation on speed */
float divergence; /* angular degree of variation from emitter normal, up to 1 */
float tumble; /* amount of random rotation of particles */
float tumble_rate; /* tumble rotation */
HMM_Vec4 color; /* color over particle lifetime */
float scale;
float scale_var;
float grow_for; /* seconds to grow from small until scale */
float shrink_for; /* seconds to shrink to small prior to its death */
/* ROTATION AND COLLISION */
int collision_mask; /* mask for collision */
float bounce; /* bounce back after collision */
/* PARTICLE SPAWN */
int die_after_collision;
float persist; /* how long to linger after death */
float persist_var;
/* TRAILS */
warpmask warp_mask;
double tte; /* time to emit */
} emitter;
emitter *make_emitter();
void emitter_free(emitter *e);
void emitter_emit(emitter *e, int count, transform *t);
void emitter_step(emitter *e, double dt, transform *t);
void emitter_draw(emitter *e, sg_buffer *b);
#endif

View file

@ -5,7 +5,6 @@
#include "font.h"
#include "gameobject.h"
#include "log.h"
#include "particle.h"
#include "window.h"
#include "model.h"
#include "stb_ds.h"

View file

@ -7,7 +7,6 @@
#include "resources.h"
#include "spline.h"
#include <stdio.h>
#include "particle.h"
#include "simplex.h"
#include <wctype.h>