zdbsp/main.cpp

713 lines
17 KiB
C++

/*
The main glue for ZDBSP.
Copyright (C) 2002-2006 Randy Heit
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
// HEADER FILES ------------------------------------------------------------
#ifdef _WIN32
// Need windows.h for QueryPerformanceCounter
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define HAVE_TIMING 1
#define START_COUNTER(s,e,f) \
LARGE_INTEGER s, e, f; QueryPerformanceCounter (&s);
#define END_COUNTER(s,e,f,l) \
QueryPerformanceCounter (&e); QueryPerformanceFrequency (&f); \
if (!NoTiming) printf (l, double(e.QuadPart - s.QuadPart) / double(f.QuadPart));
#else
#include <time.h>
#define HAVE_TIMING 1
#define START_COUNTER(s,e,f) \
clock_t s, e; s = clock();
#define END_COUNTER(s,e,f,l) \
e = clock(); \
if (!NoTiming) printf (l, double(e - s) / CLOCKS_PER_SEC);
// Need these to check if input/output are the same file
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "zdbsp.h"
#include "wad.h"
#include "processor.h"
#include "getopt.h"
// MACROS ------------------------------------------------------------------
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static void ParseArgs (int argc, char **argv);
static void ShowUsage ();
static void ShowVersion ();
static bool CheckInOutNames ();
#ifndef DISABLE_SSE
static void CheckSSE ();
#endif
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern "C" int optind;
extern "C" char *optarg;
// PUBLIC DATA DEFINITIONS -------------------------------------------------
const char *Map = NULL;
const char *InName;
const char *OutName = "tmp.wad";
bool BuildNodes = true;
bool BuildGLNodes = false;
bool ConformNodes = false;
bool NoPrune = false;
EBlockmapMode BlockmapMode = EBM_Rebuild;
ERejectMode RejectMode = ERM_DontTouch;
bool WriteComments = false;
int MaxSegs = 64;
int SplitCost = 8;
int AAPreference = 16;
bool CheckPolyobjs = true;
bool ShowMap = false;
bool ShowWarnings = false;
bool NoTiming = false;
bool CompressNodes = false;
bool CompressGLNodes = false;
bool ForceCompression = false;
bool GLOnly = false;
bool V5GLNodes = false;
bool HaveSSE1, HaveSSE2;
int SSELevel;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static option long_opts[] =
{
{"help", no_argument, 0, 1000},
{"version", no_argument, 0, 'V'},
{"view", no_argument, 0, 'v'},
{"warn", no_argument, 0, 'w'},
{"map", required_argument, 0, 'm'},
{"output", required_argument, 0, 'o'},
{"output-file", required_argument, 0, 'o'},
{"file", required_argument, 0, 'f'},
{"no-nodes", no_argument, 0, 'N'},
{"gl", no_argument, 0, 'g'},
{"gl-matching", no_argument, 0, 'G'},
{"empty-blockmap", no_argument, 0, 'b'},
{"empty-reject", no_argument, 0, 'r'},
{"zero-reject", no_argument, 0, 'R'},
{"full-reject", no_argument, 0, 'e'},
{"no-reject", no_argument, 0, 'E'},
{"partition", required_argument, 0, 'p'},
{"split-cost", required_argument, 0, 's'},
{"diagonal-cost", required_argument, 0, 'd'},
{"no-polyobjs", no_argument, 0, 'P'},
{"no-prune", no_argument, 0, 'q'},
{"no-timing", no_argument, 0, 't'},
{"compress", no_argument, 0, 'z'},
{"compress-normal", no_argument, 0, 'Z'},
{"extended", no_argument, 0, 'X'},
{"gl-only", no_argument, 0, 'x'},
{"gl-v5", no_argument, 0, '5'},
{"no-sse", no_argument, 0, 1002},
{"no-sse2", no_argument, 0, 1003},
{"comments", no_argument, 0, 'c'},
{0,0,0,0}
};
static const char short_opts[] = "wVgGvbNrReEm:o:f:p:s:d:PqtzZXx5c";
// CODE --------------------------------------------------------------------
int main (int argc, char **argv)
{
bool fixSame = false;
#ifdef DISABLE_SSE
HaveSSE1 = HaveSSE2 = false;
#else
HaveSSE1 = HaveSSE2 = true;
#endif
ParseArgs (argc, argv);
if (InName == NULL)
{
if (optind >= argc || optind < argc-1)
{ // Source file is unspecified or followed by junk
ShowUsage ();
return 0;
}
InName = argv[optind];
}
#ifndef DISABLE_SSE
CheckSSE ();
#endif
try
{
START_COUNTER(t1a, t1b, t1c)
if (CheckInOutNames ())
{
// When the input and output files are the same, output will go to
// a temporary file. After everything is done, the input file is
// deleted and the output file is renamed to match the input file.
char *out = new char[strlen(OutName)+3], *dot;
if (out == NULL)
{
throw std::runtime_error("Could not create temporary file name.");
}
strcpy (out, OutName);
dot = strrchr (out, '.');
if (dot && (dot[1] == 'w' || dot[1] == 'W')
&& (dot[2] == 'a' || dot[2] == 'A')
&& (dot[3] == 'd' || dot[3] == 'D')
&& dot[4] == 0)
{
// *.wad becomes *.daw
dot[1] = 'd';
dot[3] = 'w';
}
else
{
// * becomes *.x
strcat (out, ".x");
}
OutName = out;
fixSame = true;
}
{
FWadReader inwad (InName);
FWadWriter outwad (OutName, inwad.IsIWAD());
int lump = 0;
int max = inwad.NumLumps ();
while (lump < max)
{
if (inwad.IsMap (lump) &&
(!Map || stricmp (inwad.LumpName (lump), Map) == 0))
{
START_COUNTER(t2a, t2b, t2c)
FProcessor builder (inwad, lump);
builder.Write (outwad);
END_COUNTER(t2a, t2b, t2c, " %.3f seconds.\n")
lump = inwad.LumpAfterMap (lump);
}
else if (inwad.IsGLNodes (lump))
{
// Ignore GL nodes from the input for any maps we process.
if (BuildNodes && (Map == NULL || stricmp (inwad.LumpName (lump)+3, Map) == 0))
{
lump = inwad.SkipGLNodes (lump);
}
else
{
outwad.CopyLump (inwad, lump);
++lump;
}
}
else
{
//printf ("copy %s\n", inwad.LumpName (lump));
outwad.CopyLump (inwad, lump);
++lump;
}
}
outwad.Close ();
}
if (fixSame)
{
remove (InName);
if (0 != rename (OutName, InName))
{
printf ("The output file could not be renamed to %s.\nYou can find it as %s.\n",
InName, OutName);
}
}
END_COUNTER(t1a, t1b, t1c, "\nTotal time: %.3f seconds.\n")
}
catch (std::runtime_error msg)
{
printf ("%s\n", msg.what());
return 20;
}
catch (std::bad_alloc)
{
printf ("Out of memory\n");
return 20;
}
catch (std::exception msg)
{
printf ("%s\n", msg.what());
return 20;
}
#ifndef _DEBUG
catch (...)
{
printf ("Unhandled exception. ZDBSP cannot continue.\n");
return 20;
}
#endif
return 0;
}
//==========================================================================
//
// ParseArgs
//
//==========================================================================
static void ParseArgs (int argc, char **argv)
{
int ch;
while ((ch = getopt_long (argc, argv, short_opts, long_opts, NULL)) != EOF)
{
switch (ch)
{
case 0:
break;
case 'v':
ShowMap = true;
break;
case 'w':
ShowWarnings = true;
break;
case 'm':
Map = optarg;
break;
case 'o':
OutName = optarg;
break;
case 'f':
InName = optarg;
break;
case 'N':
BuildNodes = false;
break;
case 'b':
BlockmapMode = EBM_Create0;
break;
case 'r':
RejectMode = ERM_Create0;
break;
case 'R':
RejectMode = ERM_CreateZeroes;
break;
case 'e':
RejectMode = ERM_Rebuild;
break;
case 'E':
RejectMode = ERM_DontTouch;
break;
case 'p':
MaxSegs = atoi (optarg);
if (MaxSegs < 3)
{ // Don't be too unreasonable
MaxSegs = 3;
}
break;
case 's':
SplitCost = atoi (optarg);
if (SplitCost < 1)
{ // 1 means to add no extra weight at all
SplitCost = 1;
}
break;
case 'd':
AAPreference = atoi (optarg);
if (AAPreference < 1)
{
AAPreference = 1;
}
break;
case 'P':
CheckPolyobjs = false;
break;
case 'g':
BuildGLNodes = true;
ConformNodes = false;
break;
case 'G':
BuildGLNodes = true;
ConformNodes = true;
break;
case 'X':
CompressNodes = true;
CompressGLNodes = true;
ForceCompression = false;
break;
case 'z':
CompressNodes = true;
CompressGLNodes = true;
ForceCompression = true;
break;
case 'Z':
CompressNodes = true;
CompressGLNodes = false;
ForceCompression = true;
break;
case 'x':
GLOnly = true;
BuildGLNodes = true;
ConformNodes = false;
break;
case '5':
V5GLNodes = true;
break;
case 'q':
NoPrune = true;
break;
case 't':
NoTiming = true;
break;
case 'c':
WriteComments = true;
break;
case 'V':
ShowVersion ();
exit (0);
break;
case 1002: // Disable SSE/SSE2 ClassifyLine routine
HaveSSE1 = false;
HaveSSE2 = false;
break;
case 1003: // Disable only SSE2 ClassifyLine routine
HaveSSE2 = false;
break;
case 1000:
ShowUsage ();
exit (0);
default:
printf ("Try `zdbsp --help' for more information.\n");
exit (0);
}
}
}
//==========================================================================
//
// ShowUsage
//
//==========================================================================
static void ShowUsage ()
{
printf (
"Usage: zdbsp [options] sourcefile.wad\n"
" -m, --map=MAP Only affect the specified map\n"
" -o, --output=FILE Write output to FILE instead of tmp.wad\n"
" -q, --no-prune Keep unused sidedefs and sectors\n"
" -N, --no-nodes Do not rebuild nodes\n"
" -g, --gl Build GL-friendly nodes\n"
" -G, --gl-matching Build GL-friendly nodes that match normal nodes\n"
" -x, --gl-only Only build GL-friendly nodes\n"
" -5, --gl-v5 Create v5 GL-friendly nodes (overriden by -z and -X)\n"
" -X, --extended Create extended nodes (including GL nodes, if built)\n"
" -z, --compress Compress the nodes (including GL nodes, if built)\n"
" -Z, --compress-normal Compress normal nodes but not GL nodes\n"
" -b, --empty-blockmap Create an empty blockmap\n"
" -r, --empty-reject Create an empty reject table\n"
" -R, --zero-reject Create a reject table of all zeroes\n"
//" -e, --full-reject Rebuild reject table (unsupported)\n"
" -E, --no-reject Leave reject table untouched\n"
" -p, --partition=NNN Maximum segs to consider at each node (default %d)\n"
" -s, --split-cost=NNN Cost for splitting segs (default %d)\n"
" -d, --diagonal-cost=NNN Cost for avoiding diagonal splitters (default %d)\n"
" -P, --no-polyobjs Do not check for polyobject subsector splits\n"
#ifdef _WIN32
" -v, --view View the nodes\n"
#endif
" -w, --warn Show warning messages\n"
#if HAVE_TIMING
" -t, --no-timing Suppress timing information\n"
#endif
" -V, --version Display version information\n"
" --help Display this usage information"
#ifndef _WIN32
"\n"
#endif
, MaxSegs /* Partition size */
, SplitCost
, AAPreference
);
}
//==========================================================================
//
// ShowVersion
//
//==========================================================================
static void ShowVersion ()
{
printf ("ZDBSP " ZDBSP_VERSION " ("
#if defined(__GNUC__)
"GCC"
#if defined(__i386__)
"-x86"
#elif defined(__amd64__)
"-amd64"
#elif defined(__ppc__)
"-ppc"
#endif
#elif defined(_MSC_VER)
"VC"
#if defined(_M_IX86)
"-x86"
#if _M_IX86_FP > 1
"-SSE2"
#endif
#elif defined(_M_X64)
"-x64"
#endif
#endif
" : " __DATE__ ")\n");
}
//==========================================================================
//
// CheckInOutNames
//
// Returns true if InName and OutName refer to the same file. This needs
// to be implemented different under Windows than Unix because the inode
// information returned by stat is always 0, so it cannot be used to
// determine duplicate files.
//
//==========================================================================
static bool CheckInOutNames ()
{
#ifndef _WIN32
struct stat info;
dev_t outdev;
ino_t outinode;
if (0 != stat (OutName, &info))
{ // If out doesn't exist, it can't be duplicated
return false;
}
outdev = info.st_dev;
outinode = info.st_ino;
if (0 != stat (InName, &info))
{
return false;
}
return outinode == info.st_ino && outdev == info.st_dev;
#else
HANDLE inFile, outFile;
outFile = CreateFile (OutName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (outFile == INVALID_HANDLE_VALUE)
{
return false;
}
inFile = CreateFile (InName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (inFile == INVALID_HANDLE_VALUE)
{
CloseHandle (outFile);
return false;
}
BY_HANDLE_FILE_INFORMATION inInfo, outInfo;
bool same = false;
if (GetFileInformationByHandle (inFile, &inInfo) &&
GetFileInformationByHandle (outFile, &outInfo))
{
same = inInfo.dwVolumeSerialNumber == outInfo.dwVolumeSerialNumber &&
inInfo.nFileIndexLow == outInfo.nFileIndexLow &&
inInfo.nFileIndexHigh == outInfo.nFileIndexHigh;
}
CloseHandle (inFile);
CloseHandle (outFile);
return same;
#endif
}
//==========================================================================
//
// CheckSSE
//
// Checks if the processor supports SSE or SSE2.
//
//==========================================================================
#ifndef DISABLE_SSE
static void CheckSSE ()
{
#ifdef __SSE2__
// If we compiled with SSE2 support enabled for everything, then
// obviously it's available, or the program won't get very far.
return;
#endif
if (!HaveSSE2 && !HaveSSE1)
{
return;
}
bool forcenosse1 = !HaveSSE1;
bool forcenosse2 = !HaveSSE2;
HaveSSE1 = false;
HaveSSE2 = false;
#if defined(_MSC_VER) && !defined(__clang__)
#ifdef _M_X64
// Processors implementing AMD64 are required to support SSE2.
return;
#else
__asm
{
pushfd // save EFLAGS
pop eax // store EFLAGS in EAX
mov edx,eax // save in EDX for later testing
xor eax,0x00200000 // toggle bit 21
push eax // put to stack
popfd // save changed EAX to EFLAGS
pushfd // push EFLAGS to TOS
pop eax // store EFLAGS in EAX
cmp eax,edx // see if bit 21 has changed
jz noid // if no change, then no CPUID
// Check the feature flag for SSE/SSE2
mov eax,1
cpuid
test edx,(1<<25) // Check for SSE
setnz HaveSSE1
test edx,(1<<26) // Check for SSE2
setnz HaveSSE2
noid:
}
#endif
#elif defined(__GNUC__) || defined(__clang__)
// Same as above, but for GCC
asm volatile
("pushfl\n\t"
"popl %%eax\n\t"
"movl %%eax,%%edx\n\t"
"xorl $0x200000,%%eax\n\t"
"pushl %%eax\n\t"
"popfl\n\t"
"pushfl\n\t"
"popl %%eax\n\t"
"cmp %%edx,%%eax\n\t"
"jz noid\n\t"
"mov $1,%%eax\n\t"
"cpuid\n\t"
"test $(1<<25),%%edx\n\t"
"setneb %0\n"
"test $(1<<26),%%edx\n\t"
"setneb %1\n"
"noid:"
:"=m" (HaveSSE1),"=m" (HaveSSE2)::"eax","ebx","ecx","edx");
#endif
if (forcenosse1)
{
HaveSSE1 = false;
}
if (forcenosse2)
{
HaveSSE2 = false;
}
}
#endif
//==========================================================================
//
// PointToAngle
//
//==========================================================================
angle_t PointToAngle (fixed_t x, fixed_t y)
{
double ang = atan2 (double(y), double(x));
const double rad2bam = double(1<<30) / M_PI;
double dbam = ang * rad2bam;
// Convert to signed first since negative double to unsigned is undefined.
return angle_t(int(dbam)) << 1;
}
//==========================================================================
//
// Warn
//
//==========================================================================
void Warn (const char *format, ...)
{
va_list marker;
if (!ShowWarnings)
{
return;
}
va_start (marker, format);
vprintf (format, marker);
va_end (marker);
}