├── .gitignore ├── gendoc.py ├── cflags ├── UNLICENSE ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | /q3playground 2 | *.core 3 | *.swp 4 | *.log 5 | /pak* 6 | -------------------------------------------------------------------------------- /gendoc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | import sys 4 | print_lines = False 5 | 6 | for line in sys.stdin: 7 | if line.strip().endswith("*/"): 8 | sys.exit(0); 9 | if print_lines: 10 | print(line[3:-1]) 11 | elif line.strip().startswith("/*"): 12 | print_lines = True 13 | -------------------------------------------------------------------------------- /cflags: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! which sdl2-config >/dev/null 2>&1 ]; then 4 | >&2 echo "can't find sdl2-config, are you sure you have SDL2?" 5 | fi 6 | 7 | cflags="`sdl2-config --cflags`" 8 | cflags="$cflags -std=c89 -pedantic" 9 | cflags="$cflags -O3" 10 | cflags="$cflags -Wall -Wextra -Wno-unused-value -Wunused" 11 | cflags="$cflags -ffunction-sections -fdata-sections" 12 | cflags="$cflags -g0 -fno-unwind-tables -s" 13 | cflags="$cflags -fno-asynchronous-unwind-tables" 14 | cflags="$cflags -Wl,--gc-sections" 15 | 16 | ldflags="-lGL -lGLU `sdl2-config --libs`" 17 | 18 | cflags="$cflags $CFLAGS" 19 | ldflags="$ldflags $LDFLAGS" 20 | 21 | case `uname` in 22 | Darwin|FreeBSD) cc="${CC:-clang}" ;; 23 | *) cc="${CC:-gcc}" ;; 24 | esac 25 | 26 | which $cc >/dev/null 2>&1 || cc=gcc 27 | which $cc >/dev/null 2>&1 || cc=clang 28 | 29 | if [ ! which $cc >/dev/null 2>&1 ]; then 30 | >&2 echo "can't find any compiler, please specify CC" 31 | fi 32 | 33 | uname -a > flags.log 34 | echo $cc >> flags.log 35 | echo $cflags >> flags.log 36 | echo $ldflags >> flags.log 37 | $cc --version >> flags.log 38 | $cc -dumpmachine >> flags.log 39 | 40 | export cflags="$cflags" 41 | export ldflags="$ldflags" 42 | export cc="$cc" 43 | 44 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | quake 3 bsp renderer in c89 and opengl 1.x 2 | 3 | ![](https://i.imgur.com/cLpZPiW.gif) 4 | ![](https://i.imgur.com/0gMLx0a.gif) 5 | ![](https://i.imgur.com/Ig4hFPQ.gif) 6 | 7 | dependencies: libGL, libGLU, SDL2 8 | 9 | should be compatible at least with x86/x86\_64 windows, linux, freebsd 10 | with gcc, clang, msvc 11 | 12 | I might or might not add more features in the future, for now I have: 13 | 14 | * rendering meshes and patches 15 | * vertex lighting 16 | * collision detection with brushes (no patches aka curved surfaces yet) 17 | * cpm-like physics 18 | * sliding against bushes (no patches though) 19 | 20 | the current priority is getting patches collisions and implement steps 21 | so we can actually walk up stairs 22 | 23 | # compiling 24 | just run ```./build``` . it's aware of ```CC```, ```CFLAGS```, 25 | ```LDFLAGS``` in case you need to override anything 26 | 27 | windows build script is TODO 28 | 29 | # usage 30 | unzip the .pk3 files from your copy of quake 3. some of these will 31 | contain .bsp files for the maps. you can run q3playground on them 32 | like so: 33 | 34 | ``` 35 | q3playground /path/to/map.bsp 36 | ``` 37 | 38 | on *nix you might want to disable vsync by running it like 39 | 40 | ``` 41 | env vblank_mode=0 q3playground /path/to/map.bsp 42 | ``` 43 | 44 | controls are WASD, space, mouse, right click. toggle noclip with F 45 | 46 | run ```q3playground``` with no arguments for more info 47 | 48 | # license 49 | this is free and unencumbered software released into the public domain. 50 | refer to the attached UNLICENSE or http://unlicense.org/ 51 | 52 | # references 53 | * unofficial bsp format spec: http://www.mralligator.com/q3/ 54 | * tessellation implementation: 55 | http://graphics.cs.brown.edu/games/quake/quake3.html 56 | * collision detection: 57 | https://web.archive.org/web/20041206085743/http://www.nathanostgard.com:80/tutorials/quake3/collision/ 58 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * quake 3 bsp renderer in c89 and opengl 1.x 3 | * 4 | * ![](https://i.imgur.com/cLpZPiW.gif) 5 | * ![](https://i.imgur.com/0gMLx0a.gif) 6 | * ![](https://i.imgur.com/Ig4hFPQ.gif) 7 | * 8 | * dependencies: libGL, libGLU, SDL2 9 | * 10 | * should be compatible at least with x86/x86\_64 windows, linux, freebsd 11 | * with gcc, clang, msvc 12 | * 13 | * I might or might not add more features in the future, for now I have: 14 | * 15 | * * rendering meshes and patches 16 | * * vertex lighting 17 | * * collision detection with brushes (no patches aka curved surfaces yet) 18 | * * cpm-like physics 19 | * * sliding against bushes (no patches though) 20 | * 21 | * the current priority is getting patches collisions and implement steps 22 | * so we can actually walk up stairs 23 | * 24 | * # compiling 25 | * just run ```./build``` . it's aware of ```CC```, ```CFLAGS```, 26 | * ```LDFLAGS``` in case you need to override anything 27 | * 28 | * windows build script is TODO 29 | * 30 | * # usage 31 | * unzip the .pk3 files from your copy of quake 3. some of these will 32 | * contain .bsp files for the maps. you can run q3playground on them 33 | * like so: 34 | * 35 | * ``` 36 | * q3playground /path/to/map.bsp 37 | * ``` 38 | * 39 | * on *nix you might want to disable vsync by running it like 40 | * 41 | * ``` 42 | * env vblank_mode=0 q3playground /path/to/map.bsp 43 | * ``` 44 | * 45 | * controls are WASD, space, mouse, right click. toggle noclip with F 46 | * 47 | * run ```q3playground``` with no arguments for more info 48 | * 49 | * # license 50 | * this is free and unencumbered software released into the public domain. 51 | * refer to the attached UNLICENSE or http://unlicense.org/ 52 | * 53 | * # references 54 | * * unofficial bsp format spec: http://www.mralligator.com/q3/ 55 | * * tessellation implementation: 56 | * http://graphics.cs.brown.edu/games/quake/quake3.html 57 | * * collision detection: 58 | * https://web.archive.org/web/20041206085743/http://www.nathanostgard.com:80/tutorials/quake3/collision/ 59 | */ 60 | 61 | #include 62 | 63 | #define degrees(rad) ((rad) * (180.0f / M_PI)) 64 | #define radians(deg) ((deg) * (M_PI / 180.0f)) 65 | #define eq3(a, b) \ 66 | ((a)[0] == (b)[0] && (a)[1] == (b)[1] && (a)[2] == (b)[2]) 67 | #define clr3(a) ((a)[0] = 0, (a)[1] = 0, (a)[2] = 0) 68 | #define cpy3(a, b) ((a)[0] = (b)[0], (a)[1] = (b)[1], (a)[2] = (b)[2]) 69 | #define dot3(a, b) ((a)[0] * (b)[0] + (a)[1] * (b)[1] + (a)[2] * (b)[2]) 70 | #define mag3(v) (float)SDL_sqrt(dot3(v, v)) 71 | #define cross3(a, b, dst) ( \ 72 | (dst)[0] = (a)[1] * (b)[2] - (a)[2] * (b)[1], \ 73 | (dst)[1] = (a)[2] * (b)[0] - (a)[0] * (b)[2], \ 74 | (dst)[2] = (a)[0] * (b)[1] - (a)[1] * (b)[0] \ 75 | ) 76 | #define add2(a, b) ((a)[0] += (b)[0], (a)[1] += (b)[1]) 77 | #define add3(a, b) ((a)[0] += (b)[0], (a)[1] += (b)[1], (a)[2] += (b)[2]) 78 | #define mul2_scalar(a, b) ((a)[0] *= b, (a)[1] *= b) 79 | #define mul3_scalar(a, b) ((a)[0] *= b, (a)[1] *= b, (a)[2] *= b) 80 | #define div3_scalar(a, b) ((a)[0] /= b, (a)[1] /= b, (a)[2] /= b) 81 | #define expand2(x) (x)[0], (x)[1] 82 | #define expand3(x) (x)[0], (x)[1], (x)[2] 83 | 84 | #define lninfo __FILE__, __LINE__ 85 | #define log_puts(x) log_print(lninfo, "%s", x) 86 | #define log_dump(spec, var) log_print(lninfo, #var " = %" spec, var) 87 | 88 | void log_print(char const* file, int line, char* fmt, ...) 89 | { 90 | va_list va; 91 | char* msg; 92 | int msg_len; 93 | char* p; 94 | char* end; 95 | 96 | msg_len = 0; 97 | msg_len += SDL_snprintf(0, 0, "[%s:%d] ", file, line); 98 | va_start(va, fmt); 99 | msg_len += SDL_vsnprintf(0, 0, fmt, va); 100 | va_end(va); 101 | msg_len += 2; 102 | 103 | msg = SDL_malloc(msg_len); 104 | if (!msg) { 105 | SDL_Log("log_print alloc failed: %s", SDL_GetError()); 106 | return; 107 | } 108 | 109 | p = msg; 110 | end = msg + msg_len; 111 | p += SDL_snprintf(p, end - p, "[%s:%d] ", file, line); 112 | 113 | va_start(va, fmt); 114 | p += SDL_vsnprintf(p, end - p, fmt, va); 115 | va_end(va); 116 | 117 | for (p = msg; p < end; p += SDL_MAX_LOG_MESSAGE - 7) { 118 | SDL_Log("%s", p); 119 | } 120 | 121 | SDL_free(msg); 122 | } 123 | 124 | struct vec_header 125 | { 126 | int n; 127 | int cap; 128 | }; 129 | 130 | /* cover your eyes, don't look at the macro abominations */ 131 | 132 | #define vec_hdr(v) (v ? ((struct vec_header*)(v) - 1) : 0) 133 | #define vec_len(v) (v ? vec_hdr(v)->n : 0) 134 | #define vec_cap(v) (v ? vec_hdr(v)->cap : 0) 135 | #define vec_grow(v, n) (*(void**)&(v) = vec_fit(v, n, sizeof((v)[0]))) 136 | #define vec_reserve(v, n) ( \ 137 | vec_grow(v, vec_len(v) + n), \ 138 | &(v)[vec_len(v)] \ 139 | ) 140 | #define vec_append(v, x) (*vec_append_p(v) = (x)) 141 | #define vec_append_p(v) ( \ 142 | vec_reserve(v, 1), \ 143 | &(v)[vec_hdr(v)->n++] \ 144 | ) 145 | #define vec_cat(v, array, array_size) ( \ 146 | memcpy(vec_reserve(v, array_size), array, array_size * sizeof(v)[0]), \ 147 | vec_hdr(v)->n += array_size \ 148 | ) 149 | #define vec_clear(v) (v ? vec_hdr(v)->n = 0 : 0) 150 | #define vec_free(v) (SDL_free(vec_hdr(v)), v = 0) 151 | 152 | void* vec_fit(void* v, int n, int element_size) 153 | { 154 | struct vec_header* hdr; 155 | 156 | hdr = vec_hdr(v); 157 | 158 | if (!hdr || SDL_max(n, vec_len(v)) >= vec_cap(v)) 159 | { 160 | int new_cap; 161 | int alloc_size; 162 | 163 | new_cap = SDL_max(n, vec_cap(v) * 2); 164 | alloc_size = sizeof(struct vec_header) + new_cap * element_size; 165 | 166 | if (hdr) { 167 | hdr = SDL_realloc(hdr, alloc_size); 168 | } else { 169 | hdr = SDL_malloc(alloc_size); 170 | hdr->n = 0; 171 | } 172 | 173 | hdr->cap = new_cap; 174 | } 175 | 176 | return hdr + 1; 177 | } 178 | 179 | void nrm3(float* v) 180 | { 181 | float squared_len; 182 | float len; 183 | 184 | squared_len = dot3(v, v); 185 | 186 | if (squared_len < 0.0001f) { 187 | return; 188 | } 189 | 190 | len = (float)SDL_sqrt(squared_len); 191 | div3_scalar(v, len); 192 | } 193 | 194 | char* snprintf_alloc(char* fmt, ...) 195 | { 196 | va_list va; 197 | char* str; 198 | int len; 199 | 200 | va_start(va, fmt); 201 | len = SDL_vsnprintf(0, 0, fmt, va) + 1; 202 | va_end(va); 203 | 204 | str = SDL_malloc(len); 205 | if (str) { 206 | va_start(va, fmt); 207 | SDL_vsnprintf(str, len, fmt, va); 208 | va_end(va); 209 | } 210 | 211 | return str; 212 | } 213 | 214 | SDL_RWops* open_data_file(char* file, char* mode) 215 | { 216 | static char* data_path = 0; 217 | SDL_RWops* io; 218 | char* real_path; 219 | 220 | if (!data_path) 221 | { 222 | data_path = SDL_GetBasePath(); 223 | if (!data_path) { 224 | data_path = "./"; 225 | } 226 | 227 | log_dump("s", data_path); 228 | } 229 | 230 | real_path = snprintf_alloc("%s/%s", data_path, file); 231 | io = SDL_RWFromFile(real_path, mode); 232 | SDL_free(real_path); 233 | 234 | return io; 235 | } 236 | 237 | char* read_entire_file(char* file) 238 | { 239 | SDL_RWops* io; 240 | char* res = 0; 241 | char buf[1024]; 242 | size_t n; 243 | 244 | io = open_data_file(file, "rb"); 245 | if (!io) { 246 | log_puts(SDL_GetError()); 247 | SDL_ClearError(); 248 | return 0; 249 | } 250 | 251 | while (1) 252 | { 253 | n = SDL_RWread(io, buf, 1, sizeof(buf)); 254 | 255 | if (!n) 256 | { 257 | if (*SDL_GetError()) 258 | { 259 | log_print(lninfo, "SDL_RWread failed: %s", SDL_GetError()); 260 | SDL_ClearError(); 261 | vec_free(res); 262 | goto cleanup; 263 | } 264 | 265 | break; 266 | } 267 | 268 | vec_cat(res, buf, n); 269 | } 270 | 271 | cleanup: 272 | SDL_RWclose(io); 273 | return res; 274 | } 275 | 276 | /* --------------------------------------------------------------------- */ 277 | 278 | #include 279 | #include 280 | 281 | int gl_display; 282 | int gl_window_mode; 283 | int gl_width; 284 | int gl_height; 285 | 286 | SDL_Window* gl_window; 287 | SDL_GLContext* gl_context; 288 | 289 | int gl_major_version; 290 | int gl_sl_major_version; 291 | int gl_max_texture_size; 292 | 293 | #define glGetCString (char*)glGetString 294 | #define log_glstring(i) log_print(lninfo, #i " = %s", glGetCString(i)) 295 | 296 | void gl_init() 297 | { 298 | int flags; 299 | 300 | if (gl_window) { 301 | SDL_SetWindowSize(gl_window, gl_width, gl_height); 302 | return; 303 | } 304 | 305 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); 306 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 307 | 308 | flags = SDL_WINDOW_OPENGL; 309 | 310 | if (!gl_window_mode) 311 | { 312 | SDL_DisplayMode mode; 313 | 314 | flags |= SDL_WINDOW_FULLSCREEN; 315 | 316 | if (!SDL_GetDesktopDisplayMode(gl_display, &mode)) { 317 | gl_width = mode.w; 318 | gl_height = mode.h; 319 | } 320 | 321 | else { 322 | log_print(lninfo, "SDL_GetDesktopDisplayMode failed: %s", 323 | SDL_GetError()); 324 | } 325 | } 326 | 327 | gl_window = SDL_CreateWindow("opengl", 328 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 329 | gl_width, gl_height, flags); 330 | 331 | gl_context = SDL_GL_CreateContext(gl_window); 332 | 333 | glEnable(GL_BLEND); 334 | glEnable(GL_DEPTH_TEST); 335 | 336 | glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size); 337 | 338 | log_glstring(GL_EXTENSIONS); 339 | log_glstring(GL_VERSION); 340 | log_glstring(GL_RENDERER); 341 | log_glstring(GL_SHADING_LANGUAGE_VERSION); 342 | log_glstring(GL_VENDOR); 343 | log_dump("d", gl_max_texture_size); 344 | } 345 | 346 | void gl_perspective(float horizontal_fov, float near, float far) 347 | { 348 | float aspect; 349 | float vertical_fov; 350 | float tan_half_fov; 351 | 352 | aspect = (float)gl_width / gl_height; 353 | tan_half_fov = SDL_tanf(radians(horizontal_fov) * 0.5f); 354 | vertical_fov = 2 * (float)SDL_atan(tan_half_fov / aspect); 355 | gluPerspective(degrees(vertical_fov), aspect, near, far); 356 | } 357 | 358 | /* --------------------------------------------------------------------- */ 359 | 360 | /* 361 | * using packed structs isn't portable in multiple ways but I'd rather keep 362 | * the code short and simple 363 | */ 364 | 365 | #ifdef _MSC_VER 366 | #define packed(declaration) \ 367 | __pragma pack(push, 1) \ 368 | declaration \ 369 | __pragma pack(pop) 370 | #else 371 | #define packed(declaration) \ 372 | declaration __attribute__((__packed__)) 373 | #endif 374 | 375 | packed( 376 | struct bsp_dirent 377 | { 378 | int offset; 379 | int length; 380 | }); 381 | 382 | packed( 383 | struct bsp_header 384 | { 385 | char magic[4]; 386 | int version; 387 | struct bsp_dirent dirents[17]; 388 | }); 389 | 390 | enum bsp_contents 391 | { 392 | CONTENTS_SOLID = 1, 393 | LAST_CONTENTS 394 | }; 395 | 396 | packed( 397 | struct bsp_texture 398 | { 399 | char name[64]; 400 | int flags; 401 | int contents; 402 | }); 403 | 404 | packed( 405 | struct bsp_plane 406 | { 407 | float normal[3]; 408 | float dist; 409 | }); 410 | 411 | packed( 412 | struct bsp_node 413 | { 414 | int plane; 415 | int child[2]; /* front, back */ 416 | int mins[3]; 417 | int maxs[3]; 418 | }); 419 | 420 | packed( 421 | struct bsp_leaf 422 | { 423 | int cluster; 424 | int area; 425 | int mins[3]; 426 | int maxs[3]; 427 | int leafface; 428 | int n_leaffaces; 429 | int leafbrush; 430 | int n_leafbrushes; 431 | }); 432 | 433 | packed( 434 | struct bsp_model 435 | { 436 | float mins[3]; 437 | float maxs[3]; 438 | int face; 439 | int n_faces; 440 | int brush; 441 | int n_brushes; 442 | }); 443 | 444 | packed( 445 | struct bsp_brush 446 | { 447 | int brushside; 448 | int n_brushsides; 449 | int texture; 450 | }); 451 | 452 | packed( 453 | struct bsp_brushside 454 | { 455 | int plane; 456 | int texture; 457 | }); 458 | 459 | packed( 460 | struct bsp_vertex 461 | { 462 | float position[3]; 463 | float texcoord[2][2]; 464 | float normal[3]; 465 | int color; 466 | }); 467 | 468 | packed( 469 | struct bsp_effect 470 | { 471 | char name[64]; 472 | int brush; 473 | int unknown; 474 | }); 475 | 476 | packed( 477 | struct bsp_face 478 | { 479 | int texture; 480 | int effect; 481 | int type; 482 | int vertex; 483 | int n_vertices; 484 | int meshvert; 485 | int n_meshverts; 486 | int lm_index; 487 | int lm_start[2]; 488 | int lm_size[2]; 489 | float lm_origin[3]; 490 | float lm_vecs[2][3]; 491 | float normal[3]; 492 | int size[2]; 493 | }); 494 | 495 | enum bsp_face_type 496 | { 497 | BSP_POLYGON = 1, 498 | BSP_PATCH, 499 | BSP_MESH, 500 | BSP_BILLBOARD 501 | }; 502 | 503 | packed( 504 | struct bsp_lightmap 505 | { 506 | unsigned char map[128][128][3]; 507 | }); 508 | 509 | packed( 510 | struct bsp_lightvol 511 | { 512 | unsigned char ambient[3]; 513 | unsigned char directional[3]; 514 | unsigned char dir[2]; 515 | }); 516 | 517 | packed( 518 | struct bsp_visdata 519 | { 520 | int n_vecs; 521 | int sz_vecs; 522 | }); 523 | 524 | struct bsp_file 525 | { 526 | char* raw_data; 527 | struct bsp_header* header; 528 | 529 | char* entities; 530 | int entities_len; 531 | 532 | struct bsp_texture* textures; 533 | int n_textures; 534 | 535 | struct bsp_plane* planes; 536 | int n_planes; 537 | 538 | struct bsp_node* nodes; 539 | int n_nodes; 540 | 541 | struct bsp_leaf* leaves; 542 | int n_leaves; 543 | 544 | int* leaffaces; 545 | int n_leaffaces; 546 | 547 | int* leafbrushes; 548 | int n_leafbrushes; 549 | 550 | struct bsp_model* models; 551 | int n_models; 552 | 553 | struct bsp_brush* brushes; 554 | int n_brushes; 555 | 556 | struct bsp_brushside* brushsides; 557 | int n_brushsides; 558 | 559 | struct bsp_vertex* vertices; 560 | int n_vertices; 561 | 562 | int* meshverts; 563 | int n_meshverts; 564 | 565 | struct bsp_effect* effects; 566 | int n_effects; 567 | 568 | struct bsp_face* faces; 569 | int n_faces; 570 | 571 | struct bsp_lightmap* lightmaps; 572 | int n_lightmaps; 573 | 574 | struct bsp_lightvol* lightvols; 575 | int n_lightvols; 576 | 577 | struct bsp_visdata* visdata; 578 | unsigned char* visdata_vecs; 579 | }; 580 | 581 | int bsp_load(struct bsp_file* file, char* path) 582 | { 583 | char* p; 584 | struct bsp_dirent* dirents; 585 | 586 | log_puts(path); 587 | 588 | p = read_entire_file(path); 589 | if (!p) { 590 | return 0; 591 | } 592 | 593 | if (vec_len(p) < (int)sizeof(struct bsp_header)) { 594 | log_puts("E: file is too small, truncated header data"); 595 | return 0; 596 | } 597 | 598 | file->raw_data = p; 599 | file->header = (struct bsp_header*)p; 600 | 601 | dirents = file->header->dirents; 602 | file->entities = p + dirents[0].offset; 603 | file->entities_len = dirents[0].length; 604 | 605 | #define lump(i, name, type) \ 606 | file->name = (type*)(p + dirents[i].offset); \ 607 | file->n_##name = dirents[i].length / sizeof(type); \ 608 | 609 | lump(1, textures, struct bsp_texture) 610 | lump(2, planes, struct bsp_plane) 611 | lump(3, nodes, struct bsp_node) 612 | lump(4, leaves, struct bsp_leaf) 613 | lump(5, leaffaces, int) 614 | lump(6, leafbrushes, int) 615 | lump(7, models, struct bsp_model) 616 | lump(8, brushes, struct bsp_brush) 617 | lump(9, brushsides, struct bsp_brushside) 618 | lump(10, vertices, struct bsp_vertex) 619 | lump(11, meshverts, int) 620 | lump(12, effects, struct bsp_effect) 621 | lump(13, faces, struct bsp_face) 622 | lump(14, lightmaps, struct bsp_lightmap) 623 | lump(15, lightvols, struct bsp_lightvol) 624 | 625 | #undef lump 626 | 627 | file->visdata = (struct bsp_visdata*)(p + dirents[16].offset); 628 | file->visdata_vecs = (unsigned char*)&file->visdata[1]; 629 | 630 | return 1; 631 | } 632 | 633 | /* 634 | * the plane dist is the distance from the origin along the normal so if 635 | * we project the camera position onto the normal with the dot product and 636 | * subtract dist, we get the distance between the camera position and the 637 | * plane 638 | * 639 | * note that leaf indices are negative, and that's how we tell we hit a 640 | * leaf while navigating the bsp tree 641 | * 642 | * since 0 is taken by positive indices, leaf index -1 maps to leaf 0, 643 | * -2 to 1, and so on 644 | */ 645 | 646 | int bsp_find_leaf(struct bsp_file* file, float* camera_pos) 647 | { 648 | int index; 649 | 650 | index = 0; 651 | 652 | while (index >= 0) 653 | { 654 | float distance; 655 | struct bsp_node* node; 656 | struct bsp_plane* plane; 657 | 658 | node = &file->nodes[index]; 659 | plane = &file->planes[node->plane]; 660 | 661 | distance = dot3(camera_pos, plane->normal) - plane->dist; 662 | 663 | if (distance >= 0) { 664 | index = node->child[0]; 665 | } else { 666 | index = node->child[1]; 667 | } 668 | } 669 | 670 | return (-index) - 1; 671 | } 672 | 673 | /* 674 | * the visdata vecs are huge bitmasks. each bit is one y step and each 675 | * sz_vecs bytes is one x step 676 | * 677 | * cluster x is visible from cluster y if the y-th bit in the x-th bitmask 678 | * is set. it's basically a visibility matrix with every possible 679 | * combination of clusters packed as bitmask 680 | */ 681 | 682 | int bsp_cluster_visible(struct bsp_file* file, int from, int target) 683 | { 684 | int index; 685 | 686 | index = from * file->visdata->sz_vecs + target / 8; 687 | return (file->visdata_vecs[index] & (1 << (target % 8))) != 0; 688 | } 689 | 690 | /* --------------------------------------------------------------------- */ 691 | 692 | /* 693 | * tiny lexer for the quake 3 entity syntax 694 | * 695 | * { 696 | * "key1" "value1" 697 | * "key2" "value2" 698 | * } 699 | * { 700 | * "key1" "value1" 701 | * "key2" "value2" 702 | * } 703 | * ... 704 | */ 705 | 706 | enum entities_token 707 | { 708 | ENTITIES_LAST_LITERAL_TOKEN = 255, 709 | ENTITIES_STRING, 710 | ENTITIES_LAST_TOKEN 711 | }; 712 | 713 | void describe_entities_token(char* dst, int dst_size, int kind) 714 | { 715 | switch (kind) 716 | { 717 | case ENTITIES_STRING: 718 | SDL_snprintf(dst, dst_size, "%s", "string"); 719 | break; 720 | 721 | default: 722 | if (kind >= 0 && kind <= ENTITIES_LAST_LITERAL_TOKEN) { 723 | SDL_snprintf(dst, dst_size, "'%c'", (char)kind); 724 | } else { 725 | SDL_snprintf(dst, dst_size, "unknown (%d)", kind); 726 | } 727 | } 728 | } 729 | 730 | struct entities_lexer 731 | { 732 | char* p; 733 | int token_kind; 734 | char* str; 735 | int n_lines; 736 | }; 737 | 738 | int lex_entities(struct entities_lexer* lex) 739 | { 740 | again: 741 | switch (*lex->p) 742 | { 743 | case '\n': 744 | ++lex->n_lines; 745 | case '\t': 746 | case '\v': 747 | case '\f': 748 | case '\r': 749 | case ' ': 750 | ++lex->p; 751 | goto again; 752 | 753 | case '"': 754 | lex->token_kind = ENTITIES_STRING; 755 | ++lex->p; 756 | lex->str = lex->p; 757 | 758 | for (; *lex->p != '"'; ++lex->p) 759 | { 760 | if (!*lex->p) 761 | { 762 | log_print(lninfo, 763 | "W: unterminated string \"%s\" at line %d", 764 | lex->str, lex->n_lines); 765 | break; 766 | } 767 | } 768 | 769 | if (*lex->p) { 770 | *lex->p++ = 0; 771 | } 772 | break; 773 | 774 | default: 775 | lex->token_kind = (int)*lex->p; 776 | ++lex->p; 777 | } 778 | 779 | return lex->token_kind; 780 | } 781 | 782 | /* --------------------------------------------------------------------- */ 783 | 784 | struct entity_field 785 | { 786 | char* key; 787 | char* value; 788 | }; 789 | 790 | struct entity_field** entities; 791 | 792 | char* entity_get(struct entity_field* entity, char* key) 793 | { 794 | int i; 795 | 796 | for (i = 0; i < vec_len(entity); ++i) 797 | { 798 | if (!strcmp(entity[i].key, key)) { 799 | return entity[i].value; 800 | } 801 | } 802 | 803 | return 0; 804 | } 805 | 806 | struct entity_field* entity_by_classname(char* classname) 807 | { 808 | int i; 809 | 810 | for (i = 0; i < vec_len(entities); ++i) 811 | { 812 | char* cur_classname; 813 | 814 | cur_classname = entity_get(entities[i], "classname"); 815 | 816 | if (cur_classname && !strcmp(cur_classname, classname)) { 817 | return entities[i]; 818 | } 819 | } 820 | 821 | return 0; 822 | } 823 | 824 | int entities_expect(struct entities_lexer* lex, int kind) 825 | { 826 | if (lex->token_kind != kind) 827 | { 828 | char got[64]; 829 | char exp[64]; 830 | 831 | describe_entities_token(got, sizeof(got), lex->token_kind); 832 | describe_entities_token(exp, sizeof(exp), kind); 833 | 834 | log_print(lninfo, "W: got %s, expected %s at line %d", 835 | got, exp, lex->n_lines); 836 | 837 | return 0; 838 | } 839 | 840 | lex_entities(lex); 841 | 842 | return 1; 843 | } 844 | 845 | void parse_entities(char* data) 846 | { 847 | int i; 848 | struct entities_lexer lex; 849 | 850 | for (i = 0; i < vec_len(entities); ++i) { 851 | vec_free(entities[i]); 852 | } 853 | 854 | vec_clear(entities); 855 | 856 | memset(&lex, 0, sizeof(lex)); 857 | lex.p = data; 858 | lex_entities(&lex); 859 | 860 | do 861 | { 862 | struct entity_field* fields = 0; 863 | 864 | if (!entities_expect(&lex, '{')) { 865 | return; 866 | } 867 | 868 | while (lex.token_kind == ENTITIES_STRING) 869 | { 870 | struct entity_field field; 871 | 872 | field.key = lex.str; 873 | lex_entities(&lex); 874 | 875 | if (lex.token_kind != ENTITIES_STRING) { 876 | return; 877 | } 878 | 879 | field.value = lex.str; 880 | vec_append(fields, field); 881 | 882 | lex_entities(&lex); 883 | } 884 | 885 | vec_append(entities, fields); 886 | 887 | if (!entities_expect(&lex, '}')) { 888 | return; 889 | } 890 | } 891 | while (lex.token_kind); 892 | } 893 | 894 | /* --------------------------------------------------------------------- */ 895 | 896 | /* 897 | * quake3 uses a different coordinate system, so we use quake's matrix 898 | * as identity, where x, y, z are forward, left and up 899 | */ 900 | 901 | float quake_matrix[] = { 902 | 0, 0, -1, 0, 903 | -1, 0, 0, 0, 904 | 0, 1, 0, 0, 905 | 0, 0, 0, 1 906 | }; 907 | 908 | char* argv0; 909 | int running = 1; 910 | float delta_time; 911 | 912 | struct patch 913 | { 914 | int n_vertices; 915 | struct bsp_vertex* vertices; 916 | int n_indices; 917 | int* indices; 918 | int n_rows; 919 | int triangles_per_row; 920 | }; 921 | 922 | char* map_file; 923 | struct bsp_file map; 924 | int* visible_faces; 925 | unsigned char* visible_faces_mask; 926 | struct patch** patches; 927 | 928 | enum plane_type 929 | { 930 | PLANE_X, 931 | PLANE_Y, 932 | PLANE_Z, 933 | PLANE_NONAXIAL, 934 | LAST_PLANE_TYPE 935 | }; 936 | 937 | struct plane 938 | { 939 | int signbits; 940 | int type; 941 | }; 942 | 943 | struct plane* planes; 944 | 945 | int tessellation_level; 946 | float camera_pos[3]; 947 | float camera_angle[2]; /* yaw, pitch */ 948 | float velocity[3]; 949 | int noclip; 950 | 951 | int movement; 952 | float wishdir[3]; /* movement inputs in local player space, not unit */ 953 | int wishlook[2]; /* look inputs in screen space, not unit */ 954 | float* ground_normal; 955 | 956 | enum movement_bits 957 | { 958 | MOVEMENT_JUMP = 1<<1, 959 | MOVEMENT_JUMP_THIS_FRAME = 1<<2, 960 | MOVEMENT_JUMPING = 1<<3, 961 | LAST_MOVEMENT_BIT 962 | }; 963 | 964 | float player_mins[] = { -15, -15, -24 }; 965 | float player_maxs[] = { 15, 15, 32 }; 966 | 967 | float cl_forwardspeed = 400; 968 | float cl_sidespeed = 350; 969 | float cl_movement_accelerate = 15; 970 | float cl_movement_airaccelerate = 7; 971 | float cl_movement_friction = 8; 972 | float sv_gravity = 800; 973 | float sv_max_speed = 320; 974 | float cl_stop_speed = 200; 975 | float cpm_air_stop_acceleration = 2.5f; 976 | float cpm_air_control_amount = 150; 977 | float cpm_strafe_acceleration = 70; 978 | float cpm_wish_speed = 30; 979 | 980 | void print_usage() 981 | { 982 | SDL_Log( 983 | "usage: %s [options] /path/to/file.bsp\n" 984 | "\n" 985 | "available options:\n" 986 | " -window: window mode | default: off | example: -window\n" 987 | " -d: main display index | default: 0 | example: -d 0\n" 988 | " -t: tessellation level | default: 5 | example: -t 10\n" 989 | " -w: window width | default: 1280 | example: -w 800\n" 990 | " -h: window height | default: 720 | example: -h 600\n", 991 | argv0 992 | ); 993 | } 994 | 995 | void parse_args(int argc, char* argv[]) 996 | { 997 | argv0 = argv[0]; 998 | 999 | ++argv, --argc; 1000 | 1001 | for (; argc > 0; ++argv, --argc) 1002 | { 1003 | if (!strcmp(argv[0], "-window")) { 1004 | gl_window_mode = 1; 1005 | } 1006 | 1007 | else if (!strcmp(argv[0], "-d") && argc >= 2) { 1008 | gl_display = SDL_atoi(argv[1]); 1009 | ++argv, --argc; 1010 | } 1011 | 1012 | else if (!strcmp(argv[0], "-t") && argc >= 2) { 1013 | tessellation_level = SDL_atoi(argv[1]); 1014 | ++argv, --argc; 1015 | } 1016 | 1017 | else if (!strcmp(argv[0], "-w") && argc >= 2) { 1018 | gl_width = SDL_atoi(argv[1]); 1019 | ++argv, --argc; 1020 | } 1021 | 1022 | else if (!strcmp(argv[0], "-h") && argc >= 2) { 1023 | gl_height = SDL_atoi(argv[1]); 1024 | ++argv, --argc; 1025 | } 1026 | 1027 | else { 1028 | break; 1029 | } 1030 | } 1031 | 1032 | if (argc >= 1) { 1033 | map_file = argv[0]; 1034 | } else { 1035 | print_usage(); 1036 | exit(1); 1037 | } 1038 | 1039 | if (tessellation_level <= 0) { 1040 | tessellation_level = 5; 1041 | } 1042 | 1043 | if (gl_width <= 0) { 1044 | gl_width = 1280; 1045 | } 1046 | 1047 | if (gl_height <= 0) { 1048 | gl_height = 720; 1049 | } 1050 | } 1051 | 1052 | void update_fps() 1053 | { 1054 | static float one_second = 1; 1055 | static int ticks_per_second = 0; 1056 | 1057 | one_second -= delta_time; 1058 | 1059 | while (one_second <= 0) 1060 | { 1061 | log_dump("d", ticks_per_second); 1062 | log_dump("f", mag3(velocity)); 1063 | one_second = 1; 1064 | ticks_per_second = 0; 1065 | } 1066 | 1067 | ++ticks_per_second; 1068 | } 1069 | 1070 | /* 1071 | * this is slow but it makes code look nicer and we only use it on init 1072 | * anyway so it's not a real performance hit 1073 | */ 1074 | 1075 | struct bsp_vertex mul_vertex(struct bsp_vertex* vertex, float scalar) 1076 | { 1077 | struct bsp_vertex res; 1078 | 1079 | res = *vertex; 1080 | mul3_scalar(res.position, scalar); 1081 | mul2_scalar(res.texcoord[0], scalar); 1082 | mul2_scalar(res.texcoord[1], scalar); 1083 | mul3_scalar(res.normal, scalar); 1084 | 1085 | return res; 1086 | } 1087 | 1088 | struct bsp_vertex add_vertices(struct bsp_vertex a, struct bsp_vertex b) 1089 | { 1090 | add3(a.position, b.position); 1091 | add2(a.texcoord[0], b.texcoord[0]); 1092 | add2(a.texcoord[1], b.texcoord[1]); 1093 | add3(a.normal, b.normal); 1094 | 1095 | return a; 1096 | } 1097 | 1098 | #define add_vertices3(a, b, c) add_vertices(add_vertices(a, b), c) 1099 | 1100 | void tessellate(struct patch* patch, struct bsp_vertex* controls, 1101 | int level) 1102 | { 1103 | int i, j; 1104 | int l1; 1105 | struct bsp_vertex* vertices; 1106 | int* indices; 1107 | 1108 | l1 = level + 1; 1109 | 1110 | patch->n_vertices = l1 * l1; 1111 | patch->vertices = (struct bsp_vertex*) 1112 | SDL_malloc(sizeof(struct bsp_vertex) * patch->n_vertices); 1113 | vertices = patch->vertices; 1114 | 1115 | for (i = 0; i <= level; ++i) 1116 | { 1117 | float a, b; 1118 | 1119 | a = (float)i / level; 1120 | b = 1 - a; 1121 | 1122 | vertices[i] = add_vertices3( 1123 | mul_vertex(&controls[0], b * b), 1124 | mul_vertex(&controls[3], 2 * b * a), 1125 | mul_vertex(&controls[6], a * a)); 1126 | } 1127 | 1128 | for (i = 1; i <= level; ++i) 1129 | { 1130 | float a, b; 1131 | struct bsp_vertex sum[3]; 1132 | 1133 | a = (float)i / level; 1134 | b = 1 - a; 1135 | 1136 | for (j = 0; j < 3; ++j) 1137 | { 1138 | sum[j] = add_vertices3( 1139 | mul_vertex(&controls[3 * j + 0], b * b), 1140 | mul_vertex(&controls[3 * j + 1], 2 * b * a), 1141 | mul_vertex(&controls[3 * j + 2], a * a)); 1142 | } 1143 | 1144 | for (j = 0; j <= level; ++j) 1145 | { 1146 | float c, d; 1147 | 1148 | c = (float)j / level; 1149 | d = 1 - c; 1150 | 1151 | vertices[i * l1 + j] = add_vertices3( 1152 | mul_vertex(&sum[0], d * d), 1153 | mul_vertex(&sum[1], 2 * c * d), 1154 | mul_vertex(&sum[2], c * c)); 1155 | } 1156 | } 1157 | 1158 | patch->indices = SDL_malloc(sizeof(int) * level * l1 * 2); 1159 | indices = patch->indices; 1160 | 1161 | for (i = 0; i < level; ++i) 1162 | { 1163 | for (j = 0; j <= level; ++j) 1164 | { 1165 | indices[(i * l1 + j) * 2 + 1] = i * l1 + j; 1166 | indices[(i * l1 + j) * 2] = (i + 1) * l1 + j; 1167 | } 1168 | } 1169 | 1170 | patch->triangles_per_row = 2 * l1; 1171 | patch->n_rows = level; 1172 | } 1173 | 1174 | void tessellate_face(int face_index) 1175 | { 1176 | struct bsp_face* face; 1177 | int width, height; 1178 | int x, y, row, col; 1179 | 1180 | face = &map.faces[face_index]; 1181 | 1182 | if (face->type != BSP_PATCH) { 1183 | return; 1184 | } 1185 | 1186 | /* theres' multiple sets of bezier control points per face */ 1187 | width = (face->size[0] - 1) / 2; 1188 | height = (face->size[1] - 1) / 2; 1189 | 1190 | patches[face_index] = 1191 | SDL_malloc(width * height * sizeof(patches[0][0])); 1192 | 1193 | /* TODO: there's way too much nesting in here, improve it */ 1194 | for (y = 0; y < height; ++y) 1195 | { 1196 | for (x = 0; x < width; ++x) 1197 | { 1198 | struct patch* patch; 1199 | struct bsp_vertex controls[9]; 1200 | 1201 | for (row = 0; row < 3; ++row) 1202 | { 1203 | for (col = 0; col < 3; ++col) 1204 | { 1205 | int index; 1206 | 1207 | index = face->vertex + 1208 | y * 2 * face->size[0] + x * 2 + 1209 | row * face->size[0] + col; 1210 | 1211 | controls[row * 3 + col] = map.vertices[index]; 1212 | } 1213 | } 1214 | 1215 | patch = &patches[face_index][y * width + x]; 1216 | tessellate(patch, controls, tessellation_level); 1217 | } 1218 | } 1219 | } 1220 | 1221 | #define SURF_CLIP_EPSILON 0.125f 1222 | 1223 | enum tw_flags 1224 | { 1225 | TW_STARTS_OUT = 1<<1, 1226 | TW_ENDS_OUT = 1<<2, 1227 | TW_ALL_SOLID = 1<<3, 1228 | TW_LAST_FLAG 1229 | }; 1230 | 1231 | struct trace_work 1232 | { 1233 | float start[3]; 1234 | float end[3]; 1235 | float endpos[3]; 1236 | float frac; 1237 | int flags; 1238 | float mins[3]; 1239 | float maxs[3]; 1240 | float offsets[8][3]; 1241 | struct bsp_plane* plane; 1242 | }; 1243 | 1244 | /* 1245 | * - adjust plane dist to account for the bounding box 1246 | * - if both points are in front of the plane, we're done with this brush 1247 | * - if both points are behind the plane, we continue looping expecting 1248 | * other planes to clip us 1249 | * - if we are entering the brush, clip start_frac to the distance 1250 | * between the starting point and the brush minus the epsilon so we don't 1251 | * actually touch 1252 | * - if we are exiting the brush, clip end_frac to the distance between 1253 | * the starting point and the brush plus the epsilon so we don't 1254 | * actually touch 1255 | * - keep collecting the maximum start_frac and minimum end_faction so 1256 | * we get as close as possible to touching the brush but not quite 1257 | * - store the minimum start_frac out of all the brushes so we 1258 | * clip against the closest brush 1259 | * - if the trace starts and ends inside the brush, negate the move 1260 | * 1261 | * (I assume this means that the brush sides are sorted from back to front) 1262 | */ 1263 | 1264 | void trace_brush(struct trace_work* work, struct bsp_brush* brush) 1265 | { 1266 | int i; 1267 | float start_frac; 1268 | float end_frac; 1269 | struct bsp_plane* closest_plane; 1270 | 1271 | /* TODO: do optimized check for the first 6 planes which are axial */ 1272 | 1273 | start_frac = -1; 1274 | end_frac = 1; 1275 | 1276 | for (i = 0; i < brush->n_brushsides; ++i) 1277 | { 1278 | int side_index; 1279 | int plane_index; 1280 | struct bsp_plane* plane; 1281 | int signbits; 1282 | 1283 | float dist; 1284 | float start_distance, end_distance; 1285 | float frac; 1286 | 1287 | side_index = brush->brushside + i; 1288 | plane_index = map.brushsides[side_index].plane; 1289 | plane = &map.planes[plane_index]; 1290 | signbits = planes[plane_index].signbits; 1291 | 1292 | dist = plane->dist - dot3(work->offsets[signbits], plane->normal); 1293 | 1294 | start_distance = dot3(work->start, plane->normal) - dist; 1295 | end_distance = dot3(work->end, plane->normal) - dist; 1296 | 1297 | /* TODO: 1298 | * for some reason these checks incorrectly report all solid 1299 | * when they shouldn't. for now I'm just ignoring them 1300 | */ 1301 | 1302 | if (start_distance > 0) { 1303 | work->flags |= TW_STARTS_OUT; 1304 | } 1305 | 1306 | if (end_distance > 0) { 1307 | work->flags |= TW_ENDS_OUT; 1308 | } 1309 | 1310 | if (start_distance > 0 && 1311 | (end_distance >= SURF_CLIP_EPSILON || 1312 | end_distance >= start_distance)) 1313 | { 1314 | return; 1315 | } 1316 | 1317 | if (start_distance <= 0 && end_distance <= 0) { 1318 | continue; 1319 | } 1320 | 1321 | if (start_distance > end_distance) 1322 | { 1323 | frac = (start_distance - SURF_CLIP_EPSILON) / 1324 | (start_distance - end_distance); 1325 | 1326 | if (frac > start_frac) { 1327 | start_frac = frac; 1328 | closest_plane = plane; 1329 | } 1330 | } 1331 | 1332 | else 1333 | { 1334 | frac = (start_distance + SURF_CLIP_EPSILON) / 1335 | (start_distance - end_distance); 1336 | 1337 | end_frac = SDL_min(end_frac, frac); 1338 | } 1339 | } 1340 | 1341 | if (start_frac < end_frac && 1342 | start_frac > -1 && start_frac < work->frac) 1343 | { 1344 | work->frac = SDL_max(start_frac, 0); 1345 | work->plane = closest_plane; 1346 | } 1347 | 1348 | if (!(work->flags & (TW_STARTS_OUT | TW_ENDS_OUT))) { 1349 | work->frac = 0; 1350 | } 1351 | } 1352 | 1353 | /* 1354 | * - for leaves, only trace brush if the contents are solid and the brush 1355 | * has sides 1356 | * - for nodes, recurse into front/back if both start and end are in front 1357 | * of, or behind the node's plane 1358 | * - if start -> end crosses over two nodes, we need to recurse into both 1359 | * nodes and split the start -> end segment into two segment that are 1360 | * just short of crossing over by adding SURFACE_CLIP_EPSILON 1361 | * - planes contain an enum that can tell us if they are axis aligned. 1362 | * when they are axis aligned, we can skip some calculations because 1363 | * the bounding box is also axis aligned 1364 | */ 1365 | 1366 | void trace_leaf(struct trace_work* work, int index) 1367 | { 1368 | int i; 1369 | struct bsp_leaf* leaf; 1370 | 1371 | leaf = &map.leaves[index]; 1372 | 1373 | for (i = 0; i < leaf->n_leafbrushes; ++i) 1374 | { 1375 | struct bsp_brush* brush; 1376 | int contents; 1377 | int brush_index; 1378 | 1379 | brush_index = map.leafbrushes[leaf->leafbrush + i]; 1380 | brush = &map.brushes[brush_index]; 1381 | contents = map.textures[brush->texture].contents; 1382 | 1383 | if (brush->n_brushsides && (contents & CONTENTS_SOLID)) 1384 | { 1385 | trace_brush(work, brush); 1386 | 1387 | if (!work->frac) { 1388 | return; 1389 | } 1390 | } 1391 | } 1392 | 1393 | /* TODO: collision with patches */ 1394 | } 1395 | 1396 | void trace_node(struct trace_work* work, int index, float start_frac, 1397 | float end_frac, float* start, float* end) 1398 | { 1399 | int i; 1400 | struct bsp_node* node; 1401 | struct bsp_plane* plane; 1402 | int plane_type; 1403 | 1404 | float start_distance; 1405 | float end_distance; 1406 | float offset; 1407 | 1408 | int side; 1409 | float idistance; 1410 | float frac1; 1411 | float frac2; 1412 | float mid_frac; 1413 | float mid[3]; 1414 | 1415 | if (index < 0) { 1416 | trace_leaf(work, (-index) - 1); 1417 | return; 1418 | } 1419 | 1420 | node = &map.nodes[index]; 1421 | plane = &map.planes[node->plane]; 1422 | plane_type = planes[node->plane].type; 1423 | 1424 | if (plane_type < 3) 1425 | { 1426 | start_distance = start[plane_type] - plane->dist; 1427 | end_distance = end[plane_type] - plane->dist; 1428 | offset = work->maxs[plane_type]; 1429 | } 1430 | else 1431 | { 1432 | start_distance = dot3(start, plane->normal) - plane->dist; 1433 | end_distance = dot3(end, plane->normal) - plane->dist; 1434 | 1435 | if (eq3(work->mins, work->maxs)) { 1436 | offset = 0; 1437 | } else { 1438 | /* "this is silly" - id Software */ 1439 | offset = 2048; 1440 | } 1441 | } 1442 | 1443 | if (start_distance >= offset + 1 && end_distance >= offset + 1) { 1444 | trace_node(work, node->child[0], start_frac, end_frac, start, end); 1445 | return; 1446 | } 1447 | 1448 | if (start_distance < -offset - 1 && end_distance < -offset - 1) { 1449 | trace_node(work, node->child[1], start_frac, end_frac, start, end); 1450 | return; 1451 | } 1452 | 1453 | if (start_distance < end_distance) 1454 | { 1455 | side = 1; 1456 | idistance = 1.0f / (start_distance - end_distance); 1457 | frac1 = (start_distance - offset + SURF_CLIP_EPSILON) * idistance; 1458 | frac2 = (start_distance + offset + SURF_CLIP_EPSILON) * idistance; 1459 | } 1460 | 1461 | else if (start_distance > end_distance) 1462 | { 1463 | side = 0; 1464 | idistance = 1.0f / (start_distance - end_distance); 1465 | frac1 = (start_distance + offset + SURF_CLIP_EPSILON) * idistance; 1466 | frac2 = (start_distance - offset - SURF_CLIP_EPSILON) * idistance; 1467 | } 1468 | 1469 | else 1470 | { 1471 | side = 0; 1472 | frac1 = 1; 1473 | frac2 = 0; 1474 | } 1475 | 1476 | frac1 = SDL_max(0, SDL_min(1, frac1)); 1477 | frac2 = SDL_max(0, SDL_min(1, frac2)); 1478 | 1479 | mid_frac = start_frac + (end_frac - start_frac) * frac1; 1480 | 1481 | for (i = 0; i < 3; ++i) { 1482 | mid[i] = start[i] + (end[i] - start[i]) * frac1; 1483 | } 1484 | 1485 | trace_node(work, node->child[side], start_frac, mid_frac, start, mid); 1486 | 1487 | mid_frac = start_frac + (end_frac - start_frac) * frac2; 1488 | 1489 | for (i = 0; i < 3; ++i) { 1490 | mid[i] = start[i] + (end[i] - start[i]) * frac2; 1491 | } 1492 | 1493 | trace_node(work, node->child[side^1], mid_frac, end_frac, mid, end); 1494 | } 1495 | 1496 | /* 1497 | * - adjust bounding box so it's symmetric. this is simply done by finding 1498 | * the middle point and moving start/end to align with it 1499 | * - initialize offsets. this is a lookup table for mins/maxs with any 1500 | * sign combination for the plane's normal. it ensures that we account 1501 | * for the hitbox in the right orientation in trace_brush 1502 | * - do the tracing 1503 | * - if we hit anything, calculate end from the unmodified start/end 1504 | */ 1505 | 1506 | void trace(struct trace_work* work, float* start, float* end, float* mins, 1507 | float* maxs) 1508 | { 1509 | int i; 1510 | 1511 | work->frac = 1; 1512 | work->flags = 0; 1513 | 1514 | for (i = 0; i < 3; ++i) 1515 | { 1516 | float offset; 1517 | 1518 | offset = (mins[i] + maxs[i]) * 0.5f; 1519 | work->mins[i] = mins[i] - offset; 1520 | work->maxs[i] = maxs[i] - offset; 1521 | work->start[i] = start[i] + offset; 1522 | work->end[i] = end[i] + offset; 1523 | } 1524 | 1525 | work->offsets[0][0] = work->mins[0]; 1526 | work->offsets[0][1] = work->mins[1]; 1527 | work->offsets[0][2] = work->mins[2]; 1528 | 1529 | work->offsets[1][0] = work->maxs[0]; 1530 | work->offsets[1][1] = work->mins[1]; 1531 | work->offsets[1][2] = work->mins[2]; 1532 | 1533 | work->offsets[2][0] = work->mins[0]; 1534 | work->offsets[2][1] = work->maxs[1]; 1535 | work->offsets[2][2] = work->mins[2]; 1536 | 1537 | work->offsets[3][0] = work->maxs[0]; 1538 | work->offsets[3][1] = work->maxs[1]; 1539 | work->offsets[3][2] = work->mins[2]; 1540 | 1541 | work->offsets[4][0] = work->mins[0]; 1542 | work->offsets[4][1] = work->mins[1]; 1543 | work->offsets[4][2] = work->maxs[2]; 1544 | 1545 | work->offsets[5][0] = work->maxs[0]; 1546 | work->offsets[5][1] = work->mins[1]; 1547 | work->offsets[5][2] = work->maxs[2]; 1548 | 1549 | work->offsets[6][0] = work->mins[0]; 1550 | work->offsets[6][1] = work->maxs[1]; 1551 | work->offsets[6][2] = work->maxs[2]; 1552 | 1553 | work->offsets[7][0] = work->maxs[0]; 1554 | work->offsets[7][1] = work->maxs[1]; 1555 | work->offsets[7][2] = work->maxs[2]; 1556 | 1557 | trace_node(work, 0, 0, 1, work->start, work->end); 1558 | 1559 | if (work->frac == 1) { 1560 | cpy3(work->endpos, end); 1561 | } else { 1562 | int i; 1563 | 1564 | for (i = 0; i < 3; ++i) { 1565 | work->endpos[i] = start[i] + work->frac * (end[i] - start[i]); 1566 | } 1567 | } 1568 | } 1569 | 1570 | void trace_point(struct trace_work* work, float* start, float* end) 1571 | { 1572 | float zero[3]; 1573 | 1574 | clr3(zero); 1575 | trace(work, start, end, zero, zero); 1576 | } 1577 | 1578 | int plane_type_for_normal(float* normal) 1579 | { 1580 | if (normal[0] == 1.0f || normal[0] == -1.0f) { 1581 | return PLANE_X; 1582 | } 1583 | 1584 | if (normal[1] == 1.0f || normal[1] == -1.0f) { 1585 | return PLANE_Y; 1586 | } 1587 | 1588 | if (normal[2] == 1.0f || normal[2] == -1.0f) { 1589 | return PLANE_Z; 1590 | } 1591 | 1592 | return PLANE_NONAXIAL; 1593 | } 1594 | 1595 | int signbits_for_normal(float* normal) 1596 | { 1597 | int i; 1598 | int bits; 1599 | 1600 | bits = 0; 1601 | 1602 | for (i = 0; i < 3; ++i) 1603 | { 1604 | if (normal[i] < 0) { 1605 | bits |= 1<= 2*M_PI; angles[i] -= 2*M_PI); 1749 | } 1750 | } 1751 | 1752 | void trace_ground() 1753 | { 1754 | float point[3]; 1755 | struct trace_work work; 1756 | 1757 | point[0] = camera_pos[0]; 1758 | point[1] = camera_pos[1]; 1759 | point[2] = camera_pos[2] - 0.25; 1760 | 1761 | trace(&work, camera_pos, point, player_mins, player_maxs); 1762 | 1763 | if (work.frac == 1 || (movement & MOVEMENT_JUMP_THIS_FRAME)) { 1764 | movement |= MOVEMENT_JUMPING; 1765 | ground_normal = 0; 1766 | } else { 1767 | movement &= ~MOVEMENT_JUMPING; 1768 | ground_normal = work.plane->normal; 1769 | } 1770 | } 1771 | 1772 | void apply_jump() 1773 | { 1774 | if (!(movement & MOVEMENT_JUMP)) { 1775 | return; 1776 | } 1777 | 1778 | if ((movement & MOVEMENT_JUMPING) && !noclip) { 1779 | return; 1780 | } 1781 | 1782 | movement |= MOVEMENT_JUMP_THIS_FRAME; 1783 | velocity[2] = 270; 1784 | movement &= ~MOVEMENT_JUMP; /* no auto bunnyhop */ 1785 | } 1786 | 1787 | void apply_friction() 1788 | { 1789 | float speed; 1790 | float control; 1791 | float new_speed; 1792 | 1793 | if (!noclip) 1794 | { 1795 | if ((movement & MOVEMENT_JUMPING) || 1796 | (movement & MOVEMENT_JUMP_THIS_FRAME)) 1797 | { 1798 | return; 1799 | } 1800 | } 1801 | 1802 | speed = (float)SDL_sqrt(dot3(velocity, velocity)); 1803 | if (speed < 1) { 1804 | velocity[0] = 0; 1805 | velocity[1] = 0; 1806 | return; 1807 | } 1808 | 1809 | control = speed < cl_stop_speed ? cl_stop_speed : speed; 1810 | new_speed = speed - control * cl_movement_friction * delta_time; 1811 | new_speed = SDL_max(0, new_speed); 1812 | mul3_scalar(velocity, new_speed / speed); 1813 | } 1814 | 1815 | void apply_acceleration(float* direction, float wishspeed, 1816 | float acceleration) 1817 | { 1818 | float cur_speed; 1819 | float add_speed; 1820 | float accel_speed; 1821 | float amount[3]; 1822 | 1823 | if (!noclip && (movement & MOVEMENT_JUMPING)) { 1824 | wishspeed = SDL_min(cpm_wish_speed, wishspeed); 1825 | } 1826 | 1827 | cur_speed = dot3(velocity, direction); 1828 | add_speed = wishspeed - cur_speed; 1829 | 1830 | if (add_speed <= 0) { 1831 | return; 1832 | } 1833 | 1834 | accel_speed = acceleration * delta_time * wishspeed; 1835 | accel_speed = SDL_min(accel_speed, add_speed); 1836 | 1837 | cpy3(amount, direction); 1838 | mul3_scalar(amount, accel_speed); 1839 | add3(velocity, amount); 1840 | } 1841 | 1842 | void apply_air_control(float* direction, float wishspeed) 1843 | { 1844 | float zspeed; 1845 | float speed; 1846 | float dot; 1847 | 1848 | if (wishdir[0] == 0 || wishspeed == 0) { 1849 | return; 1850 | } 1851 | 1852 | zspeed = velocity[2]; 1853 | velocity[2] = 0; 1854 | speed = mag3(velocity); 1855 | if (speed >= 0.0001f) { 1856 | div3_scalar(velocity, speed); 1857 | } 1858 | dot = dot3(velocity, direction); 1859 | 1860 | if (dot > 0) { 1861 | /* can only change direction if we aren't trying to slow down */ 1862 | float k; 1863 | float amount[3]; 1864 | 1865 | k = 32 * cpm_air_control_amount * dot * dot * delta_time; 1866 | mul3_scalar(velocity, speed); 1867 | cpy3(amount, direction); 1868 | mul3_scalar(amount, k); 1869 | nrm3(velocity); 1870 | } 1871 | 1872 | mul3_scalar(velocity, speed); 1873 | velocity[2] = zspeed; 1874 | } 1875 | 1876 | void apply_inputs() 1877 | { 1878 | float direction[3]; 1879 | float pitch_sin, pitch_cos, yaw_sin, yaw_cos; 1880 | float pitch_x; 1881 | float wishspeed; 1882 | float selected_acceleration; 1883 | float base_wishspeed; 1884 | 1885 | /* camera look */ 1886 | camera_angle[0] += 0.002f * wishlook[0]; 1887 | camera_angle[1] += 0.002f * wishlook[1]; 1888 | clamp_angles(camera_angle, 2); 1889 | 1890 | if (noclip) { 1891 | pitch_sin = SDL_sinf(2*M_PI - camera_angle[1]); 1892 | pitch_cos = SDL_cosf(2*M_PI - camera_angle[1]); 1893 | } else { 1894 | pitch_sin = 0; 1895 | pitch_cos = 1; 1896 | } 1897 | 1898 | yaw_sin = SDL_sinf(2*M_PI - camera_angle[0]); 1899 | yaw_cos = SDL_cosf(2*M_PI - camera_angle[0]); 1900 | 1901 | /* this applies 2 rotations, pitch first then yaw */ 1902 | pitch_x = wishdir[0] * pitch_cos + wishdir[2] * (-pitch_sin); 1903 | direction[0] = pitch_x * yaw_cos + wishdir[1] * (-yaw_sin); 1904 | direction[1] = pitch_x * yaw_sin + wishdir[1] * yaw_cos; 1905 | direction[2] = wishdir[0] * pitch_sin + wishdir[2] * pitch_cos; 1906 | 1907 | /* movement */ 1908 | wishspeed = (float)SDL_sqrt(dot3(direction, direction)); 1909 | if (wishspeed >= 0.0001f) { 1910 | div3_scalar(direction, wishspeed); 1911 | } 1912 | wishspeed = SDL_min(wishspeed, sv_max_speed); 1913 | 1914 | apply_jump(); 1915 | apply_friction(); 1916 | 1917 | selected_acceleration = cl_movement_accelerate; 1918 | base_wishspeed = wishspeed; 1919 | 1920 | /* cpm air acceleration | TODO: pull this out */ 1921 | if (noclip || (movement & MOVEMENT_JUMPING) || 1922 | (movement & MOVEMENT_JUMP_THIS_FRAME)) 1923 | { 1924 | if (dot3(velocity, direction) < 0) { 1925 | selected_acceleration = cpm_air_stop_acceleration; 1926 | } else { 1927 | selected_acceleration = cl_movement_airaccelerate; 1928 | } 1929 | 1930 | if (wishdir[1] != 0 && wishdir[0] == 0) { 1931 | wishspeed = SDL_min(wishspeed, cpm_wish_speed); 1932 | selected_acceleration = cpm_strafe_acceleration; 1933 | } 1934 | } 1935 | 1936 | apply_acceleration(direction, wishspeed, selected_acceleration); 1937 | apply_air_control(direction, base_wishspeed); 1938 | } 1939 | 1940 | void clip_velocity(float* in, float* normal, float* out, float overbounce) 1941 | { 1942 | float backoff; 1943 | int i; 1944 | 1945 | backoff = dot3(in, normal); 1946 | 1947 | if (backoff < 0) { 1948 | backoff *= overbounce; 1949 | } else { 1950 | backoff /= overbounce; 1951 | } 1952 | 1953 | for (i = 0; i < 3; ++i) 1954 | { 1955 | float change; 1956 | 1957 | change = normal[i] * backoff; 1958 | out[i] = in[i] - change; 1959 | } 1960 | } 1961 | 1962 | /* 1963 | * clip the velocity against all brushes until we stop colliding. this 1964 | * allows the player to slide against walls and the floor 1965 | */ 1966 | 1967 | #define OVERCLIP 1.001f 1968 | #define MAX_CLIP_PLANES 5 1969 | 1970 | int slide(int gravity) 1971 | { 1972 | float end_velocity[3]; 1973 | float planes[MAX_CLIP_PLANES][3]; 1974 | int n_planes; 1975 | float time_left; 1976 | int n_bumps; 1977 | float end[3]; 1978 | 1979 | n_planes = 0; 1980 | time_left = delta_time; 1981 | 1982 | if (gravity) 1983 | { 1984 | cpy3(end_velocity, velocity); 1985 | end_velocity[2] -= sv_gravity * delta_time; 1986 | 1987 | /* 1988 | * not 100% sure why this is necessary, maybe to avoid tunneling 1989 | * through the floor when really close to it 1990 | */ 1991 | 1992 | velocity[2] = (end_velocity[2] + velocity[2]) * 0.5f; 1993 | 1994 | /* slide against floor */ 1995 | if (ground_normal) { 1996 | clip_velocity(velocity, ground_normal, velocity, OVERCLIP); 1997 | } 1998 | } 1999 | 2000 | if (ground_normal) { 2001 | cpy3(planes[n_planes], ground_normal); 2002 | ++n_planes; 2003 | } 2004 | 2005 | cpy3(planes[n_planes], velocity); 2006 | nrm3(planes[n_planes]); 2007 | ++n_planes; 2008 | 2009 | for (n_bumps = 0; n_bumps < 4; ++n_bumps) 2010 | { 2011 | struct trace_work work; 2012 | int i; 2013 | 2014 | /* calculate future position and attempt the move */ 2015 | cpy3(end, velocity); 2016 | mul3_scalar(end, time_left); 2017 | add3(end, camera_pos); 2018 | trace(&work, camera_pos, end, player_mins, player_maxs); 2019 | 2020 | if (work.frac > 0) { 2021 | cpy3(camera_pos, work.endpos); 2022 | } 2023 | 2024 | /* if nothing blocked us we are done */ 2025 | if (work.frac == 1) { 2026 | break; 2027 | } 2028 | 2029 | time_left -= time_left * work.frac; 2030 | 2031 | if (n_planes >= MAX_CLIP_PLANES) { 2032 | clr3(velocity); 2033 | return 1; 2034 | } 2035 | 2036 | /* 2037 | * if it's a plane we hit before, nudge velocity along it 2038 | * to prevent epsilon issues and dont re-test it 2039 | */ 2040 | 2041 | for (i = 0; i < n_planes; ++i) 2042 | { 2043 | if (dot3(work.plane->normal, planes[i]) > 0.99) { 2044 | add3(velocity, work.plane->normal); 2045 | break; 2046 | } 2047 | } 2048 | 2049 | if (i < n_planes) { 2050 | continue; 2051 | } 2052 | 2053 | /* 2054 | * entirely new plane, add it and clip velocity against all 2055 | * planes that the move interacts with 2056 | */ 2057 | 2058 | cpy3(planes[n_planes], work.plane->normal); 2059 | ++n_planes; 2060 | 2061 | for (i = 0; i < n_planes; ++i) 2062 | { 2063 | float clipped[3]; 2064 | float end_clipped[3]; 2065 | int j; 2066 | 2067 | if (dot3(velocity, planes[i]) >= 0.1) { 2068 | continue; 2069 | } 2070 | 2071 | clip_velocity(velocity, planes[i], clipped, OVERCLIP); 2072 | clip_velocity(end_velocity, planes[i], end_clipped, OVERCLIP); 2073 | 2074 | /* 2075 | * if the clipped move still hits another plane, slide along 2076 | * the line where the two planes meet (cross product) with the 2077 | * un-clipped velocity 2078 | * 2079 | * TODO: reduce nesting in here 2080 | */ 2081 | 2082 | for (j = 0; j < n_planes; ++j) 2083 | { 2084 | int k; 2085 | float dir[3]; 2086 | float speed; 2087 | 2088 | if (j == i) { 2089 | continue; 2090 | } 2091 | 2092 | if (dot3(clipped, planes[j]) >= 0.1) { 2093 | continue; 2094 | } 2095 | 2096 | clip_velocity(clipped, planes[j], clipped, OVERCLIP); 2097 | clip_velocity(end_clipped, planes[j], end_clipped, 2098 | OVERCLIP); 2099 | 2100 | if (dot3(clipped, planes[i]) >= 0) { 2101 | /* goes back into the first plane */ 2102 | continue; 2103 | } 2104 | 2105 | cross3(planes[i], planes[j], dir); 2106 | nrm3(dir); 2107 | 2108 | speed = dot3(dir, velocity); 2109 | cpy3(clipped, dir); 2110 | mul3_scalar(clipped, speed); 2111 | 2112 | speed = dot3(dir, end_velocity); 2113 | cpy3(end_clipped, dir); 2114 | mul3_scalar(end_clipped, speed); 2115 | 2116 | /* if we still hit a plane, just give up and dead stop */ 2117 | 2118 | for (k = 0; k < n_planes; ++k) 2119 | { 2120 | if (k == j || k == i) { 2121 | continue; 2122 | } 2123 | 2124 | if (dot3(clipped, planes[k]) >= 0.1) { 2125 | continue; 2126 | } 2127 | 2128 | clr3(velocity); 2129 | return 1; 2130 | } 2131 | } 2132 | 2133 | /* resolved all collisions for this move */ 2134 | cpy3(velocity, clipped); 2135 | cpy3(end_velocity, end_clipped); 2136 | break; 2137 | } 2138 | } 2139 | 2140 | if (gravity) { 2141 | cpy3(velocity, end_velocity); 2142 | } 2143 | 2144 | return n_bumps != 0; 2145 | } 2146 | 2147 | void update() 2148 | { 2149 | float amount[3]; 2150 | 2151 | update_fps(); 2152 | trace_ground(); 2153 | apply_inputs(); 2154 | 2155 | if (!noclip) { 2156 | slide((movement & MOVEMENT_JUMPING) != 0); 2157 | } 2158 | 2159 | else 2160 | { 2161 | cpy3(amount, velocity); 2162 | mul3_scalar(amount, delta_time); 2163 | add3(camera_pos, amount); 2164 | } 2165 | 2166 | movement &= ~MOVEMENT_JUMP_THIS_FRAME; 2167 | } 2168 | 2169 | void render_mesh(struct bsp_face* face) 2170 | { 2171 | int stride; 2172 | 2173 | stride = sizeof(struct bsp_vertex); 2174 | 2175 | glEnableClientState(GL_VERTEX_ARRAY); 2176 | glEnableClientState(GL_COLOR_ARRAY); 2177 | 2178 | glVertexPointer(3, GL_FLOAT, stride, 2179 | &map.vertices[face->vertex].position); 2180 | 2181 | glColorPointer(4, GL_UNSIGNED_BYTE, stride, 2182 | &map.vertices[face->vertex].color); 2183 | 2184 | glDrawElements(GL_TRIANGLES, face->n_meshverts, GL_UNSIGNED_INT, 2185 | &map.meshverts[face->meshvert]); 2186 | 2187 | glDisableClientState(GL_VERTEX_ARRAY); 2188 | glDisableClientState(GL_COLOR_ARRAY); 2189 | } 2190 | 2191 | void render_patch(struct patch* patch) 2192 | { 2193 | int stride; 2194 | int i; 2195 | 2196 | stride = sizeof(struct bsp_vertex); 2197 | 2198 | glEnableClientState(GL_VERTEX_ARRAY); 2199 | glEnableClientState(GL_COLOR_ARRAY); 2200 | 2201 | glVertexPointer(3, GL_FLOAT, stride, &patch->vertices[0].position); 2202 | glColorPointer(4, GL_UNSIGNED_BYTE, stride, &patch->vertices[0].color); 2203 | 2204 | for (i = 0; i < patch->n_rows; ++i) 2205 | { 2206 | glDrawElements(GL_TRIANGLE_STRIP, 2207 | patch->triangles_per_row, GL_UNSIGNED_INT, 2208 | &patch->indices[i * patch->triangles_per_row]); 2209 | } 2210 | 2211 | glDisableClientState(GL_VERTEX_ARRAY); 2212 | glDisableClientState(GL_COLOR_ARRAY); 2213 | } 2214 | 2215 | /* 2216 | * - find the leaf and cluster we are in 2217 | * - find out which leaves are visible from here 2218 | * - for each of the visible leaves, append all of its faces to the list 2219 | * of faces to be rendered 2220 | * - because faces can be included twice, we use a huge bitmask to keep 2221 | * track of which faces we already added, should be faster than searching 2222 | * the array every time 2223 | * - render the faces, camera view frustum culling is handled by hardware 2224 | * - z-order culling is also handled by the hardware but it might be 2225 | * a good idea to sort opaque triangles front to back 2226 | * - with textures you would want to sort faces by texture to minimize 2227 | * texture switching (or build an atlas with all the textures) 2228 | */ 2229 | 2230 | void render() 2231 | { 2232 | int i, j; 2233 | int leaf_index; 2234 | struct bsp_leaf* leaf; 2235 | int cluster; 2236 | int n_visible_faces; 2237 | 2238 | leaf_index = bsp_find_leaf(&map, camera_pos); 2239 | leaf = &map.leaves[leaf_index]; 2240 | cluster = leaf->cluster; 2241 | 2242 | n_visible_faces = 0; 2243 | memset(visible_faces_mask, 0, map.n_faces / 8); 2244 | 2245 | for (i = 0; i < map.n_leaves; ++i) 2246 | { 2247 | int j; 2248 | int first_face; 2249 | int n_faces; 2250 | 2251 | if (!bsp_cluster_visible(&map, cluster, map.leaves[i].cluster)) { 2252 | continue; 2253 | } 2254 | 2255 | first_face = map.leaves[i].leafface; 2256 | n_faces = map.leaves[i].n_leaffaces; 2257 | 2258 | for (j = first_face; j < first_face + n_faces; ++j) 2259 | { 2260 | int face_index; 2261 | int face_bit; 2262 | 2263 | face_index = map.leaffaces[j]; 2264 | face_bit = 1 << (face_index % 8); 2265 | 2266 | if (visible_faces_mask[face_index / 8] & face_bit) { 2267 | continue; 2268 | } 2269 | 2270 | visible_faces[n_visible_faces++] = face_index; 2271 | visible_faces_mask[face_index / 8] |= face_bit; 2272 | } 2273 | } 2274 | 2275 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 2276 | glLoadMatrixf(quake_matrix); 2277 | glRotatef(degrees(camera_angle[1]), 0, -1, 0); 2278 | glRotatef(degrees(camera_angle[0]), 0, 0, 1); 2279 | glTranslatef(-camera_pos[0], -camera_pos[1], -camera_pos[2] - 30); 2280 | 2281 | for (i = 0; i < n_visible_faces; ++i) 2282 | { 2283 | int face_index; 2284 | struct bsp_face* face; 2285 | int npatches; 2286 | 2287 | face_index = visible_faces[i]; 2288 | face = &map.faces[face_index]; 2289 | 2290 | switch (face->type) 2291 | { 2292 | case BSP_BILLBOARD: 2293 | /* TODO */ 2294 | break; 2295 | 2296 | case BSP_POLYGON: 2297 | case BSP_MESH: 2298 | render_mesh(face); 2299 | break; 2300 | 2301 | case BSP_PATCH: 2302 | npatches = (face->size[0] - 1) / 2; 2303 | npatches *= (face->size[1] - 1) / 2; 2304 | 2305 | for (j = 0; j < npatches; ++j) { 2306 | render_patch(&patches[face_index][j]); 2307 | } 2308 | break; 2309 | } 2310 | } 2311 | 2312 | SDL_GL_SwapWindow(gl_window); 2313 | } 2314 | 2315 | void tick() 2316 | { 2317 | update(); 2318 | render(); 2319 | } 2320 | 2321 | /* --------------------------------------------------------------------- */ 2322 | 2323 | void handle(SDL_Event* e) 2324 | { 2325 | switch (e->type) 2326 | { 2327 | case SDL_QUIT: 2328 | running = 0; 2329 | break; 2330 | 2331 | case SDL_KEYDOWN: 2332 | if (e->key.repeat) { 2333 | break; 2334 | } 2335 | 2336 | switch (e->key.keysym.sym) 2337 | { 2338 | case SDLK_ESCAPE: 2339 | running = 0; 2340 | break; 2341 | case SDLK_w: 2342 | wishdir[0] = cl_forwardspeed; 2343 | break; 2344 | case SDLK_s: 2345 | wishdir[0] = -cl_forwardspeed; 2346 | break; 2347 | case SDLK_a: 2348 | wishdir[1] = cl_sidespeed; 2349 | break; 2350 | case SDLK_d: 2351 | wishdir[1] = -cl_sidespeed; 2352 | break; 2353 | case SDLK_f: 2354 | noclip ^= 1; 2355 | log_dump("d", noclip); 2356 | break; 2357 | case SDLK_SPACE: 2358 | movement |= MOVEMENT_JUMP; 2359 | break; 2360 | } 2361 | break; 2362 | 2363 | case SDL_KEYUP: 2364 | switch (e->key.keysym.sym) 2365 | { 2366 | case SDLK_ESCAPE: 2367 | running = 0; 2368 | break; 2369 | case SDLK_w: 2370 | wishdir[0] = 0; 2371 | break; 2372 | case SDLK_s: 2373 | wishdir[0] = 0; 2374 | break; 2375 | case SDLK_a: 2376 | wishdir[1] = 0; 2377 | break; 2378 | case SDLK_d: 2379 | wishdir[1] = 0; 2380 | break; 2381 | case SDLK_SPACE: 2382 | movement &= ~MOVEMENT_JUMP; 2383 | break; 2384 | } 2385 | break; 2386 | 2387 | case SDL_MOUSEBUTTONDOWN: 2388 | if (e->button.button == SDL_BUTTON_RIGHT) { 2389 | movement |= MOVEMENT_JUMP; 2390 | } 2391 | break; 2392 | 2393 | case SDL_MOUSEBUTTONUP: 2394 | if (e->button.button == SDL_BUTTON_RIGHT) { 2395 | movement &= ~MOVEMENT_JUMP; 2396 | } 2397 | break; 2398 | } 2399 | } 2400 | 2401 | int main(int argc, char* argv[]) 2402 | { 2403 | unsigned prev_ticks; 2404 | 2405 | SDL_Init(SDL_INIT_VIDEO); 2406 | init(argc, argv); 2407 | 2408 | prev_ticks = SDL_GetTicks(); 2409 | 2410 | while (running) 2411 | { 2412 | SDL_Event e; 2413 | unsigned ticks; 2414 | 2415 | /* cap tick rate to sdl's maximum timer resolution */ 2416 | for (; prev_ticks == (ticks = SDL_GetTicks()); SDL_Delay(0)); 2417 | 2418 | delta_time = (ticks - prev_ticks) * 0.001f; 2419 | prev_ticks = ticks; 2420 | 2421 | SDL_GetRelativeMouseState(&wishlook[0], &wishlook[1]); 2422 | for (; SDL_PollEvent(&e); handle(&e)); 2423 | 2424 | tick(); 2425 | } 2426 | 2427 | return 0; 2428 | } 2429 | --------------------------------------------------------------------------------