@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
583 lines (461 loc) • 15 kB
JavaScript
/**
* @author Alex Goldring 14/05/2018 - Ported to JS using JSweet + manual editing
* @author Kaspar Fischer (hbf) 09/05/2013
* @source https://github.com/hbf/miniball
* @generated Generated from Java with JSweet 2.2.0-SNAPSHOT - http://www.jsweet.org
*/
import { assert } from "../../../assert.js";
import { BitSet } from "../../../binary/BitSet.js";
export class Subspan {
/**
*
* @param {number} dim dimension count
* @param {PointSet} s
* @param {number} k
*/
constructor(dim, s, k) {
assert.isNonNegativeInteger(dim, 'dim');
assert.defined(s, 's');
assert.isNonNegativeInteger(k, 'k');
/**
* @type {PointSet}
*/
this.S = s;
/**
* reserve bit-field size
* @type {BitSet}
*/
this.membership = BitSet.fixedSize(s.size());
/**
*
* @type {number}
*/
this.dim = dim;
/**
*
* @type {Int32Array}
*/
this.members = new Int32Array(dim + 1);
const Q = new Array(dim);
const R = new Array(dim);
for (let i = 0; i < dim; i++) {
Q[i] = new Array(dim);
R[i] = new Array(dim);
}
// initialize Q vector
for (let i = 0; i < dim; i++) {
for (let j = 0; j < dim; j++) {
// set diagonal to 1
Q[j][i] = (i === j) ? 1.0 : 0.0;
}
}
/**
* DxD square matrix, where D is number of dimensions
* @type {number[][]}
*/
this.Q = Q;
/**
* DxD square matrix, where D is number of dimensions
* @type {number[][]}
*/
this.R = R;
/**
* D sized vector
* @type {number[]}
*/
this.u = new Array(dim);
/**
* D sized vector
* @type {number[]}
*/
this.w = new Array(dim);
this.r = 0;
/**
* Givens C coefficient
* @type {number}
*/
this.c = 0;
/**
* Givens S coefficient
* @type {number}
*/
this.s = 0;
this.membership.set(k, true);
this.members[this.r] = k;
}
dimension() {
return this.dim;
}
/**
* The size of the instance's set <i>M</i>, a number between 0 and `dim+1`.
*
* Complexity: O(1).
*
* @returns {int} <i>|M|</i>
*/
size() {
return this.r + 1;
}
/**
* Whether <i>S[i]</i> is a member of <i>M</i>.
*
* Complexity: O(1)
*
* @param {int} i
* the "global" index into <i>S</i>
* @returns {boolean} true iff <i>S[i]</i> is a member of <i>M</i>
*/
isMember(i) {
return this.membership.get(i);
}
/**
* The global index (into <i>S</i>) of an arbitrary element of <i>M</i>.
*
* Precondition: `size()>0`
*
* Postcondition: `isMember(anyMember())`
* @returns {number}
*/
anyMember() {
return this.members[this.r];
}
/**
* The index (into <i>S</i>) of the <i>i</i>th point in <i>M</i>. The points in <i>M</i> are
* internally ordered (in an arbitrary way) and this order only changes when {@link add()} or
* {@link remove()} is called.
*
* Complexity: O(1)
*
* @param {number} i
* the "local" index, 0 ≤ i < `size()`
* @return {number} <i>j</i> such that <i>S[j]</i> equals the <i>i</i>th point of M
*/
globalIndex(i) {
return this.members[i];
}
/**
* Short-hand for code readability to access element <i>(i,j)</i> of a matrix that is stored in a
* one-dimensional array.
*
* @param {number} i
* zero-based row number
* @param {number} j
* zero-based column number
* @return {number} the index into the one-dimensional array to get the element at position <i>(i,j)</i> in
* the matrix
* @private
*/
ind(i, j) {
return i * this.dim + j;
}
/**
* The point `members[r]` is called the <i>origin</i>.
*
* @return {number} index into <i>S</i> of the origin.
* @private
*/
origin() {
return this.members[this.r];
}
/**
* Determine the Givens coefficients <i>(c,s)</i> satisfying
*
* <pre>
* c * a + s * b = +/- (a^2 + b^2) c * b - s * a = 0
* </pre>
*
* We don't care about the signs here, for efficiency, so make sure not to rely on them anywhere.
*
* <i>Source:</i> "Matrix Computations" (2nd edition) by Gene H. B. Golub & Charles F. B. Van Loan
* (Johns Hopkins University Press, 1989), p. 216.
*
* Note that the code of this class sometimes does not call this method but only mentions it in a
* comment. The reason for this is performance; Java does not allow an efficient way of returning
* a pair of doubles, so we sometimes manually "inline" `givens()` for the sake of
* performance.
* @param {number} a
* @param {number} b
* @private
*/
givens(a, b) {
if (b === 0.0) {
this.c = 1.0;
this.s = 0.0;
} else if (Math.abs(b) > Math.abs(a)) {
const t = a / b;
this.s = 1 / Math.sqrt(1 + t * t);
this.c = this.s * t;
} else {
const t = b / a;
this.c = 1 / Math.sqrt(1 + t * t);
this.s = this.c * t;
}
}
/**
* Appends the new column <i>u</i> (which is a member field of this instance) to the right of <i>A
* = QR</i>, updating <i>Q</i> and <i>R</i>. It assumes <i>r</i> to still be the old value, i.e.,
* the index of the column used now for insertion; <i>r</i> is not altered by this routine and
* should be changed by the caller afterwards.
*
* Precondition: `r<dim`
* @private
*/
appendColumn() {
const R = this.R;
const Q = this.Q;
const dim = this.dim;
for (let i = 0; i < dim; ++i) {
R[this.r][i] = 0;
for (let k = 0; k < dim; ++k) {
R[this.r][i] += Q[i][k] * this.u[k];
}
}
for (let j = dim - 1; j > this.r; --j) {
this.givens(R[this.r][j - 1], R[this.r][j]);
R[this.r][j - 1] = this.c * R[this.r][j - 1] + this.s * R[this.r][j];
for (let i = 0; i < dim; ++i) {
let a = Q[j - 1][i];
let b = Q[j][i];
Q[j - 1][i] = this.c * a + this.s * b;
Q[j][i] = this.c * b - this.s * a;
}
}
}
/**
* Adds the point <i>S[index]</i> to the instance's set <i>M</i>.
*
* Precondition: `!isMember(index)`
*
* Complexity: O(dim^2).
*
* @param {number} index index into <i>S</i> of the point to add
*/
add(index) {
let o = this.origin();
const dim = this.dim;
const u = this.u;
const S = this.S;
for (let i = 0; i < dim; ++i) {
u[i] = S.coord(index, i) - S.coord(o, i);
}
this.appendColumn();
this.membership.set(index, true);
this.members[this.r + 1] = this.members[this.r];
this.members[this.r] = index;
++this.r;
}
/**
* Computes the vector <i>w</i> directed from point <i>p</i> to <i>v</i>, where <i>v</i> is the
* point in <i>aff(M)</i> that lies nearest to <i>p</i>.
*
* Precondition: `size()>0`
*
* Complexity: O(dim^2)
*
* @param {number[]} p
* @param {number[]} w Euclidean coordinates of point <i>p</i>
* @return {number} the squared length of <i>w</i>
*/
shortestVectorToSpan(p, w) {
const o = this.origin();
const dim = this.dim;
const S = this.S;
for (let i = 0; i < dim; ++i) {
w[i] = S.coord(o, i) - p[i];
}
const Q = this.Q;
const r = this.r;
for (let j = 0; j < r; ++j) {
let scale = 0;
const QJ = Q[j];
for (let i = 0; i < dim; ++i) {
scale += w[i] * QJ[i];
}
for (let i = 0; i < dim; ++i) {
w[i] -= scale * QJ[i];
}
}
let sl = 0;
for (let i = 0; i < dim; ++i) {
sl += w[i] * w[i];
}
return sl;
}
/**
* Use this for testing only; the method allocates additional storage and copies point
* coordinates.
* @return {number}
*/
representationError() {
const size = this.size();
const lambdas = new Float64Array(size);
const dim = this.dim;
const pt = new Float64Array(dim);
let max = 0;
for (let j = 0; j < size; ++j) {
for (let i = 0; i < dim; ++i) {
pt[i] = this.S.coord(this.globalIndex(j), i);
}
this.findAffineCoefficients(pt, lambdas);
let error = Math.abs(lambdas[j] - 1.0);
if (error > max) {
max = error;
}
for (let i = 0; i < j; ++i) {
error = Math.abs(lambdas[i]);
if (error > max) {
max = error;
}
}
// skip over lambdas[j]
for (let i = j + 1; i < size; ++i) {
error = Math.abs(lambdas[i]);
if (error > max) {
max = error;
}
}
}
return max;
}
/**
* Calculates the `size()`-many coefficients in the representation of <i>p</i> as an affine
* combination of the points <i>M</i>.
*
* The <i>i</i>th computed coefficient `lambdas[i] `corresponds to the <i>i</i>th point in
* <i>M</i>, or, in other words, to the point in <i>S</i> with index `globalIndex(i)`.
*
* Complexity: O(dim^2)
*
* Preconditions: c lies in the affine hull aff(M) and size() > 0.
* @param {number[]|Float64Array} p
* @param {number[]|Float64Array} lambdas
*/
findAffineCoefficients(p, lambdas) {
let o = this.origin();
const dim = this.dim;
const u = this.u;
for (let i = 0; i < dim; ++i) {
u[i] = p[i] - this.S.coord(o, i);
}
const w = this.w;
const Q = this.Q;
for (let i = 0; i < dim; ++i) {
w[i] = 0;
for (let k = 0; k < dim; ++k) {
w[i] += Q[i][k] * u[k];
}
}
let origin_lambda = 1;
const R = this.R;
for (let j = this.r - 1; j >= 0; --j) {
for (let k = j + 1; k < this.r; ++k) {
w[j] -= lambdas[k] * R[k][j];
}
const lj = w[j] / R[j][j];
lambdas[j] = lj;
origin_lambda -= lj;
}
lambdas[this.r] = origin_lambda;
}
/**
* Given <i>R</i> in lower Hessenberg form with subdiagonal entries 0 to `pos-1` already all
* zero, clears the remaining subdiagonal entries via Givens rotations.
* @param {number} pos
* @private
*/
hessenberg_clear(pos) {
let cursor = pos;
const R = this.R;
const Q = this.Q;
const dim = this.dim;
for (; cursor < this.r; ++cursor) {
this.givens(R[cursor][cursor], R[cursor][cursor + 1]);
R[cursor][cursor] = this.c * R[cursor][cursor] + this.s * R[cursor][cursor + 1];
for (let j = cursor + 1; j < this.r; ++j) {
const a = R[j][cursor];
const b = R[j][cursor + 1];
R[j][cursor] = this.c * a + this.s * b;
R[j][cursor + 1] = this.c * b - this.s * a;
}
for (let i = 0; i < dim; ++i) {
const a = Q[cursor][i];
const b = Q[cursor + 1][i];
Q[cursor][i] = this.c * a + this.s * b;
Q[cursor + 1][i] = this.c * b - this.s * a;
}
}
}
/**
* Update current QR-decomposition <i>A = QR</i> to
*
* <pre>
* A + u * [1,...,1] = Q' R'.
* </pre>
* @private
*/
special_rank_1_update() {
const dim = this.dim;
const w = this.w;
const u = this.u;
const Q = this.Q;
const R = this.R;
for (let i = 0; i < dim; ++i) {
w[i] = 0;
for (let k = 0; k < dim; ++k) {
w[i] += Q[i][k] * u[k];
}
}
for (let k = dim - 1; k > 0; --k) {
this.givens(w[k - 1], w[k]);
w[k - 1] = this.c * w[k - 1] + this.s * w[k];
R[k - 1][k] = -this.s * R[k - 1][k - 1];
R[k - 1][k - 1] *= this.c;
for (let j = k; j < this.r; ++j) {
const a = R[j][k - 1];
const b = R[j][k];
R[j][k - 1] = this.c * a + this.s * b;
R[j][k] = this.c * b - this.s * a;
}
for (let i = 0; i < dim; ++i) {
const a = Q[k - 1][i];
const b = Q[k][i];
Q[k - 1][i] = this.c * a + this.s * b;
Q[k][i] = this.c * b - this.s * a;
}
}
for (let j = 0; j < this.r; ++j) {
R[j][0] += w[0];
}
this.hessenberg_clear(0);
}
/**
*
* @param {number} index
*/
remove(index) {
this.membership.clear(this.globalIndex(index));
if (index === this.r) {
let o = this.origin();
let gi = this.globalIndex(this.r - 1);
const dim = this.dim;
const S = this.S;
const u = this.u;
for (let i = 0; i < dim; ++i) {
u[i] = S.coord(o, i) - S.coord(gi, i);
}
--this.r;
this.special_rank_1_update();
} else {
const R = this.R;
const dummy = R[index];
const members = this.members;
for (let j = index + 1; j < this.r; ++j) {
R[j - 1] = R[j];
members[j - 1] = members[j];
}
members[this.r - 1] = members[this.r];
R[--this.r] = dummy;
this.hessenberg_clear(index);
}
}
}