├── .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 | 
4 | 
5 | 
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 | * 
5 | * 
6 | * 
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 |
--------------------------------------------------------------------------------