--- /dev/null
+#ifndef POINTCLOUD_H
+#define POINTCLOUD_H
+
+#include "common.h"
+#include "world.h"
+#include "shaders/point_map.h"
+
+#define POINTCLOUD_POINTS 250000
+
+struct pointcloud{
+ GLuint vao, vbo;
+ u32 count;
+
+ v4f control;
+}
+static pointcloud = { .control = {0.0f,0.0f,0.0f,1.0f} };
+
+#pragma pack(push,1)
+struct pointcloud_vert{
+ u16 pos[4]; /* float[ 0 -> 1 ] */
+ i8 norm[4]; /* float[ -1 -> 1 ] */
+ u8 colour[4]; /* float[ 0 -> 1 ] */
+};
+#pragma pack(pop)
+
+static void async_pointcloud_sub( void *payload, u32 size )
+{
+
+ glBindVertexArray( pointcloud.vao );
+ glBindBuffer( GL_ARRAY_BUFFER, pointcloud.vbo );
+ glBufferSubData( GL_ARRAY_BUFFER, 0,
+ sizeof(struct pointcloud_vert)*size, payload );
+ pointcloud.count = size;
+}
+
+static void async_pointcloud_alloc( void *payload, u32 size )
+{
+ glGenVertexArrays( 1, &pointcloud.vao );
+ glGenBuffers( 1, &pointcloud.vbo );
+ glBindVertexArray( pointcloud.vao );
+
+ size_t stride = sizeof( struct pointcloud_vert );
+
+ glBindBuffer( GL_ARRAY_BUFFER, pointcloud.vbo );
+ glBufferData( GL_ARRAY_BUFFER, stride * POINTCLOUD_POINTS,
+ payload, GL_DYNAMIC_DRAW );
+
+ /* 0: coordinates */
+ glVertexAttribPointer( 0, 4, GL_UNSIGNED_SHORT, GL_TRUE, stride, (void*)0 );
+ glEnableVertexAttribArray( 0 );
+
+ /* 1: normal */
+ glVertexAttribPointer( 1, 4, GL_BYTE, GL_TRUE,
+ stride, (void *)offsetof(struct pointcloud_vert, norm) );
+ glEnableVertexAttribArray( 1 );
+
+ /* 2: colour */
+ glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE,
+ stride, (void *)offsetof(struct pointcloud_vert, colour) );
+ glEnableVertexAttribArray( 2 );
+ VG_CHECK_GL_ERR();
+ pointcloud.count = size;
+}
+
+static void pointcloud_init(void)
+{
+ vg_info( "Generating random test point cloud\n" );
+
+ vg_rand_seed( 2000 );
+ vg_async_item *call =
+ vg_async_alloc( sizeof(struct pointcloud_vert)*POINTCLOUD_POINTS );
+ struct pointcloud_vert *test_data = call->payload;
+ call->size = POINTCLOUD_POINTS;
+
+ for( u32 i=0; i<POINTCLOUD_POINTS; i++ ){
+ test_data[i].pos[0] = vg_randf64() * 65535.0f;
+ test_data[i].pos[1] = vg_randf64() * 65535.0f;
+ test_data[i].pos[2] = vg_randf64() * 65535.0f;
+
+ v3f norm;
+ vg_rand_dir( norm );
+
+ test_data[i].norm[0] = norm[0] * 127.0f;
+ test_data[i].norm[1] = norm[1] * 127.0f;
+ test_data[i].norm[2] = norm[2] * 127.0f;
+
+ test_data[i].colour[0] = 90;
+ test_data[i].colour[1] = 90;
+ test_data[i].colour[2] = 90;
+ test_data[i].colour[3] = 255;
+ }
+
+ vg_async_dispatch( call, async_pointcloud_alloc );
+ shader_point_map_register();
+}
+
+static void pointcloud_render( world_instance *world, camera *cam, m4x3f model )
+{
+ m4x4f upvmprev;
+ m4x3_expand( model, upvmprev );
+ m4x4_mul( cam->mtx_prev.pv, upvmprev, upvmprev );
+
+ shader_point_map_use();
+ shader_point_map_uPv( cam->mtx.pv );
+ shader_point_map_uPvmPrev( upvmprev );
+ shader_point_map_uMdl( model );
+ shader_point_map_uCamera( cam->pos );
+ shader_point_map_uTime( (v2f){ vg.time, sinf(vg.time) } );
+ shader_point_map_uTransform( pointcloud.control );
+
+ m3x3f mnorm;
+ m3x3_inv( model, mnorm );
+ m3x3_transpose( mnorm, mnorm );
+ shader_point_map_uNormMtx( mnorm );
+
+ glBindVertexArray( pointcloud.vao );
+ glEnable( GL_PROGRAM_POINT_SIZE );
+ glDrawArrays( GL_POINTS, 0, pointcloud.count );
+}
+
+#endif /* POINTCLOUD_H */