
// CommonStrokeTessellatorCulling.hpp - common GPU and CPU stroking trivial culling

#ifndef __CommonStrokeTessellatorCulling_hpp__
#define __CommonStrokeTessellatorCulling_hpp__

#include "CommonStrokeTessellatorControlPrivate.hpp"

#include <Cg/mul.hpp>

bool StrokeTessellatorMETHOD triviallyAccepted()
{
    // Initial bitmask says point is outside no edges
    // (unless a zero vertex ID indicates a bogus vertex).
    PER_THREAD_COUNT(4) {
        int_tmp[TID] = (vertex_id[TID] == 0) ? 0xF : 0x0;
        CONST float w = hpos[TID].z;  // W is in Z component of hpos's vec3.
        // For the X and Y edges.
        for (int i=0; i<2; i++) {
            // Is control point X/Y beyond left edge?
            if (hpos[TID][i] < -w) {
                // Yes, set X/Y edge's clip code.
                int_tmp[TID] |= 1<<(2*i+0);
            }
            // Is control point X/Y beyond right edge?
            if (hpos[TID][i] > w) {
                // Yes, set X/Y edge's clip code.
                int_tmp[TID] |= 1<<(2*i+1);
            }
        }
    }

    // Bit-wise AND all clip codes from each control point together.
    CONST int combo_clip_code = int_tmp[0] & 
                                int_tmp[1] &
                                int_tmp[2] &
                                int_tmp[3];

    // Are all of 4 control points within every view frustum edge?
    CONST bool trivially_accepted = (combo_clip_code == 0);
    return trivially_accepted;
}

// Given a 2D projective control point (x,y,w) indexed by <ndx>, offset it in
// the <offset> direction scaled by <scale> amount, then transform to clip space.
StrokeTessellatorMETHOD vec3 StrokeTessellatorMETHOD transformOffsetControlPointToClipSpace(int ndx, vec2 offset, float scale)
{
    vec3 hpos;
    if (stroke_space == WINDOW_SPACE) {
        // Generalize to projective case.
        CONST float w = cp[ndx].z;
        hpos.xy = cp[ndx].xy + w*offset*scale;
        hpos.z = w;
        hpos.xy *= viewport_scale.zw;  // reverse viewport scale          
    } else {
        // Generalize to projective case.
        CONST float w = v_orig[ndx].z;
        CONST vec3 opos = vec3(v_orig[ndx].xy+w*offset*scale,w);
        hpos = mul(modelview_projection, opos);
    }
    return hpos;
}

bool StrokeTessellatorMETHOD dilatedTrivialCulling(int link_type)
{
    // Perform trivial clipping again on control point hull dilated by vertex_extrusion.
    // Normally we use the stroke_radius to extrude the vertex into an object-space square.
    float vertex_extrusion = stroke_radius;
    // However if is a mitered join...
    if (isJoin(link_type) && isMiterJoin(max_join_steps)) {
        // The worst case extrusion is stroke_radius scaled by the miter limit.
        vertex_extrusion = effective_miter_limit*stroke_radius;
    }
    PER_THREAD link_state[TID] = 0xF;

    PER_THREAD_COUNT(4) {
        // Offsetting each control points X by -vertex_extrusion and +vertex_extrusion
        for (int dx=-1; dx<=1; dx+=2) {
            // Offsetting each control points X by -vertex_extrusion and +vertex_extrusion
            for (int dy=-1; dy<=1; dy+=2) {
                CONST vec2 xy_dir = vec2(dx,dy);

                // Initial bitmask says offset point is outside no edges
                // (unless a zero vertex ID indicates a bogus vertex).
                int_tmp[TID] = (vertex_id[TID] == 0) ? 0xF : 0x0;
                // Transform each offset control point into clip space
                vec3 hpos = transformOffsetControlPointToClipSpace(TID, xy_dir, vertex_extrusion);
                CONST float ww = hpos.z;  // W is in Z component of hpos's vec3.
                // For the X and Y edges.
                for (int i=0; i<2; i++) {
                    // Is the ofset point outside the negative X or Y edge?
                    if (hpos[i] < -ww) {
                        // Mark X or Y negative edge in bitmask as outside.
                        int_tmp[TID] |= 1<<(2*i+0);
                    }
                    // Is the ofset point outside the positive X or Y edge?
                    if (hpos[i] > ww) {
                        // Mark X or Y positive edge in bitmask as outside.
                        int_tmp[TID] |= 1<<(2*i+1);
                    }
                }
                // Bitwise-AND "outside edge" booleans.
                link_state[TID] &= int_tmp[TID];
            }
        }
    }
    // At this point, an edge bit will only be set if EVERY offset of a given
    // control point is outside a given edge...
    // Now merge masks among all 4 control points by ANDing.
    const int combo_clip_code = link_state[0] & link_state[1] & link_state[2] & link_state[3];

   // At this point, if a bit is still set, EVERY control point for EVERY offset
    // is outside a given edge.
    if (combo_clip_code != 0) {
        // Trivially culled.
        return true;
    }
    // Not trivially culled, must be rendered.
    return false;
}

// Returns true if link can be trivially culled.
bool StrokeTessellatorMETHOD trivialCulling(int link_type)
{
    // Thread parallel trivial stroke segment/join/cap culling.

    // First check if (non-offset) control points indicate trivial culling against
    // an X or Y positive or negative edge is possible.
    if (triviallyAccepted()) {
        return false;  // Not culled
    }

#if 000
    // WRONG (but fast): Trivial cull, igoring dilation by stroke_radius (or miter).
    return true;
#else
    // Now do more expensive check to see if all four diagonal offsets
    // of all four control points are trivially culled by a view frustum edge.
    return dilatedTrivialCulling(link_type);
#endif
}

#endif // __CommonStrokeTessellatorCulling_hpp__
