diff --git a/package-lock.json b/package-lock.json index edafd4d1f9c93c4ded380346f45198682ee709a0..efc391799b487e8249308c936f2976f80c7efd13 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index eede657509d5f0454525f990cd395429c99582ee..531f60c9193d20ca9a1fff0d9c0117874c172c19 100644 --- a/package.json +++ b/package.json @@ -82,9 +82,9 @@ "@types/compression": "0.0.36", "@types/express": "^4.16.0", "@types/jest": "^23.3.9", - "@types/node": "^10.12.1", - "@types/node-fetch": "^2.1.2", - "@types/react": "^16.4.18", + "@types/node": "^10.12.9", + "@types/node-fetch": "^2.1.4", + "@types/react": "^16.7.6", "@types/react-dom": "^16.0.9", "@types/webgl2": "0.0.4", "benchmark": "^2.1.4", @@ -93,24 +93,25 @@ "css-loader": "^1.0.1", "extra-watch-webpack-plugin": "^1.0.3", "file-loader": "^2.0.0", + "glslify": "^7.0.0", "glslify-import": "^3.1.0", "glslify-loader": "^1.0.2", - "graphql-code-generator": "^0.13.0", - "graphql-codegen-typescript-template": "^0.13.0", + "graphql-code-generator": "^0.14.1", + "graphql-codegen-typescript-template": "^0.14.1", "jest": "^23.6.0", "jest-raw-loader": "^1.0.1", "mini-css-extract-plugin": "^0.4.4", - "node-sass": "^4.9.4", + "node-sass": "^4.10.0", "raw-loader": "^0.5.1", "resolve-url-loader": "^3.0.0", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "ts-jest": "^23.10.4", "tslint": "^5.11.0", - "typescript": "^3.1.4", + "typescript": "^3.1.6", "uglify-js": "^3.4.9", "util.promisify": "^1.0.0", - "webpack": "^4.23.1", + "webpack": "^4.26.0", "webpack-cli": "^3.1.2" }, "dependencies": { @@ -120,9 +121,9 @@ "graphql": "^14.0.2", "graphql-request": "^1.8.2", "immutable": "^3.8.2", - "node-fetch": "^2.2.0", - "react": "^16.6.0", - "react-dom": "^16.6.0", + "node-fetch": "^2.3.0", + "react": "^16.6.3", + "react-dom": "^16.6.3", "rxjs": "^6.3.3" } } diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index af49e212481b58f9f8469555c263bd59115e0c24..4bc8f20eed02909f432a3042aee54095624e80d5 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -124,7 +124,7 @@ export namespace DirectVolume { } } - export function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) { + export function updateValues(values: DirectVolumeValues, directVolume: DirectVolume, props: PD.Values<Params>) { const vertices = new Float32Array(values.aPosition.ref.value) transformPositionArray(values.uTransform.ref.value, vertices, 0, vertices.length / 3) const boundingSphere = calculateBoundingSphere( diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index 5e2764ce4fb8e6974775dd7d393b0eb115eaeed0..9f56f34b7b3775bb69f39c72d2cc444750652083 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/src/mol-geo/geometry/lines/lines.ts @@ -137,14 +137,14 @@ export namespace Lines { } } - export function updateValues(values: LinesValues, props: PD.Values<Params>) { + export function updateValues(values: LinesValues, lines: Lines, props: PD.Values<Params>) { const boundingSphere = Sphere3D.addSphere( calculateBoundingSphere( - values.aStart.ref.value, Math.floor(values.aStart.ref.value.length / 3), + values.aStart.ref.value, lines.lineCount, values.aTransform.ref.value, values.instanceCount.ref.value ), calculateBoundingSphere( - values.aEnd.ref.value, Math.floor(values.aEnd.ref.value.length / 3), + values.aEnd.ref.value, lines.lineCount, values.aTransform.ref.value, values.instanceCount.ref.value ), ) diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index 6d4b450d479f5acff8c5a717ba0ee71603c3a8e7..1b2f29df6a53e1f333d62bcb4d8e4b10c4f1bf49 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -408,9 +408,9 @@ export namespace Mesh { } } - export function updateValues(values: MeshValues, props: PD.Values<Params>) { + export function updateValues(values: MeshValues, mesh: Mesh, props: PD.Values<Params>) { const boundingSphere = calculateBoundingSphere( - values.aPosition.ref.value, Math.floor(values.aPosition.ref.value.length / 3), + values.aPosition.ref.value, mesh.vertexCount, values.aTransform.ref.value, values.instanceCount.ref.value ) if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) { diff --git a/src/mol-geo/geometry/points/points.ts b/src/mol-geo/geometry/points/points.ts index 4d443427c9d2028e236acf9826816a3abd35c086..d6c28569add36be0ab6be1a6ad67c3a70ea44d04 100644 --- a/src/mol-geo/geometry/points/points.ts +++ b/src/mol-geo/geometry/points/points.ts @@ -92,9 +92,9 @@ export namespace Points { } } - export function updateValues(values: PointsValues, props: PD.Values<Params>) { + export function updateValues(values: PointsValues, points: Points, props: PD.Values<Params>) { const boundingSphere = calculateBoundingSphere( - values.aPosition.ref.value, Math.floor(values.aPosition.ref.value.length / 3), + values.aPosition.ref.value, points.pointCount, values.aTransform.ref.value, values.instanceCount.ref.value ) if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) { diff --git a/src/mol-gl/shader/chunks/apply-marker-color.glsl b/src/mol-gl/shader/chunks/apply-marker-color.glsl index cc65b55255af0f62e342933641ebf6ab60c05e84..327ba74bb881a097df42ef173ec4bc1db168dc3c 100644 --- a/src/mol-gl/shader/chunks/apply-marker-color.glsl +++ b/src/mol-gl/shader/chunks/apply-marker-color.glsl @@ -1,8 +1,8 @@ // only mark elements with an alpha above the picking threshold if (uAlpha >= uPickingAlphaThreshold) { - float marker = vMarker * 255.0; + float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win if (marker > 0.1) { - if (mod(marker, 2.0) > 0.1) { + if (intMod(marker, 2.0) > 0.1) { gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3); } else { gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3); diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl b/src/mol-gl/shader/chunks/common-frag-params.glsl index dc62dbe9bec92fae2018665d136b5f10e624f71b..9048de9fd32056f16cbd386f9683cccf9e09ac46 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl @@ -14,4 +14,6 @@ uniform vec3 uFogColor; uniform float uAlpha; uniform float uPickingAlphaThreshold; -uniform int uPickable; \ No newline at end of file +uniform int uPickable; + +#pragma glslify: import('./common.glsl') \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl b/src/mol-gl/shader/chunks/common-vert-params.glsl index 5028d0abb23450b2c7126a1ed256a6ebfeff95d6..e2b15799cbbdc638f2796e680838ffdcd24a566f 100644 --- a/src/mol-gl/shader/chunks/common-vert-params.glsl +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl @@ -10,4 +10,5 @@ varying float vMarker; varying vec3 vViewPosition; -#pragma glslify: readFromTexture = require(../utils/read-from-texture.glsl) \ No newline at end of file +#pragma glslify: import('./common.glsl') +#pragma glslify: readFromTexture = require('../utils/read-from-texture.glsl', intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common.glsl b/src/mol-gl/shader/chunks/common.glsl new file mode 100644 index 0000000000000000000000000000000000000000..cea7b801c7dbecfd57c393de02fed6023156ffbb --- /dev/null +++ b/src/mol-gl/shader/chunks/common.glsl @@ -0,0 +1,2 @@ +float intDiv(float a, float b) { return float(int(a) / int(b)); } +float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index d94f580286440e3c29ef0aaf57e391d8a289b5a7..47ccb7dd689daca83a62ebf63c36e2b8670f78f1 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/src/mol-gl/shader/direct-volume.frag @@ -45,11 +45,12 @@ uniform int uPickable; uniform sampler2D tColor; #endif -#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl) +#pragma glslify: import('./chunks/common.glsl') +#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify #pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl) #pragma glslify: decodeIdRGB = require(./utils/decode-id-rgb.glsl) -#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl) -#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl) +#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify +#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify // uniform vec3 uLightPosition; uniform vec3 uLightColor; diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag index ce7bacf7acdf79d87af242c8d8e20379041f83dd..137c2189100d4c1ba4c070e189cc10de0f0a5dbe 100644 --- a/src/mol-gl/shader/gaussian-density.frag +++ b/src/mol-gl/shader/gaussian-density.frag @@ -21,8 +21,9 @@ varying float vRadius; varying float vGroup; #endif +#pragma glslify: import('./chunks/common.glsl') #pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl) -#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl) +#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify uniform vec3 uBboxSize; uniform vec3 uBboxMin; diff --git a/src/mol-gl/shader/utils/encode-float-rgb.glsl b/src/mol-gl/shader/utils/encode-float-rgb.glsl index 1cecbf759e9efc95c259c9e2389c052b6a627c59..07183663150d825630e63e886432bb0d9f2993c1 100644 --- a/src/mol-gl/shader/utils/encode-float-rgb.glsl +++ b/src/mol-gl/shader/utils/encode-float-rgb.glsl @@ -4,8 +4,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -// TODO use myMod and myDiv to fix issues with picking? - vec3 encodeFloatRGB(in float value) { value = clamp(value, 0.0, 16777216.0); vec3 c = vec3(0.0); diff --git a/src/mol-gl/shader/utils/read-from-texture.glsl b/src/mol-gl/shader/utils/read-from-texture.glsl index 7442b957dcd212f310fd8090102c618093f29a44..97843f8e2df610df67b6d19f463626351b144251 100644 --- a/src/mol-gl/shader/utils/read-from-texture.glsl +++ b/src/mol-gl/shader/utils/read-from-texture.glsl @@ -4,9 +4,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -float intDiv(float a, float b) { return float(int(a) / int(b)); } -float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } - vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) { float x = intMod(i, dim.x); float y = floor(intDiv(i, dim.x)); diff --git a/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl b/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl index d0d4f928722704f05356bb122005853f55cac4b1..0a9ceffa8c8798ced39e5e1b68758f1ae0c39b96 100644 --- a/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl +++ b/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl @@ -5,9 +5,6 @@ * @author Michael Krone <michael.krone@uni-tuebingen.de> */ -float intDiv(float a, float b) { return float(int(a) / int(b)); } -float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } - vec4 texture3dFrom2dLinear(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) { float zSlice0 = floor(pos.z * gridDim.z); float column0 = intMod(zSlice0 * gridDim.x, texDim.x) / gridDim.x; diff --git a/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl b/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl index 8c4fb0882c831709358714535f7c24be8637e4a9..b9570eaef378a8d3be78ab8aebb4e7d0570f2d86 100644 --- a/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl +++ b/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl @@ -5,9 +5,6 @@ * @author Michael Krone <michael.krone@uni-tuebingen.de> */ -float intDiv(float a, float b) { return float(int(a) / int(b)); } -float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } - vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) { float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x; diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 88d12323f27647b6921f9d827232f1746729c697..aa703b8be7fbb63321286a33ce8f25119bbbfd12 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -58,27 +58,27 @@ namespace SymmetryOperator { return create(second.name, matrix, second.hkl); } - export interface CoordinateMapper { (index: number, slot: Vec3): Vec3 } - export interface ArrayMapping { + export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 } + export interface ArrayMapping<T extends number> { readonly operator: SymmetryOperator, - readonly invariantPosition: CoordinateMapper, - readonly position: CoordinateMapper, - x(index: number): number, - y(index: number): number, - z(index: number): number, - r(index: number): number + readonly invariantPosition: CoordinateMapper<T>, + readonly position: CoordinateMapper<T>, + x(index: T): number, + y(index: T): number, + z(index: T): number, + r(index: T): number } export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> } - export function createMapping(operator: SymmetryOperator, coords: Coordinates, radius: ((index: number) => number) | undefined): ArrayMapping { + export function createMapping<T extends number>(operator: SymmetryOperator, coords: Coordinates, radius: ((index: T) => number) | undefined): ArrayMapping<T> { const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords); const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords); const { x, y, z } = createProjections(operator, coords); return { operator, invariantPosition, position, x, y, z, r: radius ? radius : _zeroRadius }; } - export function createCoordinateMapper(t: SymmetryOperator, coords: Coordinates): CoordinateMapper { + export function createCoordinateMapper<T extends number>(t: SymmetryOperator, coords: Coordinates): CoordinateMapper<T> { if (t.isIdentity) return identityPosition(coords); return generalPosition(t, coords); } @@ -145,7 +145,7 @@ function projectZ({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: Symm } } -function identityPosition({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper { +function identityPosition<T extends number>({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper<T> { return (i, s) => { s[0] = x[i]; s[1] = y[i]; @@ -154,10 +154,10 @@ function identityPosition({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOp } } -function generalPosition({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) { +function generalPosition<T extends number>({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) { if (isW1(m)) { // this should always be the case. - return (i: number, r: Vec3): Vec3 => { + return (i: T, r: Vec3): Vec3 => { const x = xs[i], y = ys[i], z = zs[i]; r[0] = m[0] * x + m[4] * y + m[8] * z + m[12]; r[1] = m[1] * x + m[5] * y + m[9] * z + m[13]; @@ -165,7 +165,7 @@ function generalPosition({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs return r; } } - return (i: number, r: Vec3): Vec3 => { + return (i: T, r: Vec3): Vec3 => { r[0] = xs[i]; r[1] = ys[i]; r[2] = zs[i]; diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 155a94777881da609608b77cea9d6a0620d21446..ca0cb94051ebfb58b04d716eb8ff204dec86f51c 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -24,10 +24,10 @@ import { getSequence } from './mmcif/sequence'; import { sortAtomSite } from './mmcif/sort'; import { StructConn } from './mmcif/bonds/struct_conn'; import { ChemicalComponent, ChemicalComponentMap } from '../properties/chemical-component'; -import { ComponentType, getMoleculeType } from '../types'; +import { ComponentType, getMoleculeType, MoleculeType } from '../types'; import mmCIF_Format = Format.mmCIF -import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap } from 'mol-model/structure/structure/carbohydrates/constants'; +import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from 'mol-model/structure/structure/carbohydrates/constants'; type AtomSite = mmCIF_Database['atom_site'] @@ -88,24 +88,6 @@ function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['m return { parentId, details }; } -function getAsymIdSerialMap(format: mmCIF_Format): ReadonlyMap<string, number> { - const data = format.data.struct_asym; - const map = new Map<string, number>(); - let serial = 0 - - const id = data.id - const count = data._rowCount - for (let i = 0; i < count; ++i) { - const _id = id.value(i) - if (!map.has(_id)) { - map.set(_id, serial) - serial += 1 - } - } - - return map; -} - function getChemicalComponentMap(format: mmCIF_Format): ChemicalComponentMap { const map = new Map<string, ChemicalComponent>(); const { id, type, name, pdbx_synonyms, formula, formula_weight } = format.data.chem_comp @@ -142,15 +124,22 @@ function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap } } } - return map } else { - return SaccharideCompIdMap + SaccharideCompIdMap.forEach((v, k) => map.set(k, v)) + const { id, type } = format.data.chem_comp + for (let i = 0, il = id.rowCount; i < il; ++i) { + const _id = id.value(i) + const _type = type.value(i) + if (!map.has(_id) && getMoleculeType(_type, _id) === MoleculeType.saccharide) { + map.set(_id, UnknownSaccharideComponent) + } + } } + return map } export interface FormatData { modifiedResidues: Model['properties']['modifiedResidues'] - asymIdSerialMap: Model['properties']['asymIdSerialMap'] chemicalComponentMap: Model['properties']['chemicalComponentMap'] saccharideComponentMap: Model['properties']['saccharideComponentMap'] } @@ -158,7 +147,6 @@ export interface FormatData { function getFormatData(format: mmCIF_Format): FormatData { return { modifiedResidues: getModifiedResidueNameMap(format), - asymIdSerialMap: getAsymIdSerialMap(format), chemicalComponentMap: getChemicalComponentMap(format), saccharideComponentMap: getSaccharideComponentMap(format) } diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 1eaad6e4554730175c54c80fb651b27cb71ac2e3..a9442fc948c6ffe8e1de0f19236ec750760e8a68 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -47,8 +47,6 @@ export interface Model extends Readonly<{ parentId: ReadonlyMap<string, string>, details: ReadonlyMap<string, string> }>, - /** maps asym id to unique serial number */ - readonly asymIdSerialMap: ReadonlyMap<string, number> /** maps residue name to `ChemicalComponent` data */ readonly chemicalComponentMap: ChemicalComponentMap /** maps residue name to `SaccharideComponent` data */ diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index fb753f14a01690e6141f25edf922be8d80294a26..e621cbc2245e2186703566ca7879a6766b112d5b 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -12,12 +12,11 @@ import { Vec3 } from 'mol-math/linear-algebra'; import PrincipalAxes from 'mol-math/linear-algebra/matrix/principal-axes'; import { fillSerial } from 'mol-util/array'; import { ResidueIndex, Model } from '../../model'; -import { ElementSymbol, MoleculeType } from '../../model/types'; -import { getAtomicMoleculeType, getPositionMatrix } from '../../util'; +import { ElementSymbol } from '../../model/types'; +import { getPositionMatrix } from '../../util'; import StructureElement from '../element'; import Structure from '../structure'; import Unit from '../unit'; -import { UnknownSaccharideComponent, SaccharideComponent } from './constants'; import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink, PartialCarbohydrateElement } from './data'; import { UnitRings, UnitRing } from '../unit/rings'; import { ElementIndex } from '../../model/indexing'; @@ -118,8 +117,8 @@ function filterFusedRings(unitRings: UnitRings, rings: UnitRings.Index[] | undef } } -function getSaccharideComp(compId: string, model: Model): SaccharideComponent { - return model.properties.saccharideComponentMap.get(compId) || UnknownSaccharideComponent +function getSaccharideComp(compId: string, model: Model) { + return model.properties.saccharideComponentMap.get(compId) } export function computeCarbohydrates(structure: Structure): Carbohydrates { @@ -167,9 +166,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const { index: residueIndex } = residueIt.move(); const saccharideComp = getSaccharideComp(label_comp_id.value(residueIndex), model) - if (saccharideComp === UnknownSaccharideComponent) { - if (getAtomicMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue - } + if (!saccharideComp) continue if (!sugarResidueMap) { sugarResidueMap = UnitRings.byFingerprintAndResidue(unit.rings, SugarRingFps); @@ -402,7 +399,7 @@ function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[ let k: string if (fromCarbohydrate) { k = terminalLinksKey(unit, anomericCarbon) - } else{ + } else { k = terminalLinksKey(elementUnit, elementUnit.elements[elementIndex]) } const e = terminalLinksMap.get(k) diff --git a/src/mol-model/structure/structure/carbohydrates/constants.ts b/src/mol-model/structure/structure/carbohydrates/constants.ts index 5c74ddfafa05adbe51aa26bc8030d7680a5fdc81..e8fe42818485783aaf69d986565968206a5938f2 100644 --- a/src/mol-model/structure/structure/carbohydrates/constants.ts +++ b/src/mol-model/structure/structure/carbohydrates/constants.ts @@ -205,7 +205,10 @@ const CommonSaccharideNames: { [k: string]: string[] } = { 'MLR', // via GlyFinder, tri-saccharide but homomer ], Man: ['MAN', 'BMA'], - Gal: ['GAL', 'GLA'], + Gal: [ + 'GAL', 'GLA', + 'GXL' // via PubChem + ], Gul: ['GUP', 'GL0'], Alt: ['ALT'], All: ['ALL', 'AFD'], @@ -296,6 +299,10 @@ const CommonSaccharideNames: { [k: string]: string[] } = { Psi: [], } +const UnknownSaccharideNames = [ + 'NGZ', // via CCD +] + export const SaccharideCompIdMap = (function () { const map = new Map<string, SaccharideComponent>() for (let i = 0, il = Monosaccharides.length; i < il; ++i) { @@ -307,6 +314,9 @@ export const SaccharideCompIdMap = (function () { } } } + for (let i = 0, il = UnknownSaccharideNames.length; i < il; ++i) { + map.set(UnknownSaccharideNames[i], UnknownSaccharideComponent) + } return map })() diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index d4e76255bbccc94bb56e78155ddfd51fad43b24a..6eaa79bb0443d51334860fc4870cb43b359e0053 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -8,7 +8,7 @@ import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int' import { UniqueArray } from 'mol-data/generic' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator' import { Model, ElementIndex } from '../model' -import { sort, arraySwap, hash1, sortArray, hashString } from 'mol-data/util'; +import { sort, arraySwap, hash1, sortArray, hashString, hashFnv32a } from 'mol-data/util'; import StructureElement from './element' import Unit from './unit' import { StructureLookup3D } from './util/lookup3d'; @@ -44,9 +44,11 @@ class Structure { entityIndices?: ReadonlyArray<EntityIndex>, uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>, hashCode: number, + /** Hash based on all unit.id values in the structure, reflecting the units transformation */ + transformHash: number, elementCount: number, polymerResidueCount: number, - } = { hashCode: -1, elementCount: 0, polymerResidueCount: 0 }; + } = { hashCode: -1, transformHash: -1, elementCount: 0, polymerResidueCount: 0 }; subsetBuilder(isSorted: boolean) { return new StructureSubsetBuilder(this, isSorted); @@ -74,6 +76,12 @@ class Structure { return this.computeHash(); } + get transformHash() { + if (this._props.transformHash !== -1) return this._props.transformHash; + this._props.transformHash = hashFnv32a(this.units.map(u => u.id)) + return this._props.transformHash; + } + private computeHash() { let hash = 23; for (let i = 0, _i = this.units.length; i < _i; i++) { @@ -389,6 +397,7 @@ namespace Structure { return s.hashCode; } + /** Hash based on all unit.model conformation values in the structure */ export function conformationHash(s: Structure) { return hashString(s.units.map(u => Unit.conformationId(u)).join('|')) } @@ -409,6 +418,13 @@ namespace Structure { return true; } + export function areEquivalent(a: Structure, b: Structure) { + return a === b || ( + a.hashCode === b.hashCode && + StructureSymmetry.areTransformGroupsEquivalent(a.unitSymmetryGroups, b.unitSymmetryGroups) + ) + } + export class ElementLocationIterator implements Iterator<StructureElement> { private current = StructureElement.create(); private unitIndex = 0; diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 1b35f8341f75d86dccf08e88b08cc8a475e3506f..5af14696a9e1b5017299ace7cbe5643956a2bc1f 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -78,6 +78,15 @@ namespace StructureSymmetry { return ret; } + + /** Checks if transform groups are equal up to their unit's transformations */ + export function areTransformGroupsEquivalent(a: ReadonlyArray<Unit.SymmetryGroup>, b: ReadonlyArray<Unit.SymmetryGroup>) { + if (a.length !== b.length) return false + for (let i = 0, il = a.length; i < il; ++i) { + if (a[i].hashCode !== b[i].hashCode) return false + } + return true + } } function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) { diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index fd41f3db12facc596554f41aacc7d8af0d36c489..933cd583a02d1194bbabb6ce241899d8e96d5554 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -15,7 +15,7 @@ import { UnitRings } from './unit/rings'; import StructureElement from './element' import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing'; import { IntMap, SortedArray } from 'mol-data/int'; -import { hash2 } from 'mol-data/util'; +import { hash2, hashFnv32a } from 'mol-data/util'; import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer'; import { getNucleotideElements } from './util/nucleotide'; import { GaussianDensityProps, computeUnitGaussianDensityCached } from './unit/gaussian-density'; @@ -48,7 +48,10 @@ namespace Unit { readonly units: ReadonlyArray<Unit> /** Maps unit.id to index of unit in units array */ readonly unitIndexMap: IntMap<number> + /** Hash based on unit.invariantId which is the same for all units in the group */ readonly hashCode: number + /** Hash based on all unit.id values in the group, reflecting the units transformation*/ + readonly transformHash: number } function getUnitIndexMap(units: Unit[]) { @@ -72,7 +75,8 @@ namespace Unit { props.unitIndexMap = getUnitIndexMap(units) return props.unitIndexMap }, - hashCode: hashUnit(units[0]) + hashCode: hashUnit(units[0]), + transformHash: hashFnv32a(units.map(u => u.id)) } } @@ -90,7 +94,7 @@ namespace Unit { readonly invariantId: number, readonly elements: StructureElement.Set, readonly model: Model, - readonly conformation: SymmetryOperator.ArrayMapping, + readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>, getChild(elements: StructureElement.Set): Unit, applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit, @@ -124,7 +128,7 @@ namespace Unit { readonly invariantId: number; readonly elements: StructureElement.Set; readonly model: Model; - readonly conformation: SymmetryOperator.ArrayMapping; + readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>; // Reference some commonly accessed things for faster access. readonly residueIndex: ArrayLike<ResidueIndex>; @@ -187,7 +191,7 @@ namespace Unit { return computeUnitGaussianDensityCached(this, props, this.props.gaussianDensities, ctx, webgl); } - constructor(id: number, invariantId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) { + constructor(id: number, invariantId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: AtomicProperties) { this.id = id; this.invariantId = invariantId; this.model = model; @@ -229,7 +233,7 @@ namespace Unit { readonly invariantId: number; readonly elements: StructureElement.Set; readonly model: Model; - readonly conformation: SymmetryOperator.ArrayMapping; + readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>; readonly coarseElements: CoarseElements; readonly coarseConformation: C; @@ -276,7 +280,7 @@ namespace Unit { return computeUnitGaussianDensityCached(this as Unit.Spheres | Unit.Gaussians, props, this.props.gaussianDensities, ctx, webgl); // TODO get rid of casting } - constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: CoarseProperties) { + constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) { this.kind = kind; this.id = id; this.invariantId = invariantId; @@ -305,7 +309,7 @@ namespace Unit { }; } - function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: CoarseProperties): Unit { + function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties): Unit { return new Coarse(id, invariantId, model, kind, elements, conformation, props) as any as Unit /** lets call this an ugly temporary hack */; } diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 4e39ce449219d0c663d2c9f8cad3bb24fe9aa0fc..a5adc76f991b2b652ac9f9d290adc256ae67fb56 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -48,7 +48,7 @@ interface ComplexVisualBuilder<P extends ComplexParams, G extends Geometry> { interface ComplexVisualGeometryBuilder<P extends ComplexParams, G extends Geometry> extends ComplexVisualBuilder<P, G> { createEmptyGeometry(geometry?: G): G createRenderObject(ctx: VisualContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): Promise<ComplexRenderObject> - updateValues(values: RenderableValues, newProps: PD.Values<P>): void + updateValues(values: RenderableValues, geometry: Geometry, newProps: PD.Values<P>): void } export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeometryBuilder<P, Geometry>): ComplexVisual<P> { @@ -85,15 +85,15 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo VisualUpdateState.reset(updateState) setUpdateState(updateState, newProps, currentProps, theme, currentTheme) + if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true + const newConformationHash = Structure.conformationHash(currentStructure) if (newConformationHash !== conformationHash) { conformationHash = newConformationHash updateState.createGeometry = true } - if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - // if (updateState.createGeometry) { @@ -113,7 +113,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo await createColors(ctx.runtime, locationIt, theme.color, renderObject.values) } - updateValues(renderObject.values, newProps) + updateValues(renderObject.values, geometry, newProps) updateRenderableState(renderObject.state, newProps) currentProps = newProps @@ -129,7 +129,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo throw new Error('missing structure') } else if (structure && (!currentStructure || !renderObject)) { await create(ctx, structure, theme, props) - } else if (structure && structure.hashCode !== currentStructure.hashCode) { + } else if (structure && !Structure.areEquivalent(structure, currentStructure)) { await create(ctx, structure, theme, props) } else { if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) { diff --git a/src/mol-repr/structure/representation/cartoon.ts b/src/mol-repr/structure/representation/cartoon.ts index ccb54f9332aca73d05b0e589cc31cbe7352a5028..4d21933e882b5a8274eabd8eee013a36c9d51f58 100644 --- a/src/mol-repr/structure/representation/cartoon.ts +++ b/src/mol-repr/structure/representation/cartoon.ts @@ -31,7 +31,7 @@ export const CartoonParams = { ...NucleotideBlockParams, ...PolymerDirectionParams, sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), - colorTheme: PD.Mapped('polymer-index', BuiltInColorThemeOptions, getBuiltInColorThemeParams), + colorTheme: PD.Mapped('polymer-id', BuiltInColorThemeOptions, getBuiltInColorThemeParams), visuals: PD.MultiSelect<CartoonVisualName>(['polymer-trace', 'polymer-gap', 'nucleotide-block'], CartoonVisualOptions), } PD.getDefaultValues(CartoonParams).colorTheme.name diff --git a/src/mol-repr/structure/representation/molecular-surface.ts b/src/mol-repr/structure/representation/molecular-surface.ts index cdf89b914e547ee033b46552881bc8d4383440ca..71ac6eb26fa22999ce6aaa6b7dfde09dd76927fb 100644 --- a/src/mol-repr/structure/representation/molecular-surface.ts +++ b/src/mol-repr/structure/representation/molecular-surface.ts @@ -27,7 +27,7 @@ export const MolecularSurfaceParams = { ...GaussianSurfaceParams, ...GaussianWireframeParams, ...GaussianDensityVolumeParams, - colorTheme: PD.Mapped('polymer-index', BuiltInColorThemeOptions, getBuiltInColorThemeParams), + colorTheme: PD.Mapped('polymer-id', BuiltInColorThemeOptions, getBuiltInColorThemeParams), visuals: PD.MultiSelect<MolecularSurfaceVisualName>(['gaussian-surface'], MolecularSurfaceVisualOptions), } PD.getDefaultValues(MolecularSurfaceParams).colorTheme.name diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index abd56451a196e91b1ada57ad8a2319b1129224f5..ac47dae9dc1f26b3288a17b6332c7b94895af485 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -50,7 +50,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar if (!_structure && !structure) { throw new Error('missing structure') } else if (structure && !_structure) { - // console.log('initial structure') + // console.log(label, 'initial structure') // First call with a structure, create visuals for each group. _groups = structure.unitSymmetryGroups; for (let i = 0; i < _groups.length; i++) { @@ -59,8 +59,8 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) visuals.set(group.hashCode, { visual, group }) } - } else if (structure && _structure.hashCode !== structure.hashCode) { - // console.log('_structure.hashCode !== structure.hashCode') + } else if (structure && !Structure.areEquivalent(structure, _structure)) { + // console.log(label, 'structure not equivalent') // Tries to re-use existing visuals for the groups of the new structure. // Creates additional visuals if needed, destroys left-over visuals. _groups = structure.unitSymmetryGroups; @@ -71,18 +71,25 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar const group = _groups[i]; const visualGroup = oldVisuals.get(group.hashCode) if (visualGroup) { + // console.log(label, 'found visualGroup to reuse') + // console.log('old', visualGroup.group) + // console.log('new', group) const { visual } = visualGroup await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) visuals.set(group.hashCode, { visual, group }) oldVisuals.delete(group.hashCode) } else { + // console.log(label, 'not found visualGroup to reuse, creating new') // newGroups.push(group) const visual = visualCtor() await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) visuals.set(group.hashCode, { visual, group }) } } - oldVisuals.forEach(({ visual }) => visual.destroy()) + oldVisuals.forEach(({ visual }) => { + // console.log(label, 'removed unused visual') + visual.destroy() + }) // TODO review logic // For new groups, re-use left-over visuals @@ -94,12 +101,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar // visuals.set(group.hashCode, { visual, group }) // }) // unusedVisuals.forEach(visual => visual.destroy()) - } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) { - // console.log('_structure.hashCode === structure.hashCode') + } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) { + console.log(label, 'structures equivalent but not identical') // Expects that for structures with the same hashCode, // the unitSymmetryGroups are the same as well. // Re-uses existing visuals for the groups of the new structure. _groups = structure.unitSymmetryGroups; + // console.log('new', structure.unitSymmetryGroups) + // console.log('old', _structure.unitSymmetryGroups) for (let i = 0; i < _groups.length; i++) { const group = _groups[i]; const visualGroup = visuals.get(group.hashCode) @@ -111,7 +120,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar } } } else { - // console.log('no new structure') + // console.log(label, 'no new structure') // No new structure given, just update all visuals with new props. const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ])) diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index e0f4ba605d0ae2ef534336adb59ef618c43812e5..a80885a74a463239d8abeae566e131512e352270 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -34,13 +34,6 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup } export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { } -function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) { - return ( - groupA.units.length === groupB.units.length && - Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0]) - ) -} - type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> { @@ -55,7 +48,7 @@ interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> { interface UnitsVisualGeometryBuilder<P extends UnitsParams, G extends Geometry> extends UnitsVisualBuilder<P, G> { createEmptyGeometry(geometry?: G): G createRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): Promise<UnitsRenderObject> - updateValues(values: RenderableValues, newProps: PD.Values<P>): void + updateValues(values: RenderableValues, geometry: Geometry, newProps: PD.Values<P>): void } export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> { @@ -88,35 +81,46 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps) } - async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}) { + async function update(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<PD.Values<P>> = {}) { if (!renderObject) return const newProps = Object.assign({}, currentProps, props, { structure: currentStructure }) - const unit = currentGroup.units[0] + const unit = group.units[0] locationIt.reset() VisualUpdateState.reset(updateState) setUpdateState(updateState, newProps, currentProps, theme, currentTheme) + if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true + + if (group.transformHash !== currentGroup.transformHash) { + if (group.units.length !== currentGroup.units.length || updateState.updateColor) { + updateState.updateTransform = true + } else { + updateState.updateMatrix = true + } + } + + // check if the conformation of unit.model has changed const newConformationId = Unit.conformationId(unit) if (newConformationId !== currentConformationId) { currentConformationId = newConformationId updateState.createGeometry = true } - if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - - if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - // if (updateState.updateTransform) { - locationIt = createLocationIterator(currentGroup) + locationIt = createLocationIterator(group) const { instanceCount, groupCount } = locationIt - createUnitsTransform(currentGroup, renderObject.values) createMarkers(instanceCount * groupCount, renderObject.values) updateState.updateColor = true + updateState.updateMatrix = true + } + + if (updateState.updateMatrix) { + createUnitsTransform(group, renderObject.values) } if (updateState.createGeometry) { @@ -138,11 +142,12 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB await createColors(ctx.runtime, locationIt, theme.color, renderObject.values) } - updateValues(renderObject.values, newProps) + updateValues(renderObject.values, geometry, newProps) updateRenderableState(renderObject.state, newProps) currentProps = newProps currentTheme = theme + currentGroup = group } return { @@ -161,11 +166,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB await create(ctx, group, theme, props) } else { // console.log('unit-visual update') - if (group && !sameGroupConformation(group, currentGroup)) { - // console.log('unit-visual new conformation') - currentGroup = group - } - await update(ctx, theme, props) + await update(ctx, group || currentGroup, theme, props) } }, getLoci(pickingId: PickingId) { diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index ba07ae3a78f973fe468237d50406c9571ac03208..dbdf3ada034ac6cf98b672a53c1a5c868b83444f 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -203,12 +203,12 @@ function markCarbohydrate(loci: Loci, structure: Structure, apply: (interval: In for (const e of loci.elements) { OrderedSet.forEach(e.indices, v => { const { model, elements } = e.unit - const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const { index, offsets } = model.atomicHierarchy.residueAtomSegments const rI = index[elements[v]] const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI]) const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1) const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax) - if(!OrderedSet.isSubset(e.indices, unitIndexInterval)) return + if (!OrderedSet.isSubset(e.indices, unitIndexInterval)) return const eI = getAnomericCarbon(e.unit, rI) if (eI !== undefined) { const idx = getElementIndex(e.unit, eI) diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts index 30b64384b8d6098f7870bb289faf5f529d6c1e1b..fe9d2414e1b9bccce3b00675ef9792e55ed6ecc1 100644 --- a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts @@ -34,9 +34,9 @@ async function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, st const l = terminalLinks[edgeIndex] if (l.fromCarbohydrate) { Vec3.copy(posA, elements[l.carbohydrateIndex].geometry.center) - l.elementUnit.conformation.position(l.elementIndex, posB) + l.elementUnit.conformation.position(l.elementUnit.elements[l.elementIndex], posB) } else { - l.elementUnit.conformation.position(l.elementIndex, posA) + l.elementUnit.conformation.position(l.elementUnit.elements[l.elementIndex], posA) Vec3.copy(posB, elements[l.carbohydrateIndex].geometry.center) } }, @@ -123,7 +123,7 @@ function getTerminalLinkLoci(pickingId: PickingId, structure: Structure, id: num l.elementUnit, l.elementIndex, carb.unit, carbIndex as StructureElement.UnitIndex ) - ]) + ]) } return EmptyLoci } diff --git a/src/mol-repr/structure/visual/nucleotide-block-mesh.ts b/src/mol-repr/structure/visual/nucleotide-block-mesh.ts index 8e5188611682aa9e0f533e9cde7da22311997263..b3be2492c664406176724afa7772aeddb26c877b 100644 --- a/src/mol-repr/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-repr/structure/visual/nucleotide-block-mesh.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, Structure } from 'mol-model/structure'; +import { Unit, Structure, ElementIndex } from 'mol-model/structure'; import { UnitsVisual } from '../representation'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { Segmentation } from 'mol-data/int'; @@ -71,7 +71,7 @@ async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structu if (isNucleic(moleculeType)) { const parentId = modifiedResidues.parentId.get(compId) if (parentId !== undefined) compId = parentId - let idx1 = -1, idx2 = -1, idx3 = -1, idx4 = -1, idx5 = -1, idx6 = -1 + let idx1: ElementIndex | -1 = -1, idx2: ElementIndex | -1 = -1, idx3: ElementIndex | -1 = -1, idx4: ElementIndex | -1 = -1, idx5: ElementIndex | -1 = -1, idx6: ElementIndex | -1 = -1 let width = 4.5, height = 4.5, depth = 2.5 * sizeFactor if (isPurinBase(compId)) { diff --git a/src/mol-repr/util.ts b/src/mol-repr/util.ts index ad7d101646a55f50ca2820049e251d538b4ff006..dd3acb447f6b4d09febdddc97fc27f098aa7c264 100644 --- a/src/mol-repr/util.ts +++ b/src/mol-repr/util.ts @@ -10,6 +10,7 @@ import { VisualQuality } from 'mol-geo/geometry/geometry'; export interface VisualUpdateState { updateTransform: boolean + updateMatrix: boolean updateColor: boolean updateSize: boolean createGeometry: boolean @@ -18,6 +19,7 @@ export namespace VisualUpdateState { export function create(): VisualUpdateState { return { updateTransform: false, + updateMatrix: false, updateColor: false, updateSize: false, createGeometry: false @@ -25,6 +27,7 @@ export namespace VisualUpdateState { } export function reset(state: VisualUpdateState) { state.updateTransform = false + state.updateMatrix = false state.updateColor = false state.updateSize = false state.createGeometry = false diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index 85a467af2ec2a2aa4b63ad115cbf129d72f3d4f4..9c2e642ece0912ac08e59798fd418ff18920d625 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -36,7 +36,7 @@ interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> { interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> { createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): Promise<VolumeRenderObject> - updateValues(values: RenderableValues, newProps: PD.Values<P>): void + updateValues(values: RenderableValues, geometry: G, newProps: PD.Values<P>): void } export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>): VolumeVisual<P> { @@ -69,7 +69,7 @@ export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeomet ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry)) } - updateValues(renderObject.values, newProps) + updateValues(renderObject.values, geometry, newProps) updateRenderableState(renderObject.state, newProps) currentProps = newProps diff --git a/src/mol-theme/color.ts b/src/mol-theme/color.ts index 31c5b3fd542397ba368eb6e7ed53c751621b69b3..52d9608cf8e9269fd24926dabda99bea3eadb597 100644 --- a/src/mol-theme/color.ts +++ b/src/mol-theme/color.ts @@ -17,6 +17,7 @@ import { CrossLinkColorThemeProvider } from './color/cross-link'; import { ElementIndexColorThemeProvider } from './color/element-index'; import { ElementSymbolColorThemeProvider } from './color/element-symbol'; import { MoleculeTypeColorThemeProvider } from './color/molecule-type'; +import { PolymerIdColorThemeProvider } from './color/polymer-id'; import { PolymerIndexColorThemeProvider } from './color/polymer-index'; import { ResidueNameColorThemeProvider } from './color/residue-name'; import { SecondaryStructureColorThemeProvider } from './color/secondary-structure'; @@ -96,6 +97,7 @@ export const BuiltInColorThemes = { 'element-index': ElementIndexColorThemeProvider, 'element-symbol': ElementSymbolColorThemeProvider, 'molecule-type': MoleculeTypeColorThemeProvider, + 'polymer-id': PolymerIdColorThemeProvider, 'polymer-index': PolymerIndexColorThemeProvider, 'residue-name': ResidueNameColorThemeProvider, 'secondary-structure': SecondaryStructureColorThemeProvider, diff --git a/src/mol-theme/color/chain-id.ts b/src/mol-theme/color/chain-id.ts index 2983ce357fef282afe23182257b8e558fc8cb59f..0d90464d8fafe845e6db32af937870f0fa44d3cc 100644 --- a/src/mol-theme/color/chain-id.ts +++ b/src/mol-theme/color/chain-id.ts @@ -12,6 +12,7 @@ import { ColorTheme, LocationColor } from '../color'; import { ParamDefinition as PD } from 'mol-util/param-definition' import { ThemeDataContext } from 'mol-theme/theme'; import { ColorListOptions, ColorListName } from 'mol-util/color/scale'; +import { Column } from 'mol-data/db'; const DefaultColor = Color(0xCCCCCC) const Description = 'Gives every chain a color based on its `asym_id` value.' @@ -34,6 +35,17 @@ function getAsymId(unit: Unit): StructureElement.Property<string> { } } +function addAsymIds(map: Map<string, number>, data: Column<string>) { + let j = map.size + for (let o = 0, ol = data.rowCount; o < ol; ++o) { + const k = data.value(o) + if (!map.has(k)) { + map.set(k, j) + j += 1 + } + } +} + export function ChainIdColorTheme(ctx: ThemeDataContext, props: ChainIdColorThemeProps): ColorTheme<ChainIdColorThemeProps> { let color: LocationColor const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' }) @@ -42,14 +54,13 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: ChainIdColorThem const l = StructureElement.create() const { models } = ctx.structure const asymIdSerialMap = new Map<string, number>() - let j = 0 for (let i = 0, il = models.length; i <il; ++i) { - models[i].properties.asymIdSerialMap.forEach((v, k) => { - if (!asymIdSerialMap.has(k)) { - asymIdSerialMap.set(k, j) - j += 1 - } - }) + const m = models[i] + addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id) + if (m.coarseHierarchy.isDefined) { + addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id) + addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id) + } } scale.setDomain(0, asymIdSerialMap.size - 1) const scaleColor = scale.color diff --git a/src/mol-theme/color/cross-link.ts b/src/mol-theme/color/cross-link.ts index 74b36b07ee393fe476c8dd1fdf030714164a9eb6..2db5d32b2089070784049644a7c79f86e71fd836 100644 --- a/src/mol-theme/color/cross-link.ts +++ b/src/mol-theme/color/cross-link.ts @@ -28,8 +28,8 @@ export type CrossLinkColorThemeProps = PD.Values<typeof CrossLinkColorThemeParam const distVecA = Vec3.zero(), distVecB = Vec3.zero() function linkDistance(link: Link.Location) { - link.aUnit.conformation.position(link.aIndex, distVecA) - link.bUnit.conformation.position(link.bIndex, distVecB) + link.aUnit.conformation.position(link.aUnit.elements[link.aIndex], distVecA) + link.bUnit.conformation.position(link.bUnit.elements[link.bIndex], distVecB) return Vec3.distance(distVecA, distVecB) } diff --git a/src/mol-theme/color/polymer-id.ts b/src/mol-theme/color/polymer-id.ts new file mode 100644 index 0000000000000000000000000000000000000000..c543ca7e09eaeaed62e1411dd44577e3302a68bc --- /dev/null +++ b/src/mol-theme/color/polymer-id.ts @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure'; + +import { ColorScale, Color } from 'mol-util/color'; +import { Location } from 'mol-model/location'; +import { ColorTheme, LocationColor } from '../color'; +import { ParamDefinition as PD } from 'mol-util/param-definition' +import { ThemeDataContext } from 'mol-theme/theme'; +import { ColorListOptions, ColorListName } from 'mol-util/color/scale'; +import { Column } from 'mol-data/db'; +import { Entities } from 'mol-model/structure/model/properties/common'; + +const DefaultColor = Color(0xCCCCCC) +const Description = 'Gives every polymer chain a color based on its `asym_id` value.' + +export const PolymerIdColorThemeParams = { + list: PD.Select<ColorListName>('RdYlBu', ColorListOptions), +} +export function getPolymerIdColorThemeParams(ctx: ThemeDataContext) { + return PolymerIdColorThemeParams // TODO return copy +} +export type PolymerIdColorThemeProps = PD.Values<typeof PolymerIdColorThemeParams> + +function getAsymId(unit: Unit): StructureElement.Property<string> { + switch (unit.kind) { + case Unit.Kind.Atomic: + return StructureProperties.chain.label_asym_id + case Unit.Kind.Spheres: + case Unit.Kind.Gaussians: + return StructureProperties.coarse.asym_id + } +} + +function addPolymerAsymIds(map: Map<string, number>, asymId: Column<string>, entityId: Column<string>, entities: Entities) { + let j = map.size + for (let o = 0, ol = asymId.rowCount; o < ol; ++o) { + const e = entityId.value(o) + const eI = entities.getEntityIndex(e) + if (entities.data.type.value(eI) === 'polymer') { + const k = asymId.value(o) + if (!map.has(k)) { + map.set(k, j) + j += 1 + } + } + } +} + +export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PolymerIdColorThemeProps): ColorTheme<PolymerIdColorThemeProps> { + let color: LocationColor + const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' }) + + if (ctx.structure) { + const l = StructureElement.create() + const { models } = ctx.structure + const polymerAsymIdSerialMap = new Map<string, number>() + for (let i = 0, il = models.length; i <il; ++i) { + for (let i = 0, il = models.length; i <il; ++i) { + const m = models[i] + addPolymerAsymIds(polymerAsymIdSerialMap, m.atomicHierarchy.chains.label_asym_id, m.atomicHierarchy.chains.label_entity_id, m.entities) + if (m.coarseHierarchy.isDefined) { + addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.spheres.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities) + addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.gaussians.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities) + } + } + } + scale.setDomain(0, polymerAsymIdSerialMap.size - 1) + const scaleColor = scale.color + + color = (location: Location): Color => { + if (StructureElement.isLocation(location)) { + const asym_id = getAsymId(location.unit) + return scaleColor(polymerAsymIdSerialMap.get(asym_id(location)) || 0) + } else if (Link.isLocation(location)) { + const asym_id = getAsymId(location.aUnit) + l.unit = location.aUnit + l.element = location.aUnit.elements[location.aIndex] + return scaleColor(polymerAsymIdSerialMap.get(asym_id(l)) || 0) + } + return DefaultColor + } + } else { + color = () => DefaultColor + } + + return { + granularity: 'group', + color, + props, + description: Description, + legend: scale ? scale.legend : undefined + } +} + +export const PolymerIdColorThemeProvider: ColorTheme.Provider<typeof PolymerIdColorThemeParams> = { + label: 'Polymer Id', + factory: PolymerIdColorTheme, + getParams: getPolymerIdColorThemeParams +} \ No newline at end of file