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