@ccp-nc/crystvis-js
Version:
A Three.js based crystallographic visualisation tool
189 lines (161 loc) • 5.28 kB
JavaScript
;
/**
* @fileoverview Utility functions
* @module
*/
import _ from 'lodash';
import * as THREE from 'three';
/**
* Compute a full list of indices of all cells for
* a supercell of given size
* @param {Array} scell Size of the requested supercell
*/
function supercellGrid(scell) {
var bounds = _.map(scell, function(x) {
var lb = Math.ceil(-x / 2)+(1-x%2); // This makes it so that for even supercells we skew on the positive side
if (Object.is(lb, -0)) {
lb = 0; // Avoids -0
}
var ub = Math.ceil(x / 2)+(1-x%2);
return [lb, ub];
});
var grid = [];
for (var i = bounds[0][0]; i < bounds[0][1]; ++i) {
for (var j = bounds[1][0]; j < bounds[1][1]; ++j) {
for (var k = bounds[2][0]; k < bounds[2][1]; ++k) {
grid.push([i, j, k]);
}
}
}
return grid;
}
/**
* Reduce a tuple of atomic index + i,j,k cell indices to a single integer
* @param {int} i Atomic index
* @param {Array} ijk Cell indices
* @param {Array} scell Supercell size
* @param {int} n Number of atoms in the model
*
* @return {int} Overall index
*/
function supercellIndex(i, ijk, scell, n) {
ijk = _.map(ijk, function(x, i) {
return x + Math.floor((scell[i]-1)/2); // Important (depends on the convention in supercellGrid)
});
// If any of these is smaller than 0, we're sure to be out
if (ijk[0] < 0 || ijk[1] < 0 || ijk[2] < 0) {
return -1;
}
if (ijk[0] >= scell[0] || ijk[1] >= scell[1] || ijk[2] >= scell[2]) {
return -1;
}
var itot = ijk[2] + ijk[1] * scell[2] + ijk[0] * scell[2] * scell[1];
itot = i + itot*n;
return itot;
}
/**
* Turn a unit cell expressed as Array of Arrays into a THREE.Matrix3 object
* @param {Array} cell Cell in Array form
*
* @return {THREE.Matrix3} Cell in THREE.Matrix3 form
*/
function cellMatrix3(cell) {
var lc = cell;
cell = new THREE.Matrix3();
cell.set(lc[0][0], lc[1][0], lc[2][0],
lc[0][1], lc[1][1], lc[2][1],
lc[0][2], lc[1][2], lc[2][2]);
return cell;
}
/** Add a static variable to a class definition, old style (will become
* obsolete once the static keyword is widely accepted in ES)
* @param {Object} cls Class
* @param {String} name Name of the variable to define
* @param {any} value Value to assign to it
*/
function addStaticVar(cls, name, value) {
Object.defineProperty(cls, name, {
value: value,
writable: false
});
}
/** Shift a CPK Color to be more distinct (used for isotopes)
*
* @param {int} basec Base color to modify. Can be anything accepted
* by the THREE.Color constructor, but by default
* we assume it's an hex integer.
* @param {float} shift Shift to apply. Should range from -1 to 1.
*
* @return {int} Shifted color, returned as hex code.
*
* */
function shiftCpkColor(basec, shift=0) {
let c = new THREE.Color(basec);
let hsl = {};
c.getHSL(hsl);
// Here the goal is to choose a color that's still similar enough
// to the original, but also contrasts to it.
// We shift it more in hue if it's not very saturated/bright, and
// also shift its lightness towards 0.5 and saturation towards 1,
// so the hue becomes more evident
/*
let f = ((1.0-hsl.s)/2 + Math.abs(hsl.l-0.5))*0.7 + 0.3;
hsl.h = (hsl.h+shift*f*0.5)%1;
f *= 0.4;
hsl.s = f+(1-f)*hsl.s;
f *= 0.5;
hsl.l = f/2.0+(1-f)*hsl.l;
*/
// How close to white/black is the color?
let bw = Math.abs(hsl.l-0.5)/0.5;
if (Math.abs(bw-1) < 1e-2) {
// By convention we set the hue as blue
hsl.h = 0.6666;
}
// Reduce/increase luminance most for blacks and whites
hsl.l = (hsl.l-0.5)*(1-0.05*bw)+0.5;
// Increase saturation most for blacks and whites
hsl.s = hsl.s + 0.8*bw;
// Rotate hue least for vivid colors
hsl.h = (hsl.h+0.1*(0.5+bw)*shift);
c.setHSL(hsl.h, hsl.s, hsl.l);
return c.getHex();
}
/** Produce a low-collision hash code from a string argument. The algorithm
* is inspired by Java's .hashCode() method.
*
* @param {String} arg The string to hash
*
* @return {int} Hash code
*/
function hashCode(arg) {
arg = arg.toString(); // For sanity
let hash = 0;
for (let i = 0; i < arg.length; ++i) {
let char = arg.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
/**
* Compare floating point numbers for equality with a given tolerance
*
* @param {float} a First number
* @param {float} b Second number
* @param {float} tol Tolerance (absolute)
*
* @return {bool} True if the numbers are equal within the tolerance
*
* TODO: could add relative tolerance as well
*/
function floatEqual(a, b, tol=1e-6) {
// check for NaNs
if (a !== a || b !== b) {
return false;
}
return Math.abs(a-b) < tol;
}
export {
supercellGrid, supercellIndex, cellMatrix3, addStaticVar, shiftCpkColor, hashCode, floatEqual
}