/*

    BINARY SPACE PARTITION    -aka-    B S P

    Code based on original code from Valve Software,
    Modified by Sean "Zoner" Cavanaugh (seanc@gearboxsoftware.com) with permission.
    Modified by Tony "Merl" Moore (merlinis@bigpond.net.au) [AJM]

*/

#ifdef SYSTEM_WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(disable:4996)
#endif

#include "bsp5.h"

/*

 NOTES


*/

static FILE*    polyfiles[NUM_HULLS];
int             g_hullnum = 0;

static face_t*  validfaces[MAX_INTERNAL_MAP_PLANES];

char            g_bspfilename[_MAX_PATH];
char            g_pointfilename[_MAX_PATH];
char            g_linefilename[_MAX_PATH];
char            g_portfilename[_MAX_PATH];

// command line flags
bool			g_noopt = DEFAULT_NOOPT;		// don't optimize BSP on write
bool            g_nofill = DEFAULT_NOFILL;      // dont fill "-nofill"
bool            g_notjunc = DEFAULT_NOTJUNC;
bool            g_noclip = DEFAULT_NOCLIP;      // no clipping hull "-noclip"
bool            g_chart = DEFAULT_CHART;        // print out chart? "-chart"
bool            g_estimate = DEFAULT_ESTIMATE;  // estimate mode "-estimate"
bool            g_info = DEFAULT_INFO;
bool            g_bLeakOnly = DEFAULT_LEAKONLY; // leakonly mode "-leakonly"
bool            g_bLeaked = false;

int             g_subdivide_size = DEFAULT_SUBDIVIDE_SIZE;

#ifdef ZHLT_NULLTEX // AJM
bool            g_bUseNullTex = DEFAULT_NULLTEX; // "-nonulltex"
#endif

#ifdef ZHLT_DETAIL // AJM
bool            g_bDetailBrushes = DEFAULT_DETAIL; // "-nodetail"
#endif

#ifdef ZHLT_PROGRESSFILE // AJM
char*           g_progressfile = DEFAULT_PROGRESSFILE; // "-progressfile path"
#endif

#ifdef ZHLT_INFO_COMPILE_PARAMETERS// AJM
// =====================================================================================
//  GetParamsFromEnt
//      this function is called from parseentity when it encounters the
//      info_compile_parameters entity. each tool should have its own version of this
//      to handle its own specific settings.
// =====================================================================================
void            GetParamsFromEnt(entity_t* mapent)
{
    int iTmp;

    Log("\nCompile Settings detected from info_compile_parameters entity\n");

    // verbose(choices) : "Verbose compile messages" : 0 = [ 0 : "Off" 1 : "On" ]
    iTmp = IntForKey(mapent, "verbose");
    if (iTmp == 1)
    {
        g_verbose = true;
    }
    else if (iTmp == 0)
    {
        g_verbose = false;
    }
    Log("%30s [ %-9s ]\n", "Compile Option", "setting");
    Log("%30s [ %-9s ]\n", "Verbose Compile Messages", g_verbose ? "on" : "off");

    // estimate(choices) :"Estimate Compile Times?" : 0 = [ 0: "Yes" 1: "No" ]
    if (IntForKey(mapent, "estimate"))
    {
        g_estimate = true;
    }
    else
    {
        g_estimate = false;
    }
    Log("%30s [ %-9s ]\n", "Estimate Compile Times", g_estimate ? "on" : "off");

	// priority(choices) : "Priority Level" : 0 = [	0 : "Normal" 1 : "High"	-1 : "Low" ]
	if (!strcmp(ValueForKey(mapent, "priority"), "1"))
    {
        g_threadpriority = eThreadPriorityHigh;
        Log("%30s [ %-9s ]\n", "Thread Priority", "high");
    }
    else if (!strcmp(ValueForKey(mapent, "priority"), "-1"))
    {
        g_threadpriority = eThreadPriorityLow;
        Log("%30s [ %-9s ]\n", "Thread Priority", "low");
    }

    /*
    hlbsp(choices) : "HLBSP" : 0 =
    [
       0 : "Off"
       1 : "Normal"
       2 : "Leakonly"
    ]
    */
    iTmp = IntForKey(mapent, "hlbsp");
    if (iTmp == 0)
    {
        Fatal(assume_TOOL_CANCEL,
            "%s flag was not checked in info_compile_parameters entity, execution of %s cancelled", g_Program, g_Program);
        CheckFatal();
    }
    else if (iTmp == 1)
    {
        g_bLeakOnly = false;
    }
    else if (iTmp == 2)
    {
        g_bLeakOnly = true;
    }
    Log("%30s [ %-9s ]\n", "Leakonly Mode", g_bLeakOnly ? "on" : "off");

	iTmp = IntForKey(mapent, "noopt");
	if(iTmp == 0)
	{
		g_noopt = false;
	}
	else
	{
		g_noopt = true;
	}

    /*
    nocliphull(choices) : "Generate clipping hulls" : 0 =
    [
        0 : "Yes"
        1 : "No"
    ]
    */
    iTmp = IntForKey(mapent, "nocliphull");
    if (iTmp == 0)
    {
        g_noclip = false;
    }
    else if (iTmp == 1)
    {
        g_noclip = true;
    }
    Log("%30s [ %-9s ]\n", "Clipping Hull Generation", g_noclip ? "off" : "on");

    //////////////////
    Verbose("\n");
}
#endif

// =====================================================================================
//  Extract File stuff (ExtractFile | ExtractFilePath | ExtractFileBase)
//
// With VS 2005 - and the 64 bit build, i had to pull 3 classes over from
// cmdlib.cpp even with the proper includes to get rid of the lnk2001 error
//
// amckern - amckern@yahoo.com
// =====================================================================================

#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')

void            ExtractFileBase(const char* const path, char* dest)
{
    hlassert (path != dest);

    const char*           src;

    src = path + strlen(path) - 1;

    //
    // back up until a \ or the start
    //
    while (src != path && !PATHSEPARATOR(*(src - 1)))
        src--;

    while (*src && *src != '.')
    {
        *dest++ = *src++;
    }
    *dest = 0;
}

void            ExtractFilePath(const char* const path, char* dest)
{
    hlassert (path != dest);

    const char*           src;

    src = path + strlen(path) - 1;

    //
    // back up until a \ or the start
    //
    while (src != path && !PATHSEPARATOR(*(src - 1)))
        src--;

    memcpy(dest, path, src - path);
    dest[src - path] = 0;
}

void            ExtractFile(const char* const path, char* dest)
{
    hlassert (path != dest);

    const char*           src;

    src = path + strlen(path) - 1;

    while (src != path && !PATHSEPARATOR(*(src - 1)))
        src--;

    while (*src)
    {
        *dest++ = *src++;
    }
    *dest = 0;
}

// =====================================================================================
//  NewFaceFromFace
//      Duplicates the non point information of a face, used by SplitFace and MergeFace.
// =====================================================================================
face_t*         NewFaceFromFace(const face_t* const in)
{
    face_t*         newf;

    newf = AllocFace();

    newf->planenum = in->planenum;
    newf->texturenum = in->texturenum;
    newf->original = in->original;
    newf->contents = in->contents;

    return newf;
}

// =====================================================================================
//  SplitFaceTmp
//      blah
// =====================================================================================
static void     SplitFaceTmp(face_t* in, const dplane_t* const split, face_t** front, face_t** back)
{
    vec_t           dists[MAXEDGES + 1];
    int             sides[MAXEDGES + 1];
    int             counts[3];
    vec_t           dot;
    int             i;
    int             j;
    face_t*         newf;
    face_t*         new2;
    vec_t*          p1;
    vec_t*          p2;
    vec3_t          mid;

    if (in->numpoints < 0)
    {
        Error("SplitFace: freed face");
    }
    counts[0] = counts[1] = counts[2] = 0;

    // determine sides for each point
    for (i = 0; i < in->numpoints; i++)
    {
        dot = DotProduct(in->pts[i], split->normal);
        dot -= split->dist;
        dists[i] = dot;
        if (dot > ON_EPSILON)
        {
            sides[i] = SIDE_FRONT;
        }
        else if (dot < -ON_EPSILON)
        {
            sides[i] = SIDE_BACK;
        }
        else
        {
            sides[i] = SIDE_ON;
        }
        counts[sides[i]]++;
    }
    sides[i] = sides[0];
    dists[i] = dists[0];

    if (!counts[0])
    {
        *front = NULL;
        *back = in;
        return;
    }
    if (!counts[1])
    {
        *front = in;
        *back = NULL;
        return;
    }

    *back = newf = NewFaceFromFace(in);
    *front = new2 = NewFaceFromFace(in);

    // distribute the points and generate splits

    for (i = 0; i < in->numpoints; i++)
    {
        if (newf->numpoints > MAXEDGES || new2->numpoints > MAXEDGES)
        {
            Error("SplitFace: numpoints > MAXEDGES");
        }

        p1 = in->pts[i];

        if (sides[i] == SIDE_ON)
        {
            VectorCopy(p1, newf->pts[newf->numpoints]);
            newf->numpoints++;
            VectorCopy(p1, new2->pts[new2->numpoints]);
            new2->numpoints++;
            continue;
        }

        if (sides[i] == SIDE_FRONT)
        {
            VectorCopy(p1, new2->pts[new2->numpoints]);
            new2->numpoints++;
        }
        else
        {
            VectorCopy(p1, newf->pts[newf->numpoints]);
            newf->numpoints++;
        }

        if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
        {
            continue;
        }

        // generate a split point
        p2 = in->pts[(i + 1) % in->numpoints];

        dot = dists[i] / (dists[i] - dists[i + 1]);
        for (j = 0; j < 3; j++)
        {                                                  // avoid round off error when possible
            if (split->normal[j] == 1)
            {
                mid[j] = split->dist;
            }
            else if (split->normal[j] == -1)
            {
                mid[j] = -split->dist;
            }
            else
            {
                mid[j] = p1[j] + dot * (p2[j] - p1[j]);
            }
        }

        VectorCopy(mid, newf->pts[newf->numpoints]);
        newf->numpoints++;
        VectorCopy(mid, new2->pts[new2->numpoints]);
        new2->numpoints++;
    }

    if (newf->numpoints > MAXEDGES || new2->numpoints > MAXEDGES)
    {
        Error("SplitFace: numpoints > MAXEDGES");
    }
}

// =====================================================================================
//  SplitFace
//      blah
// =====================================================================================
void            SplitFace(face_t* in, const dplane_t* const split, face_t** front, face_t** back)
{
    SplitFaceTmp(in, split, front, back);

    // free the original face now that is is represented by the fragments
    if (*front && *back)
    {
        FreeFace(in);
    }
}

// =====================================================================================
//  AllocFace
// =====================================================================================
face_t*         AllocFace()
{
    face_t*         f;

    f = (face_t*)malloc(sizeof(face_t));
    memset(f, 0, sizeof(face_t));

    f->planenum = -1;

    return f;
}

// =====================================================================================
//  FreeFace
// =====================================================================================
void            FreeFace(face_t* f)
{
    free(f);
}

// =====================================================================================
//  AllocSurface
// =====================================================================================
surface_t*      AllocSurface()
{
    surface_t*      s;

    s = (surface_t*)malloc(sizeof(surface_t));
    memset(s, 0, sizeof(surface_t));

    return s;
}

// =====================================================================================
//  FreeSurface
// =====================================================================================
void            FreeSurface(surface_t* s)
{
    free(s);
}

// =====================================================================================
//  AllocPortal
// =====================================================================================
portal_t*       AllocPortal()
{
    portal_t*       p;

    p = (portal_t*)malloc(sizeof(portal_t));
    memset(p, 0, sizeof(portal_t));

    return p;
}

// =====================================================================================
//  FreePortal
// =====================================================================================
void            FreePortal(portal_t* p) // consider: inline
{
    free(p);
}


// =====================================================================================
//  AllocNode
//      blah
// =====================================================================================
node_t*         AllocNode()
{
    node_t*         n;

    n = (node_t*)malloc(sizeof(node_t));
    memset(n, 0, sizeof(node_t));

    return n;
}

// =====================================================================================
//  AddPointToBounds
// =====================================================================================
void            AddPointToBounds(const vec3_t v, vec3_t mins, vec3_t maxs)
{
    int             i;
    vec_t           val;

    for (i = 0; i < 3; i++)
    {
        val = v[i];
        if (val < mins[i])
        {
            mins[i] = val;
        }
        if (val > maxs[i])
        {
            maxs[i] = val;
        }
    }
}

// =====================================================================================
//  AddFaceToBounds
// =====================================================================================
static void     AddFaceToBounds(const face_t* const f, vec3_t mins, vec3_t maxs)
{
    int             i;

    for (i = 0; i < f->numpoints; i++)
    {
        AddPointToBounds(f->pts[i], mins, maxs);
    }
}

// =====================================================================================
//  ClearBounds
// =====================================================================================
static void     ClearBounds(vec3_t mins, vec3_t maxs)
{
    mins[0] = mins[1] = mins[2] = 99999;
    maxs[0] = maxs[1] = maxs[2] = -99999;
}

// =====================================================================================
//  SurflistFromValidFaces
//      blah
// =====================================================================================
static surfchain_t* SurflistFromValidFaces()
{
    surface_t*      n;
    int             i;
    face_t*         f;
    face_t*         next;
    surfchain_t*    sc;

    sc = (surfchain_t*)malloc(sizeof(*sc));
    ClearBounds(sc->mins, sc->maxs);
    sc->surfaces = NULL;

    // grab planes from both sides
    for (i = 0; i < g_numplanes; i += 2)
    {
        if (!validfaces[i] && !validfaces[i + 1])
        {
            continue;
        }
        n = AllocSurface();
        n->next = sc->surfaces;
        sc->surfaces = n;
        ClearBounds(n->mins, n->maxs);
        n->planenum = i;

        n->faces = NULL;
        for (f = validfaces[i]; f; f = next)
        {
            next = f->next;
            f->next = n->faces;
            n->faces = f;
            AddFaceToBounds(f, n->mins, n->maxs);
        }
        for (f = validfaces[i + 1]; f; f = next)
        {
            next = f->next;
            f->next = n->faces;
            n->faces = f;
            AddFaceToBounds(f, n->mins, n->maxs);
        }

        AddPointToBounds(n->mins, sc->mins, sc->maxs);
        AddPointToBounds(n->maxs, sc->mins, sc->maxs);

        validfaces[i] = NULL;
        validfaces[i + 1] = NULL;
    }

    // merge all possible polygons

    MergeAll(sc->surfaces);

    return sc;
}

#ifdef ZHLT_NULLTEX// AJM
// =====================================================================================
//  CheckFaceForNull
//      Returns true if the passed face is facetype null
// =====================================================================================
bool            CheckFaceForNull(const face_t* const f)
{
    // null faces are only of facetype face_null if we are using null texture stripping
    if (g_bUseNullTex)
    {
        texinfo_t*      info;
        miptex_t*       miptex;
        int             ofs;

        info = &g_texinfo[f->texturenum];
        ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex];
        miptex = (miptex_t*)(&g_dtexdata[ofs]);

        if (!strcasecmp(miptex->name, "null"))
            return true;
        else
            return false;
    }
    else // otherwise, under normal cases, null textured faces should be facetype face_normal
    {
        return false;
    }
}
// =====================================================================================
//Cpt_Andrew - UTSky Check
// =====================================================================================
bool            CheckFaceForEnv_Sky(const face_t* const f)
{
        texinfo_t*      info;
        miptex_t*       miptex;
        int             ofs;

        info = &g_texinfo[f->texturenum];
        ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex];
        miptex = (miptex_t*)(&g_dtexdata[ofs]);

        if (!strcasecmp(miptex->name, "env_sky"))
            return true;
        else
            return false;
}
// =====================================================================================




#endif

// SILENCER -->
// =====================================================================================
//  CheckFaceForBlocklight
//      Returns true if the passed face is facetype blocklight
// =====================================================================================
// Not yet implemented - just altered copypasta here
/*bool            CheckFaceForBlocklight(const face_t* const f)
{
    texinfo_t*      info;
    miptex_t*       miptex;
    int             ofs;

    info = &g_texinfo[f->texturenum];
    ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex];
    miptex = (miptex_t*)(&g_dtexdata[ofs]);

    if (!strcasecmp(miptex->name, "blocklight"))
        return true;
    else
        return false;
}*/
// <-- SILENCER

#ifdef ZHLT_DETAIL
// =====================================================================================
//  CheckFaceForDetail
//      Returns true if the passed face is part of a detail brush
// =====================================================================================
bool            CheckFaceForDetail(const face_t* const f)
{
    if (f->contents == CONTENTS_DETAIL)
    {
        //Log("CheckFaceForDetail:: got a detail face");
        return true;
    }

    return false;
}
#endif

// =====================================================================================
//  CheckFaceForHint
//      Returns true if the passed face is facetype hint
// =====================================================================================
bool            CheckFaceForHint(const face_t* const f)
{
    texinfo_t*      info;
    miptex_t*       miptex;
    int             ofs;

    info = &g_texinfo[f->texturenum];
    ofs = ((dmiptexlump_t *)g_dtexdata)->dataofs[info->miptex];
    miptex = (miptex_t *)(&g_dtexdata[ofs]);

    if (!strcasecmp(miptex->name, "hint"))
    {
        return true;
    }
    else
    {
        return false;
    }
}

// =====================================================================================
//  CheckFaceForSkipt
//      Returns true if the passed face is facetype skip
// =====================================================================================
bool            CheckFaceForSkip(const face_t* const f)
{
    texinfo_t*      info;
    miptex_t*       miptex;
    int             ofs;

    info = &g_texinfo[f->texturenum];
    ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex];
    miptex = (miptex_t*)(&g_dtexdata[ofs]);

    if (!strcasecmp(miptex->name, "skip"))
    {
        return true;
    }
    else
    {
        return false;
    }
}

// =====================================================================================
//  SetFaceType
// =====================================================================================
static          facestyle_e SetFaceType(face_t* f)
{
    if (CheckFaceForHint(f))
    {
        f->facestyle = face_hint;
    }
    else if (CheckFaceForSkip(f))
    {
        f->facestyle = face_skip;
    }
#ifdef ZHLT_NULLTEX         // AJM
    else if (CheckFaceForNull(f))
    {
        f->facestyle = face_null;
    }
#endif
	// SILENCER --> Not yet implemented - just altered copypasta here
	/*
	else if(CheckFaceForBlocklight(f))
	{
		f->facestyle = face_blocklight;
	}
	*/
	// <-- SILENCER

// =====================================================================================
//Cpt_Andrew - Env_Sky Check
// =====================================================================================
   //else if (CheckFaceForUTSky(f))
	else if (CheckFaceForEnv_Sky(f))
    {
        f->facestyle = face_null;
    }
// =====================================================================================


#ifdef ZHLT_DETAIL
    else if (CheckFaceForDetail(f))
    {
        //Log("SetFaceType::detail face\n");
        f->facestyle = face_detail;
    }
#endif
    else
    {
        f->facestyle = face_normal;
    }
    return f->facestyle;
}

// =====================================================================================
//  ReadSurfs
// =====================================================================================
static surfchain_t* ReadSurfs(FILE* file)
{
    int             r;
    int             planenum, g_texinfo, contents, numpoints;
    face_t*         f;
    int             i;
    double          v[3];
    int             line = 0;

    // read in the polygons
    while (1)
    {
        line++;
        r = fscanf(file, "%i %i %i %i\n", &planenum, &g_texinfo, &contents, &numpoints);
        if (r == 0 || r == -1)
        {
            return NULL;
        }
        if (planenum == -1)                                // end of model
        {
            break;
        }
        if (r != 4)
        {
            Error("ReadSurfs (line %i): scanf failure", line);
        }
        if (numpoints > MAXPOINTS)
        {
            Error("ReadSurfs (line %i): %i > MAXPOINTS\nThis is caused by a face with too many verticies (typically found on end-caps of high-poly cylinders)\n", line, numpoints);
        }
        if (planenum > g_numplanes)
        {
            Error("ReadSurfs (line %i): %i > g_numplanes\n", line, planenum);
        }
        if (g_texinfo > g_numtexinfo)
        {
            Error("ReadSurfs (line %i): %i > g_numtexinfo", line, g_texinfo);
        }

        if (!strcasecmp(GetTextureByNumber(g_texinfo), "skip"))
        {
            Verbose("ReadSurfs (line %i): skipping a surface", line);

            for (i = 0; i < numpoints; i++)
            {
                line++;
                //Verbose("skipping line %d", line);
                r = fscanf(file, "%lf %lf %lf\n", &v[0], &v[1], &v[2]);
                if (r != 3)
                {
                    Error("::ReadSurfs (face_skip), fscanf of points failed at line %i", line);
                }
            }
            fscanf(file, "\n");
            continue;
        }

        f = AllocFace();
        f->planenum = planenum;
        f->texturenum = g_texinfo;
        f->contents = contents;
        f->numpoints = numpoints;
        f->next = validfaces[planenum];
        validfaces[planenum] = f;

        SetFaceType(f);

        for (i = 0; i < f->numpoints; i++)
        {
            line++;
            r = fscanf(file, "%lf %lf %lf\n", &v[0], &v[1], &v[2]);
            if (r != 3)
            {
                Error("::ReadSurfs (face_normal), fscanf of points failed at line %i", line);
            }
            VectorCopy(v, f->pts[i]);
        }
        fscanf(file, "\n");
    }

    return SurflistFromValidFaces();
}


#ifdef HLBSP_THREADS// AJM
// =====================================================================================
//  ProcessModelThreaded
// time to compl
// =====================================================================================
void            ProcessModel(int modelnum)
{
    surfchain_t*    surfs;
    node_t*         nodes;
    dmodel_t*       model;
    int             startleafs;

    surfs = ReadSurfs(polyfiles[0]);

    if (!surfs)
        return; // all models are done

    hlassume(g_nummodels < MAX_MAP_MODELS, assume_MAX_MAP_MODELS);

    startleafs = g_numleafs;
    int modnum = g_nummodels;
    model = &g_dmodels[modnum];
    g_nummodels++;

//    Log("ProcessModel: %i (%i f)\n", modnum, model->numfaces);

    VectorCopy(surfs->mins, model->mins);
    VectorCopy(surfs->maxs, model->maxs);

    // SolidBSP generates a node tree
    nodes = SolidBSP(surfs);

    // build all the portals in the bsp tree
    // some portals are solid polygons, and some are paths to other leafs
    if (g_nummodels == 1 && !g_nofill)                       // assume non-world bmodels are simple
    {
        nodes = FillOutside(nodes, (g_bLeaked != true), 0);                  // make a leakfile if bad
    }

    FreePortals(nodes);

    // fix tjunctions
    tjunc(nodes);

    MakeFaceEdges();

    // emit the faces for the bsp file
    model->headnode[0] = g_numnodes;
    model->firstface = g_numfaces;
    WriteDrawNodes(nodes);
    model->numfaces = g_numfaces - model->firstface;
    model->visleafs = g_numleafs - startleafs;

    if (g_noclip)
    {
        return true;
    }

    // the clipping hulls are simpler
    for (g_hullnum = 1; g_hullnum < NUM_HULLS; g_hullnum++)
    {
        surfs = ReadSurfs(polyfiles[g_hullnum]);
        nodes = SolidBSP(surfs);
        if (g_nummodels == 1 && !g_nofill)                   // assume non-world bmodels are simple
        {
            nodes = FillOutside(nodes, (g_bLeaked != true), g_hullnum);
        }
        FreePortals(nodes);
        model->headnode[g_hullnum] = g_numclipnodes;
        WriteClipNodes(nodes);
    }

    return true;
}

#else
// =====================================================================================
//  ProcessModel
// =====================================================================================
static bool     ProcessModel()
{
    surfchain_t*    surfs;
    node_t*         nodes;
    dmodel_t*       model;
    int             startleafs;

    surfs = ReadSurfs(polyfiles[0]);

    if (!surfs)
        return false;                                      // all models are done

    hlassume(g_nummodels < MAX_MAP_MODELS, assume_MAX_MAP_MODELS);

    startleafs = g_numleafs;
    int modnum = g_nummodels;
    model = &g_dmodels[modnum];
    g_nummodels++;

//    Log("ProcessModel: %i (%i f)\n", modnum, model->numfaces);

    VectorCopy(surfs->mins, model->mins);
    VectorCopy(surfs->maxs, model->maxs);

    // SolidBSP generates a node tree
    nodes = SolidBSP(surfs,modnum==0);

    // build all the portals in the bsp tree
    // some portals are solid polygons, and some are paths to other leafs
    if (g_nummodels == 1 && !g_nofill)                       // assume non-world bmodels are simple
    {
        nodes = FillOutside(nodes, (g_bLeaked != true), 0);                  // make a leakfile if bad
    }

    FreePortals(nodes);

    // fix tjunctions
    tjunc(nodes);

    MakeFaceEdges();

    // emit the faces for the bsp file
    model->headnode[0] = g_numnodes;
    model->firstface = g_numfaces;
    WriteDrawNodes(nodes);
    model->numfaces = g_numfaces - model->firstface;
    model->visleafs = g_numleafs - startleafs;

    if (g_noclip)
    {
		/*
			KGP 12/31/03 - store empty content type in headnode pointers to signify
			lack of clipping information in a way that doesn't crash the half-life
			engine at runtime.
		*/
		model->headnode[1] = CONTENTS_EMPTY;
		model->headnode[2] = CONTENTS_EMPTY;
		model->headnode[3] = CONTENTS_EMPTY;
        return true;
    }

    // the clipping hulls are simpler
    for (g_hullnum = 1; g_hullnum < NUM_HULLS; g_hullnum++)
    {
        surfs = ReadSurfs(polyfiles[g_hullnum]);
        nodes = SolidBSP(surfs,modnum==0);
        if (g_nummodels == 1 && !g_nofill)                   // assume non-world bmodels are simple
        {
            nodes = FillOutside(nodes, (g_bLeaked != true), g_hullnum);
        }
        FreePortals(nodes);
		/*
			KGP 12/31/03 - need to test that the head clip node isn't empty; if it is
			we need to set model->headnode equal to the content type of the head, or create
			a trivial single-node case where the content type is the same for both leaves
			if setting the content type is invalid.
		*/
		if(nodes->planenum == -1) //empty!
		{
			model->headnode[g_hullnum] = nodes->contents;
		}
		else
		{
	        model->headnode[g_hullnum] = g_numclipnodes;
		    WriteClipNodes(nodes);
		}
    }

    return true;
}
#endif

// =====================================================================================
//  Usage
// =====================================================================================
static void     Usage()
{
    Banner();

    Log("\n-= %s Options =-\n\n", g_Program);
    Log("    -leakonly      : Run BSP only enough to check for LEAKs\n");
    Log("    -subdivide #   : Sets the face subdivide size\n");
    Log("    -maxnodesize # : Sets the maximum portal node size\n\n");
    Log("    -notjunc       : Don't break edges on t-junctions     (not for final runs)\n");
    Log("    -noclip        : Don't process the clipping hull      (not for final runs)\n");
    Log("    -nofill        : Don't fill outside (will mask LEAKs) (not for final runs)\n");
	Log("	 -noopt         : Don't optimize planes on BSP write   (not for final runs)\n\n");
    Log("    -texdata #     : Alter maximum texture memory limit (in kb)\n");
    Log("    -lightdata #   : Alter maximum lighting memory limit (in kb)\n");
    Log("    -chart         : display bsp statitics\n");
    Log("    -low | -high   : run program an altered priority level\n");
    Log("    -nolog         : don't generate the compile logfiles\n");
    Log("    -threads #     : manually specify the number of threads to run\n");
	Log("    -noconcolors   : Disable Windows console text coloring\n");
#ifdef SYSTEM_WIN32
    Log("    -estimate      : display estimated time during compile\n");
#endif
#ifdef ZHLT_PROGRESSFILE // AJM
    Log("    -progressfile path  : specify the path to a file for progress estimate output\n");
#endif
#ifdef SYSTEM_POSIX
    Log("    -noestimate    : do not display continuous compile time estimates\n");
#endif

#ifdef ZHLT_NULLTEX         // AJM
    Log("    -nonulltex     : Don't strip NULL faces\n");
#endif

#ifdef ZHLT_DETAIL // AJM
    Log("    -nodetail      : don't handle detail brushes\n");
#endif

    Log("    -verbose       : compile with verbose messages\n");
    Log("    -noinfo        : Do not show tool configuration information\n");
    Log("    -dev #         : compile with developer message\n\n");
    Log("    mapfile        : The mapfile to compile\n\n");

    exit(1);
}

// =====================================================================================
//  Settings
// =====================================================================================
static void     Settings()
{
    char*           tmp;

    if (!g_info)
        return;

    Log("\nCurrent %s Settings\n", g_Program);
    Log("Name               |   Setting   |  Default\n" "-------------------|-------------|-------------------------\n");

    // ZHLT Common Settings
    if (DEFAULT_NUMTHREADS == -1)
    {
        Log("threads             [ %9d ] [    Varies ]\n", g_numthreads);
    }
    else
    {
        Log("threads             [ %9d ] [ %9d ]\n", g_numthreads, DEFAULT_NUMTHREADS);
    }

    Log("verbose             [ %9s ] [ %9s ]\n", g_verbose ? "on" : "off", DEFAULT_VERBOSE ? "on" : "off");
    Log("log                 [ %9s ] [ %9s ]\n", g_log ? "on" : "off", DEFAULT_LOG ? "on" : "off");
    Log("developer           [ %9d ] [ %9d ]\n", g_developer, DEFAULT_DEVELOPER);
    Log("chart               [ %9s ] [ %9s ]\n", g_chart ? "on" : "off", DEFAULT_CHART ? "on" : "off");
    Log("estimate            [ %9s ] [ %9s ]\n", g_estimate ? "on" : "off", DEFAULT_ESTIMATE ? "on" : "off");
    Log("max texture memory  [ %9d ] [ %9d ]\n", g_max_map_miptex, DEFAULT_MAX_MAP_MIPTEX);

    switch (g_threadpriority)
    {
    case eThreadPriorityNormal:
    default:
        tmp = "Normal";
        break;
    case eThreadPriorityLow:
        tmp = "Low";
        break;
    case eThreadPriorityHigh:
        tmp = "High";
        break;
    }
    Log("priority            [ %9s ] [ %9s ]\n", tmp, "Normal");
    Log("\n");

    // HLBSP Specific Settings
    Log("noclip              [ %9s ] [ %9s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off");
    Log("nofill              [ %9s ] [ %9s ]\n", g_nofill ? "on" : "off", DEFAULT_NOFILL ? "on" : "off");
	Log("noopt               [ %9s ] [ %9s ]\n", g_noopt ? "on" : "off", DEFAULT_NOOPT ? "on" : "off");
#ifdef ZHLT_NULLTEX // AJM
    Log("null tex. stripping [ %9s ] [ %9s ]\n", g_bUseNullTex ? "on" : "off", DEFAULT_NULLTEX ? "on" : "off" );
#endif
#ifdef ZHLT_DETAIL // AJM
    Log("detail brushes      [ %9s ] [ %9s ]\n", g_bDetailBrushes ? "on" : "off", DEFAULT_DETAIL ? "on" : "off" );
#endif
    Log("notjunc             [ %9s ] [ %9s ]\n", g_notjunc ? "on" : "off", DEFAULT_NOTJUNC ? "on" : "off");
    Log("subdivide size      [ %9d ] [ %9d ] (Min %d) (Max %d)\n",
        g_subdivide_size, DEFAULT_SUBDIVIDE_SIZE, MIN_SUBDIVIDE_SIZE, MAX_SUBDIVIDE_SIZE);
    Log("max node size       [ %9d ] [ %9d ] (Min %d) (Max %d)\n",
        g_maxnode_size, DEFAULT_MAXNODE_SIZE, MIN_MAXNODE_SIZE, MAX_MAXNODE_SIZE);

    Log("\n\n");
}

// =====================================================================================
//  ProcessFile
// =====================================================================================
static void     ProcessFile(const char* const filename)
{
    int             i;
    char            name[_MAX_PATH];

    // delete existing files
    safe_snprintf(g_portfilename, _MAX_PATH, "%s.prt", filename);
    unlink(g_portfilename);

    safe_snprintf(g_pointfilename, _MAX_PATH, "%s.pts", filename);
    unlink(g_pointfilename);

    safe_snprintf(g_linefilename, _MAX_PATH, "%s.lin", filename);
    unlink(g_linefilename);

    // open the hull files
    for (i = 0; i < NUM_HULLS; i++)
    {
                   //mapname.p[0-3]
        sprintf(name, "%s.p%i", filename, i);
        polyfiles[i] = fopen(name, "r");

        if (!polyfiles[i])
            Error("Can't open %s", name);
    }

    // load the output of csg
    safe_snprintf(g_bspfilename, _MAX_PATH, "%s.bsp", filename);
    LoadBSPFile(g_bspfilename);
    ParseEntities();

    Settings(); // AJM: moved here due to info_compile_parameters entity

    // init the tables to be shared by all models
    BeginBSPFile();

#ifdef HLBSP_THREADS // AJM
    NamedRunThreadsOnIndividual(nummodels, g_estimate, ProcessModel);
#else
    // process each model individually
    while (ProcessModel())
        ;
#endif

	// SILENCER -->
	// Just warn the mapper, don't abort the compilation.
	// It is up to him whether to try this or not.
	if(g_numclipnodes > 32767) {
		Warning("%i out of 32767 clipnodes maximum.\n"
			    "         Your compiled map will have unsolid brushes, through which\n"
				"         the player can fall outside world. Try to make use of the clip\n"
				"         texture to simplify the clipping of complex structures and details,\n"
				"         if you did not already.", g_numclipnodes);
	}
	// <-- SILENCER

    // write the updated bsp file out

    FinishBSPFile();
}

// =====================================================================================
//  main
// =====================================================================================
int             main(const int argc, char** argv)
{
    int             i;
    double          start, end;
    const char*     mapname_from_arg = NULL;

    g_Program = "hlbsp";

    // if we dont have any command line argvars, print out usage and die
    if (argc == 1)
        Usage();

    // check command line args
    for (i = 1; i < argc; i++)
    {
        if (!strcasecmp(argv[i], "-threads"))
        {
            if (i < argc)
            {
                int             g_numthreads = atoi(argv[++i]);

                if (g_numthreads < 1)
                {
                    Log("Expected value of at least 1 for '-threads'\n");
                    Usage();
                }
            }
            else
            {
                Usage();
            }
        }
        else if (!strcasecmp(argv[i], "-notjunc"))
        {
            g_notjunc = true;
        }
        else if (!strcasecmp(argv[i], "-noclip"))
        {
            g_noclip = true;
        }
        else if (!strcasecmp(argv[i], "-nofill"))
        {
            g_nofill = true;
        }

#ifdef SYSTEM_WIN32
        else if (!strcasecmp(argv[i], "-estimate"))
        {
            g_estimate = true;
        }
#endif

#ifdef SYSTEM_POSIX
        else if (!strcasecmp(argv[i], "-noestimate"))
        {
            g_estimate = false;
        }
#endif

#ifdef ZHLT_NETVIS
        else if (!strcasecmp(argv[i], "-client"))
        {
            if (i < argc)
            {
                g_clientid = atoi(argv[++i]);
            }
            else
            {
                Usage();
            }
        }
#endif

#ifdef ZHLT_PROGRESSFILE // AJM
        else if (!strcasecmp(argv[i], "-progressfile"))
        {
            if (i < argc)
            {
                g_progressfile = argv[++i];
            }
            else
            {
            	Log("Error: -progressfile: expected path to progress file following parameter\n");
                Usage();
            }
        }
#endif

        else if (!strcasecmp(argv[i], "-dev"))
        {
            if (i < argc)
            {
                g_developer = (developer_level_t)atoi(argv[++i]);
            }
            else
            {
                Usage();
            }
        }
        else if (!strcasecmp(argv[i], "-verbose"))
        {
            g_verbose = true;
        }
        else if (!strcasecmp(argv[i], "-noinfo"))
        {
            g_info = false;
        }
        else if (!strcasecmp(argv[i], "-leakonly"))
        {
            g_bLeakOnly = true;
        }
        else if (!strcasecmp(argv[i], "-chart"))
        {
            g_chart = true;
        }
        else if (!strcasecmp(argv[i], "-low"))
        {
            g_threadpriority = eThreadPriorityLow;
        }
        else if (!strcasecmp(argv[i], "-high"))
        {
            g_threadpriority = eThreadPriorityHigh;
        }
        else if (!strcasecmp(argv[i], "-nolog"))
        {
            g_log = false;
        }

#ifdef ZHLT_NULLTEX // AJM
        else if (!strcasecmp(argv[i], "-nonulltex"))
        {
            g_bUseNullTex = false;
        }
#endif

#ifdef ZHLT_DETAIL // AJM
        else if (!strcasecmp(argv[i], "-nodetail"))
        {
            g_bDetailBrushes = false;
        }
#endif
		else if (!strcasecmp(argv[i], "-noopt"))
		{
			g_noopt = true;
		}
        else if (!strcasecmp(argv[i], "-subdivide"))
        {
            if (i < argc)
            {
                g_subdivide_size = atoi(argv[++i]);
                if (g_subdivide_size > MAX_SUBDIVIDE_SIZE)
                {
                    Warning
                        ("Maximum value for subdivide size is %i, '-subdivide %i' ignored",
                         MAX_SUBDIVIDE_SIZE, g_subdivide_size);
                    g_subdivide_size = MAX_SUBDIVIDE_SIZE;
                }
                else if (g_subdivide_size < MIN_SUBDIVIDE_SIZE)
                {
                    Warning
                        ("Mininum value for subdivide size is %i, '-subdivide %i' ignored",
                         MIN_SUBDIVIDE_SIZE, g_subdivide_size);
                    g_subdivide_size = MAX_SUBDIVIDE_SIZE;
                }
            }
            else
            {
                Usage();
            }
        }
        else if (!strcasecmp(argv[i], "-maxnodesize"))
        {
            if (i < argc)
            {
                g_maxnode_size = atoi(argv[++i]);
                if (g_maxnode_size > MAX_MAXNODE_SIZE)
                {
                    Warning
                        ("Maximum value for max node size is %i, '-maxnodesize %i' ignored",
                         MAX_MAXNODE_SIZE, g_maxnode_size);
                    g_maxnode_size = MAX_MAXNODE_SIZE;
                }
                else if (g_maxnode_size < MIN_MAXNODE_SIZE)
                {
                    Warning
                        ("Mininimum value for max node size is %i, '-maxnodesize %i' ignored",
                         MIN_MAXNODE_SIZE, g_maxnode_size);
                    g_maxnode_size = MAX_MAXNODE_SIZE;
                }
            }
            else
            {
                Usage();
            }
        }
        else if (!strcasecmp(argv[i], "-texdata"))
        {
            if (i < argc)
            {
                int             x = atoi(argv[++i]) * 1024;

                if (x > g_max_map_miptex)
                {
                    g_max_map_miptex = x;
                }
            }
            else
            {
                Usage();
            }
        }
        else if (!strcasecmp(argv[i], "-lightdata"))
        {
            if (i < argc)
            {
                int             x = atoi(argv[++i]) * 1024;

                if (x > g_max_map_lightdata)
                {
                    g_max_map_lightdata = x;
                }
            }
            else
            {
                Usage();
            }
        }
		// SILENCER -->
		else if (!strcasecmp(argv[i], "-noconcolors"))
        {
            g_noConColors = true;
        }
		// <-- SILENCER
        else if (argv[i][0] == '-')
        {
            Log("Unknown option \"%s\"\n", argv[i]);
            Usage();
        }
        else if (!mapname_from_arg)
        {
            mapname_from_arg = argv[i];
        }
        else
        {
            Log("Unknown option \"%s\"\n", argv[i]);
            Usage();
        }
    }

    if (!mapname_from_arg)
    {
        Log("No mapfile specified\n");
        Usage();
    }

    safe_strncpy(g_Mapname, mapname_from_arg, _MAX_PATH);
    FlipSlashes(g_Mapname);
    StripExtension(g_Mapname);
    OpenLog(g_clientid);
    atexit(CloseLog);
    ThreadSetDefault();
    ThreadSetPriority(g_threadpriority);
    LogStart(argc, argv);

    CheckForErrorLog();

    dtexdata_init();
    atexit(dtexdata_free);
    //Settings();
    // END INIT

    // Load the .void files for allowable entities in the void
    {
        char            g_source[_MAX_PATH];
        char            strSystemEntitiesVoidFile[_MAX_PATH];
        char            strMapEntitiesVoidFile[_MAX_PATH];

        safe_strncpy(g_source, mapname_from_arg, _MAX_PATH);
        StripExtension(g_source);

        // try looking in the current directory
        safe_strncpy(strSystemEntitiesVoidFile, ENTITIES_VOID, _MAX_PATH);
        if (!q_exists(strSystemEntitiesVoidFile))
        {
            char tmp[_MAX_PATH];
            // try looking in the directory we were run from
#ifdef SYSTEM_WIN32
            GetModuleFileName(NULL, tmp, _MAX_PATH);
#else
            safe_strncpy(tmp, argv[0], _MAX_PATH);
#endif
            ExtractFilePath(tmp, strSystemEntitiesVoidFile);
            safe_strncat(strSystemEntitiesVoidFile, ENTITIES_VOID, _MAX_PATH);
        }

        // Set the optional level specific lights filename
        safe_strncpy(strMapEntitiesVoidFile, g_source, _MAX_PATH);
        DefaultExtension(strMapEntitiesVoidFile, ENTITIES_VOID_EXT);

        LoadAllowableOutsideList(strSystemEntitiesVoidFile);    // default entities.void
        if (*strMapEntitiesVoidFile)
        {
            LoadAllowableOutsideList(strMapEntitiesVoidFile);   // automatic mapname.void
        }
    }

    // BEGIN BSP
    start = I_FloatTime();

    ProcessFile(g_Mapname);

    end = I_FloatTime();
    LogTimeElapsed(end - start);
    // END BSP

    FreeAllowableOutsideList();

    return 0;
}
