diff --git a/docs/primum.md b/docs/primum.md index 2784d7c..35941d0 100644 --- a/docs/primum.md +++ b/docs/primum.md @@ -3,4 +3,6 @@ The core tenents of Primum are as follows. When choosing features for Primum, these are the guidelines we follow. - 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. \ No newline at end of file +- 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. \ No newline at end of file diff --git a/scripts/components.js b/scripts/components.js index 7e331e4..1added1 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -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() { + + }, }); diff --git a/scripts/engine.js b/scripts/engine.js index b2cc576..4475e15 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -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) diff --git a/scripts/geometry.js b/scripts/geometry.js new file mode 100644 index 0000000..587a247 --- /dev/null +++ b/scripts/geometry.js @@ -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; +} diff --git a/source/engine/HandmadeMath.h b/source/engine/HandmadeMath.h index 82ff11d..b742df4 100644 --- a/source/engine/HandmadeMath.h +++ b/source/engine/HandmadeMath.h @@ -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; diff --git a/source/engine/anim.c b/source/engine/anim.c index 0cc8fa0..efe5105 100644 --- a/source/engine/anim.c +++ b/source/engine/anim.c @@ -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); } diff --git a/source/engine/anim.h b/source/engine/anim.h index 274b578..a0de65d 100644 --- a/source/engine/anim.h +++ b/source/engine/anim.h @@ -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 diff --git a/source/engine/jsffi.c b/source/engine/jsffi.c index 4be5901..90e4ebb 100644 --- a/source/engine/jsffi.c +++ b/source/engine/jsffi.c @@ -21,6 +21,7 @@ #include "window.h" #include "spline.h" #include "yugine.h" +#include "particle.h" #include #include "resources.h" #include @@ -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) diff --git a/source/engine/particle.c b/source/engine/particle.c index 5f2f0fd..0dfc36d 100644 --- a/source/engine/particle.c +++ b/source/engine/particle.c @@ -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); } } diff --git a/source/engine/particle.h b/source/engine/particle.h index cd096b0..7d77641 100644 --- a/source/engine/particle.h +++ b/source/engine/particle.h @@ -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(); diff --git a/source/engine/render.h b/source/engine/render.h index e73e58e..1755ba9 100644 --- a/source/engine/render.h +++ b/source/engine/render.h @@ -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; diff --git a/source/engine/yugine.c b/source/engine/yugine.c index 892ee23..b78b02c 100644 --- a/source/engine/yugine.c +++ b/source/engine/yugine.c @@ -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(); } diff --git a/source/shaders/particle.sglsl b/source/shaders/particle.sglsl index 0c9b255..4327cae 100644 --- a/source/shaders/particle.sglsl +++ b/source/shaders/particle.sglsl @@ -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;