#include #include "map.h" #include "../../game.h" #include "../assets.h" #include "bsp.h" const F32 SKYBOX_OFFSET = 24.f; MAP_TEXTURE_ENTRY* map_load_texture( WORLD_MAP* m, GAME_DATA* game, const char* name ) { FNV1A hash = fnv1a( name ); MAP_TEXTURE_ENTRY** pentry = m->textures.where( fn( MAP_TEXTURE_ENTRY** t ) { return (*t)->hash == hash; } ); if( pentry ) { return *pentry; } GL_TEX2D* tex = gl_texture_from_file( game->gl, name ); if( !tex ) { dlog( "map_load_texture() : failed to load texture %s\n", name ); return 0; } MAP_TEXTURE_ENTRY* entry = new MAP_TEXTURE_ENTRY; entry->tex = tex; entry->hash = fnv1a( name ); strcpy( entry->name, name ); m->textures.push( entry ); return entry; } STAT map_add_texture_ref( WORLD_MAP* map, GL_TEX2D *tex ) { const char* name = assets_abspath( tex->name ); FNV1A hash = fnv1a( name ); I32 idx = map->textures.idx_where( fn( MAP_TEXTURE_ENTRY** e ) { return (*e)->hash == hash; } ); if( idx != -1 ) return STAT_ALREADYEXISTS; MAP_TEXTURE_ENTRY* entry = new MAP_TEXTURE_ENTRY; entry->tex = tex; entry->hash = hash; strcpy( entry->name, name ); return STAT_OK; } GL_TEX2D* map_find_texture( WORLD_MAP* map, const char* name ) { FNV1A hash = fnv1a( name ); MAP_TEXTURE_ENTRY** entry = map->textures.where( fn( MAP_TEXTURE_ENTRY** e ) { return (*e)->hash == hash; } ); if( entry ) return (*entry)->tex; return 0; } void map_polygon_calc_vertex_normals( MAP_POLYGON* p ) { for( U32 i = 0; i < p->vertices.size; i += 3 ) { MAP_VERTEX* v1 = &p->vertices[i]; MAP_VERTEX* v2 = &p->vertices[(i + 1) % p->vertices.size]; MAP_VERTEX* v3 = &p->vertices[(i + 2) % p->vertices.size]; VEC3 d1 = v2->pos - v1->pos; VEC3 d2 = v3->pos - v1->pos; VEC3 n = vec_cross( d1, d2 ); vec_normalize( &n ); v1->normal = v2->normal = v3->normal = n; } } STAT map_polygon_verts_from_section( WORLD_MAP* m, MAP_POLYGON* p, CFG_SECTION* vertices, U32 vertc ) { char vertsec[256] = { 0 }; for( U32 i = 0; i < vertc; ++i ) { MAP_VERTEX vert; sprintf( vertsec, "%d", i ); CFG_SECTION* v = cfg_section( vertices, vertsec ); if( !v ) { dlog( "map_polygon_verts_from_section() : missing vertex %d in polygon with propid %d in map %s\n", i, p->propid, m->name ); continue; } CFG_VEC3* pos = cfg_vec3( v, "pos" ); CFG_VEC2* uv = cfg_vec2( v, "uv" ); CFG_CLR* clr = cfg_clr( v, "clr" ); if( !pos ) { dlog( "map_polygon_verts_from_section() : missing pos for vertex %d in polygon with propid %d in map %s\n", i, p->propid, m->name ); return STAT_ERR; } vert.pos = pos->value; if( uv ) vert.uv = uv->value; if( clr ) vert.clr = clr->value; p->vertices.push( vert ); } if( !p->vertices.size ) { dlog( "map_polygon_verts_from_section() : invalid vertex count for polygon with propid %d in map %s\n", p->propid, m->name ); return STAT_ERR; } map_polygon_calc_vertex_normals( p ); return STAT_OK; } void map_polygon_calc_bounds( MAP_POLYGON* p ) { p->vertices.each( fn( MAP_VERTEX* v ) { p->mins.x = min( v->pos.x, p->mins.x ); p->mins.y = min( v->pos.y, p->mins.y ); p->mins.z = min( v->pos.z, p->mins.z ); p->maxs.x = max( v->pos.x, p->maxs.x ); p->maxs.y = max( v->pos.y, p->maxs.y ); p->maxs.z = max( v->pos.z, p->maxs.z ); } ); } STAT map_polygons_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* polygons, U32 polyc ) { if( !polyc ) { dlog( "map_polygons_from_section() : no polygons in %s\n", m->name ); return STAT_OK; } U32 parsec = 0; char polysec[256] = { 0 }; for( U32 i = 0; i < polyc; ++i ) { MAP_POLYGON poly; sprintf( polysec, "%d", i ); CFG_SECTION* p = cfg_section( polygons, polysec ); if( !p ) { dlog( "map_polygons_from_section() : missing polygon %d in %s\n", i, m->name ); continue; } CFG_INT* propid = cfg_int( p, "propid" ); if( !propid ) { dlog( "map_polygons_from_section() : missing propid for polygon %d in map %s\n", i, m->name ); continue; } poly.propid = propid->value; CFG_INT* polytype = cfg_int( p, "type" ); if( !polytype ) { dlog( "map_polygons_from_section() : missing polygon type for polygon %d in %s\n", i, m->name ); continue; } poly.type = (U8)polytype->value; CFG_INT* vertc = cfg_int( p, "vertcount" ); if( !vertc || !vertc->value ) { dlog( "map_polygons_from_section() : missing vertex count for polygon %d in %s\n", i, m->name ); continue; } CFG_SECTION* vertices = cfg_section( p, "vertices" ); if( !vertices ) { dlog( "map_polygons_from_section() : missing vertices for polygon %d in %s", i, m->name ); continue; } if( !OK( map_polygon_verts_from_section( m, &poly, vertices, vertc->value ) ) ) continue; map_polygon_calc_bounds( &poly ); m->polygons.push( poly ); ++parsec; } return parsec > 0 ? STAT_OK : STAT_ERR; } STAT map_walls_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* walls, U32 wallc ) { if( !wallc ) { dlog( "map_walls_from_section() : no walls in %s\n", m->name ); return STAT_OK; } U32 parsec = 0; char wallsec[256] = { 0 }; for( U32 i = 0; i < wallc; i++ ) { MAP_WALL seg; sprintf( wallsec, "%d", i ); CFG_SECTION* w = cfg_section( walls, wallsec ); if( !w ) { dlog( "map_walls_from_section() : missing wall %d in %s\n", i, m->name ); continue; } CFG_VEC3* start = cfg_vec3( w, "start" ); CFG_VEC3* end = cfg_vec3( w, "end" ); CFG_INT* prop = cfg_int( w, "propid" ); if( !start || !end || !prop ) { dlog( "map_walls_from_section() : bad wall definition in %s idx %d [%p %p %p]\n", m->name, i, start, end, prop ); continue; } seg.start = start->value; seg.end = end->value; seg.propid = prop->value; m->walls.push( seg ); ++parsec; } return parsec > 0 ? STAT_OK : STAT_ERR; } STAT map_props_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* props, U32 propc ) { if( !propc ) { dlog( "map_props_from_section() : no props in %s\n", m->name ); return STAT_ERR; } U32 parsec = 0; char propsec[256] = { 0 }; for( U32 i = 0; i < propc; ++i ) { SURF_PROPS prop; sprintf( propsec, "%d", i ); CFG_SECTION* p = cfg_section( props, propsec ); if( !p ) { dlog( "map_props_from_section() : missing prop %d in %s\n", i, m->name ); continue; } CFG_CLR* clr = cfg_clr( p, "clr" ); CFG_STR* tex = cfg_str( p, "tex" ); CLR propclr = (!!clr)? clr->value : CLR{ 1.f, 1.f, 1.f, 1.f }; GL_TEX2D* proptex = 0; if( tex ) { MAP_TEXTURE_ENTRY* entry = map_load_texture( m, g, tex->str ); if( !entry ) continue; proptex = entry->tex; } prop.clr = propclr; prop.tex = proptex; m->props.push( prop ); ++parsec; } return parsec > 0 ? STAT_OK : STAT_ERR; } STAT map_sprites_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* sprites, U32 spritec ) { if( !spritec ) { dlog( "map_sprites_from_section() : no sprites in %s\n", m->name ); return STAT_OK; } U32 parsec = 0; char spritesec[256] = { 0 }; for( U32 i = 0; i < spritec; ++i ) { MAP_SPRITE sprite; sprintf( spritesec, "%d", i ); CFG_SECTION* s = cfg_section( sprites, spritesec ); if( !s ) { dlog( "map_sprites_from_section() : missing sprite %d in %s\n", i, m->name ); continue; } CFG_CLR* clr = cfg_clr( s, "clr" ); CFG_STR* tex = cfg_str( s, "tex" ); CFG_VEC3* pos = cfg_vec3( s, "pos" ); CFG_VEC2* size = cfg_vec2( s, "size" ); if( !tex || !pos || !size ) { dlog( "map_sprites_from_section() : invalid sprite %d in %s\n", i, m->name ); continue; } CLR spriteclr = (!!clr)? clr->value : CLR{ 1.f, 1.f, 1.f, 1.f }; MAP_TEXTURE_ENTRY* texentry = map_load_texture( m, g, tex->str ); GL_TEX2D* spritetex = texentry->tex; sprite.tex = spritetex; sprite.pos = pos->value; sprite.size = size->value; sprite.clr = spriteclr; m->sprites.push( sprite ); ++parsec; } return parsec > 0 ? STAT_OK : STAT_ERR; } STAT map_skybox_from_section( CFG_SECTION* s, GAME_DATA* g, WORLD_MAP* m ) { CFG_SECTION* props = cfg_section( s, "props" ); if( !props ) return STAT_ERR; CFG_CLR* clr = cfg_clr( props, "clr" ); CFG_STR* tex = cfg_str( props, "tex" ); CLR skyclr; GL_TEX2D* skytex; if( !tex ) { skyclr = (!!clr)? clr->value : CLR::BLACK(); skytex = 0; } else { MAP_TEXTURE_ENTRY* texentry = map_load_texture( m, g, tex->str ); skyclr = (!!clr)? clr->value : CLR::WHITE(); if( !texentry || !texentry->tex ) { dlog( "map_skybox_from_section() : could not load skybox texture '%s'", tex->str ); return STAT_ERR; } else { skytex = texentry->tex; } } m->skybox.props = SURF_PROPS{ .tex = skytex, .clr = skyclr }; return STAT_OK; } MAP_SKYBOX map_default_skybox() { return { .props = { .tex = 0, .clr = CLR::BLACK() } }; } // generates 4 walls and 2 polygons to enclose the map. void map_gen_skybox( WORLD_MAP* m ) { VEC3 mins = m->mins - VEC3( SKYBOX_OFFSET, SKYBOX_OFFSET, SKYBOX_OFFSET ); VEC3 maxs = m->maxs + VEC3( SKYBOX_OFFSET, SKYBOX_OFFSET, SKYBOX_OFFSET ); m->skybox.walls[0] = { .start = { mins.x, mins.y, mins.z }, .end = { maxs.x, mins.y, maxs.z - mins.z }, .propid = MAPPROP_SKYBOX }; m->skybox.walls[1] = { .start = { maxs.x, mins.y, mins.z }, .end = { maxs.x, maxs.y, maxs.z - mins.z }, .propid = MAPPROP_SKYBOX }; m->skybox.walls[2] = { .start = { maxs.x, maxs.y, mins.z }, .end = { mins.x, maxs.y, maxs.z - mins.z }, .propid = MAPPROP_SKYBOX }; m->skybox.walls[3] = { .start = { mins.x, maxs.y, mins.z }, .end = { mins.x, mins.y, maxs.z - mins.z }, .propid = MAPPROP_SKYBOX }; VEC2 floor[] = { { mins.x, mins.y }, { maxs.x, mins.y }, { maxs.x, maxs.y }, { mins.x, maxs.y } }; VEC2 floor_uv[] = { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 0, 1 } }; m->skybox.polygons[0] = m->skybox.polygons[1] = { .propid = MAPPROP_SKYBOX }; for( U32 i = 0; i < 4; ++i ) { m->skybox.polygons[0].vertices.push( { .pos = { floor[i].x, floor[i].y, mins.z }, .uv = floor_uv[i], // todo: fix .clr = CLR::WHITE() } ); m->skybox.polygons[1].vertices.push( { .pos = { floor[i].x, floor[i].y, maxs.z }, .uv = floor_uv[i], // todo: fix .clr = CLR::WHITE() } ); } } void map_calc_bounds( WORLD_MAP* m ) { m->mins = { FLT_MAX, FLT_MAX, FLT_MAX }; m->maxs = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; m->walls.each( fn( MAP_WALL* l ) { m->mins.x = min( min( l->start.x, l->end.x ), m->mins.x ); m->mins.y = min( min( l->start.y, l->end.y ), m->mins.y ); m->mins.z = min( min( l->start.z, l->start.z + l->end.z ), m->mins.z ); m->maxs.x = max( max( l->start.x, l->end.x ), m->maxs.x ); m->maxs.y = max( max( l->start.y, l->end.y ), m->maxs.y ); m->maxs.z = max( max( l->start.z, l->start.z + l->end.z ), m->maxs.z ); } ); m->polygons.each( fn( MAP_POLYGON* p ) { m->mins.x = min( p->mins.x, m->mins.x ); m->mins.y = min( p->mins.y, m->mins.y ); m->mins.z = min( p->mins.z, m->mins.z ); m->maxs.x = max( p->maxs.x, m->maxs.x ); m->maxs.y = max( p->maxs.y, m->maxs.y ); m->maxs.z = max( p->maxs.z, m->maxs.z ); } ); } void map_check_bounds( WORLD_MAP* m ) { VEC3 mins = { FLT_MAX, FLT_MAX, FLT_MAX }; VEC3 maxs = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; m->walls.each( fn( MAP_WALL* l ) { mins.x = min( min( l->start.x, l->end.x ), mins.x ); mins.y = min( min( l->start.y, l->end.y ), mins.y ); mins.z = min( min( l->start.z, l->start.z + l->end.z ), mins.z ); maxs.x = max( max( l->start.x, l->end.x ), maxs.x ); maxs.y = max( max( l->start.y, l->end.y ), maxs.y ); maxs.z = max( max( l->start.z, l->start.z + l->end.z ), maxs.z ); } ); m->polygons.each( fn( MAP_POLYGON* p ) { mins.x = min( p->mins.x, mins.x ); mins.y = min( p->mins.y, mins.y ); mins.z = min( p->mins.z, mins.z ); maxs.x = max( p->maxs.x, maxs.x ); maxs.y = max( p->maxs.y, maxs.y ); maxs.z = max( p->maxs.z, maxs.z ); } ); if( vec_dist( mins, m->mins ) > 0.01f || vec_dist( maxs, m->maxs ) > 0.01f ) { m->mins = mins; m->maxs = maxs; map_gen_skybox( m ); } } // todo: too long -- extract parts into funcs WORLD_MAP* map_from_file( GAME_DATA* game, const char* path ) { CFG_SECTION* root = cfg_load( path ); ddef( const char* errstr = "map_from_file() : %s - missing %s section\n" ); if( !root ) { dlog( "map_from_file() : error opening file %s\n", path ); return 0; } defer( cfg_free( root ); ); WORLD_MAP* m = new WORLD_MAP; const char* name = file_path_last_of( path ); strcpy( m->name, name ); CFG_SECTION* s = cfg_section( root, "map" ); if( !s ) { dlog( errstr, "map", path ); delete m; return 0; } CFG_SECTION* props = cfg_section( s, "props" ); if( !props ) { dlog( errstr, path, "props" ); delete m; return 0; } CFG_SECTION* walls = cfg_section( s, "walls" ); if( !walls ) { dlog( errstr, path, "walls" ); delete m; return 0; } CFG_SECTION* polys = cfg_section( s, "polygons" ); if( !polys ) { dlog( errstr, path, "polygons" ); delete m; return 0; } CFG_INT* propc = cfg_int( s, "propcount" ); if( !propc ) { dlog( errstr, path, "propcount" ); delete m; return 0; } CFG_INT* wallc = cfg_int( s, "wallcount" ); if( !wallc ) { dlog( errstr, path, "wallcount" ); delete m; return 0; } CFG_INT* polyc = cfg_int( s, "polycount" ); if( !polyc ) { dlog( errstr, path, "polycount" ); delete m; return 0; } if( !OK( map_props_from_section( m, game, props, propc->value ) ) ) { delete m; return 0; } if( !OK( map_walls_from_section( m, game, walls, wallc->value ) ) ) { delete m; return 0; } if( !OK( map_polygons_from_section( m, game, polys, polyc->value ) ) ) { delete m; return 0; } CFG_INT* spritec = cfg_int( s, "spritecount" ); if( spritec ) { CFG_SECTION* sprites = cfg_section( s, "sprites" ); if( !sprites ) { dlog( errstr, path, "sprites" ); delete m; return 0; } if( !OK( map_sprites_from_section( m, game, sprites, spritec->value ) ) ) { delete m; return 0; } } CFG_VEC3* startpos = cfg_vec3( s, "startpos" ); if( !startpos ) { dlog( errstr, path, "startpos" ); m->startpos = { 0, 0, 0 }; } else { m->startpos = startpos->value; } CFG_FLOAT* startang = cfg_float( s, "startang" ); if( !startang ) { dlog( errstr, path, "startang" ); m->startang = 0; } else { m->startang = startang->value; } map_calc_bounds( m ); CFG_SECTION* skybox = cfg_section( s, "skybox" ); if( !skybox ) { dlog( errstr, path, "skybox" ); dlog( "using default skybox\n" ); m->skybox = map_default_skybox(); } else { if( !OK( map_skybox_from_section( skybox, game, m ) ) ) { dlog( errstr, path, "skybox" ); dlog( "using default skybox\n" ); m->skybox = map_default_skybox(); } } map_gen_skybox( m ); return m; } void map_serialize_info( CFG_SECTION* s, WORLD_MAP* map ) { cfg_int( "wallcount", s, map->walls.size ); cfg_int( "polycount", s, map->polygons.size ); cfg_int( "propcount", s, map->props.size ); cfg_int( "spritecount", s, map->sprites.size ); cfg_vec3( "startpos", s, map->startpos ); cfg_float( "startang", s, map->startang ); } void map_serialize_walls( CFG_SECTION* s, WORLD_MAP* map ) { CFG_SECTION* walls = cfg_section_new( "walls", s ); char name[64]; U32 i = 0; map->walls.each( fn( MAP_WALL* seg ) { sprintf( name, "%d", i++ ); CFG_SECTION* wallsec = cfg_section_new( name, walls ); cfg_vec3( "start", wallsec, seg->start ); cfg_vec3( "end", wallsec, seg->end ); cfg_int( "propid", wallsec, seg->propid ); } ); } void map_serialize_polygons( CFG_SECTION* s, WORLD_MAP* map ) { CFG_SECTION* polygons = cfg_section_new( "polygons", s ); char name[64]; U32 i = 0; map->polygons.each( fn( MAP_POLYGON* p ) { sprintf( name, "%d", i++ ); CFG_SECTION* polygonsec = cfg_section_new( name, polygons ); cfg_int( "vertcount", polygonsec, p->vertices.size ); cfg_int( "propid", polygonsec, p->propid ); cfg_int( "type", polygonsec, p->type ); CFG_SECTION* vertices = cfg_section_new( "vertices", polygonsec ); U32 i2 = 0; p->vertices.each( fn( MAP_VERTEX* v ) { sprintf( name, "%d", i2++ ); CFG_SECTION* vertsec = cfg_section_new( name, vertices ); cfg_vec3( "pos", vertsec, v->pos ); cfg_vec2( "uv", vertsec, v->uv ); cfg_clr( "clr", vertsec, v->clr ); } ); } ); } void map_serialize_sprites( CFG_SECTION* s, WORLD_MAP* map ) { CFG_SECTION* sprites = cfg_section_new( "sprites", s ); char name[64]; U32 i = 0; map->sprites.each( fn( MAP_SPRITE* s ) { sprintf( name, "%d", i++ ); CFG_SECTION* spritesec = cfg_section_new( name, sprites ); cfg_vec3( "pos", spritesec, s->pos ); cfg_vec2( "size", spritesec, s->size ); cfg_clr( "clr", spritesec, s->clr ); if( s->tex ) { const char* endp = assets_abspath( s->tex->name ); cfg_str( "tex", spritesec, endp, 256 ); } } ); } void map_serialize_props( CFG_SECTION* s, WORLD_MAP* map ) { CFG_SECTION* props = cfg_section_new( "props", s ); char name[64]; U32 i = 0; map->props.each( fn( SURF_PROPS* p ) { sprintf( name, "%d", i++ ); CFG_SECTION* propsec = cfg_section_new( name, props ); cfg_clr( "clr", propsec, p->clr ); if( p->tex ) { const char* endp = assets_abspath( p->tex->name ); cfg_str( "tex", propsec, endp, 256 ); } } ); } CFG_SECTION* map_serialize( WORLD_MAP* map ) { CFG_SECTION* root = cfg_section_new( "root", 0 ); CFG_SECTION* s = cfg_section_new( "map", root ); map_serialize_info( s, map ); map_serialize_props( s, map ); map_serialize_walls( s, map ); map_serialize_polygons( s, map ); map_serialize_sprites( s, map ); return s; } void map_free( GAME_DATA* game, WORLD_MAP* m ) { if( !m ) return; if( m->bsp ) bsp_free( m->bsp ); m->textures.each( fn( MAP_TEXTURE_ENTRY** e ) { gl_texture_destroy( game->gl, (*e)->tex ); delete *e; } ); delete m; }