--- /dev/null
+#pragma once
+#include "trail.h"
+
+static void trail_system_update( trail_system *sys, f32 dt,
+ v3f co, v3f normal, f32 alpha ){
+ /* update existing points and clip dead ones */
+ bool clip_allowed = 1;
+ for( i32 i=0; i<sys->count; i ++ ){
+ i32 i0 = sys->head - sys->count + i;
+ if( i0 < 0 ) i0 += sys->max;
+
+ trail_point *p0 = &sys->array[i0];
+ p0->alpha -= dt/sys->lifetime;
+
+ if( clip_allowed ){
+ if( p0->alpha <= 0.0f )
+ sys->count --;
+ else
+ clip_allowed = 0;
+ }
+ }
+
+ bool add_point = 1;
+ i32 index_current = sys->head;
+ if( sys->count >= 2 ) index_current = sys->head -1;
+
+ i32 index_prev = index_current -1;
+
+ if( index_current < 0 ) index_current += sys->max;
+ if( index_prev < 0 ) index_prev += sys->max;
+
+ /* always enforced non-zero distance */
+ if( sys->count >= 1 ){
+ if( v3_dist2( sys->array[index_prev].co, co ) < 0.001f*0.001f )
+ return;
+ }
+
+ /* copy new info in */
+ trail_point *p_current = &sys->array[index_current];
+ v3_copy( co, p_current->co );
+ v3_copy( normal, p_current->normal );
+ p_current->alpha = alpha;
+
+ /* update direction */
+ if( sys->count >= 2 ){
+ trail_point *p_prev = &sys->array[index_prev];
+
+ v3f dir;
+ v3_sub( co, p_prev->co, dir );
+ v3_normalize( dir );
+ v3_cross( dir, normal, p_current->right );
+ v3_copy( p_current->right, p_prev->right );
+
+ /* decide if to prevent split based on user min-distance */
+ if( v3_dist2( p_prev->co, co ) < sys->min_dist*sys->min_dist )
+ add_point = 0;
+ }
+ else
+ v3_zero( p_current->right );
+
+ if( add_point ){
+ sys->head ++;
+
+ if( sys->head == sys->max )
+ sys->head = 0;
+
+ /* undesirable effect: will remove active points if out of space! */
+ if( sys->count < sys->max )
+ sys->count ++;
+
+ index_current = sys->head;
+ }
+
+ p_current = &sys->array[index_current];
+ v3_copy( co, p_current->co );
+ v3_copy( normal, p_current->normal );
+ p_current->alpha = alpha;
+ v3_zero( p_current->right );
+}
+
+static void trail_system_debug( trail_system *sys ){
+ for( i32 i=0; i<sys->count; i ++ ){
+ i32 i0 = sys->head - sys->count + i;
+ if( i0 < 0 ) i0 += sys->max;
+
+ trail_point *p0 = &sys->array[i0];
+ vg_line_point( p0->co, 0.04f, 0xff000000 | (u32)(p0->alpha*255.0f) );
+ vg_line_arrow( p0->co, p0->right, 0.3f, VG__GREEN );
+
+ if( i == sys->count-1 ) break;
+
+ i32 i1 = i0+1;
+ if( i1 == sys->max ) i1 = 0;
+
+ trail_point *p1 = &sys->array[i1];
+ vg_line( p0->co, p1->co, VG__RED );
+ }
+}
+
+struct trail_init_args {
+ trail_system *sys;
+};
+
+static void async_trail_init( void *payload, u32 size ){
+ struct trail_init_args *args = payload;
+ trail_system *sys = args->sys;
+
+ glGenVertexArrays( 1, &sys->vao );
+ glGenBuffers( 1, &sys->vbo );
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(trail_vert);
+
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferData( GL_ARRAY_BUFFER, sys->max*stride*2, NULL, GL_DYNAMIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 4, GL_FLOAT, GL_FALSE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ VG_CHECK_GL_ERR();
+}
+
+static void trail_alloc( trail_system *sys, u32 max ){
+ size_t stride = sizeof(trail_vert);
+ sys->max = max;
+ sys->array = vg_linear_alloc( vg_mem.rtmemory, max*sizeof(trail_point) );
+ sys->vertices = vg_linear_alloc( vg_mem.rtmemory, max*stride*2 );
+
+ vg_async_item *call = vg_async_alloc( sizeof(struct trail_init_args) );
+
+ struct trail_init_args *init = call->payload;
+ init->sys = sys;
+ vg_async_dispatch( call, async_trail_init );
+}
+
+static void trail_system_prerender( trail_system *sys ){
+#if 0
+ glBindVertexArray( sys->vao );
+
+ size_t stride = sizeof(trail_vert);
+ glBindBuffer( GL_ARRAY_BUFFER, sys->vbo );
+ glBufferSubData( GL_ARRAY_BUFFER, 0, sys->alive*stride*4, sys->vertices );
+#endif
+}
+
+static void trail_system_render( trail_system *sys, camera *cam ){
+#if 0
+ glDisable( GL_CULL_FACE );
+ glEnable( GL_DEPTH_TEST );
+
+ shader_trail_use();
+ shader_trail_uPv( cam->mtx.pv );
+ shader_trail_uPvPrev( cam->mtx_prev.pv );
+
+ glBindVertexArray( sys->vao );
+ glDrawElements( GL_TRIANGLES, sys->alive*6, GL_UNSIGNED_SHORT, NULL );
+#endif
+}