UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

583 lines (461 loc) 15 kB
/** * @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); } } }