Home

Resume

Blog

Teikitu


// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //
//  »Project«   Teikitu Gaming System (TgS) (∂)
//  »File«      TgS Collision - F - Sphere-Sphere.c_inc
//  »Author«    Andrew Aye (EMail: mailto:andrew.aye@gmail.com, Web: http://www.andrewaye.com)
//  »Version«   4.0
//  »Keywords«  Collision;Distance;Closest;Intersect;Penetrate;Sweep;Sphere;
// ------------------------------------------------------------------------------------------------------------------------------ //
//  Copyright: © 2002-2010, 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".
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //

/* Let S0 and S1 represent two spheres, with radius R0, R1, centers C0,C1 and velocities V0, V1 respectively./
// W.O.L.O.G. Perform the following calculations within S0 frame of reference.
// Assume the frame of reference in non-rotational (only translation).
//
// SWEEP TEST
//
// As with all sweep tests, an arbitrary body is chosen as the principal reference frame so that only the relative
// motion of one of the bodies is relevant.  The solution is as follows,
//  (1) Create a line (RV) using the centre of the second sphere and the vector of relative motion.
//  (2) Form a right-sided triangle.
//      (a) The hypotenuse is the sum of the sphere radii.
//      (b) The known side is the line from the centre of the first sphere to the closest point on line RV.
//      (c) The length of the remaining side (along RV) is the distance from the point of closest proximity and the
//          point of first contact between the two spheres.
//
//                              .(C0)
//                             /|\
//                            / | \
//                           /  |  \
//                          /   |   \
//                         /    |    \
//                        /(R0) |     \(R0)
//          (R1)/    (R1)/      |
//             /        /       |
//            /        /        |
//           .--------.---------.--------------> (RV)
//          (C1)     (P1)      (P0)
//
// Let RV = V1 - V0, the relative velocity of S1 in the frame of reference of S0
// Let RV_N be the normalized RV vector, and RV_L the length of the original RV vector
// Let DS = C1 - C0, the vector joining the two sphere centers
// Let RS = R0+R1, the sum of the radii of the two spheres
// 
// Test 1: Pre penetration - The spheres are in contact if DS•DS < RS•RS
// Test 2: Movement - RV_L ~= 0, no contact takes place
// Test 3: Parallel - ψ = DS•RV_N
//   Contact can not occur under the following conditions:
//     [ψ > 0]          - Second sphere is moving away from the first sphere
//     [ψ < -(RV_L+RS)] - Motion is insufficient for them to come within proximity
// Test 4: Perpendicular - φ = DS - ψ•RV_N
//   Contact can not occur under the following conditions:
//     [φ•φ > RS•RS] - The sphere path does not bring them within proximity
// Result: Υ = RS•RS - φ•φ, ζ = ψ + √Υ
//   [ζ < RV_L] - The two sphere's contact at (ζ / RV_L) of the complete path of motion.
*/ 

// == Collision ================================================================================================================= //

// ---- tgCO_F_Closest_SP_SP ---------------------------------------------------------------------------------------------------- //
// Input:  psSP0,psSP1: Sphere primitives
// Output: vSP0,vSP1: Point of closest proximity on the sphere #1 and #2 respectively
// Return: Minimal distance between the two primitives or negative type max if they intersect or are invalid.
// ------------------------------------------------------------------------------------------------------------------------------ //
TYPE V(tgCO_F_Closest_SP_SP)( V(PCU_TgVEC) pvSP0, V(PCU_TgVEC) pvSP1, V(CPCU_TgSPHERE) psSP0, V(CPCU_TgSPHERE) psSP1 )
{
    TYPE                                fDS;
    V(C_TgVEC)                          vDS = V(F_SUB_VV)( &psSP1->m_vOrigin, &psSP0->m_vOrigin );
    V(C_TgVEC)                          vDN = V(F_NORM_LEN)( &fDS, &vDS );
    const TYPE                          fSumRad = psSP0->m_fRadius + psSP1->m_fRadius;

    TgASSERT(V(tgGM_Is_Valid_SP)( psSP0 ) && V(tgGM_Is_Valid_SP)( psSP1 ));

    if (fDS <= fSumRad)
    {
        return (-F(KTgMAX));
    }
    else
    {
        V(C_TgVEC)                          vK0 = V(F_MUL_SV)( psSP0->m_fRadius, &vDN );
        V(C_TgVEC)                          vK1 = V(F_MUL_SV)( psSP1->m_fRadius, &vDN );

        *pvSP0 = V(F_ADD_VV)( &psSP0->m_vOrigin, &vK0 );
        *pvSP1 = V(F_SUB_VV)( &psSP1->m_vOrigin, &vK1 );

        return (fDS - fSumRad);
    };
}


// ---- tgCO_F_Penetrate_SP_SP -------------------------------------------------------------------------------------------------- //
// Input:  tgPacket: The current series of contact points for this query-series, and contact generation parameters.
// Input:  psSP0,psSP1: Sphere primitives - contact points are generated on sphere #2 (psSP1)
// Output: tgPacket: Points of penetration between the two primitives are added to it
// Return: Result Code
// ------------------------------------------------------------------------------------------------------------------------------ //
TgRESULT V(tgCO_F_Penetrate_SP_SP)( V(PCU_STg2_CO_Packet) psPacket, V(CPCU_TgSPHERE) psSP0, V(CPCU_TgSPHERE) psSP1 )
{
    TgASSERT_PARAM((TgSIZE)psPacket->m_iStride >= sizeof( V(P_STg2_CO_Contact) ));
    TgASSERT_PARAM(V(tgGM_Is_Valid_SP)( psSP1 ) && V(tgGM_Is_Valid_SP)( psSP0 ));

    if (0 == psPacket->m_niMaxContact || psPacket->m_niContact >= psPacket->m_niMaxContact || NULL == psPacket->m.psContact)
    {
        return (TgE_FAIL);
    }
    else
    {
        TYPE                                fDS;
        V(C_TgVEC)                          vDS = V(F_SUB_VV)( &psSP1->m_vOrigin, &psSP0->m_vOrigin );
        V(C_TgVEC)                          vDN = V(F_NORM_LEN)( &fDS, &vDS );
        const TYPE                          fSumRad = psSP1->m_fRadius + psSP0->m_fRadius;
        V(P_STg2_CO_Contact)                psContact;

        if (fDS > fSumRad)
        {
            return (ETgE_NO_INTERSECT);
        };

        psContact = (V(P_STg2_CO_Contact))(psPacket->m.piContact + psPacket->m_niContact*psPacket->m_iStride);

        psContact->m_fT0 = MKL(0.0);
        psContact->m_fDepth = fSumRad - fDS;

        if (fDS <= F(KTgEPS))
        {
            V(C_TgVEC)                          vK0 = V(F_MUL_VS)( &V(KTgV_UNIT_Y), psSP1->m_fRadius );

            psContact->m_vS0 = V(F_SUB_VV)( &psSP1->m_vOrigin, &vK0 );
            psContact->m_vN0 = V(KTgV_UNIT_Y);
        }
        else
        {
            V(C_TgVEC)                          vK0 = V(F_MUL_VS)( &vDN, psSP1->m_fRadius );

            psContact->m_vS0 = V(F_SUB_VV)( &psSP1->m_vOrigin, &vK0 );
            psContact->m_vN0 = vDN;
        };

        ++psPacket->m_niContact;
        return (TgS_OK);
    };
}


// ---- tgCO_F_Sweep_SP_SP ------------------------------------------------------------------------------------------------------ //
// Input:  tgPacket: The current series of contact points for this query-series, and contact generation parameters.
// Input:  fPM: 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:  psSP0,psSP1: Sphere primitives
// Input:  psDT: 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: fPM: New normalized time of first contact
// Return: Result Code
// ------------------------------------------------------------------------------------------------------------------------------ //
TgRESULT V(tgCO_F_Sweep_SP_SP)(
    V(PCU_STg2_CO_Packet) psPacket, PCU_TYPE pfPM, V(CPCU_TgSPHERE) psSP0, V(CPCU_TgSPHERE) psSP1, V(CPCU_TgDELTA) psDT )
{
    TgASSERT(V(tgGM_Is_Valid_SP)( psSP1 ) && V(tgGM_Is_Valid_SP)( psSP0 ));

    if (0 == psPacket->m_niMaxContact || psPacket->m_niContact >= psPacket->m_niMaxContact || NULL == psPacket->m.psContact)
    {
        return (TgE_FAIL);
    }
    else
    {
        // Check for pre-penetration.

        const TYPE                          fRS = psSP1->m_fRadius + psSP0->m_fRadius;
        const TYPE                          fRS2 = fRS*fRS;
        V(C_TgVEC)                          vDS = V(F_SUB_VV)( &psSP0->m_vOrigin, &psSP1->m_vOrigin );
        const TYPE                          fDS_DS = V(F_LSQ)( &vDS );

        if (fDS_DS <= fRS2)
        {   // Pre-Penetration.
            C_TgBOOL                            bPenetrate = TgTRUE == psPacket->m_bReport_Penetration;

            if (*pfPM > psPacket->m_fSweepTol)
            {
                psPacket->m_niContact = 0;
            };

            *pfPM = MKL(0.0);

            if (bPenetrate && ETgE_MAX_CONTACTS == V(tgCO_F_Penetrate_SP_SP)( psPacket, psSP0, psSP1 ))
            {
                return (ETgE_MAX_CONTACTS);
            };

            return (ETgE_PREPENETRATION);
        }
        else
        {
            TYPE                                fK0 = psDT->m_fDT - F(KTgEPS);
            const TYPE                          fDS_UDT = V(F_DOT_VV)( &vDS, &psDT->m_vUDT );

            fK0 = F(tgPM_FSEL)( fK0, fDS_UDT - F(KTgEPS), MKL(-1.0) ); // Negligible distance towards each other.
            fK0 = F(tgPM_FSEL)( fK0, fRS + *pfPM*psDT->m_fDT - fDS_UDT, MKL(-1.0) ); // Separation along displacement is too large.
            fK0 = F(tgPM_FSEL)( fK0, fRS2 - fDS_DS + fDS_UDT*fDS_UDT, MKL(-1.0) ); // Orthogonal separation is too large.
            fK0 = F(tgPM_FSEL)( fK0, fDS_UDT - F(tgPM_SQRT)( fK0 ), MKL(-1.0) ); // Time occurs after first time of contact.

            if (fK0 > (*pfPM + psPacket->m_fSweepTol)*psDT->m_fDT)
            {
                return (ETgE_NO_INTERSECT); // Outside of sweep space.
            }
            else
            {
                const TYPE                          fT0 = fK0 / psDT->m_fDT;
                V(C_TgVEC)                          vK0 = V(F_MUL_SV)( fK0, &psDT->m_vUDT );
                V(C_TgVEC)                          vSP0 = V(F_ADD_VV)( &psSP1->m_vOrigin, &vK0 );
                V(C_TgVEC)                          vK1 = V(F_SUB_VV)( &vSP0, &psSP0->m_vOrigin );
                V(C_TgVEC)                          vNormal = V(F_NORM)( &vK1 );
                V(C_TgVEC)                          vK2 = V(F_MUL_VS)( &vNormal, psSP1->m_fRadius );
                V(P_STg2_CO_Contact)                psContact;

                if (fT0 < *pfPM - psPacket->m_fSweepTol)
                {
                    psPacket->m_niContact = 0;
                    *pfPM = fT0;
                };

                psContact = (V(P_STg2_CO_Contact))(psPacket->m.piContact + psPacket->m_niContact*psPacket->m_iStride);

                psContact->m_vS0 = V(F_SUB_VV)( &vSP0, &vK2 );
                psContact->m_vN0 = vNormal;
                psContact->m_fT0 = fT0;
                psContact->m_fDepth = MKL(0.0);

                ++psPacket->m_niContact;

                return (TgS_OK);
            };
        };
    };
}