+#include "particle.h"
+
+static void particle_spawn( particle_system *sys,
+ v3f co, v3f v, f32 lifetime, u32 colour ){
+ if( sys->alive == sys->max ) return;
+
+ particle *p = &sys->array[ sys->alive ++ ];
+ v3_copy( co, p->co );
+ v3_copy( v, p->v );
+ p->life = lifetime;
+ p->colour = colour;
+}
+
+static void particle_system_update( particle_system *sys, f32 dt ){
+ u32 i = 0;
+iter: if( i == sys->alive ) return;
+
+ particle *p = &sys->array[i];
+ p->life -= dt;
+
+ if( p->life < 0.0f ){
+ *p = sys->array[ -- sys->alive ];
+ goto iter;
+ }
+
+ v3_muladds( p->co, p->v, dt, p->co );
+ p->v[1] += -9.8f * dt;
+
+ i ++;
+ goto iter;
+}
+
+static void particle_system_debug( particle_system *sys ){
+ for( u32 i=0; i<sys->alive; i ++ ){
+ particle *p = &sys->array[i];
+ v3f p1;
+ v3_muladds( p->co, p->v, 0.2f, p1 );
+ vg_line( p->co, p1, p->colour );
+ }
+}
+
+struct particle_init_args {
+ particle_system *sys;
+ u16 indices[];
+};
+
+static void async_particle_init( void *payload, u32 size ){
+ struct particle_init_args *args = payload;
+ particle_system *sys = args->sys;
+
+ glGenVertexArrays( 1, &sys->vao );
+ glGenBuffers( 1, &sys->vbo );
+ glGenBuffers( 1, &sys->ebo );
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(particle_vert);
+
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferData( GL_ARRAY_BUFFER, sys->max*stride*4, NULL, GL_DYNAMIC_DRAW );
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, sys->ebo );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER,
+ sys->max*sizeof(u16)*6, args->indices, GL_STATIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ /* 3: colour */
+ glVertexAttribPointer( 1, 4, GL_UNSIGNED_BYTE, GL_TRUE,
+ stride, (void *)offsetof(particle_vert, colour) );
+ glEnableVertexAttribArray( 1 );
+
+ VG_CHECK_GL_ERR();
+}
+
+static void particle_init( particle_system *sys, u32 max ){
+ static int reg = 1;
+ if( reg ){
+ shader_particle_register();
+ reg = 0;
+ }
+
+ size_t stride = sizeof(particle_vert);
+
+ particles_grind.max = max;
+ particles_grind.array =
+ vg_linear_alloc( vg_mem.rtmemory, max*sizeof(particle) );
+ particles_grind.vertices =
+ vg_linear_alloc( vg_mem.rtmemory, max*stride*4 );
+
+ vg_async_item *call =
+ vg_async_alloc( sizeof(particle_system *) + max*sizeof(u16)*6 );
+ struct particle_init_args *init = call->payload;
+ init->sys = sys;
+
+ for( u32 i=0; i<max; i ++ ){
+ init->indices[i*6+0] = i*4;
+ init->indices[i*6+1] = i*4+1;
+ init->indices[i*6+2] = i*4+2;
+ init->indices[i*6+3] = i*4;
+ init->indices[i*6+4] = i*4+2;
+ init->indices[i*6+5] = i*4+3;
+ }
+
+ vg_async_dispatch( call, async_particle_init );
+}
+
+static void particle_system_prerender( particle_system *sys ){
+ for( u32 i=0; i<sys->alive; i ++ ){
+ particle *p = &sys->array[i];
+ particle_vert *vs = &sys->vertices[i*4];
+
+ v3f v, right;
+ v3_copy( p->v, v );
+ v3_normalize( v );
+ v3_cross( v, (v3f){0,1,0}, right );
+
+ f32 l = 0.3f, w = 0.025f;
+
+ v3f p0, p1;
+ v3_muladds( p->co, p->v, l, p0 );
+ v3_muladds( p->co, p->v, -l, p1 );
+
+ v3_muladds( p0, right, w, vs[0].co );
+ v3_muladds( p1, right, w, vs[1].co );
+ v3_muladds( p1, right, -w, vs[2].co );
+ v3_muladds( p0, right, -w, vs[3].co );
+
+ vs[0].colour = p->colour;
+ vs[1].colour = p->colour;
+ vs[2].colour = p->colour;
+ vs[3].colour = p->colour;
+ }
+
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(particle_vert);
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferSubData( GL_ARRAY_BUFFER, 0, sys->alive*stride*4, sys->vertices );
+}
+
+static void particle_system_render( particle_system *sys, camera *cam ){
+ glDisable( GL_CULL_FACE );
+ glDisable( GL_DEPTH_TEST );
+
+ shader_particle_use();
+ shader_particle_uPv( cam->mtx.pv );
+ shader_particle_uPvPrev( cam->mtx_prev.pv );
+
+ glBindVertexArray( sys->vao );
+ glDrawElements( GL_TRIANGLES, sys->alive*6, GL_UNSIGNED_SHORT, NULL );
+}