UNPKG

molstar

Version:

A comprehensive macromolecular library.

278 lines (277 loc) 10.8 kB
/** * Copyright (c) 2018-2022 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 { Vec3, Mat4, EPSILON } from '../../linear-algebra'; import { OrderedSet } from '../../../mol-data/int'; import { Axes3D } from './axes3d'; import { PrincipalAxes } from '../../linear-algebra/matrix/principal-axes'; function Sphere3D() { return Sphere3D.zero(); } (function (Sphere3D) { function hasExtrema(sphere) { return sphere.extrema !== undefined; } Sphere3D.hasExtrema = hasExtrema; function create(center, radius) { return { center, radius }; } Sphere3D.create = create; function zero() { return { center: Vec3(), radius: 0 }; } Sphere3D.zero = zero; function clone(a) { const out = create(Vec3.clone(a.center), a.radius); if (hasExtrema(a)) out.extrema = a.extrema.map(e => Vec3.clone(e)); return out; } Sphere3D.clone = clone; function set(out, center, radius) { Vec3.copy(out.center, center); out.radius = radius; return out; } Sphere3D.set = set; function copy(out, a) { Vec3.copy(out.center, a.center); out.radius = a.radius; if (hasExtrema(a)) setExtrema(out, a.extrema.map(e => Vec3.clone(e))); return out; } Sphere3D.copy = copy; /** Note that `extrema` must not be reused elsewhere */ function setExtrema(out, extrema) { if (out.extrema !== undefined) { out.extrema.length = 0; out.extrema.push(...extrema); } else { out.extrema = extrema; } return out; } Sphere3D.setExtrema = setExtrema; function computeBounding(data) { 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) }; } Sphere3D.computeBounding = computeBounding; /** Transform sphere with a Mat4 */ function transform(out, sphere, m) { Vec3.transformMat4(out.center, sphere.center, m); out.radius = sphere.radius * Mat4.getMaxScaleOnAxis(m); if (hasExtrema(sphere)) { setExtrema(out, sphere.extrema.map(e => Vec3.transformMat4(Vec3(), e, m))); } return out; } Sphere3D.transform = transform; /** Translate sphere by Vec3 */ function translate(out, sphere, v) { Vec3.add(out.center, sphere.center, v); if (hasExtrema(sphere)) { setExtrema(out, sphere.extrema.map(e => Vec3.add(Vec3(), e, v))); } return out; } Sphere3D.translate = translate; function toArray(s, out, offset) { Vec3.toArray(s.center, out, offset); out[offset + 3] = s.radius; return out; } Sphere3D.toArray = toArray; function fromArray(out, array, offset) { Vec3.fromArray(out.center, array, offset); out.radius = array[offset + 3]; return out; } Sphere3D.fromArray = fromArray; function fromBox3D(out, box) { Vec3.scale(out.center, Vec3.add(out.center, box.max, box.min), 0.5); out.radius = Vec3.distance(out.center, box.max); Sphere3D.setExtrema(out, [ Vec3.create(box.min[0], box.min[1], box.min[2]), Vec3.create(box.max[0], box.max[1], box.max[2]), Vec3.create(box.max[0], box.min[1], box.min[2]), Vec3.create(box.min[0], box.max[1], box.max[2]), Vec3.create(box.min[0], box.min[1], box.max[2]), Vec3.create(box.max[0], box.min[1], box.max[2]), Vec3.create(box.max[0], box.max[1], box.min[2]), Vec3.create(box.min[0], box.max[1], box.min[2]), ]); return out; } Sphere3D.fromBox3D = fromBox3D; function fromAxes3D(out, axes) { Vec3.copy(out.center, axes.origin); out.radius = Math.max(Vec3.magnitude(axes.dirA), Vec3.magnitude(axes.dirB), Vec3.magnitude(axes.dirC)); return out; } Sphere3D.fromAxes3D = fromAxes3D; const tmpCenter = Vec3(); /** Get a tight sphere around a transformed box */ function fromDimensionsAndTransform(out, dimensions, transform) { const [x, y, z] = dimensions; const cpA = Vec3.create(0, 0, 0); Vec3.transformMat4(cpA, cpA, transform); const cpB = Vec3.create(x, y, z); Vec3.transformMat4(cpB, cpB, transform); const cpC = Vec3.create(x, 0, 0); Vec3.transformMat4(cpC, cpC, transform); const cpD = Vec3.create(0, y, z); Vec3.transformMat4(cpD, cpD, transform); const cpE = Vec3.create(0, 0, z); Vec3.transformMat4(cpE, cpE, transform); const cpF = Vec3.create(x, 0, z); Vec3.transformMat4(cpF, cpF, transform); const cpG = Vec3.create(x, y, 0); Vec3.transformMat4(cpG, cpG, transform); const cpH = Vec3.create(0, y, 0); Vec3.transformMat4(cpH, cpH, transform); Vec3.add(tmpCenter, cpA, cpB); Vec3.scale(tmpCenter, tmpCenter, 0.5); const d = Math.max(Vec3.distance(cpA, cpB), Vec3.distance(cpC, cpD)); Sphere3D.set(out, tmpCenter, d / 2); Sphere3D.setExtrema(out, [cpA, cpB, cpC, cpD, cpE, cpF, cpG, cpH]); return out; } Sphere3D.fromDimensionsAndTransform = fromDimensionsAndTransform; const tmpAddVec3 = Vec3(); function addVec3(out, s, v) { const d = Vec3.distance(s.center, v); if (d < s.radius) return Sphere3D.copy(out, s); Vec3.sub(tmpAddVec3, s.center, v); Vec3.sub(tmpAddVec3, s.center, tmpAddVec3); Vec3.setMagnitude(tmpAddVec3, tmpAddVec3, s.radius); Vec3.scale(out.center, Vec3.add(tmpAddVec3, tmpAddVec3, v), 0.5); out.radius = Vec3.distance(out.center, v); return out; } Sphere3D.addVec3 = addVec3; /** Expand sphere radius by another sphere */ function expandBySphere(out, sphere, by) { Vec3.copy(out.center, sphere.center); out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius); if (hasExtrema(sphere) && hasExtrema(by)) { setExtrema(out, [ ...sphere.extrema.map(e => Vec3.clone(e)), ...by.extrema.map(e => Vec3.clone(e)) ]); } return out; } Sphere3D.expandBySphere = expandBySphere; const tmpDir = Vec3(); /** Expand sphere radius by delta */ function expand(out, sphere, delta) { var _a, _b; Vec3.copy(out.center, sphere.center); out.radius = sphere.radius + delta; if (sphere.radius < 1e-12 || ((_b = (_a = sphere.extrema) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) <= 1) { out.extrema = void 0; return out; } if (hasExtrema(sphere)) { const positions = new Float32Array(sphere.extrema.length * 3); for (let i = 0; i < sphere.extrema.length; i++) { Vec3.toArray(sphere.extrema[i], positions, i * 3); } const axes = PrincipalAxes.calculateMomentsAxes(positions); Axes3D.scale(axes, Axes3D.normalize(axes, axes), delta); setExtrema(out, sphere.extrema.map(e => { Vec3.normalize(tmpDir, Vec3.sub(tmpDir, e, sphere.center)); const o = Vec3.clone(e); const sA = Vec3.dot(tmpDir, axes.dirA) < 0 ? -1 : 1; Vec3.scaleAndAdd(o, o, axes.dirA, sA); const sB = Vec3.dot(tmpDir, axes.dirB) < 0 ? -1 : 1; Vec3.scaleAndAdd(o, o, axes.dirB, sB); const sC = Vec3.dot(tmpDir, axes.dirC) < 0 ? -1 : 1; Vec3.scaleAndAdd(o, o, axes.dirC, sC); if (Vec3.distance(out.center, o) > out.radius) { if (sphere.extrema.length >= 14) { // 14 extrema with coarse boundary helper Vec3.normalize(tmpDir, Vec3.sub(tmpDir, o, sphere.center)); } Vec3.scaleAndAdd(o, out.center, tmpDir, out.radius); } return o; })); } return out; } Sphere3D.expand = expand; /** * Returns whether or not the spheres have exactly the same center and radius (when compared with ===) */ function exactEquals(a, b) { return a.radius === b.radius && Vec3.exactEquals(a.center, b.center); } Sphere3D.exactEquals = exactEquals; /** * Returns whether or not the spheres have approximately the same center and radius. */ function equals(a, b) { const ar = a.radius; const br = b.radius; return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) && Vec3.equals(a.center, b.center)); } Sphere3D.equals = equals; /** * Check if `a` includes `b`, use `extrema` of `b` when available */ function includes(a, b) { if (hasExtrema(b)) { for (const e of b.extrema) { if (Vec3.distance(a.center, e) > a.radius) return false; } return true; } else { return Vec3.distance(a.center, b.center) + b.radius <= a.radius; } } Sphere3D.includes = includes; /** Check if `a` and `b` are overlapping */ function overlaps(a, b) { return Vec3.distance(a.center, b.center) <= a.radius + b.radius; } Sphere3D.overlaps = overlaps; /** Get the signed distance of `a` and `b` */ function distance(a, b) { return Vec3.distance(a.center, b.center) - a.radius + b.radius; } Sphere3D.distance = distance; /** Get the distance of v from sphere. If negative, v is inside sphere */ function distanceToVec(sphere, v) { const { center, radius } = sphere; return Vec3.distance(v, center) - radius; } Sphere3D.distanceToVec = distanceToVec; })(Sphere3D || (Sphere3D = {})); export { Sphere3D };