├── README.md ├── qll_q3.h └── tests ├── data └── test.bsp └── q3.cpp /README.md: -------------------------------------------------------------------------------- 1 | # QLL : Level loaders 2 | 3 | ## What is it? 4 | QLL is a collection of libs aimed to game developers / enthusiasts who want to use existing game files 5 | 6 | * Game developers who don't want the hassle of creating a new file format for their game levels 7 | * People who want to bring existing game levels to new purposes (by creating viewers for example) 8 | 9 | We are mainly targeting game levels which formats are well documented and open-source (to prevent us / library users of legal issues with file format) 10 | 11 | ## A word about Licence 12 | Those loaders are published under the WTFPLv2 13 | 14 | If you don't understand, think its kind alike public domain / CC0 (basically that means that you can do anything you want) 15 | 16 | ## Loaders 17 | ### QLL_Q3: Quake3 bsp loader 18 | 19 | This loader let you open Quake3 maps (or any game that uses IBSP version 0x02 format) 20 | All lumps can be retrived 21 | 22 | Example usage: 23 | ```cpp 24 | #define QLL_Q3_IMPLEMENTATION // Do this define only in one CPP file 25 | #include "qll_q3.h" 26 | 27 | int main(int argc, char** argv) 28 | { 29 | // In this example, we consider that a map called "my_map.bsp" is in the application folder 30 | qll::q3::Q3Level level = new qll::q3::Q3Level("my_map.bsp"); 31 | 32 | const qll::q3::LevelData level_data = level.getData(); 33 | 34 | // Example: we fetch entities key/values 35 | // Each map contained in the vector represents one entity with its key/values (all as string) 36 | std::vector> entities = qll::q3::parse_entities(level_data.entities); 37 | 38 | // Example game code to load entities 39 | MyGame::EntityList::loadFromQ3(entities, level_data.models); 40 | 41 | // Another example: Extract all level textures names 42 | for (unsigned int i = 0; i < level_data.textures.size(); ++i) 43 | { 44 | const qll::q3::Texture& texture_raw = level_data.textures[i]; 45 | MyGame::SomeTextureLoader::loadTexture(texture_raw.name); 46 | } 47 | 48 | // You can fetch many other data (like Models, Brushes, Faces and so on), just take a look at qll::q3::LevelData definition 49 | // But the data types are very similar to the ones used in http://www.mralligator.com/q3/ 50 | 51 | return 0; 52 | } 53 | ``` 54 | You can also extend the file to use your own containers / file classes, just take a look at the first `#defines` 55 | 56 | ## TODO 57 | * More game loaders 58 | 59 | ## Disclaimer 60 | You will solely be responsible if you use this library to load copyrighted content (ie. official game assets) 61 | 62 | ## Links 63 | 64 | * Q3 loader is based on "Unofficial Quake 3 Map Specs" by Kekoa Proudfoot at http://www.mralligator.com/q3/ 65 | 66 | Feel free to ask in issues if you have any problems / suggestions 67 | -------------------------------------------------------------------------------- /qll_q3.h: -------------------------------------------------------------------------------- 1 | /** 2 | * QLL Level loaders by Lautrivad 3 | * 4 | * Q3 Bsp loader 5 | * Based on "Unofficial Quake 3 Map Specs" by Kekoa Proudfoot 6 | * http://www.mralligator.com/q3/ 7 | * 8 | * This work is free. You can redistribute it and/or modify it under the 9 | * terms of the Do What The Fuck You Want To Public License, Version 2, 10 | * as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. 11 | */ 12 | 13 | #ifndef QLL_LOADERS_Q3_H 14 | #define QLL_LOADERS_Q3_H 15 | 16 | #ifndef QLL_Q3_STRING 17 | #include 18 | #define QLL_Q3_STRING std::string 19 | #define QLL_Q3_STRING_LENGTH(VALUE) VALUE.length() 20 | #define QLL_Q3_STRING_GET_CHAR(VALUE, POS) VALUE[POS] 21 | #define QLL_Q3_STRING_ADD_CHAR(VALUE, CHAR) VALUE.push_back(CHAR) 22 | #define QLL_Q3_STRING_FROM_VALUE(VALUE) std::to_string(VALUE) 23 | #endif 24 | 25 | #ifndef QLL_Q3_ARRAY 26 | #include 27 | #define QLL_Q3_ARRAY(T) std::vector 28 | #define QLL_Q3_ARRAY_APPEND(ARRAY, ITEM) ARRAY.push_back(ITEM) 29 | #define QLL_Q3_ARRAY_ACCESS(ARRAY, INDEX) ARRAY[INDEX] 30 | #endif 31 | 32 | #ifndef QLL_Q3_PREVENT_ENTITY_PARSER 33 | #define QLL_Q3_USE_ENTITY_PARSER 34 | #endif 35 | 36 | #ifdef QLL_Q3_USE_ENTITY_PARSER 37 | #ifndef QLL_Q3_ASSOCIATIVE_ARRAY 38 | #include 39 | #define QLL_Q3_ASSOCIATIVE_ARRAY(KEY_TYPE, VALUE_TYPE) std::map 40 | #define QLL_Q3_ASSOCIATIVE_ARRAY_SET(ARRAY, KEY, VALUE) ARRAY.emplace(KEY, VALUE) 41 | #define QLL_Q3_ASSOCIATIVE_ARRAY_GET(ARRAY, KEY) ARRAY[KEY] 42 | #endif 43 | 44 | #ifndef QLL_Q3_LOG_ERROR 45 | #define QLL_Q3_LOG_ERROR(ERROR) throw std::runtime_error(ERROR) 46 | #endif 47 | #endif 48 | 49 | namespace qll { namespace q3 { 50 | typedef unsigned char q3_ubyte; 51 | typedef int32_t q3_int; 52 | typedef float q3_float; 53 | 54 | /** 55 | * The textures lump stores information about surfaces and volumes. 56 | */ 57 | struct Texture 58 | { 59 | QLL_Q3_STRING name; // Texture name 60 | q3_int flags; // Surface flags 61 | q3_int contents; // Surface contents 62 | }; 63 | 64 | /** 65 | * The planes lump stores a generic set of planes that are in turn referenced by nodes and brushsides. 66 | */ 67 | struct Plane 68 | { 69 | q3_float normal[3]; // Plane normal 70 | q3_float distance; // Distance from origin to plane along normal 71 | }; 72 | 73 | /** 74 | * The nodes lump stores all of the nodes in the map's BSP tree. 75 | */ 76 | struct Node 77 | { 78 | q3_int plane; // Plane index 79 | q3_int front; // The child index for the front node 80 | q3_int back; // The child index for the back node 81 | q3_int mins[3]; // Integer bounding box min coord 82 | q3_int maxs[3]; // Integer bounding box max coord 83 | }; 84 | 85 | /** 86 | * The leafs lump stores the leaves of the map's BSP tree. 87 | */ 88 | struct Leaf 89 | { 90 | q3_int cluster; // Visdata cluster index 91 | q3_int area; // Areaportal area 92 | q3_int mins[3]; // Integer bounding box min coord 93 | q3_int maxs[3]; // Integer bounding box max coord 94 | q3_int leafface; // First leafface for leaf 95 | q3_int n_leaffaces; // Number of leaffaces for leaf 96 | q3_int leafbrush; // First leafbrush for leaf 97 | q3_int n_leafbrushes; // Number of leafbrushes for leaf 98 | }; 99 | 100 | /** 101 | * The leaffaces lump stores lists of face indices, with one list per leaf. 102 | */ 103 | typedef q3_int Leafface; 104 | 105 | /** 106 | * The leafbrushes lump stores lists of brush indices, with one list per leaf 107 | */ 108 | typedef q3_int Leafbrush; 109 | 110 | /** 111 | * The models lump describes rigid groups of world geometry. 112 | */ 113 | struct Model 114 | { 115 | q3_float mins[3]; // Bounding box min coord 116 | q3_float maxs[3]; // Bounding box max coord 117 | q3_int face; // First face for model 118 | q3_int n_faces; // Number of faces for model 119 | q3_int brush; // First brush for model 120 | q3_int n_brushes; // Number of brushes for model 121 | }; 122 | 123 | /** 124 | * The brushes lump stores a set of brushes, which are in turn used for collision detection. 125 | */ 126 | struct Brush 127 | { 128 | q3_int brushside; // First brushside for brush 129 | q3_int n_brushsides; // Number of brushsides for brush 130 | q3_int texture; // Texture index 131 | }; 132 | 133 | /** 134 | * The brushsides lump stores descriptions of brush bounding surfaces. 135 | */ 136 | struct Brushside 137 | { 138 | q3_int plane; // Plane index. 139 | q3_int texture; // Texture index. 140 | }; 141 | 142 | /** 143 | * The vertexes lump stores lists of vertices used to describe faces. 144 | */ 145 | struct Vertex 146 | { 147 | q3_float position[3]; // Vertex position 148 | q3_float tex_coord[2][2]; // Vertex texture coordinates. 0 = Surface, 1 = Lightmap 149 | q3_float normal[3]; // Vertex normal 150 | q3_ubyte color[4]; // Vertex color (RGBA) 151 | }; 152 | 153 | typedef q3_int Meshvert; 154 | 155 | /** 156 | * The effects lump stores references to volumetric shaders (typically fog) which affect the rendering of a particular group of faces. 157 | */ 158 | struct Effect 159 | { 160 | QLL_Q3_STRING name; // Effect shader 161 | q3_int brush; // Brush that generated this effect 162 | q3_int unknown; // Always 5, except in q3dm8, which has one effect with -1 163 | }; 164 | 165 | /** 166 | * The faces lump stores information used to render the surfaces of the map. 167 | */ 168 | struct Face 169 | { 170 | q3_int texture; // Texture index 171 | q3_int effect; // Index into Effects lump 12, or -1 172 | q3_int type; // Face type. 1 = Polygon, 2 = Patch, 3 = Mesh, 4 = Billboard 173 | q3_int vertex; // Index of first vertex 174 | q3_int n_vertices; // Number of vertices 175 | q3_int meshvert; // Index of first meshvert 176 | q3_int n_meshverts; // Number of meshverts 177 | q3_int lm_index; // Lightmap index 178 | q3_int lm_start[2]; // Corner of this face's lightmap image in lightmap 179 | q3_int lm_size[2]; // Size of this face's lightmap image in lightmap 180 | q3_float lm_origin[3]; // World space origin of lightmap 181 | q3_float lm_vecs[2][3]; // World space lightmap s and t unit vectors 182 | q3_float normal[3]; // Surface normal 183 | q3_int patch_size[2]; // Patch dimensions 184 | }; 185 | 186 | /** 187 | * The lightmaps lump stores the light map textures used make surface lighting look more realistic. 188 | */ 189 | struct Lightmap 190 | { 191 | q3_ubyte data[128][128][3]; // Lightmap color data. RGB 192 | }; 193 | 194 | /** 195 | * The lightvols lump stores a uniform grid of lighting information used to illuminate non-map objects. 196 | */ 197 | struct Lightvol 198 | { 199 | q3_ubyte ambient[3]; // Ambient color component. RGB 200 | q3_ubyte directional[3]; // Directional color component. RGB 201 | q3_ubyte dir[2]; // Direction to light. 0=phi, 1=theta 202 | }; 203 | 204 | /** 205 | * The visdata lump stores bit vectors that provide cluster-to-cluster visibility information. 206 | * Cluster x is visible from cluster y if the (1 << y % 8) bit of vecs[x * sz_vecs + y / 8] is set 207 | */ 208 | struct Visdata 209 | { 210 | q3_int n_vecs; // The number of clusters 211 | q3_int sz_vecs; // Size of each vector, in bytes 212 | q3_ubyte* vecs; // Array of bytes holding the cluster vis 213 | }; 214 | 215 | struct LevelData 216 | { 217 | QLL_Q3_STRING entities; 218 | QLL_Q3_ARRAY(Texture) textures; 219 | QLL_Q3_ARRAY(Plane) planes; 220 | QLL_Q3_ARRAY(Node) nodes; 221 | QLL_Q3_ARRAY(Leaf) leaves; 222 | QLL_Q3_ARRAY(Leafface) leaf_faces; 223 | QLL_Q3_ARRAY(Leafbrush) leaf_brushes; 224 | QLL_Q3_ARRAY(Model) models; 225 | QLL_Q3_ARRAY(Brush) brushes; 226 | QLL_Q3_ARRAY(Brushside) brush_sides; 227 | QLL_Q3_ARRAY(Vertex) vertices; 228 | QLL_Q3_ARRAY(Meshvert) mesh_vertices; 229 | QLL_Q3_ARRAY(Effect) effects; 230 | QLL_Q3_ARRAY(Face) faces; 231 | QLL_Q3_ARRAY(Lightmap) light_maps; 232 | QLL_Q3_ARRAY(Lightvol) light_vols; 233 | Visdata vis_data; 234 | }; 235 | 236 | #ifdef QLL_Q3_USE_ENTITY_PARSER 237 | QLL_Q3_ARRAY(QLL_Q3_ASSOCIATIVE_ARRAY(QLL_Q3_STRING, QLL_Q3_STRING)) parse_entities(const QLL_Q3_STRING& lump_data); 238 | #endif 239 | 240 | class Q3Level 241 | { 242 | public: 243 | Q3Level(const QLL_Q3_STRING& filename); 244 | ~Q3Level(); 245 | 246 | /** 247 | * Get raw level data 248 | */ 249 | const LevelData& getData() const { return _data; } 250 | 251 | /** 252 | * Check if the file is a valid bsp map 253 | */ 254 | static bool isValid(const QLL_Q3_STRING& filename); 255 | protected: 256 | LevelData _data; 257 | }; 258 | }} 259 | 260 | #endif 261 | 262 | #ifdef QLL_Q3_IMPLEMENTATION 263 | 264 | #ifndef QLL_Q3_FILE_TYPE 265 | #include 266 | #define QLL_Q3_FILE_TYPE FILE* 267 | 268 | #define QLL_Q3_FILE_FOPEN(STR) fopen(STR.c_str(), "r+b") 269 | #define QLL_Q3_FILE_FCLOSE(HANDLE) fclose(HANDLE) 270 | 271 | #define QLL_Q3_FILE_FREAD(TARGET, COUNT, SIZE, HANDLE) fread(TARGET, SIZE, COUNT, HANDLE) 272 | 273 | #define QLL_Q3_FILE_FSEEK(HANDLE, OFFSET) fseek(HANDLE, OFFSET, SEEK_SET) 274 | #endif 275 | 276 | #ifndef QLL_Q3_CUSTOM_MEMALLOC 277 | #define QLL_Q3_MALLOC(SIZE) malloc(SIZE) 278 | #define QLL_Q3_FREE(BUFFER) free(BUFFER) 279 | #endif 280 | 281 | #define ENTITIES_LUMP 0x00 282 | #define TEXTURES_LUMP 0x01 283 | #define PLANES_LUMP 0x02 284 | #define NODES_LUMP 0x03 285 | #define LEAF_LUMP 0x04 286 | #define LEAFFACES_LUMP 0x05 287 | #define LEAFBRUSHES_LUMP 0x06 288 | #define MODELS_LUMP 0x07 289 | #define BRUSHES_LUMP 0x08 290 | #define BRUSHSIDES_LUMP 0x09 291 | #define VERTICES_LUMP 0x0A 292 | #define MESHVERTS_LUMP 0x0B 293 | #define EFFECTS_LUMP 0x0C 294 | #define FACES_LUMP 0x0D 295 | #define LIGHTMAPS_LUMP 0x0E 296 | #define LIGHTVOLS_LUMP 0x0F 297 | #define VISDATA_LUMP 0x10 298 | 299 | namespace qll { namespace q3 { 300 | #define QUAKE3_BSP_MAGIC_LEN 4 301 | static const q3_ubyte __quake3_bsp_magic[] = "IBSP"; 302 | static const q3_int __quake3_bsp_version = 0x2e; // 46 303 | 304 | static const q3_int __quake3_bsp_lumps_count = 17; 305 | 306 | bool Q3Level::isValid(const QLL_Q3_STRING& filename) 307 | { 308 | bool result = true; 309 | 310 | QLL_Q3_FILE_TYPE file_handle = QLL_Q3_FILE_FOPEN(filename); 311 | 312 | if (!file_handle) 313 | return false; 314 | 315 | if (result) 316 | { 317 | // Check the magic number 318 | q3_ubyte magic[QUAKE3_BSP_MAGIC_LEN]; 319 | QLL_Q3_FILE_FREAD(magic, 1, QUAKE3_BSP_MAGIC_LEN, file_handle); 320 | 321 | for (q3_ubyte i = 0; i< QUAKE3_BSP_MAGIC_LEN; ++i) 322 | { 323 | if (magic[i] != __quake3_bsp_magic[i]) 324 | { 325 | result = false; 326 | break; 327 | } 328 | } 329 | } 330 | 331 | if (result) 332 | { 333 | // Check if the bsp version is the Quake3 one 334 | q3_int version; 335 | QLL_Q3_FILE_FREAD(&version, 1, sizeof(q3_int), file_handle); 336 | 337 | if (version != __quake3_bsp_version) 338 | result = false; 339 | } 340 | 341 | if (file_handle) 342 | QLL_Q3_FILE_FCLOSE(file_handle); 343 | 344 | return result; 345 | } 346 | 347 | struct __lump_header 348 | { 349 | q3_int offset; 350 | q3_int length; 351 | }; 352 | 353 | template static void __read_lump 354 | ( 355 | QLL_Q3_FILE_TYPE file_handle, QLL_Q3_ARRAY(T)& result, const __lump_header& header 356 | ); 357 | 358 | // Special case for texture lump 359 | template <> void __read_lump(QLL_Q3_FILE_TYPE file_handle, QLL_Q3_ARRAY(Texture)& result, const __lump_header& header); 360 | 361 | // Special case for effect lump 362 | template <> void __read_lump(QLL_Q3_FILE_TYPE file_handle, QLL_Q3_ARRAY(Effect)& result, const __lump_header& header); 363 | 364 | // Entities / Visdata have their own special cases 365 | static void __read_entities_lump(QLL_Q3_FILE_TYPE file_handle, QLL_Q3_STRING& result, const __lump_header& header); 366 | static void __read_visdata_lump(QLL_Q3_FILE_TYPE file_handle, Visdata& result, const __lump_header& header); 367 | 368 | static void __read_string(QLL_Q3_FILE_TYPE file_handle, QLL_Q3_STRING& result, const q3_int& length); 369 | 370 | Q3Level::Q3Level(const QLL_Q3_STRING& filename) 371 | { 372 | if (!isValid(filename)) 373 | return; 374 | 375 | QLL_Q3_FILE_TYPE file_handle = QLL_Q3_FILE_FOPEN(filename); 376 | 377 | // Read headers (Ignore magic + version) 378 | QLL_Q3_FILE_FSEEK(file_handle, QUAKE3_BSP_MAGIC_LEN + sizeof(q3_int)); 379 | 380 | QLL_Q3_ARRAY(__lump_header) headers; 381 | 382 | for (q3_int i = 0; i<__quake3_bsp_lumps_count; ++i) 383 | { 384 | __lump_header item; 385 | QLL_Q3_FILE_FREAD(&item, 1, sizeof(__lump_header), file_handle); 386 | QLL_Q3_ARRAY_APPEND(headers, item); 387 | } 388 | 389 | // Read all lumpes 390 | __read_entities_lump(file_handle, _data.entities, headers[ENTITIES_LUMP]); 391 | __read_lump(file_handle, _data.textures, headers[TEXTURES_LUMP]); 392 | __read_lump(file_handle, _data.planes, headers[PLANES_LUMP]); 393 | __read_lump(file_handle, _data.nodes, headers[NODES_LUMP]); 394 | __read_lump(file_handle, _data.leaves, headers[LEAF_LUMP]); 395 | __read_lump(file_handle, _data.leaf_faces, headers[LEAFFACES_LUMP]); 396 | __read_lump(file_handle, _data.leaf_brushes, headers[LEAFBRUSHES_LUMP]); 397 | __read_lump(file_handle, _data.models, headers[MODELS_LUMP]); 398 | __read_lump(file_handle, _data.brushes, headers[BRUSHES_LUMP]); 399 | __read_lump(file_handle, _data.brush_sides, headers[BRUSHSIDES_LUMP]); 400 | __read_lump(file_handle, _data.vertices, headers[VERTICES_LUMP]); 401 | __read_lump(file_handle, _data.mesh_vertices, headers[MESHVERTS_LUMP]); 402 | __read_lump(file_handle, _data.effects, headers[EFFECTS_LUMP]); 403 | __read_lump(file_handle, _data.faces, headers[FACES_LUMP]); 404 | __read_lump(file_handle, _data.light_maps, headers[LIGHTMAPS_LUMP]); 405 | __read_lump(file_handle, _data.light_vols, headers[LIGHTVOLS_LUMP]); 406 | __read_visdata_lump(file_handle, _data.vis_data, headers[VISDATA_LUMP]); 407 | 408 | QLL_Q3_FILE_FCLOSE(file_handle); 409 | } 410 | 411 | Q3Level::~Q3Level() 412 | { 413 | if (_data.vis_data.vecs) 414 | QLL_Q3_FREE(_data.vis_data.vecs); 415 | } 416 | 417 | // Tools functions 418 | 419 | static void __read_string( 420 | QLL_Q3_FILE_TYPE file_handle, 421 | QLL_Q3_STRING& result, 422 | const q3_int& length 423 | ) 424 | { 425 | char* raw_data = (char*)QLL_Q3_MALLOC(length); 426 | 427 | QLL_Q3_FILE_FREAD(raw_data, 1, length, file_handle); 428 | result = QLL_Q3_STRING(raw_data); 429 | QLL_Q3_FREE(raw_data); 430 | } 431 | 432 | template static void __read_lump 433 | ( 434 | QLL_Q3_FILE_TYPE file_handle, 435 | QLL_Q3_ARRAY(T)& result, 436 | const __lump_header& header 437 | ) 438 | { 439 | int item_count = header.length / sizeof(T); 440 | 441 | // Go to the start of the chunk. 442 | QLL_Q3_FILE_FSEEK(file_handle, header.offset); 443 | 444 | for (int i = 0; i < item_count; ++i) 445 | { 446 | T item; 447 | QLL_Q3_FILE_FREAD(&item, 1, sizeof(T), file_handle); 448 | QLL_Q3_ARRAY_APPEND(result, item); 449 | } 450 | } 451 | 452 | template <> void __read_lump 453 | ( 454 | QLL_Q3_FILE_TYPE file_handle, 455 | QLL_Q3_ARRAY(Texture)& result, 456 | const __lump_header& header 457 | ) 458 | { 459 | const int lump_size = 2 * sizeof(q3_int) + 64; 460 | int item_count = header.length / lump_size; 461 | 462 | // Go to the start of the chunk. 463 | QLL_Q3_FILE_FSEEK(file_handle, header.offset); 464 | 465 | for (int i = 0; i < item_count; ++i) 466 | { 467 | Texture item; 468 | 469 | __read_string(file_handle, item.name, 64); 470 | QLL_Q3_FILE_FREAD(&item.flags, 1, sizeof(q3_int), file_handle); 471 | QLL_Q3_FILE_FREAD(&item.contents, 1, sizeof(q3_int), file_handle); 472 | 473 | QLL_Q3_ARRAY_APPEND(result, item); 474 | } 475 | } 476 | 477 | template <> void __read_lump 478 | ( 479 | QLL_Q3_FILE_TYPE file_handle, 480 | QLL_Q3_ARRAY(Effect)& result, 481 | const __lump_header& header 482 | ) 483 | { 484 | const int lump_size = 2 * sizeof(q3_int) + 64; 485 | int item_count = header.length / lump_size; 486 | 487 | // Go to the start of the chunk. 488 | QLL_Q3_FILE_FSEEK(file_handle, header.offset); 489 | 490 | for (int i = 0; i < item_count; ++i) 491 | { 492 | Effect item; 493 | 494 | __read_string(file_handle, item.name, 64); 495 | QLL_Q3_FILE_FREAD(&item.brush, 1, sizeof(q3_int), file_handle); 496 | QLL_Q3_FILE_FREAD(&item.unknown, 1, sizeof(q3_int), file_handle); 497 | 498 | QLL_Q3_ARRAY_APPEND(result, item); 499 | } 500 | } 501 | 502 | static void __read_visdata_lump 503 | ( 504 | QLL_Q3_FILE_TYPE file_handle, 505 | Visdata& result, 506 | const __lump_header& header 507 | ) 508 | { 509 | QLL_Q3_FILE_FSEEK(file_handle, header.offset); 510 | 511 | QLL_Q3_FILE_FREAD(&result.n_vecs, 1, sizeof(q3_int), file_handle); 512 | QLL_Q3_FILE_FREAD(&result.sz_vecs, 1, sizeof(q3_int), file_handle); 513 | 514 | int visdata_length = result.n_vecs * result.sz_vecs; 515 | 516 | result.vecs = (q3_ubyte*)QLL_Q3_MALLOC(visdata_length); 517 | 518 | QLL_Q3_FILE_FREAD(result.vecs, 1, visdata_length, file_handle); 519 | } 520 | 521 | static void __read_entities_lump 522 | ( 523 | QLL_Q3_FILE_TYPE file_handle, 524 | QLL_Q3_STRING& result, 525 | const __lump_header& header 526 | ) 527 | { 528 | QLL_Q3_FILE_FSEEK(file_handle, header.offset); 529 | 530 | char* entity_raw = (char*)QLL_Q3_MALLOC(header.length); 531 | 532 | QLL_Q3_FILE_FREAD(entity_raw, header.length, 1, file_handle); 533 | 534 | result = QLL_Q3_STRING(entity_raw); 535 | 536 | QLL_Q3_FREE(entity_raw); 537 | } 538 | 539 | 540 | #define QLL_Q3_ENTITY_TYPE QLL_Q3_ASSOCIATIVE_ARRAY(QLL_Q3_STRING, QLL_Q3_STRING) 541 | #define QLL_Q3_ENTITIES_RESULT_TYPE QLL_Q3_ARRAY(QLL_Q3_ENTITY_TYPE) 542 | QLL_Q3_ENTITIES_RESULT_TYPE parse_entities(const QLL_Q3_STRING& entities_lump) 543 | { 544 | QLL_Q3_ENTITIES_RESULT_TYPE result; 545 | QLL_Q3_ENTITY_TYPE current_entity; 546 | 547 | unsigned int i = 0; 548 | 549 | bool inKey = false; 550 | bool inValue = false; 551 | bool inGroup = false; 552 | bool keyFilled = false; 553 | 554 | QLL_Q3_STRING key, value; 555 | 556 | while (i < QLL_Q3_STRING_LENGTH(entities_lump)) 557 | { 558 | const char current_char = QLL_Q3_STRING_GET_CHAR(entities_lump, i); 559 | 560 | if (current_char == '{' && !inKey && !inValue) 561 | { 562 | if (!inGroup) 563 | inGroup = true; 564 | else 565 | QLL_Q3_LOG_ERROR("Unexpected '{' at position " + QLL_Q3_STRING_FROM_VALUE(i)); 566 | } 567 | else if (current_char == '}' && !inKey && !inValue) 568 | { 569 | if (inGroup) 570 | { 571 | QLL_Q3_ARRAY_APPEND(result, current_entity); 572 | current_entity = {}; 573 | 574 | inGroup = false; 575 | } 576 | else 577 | QLL_Q3_LOG_ERROR("Unexpected '}' at position " + QLL_Q3_STRING_FROM_VALUE(i)); 578 | } 579 | else if (current_char == '"') 580 | { 581 | if (inGroup) 582 | { 583 | if (!inKey && !inValue) 584 | { 585 | // This is the begining of a string (key or value) 586 | 587 | if (!keyFilled) 588 | inKey = true; 589 | else 590 | inValue = true; 591 | } 592 | else 593 | { 594 | // End of a string (key or value) 595 | 596 | if (inKey) 597 | { 598 | keyFilled = true; 599 | inKey = false; 600 | } 601 | else if (inValue) 602 | { 603 | QLL_Q3_ASSOCIATIVE_ARRAY_SET(current_entity, key, value); 604 | key = value = ""; 605 | 606 | // Reset the parser states 607 | inValue = false; 608 | keyFilled = false; 609 | } 610 | } 611 | } 612 | else 613 | QLL_Q3_LOG_ERROR("Unexpected '\"' at position " + QLL_Q3_STRING_FROM_VALUE(i)); 614 | } 615 | else 616 | { 617 | // Adding a character 618 | 619 | if (inKey) 620 | QLL_Q3_STRING_ADD_CHAR(key, current_char); 621 | 622 | if (inValue) 623 | QLL_Q3_STRING_ADD_CHAR(value, current_char); 624 | } 625 | 626 | i++; 627 | } 628 | 629 | if (inGroup) 630 | QLL_Q3_LOG_ERROR("Unexpected end!"); 631 | 632 | return result; 633 | } 634 | }} 635 | #endif 636 | -------------------------------------------------------------------------------- /tests/data/test.bsp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lautriva/qll/f19866f089347ae170a6c037c1a1112d677a5a32/tests/data/test.bsp -------------------------------------------------------------------------------- /tests/q3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define QLL_Q3_IMPLEMENTATION 4 | #include "../qll_q3.h" 5 | 6 | int main(int argc, char** argv) 7 | { 8 | unsigned int count; 9 | 10 | // Load the map 11 | qll::q3::Q3Level level = qll::q3::Q3Level("data/test.bsp"); 12 | 13 | qll::q3::LevelData level_data = level.getData(); 14 | 15 | // Example: we fetch entities key/values 16 | // Each map contained in the vector represents one entity with its key/values (all as string) 17 | std::vector> entities = qll::q3::parse_entities(level_data.entities); 18 | 19 | count = entities.size(); 20 | std::cout << "There are " << count << " entities:" << std::endl; 21 | 22 | for (unsigned int i = 0; i < count; ++i) 23 | { 24 | std::cout << "- " << (entities[i].count("classname") ? entities[i]["classname"] : "?") << " (" << i << ")" << std::endl; 25 | 26 | for (auto it : entities[i]) 27 | { 28 | // Do not show again entity class name 29 | if (it.first != "classname") 30 | std::cout << " - " << it.first << " => '" << it.second << "'" << std::endl; 31 | } 32 | 33 | std::cout << std::endl; 34 | } 35 | 36 | std::cout << std::endl; 37 | 38 | // Extract all level textures names 39 | count = level_data.textures.size(); 40 | std::cout << "There are " << count << " textures:" << std::endl; 41 | 42 | for (unsigned int i = 0; i < count; ++i) 43 | { 44 | const qll::q3::Texture& texture_raw = level_data.textures[i]; 45 | std::cout << "- " << texture_raw.name << std::endl; 46 | } 47 | 48 | std::cout << std::endl; 49 | 50 | // For those next, only print the count 51 | std::cout << "There are " << level_data.models.size() << " models" << std::endl; 52 | std::cout << "There are " << level_data.faces.size() << " faces" << std::endl; 53 | std::cout << "There are " << level_data.brushes.size() << " brushes" << std::endl; 54 | 55 | return 0; 56 | } --------------------------------------------------------------------------------