From f8b92ce3aa08b1445c9f956d8166830946562d12 Mon Sep 17 00:00:00 2001 From: navewindre Date: Wed, 3 Sep 2025 20:10:09 +0200 Subject: a --- src/game/world/map.cpp | 640 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 640 insertions(+) create mode 100644 src/game/world/map.cpp (limited to 'src/game/world/map.cpp') 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 + +#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; +} -- cgit v1.2.3