#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */

#include "cm_trace_core_private.h"

qboolean CM_TraceMayIntersectBox(
    const traceWork_t *traceWork,
    const vec3_t mins,
    const vec3_t maxs)
{
    if ((maxs[0] < traceWork->bounds[0][0]) ||
        (traceWork->bounds[1][0] < mins[0])) {
        return 0;
    }

    if ((maxs[1] < traceWork->bounds[0][1]) ||
        (traceWork->bounds[1][1] < mins[1])) {
        return 0;
    }

    if ((maxs[2] < traceWork->bounds[0][2]) ||
        (traceWork->bounds[1][2] < mins[2])) {
        return 0;
    }

    if (traceWork->isPoint == 0) {
        return 1;
    }

#if EMULATE_X87
    /*
     * x87-faithful transcription (DLL 0x08058b85). This is the separating-axis
     * reject that gates EVERY patch trace, so an ULP here changes which patches
     * get traced at all. Each size/sum/delta is one 80-bit chain rounded to its
     * float slot (fld;fsub|fadd;fstp); each cross is
     * `delta[a]*centerDelta[b] - delta[b]*centerDelta[a]` in one chain
     * (fld;fmul;fld;fmul;fsubp;fstp) and each projected likewise with faddp;
     * the rejects then square both sides and compare in 80-bit with no store.
     */
    const float boundsSizeX = x87f_store_f32(
        x87f_sub(x87f_load_f32(maxs[0]), x87f_load_f32(mins[0])));
    const float boundsSizeY = x87f_store_f32(
        x87f_sub(x87f_load_f32(maxs[1]), x87f_load_f32(mins[1])));
    const float boundsSizeZ = x87f_store_f32(
        x87f_sub(x87f_load_f32(maxs[2]), x87f_load_f32(mins[2])));

    const float boundsCenterSumX = x87f_store_f32(
        x87f_add(x87f_load_f32(maxs[0]), x87f_load_f32(mins[0])));
    const float boundsCenterSumY = x87f_store_f32(
        x87f_add(x87f_load_f32(maxs[1]), x87f_load_f32(mins[1])));
    const float boundsCenterSumZ = x87f_store_f32(
        x87f_add(x87f_load_f32(maxs[2]), x87f_load_f32(mins[2])));

    const float traceCenterSumX =
        x87f_store_f32(x87f_add(x87f_load_f32(traceWork->end[0]),
                                x87f_load_f32(traceWork->start[0])));
    const float traceCenterSumY =
        x87f_store_f32(x87f_add(x87f_load_f32(traceWork->end[1]),
                                x87f_load_f32(traceWork->start[1])));
    const float traceCenterSumZ =
        x87f_store_f32(x87f_add(x87f_load_f32(traceWork->end[2]),
                                x87f_load_f32(traceWork->start[2])));

    const float centerDeltaX = x87f_store_f32(x87f_sub(
        x87f_load_f32(traceCenterSumX), x87f_load_f32(boundsCenterSumX)));
    const float centerDeltaY = x87f_store_f32(x87f_sub(
        x87f_load_f32(traceCenterSumY), x87f_load_f32(boundsCenterSumY)));
    const float centerDeltaZ = x87f_store_f32(x87f_sub(
        x87f_load_f32(traceCenterSumZ), x87f_load_f32(boundsCenterSumZ)));

    const float traceBoundsSizeX =
        x87f_store_f32(x87f_sub(x87f_load_f32(traceWork->bounds[1][0]),
                                x87f_load_f32(traceWork->bounds[0][0])));
    const float traceBoundsSizeY =
        x87f_store_f32(x87f_sub(x87f_load_f32(traceWork->bounds[1][1]),
                                x87f_load_f32(traceWork->bounds[0][1])));
    const float traceBoundsSizeZ =
        x87f_store_f32(x87f_sub(x87f_load_f32(traceWork->bounds[1][2]),
                                x87f_load_f32(traceWork->bounds[0][2])));

    const float crossX = x87f_store_f32(
        x87f_sub(x87f_mul(x87f_load_f32(traceWork->delta[1]),
                          x87f_load_f32(centerDeltaZ)),
                 x87f_mul(x87f_load_f32(traceWork->delta[2]),
                          x87f_load_f32(centerDeltaY))));
    const float projectedX = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(boundsSizeY), x87f_load_f32(traceBoundsSizeZ)),
        x87f_mul(x87f_load_f32(boundsSizeZ), x87f_load_f32(traceBoundsSizeY))));
    if (x87f_lt(x87f_mul(x87f_load_f32(projectedX), x87f_load_f32(projectedX)),
                x87f_mul(x87f_load_f32(crossX), x87f_load_f32(crossX)))) {
        return 0;
    }

    const float crossY = x87f_store_f32(
        x87f_sub(x87f_mul(x87f_load_f32(traceWork->delta[2]),
                          x87f_load_f32(centerDeltaX)),
                 x87f_mul(x87f_load_f32(traceWork->delta[0]),
                          x87f_load_f32(centerDeltaZ))));
    const float projectedY = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(boundsSizeX), x87f_load_f32(traceBoundsSizeZ)),
        x87f_mul(x87f_load_f32(boundsSizeZ), x87f_load_f32(traceBoundsSizeX))));
    if (x87f_lt(x87f_mul(x87f_load_f32(projectedY), x87f_load_f32(projectedY)),
                x87f_mul(x87f_load_f32(crossY), x87f_load_f32(crossY)))) {
        return 0;
    }

    const float crossZ = x87f_store_f32(
        x87f_sub(x87f_mul(x87f_load_f32(traceWork->delta[0]),
                          x87f_load_f32(centerDeltaY)),
                 x87f_mul(x87f_load_f32(traceWork->delta[1]),
                          x87f_load_f32(centerDeltaX))));
    const float projectedZ = x87f_store_f32(x87f_add(
        x87f_mul(x87f_load_f32(boundsSizeX), x87f_load_f32(traceBoundsSizeY)),
        x87f_mul(x87f_load_f32(boundsSizeY), x87f_load_f32(traceBoundsSizeX))));
    if (x87f_lt(x87f_mul(x87f_load_f32(projectedZ), x87f_load_f32(projectedZ)),
                x87f_mul(x87f_load_f32(crossZ), x87f_load_f32(crossZ)))) {
        return 0;
    }

    return 1;
#else
    const float boundsSizeX = maxs[0] - mins[0];
    const float boundsSizeY = maxs[1] - mins[1];
    const float boundsSizeZ = maxs[2] - mins[2];

    const float boundsCenterSumX = maxs[0] + mins[0];
    const float boundsCenterSumY = maxs[1] + mins[1];
    const float boundsCenterSumZ = maxs[2] + mins[2];

    const float traceCenterSumX = traceWork->end[0] + traceWork->start[0];
    const float traceCenterSumY = traceWork->end[1] + traceWork->start[1];
    const float traceCenterSumZ = traceWork->end[2] + traceWork->start[2];

    const float centerDeltaX = traceCenterSumX - boundsCenterSumX;
    const float centerDeltaY = traceCenterSumY - boundsCenterSumY;
    const float centerDeltaZ = traceCenterSumZ - boundsCenterSumZ;

    const float traceBoundsSizeX =
        traceWork->bounds[1][0] - traceWork->bounds[0][0];
    const float traceBoundsSizeY =
        traceWork->bounds[1][1] - traceWork->bounds[0][1];
    const float traceBoundsSizeZ =
        traceWork->bounds[1][2] - traceWork->bounds[0][2];

    const float crossX =
        (traceWork->delta[1] * centerDeltaZ) -
        (traceWork->delta[2] * centerDeltaY);
    const float projectedX =
        (boundsSizeY * traceBoundsSizeZ) + (boundsSizeZ * traceBoundsSizeY);
    if ((projectedX * projectedX) < (crossX * crossX)) {
        return 0;
    }

    const float crossY =
        (traceWork->delta[2] * centerDeltaX) -
        (traceWork->delta[0] * centerDeltaZ);
    const float projectedY =
        (boundsSizeX * traceBoundsSizeZ) + (boundsSizeZ * traceBoundsSizeX);
    if ((projectedY * projectedY) < (crossY * crossY)) {
        return 0;
    }

    const float crossZ =
        (traceWork->delta[0] * centerDeltaY) -
        (traceWork->delta[1] * centerDeltaX);
    const float projectedZ =
        (boundsSizeX * traceBoundsSizeY) + (boundsSizeY * traceBoundsSizeX);
    if ((projectedZ * projectedZ) < (crossZ * crossZ)) {
        return 0;
    }

    return 1;
#endif
}
