summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c1556
1 files changed, 1556 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..71ae19d
--- /dev/null
+++ b/main.c
@@ -0,0 +1,1556 @@
+/*
+=======================================================================
+This file is part of Redman's RT.
+
+Redman's RT is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Redman's RT is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Redman's RT. If not, see <http://www.gnu.org/licenses/>.
+=======================================================================
+*/
+
+#include "common.h"
+
+//#define NO_THREADING
+
+typedef struct
+{
+ size_t x0, y0, x1, y1;
+} R_tile_t;
+
+typedef struct
+{
+ bool_t done;
+
+ uint8_t *pixels;
+ size_t width, height;
+ int pitch;
+ uint32_t format, bpp;
+
+ size_t tileSize;
+ SDL_mutex *tilesLock;
+ R_tile_t *tiles;
+ size_t tileCount, tileNext;
+} R_frame_t;
+
+typedef struct
+{
+ bool_t shaded;
+ bool_t overlay; //crosshair
+ size_t samples;
+
+ bool_t phong;
+ bool_t phong_specular;
+ bool_t phong_shadows;
+
+ bool_t monteCarlo;
+ bool_t globalIllumination;
+
+ bool_t fresnel;
+} R_quality_t;
+
+typedef enum
+{
+ R_WORKER_QUIT,
+ R_WORKER_IDLE,
+ R_WORKER_RENDERING,
+ R_WORKER_DONE
+} R_workerstate_t;
+
+typedef struct
+{
+ vec3_t origin;
+ vec3_t angles;
+ float fov, projdist;
+ m3_t matrix;
+} R_view_t;
+
+typedef struct
+{
+ size_t id;
+ R_workerstate_t state;
+
+ R_frame_t *fb;
+ size_t x0, y0, x1, y1;
+ SDL_mutex *sync, *sync2;
+ R_quality_t quality;
+
+ size_t pixelsDone;
+
+ W_world_t *world;
+ R_view_t *view;
+} R_workerdata_t;
+
+typedef struct
+{
+ SDL_Thread *thread;
+ R_workerdata_t data;
+} R_worker_t;
+
+typedef struct
+{
+ size_t depth;
+} R_workerlocal_t;
+
+#define R_FRAMETIME_SAMPLES 8
+typedef struct
+{
+ bool_t ready;
+
+ struct
+ {
+ size_t resw, resh;
+ float supersampling;
+ int scaleQuality;
+ size_t workerCount;
+ size_t tileSize;
+ } cfg;
+
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ SDL_Texture *frameTexture;
+ R_frame_t frame;
+ R_quality_t quality;
+
+ //R_world_t world;
+ R_view_t view;
+
+ R_worker_t *workers;
+ size_t workerCount;
+
+ size_t frameCount;
+ size_t frameTimes[ R_FRAMETIME_SAMPLES ];
+ size_t nextFrameTime;
+ float fps;
+} R_state_t;
+
+typedef struct
+{
+ bool_t transparent;
+ vec3_t color;
+
+ vec_t mapScale;
+
+ //Phong
+ vec_t diffuseCoeff;
+ vec_t specularCoeff;
+ vec_t specularExponent;
+
+ //Snell, Fresnel
+ vec_t refractiveIndex;
+} R_material_t;
+
+static const R_material_t _Materials[ ] =
+{
+ { }, // BLOCK_NONE
+ // R G B uv dif spe exp ir
+ { false, { 0.7, 0.3, 0.0 }, 0.15, 0.9, 0.1, 1.0, 1.5 }, // BLOCK_GROUND
+ { false, { 0.8, 0.8, 0.8 }, 0.15, 0.8, 0.2, 1.5, 1.5 }, // BLOCK_CONCRETE
+
+ { false, { 1.0, 1.0, 1.0 }, 1.0, 0.3, 0.7, 12, 1.5 }, // BLOCK_METAL
+ { false, { 0.0, 1.0, 0.0 }, 1.0, 0.0, 1.0, INFINITY, 1.5 }, // BLOCK_MIRROR
+ { false, { 25.0, 25.0, 25.0 }, 1.0, 0.0, 0.0, 0.0, 0.0 }, // BLOCK_LIGHT
+
+ { false, { 0.7, 0.3, 0.0 }, 1.0, 0.8, 0.2, 3.0, 1.5 } // BLOCK_BANNER
+};
+const size_t _Material_count = sizeof( _Materials ) / sizeof( R_material_t );
+
+typedef struct
+{
+ SDL_Surface *diffuse;
+} R_mapset_t;
+
+R_mapset_t _Material_mapsets[ sizeof( _Materials ) / sizeof( R_material_t ) ];
+
+int R_Init( R_state_t *rs )
+{
+ size_t i;
+ R_mapset_t *mapset;
+
+ //TODO: error handling
+ IMG_Init( IMG_INIT_JPG | IMG_INIT_PNG );
+
+ memset( rs, 0, sizeof( R_state_t ) );
+ memset( &_Material_mapsets, 0, sizeof( _Material_mapsets ) );
+
+ for( i = 1; i < _Material_count; i++ )
+ {
+ mapset = _Material_mapsets + i;
+
+ mapset->diffuse = IMG_Load( va( "tex/%i.jpg", i ) );
+ if( !mapset->diffuse )
+ fprintf( stderr, "IMG_Load: %s\n", IMG_GetError( ) );
+ }
+
+ return 0;
+}
+
+int R_Configure( R_state_t *rs )
+{
+ rs->cfg.resw = 1280;
+ rs->cfg.resh = 720;
+ rs->cfg.supersampling = 0.2f;
+ rs->cfg.scaleQuality = 1;
+ rs->cfg.workerCount = 8;
+ rs->cfg.tileSize = 64;
+ rs->view.fov = 0.5 * M_PI;
+
+ rs->quality.shaded = false;
+ return 0;
+}
+
+int R_InitScreen(R_state_t *rs)
+{
+ //todo: error catching
+ printf("R_InitScreen: %zux%zu\n",rs->cfg.resw,rs->cfg.resh);
+
+ if( rs->frameTexture )
+ SDL_DestroyTexture(rs->frameTexture);
+ if( rs->window )
+ SDL_DestroyWindow(rs->window);
+ if( rs->renderer )
+ SDL_DestroyRenderer(rs->renderer);
+
+ rs->window = SDL_CreateWindow(
+ PROGRAM_FULLNAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ rs->cfg.resw, rs->cfg.resh, SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_GRABBED
+ );
+ if( !rs->window )
+ {
+ printf( "SDL_CreateWindow: %s\n", SDL_GetError( ) );
+ abort( );
+ }
+
+ rs->renderer=SDL_CreateRenderer(
+ rs->window,-1, SDL_RENDERER_ACCELERATED
+ );
+ if( !rs->renderer )
+ {
+ printf( "SDL_CreateRenderer: %s\n", SDL_GetError( ) );
+ abort( );
+ }
+
+ rs->frame.width = rs->cfg.resw * rs->cfg.supersampling;
+ rs->frame.height = rs->cfg.resh * rs->cfg.supersampling;
+ rs->frameTexture=SDL_CreateTexture(
+ rs->renderer, SDL_PIXELFORMAT_BGR888, SDL_TEXTUREACCESS_STREAMING,
+ rs->frame.width, rs->frame.height
+ );
+ rs->frame.tileSize = rs->cfg.tileSize;
+
+ {
+ int w, h;
+
+ SDL_QueryTexture(
+ rs->frameTexture, &rs->frame.format, NULL, &w, &h );
+ rs->frame.width = w;
+ rs->frame.height = h;
+ }
+
+ if( rs->frame.format != SDL_PIXELFORMAT_BGR888 )
+ printf( "R_InitScreen: wrong pixel format %i\n", rs->frame.format );
+
+ rs->frame.bpp = 4;
+ rs->frame.pitch = rs->frame.width * rs->frame.bpp;
+
+ rs->frame.tilesLock = SDL_CreateMutex( );
+
+ rs->ready = true;
+ return 0;
+}
+
+
+//R_InitWorkers: set up the configured worker count
+int R_Worker( R_workerdata_t *w );
+int R_InitWorkers( R_state_t *rs )
+{
+#ifndef NO_THREADING
+ size_t i;
+ R_worker_t *w;
+
+ printf( "R_InitWorkers: %zu thread%s\n",
+ rs->cfg.workerCount, ( rs->cfg.workerCount == 1 ? "" : "s" ) );
+
+ //nothing to do
+ if( rs->cfg.workerCount == rs->workerCount )
+ return 0;
+
+ //remove some workers
+ if( rs->cfg.workerCount < rs->workerCount )
+ {
+ for( i = rs->cfg.workerCount; i < rs->workerCount; i++ )
+ {
+ w = rs->workers + i;
+ w->data.state = R_WORKER_QUIT;
+ SDL_UnlockMutex( w->data.sync );
+ SDL_WaitThread( w->thread, NULL );
+ }
+ }
+
+ rs->workers = realloc(
+ rs->workers,
+ rs->cfg.workerCount * sizeof( R_worker_t )
+ );
+
+ //allocate more workers
+ if( rs->cfg.workerCount > rs->workerCount )
+ {
+ for( i = rs->workerCount; i < rs->cfg.workerCount; i++ )
+ {
+ w = rs->workers + i;
+ w->data.id = i;
+ w->data.state = R_WORKER_IDLE;
+ w->data.sync = SDL_CreateMutex();
+ SDL_LockMutex( w->data.sync );
+ w->data.sync2 = SDL_CreateMutex();
+ w->thread = SDL_CreateThread(
+ ( SDL_ThreadFunction )R_Worker,
+ va( "render worker %i", i ),
+ &w->data
+ );
+ }
+ }
+
+ rs->workerCount = rs->cfg.workerCount;
+
+ return 0;
+#endif
+}
+
+void R_GenerateTiles( R_frame_t *fb )
+{
+/*
+ R_tile_t *t;
+ size_t x, y, th, tv;
+
+ th = fb->width / fb->tileSize;
+ if( fb->width % fb->tileSize )
+ th++;
+ tv = fb->height / fb->tileSize;
+ if( fb->height % fb->tileSize )
+ tv++;
+
+ fb->tileCount = th * tv;
+ fb->tiles = realloc( fb->tiles, sizeof( R_tile_t ) * fb->tileCount );
+
+ for( t = fb->tiles, y = 0; y < tv; y++ )
+ for( x = 0; x < th; x++, t++ )
+ {
+ t->x0 = x * fb->tileSize;
+ t->y0 = y * fb->tileSize;
+ t->x1 = ( x + 1 ) * fb->tileSize - 1;
+ if( t->x1 > fb->width - 1 )
+ t->x1 = fb->width - 1;
+ t->y1 = ( y + 1 ) * fb->tileSize - 1;
+ if( t->y1 > fb->height - 1 )
+ t->y1 = fb->height - 1;
+ }
+
+ fb->tileNext = 0;
+*/
+
+ R_tile_t *t;
+ size_t y;
+
+ fb->tileCount = fb->height;
+ fb->tiles = realloc( fb->tiles, sizeof( R_tile_t ) * fb->tileCount );
+
+ for( t = fb->tiles, y = 0; y < fb->height; y++, t++ )
+ {
+ t->x0 = 0;
+ t->x1 = fb->width - 1;
+ t->y0 = y;
+ t->y1 = y;
+ }
+
+ fb->tileNext = 0;
+};
+
+inline bool_t R_Worker_FindTile( R_workerdata_t *w )
+{
+ R_tile_t *t;
+
+ SDL_LockMutex( w->fb->tilesLock );
+
+ if( w->fb->tileNext >= w->fb->tileCount )
+ {
+ SDL_UnlockMutex( w->fb->tilesLock );
+ return false;
+ }
+
+ t = w->fb->tiles + w->fb->tileNext++;
+
+ SDL_UnlockMutex( w->fb->tilesLock );
+
+ w->x0 = t->x0;
+ w->y0 = t->y0;
+ w->x1 = t->x1;
+ w->y1 = t->y1;
+
+ return true;
+}
+
+W_world_t world;
+
+
+static void R_Sky( vec3_t out, const vec3_t dir, const R_quality_t *quality )
+{
+ vec_t dot;
+
+ dot = V3Dot( dir, world.lights[ 0 ].direction );
+
+ //sun
+ if( dot > 0.9999 )
+ {
+ V3Set( out, 3, 3, 3 );
+ return;
+ }
+
+ if( !quality->globalIllumination )
+ {
+ V3Set( out, 0, 0, 0 );
+ return;
+ }
+
+ if( dot < 0 )
+ dot = 0;
+
+ dot *= dot * dot * dot * 1.6;
+ dot += 0.4;
+
+ V3Set( out, dot, dot, dot );
+}
+
+static void R_MapTexture( vec3_t out, const vec3_t point, const vec3_t normal, vec_t scale, const SDL_Surface *surf )
+{
+ vec_t u = 0.0, v = 0.0;
+ size_t x, y;
+
+ if( surf == _Material_mapsets[ BLOCK_BANNER ].diffuse )
+ {
+ u = ( point[ 1 ] + 9.0 ) / 16.0;
+ v = ( point[ 2 ] - 24.0 ) / 16.0;
+ }
+ else
+ {
+ if( normal[ 2 ] == -1.0 || normal[ 2 ] == 1.0 )
+ {
+ u = point[ 0 ];
+ v = point[ 1 ];
+ }
+ else if( normal[ 0 ] == -1.0 || normal[ 0 ] == 1.0 )
+ {
+ u = point[ 1 ];
+ v = point[ 2 ];
+ }
+ else if( normal[ 1 ] == -1.0 || normal[ 1 ] == 1.0 )
+ {
+ u = point[ 2 ];
+ v = point[ 0 ];
+ }
+ }
+
+ //FIXME: lame negative number handling
+ u = fmod( u + 500, 1.0 / scale ) * scale;
+ v = fmod( v + 500, 1.0 / scale ) * scale;
+
+ x = u * surf->w;
+ y = v * surf->h;
+
+ x %= surf->w;
+ y %= surf->h;
+ //*(uint32_t*)(w->fb->pixels+y*w->fb->pitch+x*4)=r_|(g_<<8)|(b_<<16);
+ out[ 0 ] = 0.00390625 * *(uint8_t*)(surf->pixels + y * surf->pitch + x * 3 );
+ out[ 1 ] = 0.00390625 * *(uint8_t*)(surf->pixels + y * surf->pitch + x * 3 + 1 );
+ out[ 2 ] = 0.00390625 * *(uint8_t*)(surf->pixels + y * surf->pitch + x * 3 + 2 );
+}
+
+inline static void R_Phong( vec3_t out, const vec3_t point, const vec3_t viewdir, const vec3_t normal, const W_otnode_t *ignore, const R_material_t *mat, const R_quality_t *quality )
+{
+ int i;
+ ray_t shray;
+ rtres_t shrt;
+ vec_t dot, dist, lightMod;
+ vec3_t pass;
+ bool_t shadowed;
+
+ V3Set( out, 0, 0, 0 );
+
+ //iterate through all dynamic lights
+ for( i = 0; i < MAX_DLIGHTS; i++ )
+ {
+ W_light_t *light = world.lights + i;
+
+ V3Set( pass, 0, 0, 0 );
+
+ if( !light->inuse )
+ continue;
+
+ V3Copy( shray.org, point );
+
+ if( !light->sun )
+ {
+ V3Sub( shray.dir, light->origin, point );
+ dist = V3Normalize( shray.dir );
+ }
+ else
+ {
+ V3Copy( shray.dir, light->direction );
+ dist = INFINITY;
+ }
+
+ if( light->flashlight )
+ {
+ if( !light->flashlight_enabled )
+ continue;
+
+ dot = V3Dot( light->direction, shray.dir );
+ if( dot > -0.6 )
+ continue;
+
+ dot = -dot;
+ dot -= 0.6;
+ dot /= 0.4;
+ lightMod = sqrt( dot );
+ }
+ else
+ lightMod = 1.0;
+
+ dot = V3Dot( normal, shray.dir );
+
+ if( dot < 0 )
+ continue;
+
+ shadowed = false;
+
+ if( quality->phong_shadows )
+ {
+ U_BuildRay( NULL, NULL, dist, &shray, &shrt );
+ shadowed = ( W_Trace( &world, ignore, &shray, &shrt ) != NULL );
+ }
+
+ if( shadowed )
+ continue;
+
+ if( light->sun )
+ V3Mul( pass, light->light, dot );
+ else
+ V3Mul( pass, light->light, dot / dist / dist * lightMod );
+
+ V3Mul( pass, pass, mat->diffuseCoeff );
+
+ if( mat->specularCoeff > 0 )
+ {
+ vec_t specular;
+ vec3_t ref;
+
+ V3Reflect( ref, shray.dir, normal );
+
+ dot = V3Dot( ref, viewdir );
+
+ if( dot > 0 )
+ {
+ specular = pow( dot, mat->specularExponent ) * mat->specularCoeff;
+ V3MA( pass, pass, specular, light->light );
+ }
+ }
+
+ V3Add( out, out, pass );
+ }
+}
+
+#define R_RVP_SIZE 123456
+
+typedef struct
+{
+ vec3_t table[ R_RVP_SIZE ];
+ size_t i;
+} R_randvecpool_t;
+
+void R_GenerateRVP( R_randvecpool_t *rvp )
+{
+ size_t i;
+ vec_t p, y;
+ m3_t M, My, Mz;
+
+ for( i = 0; i < R_RVP_SIZE; i++ )
+ {
+ p = ( (vec_t)rand( ) / RAND_MAX ) * 2.0 * M_PI;
+ y = ( (vec_t)rand( ) / RAND_MAX ) * 2.0 * M_PI;
+ M3RotY( My, p );
+ M3RotZ( Mz, y );
+ M3Product( M, My, Mz );
+ V3Copy( rvp->table[ i ], M );
+ }
+}
+
+void R_v3rand( vec3_t out, R_randvecpool_t *rvp, const vec3_t normal )
+{
+ rvp->i++;
+ rvp->i %= R_RVP_SIZE;
+
+ V3Copy( out, rvp->table[ rvp->i ] );
+
+ if( !normal )
+ return;
+
+ if( V1Epscmp( normal[ 0 ], 1, 1e-5 ) )
+ out[ 0 ] = fabs( out[ 0 ] );
+ else if( V1Epscmp( normal[ 0 ], -1, 1e-5 ) )
+ out[ 0 ] = -fabs( out[ 0 ] );
+ else if( V1Epscmp( normal[ 1 ], 1, 1e-5 ) )
+ out[ 1 ] = fabs( out[ 1 ] );
+ else if( V1Epscmp( normal[ 1 ], -1, 1e-5 ) )
+ out[ 1 ] = -fabs( out[ 1 ] );
+ else if( V1Epscmp( normal[ 2 ], 1, 1e-5 ) )
+ out[ 2 ] = fabs( out[ 2 ] );
+ else if( V1Epscmp( normal[ 2 ], -1, 1e-5 ) )
+ out[ 2 ] = -fabs( out[ 2 ] );
+}
+
+void R_PT_Trace_R( vec3_t out, vec3_t in_mask, const vec3_t org, const vec3_t dir, const W_otnode_t *ignore, const R_quality_t *quality, R_randvecpool_t *rvp, size_t *depth )
+{
+ vec3_t mask;
+ W_otnode_t *node;
+ ray_t ray;
+ rtres_t rt;
+ const R_material_t *mat;
+ bool_t reflection = false;
+ const R_mapset_t *mapset;
+
+ (*depth)++;
+
+ U_BuildRay( org, dir, INFINITY, &ray, &rt );
+ node = W_Trace( &world, ignore, &ray, &rt );
+
+ if( !node )
+ {
+ vec3_t sky;
+
+ R_Sky( sky, ray.dir, quality );
+ V3VMA( out, out, in_mask, sky );
+ goto out;
+ }
+
+ V3MA( rt.hit, ray.org, rt.dist, ray.dir );
+ mat = _Materials + node->data;
+ mapset = _Material_mapsets + node->data;
+
+ if( node->data == BLOCK_LIGHT )
+ {
+ V3VMA( out, out, in_mask, mat->color );
+
+ goto out;
+ }
+
+ if( mapset->diffuse )
+ {
+ vec3_t color;
+ R_MapTexture( color, rt.hit, rt.normal, mat->mapScale, mapset->diffuse );
+ V3VMul( mask, in_mask, color );
+ }
+ else
+ V3VMul( mask, in_mask, mat->color );
+
+ //Phong shading
+ if( !mat->transparent )
+ {
+ if( quality->phong )
+ {
+ vec3_t phong;
+ R_Phong( phong, rt.hit, ray.dir, rt.normal, node, mat, quality );
+ V3VMA( out, out, mask, phong );
+ }
+ }
+
+ if( (*depth) > 3 )
+ goto out;
+
+ reflection = false;
+
+ if( mat->specularExponent == INFINITY )
+ reflection = true;
+ else if( quality->monteCarlo )
+ {
+ vec_t R;
+ R = (vec_t)rand() / RAND_MAX;
+ reflection = ( R < mat->specularCoeff );
+ }
+
+ if( !reflection )
+ {
+ //global illumination
+ if( !mat->transparent )
+ {
+ if( quality->monteCarlo && quality->globalIllumination )
+ {
+ vec3_t r;
+ vec3_t mask_gi;
+
+ R_v3rand( r, rvp, rt.normal );
+
+ V3Mul( mask_gi, mask, mat->diffuseCoeff );
+
+ R_PT_Trace_R( out, mask_gi, rt.hit, r, node, quality, rvp, depth );
+ }
+ }
+ //pass through (DOES NOT WORK)
+ /*else
+ {
+ vec_t R, C, B;
+ vec3_t rfa;
+
+ ray_t rfaray;
+ rtres_t rfart;
+
+ R = 1.0 / mat->refractiveIndex;
+ C = -V3Dot( rt.normal, ray.dir );
+
+ B = R * C - sqrt( 1 - R * R * ( 1 - C * C ) );
+
+ V3Mul2( rfa, ray.dir, R, rt.normal, B );
+
+ V3MA( rfaray.org, rt.hit, 150, rfa );
+ V3Mul( rfaray.dir, rfa, -1 );
+
+ U_BuildRay( NULL, NULL, INFINITY, &rfaray, &rfart );
+
+ if( !U_RT_AABB( (const vec3_t*)node->aabb, &ray, &rt ) )
+ printf( "wtf\n" );
+
+ V3MA( rfart.hit, rfaray.org, rfart.dist, rfaray.dir );
+
+ R_PT_Trace_R( out, mask, rfart.hit, rfa, node, quality, rvp, depth );
+
+ goto out;
+ }*/
+ }
+ else
+ {
+ vec3_t ref;
+
+ V3Reflect( ref, ray.dir, rt.normal );
+
+ if( mat->specularExponent == INFINITY )
+ {
+ V3VMul( mask, mask, mat->color );
+ R_PT_Trace_R( out, mask, rt.hit, ref, node, quality, rvp, depth );
+ }
+ else if( quality->monteCarlo )
+ {
+ vec3_t rnd;
+
+ R_v3rand( rnd, rvp, NULL );
+ V3MA( ref, ref, 1.0 / mat->specularExponent, rnd );
+ V3Normalize( ref );
+
+ if( V3Dot( ref, rt.normal ) < 0 )
+ goto out;
+
+ V3VMul( mask, mask, mat->color );
+
+ R_PT_Trace_R( out, mask, rt.hit, ref, node, quality, rvp, depth );
+ }
+ }
+
+out:
+ (*depth)--;
+}
+
+void R_PT_Trace( vec3_t out, const ray_t *ray, const R_quality_t *quality, R_randvecpool_t *rvp )
+{
+ vec3_t mask;
+ size_t i, depth;
+
+ V3Set( out, 0, 0, 0 );
+ V3Set( mask, 1, 1, 1 );
+ depth = 0;
+
+ for( i = 0; i < quality->samples; i++ )
+ R_PT_Trace_R( out, mask, ray->org, ray->dir, NULL, quality, rvp, &depth );
+
+ V3Mul( out, out, 1.0 / quality->samples );
+
+ if( quality->phong && quality->globalIllumination )
+ V3Mul( out, out, 0.5 );
+}
+
+bool_t R_Lightbulbs( vec3_t out, const ray_t *ray )
+{
+ size_t i;
+ W_light_t *light;
+ vec3_t delta;
+ vec_t dist;
+
+ for( i = 0; i < MAX_DLIGHTS; i++ )
+ {
+ light = world.lights + i;
+
+ if( !light->inuse || light->sun || light->flashlight )
+ continue;
+
+ V3Sub( delta, light->origin, ray->org );
+ dist = V3Normalize( delta );
+
+ if( V3Dot( delta, ray->dir ) > ( 1 - 1 / ( dist * 500 ) ) )
+ {
+ V3Copy( out, light->light );
+ return true;
+ }
+ }
+ return false;
+}
+
+void R_SimpleTrace( vec3_t out, const ray_t *ray )
+{
+ rtres_t rt;
+
+ memset( &rt, 0, sizeof( rtres_t ) );
+
+ if( W_Trace( &world, NULL, ray, &rt ) )
+ V3Set( out, 4.0 / rt.dist, 0, 0 );
+ else
+ V3Set( out, 0, 0, 1 );
+}
+
+int R_Worker( R_workerdata_t *w )
+{
+ size_t x, y;
+ R_randvecpool_t rvp;
+
+#ifdef NO_THREADING
+ {
+ static int init = 0;
+ if( !init )
+ R_GenerateRVP( &rvp );
+ init = 1;
+ }
+#else
+ R_GenerateRVP( &rvp );
+#endif
+
+#ifndef NO_THREADING
+ while( 1 )
+ {
+ if( w->state == R_WORKER_IDLE )
+ {
+ //lock the sync2 to indicate that work is not complete (since the
+ //worker hasn't even started computing)
+ SDL_LockMutex( w->sync2 );
+
+ //sleep until the main thread wakes the worker up by releasing the
+ //sync mutex
+ SDL_LockMutex( w->sync );
+ SDL_UnlockMutex( w->sync );
+ }
+ else if( w->state==R_WORKER_QUIT )
+ break;
+ else if( w->state==R_WORKER_RENDERING )
+ {
+#endif
+ ray_t ray;
+ vec3_t color;
+ uint8_t r_, g_, b_;
+ float _x,_y;
+ float _x_min;
+ float d_x, d_y;
+ int cx, cy;
+
+ w->pixelsDone = 0;
+
+ cx = w->fb->width / 2;
+ cy = w->fb->height / 2;
+
+ V3Copy( ray.org, w->view->origin );
+
+ d_x = 2.0 / w->fb->height;
+ d_y = 2.0 / w->fb->height;
+
+ while( R_Worker_FindTile( w ) )
+ {
+ _x = _x_min = (float)( (int)w->x0 + ( (int)w->fb->height - (int)w->fb->width ) / 2 ) / w->fb->height * 2.0 - 1.0;
+ _y = (float)w->y0 / w->fb->height * 2.0 - 1.0;
+
+ for( y = w->y0; y <= w->y1; y++, _x = _x_min, _y += d_y )
+ for( x = w->x0; x <= w->x1; x++, _x += d_x )
+ {
+ //GETRIDOFITLATER: crosshair
+ if( w->quality.overlay )
+ {
+ int dx, dy;
+
+ dx = abs( cx - x );
+ dy = abs( cy - y );
+
+ if( dx + dy <= 1 )
+ {
+ *(uint32_t*)(w->fb->pixels+y*w->fb->pitch+x*4)=0xFFFFFFFF;
+ w->pixelsDone++;
+ continue;
+ }
+ }
+
+ V3Mul( ray.dir, w->view->matrix, w->view->projdist );
+
+ V3MA2( ray.dir, ray.dir,
+ _x, w->view->matrix + 3,
+ _y, w->view->matrix + 6 );
+
+ V3Normalize( ray.dir );
+
+ U_BuildRay( NULL, NULL, INFINITY, &ray, NULL );
+
+ if( !R_Lightbulbs( color, &ray ) )
+ {
+ if( w->quality.shaded )
+ {
+ R_PT_Trace( color, &ray, &w->quality, &rvp );
+ }
+ else
+ R_SimpleTrace( color, &ray );
+ }
+
+ r_ = (uint8_t)( V1Clamp( color[ 0 ], 0.0, 1.0) * 255.0 );
+ g_ = (uint8_t)( V1Clamp( color[ 1 ], 0.0, 1.0) * 255.0 );
+ b_ = (uint8_t)( V1Clamp( color[ 2 ], 0.0, 1.0) * 255.0 );
+ *(uint32_t*)(w->fb->pixels+y*w->fb->pitch+x*4)=r_|(g_<<8)|(b_<<16);
+
+ w->pixelsDone++;
+ }
+ }
+
+#ifndef NO_THREADING
+
+ w->state = R_WORKER_DONE;
+
+ //work is done, unlock sync2 so the main thread can synchronize
+ //and display the frame
+ SDL_UnlockMutex( w->sync2 );
+ }
+ else if( w->state == R_WORKER_DONE )
+ SDL_Delay( 0 ); //FIXME: get rid of this
+ }
+
+ SDL_DestroyMutex( w->sync );
+ SDL_DestroyMutex( w->sync2 );
+
+#endif
+
+ return 0;
+}
+
+void R_CalculateView( R_view_t *view )
+{
+ V3AnglesToMatrix( view->matrix, view->angles );
+ view->projdist = 1.0 / ( tan ( 0.5 * view->fov ) );
+}
+
+void R_ViewFromPlayer( R_state_t *rs, const G_player_t *p )
+{
+ V3Copy( rs->view.origin, p->origin );
+ V3Copy( rs->view.angles, p->angles );
+ M3Copy( rs->view.matrix, p->matrix );
+}
+
+void R_Render( R_state_t *rs )
+{
+ uint64_t t_begin, t_delta;
+ size_t i;
+ R_workerdata_t *w;
+
+ //start measuring render time
+ t_begin = U_Microclock();
+
+ //reset tiles
+ rs->frame.tileNext = 0;
+
+ //calculate the view matrix
+ R_CalculateView( &rs->view );
+
+ SDL_LockTexture(
+ rs->frameTexture, NULL,
+ (void**)&rs->frame.pixels,
+ &rs->frame.pitch
+ );
+
+#ifndef NO_THREADING
+ //update & resume all workers
+ for( i = 0; i < rs->workerCount; i++ )
+ {
+ w = &rs->workers[ i ].data;
+
+ w->fb = &rs->frame;
+ w->view = &rs->view;
+ memcpy( &w->quality, &rs->quality, sizeof( R_quality_t ) );
+
+ //start the thread by changing the state to RENDERING and unlocking
+ //the sync mutex
+ w->state = R_WORKER_RENDERING;
+ SDL_UnlockMutex( w->sync );
+ }
+
+ //wait for all workers
+ for( i = 0; i < rs->workerCount; i++ )
+ {
+ w = &rs->workers[ i ].data;
+ //wait for the thread to finish computing
+ SDL_LockMutex( w->sync2 );
+ SDL_UnlockMutex( w->sync2 );
+ //lock the sync mutex THEN change the worker's state back to idle
+ SDL_LockMutex( w->sync );
+ w->state = R_WORKER_IDLE;
+ }
+#else
+ {
+ /* size_t id;
+ R_workerstate_t state;
+
+ R_frame_t *fb;
+ size_t x0, y0, x1, y1;
+ SDL_mutex *sync, *sync2;
+ R_quality_t quality;
+
+ size_t pixelsDone;
+
+ W_world_t *world;
+ R_view_t *view;*/
+ R_workerdata_t worker;
+ worker.fb = &rs->frame;
+ worker.quality = rs->quality;
+ worker.world = &world;
+ worker.view = &rs->view;
+
+ R_Worker( &worker );
+ }
+#endif
+
+ //display the render result
+ SDL_UnlockTexture( rs->frameTexture );
+ SDL_RenderCopy( rs->renderer, rs->frameTexture, NULL, NULL );
+ SDL_RenderPresent( rs->renderer );
+
+ //finish measuring render time
+ t_delta = U_Microclock() - t_begin;
+ rs->frameCount++;
+ rs->frameTimes[ rs->nextFrameTime++ ] = t_delta;
+ rs->nextFrameTime %= R_FRAMETIME_SAMPLES;
+ rs->fps = 0.0f;
+ if(rs->frameCount >= R_FRAMETIME_SAMPLES)
+ {
+ for( i = 0; i < R_FRAMETIME_SAMPLES; i++ )
+ rs->fps += 1.0e+6 / rs->frameTimes[ i ];
+ rs->fps /= R_FRAMETIME_SAMPLES;
+ }
+}
+
+void R_OfflineRender( R_state_t *rs )
+{
+#ifndef NO_THREADING
+ uint64_t t_begin, t_delta;
+ size_t i, totalPixels;
+ R_workerdata_t *w;
+ R_frame_t frame;
+ R_quality_t offq; //TODO: move out of here
+ SDL_Surface *image;
+
+ t_begin = U_Microclock();
+
+ R_CalculateView( &rs->view );
+
+ frame.width = 1280;
+ frame.height = 720;
+ frame.tileSize = 64;
+
+ totalPixels = frame.width * frame.height;
+
+ memcpy( &offq, &rs->quality, sizeof( R_quality_t ) );
+ offq.overlay = false;
+ if( offq.monteCarlo )
+ offq.samples *= 10;
+
+ R_GenerateTiles( &frame );
+
+ frame.tilesLock = SDL_CreateMutex( );
+
+ image = SDL_CreateRGBSurface(
+ SDL_SWSURFACE, frame.width, frame.height, 32,
+ 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
+ );
+
+ SDL_LockSurface( image );
+
+ frame.pixels = image->pixels;
+ frame.pitch = image->pitch;
+
+ //resume all workers
+ for( i = 0; i < rs->workerCount; i++ )
+ {
+ w=&rs->workers[i].data;
+
+ w->fb = &frame;
+ w->view = &rs->view;
+ memcpy( &w->quality, &offq, sizeof( R_quality_t ) );
+
+ //start the thread by changing the state to RENDERING and unlocking
+ //the sync mutex
+ w->state = R_WORKER_RENDERING;
+ SDL_UnlockMutex( w->sync );
+ }
+
+ printf( "\nRendering: ---%%") ;
+
+ while( 1 )
+ {
+ size_t i, pixelsDone = 0;
+
+ for( i = 0; i < rs->workerCount; i++ )
+ pixelsDone += rs->workers[ i ].data.pixelsDone;
+
+ printf( "\b\b\b\b%3i%%", (int)( 100.0 * (float)pixelsDone / totalPixels ) );
+ fflush( stdout );
+
+ if( pixelsDone == totalPixels )
+ break;
+
+ SDL_Delay( 50 );
+ }
+
+ //wait for all workers
+ for( i = 0; i < rs->workerCount; i++ )
+ {
+ w = &rs->workers[ i ].data;
+ //wait for the thread to finish computing
+ SDL_LockMutex( w->sync2 );
+ SDL_UnlockMutex( w->sync2 );
+ //lock the sync mutex THEN change the worker's state back to idle
+ SDL_LockMutex( w->sync );
+ w->state = R_WORKER_IDLE;
+ }
+
+ //display the render result
+ SDL_UnlockSurface( image );
+
+ //finish measuring render time
+ t_delta = U_Microclock() - t_begin;
+ printf("\b\b\b\b100%% (%.1fs)\n", (float)t_delta * 1.0e-6 );
+
+ SDL_SaveBMP( image, "offline.bmp" );
+
+ SDL_DestroyMutex( frame.tilesLock );
+ free( frame.tiles );
+ SDL_FreeSurface( image );
+#endif
+}
+
+void R_Quit( R_state_t *rs )
+{
+ size_t i;
+
+ //workers
+ rs->cfg.workerCount = 0;
+ R_InitWorkers( rs );
+
+ //tiles
+ free( rs->frame.tiles );
+ SDL_DestroyMutex( rs->frame.tilesLock );
+
+ //mapsets
+ for( i = 1; i < _Material_count; i++ )
+ SDL_FreeSurface( _Material_mapsets[ i ].diffuse );
+
+
+ //SDL stuff
+ SDL_DestroyTexture( rs->frameTexture );
+ SDL_DestroyRenderer( rs->renderer );
+ SDL_DestroyWindow( rs->window );
+
+ rs->ready = false;
+}
+
+
+ G_player_t player;
+ R_state_t rs;
+
+int main( int argc, char **argv )
+{
+ SDL_Event event;
+ W_blockdata_t blockSelection = BLOCK_CONCRETE;
+ bool_t grabbed = true;
+ SDL_Thread *evthd;
+ uint64_t phystime = 0;
+
+ R_Init( &rs );
+ R_Configure( &rs );
+ R_InitScreen( &rs );
+ R_InitWorkers( &rs );
+ R_GenerateTiles( &rs.frame );
+ rs.quality.overlay = true;
+
+ SDL_SetRelativeMouseMode( SDL_TRUE );
+
+
+ W_InitWorld( &world );
+
+ world.otroot = W_NewOTNode( NULL, 0 );
+ V3Set( world.otroot->aabb[ 0 ], -32, -32, -32 );
+ V3Set( world.otroot->aabb[ 1 ], 32, 32, 32 );
+
+ if( W_Load( &world, "world" ) )
+ {
+ W_DeleteWorld( &world );
+ W_CreateEmptyWorld( &world );
+ }
+
+ V3Copy( player.origin, world.saved_origin );
+ V3Copy( player.angles, world.saved_angles );
+ V3Set( player.velocity, 0, 0, 0 );
+ player.lastPhysicsTime = U_Clock( );
+
+ V3Set( player.aabb[ 0 ], -0.4, -0.4, -1 );
+ V3Set( player.aabb[ 1 ], 0.4, 0.4, 0.5 );
+
+ world.lights[ 0 ].inuse =
+ world.lights[ 0 ].sun = true;
+ V3Set( world.lights[ 0 ].light, 1.6, 1.6, 1.6 );
+
+ world.lights[ 1 ].inuse =
+ world.lights[ 1 ].flashlight = true;
+
+ phystime = U_Clock( );
+
+ while( true )
+ {
+ while( SDL_PollEvent( &event ) )
+ switch( event.type )
+ {
+ case SDL_QUIT:
+ goto main_loop_break;
+ case SDL_KEYDOWN:
+ {
+ switch( event.key.keysym.sym )
+ {
+ case SDLK_F1:
+ grabbed ^= true;
+ SDL_SetRelativeMouseMode( ( grabbed ? SDL_TRUE : SDL_FALSE ) );
+ break;
+
+ case SDLK_F4:
+ V3Set( player.origin, 0, 0, -10 );
+ V3Set( player.angles, 0, 0, 0 );
+ V3Set( player.velocity, 0, 0, 0 );
+ break;
+
+ case SDLK_f:
+ world.lights[ 1 ].flashlight_enabled ^= 1;
+ break;
+
+ case SDLK_m:
+ G_RecolorBlock( &world, rs.view.origin, rs.view.matrix );
+ break;
+
+ case SDLK_o:
+ if( world.otroot )
+ W_Optimize( world.otroot );
+ break;
+
+ case SDLK_p:
+ W_Dump( &world );
+ break;
+
+ //throw a light
+ case SDLK_q:
+ {
+ int i;
+ W_light_t *light;
+
+ for( i = 0; i < MAX_DLIGHTS; i++ )
+ {
+ light = world.lights + i;
+
+ if( light->inuse )
+ continue;
+
+ light->timestamp = U_Clock( );
+ light->inuse = true;
+ light->sun = false;
+
+ V3Copy( light->origin, rs.view.origin );
+ V3Mul( light->velocity, rs.view.matrix, 3.0 );
+
+ switch( rand( ) % 6 )
+ {
+ case 0: V3Set( light->light, 1, 0, 0 ); break;
+ case 1: V3Set( light->light, 1, 1, 0 ); break;
+ case 2: V3Set( light->light, 0, 1, 0 ); break;
+ case 3: V3Set( light->light, 0, 1, 1 ); break;
+ case 4: V3Set( light->light, 0, 0, 1 ); break;
+ case 5: V3Set( light->light, 1, 0, 1 ); break;
+ }
+ V3Mul( light->light, light->light, 10 );
+
+ break;
+ }
+ }
+ break;
+
+ case SDLK_F11:
+ R_OfflineRender( &rs );
+ break;
+
+ //not shaded
+ case SDLK_1:
+ rs.quality.shaded = false;
+ break;
+
+ //Phong
+ case SDLK_2:
+ rs.quality.shaded = true;
+ rs.quality.samples = 1;
+ rs.quality.phong = true;
+ rs.quality.phong_shadows = false;
+ rs.quality.monteCarlo = false;
+ rs.quality.globalIllumination = false;
+ rs.quality.fresnel = false;
+ break;
+
+ //shadowed Phong
+ case SDLK_3:
+ rs.quality.shaded = true;
+ rs.quality.samples = 1;
+ rs.quality.phong = true;
+ rs.quality.phong_shadows = true;
+ rs.quality.monteCarlo = false;
+ rs.quality.globalIllumination = false;
+ rs.quality.fresnel = false;
+ break;
+
+ //full w/o GI
+ case SDLK_4:
+ rs.quality.shaded = true;
+ rs.quality.samples = 1;
+ rs.quality.phong = true;
+ rs.quality.phong_shadows = true;
+ rs.quality.monteCarlo = true;
+ rs.quality.globalIllumination = false;
+ break;
+
+ //GI only
+ case SDLK_5:
+ rs.quality.shaded = true;
+ rs.quality.samples = 1;
+ rs.quality.phong = false;
+ rs.quality.monteCarlo = true;
+ rs.quality.globalIllumination = true;
+ break;
+
+ //full
+ case SDLK_6:
+ rs.quality.shaded = true;
+ rs.quality.samples = 1;
+ rs.quality.phong = true;
+ rs.quality.phong_shadows = true;
+ rs.quality.monteCarlo = true;
+ rs.quality.globalIllumination = true;
+ break;
+
+ //full @ 5 samples
+ case SDLK_7:
+ rs.quality.shaded = true;
+ rs.quality.samples = 5;
+ rs.quality.phong = true;
+ rs.quality.phong_shadows = true;
+ rs.quality.monteCarlo = true;
+ rs.quality.globalIllumination = true;
+ break;
+
+ case SDLK_z: blockSelection = BLOCK_CONCRETE; break;
+ case SDLK_x: blockSelection = BLOCK_GROUND; break;
+ case SDLK_r: blockSelection = BLOCK_METAL; break;
+ case SDLK_t: blockSelection = BLOCK_MIRROR; break;
+ case SDLK_y: blockSelection = BLOCK_LIGHT; break;
+ case SDLK_u: blockSelection = BLOCK_BANNER; break;
+ }
+ }
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ switch( event.button.button )
+ {
+ case SDL_BUTTON_LEFT:
+ G_RemoveBlock( &world, rs.view.origin, rs.view.matrix, 6 );
+ break;
+
+ case SDL_BUTTON_RIGHT:
+ G_PlaceBlock( &world, rs.view.origin, rs.view.matrix, blockSelection, 6 );
+ break;
+ }
+ break;
+
+ case SDL_MOUSEMOTION:
+ {
+ float x_, y_;
+
+ if( !rs.ready )
+ break;
+
+ x_ = -0.005f * event.motion.xrel / rs.view.projdist;
+ y_ = 0.005f * event.motion.yrel / rs.view.projdist;
+
+ player.angles[ 2 ] += x_;
+ player.angles[ 1 ] += y_;
+
+ player.angles[ 1 ] =
+ V1Clamp( player.angles[ 1 ], -0.5 * M_PI, 0.5 * M_PI );
+ }
+ break;
+
+ case SDL_MOUSEWHEEL:
+ {
+ static int level = 0;
+
+ level -= event.wheel.y;
+ if( level > 0 )
+ level = 0;
+ rs.view.fov = 0.6 * M_PI * pow( 1.1, level );
+ }
+ break;
+ }
+
+ //input
+ {
+ const uint8_t *keys;
+ keys = SDL_GetKeyboardState( NULL );
+
+ V2Set( player.cmd_move, 0, 0 );
+ player.cmd_jump = false;
+ player.cmd_down = false;
+ player.cmd_sprint = false;
+ player.cmd_noclip = false;
+
+ if( keys[ SDL_SCANCODE_A ] )
+ player.cmd_move[ 0 ] -= 1.0;
+ if( keys[ SDL_SCANCODE_D ] )
+ player.cmd_move[ 0 ] += 1.0;
+ if( keys[ SDL_SCANCODE_W ] )
+ player.cmd_move[ 1 ] += 1.0;
+ if( keys[ SDL_SCANCODE_S ] )
+ player.cmd_move[ 1 ] -= 1.0;
+ if( keys[ SDL_SCANCODE_SPACE ] )
+ player.cmd_jump = true;
+ if( keys[ SDL_SCANCODE_C] )
+ player.cmd_down = true;
+ if( keys[ SDL_SCANCODE_LSHIFT ] )
+ player.cmd_sprint = true;
+ if( keys[ SDL_SCANCODE_LCTRL ] )
+ player.cmd_noclip = true;
+ }
+
+ G_RunPlayer( &world, &player );
+ R_ViewFromPlayer( &rs, &player );
+
+ //physics
+ {
+ G_time_t frametime;
+ frametime = U_Clock( );
+
+ while( phystime + 10 < frametime )
+ {
+ size_t i;
+ const vec_t delta = 0.01;
+
+ for( i = 0; i < MAX_DLIGHTS; i++ )
+ {
+ W_light_t *light = world.lights + i;
+
+ if( !light->inuse )
+ continue;
+
+ if( light->sun )
+ {
+ vec_t t;
+
+ t = 1.0e-6 * U_Microclock( ) * 0.3;
+ V3Set( light->direction, cos( t ), sin( t ), -0.5 );
+ V3Normalize( light->direction );
+ continue;
+ }
+
+ if( light->flashlight )
+ {
+ vec_t brightness;
+
+ V3MA2( light->origin, player.origin, 0.1, player.matrix,
+ 0.1, player.matrix + 3 );
+ V3Copy( light->direction, player.matrix );
+
+ brightness = (vec_t)rand() / RAND_MAX * 0.1 + 1.0 * 5.0;
+
+ V3Set( light->light, 1.0, 0.8, 0.5 );
+ V3Mul( light->light, light->light, brightness );
+
+ continue;
+ }
+
+ light->velocity[ 2 ] += 5 * delta;
+
+ {
+ G_player_t fake;
+
+ V3Copy( fake.origin, light->origin );
+ V3Copy( fake.velocity, light->velocity );
+
+ V3Set( fake.aabb[ 0 ], -0.1, -0.1, -0.1 );
+ V3Set( fake.aabb[ 1 ], 0.1, 0.1, 0.1 );
+
+ G_MovePlayer( &world, &fake, delta );
+
+ V3Copy( light->origin, fake.origin );
+ V3Copy( light->velocity, fake.velocity );
+ }
+
+ if( frametime - light->timestamp > 8000 || light->origin[ 2 ] > 32 )
+ light->inuse = false;
+ }
+
+ phystime += 10;
+ }
+ }
+
+ R_Render( &rs );
+
+ {
+ //static uint64_t last = 0;
+
+ //if( U_Microclock() > last + 500000 )
+ {
+ size_t nodes, leaves;
+ W_Stats( &world, &nodes, &leaves );
+ printf( "\r \r%.1f %zu(%zu)", rs.fps, nodes, leaves );
+ fflush( stdout );
+ //last = U_Microclock();
+ }
+ }
+ }
+
+main_loop_break:
+
+ V3Copy( world.saved_origin, player.origin );
+ V3Copy( world.saved_angles, player.angles );
+ W_Save( &world, "world" );
+
+ W_DeleteWorld( &world );
+
+ R_Quit( &rs );
+ SDL_WaitThread( evthd, 0 );
+ SDL_Quit();
+
+ return 0;
+}