particle component

This commit is contained in:
John Alanbrook 2024-01-01 23:30:42 +00:00
parent 4dfaaf2b52
commit 1d776855be
13 changed files with 140 additions and 54 deletions

View file

@ -4,3 +4,5 @@ The core tenents of Primum are as follows. When choosing features for Primum, th
- Gameplay first. Visuals are important but should be chosen to be more simple if it makes implementing gameplay more difficult.
- Dynamic first. Video games are dynamic, so as much as possible should be dynamically generated. For example, signed distance fields for particle system collision will not be used, as it requires baking sometimes false geometry. We include midi playback, which can be changed in real time far easier than wavs or mp3s.
- Marriage of code and editor. Neither completely replaces the other. What is easier to do in code should be done in code, and what should be done in editor should be done in editor. No solutions try to step on the toes of the other solution.
- Uniformity. There should not be a specific wind system that interacts with a specific tree system, for example. If there is a "wind" system, it will affect trees, grass, particles, potentially objects, the clouds in the sky, and everything else.
- Mathematics.

View file

@ -872,10 +872,29 @@ component.circle2d.impl = Object.mix({
}, collider2d.impl);
component.particle = Object.copy(component, {
make() {
var p = Object.create(this);
p.id = cmd(234);
return p;
},
get pos() {},
set pos(x) {},
get angle() {},
set angle(x) {},
get life() {},
set life(x) { cmd(235,this.id,x); },
get explosiveness() {},
set explosiveness(x) {},
get max() {},
set max(x) {},
emit(n) {
cmd(236,this.id,n);
},
play() {
},
pause() {
},
});

View file

@ -116,6 +116,7 @@ load("scripts/physics.js");
load("scripts/input.js");
load("scripts/sound.js");
load("scripts/ai.js");
load("scripts/geometry.js");
function screen2world(screenpos) {
if (Game.camera)

26
scripts/geometry.js Normal file
View file

@ -0,0 +1,26 @@
var Sphere = {};
Sphere.volume = function(r) { return Math.pi*r*r*r*4/3; };
Sphere.random = function(r,theta,phi)
{
if (typeof r === 'number') r = [r,r];
theta ??= [0,1];
phi ??= [-0.5,0.5];
if (typeof theta === 'number') theta = [theta,theta];
if (typeof phi === 'number') phi = [phi,phi];
var ra = Math.random_range(r[0],r[1]);
var ta = Math.turn2rad(Math.random_range(theta[0],theta[1]));
var pa = Math.turn2rad(Math.random_range(phi[0],phi[1]));
return [
ra*Math.sin(ta)*Math.cos(pa),
ra*Math.sin(ta)*Math.sin(pa),
ra*Math.cos(ta)
];
}
var Circle = {};
Circle.area = function(r) { return Math.pi*r*r; };
Circle.random = function(r,theta)
{
return Sphere.random(r,theta).xz;
}

View file

@ -367,6 +367,7 @@ typedef union HMM_Vec4 {
HMM_Quat quat;
float Elements[4];
float e[4];
#ifdef HANDMADE_MATH__USE_SSE
__m128 SSE;
@ -513,6 +514,10 @@ static inline float HMM_Clamp(float Min, float Value, float Max) {
return Result;
}
static inline float frand(float max) {
return ((float)rand()/(float)(RAND_MAX))*max;
}
/*
* Vector initialization
*/
@ -651,6 +656,11 @@ static inline HMM_Vec4 HMM_SubV4(HMM_Vec4 Left, HMM_Vec4 Right) {
return HMM_V2(v.X*s, v.Y*s);
}
static inline HMM_Vec3 HMM_ScaleV3(HMM_Vec3 v, double s)
{
return HMM_V3(v.x*s,v.y*s,v.z*s);
}
static inline HMM_Vec2 HMM_MulV2(HMM_Vec2 Left, HMM_Vec2 Right) {
HMM_Vec2 Result;

View file

@ -2,12 +2,10 @@
#include "log.h"
#include "stb_ds.h"
HMM_Vec4 sample_linear(sampler *sampler, float time, int prev, int next)
void sampler_add(sampler *s, float time, HMM_Vec4 val)
{
if (sampler->rotation)
return (HMM_Vec4)HMM_SLerp(sampler->data[prev].quat, time, sampler->data[next].quat);
else
return HMM_LerpV4(sampler->data[prev], time, sampler->data[next]);
arrput(s->times,time);
arrput(s->data,val);
}
HMM_Vec4 sample_cubicspline(sampler *sampler, float t, int prev, int next)
@ -25,6 +23,8 @@ HMM_Vec4 sample_cubicspline(sampler *sampler, float t, int prev, int next)
HMM_Vec4 sample_sampler(sampler *sampler, float time)
{
if (arrlen(sampler->data) == 0) return (HMM_Vec4){0,0,0,0};
if (arrlen(sampler->data) == 1) return sampler->data[0];
int previous_time=0;
int next_time=0;
@ -41,7 +41,7 @@ HMM_Vec4 sample_sampler(sampler *sampler, float time)
switch(sampler->type) {
case LINEAR:
return sample_linear(sampler,t,previous_time,next_time);
return HMM_LerpV4(sampler->data[previous_time],time,sampler->data[next_time]);
break;
case STEP:
return sampler->data[previous_time];
@ -49,6 +49,9 @@ HMM_Vec4 sample_sampler(sampler *sampler, float time)
case CUBICSPLINE:
return sample_cubicspline(sampler,t, previous_time, next_time);
break;
case SLERP:
return (HMM_Vec4)HMM_SLerp(sampler->data[previous_time].quat, time, sampler->data[next_time].quat);
break;
}
return sample_cubicspline(sampler,t, previous_time, next_time);
}

View file

@ -11,12 +11,18 @@ struct keyframe {
#define LINEAR 0
#define STEP 1
#define CUBICSPLINE 2
#define SLERP 3
typedef struct samplerf {
float *times;
float *data;
int type;
} samplerf;
typedef struct sampler {
float *times;
HMM_Vec4 *data;
int type;
int rotation;
} sampler;
struct anim_channel {
@ -29,6 +35,8 @@ struct animation {
struct anim_channel *channels;
};
void sampler_add(sampler *s, float time, HMM_Vec4 val);
HMM_Vec4 sample_sampler(sampler *sampler, float time);
#endif

View file

@ -21,6 +21,7 @@
#include "window.h"
#include "spline.h"
#include "yugine.h"
#include "particle.h"
#include <assert.h>
#include "resources.h"
#include <sokol/sokol_time.h>
@ -158,11 +159,14 @@ double js2number(JSValue v) {
return g;
}
void *js2ptr(JSValue v) { return JS_GetOpaque(v,js_ptr_id); }
JSValue float2js(double g) { return JS_NewFloat64(js, g);}
JSValue num2js(double g) { return float2js(g); }
struct sprite *js2sprite(JSValue v) { return id2sprite(js2int(v)); }
emitter *js2emitter(JSValue v) { return (emitter*)js2ptr(v); }
void *js2ptr(JSValue v) { return JS_GetOpaque(v,js_ptr_id); }
JSValue ptr2js(void *ptr) {
JSValue obj = JS_NewObjectClass(js, js_ptr_id);
@ -1420,6 +1424,15 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
case 233:
js2sprite(argv[1])->parallax = js2number(argv[2]);
break;
case 234:
ret = ptr2js(make_emitter());
break;
case 235:
js2emitter(argv[1])->life = js2number(argv[2]);
break;
case 236:
emitter_emit(js2emitter(argv[1]), js2number(argv[2]));
break;
}
if (str)

View file

@ -2,6 +2,7 @@
#include "stb_ds.h"
#include "render.h"
#include "particle.sglsl.h"
#include "2dphysics.h"
#include "log.h"
static emitter **emitters;
@ -14,7 +15,7 @@ static int draw_count;
struct par_vert {
HMM_Vec2 pos;
float angle;
float scale;
HMM_Vec2 scale;
struct rgba color;
};
@ -30,7 +31,7 @@ void particle_init()
.attrs = {
[1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_FLOAT,
[3].format = SG_VERTEXFORMAT_FLOAT,
[3].format = SG_VERTEXFORMAT_FLOAT2,
[4].format = SG_VERTEXFORMAT_UBYTE4N,
[0].format = SG_VERTEXFORMAT_FLOAT2,
[0].buffer_index = 1
@ -74,15 +75,23 @@ emitter *make_emitter() {
emitter *e = NULL;
e = malloc(sizeof(*e));
e->max = 20;
arrsetlen(e->particles, e->max);
e->particles = NULL;
arrsetcap(e->particles, e->max);
for (int i = 0; i < arrlen(e->particles); i++)
e->particles[i].life = 0;
e->life = 10;
e->explosiveness = 1;
e->explosiveness = 0;
e->tte = lerp(e->life/e->max, 0, e->explosiveness);
e->color = color_white;
e->scale = 20;
e->color.times = NULL;
e->color.data = NULL;
e->color.type = LINEAR;
sampler_add(&e->color, 0, (HMM_Vec4){1,1,1,1});
e->scale = 1;
e->speed = 20;
e->gravity = 1;
e->on = 0;
e->texture = texture_pullfromfile("glass_chunk2.gif");
arrpush(emitters,e);
return e;
}
@ -97,37 +106,26 @@ void free_emitter(emitter *e)
}
}
void start_emitter(emitter *e)
{
}
void pause_emitter(emitter *e) {
}
void stop_emitter(emitter *e) {
}
void start_emitter(emitter *e) { e->on = 1; }
void stop_emitter(emitter *e) { e->on = 0; }
int emitter_spawn(emitter *e)
{
for (int i = 0; i < e->max; i++) {
if (e->particles[i].life > 0) continue;
e->particles[i].life = e->life;
e->particles[i].pos = (HMM_Vec3){0,0,0};
e->particles[i].v = (HMM_Vec3){20,1,0};
e->particles[i].angle = 0;
e->particles[i].av = 1;
return 1;
}
return 0;
particle p;
p.life = e->life;
p.pos = (HMM_Vec3){0,0,0};
p.v = (HMM_Vec3){frand(1)-0.5,frand(1)-0.5,0};
p.v = HMM_ScaleV3(HMM_NormV3(p.v), e->speed);
p.angle = 0;
p.av = 1;
arrput(e->particles,p);
return 1;
}
void emitter_emit(emitter *e, int count)
{
for (int i = 0; i < count; i++)
if (!emitter_spawn(e)) return;
emitter_spawn(e);
}
void emitters_step(double dt)
@ -138,7 +136,6 @@ void emitters_step(double dt)
void emitters_draw()
{
draw_count = 0;
for (int i = 0; i < arrlen(emitters); i++) {
@ -146,12 +143,11 @@ void emitters_draw()
par_bind.fs.images[0] = e->texture->id;
for (int j = 0; j < arrlen(e->particles); j++) {
particle *p = &e->particles[j];
if (p->life <= 0) continue;
struct par_vert pv;
pv.pos = p->pos.xy;
pv.angle = p->angle;
pv.scale = p->scale;
pv.color = p->color;
pv.scale = HMM_ScaleV2(tex_get_dimensions(e->texture), p->scale);
pv.color = vec2rgba(p->color);
sg_append_buffer(par_bind.vertex_buffers[0], &(sg_range){.ptr=&pv, .size=sizeof(struct par_vert)});
draw_count++;
}
@ -164,21 +160,24 @@ void emitters_draw()
}
void emitter_step(emitter *e, double dt) {
for (int i = 0; i < arrlen(e->particles); i++) {
if (e->particles[i].life <= 0) continue;
for (int i = arrlen(e->particles)-1; i >= 0; i--) {
if (e->gravity)
e->particles[i].v = HMM_AddV3(e->particles[i].v, HMM_MulV3F((HMM_Vec3){cpSpaceGetGravity(space).x, cpSpaceGetGravity(space).y, 0}, dt));
e->particles[i].pos = HMM_AddV3(e->particles[i].pos, HMM_MulV3F(e->particles[i].v, dt));
e->particles[i].angle += e->particles[i].av*dt;
e->particles[i].life -= dt;
e->particles[i].color = e->color;
e->particles[i].color = sample_sampler(&e->color, (e->life-e->particles[i].life)/e->life);
e->particles[i].scale = e->scale;
if (e->particles[i].life <= 0)
e->particles[i].life = 0;
arrdelswap(e->particles,i);
}
if (!e->on) return;
e->tte-=dt;
if (e->tte <= 0) {
emitter_emit(e,1);
emitter_spawn(e);
e->tte = lerp(e->life/e->max,0,e->explosiveness);
}
}

View file

@ -4,6 +4,7 @@
#include "HandmadeMath.h"
#include "transform.h"
#include "texture.h"
#include "anim.h"
typedef struct particle {
HMM_Vec3 pos;
@ -12,7 +13,7 @@ typedef struct particle {
float av; /* angular velocity */
float scale;
double life;
rgba color;
HMM_Vec4 color;
} particle;
typedef struct emitter {
@ -22,9 +23,12 @@ typedef struct emitter {
int max; /* number of particles */
double life; /* how long a particle lasts */
double tte; /* time to emit */
rgba color;
sampler color; /* color over particle lifetime */
float scale;
float speed;
int gravity; /* true if affected by gravity */
texture *texture;
int on;
} emitter;
void particle_init();

View file

@ -93,6 +93,10 @@ struct rgba {
typedef struct rgba rgba;
static inline rgba vec2rgba(HMM_Vec4 v) {
return (rgba){v.e[0]*255,v.e[1]*255,v.e[2]*255,v.e[3]*255};
}
struct boundingbox {
float t;
float b;

View file

@ -103,10 +103,7 @@ void c_init() {
window_resize(sapp_width(), sapp_height());
script_evalf("Game.init();");
/* particle_init();
emitter *e = make_emitter();
e->texture = texture_pullfromfile("bolt.gif");
start_emitter(e);*/
particle_init();
}
int frame_fps() { return 1.0/sapp_frame_duration(); }

View file

@ -2,7 +2,7 @@
in vec2 vertex;
in vec2 apos;
in float angle;
in float scale;
in vec2 scale;
in vec4 vc;
out vec4 fcolor;