ts-scikit
Version:
A scientific toolkit written in Typescript
284 lines • 12 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnitSphereSampling = void 0;
const check_1 = require("./check");
class UnitSphereSampling {
constructor(nbits = 16) {
this._nbits = nbits;
this._init(nbits);
}
get countSamples() { return this._npoint; }
get maxIndex() { return this._mindex; }
_init(nbits) {
check_1.Check.argument(nbits >= 4, 'nbits >= 4');
check_1.Check.argument(nbits <= 32, 'nbits <= 32');
// Sampling of the r-s plane with an n-by-n grid. Compute the
// largest m such that the number of sample indices fits in a
// signed integer with the specified number of bits. Note that
// nbits-1 is the number of bits not counting the sign bit. The
// upper limit on the largest positive index is 2^(nbits-1)-1.
// The largest positive index is 1+2*m*(1+m), which also equals
// the number (nindex) of positive indices.
const indexLimit = (1 << (nbits - 1)) - 1;
let m = 1;
while (1 + 2 * m * (1 + m) <= indexLimit) {
++m;
}
this._m = --m;
this._n = 2 * this._m + 1;
// Sampling interval and its inverse in the r-s plane.
this._d = 1.0 / this._m;
this._od = this._m;
// Maximum positive index.
this._mindex = 1 + 2 * this._m * (1 + this._m);
// Constant used for negative indices. Points in upper/lower
// hemispheres are related by pu[index] = -pl[_nindex - index].
this._nindex = this._mindex + 1;
// Number of unique sampled points. The number of unique points on the equator
// is 4 * m; these points (for which z = 0) appear in the tables for both
// upper and lower hemispheres. They have both positive and negative
// indices, and are not counted twice here.
this._npoint = 2 * this._mindex - 4 * this._m; // = 2 + 4 * _m * _m
// Tables for points in upper and lower hemispheres.
this._pu = new Array(this._nindex);
this._pl = new Array(this._nindex);
// Table of point indices.
this._ip = new Array(this._n);
for (let i = 0; i < this._n; ++i) {
this._ip[i] = new Array(this._n);
}
// For all sampled s on flattened octahedron...
for (let is = 0, js = -this._m, index = 0; is < this._n; ++is, ++js) {
// Planar coordinate s and |s|.
const s = js * this._d;
const as = (s >= 0.0) ? s : -s;
// For all sampled r on flattened octahedron...
for (let ir = 0, jr = -this._m; ir < this._n; ++ir, ++jr) {
// Process only samples in the octahedral diamond corresponding
// to the upper and lower hemispheres. Other points in the table
// will be null.
const jrs = Math.abs(jr) + Math.abs(js);
if (jrs <= this._m) {
// Increment and store index in table.
this._ip[is][ir] = ++index;
// Planar coordinate r and |r|.
const r = jr * this._d;
const ar = (r >= 0.0) ? r : -r;
// Third coordinate t (t >= 0) on octahedron.
let t = Math.max(0.0, 1.0 - ar - as);
if (jrs === this._m) {
t = 0.0;
}
// Coordinates of point in upper hemisphere (z >= 0)
const scale = 1.0 / Math.sqrt(s * s + r * r + t * t);
const x = r * scale;
const y = s * scale;
const z = t * scale;
const pu = this._pu[index] = new Array(3);
const pl = this._pl[this._nindex - index] = new Array(3);
pu[0] = x;
pu[1] = y;
pu[2] = z;
pl[0] = -x;
pl[1] = -y;
pl[2] = -z;
}
}
}
}
/**
* Gets an array { ia, ib, ic } of three sample indices for the spherical
* triangle that contains the specified point.
* <p>
* As viewed from outside the sphere, the sampled points corresponding to
* the returned indices are ordered counter-clockwise.
* @returns array of sample indices.
*/
getTriangle(x, y, z) {
if (x instanceof Array) {
y = x[1];
z = x[2];
x = x[0];
}
// Coordinates in r-s plane in [-1, 1].
const ax = (x >= 0.0) ? x : -x;
const ay = (y >= 0.0) ? y : -y;
const az = (z >= 0.0) ? z : -z;
const scale = UnitSphereSampling.ALMOST_ONE / (ax + ay + az);
const r = x * scale;
const s = y * scale;
// Integer grid indices in [0, 2m]. These are the indices of the lower
// left corner of the square in the grid that contains the point.
const rn = (r + 1.0) * this._od;
const sn = (s + 1.0) * this._od;
let ir = Math.floor(rn);
let is = Math.floor(sn);
// Centered integer grid indices in [-m, m]. Useful for determining
// which quadrant the point lies in, and whether indices lie outside
// the sampled diamond. Points not outside satisfy |jr| + |js| <= m.
let jr = ir - this._m;
let js = is - this._m;
// Adjust for points exactly on the equator in quadrant 1. The square
// that contains the point must contain at least one triangle.
if (jr + js === this._m) {
if (jr > 0) {
--jr;
--ir;
}
else {
--js;
--is;
}
}
// Fractional parts in [0, 1). The grid square that contains the point
// is split into two triangles. Squares in quadrants 1 and 3 have
// lower-left and upper-right triangles. Squares in quadrants 2 and
// 4 have upper-left and lower-right triangles. These fractional parts
// are used to determine which triangle contains the point.
const fr = rn - ir;
const fs = sn - is;
// Indices for sampled points of triangle.
let ia, ib, ic;
// If quadrant 1...
if (jr >= 0 && js >= 0) {
if (jr + js + 2 > this._m || fr + fs <= 1.0) {
ia = this._ip[is][ir]; // lower-left triangle
ib = this._ip[is][ir + 1];
ic = this._ip[is + 1][ir];
}
else {
ia = this._ip[is + 1][ir + 1]; // upper-right triangle
ib = this._ip[is + 1][ir];
ic = this._ip[is][ir + 1];
}
}
// Else if quadrant 2...
else if (jr < 0 && js >= 0) {
if (-jr + js + 1 > this._m || fr >= fs) {
ia = this._ip[is][ir + 1]; // lower-right triangle
ib = this._ip[is + 1][ir + 1];
ic = this._ip[is][ir];
}
else {
ia = this._ip[is + 1][ir]; // upper-left triangle
ib = this._ip[is][ir];
ic = this._ip[is + 1][ir + 1];
}
}
// Else if quadrant 3...
else if (jr < 0 && js < 0) {
if (-jr - js > this._m || fr + fs >= 1.0) {
ia = this._ip[is + 1][ir + 1]; // upper-right triangle
ib = this._ip[is + 1][ir];
ic = this._ip[is][ir + 1];
}
else {
ia = this._ip[is][ir]; // lower-left triangle
ib = this._ip[is][ir + 1];
ic = this._ip[is + 1][ir];
}
}
// Else if quadrant 4...
else {
if (jr + 1 - js > this._m || fr <= fs) {
ia = this._ip[is + 1][ir]; // upper-left triangle
ib = this._ip[is][ir];
ic = this._ip[is + 1][ir + 1];
}
else {
ia = this._ip[is][ir + 1]; // lower-right triangle
ib = this._ip[is + 1][ir + 1];
ic = this._ip[is][ir];
}
}
// All indices should be non-zero.
check_1.Check.argument(ia !== 0, 'ia !== 0');
check_1.Check.argument(ib !== 0, 'ib !== 0');
check_1.Check.argument(ic !== 0, 'ic !== 0');
// Signs of indices depend on sign of z. Order the indices so that
// points are in counter-clockwise order when viewed from outside
// the sphere.
return (z >= 0.0)
? [ia, ib, ic]
: [ia - this._nindex, ic - this._nindex, ib - this._nindex];
}
/**
* Gets an array { wa, wb, wc } of three weights for a point in a spherical
* triangle specified by sample indices of three points.
* <p>
* The weights are proportional to volumes of tetrahedra, and are used for
* interpolation. Weights are non-negative and normalized so that their
* sum wa + wb + wc = 1.
* <p>
* For example, let p denote the specified point with coordinates { x, y, z },
* and let o denote the center of the sphere with coordinates { 0, 0, 0 }.
* Then the weight wa is proportional to the volume of the tetrahedron
* formed by points p, b, c, and o.
* @returns array { wa, wb, wc } of weights.
*/
getWeights(x, y, z, iabc) {
if (x instanceof Array && y instanceof Array) {
iabc = Object.assign([], y);
y = x[1];
z = x[2];
x = x[0];
}
const pa = this.getPoint(iabc[0]);
const pb = this.getPoint(iabc[1]);
const pc = this.getPoint(iabc[2]);
const xa = pa[0], ya = pa[1], za = pa[2];
const xb = pb[0], yb = pb[1], zb = pb[2];
const xc = pc[0], yc = pc[1], zc = pc[2];
let wa = x * (yb * zc - yc * zb) + y * (zb * xc - zc * xb) + z * (xb * yc - xc * yb);
let wb = x * (yc * za - ya * zc) + y * (zc * xa - za * xc) + z * (xc * ya - xa * yc);
let wc = x * (ya * zb - yb * za) + y * (za * xb - zb * xa) + z * (xa * yb - xb * ya);
if (wa < 0.0) {
wa = 0.0;
}
if (wb < 0.0) {
wb = 0.0;
}
if (wc < 0.0) {
wc = 0.0;
}
const ws = 1.0 / (wa + wb + wc);
return [wa * ws, wb * ws, wc * ws];
}
/**
* Gets the index of the sampled point nearest to the specified point.
* @returns the sample index.
*/
getIndex(x, y, z) {
if (x instanceof Array) {
y = x[1];
z = x[2];
x = x[0];
}
const ax = (x >= 0.0) ? x : -x;
const ay = (y >= 0.0) ? y : -y;
const az = (z >= 0.0) ? z : -z;
const scale = UnitSphereSampling.ALMOST_ONE / (ax + ay + az);
const r = x * scale;
const s = y * scale;
const ir = Math.floor(0.5 + (r + 1.0) * this._od);
const is = Math.floor(0.5 + (s + 1.0) * this._od);
const index = this._ip[is][ir];
check_1.Check.argument(index > 0, 'index > 0');
return (z >= 0.0) ? index : index - this._nindex;
}
/**
* Gets the sampled point for the specified index, which must be non-zero.
* <p>
* For efficiency, returns the array { x, y, z } of point coordinates
* by reference, not by copy. These coordinates must not be modified.
* @param index the index of the sampled point; must be non-zero.
* @returns array { x, y, z } of point coordinates; by reference, not by copy.
*/
getPoint(index) {
check_1.Check.argument(index !== 0, 'index !== 0');
return (index >= 0) ? this._pu[index] : this._pl[index + this._nindex];
}
}
exports.UnitSphereSampling = UnitSphereSampling;
UnitSphereSampling.ALMOST_ONE = 1.0 - 5.0 * Number.EPSILON;
//# sourceMappingURL=unit-sphere-sampling.js.map