summaryrefslogtreecommitdiff
path: root/src/game/world/map.cpp
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2025-09-03 20:10:09 +0200
committernavewindre <boneyaard@gmail.com>2025-09-03 20:10:09 +0200
commitf8b92ce3aa08b1445c9f956d8166830946562d12 (patch)
tree94e63a5aec9f8f52b577f56799e0c9201fd976a5 /src/game/world/map.cpp
a
Diffstat (limited to 'src/game/world/map.cpp')
-rw-r--r--src/game/world/map.cpp640
1 files changed, 640 insertions, 0 deletions
diff --git a/src/game/world/map.cpp b/src/game/world/map.cpp
new file mode 100644
index 0000000..aba283d
--- /dev/null
+++ b/src/game/world/map.cpp
@@ -0,0 +1,640 @@
+#include <cfloat>
+
+#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.x + 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.x + 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.x + 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.x + 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" );
+ m->skybox = map_default_skybox();
+ }
+ else {
+ if( !OK( map_skybox_from_section( skybox, game, m ) ) ) {
+ dlog( errstr, path, "skybox" );
+ dlog( "using default skybox" );
+ 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;
+}