Linear algebra package

These functions are used to numerically calculate solutions to linear systems and find eigenvalues of symmetric matrices. For details, see Mathematics for 3D Game Programming & Computer Graphics, Chapter 14.

SolveLinearSystem

bool SolveLinearSystem(int n, float *m, float *r)
{
    float *rowNormalizer = new float[n];
    bool result = false;
    
    // Calculate a normalizer for each row
    for (int i = 0; i < n; i++)
    {
        const float *entry = m + i;
        float maxvalue = 0.0F;
        
        for (int j = 0; j < n; j++)
        {
            float value = fabs(*entry);
            if (value > maxvalue) maxvalue = value;
            entry += n;
        }
        
        if (maxvalue == 0.0F) goto exit; // Singular
        rowNormalizer[i] = 1.0F / maxvalue;
    }
    
    // Perform elimination one column at a time
    for (int j = 0; j < n - 1; j++)
    {
        // Find pivot element
        int pivotRow = -1;
        float maxvalue = 0.0F;
        for (int i = j; i < n; i++)
        {
            float p = fabs(m[j * n + i]) * rowNormalizer[i];
            if (p > maxvalue)
            {
                maxvalue = p;
                pivotRow = i;
            }
        }
        
        if (pivotRow != j)
        {
            if (pivotRow == -1) goto exit; // Singular
            
            // Exchange rows
            for (int k = 0; k < n; k++)
            {
                float temp = m[k * n + j];
                m[k * n + j] = m[k * n + pivotRow];
                m[k * n + pivotRow] = temp;
            }
            
            float temp = r[j];
            r[j] = r[pivotRow];
            r[pivotRow] = temp;
            
            rowNormalizer[pivotRow] = rowNormalizer[j];
        }
        
        float denom = 1.0F / m[j * n + j];
        for (int i = j + 1; i < n; i++)
        {
            float factor = m[j * n + i] * denom;
            r[i] -= r[j] * factor;
            for (int k = 0; k < n; k++) m[k * n + i] -= m[k * n + j] * factor;
        }
    }
    
    // Perform backward substitution
    for (int i = n - 1; i >= 0; i--)
    {
        float sum = r[i];
        for (int k = i + 1; k < n; k++) sum -= m[k * n + i] * r[k];
        r[i] = sum / m[i * n + i];
    }

    result = true;
    
    exit:
    delete[] rowNormalizer;
    return (result);
}

LUDecompose

bool LUDecompose(int n, float *m,
    unsigned short *index, float *detSign)
{
    float *rowNormalizer = new float[n];
    float exchangeParity = 1.0F;
    bool result = false;
    
    // Calculate a normalizer for each row
    for (int i = 0; i < n; i++)
    {
        const float *entry = m + i;
        float maxvalue = 0.0F;
        
        for (int j = 0; j < n; j++)
        {
            float value = fabs(*entry);
            if (value > maxvalue) maxvalue = value;
            entry += n;
        }
        
        if (maxvalue == 0.0F) goto exit; // Singular
        rowNormalizer[i] = 1.0F / maxvalue;
        index[i] = i;
    }
    
    // Perform decomposition
    for (int j = 0; j < n; j++)
    {
        for (int i = 1; i < j; i++)
        {
            // Evaluate Equation (14.15)
            float sum = m[j * n + i];
            for (int k = 0; k < i; k++) sum -= m[k * n + i] * m[j * n + k];
            m[j * n + i] = sum;
        }
        
        // Find pivot element
        int pivotRow = -1;
        float maxvalue = 0.0F;
        for (int i = j; i < n; i++)
        {
            // Evaluate Equation (14.17)
            float sum = m[j * n + i];
            for (int k = 0; k < j; k++) sum -= m[k * n + i] * m[j * n + k];
            m[j * n + i] = sum;
            
            sum = fabs(sum) * rowNormalizer[i];
            if (sum > maxvalue)
            {
                maxvalue = sum;
                pivotRow = i;
            }
        }
        
        if (pivotRow != j)
        {
            if (pivotRow == -1) goto exit; // Singular
            
            // Exchange rows
            for (int k = 0; k < n; k++)
            {
                float temp = m[k * n + j];
                m[k * n + j] = m[k * n + pivotRow];
                m[k * n + pivotRow] = temp;
            }
            
            unsigned short temp = index[j];
            index[j] = index[pivotRow];
            index[pivotRow] = temp;
            
            rowNormalizer[pivotRow] = rowNormalizer[j];
            exchangeParity = -exchangeParity;
        }
        
        // Divide by pivot element
        if (j != n - 1)
        {
            float denom = 1.0F / m[j * n + j];
            for (int i = j + 1; i < n; i++) m[j * n + i] *= denom;
        }
    }
    
    if (detSign) *detSign = exchangeParity;
    result = true;

    exit:
    delete[] rowNormalizer;
    return (result);
}

LUBacksubstitute

void LUBacksubstitute(int n, const float *d,
    const unsigned short *index, const float *r, float *x)
{
    for (int i = 0; i < n; i++) x[i] = r[index[i]];
    
    // Perform forward substitution for Ly = r
    for (int i = 0; i < n; i++)
    {
        float sum = x[i];
        for (int k = 0; k < i; k++) sum -= d[k * n + i] * x[k];
        x[i] = sum;
    }
    
    // Perform backward substitution for Ux = y
    for (int i = n - 1; i >= 0; i--)
    {
        float sum = x[i];
        for (int k = i + 1; k < n; k++) sum -= d[k * n + i] * x[k];
        x[i] = sum / d[i * n + i];
    }
}

LURefineSolution

void LURefineSolution(int n, const float *m,
    const float *d, const unsigned short *index,
    const float *r, float *x)
{
    float *t = new float[n];
    
    for (int i = 0; i < n; i++)
    {
        double q = -r[i];
        for (int k = 0; k < n; k++) q += m[k * n + i] * x[k];
        t[i] = (float) q;
    }
    
    LUBacksubstitute(n, d, index, t, t);
    for (int i = 0; i < n; i++) x[i] -= t[i];
    
    delete[] t;
}

SolveTridiagonalSystem

void SolveTridiagonalSystem(int n,
    const float *a, const float *b, const float *c,
    const float *r, float *x)
{
    // Allocate temporary storage for c[i]/beta[i]
    float *t = new float[n - 1];

    float recipBeta = 1.0F / b[0];
    x[0] = r[0] * recipBeta;

    for (int i = 1; i < n; i++)
    {
        t[i - 1] = c[i - 1] * recipBeta; 
        recipBeta = 1.0F / (b[i] - a[i] * t[i - 1]);
        x[i] = (r[i] - a[i] * x[i - 1]) * recipBeta;
    }

    for (int i = n - 2; i >= 0; i--) x[i] -= t[i] * x[i + 1];

    delete[] t;
}

CalculateEigensystem

const float epsilon = 1.0e-10F;
const int maxSweeps = 32;


struct Matrix3D
{
    float n[3][3];

    float& operator()(int i, int j)
    {
        return (n[j][i]);
    }

    const float& operator()(int i, int j) const
    {
        return (n[j][i]);
    }

    void SetIdentity(void)
    {
        n[0][0] = n[1][1] = n[2][2] = 1.0F;
        n[0][1] = n[0][2] = n[1][0] = n[1][2] = n[2][0] = n[2][1] = 0.0F;
    }
};


void CalculateEigensystem(const Matrix3D& m, float *lambda, Matrix3D& r)
{
    float m11 = m(0,0);
    float m12 = m(0,1);
    float m13 = m(0,2);
    float m22 = m(1,1);
    float m23 = m(1,2);
    float m33 = m(2,2);
    
    r.SetIdentity();
    for (int a = 0; a < maxSweeps; a++)
    {
        // Exit if off-diagonal entries small enough
        if ((Fabs(m12) < epsilon) && (Fabs(m13) < epsilon) &&
            (Fabs(m23) < epsilon)) break;
        
        // Annihilate (1,2) entry
        if (m12 != 0.0F)
        {
            float u = (m22 - m11) * 0.5F / m12;
            float u2 = u * u;
            float u2p1 = u2 + 1.0F;
            float t = (u2p1 != u2) ?
                ((u < 0.0F) ? -1.0F : 1.0F) * (sqrt(u2p1) - fabs(u)) : 0.5F / u;
            float c = 1.0F / sqrt(t * t + 1.0F);
            float s = c * t;
            
            m11 -= t * m12;
            m22 += t * m12;
            m12 = 0.0F;
            
            float temp = c * m13 - s * m23;
            m23 = s * m13 + c * m23;
            m13 = temp;
            
            for (int i = 0; i < 3; i++)
            {
                float temp = c * r(i,0) - s * r(i,1);
                r(i,1) = s * r(i,0) + c * r(i,1);
                r(i,0) = temp;
            }
        }
        
        // Annihilate (1,3) entry
        if (m13 != 0.0F)
        {
            float u = (m33 - m11) * 0.5F / m13;
            float u2 = u * u;
            float u2p1 = u2 + 1.0F;
            float t = (u2p1 != u2) ?
                ((u < 0.0F) ? -1.0F : 1.0F) * (sqrt(u2p1) - fabs(u)) : 0.5F / u;
            float c = 1.0F / sqrt(t * t + 1.0F);
            float s = c * t;
            
            m11 -= t * m13;
            m33 += t * m13;
            m13 = 0.0F;
            
            float temp = c * m12 - s * m23;
            m23 = s * m12 + c * m23;
            m12 = temp;
            
            for (int i = 0; i < 3; i++)
            {
                float temp = c * r(i,0) - s * r(i,2);
                r(i,2) = s * r(i,0) + c * r(i,2);
                r(i,0) = temp;
            }
        }
        
        // Annihilate (2,3) entry
        if (m23 != 0.0F)
        {
            float u = (m33 - m22) * 0.5F / m23;
            float u2 = u * u;
            float u2p1 = u2 + 1.0F;
            float t = (u2p1 != u2) ?
                ((u < 0.0F) ? -1.0F : 1.0F) * (sqrt(u2p1) - fabs(u)) : 0.5F / u;
            float c = 1.0F / sqrt(t * t + 1.0F);
            float s = c * t;
            
            m22 -= t * m23;
            m33 += t * m23;
            m23 = 0.0F;
            
            float temp = c * m12 - s * m13;
            m13 = s * m12 + c * m13;
            m12 = temp;
            
            for (int i = 0; i < 3; i++)
            {
                float temp = c * r(i,1) - s * r(i,2);
                r(i,2) = s * r(i,1) + c * r(i,2);
                r(i,1) = temp;
            }
        }
    }
    
    lambda[0] = m11;
    lambda[1] = m22;
    lambda[2] = m33;
}

Public Source Code

The following are links to C++ snippets that we've released for various algorithms and techniques pertinent to 3D game programming.

Books by Eric Lengyel
Book

Mathematics for 3D Game Programming & Computer Graphics, Second Edition

This best-selling book teaches the mathematics and rendering techniques that a game programmer needs to develop a professional-quality 3D engine.
Book

The OpenGL Extensions Guide

This is the ultimate extension reference for the serious OpenGL programmer.
Books See all publications by Eric Lengyel

Conference Slides and Articles
GDC07

Projection Matrix Tricks (2007)
(PowerPoint, 3.26 MB)

This presentation examines the inner workings of the perspective projection matrix and discusses several techniques for modifying the properties of the projection matrix to solve specific rendering problems at zero cost.
GDC06

Advanced Light and Shadow Culling Methods (2006)
(PowerPoint, 852 kB)

This presentation focuses primarily on portal systems and describes algorithms and optimizations that can be applied to a graphics engine supporting completely dynamic lighting and shadows.
GDC05

Advanced Stencil Shadow and Penumbral Wedge Rendering (2005)
(PowerPoint, 1.00 MB)

This presentation reviews advanced implementation techniques of the stencil shadow algorithm and focuses on the relatively new method of penumbral wedge rendering used to generate soft-edged shadows.
Gamasutra

The Mechanics of Robust
Stencil Shadows

This article describes the mathematical details of efficient and robust stencil shadow rendering. These techniques are implemented in the C4 Engine.