NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/math/matrix-utils.js

/**
 * @file Matrix Utils
 * @private
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 *
 * svd methods from Eugene Zatepyakin / http://inspirit.github.io/jsfeat/
 */

import { v3new, v3cross } from './vector-utils.js'

function Matrix (columns, rows) {
  this.cols = columns
  this.rows = rows
  this.size = this.cols * this.rows

  this.data = new Float32Array(this.size)
}

Matrix.prototype = {

  copyTo: function (matrix) {
    matrix.data.set(this.data)
  }

}

function transpose (At, A) {
  var i = 0
  var j = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ai = 0
  var Ati = 0
  var pAt = 0
  var ad = A.data
  var atd = At.data

  for (; i < nrows; Ati += 1, Ai += ncols, i++) {
    pAt = Ati
    for (j = 0; j < ncols; pAt += nrows, j++) atd[pAt] = ad[Ai + j]
  }
}

// C = A * B
function multiply (C, A, B) {
  var i = 0
  var j = 0
  var k = 0
  var Ap = 0
  var pA = 0
  var pB = 0
  var _pB = 0
  var Cp = 0
  var ncols = A.cols
  var nrows = A.rows
  var mcols = B.cols
  var ad = A.data
  var bd = B.data
  var cd = C.data
  var sum = 0.0

  for (; i < nrows; Ap += ncols, i++) {
    for (_pB = 0, j = 0; j < mcols; Cp++, _pB++, j++) {
      pB = _pB
      pA = Ap
      sum = 0.0
      for (k = 0; k < ncols; pA++, pB += mcols, k++) {
        sum += ad[pA] * bd[pB]
      }
      cd[Cp] = sum
    }
  }
}

// C = A * B'
function multiplyABt (C, A, B) {
  var i = 0
  var j = 0
  var k = 0
  var Ap = 0
  var pA = 0
  var pB = 0
  var Cp = 0
  var ncols = A.cols
  var nrows = A.rows
  var mrows = B.rows
  var ad = A.data
  var bd = B.data
  var cd = C.data
  var sum = 0.0

  for (; i < nrows; Ap += ncols, i++) {
    for (pB = 0, j = 0; j < mrows; Cp++, j++) {
      pA = Ap
      sum = 0.0
      for (k = 0; k < ncols; pA++, pB++, k++) {
        sum += ad[pA] * bd[pB]
      }
      cd[Cp] = sum
    }
  }
}

// C = A' * B
function multiplyAtB (C, A, B) {
  var i = 0
  var j = 0
  var k = 0
  var Ap = 0
  var pA = 0
  var pB = 0
  var _pB = 0
  var Cp = 0
  var ncols = A.cols
  var nrows = A.rows
  var mcols = B.cols
  var ad = A.data
  var bd = B.data
  var cd = C.data
  var sum = 0.0

  for (; i < ncols; Ap++, i++) {
    for (_pB = 0, j = 0; j < mcols; Cp++, _pB++, j++) {
      pB = _pB
      pA = Ap
      sum = 0.0
      for (k = 0; k < nrows; pA += ncols, pB += mcols, k++) {
        sum += ad[pA] * bd[pB]
      }
      cd[Cp] = sum
    }
  }
}

function invert3x3 (from, to) {
  var A = from.data
  var invA = to.data
  var t1 = A[4]
  var t2 = A[8]
  var t4 = A[5]
  var t5 = A[7]
  var t8 = A[0]

  var t9 = t8 * t1
  var t11 = t8 * t4
  var t13 = A[3]
  var t14 = A[1]
  var t15 = t13 * t14
  var t17 = A[2]
  var t18 = t13 * t17
  var t20 = A[6]
  var t21 = t20 * t14
  var t23 = t20 * t17
  var t26 = 1.0 / (t9 * t2 - t11 * t5 - t15 * t2 + t18 * t5 + t21 * t4 - t23 * t1)
  invA[0] = (t1 * t2 - t4 * t5) * t26
  invA[1] = -(t14 * t2 - t17 * t5) * t26
  invA[2] = -(-t14 * t4 + t17 * t1) * t26
  invA[3] = -(t13 * t2 - t4 * t20) * t26
  invA[4] = (t8 * t2 - t23) * t26
  invA[5] = -(t11 - t18) * t26
  invA[6] = -(-t13 * t5 + t1 * t20) * t26
  invA[7] = -(t8 * t5 - t21) * t26
  invA[8] = (t9 - t15) * t26
}

function mat3x3determinant (M) {
  var md = M.data
  return md[0] * md[4] * md[8] -
    md[0] * md[5] * md[7] -
    md[3] * md[1] * md[8] +
    md[3] * md[2] * md[7] +
    md[6] * md[1] * md[5] -
    md[6] * md[2] * md[4]
}

// C = A * B
function multiply3x3 (C, A, B) {
  var Cd = C.data
  var Ad = A.data
  var Bd = B.data
  var m10 = Ad[0]
  var m11 = Ad[1]
  var m12 = Ad[2]
  var m13 = Ad[3]
  var m14 = Ad[4]
  var m15 = Ad[5]
  var m16 = Ad[6]
  var m17 = Ad[7]
  var m18 = Ad[8]

  var m20 = Bd[0]
  var m21 = Bd[1]
  var m22 = Bd[2]
  var m23 = Bd[3]
  var m24 = Bd[4]
  var m25 = Bd[5]
  var m26 = Bd[6]
  var m27 = Bd[7]
  var m28 = Bd[8]

  Cd[0] = m10 * m20 + m11 * m23 + m12 * m26
  Cd[1] = m10 * m21 + m11 * m24 + m12 * m27
  Cd[2] = m10 * m22 + m11 * m25 + m12 * m28
  Cd[3] = m13 * m20 + m14 * m23 + m15 * m26
  Cd[4] = m13 * m21 + m14 * m24 + m15 * m27
  Cd[5] = m13 * m22 + m14 * m25 + m15 * m28
  Cd[6] = m16 * m20 + m17 * m23 + m18 * m26
  Cd[7] = m16 * m21 + m17 * m24 + m18 * m27
  Cd[8] = m16 * m22 + m17 * m25 + m18 * m28
}

function meanRows (A) {
  var i, j
  var p = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ad = A.data
  var mean = new Array(ncols)

  for (j = 0; j < ncols; ++j) {
    mean[ j ] = 0.0
  }

  for (i = 0; i < nrows; ++i) {
    for (j = 0; j < ncols; ++j, ++p) {
      mean[ j ] += Ad[ p ]
    }
  }

  for (j = 0; j < ncols; ++j) {
    mean[ j ] /= nrows
  }

  return mean
}

function meanCols (A) {
  var i, j
  var p = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ad = A.data
  var mean = new Array(nrows)

  for (j = 0; j < nrows; ++j) {
    mean[ j ] = 0.0
  }

  for (i = 0; i < ncols; ++i) {
    for (j = 0; j < nrows; ++j, ++p) {
      mean[ j ] += Ad[ p ]
    }
  }

  for (j = 0; j < nrows; ++j) {
    mean[ j ] /= ncols
  }

  return mean
}

function subRows (A, row) {
  var i, j
  var p = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ad = A.data

  for (i = 0; i < nrows; ++i) {
    for (j = 0; j < ncols; ++j, ++p) {
      Ad[ p ] -= row[ j ]
    }
  }
}

function subCols (A, col) {
  var i, j
  var p = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ad = A.data

  for (i = 0; i < ncols; ++i) {
    for (j = 0; j < nrows; ++j, ++p) {
      Ad[ p ] -= col[ j ]
    }
  }
}

function addRows (A, row) {
  var i, j
  var p = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ad = A.data

  for (i = 0; i < nrows; ++i) {
    for (j = 0; j < ncols; ++j, ++p) {
      Ad[ p ] += row[ j ]
    }
  }
}

function addCols (A, col) {
  var i, j
  var p = 0
  var nrows = A.rows
  var ncols = A.cols
  var Ad = A.data

  for (i = 0; i < ncols; ++i) {
    for (j = 0; j < nrows; ++j, ++p) {
      Ad[ p ] += col[ j ]
    }
  }
}

function swap (A, i0, i1, t) {
  t = A[i0]
  A[i0] = A[i1]
  A[i1] = t
}

function hypot (a, b) {
  a = Math.abs(a)
  b = Math.abs(b)
  if (a > b) {
    b /= a
    return a * Math.sqrt(1.0 + b * b)
  }
  if (b > 0) {
    a /= b
    return b * Math.sqrt(1.0 + a * a)
  }
  return 0.0
}

var EPSILON = 0.0000001192092896
var FLT_MIN = 1E-37

function JacobiSVDImpl (At, astep, _W, Vt, vstep, m, n, n1) {
  var eps = EPSILON * 2.0
  var minval = FLT_MIN
  var i = 0
  var j = 0
  var k = 0
  var iter = 0
  var maxIter = Math.max(m, 30)
  var Ai = 0
  var Aj = 0
  var Vi = 0
  var Vj = 0
  var changed = 0
  var c = 0.0
  var s = 0.0
  var t = 0.0
  var t0 = 0.0
  var t1 = 0.0
  var sd = 0.0
  var beta = 0.0
  var gamma = 0.0
  var delta = 0.0
  var a = 0.0
  var p = 0.0
  var b = 0.0
  var seed = 0x1234
  var val = 0.0
  var val0 = 0.0
  var asum = 0.0

  var W = new Float64Array(n << 3)

  for (; i < n; i++) {
    for (k = 0, sd = 0; k < m; k++) {
      t = At[i * astep + k]
      sd += t * t
    }
    W[i] = sd

    if (Vt) {
      for (k = 0; k < n; k++) {
        Vt[i * vstep + k] = 0
      }
      Vt[i * vstep + i] = 1
    }
  }

  for (; iter < maxIter; iter++) {
    changed = 0

    for (i = 0; i < n - 1; i++) {
      for (j = i + 1; j < n; j++) {
        Ai = (i * astep) | 0
        Aj = (j * astep) | 0
        a = W[i]
        p = 0
        b = W[j]

        k = 2
        p += At[Ai] * At[Aj]
        p += At[Ai + 1] * At[Aj + 1]

        for (; k < m; k++) { p += At[Ai + k] * At[Aj + k] }

        if (Math.abs(p) <= eps * Math.sqrt(a * b)) continue

        p *= 2.0
        beta = a - b
        gamma = hypot(p, beta)
        if (beta < 0) {
          delta = (gamma - beta) * 0.5
          s = Math.sqrt(delta / gamma)
          c = (p / (gamma * s * 2.0))
        } else {
          c = Math.sqrt((gamma + beta) / (gamma * 2.0))
          s = (p / (gamma * c * 2.0))
        }

        a = 0.0
        b = 0.0

        k = 2 // unroll
        t0 = c * At[Ai] + s * At[Aj]
        t1 = -s * At[Ai] + c * At[Aj]
        At[Ai] = t0; At[Aj] = t1
        a += t0 * t0; b += t1 * t1

        t0 = c * At[Ai + 1] + s * At[Aj + 1]
        t1 = -s * At[Ai + 1] + c * At[Aj + 1]
        At[Ai + 1] = t0; At[Aj + 1] = t1
        a += t0 * t0; b += t1 * t1

        for (; k < m; k++) {
          t0 = c * At[Ai + k] + s * At[Aj + k]
          t1 = -s * At[Ai + k] + c * At[Aj + k]
          At[Ai + k] = t0; At[Aj + k] = t1

          a += t0 * t0; b += t1 * t1
        }

        W[i] = a
        W[j] = b

        changed = 1

        if (Vt) {
          Vi = (i * vstep) | 0
          Vj = (j * vstep) | 0

          k = 2
          t0 = c * Vt[Vi] + s * Vt[Vj]
          t1 = -s * Vt[Vi] + c * Vt[Vj]
          Vt[Vi] = t0; Vt[Vj] = t1

          t0 = c * Vt[Vi + 1] + s * Vt[Vj + 1]
          t1 = -s * Vt[Vi + 1] + c * Vt[Vj + 1]
          Vt[Vi + 1] = t0; Vt[Vj + 1] = t1

          for (; k < n; k++) {
            t0 = c * Vt[Vi + k] + s * Vt[Vj + k]
            t1 = -s * Vt[Vi + k] + c * Vt[Vj + k]
            Vt[Vi + k] = t0; Vt[Vj + k] = t1
          }
        }
      }
    }
    if (changed === 0) break
  }

  for (i = 0; i < n; i++) {
    for (k = 0, sd = 0; k < m; k++) {
      t = At[i * astep + k]
      sd += t * t
    }
    W[i] = Math.sqrt(sd)
  }

  for (i = 0; i < n - 1; i++) {
    j = i
    for (k = i + 1; k < n; k++) {
      if (W[j] < W[k]) { j = k }
    }
    if (i !== j) {
      swap(W, i, j, sd)
      if (Vt) {
        for (k = 0; k < m; k++) {
          swap(At, i * astep + k, j * astep + k, t)
        }

        for (k = 0; k < n; k++) {
          swap(Vt, i * vstep + k, j * vstep + k, t)
        }
      }
    }
  }

  for (i = 0; i < n; i++) {
    _W[i] = W[i]
  }

  if (!Vt) {
    return
  }

  for (i = 0; i < n1; i++) {
    sd = i < n ? W[i] : 0

    while (sd <= minval) {
      // if we got a zero singular value, then in order to get the corresponding left singular vector
      // we generate a random vector, project it to the previously computed left singular vectors,
      // subtract the projection and normalize the difference.
      val0 = (1.0 / m)
      for (k = 0; k < m; k++) {
        seed = (seed * 214013 + 2531011)
        val = (((seed >> 16) & 0x7fff) & 256) !== 0 ? val0 : -val0
        At[i * astep + k] = val
      }
      for (iter = 0; iter < 2; iter++) {
        for (j = 0; j < i; j++) {
          sd = 0
          for (k = 0; k < m; k++) {
            sd += At[i * astep + k] * At[j * astep + k]
          }
          asum = 0.0
          for (k = 0; k < m; k++) {
            t = (At[i * astep + k] - sd * At[j * astep + k])
            At[i * astep + k] = t
            asum += Math.abs(t)
          }
          asum = asum ? 1.0 / asum : 0
          for (k = 0; k < m; k++) {
            At[i * astep + k] *= asum
          }
        }
      }
      sd = 0
      for (k = 0; k < m; k++) {
        t = At[i * astep + k]
        sd += t * t
      }
      sd = Math.sqrt(sd)
    }

    s = (1.0 / sd)
    for (k = 0; k < m; k++) {
      At[i * astep + k] *= s
    }
  }
}

function svd (A, W, U, V) {
  var at = 0
  var i = 0
  var _m = A.rows
  var _n = A.cols
  var m = _m
  var n = _n

  if (m < n) {
    at = 1
    i = m
    m = n
    n = i
  }

  var amt = new Matrix(m, m)
  var wmt = new Matrix(1, n)
  var vmt = new Matrix(n, n)

  if (at === 0) {
    transpose(amt, A)
  } else {
    for (i = 0; i < _n * _m; i++) {
      amt.data[i] = A.data[i]
    }
    for (; i < n * m; i++) {
      amt.data[i] = 0
    }
  }

  JacobiSVDImpl(amt.data, m, wmt.data, vmt.data, n, m, n, m)

  if (W) {
    for (i = 0; i < n; i++) {
      W.data[i] = wmt.data[i]
    }
    for (; i < _n; i++) {
      W.data[i] = 0
    }
  }

  if (at === 0) {
    if (U) transpose(U, amt)
    if (V) transpose(V, vmt)
  } else {
    if (U) transpose(U, vmt)
    if (V) transpose(V, amt)
  }
}

//

function m4new () {
  return new Float32Array([
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  ])
}

function m4set (out, n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) {
  out[ 0 ] = n11; out[ 4 ] = n12; out[ 8 ] = n13; out[ 12 ] = n14
  out[ 1 ] = n21; out[ 5 ] = n22; out[ 9 ] = n23; out[ 13 ] = n24
  out[ 2 ] = n31; out[ 6 ] = n32; out[ 10 ] = n33; out[ 14 ] = n34
  out[ 3 ] = n41; out[ 7 ] = n42; out[ 11 ] = n43; out[ 15 ] = n44
}

function m4identity (out) {
  m4set(out,
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    )
}
m4identity.__deps = [ m4set ]

function m4multiply (out, a, b) {
  var a11 = a[ 0 ]
  var a12 = a[ 4 ]
  var a13 = a[ 8 ]
  var a14 = a[ 12 ]
  var a21 = a[ 1 ]
  var a22 = a[ 5 ]
  var a23 = a[ 9 ]
  var a24 = a[ 13 ]
  var a31 = a[ 2 ]
  var a32 = a[ 6 ]
  var a33 = a[ 10 ]
  var a34 = a[ 14 ]
  var a41 = a[ 3 ]
  var a42 = a[ 7 ]
  var a43 = a[ 11 ]
  var a44 = a[ 15 ]

  var b11 = b[ 0 ]
  var b12 = b[ 4 ]
  var b13 = b[ 8 ]
  var b14 = b[ 12 ]
  var b21 = b[ 1 ]
  var b22 = b[ 5 ]
  var b23 = b[ 9 ]
  var b24 = b[ 13 ]
  var b31 = b[ 2 ]
  var b32 = b[ 6 ]
  var b33 = b[ 10 ]
  var b34 = b[ 14 ]
  var b41 = b[ 3 ]
  var b42 = b[ 7 ]
  var b43 = b[ 11 ]
  var b44 = b[ 15 ]

  out[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41
  out[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42
  out[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43
  out[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44

  out[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41
  out[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42
  out[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43
  out[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44

  out[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41
  out[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42
  out[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43
  out[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44

  out[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41
  out[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42
  out[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43
  out[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44
}

function m4makeScale (out, x, y, z) {
  m4set(out,
    x, 0, 0, 0,
    0, y, 0, 0,
    0, 0, z, 0,
    0, 0, 0, 1
  )
}
m4makeScale.__deps = [ m4set ]

function m4makeTranslation (out, x, y, z) {
  m4set(out,
    1, 0, 0, x,
    0, 1, 0, y,
    0, 0, 1, z,
    0, 0, 0, 1
  )
}
m4makeTranslation.__deps = [ m4set ]

function m4makeRotationY (out, theta) {
  var c = Math.cos(theta)
  var s = Math.sin(theta)
  m4set(out,
    c, 0, s, 0,
    0, 1, 0, 0,
    -s, 0, c, 0,
    0, 0, 0, 1
  )
}
m4makeRotationY.__deps = [ m4set ]

//

function m3new () {
  return new Float32Array([
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
  ])
}

function m3makeNormal (out, m4) {
  var r0 = v3new([ m4[0], m4[1], m4[2] ])
  var r1 = v3new([ m4[4], m4[5], m4[6] ])
  var r2 = v3new([ m4[8], m4[9], m4[10] ])
  var cp = v3new()
  //        [ r0 ]       [ r1 x r2 ]
  // M3x3 = [ r1 ]   N = [ r2 x r0 ]
  //        [ r2 ]       [ r0 x r1 ]
  v3cross(cp, r1, r2)
  out[ 0 ] = cp[ 0 ]
  out[ 1 ] = cp[ 1 ]
  out[ 2 ] = cp[ 2 ]
  v3cross(cp, r2, r0)
  out[ 3 ] = cp[ 0 ]
  out[ 4 ] = cp[ 1 ]
  out[ 5 ] = cp[ 2 ]
  v3cross(cp, r0, r1)
  out[ 6 ] = cp[ 0 ]
  out[ 7 ] = cp[ 1 ]
  out[ 8 ] = cp[ 2 ]
}
m3makeNormal.__deps = [ v3new, v3cross ]

export {
  Matrix,
  svd,
  meanRows,
  meanCols,
  subRows,
  subCols,
  addRows,
  addCols,
  transpose,
  multiply,
  multiplyABt,
  multiplyAtB,
  invert3x3,
  multiply3x3,
  mat3x3determinant,

  m4new,
  m4identity,
  m4multiply,
  m4makeScale,
  m4makeTranslation,
  m4makeRotationY,

  m3new,
  m3makeNormal
}