#include "qbsp.h"
int32_t c_nodes;
int32_t c_nonvis;
int32_t c_active_brushes;
#define PSIDE_FRONT  1
#define PSIDE_BACK   2
#define PSIDE_BOTH   (PSIDE_FRONT | PSIDE_BACK)
#define PSIDE_FACING 4
int32_t BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane); 
void FindBrushInTree(node_t *node, int32_t brushnum) {
    bspbrush_t *b;
    if (node->planenum == PLANENUM_LEAF) {
        for (b = node->brushlist; b; b = b->next)
            if (b->original->brushnum == brushnum)
                return;
    }
    FindBrushInTree(node->children[0], brushnum);
    FindBrushInTree(node->children[1], brushnum);
}
void BoundBrush(bspbrush_t *brush) {
    int32_t i, j;
    winding_t *w;
    ClearBounds(brush->mins, brush->maxs);
    for (i = 0; i < brush->numsides; i++) {
        w = brush->sides[i].winding;
        if (!w)
            continue;
        for (j = 0; j < w->numpoints; j++)
            AddPointToBounds(w->p[j], brush->mins, brush->maxs);
    }
}
void CreateBrushWindings(bspbrush_t *brush) {
    int32_t i, j;
    winding_t *w;
    side_t *side;
    plane_t *plane;
    for (i = 0; i < brush->numsides; i++) {
        side  = &brush->sides[i];
        plane = &mapplanes[side->planenum];
        w     = BaseWindingForPlane(plane->normal, plane->dist);
        for (j = 0; j < brush->numsides && w; j++) {
            if (i == j)
                continue;
            if (brush->sides[j].bevel)
                continue;
            plane = &mapplanes[brush->sides[j].planenum ^ 1];
            ChopWindingInPlace(&w, plane->normal, plane->dist, 0);
        }
        side->winding = w;
    }
    BoundBrush(brush);
}
bspbrush_t *BrushFromBounds(vec3_t mins, vec3_t maxs) {
    bspbrush_t *b;
    int32_t i;
    vec3_t normal;
    vec_t dist;
    b           = AllocBrush(6);
    b->numsides = 6;
    for (i = 0; i < 3; i++) {
        VectorClear(normal);
        normal[i]                = 1;
        dist                     = maxs[i];
        b->sides[i].planenum     = FindFloatPlane(normal, dist, 0);
        normal[i]                = -1;
        dist                     = -mins[i];
        b->sides[3 + i].planenum = FindFloatPlane(normal, dist, 0);
    }
    CreateBrushWindings(b);
    return b;
}
vec_t BrushVolume(bspbrush_t *brush) {
    int32_t i;
    winding_t *w;
    vec3_t corner;
    vec_t d, area, volume;
    plane_t *plane;
    if (!brush)
        return 0;
    
    w = NULL;
    for (i = 0; i < brush->numsides; i++) {
        w = brush->sides[i].winding;
        if (w)
            break;
    }
    if (!w)
        return 0;
    VectorCopy(w->p[0], corner);
    
    volume = 0;
    for (; i < brush->numsides; i++) {
        w = brush->sides[i].winding;
        if (!w)
            continue;
        plane = &mapplanes[brush->sides[i].planenum];
        d     = -(DotProduct(corner, plane->normal) - plane->dist);
        area  = WindingArea(w);
        volume += d * area;
    }
    volume /= 3;
    return volume;
}
int32_t CountBrushList(bspbrush_t *brushes) {
    int32_t c;
    c = 0;
    for (; brushes; brushes = brushes->next)
        c++;
    return c;
}
tree_t *AllocTree(void) {
    tree_t *tree;
    tree = malloc(sizeof(*tree));
    memset(tree, 0, sizeof(*tree));
    ClearBounds(tree->mins, tree->maxs);
    return tree;
}
node_t *AllocNode(void) {
    node_t *node;
    node = malloc(sizeof(*node));
    memset(node, 0, sizeof(*node));
    return node;
}
bspbrush_t *AllocBrush(int32_t numsides) {
    bspbrush_t *bb;
    int32_t c;
    c  = (intptr_t) & (((bspbrush_t *)0)->sides[numsides]);
    bb = malloc(c);
    memset(bb, 0, c);
    if (numthreads == 1)
        c_active_brushes++;
    return bb;
}
void FreeBrush(bspbrush_t *brushes) {
    int32_t i;
    for (i = 0; i < brushes->numsides; i++)
        if (brushes->sides[i].winding)
            FreeWinding(brushes->sides[i].winding);
    free(brushes);
    if (numthreads == 1)
        c_active_brushes--;
}
void FreeBrushList(bspbrush_t *brushes) {
    bspbrush_t *next;
    for (; brushes; brushes = next) {
        next = brushes->next;
        FreeBrush(brushes);
    }
}
bspbrush_t *CopyBrush(bspbrush_t *brush) {
    bspbrush_t *newbrush;
    int32_t size;
    int32_t i;
    size     = (intptr_t) & (((bspbrush_t *)0)->sides[brush->numsides]);
    newbrush = AllocBrush(brush->numsides);
    memcpy(newbrush, brush, size);
    for (i = 0; i < brush->numsides; i++) {
        if (brush->sides[i].winding)
            newbrush->sides[i].winding = CopyWinding(brush->sides[i].winding);
    }
    return newbrush;
}
node_t *PointInLeaf(node_t *node, vec3_t point) {
    vec_t d;
    plane_t *plane;
    while (node->planenum != PLANENUM_LEAF) {
        plane = &mapplanes[node->planenum];
        d     = DotProduct(point, plane->normal) - plane->dist;
        if (d > 0)
            node = node->children[0];
        else
            node = node->children[1];
    }
    return node;
}
int32_t BoxOnPlaneSide(vec3_t mins, vec3_t maxs, plane_t *plane) {
    int32_t side;
    int32_t i;
    vec3_t corners[2];
    vec_t dist1, dist2;
        if (plane->type < 3) {
        side = 0;
        if (maxs[plane->type] > plane->dist + PLANESIDE_EPSILON)
            side |= PSIDE_FRONT;
        if (mins[plane->type] < plane->dist - PLANESIDE_EPSILON)
            side |= PSIDE_BACK;
        return side;
    }
    
    for (i = 0; i < 3; i++) {
        if (plane->normal[i] < 0) {
            corners[0][i] = mins[i];
            corners[1][i] = maxs[i];
        } else {
            corners[1][i] = mins[i];
            corners[0][i] = maxs[i];
        }
    }
    dist1 = DotProduct(plane->normal, corners[0]) - plane->dist;
    dist2 = DotProduct(plane->normal, corners[1]) - plane->dist;
    side  = 0;
    if (dist1 >= PLANESIDE_EPSILON)
        side = PSIDE_FRONT;
    if (dist2 < PLANESIDE_EPSILON)
        side |= PSIDE_BACK;
    return side;
}
int32_t QuickTestBrushToPlanenum(bspbrush_t *brush, int32_t planenum, int32_t *numsplits) {
    int32_t i, num;
    plane_t *plane;
    int32_t s;
    *numsplits = 0;
            for (i = 0; i < brush->numsides; i++) {
        num = brush->sides[i].planenum;
        if (num >= MAX_MAP_PLANES_QBSP)
            Error("bad planenum");
        if (num == planenum)
            return PSIDE_BACK | PSIDE_FACING;
        if (num == (planenum ^ 1))
            return PSIDE_FRONT | PSIDE_FACING;
    }
        plane = &mapplanes[planenum];
    s     = BoxOnPlaneSide(brush->mins, brush->maxs, plane);
        if (s == PSIDE_BOTH) {
        *numsplits += 3;
    }
    return s;
}
int32_t TestBrushToPlanenum(bspbrush_t *brush, int32_t planenum,
                            int32_t *numsplits, qboolean *hintsplit, qboolean *detailsplit, int32_t *epsilonbrush) {
    int32_t i, j, num;
    plane_t *plane;
    int32_t s;
    winding_t *w;
    vec_t d, d_front, d_back;
    int32_t front, back;
    *numsplits = 0;
    *hintsplit = false;
            for (i = 0; i < brush->numsides; i++) {
        num = brush->sides[i].planenum;
        if (num >= MAX_MAP_PLANES_QBSP)
            Error("bad planenum");
        if (num == planenum)
            return PSIDE_BACK | PSIDE_FACING;
        if (num == (planenum ^ 1))
            return PSIDE_FRONT | PSIDE_FACING;
    }
        plane = &mapplanes[planenum];
    s     = BoxOnPlaneSide(brush->mins, brush->maxs, plane);
    if (s != PSIDE_BOTH)
        return s;
        d_front = d_back = 0;
    for (i = 0; i < brush->numsides; i++) {
        if (brush->sides[i].texinfo == TEXINFO_NODE)
            continue;         if (!brush->sides[i].visible)
            continue;         w = brush->sides[i].winding;
        if (!w)
            continue;
        front = back = 0;
        for (j = 0; j < w->numpoints; j++) {
            d = DotProduct(w->p[j], plane->normal) - plane->dist;
            if (d > d_front)
                d_front = d;
            if (d < d_back)
                d_back = d;
            if (d > ON_EPSILON)                 front = 1;
            if (d < -ON_EPSILON)                 back = 1;
        }
        if (front && back) {
            if (!(brush->sides[i].surf & SURF_SKIP)) {
                (*numsplits)++;
                if (brush->sides[i].surf & SURF_HINT)
                    *hintsplit = true;
                if (brush->sides[i].contents & CONTENTS_DETAIL)
                    *detailsplit = true;
            }
        }
    }
    if ((d_front > 0.0 && d_front < 1.0) || (d_back < 0.0 && d_back > -1.0))
        (*epsilonbrush)++;
#if 0#endif
    return s;
}
#define EDGE_LENGTH 0.2
qboolean WindingIsTiny(winding_t *w) {
#if 0#else
    int32_t i, j;
    vec_t len;
    vec3_t delta;
    int32_t edges;
    edges = 0;
    for (i = 0; i < w->numpoints; i++) {
        j = i == w->numpoints - 1 ? 0 : i + 1;
        VectorSubtract(w->p[j], w->p[i], delta);
        len = VectorLength(delta);
        if (len > EDGE_LENGTH) {
            if (++edges == 3)
                return false;
        }
    }
    return true;
#endif
}
qboolean WindingIsHuge(winding_t *w) {
    int32_t i, j;
    for (i = 0; i < w->numpoints; i++) {
        for (j = 0; j < 3; j++)
            if (w->p[i][j] < -2 * max_bounds || w->p[i][j] > 2 * max_bounds)
                return true;
    }
    return false;
}
void LeafNode(node_t *node, bspbrush_t *brushes) {
    bspbrush_t *b;
    int32_t i;
    node->planenum = PLANENUM_LEAF;
    node->contents = 0;
    for (b = brushes; b; b = b->next) {
                        if (b->original->contents & CONTENTS_SOLID) {
            for (i = 0; i < b->numsides; i++)
                if (b->sides[i].texinfo != TEXINFO_NODE)
                    break;
            if (i == b->numsides) {
                node->contents = CONTENTS_SOLID;
                break;
            }
        }
        node->contents |= b->original->contents;
    }
    node->brushlist = brushes;
}
void CheckPlaneAgainstParents(int32_t pnum, node_t *node, bspbrush_t *brush) {
    node_t *p;
    for (p = node->parent; p; p = p->parent) {
        if (p->planenum == pnum) {
            Error("Tried parent\n  Brush Bounds: %g %g %g -> %g %g %g\n",
                  brush->mins[0], brush->mins[1], brush->mins[2], brush->maxs[0], brush->maxs[1], brush->maxs[2]);
        }
    }
}
qboolean CheckPlaneAgainstVolume(int32_t pnum, node_t *node) {
    bspbrush_t *front, *back;
    qboolean good;
    SplitBrush(node->volume, pnum, &front, &back);
    good = (front && back);
    if (front)
        FreeBrush(front);
    if (back)
        FreeBrush(back);
    return good;
}
side_t *SelectSplitSide(bspbrush_t *brushes, node_t *node) {
    int32_t value, bestvalue;
    bspbrush_t *brush, *test;
    side_t *side, *bestside;
    int32_t i, j, pass, numpasses;
    int32_t pnum;
    int32_t s;
    int32_t front, back, both, facing, splits;
    int32_t bsplits;
    int32_t epsilonbrush;
    qboolean hintsplit, detailsplit;
    bestside  = NULL;
    bestvalue = -BOGUS_RANGE;
                    numpasses = 4;
    for (pass = 0; pass < numpasses; pass++) {
        for (brush = brushes; brush; brush = brush->next) {
            if ((pass & 1) && !(brush->original->contents & CONTENTS_DETAIL))
                continue;
            if (!(pass & 1) && (brush->original->contents & CONTENTS_DETAIL))
                continue;
            for (i = 0; i < brush->numsides; i++) {
                side = brush->sides + i;
                if (side->bevel)
                    continue;                 if (!side->winding)
                    continue;                 if (side->texinfo == TEXINFO_NODE)
                    continue;                 if (side->tested)
                    continue;                 if (side->surf & SURF_SKIP)
                    continue;                 if (side->visible ^ (pass < 2))
                    continue; 
                pnum = side->planenum;
                pnum &= ~1; 
                CheckPlaneAgainstParents(pnum, node, brush);
                if (!CheckPlaneAgainstVolume(pnum, node))
                    continue; 
                front        = 0;
                back         = 0;
                both         = 0;
                facing       = 0;
                splits       = 0;
                epsilonbrush = 0;
                for (test = brushes; test; test = test->next) {
                    s = TestBrushToPlanenum(test, pnum, &bsplits, &hintsplit, &detailsplit, &epsilonbrush);
                    splits += bsplits;
                    if (bsplits && (s & PSIDE_FACING))
                        Error("PSIDE_FACING with splits");
                    test->testside = s;
                                                            if (s & PSIDE_FACING) {
                        facing++;
                        for (j = 0; j < test->numsides; j++) {
                            if ((test->sides[j].planenum & ~1) == pnum)
                                test->sides[j].tested = true;
                        }
                    }
                    if (s & PSIDE_FRONT)
                        front++;
                    if (s & PSIDE_BACK)
                        back++;
                    if (s == PSIDE_BOTH)
                        both++;
                }
                
                value = 5 * facing - 5 * splits - abs(front - back);
                                                if (mapplanes[pnum].type < 3)
                    value += 5;                               value -= epsilonbrush * 1000; 
                                if ((hintsplit && !(side->surf & SURF_HINT)) && (!detailsplit || (side->contents & CONTENTS_DETAIL)))
                    value = -BOGUS_RANGE;
                                                                if (value > bestvalue) {
                    bestvalue = value;
                    bestside  = side;
                    for (test = brushes; test; test = test->next)
                        test->side = test->testside;
                }
            }
        }
                        if (bestside) {
            if (pass > 1) {
                if (numthreads == 1)
                    c_nonvis++;
            }
            if (pass > 0)
                node->detail_seperator = true;             break;
        }
    }
                for (brush = brushes; brush; brush = brush->next) {
        for (i = 0; i < brush->numsides; i++)
            brush->sides[i].tested = false;
    }
    return bestside;
}
int32_t BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane) {
    int32_t i, j;
    winding_t *w;
    vec_t d, max;
    int32_t side;
    max  = 0;
    side = PSIDE_FRONT;
    for (i = 0; i < brush->numsides; i++) {
        w = brush->sides[i].winding;
        if (!w)
            continue;
        for (j = 0; j < w->numpoints; j++) {
            d = DotProduct(w->p[j], plane->normal) - plane->dist;
            if (d > max) {
                max  = d;
                side = PSIDE_FRONT;
            }
            if (-d > max) {
                max  = -d;
                side = PSIDE_BACK;
            }
        }
    }
    return side;
}
void SplitBrush(bspbrush_t *brush, int32_t planenum,
                bspbrush_t **front, bspbrush_t **back) {
    bspbrush_t *b[2];
    int32_t i, j;
    winding_t *w, *cw[2], *midwinding;
    plane_t *plane, *plane2;
    side_t *s, *cs;
    vec_t d, d_front, d_back;
    *front = *back = NULL;
    plane          = &mapplanes[planenum];
        d_front = d_back = 0;
    for (i = 0; i < brush->numsides; i++) {
        w = brush->sides[i].winding;
        if (!w)
            continue;
        for (j = 0; j < w->numpoints; j++) {
            d = DotProduct(w->p[j], plane->normal) - plane->dist;
            if (d > 0 && d > d_front)
                d_front = d;
            if (d < 0 && d < d_back)
                d_back = d;
        }
    }
        if (d_front < ON_EPSILON)     {
                *back = CopyBrush(brush);
        return;
    }
    if (d_back > -ON_EPSILON)     {
                *front = CopyBrush(brush);
        return;
    }
    
    w = BaseWindingForPlane(plane->normal, plane->dist);
    for (i = 0; i < brush->numsides && w; i++) {
        plane2 = &mapplanes[brush->sides[i].planenum ^ 1];
        ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0);     }
    if (!w || WindingIsTiny(w)) {
                int32_t side;
        side = BrushMostlyOnSide(brush, plane);
        if (side == PSIDE_FRONT)
            *front = CopyBrush(brush);
        if (side == PSIDE_BACK)
            *back = CopyBrush(brush);
        return;
    }
    if (WindingIsHuge(w)) {
        qprintf("WARNING: huge winding\n");
    }
    midwinding = w;
    
    for (i = 0; i < 2; i++) {
        b[i]           = AllocBrush(brush->numsides + 1);
        b[i]->original = brush->original;
    }
    
    for (i = 0; i < brush->numsides; i++) {
        s = &brush->sides[i];
        w = s->winding;
        if (!w)
            continue;
        ClipWindingEpsilon(w, plane->normal, plane->dist,
                           0 , &cw[0], &cw[1]);         for (j = 0; j < 2; j++) {
            if (!cw[j])
                continue;
#if 0#endif
            cs = &b[j]->sides[b[j]->numsides];
            b[j]->numsides++;
            *cs         = *s;
                                                            cs->winding = cw[j];
            cs->tested  = false;
        }
    }
    
    for (i = 0; i < 2; i++) {
        BoundBrush(b[i]);
        for (j = 0; j < 3; j++) {
            if (b[i]->mins[j] < -max_bounds || b[i]->maxs[j] > max_bounds) {
                qprintf("bogus brush after clip\n");
                break;
            }
        }
        if (b[i]->numsides < 3 || j < 3) {
            FreeBrush(b[i]);
            b[i] = NULL;
        }
    }
    if (!(b[0] && b[1])) {
        if (!b[0] && !b[1])
            qprintf("split removed brush\n");
        else
            qprintf("split not on both sides\n");
        if (b[0]) {
            FreeBrush(b[0]);
            *front = CopyBrush(brush);
        }
        if (b[1]) {
            FreeBrush(b[1]);
            *back = CopyBrush(brush);
        }
        return;
    }
        for (i = 0; i < 2; i++) {
        cs = &b[i]->sides[b[i]->numsides];
        b[i]->numsides++;
        cs->planenum = planenum ^ i ^ 1;
        cs->texinfo  = TEXINFO_NODE;
        cs->visible  = false;
        cs->tested   = false;
        if (i == 0)
            cs->winding = CopyWinding(midwinding);
        else
            cs->winding = midwinding;
    }
    {
        vec_t v1;
        int32_t i;
        for (i = 0; i < 2; i++) {
            v1 = BrushVolume(b[i]);
            if (v1 < microvolume)             {
                FreeBrush(b[i]);
                b[i] = NULL;
                            }
        }
    }
    *front = b[0];
    *back  = b[1];
}
void SplitBrushList(bspbrush_t *brushes,
                    node_t *node, bspbrush_t **front, bspbrush_t **back) {
    bspbrush_t *brush, *newbrush, *newbrush2;
    side_t *side;
    int32_t sides;
    int32_t i;
    *front = *back = NULL;
    for (brush = brushes; brush; brush = brush->next) {
        sides = brush->side;
        if (sides == PSIDE_BOTH) {
                        SplitBrush(brush, node->planenum, &newbrush, &newbrush2);
            if (newbrush) {
                newbrush->next = *front;
                *front         = newbrush;
            }
            if (newbrush2) {
                newbrush2->next = *back;
                *back           = newbrush2;
            }
            continue;
        }
        newbrush = CopyBrush(brush);
                                if (sides & PSIDE_FACING) {
            for (i = 0; i < newbrush->numsides; i++) {
                side = newbrush->sides + i;
                if ((side->planenum & ~1) == node->planenum)
                    side->texinfo = TEXINFO_NODE;
            }
        }
        if (sides & PSIDE_FRONT) {
            newbrush->next = *front;
            *front         = newbrush;
            continue;
        }
        if (sides & PSIDE_BACK) {
            newbrush->next = *back;
            *back          = newbrush;
            continue;
        }
    }
}
node_t *BuildTree_r(node_t *node, bspbrush_t *brushes) {
    node_t *newnode;
    side_t *bestside;
    int32_t i;
    bspbrush_t *children[2];
    if (numthreads == 1)
        c_nodes++;
        bestside = SelectSplitSide(brushes, node);
    if (!bestside) {
                node->side     = NULL;
        node->planenum = -1;
        LeafNode(node, brushes);
        return node;
    }
        node->side     = bestside;
    node->planenum = bestside->planenum & ~1; 
    SplitBrushList(brushes, node, &children[0], &children[1]);
    FreeBrushList(brushes);
        for (i = 0; i < 2; i++) {
        newnode           = AllocNode();
        newnode->parent   = node;
        node->children[i] = newnode;
    }
    SplitBrush(node->volume, node->planenum, &node->children[0]->volume,
               &node->children[1]->volume);
        for (i = 0; i < 2; i++) {
        node->children[i] = BuildTree_r(node->children[i], children[i]);
    }
    return node;
}
tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) {
    node_t *node;
    bspbrush_t *b;
    int32_t c_faces, c_nonvisfaces;
    int32_t c_brushes;
    tree_t *tree;
    int32_t i;
    vec_t volume;
    qprintf("--- BrushBSP ---\n");
    tree          = AllocTree();
    c_faces       = 0;
    c_nonvisfaces = 0;
    c_brushes     = 0;
    for (b = brushlist; b; b = b->next) {
        c_brushes++;
        volume = BrushVolume(b);
        if (volume < microvolume) {
            printf("WARNING: Entity %i, Brush %i, Line %i: microbrush\n  Bounds: %g %g %g -> %g %g %g\n",
                   b->original->entitynum, b->original->brushnum, scriptline + 1,                    b->mins[0], b->mins[1], b->mins[2], b->maxs[0], b->maxs[1], b->maxs[2]);
        }
        for (i = 0; i < b->numsides; i++) {
            if (b->sides[i].bevel)
                continue;
            if (!b->sides[i].winding)
                continue;
            if (b->sides[i].texinfo == TEXINFO_NODE)
                continue;
            if (b->sides[i].visible)
                c_faces++;
            else
                c_nonvisfaces++;
        }
        AddPointToBounds(b->mins, tree->mins, tree->maxs);
        AddPointToBounds(b->maxs, tree->mins, tree->maxs);
    }
    qprintf("%5i brushes\n", c_brushes);
    qprintf("%5i visible faces\n", c_faces);
    qprintf("%5i nonvisible faces\n", c_nonvisfaces);
    c_nodes        = 0;
    c_nonvis       = 0;
    node           = AllocNode();
    node->volume   = BrushFromBounds(mins, maxs);
    tree->headnode = node;
    node           = BuildTree_r(node, brushlist);
    qprintf("%5i visible nodes\n", c_nodes / 2 - c_nonvis);
    qprintf("%5i nonvis nodes\n", c_nonvis);
    qprintf("%5i leafs\n", (c_nodes + 1) / 2);
#if 0#endif
    return tree;
}