UNPKG

uglymol

Version:

Macromolecular Viewer for Crystallographers

1,488 lines (1,414 loc) 267 kB
/*! * 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 =