uglymol
Version:
Macromolecular Viewer for Crystallographers
1,488 lines (1,414 loc) • 267 kB
JavaScript
/*!
* UglyMol v0.7.2. Macromolecular Viewer for Crystallographers.
* Copyright 2014 Nat Echols
* Copyright 2016 Diamond Light Source Ltd
* Copyright 2016 Marcin Wojdyr
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.UM = {}));
})(this, (function (exports) { 'use strict';
var VERSION = exports.VERSION = '0.7.2';
var UnitCell = function UnitCell(a, b, c,
alpha, beta, gamma) {
if (a <= 0 || b <= 0 || c <= 0 || alpha <= 0 || beta <= 0 || gamma <= 0) {
throw Error('Zero or negative unit cell parameter(s).');
}
this.parameters = [a, b, c, alpha, beta, gamma];
var deg2rad = Math.PI / 180.0;
var cos_alpha = Math.cos(deg2rad * alpha);
var cos_beta = Math.cos(deg2rad * beta);
var cos_gamma = Math.cos(deg2rad * gamma);
var sin_alpha = Math.sin(deg2rad * alpha);
var sin_beta = Math.sin(deg2rad * beta);
var sin_gamma = Math.sin(deg2rad * gamma);
if (sin_alpha === 0 || sin_beta === 0 || sin_gamma === 0) {
throw Error('Impossible angle - N*180deg.');
}
var cos_alpha_star_sin_beta = (cos_beta * cos_gamma - cos_alpha) /
sin_gamma;
var cos_alpha_star = cos_alpha_star_sin_beta / sin_beta;
var s1rca2 = Math.sqrt(1.0 - cos_alpha_star * cos_alpha_star);
// The orthogonalization matrix we use is described in ITfC B p.262:
// "An alternative mode of orthogonalization, used by the Protein
// Data Bank and most programs, is to align the a1 axis of the unit
// cell with the Cartesian X_1 axis, and to align the a*_3 axis with the
// Cartesian X_3 axis."
//
// Zeros in the matrices below are kept to make matrix multiplication
// faster: they make extract_block() 2x (!) faster on V8 4.5.103,
// no difference on FF 50.
/* eslint-disable no-multi-spaces, comma-spacing */
this.orth = [a, b * cos_gamma,c * cos_beta,
0.0, b * sin_gamma, -c * cos_alpha_star_sin_beta,
0.0, 0.0 ,c * sin_beta * s1rca2];
// based on xtal.js which is based on cctbx.uctbx
this.frac = [
1.0 / a,
-cos_gamma / (sin_gamma * a),
-(cos_gamma * cos_alpha_star_sin_beta + cos_beta * sin_gamma) /
(sin_beta * s1rca2 * sin_gamma * a),
0.0,
1.0 / (sin_gamma * b),
cos_alpha_star / (s1rca2 * sin_gamma * b),
0.0,
0.0,
1.0 / (sin_beta * s1rca2 * c) ];
};
UnitCell.prototype.fractionalize = function fractionalize (xyz) {
return multiply(xyz, this.frac);
};
UnitCell.prototype.orthogonalize = function orthogonalize (xyz) {
return multiply(xyz, this.orth);
};
// This function is only used with matrices frac and orth, which have 3 zeros.
// We skip these elements, but it doesn't affect performance (on FF50 and V8).
function multiply(xyz, mat) {
/* eslint-disable indent */
return [mat[0] * xyz[0] + mat[1] * xyz[1] + mat[2] * xyz[2],
/*mat[3] * xyz[0]*/+ mat[4] * xyz[1] + mat[5] * xyz[2],
/*mat[6] * xyz[0] + mat[7] * xyz[1]*/+ mat[8] * xyz[2]];
}
var AMINO_ACIDS = [
'ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'GLN', 'GLU', 'GLY', 'HIS', 'ILE', 'LEU',
'LYS', 'MET', 'MSE', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL', 'UNK' ];
var NUCLEIC_ACIDS = [
'DA', 'DC', 'DG', 'DT', 'A', 'C', 'G', 'U', 'rA', 'rC', 'rG', 'rU',
'Ar', 'Cr', 'Gr', 'Ur' ];
var NOT_LIGANDS = ['HOH'].concat(AMINO_ACIDS, NUCLEIC_ACIDS);
function modelsFromPDB(pdb_string) {
var models = [new Model()];
var pdb_tail = models[0].from_pdb(pdb_string.split('\n'));
while (pdb_tail != null) {
var model = new Model();
pdb_tail = model.from_pdb(pdb_tail);
if (model.atoms.length > 0) { models.push(model); }
}
return models;
}
function modelsFromGemmi(gemmi, buffer, name) {
var st = gemmi.read_structure(buffer, name);
var cell = st.cell; // TODO: check if a copy of cell is created here
var models = [];
for (var i_model = 0; i_model < st.length; ++i_model) {
var model = st.at(i_model);
var m = new Model();
m.unit_cell = new UnitCell(cell.a, cell.b, cell.c, cell.alpha, cell.beta, cell.gamma);
var atom_i_seq = 0;
for (var i_chain = 0; i_chain < model.length; ++i_chain) {
var chain = model.at(i_chain);
var chain_name = chain.name;
for (var i_res = 0; i_res < chain.length; ++i_res) {
var res = chain.at(i_res);
var seqid = res.seqid_string;
var resname = res.name;
var ent_type = res.entity_type_string;
var is_ligand = (ent_type === "non-polymer" || ent_type === "branched");
for (var i_atom = 0; i_atom < res.length; ++i_atom) {
var atom = res.at(i_atom);
var new_atom = new Atom();
new_atom.i_seq = atom_i_seq++;
new_atom.chain = chain_name;
new_atom.chain_index = i_chain + 1;
new_atom.resname = resname;
new_atom.seqid = seqid;
new_atom.name = atom.name;
new_atom.altloc = atom.altloc === 0 ? '' : String.fromCharCode(atom.altloc);
new_atom.xyz = atom.pos;
new_atom.occ = atom.occ;
new_atom.b = atom.b_iso;
new_atom.element = atom.element_uname;
new_atom.is_ligand = is_ligand;
m.atoms.push(new_atom);
}
}
}
m.calculate_bounds();
m.calculate_connectivity();
models.push(m);
}
st.delete();
//console.log("[after modelsFromGemmi] wasm mem:", gemmi.HEAPU8.length / 1024, "kb");
return models;
}
var Model = function Model() {
this.atoms = [];
this.unit_cell = null;
this.has_hydrogens = false;
this.lower_bound = [0, 0, 0];
this.upper_bound = [0, 0, 0];
this.residue_map = null;
this.cubes = null;
};
Model.prototype.from_pdb = function from_pdb (pdb_lines) {
var chain_index = 0;// will be ++'ed for the first atom
var last_chain = null;
var atom_i_seq = 0;
var continuation = null;
for (var i = 0; i < pdb_lines.length; i++) {
var line = pdb_lines[i];
var rec_type = line.substring(0, 6).toUpperCase();
if (rec_type === 'ATOM ' || rec_type === 'HETATM') {
var new_atom = new Atom();
new_atom.from_pdb_line(line);
new_atom.i_seq = atom_i_seq++;
if (!this.has_hydrogens && new_atom.element === 'H') {
this.has_hydrogens = true;
}
if (new_atom.chain !== last_chain) {
chain_index++;
}
new_atom.chain_index = chain_index;
last_chain = new_atom.chain;
this.atoms.push(new_atom);
} else if (rec_type === 'ANISOU') ; else if (rec_type === 'CRYST1') {
var a = parseFloat(line.substring(6, 15));
var b = parseFloat(line.substring(15, 24));
var c = parseFloat(line.substring(24, 33));
var alpha = parseFloat(line.substring(33, 40));
var beta = parseFloat(line.substring(40, 47));
var gamma = parseFloat(line.substring(47, 54));
//const sg_symbol = line.substring(55, 66);
this.unit_cell = new UnitCell(a, b, c, alpha, beta, gamma);
} else if (rec_type.substring(0, 3) === 'TER') {
last_chain = null;
} else if (rec_type === 'ENDMDL') {
for (; i < pdb_lines.length; i++) {
if (pdb_lines[i].substring(0, 6).toUpperCase() === 'MODEL ') {
continuation = pdb_lines.slice(i);
break;
}
}
break;
}
}
if (this.atoms.length === 0) { throw Error('No atom records found.'); }
this.calculate_bounds();
this.calculate_connectivity();
return continuation;
};
Model.prototype.calculate_bounds = function calculate_bounds () {
var lower = this.lower_bound = [Infinity, Infinity, Infinity];
var upper = this.upper_bound = [-Infinity, -Infinity, -Infinity];
for (var i = 0; i < this.atoms.length; i++) {
var atom = this.atoms[i];
for (var j = 0; j < 3; j++) {
var v = atom.xyz[j];
if (v < lower[j]) { lower[j] = v; }
if (v > upper[j]) { upper[j] = v; }
}
}
// with a margin
for (var k = 0; k < 3; ++k) {
lower[k] -= 0.001;
upper[k] += 0.001;
}
};
Model.prototype.next_residue = function next_residue (atom, backward) {
var len = this.atoms.length;
var start = (atom ? atom.i_seq : 0) + len;// +len to avoid idx<0 below
for (var i = (atom ? 1 : 0); i < len; i++) {
var idx = (start + (backward ? -i : i)) % len;
var a = this.atoms[idx];
if (!a.is_main_conformer()) { continue; }
if ((a.name === 'CA' && a.element === 'C') || a.name === 'P') {
return a;
}
}
};
Model.prototype.extract_trace = function extract_trace () {
var segments = [];
var current_segment = [];
var last_atom = null;
for (var i = 0; i < this.atoms.length; i++) {
var atom = this.atoms[i];
if (atom.altloc !== '' && atom.altloc !== 'A') { continue; }
if ((atom.name === 'CA' && atom.element === 'C') || atom.name === 'P') {
var start_new = true;
if (last_atom !== null && last_atom.chain_index === atom.chain_index) {
var dxyz2 = atom.distance_sq(last_atom);
if ((atom.name === 'CA' && dxyz2 <= 5.5*5.5) ||
(atom.name === 'P' && dxyz2 < 7.5*7.5)) {
current_segment.push(atom);
start_new = false;
}
}
if (start_new) {
if (current_segment.length > 2) {
segments.push(current_segment);
}
current_segment = [atom];
}
last_atom = atom;
}
}
if (current_segment.length > 2) {
segments.push(current_segment);
}
//console.log(segments.length + " segments extracted");
return segments;
};
Model.prototype.get_residues = function get_residues () {
if (this.residue_map != null) { return this.residue_map; }
var residues = {};
for (var i = 0; i < this.atoms.length; i++) {
var atom = this.atoms[i];
var resid = atom.resid();
var reslist = residues[resid];
if (reslist === undefined) {
residues[resid] = [atom];
} else {
reslist.push(atom);
}
}
this.residue_map = residues;
return residues;
};
// tangent vector to the ribbon representation
Model.prototype.calculate_tangent_vector = function calculate_tangent_vector (residue) {
var a1 = null;
var a2 = null;
// it may be too simplistic
var peptide = (residue[0].resname.length === 3);
var name1 = peptide ? 'C' : 'C2\'';
var name2 = peptide ? 'O' : 'O4\'';
for (var i = 0; i < residue.length; i++) {
var atom = residue[i];
if (!atom.is_main_conformer()) { continue; }
if (atom.name === name1) {
a1 = atom.xyz;
} else if (atom.name === name2) {
a2 = atom.xyz;
}
}
if (a1 === null || a2 === null) { return [0, 0, 1]; } // arbitrary value
var d = [a1[0]-a2[0], a1[1]-a2[1], a1[2]-a2[2]];
var len = Math.sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]);
return [d[0]/len, d[1]/len, d[2]/len];
};
Model.prototype.get_center = function get_center () {
var xsum = 0, ysum = 0, zsum = 0;// eslint-disable-line
var n_atoms = this.atoms.length;
for (var i = 0; i < n_atoms; i++) {
var xyz = this.atoms[i].xyz;
xsum += xyz[0];
ysum += xyz[1];
zsum += xyz[2];
}
return [xsum / n_atoms, ysum / n_atoms, zsum / n_atoms];
};
Model.prototype.calculate_connectivity = function calculate_connectivity () {
var atoms = this.atoms;
var cubes = new Cubicles(atoms, 3.0, this.lower_bound, this.upper_bound);
//let cnt = 0;
for (var i = 0; i < cubes.boxes.length; i++) {
var box = cubes.boxes[i];
if (box.length === 0) { continue; }
var nearby_atoms = cubes.get_nearby_atoms(i);
for (var a = 0; a < box.length; a++) {
var atom_id = box[a];
for (var k = 0; k < nearby_atoms.length; k++) {
var j = nearby_atoms[k];
if (j > atom_id && atoms[atom_id].is_bonded_to(atoms[j])) {
atoms[atom_id].bonds.push(j);
atoms[j].bonds.push(atom_id);
//cnt++;
}
}
}
}
//console.log(atoms.length + ' atoms, ' + cnt + ' bonds.');
this.cubes = cubes;
};
Model.prototype.get_nearest_atom = function get_nearest_atom (x, y, z, atom_name) {
var cubes = this.cubes;
if (cubes == null) { throw Error('Missing Cubicles'); }
var box_id = cubes.find_box_id(x, y, z);
var indices = cubes.get_nearby_atoms(box_id);
var nearest = null;
var min_d2 = Infinity;
for (var i = 0; i < indices.length; i++) {
var atom = this.atoms[indices[i]];
if (atom_name != null && atom_name !== atom.name) { continue; }
var dx = atom.xyz[0] - x;
var dy = atom.xyz[1] - y;
var dz = atom.xyz[2] - z;
var d2 = dx*dx + dy*dy + dz*dz;
if (d2 < min_d2) {
nearest = atom;
min_d2 = d2;
}
}
return nearest;
};
// Single atom and associated labels
var Atom = function Atom() {
this.name = '';
this.altloc = '';
this.resname = '';
this.chain = '';
this.chain_index = -1;
this.seqid = '';
this.xyz = [0, 0, 0];
this.occ = 1.0;
this.b = 0;
this.element = '';
this.i_seq = -1;
this.is_ligand = null;
this.bonds = [];
};
// http://www.wwpdb.org/documentation/format33/sect9.html#ATOM
Atom.prototype.from_pdb_line = function from_pdb_line (pdb_line) {
if (pdb_line.length < 66) {
throw Error('ATOM or HETATM record is too short: ' + pdb_line);
}
var rec_type = pdb_line.substring(0, 6);
if (rec_type !== 'HETATM' && rec_type !== 'ATOM ') {
throw Error('Wrong record type: ' + rec_type);
}
this.name = pdb_line.substring(12, 16).trim();
this.altloc = pdb_line.substring(16, 17).trim();
this.resname = pdb_line.substring(17, 20).trim();
this.chain = pdb_line.substring(20, 22).trim();
this.seqid = pdb_line.substring(22, 27).trim();
var x = parseFloat(pdb_line.substring(30, 38));
var y = parseFloat(pdb_line.substring(38, 46));
var z = parseFloat(pdb_line.substring(46, 54));
this.xyz = [x, y, z];
this.occ = parseFloat(pdb_line.substring(54, 60));
this.b = parseFloat(pdb_line.substring(60, 66));
if (pdb_line.length >= 78) {
this.element = pdb_line.substring(76, 78).trim().toUpperCase();
}
//if (pdb_line.length >= 80) {
//this.charge = pdb_line.substring(78, 80).trim();
//}
this.is_ligand = (NOT_LIGANDS.indexOf(this.resname) === -1);
};
Atom.prototype.distance_sq = function distance_sq (other) {
var dx = this.xyz[0] - other.xyz[0];
var dy = this.xyz[1] - other.xyz[1];
var dz = this.xyz[2] - other.xyz[2];
return dx*dx + dy*dy + dz*dz;
};
Atom.prototype.distance = function distance (other) {
return Math.sqrt(this.distance_sq(other));
};
Atom.prototype.midpoint = function midpoint (other) {
return [(this.xyz[0] + other.xyz[0]) / 2,
(this.xyz[1] + other.xyz[1]) / 2,
(this.xyz[2] + other.xyz[2]) / 2];
};
Atom.prototype.is_hydrogen = function is_hydrogen () {
return this.element === 'H' || this.element === 'D';
};
Atom.prototype.is_ion = function is_ion () {
return this.element === this.resname;
};
Atom.prototype.is_water = function is_water () {
return this.resname === 'HOH';
};
Atom.prototype.is_same_conformer = function is_same_conformer (other) {
return this.altloc === '' || other.altloc === '' ||
this.altloc === other.altloc;
};
Atom.prototype.is_main_conformer = function is_main_conformer () {
return this.altloc === '' || this.altloc === 'A';
};
Atom.prototype.bond_radius = function bond_radius () { // rather crude
if (this.element === 'H') { return 1.3; }
if (this.element === 'S' || this.element === 'P') { return 2.43; }
return 1.99;
};
Atom.prototype.is_bonded_to = function is_bonded_to (other) {
var MAX_DIST = 2.2 * 2.2;
if (!this.is_same_conformer(other)) { return false; }
var dxyz2 = this.distance_sq(other);
if (dxyz2 > MAX_DIST) { return false; }
if (this.element === 'H' && other.element === 'H') { return false; }
return dxyz2 <= this.bond_radius() * other.bond_radius();
};
Atom.prototype.resid = function resid () {
return this.seqid + '/' + this.chain;
};
Atom.prototype.long_label = function long_label () {
var a = this;// eslint-disable-line @typescript-eslint/no-this-alias
return a.name + ' /' + a.seqid + ' ' + a.resname + '/' + a.chain +
' - occ: ' + a.occ.toFixed(2) + ' bf: ' + a.b.toFixed(2) +
' ele: ' + a.element + ' pos: (' + a.xyz[0].toFixed(2) + ',' +
a.xyz[1].toFixed(2) + ',' + a.xyz[2].toFixed(2) + ')';
};
Atom.prototype.short_label = function short_label () {
var a = this;// eslint-disable-line @typescript-eslint/no-this-alias
return a.name + ' /' + a.seqid + ' ' + a.resname + '/' + a.chain;
};
// Partition atoms into boxes for quick neighbor searching.
var Cubicles = function Cubicles(atoms, box_length,
lower_bound, upper_bound) {
this.boxes = [];
this.box_length = box_length;
this.lower_bound = lower_bound;
this.upper_bound = upper_bound;
this.xdim = Math.ceil((upper_bound[0] - lower_bound[0]) / box_length);
this.ydim = Math.ceil((upper_bound[1] - lower_bound[1]) / box_length);
this.zdim = Math.ceil((upper_bound[2] - lower_bound[2]) / box_length);
//console.log("Cubicles: " + this.xdim + "x" + this.ydim + "x" + this.zdim);
var nxyz = this.xdim * this.ydim * this.zdim;
for (var j = 0; j < nxyz; j++) {
this.boxes.push([]);
}
for (var i = 0; i < atoms.length; i++) {
var xyz = atoms[i].xyz;
var box_id = this.find_box_id(xyz[0], xyz[1], xyz[2]);
if (box_id === null) {
throw Error('wrong cubicle');
}
this.boxes[box_id].push(i);
}
};
Cubicles.prototype.find_box_id = function find_box_id (x, y, z) {
var xstep = Math.floor((x - this.lower_bound[0]) / this.box_length);
var ystep = Math.floor((y - this.lower_bound[1]) / this.box_length);
var zstep = Math.floor((z - this.lower_bound[2]) / this.box_length);
var box_id = (zstep * this.ydim + ystep) * this.xdim + xstep;
if (box_id < 0 || box_id >= this.boxes.length) { throw Error('Ups!'); }
return box_id;
};
Cubicles.prototype.get_nearby_atoms = function get_nearby_atoms (box_id) {
var indices = [];
var xydim = this.xdim * this.ydim;
var uv = Math.max(box_id % xydim, 0);
var u = Math.max(uv % this.xdim, 0);
var v = Math.floor(uv / this.xdim);
var w = Math.floor(box_id / xydim);
console.assert((w * xydim) + (v * this.xdim) + u === box_id);
for (var iu = u-1; iu <= u+1; iu++) {
if (iu < 0 || iu >= this.xdim) { continue; }
for (var iv = v-1; iv <= v+1; iv++) {
if (iv < 0 || iv >= this.ydim) { continue; }
for (var iw = w-1; iw <= w+1; iw++) {
if (iw < 0 || iw >= this.zdim) { continue; }
var other_box_id = (iw * xydim) + (iv * this.xdim) + iu;
if (other_box_id >= this.boxes.length || other_box_id < 0) {
throw Error('Box out of bounds: ID ' + other_box_id);
}
var box = this.boxes[other_box_id];
for (var i = 0; i < box.length; i++) {
indices.push(box[i]);
}
}
}
}
return indices;
};
var Block = function Block() {
this._points = null;
this._values = null;
this._size = [0, 0, 0];
};
Block.prototype.set = function set (points, values, size) {
if (size[0] <= 0 || size[1] <= 0 || size[2] <= 0) {
throw Error('Grid dimensions are zero along at least one edge');
}
var len = size[0] * size[1] * size[2];
if (values.length !== len || points.length !== len) {
throw Error('isosurface: array size mismatch');
}
this._points = points;
this._values = values;
this._size = size;
};
Block.prototype.clear = function clear () {
this._points = null;
this._values = null;
};
Block.prototype.empty = function empty (){
return this._values === null;
};
Block.prototype.isosurface = function isosurface (isolevel, method) {
//if (method === 'marching tetrahedra') {
//return marchingTetrahedra(block, isolevel);
//}
return marchingCubes(this._size, this._values, this._points,
isolevel, method);
};
/* eslint comma-spacing: 0, no-multi-spaces: 0 */
var edgeTable = new Int32Array([
0x0 , 0x0 , 0x202, 0x302, 0x406, 0x406, 0x604, 0x704,
0x804, 0x805, 0xa06, 0xa06, 0xc0a, 0xd03, 0xe08, 0xf00,
0x90 , 0x98 , 0x292, 0x292, 0x496, 0x49e, 0x694, 0x694,
0x894, 0x894, 0xa96, 0xa96, 0xc9a, 0xc92, 0xe91, 0xe90,
0x230, 0x230, 0x33 , 0x13a, 0x636, 0x636, 0x434, 0x43c,
0xa34, 0xa35, 0x837, 0x936, 0xe3a, 0xf32, 0xc31, 0xd30,
0x2a0, 0x2a8, 0xa3 , 0xaa , 0x6a6, 0x6af, 0x5a4, 0x4ac,
0xaa4, 0xaa4, 0x9a6, 0x8a6, 0xfaa, 0xea3, 0xca1, 0xca0,
0x460, 0x460, 0x662, 0x762, 0x66 , 0x66 , 0x265, 0x364,
0xc64, 0xc65, 0xe66, 0xe66, 0x86a, 0x863, 0xa69, 0xa60,
0x4f0, 0x4f8, 0x6f2, 0x6f2, 0xf6 , 0xfe , 0x2f5, 0x2fc,
0xcf4, 0xcf4, 0xef6, 0xef6, 0x8fa, 0x8f3, 0xaf9, 0xaf0,
0x650, 0x650, 0x453, 0x552, 0x256, 0x256, 0x54 , 0x154,
0xe54, 0xf54, 0xc57, 0xd56, 0xa5a, 0xb52, 0x859, 0x950,
0x7c0, 0x6c1, 0x5c2, 0x4c2, 0x3c6, 0x2ce, 0xc5 , 0xc4 ,
0xfc4, 0xec5, 0xdc6, 0xcc6, 0xbca, 0xac2, 0x8c1, 0x8c0,
0x8c0, 0x8c0, 0xac2, 0xbc2, 0xcc6, 0xcc6, 0xec4, 0xfcc,
0xc4 , 0xc5 , 0x2c6, 0x3c6, 0x4c2, 0x5c2, 0x6c1, 0x7c0,
0x950, 0x859, 0xb52, 0xa5a, 0xd56, 0xc57, 0xe54, 0xe5c,
0x154, 0x54 , 0x25e, 0x256, 0x552, 0x453, 0x658, 0x650,
0xaf0, 0xaf0, 0x8f3, 0x8fa, 0xef6, 0xef6, 0xcf4, 0xcfc,
0x2f4, 0x3f5, 0xff , 0x1f6, 0x6f2, 0x6f3, 0x4f9, 0x5f0,
0xa60, 0xa69, 0x863, 0x86a, 0xe66, 0xe67, 0xd65, 0xc6c,
0x364, 0x265, 0x166, 0x66 , 0x76a, 0x663, 0x460, 0x460,
0xca0, 0xca0, 0xea2, 0xfa2, 0x8a6, 0x8a6, 0xaa4, 0xba4,
0x4ac, 0x5a4, 0x6ae, 0x7a6, 0xaa , 0xa3 , 0x2a8, 0x2a0,
0xd30, 0xc31, 0xf32, 0xe3a, 0x936, 0x837, 0xb35, 0xa34,
0x43c, 0x434, 0x73e, 0x636, 0x13a, 0x33 , 0x339, 0x230,
0xe90, 0xe90, 0xc92, 0xc9a, 0xa96, 0xa96, 0x894, 0x89c,
0x694, 0x695, 0x49f, 0x496, 0x292, 0x392, 0x98 , 0x90 ,
0xf00, 0xe08, 0xd03, 0xc0a, 0xa06, 0xa0e, 0x805, 0x804,
0x704, 0x604, 0x506, 0x406, 0x302, 0x202, 0x0 , 0x0]);
// generated from classical triTable by tools/isolut.py
var segTable = [
[],
[],
[1, 9],
[1, 8, 1, 9],
[2, 10, 10, 1],
[2, 10, 10, 1],
[9, 2, 2, 10, 10, 9],
[2, 8, 2, 10, 10, 8, 10, 9],
[11, 2],
[0, 11, 11, 2],
[1, 9, 11, 2],
[1, 11, 11, 2, 1, 9, 9, 11],
[3, 10, 10, 1, 11, 10],
[0, 10, 10, 1, 8, 10, 11, 10],
[3, 9, 11, 9, 11, 10, 10, 9],
[8, 10, 10, 9, 11, 10],
[4, 7],
[4, 3, 4, 7],
[1, 9, 4, 7],
[4, 1, 1, 9, 4, 7, 7, 1],
[2, 10, 10, 1, 4, 7],
[3, 4, 4, 7, 2, 10, 10, 1],
[9, 2, 2, 10, 10, 9, 4, 7],
[2, 10, 10, 9, 9, 2, 9, 7, 7, 2, 4, 7],
[4, 7, 11, 2],
[11, 4, 4, 7, 11, 2, 2, 4],
[1, 9, 4, 7, 11, 2],
[4, 7, 11, 4, 11, 9, 11, 2, 2, 9, 1, 9],
[3, 10, 10, 1, 11, 10, 4, 7],
[1, 11, 11, 10, 10, 1, 1, 4, 4, 11, 4, 7],
[4, 7, 0, 11, 11, 9, 11, 10, 10, 9],
[4, 7, 11, 4, 11, 9, 11, 10, 10, 9],
[9, 5, 5, 4],
[9, 5, 5, 4],
[0, 5, 5, 4, 1, 5],
[8, 5, 5, 4, 3, 5, 1, 5],
[2, 10, 10, 1, 9, 5, 5, 4],
[2, 10, 10, 1, 9, 5, 5, 4],
[5, 2, 2, 10, 10, 5, 5, 4, 4, 2],
[2, 10, 10, 5, 5, 2, 5, 3, 5, 4, 4, 3],
[9, 5, 5, 4, 11, 2],
[0, 11, 11, 2, 9, 5, 5, 4],
[0, 5, 5, 4, 1, 5, 11, 2],
[1, 5, 5, 2, 5, 8, 8, 2, 11, 2, 5, 4],
[10, 3, 11, 10, 10, 1, 9, 5, 5, 4],
[9, 5, 5, 4, 8, 1, 8, 10, 10, 1, 11, 10],
[5, 4, 0, 5, 0, 11, 11, 5, 11, 10, 10, 5],
[5, 4, 8, 5, 8, 10, 10, 5, 11, 10],
[9, 7, 5, 7, 9, 5],
[9, 3, 9, 5, 5, 3, 5, 7],
[0, 7, 1, 7, 1, 5, 5, 7],
[1, 5, 5, 3, 5, 7],
[9, 7, 9, 5, 5, 7, 10, 1, 2, 10],
[10, 1, 2, 10, 9, 5, 5, 0, 5, 3, 5, 7],
[2, 8, 2, 5, 5, 8, 5, 7, 10, 5, 2, 10],
[2, 10, 10, 5, 5, 2, 5, 3, 5, 7],
[7, 9, 9, 5, 5, 7, 11, 2],
[9, 5, 5, 7, 7, 9, 7, 2, 2, 9, 11, 2],
[11, 2, 1, 8, 1, 7, 1, 5, 5, 7],
[11, 2, 1, 11, 1, 7, 1, 5, 5, 7],
[9, 5, 5, 8, 5, 7, 10, 1, 3, 10, 11, 10],
[5, 7, 7, 0, 0, 5, 9, 5, 11, 0, 0, 10, 10, 1, 11, 10],
[11, 10, 10, 0, 0, 11, 10, 5, 5, 0, 0, 7, 5, 7],
[11, 10, 10, 5, 5, 11, 5, 7],
[10, 6, 6, 5, 5, 10],
[5, 10, 10, 6, 6, 5],
[1, 9, 5, 10, 10, 6, 6, 5],
[1, 8, 1, 9, 5, 10, 10, 6, 6, 5],
[1, 6, 6, 5, 5, 1, 2, 6],
[1, 6, 6, 5, 5, 1, 2, 6],
[9, 6, 6, 5, 5, 9, 0, 6, 2, 6],
[5, 9, 8, 5, 8, 2, 2, 5, 2, 6, 6, 5],
[11, 2, 10, 6, 6, 5, 5, 10],
[11, 0, 11, 2, 10, 6, 6, 5, 5, 10],
[1, 9, 11, 2, 5, 10, 10, 6, 6, 5],
[5, 10, 10, 6, 6, 5, 1, 9, 9, 2, 9, 11, 11, 2],
[6, 3, 11, 6, 6, 5, 5, 3, 5, 1],
[11, 0, 11, 5, 5, 0, 5, 1, 11, 6, 6, 5],
[11, 6, 6, 3, 6, 0, 6, 5, 5, 0, 5, 9],
[6, 5, 5, 9, 9, 6, 9, 11, 11, 6],
[5, 10, 10, 6, 6, 5, 4, 7],
[4, 3, 4, 7, 6, 5, 5, 10, 10, 6],
[1, 9, 5, 10, 10, 6, 6, 5, 4, 7],
[10, 6, 6, 5, 5, 10, 1, 9, 9, 7, 7, 1, 4, 7],
[6, 1, 2, 6, 6, 5, 5, 1, 4, 7],
[2, 5, 5, 1, 2, 6, 6, 5, 4, 3, 4, 7],
[4, 7, 0, 5, 5, 9, 0, 6, 6, 5, 2, 6],
[3, 9, 9, 7, 4, 7, 2, 9, 5, 9, 9, 6, 6, 5, 2, 6],
[11, 2, 4, 7, 10, 6, 6, 5, 5, 10],
[5, 10, 10, 6, 6, 5, 4, 7, 7, 2, 2, 4, 11, 2],
[1, 9, 4, 7, 11, 2, 5, 10, 10, 6, 6, 5],
[9, 2, 1, 9, 9, 11, 11, 2, 4, 11, 4, 7, 5, 10, 10, 6, 6, 5],
[4, 7, 11, 5, 5, 3, 5, 1, 11, 6, 6, 5],
[5, 1, 1, 11, 11, 5, 11, 6, 6, 5, 0, 11, 11, 4, 4, 7],
[0, 5, 5, 9, 0, 6, 6, 5, 3, 6, 11, 6, 4, 7],
[6, 5, 5, 9, 9, 6, 9, 11, 11, 6, 4, 7, 7, 9],
[10, 4, 9, 10, 6, 4, 10, 6],
[4, 10, 10, 6, 6, 4, 9, 10],
[10, 0, 1, 10, 10, 6, 6, 0, 6, 4],
[1, 8, 1, 6, 6, 8, 6, 4, 1, 10, 10, 6],
[1, 4, 9, 1, 2, 4, 2, 6, 6, 4],
[2, 9, 9, 1, 2, 4, 2, 6, 6, 4],
[2, 4, 2, 6, 6, 4],
[2, 8, 2, 4, 2, 6, 6, 4],
[10, 4, 9, 10, 10, 6, 6, 4, 11, 2],
[8, 2, 11, 2, 9, 10, 10, 4, 10, 6, 6, 4],
[11, 2, 1, 6, 6, 0, 6, 4, 1, 10, 10, 6],
[6, 4, 4, 1, 1, 6, 1, 10, 10, 6, 8, 1, 1, 11, 11, 2],
[9, 6, 6, 4, 9, 3, 3, 6, 9, 1, 11, 6],
[11, 1, 1, 8, 11, 6, 6, 1, 9, 1, 1, 4, 6, 4],
[11, 6, 6, 3, 6, 0, 6, 4],
[6, 4, 8, 6, 11, 6],
[7, 10, 10, 6, 6, 7, 8, 10, 9, 10],
[0, 7, 0, 10, 10, 7, 9, 10, 6, 7, 10, 6],
[10, 6, 6, 7, 7, 10, 1, 10, 7, 1, 8, 1],
[10, 6, 6, 7, 7, 10, 7, 1, 1, 10],
[2, 6, 6, 1, 6, 8, 8, 1, 9, 1, 6, 7],
[2, 6, 6, 9, 9, 2, 9, 1, 6, 7, 7, 9, 9, 3],
[0, 7, 0, 6, 6, 7, 2, 6],
[2, 7, 6, 7, 2, 6],
[11, 2, 10, 6, 6, 8, 8, 10, 9, 10, 6, 7],
[0, 7, 7, 2, 11, 2, 9, 7, 6, 7, 7, 10, 10, 6, 9, 10],
[1, 8, 1, 7, 1, 10, 10, 7, 6, 7, 10, 6, 11, 2],
[11, 2, 1, 11, 1, 7, 10, 6, 6, 1, 1, 10, 6, 7],
[9, 6, 6, 8, 6, 7, 9, 1, 1, 6, 11, 6, 6, 3],
[9, 1, 11, 6, 6, 7],
[0, 7, 0, 6, 6, 7, 11, 0, 11, 6],
[11, 6, 6, 7],
[7, 6, 6, 11],
[7, 6, 6, 11],
[1, 9, 7, 6, 6, 11],
[8, 1, 1, 9, 7, 6, 6, 11],
[10, 1, 2, 10, 6, 11, 7, 6],
[2, 10, 10, 1, 6, 11, 7, 6],
[2, 9, 2, 10, 10, 9, 6, 11, 7, 6],
[6, 11, 7, 6, 2, 10, 10, 3, 10, 8, 10, 9],
[7, 2, 6, 2, 7, 6],
[7, 0, 7, 6, 6, 0, 6, 2],
[2, 7, 7, 6, 6, 2, 1, 9],
[1, 6, 6, 2, 1, 8, 8, 6, 1, 9, 7, 6],
[10, 7, 7, 6, 6, 10, 10, 1, 1, 7],
[10, 7, 7, 6, 6, 10, 1, 7, 10, 1, 1, 8],
[7, 0, 7, 10, 10, 0, 10, 9, 6, 10, 7, 6],
[7, 6, 6, 10, 10, 7, 10, 8, 10, 9],
[6, 8, 4, 6, 6, 11],
[3, 6, 6, 11, 0, 6, 4, 6],
[8, 6, 6, 11, 4, 6, 1, 9],
[4, 6, 6, 9, 6, 3, 3, 9, 1, 9, 6, 11],
[6, 8, 4, 6, 6, 11, 2, 10, 10, 1],
[2, 10, 10, 1, 0, 11, 0, 6, 6, 11, 4, 6],
[4, 11, 4, 6, 6, 11, 2, 9, 2, 10, 10, 9],
[10, 9, 9, 3, 3, 10, 2, 10, 4, 3, 3, 6, 6, 11, 4, 6],
[8, 2, 4, 2, 4, 6, 6, 2],
[4, 2, 4, 6, 6, 2],
[1, 9, 3, 4, 4, 2, 4, 6, 6, 2],
[1, 9, 4, 1, 4, 2, 4, 6, 6, 2],
[8, 1, 8, 6, 6, 1, 4, 6, 6, 10, 10, 1],
[10, 1, 0, 10, 0, 6, 6, 10, 4, 6],
[4, 6, 6, 3, 3, 4, 6, 10, 10, 3, 3, 9, 10, 9],
[10, 9, 4, 10, 6, 10, 4, 6],
[9, 5, 5, 4, 7, 6, 6, 11],
[9, 5, 5, 4, 7, 6, 6, 11],
[5, 0, 1, 5, 5, 4, 7, 6, 6, 11],
[7, 6, 6, 11, 3, 4, 3, 5, 5, 4, 1, 5],
[9, 5, 5, 4, 10, 1, 2, 10, 7, 6, 6, 11],
[6, 11, 7, 6, 2, 10, 10, 1, 9, 5, 5, 4],
[7, 6, 6, 11, 5, 4, 4, 10, 10, 5, 4, 2, 2, 10],
[3, 4, 3, 5, 5, 4, 2, 5, 10, 5, 2, 10, 7, 6, 6, 11],
[7, 2, 7, 6, 6, 2, 5, 4, 9, 5],
[9, 5, 5, 4, 8, 6, 6, 0, 6, 2, 7, 6],
[3, 6, 6, 2, 7, 6, 1, 5, 5, 0, 5, 4],
[6, 2, 2, 8, 8, 6, 7, 6, 1, 8, 8, 5, 5, 4, 1, 5],
[9, 5, 5, 4, 10, 1, 1, 6, 6, 10, 1, 7, 7, 6],
[1, 6, 6, 10, 10, 1, 1, 7, 7, 6, 0, 7, 9, 5, 5, 4],
[0, 10, 10, 4, 10, 5, 5, 4, 3, 10, 6, 10, 10, 7, 7, 6],
[7, 6, 6, 10, 10, 7, 10, 8, 5, 4, 4, 10, 10, 5],
[6, 9, 9, 5, 5, 6, 6, 11, 11, 9],
[3, 6, 6, 11, 0, 6, 0, 5, 5, 6, 9, 5],
[0, 11, 0, 5, 5, 11, 1, 5, 5, 6, 6, 11],
[6, 11, 3, 6, 3, 5, 5, 6, 1, 5],
[2, 10, 10, 1, 9, 5, 5, 11, 11, 9, 5, 6, 6, 11],
[0, 11, 0, 6, 6, 11, 9, 6, 5, 6, 9, 5, 2, 10, 10, 1],
[8, 5, 5, 11, 5, 6, 6, 11, 0, 5, 10, 5, 5, 2, 2, 10],
[6, 11, 3, 6, 3, 5, 5, 6, 2, 10, 10, 3, 10, 5],
[5, 8, 9, 5, 5, 2, 2, 8, 5, 6, 6, 2],
[9, 5, 5, 6, 6, 9, 6, 0, 6, 2],
[1, 5, 5, 8, 8, 1, 5, 6, 6, 8, 8, 2, 6, 2],
[1, 5, 5, 6, 6, 1, 6, 2],
[3, 6, 6, 1, 6, 10, 10, 1, 8, 6, 5, 6, 6, 9, 9, 5],
[10, 1, 0, 10, 0, 6, 6, 10, 9, 5, 5, 0, 5, 6],
[5, 6, 6, 10, 10, 5],
[10, 5, 5, 6, 6, 10],
[11, 5, 5, 10, 10, 11, 7, 5],
[11, 5, 5, 10, 10, 11, 7, 5],
[5, 11, 7, 5, 5, 10, 10, 11, 1, 9],
[10, 7, 7, 5, 5, 10, 10, 11, 8, 1, 1, 9],
[11, 1, 2, 11, 7, 1, 7, 5, 5, 1],
[2, 7, 7, 1, 7, 5, 5, 1, 2, 11],
[9, 7, 7, 5, 5, 9, 9, 2, 2, 7, 2, 11],
[7, 5, 5, 2, 2, 7, 2, 11, 5, 9, 9, 2, 2, 8],
[2, 5, 5, 10, 10, 2, 3, 5, 7, 5],
[8, 2, 8, 5, 5, 2, 7, 5, 10, 2, 5, 10],
[1, 9, 5, 10, 10, 3, 3, 5, 7, 5, 10, 2],
[8, 2, 2, 9, 1, 9, 7, 2, 10, 2, 2, 5, 5, 10, 7, 5],
[3, 5, 5, 1, 7, 5],
[7, 0, 7, 1, 7, 5, 5, 1],
[3, 9, 3, 5, 5, 9, 7, 5],
[7, 9, 5, 9, 7, 5],
[5, 8, 4, 5, 5, 10, 10, 8, 10, 11],
[5, 0, 4, 5, 5, 11, 11, 0, 5, 10, 10, 11],
[1, 9, 4, 10, 10, 8, 10, 11, 4, 5, 5, 10],
[10, 11, 11, 4, 4, 10, 4, 5, 5, 10, 3, 4, 4, 1, 1, 9],
[2, 5, 5, 1, 2, 8, 8, 5, 2, 11, 4, 5],
[4, 11, 11, 0, 4, 5, 5, 11, 2, 11, 11, 1, 5, 1],
[2, 5, 5, 0, 5, 9, 2, 11, 11, 5, 4, 5, 5, 8],
[4, 5, 5, 9, 2, 11],
[2, 5, 5, 10, 10, 2, 3, 5, 3, 4, 4, 5],
[5, 10, 10, 2, 2, 5, 2, 4, 4, 5],
[3, 10, 10, 2, 3, 5, 5, 10, 8, 5, 4, 5, 1, 9],
[5, 10, 10, 2, 2, 5, 2, 4, 4, 5, 1, 9, 9, 2],
[4, 5, 5, 8, 5, 3, 5, 1],
[4, 5, 5, 0, 5, 1],
[4, 5, 5, 8, 5, 3, 0, 5, 5, 9],
[4, 5, 5, 9],
[4, 11, 7, 4, 9, 11, 9, 10, 10, 11],
[9, 7, 7, 4, 9, 11, 9, 10, 10, 11],
[1, 10, 10, 11, 11, 1, 11, 4, 4, 1, 7, 4],
[1, 4, 4, 3, 1, 10, 10, 4, 7, 4, 4, 11, 10, 11],
[4, 11, 7, 4, 9, 11, 9, 2, 2, 11, 9, 1],
[9, 7, 7, 4, 9, 11, 9, 1, 1, 11, 2, 11],
[7, 4, 4, 11, 4, 2, 2, 11],
[7, 4, 4, 11, 4, 2, 2, 11, 3, 4],
[2, 9, 9, 10, 10, 2, 2, 7, 7, 9, 7, 4],
[9, 10, 10, 7, 7, 9, 7, 4, 10, 2, 2, 7, 7, 0],
[7, 10, 10, 3, 10, 2, 7, 4, 4, 10, 1, 10, 10, 0],
[1, 10, 10, 2, 7, 4],
[9, 1, 1, 4, 1, 7, 7, 4],
[9, 1, 1, 4, 1, 7, 7, 4, 8, 1],
[3, 4, 7, 4],
[7, 4],
[9, 10, 10, 8, 10, 11],
[9, 3, 9, 11, 9, 10, 10, 11],
[1, 10, 10, 0, 10, 8, 10, 11],
[1, 10, 10, 3, 10, 11],
[2, 11, 11, 1, 11, 9, 9, 1],
[9, 3, 9, 11, 2, 9, 9, 1, 2, 11],
[2, 11, 11, 0],
[2, 11],
[8, 2, 8, 10, 10, 2, 9, 10],
[9, 10, 10, 2, 2, 9],
[8, 2, 8, 10, 10, 2, 1, 8, 1, 10],
[1, 10, 10, 2],
[8, 1, 9, 1],
[9, 1],
[],
[]];
var segTable2 = [
[],
[],
[1, 9],
[1, 9],
[2, 10, 10, 1],
[2, 10, 10, 1],
[2, 10, 10, 9],
[2, 10, 10, 9],
[11, 2],
[11, 2],
[1, 9, 11, 2],
[11, 2, 1, 9],
[10, 1, 11, 10],
[10, 1, 11, 10],
[11, 10, 10, 9],
[10, 9, 11, 10],
[4, 7],
[4, 7],
[1, 9, 4, 7],
[1, 9, 4, 7],
[2, 10, 10, 1, 4, 7],
[4, 7, 2, 10, 10, 1],
[2, 10, 10, 9, 4, 7],
[2, 10, 10, 9, 4, 7],
[4, 7, 11, 2],
[4, 7, 11, 2],
[1, 9, 4, 7, 11, 2],
[4, 7, 11, 2, 1, 9],
[10, 1, 11, 10, 4, 7],
[11, 10, 10, 1, 4, 7],
[4, 7, 11, 10, 10, 9],
[4, 7, 11, 10, 10, 9],
[9, 5, 5, 4],
[9, 5, 5, 4],
[5, 4, 1, 5],
[5, 4, 1, 5],
[2, 10, 10, 1, 9, 5, 5, 4],
[2, 10, 10, 1, 9, 5, 5, 4],
[2, 10, 10, 5, 5, 4],
[2, 10, 10, 5, 5, 4],
[9, 5, 5, 4, 11, 2],
[11, 2, 9, 5, 5, 4],
[5, 4, 1, 5, 11, 2],
[1, 5, 11, 2, 5, 4],
[11, 10, 10, 1, 9, 5, 5, 4],
[9, 5, 5, 4, 10, 1, 11, 10],
[5, 4, 11, 10, 10, 5],
[5, 4, 10, 5, 11, 10],
[5, 7, 9, 5],
[9, 5, 5, 7],
[1, 5, 5, 7],
[1, 5, 5, 7],
[9, 5, 5, 7, 10, 1, 2, 10],
[10, 1, 2, 10, 9, 5, 5, 7],
[5, 7, 10, 5, 2, 10],
[2, 10, 10, 5, 5, 7],
[9, 5, 5, 7, 11, 2],
[9, 5, 5, 7, 11, 2],
[11, 2, 1, 5, 5, 7],
[11, 2, 1, 5, 5, 7],
[9, 5, 5, 7, 10, 1, 11, 10],
[5, 7, 9, 5, 10, 1, 11, 10],
[11, 10, 10, 5, 5, 7],
[11, 10, 10, 5, 5, 7],
[10, 6, 6, 5, 5, 10],
[5, 10, 10, 6, 6, 5],
[1, 9, 5, 10, 10, 6, 6, 5],
[1, 9, 5, 10, 10, 6, 6, 5],
[6, 5, 5, 1, 2, 6],
[6, 5, 5, 1, 2, 6],
[6, 5, 5, 9, 2, 6],
[5, 9, 2, 6, 6, 5],
[11, 2, 10, 6, 6, 5, 5, 10],
[11, 2, 10, 6, 6, 5, 5, 10],
[1, 9, 11, 2, 5, 10, 10, 6, 6, 5],
[5, 10, 10, 6, 6, 5, 1, 9, 11, 2],
[11, 6, 6, 5, 5, 1],
[5, 1, 11, 6, 6, 5],
[11, 6, 6, 5, 5, 9],
[6, 5, 5, 9, 11, 6],
[5, 10, 10, 6, 6, 5, 4, 7],
[4, 7, 6, 5, 5, 10, 10, 6],
[1, 9, 5, 10, 10, 6, 6, 5, 4, 7],
[10, 6, 6, 5, 5, 10, 1, 9, 4, 7],
[2, 6, 6, 5, 5, 1, 4, 7],
[5, 1, 2, 6, 6, 5, 4, 7],
[4, 7, 5, 9, 6, 5, 2, 6],
[4, 7, 5, 9, 6, 5, 2, 6],
[11, 2, 4, 7, 10, 6, 6, 5, 5, 10],
[5, 10, 10, 6, 6, 5, 4, 7, 11, 2],
[1, 9, 4, 7, 11, 2, 5, 10, 10, 6, 6, 5],
[1, 9, 11, 2, 4, 7, 5, 10, 10, 6, 6, 5],
[4, 7, 5, 1, 11, 6, 6, 5],
[5, 1, 11, 6, 6, 5, 4, 7],
[5, 9, 6, 5, 11, 6, 4, 7],
[6, 5, 5, 9, 11, 6, 4, 7],
[9, 10, 6, 4, 10, 6],
[10, 6, 6, 4, 9, 10],
[1, 10, 10, 6, 6, 4],
[6, 4, 1, 10, 10, 6],
[9, 1, 2, 6, 6, 4],
[9, 1, 2, 6, 6, 4],
[2, 6, 6, 4],
[2, 6, 6, 4],
[9, 10, 10, 6, 6, 4, 11, 2],
[11, 2, 9, 10, 10, 6, 6, 4],
[11, 2, 6, 4, 1, 10, 10, 6],
[6, 4, 1, 10, 10, 6, 11, 2],
[6, 4, 9, 1, 11, 6],
[11, 6, 9, 1, 6, 4],
[11, 6, 6, 4],
[6, 4, 11, 6],
[10, 6, 6, 7, 9, 10],
[9, 10, 6, 7, 10, 6],
[10, 6, 6, 7, 1, 10],
[10, 6, 6, 7, 1, 10],
[2, 6, 9, 1, 6, 7],
[2, 6, 9, 1, 6, 7],
[6, 7, 2, 6],
[6, 7, 2, 6],
[11, 2, 10, 6, 9, 10, 6, 7],
[11, 2, 6, 7, 10, 6, 9, 10],
[1, 10, 6, 7, 10, 6, 11, 2],
[11, 2, 10, 6, 1, 10, 6, 7],
[6, 7, 9, 1, 11, 6],
[9, 1, 11, 6, 6, 7],
[6, 7, 11, 6],
[11, 6, 6, 7],
[7, 6, 6, 11],
[7, 6, 6, 11],
[1, 9, 7, 6, 6, 11],
[1, 9, 7, 6, 6, 11],
[10, 1, 2, 10, 6, 11, 7, 6],
[2, 10, 10, 1, 6, 11, 7, 6],
[2, 10, 10, 9, 6, 11, 7, 6],
[6, 11, 7, 6, 2, 10, 10, 9],
[6, 2, 7, 6],
[7, 6, 6, 2],
[7, 6, 6, 2, 1, 9],
[6, 2, 1, 9, 7, 6],
[7, 6, 6, 10, 10, 1],
[7, 6, 6, 10, 10, 1],
[10, 9, 6, 10, 7, 6],
[7, 6, 6, 10, 10, 9],
[4, 6, 6, 11],
[6, 11, 4, 6],
[6, 11, 4, 6, 1, 9],
[4, 6, 1, 9, 6, 11],
[4, 6, 6, 11, 2, 10, 10, 1],
[2, 10, 10, 1, 6, 11, 4, 6],
[4, 6, 6, 11, 2, 10, 10, 9],
[10, 9, 2, 10, 6, 11, 4, 6],
[4, 6, 6, 2],
[4, 6, 6, 2],
[1, 9, 4, 6, 6, 2],
[1, 9, 4, 6, 6, 2],
[4, 6, 6, 10, 10, 1],
[10, 1, 6, 10, 4, 6],
[4, 6, 6, 10, 10, 9],
[10, 9, 6, 10, 4, 6],
[9, 5, 5, 4, 7, 6, 6, 11],
[9, 5, 5, 4, 7, 6, 6, 11],
[1, 5, 5, 4, 7, 6, 6, 11],
[7, 6, 6, 11, 5, 4, 1, 5],
[9, 5, 5, 4, 10, 1, 2, 10, 7, 6, 6, 11],
[6, 11, 7, 6, 2, 10, 10, 1, 9, 5, 5, 4],
[7, 6, 6, 11, 5, 4, 10, 5, 2, 10],
[5, 4, 10, 5, 2, 10, 7, 6, 6, 11],
[7, 6, 6, 2, 5, 4, 9, 5],
[9, 5, 5, 4, 6, 2, 7, 6],
[6, 2, 7, 6, 1, 5, 5, 4],
[6, 2, 7, 6, 5, 4, 1, 5],
[9, 5, 5, 4, 10, 1, 6, 10, 7, 6],
[6, 10, 10, 1, 7, 6, 9, 5, 5, 4],
[10, 5, 5, 4, 6, 10, 7, 6],
[7, 6, 6, 10, 5, 4, 10, 5],
[9, 5, 5, 6, 6, 11],
[6, 11, 5, 6, 9, 5],
[1, 5, 5, 6, 6, 11],
[6, 11, 5, 6, 1, 5],
[2, 10, 10, 1, 9, 5, 5, 6, 6, 11],
[6, 11, 5, 6, 9, 5, 2, 10, 10, 1],
[5, 6, 6, 11, 10, 5, 2, 10],
[6, 11, 5, 6, 2, 10, 10, 5],
[9, 5, 5, 6, 6, 2],
[9, 5, 5, 6, 6, 2],
[1, 5, 5, 6, 6, 2],
[1, 5, 5, 6, 6, 2],
[6, 10, 10, 1, 5, 6, 9, 5],
[10, 1, 6, 10, 9, 5, 5, 6],
[5, 6, 6, 10, 10, 5],
[10, 5, 5, 6, 6, 10],
[5, 10, 10, 11, 7, 5],
[5, 10, 10, 11, 7, 5],
[7, 5, 5, 10, 10, 11, 1, 9],
[7, 5, 5, 10, 10, 11, 1, 9],
[2, 11, 7, 5, 5, 1],
[7, 5, 5, 1, 2, 11],
[7, 5, 5, 9, 2, 11],
[7, 5, 2, 11, 5, 9],
[5, 10, 10, 2, 7, 5],
[7, 5, 10, 2, 5, 10],
[1, 9, 5, 10, 7, 5, 10, 2],
[1, 9, 10, 2, 5, 10, 7, 5],
[5, 1, 7, 5],
[7, 5, 5, 1],
[5, 9, 7, 5],
[5, 9, 7, 5],
[4, 5, 5, 10, 10, 11],
[4, 5, 5, 10, 10, 11],
[1, 9, 10, 11, 4, 5, 5, 10],
[10, 11, 4, 5, 5, 10, 1, 9],
[5, 1, 2, 11, 4, 5],
[4, 5, 2, 11, 5, 1],
[5, 9, 2, 11, 4, 5],
[4, 5, 5, 9, 2, 11],
[5, 10, 10, 2, 4, 5],
[5, 10, 10, 2, 4, 5],
[10, 2, 5, 10, 4, 5, 1, 9],
[5, 10, 10, 2, 4, 5, 1, 9],
[4, 5, 5, 1],
[4, 5, 5, 1],
[4, 5, 5, 9],
[4, 5, 5, 9],
[7, 4, 9, 10, 10, 11],
[7, 4, 9, 10, 10, 11],
[1, 10, 10, 11, 7, 4],
[1, 10, 7, 4, 10, 11],
[7, 4, 2, 11, 9, 1],
[7, 4, 9, 1, 2, 11],
[7, 4, 2, 11],
[7, 4, 2, 11],
[9, 10, 10, 2, 7, 4],
[9, 10, 7, 4, 10, 2],
[10, 2, 7, 4, 1, 10],
[1, 10, 10, 2, 7, 4],
[9, 1, 7, 4],
[9, 1, 7, 4],
[7, 4],
[7, 4],
[9, 10, 10, 11],
[9, 10, 10, 11],
[1, 10, 10, 11],
[1, 10, 10, 11],
[2, 11, 9, 1],
[9, 1, 2, 11],
[2, 11],
[2, 11],
[10, 2, 9, 10],
[9, 10, 10, 2],
[10, 2, 1, 10],
[1, 10, 10, 2],
[9, 1],
[9, 1],
[],
[]];
var cubeVerts = [[0,0,0], [1,0,0], [1,1,0], [0,1,0],
[0,0,1], [1,0,1], [1,1,1], [0,1,1]];
var edgeIndex = [[0,1], [1,2], [2,3], [3,0], [4,5], [5,6],
[6,7], [7,4], [0,4], [1,5], [2,6], [3,7]];
// edge directions: [x, y, -x, -y, x, y, -x, -y, z, z, z, z]
// return offsets relative to vertex [0,0,0]
function calculateVertOffsets(dims) {
var vert_offsets = [];
for (var i = 0; i < 8; ++i) {
var v = cubeVerts[i];
vert_offsets.push(v[0] + dims[2] * (v[1] + dims[1] * v[2]));
}
return vert_offsets;
}
function marchingCubes(dims,
values,
points,
isolevel,
method) {
var snap = (method === 'snapped MC');
var seg_table = (method === 'squarish' ? segTable2 : segTable);
var vlist = new Array(12);
var vert_offsets = calculateVertOffsets(dims);
var vertex_values = new Float32Array(8);
var vertex_points = new Array(8);
var size_x = dims[0];
var size_y = dims[1];
var size_z = dims[2];
if (values == null || points == null) { return; }
var vertices = [];
var segments = [];
var vertex_count = 0;
for (var x = 0; x < size_x - 1; x++) {
for (var y = 0; y < size_y - 1; y++) {
for (var z = 0; z < size_z - 1; z++) {
var offset0 = z + size_z * (y + size_y * x);
var cubeindex = 0;
var i = (void 0);
var j = (void 0);
for (i = 0; i < 8; ++i) {
j = offset0 + vert_offsets[i];
cubeindex |= (values[j] < isolevel) ? 1 << i : 0;
}
if (cubeindex === 0 || cubeindex === 255) { continue; }
for (i = 0; i < 8; ++i) {
j = offset0 + vert_offsets[i];
vertex_values[i] = values[j];
vertex_points[i] = points[j];
}
// 12 bit number, indicates which edges are crossed by the isosurface
var edge_mask = edgeTable[cubeindex];
// check which edges are crossed, and estimate the point location
// using a weighted average of scalar values at edge endpoints.
for (i = 0; i < 12; ++i) {
if ((edge_mask & (1 << i)) !== 0) {
var e = edgeIndex[i];
var mu = (isolevel - vertex_values[e[0]]) /
(vertex_values[e[1]] - vertex_values[e[0]]);
if (snap === true) {
if (mu > 0.85) { mu = 1; }
else if (mu < 0.15) { mu = 0; }
}
var p1 = vertex_points[e[0]];
var p2 = vertex_points[e[1]];
// The number of added vertices could be roughly halved
// if we avoided duplicates between neighbouring cells.
// Using a map for lookups is too slow, perhaps a big
// array would do?
vertices.push(p1[0] + (p2[0] - p1[0]) * mu,
p1[1] + (p2[1] - p1[1]) * mu,
p1[2] + (p2[2] - p1[2]) * mu);
vlist[i] = vertex_count++;
}
}
var t = seg_table[cubeindex];
for (i = 0; i < t.length; i++) {
segments.push(vlist[t[i]]);
}
}
}
}
return { vertices: vertices, segments: segments };
}
function modulo(a, b) {
var reminder = a % b;
return reminder >= 0 ? reminder : reminder + b;
}
var GridArray = function GridArray(dim) {
this.dim = dim; // dimensions of the grid for the entire unit cell
this.values = new Float32Array(dim[0] * dim[1] * dim[2]);
};
GridArray.prototype.grid2index = function grid2index (i, j, k) {
i = modulo(i, this.dim[0]);
j = modulo(j, this.dim[1]);
k = modulo(k, this.dim[2]);
return this.dim[2] * (this.dim[1] * i + j) + k;
};
GridArray.prototype.grid2index_unchecked = function grid2index_unchecked (i, j, k) {
return this.dim[2] * (this.dim[1] * i + j) + k;
};
GridArray.prototype.grid2frac = function grid2frac (i, j, k) {
return [i / this.dim[0], j / this.dim[1], k / this.dim[2]];
};
// return grid coordinates (rounded down) for the given fractional coordinates
GridArray.prototype.frac2grid = function frac2grid (xyz) {
// at one point "| 0" here made extract_block() 40% faster on V8 3.14,
// but I don't see any effect now
return [Math.floor(xyz[0] * this.dim[0]) | 0,
Math.floor(xyz[1] * this.dim[1]) | 0,
Math.floor(xyz[2] * this.dim[2]) | 0];
};
GridArray.prototype.set_grid_value = function set_grid_value (i, j, k, value) {
var idx = this.grid2index(i, j, k);
this.values[idx] = value;
};
GridArray.prototype.get_grid_value = function get_grid_value (i, j, k) {
var idx = this.grid2index(i, j, k);
return this.values[idx];
};
function calculate_stddev(a, offset) {
var sum = 0;
var sq_sum = 0;
var alen = a.length;
for (var i = offset; i < alen; i++) {
sum += a[i];
sq_sum += a[i] * a[i];
}
var mean = sum / (alen - offset);
var variance = sq_sum / (alen - offset) - mean * mean;
return {mean: mean, rms: Math.sqrt(variance)};
}
var ElMap = function ElMap() {
this.unit_cell = null;
this.grid = null;
this.stats = { mean: 0.0, rms: 1.0 };
this.block = new Block();
};
ElMap.prototype.abs_level = function abs_level (sigma) {
return sigma * this.stats.rms + this.stats.mean;
};
// http://www.ccp4.ac.uk/html/maplib.html#description
// eslint-disable-next-line complexity
ElMap.prototype.from_ccp4 = function from_ccp4 (buf, expand_symmetry) {
if (expand_symmetry === undefined) { expand_symmetry = true; }
if (buf.byteLength < 1024) { throw Error('File shorter than 1024 bytes.'); }
//console.log('buf type: ' + Object.prototype.toString.call(buf));
// for now we assume both file and host are little endian
var iview = new Int32Array(buf, 0, 256);
// word 53 - character string 'MAP ' to identify file type
if (iview[52] !== 0x2050414d) { throw Error('not a CCP4 map'); }
// map has 3 dimensions referred to as columns (fastest changing), rows
// and sections (c-r-s)
var n_crs = [iview[0], iview[1], iview[2]];
var mode = iview[3];
var nb;
if (mode === 2) { nb = 4; }
else if (mode === 0) { nb = 1; }
else { throw Error('Only Mode 2 and Mode 0 of CCP4 map is supported.'); }
var start = [iview[4], iview[5], iview[6]];
var n_grid = [iview[7], iview[8], iview[9]];
var nsymbt = iview[23]; // size of extended header in bytes
if (1024 + nsymbt + nb*n_crs[0]*n_crs[1]*n_crs[2] !== buf.byteLength) {
throw Error('ccp4 file too short or too long');
}
var fview = new Float32Array(buf, 0, buf.byteLength / 4);
this.unit_cell = new UnitCell(fview[10], fview[11], fview[12],
fview[13], fview[14], fview[15]);
// MAPC, MAPR, MAPS - axis corresp to cols, rows, sections (1,2,3 for X,Y,Z)
var map_crs = [iview[16], iview[17], iview[18]];
var ax = map_crs.indexOf(1);
var ay = map_crs.indexOf(2);
var az = map_crs.indexOf(3);
var min = fview[19];
var max = fview[20];
//const sg_number = iview[22];
//const lskflg = iview[24];
var grid = new GridArray(n_grid);
if (nsymbt % 4 !== 0) {
throw Error('CCP4 map with NSYMBT not divisible by 4 is not supported.');
}
var data_view;
if (mode === 2) { data_view = fview; }
else /* mode === 0 */ { data_view = new Int8Array(buf); }
var idx = (1024 + nsymbt) / nb | 0;
// We assume that if DMEAN and RMS from the header are not clearly wrong
// they are what the user wants. Because the map can cover a small part
// of the asu and its rmsd may be different than the total rmsd.
this.stats.mean = fview[21];
this.stats.rms = fview[54];
if (this.stats.mean < min || this.stats.mean > max || this.stats.rms <= 0) {
this.stats = calculate_stddev(data_view, idx);
}
var b1 = 1;
var b0 = 0;
// if the file was converted by mapmode2to0 - scale the data
if (mode === 0 && iview[39] === -128 && iview[40] === 127) {
// scaling f(x)=b1*x+b0 such that f(-128)=min and f(127)=max
b1 = (max - min) / 255.0;
b0 = 0.5 * (min + max + b1);
}
var end = [start[0] + n_crs[0], start[1] + n_crs[1], start[2] + n_crs[2]];
var it = [0, 0, 0];
for (it[2] = start[2]; it[2] < end[2]; it[2]++) { // sections
for (it[1] = start[1]; it[1] < end[1]; it[1]++) { // rows
for (it[0] = start[0]; it[0] < end[0]; it[0]++) { // cols
grid.set_grid_value(it[ax], it[ay], it[az], b1 * data_view[idx] + b0);
idx++;
}
}
}
if (expand_symmetry && nsymbt > 0) {
var u8view = new Uint8Array(buf);
for (var i = 0; i+80 <= nsymbt; i += 80) {
var j = (void 0);
var symop = '';
for (j = 0; j < 80; ++j) {
symop += String.fromCharCode(u8view[1024 + i + j]);
}
if (/^\s*x\s*,\s*y\s*,\s*z\s*$/i.test(symop)) { continue; }// skip x,y,z
//console.log('sym ops', symop.trim());
var mat = parse_symop(symop);
// Note: we apply here symops to grid points instead of coordinates.
// In the cases we came across it is equivalent, but in general not.
for (j = 0; j < 3; ++j) {
mat[j][3] = Math.round(mat[j][3] * n_grid[j]) | 0;
}
idx = (1024 + nsymbt) / nb | 0;
var xyz = [0, 0, 0];
for (it[2] = start[2]; it[2] < end[2]; it[2]++) { // sections
for (it[1] = start[1]; it[1] < end[1]; it[1]++) { // rows
for (it[0] = start[0]; it[0] < end[0]; it[0]++) { // cols
for (j = 0; j < 3; ++j) {
xyz[j] = it[ax] * mat[j][0] + it[ay] * mat[j][1] +
it[az] * mat[j][2] + mat[j][3];
}
grid.set_grid_value(xyz[0], xyz[1], xyz[2],
b1 * data_view[idx] + b0);
idx++;
}
}
}
}
}
this.grid = grid;
};
// DSN6 MAP FORMAT
// http://www.uoxray.uoregon.edu/tnt/manual/node104.html
// Density values are stored as bytes.
ElMap.prototype.from_dsn6 = function from_dsn6 (buf) {
//console.log('buf type: ' + Object.prototype.toString.call(buf));
var u8data = new Uint8Array(buf);
var iview = new Int16Array(u8data.buffer);
if (iview[18] !== 100) {
var len = iview.length;// or only header, 256?
for (var n = 0; n < len; n++) {
// swapping bytes with Uint8Array like this:
// var tmp=u8data[n*2]; u8data[n*2]=u8data[n*2+1]; u8data[n*2+1]=tmp;
// was slowing down this whole function 5x times (!?) on V8.
var val = iview[n];
iview[n] = ((val & 0xff) << 8) | ((val >> 8) & 0xff);
}
}
if (iview[18] !== 100) {
throw Error('Endian swap failed');
}
var origin = [iview[0], iview[1], iview[2]];
var n_real = [iview[3], iview[4], iview[5]];
var n_grid = [iview[6], iview[7], iview[8]];
var cell_mult = 1.0 / iview[17];
this.unit_cell = new UnitCell(cell_mult * iview[9],
cell_mult * iview[10],
cell_mult * iview[11],
cell_mult * iview[12],
cell_mult * iview[13],
cell_mult * iview[14]);
var grid = new GridArray(n_grid);
var prod = iview[15] / 100;
var plus = iview[16];
//var data_scale_factor = iview[15] / iview[18] + iview[16];
// bricks have 512 (8x8x8) values
var offset = 512;
var n_blocks = [Math.ceil(n_real[0] / 8),
Math.ceil(n_real[1] / 8),
Math.ceil(n_real[2] / 8)];
for (var zz = 0; zz < n_blocks[2]; zz++) {
for (var yy = 0; yy < n_blocks[1]; yy++) {
for (var xx = 0; xx < n_blocks[0]; xx++) { // loop over bricks
for (var k = 0; k < 8; k++) {
var z = 8 * zz + k;
for (var j = 0; j < 8; j++) {
var y = 8 * yy + j;
for (var i = 0; i < 8; i++) { // loop inside brick
var x = 8 * xx + i;
if (x < n_real[0] && y < n_real[1] && z < n_real[2]) {
var density = (u8data[offset] - plus) / prod;
offset++;
grid.set_grid_value(origin[0] + x,
origin[1] + y,
origin[2] + z, density);
} else {
offset += 8 - i;
break;
}
}
}
}
}
}
}
this.stats = calculate_stddev(grid.values, 0);
this.grid = grid;
//this.show_debug_info();
};
ElMap.prototype.show_debug_info = function show_debug_info () {
console.log('unit cell:', this.unit_cell && this.unit_cell.parameters);
console.log('grid:', this.grid && this.grid.dim);
};
// Extract a block of density for calculating an isosurface using the
// separate marching cubes implementation.
ElMap.prototype.extract_block = function extract_block (radius, center) {
var grid = this.grid;
var unit_cell = this.unit_cell;
if (grid == null || unit_cell == null) { return; }
var fc = unit_cell.fractionalize(center);
var r = [radius / unit_cell.parameters[0],
radius / unit_cell.parameters[1],
radius / unit_cell.parameters[2]];
var grid_min = grid.frac2grid([fc[0] - r[0], fc[1] - r[1], fc[2] - r[2]]);
var grid_max =