Matrix Functions

From Second Life Wiki
Revision as of 17:41, 21 December 2015 by KyleFlynn Resident (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
//
// *******************************************************************************
// *******************************************************************************
//
// Developed by KyleFlynn.
//
// This is free and open source code, protected by the GPLv3 license.
// http://www.gnu.org/licenses/gpl-3.0.html
// Accepting this code is conditioned upon accepting this license.
//
//
// The following are some core linear algebra functions.
// They are primarily used to develop and use 3D transformation matrices.
// Note that ALL matrices herein are assumed to be 3x3 matrices.
// And all of these 3x3 matrices are assumed to be stored as a 3 element list of vectors.
// In all cases, the vectors are assumed to be column (not row) vectors.
//
// For all purposes herein, rows are numbered 1,2,3 and columns are also numbered 1,2,3 (both 1 based).
//
// As a last note, there are certainly faster ways to perform these function.
// However, the following imposes an excellent organization when performing
// complex 3D transformations.
//
// The available functions are:
//
//      list    MakeMatrix(float r1c1, float r1c2, float r1c3, r2c1, ...
//      float   GetMatrixElement(list m, integer row, integer col)
//      list    SetMatrixElement(list m, integer row, integer col, float value) // Returns a new matrix with the new element set.
//      list    MultMatrices(list m1, list m2) 
//      list    DivideMatrices(list m1, list m2)
//      list    BackDivideMatrices(list m1, list m2) 
//      list    InvertMatrix(list m)
//      list    TransposeMatrix(list m)  
//      integer MatrixIsZero(list m) 
//      integer MatrixIsOrthogonal(list m)
//      integer MatrixIsNormal(list m)
//
//      vector  SetVectorElement(vector v, integer row, float value) // Returns a new vector with the new element set.
//      float   GetVectorElement(vector v, integer row)
//      list    MatrixFrom3ColVectors(vector v1, vector v2, vector v3) 
//      list    ZeroMatrix()
//      list    UnitMatrix()
//      vector  MakeVector(float x, float y, float z)
//      string  VectorString(vector v)          // For debugging.
//      string  MatrixString(list m)            // For debugging.
//
// *******************************************************************************
// *******************************************************************************
//

list MakeMatrix(float r1c1, float r1c2, float r1c3,  
                float r2c1, float r2c2, float r2c3,  
                float r3c1, float r3c2, float r3c3) 
{
    return [<r1c1, r2c1, r3c1>, <r1c2, r2c2, r3c2>, <r1c3, r2c3, r3c3>];
}

float GetMatrixElement(list m, integer row, integer col)
{
    return GetVectorElement(llList2Vector(m, col - 1), row);
}

list SetMatrixElement(list m, integer row, integer col, float value) // Returns a new matrix with the new element set.
{
    return llListReplaceList(m, [SetVectorElement(llList2Vector(m, col - 1), row, value)], col - 1, col - 1);
}

float GetVectorElement(vector v, integer row)
{
         if (row == 1) return v.x;
    else if (row == 2) return v.y;
    else if (row == 3) return v.z;
    //
    return 0;
}

vector SetVectorElement(vector v, integer row, float value) // Returns a new vector with the new element set.
{
         if (row == 1) v.x = value;
    else if (row == 2) v.y = value;
    else if (row == 3) v.z = value;
    //
    return v;
}

list ZeroMatrix()
{
    return MakeMatrix(0, 0, 0,
                      0, 0, 0,
                      0, 0, 0);
}

list UnitMatrix()
{
    return MakeMatrix(1, 0, 0,
                      0, 1, 0,
                      0, 0, 1);
}

vector MakeVector(float x, float y, float z)
{
    return <x, y, z>;
}

list  MatrixFrom3ColVectors(vector v1, vector v2, vector v3) 
{
    return [v1, v2, v3];
}

list DivideMatrices(list m1, list m2)
{
    // In Matlab, this is equivalent to:     X = m1 / m2;
    return MultMatrices(m1, InvertMatrix(m2));
}

list BackDivideMatrices(list m1, list m2) 
{
    // In Matlab, this is equivalent to:     X = m1 \ m2;
    return MultMatrices(InvertMatrix(m1), m2);
}

list MultMatrices(list m1, list m2) 
{
    // A matrix generalization of the vector dot product.
    // This procedure specifically multiplies 3x3 matrices, and returns a 3x3 matrix.
    integer r1Ptr;
    integer c1Ptr;
    integer c2Ptr;
    list m3 = ZeroMatrix();
    //
    for (r1Ptr = 1; r1Ptr <= 3; r1Ptr++)
    {    
        for (c2Ptr = 1; c2Ptr <=3; c2Ptr++)
        {
            for (c1Ptr = 1; c1Ptr <=3; c1Ptr++)
            {
                // This uses column ptr for row, but that's okay.
                m3 = SetMatrixElement(m3, r1Ptr, c2Ptr, GetMatrixElement(m3, r1Ptr, c2Ptr) + GetMatrixElement(m1, r1Ptr, c1Ptr) * GetMatrixElement(m2, c1Ptr, c2Ptr));
            }
        }
    }
    //
    return m3;
}

list InvertMatrix(list m)
{
    // Don't forget that InvertMatrix(m)=TransposeMatrix(m) if m is orthonormal.
    // And TransposeMatrix(m) would be much faster.
    //
    // This ONLY works with 3x3 matrices.
    integer rPtr;
    integer cPtr;
    integer ptr;
    integer rPtr2;
    integer cPtr2;
    float PivotMax;
    float dNorm;
    float dSwap;
    integer swpCnt;
    //
    for (ptr = 1; ptr <= 3; ptr++)
    {
        // Search max pivot.
        rPtr2 = ptr;
        cPtr2 = ptr;
        PivotMax = 0;
        for (rPtr = ptr; rPtr <= 3; rPtr++)
        {
            for (cPtr = ptr; cPtr <= 3; cPtr++)
            {            
                if (llFabs(GetMatrixElement(m, rPtr, cPtr)) > PivotMax)
                {
                    rPtr2 = rPtr;
                    cPtr2 = cPtr;
                    PivotMax = llFabs(GetMatrixElement(m, rPtr, cPtr));
                }
            }
        }
        // Swap rows and columns.
        if (rPtr2 > ptr)
        {
            // Swap row.
            for (cPtr = 1; cPtr <= 3; cPtr++)
            {
                dSwap = GetMatrixElement(m, rPtr2, cPtr);
                m = SetMatrixElement(m, rPtr2, cPtr, GetMatrixElement(m, ptr, cPtr));
                m = SetMatrixElement(m, ptr, cPtr, dSwap);
            }
            swpCnt++;
            SetSwapDiag(swpCnt, 1, ptr);
            SetSwapDiag(swpCnt, 2, rPtr2);
            SetSwapDiag(swpCnt, 3, 1);
        }
        if (cPtr2 > ptr)
        {
            // Swap column.
            for (rPtr = 1; rPtr <= 3; rPtr++)
            {
                dSwap = GetMatrixElement(m, rPtr, cPtr2);
                m = SetMatrixElement(m, rPtr, cPtr2, GetMatrixElement(m, rPtr, ptr));
                m = SetMatrixElement(m, rPtr, ptr, dSwap);
            }
            swpCnt++;
            SetSwapDiag(swpCnt, 1, ptr);
            SetSwapDiag(swpCnt, 2, cPtr2);
            SetSwapDiag(swpCnt, 3, 2);
        }
        //
        // Check pivot 0.
        if (llFabs(GetMatrixElement(m, ptr, ptr)) <= 0)
        {
            llSay(DEBUG_CHANNEL, "Matrix inversion error, script is stopped.");
            rPtr = 1 / 0; // Crash the script.
        }
        //
        // Normalization.
        dNorm = GetMatrixElement(m, ptr, ptr);
        m = SetMatrixElement(m, ptr, ptr, 1);
        for (cPtr = 1; cPtr <= 3; cPtr++)
        {
            m = SetMatrixElement(m, ptr, cPtr, GetMatrixElement(m, ptr, cPtr) / dNorm);
        }
        // Linear reduction.
        for (rPtr = 1; rPtr <= 3; rPtr++)
        {
            if (rPtr != ptr && GetMatrixElement(m, rPtr, ptr) != 0)
            {
                dNorm = GetMatrixElement(m, rPtr, ptr);
                m = SetMatrixElement(m, rPtr, ptr, 0);
                for (cPtr = 1; cPtr <= 3; cPtr++)
                {
                    m = SetMatrixElement(m, rPtr, cPtr, GetMatrixElement(m, rPtr, cPtr) - dNorm * GetMatrixElement(m, ptr, cPtr));
                }
            }
        }
    }
    //
    // Un-Scramble rows.
    for (ptr = swpCnt; ptr >= 1; ptr--)
    {
        if (GetSwapDiag(ptr, 3) == 1)
        {
            // Swap column.
            for (rPtr = 1; rPtr <=3; rPtr++)
            {
                dSwap = GetMatrixElement(m, rPtr, GetSwapDiag(ptr, 2));
                m = SetMatrixElement(m, rPtr, GetSwapDiag(ptr, 2), GetMatrixElement(m, rPtr, GetSwapDiag(ptr, 1)));
                m = SetMatrixElement(m, rPtr, GetSwapDiag(ptr, 1), dSwap);
            }
        }
        else if (GetSwapDiag(ptr, 3) == 2)
        {
            // Swap row.
            for (cPtr = 1; cPtr <= 3; cPtr++)
            {
                dSwap = GetMatrixElement(m, GetSwapDiag(ptr, 2), cPtr);
                m = SetMatrixElement(m, GetSwapDiag(ptr, 2), cPtr, GetMatrixElement(m, GetSwapDiag(ptr, 1), cPtr));
                m = SetMatrixElement(m, GetSwapDiag(ptr, 1), cPtr, dSwap);
            }
        }
    }
    //
    return m;
}

list TransposeMatrix(list m)  
{
    list m2 = ZeroMatrix();
    //
    m2 = SetMatrixElement(m2, 1, 2, GetMatrixElement(m, 2, 1));
    m2 = SetMatrixElement(m2, 1, 3, GetMatrixElement(m, 3, 1));
    m2 = SetMatrixElement(m2, 2, 1, GetMatrixElement(m, 1, 2));
    m2 = SetMatrixElement(m2, 2, 3, GetMatrixElement(m, 3, 2));
    m2 = SetMatrixElement(m2, 3, 1, GetMatrixElement(m, 1, 3));
    m2 = SetMatrixElement(m2, 3, 2, GetMatrixElement(m, 2, 3));
    //
    // Don't need to transpose the diagonal elements.
    m2 = SetMatrixElement(m2, 1, 1, GetMatrixElement(m, 1, 1));
    m2 = SetMatrixElement(m2, 2, 2, GetMatrixElement(m, 2, 2));
    m2 = SetMatrixElement(m2, 3, 3, GetMatrixElement(m, 3, 3));
    //
    return m2;
}

integer MatrixIsZero(list m) 
{
    return (GetMatrixElement(m, 1, 1) == 0 && GetMatrixElement(m, 2, 1) == 0 && GetMatrixElement(m, 3, 1) == 0 && 
            GetMatrixElement(m, 1, 2) == 0 && GetMatrixElement(m, 2, 2) == 0 && GetMatrixElement(m, 3, 2) == 0 && 
            GetMatrixElement(m, 1, 3) == 0 && GetMatrixElement(m, 2, 3) == 0 && GetMatrixElement(m, 3, 3) == 0);
}

integer MatrixIsOrthogonal(list m)
{
    float epsilon = .00002; // Our allowed tolerance.
    //
    vector v1 = llList2Vector(m, 0);
    vector v2 = llList2Vector(m, 1);
    vector v3 = llList2Vector(m, 2);
    //
    return llFabs(llFabs(llRot2Angle(llRotBetween(v1, v2))) - 90) < epsilon &&
           llFabs(llFabs(llRot2Angle(llRotBetween(v1, v3))) - 90) < epsilon &&
           llFabs(llFabs(llRot2Angle(llRotBetween(v2, v3))) - 90) < epsilon;
}

integer MatrixIsNormal(list m)
{
    float epsilon = .00002; // Our allowed tolerance.
    //
    vector v1 = llList2Vector(m, 0);
    vector v2 = llList2Vector(m, 1);
    vector v3 = llList2Vector(m, 2);
    //
    return llFabs(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z - 1) < epsilon &&
           llFabs(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z - 1) < epsilon &&
           llFabs(v3.x * v3.x + v3.y * v3.y + v3.z * v3.z - 1) < epsilon;
}

string VectorString(vector v)  
// Primarily used for debugging purposes.
{
    return "[ " + Float2StringFormat(v.x, 5, TRUE) + ", " + Float2StringFormat(v.y, 5, TRUE) + ", " + Float2StringFormat(v.z, 5, TRUE) + " ]";
}

string MatrixString(list m)
// Primarily used for debugging purposes.
{
    return "\n[ " + Float2StringFormat(GetMatrixElement(m, 1, 1), 5, TRUE) + ", " + Float2StringFormat(GetMatrixElement(m, 1, 2), 5, TRUE) + ", " + Float2StringFormat(GetMatrixElement(m, 1, 3), 5, TRUE) + " ;" + "\n" + 
             "  " + Float2StringFormat(GetMatrixElement(m, 2, 1), 5, TRUE) + ", " + Float2StringFormat(GetMatrixElement(m, 2, 2), 5, TRUE) + ", " + Float2StringFormat(GetMatrixElement(m, 2, 3), 5, TRUE) + " ;" + "\n" + 
             "  " + Float2StringFormat(GetMatrixElement(m, 3, 1), 5, TRUE) + ", " + Float2StringFormat(GetMatrixElement(m, 3, 2), 5, TRUE) + ", " + Float2StringFormat(GetMatrixElement(m, 3, 3), 5, TRUE) + " ]";
}

string Float2StringFormat(float num, integer places, integer rnd) 
// Allows string output of a float in a tidy text format.
// rnd (rounding) should be set to TRUE for rounding, FALSE for no rounding
{ 
    if (rnd) 
    {
        float f = llPow(10.0, places);
        integer i = llRound(llFabs(num) * f);
        string s = "00000" + (string)i; // Number of 0s is (value of max places - 1).
        if (num < 0.0) return "-" + (string)( (integer)(i / f) ) + "." + llGetSubString(s, -places, -1);
        return (string)((integer)(i / f)) + "." + llGetSubString(s, -places, -1);
    }
    if (!places) return (string)((integer)num );
    if ((places = (places - 7 - (places < 1))) & 0x80000000) return llGetSubString((string)num, 0, places);
    return (string)num;
}

// These globals are just used in SetSwapDiag & GetSwapDiag, which are used only in InvertMatrix.
// They emulate a 6x3 array of integers.
integer iSwapDiag11;
integer iSwapDiag21;
integer iSwapDiag31;
integer iSwapDiag41;
integer iSwapDiag51;
integer iSwapDiag61;
integer iSwapDiag12;
integer iSwapDiag22;
integer iSwapDiag32;
integer iSwapDiag42;
integer iSwapDiag52;
integer iSwapDiag62;
integer iSwapDiag13;
integer iSwapDiag23;
integer iSwapDiag33;
integer iSwapDiag43;
integer iSwapDiag53;
integer iSwapDiag63;
//

SetSwapDiag(integer i, integer j, integer value)
// Just used for the InvertMatrix function.
{
         if (i == 1) 
    { 
             if (j == 1) iSwapDiag11 = value;
        else if (j == 2) iSwapDiag12 = value;
        else if (j == 3) iSwapDiag13 = value;
    }
    else if (i == 2)
    {
             if (j == 1) iSwapDiag21 = value;
        else if (j == 2) iSwapDiag22 = value;
        else if (j == 3) iSwapDiag23 = value;
    }
    else if (i == 3)
    {
             if (j == 1) iSwapDiag31 = value;
        else if (j == 2) iSwapDiag32 = value;
        else if (j == 3) iSwapDiag33 = value;
    }
    else if (i == 4)
    {
             if (j == 1) iSwapDiag41 = value;
        else if (j == 2) iSwapDiag42 = value;
        else if (j == 3) iSwapDiag43 = value;
    }
    else if (i == 5)
    {
             if (j == 1) iSwapDiag51 = value;
        else if (j == 2) iSwapDiag52 = value;
        else if (j == 3) iSwapDiag53 = value;
    }
    else if (i == 6)
    {
             if (j == 1) iSwapDiag61 = value;
        else if (j == 2) iSwapDiag62 = value;
        else if (j == 3) iSwapDiag63 = value;
    }
}

integer GetSwapDiag(integer i, integer j)
// Just used for the InvertMatrix function.
{
    integer i1;
    integer i2;
    integer i3;
    //
         if (i == 1) { i1 = iSwapDiag11; i2 = iSwapDiag12; i3 = iSwapDiag13; }
    else if (i == 2) { i1 = iSwapDiag21; i2 = iSwapDiag22; i3 = iSwapDiag23; }
    else if (i == 3) { i1 = iSwapDiag31; i2 = iSwapDiag32; i3 = iSwapDiag33; }
    else if (i == 4) { i1 = iSwapDiag41; i2 = iSwapDiag42; i3 = iSwapDiag43; }
    else if (i == 5) { i1 = iSwapDiag51; i2 = iSwapDiag52; i3 = iSwapDiag53; }
    else if (i == 6) { i1 = iSwapDiag61; i2 = iSwapDiag62; i3 = iSwapDiag63; }
    //
         if (j == 1) return i1;
    else if (j == 2) return i2;
    else if (j == 3) return i3;
    return 0; // Needed for compiler.
}

default
{
    on_rez(integer iStartParam)
    {
        llResetScript();
    }
    state_entry()
    {
    
        list m1;
        list m2;
        list m3;
        list m4;
        //
        m1 = MakeMatrix(1,2,3,4,5,6,7,8,9);
        m2 = MakeMatrix(2.2, 3.3, 4.4, 
                        5.5, 6.6, 5.5, 
                        4.4, 3.2, 2.1);
        m3 = MultMatrices(m1, m2);
        llSay(0, MatrixString(m3));
        
        m4 = DivideMatrices(m3, m2);
        llSay(0, MatrixString(m4)); // m4 should take us back to m1.
        
        // With the above, the MultMatrices and DivideMatrices functions are tested, 
        // along with InvertMatrix via divide.  This is the core of these procedures.
        
        
    
    
    }
    touch_start(integer iNumDetected)
    {
    }
    timer()
    {
    }
}