#include "engine.h"

extern vec hitsurface;

bool BIH::triintersect(const mesh &m, int tidx, const vec &mo, const vec &mray, float maxdist, float &dist, int mode)
{
    const tri &t = m.tris[tidx];
    vec a = m.getpos(t.vert[0]), b = m.getpos(t.vert[1]).sub(a), c = m.getpos(t.vert[2]).sub(a),
        n = vec().cross(b, c), r = vec(a).sub(mo), e = vec().cross(r, mray);
    float det = mray.dot(n), v, w, f;
    if(det >= 0)
    {
        if(!(mode&RAY_SHADOW) && m.flags&MESH_CULLFACE) return false;
        v = e.dot(c);
        if(v < 0 || v > det) return false;
        w = -e.dot(b);
        if(w < 0 || v + w > det) return false;
        f = r.dot(n)*m.scale;
        if(f < 0 || f > maxdist*det || !det) return false;
    }
    else
    {
        v = e.dot(c);
        if(v > 0 || v < det) return false;
        w = -e.dot(b);
        if(w > 0 || v + w < det) return false;
        f = r.dot(n)*m.scale;
        if(f > 0 || f < maxdist*det) return false;
    }
    float invdet = 1/det;
    if(m.flags&MESH_ALPHA && (mode&RAY_ALPHAPOLY)==RAY_ALPHAPOLY && (m.tex->alphamask || loadalphamask(m.tex)))
    {
        vec2 at = m.gettc(t.vert[0]), bt = m.gettc(t.vert[1]).sub(at).mul(v*invdet), ct = m.gettc(t.vert[2]).sub(at).mul(w*invdet);
        at.add(bt).add(ct);
        int si = clamp(int(m.tex->xs * at.x), 0, m.tex->xs-1),
            ti = clamp(int(m.tex->ys * at.y), 0, m.tex->ys-1);
        if(!(m.tex->alphamask[ti*((m.tex->xs+7)/8) + si/8] & (1<<(si%8)))) return false;
    }
    if(!(mode&RAY_SHADOW)) hitsurface = m.xformnorm.transform(n).normalize();
    dist = f*invdet;
    return true;
}

struct traversestate
{
    BIH::node *node;
    float tmin, tmax;
};

inline bool BIH::traverse(const mesh &m, const vec &o, const vec &ray, const vec &invray, float maxdist, float &dist, int mode, node *curnode, float tmin, float tmax)
{
    traversestate stack[128];
    int stacksize = 0;
    ivec order(ray.x>0 ? 0 : 1, ray.y>0 ? 0 : 1, ray.z>0 ? 0 : 1);
    vec mo = m.invxform.transform(o), mray = m.invxformnorm.transform(ray);
    for(;;)
    {
        int axis = curnode->axis();
        int nearidx = order[axis], faridx = nearidx^1;
        float nearsplit = (curnode->split[nearidx] - o[axis])*invray[axis],
              farsplit = (curnode->split[faridx] - o[axis])*invray[axis];

        if(nearsplit <= tmin)
        {
            if(farsplit < tmax)
            {
                if(!curnode->isleaf(faridx))
                {
                    curnode += curnode->childindex(faridx);
                    tmin = max(tmin, farsplit);
                    continue;
                }
                else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true;
            }
        }
        else if(curnode->isleaf(nearidx))
        {
            if(triintersect(m, curnode->childindex(nearidx), mo, mray, maxdist, dist, mode)) return true;
            if(farsplit < tmax)
            {
                if(!curnode->isleaf(faridx))
                {
                    curnode += curnode->childindex(faridx);
                    tmin = max(tmin, farsplit);
                    continue;
                }
                else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true;
            }
        }
        else
        {
            if(farsplit < tmax)
            {
                if(!curnode->isleaf(faridx))
                {
                    if(stacksize < int(sizeof(stack)/sizeof(stack[0])))
                    {
                        traversestate &save = stack[stacksize++];
                        save.node = curnode + curnode->childindex(faridx);
                        save.tmin = max(tmin, farsplit);
                        save.tmax = tmax;
                    }
                    else
                    {
                        if(traverse(m, o, ray, invray, maxdist, dist, mode, curnode + curnode->childindex(nearidx), tmin, min(tmax, nearsplit))) return true;
                        curnode += curnode->childindex(faridx);
                        tmin = max(tmin, farsplit);
                        continue;
                    }
                }
                else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true;
            }
            curnode += curnode->childindex(nearidx);
            tmax = min(tmax, nearsplit);
            continue;
        }
        if(stacksize <= 0) return false;
        traversestate &restore = stack[--stacksize];
        curnode = restore.node;
        tmin = restore.tmin;
        tmax = restore.tmax;
    }
}

inline bool BIH::traverse(const vec &o, const vec &ray, float maxdist, float &dist, int mode)
{
    vec invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f);
    loopi(nummeshes)
    {
        mesh &m = meshes[i];
        if(!(m.flags&MESH_RENDER) || (!(mode&RAY_SHADOW) && m.flags&MESH_NOCLIP)) continue;
        float t1 = (m.bbmin.x - o.x)*invray.x,
              t2 = (m.bbmax.x - o.x)*invray.x,
              tmin, tmax;
        if(invray.x > 0) { tmin = t1; tmax = t2; } else { tmin = t2; tmax = t1; }
        t1 = (m.bbmin.y - o.y)*invray.y;
        t2 = (m.bbmax.y - o.y)*invray.y;
        if(invray.y > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); }
        t1 = (m.bbmin.z - o.z)*invray.z;
        t2 = (m.bbmax.z - o.z)*invray.z;
        if(invray.z > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); }
        tmax = min(tmax, maxdist);
        if(tmin < tmax && traverse(m, o, ray, invray, maxdist, dist, mode, m.nodes, tmin, tmax)) return true;
    }
    return false;
}

void BIH::build(mesh &m, ushort *indices, int numindices, const ivec &vmin, const ivec &vmax)
{
    int axis = 2;
    loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k;

    ivec leftmin, leftmax, rightmin, rightmax;
    int splitleft, splitright;
    int left, right;
    loopk(3)
    {
        leftmin = rightmin = ivec(INT_MAX, INT_MAX, INT_MAX);
        leftmax = rightmax = ivec(INT_MIN, INT_MIN, INT_MIN);
        int split = (vmax[axis] + vmin[axis])/2;
        for(left = 0, right = numindices, splitleft = SHRT_MIN, splitright = SHRT_MAX; left < right;)
        {
            const tribb &tri = m.tribbs[indices[left]];
            ivec trimin = ivec(tri.center).sub(ivec(tri.radius)),
                 trimax = ivec(tri.center).add(ivec(tri.radius));
            int amin = trimin[axis], amax = trimax[axis];
            if(max(split - amin, 0) > max(amax - split, 0))
            {
                ++left;
                splitleft = max(splitleft, amax);
                leftmin.min(trimin);
                leftmax.max(trimax);
            }
            else
            {
                --right;
                swap(indices[left], indices[right]);
                splitright = min(splitright, amin);
                rightmin.min(trimin);
                rightmax.max(trimax);
            }
        }
        if(left > 0 && right < numindices) break;
        axis = (axis+1)%3;
    }

    if(!left || right==numindices)
    {
        leftmin = rightmin = ivec(INT_MAX, INT_MAX, INT_MAX);
        leftmax = rightmax = ivec(INT_MIN, INT_MIN, INT_MIN);
        left = right = numindices/2;
        splitleft = SHRT_MIN;
        splitright = SHRT_MAX;
        loopi(numindices)
        {
            const tribb &tri = m.tribbs[indices[i]];
            ivec trimin = ivec(tri.center).sub(ivec(tri.radius)),
                 trimax = ivec(tri.center).add(ivec(tri.radius));
            if(i < left)
            {
                splitleft = max(splitleft, trimax[axis]);
                leftmin.min(trimin);
                leftmax.max(trimax);
            }
            else
            {
                splitright = min(splitright, trimin[axis]);
                rightmin.min(trimin);
                rightmax.max(trimax);
            }
        }
    }

    int offset = m.numnodes++;
    node &curnode = m.nodes[offset];
    curnode.split[0] = short(splitleft);
    curnode.split[1] = short(splitright);

    if(left==1) curnode.child[0] = (axis<<14) | indices[0];
    else
    {
        curnode.child[0] = (axis<<14) | (m.numnodes - offset);
        build(m, indices, left, leftmin, leftmax);
    }

    if(numindices-right==1) curnode.child[1] = (1<<15) | (left==1 ? 1<<14 : 0) | indices[right];
    else
    {
        curnode.child[1] = (left==1 ? 1<<14 : 0) | (m.numnodes - offset);
        build(m, &indices[right], numindices-right, rightmin, rightmax);
    }
}

BIH::BIH(vector<mesh> &buildmeshes)
  : meshes(NULL), nummeshes(0), nodes(NULL), numnodes(0), tribbs(NULL), numtris(0), bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f), center(0, 0, 0), radius(0), entradius(0)
{
    if(buildmeshes.empty()) return;
    loopv(buildmeshes) numtris += buildmeshes[i].numtris;
    if(!numtris) return;

    nummeshes = buildmeshes.length();
    meshes = new mesh[nummeshes];
    memcpy(meshes, buildmeshes.getbuf(), sizeof(mesh)*buildmeshes.length());
    tribbs = new tribb[numtris];
    tribb *dsttri = tribbs;
    loopi(nummeshes)
    {
        mesh &m = meshes[i];
        m.scale = m.xform.a.magnitude();
        m.invscale = 1/m.scale;
        m.xformnorm = matrix3(m.xform);
        m.xformnorm.normalize();
        m.invxform.invert(m.xform);
        m.invxformnorm = matrix3(m.invxform);
        m.invxformnorm.normalize();
        m.tribbs = dsttri;
        const tri *srctri = m.tris;
        vec mmin(1e16f, 1e16f, 1e16f), mmax(-1e16f, -1e16f, -1e16f);
        loopj(m.numtris)
        {
            vec s0 = m.getpos(srctri->vert[0]), s1 = m.getpos(srctri->vert[1]), s2 = m.getpos(srctri->vert[2]),
                v0 = m.xform.transform(s0), v1 = m.xform.transform(s1), v2 = m.xform.transform(s2),
                vmin = vec(v0).min(v1).min(v2),
                vmax = vec(v0).max(v1).max(v2);
            mmin.min(vmin);
            mmax.max(vmax);
            ivec imin = ivec::floor(vmin), imax = ivec::ceil(vmax);
            dsttri->center = svec(ivec(imin).add(imax).div(2));
            dsttri->radius = svec(ivec(imax).sub(imin).add(1).div(2));
            ++srctri;
            ++dsttri;
        }
        loopk(3) if(fabs(mmax[k] - mmin[k]) < 0.125f)
        {
            float mid = (mmin[k] + mmax[k]) / 2;
            mmin[k] = mid - 0.0625f;
            mmax[k] = mid + 0.0625f;
        }
        m.bbmin = mmin;
        m.bbmax = mmax;
        bbmin.min(mmin);
        bbmax.max(mmax);
    }

    center = vec(bbmin).add(bbmax).mul(0.5f);
    radius = vec(bbmax).sub(bbmin).mul(0.5f).magnitude();
    entradius = max(bbmin.squaredlen(), bbmax.squaredlen());

    nodes = new node[numtris];
    node *curnode = nodes;
    ushort *indices = new ushort[numtris];
    loopi(nummeshes)
    {
        mesh &m = meshes[i];
        m.nodes = curnode;
        loopj(m.numtris) indices[j] = j;
        build(m, indices, m.numtris, ivec::floor(m.bbmin), ivec::ceil(m.bbmax));
        curnode += m.numnodes;
    }
    delete[] indices;
    numnodes = int(curnode - nodes);
}

BIH::~BIH()
{
    delete[] meshes;
    delete[] nodes;
    delete[] tribbs;
}

bool mmintersect(const extentity &e, const vec &o, const vec &ray, float maxdist, int mode, float &dist)
{
    model *m = loadmapmodel(e.attr1);
    if(!m) return false;
    if(mode&RAY_SHADOW)
    {
        if(!m->shadow || e.flags&EF_NOSHADOW) return false;
    }
    else if((mode&RAY_ENTS)!=RAY_ENTS && (!m->collide || e.flags&EF_NOCOLLIDE)) return false;
    if(!m->bih && !m->setBIH()) return false;
    float scale = e.attr5 ? 100.0f/e.attr5 : 1.0f;
    vec mo = vec(o).sub(e.o).mul(scale), mray(ray);
    float v = mo.dot(mray), inside = m->bih->entradius - mo.squaredlen();
    if((inside < 0 && v > 0) || inside + v*v < 0) return false;
    int yaw = e.attr2, pitch = e.attr3, roll = e.attr4;
    if(yaw != 0)
    {
        const vec2 &rot = sincosmod360(-yaw);
        mo.rotate_around_z(rot);
        mray.rotate_around_z(rot);
    }
    if(pitch != 0)
    {
        const vec2 &rot = sincosmod360(-pitch);
        mo.rotate_around_x(rot);
        mray.rotate_around_x(rot);
    }
    if(roll != 0)
    {
        const vec2 &rot = sincosmod360(roll);
        mo.rotate_around_y(rot);
        mray.rotate_around_y(rot);
    }
    if(m->bih->traverse(mo, mray, maxdist ? maxdist*scale : 1e16f, dist, mode))
    {
        dist /= scale;
        if(!(mode&RAY_SHADOW))
        {
            if(roll != 0) hitsurface.rotate_around_y(sincosmod360(-roll));
            if(pitch != 0) hitsurface.rotate_around_x(sincosmod360(pitch));
            if(yaw != 0) hitsurface.rotate_around_z(sincosmod360(yaw));
        }
        return true;
    }
    return false;
}

static inline float segmentdistance(const vec &d1, const vec &d2, const vec &r)
{
    float a = d1.squaredlen(), e = d2.squaredlen(), f = d2.dot(r), s, t;
    if(a <= 1e-4f)
    {
        if(e <= 1e-4f) return r.squaredlen();
        s = 0;
        t = clamp(-f / e, 0.0f, 1.0f);
    }
    else
    {
        float c = d1.dot(r);
        if(e <= 1e-4f)
        {
            t = 0;
            s = clamp(c / a, 0.0f, 1.0f);
        }
        else
        {
            float b = d1.dot(d2), denom = a*e - b*b;
            s = denom ? clamp((c*e - b*f) / denom, 0.0f, 1.0f) : 0.0f;
            t = b*s - f;
            if(t < 0)
            {
                t = 0;
                s = clamp(c / a, 0.0f, 1.0f);
            }
            else if(t > e)
            {
                t = 1;
                s = clamp((b + c) / a, 0.0f, 1.0f);
            }
            else t /= e;
        }
    }
    vec c1 = vec(d1).mul(s),
        c2 = vec(d2).mul(t);
    return vec(c2).sub(c1).add(r).squaredlen();
}

static inline float trisegmentdistance(const vec &a, const vec &b, const vec &c, const vec &p, const vec &q)
{
    vec pq = vec(q).sub(p), ab = vec(b).sub(a), bc = vec(c).sub(b), ca = vec(a).sub(c),
        ap = vec(p).sub(a), bp = vec(p).sub(b), cp = vec(p).sub(c),
        aq = vec(q).sub(a), bq = vec(q).sub(b),
        n, nab, nbc, nca;
    n.cross(ab, bc);
    nab.cross(n, ab);
    nbc.cross(n, bc);
    nca.cross(n, ca);
    float dp = n.dot(ap), dq = n.dot(aq), dist;
    if(ap.dot(nab) < 0) // P outside AB
    {
        dist = segmentdistance(ab, pq, ap);
        if(bq.dot(nbc) < 0) dist = min(dist, segmentdistance(bc, pq, bp)); // Q outside BC
        else if(aq.dot(nca) < 0) dist = min(dist, segmentdistance(pq, ca, cp)); // Q outside CA
        else if(aq.dot(nab) >= 0) dist = min(dist, dq*dq/n.squaredlen()); // Q inside AB
        else return dist;
    }
    else if(bp.dot(nbc) < 0) // P outside BC
    {
        dist = segmentdistance(bc, pq, bp);
        if(aq.dot(nca) < 0) dist = min(dist, segmentdistance(ca, pq, cp)); // Q outside CA
        else if(aq.dot(nab) < 0) dist = min(dist, segmentdistance(ab, pq, ap)); // Q outside AB
        else if(bq.dot(nbc) >= 0) dist = min(dist, dq*dq/n.squaredlen()); // Q inside BC
        else return dist;
    }
    else if(cp.dot(nca) < 0) // P outside CA
    {
        dist = segmentdistance(ca, pq, cp);
        if(aq.dot(nab) < 0) dist = min(dist, segmentdistance(ab, pq, ap)); // Q outside AB
        else if(bq.dot(nbc) < 0) dist = min(dist, segmentdistance(bc, pq, bp)); // Q outside BC
        else if(aq.dot(nca) >= 0) dist = min(dist, dq*dq/n.squaredlen()); // Q inside CA
        else return dist;
    }
    else if(aq.dot(nab) < 0) dist = min(segmentdistance(ab, pq, ap), dp); // Q outside AB
    else if(bq.dot(nbc) < 0) dist = min(segmentdistance(bc, pq, bp), dp); // Q outside BC
    else if(aq.dot(nca) < 0) dist = min(segmentdistance(ca, pq, cp), dp); // Q outside CA
    else // both P and Q inside
    {
        if(dp > 0 ? dq <= 0 : dq >= 0) return 0; // P and Q on different sides of triangle
        dist = min(dp*dp, dq*dq)/n.squaredlen();
        return dist;
    }
    if(dp > 0 ? dq >= 0 : dq <= 0) return dist; // both P and Q on same side of triangle
    vec e = vec().cross(pq, ap);
    float det = fabs(dq - dp), v = ca.dot(e);
    if(v < 0 || v > det) return dist;
    float w = ab.dot(e);
    if(w < 0 || v + w > det) return dist;
    return 0; // segment intersects triangle
}

static inline bool triboxoverlap(const vec &radius, const vec &a, const vec &b, const vec &c)
{
    vec ab = vec(b).sub(a), bc = vec(c).sub(b), ca = vec(a).sub(c);

    #define TESTAXIS(v0, v1, v2, e, s, t) { \
        float p = v0.s*v1.t - v0.t*v1.s, \
              q = v2.s*e.t - v2.t*e.s, \
              r = radius.s*fabs(e.t) + radius.t*fabs(e.s); \
        if(p < q) { if(q < -r || p > r) return false; } \
        else if(p < -r || q > r) return false; \
    }

    TESTAXIS(a, b, c, ab, z, y);
    TESTAXIS(a, b, c, ab, x, z);
    TESTAXIS(a, b, c, ab, y, x);

    TESTAXIS(b, c, a, bc, z, y);
    TESTAXIS(b, c, a, bc, x, z);
    TESTAXIS(b, c, a, bc, y, x);

    TESTAXIS(c, a, b, ca, z, y);
    TESTAXIS(c, a, b, ca, x, z);
    TESTAXIS(c, a, b, ca, y, x);

    #define TESTFACE(w) { \
        if(a.w < b.w) \
        { \
            if(b.w < c.w) { if(c.w < -radius.w || a.w > radius.w) return false; } \
            else if(b.w < -radius.w || min(a.w, c.w) > radius.w) return false; \
        } \
        else if(a.w < c.w) { if(c.w < -radius.w || b.w > radius.w) return false; } \
        else if(a.w < -radius.w || min(b.w, c.w) > radius.w) return false; \
    }

    TESTFACE(x);
    TESTFACE(y);
    TESTFACE(z);

    return true;
}

template<>
inline void BIH::tricollide<COLLIDE_ELLIPSE>(const mesh &m, int tidx, physent *d, const vec &dir, float cutoff, const vec &center, const vec &radius, const matrix4x3 &orient, float &dist, const ivec &bo, const ivec &br)
{
    if(m.tribbs[tidx].outside(bo, br)) return; 

    const tri &t = m.tris[tidx];
    vec a = m.getpos(t.vert[0]), b = m.getpos(t.vert[1]), c = m.getpos(t.vert[2]),
        zdir = vec(orient.rowz()).mul(m.invscale*m.invscale*(radius.z - radius.x));
    if(trisegmentdistance(a, b, c, vec(center).sub(zdir), vec(center).add(zdir)) > m.invscale*m.invscale*radius.x*radius.x) return;

    vec n;
    n.cross(a, b, c).normalize();
    float pdist = (n.dot(vec(center).sub(a)) - fabs(n.dot(zdir)))*m.scale - radius.x;
    if(pdist > 0 || pdist <= dist) return;

    collideinside = true;

    n = orient.transformnormal(n).mul(m.invscale);

    if(!dir.iszero())
    {
        if(n.dot(dir) >= -cutoff*dir.magnitude()) return;
        if(d->type==ENT_PLAYER &&
            pdist < (dir.z*n.z < 0 ?
               2*radius.z*(d->zmargin/(d->aboveeye+d->eyeheight)-(dir.z < 0 ? 1/3.0f : 1/4.0f)) :
               (dir.x*n.x < 0 || dir.y*n.y < 0 ? -radius.x : 0)))
            return;
    }

    dist = pdist;
    collidewall = n;
}

template<>
inline void BIH::tricollide<COLLIDE_OBB>(const mesh &m, int tidx, physent *d, const vec &dir, float cutoff, const vec &center, const vec &radius, const matrix4x3 &orient, float &dist, const ivec &bo, const ivec &br)
{
    if(m.tribbs[tidx].outside(bo, br)) return;

    const tri &t = m.tris[tidx];
    vec a = orient.transform(m.getpos(t.vert[0])), b = orient.transform(m.getpos(t.vert[1])), c = orient.transform(m.getpos(t.vert[2]));
    if(!triboxoverlap(radius, a, b, c)) return;

    vec n;
    n.cross(a, b, c).normalize();
    float pdist = -n.dot(a), r = radius.absdot(n);
    if(fabs(pdist) > r) return;

    pdist -= r;
    if(pdist <= dist) return;

    collideinside = true;

    if(!dir.iszero())
    {
        if(n.dot(dir) >= -cutoff*dir.magnitude()) return;
        if(d->type==ENT_PLAYER &&
            pdist < (dir.z*n.z < 0 ?
               2*radius.z*(d->zmargin/(d->aboveeye+d->eyeheight)-(dir.z < 0 ? 1/3.0f : 1/4.0f)) :
               (dir.x*n.x < 0 || dir.y*n.y < 0 ? -max(radius.x, radius.y) : 0)))
            return;
    }

    dist = pdist;
    collidewall = n;
}

template<int C>
inline void BIH::collide(const mesh &m, physent *d, const vec &dir, float cutoff, const vec &center, const vec &radius, const matrix4x3 &orient, float &dist, node *curnode, const ivec &bo, const ivec &br)
{
    node *stack[128];
    int stacksize = 0;
    ivec bmin = ivec(bo).sub(br), bmax = ivec(bo).add(br);
    for(;;)
    {
        int axis = curnode->axis();
        const int nearidx = 0, faridx = nearidx^1;
        int nearsplit = bmin[axis] - curnode->split[nearidx],
            farsplit = curnode->split[faridx] - bmax[axis];

        if(nearsplit > 0)
        {
            if(farsplit <= 0)
            {
                if(!curnode->isleaf(faridx))
                {
                    curnode += curnode->childindex(faridx);
                    continue;
                }
                else tricollide<C>(m, curnode->childindex(faridx), d, dir, cutoff, center, radius, orient, dist, bo, br);
            }
        }
        else if(curnode->isleaf(nearidx))
        {
            tricollide<C>(m, curnode->childindex(nearidx), d, dir, cutoff, center, radius, orient, dist, bo, br);
            if(farsplit <= 0)
            {
                if(!curnode->isleaf(faridx))
                {
                    curnode += curnode->childindex(faridx);
                    continue;
                }
                else tricollide<C>(m, curnode->childindex(faridx), d, dir, cutoff, center, radius, orient, dist, bo, br);
            }
        }
        else
        {
            if(farsplit <= 0)
            {
                if(!curnode->isleaf(faridx))
                {
                    if(stacksize < int(sizeof(stack)/sizeof(stack[0])))
                    {
                        stack[stacksize++] = curnode + curnode->childindex(faridx);
                    }
                    else
                    {
                        collide<C>(m, d, dir, cutoff, center, radius, orient, dist, &nodes[curnode->childindex(nearidx)], bo, br);
                        curnode += curnode->childindex(faridx);
                        continue;
                    }
                }
                else tricollide<C>(m, curnode->childindex(faridx), d, dir, cutoff, center, radius, orient, dist, bo, br);
            }
            curnode += curnode->childindex(nearidx);
            continue;
        }
        if(stacksize <= 0) return;
        curnode = stack[--stacksize];
    }
}


bool BIH::ellipsecollide(physent *d, const vec &dir, float cutoff, const vec &o, int yaw, int pitch, int roll, float scale)
{
    if(!numnodes) return false;

    vec center(d->o.x, d->o.y, d->o.z + 0.5f*(d->aboveeye - d->eyeheight)),
        radius(d->radius, d->radius, 0.5f*(d->eyeheight + d->aboveeye));
    center.sub(o);
    if(scale != 1) { float invscale = 1/scale; center.mul(invscale); radius.mul(invscale); }

    matrix3 orient;
    orient.identity();
    if(yaw) orient.rotate_around_z(sincosmod360(yaw));
    if(pitch) orient.rotate_around_x(sincosmod360(pitch));
    if(roll) orient.rotate_around_y(sincosmod360(-roll));

    vec bo = orient.transposedtransform(center), br = orient.abstransposedtransform(radius);
    if(bo.x + br.x < bbmin.x || bo.y + br.y < bbmin.y || bo.z + br.z < bbmin.z ||
       bo.x - br.x > bbmax.x || bo.y - br.y > bbmax.y || bo.z - br.z > bbmax.z)
        return false;

    ivec imin = ivec::floor(vec(bo).sub(br)), imax = ivec::ceil(vec(bo).add(br)),
         icenter = ivec(imin).add(imax).div(2),
         iradius = ivec(imax).sub(imin).add(1).div(2);

    float dist = -1e10f;
    loopi(nummeshes)
    {
        mesh &m = meshes[i];
        if(!(m.flags&MESH_COLLIDE) || m.flags&MESH_NOCLIP) continue;
        matrix4x3 morient;
        morient.mul(orient, m.xform);
        collide<COLLIDE_ELLIPSE>(m, d, dir, cutoff, m.invxform.transform(bo), radius, morient, dist, m.nodes, icenter, iradius);
    }
    return dist > -1e9f;
}

bool BIH::boxcollide(physent *d, const vec &dir, float cutoff, const vec &o, int yaw, int pitch, int roll, float scale)
{
    if(!numnodes) return false;

    vec center(d->o.x, d->o.y, d->o.z + 0.5f*(d->aboveeye - d->eyeheight)),
        radius(d->xradius, d->yradius, 0.5f*(d->eyeheight + d->aboveeye));
    center.sub(o);
    if(scale != 1) { float invscale = 1/scale; center.mul(invscale); radius.mul(invscale); }

    matrix3 orient;
    orient.identity();
    if(yaw) orient.rotate_around_z(sincosmod360(yaw));
    if(pitch) orient.rotate_around_x(sincosmod360(pitch));
    if(roll) orient.rotate_around_y(sincosmod360(-roll));

    vec bo = orient.transposedtransform(center), br = orient.abstransposedtransform(vec(d->radius, d->radius, radius.z));
    if(bo.x + br.x < bbmin.x || bo.y + br.y < bbmin.y || bo.z + br.z < bbmin.z ||
       bo.x - br.x > bbmax.x || bo.y - br.y > bbmax.y || bo.z - br.z > bbmax.z)
        return false;

    ivec imin = ivec::floor(vec(bo).sub(br)), imax = ivec::ceil(vec(bo).add(br)),
         icenter = ivec(imin).add(imax).div(2),
         iradius = ivec(imax).sub(imin).add(1).div(2);

    matrix3 drot, dorient;
    drot.setyaw(d->yaw*RAD);
    vec ddir = drot.transform(dir), dcenter = drot.transform(center).neg();
    dorient.mul(drot, orient);

    float dist = -1e10f;
    loopi(nummeshes)
    {
        mesh &m = meshes[i];
        if(!(m.flags&MESH_COLLIDE) || m.flags&MESH_NOCLIP) continue;
        matrix4x3 morient;
        morient.mul(dorient, dcenter, m.xform);
        collide<COLLIDE_OBB>(m, d, ddir, cutoff, center, radius, morient, dist, m.nodes, icenter, iradius);
    }
    if(dist > -1e9f)
    {
        collidewall = drot.transposedtransform(collidewall);
        return true;
    }
    return false;
}

inline void BIH::genstaintris(stainrenderer *s, const mesh &m, int tidx, const vec &center, float radius, const matrix4x3 &orient, const ivec &bo, const ivec &br)
{
    if(m.tribbs[tidx].outside(bo, br)) return;

    const tri &t = m.tris[tidx];
    vec v[3] =
    {
        orient.transform(m.getpos(t.vert[0])),
        orient.transform(m.getpos(t.vert[1])),
        orient.transform(m.getpos(t.vert[2]))
    };

    genstainmmtri(s, v);
}

void BIH::genstaintris(stainrenderer *s, const mesh &m, const vec &center, float radius, const matrix4x3 &orient, node *curnode, const ivec &bo, const ivec &br)
{
    node *stack[128];
    int stacksize = 0;
    ivec bmin = ivec(bo).sub(br), bmax = ivec(bo).add(br);
    for(;;)
    {
        int axis = curnode->axis();
        const int nearidx = 0, faridx = nearidx^1;
        int nearsplit = bmin[axis] - curnode->split[nearidx],
            farsplit = curnode->split[faridx] - bmax[axis];

        if(nearsplit > 0)
        {
            if(farsplit <= 0)
            {
                if(!curnode->isleaf(faridx))
                {
                    curnode += curnode->childindex(faridx);
                    continue;
                }
                else genstaintris(s, m, curnode->childindex(faridx), center, radius, orient, bo, br);
            }
        }
        else if(curnode->isleaf(nearidx))
        {
            genstaintris(s, m, curnode->childindex(nearidx), center, radius, orient, bo, br);
            if(farsplit <= 0)
            {
                if(!curnode->isleaf(faridx))
                {
                    curnode += curnode->childindex(faridx);
                    continue;
                }
                else genstaintris(s, m, curnode->childindex(faridx), center, radius, orient, bo, br);
            }
        }
        else
        {
            if(farsplit <= 0)
            {
                if(!curnode->isleaf(faridx))
                {
                    if(stacksize < int(sizeof(stack)/sizeof(stack[0])))
                    {
                        stack[stacksize++] = curnode + curnode->childindex(faridx);
                    }
                    else
                    {
                        genstaintris(s, m, center, radius, orient, &nodes[curnode->childindex(nearidx)], bo, br);
                        curnode += curnode->childindex(faridx);
                        continue;
                    }
                }
                else genstaintris(s, m, curnode->childindex(faridx), center, radius, orient, bo, br);
            }
            curnode += curnode->childindex(nearidx);
            continue;
        }
        if(stacksize <= 0) return;
        curnode = stack[--stacksize];
    }
}

void BIH::genstaintris(stainrenderer *s, const vec &staincenter, float stainradius, const vec &o, int yaw, int pitch, int roll, float scale)
{
    if(!numnodes) return;

    vec center = vec(staincenter).sub(o);
    float radius = stainradius;
    if(scale != 1) { float invscale = 1/scale; center.mul(invscale); radius *= invscale; }

    matrix3 orient;
    orient.identity();
    if(yaw) orient.rotate_around_z(sincosmod360(yaw));
    if(pitch) orient.rotate_around_x(sincosmod360(pitch));
    if(roll) orient.rotate_around_y(sincosmod360(-roll));

    vec bo = orient.transposedtransform(center);
    if(bo.x + radius < bbmin.x || bo.y + radius < bbmin.y || bo.z + radius < bbmin.z ||
       bo.x - radius > bbmax.x || bo.y - radius > bbmax.y || bo.z - radius > bbmax.z)
        return;

    orient.scale(scale);

    ivec imin = ivec::floor(vec(bo).sub(radius)), imax = ivec::ceil(vec(bo).add(radius)),
         icenter = ivec(imin).add(imax).div(2),
         iradius = ivec(imax).sub(imin).add(1).div(2);

    loopi(nummeshes)
    {
        mesh &m = meshes[i];
        if(!(m.flags&MESH_RENDER) || m.flags&MESH_ALPHA) continue;
        matrix4x3 morient;
        morient.mul(orient, o, m.xform);
        genstaintris(s, m, m.invxform.transform(bo), radius, morient, m.nodes, icenter, iradius);
    }
}

