//---------------------------------------------------------------------------- // ThreeD Quadric Error Function //---------------------------------------------------------------------------- #include #include namespace PolyVox { //---------------------------------------------------------------------------- #define MAXROWS 12 #define EPSILON 1e-5 //---------------------------------------------------------------------------- Vector3DFloat evaluateQEF( double mat[][3], double *vec, int rows) { // perform singular value decomposition on matrix mat // into u, v and d. // u is a matrix of rows x 3 (same as mat); // v is a square matrix 3 x 3 (for 3 columns in mat); // d is vector of 3 values representing the diagonal // matrix 3 x 3 (for 3 colums in mat). double u[MAXROWS][3], v[3][3], d[3]; computeSVD(mat, u, v, d, rows); // solve linear system given by mat and vec using the // singular value decomposition of mat into u, v and d. if (d[2] < 0.1) d[2] = 0.0; if (d[1] < 0.1) d[1] = 0.0; if (d[0] < 0.1) d[0] = 0.0; double x[3]; solveSVD(u, v, d, vec, x, rows); return {static_cast(x[0]), static_cast(x[1]), static_cast(x[2])}; } //---------------------------------------------------------------------------- void computeSVD( double mat[][3], // matrix (rows x 3) double u[][3], // matrix (rows x 3) double v[3][3], // matrix (3x3) double d[3], // vector (1x3) int rows) { std::memcpy(u, mat, rows * 3 * sizeof(double)); double *tau_u = d; double tau_v[2]; factorize(u, tau_u, tau_v, rows); unpack(u, v, tau_u, tau_v, rows); diagonalize(u, v, tau_u, tau_v, rows); singularize(u, v, tau_u, rows); } //---------------------------------------------------------------------------- void factorize( double mat[][3], // matrix (rows x 3) double tau_u[3], // vector, (1x3) double tau_v[2], // vector, (1x2) int rows) { int y; // bidiagonal factorization of (rows x 3) matrix into :- // tau_u, a vector of 1x3 (for 3 columns in the matrix) // tau_v, a vector of 1x2 (one less column than the matrix) for (int i = 0; i < 3; ++i) { // set up a vector to reference into the matrix // from mat(i,i) to mat(m,i), that is, from the // i'th column of the i'th row and down all the way // through that column double *ptrs[MAXROWS]; int num_ptrs = rows - i; for (int q = 0; q < num_ptrs; ++q) ptrs[q] = &mat[q + i][i]; // perform householder transformation on this vector double tau = factorize_hh(ptrs, num_ptrs); tau_u[i] = tau; // all computations below this point are performed // only for the first two columns: i=0 or i=1 if (i + 1 < 3) { // perform householder transformation on the matrix // mat(i,i+1) to mat(m,n), that is, on the sub-matrix // that begins in the (i+1)'th column of the i'th // row and extends to the end of the matrix at (m,n) if (tau != 0.0) { for (int x = i + 1; x < 3; ++x) { double wx = mat[i][x]; for (y = i + 1; y < rows; ++y) wx += mat[y][x] * (*ptrs[y - i]); double tau_wx = tau * wx; mat[i][x] -= tau_wx; for (y = i + 1; y < rows; ++y) mat[y][x] -= tau_wx * (*ptrs[y - i]); } } // perform householder transformation on i'th row // (remember at this point, i is either 0 or 1) // set up a vector to reference into the matrix // from mat(i,i+1) to mat(i,n), that is, from the // (i+1)'th column of the i'th row and all the way // through to the end of that row ptrs[0] = &mat[i][i + 1]; if (i == 0) { ptrs[1] = &mat[i][i + 2]; num_ptrs = 2; } else // i == 1 num_ptrs = 1; // perform householder transformation on this vector tau = factorize_hh(ptrs, num_ptrs); tau_v[i] = tau; // perform householder transformation on the sub-matrix // mat(i+1,i+1) to mat(m,n), that is, on the sub-matrix // that begins in the (i+1)'th column of the (i+1)'th // row and extends to the end of the matrix at (m,n) if (tau != 0.0) { for (y = i + 1; y < rows; ++y) { double wy = mat[y][i + 1]; if (i == 0) wy += mat[y][i + 2] * (*ptrs[1]); double tau_wy = tau * wy; mat[y][i + 1] -= tau_wy; if (i == 0) mat[y][i + 2] -= tau_wy * (*ptrs[1]); } } } // if (i + 1 < 3) } } //---------------------------------------------------------------------------- double factorize_hh(double *ptrs[], int n) { double tau = 0.0; if (n > 1) { double xnorm; if (n == 2) xnorm = std::abs(*ptrs[1]); else { double scl = 0.0; double ssq = 1.0; for (int i = 1; i < n; ++i) { double x = std::abs(*ptrs[i]); if (x != 0.0) { if (scl < x) { ssq = 1.0 + ssq * (scl / x) * (scl / x); scl = x; } else ssq += (x / scl) * (x / scl); } } xnorm = scl * sqrt(ssq); } if (xnorm != 0.0) { double alpha = *ptrs[0]; double beta = sqrt(alpha * alpha + xnorm * xnorm); if (alpha >= 0.0) beta = -beta; tau = (beta - alpha) / beta; double scl = 1.0 / (alpha - beta); *ptrs[0] = beta; for (int i = 1; i < n; ++i) *ptrs[i] *= scl; } } return tau; } //---------------------------------------------------------------------------- void unpack( double u[][3], // matrix (rows x 3) double v[3][3], // matrix (3x3) double tau_u[3], // vector, (1x3) double tau_v[2], // vector, (1x2) int rows) { int i, y; // reset v to the identity matrix v[0][0] = v[1][1] = v[2][2] = 1.0; v[0][1] = v[0][2] = v[1][0] = v[1][2] = v[2][0] = v[2][1] = 0.0; for (i = 1; i >= 0; --i) { double tau = tau_v[i]; // perform householder transformation on the sub-matrix // v(i+1,i+1) to v(m,n), that is, on the sub-matrix of v // that begins in the (i+1)'th column of the (i+1)'th row // and extends to the end of the matrix at (m,n). the // householder vector used to perform this is the vector // from u(i,i+1) to u(i,n) if (tau != 0.0) { for (int x = i + 1; x < 3; ++x) { double wx = v[i + 1][x]; for (y = i + 1 + 1; y < 3; ++y) wx += v[y][x] * u[i][y]; double tau_wx = tau * wx; v[i + 1][x] -= tau_wx; for (y = i + 1 + 1; y < 3; ++y) v[y][x] -= tau_wx * u[i][y]; } } } // copy superdiagonal of u into tau_v for (i = 0; i < 2; ++i) tau_v[i] = u[i][i + 1]; // below, same idea for u: householder transformations // and the superdiagonal copy for (i = 2; i >= 0; --i) { // copy superdiagonal of u into tau_u double tau = tau_u[i]; tau_u[i] = u[i][i]; // perform householder transformation on the sub-matrix // u(i,i) to u(m,n), that is, on the sub-matrix of u that // begins in the i'th column of the i'th row and extends // to the end of the matrix at (m,n). the householder // vector used to perform this is the i'th column of u, // that is, u(0,i) to u(m,i) if (tau == 0.0) { u[i][i] = 1.0; if (i < 2) { u[i][2] = 0.0; if (i < 1) u[i][1] = 0.0; } for (y = i + 1; y < rows; ++y) u[y][i] = 0.0; } else { for (int x = i + 1; x < 3; ++x) { double wx = 0.0; for (y = i + 1; y < rows; ++y) wx += u[y][x] * u[y][i]; double tau_wx = tau * wx; u[i][x] = -tau_wx; for (y = i + 1; y < rows; ++y) u[y][x] -= tau_wx * u[y][i]; } for (y = i + 1; y < rows; ++y) u[y][i] = u[y][i] * -tau; u[i][i] = 1.0 - tau; } } } //---------------------------------------------------------------------------- void diagonalize( double u[][3], // matrix (rows x 3) double v[3][3], // matrix (3x3) double tau_u[3], // vector, (1x3) double tau_v[2], // vector, (1x2) int rows) { int i, j; chop(tau_u, tau_v, 3); // progressively reduce the matrices into diagonal form int b = 3 - 1; while (b > 0) { if (tau_v[b - 1] == 0.0) --b; else { int a = b - 1; while (a > 0 && tau_v[a - 1] != 0.0) --a; int n = b - a + 1; double u1[MAXROWS][3]; double v1[3][3]; for (j = a; j <= b; ++j) { for (i = 0; i < rows; ++i) u1[i][j - a] = u[i][j]; for (i = 0; i < 3; ++i) v1[i][j - a] = v[i][j]; } qrstep(u1, v1, &tau_u[a], &tau_v[a], rows, n); for (j = a; j <= b; ++j) { for (i = 0; i < rows; ++i) u[i][j] = u1[i][j - a]; for (i = 0; i < 3; ++i) v[i][j] = v1[i][j - a]; } chop(&tau_u[a], &tau_v[a], n); } } } //---------------------------------------------------------------------------- void chop(double *a, double *b, int n) { double ai = a[0]; for (int i = 0; i < n - 1; ++i) { double bi = b[i]; double ai1 = a[i + 1]; if (std::abs(bi) < EPSILON * (std::abs(ai) + std::abs(ai1))) b[i] = 0.0; ai = ai1; } } //---------------------------------------------------------------------------- void qrstep( double u[][3], // matrix (rows x cols) double v[][3], // matrix (3 x cols) double tau_u[], // vector (1 x cols) double tau_v[], // vector (1 x cols - 1) int rows, int cols) { int i; if (cols == 2) { qrstep_cols2(u, v, tau_u, tau_v, rows); return; } // handle zeros on the diagonal or at its end for (i = 0; i < cols - 1; ++i) if (tau_u[i] == 0.0) { qrstep_middle(u, tau_u, tau_v, rows, cols, i); return; } if (tau_u[cols - 1] == 0.0) { qrstep_end(v, tau_u, tau_v, cols); return; } // perform qr reduction on the diagonal and off-diagonal double mu = qrstep_eigenvalue(tau_u, tau_v, cols); double y = tau_u[0] * tau_u[0] - mu; double z = tau_u[0] * tau_v[0]; double ak = 0.0; double bk = 0.0; double zk; double ap = tau_u[0]; double bp = tau_v[0]; double aq = tau_u[1]; double bq = tau_v[1]; for (int k = 0; k < cols - 1; ++k) { double c, s; // perform Givens rotation on V computeGivens(y, z, &c, &s); for (i = 0; i < 3; ++i) { double vip = v[i][k]; double viq = v[i][k + 1]; v[i][k] = vip * c - viq * s; v[i][k + 1] = vip * s + viq * c; } // perform Givens rotation on B double bk1 = bk * c - z * s; double ap1 = ap * c - bp * s; double bp1 = ap * s + bp * c; double zp1 = aq * -s; double aq1 = aq * c; if (k > 0) tau_v[k - 1] = bk1; ak = ap1; bk = bp1; zk = zp1; ap = aq1; if (k < cols - 2) bp = tau_v[k + 1]; else bp = 0.0; y = ak; z = zk; // perform Givens rotation on U computeGivens(y, z, &c, &s); for (i = 0; i < rows; ++i) { double uip = u[i][k]; double uiq = u[i][k + 1]; u[i][k] = uip * c - uiq * s; u[i][k + 1] = uip * s + uiq * c; } // perform Givens rotation on B double ak1 = ak * c - zk * s; bk1 = bk * c - ap * s; double zk1 = bp * -s; ap1 = bk * s + ap * c; bp1 = bp * c; tau_u[k] = ak1; ak = ak1; bk = bk1; zk = zk1; ap = ap1; bp = bp1; if (k < cols - 2) aq = tau_u[k + 2]; else aq = 0.0; y = bk; z = zk; } tau_v[cols - 2] = bk; tau_u[cols - 1] = ap; } //---------------------------------------------------------------------------- void qrstep_middle( double u[][3], // matrix (rows x cols) double tau_u[], // vector (1 x cols) double tau_v[], // vector (1 x cols - 1) int rows, int cols, int col) { double x = tau_v[col]; double y = tau_u[col + 1]; for (int j = col; j < cols - 1; ++j) { double c, s; // perform Givens rotation on U computeGivens(y, -x, &c, &s); for (int i = 0; i < rows; ++i) { double uip = u[i][col]; double uiq = u[i][j + 1]; u[i][col] = uip * c - uiq * s; u[i][j + 1] = uip * s + uiq * c; } // perform transposed Givens rotation on B tau_u[j + 1] = x * s + y * c; if (j == col) tau_v[j] = x * c - y * s; if (j < cols - 2) { double z = tau_v[j + 1]; tau_v[j + 1] *= c; x = z * -s; y = tau_u[j + 2]; } } } //---------------------------------------------------------------------------- void qrstep_end( double v[][3], // matrix (3 x 3) double tau_u[], // vector (1 x 3) double tau_v[], // vector (1 x 2) int cols) { double x = tau_u[1]; double y = tau_v[1]; for (int k = 1; k >= 0; --k) { double c, s; // perform Givens rotation on V computeGivens(x, y, &c, &s); for (int i = 0; i < 3; ++i) { double vip = v[i][k]; double viq = v[i][2]; v[i][k] = vip * c - viq * s; v[i][2] = vip * s + viq * c; } // perform Givens rotation on B tau_u[k] = x * c - y * s; if (k == 1) tau_v[k] = x * s + y * c; if (k > 0) { double z = tau_v[k - 1]; tau_v[k - 1] *= c; x = tau_u[k - 1]; y = z * s; } } } //---------------------------------------------------------------------------- double qrstep_eigenvalue( double tau_u[], // vector (1 x 3) double tau_v[], // vector (1 x 2) int cols) { double ta = tau_u[1] * tau_u[1] + tau_v[0] * tau_v[0]; double tb = tau_u[2] * tau_u[2] + tau_v[1] * tau_v[1]; double tab = tau_u[1] * tau_v[1]; double dt = (ta - tb) / 2.0; double mu; if (dt >= 0.0) mu = tb - (tab * tab) / (dt + sqrt(dt * dt + tab * tab)); else mu = tb + (tab * tab) / (sqrt(dt * dt + tab * tab) - dt); return mu; } //---------------------------------------------------------------------------- void qrstep_cols2( double u[][3], // matrix (rows x 2) double v[][3], // matrix (3 x 2) double tau_u[], // vector (1 x 2) double tau_v[], // vector (1 x 1) int rows) { int i; double tmp; // eliminate off-diagonal element in [ 0 tau_v0 ] // [ 0 tau_u1 ] // to make [ tau_u[0] 0 ] // [ 0 0 ] if (tau_u[0] == 0.0) { double c, s; // perform transposed Givens rotation on B // multiplied by X = [ 0 1 ] // [ 1 0 ] computeGivens(tau_v[0], tau_u[1], &c, &s); tau_u[0] = tau_v[0] * c - tau_u[1] * s; tau_v[0] = tau_v[0] * s + tau_u[1] * c; tau_u[1] = 0.0; // perform Givens rotation on U for (i = 0; i < rows; ++i) { double uip = u[i][0]; double uiq = u[i][1]; u[i][0] = uip * c - uiq * s; u[i][1] = uip * s + uiq * c; } // multiply V by X, effectively swapping first two columns for (i = 0; i < 3; ++i) { tmp = v[i][0]; v[i][0] = v[i][1]; v[i][1] = tmp; } } // eliminate off-diagonal element in [ tau_u0 tau_v0 ] // [ 0 0 ] else if (tau_u[1] == 0.0) { double c, s; // perform Givens rotation on B computeGivens(tau_u[0], tau_v[0], &c, &s); tau_u[0] = tau_u[0] * c - tau_v[0] * s; tau_v[0] = 0.0; // perform Givens rotation on V for (i = 0; i < 3; ++i) { double vip = v[i][0]; double viq = v[i][1]; v[i][0] = vip * c - viq * s; v[i][1] = vip * s + viq * c; } } // make colums orthogonal, else { double c, s; // perform Schur rotation on B computeSchur(tau_u[0], tau_v[0], tau_u[1], &c, &s); double a11 = tau_u[0] * c - tau_v[0] * s; double a21 = - tau_u[1] * s; double a12 = tau_u[0] * s + tau_v[0] * c; double a22 = tau_u[1] * c; // perform Schur rotation on V for (i = 0; i < 3; ++i) { double vip = v[i][0]; double viq = v[i][1]; v[i][0] = vip * c - viq * s; v[i][1] = vip * s + viq * c; } // eliminate off diagonal elements if ((a11 * a11 + a21 * a21) < (a12 * a12 + a22 * a22)) { // multiply B by X tmp = a11; a11 = a12; a12 = tmp; tmp = a21; a21 = a22; a22 = tmp; // multiply V by X, effectively swapping first // two columns for (i = 0; i < 3; ++i) { tmp = v[i][0]; v[i][0] = v[i][1]; v[i][1] = tmp; } } // perform transposed Givens rotation on B computeGivens(a11, a21, &c, &s); tau_u[0] = a11 * c - a21 * s; tau_v[0] = a12 * c - a22 * s; tau_u[1] = a12 * s + a22 * c; // perform Givens rotation on U for (i = 0; i < rows; ++i) { double uip = u[i][0]; double uiq = u[i][1]; u[i][0] = uip * c - uiq * s; u[i][1] = uip * s + uiq * c; } } } //---------------------------------------------------------------------------- void computeGivens( double a, double b, double *c, double *s) { if (b == 0.0) { *c = 1.0; *s = 0.0; } else if (std::abs(b) > std::abs(a)) { double t = -a / b; double s1 = 1.0 / sqrt(1 + t * t); *s = s1; *c = s1 * t; } else { double t = -b / a; double c1 = 1.0 / sqrt(1 + t * t); *c = c1; *s = c1 * t; } } //---------------------------------------------------------------------------- void computeSchur( double a1, double a2, double a3, double *c, double *s) { double apq = a1 * a2 * 2.0; if (apq == 0.0) { *c = 1.0; *s = 0.0; } else { double t; double tau = (a2 * a2 + (a3 + a1) * (a3 - a1)) / apq; if (tau >= 0.0) t = 1.0 / (tau + sqrt(1.0 + tau * tau)); else t = - 1.0 / (sqrt(1.0 + tau * tau) - tau); *c = 1.0 / sqrt(1.0 + t * t); *s = t * (*c); } } //---------------------------------------------------------------------------- void singularize( double u[][3], // matrix (rows x 3) double v[3][3], // matrix (3x3) double d[3], // vector, (1x3) int rows) { int i, j, y; // make singularize values positive for (j = 0; j < 3; ++j) if (d[j] < 0.0) { for (i = 0; i < 3; ++i) v[i][j] = -v[i][j]; d[j] = -d[j]; } // sort singular values in decreasing order for (i = 0; i < 3; ++i) { double d_max = d[i]; int i_max = i; for (j = i + 1; j < 3; ++j) if (d[j] > d_max) { d_max = d[j]; i_max = j; } if (i_max != i) { // swap eigenvalues double tmp = d[i]; d[i] = d[i_max]; d[i_max] = tmp; // swap eigenvectors for (y = 0; y < rows; ++y) { tmp = u[y][i]; u[y][i] = u[y][i_max]; u[y][i_max] = tmp; } for (y = 0; y < 3; ++y) { tmp = v[y][i]; v[y][i] = v[y][i_max]; v[y][i_max] = tmp; } } } } //---------------------------------------------------------------------------- void solveSVD( double u[][3], // matrix (rows x 3) double v[3][3], // matrix (3x3) double d[3], // vector (1x3) double b[], // vector (1 x rows) double x[3], // vector (1x3) int rows) { static double zeroes[3] = { 0.0, 0.0, 0.0 }; int i, j; // compute vector w = U^T * b double w[3]; std::memcpy(w, zeroes, sizeof(w)); for (i = 0; i < rows; ++i) { if (b[i] != 0.0) for (j = 0; j < 3; ++j) w[j] += b[i] * u[i][j]; } // introduce non-zero singular values in d into w for (i = 0; i < 3; ++i) { if (d[i] != 0.0) w[i] /= d[i]; } // compute result vector x = V * w for (i = 0; i < 3; ++i) { double tmp = 0.0; for (j = 0; j < 3; ++j) tmp += w[j] * v[i][j]; x[i] = tmp; } } }