// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //
//
//  Project:   Talina Gaming System (TgS) (∂)
//  File:      TgS Collision - Box-Triangle.cpp
//  Author:    Andrew Aye (EMail: andrew.aye@gmail.com, Web: http://www.andrewaye.com) 
//  Version:   3.11
//
// ------------------------------------------------------------------------------------------------------------------------------ //
//
//  Copyright: © 2002-2008, Andrew Aye.  All Rights Reserved.
//
//  This software is free for non-commercial use. Redistribution and use in source and binary forms, with or without modification,
//  are permitted provided that the following conditions are met: 
//    Redistributions of source code must retain this copyright notice, this list of conditions and the following disclaimers. 
//    Redistributions in binary form must reproduce this copyright notice, this list of conditions and the following
//      disclaimers in the documentation and other materials provided with the distribution. 
//
//  Neither the names of the copyright owner nor the names of its contributors may be used to endorse or promote products derived
//  from this software without specific prior written permission. 
//
//  The intellectual property rights of the algorithms used reside with Andrew Aye.  You may not use this software, in whole or
//  in part, in support of any commercial product without the express written consent of the author.
//
//  There is no warranty or other guarantee of fitness of this software for any purpose. It is provided solely "as is".
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //




namespace TGS { // START TGS ///////////////////////////////////////////////////////////////////////////////////////////////////////
namespace COL { // START COL ///////////////////////////////////////////////////////////////////////////////////////////////////////

// ============================================================================================================================== //

// ---- F_Axis_Seperation ------------------------------------------------------------------------------------------------------- //
// Input:  tgST0: Space Triangle primitive
// Input:  tgBX0: Box primitive
// Output: tgAxS: Structure holds the resulting axis separation information necessary to create a contact set.
// Return: False if a separating axis exists, true otherwise
// ------------------------------------------------------------------------------------------------------------------------------ //

template <typename TYPE, int DIM>
TgBOOL F_Axis_Seperation( PC_(AXIS_RESULT,DIM) ptgAxS, CR_(STRI,DIM) tgST0, CR_(BOX,DIM) tgBX0 )
{
    TYPE                                tyMinBox, tyMaxBox, tyMinTri, tyMaxTri;

    // -- Axis: Triangle Normal --------------------------------

    tyMinTri = MATH::F_DOT(tgST0.Query_Origin(),tgST0.Query_Normal());
    tgBX0.Project( &tyMinBox, &tyMaxBox, tgST0.Query_Normal() );

    if (tyMaxBox < tyMinTri || tyMinBox > tyMinTri) //« Separation test.
    {
        return (TgFALSE);
    };

    ptgAxS->m_tvNormal = tgST0.Query_Normal();
    ptgAxS->m_tyDepth = tyMinTri - tyMinBox;
    ptgAxS->m_iAxis = 1;

    // -- Axis: Box Face/Plane Normals -------------------------

    TgBOOL                              bTriIsCoPlanarToFace = TgFALSE;

    for (TgUINT iIndex = 0; iIndex < 3; ++iIndex)
    {
        C_(VECTOR,DIM)                      &tvAxis = tgBX0.Query_AxisUnit( iIndex );

        // Weight the function towards the triangle normal.  In this case ignore any box axis that is "close".

        if (Near_One( MATH::F_DOT( tgST0.Query_Normal(), tvAxis ) ))
        {
            //  Optimization step: Since a box axis is identical to the triangle normal, the remaining two box axes would be
            // coplanar with the triangle.  Thus, the cross product values would all return vectors equivalent to the triangle
            // normal.  The remaining cross-product values would be the edge-plane normals.

            bTriIsCoPlanarToFace = TgTRUE;
            continue;
        };

        // Determine the extents of the primitives along the chosen axis.

        const TYPE                          tyBO_AU = MATH::F_DOT( tgBX0.Query_Origin(), tvAxis );

        tgST0.Project( &tyMinTri, &tyMaxTri, tvAxis );

        tyMinBox = tyBO_AU - tgBX0.Query_Extent( iIndex );
        tyMaxBox = tyBO_AU + tgBX0.Query_Extent( iIndex );

        // Separation Test.

        if (tyMaxBox < tyMinTri || tyMinBox > tyMaxTri)
        {
            return (TgFALSE);
        };

        // Selection of the best (minimal depth) axis.

        const TYPE                          tyMinDepth = tyMaxBox - tyMinTri;
        const TYPE                          tyMaxDepth = tyMaxTri - tyMinBox;

        C_TgBOOL                            bNegAxis = tyMinDepth > tyMaxDepth;

        if ((bNegAxis ? tyMaxDepth : tyMinDepth) < ptgAxS->m_tyDepth)
        {
            ptgAxS->m_tvNormal = bNegAxis ? tvAxis : MATH::F_NEG( tvAxis );
            ptgAxS->m_tyDepth = (bNegAxis ? tyMaxDepth : tyMinDepth);
            ptgAxS->m_iAxis = iIndex + (bNegAxis ? 5 : 2);
        };
    };

    // -- Axis: Axis-Box Cross Product -------------------------

    if (!bTriIsCoPlanarToFace)
    {
        for (TgINT iTriIndex = 0; iTriIndex < 3; ++iTriIndex)
        {
            // Disregard edges that are indicated as invalid.  This is usually due to feature reduction removing this element.

            for (TgINT iBoxIndex = 0; iBoxIndex < 3; ++iBoxIndex)
            {
                TYPE                                tyAxisLen;
                T_(VECTOR,DIM)                      tvAxis;

                // Axis is created by taking the cross product of the triangle edge and a box axis.

                tyAxisLen = MATH::F_UCX( &tvAxis, tgBX0.Query_AxisUnit( iBoxIndex ), tgST0.Query_Edge( iTriIndex ) );

                if (Near_Zero( tyAxisLen )) //« Sanity/Parallel check for the resulting vector.
                {
                    continue;
                };

                //  Check to see if the resultant normal is approximately the same as that of the triangle normal. If this is the
                // case skip this axis.  This check is primarily to aid in contact generation, since a resultant in this context
                // implies an edge-edge contact (1-2 point) instead of the more stable cases (3-4 points).

                if (
                    Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit0(), tvAxis ) ) ||
                    Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit1(), tvAxis ) ) ||
                    Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit2(), tvAxis ) ) ||
                    Near_One( MATH::F_DOT( tgST0.Query_Normal(), tvAxis ) )
                ) {
                    continue;
                };

                // Determine the extents of the primitives along the chosen axis.

                tgST0.Project( &tyMinTri, &tyMaxTri, tvAxis );
                tgBX0.Project( &tyMinBox, &tyMaxBox, tvAxis );

                // Separation Test.

                if (tyMaxBox < tyMinTri || tyMinBox < tyMaxTri)
                {
                    return (TgFALSE);
                };

                if (!tgST0.Test_Edge( iTriIndex )) //« Feature reduction test - ignore axes associated with reduced edges.
                {
                    continue;
                };

                // Selection of the best (minimal depth) axis.

                const TYPE                          tyMinDepth = tyMaxBox - tyMinTri;
                const TYPE                          tyMaxDepth = tyMaxTri - tyMinBox;

                C_TgBOOL                            bNegAxis = tyMinDepth > tyMaxDepth;

                if ((bNegAxis ? tyMaxDepth : tyMinDepth) < ptgAxS->m_tyDepth)
                {
                    ptgAxS->m_tvNormal = bNegAxis ? tvAxis : MATH::F_NEG( tvAxis );
                    ptgAxS->m_tyDepth = bNegAxis ? tyMaxDepth : tyMinDepth;
                    ptgAxS->m_iAxis = iTriIndex * 3 + iBoxIndex + 8;
                };
            };
        };
    }
    else
    {
        for (TgINT iTriIndex = 0; iTriIndex < 3; ++iTriIndex)
        {
            C_(VECTOR,DIM)                      &tvAxis = tgST0.Query_EdgePlane( iTriIndex ).Query_Normal();

            // Check to see if the resultant normal is approximately the same as that of the triangle normal. If this is the
            // case skip this axis.  This check is primarily to aid in contact generation, since a resultant in this context
            // implies an edge-edge contact (1-2 point) instead of the more stable cases (3-4 points).

            if (
                Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit0(), tvAxis ) ) ||
                Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit1(), tvAxis ) ) ||
                Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit2(), tvAxis ) )
            ) {
                continue;
            };

            // Determine the extents of the primitives along the chosen axis.

            tgST0.Project( &tyMinTri, &tyMaxTri, tvAxis );
            tgBX0.Project( &tyMinBox, &tyMaxBox, tvAxis );

            // Separation Test.

            if (tyMaxBox < tyMinTri || tyMinBox < tyMaxTri)
            {
                return (TgFALSE);
            };

            if (!tgST0.Test_Edge( iTriIndex )) //« Feature reduction test - ignore axes associated with reduced edges.
            {
                continue;
            };

            // Selection of the best (minimal depth) axis.

            const TYPE                          tyMinDepth = tyMaxBox - tyMinTri;
            const TYPE                          tyMaxDepth = tyMaxTri - tyMinBox;

            C_TgBOOL                            bNegAxis = tyMinDepth > tyMaxDepth;

            if (bNegAxis ? tyMaxDepth : tyMinDepth < ptgAxS->m_tyDepth)
            {
                ptgAxS->m_tvNormal = bNegAxis ? tvAxis : MATH::F_NEG( tvAxis );
                ptgAxS->m_tyDepth = bNegAxis ? tyMaxDepth : tyMinDepth;
                ptgAxS->m_iAxis = iTriIndex + 21;
            };
        };
    };

    return (TgTRUE);
};

template TgBOOL F_Axis_Seperation( PC_TgF4AXIS_RESULT, CR_TgF4STRI, CR_TgF4BOX );


// ============================================================================================================================== //

// ---- F_Axis_Seperation ------------------------------------------------------------------------------------------------------- //
//  Feature reduction removal of reduced edges and their associated directions implemented.
// ------------------------------------------------------------------------------------------------------------------------------ //

template <typename TYPE, int DIM>
TgRESULT F_Axis_Seperation( PC_(AXIS_INFO,DIM) ptgNFO, const TYPE tyLimitT, CR_(STRI,DIM) tgST0, CR_(BOX,DIM) tgBX0, CR_(DELTA,DIM) tgDT )
{
    typedef TTgAXIS_INFO<TYPE,DIM>      TgAXIS_INFO;

    T_(AXIS_TEST,DIM)                   tgAxTest; //« Parameter data output variable
    TYPE                                tySkipT = -LIMITS<TYPE>::MAX; //« T-value for axes associated with reduced features.
    TgINT                               iResult;

    tgAxTest.m_tyLimitT = tyLimitT; // The maximum t-value by which a contact must happen resulting in non-contact.
    tgAxTest.m_tyMinT = -LIMITS<TYPE>::MAX;
    tgAxTest.m_tyMaxT =  LIMITS<TYPE>::MAX;

    // -- Axis: Triangle Normal --------------------------------

    const TYPE                          tyS0_NM = MATH::F_DOT( tgST0.Query_Origin(), tgST0.Query_Normal() );

    tgAxTest.m_tyMin0 = tyS0_NM;
    tgAxTest.m_tyMax0 = tyS0_NM;
    tgBX0.Project( &tgAxTest.m_tyMin1, &tgAxTest.m_tyMax1, tgST0.Query_Normal() );
    tgAxTest.m_tySpeed = MATH::F_DOT( tgDT.m_tvDT, tgST0.Query_Normal() );

    iResult = F_Test_Seperating_Axis( &tgAxTest );
    switch (iResult) {
        case  1: //« Update has occurred
            ptgNFO->m_eSide = (tgAxTest.m_tyMax1 < tgAxTest.m_tyMin0) ? TgAXIS_INFO::NEGATIVE : TgAXIS_INFO::POSITIVE;
            ptgNFO->m_tvNormal = tgST0.Query_Normal();
            ptgNFO->m_tyMinT = tgAxTest.m_tyMinT;
        case  0: //« Contact occurred in valid interval, but earlier contact already recorded.
            break;
        case -1: //« No contact occurred during valid interval, thus primitives are separated on this axis.
            return (TgE_NOINTERSECT);
    };

    TgASSERT( (ptgNFO->m_eSide != TgAXIS_INFO::NEGATIVE) ) //« Sanity Check: Box can not contact the triangle back side.

    // -- Axis: Box Face/Plane Normals -------------------------

    TgBOOL                              bTriIsCoPlanarToFace = TgFALSE;

    for (TgUINT iIndex = 0; iIndex < 3; ++iIndex)
    {
        C_(VECTOR,DIM)                      &tvAxis = tgBX0.Query_AxisUnit( iIndex );

        // Weight the function towards the triangle normal.  In this case ignore any box axis that is "close".

        if (Near_One( MATH::F_DOT(tgST0.Query_Normal(),tvAxis) ))
        {
            //  Optimization step: Since a box axis is identical to the triangle normal, the remaining two box axes would be
            // coplanar with the triangle.  Thus, the cross product values would all return vectors equivalent to the triangle
            // normal.  The remaining cross-product values would be the edge-plane normals.

            bTriIsCoPlanarToFace = TgTRUE;
            continue;
        };

        const TYPE                          tyBO_AU = MATH::F_DOT( tgBX0.Query_Origin(), tvAxis );

        tgST0.Project( &tgAxTest.m_tyMin0, &tgAxTest.m_tyMax0, tvAxis );
        tgAxTest.m_tyMin1 = tyBO_AU - tgBX0.Query_Extent( iIndex );
        tgAxTest.m_tyMax1 = tyBO_AU + tgBX0.Query_Extent( iIndex );
        tgAxTest.m_tySpeed = MATH::F_DOT( tvAxis, tgDT.m_tvDT );

        iResult = F_Test_Seperating_Axis( &tgAxTest );
        switch (iResult) {
            case  1: //« Update has occurred
                ptgNFO->m_eSide = (tgAxTest.m_tyMax1 < tgAxTest.m_tyMin0) ? TgAXIS_INFO::NEGATIVE : TgAXIS_INFO::POSITIVE;
                ptgNFO->m_tvNormal = tvAxis;
                ptgNFO->m_tyMinT = tgAxTest.m_tyMinT;
            case  0: //« Contact occurred in valid interval, but earlier contact already recorded.
                break;
            case -1: //« No contact occurred during valid interval, thus primitives are separated on this axis.
                return (TgE_NOINTERSECT);
        };
    };

    T_(VECTOR,DIM)                      tvAxis;

    if (!bTriIsCoPlanarToFace)
    {
        for (TgINT iTriIndex = 0; iTriIndex < 3; ++iTriIndex)
        {
            // Disregard edges that are indicated as invalid.  This is usually due to feature reduction removing this element.

            for (TgINT iBoxIndex = 0; iBoxIndex < 3; ++iBoxIndex)
            {
                TYPE                                tyAxisLen;

                // Axis is created by taking the cross product of the triangle edge and a box axis.

                tyAxisLen = MATH::F_UCX( &tvAxis, tgBX0.Query_AxisUnit( iBoxIndex ), tgST0.Query_Edge( iTriIndex ) );

                if (Near_Zero( tyAxisLen ))
                {
                    continue;
                };

                // Check to see if the resultant normal is approximately the same as that of the triangle normal. If this is the
                // case skip this axis.  This check is primarily to aid in contact generation, since a resultant in this context
                // implies an edge-edge contact (1-2 point) instead of the more stable cases (3-4 points).

                if (
                    Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit0(), tvAxis ) ) ||
                    Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit1(), tvAxis ) ) ||
                    Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit2(), tvAxis ) ) ||
                    Near_One( MATH::F_DOT( tgST0.Query_Normal(), tvAxis ) )
                ) {
                    continue;
                };

                // Perform the separation test.

                tgST0.Project( &tgAxTest.m_tyMin0, &tgAxTest.m_tyMax0, tvAxis );
                tgBX0.Project( &tgAxTest.m_tyMin1, &tgAxTest.m_tyMax1, tvAxis );
                tgAxTest.m_tySpeed = MATH::F_DOT(tvAxis,tgDT.m_tvDT);

                iResult = F_Test_Seperating_Axis( &tgAxTest );
                switch (iResult) {
                    case  1: //« Update has occurred
                        if (!tgST0.Test_Edge( iTriIndex )) {
                            tySkipT = tySkipT > tgAxTest.m_tyMinT ? tySkipT : tgAxTest.m_tyMinT;
                            break;
                        };

                        ptgNFO->m_eSide = (tgAxTest.m_tyMax1 < tgAxTest.m_tyMin0) ? TgAXIS_INFO::NEGATIVE : TgAXIS_INFO::POSITIVE;
                        ptgNFO->m_tvNormal = tvAxis;
                        ptgNFO->m_tyMinT = tgAxTest.m_tyMinT;

                    case  0: //« Contact occurred in valid interval, but earlier contact already recorded.
                        break;
                    case -1: //« No contact occurred during valid interval, thus primitives are separated on this axis.
                        return (TgE_NOINTERSECT);
                };
            };
        };
    }
    else
    {
        for (TgINT iTriIndex = 0; iTriIndex < 3; ++iTriIndex)
        {
            tvAxis = tgST0.Query_EdgePlane(iTriIndex).Query_Normal();

            // Check to see if the resultant normal is approximately the same as that of the triangle normal. If this is the
            // case skip this axis.  This check is primarily to aid in contact generation, since a resultant in this context
            // implies an edge-edge contact (1-2 point) instead of the more stable cases (3-4 points).

            if (
                Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit0(), tvAxis ) ) ||
                Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit1(), tvAxis ) ) ||
                Near_One( MATH::F_DOT( tgBX0.Query_AxisUnit2(), tvAxis ) )
            ) {
                continue;
            };

            tgST0.Project( &tgAxTest.m_tyMin0, &tgAxTest.m_tyMax0, tvAxis );
            tgBX0.Project( &tgAxTest.m_tyMin1, &tgAxTest.m_tyMax1, tvAxis );
            tgAxTest.m_tySpeed = MATH::F_DOT(tvAxis,tgDT.m_tvDT);

            iResult = F_Test_Seperating_Axis( &tgAxTest );
            switch (iResult) {
                case  1: //« Update has occurred
                    if (!tgST0.Test_Edge( iTriIndex )) {
                        tySkipT = tySkipT > tgAxTest.m_tyMinT ? tySkipT : tgAxTest.m_tyMinT;
                        break;
                    };

                    ptgNFO->m_eSide = (tgAxTest.m_tyMax1 < tgAxTest.m_tyMin0) ? TgAXIS_INFO::NEGATIVE : TgAXIS_INFO::POSITIVE;
                    ptgNFO->m_tvNormal = tvAxis;
                    ptgNFO->m_tyMinT = tgAxTest.m_tyMinT;
                case  0: //« Contact occurred in valid interval, but earlier contact already recorded.
                    break;
                case -1: //« No contact occurred during valid interval, thus primitives are separated on this axis.
                    return (TgE_NOINTERSECT);
            };
        };
    };

    for (TgINT iBoxIndex = 0; iBoxIndex < 3; ++iBoxIndex)
    {
        TYPE                                tyAxisLen;
        T_(VECTOR,DIM)                      tvAxis;

        // Axis is created by taking the cross product of the triangle edge and a box axis.

        tyAxisLen = MATH::F_UCX( &tvAxis, tgDT.m_tvDT, tgBX0.Query_AxisUnit( iBoxIndex ) );

        if (Near_Zero( tyAxisLen ))
        {
            continue;
        };

        // Check to see if the resultant normal is approximately the same as that of the triangle normal. If this is the
        // case skip this axis.  This check is primarily to aid in contact generation, since a resultant in this context
        // implies an edge-edge contact (1-2 point) instead of the more stable cases (3-4 points).

        if (
            Near_One( MATH::F_DOT(tgBX0.Query_AxisUnit0(),tvAxis) ) ||
            Near_One( MATH::F_DOT(tgBX0.Query_AxisUnit1(),tvAxis) ) ||
            Near_One( MATH::F_DOT(tgBX0.Query_AxisUnit2(),tvAxis) ) ||
            Near_One( MATH::F_DOT(tgST0.Query_Normal(),tvAxis) )
        ) {
            continue;
        };

        // Perform the separation test.

        tgST0.Project( &tgAxTest.m_tyMin0, &tgAxTest.m_tyMax0, tvAxis );
        tgBX0.Project( &tgAxTest.m_tyMin1, &tgAxTest.m_tyMax1, tvAxis );
        tgAxTest.m_tySpeed = MATH::F_DOT(tvAxis,tgDT.m_tvDT);

        iResult = F_Test_Seperating_Axis( &tgAxTest );
        switch (iResult) {
            case  1: //« Update has occurred
                ptgNFO->m_eSide = (tgAxTest.m_tyMax1 < tgAxTest.m_tyMin0) ? TgAXIS_INFO::NEGATIVE : TgAXIS_INFO::POSITIVE;
                ptgNFO->m_tvNormal = tvAxis;
                ptgNFO->m_tyMinT = tgAxTest.m_tyMinT;
            case  0: //« Contact occurred in valid interval, but earlier contact already recorded.
                break;
            case -1: //« No contact occurred during valid interval, thus primitives are separated on this axis.
                return (TgE_NOINTERSECT);
        };
    };

    //  In certain cases, because of feature reduction, the time of contact for an axis is not updated into the return data 
    // structure.  If this axis happens to be the determining direction for the separation, then contact does not occur.
    // Specifically, since the return data structure has an earlier time stamp for the contact between the triangle and the
    // box, its known that the missing axis represents a separation between the triangle and the box.

    if (tySkipT >= tgAxTest.m_tyMinT)
    {
        return (TgE_NOINTERSECT);
    };

    return (TgS_OK);
};

template TgRESULT F_Axis_Seperation( PC_TgF4AXIS_INFO, C_TgFLOAT32, CR_TgF4STRI, CR_TgF4BOX, CR_TgF4DELTA );


// ============================================================================================================================== //

// ---- F_Contact_Penetrate ----------------------------------------------------------------------------------------------------- //
// Input:  tgPacket: The current series of contact points for this query-series, and contact generation parameters.
// Input:  tgST0: Space Triangle primitive
// Input:  tgBX0: Box primitive - contact points are generated on this primitive
// Output: tgPacket: Points of penetration between the two primitives are added to it
// Return: Result Code
// ------------------------------------------------------------------------------------------------------------------------------ //

// Let α represent the simulation frequency, β the maximum velocity in the system.
//  Thus, β/α is the displacement vector used as the function parameter.
// Let Φ represent the minimal distance between the triangle centre and the nearest edge.
// For the system to retain coherency/validity then β/α < Φ - feature reduction may help to reduce this issue.

template <typename TYPE, int DIM>
TgRESULT F_Contact_Penetrate( PC_(CONTACT_PACKET,DIM) ptgPacket, CR_(STRI,DIM) tgST0, CR_(BOX,DIM) tgBX0 )
{
    TgBLOCK_FCN_NOOBJ(ETgFAC_COLLISION, 0, ETgTEST_PENETRATE, (((TgUINT)ETgTRIANGLE<<16)|(TgUINT)ETgBOX))

    TgASSERT((TgSIZE)ptgPacket->m_iStride >= sizeof( P_(CONTACT,DIM) ))
    TgASSERT(tgST0.Is_Valid() && tgBX0.Is_Valid())

    if (0 == ptgPacket->m_niMaxContact || ptgPacket->m_niContact >= ptgPacket->m_niMaxContact || NULL == ptgPacket->m_ptgContact)
    {
        return (TgE_FAIL);
    };

    //  Quick out logic - for the contact generation routines to be accurate then the box origin is assumed to be outside of the
    // infinite negative projection of the triangle space.  (ie. that it is on the positive half-space of the plane defined by
    // the triangle.

    if (MATH::F_DOT( tgST0.Query_Normal(), MATH::F_SUB( tgBX0.Query_Origin(), tgST0.Query_Origin() )) < TYPE(0.0))
    {
        return (TgE_NOINTERSECT);
    };

    // Find the minimal axis of separation, or return if the primitives are not in contact.

    TTgAXIS_RESULT<TYPE,DIM>            tgAxS;

    if (!F_Axis_Seperation( &tgAxS, tgST0,tgBX0 ))
    {
        return (TgE_NOINTERSECT);
    };

    TgASSERT( Near_One( MATH::F_LSQ(tgAxS.m_tvNormal) ) && tgAxS.m_tyDepth >= TYPE(0.0) )

    // == Contact Generation ==================================================================================================== //

    C_TgINT                             niContact = ptgPacket->m_niContact;
    P_(CONTACT,DIM)                     ptgContact;

    if (tgAxS.m_iAxis == 1 || tgAxS.m_iAxis >= 21)
    {
        // -- Triangle Normal or Edge Plane Intersection ---------------------------------------------------------------------------
        //  The minimal separation axis is triangle normal or one of the edge planes.

        C_TgINT                             iIdx = tgAxS.m_iAxis - 21;

        //  Analyze the valid triangle vertices to see if they are contact points - this is done since contained points are not
        // found by the following algorithm (box edge clipping).

        C_(VECTOR,DIM)            tvK0 = MATH::F_NEG( tgST0.Query_EdgePlane( iIdx ).Query_Normal() );
        C_(VECTOR,DIM)            tvNormal = iIdx >= 0 ? tvK0 : tgST0.Query_Normal();

        for (TgINT iVert = 0; iVert < 3; ++iVert)
        {
            if (!tgST0.Test_Point( iVert )) //« Skip reduced features/vertices.
            {
                continue;
            };

            C_(VECTOR,DIM)                      &tvVert = tgST0.Query_Point( iVert );

            if (!tgBX0.Is_Contained( tvVert )) //« Skip exterior points - edge crossings are handled differently.
            {
                continue;
            };

            if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
            {
                return (TgS_MAXCONTACTS);
            };

            ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

            // Find the point on the box along the negative normal direction.

            TYPE                                tyT0, tyT1;

            C_TgRESULT tgResult = TTgCLP_BXLN<TYPE,DIM,1,0>::DO( &tyT0,&tyT1, tgBX0, tvVert,MATH::F_NEG( tvNormal ) );
            TgASSERT(P::ABS( tyT0 ) <= LIMITS<TYPE>::EPSILON)

            const TYPE                          tyT = (TgFAILED(tgResult) || tyT1 < TYPE(0.0)) ? TYPE(0.0) : tyT1;

            ptgContact->m_tvPos = MATH::F_SUB( tvVert, MATH::F_MUL( tyT, tvNormal ) );
            ptgContact->m_tvNormal = tvNormal;
            ptgContact->m_tyT0 = TYPE(0.0);
            ptgContact->m_tyDepth = tyT;

            ++ptgPacket->m_niContact;
            TgFEBUG_COLLISION_TRIANGLE( iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED );
        };

        //  F_Clip the edges of the box against the triangle space.

        for (TgINT iFlag = 0; iFlag < 8; ++iFlag)
        {
            const TYPE                          tyE0 = 0 != (iFlag & 1) ? tgBX0.Query_Extent0() : -tgBX0.Query_Extent0();
            const TYPE                          tyE1 = 0 != (iFlag & 2) ? tgBX0.Query_Extent1() : -tgBX0.Query_Extent1();
            const TYPE                          tyE2 = 0 != (iFlag & 4) ? tgBX0.Query_Extent2() : -tgBX0.Query_Extent2();

            T_(VECTOR,DIM)                      tvCS;

            tvCS = tgBX0.Calc_Point( tyE0,tyE1,tyE2 );

            const TYPE                      tyDistN = MATH::F_DOT( MATH::F_SUB( tvCS, tgST0.Query_Origin() ), tgST0.Query_Normal() );

            if (tyDistN >= TYPE(0.0))
            {
                // The point/vertex is above the triangle plane - ignore and continue processing.

                continue;
            };

            TYPE                                tyDist[3];

            tyDist[0] = F_Dist( tgST0.Query_EdgePlane0(), tvCS );
            tyDist[1] = F_Dist( tgST0.Query_EdgePlane1(), tvCS );
            tyDist[2] = F_Dist( tgST0.Query_EdgePlane2(), tvCS );

            if (tyDist[0] >= -LIMITS<TYPE>::EPSILON && tyDist[1] >= -LIMITS<TYPE>::EPSILON && tyDist[2] >= -LIMITS<TYPE>::EPSILON)
            {
                // The vertex is contained inside the negative triangle space - add it as a contact and test next vertex.

                if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
                {
                    return (TgS_MAXCONTACTS);
                };

                ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

                ptgContact->m_tvPos = tvCS;
                ptgContact->m_tyT0 = TYPE(0.0);

                if (tgAxS.m_iAxis >= 21)
                {
                    TYPE                                tyT0, tyT1;
                    TgINT                               iCode = 0;

                    C_TgRESULT tgResult = TTgCLF_STLN<TYPE,DIM,1,0>::DO( &tyT0,&tyT1, &iCode, tgST0, tvCS,tvNormal );
                    TgASSERT(P::ABS( tyT0 ) <= LIMITS<TYPE>::EPSILON)

                    ptgContact->m_tyDepth = (TgFAILED(tgResult) || 3 != (iCode & 3) || tyT1 < TYPE(0.0)) ? TYPE(0.0) : tyT1;
                }
                else
                {
                    ptgContact->m_tyDepth = -tyDistN;
                };

                ptgContact->m_tvNormal = tvNormal;
                ++ptgPacket->m_niContact;
                TgFEBUG_COLLISION_TRIANGLE( iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED );

                continue;
            };

            //  The vertex is under the triangle plane but not inside the triangle itself.  Attempt to create contact points where
            // the triangle would clip the box edges.

            for (TgINT iAxis = 0; iAxis < 3; ++iAxis)
            {
                C_(VECTOR,DIM)                      tvK0 = tgST0.Query_EdgePlane0().Query_Normal();
                C_(VECTOR,DIM)                      tvK1 = tgST0.Query_EdgePlane1().Query_Normal();
                C_(VECTOR,DIM)                      tvK2 = tgST0.Query_EdgePlane2().Query_Normal();

                const TYPE                          tyBX_EN0 = MATH::F_DOT( tvK0, tgBX0.Query_AxisUnit( iAxis ) );
                const TYPE                          tyBX_EN1 = MATH::F_DOT( tvK1, tgBX0.Query_AxisUnit( iAxis ) );
                const TYPE                          tyBX_EN2 = MATH::F_DOT( tvK2, tgBX0.Query_AxisUnit( iAxis ) );

                TYPE                                tyBX_EN[3];
                T_(VECTOR,DIM)                      tvS2;

                tyBX_EN[0] = 0 == (iFlag & (1<<iAxis)) ? tyBX_EN0 : -tyBX_EN0;
                tyBX_EN[1] = 0 == (iFlag & (1<<iAxis)) ? tyBX_EN1 : -tyBX_EN1;
                tyBX_EN[2] = 0 == (iFlag & (1<<iAxis)) ? tyBX_EN2 : -tyBX_EN2;

                for (TgINT iEdge = 0; iEdge < 3; ++iEdge)
                {
                    if (!tgST0.Test_Edge(iEdge) || P::ABS( tyBX_EN[iEdge] ))
                    {
                        // Ignore edge planes where the axis is near-parallel, or has been marked as non-valid.

                        continue;
                    };

                    const TYPE                          tyInt = -tyDist[iEdge] / tyBX_EN[iEdge];

                    if (tyInt < TYPE(0.0) || tyInt > TYPE(2.0)*tgBX0.Query_Extent( iAxis ))
                    {
                        // The resulting clipping point is outside of legal space.

                        continue;
                    };

                    const TYPE                          tyK0 = 0 == (iFlag & (1<<iAxis)) ? tyInt : -tyInt;

                    tvS2 = MATH::F_ADD( tvCS, MATH::F_MUL( tgBX0.Query_AxisUnit( iAxis ), tyK0 ) );

                    // If the resulting point is not contained inside of the negative triangle space - ignore it.

                    C_(VECTOR,DIM)                      tvK3 = MATH::F_SUB( tvS2, tgST0.Query_Origin() );
                    TYPE                                tyDepth = -MATH::F_DOT( tvK3, tgST0.Query_Normal() );

                    if (tyDepth < -LIMITS<TYPE>::EPSILON)
                    {
                        continue;
                    };

                    if (
                        (F_Dist( tgST0.Query_EdgePlane( (iEdge+1) % 3 ), tvS2 )) < -LIMITS<TYPE>::EPSILON ||
                        (F_Dist( tgST0.Query_EdgePlane( (iEdge+2) % 3 ), tvS2 )) < -LIMITS<TYPE>::EPSILON
                    ) {
                        continue;
                    };

                    if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
                    {
                        return (TgS_MAXCONTACTS);
                    };

                    ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

                    ptgContact->m_tvPos = tvS2;
                    ptgContact->m_tvNormal = tvNormal;
                    ptgContact->m_tyT0 = TYPE(0.0);

                    if (iIdx >= 0)
                    {
                        if (iIdx != iEdge)
                        {
                            TYPE                                tyT0, tyT1;
                            TgINT                               iCode = 0;

                            C_TgRESULT tgResult = TTgCLF_STLN<TYPE,DIM,1,0>::DO( &tyT0,&tyT1, &iCode, tgST0, tvS2,tvNormal );
                            TgASSERT(P::ABS( tyT0 ) <= LIMITS<TYPE>::EPSILON)

                            ptgContact->m_tyDepth = (TgFAILED(tgResult) || 3 != (iCode & 3) || tyT1 < TYPE(0.0)) ? TYPE(0.0) : tyT1;
                        }
                        else
                        {
                            ptgContact->m_tyDepth = TYPE(0.0);
                        };
                    }
                    else
                    {
                        ptgContact->m_tyDepth = tyDepth < TYPE(0.0) ? TYPE(0.0) : tyDepth;
                    };

                    ++ptgPacket->m_niContact;
                    TgFEBUG_COLLISION_TRIANGLE( iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED );
                };
            };
        };

        return (niContact == ptgPacket->m_niContact ? TgE_NOINTERSECT : TgS_OK);
    };

    if (tgAxS.m_iAxis >= 8)
    {
        // -- Edge/Edge Intersection -----------------------------------------------------------------------------------------------
        //   The minimal separation axis is the result of the cross product of both a triangle and box axis.

        // Construct the line segments representing the colliding edges.

        C_TgINT                             iTriIdx = (tgAxS.m_iAxis - 8) / 3;
        C_TgINT                             iBoxIdx0 = (tgAxS.m_iAxis - 8) % 3;
        TTgSEGMENT<TYPE,DIM>                tgBXE, tgCTE;
        T_(VECTOR,DIM)                      tvBXP, tvCTP;

        //  The support point function works in this case because the axis separation routine is known NOT to return back a
        // axis (normal) that is orthogonal to an existing box axis. This ensures the resulting vector is on a box edge.

        C_TgINT                             iIdx1 = (iBoxIdx0 + 1) % 3, iIdx2 = (iBoxIdx0 + 1) % 3;
        const TYPE                          tyBX1_N = MATH::F_DOT( tgBX0.Query_AxisUnit( iIdx1 ), tgAxS.m_tvNormal );
        const TYPE                          tyBX2_N = MATH::F_DOT( tgBX0.Query_AxisUnit( iIdx2 ), tgAxS.m_tvNormal );

        const TYPE                          tyK0 = tyBX1_N > TYPE(0.0) ? tgBX0.Query_Extent( iIdx1 ) : -tgBX0.Query_Extent( iIdx1 );
        const TYPE                          tyK1 = tyBX2_N > TYPE(0.0) ? tgBX0.Query_Extent( iIdx2 ) : -tgBX0.Query_Extent( iIdx2 );
        C_(VECTOR,DIM)                      tvK0 = MATH::F_MUL( tyK0, tgBX0.Query_AxisUnit( iIdx1 ) );
        C_(VECTOR,DIM)                      tvK1 = MATH::F_MUL( tyK1, tgBX0.Query_AxisUnit( iIdx2 ) );
        C_(VECTOR,DIM)                      tvK2 = MATH::F_MUL( tgBX0.Query_Extent( iBoxIdx0 ), tgBX0.Query_AxisUnit( iBoxIdx0 ) );

        T_(VECTOR,DIM)                      tvP0;

        tvP0 = MATH::F_ADD( MATH::F_ADD( tvK0, tvK1 ), MATH::F_SUB( tgBX0.Query_Origin(), tvK2 ) );

        tgBXE.Set_Origin( tvP0 );
        tgBXE.Set_DirN( MATH::F_MUL( TYPE(2.0)*tgBX0.Query_Extent( iBoxIdx0 ), tgBX0.Query_AxisUnit( iBoxIdx0 ) ) );

        tgCTE.Set_Origin( tgST0.Query_Point( iTriIdx ) );
        tgCTE.Set_DirN( tgST0.Query_Edge( iTriIdx ) );

        // Calculate the contact point based on the points of closest proximity between the two edges.

        const TYPE                          tyDistSq = F_ClosestSq( &tvBXP,&tvCTP, tgBXE,tgCTE );

        ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

        ptgContact->m_tvPos = tvBXP;
        ptgContact->m_tvNormal = tgAxS.m_tvNormal;
        ptgContact->m_tyT0 = TYPE(0.0);
        ptgContact->m_tyDepth = P::SQRT( tyDistSq );

        TgASSERT(Near_Zero( tyDistSq - tgAxS.m_tyDepth*tgAxS.m_tyDepth ));
        TgASSERT(MATH::F_DOT( tgAxS.m_tvNormal, MATH::F_SUB( tvBXP, tvCTP ) ) < TYPE(0.0));

        ++ptgPacket->m_niContact;
        TgFEBUG_COLLISION_TRIANGLE( iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED );

        return (TgS_OK);
    }

    TgASSERT(tgAxS.m_iAxis > 1)

    // -- Box Axis Intersection ----------------------------------------------------------------------------------------------------
    //  The minimal separation axis is one of the box axes.

    const TYPE                      tyAX_NM = MATH::F_DOT(tgAxS.m_tvNormal,tgST0.Query_Normal());

    if (!Near_Zero( tyAX_NM ))
    {
        //  Analyze the box vertices to see if they are contact points - this is done since contained points are not found by the
        // following algorithm (edge clipping).  Take each box vertex, project it down along the contact normal to see if it
        // intersects the triangle making a contact.

        for (TgINT iFlag = 0; iFlag < 8; ++iFlag)
        {
            const TYPE                          tyE0 = 0 != (iFlag & 1) ? tgBX0.Query_Extent0() : -tgBX0.Query_Extent0();
            const TYPE                          tyE1 = 0 != (iFlag & 2) ? tgBX0.Query_Extent1() : -tgBX0.Query_Extent1();
            const TYPE                          tyE2 = 0 != (iFlag & 4) ? tgBX0.Query_Extent2() : -tgBX0.Query_Extent2();

            T_(VECTOR,DIM)                      tvCS;

            tvCS = tgBX0.Calc_Point( tyE0,tyE1,tyE2 );

            C_(VECTOR,DIM)                      tvK0 = MATH::F_SUB( tvCS, tgST0.Query_Origin() );
            const TYPE                          tyDistN = MATH::F_DOT( tvK0, tgST0.Query_Normal() );

            if (tyDistN >= TYPE(0.0))
            {
                // The point/vertex is above the triangle plane - ignore and continue processing.

                continue;
            };

            const TYPE                          tyDepth = P::ABS(tyDistN / tyAX_NM);
            C_(VECTOR,DIM)                      tvK1 = MATH::F_MUL( tyAX_NM ? tyDepth : -tyDepth, tgAxS.m_tvNormal );
            C_(VECTOR,DIM)                      tvP0 = MATH::F_ADD( tvCS, tvK1 );

            if (!tgST0.Is_Contained( tvP0 ))
            {
                continue;
            };

            if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
            {
                return (TgS_MAXCONTACTS);
            };

            ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

            ptgContact->m_tvPos = tvCS;
            ptgContact->m_tvNormal = tgAxS.m_tvNormal;
            ptgContact->m_tyT0 = TYPE(0.0);
            ptgContact->m_tyDepth = tyDepth;

            ++ptgPacket->m_niContact;
            TgFEBUG_COLLISION_TRIANGLE( iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED );
        };
    };

    TgASSERT(!Near_One( MATH::F_DOT(tgAxS.m_tvNormal,tgST0.Query_Normal()) ))

    TTgCLIP_LIST<TYPE,DIM>              tgClipList;

    //  Need to use the special version of the clip function so that the cutting face opposite of the resultant contact surface
    // can be disabled so that the correct points on the triangle can be found.

    C_TgINT                             iFlag = ~(1 << (tgAxS.m_iAxis - 2));

    C_TgRESULT tgResult = F_Clip( &tgClipList, tgBX0, iFlag, tgST0.Query_PT() );
    TgASSERT(TgSUCCEEDED(tgResult))

    const TYPE                          tyExtent = tgBX0.Query_Extent( (tgAxS.m_iAxis - 2) % 3 );
    TYPE                                tyRadSq;
    
    tyRadSq  = tgBX0.Query_Extent0()*tgBX0.Query_Extent0();
    tyRadSq += tgBX0.Query_Extent1()*tgBX0.Query_Extent1();
    tyRadSq += tgBX0.Query_Extent2()*tgBX0.Query_Extent2();

    for (TgINT iResult = 0; iResult < tgClipList.m_niPoint; iResult++)
    {
        //  If the contact point is on an invalid edge and is not a box vertex then cull the point.

        C_(VECTOR,DIM)                      tvRelPoint = MATH::F_SUB( tgClipList.m_ptvPoint[iResult], tgBX0.Query_Origin() );

        // Box vertices are not eligible for culling.

        if (!Near_Zero( MATH::F_LSQ( tvRelPoint ) - tyRadSq ))
        {
            if (!tgST0.Test_Edge0() && F_Dist( tgST0.Query_EdgePlane0(), tgClipList.m_ptvPoint[iResult] ) < ptgPacket->m_tySnapTol)
            {
                continue;
            };

            if (!tgST0.Test_Edge1() && F_Dist( tgST0.Query_EdgePlane1(), tgClipList.m_ptvPoint[iResult] ) < ptgPacket->m_tySnapTol)
            {
                continue;
            };

            if (!tgST0.Test_Edge2() && F_Dist( tgST0.Query_EdgePlane2(), tgClipList.m_ptvPoint[iResult] ) < ptgPacket->m_tySnapTol)
            {
                continue;
            };
        };

        if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
        {
            return (TgS_MAXCONTACTS);
        };

        ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

        const TYPE                          tyDepth = tyExtent + MATH::F_DOT(tvRelPoint,tgAxS.m_tvNormal);

        ptgContact->m_tvPos = MATH::F_SUB( tgClipList.m_ptvPoint[iResult], MATH::F_MUL( tyDepth, tgAxS.m_tvNormal ) );
        ptgContact->m_tvNormal = tgAxS.m_tvNormal;
        ptgContact->m_tyT0 = TYPE(0.0);
        ptgContact->m_tyDepth = tyDepth < TYPE(0.0) ? TYPE(0.0) : tyDepth;

        ++ptgPacket->m_niContact;
        TgFEBUG_COLLISION_TRIANGLE( iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED );
    };

    return (niContact == ptgPacket->m_niContact ? TgE_NOINTERSECT : TgS_OK);
};

template TgRESULT F_Contact_Penetrate( PC_TgF4CONTACT_PACKET, CR_TgF4STRI, CR_TgF4BOX );


// ============================================================================================================================== //

// ---- F_Contact_Sweep --------------------------------------------------------------------------------------------------------- //
// Input:  tgPacket: The current series of contact points for this query-series, and contact generation parameters.
// Input:  tyPM: Current normalized time of first contact.
// Input:  bPenetrate: If the swept primitives are in penetration, if true the function will return points of penetration.
// Input:  tgST0: Space Triangle primitive
// Input:  tgBX0: Box primitive
// Input:  tgDT: A structure holding the swept primitive displacement for the entire duration of the test period
// Output: tgPacket: Contact points are added or replace the current set depending on the time comparison and given parameters
// Output: tyPM: New normalized time of first contact
// Return: Result Code
// ------------------------------------------------------------------------------------------------------------------------------ //

template <typename TYPE, int DIM>
TgRESULT F_Contact_Sweep( PC_(CONTACT_PACKET,DIM) ptgPacket, TYPE *ptyPM, CR_(STRI,DIM) tgST0, CR_(BOX,DIM) tgBX0, CR_(DELTA,DIM) tgDT )
{
    TgBLOCK_FCN_NOOBJ(ETgFAC_COLLISION, 0, ETgTEST_SWEEP, (((TgUINT)ETgTRIANGLE<<16)|(TgUINT)ETgBOX))

    TgASSERT((TgSIZE)ptgPacket->m_iStride >= sizeof( P_(CONTACT,DIM) ))
    TgASSERT(tgST0.Is_Valid() && tgBX0.Is_Valid())

    if (0 == ptgPacket->m_niMaxContact || ptgPacket->m_niContact >= ptgPacket->m_niMaxContact || NULL == ptgPacket->m_ptgContact)
    {
        return (TgE_FAIL);
    };

    TgFEBUG_COLLISION_TRIANGLE_CREATEID(iDBG_TriID, tgST1, ETgFEBUG_COLLISION_ENTERFCN);

    TTgAXIS_INFO<TYPE,DIM>              tgNFO;

    tgNFO.m_eSide = TTgAXIS_INFO<TYPE,DIM>::UNKNOWN;
    tgNFO.m_tyMinT =  LIMITS<TYPE>::MAX;

    C_TgRESULT tgResult = F_Axis_Seperation( &tgNFO, *ptyPM + ptgPacket->m_tySweepTol, tgST0, tgBX0, tgDT );

    if (TgFAILED(tgResult))
    {
        TgASSERT(TgE_NOINTERSECT == tgResult)
        return (tgResult);
    };

    // == Contact Generation ==================================================================================================== //

    TgASSERT((tgNFO.m_eSide != TTgAXIS_INFO<TYPE,DIM>::UNKNOWN))

    if (tgNFO.m_eSide == TTgAXIS_INFO<TYPE,DIM>::PENETRATED || tgNFO.m_tyMinT < TYPE(0.0))
    {
        // Pre-Penetration.

        if (*ptyPM > ptgPacket->m_tySweepTol)
        {
            ptgPacket->m_niContact = 0;
        };

        *ptyPM = TYPE(0.0);

        C_TgBOOL                            bPenetrate = TgTRUE == ptgPacket->m_bReport_Penetration;

        if (bPenetrate && (TgS_MAXCONTACTS == F_Contact_Penetrate( ptgPacket, tgST0,tgBX0 )))
        {
            return (TgE_PREPENETRATION_MAXCONTACTS);
        };

        return (TgE_PREPENETRATION);
    };

    // Make sure this contact has not occurred more than tolerance later than the current sweep time.

    if (tgNFO.m_tyMinT > *ptyPM + ptgPacket->m_tySweepTol)
    {
        return (TgE_NOINTERSECT);
    };

    // Generate the axis projection information using the returned axis vector - and then choose the pertinent values.

    C_(VECTOR,DIM)                      tvOffset = MATH::F_MUL( tgNFO.m_tyMinT, tgDT.m_tvDT );
    T_(AXIS_PROJECT,DIM)                tgP0, tgP1;
    P_(CONTACT,DIM)                     ptgContact;

    F_Axis_ProjInfo( &tgP0, tgNFO.m_tvNormal, tgST0.Query_PT() );
    F_Axis_ProjInfo( &tgP1, tgNFO.m_tvNormal, tgBX0 );

    C_TgINT                             niVertD0 = (tgNFO.m_eSide == T_(AXIS_INFO,DIM)::NEGATIVE) ? tgP0.m_niMinDepth : tgP0.m_niMaxDepth;
    PC_(VECTOR,DIM)                     atvVert0 = (tgNFO.m_eSide == T_(AXIS_INFO,DIM)::NEGATIVE) ? tgP0.m_atvMinVert : tgP0.m_atvMaxVert;
    C_TgINT                             niVertD1 = (tgNFO.m_eSide == T_(AXIS_INFO,DIM)::NEGATIVE) ? tgP1.m_niMaxDepth : tgP1.m_niMinDepth;
    PC_(VECTOR,DIM)                     atvVert1 = (tgNFO.m_eSide == T_(AXIS_INFO,DIM)::NEGATIVE) ? tgP1.m_atvMaxVert : tgP1.m_atvMinVert;
    C_(VECTOR,DIM)                      tvK0 = MATH::F_NEG( tgNFO.m_tvNormal );
    C_(VECTOR,DIM)                      tvNormal = (tgNFO.m_eSide == T_(AXIS_INFO,DIM)::NEGATIVE) ? tvK0 : tgNFO.m_tvNormal;
    C_TgINT                             iFeatureID = (tgNFO.m_eSide == T_(AXIS_INFO,DIM)::NEGATIVE) ? tgP0.m_iMinID : tgP0.m_iMaxID;

    // Based on the axis projection information - generate the appropriate contact for this axis.

    if (1 == niVertD0) //« Triangle Vertex in contact with the box.
    {
        // Check feature reduction on the triangle to make sure the vertex is valid.

        if (((iFeatureID >= 0) & (iFeatureID <= 2)) && !tgST0.Test_Point( iFeatureID ))
        {
            return (TgE_NOINTERSECT);
        };

        // Check to see if result values need to be reset because of the change in intersection time.

        if (tgNFO.m_tyMinT < *ptyPM - ptgPacket->m_tySweepTol)
        {
            ptgPacket->m_niContact = 0;
            *ptyPM = tgNFO.m_tyMinT;
        };

        ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

        ptgContact->m_tvPos = atvVert0[0];
        ptgContact->m_tvNormal = tvNormal;
        ptgContact->m_tyT0 = tgNFO.m_tyMinT;
        ptgContact->m_tyDepth = TYPE(0.0);

        ++ptgPacket->m_niContact;

        TgFEBUG_COLLISION_TRIANGLE(iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED);
        return (TgS_OK);
    };

    if (niVertD1 == 1)
    {
        // Box Vertex in contact with the triangle.

        if (tgNFO.m_tyMinT < *ptyPM - ptgPacket->m_tySweepTol)
        {
            ptgPacket->m_niContact = 0;
            *ptyPM = tgNFO.m_tyMinT;
        };

        ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

        ptgContact->m_tvPos = MATH::F_ADD( tvOffset, atvVert1[0] );
        ptgContact->m_tvNormal = tvNormal;
        ptgContact->m_tyT0 = tgNFO.m_tyMinT;
        ptgContact->m_tyDepth = TYPE(0.0);

        ++ptgPacket->m_niContact;

        TgFEBUG_COLLISION_TRIANGLE(iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED);
        return (TgS_OK);
    };

    if (niVertD0 == 2 || niVertD1 == 2) //« Box or Tri Edge Contact
    {
        // Check feature reduction on the triangle to make sure the edge is valid.

        if (((0 != (iFeatureID & 4)) & (niVertD0 == 2)) && !tgST0.Test_Edge( iFeatureID - 4 ))
        {
            return (TgE_NOINTERSECT);
        };

        T_(VECTOR,DIM)                      tvRN0 = T_(VECTOR,DIM)::ZERO, tvRN1 = T_(VECTOR,DIM)::ZERO;
        TgINT                               iResult = -1;
        T_(SEGMENT,DIM)                     tgTriE;
        TYPE                                tyT0, tyT1;

        tgTriE.Set_DirN( MATH::F_SUB( atvVert0[1], atvVert0[0] ) );
        tgTriE.Set_Origin( atvVert0[0] );

        if (niVertD1 == 4) //« Triangle Edge in contact with a Box Face
        {
            T_(RECTANGLE,DIM)                   tgBoxFace;

            tgBoxFace.Set_Origin( MATH::F_ADD( atvVert1[0], tvOffset ) );
            tgBoxFace.Set_Edge0( MATH::F_SUB( atvVert1[1], atvVert1[0] ) );
            tgBoxFace.Set_Edge1( MATH::F_SUB( atvVert1[2], atvVert1[0] ) );

            if (TgSUCCEEDED(F_Clip( &tyT0,&tyT1, tgBoxFace,tgTriE )))
            {
                tvRN0 = MATH::F_ADD( tgTriE.Query_Origin(), MATH::F_MUL( tyT0, tgTriE.Query_DirN() ) );
                tvRN1 = MATH::F_ADD( tgTriE.Query_Origin(), MATH::F_MUL( tyT1, tgTriE.Query_DirN() ) );
                iResult = MATH::F_LSQ( MATH::F_SUB( tvRN0, tvRN1 ) ) > LIMITS<TYPE>::ROOTEPSILON ? 2 : 1;
            };
        }
        else
        {
            T_(SEGMENT,DIM)                     tgBoxE;

            tgBoxE.Set_DirN( MATH::F_SUB( atvVert1[1], atvVert1[0] ) );
            tgBoxE.Set_Origin( MATH::F_ADD( tvOffset, atvVert1[0] ) );

            if (niVertD0 == 2) //« Triangle Edge in contact with a Box Edge
            {
                iResult = F_Internal_Intersect(
                    &tvRN0,&tvRN1, tgTriE.Query_Origin(),tgTriE.Query_DirN(), tgBoxE.Query_Origin(),tgBoxE.Query_DirN()
                );
            }
            else // Triangle Face in contact with a Box Edge
            {
                TgINT                               iCode;

                C_TgRESULT tgResult = F_ClipF( &tyT0,&tyT1, &iCode, tgST0,tgBoxE );
                TgASSERT(TgSUCCEEDED(tgResult))

                if (0 == (iCode & 12)) //« Check to see if both points are either invalid or reduced.
                {
                    return (TgE_NOINTERSECT);
                };

                const TYPE                          tyK0 = (0 != (iCode & 4)) ? tyT0 : tyT1;

                tvRN0 = MATH::F_ADD( tgBoxE.Query_Origin(), MATH::F_MUL( tyK0, tgBoxE.Query_DirN() ) );
                tvRN1 = MATH::F_ADD( tgBoxE.Query_Origin(), MATH::F_MUL( tyT1, tgBoxE.Query_DirN() ) );

                iResult = (12 == (iCode & 12)) && MATH::F_LSQ( MATH::F_SUB( tvRN0, tvRN1 ) ) > LIMITS<TYPE>::ROOTEPSILON ? 2 : 1;
            };
        };

        if (tgNFO.m_tyMinT < *ptyPM - ptgPacket->m_tySweepTol)
        {
            ptgPacket->m_niContact = 0;
            *ptyPM = tgNFO.m_tyMinT;
        };

        switch (iResult) {
            case 2:
                ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

                ptgContact->m_tvPos = tvRN1;
                ptgContact->m_tvNormal = tvNormal;
                ptgContact->m_tyT0 = tgNFO.m_tyMinT;
                ptgContact->m_tyDepth = TYPE(0.0);

                ++ptgPacket->m_niContact;

            case 1:
                if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
                {
                    return (TgS_MAXCONTACTS);
                };

                ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);

                ptgContact->m_tvPos = tvRN0;
                ptgContact->m_tvNormal = tvNormal;
                ptgContact->m_tyT0 = tgNFO.m_tyMinT;
                ptgContact->m_tyDepth = TYPE(0.0);

                ++ptgPacket->m_niContact;
                TgFEBUG_COLLISION_TRIANGLE(iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED);

                break;

            default:
                TgASSERT(TgFALSE);
        };

        return (iResult > 0 ? TgS_OK : TgE_NOINTERSECT);
    };

    // Triangle Face in contact with a Box Face

    // Construct a centroid orientated rectangle definition

    C_(VECTOR,DIM)                      tvE0 = MATH::F_SUB( atvVert1[1], atvVert1[0] );
    C_(VECTOR,DIM)                      tvE1 = MATH::F_SUB( atvVert1[2], atvVert1[0] );
    const TYPE                          tyE0_E0 = MATH::F_LSQ( tvE0 );
    const TYPE                          tyE1_E1 = MATH::F_LSQ( tvE1 );
    C_(VECTOR,DIM)                      tvS0 = MATH::F_ADD( atvVert1[0], tvOffset );

    TgINT                               iCode, niPoint = 0;
    T_(VECTOR,DIM)                      atvContact[12];
    TYPE                                tyT0, tyT1;

    //  Analyze the valid triangle vertices to see if they are contact points - this is done since contained points are not
    // found by the following algorithm (box edge clipping).

    for (TgINT iVert = 0; iVert < 3; ++iVert)
    {
        // Check to see if the triangle point is contained in the box face.

        C_(VECTOR,DIM)                      tvDS = MATH::F_SUB( tgST0.Query_Point( iVert ), tvS0 );
        const TYPE                          tyDS_E0 = MATH::F_DOT(tvDS,tvE0);
        const TYPE                          tyDS_E1 = MATH::F_DOT(tvDS,tvE1);

        if (!tgST0.Test_Point( iVert ) || tyDS_E0 < TYPE(0.0) || tyDS_E1 < TYPE(0.0) || tyDS_E0 > tyE0_E0 || tyDS_E1 > tyE1_E1)
        {
            continue;
        };

        atvContact[niPoint++] = tgST0.Query_Point( iVert );
    };

    TTgCLF_STLN<TYPE,DIM,1,1>::DO( &tyT0,&tyT1, &iCode, tgST0, tvS0,tvE0 );
    if (0 != (iCode & 4))
    {
        atvContact[niPoint++] = MATH::F_ADD( tvS0, MATH::F_MUL( tyT0, tvE0 ) );
    };

    if (0 != (iCode & 8))
    {
        atvContact[niPoint++] = MATH::F_ADD( tvS0, MATH::F_MUL( tyT1, tvE0 ) );
    };

    TTgCLF_STLN<TYPE,DIM,1,1>::DO( &tyT0,&tyT1, &iCode, tgST0, tvS0,tvE1 );
    if (0 != (iCode & 4))
    {
        atvContact[niPoint++] = MATH::F_ADD( tvS0, MATH::F_MUL( tyT0, tvE1 ) );
    };

    if (0 != (iCode & 8))
    {
        atvContact[niPoint++] = MATH::F_ADD( tvS0, MATH::F_MUL( tyT1, tvE1 ) );
    };

    TTgCLF_STLN<TYPE,DIM,1,1>::DO( &tyT0,&tyT1, &iCode, tgST0, MATH::F_ADD( tvS0, tvE1 ), tvE0 );
    if (0 != (iCode & 4))
    {
        atvContact[niPoint++] = MATH::F_ADD( MATH::F_ADD( tvS0, tvE1 ), MATH::F_MUL( tyT0, tvE0 ) );
    };

    if (0 != (iCode & 8))
    {
        atvContact[niPoint++] = MATH::F_ADD( MATH::F_ADD( tvS0, tvE1 ), MATH::F_MUL( tyT1, tvE0 ) );
    };

    TTgCLF_STLN<TYPE,DIM,1,1>::DO( &tyT0,&tyT1, &iCode, tgST0, MATH::F_ADD( tvS0, tvE0 ), tvE1 );
    if (0 != (iCode & 4))
    {
        atvContact[niPoint++] = MATH::F_ADD( MATH::F_ADD( tvS0, tvE0 ), MATH::F_MUL( tyT0, tvE1 ) );
    };

    if (0 != (iCode & 8))
    {
        atvContact[niPoint++] = MATH::F_ADD( MATH::F_ADD( tvS0, tvE0 ), MATH::F_MUL( tyT1, tvE1 ) );
    };

    TgASSERT( niPoint > 0 );

    if (tgNFO.m_tyMinT < *ptyPM - ptgPacket->m_tySweepTol)
    {
        ptgPacket->m_niContact = 0;
        *ptyPM = tgNFO.m_tyMinT;
    };

    for (TgINT iIdx = 0; iIdx < niPoint; ++iIdx)
    {
        TgINT                               iTest = 0;

        for (; iTest < iIdx && !Near_Zero( MATH::F_LSQ( MATH::F_SUB( atvContact[iTest], atvContact[iIdx] ) ) ); ++iTest);

        if (iTest != iIdx)
        {
            continue;
        };

        if (ptgPacket->m_niContact >= ptgPacket->m_niMaxContact)
        {
            return (TgS_MAXCONTACTS);
        };

        ptgContact = (P_(CONTACT,DIM))((PC_TgUINT08)ptgPacket->m_ptgContact + ptgPacket->m_niContact*ptgPacket->m_iStride);
        ptgContact->m_tvPos = atvContact[iIdx];
        ptgContact->m_tvNormal = tvNormal;
        ptgContact->m_tyT0 = tgNFO.m_tyMinT;
        ptgContact->m_tyDepth = TYPE(0.0);

        ++ptgPacket->m_niContact;
    };

    TgFEBUG_COLLISION_TRIANGLE(iDBG_TriID, ETgFEBUG_COLLISION_COLLIDED);
    return (TgS_OK);
};

template TgRESULT F_Contact_Sweep( PC_TgF4CONTACT_PACKET, P_TgFLOAT32, CR_TgF4STRI, CR_TgF4BOX, CR_TgF4DELTA );


// ============================================================================================================================== //

}; // END COL //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}; // END TGS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////