Skip to main content
Sign in
Snippets Groups Projects
Commit 0a368795 authored by Alexander Rose's avatar Alexander Rose
Browse files

Merge branch 'master' into gl-geo

# Conflicts:
#	src/mol-math/geometry/sphere.ts
parents d54c9f8a e69b31ba
No related branches found
No related tags found
No related merge requests found
Showing
with 958 additions and 578 deletions
...@@ -26,7 +26,7 @@ async function getPdb(pdb: string) { ...@@ -26,7 +26,7 @@ async function getPdb(pdb: string) {
return CIF.schema.mmCIF(parsed.result.blocks[0]) return CIF.schema.mmCIF(parsed.result.blocks[0])
} }
function atomLabel(model: Model, aI: number) { export function atomLabel(model: Model, aI: number) {
const { atoms, residues, chains, residueSegments, chainSegments } = model.hierarchy const { atoms, residues, chains, residueSegments, chainSegments } = model.hierarchy
const { label_atom_id } = atoms const { label_atom_id } = atoms
const { label_comp_id, label_seq_id } = residues const { label_comp_id, label_seq_id } = residues
...@@ -36,15 +36,17 @@ function atomLabel(model: Model, aI: number) { ...@@ -36,15 +36,17 @@ function atomLabel(model: Model, aI: number) {
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}` return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
} }
function printBonds(model: Model) { function printBonds(model: Model) {
const { count, offset, neighbor } = Model.bonds(model) // TODO: do bonds again
for (let i = 0; i < count; ++i) { // const { count, offset, neighbor } = Model.bonds(model)
const start = offset[i]; // for (let i = 0; i < count; ++i) {
const end = offset[i + 1]; // const start = offset[i];
for (let bI = start; bI < end; bI++) { // const end = offset[i + 1];
console.log(`${atomLabel(model, i)} -- ${atomLabel(model, neighbor[bI])}`) // for (let bI = start; bI < end; bI++) {
} // console.log(`${atomLabel(model, i)} -- ${atomLabel(model, neighbor[bI])}`)
} // }
// }
} }
async function run(pdb: string) { async function run(pdb: string) {
... ...
......
/*
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export * from './geometry/common'
export * from './geometry/symmetry-operator'
export * from './geometry/lookup3d/common'
export * from './geometry/lookup3d/grid'
export * from './geometry/primitives/box3d'
export * from './geometry/primitives/sphere3d'
\ No newline at end of file
/*
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { GridLookup3D } from '../../geometry';
import { sortArray } from 'mol-data/util';
import { OrderedSet } from 'mol-data/int';
const xs = [0, 0, 1];
const ys = [0, 1, 0];
const zs = [0, 0, 0];
const rs = [0, 0.5, 1/3];
describe('GridLookup3d', () => {
it('basic', () => {
const grid = GridLookup3D({ x: xs, y: ys, z: zs, indices: OrderedSet.ofBounds(0, 3) });
let r = grid.find(0, 0, 0, 0);
expect(r.count).toBe(1);
expect(r.indices[0]).toBe(0);
r = grid.find(0, 0, 0, 1);
expect(r.count).toBe(3);
expect(sortArray(r.indices)).toEqual([0, 1, 2]);
});
it('radius', () => {
const grid = GridLookup3D({ x: xs, y: ys, z: zs, radius: [0, 0.5, 1 / 3], indices: OrderedSet.ofBounds(0, 3) });
let r = grid.find(0, 0, 0, 0);
expect(r.count).toBe(1);
expect(r.indices[0]).toBe(0);
r = grid.find(0, 0, 0, 0.5);
expect(r.count).toBe(2);
expect(sortArray(r.indices)).toEqual([0, 1]);
});
it('indexed', () => {
const grid = GridLookup3D({ x: xs, y: ys, z: zs, indices: OrderedSet.ofSingleton(1), radius: rs });
let r = grid.find(0, 0, 0, 0);
expect(r.count).toBe(0);
r = grid.find(0, 0, 0, 0.5);
expect(r.count).toBe(1);
expect(sortArray(r.indices)).toEqual([0]);
});
});
\ No newline at end of file
/*
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { OrderedSet } from 'mol-data/int'
export interface PositionData {
x: ArrayLike<number>,
y: ArrayLike<number>,
z: ArrayLike<number>,
// subset indices into the x/y/z/radius arrays
indices: OrderedSet,
// optional element radius
radius?: ArrayLike<number>
}
/*
* Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
// TODO: 3d grid lookup (use single cell for small sets), make bounding sphere part of the structure
// TODO: assign radius to points?
/**
* A "masked" 3D spatial lookup structure.
*/
import Mask from 'mol-util/mask'
export type FindFunc = (x: number, y: number, z: number, radius: number) => Result
export type CheckFunc = (x: number, y: number, z: number, radius: number) => boolean
export interface Result {
readonly count: number,
readonly indices: number[],
readonly squaredDistances: number[]
}
export interface Positions { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
interface GridLookup { find: (mask: Mask) => FindFunc, check: (mask: Mask) => CheckFunc }
function GridLookup(positions: Positions): GridLookup {
const tree = build(createInputData(positions));
return {
find(mask) {
const ctx = QueryContext.create(tree, mask, false);
return function (x: number, y: number, z: number, radius: number) {
QueryContext.update(ctx, x, y, z, radius);
nearest(ctx);
return ctx.buffer;
}
},
check(mask) {
const ctx = QueryContext.create(tree, mask, true);
return function (x: number, y: number, z: number, radius: number) {
QueryContext.update(ctx, x, y, z, radius);
return nearest(ctx);
}
}
}
}
interface InputData {
bounds: Box3D,
count: number,
positions: Positions
}
interface Box3D {
min: number[],
max: number[]
}
namespace Box3D {
export function createInfinite(): Box3D {
return {
min: [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE],
max: [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE]
}
}
}
/**
* Query context. Handles the actual querying.
*/
interface QueryContext<T> {
structure: T,
pivot: number[],
radius: number,
radiusSq: number,
buffer: QueryContext.Buffer,
mask: Mask,
isCheck: boolean
}
namespace QueryContext {
export interface Buffer extends Result {
count: number;
indices: any[];
squaredDistances: number[];
}
export function add<T>(ctx: QueryContext<T>, distSq: number, index: number) {
const buffer = ctx.buffer;
buffer.squaredDistances[buffer.count] = distSq;
buffer.indices[buffer.count++] = index;
}
function resetBuffer(buffer: Buffer) { buffer.count = 0; }
function createBuffer(): Buffer {
return {
indices: [],
count: 0,
squaredDistances: []
}
}
/**
* Query the tree and store the result to this.buffer. Overwrites the old result.
*/
export function update<T>(ctx: QueryContext<T>, x: number, y: number, z: number, radius: number) {
ctx.pivot[0] = x;
ctx.pivot[1] = y;
ctx.pivot[2] = z;
ctx.radius = radius;
ctx.radiusSq = radius * radius;
resetBuffer(ctx.buffer);
}
export function create<T>(structure: T, mask: Mask, isCheck: boolean): QueryContext<T> {
return {
structure,
buffer: createBuffer(),
pivot: [0.1, 0.1, 0.1],
radius: 1.1,
radiusSq: 1.1 * 1.1,
mask,
isCheck
}
}
}
function createInputData(positions: { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }): InputData {
const { x, y, z } = positions;
const bounds = Box3D.createInfinite();
const count = x.length;
const { min, max } = bounds;
for (let i = 0; i < count; i++) {
min[0] = Math.min(x[i], min[0]);
min[1] = Math.min(y[i], min[1]);
min[2] = Math.min(z[i], min[2]);
max[0] = Math.max(x[i], max[0]);
max[1] = Math.max(y[i], max[1]);
max[2] = Math.max(z[i], max[2]);
}
return { positions, bounds, count };
}
/**
* Adapted from https://github.com/arose/ngl
* MIT License Copyright (C) 2014+ Alexander Rose
*/
interface SpatialHash {
size: number[],
min: number[],
grid: Uint32Array,
bucketOffset: Uint32Array,
bucketCounts: Int32Array,
bucketArray: Int32Array,
positions: Positions
}
interface State {
size: number[],
positions: Positions,
bounds: Box3D,
count: number
}
const enum Constants { Exp = 3 }
function nearest(ctx: QueryContext<SpatialHash>): boolean {
const { min: [minX, minY, minZ], size: [sX, sY, sZ], bucketOffset, bucketCounts, bucketArray, grid, positions: { x: px, y: py, z: pz } } = ctx.structure;
const { radius: r, radiusSq: rSq, pivot: [x, y, z], isCheck, mask } = ctx;
const loX = Math.max(0, (x - r - minX) >> Constants.Exp);
const loY = Math.max(0, (y - r - minY) >> Constants.Exp);
const loZ = Math.max(0, (z - r - minZ) >> Constants.Exp);
const hiX = Math.min(sX, (x + r - minX) >> Constants.Exp);
const hiY = Math.min(sY, (y + r - minY) >> Constants.Exp);
const hiZ = Math.min(sZ, (z + r - minZ) >> Constants.Exp);
for (let ix = loX; ix <= hiX; ix++) {
for (let iy = loY; iy <= hiY; iy++) {
for (let iz = loZ; iz <= hiZ; iz++) {
const bucketIdx = grid[(((ix * sY) + iy) * sZ) + iz];
if (bucketIdx > 0) {
const k = bucketIdx - 1;
const offset = bucketOffset[k];
const count = bucketCounts[k];
const end = offset + count;
for (let i = offset; i < end; i++) {
const idx = bucketArray[i];
if (!mask.has(idx)) continue;
const dx = px[idx] - x;
const dy = py[idx] - y;
const dz = pz[idx] - z;
const distSq = dx * dx + dy * dy + dz * dz;
if (distSq <= rSq) {
if (isCheck) return true;
QueryContext.add(ctx, distSq, idx)
}
}
}
}
}
}
return ctx.buffer.count > 0;
}
function _build(state: State): SpatialHash {
const { bounds, size: [sX, sY, sZ], positions: { x: px, y: py, z: pz }, count } = state;
const n = sX * sY * sZ;
const { min: [minX, minY, minZ] } = bounds;
let bucketCount = 0;
const grid = new Uint32Array(n);
const bucketIndex = new Int32Array(count);
for (let i = 0; i < count; i++) {
const x = (px[i] - minX) >> Constants.Exp;
const y = (py[i] - minY) >> Constants.Exp;
const z = (pz[i] - minZ) >> Constants.Exp;
const idx = (((x * sY) + y) * sZ) + z;
if ((grid[idx] += 1) === 1) {
bucketCount += 1
}
bucketIndex[i] = idx;
}
const bucketCounts = new Int32Array(bucketCount);
for (let i = 0, j = 0; i < n; i++) {
const c = grid[i];
if (c > 0) {
grid[i] = j + 1;
bucketCounts[j] = c;
j += 1;
}
}
const bucketOffset = new Uint32Array(count);
for (let i = 1; i < count; ++i) {
bucketOffset[i] += bucketOffset[i - 1] + bucketCounts[i - 1];
}
const bucketFill = new Int32Array(bucketCount);
const bucketArray = new Int32Array(count);
for (let i = 0; i < count; i++) {
const bucketIdx = grid[bucketIndex[i]]
if (bucketIdx > 0) {
const k = bucketIdx - 1;
bucketArray[bucketOffset[k] + bucketFill[k]] = i;
bucketFill[k] += 1;
}
}
return {
size: state.size,
bucketArray,
bucketCounts,
bucketOffset,
grid,
min: state.bounds.min,
positions: state.positions
}
}
function build({ positions, bounds, count }: InputData): SpatialHash {
const size = [
((bounds.max[0] - bounds.min[0]) >> Constants.Exp) + 1,
((bounds.max[1] - bounds.min[1]) >> Constants.Exp) + 1,
((bounds.max[2] - bounds.min[2]) >> Constants.Exp) + 1
];
const state: State = {
size,
positions,
bounds,
count
}
return _build(state);
}
export default GridLookup;
\ No newline at end of file
/*
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Box3D } from '../primitives/box3d'
import { Sphere3D } from '../primitives/sphere3d'
export interface Result<T> {
count: number,
indices: T[],
squaredDistances: number[]
}
export namespace Result {
export function add<T>(result: Result<T>, index: T, distSq: number) {
result.squaredDistances[result.count] = distSq;
result.indices[result.count++] = index;
}
export function reset(result: Result<any>) {
result.count = 0;
}
export function create<T>(): Result<T> {
return { count: 0, indices: [], squaredDistances: [] };
}
}
export interface Lookup3D<T = number> {
// The result is mutated with each call to find.
find(x: number, y: number, z: number, radius: number): Result<T>,
check(x: number, y: number, z: number, radius: number): boolean,
readonly boundary: { readonly box: Box3D, readonly sphere: Sphere3D }
}
\ No newline at end of file
/*
* Copyright (c) 2018 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>
*/
import { Result, Lookup3D } from './common'
import { Box3D } from '../primitives/box3d';
import { Sphere3D } from '../primitives/sphere3d';
import { PositionData } from '../common';
import { Vec3 } from '../../linear-algebra';
import { OrderedSet } from 'mol-data/int';
function GridLookup3D(data: PositionData): Lookup3D {
return new GridLookup3DImpl(data);
}
export { GridLookup3D }
class GridLookup3DImpl implements Lookup3D<number> {
private ctx: QueryContext;
boundary: Lookup3D['boundary'];
find(x: number, y: number, z: number, radius: number): Result<number> {
this.ctx.x = x;
this.ctx.y = y;
this.ctx.z = z;
this.ctx.radius = radius;
this.ctx.isCheck = false;
query(this.ctx);
return this.ctx.result;
}
check(x: number, y: number, z: number, radius: number): boolean {
this.ctx.x = x;
this.ctx.y = y;
this.ctx.z = z;
this.ctx.radius = radius;
this.ctx.isCheck = true;
return query(this.ctx);
}
constructor(data: PositionData) {
const structure = build(data);
this.ctx = createContext(structure);
this.boundary = { box: structure.boundingBox, sphere: structure.boundingSphere };
}
}
/**
* Adapted from https://github.com/arose/ngl
* MIT License Copyright (C) 2014+ Alexander Rose
*/
interface InputData {
x: ArrayLike<number>,
y: ArrayLike<number>,
z: ArrayLike<number>,
indices: OrderedSet,
radius?: ArrayLike<number>,
}
interface Grid3D {
size: number[],
min: number[],
grid: Uint32Array,
delta: number[],
bucketOffset: Uint32Array,
bucketCounts: Int32Array,
bucketArray: Int32Array,
data: InputData,
maxRadius: number,
expandedBox: Box3D,
boundingBox: Box3D,
boundingSphere: Sphere3D
}
interface BuildState {
size: number[],
delta: number[],
data: InputData,
expandedBox: Box3D,
boundingBox: Box3D,
boundingSphere: Sphere3D,
count: number
}
function _build(state: BuildState): Grid3D {
const { expandedBox, size: [sX, sY, sZ], data: { x: px, y: py, z: pz, radius, indices }, count, delta } = state;
const n = sX * sY * sZ;
const { min: [minX, minY, minZ] } = expandedBox;
let maxRadius = 0;
let bucketCount = 0;
const grid = new Uint32Array(n);
const bucketIndex = new Int32Array(count);
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
const x = Math.floor((px[i] - minX) / delta[0]);
const y = Math.floor((py[i] - minY) / delta[1]);
const z = Math.floor((pz[i] - minZ) / delta[2]);
const idx = (((x * sY) + y) * sZ) + z;
if ((grid[idx] += 1) === 1) {
bucketCount += 1
}
bucketIndex[t] = idx;
}
if (radius) {
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
if (radius[i] > maxRadius) maxRadius = radius[i];
}
}
const bucketCounts = new Int32Array(bucketCount);
for (let i = 0, j = 0; i < n; i++) {
const c = grid[i];
if (c > 0) {
grid[i] = j + 1;
bucketCounts[j] = c;
j += 1;
}
}
const bucketOffset = new Uint32Array(count);
for (let i = 1; i < count; ++i) {
bucketOffset[i] += bucketOffset[i - 1] + bucketCounts[i - 1];
}
const bucketFill = new Int32Array(bucketCount);
const bucketArray = new Int32Array(count);
for (let i = 0; i < count; i++) {
const bucketIdx = grid[bucketIndex[i]]
if (bucketIdx > 0) {
const k = bucketIdx - 1;
bucketArray[bucketOffset[k] + bucketFill[k]] = i;
bucketFill[k] += 1;
}
}
return {
size: state.size,
bucketArray,
bucketCounts,
bucketOffset,
grid,
delta,
min: state.expandedBox.min,
data: state.data,
maxRadius,
expandedBox: state.expandedBox,
boundingBox: state.boundingBox,
boundingSphere: state.boundingSphere
}
}
function build(data: PositionData) {
const boundingBox = Box3D.computeBounding(data);
// need to expand the grid bounds to avoid rounding errors
const expandedBox = Box3D.expand(boundingBox, Vec3.create(0.5, 0.5, 0.5));
const boundingSphere = Sphere3D.computeBounding(data);
const { indices } = data;
const S = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min);
let delta, size;
const elementCount = OrderedSet.size(indices);
if (elementCount > 0) {
// size of the box
// required "grid volume" so that each cell contains on average 32 elements.
const V = Math.ceil(elementCount / 32);
const f = Math.pow(V / (S[0] * S[1] * S[2]), 1 / 3);
size = [Math.ceil(S[0] * f), Math.ceil(S[1] * f), Math.ceil(S[2] * f)];
delta = [S[0] / size[0], S[1] / size[1], S[2] / size[2]];
} else {
delta = S;
size = [1, 1, 1];
}
const inputData: InputData = {
x: data.x,
y: data.y,
z: data.z,
indices,
radius: data.radius
};
const state: BuildState = {
size,
data: inputData,
expandedBox,
boundingBox,
boundingSphere,
count: elementCount,
delta
}
return _build(state);
}
interface QueryContext {
structure: Grid3D,
x: number,
y: number,
z: number,
radius: number,
result: Result<number>,
isCheck: boolean
}
function createContext(structure: Grid3D): QueryContext {
return { structure, x: 0.1, y: 0.1, z: 0.1, radius: 0.1, result: Result.create(), isCheck: false }
}
function query(ctx: QueryContext): boolean {
const { min, size: [sX, sY, sZ], bucketOffset, bucketCounts, bucketArray, grid, data: { x: px, y: py, z: pz, indices, radius }, delta, maxRadius } = ctx.structure;
const { radius: inputRadius, isCheck, x, y, z, result } = ctx;
const r = inputRadius + maxRadius;
const rSq = r * r;
Result.reset(result);
const loX = Math.max(0, Math.floor((x - r - min[0]) / delta[0]));
const loY = Math.max(0, Math.floor((y - r - min[1]) / delta[1]));
const loZ = Math.max(0, Math.floor((z - r - min[2]) / delta[2]));
const hiX = Math.min(sX - 1, Math.floor((x + r - min[0]) / delta[0]));
const hiY = Math.min(sY - 1, Math.floor((y + r - min[1]) / delta[1]));
const hiZ = Math.min(sZ - 1, Math.floor((z + r - min[2]) / delta[2]));
if (loX > hiX || loY > hiY || loZ > hiZ) return false;
for (let ix = loX; ix <= hiX; ix++) {
for (let iy = loY; iy <= hiY; iy++) {
for (let iz = loZ; iz <= hiZ; iz++) {
const bucketIdx = grid[(((ix * sY) + iy) * sZ) + iz];
if (bucketIdx === 0) continue;
const k = bucketIdx - 1;
const offset = bucketOffset[k];
const count = bucketCounts[k];
const end = offset + count;
for (let i = offset; i < end; i++) {
const idx = OrderedSet.getAt(indices, bucketArray[i]);
const dx = px[idx] - x;
const dy = py[idx] - y;
const dz = pz[idx] - z;
const distSq = dx * dx + dy * dy + dz * dz;
if (distSq <= rSq) {
if (maxRadius > 0 && Math.sqrt(distSq) - radius![idx] > inputRadius) continue;
if (isCheck) return true;
Result.add(result, bucketArray[i], distSq);
}
}
}
}
}
return result.count > 0;
}
\ No newline at end of file
/*
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../linear-algebra'
import { PositionData } from '../common'
import { OrderedSet } from 'mol-data/int';
interface Box3D { min: Vec3, max: Vec3 }
namespace Box3D {
export function computeBounding(data: PositionData): Box3D {
const min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
const max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
const { x, y, z, indices } = data;
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
min[0] = Math.min(x[i], min[0]);
min[1] = Math.min(y[i], min[1]);
min[2] = Math.min(z[i], min[2]);
max[0] = Math.max(x[i], max[0]);
max[1] = Math.max(y[i], max[1]);
max[2] = Math.max(z[i], max[2]);
}
return { min: Vec3.create(min[0], min[1], min[2]), max: Vec3.create(max[0], max[1], max[2]) }
}
export function expand(box: Box3D, delta: Vec3): Box3D {
return {
min: Vec3.sub(Vec3.zero(), box.min, delta),
max: Vec3.add(Vec3.zero(), box.max, delta)
}
}
}
export { Box3D }
\ No newline at end of file
/*
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../linear-algebra'
import { PositionData } from '../common'
import { OrderedSet } from 'mol-data/int';
interface Sphere3D { center: Vec3, radius: number }
namespace Sphere3D {
export function computeBounding(data: PositionData): Sphere3D {
const { x, y, z, indices } = data;
let cx = 0, cy = 0, cz = 0;
let radiusSq = 0;
const size = OrderedSet.size(indices);
for (let t = 0; t < size; t++) {
const i = OrderedSet.getAt(indices, t);
cx += x[i];
cy += y[i];
cz += z[i];
}
if (size > 0) {
cx /= size;
cy /= size;
cz /= size;
}
for (let t = 0; t < size; t++) {
const i = OrderedSet.getAt(indices, t);
const dx = x[i] - cx, dy = y[i] - cy, dz = z[i] - cz;
const d = dx * dx + dy * dy + dz * dz;
if (d > radiusSq) radiusSq = d;
}
return { center: Vec3.create(cx, cy, cz), radius: Math.sqrt(radiusSq) };
}
}
export { Sphere3D }
\ No newline at end of file
...@@ -61,7 +61,7 @@ namespace SymmetryOperator { ...@@ -61,7 +61,7 @@ namespace SymmetryOperator {
} }
} }
export default SymmetryOperator export { SymmetryOperator }
interface Projections { x(index: number): number, y(index: number): number, z(index: number): number } interface Projections { x(index: number): number, y(index: number): number, z(index: number): number }
... ...
......
...@@ -66,6 +66,14 @@ namespace Vec3 { ...@@ -66,6 +66,14 @@ namespace Vec3 {
return out; return out;
} }
export function ofArray(array: ArrayLike<number>) {
const out = zero();
out[0] = array[0];
out[1] = array[1];
out[2] = array[2];
return out;
}
export function set(out: Vec3, x: number, y: number, z: number): Vec3 { export function set(out: Vec3, x: number, y: number, z: number): Vec3 {
out[0] = x; out[0] = x;
out[1] = y; out[1] = y;
... ...
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
import { Mat4, Tensor } from 'mol-math/linear-algebra' import { Mat4, Tensor } from 'mol-math/linear-algebra'
import SymmetryOperator from 'mol-math/geometry/symmetry-operator' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
import Format from '../../format' import Format from '../../format'
import { Assembly, OperatorGroup, OperatorGroups } from '../../properties/symmetry' import { Assembly, OperatorGroup, OperatorGroups } from '../../properties/symmetry'
import { Queries as Q } from '../../../query' import { Queries as Q } from '../../../query'
... ...
......
...@@ -5,16 +5,12 @@ ...@@ -5,16 +5,12 @@
*/ */
import UUID from 'mol-util/uuid' import UUID from 'mol-util/uuid'
import GridLookup from 'mol-math/geometry/grid-lookup'
import Format from './format' import Format from './format'
import Hierarchy from './properties/hierarchy' import Hierarchy from './properties/hierarchy'
import Conformation from './properties/conformation' import Conformation from './properties/conformation'
import Symmetry from './properties/symmetry' import Symmetry from './properties/symmetry'
import Bonds from './properties/bonds'
import CoarseGrained from './properties/coarse-grained' import CoarseGrained from './properties/coarse-grained'
import computeBonds from './utils/compute-bonds'
import from_gro from './formats/gro' import from_gro from './formats/gro'
import from_mmCIF from './formats/mmcif' import from_mmCIF from './formats/mmcif'
...@@ -38,8 +34,7 @@ interface Model extends Readonly<{ ...@@ -38,8 +34,7 @@ interface Model extends Readonly<{
atomCount: number, atomCount: number,
}> { }> {
'@spatialLookup'?: GridLookup,
'@bonds'?: Bonds
} { } } { }
namespace Model { namespace Model {
...@@ -49,18 +44,18 @@ namespace Model { ...@@ -49,18 +44,18 @@ namespace Model {
case 'mmCIF': return from_mmCIF(format); case 'mmCIF': return from_mmCIF(format);
} }
} }
export function spatialLookup(model: Model): GridLookup { // export function spatialLookup(model: Model): GridLookup {
if (model['@spatialLookup']) return model['@spatialLookup']!; // if (model['@spatialLookup']) return model['@spatialLookup']!;
const lookup = GridLookup(model.conformation); // const lookup = GridLookup(model.conformation);
model['@spatialLookup'] = lookup; // model['@spatialLookup'] = lookup;
return lookup; // return lookup;
} // }
export function bonds(model: Model): Bonds { // export function bonds(model: Model): Bonds {
if (model['@bonds']) return model['@bonds']!; // if (model['@bonds']) return model['@bonds']!;
const bonds = computeBonds(model); // const bonds = computeBonds(model);
model['@bonds'] = bonds; // model['@bonds'] = bonds;
return bonds; // return bonds;
} // }
} }
export default Model export default Model
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
*/ */
import SymmetryOperator from 'mol-math/geometry/symmetry-operator' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
import { arrayFind } from 'mol-data/util' import { arrayFind } from 'mol-data/util'
import { Query } from '../../query' import { Query } from '../../query'
import { Model } from '../../model' import { Model } from '../../model'
... ...
......
/** // /**
* Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info. // * Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info.
* // *
* @author David Sehnal <david.sehnal@gmail.com> // * @author David Sehnal <david.sehnal@gmail.com>
*/ // */
import Mask from 'mol-util/mask' // import Mask from 'mol-util/mask'
import Model from '../model' // import Model from '../model'
import { BondType, ElementSymbol } from '../types' // import { BondType, ElementSymbol } from '../types'
import Bonds from '../properties/bonds' // import Bonds from '../properties/bonds'
import { StructConn, ComponentBondInfo } from '../formats/mmcif/bonds' // import { StructConn, ComponentBondInfo } from '../formats/mmcif/bonds'
export interface BondComputationParameters { // export interface BondComputationParameters {
maxHbondLength: number, // maxHbondLength: number,
forceCompute: boolean // forceCompute: boolean
} // }
// H,D,T are all mapped to H // // H,D,T are all mapped to H
const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 }; // const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 };
const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 }; // const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 }; // const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
const __DefaultBondingRadius = 2.001; // const __DefaultBondingRadius = 2.001;
const MetalsSet = (function () { // const MetalsSet = (function () {
const metals = ['LI', 'NA', 'K', 'RB', 'CS', 'FR', 'BE', 'MG', 'CA', 'SR', 'BA', 'RA', 'AL', 'GA', 'IN', 'SN', 'TL', 'PB', 'BI', 'SC', 'TI', 'V', 'CR', 'MN', 'FE', 'CO', 'NI', 'CU', 'ZN', 'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'AG', 'CD', 'LA', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'AU', 'HG', 'AC', 'RF', 'DB', 'SG', 'BH', 'HS', 'MT', 'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM', 'YB', 'LU', 'TH', 'PA', 'U', 'NP', 'PU', 'AM', 'CM', 'BK', 'CF', 'ES', 'FM', 'MD', 'NO', 'LR']; // const metals = ['LI', 'NA', 'K', 'RB', 'CS', 'FR', 'BE', 'MG', 'CA', 'SR', 'BA', 'RA', 'AL', 'GA', 'IN', 'SN', 'TL', 'PB', 'BI', 'SC', 'TI', 'V', 'CR', 'MN', 'FE', 'CO', 'NI', 'CU', 'ZN', 'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'AG', 'CD', 'LA', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'AU', 'HG', 'AC', 'RF', 'DB', 'SG', 'BH', 'HS', 'MT', 'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM', 'YB', 'LU', 'TH', 'PA', 'U', 'NP', 'PU', 'AM', 'CM', 'BK', 'CF', 'ES', 'FM', 'MD', 'NO', 'LR'];
const set = new Set<number>(); // const set = new Set<number>();
for (const m of metals) { // for (const m of metals) {
set.add(__ElementIndex[m]!); // set.add(__ElementIndex[m]!);
} // }
return set; // return set;
})(); // })();
function pair(a: number, b: number) { // function pair(a: number, b: number) {
if (a < b) return (a + b) * (a + b + 1) / 2 + b; // if (a < b) return (a + b) * (a + b + 1) / 2 + b;
else return (a + b) * (a + b + 1) / 2 + a; // else return (a + b) * (a + b + 1) / 2 + a;
} // }
function idx(e: ElementSymbol) { // function idx(e: ElementSymbol) {
const i = __ElementIndex[e as any as string]; // const i = __ElementIndex[e as any as string];
if (i === void 0) return -1; // if (i === void 0) return -1;
return i; // return i;
} // }
function pairThreshold(i: number, j: number) { // function pairThreshold(i: number, j: number) {
if (i < 0 || j < 0) return -1; // if (i < 0 || j < 0) return -1;
const r = __ElementPairThresholds[pair(i, j)]; // const r = __ElementPairThresholds[pair(i, j)];
if (r === void 0) return -1; // if (r === void 0) return -1;
return r; // return r;
} // }
function threshold(i: number) { // function threshold(i: number) {
if (i < 0) return __DefaultBondingRadius; // if (i < 0) return __DefaultBondingRadius;
const r = __ElementBondThresholds[i]; // const r = __ElementBondThresholds[i];
if (r === void 0) return __DefaultBondingRadius; // if (r === void 0) return __DefaultBondingRadius;
return r; // return r;
} // }
const H_ID = __ElementIndex['H']!; // const H_ID = __ElementIndex['H']!;
function isHydrogen(i: number) { // function isHydrogen(i: number) {
return i === H_ID; // return i === H_ID;
} // }
function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number) { // function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number) {
const bucketSizes = new Int32Array(atomCount); // const bucketSizes = new Int32Array(atomCount);
const bucketOffsets = new Int32Array(atomCount + 1) as any as number[]; // const bucketOffsets = new Int32Array(atomCount + 1) as any as number[];
const bucketFill = new Int32Array(atomCount); // const bucketFill = new Int32Array(atomCount);
for (const i of atomA) bucketSizes[i]++; // for (const i of atomA) bucketSizes[i]++;
for (const i of atomB) bucketSizes[i]++; // for (const i of atomB) bucketSizes[i]++;
let offset = 0; // let offset = 0;
for (let i = 0; i < atomCount; i++) { // for (let i = 0; i < atomCount; i++) {
bucketOffsets[i] = offset; // bucketOffsets[i] = offset;
offset += bucketSizes[i]; // offset += bucketSizes[i];
} // }
bucketOffsets[atomCount] = offset; // bucketOffsets[atomCount] = offset;
const neighbor = new Int32Array(offset) as any as number[]; // const neighbor = new Int32Array(offset) as any as number[];
const flags = new Uint16Array(offset) as any as number[]; // const flags = new Uint16Array(offset) as any as number[];
const order = new Int8Array(offset) as any as number[]; // const order = new Int8Array(offset) as any as number[];
for (let i = 0, _i = atomA.length; i < _i; i++) { // for (let i = 0, _i = atomA.length; i < _i; i++) {
const a = atomA[i], b = atomB[i], f = _flags[i], o = _order[i]; // const a = atomA[i], b = atomB[i], f = _flags[i], o = _order[i];
const oa = bucketOffsets[a] + bucketFill[a]; // const oa = bucketOffsets[a] + bucketFill[a];
const ob = bucketOffsets[b] + bucketFill[b]; // const ob = bucketOffsets[b] + bucketFill[b];
neighbor[oa] = b; // neighbor[oa] = b;
flags[oa] = f; // flags[oa] = f;
order[oa] = o; // order[oa] = o;
bucketFill[a]++; // bucketFill[a]++;
neighbor[ob] = a; // neighbor[ob] = a;
flags[ob] = f; // flags[ob] = f;
order[ob] = o; // order[ob] = o;
bucketFill[b]++; // bucketFill[b]++;
} // }
return { // return {
offsets: bucketOffsets, // offsets: bucketOffsets,
neighbor, // neighbor,
flags, // flags,
order // order
}; // };
} // }
function _computeBonds(model: Model, params: BondComputationParameters): Bonds { // function _computeBonds(model: Model, params: BondComputationParameters): Bonds {
const MAX_RADIUS = 3; // const MAX_RADIUS = 3;
const { x, y, z } = model.conformation; // const { x, y, z } = model.conformation;
const { atomCount } = model; // const { atomCount } = model;
const { residueKey } = model.hierarchy // const { residueKey } = model.hierarchy
const { type_symbol, label_atom_id, label_alt_id } = model.hierarchy.atoms; // const { type_symbol, label_atom_id, label_alt_id } = model.hierarchy.atoms;
const { label_comp_id } = model.hierarchy.residues; // const { label_comp_id } = model.hierarchy.residues;
const query3d = Model.spatialLookup(model).find(Mask.always(atomCount)); // const query3d = Model.spatialLookup(model).find(Mask.always(atomCount));
const structConn = model.sourceData.kind === 'mmCIF' ? StructConn.create(model) : void 0 // const structConn = model.sourceData.kind === 'mmCIF' ? StructConn.create(model) : void 0
const component = model.sourceData.kind === 'mmCIF' ? ComponentBondInfo.create(model) : void 0 // const component = model.sourceData.kind === 'mmCIF' ? ComponentBondInfo.create(model) : void 0
const atomA: number[] = []; // const atomA: number[] = [];
const atomB: number[] = []; // const atomB: number[] = [];
const flags: number[] = []; // const flags: number[] = [];
const order: number[] = []; // const order: number[] = [];
let lastResidue = -1; // let lastResidue = -1;
let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0; // let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
for (let aI = 0; aI < atomCount; aI++) { // for (let aI = 0; aI < atomCount; aI++) {
const raI = residueKey.value(aI); // const raI = residueKey.value(aI);
// const rowA = dataIndex[aI]; // TODO // // const rowA = dataIndex[aI]; // TODO
const rowA = aI; // const rowA = aI;
if (!params.forceCompute && raI !== lastResidue) { // if (!params.forceCompute && raI !== lastResidue) {
const resn = label_comp_id.value(rowA)!; // const resn = label_comp_id.value(rowA)!;
if (!!component && component.entries.has(resn)) { // if (!!component && component.entries.has(resn)) {
componentMap = component.entries.get(resn)!.map; // componentMap = component.entries.get(resn)!.map;
} else { // } else {
componentMap = void 0; // componentMap = void 0;
} // }
} // }
lastResidue = raI; // lastResidue = raI;
const componentPairs = componentMap ? componentMap.get(label_atom_id.value(rowA)) : void 0; // const componentPairs = componentMap ? componentMap.get(label_atom_id.value(rowA)) : void 0;
const aeI = idx(type_symbol.value(rowA)!); // const aeI = idx(type_symbol.value(rowA)!);
const { indices, count, squaredDistances } = query3d(x[aI], y[aI], z[aI], MAX_RADIUS); // const { indices, count, squaredDistances } = query3d(x[aI], y[aI], z[aI], MAX_RADIUS);
const isHa = isHydrogen(aeI); // const isHa = isHydrogen(aeI);
const thresholdA = threshold(aeI); // const thresholdA = threshold(aeI);
const altA = label_alt_id.value(rowA); // const altA = label_alt_id.value(rowA);
const metalA = MetalsSet.has(aeI); // const metalA = MetalsSet.has(aeI);
const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); // const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
for (let ni = 0; ni < count; ni++) { // for (let ni = 0; ni < count; ni++) {
const bI = indices[ni]; // const bI = indices[ni];
if (bI <= aI) continue; // if (bI <= aI) continue;
// const rowB = dataIndex[bI]; // TODO // // const rowB = dataIndex[bI]; // TODO
const rowB = bI; // const rowB = bI;
const altB = label_alt_id.value(rowB); // const altB = label_alt_id.value(rowB);
if (altA && altB && altA !== altB) continue; // if (altA && altB && altA !== altB) continue;
const beI = idx(type_symbol.value(rowB)!); // const beI = idx(type_symbol.value(rowB)!);
const isMetal = metalA || MetalsSet.has(beI); // const isMetal = metalA || MetalsSet.has(beI);
const rbI = residueKey.value(bI); // const rbI = residueKey.value(bI);
// handle "component dictionary" bonds. // // handle "component dictionary" bonds.
if (raI === rbI && componentPairs) { // if (raI === rbI && componentPairs) {
const e = componentPairs.get(label_atom_id.value(rowB)!); // const e = componentPairs.get(label_atom_id.value(rowB)!);
if (e) { // if (e) {
atomA[atomA.length] = aI; // atomA[atomA.length] = aI;
atomB[atomB.length] = bI; // atomB[atomB.length] = bI;
order[order.length] = e.order; // order[order.length] = e.order;
let flag = e.flags; // let flag = e.flags;
if (isMetal) { // if (isMetal) {
if (flag | BondType.Flag.Covalent) flag ^= BondType.Flag.Covalent; // if (flag | BondType.Flag.Covalent) flag ^= BondType.Flag.Covalent;
flag |= BondType.Flag.MetallicCoordination; // flag |= BondType.Flag.MetallicCoordination;
} // }
flags[flags.length] = flag; // flags[flags.length] = flag;
} // }
continue; // continue;
} // }
const isHb = isHydrogen(beI); // const isHb = isHydrogen(beI);
if (isHa && isHb) continue; // if (isHa && isHb) continue;
const dist = Math.sqrt(squaredDistances[ni]); // const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue; // if (dist === 0) continue;
// handle "struct conn" bonds. // // handle "struct conn" bonds.
if (structConnEntries && structConnEntries.length) { // if (structConnEntries && structConnEntries.length) {
let added = false; // let added = false;
for (const se of structConnEntries) { // for (const se of structConnEntries) {
for (const p of se.partners) { // for (const p of se.partners) {
if (p.atomIndex === bI) { // if (p.atomIndex === bI) {
atomA[atomA.length] = aI; // atomA[atomA.length] = aI;
atomB[atomB.length] = bI; // atomB[atomB.length] = bI;
flags[flags.length] = se.flags; // flags[flags.length] = se.flags;
order[order.length] = se.order; // order[order.length] = se.order;
added = true; // added = true;
break; // break;
} // }
} // }
if (added) break; // if (added) break;
} // }
if (added) continue; // if (added) continue;
} // }
if (isHa || isHb) { // if (isHa || isHb) {
if (dist < params.maxHbondLength) { // if (dist < params.maxHbondLength) {
atomA[atomA.length] = aI; // atomA[atomA.length] = aI;
atomB[atomB.length] = bI; // atomB[atomB.length] = bI;
order[order.length] = 1; // order[order.length] = 1;
flags[flags.length] = BondType.Flag.Covalent | BondType.Flag.Computed; // TODO: check if correct // flags[flags.length] = BondType.Flag.Covalent | BondType.Flag.Computed; // TODO: check if correct
} // }
continue; // continue;
} // }
const thresholdAB = pairThreshold(aeI, beI); // const thresholdAB = pairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0 // const pairingThreshold = thresholdAB > 0
? thresholdAB // ? thresholdAB
: beI < 0 ? thresholdA : Math.max(thresholdA, threshold(beI)); // : beI < 0 ? thresholdA : Math.max(thresholdA, threshold(beI));
if (dist <= pairingThreshold) { // if (dist <= pairingThreshold) {
atomA[atomA.length] = aI; // atomA[atomA.length] = aI;
atomB[atomB.length] = bI; // atomB[atomB.length] = bI;
order[order.length] = 1; // order[order.length] = 1;
flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed; // flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed;
} // }
} // }
} // }
const bonds = computePerAtomBonds(atomA, atomB, order, flags, atomCount); // const bonds = computePerAtomBonds(atomA, atomB, order, flags, atomCount);
return { // return {
offset: bonds.offsets, // offset: bonds.offsets,
neighbor: bonds.neighbor, // neighbor: bonds.neighbor,
flags: bonds.flags, // flags: bonds.flags,
order: bonds.order, // order: bonds.order,
count: atomA.length // count: atomA.length
}; // };
} // }
export default function computeBonds(model: Model, params?: Partial<BondComputationParameters>) { // export default function computeBonds(model: Model, params?: Partial<BondComputationParameters>) {
return _computeBonds(model, { // return _computeBonds(model, {
maxHbondLength: (params && params.maxHbondLength) || 1.15, // maxHbondLength: (params && params.maxHbondLength) || 1.15,
forceCompute: !!(params && params.forceCompute), // forceCompute: !!(params && params.forceCompute),
}); // });
} // }
\ No newline at end of file \ No newline at end of file
...@@ -5,12 +5,15 @@ ...@@ -5,12 +5,15 @@
*/ */
import { OrderedSet } from 'mol-data/int' import { OrderedSet } from 'mol-data/int'
import { Lookup3D, GridLookup3D } from 'mol-math/geometry';
import Unit from '../unit' import Unit from '../unit'
interface ElementGroup { interface ElementGroup {
elements: OrderedSet, elements: OrderedSet,
// Unique identifier of the group, usable as partial key for various "caches". // Unique identifier of the group, usable as partial key for various "caches".
key: number key: number,
__lookup3d__?: Lookup3D
} }
namespace ElementGroup { namespace ElementGroup {
...@@ -61,6 +64,17 @@ namespace ElementGroup { ...@@ -61,6 +64,17 @@ namespace ElementGroup {
return createNew(set); return createNew(set);
} }
export function getLookup3d(unit: Unit, group: ElementGroup) {
if (group.__lookup3d__) return group.__lookup3d__;
if (Unit.isAtomic(unit)) {
const { x, y, z } = unit.model.conformation;
group.__lookup3d__ = GridLookup3D({ x, y, z, indices: group.elements });
return group.__lookup3d__;
}
throw 'not implemented';
}
let _id = 0; let _id = 0;
function nextKey() { function nextKey() {
const ret = _id; const ret = _id;
... ...
......
...@@ -6,18 +6,21 @@ ...@@ -6,18 +6,21 @@
import { OrderedSet, Iterator } from 'mol-data/int' import { OrderedSet, Iterator } from 'mol-data/int'
import { UniqueArray } from 'mol-data/generic' import { UniqueArray } from 'mol-data/generic'
import SymmetryOperator from 'mol-math/geometry/symmetry-operator' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
import { Model, Format } from '../model' import { Model, Format } from '../model'
import Unit from './unit' import Unit from './unit'
import ElementSet from './element/set' import ElementSet from './element/set'
import ElementGroup from './element/group' import ElementGroup from './element/group'
import Element from './element' import Element from './element'
import { StructureLookup3D } from './util/lookup3d';
// A structure is a pair of "units" and an element set. // A structure is a pair of "units" and an element set.
// Each unit contains the data and transformation of its corresponding elements. // Each unit contains the data and transformation of its corresponding elements.
interface Structure { interface Structure {
readonly units: ReadonlyArray<Unit>, readonly units: ReadonlyArray<Unit>,
readonly elements: ElementSet readonly elements: ElementSet,
__lookup3d__?: StructureLookup3D
} }
namespace Structure { namespace Structure {
...@@ -82,6 +85,16 @@ namespace Structure { ...@@ -82,6 +85,16 @@ namespace Structure {
return arr.array; return arr.array;
} }
export function getLookup3d(s: Structure) {
if (s.__lookup3d__) return s.__lookup3d__;
s.__lookup3d__ = StructureLookup3D.create(s);
return s.__lookup3d__;
}
export function getBoundary(s: Structure) {
return getLookup3d(s).boundary;
}
// TODO: "lift" atom set operators? // TODO: "lift" atom set operators?
// TODO: "diff" // TODO: "diff"
} }
... ...
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
*/ */
import SymmetryOperator from 'mol-math/geometry/symmetry-operator' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
import ElementGroup from './element/group' import ElementGroup from './element/group'
import { Model } from '../model' import { Model } from '../model'
... ...
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Structure from '../structure'
import ElementSet from '../element/set'
import { ElementGroup } from '../../structure'
import { Box3D, Sphere3D } from 'mol-math/geometry';
import { Vec3 } from 'mol-math/linear-algebra';
function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D } {
const min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
const max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
const { units, elements } = s;
let cx = 0, cy = 0, cz = 0;
let radiusSq = 0;
let size = 0;
for (let i = 0, _i = ElementSet.unitCount(elements); i < _i; i++) {
const group = ElementSet.unitGetByIndex(elements, i);
const { x, y, z } = units[ElementSet.unitGetId(elements, i)];
size += ElementGroup.size(group);
for (let j = 0, _j = ElementGroup.size(group); j < _j; j++) {
const e = ElementGroup.getAt(group, j);
const xx = x(e), yy = y(e), zz = z(e);
min[0] = Math.min(xx, min[0]);
min[1] = Math.min(yy, min[1]);
min[2] = Math.min(zz, min[2]);
max[0] = Math.max(xx, max[0]);
max[1] = Math.max(yy, max[1]);
max[2] = Math.max(zz, max[2]);
cx += xx;
cy += yy;
cz += zz;
}
}
if (size > 0) {
cx /= size;
cy /= size;
cz /= size;
}
for (let i = 0, _i = ElementSet.unitCount(elements); i < _i; i++) {
const group = ElementSet.unitGetByIndex(elements, i);
const { x, y, z } = units[ElementSet.unitGetId(elements, i)];
size += ElementGroup.size(group);
for (let j = 0, _j = ElementGroup.size(group); j < _j; j++) {
const e = ElementGroup.getAt(group, j);
const dx = x(e) - cx, dy = y(e) - cy, dz = z(e) - cz;
const d = dx * dx + dy * dy + dz * dz;
if (d > radiusSq) radiusSq = d;
}
}
return {
box: { min: Vec3.ofArray(min), max: Vec3.ofArray(max) },
sphere: { center: Vec3.create(cx, cy, cz), radius: Math.sqrt(radiusSq) }
};
}
export { computeStructureBoundary }
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Structure from '../structure'
import Element from '../element'
import { Lookup3D, GridLookup3D, Result, Box3D, Sphere3D } from 'mol-math/geometry';
import { ElementGroup, ElementSet } from '../../structure';
import { Vec3 } from 'mol-math/linear-algebra';
import { OrderedSet } from 'mol-data/int';
import { computeStructureBoundary } from './boundary';
interface StructureLookup3D extends Lookup3D<Element> {}
namespace StructureLookup3D {
class Impl implements StructureLookup3D {
private unitLookup: Lookup3D;
private result = Result.create<Element>();
private pivot = Vec3.zero();
find(x: number, y: number, z: number, radius: number): Result<Element> {
Result.reset(this.result);
const { units, elements } = this.structure;
const closeUnits = this.unitLookup.find(x, y, z, radius);
if (closeUnits.count === 0) return this.result;
for (let t = 0, _t = closeUnits.count; t < _t; t++) {
const i = closeUnits.indices[t];
const unitId = ElementSet.unitGetId(elements, i);
const group = ElementSet.unitGetByIndex(elements, i);
const unit = units[unitId];
Vec3.set(this.pivot, x, y, z);
if (!unit.operator.isIdentity) {
Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
}
const groupLookup = ElementGroup.getLookup3d(unit, group);
const groupResult = groupLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
for (let j = 0, _j = groupResult.count; j < _j; j++) {
Result.add(this.result, Element.create(unitId, groupResult.indices[j]), groupResult.squaredDistances[j]);
}
}
return this.result;
}
check(x: number, y: number, z: number, radius: number): boolean {
const { units, elements } = this.structure;
const closeUnits = this.unitLookup.find(x, y, z, radius);
if (closeUnits.count === 0) return false;
for (let t = 0, _t = closeUnits.count; t < _t; t++) {
const i = closeUnits.indices[t];
const unitId = ElementSet.unitGetId(elements, i);
const group = ElementSet.unitGetByIndex(elements, i);
const unit = units[unitId];
Vec3.set(this.pivot, x, y, z);
if (!unit.operator.isIdentity) {
Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
}
const groupLookup = ElementGroup.getLookup3d(unit, group);
if (groupLookup.check(this.pivot[0], this.pivot[1], this.pivot[2], radius)) return true;
}
return false;
}
boundary: { box: Box3D; sphere: Sphere3D; };
constructor(private structure: Structure) {
const { units, elements } = structure;
const unitCount = ElementSet.unitCount(elements);
const xs = new Float32Array(unitCount);
const ys = new Float32Array(unitCount);
const zs = new Float32Array(unitCount);
const radius = new Float32Array(unitCount);
const center = Vec3.zero();
for (let i = 0; i < unitCount; i++) {
const group = ElementSet.unitGetByIndex(elements, i);
const unit = units[ElementSet.unitGetId(elements, i)];
const lookup = ElementGroup.getLookup3d(unit, group);
const s = lookup.boundary.sphere;
Vec3.transformMat4(center, s.center, unit.operator.matrix);
xs[i] = center[0];
ys[i] = center[1];
zs[i] = center[2];
radius[i] = s.radius;
}
this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
this.boundary = computeStructureBoundary(structure);
}
}
export function create(s: Structure): StructureLookup3D {
return new Impl(s);
}
}
export { StructureLookup3D }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment