UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

530 lines (414 loc) 14.4 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"; import { BinaryDataType } from "../../../binary/type/BinaryDataType.js"; import { SquareMatrix } from "../../../math/matrix/SquareMatrix.js"; import { vector_dot_offset } from "../../vec/vector_dot_offset.js"; import { vector_axpy_offset } from "../../vec/vector_axpy_offset.js"; import { givens_rotation_coefficients } from "../../../math/linalg/givens/givens_rotation_coefficients.js"; import { givens_apply_rows_offset } from "../../../math/linalg/givens/givens_apply_rows_offset.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); // Q and R are dim x dim, column-major (per SquareMatrix). The k-th // column lives contiguously at offsets [k*dim, (k+1)*dim). Inner // loops index the flat .data buffer directly with offset arithmetic // rather than going through subarray views — see vector_dot_offset // and friends. /** * @type {SquareMatrix} */ this.Q = new SquareMatrix(dim, BinaryDataType.Float64); this.Q.eye(); /** * @type {SquareMatrix} */ this.R = new SquareMatrix(dim, BinaryDataType.Float64); /** * D sized vector * @type {Float64Array} */ this.u = new Float64Array(dim); /** * D sized vector * @type {Float64Array} */ this.w = new Float64Array(dim); this.r = 0; /** * Givens rotation coefficients scratch: cs[0] = c, cs[1] = s. * Holding this as an instance field avoids per-call allocation of a * fresh 2-element array — see givens_rotation_coefficients's contract. * @type {Float64Array} */ this.cs = new Float64Array(2); 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]; } /** * 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 dim = this.dim; const u = this.u; const Q = this.Q.data; const R = this.R.data; const cs = this.cs; const rOff = this.r * dim; for (let i = 0; i < dim; ++i) { R[rOff + i] = vector_dot_offset(Q, i * dim, u, 0, dim); } for (let j = dim - 1; j > this.r; --j) { givens_rotation_coefficients(R[rOff + j - 1], R[rOff + j], cs); const c = cs[0]; const s = cs[1]; R[rOff + j - 1] = c * R[rOff + j - 1] + s * R[rOff + j]; givens_apply_rows_offset(Q, (j - 1) * dim, j * dim, c, s, dim); } } /** * 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.data; const r = this.r; for (let j = 0; j < r; ++j) { const qOff = j * dim; const scale = vector_dot_offset(w, 0, Q, qOff, dim); vector_axpy_offset(w, 0, -scale, Q, qOff, dim); } return vector_dot_offset(w, 0, w, 0, dim); } /** * 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.data; for (let i = 0; i < dim; ++i) { w[i] = vector_dot_offset(Q, i * dim, u, 0, dim); } let origin_lambda = 1; const R = this.R.data; const r = this.r; for (let j = r - 1; j >= 0; --j) { const jjOff = j * dim; for (let k = j + 1; k < r; ++k) { w[j] -= lambdas[k] * R[k * dim + j]; } const lj = w[j] / R[jjOff + j]; lambdas[j] = lj; origin_lambda -= lj; } lambdas[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.data; const Q = this.Q.data; const dim = this.dim; const cs = this.cs; const r = this.r; for (; cursor < r; ++cursor) { const cOff = cursor * dim; givens_rotation_coefficients(R[cOff + cursor], R[cOff + cursor + 1], cs); const c = cs[0]; const s = cs[1]; R[cOff + cursor] = c * R[cOff + cursor] + s * R[cOff + cursor + 1]; for (let j = cursor + 1; j < r; ++j) { const jOff = j * dim; const a = R[jOff + cursor]; const b = R[jOff + cursor + 1]; R[jOff + cursor] = c * a + s * b; R[jOff + cursor + 1] = c * b - s * a; } givens_apply_rows_offset(Q, cOff, cOff + dim, c, s, dim); } } /** * 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.data; const R = this.R.data; const cs = this.cs; const r = this.r; for (let i = 0; i < dim; ++i) { w[i] = vector_dot_offset(Q, i * dim, u, 0, dim); } for (let k = dim - 1; k > 0; --k) { givens_rotation_coefficients(w[k - 1], w[k], cs); const c = cs[0]; const s = cs[1]; w[k - 1] = c * w[k - 1] + s * w[k]; const prevOff = (k - 1) * dim; R[prevOff + k] = -s * R[prevOff + k - 1]; R[prevOff + k - 1] *= c; for (let j = k; j < r; ++j) { const jOff = j * dim; const a = R[jOff + k - 1]; const b = R[jOff + k]; R[jOff + k - 1] = c * a + s * b; R[jOff + k] = c * b - s * a; } givens_apply_rows_offset(Q, prevOff, prevOff + dim, c, s, dim); } const w0 = w[0]; for (let j = 0; j < r; ++j) { R[j * dim] += w0; } 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.data; const dim = this.dim; const members = this.members; // Shift the column block [index+1 .. r-1] down by one position. // Each column is `dim` contiguous floats in column-major storage, // so the whole shift is a single copyWithin call. const dstOff = index * dim; const srcOff = (index + 1) * dim; const endOff = this.r * dim; R.copyWithin(dstOff, srcOff, endOff); for (let j = index + 1; j < this.r; ++j) { members[j - 1] = members[j]; } members[this.r - 1] = members[this.r]; --this.r; this.hessenberg_clear(index); } } }