Home

Resume

Blog

Teikitu


/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/*  »Project«   Teikitu Gaming System (TgS) (∂)
    »File«      TgS Collision - F - Cylinder-Capsule.c_inc
    »Keywords«  Collision;Distance;Closest;Intersect;Penetrate;Sweep;Cylinder;Capsule;
    »Author«    Andrew Aye (EMail: mailto:andrew.aye@gmail.com, Web: http://www.andrewaye.com)
    »Version«   4.51 / »GUID« A9981407-3EC9-42AF-8B6F-8BE6DD919615                                                                                                        */
/*   -------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/*  Copyright: © 2002-2017, 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:
        Redistribution of source code must retain this copyright notice, this list of conditions and the following disclaimers.
        Redistribution 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.
    The name of the author may not 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".                                                   */
/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* == Collision ========================================================================================================================================================= */

/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Functions                                                                                                                                                  */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

static TgBOOL                               V(tgCO_F_CP_Axis_Seperation_CY)( V(PC_STg2_CO_Axis_Result), V(CPC_TgTUBE), V(CPC_TgTUBE) );
static TgRESULT                             V(tgCO_F_CP_Penetrate_CylAxis_CY)( V(PC_STg2_CO_Packet), V(CPC_STg2_CO_Axis_Result), V(CPC_TgTUBE), V(CPC_TgTUBE) );




/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  Public Functions                                                                                                                                                      */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

/* ---- V(tgCO_F_CP_Penetrate_CY) --------------------------------------------------------------------------------------------------------------------------------------- */
/* Input:  tgPacket: The current series of contact points for this query-series, and contact generation parameters.                                                       */
/* Input:  psCP0: Capsule primitive                                                                                                                                       */
/* Input:  psCY0: Cylinder primitive - contact points are generated on this primitive                                                                                     */
/* Output: tgPacket: Points of penetration between the two primitives are added to it                                                                                     */
/* Return: Result Code                                                                                                                                                    */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT V(tgCO_F_CP_Penetrate_CY)(V(PC_STg2_CO_Packet) psPacket, V(CPC_TgTUBE) psCP0, V(CPC_TgTUBE) psCY0)
{
    V(P_STg2_CO_Contact)                psContact;
    V(STg2_CO_Axis_Result)              sAxS;

    TgPARAM_CHECK( V(tgGM_TB_Is_Valid)(psCY0) && V(tgGM_TB_Is_Valid)(psCP0) );

    if (0 == psPacket->m_niMaxContact || psPacket->m_niContact >= psPacket->m_niMaxContact || nullptr == psPacket->m_psContact)
    {
        return (KTgE_FAIL);
    }

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

    if (!V(tgCO_F_CP_Axis_Seperation_CY)(&sAxS, psCP0, psCY0))
    {
        return (KTgE_NO_INTERSECT);
    };

    TgERROR( F(tgCM_NR1)(V(F_LSQ)(&sAxS.m_vNormal)) && sAxS.m_fDepth >= MKL(0.0) );

    /* == Contact Generation == */

    psContact = psPacket->m_psContact + psPacket->m_niContact;

    switch (sAxS.m_iAxis)
    {
        case 7: /* -- Axis: Rim -- */

            psContact->m_vS0 = sAxS.m_vPoint;
            psContact->m_vN0 = sAxS.m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = sAxS.m_fDepth;

            ++psPacket->m_niContact;

            return (KTgS_OK);

        case 2:
        { /* -- Axis: Perpendicular to Axis of Cylinder -- */

            V(C_TgVEC)                          vK0 = V(tgGM_CP_Support_Point)(psCP0, &sAxS.m_vNormal);
            V(C_TgVEC)                          vK1 = V(F_SUB)(&vK0, &psCY0->m.m.vOrigin);
            const TYPE                          fTest = V(F_DOT)(&vK1, &sAxS.m_vNormal) + psCY0->m_fRadius;
            V(C_TgVEC)                          vK2 = V(F_MUL_SV)(fTest, &sAxS.m_vNormal);

            if (fTest <= MKL(0.0))
            {
                TgWARN_CO(TgT("Separation Axis found with no contacts generated.\n" ));
                return (KTgE_NO_INTERSECT);
            };

            psContact->m_vS0 = V(F_SUB)(&vK0, &vK2);
            psContact->m_vN0 = sAxS.m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = sAxS.m_fDepth;

            ++psPacket->m_niContact;

            return (KTgS_OK);

        };

        case 1:
        { /* -- Axis: Cylinder Axis -- */

            return (V(tgCO_F_CP_Penetrate_CylAxis_CY)(psPacket, &sAxS, psCP0, psCY0));

        };

        default:
            TgWARN_CO(TgT("Should not be able to reach this location.\n" ));
            return (KTgE_FAIL);
    };
}




/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Functions                                                                                                                                                  */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

/* ---- V(tgCO_F_CP_Axis_Seperation_CY) --------------------------------------------------------------------------------------------------------------------------------- */
/* Input:  psCP0: Capsule primitive                                                                                                                                       */
/* Input:  psCY0: Cylinder primitive                                                                                                                                      */
/* Output: sAxS: Structure holds the resulting axis separation information necessary to create a contact set.                                                             */
/* Return: False if a separating axis exists, true otherwise                                                                                                              */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
static TgBOOL V(tgCO_F_CP_Axis_Seperation_CY)(V(PC_STg2_CO_Axis_Result) psAxS, V(CPC_TgTUBE) psCP0, V(CPC_TgTUBE) psCY0)
{
    TYPE                                fDistSq, fA0, fA1;

    /* Construct the difference vector between the two origins and calculate the reference frame projections. */

    V(C_TgVEC)                          vDS = V(F_SUB)(&psCY0->m.m.vOrigin, &psCP0->m.m.vOrigin);
    const TYPE                          fDS_A1 = V(F_DOT)(&vDS, &psCY0->m.m.vU_HAX);
    const TYPE                          fRS = psCP0->m_fRadius + psCY0->m_fRadius;
    const TYPE                          fABS_A0_A1 = F(tgPM_ABS)(V(F_DOT)(&psCP0->m.m.vU_HAX, &psCY0->m.m.vU_HAX));
    const TYPE                          fABS_A0xA1 = F(tgPM_SQRT)(MKL(1.0) - F(tgCM_MIN)(MKL(1.0), fABS_A0_A1*fABS_A0_A1));

    psAxS->m_fDepth = -F(KTgMAX);

    /* == Axis Separation Tests == */

    {
        /* -- Axis: Parallel to Axis of Cylinder -- */

        const TYPE fTest = F(tgPM_ABS)(fDS_A1) - (psCY0->m_fExtent + psCP0->m_fRadius + psCP0->m_fExtent*fABS_A0_A1);

        TgERROR( V(tgGM_TB_Is_Valid)(psCP0) && V(tgGM_TB_Is_Valid)(psCY0) );

        if (fTest > MKL(0.0))
        {
            return (TgFALSE);
        };

        if (fTest > psAxS->m_fDepth)
        {
            psAxS->m_vNormal = fDS_A1 > MKL(0.0) ? psCY0->m.m.vU_HAX : V(F_NEG)(&psCY0->m.m.vU_HAX);
            psAxS->m_fDepth = fTest;
            psAxS->m_iAxis = 1;
        };
    }

    fDistSq = V(tgCO_F_SG_ParamSq_SG)(&fA0, &fA1, &psCP0->m_sAX, &psCY0->m_sAX);

    if (fDistSq > fRS*fRS)
    {
        return (TgFALSE);
    }

    {
        V(TgVEC)                            vMinDirN;

        V(C_TgVEC)                          vK0 = V(F_MUL_SV)(fA0, &psCP0->m_sAX.m_vDirN);
        V(C_TgVEC)                          vK1 = V(F_MUL_SV)(fA1, &psCY0->m_sAX.m_vDirN);
        V(C_TgVEC)                          vMin_CP0 = V(F_ADD)(&psCP0->m_sAX.m_vOrigin, &vK0);
        V(C_TgVEC)                          vMin_CY0 = V(F_ADD)(&psCY0->m_sAX.m_vOrigin, &vK1);

        if (fDistSq > F(KTgEPS))
        {
            TYPE                                fT0, fT1, fT2, fT3;

            V(C_TgVEC)                          vK2 = V(F_SUB)(&vMin_CY0, &vMin_CP0);

            vMinDirN = V(F_NORM)(&vK2);

            /* -- Axis: Direction Between Points of Closest Proximity -- */

            V(tgGM_CP_Project)(&fT0, &fT1, psCP0, &vK2);
            V(tgGM_CY_Project)(&fT2, &fT3, psCY0, &vK2);

            if (fT1 - fT2 < MKL(0.0) || fT3 - fT0 < MKL(0.0))
            {
                return (TgFALSE);
            }
            else
            {
                const TYPE                          fTest = fT2 - fT1;

                if (fTest > psAxS->m_fDepth)
                {
                    psAxS->m_vPoint = vMin_CY0;
                    psAxS->m_vNormal = vMinDirN;
                    psAxS->m_fDepth = fTest;
                    psAxS->m_iAxis = 2;
                };
            };
        }
        else
        {
            TYPE                                fT0;

            vMinDirN = V(F_NORM_LEN)(&fT0, &vDS);

            if (F(tgCM_NR0)(fT0))
            {
                /* That's it - throw in the towel, I give up - these primitives are just too penetrated to care which axis is used. */
                return (TgTRUE);
            };
        };

        {
            /* -- Axis: Perpendicular to Axis of Cylinder -- */

            const TYPE                          fK2 = V(F_DOT)(&vMinDirN, &psCY0->m.m.vU_Basis0);
            const TYPE                          fK3 = V(F_DOT)(&vMinDirN, &psCY0->m.m.vU_Basis1);
            V(C_TgVEC)                          vK2 = V(F_MUL_SV)(fK2, &psCY0->m.m.vU_Basis0);
            V(C_TgVEC)                          vK3 = V(F_MUL_SV)(fK3, &psCY0->m.m.vU_Basis1);
            V(C_TgVEC)                          vK4 = V(F_ADD)(&vK2, &vK3);
            V(C_TgVEC)                          vK5 = V(F_NORM)(&vK4);
            const TYPE                          fK4 = F(tgPM_ABS)(V(F_DOT)(&vK5, &psCP0->m.m.vU_HAX));
            const TYPE                          fK5 = F(tgPM_ABS)(V(F_DOT)(&vK5, &vDS));

            const TYPE fTest = fK5 - (psCY0->m_fRadius + fK4*psCP0->m_fExtent + psCP0->m_fRadius);

            if (fTest > MKL(0.0))
            {
                return (TgFALSE);
            };

            if (fTest > psAxS->m_fDepth)
            {
                psAxS->m_vPoint = vMin_CP0;
                psAxS->m_vNormal = vK5;
                psAxS->m_fDepth = fTest;
                psAxS->m_iAxis = 2;
            };
        }


        if (!F(tgCM_NR1)(fABS_A0_A1) && !F(tgCM_NR0)(fABS_A0xA1))
        {
            /* -- Axis: Perpendicular to Axis of Capsule -- */

            const TYPE                          fK2 = V(F_DOT)(&vMinDirN, &psCP0->m.m.vU_Basis0);
            const TYPE                          fK3 = V(F_DOT)(&vMinDirN, &psCP0->m.m.vU_Basis1);
            V(C_TgVEC)                          vK2 = V(F_MUL_SV)(fK2, &psCP0->m.m.vU_Basis0);
            V(C_TgVEC)                          vK3 = V(F_MUL_SV)(fK3, &psCP0->m.m.vU_Basis1);
            V(C_TgVEC)                          vK4 = V(F_ADD)(&vK2, &vK3);
            V(C_TgVEC)                          vK5 = V(F_NORM)(&vK4);
            const TYPE                          fK4 = F(tgPM_ABS)(V(F_DOT)(&vK5, &psCY0->m.m.vU_HAX));
            const TYPE                          fAx_C10 = V(F_DOT)(&vK5, &psCY0->m.m.vU_Basis0);
            const TYPE                          fAx_C11 = V(F_DOT)(&vK5, &psCY0->m.m.vU_Basis1);
            const TYPE                          fK5 = F(tgPM_SQRT)(fAx_C10*fAx_C10 + fAx_C11*fAx_C11);
            const TYPE                          fK6 = F(tgPM_ABS)(V(F_DOT)(&vK5, &vDS));

            const TYPE fTest = fK6 - (psCP0->m_fRadius + fK4*psCY0->m_fExtent + fK5*psCY0->m_fRadius);

            if (fTest > MKL(0.0))
            {
                return (TgFALSE);
            };

            if (fTest > psAxS->m_fDepth)
            {
                psAxS->m_vPoint = vMin_CY0;
                psAxS->m_vNormal = vK5;
                psAxS->m_fDepth = fTest;
                psAxS->m_iAxis = 2;
            };
        };

        /* !! VOODOO MAGIC TIME !! - Red Red Wine, Drink a lot of it before proceeding, I'm telling you man - get snookered. */
        /* Need to try to test the rim cases - <shot> */

        if (F(tgCM_NR1)(fA1) || F(tgCM_NR0)(fA1))
        {
            V(TgVEC)                            vCI0, vSG0;
            V(TgCIRCLE)                         sCI0;

            V(tgGM_CI_Init)(
                &sCI0, &psCY0->m.m.vU_Basis0, &psCY0->m.m.vU_HAX, &psCY0->m.m.vU_Basis1, &vMin_CY0, psCY0->m_fRadius);

            fDistSq = V(tgCO_F_CI_ClosestSq_SG)(&vCI0, &vSG0, &sCI0, &psCP0->m_sAX);

            if (fDistSq < MKL(0.0))
            {
                return (TgTRUE);
            }
            else
            {
                TYPE                                fT0, fT1, fT2, fT3;

                V(C_TgVEC)                          vK2 = V(F_SUB)(&vCI0, &vSG0);
                V(C_TgVEC)                          vK3 = V(F_NORM)(&vK2);

                V(tgGM_CP_Project)(&fT0, &fT1, psCP0, &vK3);
                V(tgGM_CY_Project)(&fT2, &fT3, psCY0, &vK3);

                if (fT1 - fT2 < MKL(0.0) || fT3 - fT0 < MKL(0.0))
                {
                    return (TgFALSE);
                }
                else
                {
                    const TYPE                          fTest = fT2 - fT1;

                    if (fTest > psAxS->m_fDepth)
                    {
                        psAxS->m_vPoint = vCI0;
                        psAxS->m_vNormal = vK3;
                        psAxS->m_fDepth = fTest;
                        psAxS->m_iAxis = 3;
                    };
                };
            };
        };
    };

    return (TgTRUE);
}


/* ---- V(tgCO_F_CP_Penetrate_CylAxis_CY) ------------------------------------------------------------------------------------------------------------------------------- */
/* Input:  tgPacket: The current series of contact points for this query-series, and contact generation parameters.                                                       */
/* Input:  sAxS: Structure holding the resulting axis separation information necessary to create a contact set.                                                           */
/* Input:  psCP0: Capsule primitive                                                                                                                                       */
/* Input:  psCY0: Cylinder primitive - contact points are generated on this primitive                                                                                     */
/* Output: tgPacket: Points of penetration between the two primitives are added to it                                                                                     */
/* Return: Result Code                                                                                                                                                    */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
static TgRESULT V(tgCO_F_CP_Penetrate_CylAxis_CY)( V(PC_STg2_CO_Packet) psPacket, V(CPC_STg2_CO_Axis_Result) psAxS, V(CPC_TgTUBE) psCP0, V(CPC_TgTUBE) psCY0 )
{
    /* (1) Capsule is contained within the infinite projection of the cylinder (tube) */
    /* (2) Capsule axis termination is contained but also passes through the cylinder (tube). */
    /* (3) Capsule passes through-and-through the cylinder (tube) */
    /* (4) Capsule axis is entirely outside of the cylinder - the only intersecting part is the capsule cap. */

    V(P_STg2_CO_Contact)                psContact;
    TYPE                                fT0, fT1;
    V(TgSEGMENT)                        sSG0;

    C_TgSINT32                          niContact = psPacket->m_niContact;

    V(C_TgVEC)                          vK0 = V(F_MUL_SV)(psCY0->m_fExtent, &psAxS->m_vNormal);
    V(C_TgVEC)                          vK1 = V(F_SUB)(&psCY0->m.m.vOrigin, &vK0);
    const TYPE                          fAX_N = V(F_DOT)(&psCP0->m.m.vU_HAX, &psAxS->m_vNormal);
    const TYPE                          fUseBX = F(tgPM_ABS)(fAX_N) - (MKL(1.0) - F(KTgEPS));

    {
        /* Attempt to create a contact point at the lowest point on the capsule. */

        V(C_TgVEC)                          vK2 = V(F_MUL_SV)(F(tgPM_FSEL)(fAX_N, MKL(1.0), MKL(-1.0)), &psCP0->m_vHAX);
        V(C_TgVEC)                          vK3 = V(F_ADD)(&psCP0->m.m.vOrigin, &vK2);
        V(C_TgVEC)                          vK4 = V(F_SUB)(&vK3, &vK1);

        const TYPE                          fK0 = V(F_DOT)(&vK4, &psAxS->m_vNormal);

        if (fK0 > MKL(0.0) && V(tgGM_CY_Is_Contained)(psCY0, &vK3))
        {
            V(C_TgVEC)                          vK5 = V(F_MUL_SV)(psCP0->m_fRadius, &psAxS->m_vNormal);

            /* Capsule termination is a contact point ([Case: 1,2]) */

            psContact = psPacket->m_psContact + psPacket->m_niContact;

            psContact->m_vS0 = V(F_ADD)(&vK3, &vK5);
            psContact->m_vN0 = psAxS->m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = fK0;

            ++psPacket->m_niContact;
        };
    };

    {
        /* Construct a capsule axis that is directed for maximum penetration into the cylinder. */
        V(C_TgVEC)                          vK2 = V(F_MUL_SV)(fAX_N, &psCP0->m.m.vU_HAX);
        V(C_TgVEC)                          vK3 = V(F_SUB)(&psAxS->m_vNormal, &vK2); /*vT0 */

        const TYPE                          fX = F(tgPM_FSEL)(fUseBX, psCP0->m.m.vU_Basis0.m.x, vK3.m.x);
        const TYPE                          fY = F(tgPM_FSEL)(fUseBX, psCP0->m.m.vU_Basis0.m.y, vK3.m.y);
        const TYPE                          fZ = F(tgPM_FSEL)(fUseBX, psCP0->m.m.vU_Basis0.m.z, vK3.m.z);

        V(C_TgVEC)                          vK4 = V(FS_SETV)(fX, fY, fZ);
        V(C_TgVEC)                          vK5 = V(F_NORM)(&vK4); /*vX0 */
        V(C_TgVEC)                          vK6 = V(F_MUL_SV)(psCP0->m_fRadius, &vK5);
        V(C_TgVEC)                          vK7 = V(F_ADD)(&psCP0->m_sAX.m_vOrigin, &vK6);

        V(tgGM_SG_Set_Origin)(&sSG0, &vK7);
        V(tgGM_SG_Set_DirN)(&sSG0, &psCP0->m_sAX.m_vDirN);
    }

    /* Translate the capsule axis to the line of deepest penetration along the capsule, and then clip it to the cylinder. */

    if (V(tgCO_F_CY_Clip_Param_SG)(&fT0, &fT1, psCY0, &sSG0) >= 0)
    {
        V(C_TgVEC)                          vK2 = V(F_MUL_SV)(fT0, &psCP0->m_sAX.m_vDirN);
        V(C_TgVEC)                          vK3 = V(F_ADD)(&sSG0.m_vOrigin, &vK2);
        V(C_TgVEC)                          vK4 = V(F_SUB)(&vK3, &vK1);
        const TYPE                          fK0 = V(F_DOT)(&vK4, &psAxS->m_vNormal);

        V(C_TgVEC)                          vK5 = V(F_MUL_SV)(fT1, &psCP0->m_sAX.m_vDirN);
        V(C_TgVEC)                          vK6 = V(F_ADD)(&sSG0.m_vOrigin, &vK5);
        V(C_TgVEC)                          vK7 = V(F_SUB)(&vK6, &vK1);
        const TYPE                          fK1 = V(F_DOT)(&vK7, &psAxS->m_vNormal);

        if (fK0 > MKL(0.0) && niContact == psPacket->m_niContact)
        {
            if (psPacket->m_niContact >= psPacket->m_niMaxContact)
            {
                return (KTgE_MAX_CONTACTS);
            };

            /* Capsule termination is a contact point ([Case: 3]) */

            psContact = psPacket->m_psContact + psPacket->m_niContact;

            psContact->m_vS0 = vK3;
            psContact->m_vN0 = psAxS->m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = fK0;

            ++psPacket->m_niContact;
        };

        /* Similar to above, and it must not be sufficiently divergent from the first point (prevent two-point tangents) */

        if (fK1 > MKL(0.0) && !F(tgCM_NR0)(fT0 - fT1))
        {
            if (psPacket->m_niContact >= psPacket->m_niMaxContact)
            {
                return (KTgE_MAX_CONTACTS);
            };

            /* Capsule termination is a contact point ([Case: 1,2,3]) */

            psContact = psPacket->m_psContact + psPacket->m_niContact;

            psContact->m_vS0 = vK6;
            psContact->m_vN0 = psAxS->m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = fK1;

            ++psPacket->m_niContact;
        };

        return (niContact != psPacket->m_niContact ? KTgS_OK : KTgE_NO_INTERSECT);
    };

    /* The capsule contact line is outside of the cylinder.  Attempt to intersect the cylinder with the capsule. ([Case: 4]) */

    {
        /* One of the extremes of the cylinder rim must be penetrated into the capsule.  Therefore use math. */
        V(C_TgVEC)                          vK2 = V(F_MUL_SV)(fAX_N, &psAxS->m_vNormal);
        V(C_TgVEC)                          vK3 = V(F_SUB)(&psCP0->m.m.vU_HAX, &vK2);
        V(C_TgVEC)                          vK4 = V(F_MUL_SV)(F(tgPM_FSEL)(fAX_N, MKL(-1.0), MKL(-1.0)), &vK3);
        V(C_TgVEC)                          vK5 = V(F_NORM)(&vK4);
        V(C_TgVEC)                          vK6 = V(F_MUL_SV)(psCY0->m_fRadius, &vK5);
        V(C_TgVEC)                          vT0 = V(F_ADD)(&vK3, &vK6);
        V(C_TgVEC)                          vK7 = V(F_SUB)(&vT0, &vK3);
        V(C_TgVEC)                          vK8 = V(F_NORM_LEN)(&fT0, &vK7);
        const TYPE                          fK0 = V(F_DOT)(&vK8, &psAxS->m_vNormal) * (psCP0->m_fRadius - fT0);
        V(C_TgVEC)                          vT1 = V(F_SUB)(&vK3, &vK6);
        V(C_TgVEC)                          vK9 = V(F_SUB)(&vT1, &vK3);
        V(C_TgVEC)                          vKA = V(F_NORM_LEN)(&fT1, &vK9);
        const TYPE                          fK1 = V(F_DOT)(&vKA, &psAxS->m_vNormal) * (psCP0->m_fRadius - fT1);

        if (fK0 > MKL(0.0))
        {
            /* Capsule termination is a contact point ([Case: 3]) */

            psContact = psPacket->m_psContact + psPacket->m_niContact;

            psContact->m_vS0 = vT0;
            psContact->m_vN0 = psAxS->m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = fK0;

            ++psPacket->m_niContact;
        };

        if (fK1 > MKL(0.0))
        {
            /* Capsule termination is a contact point ([Case: 3]) */

            psContact = psPacket->m_psContact + psPacket->m_niContact;

            psContact->m_vS0 = vT0;
            psContact->m_vN0 = psAxS->m_vNormal;
            psContact->m_fT0 = MKL(0.0);
            psContact->m_fDepth = fK1;

            ++psPacket->m_niContact;
        };

        return (niContact != psPacket->m_niContact ? KTgS_OK : KTgE_NO_INTERSECT);
    };
}