1 //------------------------------------------------------------------------
2 // MAIN : Main program for glBSP
3 //------------------------------------------------------------------------
4 //
5 // GL-Friendly Node Builder (C) 2000-2007 Andrew Apted
6 //
7 // Based on 'BSP 2.3' by Colin Reed, Lee Killough and others.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 //------------------------------------------------------------------------
20
21 #include "system.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdarg.h>
27 #include <ctype.h>
28 #include <math.h>
29 #include <limits.h>
30 #include <assert.h>
31
32 #include "blockmap.h"
33 #include "level.h"
34 #include "node.h"
35 #include "seg.h"
36 #include "structs.h"
37 #include "util.h"
38 #include "wad.h"
39
40
41 const nodebuildinfo_t *cur_info = NULL;
42 const nodebuildfuncs_t *cur_funcs = NULL;
43 volatile nodebuildcomms_t *cur_comms = NULL;
44
45
46 const nodebuildinfo_t default_buildinfo =
47 {
48 NULL, // input_file
49 NULL, // output_file
50 NULL, // extra_files
51
52 DEFAULT_FACTOR, // factor
53
54 FALSE, // no_reject
55 FALSE, // no_progress
56 FALSE, // quiet
57 FALSE, // mini_warnings
58 FALSE, // force_hexen
59 FALSE, // pack_sides
60 FALSE, // fast
61
62 2, // spec_version
63
64 FALSE, // load_all
65 FALSE, // no_normal
66 FALSE, // force_normal
67 FALSE, // gwa_mode
68 FALSE, // prune_sect
69 FALSE, // no_prune
70 FALSE, // merge_vert
71 FALSE, // skip_self_ref
72 FALSE, // window_fx
73
74 DEFAULT_BLOCK_LIMIT, // block_limit
75
76 FALSE, // missing_output
77 FALSE // same_filenames
78 };
79
80 const nodebuildcomms_t default_buildcomms =
81 {
82 NULL, // message
83 FALSE, // cancelled
84
85 0, 0, // total warnings
86 0, 0 // build and file positions
87 };
88
89
90 /* ----- option parsing ----------------------------- */
91
92 #define EXTRA_BLOCK 10 /* includes terminating NULL */
93
AddExtraFile(nodebuildinfo_t * info,const char * str)94 static void AddExtraFile(nodebuildinfo_t *info, const char *str)
95 {
96 int count = 0;
97 int space;
98
99 if (! info->extra_files)
100 {
101 info->extra_files = (const char **)
102 UtilCalloc(EXTRA_BLOCK * sizeof(const char *));
103
104 info->extra_files[0] = str;
105 info->extra_files[1] = NULL;
106
107 return;
108 }
109
110 while (info->extra_files[count])
111 count++;
112
113 space = EXTRA_BLOCK - 1 - (count % EXTRA_BLOCK);
114
115 if (space == 0)
116 {
117 info->extra_files = (const char **) UtilRealloc((void *)info->extra_files,
118 (count + 1 + EXTRA_BLOCK) * sizeof(const char *));
119 }
120
121 info->extra_files[count] = str;
122 info->extra_files[count+1] = NULL;
123 }
124
125 #define HANDLE_BOOLEAN(name, field) \
126 if (UtilStrCaseCmp(opt_str, name) == 0) \
127 { \
128 info->field = TRUE; \
129 argv++; argc--; \
130 continue; \
131 }
132
133 #define HANDLE_BOOLEAN2(abbrev, name, field) \
134 HANDLE_BOOLEAN(abbrev, field) \
135 HANDLE_BOOLEAN(name, field)
136
GlbspParseArgs(nodebuildinfo_t * info,volatile nodebuildcomms_t * comms,const char ** argv,int argc)137 glbsp_ret_e GlbspParseArgs(nodebuildinfo_t *info,
138 volatile nodebuildcomms_t *comms,
139 const char ** argv, int argc)
140 {
141 const char *opt_str;
142 int num_files = 0;
143 int got_output = FALSE;
144
145 cur_comms = comms;
146 SetErrorMsg("(Unknown Problem)");
147
148 while (argc > 0)
149 {
150 if (argv[0][0] != '-')
151 {
152 // --- ORDINARY FILENAME ---
153
154 if (got_output)
155 {
156 SetErrorMsg("Input filenames must precede the -o option");
157 cur_comms = NULL;
158 return GLBSP_E_BadArgs;
159 }
160
161 if (CheckExtension(argv[0], "gwa"))
162 {
163 SetErrorMsg("Input file cannot be GWA (contains nothing to build)");
164 cur_comms = NULL;
165 return GLBSP_E_BadArgs;
166 }
167
168 if (num_files >= 1)
169 {
170 AddExtraFile(info, GlbspStrDup(argv[0]));
171 }
172 else
173 {
174 GlbspFree(info->input_file);
175 info->input_file = GlbspStrDup(argv[0]);
176 }
177
178 num_files++;
179
180 argv++; argc--;
181 continue;
182 }
183
184 // --- AN OPTION ---
185
186 opt_str = &argv[0][1];
187
188 // handle GNU style options beginning with '--'
189 if (opt_str[0] == '-')
190 opt_str++;
191
192 if (UtilStrCaseCmp(opt_str, "o") == 0)
193 {
194 if (got_output)
195 {
196 SetErrorMsg("The -o option cannot be used more than once");
197 cur_comms = NULL;
198 return GLBSP_E_BadArgs;
199 }
200
201 if (num_files >= 2)
202 {
203 SetErrorMsg("Cannot use -o with multiple input files.");
204 cur_comms = NULL;
205 return GLBSP_E_BadArgs;
206 }
207
208 if (argc < 2 || argv[1][0] == '-')
209 {
210 SetErrorMsg("Missing filename for the -o option");
211 cur_comms = NULL;
212 return GLBSP_E_BadArgs;
213 }
214
215 GlbspFree(info->output_file);
216 info->output_file = GlbspStrDup(argv[1]);
217
218 got_output = TRUE;
219
220 argv += 2; argc -= 2;
221 continue;
222 }
223
224 if (UtilStrCaseCmp(opt_str, "factor") == 0 ||
225 UtilStrCaseCmp(opt_str, "c") == 0)
226 {
227 if (argc < 2)
228 {
229 SetErrorMsg("Missing factor value");
230 cur_comms = NULL;
231 return GLBSP_E_BadArgs;
232 }
233
234 info->factor = (int) strtol(argv[1], NULL, 10);
235
236 argv += 2; argc -= 2;
237 continue;
238 }
239
240 if (tolower(opt_str[0]) == 'v' && isdigit(opt_str[1]))
241 {
242 info->spec_version = (opt_str[1] - '0');
243
244 argv++; argc--;
245 continue;
246 }
247
248 if (UtilStrCaseCmp(opt_str, "maxblock") == 0 ||
249 UtilStrCaseCmp(opt_str, "b") == 0)
250 {
251 if (argc < 2)
252 {
253 SetErrorMsg("Missing maxblock value");
254 cur_comms = NULL;
255 return GLBSP_E_BadArgs;
256 }
257
258 info->block_limit = (int) strtol(argv[1], NULL, 10);
259
260 argv += 2; argc -= 2;
261 continue;
262 }
263
264 HANDLE_BOOLEAN2("q", "quiet", quiet)
265 HANDLE_BOOLEAN2("f", "fast", fast)
266 HANDLE_BOOLEAN2("w", "warn", mini_warnings)
267 HANDLE_BOOLEAN2("p", "pack", pack_sides)
268 HANDLE_BOOLEAN2("n", "normal", force_normal)
269 HANDLE_BOOLEAN2("xr", "noreject", no_reject)
270 HANDLE_BOOLEAN2("xp", "noprog", no_progress)
271
272 HANDLE_BOOLEAN2("m", "mergevert", merge_vert)
273 HANDLE_BOOLEAN2("u", "prunesec", prune_sect)
274 HANDLE_BOOLEAN2("y", "windowfx", window_fx)
275 HANDLE_BOOLEAN2("s", "skipselfref", skip_self_ref)
276 HANDLE_BOOLEAN2("xu", "noprune", no_prune)
277 HANDLE_BOOLEAN2("xn", "nonormal", no_normal)
278
279 // to err is human...
280 HANDLE_BOOLEAN("noprogress", no_progress)
281 HANDLE_BOOLEAN("packsides", pack_sides)
282 HANDLE_BOOLEAN("prunesect", prune_sect)
283
284 // ignore these options for backwards compatibility
285 if (UtilStrCaseCmp(opt_str, "fresh") == 0 ||
286 UtilStrCaseCmp(opt_str, "keepdummy") == 0 ||
287 UtilStrCaseCmp(opt_str, "keepsec") == 0 ||
288 UtilStrCaseCmp(opt_str, "keepsect") == 0)
289 {
290 argv++; argc--;
291 continue;
292 }
293
294 // backwards compatibility
295 HANDLE_BOOLEAN("forcegwa", gwa_mode)
296 HANDLE_BOOLEAN("forcenormal", force_normal)
297 HANDLE_BOOLEAN("loadall", load_all)
298
299 // The -hexen option is only kept for backwards compatibility
300 HANDLE_BOOLEAN("hexen", force_hexen)
301
302 SetErrorMsg("Unknown option: %s", argv[0]);
303
304 cur_comms = NULL;
305 return GLBSP_E_BadArgs;
306 }
307
308 cur_comms = NULL;
309 return GLBSP_E_OK;
310 }
311
GlbspCheckInfo(nodebuildinfo_t * info,volatile nodebuildcomms_t * comms)312 glbsp_ret_e GlbspCheckInfo(nodebuildinfo_t *info,
313 volatile nodebuildcomms_t *comms)
314 {
315 cur_comms = comms;
316 SetErrorMsg("(Unknown Problem)");
317
318 info->same_filenames = FALSE;
319 info->missing_output = FALSE;
320
321 if (!info->input_file || info->input_file[0] == 0)
322 {
323 SetErrorMsg("Missing input filename !");
324 return GLBSP_E_BadArgs;
325 }
326
327 if (CheckExtension(info->input_file, "gwa"))
328 {
329 SetErrorMsg("Input file cannot be GWA (contains nothing to build)");
330 return GLBSP_E_BadArgs;
331 }
332
333 if (!info->output_file || info->output_file[0] == 0)
334 {
335 GlbspFree(info->output_file);
336 info->output_file = GlbspStrDup(ReplaceExtension(
337 info->input_file, "gwa"));
338
339 info->gwa_mode = TRUE;
340 info->missing_output = TRUE;
341 }
342 else /* has output filename */
343 {
344 if (CheckExtension(info->output_file, "gwa"))
345 info->gwa_mode = TRUE;
346 }
347
348 if (UtilStrCaseCmp(info->input_file, info->output_file) == 0)
349 {
350 info->load_all = TRUE;
351 info->same_filenames = TRUE;
352 }
353
354 if (info->no_prune && info->pack_sides)
355 {
356 info->pack_sides = FALSE;
357 SetErrorMsg("-noprune and -packsides cannot be used together");
358 return GLBSP_E_BadInfoFixed;
359 }
360
361 if (info->gwa_mode && info->force_normal)
362 {
363 info->force_normal = FALSE;
364 SetErrorMsg("-forcenormal used, but GWA files don't have normal nodes");
365 return GLBSP_E_BadInfoFixed;
366 }
367
368 if (info->no_normal && info->force_normal)
369 {
370 info->force_normal = FALSE;
371 SetErrorMsg("-forcenormal and -nonormal cannot be used together");
372 return GLBSP_E_BadInfoFixed;
373 }
374
375 if (info->factor <= 0 || info->factor > 32)
376 {
377 info->factor = DEFAULT_FACTOR;
378 SetErrorMsg("Bad factor value !");
379 return GLBSP_E_BadInfoFixed;
380 }
381
382 if (info->spec_version <= 0 || info->spec_version > 5)
383 {
384 info->spec_version = 2;
385 SetErrorMsg("Bad GL-Nodes version number !");
386 return GLBSP_E_BadInfoFixed;
387 }
388 else if (info->spec_version == 4)
389 {
390 info->spec_version = 5;
391 SetErrorMsg("V4 GL-Nodes is not supported");
392 return GLBSP_E_BadInfoFixed;
393 }
394
395 if (info->block_limit < 1000 || info->block_limit > 64000)
396 {
397 info->block_limit = DEFAULT_BLOCK_LIMIT;
398 SetErrorMsg("Bad blocklimit value !");
399 return GLBSP_E_BadInfoFixed;
400 }
401
402 return GLBSP_E_OK;
403 }
404
405
406 /* ----- memory functions --------------------------- */
407
GlbspStrDup(const char * str)408 const char *GlbspStrDup(const char *str)
409 {
410 if (! str)
411 return NULL;
412
413 return UtilStrDup(str);
414 }
415
GlbspFree(const char * str)416 void GlbspFree(const char *str)
417 {
418 if (! str)
419 return;
420
421 UtilFree((char *) str);
422 }
423
424
425 /* ----- build nodes for a single level --------------------------- */
426
HandleLevel(void)427 static glbsp_ret_e HandleLevel(void)
428 {
429 superblock_t *seg_list;
430 node_t *root_node;
431 node_t *root_stale_node;
432 subsec_t *root_sub;
433
434 glbsp_ret_e ret;
435
436 if (cur_comms->cancelled)
437 return GLBSP_E_Cancelled;
438
439 DisplaySetBarLimit(1, 1000);
440 DisplaySetBar(1, 0);
441
442 cur_comms->build_pos = 0;
443
444 LoadLevel();
445
446 InitBlockmap();
447
448 // create initial segs
449 seg_list = CreateSegs();
450
451 root_stale_node = (num_stale_nodes == 0) ? NULL :
452 LookupStaleNode(num_stale_nodes - 1);
453
454 // recursively create nodes
455 ret = BuildNodes(seg_list, &root_node, &root_sub, 0, root_stale_node);
456 FreeSuper(seg_list);
457
458 if (ret == GLBSP_E_OK)
459 {
460 ClockwiseBspTree(root_node);
461
462 PrintVerbose("Built %d NODES, %d SSECTORS, %d SEGS, %d VERTEXES\n",
463 num_nodes, num_subsecs, num_segs, num_normal_vert + num_gl_vert);
464
465 if (root_node)
466 PrintVerbose("Heights of left and right subtrees = (%d,%d)\n",
467 ComputeBspHeight(root_node->r.node),
468 ComputeBspHeight(root_node->l.node));
469
470 SaveLevel(root_node);
471 }
472
473 FreeLevel();
474 FreeQuickAllocCuts();
475 FreeQuickAllocSupers();
476
477 return ret;
478 }
479
480
481 /* ----- main routine -------------------------------------- */
482
GlbspBuildNodes(const nodebuildinfo_t * info,const nodebuildfuncs_t * funcs,volatile nodebuildcomms_t * comms)483 glbsp_ret_e GlbspBuildNodes(const nodebuildinfo_t *info,
484 const nodebuildfuncs_t *funcs, volatile nodebuildcomms_t *comms)
485 {
486 char *file_msg;
487
488 glbsp_ret_e ret = GLBSP_E_OK;
489
490 cur_info = info;
491 cur_funcs = funcs;
492 cur_comms = comms;
493
494 cur_comms->total_big_warn = 0;
495 cur_comms->total_small_warn = 0;
496
497 // clear cancelled flag
498 comms->cancelled = FALSE;
499
500 // sanity check
501 if (!cur_info->input_file || cur_info->input_file[0] == 0 ||
502 !cur_info->output_file || cur_info->output_file[0] == 0)
503 {
504 SetErrorMsg("INTERNAL ERROR: Missing in/out filename !");
505 return GLBSP_E_BadArgs;
506 }
507
508 InitDebug();
509 InitEndian();
510
511 if (info->missing_output)
512 PrintMsg("* No output file specified. Using: %s\n\n", info->output_file);
513
514 if (info->same_filenames)
515 PrintMsg("* Output file is same as input file. Using -loadall\n\n");
516
517 // opens and reads directory from the input wad
518 ret = ReadWadFile(cur_info->input_file);
519
520 if (ret != GLBSP_E_OK)
521 {
522 TermDebug();
523 return ret;
524 }
525
526 if (CountLevels() <= 0)
527 {
528 CloseWads();
529 TermDebug();
530
531 SetErrorMsg("No levels found in wad !");
532 return GLBSP_E_Unknown;
533 }
534
535 PrintMsg("\n");
536 PrintVerbose("Creating nodes using tunable factor of %d\n", info->factor);
537
538 DisplayOpen(DIS_BUILDPROGRESS);
539 DisplaySetTitle("glBSP Build Progress");
540
541 file_msg = UtilFormat("File: %s", cur_info->input_file);
542
543 DisplaySetBarText(2, file_msg);
544 DisplaySetBarLimit(2, CountLevels() * 10);
545 DisplaySetBar(2, 0);
546
547 UtilFree(file_msg);
548
549 cur_comms->file_pos = 0;
550
551 // loop over each level in the wad
552 while (FindNextLevel())
553 {
554 ret = HandleLevel();
555
556 if (ret != GLBSP_E_OK)
557 break;
558
559 cur_comms->file_pos += 10;
560 DisplaySetBar(2, cur_comms->file_pos);
561 }
562
563 DisplayClose();
564
565 // writes all the lumps to the output wad
566 if (ret == GLBSP_E_OK)
567 {
568 ret = WriteWadFile(cur_info->output_file);
569
570 // when modifying the original wad, any GWA companion must be deleted
571 if (ret == GLBSP_E_OK && cur_info->same_filenames)
572 DeleteGwaFile(cur_info->output_file);
573
574 PrintMsg("\n");
575 PrintMsg("Total serious warnings: %d\n", cur_comms->total_big_warn);
576 PrintMsg("Total minor warnings: %d\n", cur_comms->total_small_warn);
577
578 ReportFailedLevels();
579 }
580
581 // close wads and free memory
582 CloseWads();
583
584 TermDebug();
585
586 cur_info = NULL;
587 cur_comms = NULL;
588 cur_funcs = NULL;
589
590 return ret;
591 }
592
593