diff options
Diffstat (limited to 'main.c')
-rw-r--r-- | main.c | 1556 |
1 files changed, 1556 insertions, 0 deletions
@@ -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; +} |