molstar
Version:
A comprehensive macromolecular library.
252 lines (251 loc) • 11.3 kB
JavaScript
"use strict";
/**
* Copyright (c) 2018-2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Grid = void 0;
const geometry_1 = require("../../mol-math/geometry.js");
const linear_algebra_1 = require("../../mol-math/linear-algebra.js");
const histogram_1 = require("../../mol-math/histogram.js");
const interpolate_1 = require("../../mol-math/interpolate.js");
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3transformMat4 = linear_algebra_1.Vec3.transformMat4;
const v3lerp = linear_algebra_1.Vec3.lerp;
var Grid;
(function (Grid) {
Grid.One = {
transform: { kind: 'matrix', matrix: linear_algebra_1.Mat4.identity() },
cells: linear_algebra_1.Tensor.create(linear_algebra_1.Tensor.Space([1, 1, 1], [0, 1, 2]), linear_algebra_1.Tensor.Data1([0])),
stats: { min: 0, max: 0, mean: 0, sigma: 0 },
};
const _scale = (0, linear_algebra_1.Mat4)(), _translate = (0, linear_algebra_1.Mat4)();
function getGridToCartesianTransform(grid) {
if (grid.transform.kind === 'matrix') {
return linear_algebra_1.Mat4.copy((0, linear_algebra_1.Mat4)(), grid.transform.matrix);
}
if (grid.transform.kind === 'spacegroup') {
const { cells: { space } } = grid;
const scale = linear_algebra_1.Mat4.fromScaling(_scale, linear_algebra_1.Vec3.div((0, linear_algebra_1.Vec3)(), geometry_1.Box3D.size((0, linear_algebra_1.Vec3)(), grid.transform.fractionalBox), linear_algebra_1.Vec3.ofArray(space.dimensions)));
const translate = linear_algebra_1.Mat4.fromTranslation(_translate, grid.transform.fractionalBox.min);
return linear_algebra_1.Mat4.mul3((0, linear_algebra_1.Mat4)(), grid.transform.cell.fromFractional, translate, scale);
}
return linear_algebra_1.Mat4.identity();
}
Grid.getGridToCartesianTransform = getGridToCartesianTransform;
function areEquivalent(gridA, gridB) {
return gridA === gridB;
}
Grid.areEquivalent = areEquivalent;
function isEmpty(grid) {
return grid.cells.data.length === 0;
}
Grid.isEmpty = isEmpty;
function getBoundingSphere(grid, boundingSphere) {
if (!boundingSphere)
boundingSphere = (0, geometry_1.Sphere3D)();
const dimensions = grid.cells.space.dimensions;
const transform = Grid.getGridToCartesianTransform(grid);
return geometry_1.Sphere3D.fromDimensionsAndTransform(boundingSphere, dimensions, transform);
}
Grid.getBoundingSphere = getBoundingSphere;
/**
* Compute histogram with given bin count.
* Cached on the Grid object.
*/
function getHistogram(grid, binCount) {
let histograms = grid._historams;
if (!histograms) {
histograms = grid._historams = {};
}
if (!histograms[binCount]) {
histograms[binCount] = (0, histogram_1.calculateHistogram)(grid.cells.data, binCount, { min: grid.stats.min, max: grid.stats.max });
}
return histograms[binCount];
}
Grid.getHistogram = getHistogram;
function makeGetTrilinearlyInterpolated(grid, transform) {
const cartnToGrid = Grid.getGridToCartesianTransform(grid);
linear_algebra_1.Mat4.invert(cartnToGrid, cartnToGrid);
const gridCoords = (0, linear_algebra_1.Vec3)();
const { stats } = grid;
const { dimensions, get } = grid.cells.space;
const data = grid.cells.data;
const [mi, mj, mk] = dimensions;
const getValue = (i, j, k) => get(data, i, j, k);
return function getTrilinearlyInterpolated(position) {
v3transformMat4(gridCoords, position, cartnToGrid);
const value = trilinearlyInterpolate(gridCoords, mi, mj, mk, getValue);
if (Number.isNaN(value))
return value;
if (transform === 'relative') {
return (value - stats.mean) / stats.sigma;
}
else {
return value;
}
};
}
Grid.makeGetTrilinearlyInterpolated = makeGetTrilinearlyInterpolated;
/**
* Core trilinear interpolation function.
* @param gridCoords - Position in grid coordinates (fractional indices)
* @param mi, mj, mk - Grid dimensions
* @param getValue - Function to get value at integer grid coordinates
* @returns Interpolated value or NaN if out of bounds
*/
function trilinearlyInterpolate(gridCoords, mi, mj, mk, getValue) {
const i = Math.trunc(gridCoords[0]);
const j = Math.trunc(gridCoords[1]);
const k = Math.trunc(gridCoords[2]);
if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) {
return Number.NaN;
}
const u = gridCoords[0] - i;
const v = gridCoords[1] - j;
const w = gridCoords[2] - k;
const ii = Math.min(i + 1, mi - 1);
const jj = Math.min(j + 1, mj - 1);
const kk = Math.min(k + 1, mk - 1);
let a = getValue(i, j, k);
let b = getValue(ii, j, k);
let c = getValue(i, jj, k);
let d = getValue(ii, jj, k);
const x = (0, interpolate_1.lerp)((0, interpolate_1.lerp)(a, b, u), (0, interpolate_1.lerp)(c, d, u), v);
a = getValue(i, j, kk);
b = getValue(ii, j, kk);
c = getValue(i, jj, kk);
d = getValue(ii, jj, kk);
const y = (0, interpolate_1.lerp)((0, interpolate_1.lerp)(a, b, u), (0, interpolate_1.lerp)(c, d, u), v);
return (0, interpolate_1.lerp)(x, y, w);
}
Grid.trilinearlyInterpolate = trilinearlyInterpolate;
const _a = (0, linear_algebra_1.Vec3)(), _b = (0, linear_algebra_1.Vec3)(), _c = (0, linear_algebra_1.Vec3)(), _d = (0, linear_algebra_1.Vec3)();
const _ab = (0, linear_algebra_1.Vec3)(), _cd = (0, linear_algebra_1.Vec3)(), _x = (0, linear_algebra_1.Vec3)(), _y = (0, linear_algebra_1.Vec3)();
/**
* Core trilinear interpolation function for Vec3 values.
* More efficient for interleaved data like gradients since getValue is called once per grid point.
* @param gridCoords - Position in grid coordinates (fractional indices)
* @param mi, mj, mk - Grid dimensions
* @param getValue - Function to get Vec3 value at integer grid coordinates
* @param out - Output Vec3
* @returns true if interpolation succeeded, false if out of bounds
*/
function trilinearlyInterpolateVec3(gridCoords, mi, mj, mk, getValue, out) {
const i = Math.trunc(gridCoords[0]);
const j = Math.trunc(gridCoords[1]);
const k = Math.trunc(gridCoords[2]);
if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) {
return false;
}
const u = gridCoords[0] - i;
const v = gridCoords[1] - j;
const w = gridCoords[2] - k;
const ii = Math.min(i + 1, mi - 1);
const jj = Math.min(j + 1, mj - 1);
const kk = Math.min(k + 1, mk - 1);
// Interpolate in the k plane
getValue(i, j, k, _a);
getValue(ii, j, k, _b);
getValue(i, jj, k, _c);
getValue(ii, jj, k, _d);
v3lerp(_ab, _a, _b, u);
v3lerp(_cd, _c, _d, u);
v3lerp(_x, _ab, _cd, v);
// Interpolate in the k+1 plane
getValue(i, j, kk, _a);
getValue(ii, j, kk, _b);
getValue(i, jj, kk, _c);
getValue(ii, jj, kk, _d);
v3lerp(_ab, _a, _b, u);
v3lerp(_cd, _c, _d, u);
v3lerp(_y, _ab, _cd, v);
// Final interpolation between planes
v3lerp(out, _x, _y, w);
return true;
}
Grid.trilinearlyInterpolateVec3 = trilinearlyInterpolateVec3;
/**
* Pre-compute gradients at each grid cell using central differences.
* Returns a single Float32Array with interleaved xyz components (x1, y1, z1, x2, y2, z2, ...).
* Cached on the Grid object.
*/
function getGradients(grid) {
if (grid._gradients) {
return grid._gradients;
}
const gradients = computeGradients(grid);
grid._gradients = gradients;
return gradients;
}
Grid.getGradients = getGradients;
function computeGradients(grid) {
const { dimensions, get, dataOffset } = grid.cells.space;
const data = grid.cells.data;
const [mi, mj, mk] = dimensions;
const n = mi * mj * mk;
const values = new Float32Array(n * 3);
let min = Infinity;
let max = -Infinity;
let sum = 0;
let sumSq = 0;
for (let k = 0; k < mk; ++k) {
for (let j = 0; j < mj; ++j) {
for (let i = 0; i < mi; ++i) {
const idx = dataOffset(i, j, k) * 3;
// Use central differences where possible, forward/backward at boundaries
const im = Math.max(0, i - 1);
const ip = Math.min(mi - 1, i + 1);
const jm = Math.max(0, j - 1);
const jp = Math.min(mj - 1, j + 1);
const km = Math.max(0, k - 1);
const kp = Math.min(mk - 1, k + 1);
// Gradient components (using central differences with proper divisor)
const gx = (get(data, ip, j, k) - get(data, im, j, k)) / (ip - im || 1);
const gy = (get(data, i, jp, k) - get(data, i, jm, k)) / (jp - jm || 1);
const gz = (get(data, i, j, kp) - get(data, i, j, km)) / (kp - km || 1);
values[idx] = gx;
values[idx + 1] = gy;
values[idx + 2] = gz;
const mag = gx * gx + gy * gy + gz * gz;
if (mag < min)
min = mag;
if (mag > max)
max = mag;
sum += mag;
sumSq += mag * mag;
}
}
}
if (min === Infinity)
min = 0;
if (max === -Infinity)
max = 1;
min = Math.sqrt(min);
max = Math.sqrt(max);
const mean = sum / n;
const sigma = Math.sqrt(sumSq / n);
return { values, magnitude: { min, max, mean, sigma } };
}
/**
* Create a function that returns trilinearly interpolated gradient at a grid position.
* The gradient is pre-computed at grid cells and interpolated for smooth results.
*/
function makeGetInterpolatedGradient(grid) {
const { values: g } = getGradients(grid);
const { dimensions, dataOffset } = grid.cells.space;
const [mi, mj, mk] = dimensions;
const getGradientVec3 = (i, j, k, out) => {
const idx = dataOffset(i, j, k) * 3;
out[0] = g[idx];
out[1] = g[idx + 1];
out[2] = g[idx + 2];
};
return function getInterpolatedGradient(gridCoords, out) {
return trilinearlyInterpolateVec3(gridCoords, mi, mj, mk, getGradientVec3, out);
};
}
Grid.makeGetInterpolatedGradient = makeGetInterpolatedGradient;
})(Grid || (exports.Grid = Grid = {}));