Compare commits

...

44 Commits

Author SHA1 Message Date
dsehnal
3d2e4115ed 1.2.9 2021-01-08 15:17:42 +01:00
dsehnal
dbce1ccb3d alpha-orbitals: ability to clamp volume values 2021-01-08 15:16:08 +01:00
David Sehnal
03aa2be978 Merge pull request #116 from JonStargaryen/modelserverfixes
ModelServer: Add option to download text files
2021-01-07 17:33:38 +01:00
JonStargaryen
8dfc52e1ab cleanup 2021-01-07 14:49:32 +01:00
JonStargaryen
6058179f10 cleanup 2021-01-07 14:36:11 +01:00
JonStargaryen
ea9e25b03c ResultWriterParams 2021-01-07 14:30:01 +01:00
JonStargaryen
d60c3ddce3 handle non-string params faithfully 2021-01-07 14:21:32 +01:00
JonStargaryen
724e79bddf version/changelog 2021-01-07 12:53:44 +01:00
JonStargaryen
2de61215c4 better description 2021-01-07 12:35:16 +01:00
JonStargaryen
e783d9a9f1 Merge remote-tracking branch 'upstream/master' into modelserverfixes 2021-01-07 12:31:26 +01:00
JonStargaryen
e9e971d4f3 lock 2021-01-07 12:31:20 +01:00
JonStargaryen
96dea14cb1 cleanup 2021-01-07 12:26:50 +01:00
JonStargaryen
04fc157340 ModelServer: Save As param 2021-01-07 11:53:24 +01:00
dsehnal
cfc24fa99e SequenceView: fix polymers & everything modes 2021-01-07 11:22:57 +01:00
dsehnal
19c1088209 1.2.8 2021-01-06 15:29:29 +01:00
dsehnal
ee6c2e0841 canvas3d: add commited event 2021-01-06 15:22:54 +01:00
Alexander Rose
20ee659b00 wip, gaussian surface
- fix webgl1 gaussian volume broken
- fix 2d volume slice missing first row
- use scissor test to avoid useless calculations
- reduce radius for which gaussians are calculated
-
2021-01-03 15:15:03 -08:00
Alexander Rose
b6514a4a50 fix structure bond count calculation 2021-01-03 14:57:18 -08:00
Alexander Rose
07b8bdb951 noClip option for renderables
- exclude handle and axes helper from clip objects
2021-01-03 14:56:40 -08:00
dsehnal
249e5a3e0b SequenceView improvements
- add ability to show all polymers/chains at once
- do not redraw when structure list doesn't change (saves 3 re-renders for a typical structure load)
2020-12-29 16:29:16 +01:00
Alexander Rose
6d2578d3d0 repr/geo param update fixes
- texture-mesh geo
- text visual
2020-12-20 13:40:33 -08:00
Alexander Rose
146022dc12 wip, gaussian surface & mc
- fix iso-level
- reuse gpu resources for mc (patched many memory leaks)
2020-12-20 12:55:54 -08:00
dsehnal
5664e1d8be 1.2.7 2020-12-19 11:53:09 +01:00
dsehnal
4881a41256 set default camera radius/max = 0 2020-12-19 11:46:00 +01:00
Alexander Rose
70e07be64d anvil tweaks
- remove unused/broken bilayer-spheres visual
- ensure anvil prop is calculated
2020-12-12 18:23:07 -08:00
dsehnal
8147b3aa34 1.2.6 2020-12-10 10:46:26 +01:00
dsehnal
b21552ff36 fix wboit rendering when updating alpha 2020-12-10 10:44:35 +01:00
dsehnal
c683cbe962 1.2.5 2020-12-09 15:06:17 +01:00
dsehnal
bd270e4428 fix pdbx_PDB_ins_code "prefixed" names in CIF exporter 2020-12-09 15:03:41 +01:00
dsehnal
23d942d8a5 updated packages 2020-12-09 14:55:25 +01:00
Alexander Rose
cbcd6b99d2 Merge pull request #107 from molstar/remove-3dg
remove 3dg in favor of g3d (#93)
2020-12-05 20:44:16 -08:00
Alexander Rose
ee5c098a9f remove 3dg in favor of g3d (#93) 2020-12-05 20:38:50 -08:00
David Sehnal
cd872b47e6 1.2.4 2020-12-03 15:30:30 +01:00
David Sehnal
2683c5b318 Merge pull request #105 from molstar/gpu-grid
GPU grid 3d computation wrapper
2020-12-03 15:24:43 +01:00
David Sehnal
c71f60a164 ParamDefinition.DataRef 2020-12-03 15:21:42 +01:00
David Sehnal
881cbc1947 tweaks 2020-12-03 13:54:51 +01:00
David Sehnal
f3e7febbd1 Merge branch 'master' of https://github.com/molstar/molstar into gpu-grid 2020-12-03 06:33:19 +01:00
David Sehnal
e68ad13031 createGrid3dComputeRenderable yieldPeriod param 2020-12-02 12:29:01 +01:00
Alexander Rose
7fbbe1e63a representation state and hightlight fixes
- recreate state when repr changes
- take repr into account for non-hover hightlights (eg from state tree)
2020-12-01 17:48:40 -08:00
Alexander Rose
a5ca72af3c postprocessing tweaks and fixes
- fix missing enable scissor state
- better antialiasing defaults
- always allow fxaa
2020-12-01 17:46:51 -08:00
David Sehnal
1ce6641eb3 grid3d-compute util code 2020-12-01 20:58:27 +01:00
David Sehnal
5dc413ab8c wip grid3d renderable 2020-12-01 19:33:05 +01:00
David Sehnal
50b615e86c 1.2.3 2020-11-28 14:50:15 +01:00
David Sehnal
5b4c6743e7 GlobalModelTransformInfo
- support in volume streaming
- export in ModelServer if transform param is present
2020-11-28 14:46:58 +01:00
78 changed files with 22411 additions and 5425 deletions

25493
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "1.2.2",
"version": "1.2.9",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -86,64 +86,64 @@
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^1.17.7",
"@graphql-codegen/cli": "^1.17.8",
"@graphql-codegen/time": "^1.17.10",
"@graphql-codegen/typescript": "^1.17.9",
"@graphql-codegen/typescript-graphql-files-modules": "^1.17.8",
"@graphql-codegen/typescript-graphql-request": "^1.17.7",
"@graphql-codegen/typescript-operations": "^1.17.8",
"@types/cors": "^2.8.7",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"@graphql-codegen/add": "^2.0.2",
"@graphql-codegen/cli": "^1.19.4",
"@graphql-codegen/time": "^2.0.2",
"@graphql-codegen/typescript": "^1.19.0",
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
"@graphql-codegen/typescript-operations": "^1.17.12",
"@types/cors": "^2.8.8",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"benchmark": "^2.1.4",
"concurrently": "^5.3.0",
"cpx2": "^2.0.0",
"css-loader": "^3.6.0",
"eslint": "^7.8.1",
"cpx2": "^3.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.1.0",
"file-loader": "^6.2.0",
"fs-extra": "^9.0.1",
"graphql": "^15.3.0",
"graphql": "^15.4.0",
"http-server": "^0.12.3",
"jest": "^26.4.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.14.1",
"raw-loader": "^4.0.1",
"sass-loader": "^8.0.2",
"simple-git": "^2.20.1",
"style-loader": "^1.2.1",
"ts-jest": "^26.3.0",
"typescript": "^4.0.2",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.3.2",
"node-sass": "^5.0.0",
"raw-loader": "^4.0.2",
"sass-loader": "^10.1.0",
"simple-git": "^2.25.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.1.2",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-version-file-plugin": "^0.4.0"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^1.0.33",
"@types/benchmark": "^2.1.0",
"@types/compression": "1.7.0",
"@types/express": "^4.17.8",
"@types/jest": "^25.2.3",
"@types/node": "^14.10.1",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.18",
"@types/node": "^14.14.11",
"@types/node-fetch": "^2.5.7",
"@types/react": "^16.9.49",
"@types/react-dom": "^16.9.8",
"@types/swagger-ui-dist": "3.0.5",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/swagger-ui-dist": "3.30.0",
"argparse": "^1.0.10",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^7.0.9",
"immer": "^8.0.0",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"node-fetch": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rxjs": "^6.6.3",
"swagger-ui-dist": "^3.33.0",
"tslib": "^2.0.1",
"swagger-ui-dist": "^3.37.2",
"tslib": "^2.0.3",
"util.promisify": "^1.0.1",
"xhr2": "^0.2.0"
}

View File

@@ -26,7 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'file': return `JavaScript File Handle`;
case 'file-list': return `JavaScript FileList Handle`;
case 'select': return `One of ${oToS(param.options)}`;
case 'value-ref': return `Reference to a state object.`;
case 'value-ref': return `Reference to a runtime defined value.`;
case 'data-ref': return `Reference to a computed data value.`;
case 'text': return 'String';
case 'interval': return `Interval [min, max]`;
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;

View File

@@ -20,8 +20,8 @@ import { BehaviorSubject } from 'rxjs';
import { debounceTime, skip } from 'rxjs/operators';
import './index.html';
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
import { canComputeAlphaOrbitalsOnGPU } from '../../extensions/alpha-orbitals/gpu/compute';
import { PluginCommands } from '../../mol-plugin/commands';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
require('mol-plugin-ui/skin/light.scss');
interface DemoInput {
@@ -71,7 +71,7 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
if (!canComputeAlphaOrbitalsOnGPU(this.plugin.canvas3d?.webgl)) {
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`

View File

@@ -7,8 +7,8 @@
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
import { Grid } from '../../mol-model/volume';
import { SphericalBasisOrder } from './spherical-functions';
import { Box3D } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms } from '../../mol-util/array';
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
@@ -95,7 +95,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const BohrToAngstromFactor = 0.529177210859;
export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[]) {
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
const boxSize = Box3D.size(Vec3(), gridInfo.box);
const boxOrigin = Vec3.clone(gridInfo.box.min);
@@ -122,7 +122,7 @@ export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrd
stats: {
min: arrayMin(values),
max: arrayMax(values),
mean: arrayMax(values),
mean: arrayMean(values),
sigma: arrayRms(values),
},
};

View File

@@ -5,10 +5,11 @@
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
export function createSphericalCollocationDensityGrid(
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
@@ -17,9 +18,9 @@ export function createSphericalCollocationDensityGrid(
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(webgl!, cubeGrid, orbitals, ctx);
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
// console.timeEnd('gpu');
} else {
throw new Error('Missing OES_texture_float WebGL extension.');

View File

@@ -4,46 +4,72 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { QuadSchema, QuadValues } from '../../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../../mol-gl/renderable';
import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../../mol-gl/renderable/schema';
import { ShaderCode } from '../../../mol-gl/shader-code';
import quad_vert from '../../../mol-gl/shader/quad.vert';
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
import { RuntimeContext } from '../../../mol-task';
import { ValueCell } from '../../../mol-util';
import { arrayMin } from '../../../mol-util/array';
import { isLittleEndian } from '../../../mol-util/is-little-endian';
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
import shader_frag from './shader.frag';
import { MAIN, UTILS } from './shader.frag';
const AlphaOrbitalsSchema = {
...QuadSchema,
uDimensions: UniformSpec('v3'),
uMin: UniformSpec('v3'),
uDelta: UniformSpec('v3'),
const Schema = {
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
uWidth: UniformSpec('f'),
uNCenters: UniformSpec('i'),
uNAlpha: UniformSpec('i'),
uNCoeff: UniformSpec('i'),
uMaxCoeffs: UniformSpec('i'),
uLittleEndian: UniformSpec('b'),
uDensity: UniformSpec('b'),
uOccupancy: UniformSpec('f'),
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
};
type AlphaOrbitalsSchema = Values<typeof AlphaOrbitalsSchema>
const AlphaOrbitalsName = 'alpha-orbitals';
const AlphaOrbitalsTex0 = 'alpha-orbitals-0';
const AlphaOrbitalsTex1 = 'alpha-orbitals-1';
const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag);
type AlphaOrbitalsRenderable = ComputeRenderable<AlphaOrbitalsSchema>
const Orbitals = createGrid3dComputeRenderable({
schema: Schema,
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'v',
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
return createTextureData(params.grid, params.orbital);
}
});
const Density = createGrid3dComputeRenderable({
schema: {
...Schema,
uOccupancy: UniformSpec('f'),
},
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'current + uOccupancy * v * v',
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return {
...createTextureData(params.grid, params.orbitals[0]),
uOccupancy: 0
};
},
cumulative: {
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return params.orbitals.filter(o => o.occupancy !== 0);
},
update({ grid }, state: AlphaOrbital, values) {
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
}
}
});
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
return Orbitals(ctx, webgl, grid, { grid, orbital });
}
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
return Density(ctx, webgl, grid, { grid, orbitals });
}
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
const alpha = new Float32Array(alphaOrbitals.length);
@@ -62,7 +88,7 @@ function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrde
return alpha;
}
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let centerCount = 0;
@@ -131,179 +157,14 @@ function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) {
}
}
return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff };
}
function createAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
const data = createTextureData(grid, orbital);
const [nx, ny, nz] = grid.dimensions;
const width = Math.ceil(Math.sqrt(nx * ny * nz));
if (!ctx.namedFramebuffers[AlphaOrbitalsName]) {
ctx.namedFramebuffers[AlphaOrbitalsName] = ctx.resources.framebuffer();
}
if (!ctx.namedTextures[AlphaOrbitalsTex0]) {
ctx.namedTextures[AlphaOrbitalsTex0] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (!ctx.namedTextures[AlphaOrbitalsTex1]) {
ctx.namedTextures[AlphaOrbitalsTex1] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const values: AlphaOrbitalsSchema = {
...QuadValues,
uDimensions: ValueCell.create(grid.dimensions),
uMin: ValueCell.create(grid.box.min),
uDelta: ValueCell.create(grid.delta),
uWidth: ValueCell.create(width),
uNCenters: ValueCell.create(data.nCenters),
uNAlpha: ValueCell.create(data.nAlpha),
uNCoeff: ValueCell.create(data.nCoeff),
uMaxCoeffs: ValueCell.create(data.maxCoeffs),
tCenters: ValueCell.create({ width: data.nCenters, height: 1, array: data.centers }),
tInfo: ValueCell.create({ width: data.nCenters, height: 1, array: data.info }),
tCoeff: ValueCell.create({ width: data.nCoeff, height: 1, array: data.coeff }),
tAlpha: ValueCell.create({ width: data.nAlpha, height: 1, array: data.alpha }),
uLittleEndian: ValueCell.create(isLittleEndian()),
uDensity: ValueCell.create(false),
uOccupancy: ValueCell.create(0),
tCumulativeSum: ValueCell.create(ctx.namedTextures[AlphaOrbitalsTex1])
return {
uNCenters: centerCount,
uNAlpha: baseCount,
uNCoeff: coeffCount,
uMaxCoeffs: maxCoeffs,
tCenters: { width: centerCount, height: 1, array: centers },
tInfo: { width: centerCount, height: 1, array: info },
tCoeff: { width: coeffCount, height: 1, array: coeff },
tAlpha: { width: baseCount, height: 1, array: alpha },
};
const schema = { ...AlphaOrbitalsSchema };
if (!ctx.isWebGL2) {
// workaround for webgl1 limitation that loop counters need to be `const`
(schema.uNCenters as any) = DefineSpec('number');
(schema.uMaxCoeffs as any) = DefineSpec('number');
}
const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function getAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable {
if (ctx.namedComputeRenderables[AlphaOrbitalsName]) {
const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values as AlphaOrbitalsSchema;
const data = createTextureData(grid, orbital);
const [nx, ny, nz] = grid.dimensions;
const width = Math.ceil(Math.sqrt(nx * ny * nz));
ValueCell.update(v.uDimensions, grid.dimensions);
ValueCell.update(v.uMin, grid.box.min);
ValueCell.update(v.uDelta, grid.delta);
ValueCell.updateIfChanged(v.uWidth, width);
ValueCell.updateIfChanged(v.uNCenters, data.nCenters);
ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha);
ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff);
ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs);
ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers });
ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info });
ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff });
ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha });
ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian());
ValueCell.updateIfChanged(v.uDensity, false);
ValueCell.updateIfChanged(v.uOccupancy, 0);
ValueCell.updateIfChanged(v.tCumulativeSum, ctx.namedTextures[AlphaOrbitalsTex1]);
ctx.namedComputeRenderables[AlphaOrbitalsName].update();
} else {
ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, grid, orbital);
}
return ctx.namedComputeRenderables[AlphaOrbitalsName];
}
export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
const [nx, ny, nz] = grid.dimensions;
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbital);
const width = renderable.values.uWidth.ref.value;
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
webgl.namedTextures[AlphaOrbitalsTex0].define(width, width);
webgl.namedTextures[AlphaOrbitalsTex0].attachFramebuffer(framebuffer, 'color0');
const { gl, state } = webgl;
framebuffer.bind();
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
renderable.render();
const array = new Uint8Array(width * width * 4);
webgl.readPixels(0, 0, width, width, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
}
export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) {
return !!webgl?.extensions.textureFloat;
}
export async function gpuComputeAlphaOrbitalsDensityGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[], ctx: RuntimeContext) {
await ctx.update({ message: 'Initializing...', isIndeterminate: true });
const [nx, ny, nz] = grid.dimensions;
const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbitals[0]);
const width = renderable.values.uWidth.ref.value;
if (!webgl.namedFramebuffers[AlphaOrbitalsName]) {
webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
const tex = [webgl.namedTextures[AlphaOrbitalsTex0], webgl.namedTextures[AlphaOrbitalsTex1]];
tex[0].define(width, width);
tex[1].define(width, width);
const values = renderable.values as AlphaOrbitalsSchema;
const { gl, state } = webgl;
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
gl.clearColor(0, 0, 0, 0);
tex[0].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
tex[1].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
ValueCell.update(values.uDensity, true);
const nonZero = orbitals.filter(o => o.occupancy !== 0);
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: nonZero.length });
for (let i = 0; i < nonZero.length; i++) {
const alpha = getNormalizedAlpha(grid.params.basis, nonZero[i].alpha, grid.params.sphericalOrder);
ValueCell.update(values.uOccupancy, nonZero[i].occupancy);
ValueCell.update(values.tCumulativeSum, tex[(i + 1) % 2]);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
gl.viewport(0, 0, width, width);
gl.scissor(0, 0, width, width);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
renderable.update();
renderable.render();
if (i !== nonZero.length - 1 && ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
const array = new Uint8Array(width * width * 4);
webgl.readPixels(0, 0, width, width, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
}

View File

@@ -4,165 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform vec2 uQuadShift;
uniform vec3 uDimensions;
uniform vec3 uMin;
uniform vec3 uDelta;
uniform sampler2D tCenters;
uniform sampler2D tInfo;
uniform sampler2D tCoeff;
uniform sampler2D tAlpha;
uniform float uWidth;
#ifndef uNCenters
uniform int uNCenters;
#endif
uniform int uNCoeff;
uniform int uNAlpha;
uniform bool uDensity;
uniform float uOccupancy;
uniform sampler2D tCumulativeSum;
uniform bool uLittleEndian;
//////////////////////////////////////////////////////////
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
// MIT License, Copyright (c) 2020 Equinor
float shiftRight (float v, float amt) {
v = floor(v) + 0.5;
return floor(v / exp2(amt));
}
float shiftLeft (float v, float amt) {
return floor(v * exp2(amt) + 0.5);
}
float maskLast (float v, float bits) {
return mod(v, shiftLeft(1.0, bits));
}
float extractBits (float num, float from, float to) {
from = floor(from + 0.5); to = floor(to + 0.5);
return maskLast(shiftRight(num, from), to - from);
}
vec4 floatToRgba(float texelFloat) {
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
texelFloat = abs(texelFloat);
float exponent = floor(log2(texelFloat));
float biased_exponent = exponent + 127.0;
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
float t = biased_exponent / 2.0;
float last_bit_of_biased_exponent = fract(t) * 2.0;
float remaining_bits_of_biased_exponent = floor(t);
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
return (
uLittleEndian
? vec4(byte4, byte3, byte2, byte1)
: vec4(byte1, byte2, byte3, byte4)
);
}
///////////////////////////////////////////////////////
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
// BSD 3-Clause License
//
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
ivec4 floatsToBytes(vec4 inputFloats) {
ivec4 bytes = ivec4(inputFloats * 255.0);
return (
uLittleEndian
? bytes.abgr
: bytes
);
}
// Break the four bytes down into an array of 32 bits.
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
float acc = float(bytes[channelIndex]);
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
float powerOfTwo = exp2(float(indexInByte));
bool bit = acc >= powerOfTwo;
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
acc = mod(acc, powerOfTwo);
}
}
}
// Compute the exponent of the 32-bit float.
float getExponent(bool bits[32]) {
const int startIndex = 1;
const int bitStringLength = 8;
const int endBeforeIndex = startIndex + bitStringLength;
float acc = 0.0;
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Compute the mantissa of the 32-bit float.
float getMantissa(bool bits[32], bool subnormal) {
const int startIndex = 9;
const int bitStringLength = 23;
const int endBeforeIndex = startIndex + bitStringLength;
// Leading/implicit/hidden bit convention:
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
float acc = float(!subnormal) * exp2(float(bitStringLength));
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Parse the float from its 32 bits.
float bitsToFloat(bool bits[32]) {
float signBit = float(bits[0]) * -2.0 + 1.0;
float exponent = getExponent(bits);
bool subnormal = abs(exponent - 0.0) < 0.01;
float mantissa = getMantissa(bits, subnormal);
float exponentBias = 127.0;
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
}
float rgbaToFloat(vec4 texelRGBA) {
ivec4 rgbaBytes = floatsToBytes(texelRGBA);
bool bits[32];
bytesToBits(rgbaBytes, bits);
return bitsToFloat(bits);
}
///////////////////////////////////////////////////////
export const UTILS = `
float L1(vec3 p, float a0, float a1, float a2) {
return a0 * p.z + a1 * p.x + a2 * p.y;
}
@@ -213,12 +55,10 @@ float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, flo
}
float alpha(float offset, float f) {
#ifdef uMaxCoeffs
#ifdef WEBGL1
// in webgl1, the value is in the alpha channel!
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
#endif
#ifndef uMaxCoeffs
#else
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
#endif
}
@@ -249,7 +89,7 @@ float Y(int L, vec3 X, float aO, float fA) {
return 0.0;
}
#ifndef uMaxCoeffs
#ifndef WEBGL1
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
for (int i = start; i < end; i++) {
@@ -258,9 +98,7 @@ float Y(int L, vec3 X, float aO, float fA) {
}
return gauss;
}
#endif
#ifdef uMaxCoeffs
#else
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
int o = start;
@@ -274,28 +112,13 @@ float Y(int L, vec3 X, float aO, float fA) {
return gauss;
}
#endif
`;
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)); }
void main(void) {
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
// axis order fast to slow Z, Y, X
// TODO: support arbitrary axis orders?
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
float j = intMod(kk, uDimensions.y);
float i = intDiv(kk, uDimensions.y);
vec3 xyz = uMin + uDelta * vec3(i, j, k);
export const MAIN = `
float fCenter = 1.0 / float(uNCenters - 1);
float fCoeff = 1.0 / float(uNCoeff - 1);
float fA = 1.0 / float(uNAlpha - 1);
// gl_FragColor = floatToRgba(offset);
// return;
float v = 0.0;
for (int i = 0; i < uNCenters; i++) {
@@ -319,13 +142,4 @@ void main(void) {
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
}
if (uDensity) {
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)));
gl_FragColor = floatToRgba(current + uOccupancy * v * v);
} else {
gl_FragColor = floatToRgba(v);
}
}
`;

View File

@@ -7,11 +7,14 @@
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { sphericalCollocation } from './collocation';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
// setDebugMode(true);
export function createSphericalCollocationGrid(
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
@@ -20,9 +23,9 @@ export function createSphericalCollocationGrid(
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeAlphaOrbitalsOnGPU(webgl)) {
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cubeGrid, orbital);
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
// console.timeEnd('gpu');
} else {
// console.time('cpu');

View File

@@ -19,6 +19,7 @@ import { Theme } from '../../mol-theme/theme';
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
import { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
@@ -49,9 +50,43 @@ const CreateOrbitalVolumeParamBase = {
{ atomCount: 25, spacing: 0.4 },
{ atomCount: 0, spacing: 0.35 },
]
}),
clampValues: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
on: PD.Group({
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
})
})
};
function clampData(matrix: Tensor.Data, min: number, max: number) {
for (let i = 0, _i = matrix.length; i < _i; i++) {
const v = matrix[i];
if (v < min) matrix[i] = min;
else if (v > max) matrix[i] = max;
}
}
function clampGrid(data: CubeGrid, v: number) {
const grid = data.grid;
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
// clamp values for better direct volume resolution
// current implementation uses Byte array for values
// if this is not enough, update mol* to use float
// textures instead
if (grid.stats.min < min || grid.stats.max > max) {
clampData(data.grid.cells.data, min, max);
if (grid.stats.min < min) {
(grid.stats.min as number) = min;
}
if (grid.stats.max > max) {
(grid.stats.max as number) = max;
}
}
}
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-volume',
display: 'Orbital Volume',
@@ -84,6 +119,10 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
@@ -112,6 +151,10 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}

View File

@@ -10,7 +10,6 @@ import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
import { Structure } from '../../mol-model/structure';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
import { MembraneOrientation } from './prop';
import { ThemeRegistryContext } from '../../mol-theme/theme';
@@ -27,6 +26,7 @@ import { MembraneOrientationProvider } from './prop';
import { MarkerActions } from '../../mol-util/marker-action';
import { lociLabel } from '../../mol-theme/label';
import { ColorNames } from '../../mol-util/color/names';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
const SharedParams = {
color: PD.Color(ColorNames.lightgrey),
@@ -61,7 +61,6 @@ export type BilayerRimsParams = typeof BilayerRimsParams
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
const MembraneOrientationVisuals = {
'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
};
@@ -91,9 +90,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation
factory: MembraneOrientationRepresentation,
getParams: getMembraneOrientationParams,
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
defaultColorTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.elementCount > 0
defaultColorTheme: { name: 'shape-group' },
defaultSizeTheme: { name: 'shape-group' },
isApplicable: (structure: Structure) => structure.elementCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
detach: (data) => MembraneOrientationProvider.ref(data, false)
}
});
function membraneLabel(data: Structure) {
@@ -151,28 +154,3 @@ function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal
MeshBuilder.addPrimitive(state, Mat4.id, circle);
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
}
function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> {
const { density } = props;
const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!;
const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius);
const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry);
getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius);
getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius);
return Shape.create('Bilayer spheres', data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data));
}
function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) {
Vec3.normalize(normalVector, normalVector);
const d = -Vec3.dot(normalVector, point);
const rep = Vec3();
for (let i = -1000, il = 1000; i < il; i += density) {
for (let j = -1000, jl = 1000; j < jl; j += density) {
Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]);
if (Vec3.squaredDistance(rep, point) < sqRadius) {
spheresBuilder.add(rep[0], rep[1], rep[2], 0);
}
}
}
}

View File

@@ -209,8 +209,8 @@ namespace Camera {
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
radius: 10,
radiusMax: 10,
radius: 0,
radiusMax: 0,
fog: 50,
clipFar: true
};

View File

@@ -47,7 +47,7 @@ export const Canvas3DParams = {
on: PD.Group(StereoCameraParams),
off: PD.Group({})
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
manualReset: PD.Boolean(false, { isHidden: true })
manualReset: PD.Boolean(false, { isHidden: true }),
}, { pivot: 'mode' }),
cameraFog: PD.MappedStatic('on', {
on: PD.Group({
@@ -117,6 +117,7 @@ interface Canvas3D {
notifyDidDraw: boolean,
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly commited: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
readonly resized: BehaviorSubject<any>
@@ -150,9 +151,10 @@ namespace Canvas3D {
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number, enableWboit: boolean }> = {}) {
const antialias = (attribs.antialias ?? true) && !attribs.enableWboit;
const gl = getGLContext(canvas, {
alpha: true,
antialias: (attribs.antialias ?? true) && !attribs.enableWboit,
antialias,
depth: true,
preserveDrawingBuffer: true,
premultipliedAlpha: true,
@@ -197,6 +199,14 @@ namespace Canvas3D {
if (isDebugMode) console.log('context restored');
}, false);
// disable postprocessing anti-aliasing if canvas anti-aliasing is enabled
if (antialias && !props.postprocessing?.antialiasing) {
props.postprocessing = {
...DefaultCanvas3DParams.postprocessing,
antialiasing: { name: 'off', params: {} }
};
}
return create(webgl, input, passes, props, { pixelScale });
}
@@ -209,6 +219,7 @@ namespace Canvas3D {
let startTime = now();
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const { gl, contextRestored } = webgl;
@@ -382,6 +393,7 @@ namespace Canvas3D {
draw(true);
forceDrawAfterAllCommited = false;
}
commited.next(now());
}
}
@@ -607,6 +619,7 @@ namespace Canvas3D {
get notifyDidDraw() { return notifyDidDraw; },
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
commited,
reprCount,
resized,
setProps: (properties, doNotRequestDraw = false) => {

View File

@@ -160,5 +160,5 @@ const instanceMaterialId = getNextMaterialId();
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false }, materialId);
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
}

View File

@@ -70,6 +70,7 @@ export class CameraHelper {
this.scene.clear();
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
this.renderObject = createAxesRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();

View File

@@ -72,6 +72,7 @@ export class HandleHelper {
this.scene.clear();
const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
this.renderObject = createHandleRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();

View File

@@ -193,7 +193,7 @@ export class MultiSamplePass {
const { x, y, width, height } = camera.viewport;
const sampleWeight = 1.0 / offsetList.length;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing) || props.postprocessing.antialiasing.name === 'on';
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
if (sampleIndex === -1) {
drawPass.render(renderer, camera, scene, helper, false, transparentBackground);

View File

@@ -110,7 +110,7 @@ export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
export class PostprocessingPass {
static isEnabled(props: PostprocessingProps) {
return props.occlusion.name === 'on' || props.outline.name === 'on';
return props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'on';
}
readonly target: RenderTarget
@@ -146,7 +146,7 @@ export class PostprocessingPass {
private updateState(camera: ICamera) {
const { gl, state } = this.webgl;
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);

View File

@@ -81,6 +81,7 @@ export namespace BaseGeometry {
colorOnly: false,
opaque,
writeDepth: opaque,
noClip: false,
};
}

View File

@@ -75,6 +75,8 @@ export namespace TextureMesh {
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
};
export type Params = typeof Params
@@ -126,6 +128,8 @@ export namespace TextureMesh {
dDoubleSided: ValueCell.create(props.doubleSided),
dFlatShaded: ValueCell.create(props.flatShaded),
dFlipSided: ValueCell.create(props.flipSided),
dIgnoreLight: ValueCell.create(props.ignoreLight),
dXrayShaded: ValueCell.create(props.xrayShaded),
dGeoTexture: ValueCell.create(true),
};
}
@@ -142,6 +146,8 @@ export namespace TextureMesh {
ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
if (values.drawCount.ref.value > values.aGroup.ref.value.length) {
// console.log('updating vertex ids in aGroup to handle larger drawCount')

View File

@@ -94,7 +94,8 @@ function createPoints() {
pickable: true,
colorOnly: false,
opaque: true,
writeDepth: true
writeDepth: true,
noClip: false,
};
return createRenderObject('points', values, state, -1);

View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
import { WebGLContext } from '../webgl/context';
import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common';
import shader_template from '../shader/util/grid3d-template.frag';
import quad_vert from '../shader/quad.vert';
import { ShaderCode } from '../shader-code';
import { UUID, ValueCell } from '../../mol-util';
import { objectForEach } from '../../mol-util/object';
import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform';
import { QuadSchema, QuadValues } from './util';
import { createComputeRenderItem } from '../webgl/render-item';
import { createComputeRenderable } from '../renderable';
import { isLittleEndian } from '../../mol-util/is-little-endian';
import { RuntimeContext } from '../../mol-task';
export function canComputeGrid3dOnGPU(webgl?: WebGLContext) {
return !!webgl?.extensions.textureFloat;
}
export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> {
schema: S,
// indicate which params are loop bounds for WebGL1 compat
loopBounds?: (keyof S)[]
utilCode?: string,
mainCode: string,
returnCode: string,
values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
cumulative?: {
states(params: P): CS[],
update(params: P, state: CS, values: Values<S>): void,
// call gl.readPixes every 'yieldPeriod' states to split the computation
// into multiple parts, if not set, the computation will be synchronous
yieldPeriod?: number
}
}
const FrameBufferName = 'grid3d-computable' as const;
const Texture0Name = 'grid3d-computable-0' as const;
const Texture1Name = 'grid3d-computable-1' as const;
const SchemaBase = {
...QuadSchema,
uDimensions: UniformSpec('v3'),
uMin: UniformSpec('v3'),
uDelta: UniformSpec('v3'),
uWidth: UniformSpec('f'),
uLittleEndian: UniformSpec('b'),
};
const CumulativeSumSchema = {
tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
};
export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
const id = UUID.create22();
const uniforms: string[] = [];
objectForEach(spec.schema, (u, k) => {
if (u.type === 'define') return;
if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported');
const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0;
if (isBound) uniforms.push(`#ifndef ${k}`);
if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`);
else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`);
if (isBound) uniforms.push(`#endif`);
});
const code = shader_template
.replace('{UNIFORMS}', uniforms.join('\n'))
.replace('{UTILS}', spec.utilCode ?? '')
.replace('{MAIN}', spec.mainCode)
.replace('{RETURN}', spec.returnCode);
const shader = ShaderCode(id, quad_vert, code);
return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => {
const schema: RenderableSchema = {
...SchemaBase,
...(spec.cumulative ? CumulativeSumSchema : {}),
...spec.schema,
};
if (!webgl.isWebGL2) {
if (spec.loopBounds) {
for (const b of spec.loopBounds) {
(schema as any)[b] = DefineSpec('number');
}
}
(schema as any)['WEBGL1'] = DefineSpec('boolean');
}
if (spec.cumulative) {
(schema as any)['CUMULATIVE'] = DefineSpec('boolean');
}
if (!webgl.namedFramebuffers[FrameBufferName]) {
webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[FrameBufferName];
if (!webgl.namedTextures[Texture0Name]) {
webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (spec.cumulative && !webgl.namedTextures[Texture1Name]) {
webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]];
const [nx, ny, nz] = grid.dimensions;
const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
const values: UnboxedValues<S & typeof SchemaBase> = {
uDimensions: grid.dimensions,
uMin: grid.box.min,
uDelta: getRegularGrid3dDelta(grid),
uWidth,
uLittleEndian: isLittleEndian(),
...spec.values(params, grid)
} as any;
if (!webgl.isWebGL2) {
(values as any).WEBGL1 = true;
}
if (spec.cumulative) {
(values as any).tCumulativeSum = tex[0];
(values as any).CUMULATIVE = true;
}
let renderable = webgl.namedComputeRenderables[id];
let cells: RenderableValues;
if (renderable) {
cells = renderable.values as RenderableValues;
objectForEach(values, (c, k) => {
const s = schema[k];
if (s?.type === 'value' || s?.type === 'attribute') return;
if (!s || !isUniformValueScalar(s.kind as any)) {
ValueCell.update(cells[k], c);
} else {
ValueCell.updateIfChanged(cells[k], c);
}
});
} else {
cells = {} as any;
objectForEach(QuadValues, (v, k) => (cells as any)[k] = v);
objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v));
renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells);
}
const array = new Uint8Array(uWidth * uWidth * 4);
if (spec.cumulative) {
const { gl } = webgl;
const states = spec.cumulative.states(params);
tex[0].define(uWidth, uWidth);
tex[1].define(uWidth, uWidth);
resetGl(webgl, uWidth);
gl.clearColor(0, 0, 0, 0);
tex[0].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
tex[1].attachFramebuffer(framebuffer, 'color0');
gl.clear(gl.COLOR_BUFFER_BIT);
if (spec.cumulative.yieldPeriod) {
await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
}
const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
for (let i = 0; i < states.length; i++) {
ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
tex[i % 2].attachFramebuffer(framebuffer, 'color0');
resetGl(webgl, uWidth);
spec.cumulative.update(params, states[i], cells as any);
renderable.update();
renderable.render();
if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
if (i % yieldPeriod === yieldPeriod - 1) {
webgl.readPixels(0, 0, 1, 1, array);
}
if (ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
}
} else {
tex[0].define(uWidth, uWidth);
tex[0].attachFramebuffer(framebuffer, 'color0');
framebuffer.bind();
resetGl(webgl, uWidth);
renderable.update();
renderable.render();
}
webgl.readPixels(0, 0, uWidth, uWidth, array);
return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
};
}
function resetGl(webgl: WebGLContext, w: number) {
const { gl, state } = webgl;
gl.viewport(0, 0, w, w);
gl.scissor(0, 0, w, w);
state.disable(gl.SCISSOR_TEST);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.depthMask(false);
}

View File

@@ -1,18 +1,18 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
import { createComputeRenderable } from '../../renderable';
import { WebGLContext } from '../../webgl/context';
import { createComputeRenderItem } from '../../webgl/render-item';
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
import { Texture } from '../../../mol-gl/webgl/texture';
import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { ValueCell } from '../../../mol-util';
import { QuadSchema, QuadValues } from '../util';
import { Vec2 } from '../../../mol-math/linear-algebra';
import { Vec2, Vec3 } from '../../../mol-math/linear-algebra';
import { getHistopyramidSum } from './sum';
import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
import { isPowerOfTwo } from '../../../mol-math/misc';
@@ -26,37 +26,44 @@ const HistopyramidReductionSchema = {
uTexSize: UniformSpec('f'),
};
let HistopyramidReductionRenderable: ComputeRenderable<Values<typeof HistopyramidReductionSchema>>;
const HistogramPyramidName = 'histogram-pyramid';
function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
if (HistopyramidReductionRenderable) {
ValueCell.update(HistopyramidReductionRenderable.values.tPreviousLevel, initialTexture);
HistopyramidReductionRenderable.update();
return HistopyramidReductionRenderable;
if (ctx.namedComputeRenderables[HistogramPyramidName]) {
const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
ValueCell.update(v.tPreviousLevel, initialTexture);
ctx.namedComputeRenderables[HistogramPyramidName].update();
} else {
const values: Values<typeof HistopyramidReductionSchema> = {
...QuadValues,
tPreviousLevel: ValueCell.create(initialTexture),
uSize: ValueCell.create(0),
uTexSize: ValueCell.create(0),
};
const schema = { ...HistopyramidReductionSchema };
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
return HistopyramidReductionRenderable;
ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, initialTexture);
}
return ctx.namedComputeRenderables[HistogramPyramidName];
}
function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
const values: Values<typeof HistopyramidReductionSchema> = {
...QuadValues,
tPreviousLevel: ValueCell.create(initialTexture),
uSize: ValueCell.create(0),
uTexSize: ValueCell.create(0),
};
const schema = { ...HistopyramidReductionSchema };
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
let textureFramebuffer = LevelTexturesFramebuffers[level];
let textureFramebuffer = LevelTexturesFramebuffers[level];
const size = Math.pow(2, level);
if (textureFramebuffer === undefined) {
const texture = ctx.resources.texture('image-float32', 'rgba', 'float', 'nearest');
const framebuffer = ctx.resources.framebuffer();
const texture = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
const framebuffer = getFramebuffer(`level${level}`, ctx);
texture.attachFramebuffer(framebuffer, 0);
textureFramebuffer = { texture, framebuffer };
textureFramebuffer.texture.define(size, size);
@@ -70,12 +77,28 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.disable(gl.CULL_FACE);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.colorMask(true, true, true, true);
state.clearColor(0, 0, 0, 0);
}
function getFramebuffer(name: string, webgl: WebGLContext): Framebuffer {
const _name = `${HistogramPyramidName}-${name}`;
if (!webgl.namedFramebuffers[_name]) {
webgl.namedFramebuffers[_name] = webgl.resources.framebuffer();
}
return webgl.namedFramebuffers[_name];
}
function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
const _name = `${HistogramPyramidName}-${name}`;
if (!webgl.namedTextures[_name]) {
webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
}
return webgl.namedTextures[_name];
}
export interface HistogramPyramid {
pyramidTex: Texture
count: number
@@ -84,8 +107,8 @@ export interface HistogramPyramid {
scale: Vec2
}
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
const { gl, resources } = ctx;
export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2, gridTexDim: Vec3): HistogramPyramid {
const { gl } = ctx;
// printTexture(ctx, inputTexture, 2)
if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
@@ -95,13 +118,14 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
// This part set the levels
const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2));
const maxSize = Math.pow(2, levels);
// console.log('levels', levels, 'maxSize', maxSize)
// console.log('levels', levels, 'maxSize', maxSize, 'input', inputTexture.getWidth());
const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
const pyramidTexture = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
pyramidTexture.define(maxSize, maxSize);
const framebuffer = resources.framebuffer();
const framebuffer = getFramebuffer('pyramid', ctx);
pyramidTexture.attachFramebuffer(framebuffer, 0);
gl.viewport(0, 0, maxSize, maxSize);
gl.clear(gl.COLOR_BUFFER_BIT);
const levelTexturesFramebuffers: TextureFramebuffer[] = [];
@@ -120,8 +144,6 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
const size = Math.pow(2, currLevel);
// console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, size, size);
ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
ValueCell.update(renderable.values.uTexSize, size);
@@ -130,6 +152,10 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
renderable.update();
}
ctx.state.currentRenderItemId = -1;
gl.viewport(0, 0, size, size);
gl.scissor(0, 0, size, size);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
renderable.render();
pyramidTexture.bind(0);

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createComputeRenderable, ComputeRenderable } from '../../renderable';
import { createComputeRenderable } from '../../renderable';
import { WebGLContext } from '../../webgl/context';
import { createComputeRenderItem } from '../../webgl/render-item';
import { Values, TextureSpec } from '../../renderable/schema';
@@ -21,33 +21,32 @@ const HistopyramidSumSchema = {
tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
};
let HistopyramidSumRenderable: ComputeRenderable<Values<typeof HistopyramidSumSchema>>;
const HistopyramidSumName = 'histopyramid-sum';
function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
if (HistopyramidSumRenderable) {
ValueCell.update(HistopyramidSumRenderable.values.tTexture, texture);
HistopyramidSumRenderable.update();
return HistopyramidSumRenderable;
if (ctx.namedComputeRenderables[HistopyramidSumName]) {
const v = ctx.namedComputeRenderables[HistopyramidSumName].values;
ValueCell.update(v.tTexture, texture);
ctx.namedComputeRenderables[HistopyramidSumName].update();
} else {
const values: Values<typeof HistopyramidSumSchema> = {
...QuadValues,
tTexture: ValueCell.create(texture),
};
const schema = { ...HistopyramidSumSchema };
const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
HistopyramidSumRenderable = createComputeRenderable(renderItem, values);
return HistopyramidSumRenderable;
ctx.namedComputeRenderables[HistopyramidSumName] = createHistopyramidSumRenderable(ctx, texture);
}
return ctx.namedComputeRenderables[HistopyramidSumName];
}
let SumTexture: Texture;
function getSumTexture(ctx: WebGLContext) {
if (SumTexture) return SumTexture;
SumTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
SumTexture.define(1, 1);
return SumTexture;
function createHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
const values: Values<typeof HistopyramidSumSchema> = {
...QuadValues,
tTexture: ValueCell.create(texture),
};
const schema = { ...HistopyramidSumSchema };
const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function setRenderingDefaults(ctx: WebGLContext) {
@@ -68,8 +67,16 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture);
ctx.state.currentRenderItemId = -1;
const framebuffer = resources.framebuffer();
const sumTexture = getSumTexture(ctx);
if (!ctx.namedFramebuffers[HistopyramidSumName]) {
ctx.namedFramebuffers[HistopyramidSumName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[HistopyramidSumName];
if (!ctx.namedTextures[HistopyramidSumName]) {
ctx.namedTextures[HistopyramidSumName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
ctx.namedTextures[HistopyramidSumName].define(1, 1);
}
const sumTexture = ctx.namedTextures[HistopyramidSumName];
sumTexture.attachFramebuffer(framebuffer, 0);
setRenderingDefaults(ctx);

View File

@@ -30,18 +30,36 @@ const ActiveVoxelsSchema = {
uScale: UniformSpec('v2'),
};
const ActiveVoxelsName = 'active-voxels';
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values;
ValueCell.update(v.uQuadScale, scale);
ValueCell.update(v.tVolumeData, volumeData);
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uScale, scale);
ctx.namedComputeRenderables[ActiveVoxelsName].update();
} else {
ctx.namedComputeRenderables[ActiveVoxelsName] = createActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, scale);
}
return ctx.namedComputeRenderables[ActiveVoxelsName];
}
function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
const values: Values<typeof ActiveVoxelsSchema> = {
...QuadValues,
uQuadScale: ValueCell.create(scale),
tTriCount: ValueCell.create(getTriCount()),
uQuadScale: ValueCell.create(scale),
tVolumeData: ValueCell.create(volumeData),
uIsoValue: ValueCell.create(isoValue),
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uScale: ValueCell.create(scale),
};
@@ -57,7 +75,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.disable(gl.CULL_FACE);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.colorMask(true, true, true, true);
state.clearColor(0, 0, 0, 0);
@@ -68,10 +86,16 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
const width = volumeData.getWidth();
const height = volumeData.getHeight();
const framebuffer = resources.framebuffer();
if (!ctx.namedFramebuffers[ActiveVoxelsName]) {
ctx.namedFramebuffers[ActiveVoxelsName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[ActiveVoxelsName];
framebuffer.bind();
const activeVoxelsTex = resources.texture('image-float32', 'rgba', 'float', 'nearest');
if (!ctx.namedTextures[ActiveVoxelsName]) {
ctx.namedTextures[ActiveVoxelsName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
const activeVoxelsTex = ctx.namedTextures[ActiveVoxelsName];
activeVoxelsTex.define(width, height);
const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale);
@@ -80,6 +104,9 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
activeVoxelsTex.attachFramebuffer(framebuffer, 0);
setRenderingDefaults(ctx);
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
renderable.render();
// console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim)

View File

@@ -38,18 +38,46 @@ const IsosurfaceSchema = {
uScale: UniformSpec('v2'),
};
const IsosurfaceName = 'isosurface';
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values;
ValueCell.update(v.uQuadScale, Vec2.create(1, height / Math.pow(2, levels)));
ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
ValueCell.update(v.tVolumeData, volumeData);
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
ValueCell.updateIfChanged(v.uLevels, levels);
ValueCell.updateIfChanged(v.uCount, count);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uGridTransform, transform);
ValueCell.update(v.uScale, scale);
ctx.namedComputeRenderables[IsosurfaceName].update();
} else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
// console.log('uSize', Math.pow(2, levels))
const values: Values<typeof IsosurfaceSchema> = {
...QuadValues,
uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
tTriIndices: ValueCell.create(getTriIndices()),
uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
tVolumeData: ValueCell.create(volumeData),
uIsoValue: ValueCell.create(isoValue),
uIsoValue: ValueCell.create(isoValue),
uSize: ValueCell.create(Math.pow(2, levels)),
uLevels: ValueCell.create(levels),
uCount: ValueCell.create(count),
@@ -57,7 +85,6 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uGridTransform: ValueCell.create(transform),
uScale: ValueCell.create(scale),
};
@@ -86,27 +113,23 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
// console.log('iso volumeData', volumeData)
const framebuffer = resources.framebuffer();
if (!ctx.namedFramebuffers[IsosurfaceName]) {
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
let needsClear = false;
const w = pyramidTex.getWidth();
const h = pyramidTex.getHeight();
if (!vertexGroupTexture) {
vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else if (vertexGroupTexture.getWidth() !== pyramidTex.getWidth() || vertexGroupTexture.getHeight() !== pyramidTex.getHeight()) {
vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else {
needsClear = true;
}
vertexGroupTexture.define(w, h);
if (!normalTexture) {
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else if (normalTexture.getWidth() !== pyramidTex.getWidth() || normalTexture.getHeight() !== pyramidTex.getHeight()) {
normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
} else {
needsClear = true;
}
normalTexture.define(w, h);
// const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
// infoTex.define(pyramidTex.width, pyramidTex.height)
@@ -147,11 +170,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
]);
setRenderingDefaults(ctx);
gl.viewport(0, 0, pyramidTex.getWidth(), pyramidTex.getHeight());
if (needsClear) gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, w, h);
gl.clear(gl.COLOR_BUFFER_BIT);
renderable.render();
gl.finish();
gl.flush();
// const vgt = readTexture(ctx, vertexGroupTexture, pyramidTex.width, height)
// console.log('vertexGroupTexture', vgt.array.subarray(0, 4 * count))

View File

@@ -21,6 +21,7 @@ export type RenderableState = {
colorOnly: boolean
opaque: boolean
writeDepth: boolean
noClip: boolean
}
export interface Renderable<T extends RenderableValues> {

View File

@@ -29,6 +29,7 @@ export type ValueKind = keyof ValueKindType
export type KindValue = UniformKindValue & DataTypeArrayType & TextureKindValue & ValueKindType
export type Values<S extends RenderableSchema> = { readonly [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> }
export type UnboxedValues<S extends RenderableSchema> = { readonly [k in keyof S]: KindValue[S[k]['kind']] }
export function splitValues(schema: RenderableSchema, values: RenderableValues) {
const attributeValues: AttributeValues = {};
@@ -102,6 +103,7 @@ export type RenderableSchema = {
}
export type RenderableValues = { readonly [k: string]: ValueCell<any> }
//
export const GlobalUniformSchema = {

View File

@@ -22,6 +22,8 @@ export const TextureMeshSchema = {
dFlatShaded: DefineSpec('boolean'),
dDoubleSided: DefineSpec('boolean'),
dFlipSided: DefineSpec('boolean'),
dIgnoreLight: DefineSpec('boolean'),
dXrayShaded: DefineSpec('boolean'),
dGeoTexture: DefineSpec('boolean'),
};
export type TextureMeshSchema = typeof TextureMeshSchema

View File

@@ -76,6 +76,7 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
img.style.top = '0px';
img.style.left = '0px';
img.style.border = 'solid grey';
img.style.pointerEvents = 'none';
document.body.appendChild(img);
}, 'image/png');
}

View File

@@ -20,6 +20,7 @@ import { stringToWords } from '../mol-util/string';
import { degToRad } from '../mol-math/misc';
import { createNullTexture, Texture, Textures } from './webgl/texture';
import { arrayMapUpsert } from '../mol-util/array';
import { clamp } from '../mol-math/interpolate';
export interface RendererStats {
programCount: number
@@ -261,13 +262,20 @@ namespace Renderer {
}
let definesNeedUpdate = false;
if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
definesNeedUpdate = true;
}
if (r.values.dClipVariant.ref.value !== clip.variant) {
ValueCell.update(r.values.dClipVariant, clip.variant);
definesNeedUpdate = true;
if (r.state.noClip) {
if (r.values.dClipObjectCount.ref.value !== 0) {
ValueCell.update(r.values.dClipObjectCount, 0);
definesNeedUpdate = true;
}
} else {
if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
definesNeedUpdate = true;
}
if (r.values.dClipVariant.ref.value !== clip.variant) {
ValueCell.update(r.values.dClipVariant, clip.variant);
definesNeedUpdate = true;
}
}
if (definesNeedUpdate) r.update();
@@ -474,8 +482,11 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle on renderable.state???
if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit');
}
}
@@ -487,8 +498,11 @@ namespace Renderer {
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle on renderable.state???
if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorWboit');
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -31,7 +31,7 @@ uniform float uCurrentX;
uniform float uCurrentY;
uniform float uAlpha;
uniform float uResolution;
uniform float uRadiusFactor;
uniform float uRadiusFactorInv;
void main() {
vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
@@ -40,16 +40,16 @@ void main() {
#if defined(dCalcType_density)
float density = exp(-uAlpha * ((dist * dist) * vRadiusSqInv));
gl_FragColor.a = density / uRadiusFactor;
gl_FragColor.a = density * uRadiusFactorInv;
#elif defined(dCalcType_minDistance)
gl_FragColor.a = 1.0 - dist / uRadiusFactor;
gl_FragColor.a = 1.0 - dist * uRadiusFactorInv;
#elif defined(dCalcType_groupId)
#if defined(dGridTexType_2d)
float minDistance = 1.0 - texture2D(tMinDistanceTex, (gl_FragCoord.xy) / (uGridTexDim.xy / uGridTexScale)).a;
#elif defined(dGridTexType_3d)
float minDistance = 1.0 - texelFetch(tMinDistanceTex, ivec3(gl_FragCoord.xy, uCurrentSlice), 0).a;
#endif
if (dist / uRadiusFactor > minDistance + uResolution * 0.05)
if (dist * uRadiusFactorInv > minDistance + uResolution * 0.05)
discard;
gl_FragColor.rgb = encodeFloatRGB(vGroup);
#endif

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -21,7 +21,6 @@ varying float vRadiusSqInv;
uniform vec3 uBboxSize;
uniform vec3 uBboxMin;
uniform float uCurrentSlice;
uniform float uResolution;
void main() {
@@ -29,7 +28,7 @@ void main() {
#if defined(dCalcType_groupId)
vGroup = aGroup;
#endif
gl_PointSize = floor(((aRadius * 6.0) / uResolution) + 0.5);
gl_PointSize = ceil(((aRadius * 3.0) / uResolution) + uResolution);
vPosition = (aPosition - uBboxMin) / uResolution;
gl_Position = vec4(((aPosition - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
}

View File

@@ -0,0 +1,181 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default `
precision highp float;
precision highp int;
precision highp sampler2D;
uniform vec2 uQuadShift;
uniform vec3 uDimensions;
uniform vec3 uMin;
uniform vec3 uDelta;
uniform bool uLittleEndian;
uniform float uWidth;
#ifdef CUMULATIVE
uniform sampler2D tCumulativeSum;
#endif
{UNIFORMS}
{UTILS}
//////////////////////////////////////////////////////////
// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba
// MIT License, Copyright (c) 2020 Equinor
float shiftRight (float v, float amt) {
v = floor(v) + 0.5;
return floor(v / exp2(amt));
}
float shiftLeft (float v, float amt) {
return floor(v * exp2(amt) + 0.5);
}
float maskLast (float v, float bits) {
return mod(v, shiftLeft(1.0, bits));
}
float extractBits (float num, float from, float to) {
from = floor(from + 0.5); to = floor(to + 0.5);
return maskLast(shiftRight(num, from), to - from);
}
vec4 floatToRgba(float texelFloat) {
if (texelFloat == 0.0) return vec4(0, 0, 0, 0);
float sign = texelFloat > 0.0 ? 0.0 : 1.0;
texelFloat = abs(texelFloat);
float exponent = floor(log2(texelFloat));
float biased_exponent = exponent + 127.0;
float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0;
float t = biased_exponent / 2.0;
float last_bit_of_biased_exponent = fract(t) * 2.0;
float remaining_bits_of_biased_exponent = floor(t);
float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0;
float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0;
float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0;
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
return (
uLittleEndian
? vec4(byte4, byte3, byte2, byte1)
: vec4(byte1, byte2, byte3, byte4)
);
}
///////////////////////////////////////////////////////
// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float
// BSD 3-Clause License
//
// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#ifdef CUMULATIVE
ivec4 floatsToBytes(vec4 inputFloats) {
ivec4 bytes = ivec4(inputFloats * 255.0);
return (
uLittleEndian
? bytes.abgr
: bytes
);
}
// Break the four bytes down into an array of 32 bits.
void bytesToBits(const in ivec4 bytes, out bool bits[32]) {
for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {
float acc = float(bytes[channelIndex]);
for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {
float powerOfTwo = exp2(float(indexInByte));
bool bit = acc >= powerOfTwo;
bits[channelIndex * 8 + (7 - indexInByte)] = bit;
acc = mod(acc, powerOfTwo);
}
}
}
// Compute the exponent of the 32-bit float.
float getExponent(bool bits[32]) {
const int startIndex = 1;
const int bitStringLength = 8;
const int endBeforeIndex = startIndex + bitStringLength;
float acc = 0.0;
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Compute the mantissa of the 32-bit float.
float getMantissa(bool bits[32], bool subnormal) {
const int startIndex = 9;
const int bitStringLength = 23;
const int endBeforeIndex = startIndex + bitStringLength;
// Leading/implicit/hidden bit convention:
// If the number is not subnormal (with exponent 0), we add a leading 1 digit.
float acc = float(!subnormal) * exp2(float(bitStringLength));
int pow2 = bitStringLength - 1;
for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {
acc += float(bits[bitIndex]) * exp2(float(pow2--));
}
return acc;
}
// Parse the float from its 32 bits.
float bitsToFloat(bool bits[32]) {
float signBit = float(bits[0]) * -2.0 + 1.0;
float exponent = getExponent(bits);
bool subnormal = abs(exponent - 0.0) < 0.01;
float mantissa = getMantissa(bits, subnormal);
float exponentBias = 127.0;
return signBit * mantissa * exp2(exponent - exponentBias - 23.0);
}
float rgbaToFloat(vec4 texelRGBA) {
ivec4 rgbaBytes = floatsToBytes(texelRGBA);
bool bits[32];
bytesToBits(rgbaBytes, bits);
return bitsToFloat(bits);
}
#endif
///////////////////////////////////////////////////////
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)); }
void main(void) {
float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth;
// axis order fast to slow Z, Y, X
// TODO: support arbitrary axis orders?
float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z);
float j = intMod(kk, uDimensions.y);
float i = intDiv(kk, uDimensions.y);
vec3 xyz = uMin + uDelta * vec3(i, j, k);
{MAIN}
#ifdef CUMULATIVE
float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth)));
#endif
gl_FragColor = floatToRgba({RETURN});
}
`;

View File

@@ -79,4 +79,30 @@ export function getUniformSetters(schema: RenderableSchema) {
}
});
return setters;
}
export function getUniformGlslType(kind: UniformKind): string {
switch (kind) {
case 'f': return 'float';
case 'i': return 'int';
case 't': return 'sampler2D';
case 'b': return 'bool';
case 'v2': return 'vec2';
case 'v3': return 'vec3';
case 'v4': return 'vec4';
case 'm3': return 'mat3';
case 'm4': return 'mat4';
}
throw new Error(`${kind} has no primitive GLSL type.`);
}
export function isUniformValueScalar(kind: UniformKind): boolean {
switch (kind) {
case 'f':
case 'i':
case 'b':
return true;
default:
return false;
}
}

View File

@@ -1,57 +0,0 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ReaderResult as Result } from '../result';
import { Task } from '../../../mol-task';
import { parseCsv } from '../csv/parser';
import { Column, Table } from '../../../mol-data/db';
import { toTable } from '../cif/schema';
import Schema = Column.Schema
import { CsvTable } from '../csv/data-model';
export const Schema3DG = {
/** Chromosome name */
chromosome: Schema.str,
/** Base position */
position: Schema.int,
/** X coordinate */
x: Schema.float,
/** Y coordinate */
y: Schema.float,
/** Z coordinate */
z: Schema.float,
};
export type Schema3DG = typeof Schema3DG
export interface File3DG {
table: Table<Schema3DG>
}
const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ];
function categoryFromTable(name: string, table: CsvTable) {
return {
name,
rowCount: table.rowCount,
fieldNames: FieldNames,
getField: (name: string) => {
return table.getColumn(FieldNames.indexOf(name).toString());
}
};
}
export function parse3DG(data: string) {
return Task.create<Result<File3DG>>('Parse 3DG', async ctx => {
const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true };
const csvFile = await parseCsv(data, opts).runInContext(ctx);
if (csvFile.isError) return Result.error(csvFile.message, csvFile.line);
const category = categoryFromTable('3dg', csvFile.result.table);
const table = toTable(Schema3DG, category);
return Result.success({ table });
});
}

View File

@@ -1,33 +0,0 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { parse3DG } from '../3dg/parser';
const basic3dgString = `1(mat) 1420000 0.791377837067 10.9947291355 -13.1882897693
1(mat) 1440000 -0.268241283699 10.5200875887 -13.0896257278
1(mat) 1460000 -1.3853075236 10.5513787498 -13.1440142173
1(mat) 1480000 -1.55984101733 11.4340829129 -13.6026301209
1(mat) 1500000 -0.770991778399 11.4758488546 -14.5881137222
1(mat) 1520000 -0.0848245107875 12.2624690808 -14.354289628
1(mat) 1540000 -0.458643807046 12.5985791771 -13.4701149287
1(mat) 1560000 -0.810322906201 12.2461643989 -12.3172933413
1(mat) 1580000 -2.08211172035 12.8886838656 -12.8742007778
1(mat) 1600000 -3.52093948201 13.1850935438 -12.4118684428`;
describe('3dg reader', () => {
it('basic', async () => {
const parsed = await parse3DG(basic3dgString).run();
expect(parsed.isError).toBe(false);
if (parsed.isError) return;
const { chromosome, position, x, y, z } = parsed.result.table;
expect(chromosome.value(0)).toBe('1(mat)');
expect(position.value(1)).toBe(1440000);
expect(x.value(5)).toBe(-0.0848245107875);
expect(y.value(5)).toBe(12.2624690808);
expect(z.value(5)).toBe(-14.354289628);
});
});

View File

@@ -37,6 +37,15 @@ export type DensityTextureData = {
gridTexScale: Vec2
}
export interface RegularGrid3d {
box: Box3D,
dimensions: Vec3
}
export function getRegularGrid3dDelta({ box, dimensions }: RegularGrid3d) {
return Vec3.div(Vec3(), Box3D.size(Vec3(), box), Vec3.subScalar(Vec3(), dimensions, 1));
}
export function fillGridDim(length: number, start: number, step: number) {
const a = new Float32Array(length);
for (let i = 0; i < a.length; i++) {

View File

@@ -4,9 +4,9 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Box3D } from '../geometry';
import { Box3D, DensityData, DensityTextureData } from '../geometry';
import { RuntimeContext, Task } from '../../mol-task';
import { PositionData, DensityData } from './common';
import { PositionData } from './common';
import { GaussianDensityCPU } from './gaussian-density/cpu';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Texture } from '../../mol-gl/webgl/texture';
@@ -33,13 +33,21 @@ export const DefaultGaussianDensityProps = {
};
export type GaussianDensityProps = typeof DefaultGaussianDensityProps
export type GaussianDensityData = {
radiusFactor: number
} & DensityData
export type GaussianDensityTextureData = {
radiusFactor: number
} & DensityTextureData
export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl?: WebGLContext) {
return Task.create('Gaussian Density', async ctx => {
return await GaussianDensity(ctx, position, box, radius, props, webgl);
});
}
export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl?: WebGLContext): Promise<DensityData> {
export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl?: WebGLContext): Promise<GaussianDensityData> {
if (props.useGpu) {
if (!GaussianDensityGPU) throw 'GPU computation not supported on this platform';
if (!webgl) throw 'No WebGL context provided';
@@ -65,7 +73,7 @@ function _computeGaussianDensityTexture(type: '2d' | '3d', position: PositionDat
if (!GaussianDensityTexture) throw 'GPU computation not supported on this platform';
return Task.create('Gaussian Density', async ctx => {
return type === '2d' ?
GaussianDensityTexture2d(webgl, position, box, radius, props, texture) :
GaussianDensityTexture2d(webgl, position, box, radius, false, props, texture) :
GaussianDensityTexture3d(webgl, position, box, radius, props, texture);
});
}

View File

@@ -7,12 +7,12 @@
import { Box3D, fillGridDim } from '../../geometry';
import { Vec3, Mat4, Tensor } from '../../linear-algebra';
import { RuntimeContext } from '../../../mol-task';
import { PositionData, DensityData } from '../common';
import { PositionData } from '../common';
import { OrderedSet } from '../../../mol-data/int';
import { GaussianDensityProps } from '../gaussian-density';
import { GaussianDensityProps, GaussianDensityData } from '../gaussian-density';
import { fasterExp } from '../../approx';
export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<GaussianDensityData> {
const { resolution, radiusOffset, smoothness } = props;
const scaleFactor = 1 / resolution;
@@ -129,5 +129,5 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
Mat4.setTranslation(transform, expandedBox.min);
return { field, idField, transform };
return { field, idField, transform, radiusFactor: 1 };
}

View File

@@ -1,13 +1,13 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
*/
import { PositionData, DensityData, DensityTextureData } from '../common';
import { PositionData } from '../common';
import { Box3D } from '../../geometry';
import { GaussianDensityGPUProps } from '../gaussian-density';
import { GaussianDensityGPUProps, GaussianDensityData, GaussianDensityTextureData } from '../gaussian-density';
import { OrderedSet } from '../../../mol-data/int';
import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra';
import { ValueCell } from '../../../mol-util';
@@ -40,7 +40,7 @@ export const GaussianDensitySchema = {
uGridTexScale: UniformSpec('v2', true),
uAlpha: UniformSpec('f', true),
uResolution: UniformSpec('f', true),
uRadiusFactor: UniformSpec('f', true),
uRadiusFactorInv: UniformSpec('f', true),
tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
dGridTexType: DefineSpec('string', ['2d', '3d']),
@@ -68,35 +68,35 @@ function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format
return webgl.namedTextures[_name];
}
export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData {
export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): GaussianDensityData {
// always use texture2d when the gaussian density needs to be downloaded from the GPU,
// it's faster than texture3d
// console.time('GaussianDensityTexture2d')
const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, tmpTexture);
const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
// webgl.waitForGpuCommandsCompleteSync()
// console.timeEnd('GaussianDensityTexture2d')
const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
return { field, idField, transform: getTransform(scale, bbox) };
return { field, idField, transform: getTransform(scale, bbox), radiusFactor };
}
export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
return webgl.isWebGL2 ?
GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) :
GaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture);
GaussianDensityTexture2d(webgl, position, box, radius, false, props, oldTexture);
}
export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture));
export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture));
}
export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
}
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale }: GaussianDensityTextureData): DensityTextureData {
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale };
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor }: _GaussianDensityTextureData): GaussianDensityTextureData {
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor };
}
function getTransform(scale: Vec3, bbox: Box3D) {
@@ -108,30 +108,34 @@ function getTransform(scale: Vec3, bbox: Box3D) {
//
type GaussianDensityTextureData = {
type _GaussianDensityTextureData = {
texture: Texture,
scale: Vec3,
bbox: Box3D,
gridDim: Vec3,
gridTexDim: Vec3
gridTexScale: Vec2
radiusFactor: number
}
function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
// console.log('2d');
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
const { smoothness, resolution } = props;
const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
const [ dx, dy, dz ] = dim;
const { texDimX, texDimY, texCols } = getTexture2dSize(dim);
// console.log({ texDimX, texDimY, texCols, texSize, dim });
const { texDimX, texDimY, texCols, powerOfTwoSize } = getTexture2dSize(dim);
// console.log({ texDimX, texDimY, texCols, powerOfTwoSize, dim });
const gridTexDim = Vec3.create(texDimX, texDimY, 0);
const gridTexScale = Vec2.create(texDimX / texDimX, texDimY / texDimY);
const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize);
const radiusFactor = maxRadius * 2;
const width = powerOfTwo ? powerOfTwoSize : texDimX;
const height = powerOfTwo ? powerOfTwoSize : texDimY;
const minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest');
minDistTex.define(texDimX, texDimY);
minDistTex.define(width, height);
const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
@@ -146,14 +150,19 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
if (!texture) texture = colorBufferFloat && textureFloat
? resources.texture('image-float32', 'rgba', 'float', 'linear')
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
texture.define(texDimX, texDimY);
texture.define(width, height);
// console.log(renderable)
function render(fbTex: Texture, clear: boolean) {
state.currentRenderItemId = -1;
fbTex.attachFramebuffer(framebuffer, 0);
if (clear) gl.clear(gl.COLOR_BUFFER_BIT);
if (clear) {
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
}
ValueCell.update(uCurrentY, 0);
let currCol = 0;
let currY = 0;
let currX = 0;
@@ -164,10 +173,11 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
currX = 0;
ValueCell.update(uCurrentY, currY);
}
// console.log({ i, currX, currY })
// console.log({ i, currX, currY });
ValueCell.update(uCurrentX, currX);
ValueCell.update(uCurrentSlice, i);
gl.viewport(currX, currY, dx, dy);
gl.scissor(currX, currY, dx, dy);
renderable.render();
++currCol;
currX += dx;
@@ -184,12 +194,12 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
setupGroupIdRendering(webgl, renderable);
render(texture, false);
// printTexture(webgl, texture, 1);
// printTexture(webgl, minDistTex, 0.75);
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale };
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor };
}
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
// console.log('3d');
const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
const { smoothness, resolution } = props;
@@ -213,6 +223,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
framebuffer.bind();
setRenderingDefaults(webgl);
gl.viewport(0, 0, dx, dy);
gl.scissor(0, 0, dx, dy);
if (!texture) texture = colorBufferFloat && textureFloat
? resources.texture('volume-float32', 'rgba', 'float', 'linear')
@@ -239,7 +250,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
setupGroupIdRendering(webgl, renderable);
render(texture, false);
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale };
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor };
}
//
@@ -303,7 +314,7 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po
ValueCell.update(v.uGridTexScale, gridTexScale);
ValueCell.updateIfChanged(v.uAlpha, smoothness);
ValueCell.updateIfChanged(v.uResolution, resolution);
ValueCell.updateIfChanged(v.uRadiusFactor, radiusFactor);
ValueCell.updateIfChanged(v.uRadiusFactorInv, 1 / radiusFactor);
ValueCell.update(v.tMinDistanceTex, minDistanceTexture);
ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d');
@@ -337,7 +348,7 @@ function createGaussianDensityRenderable(webgl: WebGLContext, drawCount: number,
uGridTexScale: ValueCell.create(gridTexScale),
uAlpha: ValueCell.create(smoothness),
uResolution: ValueCell.create(resolution),
uRadiusFactor: ValueCell.create(radiusFactor),
uRadiusFactorInv: ValueCell.create(1 / radiusFactor),
tMinDistanceTex: ValueCell.create(minDistanceTexture),
dGridTexType: ValueCell.create(minDistanceTexture.getDepth() > 0 ? '3d' : '2d'),
@@ -355,7 +366,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.disable(gl.CULL_FACE);
state.enable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.clearColor(0, 0, 0, 0);
}
@@ -409,7 +420,8 @@ function getTexture2dSize(gridDim: Vec3) {
} else {
texDimX = gridDim[0] * gridDim[2];
}
return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY };
// console.log(texDimX, texDimY, texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2);
return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
}
export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {

View File

@@ -1,83 +0,0 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Task } from '../../mol-task';
import { ModelFormat } from '../format';
import { Column, Table } from '../../mol-data/db';
import { EntityBuilder } from './common/entity';
import { File3DG } from '../../mol-io/reader/3dg/parser';
import { fillSerial } from '../../mol-util/array';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { BasicSchema, createBasic } from './basic/schema';
import { createModels } from './basic/parser';
import { Trajectory } from '../../mol-model/structure';
function getBasic(table: File3DG['table']) {
const entityIds = new Array<string>(table._rowCount);
const entityBuilder = new EntityBuilder();
const seqIdStarts = table.position.toArray({ array: Uint32Array });
const seqIdEnds = new Uint32Array(table._rowCount);
const stride = seqIdStarts[1] - seqIdStarts[0];
const objectRadius = stride / 3500;
for (let i = 0, il = table._rowCount; i < il; ++i) {
const chr = table.chromosome.value(i);
const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr);
entityIds[i] = entityId;
seqIdEnds[i] = seqIdStarts[i] + stride - 1;
}
const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, {
id: Column.ofIntArray(fillSerial(new Uint32Array(table._rowCount))),
entity_id: Column.ofStringArray(entityIds),
seq_id_begin: Column.ofIntArray(seqIdStarts),
seq_id_end: Column.ofIntArray(seqIdEnds),
asym_id: table.chromosome,
Cartn_x: Column.ofFloatArray(Column.mapToArray(table.x, x => x * 10, Float32Array)),
Cartn_y: Column.ofFloatArray(Column.mapToArray(table.y, y => y * 10, Float32Array)),
Cartn_z: Column.ofFloatArray(Column.mapToArray(table.z, z => z * 10, Float32Array)),
object_radius: Column.ofConst(objectRadius, table._rowCount, Column.Schema.float),
rmsf: Column.ofConst(0, table._rowCount, Column.Schema.float),
model_id: Column.ofConst(1, table._rowCount, Column.Schema.int),
}, table._rowCount);
return createBasic({
entity: entityBuilder.getEntityTable(),
ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
model_id: Column.ofIntArray([1]),
model_name: Column.ofStringArray(['3DG Model']),
}, 1),
ihm_sphere_obj_site
});
}
//
export { Format3dg };
type Format3dg = ModelFormat<File3DG>
namespace Format3dg {
export function is(x: ModelFormat): x is Format3dg {
return x.kind === '3dg';
}
export function from3dg(file3dg: File3DG): Format3dg {
return { kind: '3dg', name: '3DG', data: file3dg };
}
}
export function trajectoryFrom3DG(file3dg: File3DG): Task<Trajectory> {
return Task.create('Parse 3DG', async ctx => {
const format = Format3dg.from3dg(file3dg);
const basic = getBasic(file3dg.table);
return createModels(basic, format, ctx);
});
}

View File

@@ -18,6 +18,7 @@ import { AtomSiteAnisotrop } from './property/anisotropic';
import { ComponentBond } from './property/bonds/chem_comp';
import { StructConn } from './property/bonds/struct_conn';
import { Trajectory } from '../../mol-model/structure';
import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
function modelSymmetryFromMmcif(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return;
@@ -69,6 +70,8 @@ function structConnFromMmcif(model: Model) {
}
StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif);
GlobalModelTransformInfo.Provider.formatRegistry.add('mmCIF', GlobalModelTransformInfo.fromMmCif, GlobalModelTransformInfo.hasData);
//
export { MmcifFormat };

View File

@@ -83,13 +83,16 @@ export const _atom_site: CifCategory<CifExportContext> = {
}
};
function prepostfixed(prefix: string | undefined, postfix: string | undefined, name: string) {
if (prefix && postfix) return `${prefix}_${name}_${postfix}`;
function prepostfixed(prefix: string | undefined, name: string) {
if (prefix) return `${prefix}_${name}`;
if (postfix) return `${name}_${postfix}`;
return name;
}
function prefixedInsCode(prefix: string | undefined) {
if (!prefix) return 'pdbx_PDB_ins_code';
return `pdbx_${prefix}_PDB_ins_code`;
}
function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement.Location, prop: (e: StructureElement.Location) => any) {
return (k: K, d: D) => prop(loc(k, d));
}
@@ -102,15 +105,14 @@ function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: (
export interface IdFieldsOptions {
prefix?: string,
postfix?: string,
includeModelNum?: boolean
}
export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
const prefix = options && options.prefix, postfix = options && options.postfix;
const prefix = options && options.prefix;
const ret = CifWriter.fields<K, D>()
.str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
.int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
.str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
.int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
encoder: E.deltaRLE,
valueKind: (k, d) => {
const e = getLocation(k, d);
@@ -118,45 +120,45 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur
return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
}
})
.str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
.str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
.str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
.int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
.str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
.int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
addModelNum(ret, getLocation, options);
return ret.getFields();
}
export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
const prefix = options && options.prefix, postfix = options && options.postfix;
const prefix = options && options.prefix;
const ret = CifField.build<K, D>()
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
addModelNum(ret, getLocation, options);
return ret.getFields();
}
export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
const prefix = options && options.prefix, postfix = options && options.postfix;
const prefix = options && options.prefix;
const ret = CifField.build<K, D>()
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id));
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id));
addModelNum(ret, getLocation, options);
return ret.getFields();
}
export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
const prefix = options && options.prefix, postfix = options && options.postfix;
const prefix = options && options.prefix;
const ret = CifWriter.fields<K, D>()
.str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
.str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
.int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
.str(prepostfixed(prefix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
.str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
.int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
encoder: E.deltaRLE,
valueKind: (k, d) => {
const e = getLocation(k, d);
@@ -164,16 +166,16 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl
return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
}
})
.str(prepostfixed(prefix, postfix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id))
.str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
.str(prepostfixed(prefix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id))
.str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code))
.str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
.str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
.str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
.str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
.str(prepostfixed(prefix, postfix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
.str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
.int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
.str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
.str(prepostfixed(prefix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
.str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
.int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
.str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
addModelNum(ret, getLocation, options);
return ret.getFields();

View File

@@ -132,7 +132,8 @@ function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifE
type encode_mmCIF_categories_Params = {
skipCategoryNames?: Set<string>,
exportCtx?: CifExportContext,
copyAllCategories?: boolean
copyAllCategories?: boolean,
customProperties?: CustomPropertyDescriptor[]
}
/** Doesn't start a data block */
@@ -144,7 +145,7 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
encode_mmCIF_categories_copyAll(encoder, ctx);
encode_mmCIF_categories_copyAll(encoder, ctx, params);
} else {
encode_mmCIF_categories_default(encoder, ctx, params);
}
@@ -168,6 +169,14 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
}
}
if (params?.customProperties) {
for (const customProp of params?.customProperties) {
for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
encoder.writeCategory(cat, propCtx);
}
}
}
for (const s of ctx.structures) {
if (!s.hasCustomProperties) continue;
for (const customProp of s.customPropertyDescriptors.all) {
@@ -178,7 +187,7 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
}
}
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) {
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) {
const providedCategories = new Map<string, CifExportCategoryInfo>();
for (const cat of Categories) {
@@ -188,12 +197,21 @@ function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExp
const mapping = atom_site_operator_mapping(ctx);
if (mapping) providedCategories.set(mapping[0].name, mapping);
const _params = params || { };
for (const customProp of ctx.firstModel.customProperties.all) {
for (const info of getCustomPropCategories(customProp, ctx)) {
for (const info of getCustomPropCategories(customProp, ctx, _params)) {
providedCategories.set(info[0].name, info);
}
}
if (params?.customProperties) {
for (const customProp of params?.customProperties) {
for (const info of getCustomPropCategories(customProp, ctx, _params)) {
providedCategories.set(info[0].name, info);
}
}
}
for (const s of ctx.structures) {
if (!s.hasCustomProperties) continue;
for (const customProp of s.customPropertyDescriptors.all) {

View File

@@ -1,7 +0,0 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
// TODO add access to things like MOL2 charge ...

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Mat4, Tensor } from '../../../../mol-math/linear-algebra';
import { FormatPropertyProvider } from '../../../../mol-model-formats/structure/common/property';
import { CustomPropertyDescriptor } from '../../../custom-property';
import { CifExportContext } from '../../structure';
import { Model } from '../model';
import { Column, Table } from '../../../../mol-data/db';
import { CifWriter } from '../../../../mol-io/writer/cif';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { toTable } from '../../../../mol-io/reader/cif/schema';
export namespace GlobalModelTransformInfo {
const CategoryName = 'molstar_global_model_transform_info' as const;
export const Schema = {
[CategoryName]: {
matrix: Column.Schema.Matrix(4, 4, Column.Schema.float)
}
};
export type Schema = typeof Schema
export const Descriptor = CustomPropertyDescriptor({
name: CategoryName,
cifExport: {
categories: [{
name: CategoryName,
instance(ctx: CifExportContext) {
const mat = get(ctx.firstModel);
if (!mat) return CifWriter.Category.Empty;
const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: mat as unknown as Tensor.Data }]);
return CifWriter.Category.ofTable(table);
}
}],
prefix: 'molstar'
}
});
export const Provider = FormatPropertyProvider.create<Mat4>(Descriptor);
export function attach(model: Model, matrix: Mat4) {
if (!model.customProperties.has(Descriptor)) {
model.customProperties.add(Descriptor);
}
Provider.set(model, matrix);
}
export function get(model: Model): Mat4 | undefined {
return Provider.get(model);
}
export function fromMmCif(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return;
const cat = model.sourceData.data.frame.categories[CategoryName];
if (!cat) return;
const table = toTable(Schema[CategoryName], cat);
if (table._rowCount === 0) return;
return table.matrix.value(0) as unknown as Mat4;
}
export function hasData(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false;
const cat = model.sourceData.data.frame.categories[CategoryName];
return !!cat && cat.rowCount > 0;
}
export function writeMmCif(encoder: CifWriter.Encoder, matrix: Mat4) {
encoder.writeCategory({
name: CategoryName,
instance() {
const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: matrix as unknown as Tensor.Data }]);
return CifWriter.Category.ofTable(table);
}
});
}
}

View File

@@ -7,7 +7,7 @@
import { UniqueArray } from '../../../../mol-data/generic';
import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
import Structure from '../structure';
import Unit from '../unit';
@@ -489,7 +489,7 @@ export namespace Loci {
const boundaryHelper = new BoundaryHelper('98');
const tempPosBoundary = Vec3();
export function getBoundary(loci: Loci): Boundary {
export function getBoundary(loci: Loci, transform?: Mat4): Boundary {
boundaryHelper.reset();
for (const e of loci.elements) {
@@ -499,6 +499,7 @@ export namespace Loci {
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
const eI = elements[OrderedSet.getAt(indices, i)];
pos(eI, tempPosBoundary);
if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
}
}
@@ -510,6 +511,7 @@ export namespace Loci {
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
const eI = elements[OrderedSet.getAt(indices, i)];
pos(eI, tempPosBoundary);
if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
}
}

View File

@@ -97,7 +97,7 @@ class Structure {
/** Count of all bonds (intra- and inter-unit) in the structure */
get bondCount() {
if (!this._props.bondCount) {
if (this._props.bondCount === -1) {
this._props.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
}
return this._props.bondCount;

View File

@@ -162,7 +162,7 @@ namespace Bond {
let count = 0;
for (let i = 0, il = structure.units.length; i < il; ++i) {
const u = structure.units[i];
if (Unit.isAtomic(u)) count += u.bonds.edgeCount / 2; // only count one direction
if (Unit.isAtomic(u)) count += u.bonds.edgeCount;
}
return count;
}

View File

@@ -113,15 +113,6 @@ export const GroProvider: TrajectoryFormatProvider = {
visuals: defaultVisuals
};
export const Provider3dg: TrajectoryFormatProvider = {
label: '3DG',
description: '3DG',
category: TrajectoryFormatCategory,
stringExtensions: ['3dg'],
parse: directTrajectory(StateTransforms.Model.TrajectoryFrom3DG),
visuals: defaultVisuals
};
export const MolProvider: TrajectoryFormatProvider = {
label: 'MOL/SDF',
description: 'MOL/SDF',
@@ -146,7 +137,6 @@ export const BuiltInTrajectoryFormats = [
['pdb', PdbProvider] as const,
['pdbqt', PdbqtProvider] as const,
['gro', GroProvider] as const,
['3dg', Provider3dg] as const,
['mol', MolProvider] as const,
['mol2', Mol2Provider] as const,
] as const;

View File

@@ -5,7 +5,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { File3DG } from '../mol-io/reader/3dg/parser';
import { Ccp4File } from '../mol-io/reader/ccp4/schema';
import { CifFile } from '../mol-io/reader/cif';
import { DcdFile } from '../mol-io/reader/dcd/parser';
@@ -83,7 +82,6 @@ export namespace PluginStateObject {
{ kind: 'cif', data: CifFile } |
{ kind: 'pdb', data: CifFile } |
{ kind: 'gro', data: CifFile } |
{ kind: '3dg', data: File3DG } |
{ kind: 'dcd', data: DcdFile } |
{ kind: 'ccp4', data: Ccp4File } |
{ kind: 'dsn6', data: Dsn6File } |

View File

@@ -5,13 +5,11 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { parse3DG } from '../../mol-io/reader/3dg/parser';
import { parseDcd } from '../../mol-io/reader/dcd/parser';
import { parseGRO } from '../../mol-io/reader/gro/parser';
import { parsePDB } from '../../mol-io/reader/pdb/parser';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
@@ -52,7 +50,6 @@ export { TrajectoryFromMOL };
export { TrajectoryFromMOL2 };
export { TrajectoryFromCube };
export { TrajectoryFromCifCore };
export { TrajectoryFrom3DG };
export { ModelFromTrajectory };
export { StructureFromTrajectory };
export { StructureFromModel };
@@ -339,24 +336,6 @@ const TrajectoryFromCifCore = PluginStateTransform.BuiltIn({
}
});
type TrajectoryFrom3DG = typeof TrajectoryFrom3DG
const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({
name: 'trajectory-from-3dg',
display: { name: 'Parse 3DG', description: 'Parse 3DG string and create trajectory.' },
from: [SO.Data.String],
to: SO.Molecule.Trajectory
})({
apply({ a }) {
return Task.create('Parse 3DG', async ctx => {
const parsed = await parse3DG(a.data).runInContext(ctx);
if (parsed.isError) throw new Error(parsed.message);
const models = await trajectoryFrom3DG(parsed.result).runInContext(ctx);
const props = trajectoryProps(models);
return new SO.Molecule.Trajectory(models, props);
});
}
});
const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
type ModelFromTrajectory = typeof ModelFromTrajectory
const ModelFromTrajectory = PluginStateTransform.BuiltIn({

View File

@@ -131,7 +131,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params);
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params));
// TODO set initial state, repr.setState({})
const props = params.type.params || {};
await repr.createOrUpdate(props, a.data).runInContext(ctx);
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
@@ -205,6 +204,8 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
const unitTransforms = b.data.state.unitTransforms!;
unwindStructureAssembly(structure, unitTransforms, newParams.t);
@@ -240,6 +241,8 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
update({ a, b, newParams, oldParams }) {
const structure = a.data.source.data;
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
const unitTransforms = b.data.state.unitTransforms!;
explodeStructure(structure.root, unitTransforms, newParams.t);
@@ -287,6 +290,8 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
const oldStructure = b.data.info as Structure;
const newStructure = a.data.source.data;
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldOverpaint = b.data.state.overpaint!;
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
@@ -337,6 +342,8 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
const oldStructure = b.data.info as Structure;
const newStructure = a.data.source.data;
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldOverpaint = b.data.state.overpaint!;
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
@@ -383,6 +390,8 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldTransparency = b.data.state.transparency!;
const newTransparency = Transparency.ofScript(newParams.layers, structure);
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
@@ -430,6 +439,8 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldTransparency = b.data.state.transparency!;
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
@@ -476,6 +487,8 @@ const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldClipping = b.data.state.clipping!;
const newClipping = Clipping.ofScript(newParams.layers, structure);
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
@@ -523,6 +536,8 @@ const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldClipping = b.data.state.clipping!;
const newClipping = Clipping.ofBundle(newParams.layers, structure);
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
@@ -619,7 +634,6 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
}
})({
canAutoUpdate({ oldParams, newParams }) {
// TODO: allow for small molecules
return oldParams.type.name === newParams.type.name;
},
apply({ a, params }, plugin: PluginContext) {
@@ -627,10 +641,10 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
const provider = plugin.representation.volume.registry.get(params.type.name);
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
const props = params.type.params || {};
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
// TODO set initial state, repr.setState({})
const props = params.type.params || {};
await repr.createOrUpdate(props, a.data).runInContext(ctx);
return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
});
@@ -673,7 +687,6 @@ const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
return Task.create('Shape Representation', async ctx => {
const props = { ...PD.getDefaultValues(a.data.params), ...params };
const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils);
// TODO set initial state, repr.setState({})
await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
});

View File

@@ -186,6 +186,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
case 'file-list': return FileListControl;
case 'select': return SelectControl;
case 'value-ref': return ValueRefControl;
case 'data-ref': return void 0;
case 'text': return TextControl;
case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined'
? BoundedIntervalControl : IntervalControl;

View File

@@ -22,6 +22,7 @@ import { ElementSequenceWrapper } from './sequence/element';
import { elementLabel } from '../mol-theme/label';
import { Icon, HelpOutlineSvg } from './controls/icons';
import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
import { arrayEqual } from '../mol-util/array';
const MaxDisplaySequenceLength = 5000;
@@ -34,14 +35,14 @@ function opKey(l: StructureElement.Location) {
}
function splitModelEntityId(modelEntityId: string) {
const [ modelIdx, entityId ] = modelEntityId.split('|');
return [ parseInt(modelIdx), entityId ];
const [modelIdx, entityId] = modelEntityId.split('|');
return [parseInt(modelIdx), entityId];
}
function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureSelectionManager): SequenceWrapper.Any | string {
function getSequenceWrapper(state: { structure: Structure, modelEntityId: string, chainGroupId: number, operatorKey: string }, structureSelection: StructureSelectionManager): SequenceWrapper.Any | string {
const { structure, modelEntityId, chainGroupId, operatorKey } = state;
const l = StructureElement.Location.create(structure);
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
const units: Unit[] = [];
@@ -93,7 +94,7 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct
}
}
function getModelEntityOptions(structure: Structure) {
function getModelEntityOptions(structure: Structure, polymersOnly = false) {
const options: [string, string][] = [];
const l = StructureElement.Location.create(structure);
const seen = new Set<string>();
@@ -104,17 +105,18 @@ function getModelEntityOptions(structure: Structure) {
const modelIdx = structure.getModelIndex(unit.model);
const key = `${modelIdx}|${id}`;
if (seen.has(key)) continue;
if (polymersOnly && SP.entity.type(l) !== 'polymer') continue;
let description = SP.entity.pdbx_description(l).join(', ');
if (structure.models.length) {
if (structure.representativeModel) { // indicates model trajectory
description += ` (Model ${structure.models[modelIdx].modelNum})`;
} else if (description.startsWith('Polymer ')) { // indicates generic entity name
} else if (description.startsWith('Polymer ')) { // indicates generic entity name
description += ` (${structure.models[modelIdx].entry})`;
}
}
const label = `${id}: ${description}`;
options.push([ key, label ]);
options.push([key, label]);
seen.add(key);
}
@@ -126,7 +128,7 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
const options: [number, string][] = [];
const l = StructureElement.Location.create(structure);
const seen = new Set<number>();
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
for (const unit of structure.units) {
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
@@ -140,7 +142,7 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
// - more than one chain in a unit
let label = elementLabel(l, { granularity: 'chain', hidePrefix: true, htmlStyling: false });
options.push([ id, label ]);
options.push([id, label]);
seen.add(id);
}
@@ -152,7 +154,7 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
const options: [string, string][] = [];
const l = StructureElement.Location.create(structure);
const seen = new Set<string>();
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId);
const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
for (const unit of structure.units) {
StructureElement.Location.set(l, structure, unit, unit.elements[0]);
@@ -164,7 +166,7 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
if (seen.has(id)) continue;
const label = unit.conformation.operator.name;
options.push([ id, label ]);
options.push([id, label]);
seen.add(id);
}
@@ -174,49 +176,64 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
function getStructureOptions(state: State) {
const options: [string, string][] = [];
const all: Structure[] = [];
const structures = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
for (const s of structures) {
if (!s.obj?.data) continue;
all.push(s.obj.data);
options.push([s.transform.ref, s.obj!.data.label]);
}
if (options.length === 0) options.push(['', 'No structure']);
return options;
return { options, all };
}
export type SequenceViewMode = 'single' | 'polymers' | 'all'
const SequenceViewModeParam = PD.Select<SequenceViewMode>('single', [['single', 'Chain'], ['polymers', 'Polymers'], ['all', 'Everything']]);
type SequenceViewState = {
structureOptions: { options: [string, string][], all: Structure[] },
structure: Structure,
structureRef: string,
modelEntityId: string,
chainGroupId: number,
operatorKey: string
operatorKey: string,
mode: SequenceViewMode
}
export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
state = { structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '' }
export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceViewMode }, SequenceViewState> {
state: SequenceViewState = { structureOptions: { options: [], all: [] }, structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '', mode: 'single' }
componentDidMount() {
if (this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)).length > 0) this.setState(this.getInitialState());
this.subscribe(this.plugin.state.events.object.updated, ({ ref, obj }) => {
if (ref === this.state.structureRef && obj && obj.type === PSO.Molecule.Structure.type && obj.data !== this.state.structure) {
this.setState(this.getInitialState());
this.sync();
}
});
this.subscribe(this.plugin.state.events.object.created, ({ obj }) => {
if (obj && obj.type === PSO.Molecule.Structure.type) {
this.setState(this.getInitialState());
this.sync();
}
});
this.subscribe(this.plugin.state.events.object.removed, ({ obj }) => {
if (obj && obj.type === PSO.Molecule.Structure.type && obj.data === this.state.structure) {
this.setState(this.getInitialState());
this.sync();
}
});
}
private sync() {
const structureOptions = getStructureOptions(this.plugin.state.data);
if (arrayEqual(structureOptions.all, this.state.structureOptions.all)) return;
this.setState(this.getInitialState());
}
private getStructure(ref: string) {
const state = this.plugin.state.data;
const cell = state.select(ref)[0];
@@ -224,12 +241,40 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
return (cell.obj as PSO.Molecule.Structure).data;
}
private getSequenceWrapper() {
return getSequenceWrapper(this.state, this.plugin.managers.structure.selection);
private getSequenceWrapper(params: SequenceView['params']) {
return {
wrapper: getSequenceWrapper(this.state, this.plugin.managers.structure.selection),
label: `${PD.optionLabel(params.chain, this.state.chainGroupId)} | ${PD.optionLabel(params.entity, this.state.modelEntityId)}`
};
}
private getSequenceWrappers(params: SequenceView['params']) {
if (this.state.mode === 'single') return [this.getSequenceWrapper(params)];
const structure = this.getStructure(this.state.structureRef);
const wrappers: { wrapper: (string | SequenceWrapper.Any), label: string }[] = [];
for (const [modelEntityId, eLabel] of getModelEntityOptions(structure, this.state.mode === 'polymers')) {
for (const [chainGroupId, cLabel] of getChainOptions(structure, modelEntityId)) {
for (const [operatorKey] of getOperatorOptions(structure, modelEntityId, chainGroupId)) {
wrappers.push({
wrapper: getSequenceWrapper({
structure,
modelEntityId,
chainGroupId,
operatorKey
}, this.plugin.managers.structure.selection),
label: `${cLabel} | ${eLabel}`
});
}
}
}
return wrappers;
}
private getInitialState(): SequenceViewState {
const structureRef = getStructureOptions(this.plugin.state.data)[0][0];
const structureOptions = getStructureOptions(this.plugin.state.data);
const structureRef = structureOptions.options[0][0];
const structure = this.getStructure(structureRef);
let modelEntityId = getModelEntityOptions(structure)[0][0];
let chainGroupId = getChainOptions(structure, modelEntityId)[0][0];
@@ -239,20 +284,20 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
chainGroupId = this.state.chainGroupId;
operatorKey = this.state.operatorKey;
}
return { structure, structureRef, modelEntityId, chainGroupId, operatorKey };
return { structureOptions, structure, structureRef, modelEntityId, chainGroupId, operatorKey, mode: this.props.defaultMode ?? 'single' };
}
private get params() {
const { structure, modelEntityId, chainGroupId } = this.state;
const structureOptions = getStructureOptions(this.plugin.state.data);
const { structureOptions, structure, modelEntityId, chainGroupId } = this.state;
const entityOptions = getModelEntityOptions(structure);
const chainOptions = getChainOptions(structure, modelEntityId);
const operatorOptions = getOperatorOptions(structure, modelEntityId, chainGroupId);
return {
structure: PD.Select(structureOptions[0][0], structureOptions, { shortLabel: true }),
structure: PD.Select(structureOptions.options[0][0], structureOptions.options, { shortLabel: true }),
entity: PD.Select(entityOptions[0][0], entityOptions, { shortLabel: true }),
chain: PD.Select(chainOptions[0][0], chainOptions, { shortLabel: true, twoColumns: true, label: 'Chain' }),
operator: PD.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true })
operator: PD.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true }),
mode: SequenceViewModeParam
};
}
@@ -261,16 +306,24 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
structure: this.state.structureRef,
entity: this.state.modelEntityId,
chain: this.state.chainGroupId,
operator: this.state.operatorKey
operator: this.state.operatorKey,
mode: this.state.mode
};
}
private setParamProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
const state = { ...this.state };
switch (p.name) {
case 'mode':
state.mode = p.value;
if (this.state.mode === state.mode) return;
if (state.mode === 'all' || state.mode === 'polymers') {
break;
}
case 'structure':
state.structureRef = p.value;
state.structure = this.getStructure(p.value);
if (p.name === 'structure') state.structureRef = p.value;
state.structure = this.getStructure(state.structureRef);
state.modelEntityId = getModelEntityOptions(state.structure)[0][0];
state.chainGroupId = getChainOptions(state.structure, state.modelEntityId)[0][0];
state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.chainGroupId)[0][0];
@@ -296,17 +349,16 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
return <div className='msp-sequence'>
<div className='msp-sequence-select'>
<Icon svg={HelpOutlineSvg} style={{ cursor: 'help', position: 'absolute', right: 0, top: 0 }}
title='This shows a single sequence. Use the controls to show a different sequence.'/>
title='Shows a sequence of one or more chains. Use the controls to alter selection.' />
<span>Sequence</span><span style={{ fontWeight: 'normal' }}>No structure available</span>
</div>
</div>;
}
const sequenceWrapper = this.getSequenceWrapper();
const params = this.params;
const values = this.values;
const sequenceWrappers = this.getSequenceWrappers(params);
return <div className='msp-sequence'>
<div className='msp-sequence-select'>
@@ -315,16 +367,34 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
<span>Sequence of</span>
<PureSelectControl title={`[Structure] ${PD.optionLabel(params.structure, values.structure)}`} param={params.structure} name='structure' value={values.structure} onChange={this.setParamProps} />
<PureSelectControl title={`[Entity] ${PD.optionLabel(params.entity, values.entity)}`} param={params.entity} name='entity' value={values.entity} onChange={this.setParamProps} />
<PureSelectControl title={`[Chain] ${PD.optionLabel(params.chain, values.chain)}`} param={params.chain} name='chain' value={values.chain} onChange={this.setParamProps} />
<PureSelectControl title={`[Mode]`} param={SequenceViewModeParam} name='mode' value={values.mode} onChange={this.setParamProps} />
{values.mode === 'single' && <PureSelectControl title={`[Entity] ${PD.optionLabel(params.entity, values.entity)}`} param={params.entity} name='entity' value={values.entity} onChange={this.setParamProps} />}
{values.mode === 'single' && <PureSelectControl title={`[Chain] ${PD.optionLabel(params.chain, values.chain)}`} param={params.chain} name='chain' value={values.chain} onChange={this.setParamProps} />}
{params.operator.options.length > 1 && <>
<PureSelectControl title={`[Instance] ${PD.optionLabel(params.operator, values.operator)}`} param={params.operator} name='operator' value={values.operator} onChange={this.setParamProps} />
</>}
</div>
{typeof sequenceWrapper === 'string'
? <div className='msp-sequence-wrapper msp-sequence-wrapper-non-empty'>{sequenceWrapper}</div>
: <Sequence sequenceWrapper={sequenceWrapper} />}
<NonEmptySequenceWrapper>
{sequenceWrappers.map((s, i) => {
const elem = typeof s.wrapper === 'string'
? <div key={i} className='msp-sequence-wrapper'>{s.wrapper}</div>
: <Sequence key={i} sequenceWrapper={s.wrapper} />;
if (values.mode === 'single') return elem;
return <>
<div className='msp-sequence-chain-label'>{s.label}</div>
{elem}
</>;
})}
</NonEmptySequenceWrapper>
</div>;
}
}
function NonEmptySequenceWrapper({ children }: { children: React.ReactNode }) {
return <div className='msp-sequence-wrapper-non-empty'>
{children}
</div>;
}

View File

@@ -19,7 +19,7 @@ import { Representation } from '../../mol-repr/representation';
type SequenceProps = {
sequenceWrapper: SequenceWrapper.Any,
sequenceNumberPeriod?: number,
hideSequenceNumbers?: boolean
hideSequenceNumbers?: boolean,
}
/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
@@ -292,7 +292,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
this.updateMarker();
return <div
className='msp-sequence-wrapper msp-sequence-wrapper-non-empty'
className='msp-sequence-wrapper'
onContextMenu={this.contextMenu}
onMouseDown={this.mouseDown}
onMouseUp={this.mouseUp}

View File

@@ -43,14 +43,6 @@ $sequence-select-height: 24px;
// use $control-spacing for top to have space for sequence numebrs
padding: $control-spacing $control-spacing $info-vertical-padding $control-spacing;
user-select: none;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
.msp-sequence-wrapper-non-empty {
@@ -58,7 +50,25 @@ $sequence-select-height: 24px;
line-height: 180%;
font-family: "Courier New", monospace;
background: $msp-form-control-background;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
top: $sequence-select-height + 1px;
left: 0;
bottom: 0;
right: 0;
}
.msp-sequence-chain-label {
margin-left: $control-spacing;
margin-top: $control-spacing;
user-select: none;
color: $sequence-number-color;
font-size: 90%;
line-height: 90%;
padding-left: 0.2em;
}
.msp-sequence-wrapper {
@@ -74,6 +84,14 @@ $sequence-select-height: 24px;
margin: 0em 0.2em 0em 0em;
}
.msp-sequence-label {
color: $sequence-number-color;
font-size: 90%;
line-height: 90%;
padding-bottom: 1em;
padding-left: 0.2em;
}
.msp-sequence-number {
color: $sequence-number-color;
word-break: keep-all;

View File

@@ -44,7 +44,7 @@ export const HighlightLoci = PluginBehavior.create({
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
if (!this.ctx.canvas3d || !this.params.mark) return;
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action);
this.ctx.canvas3d.mark(interactionLoci, action);
}
register() {
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
@@ -57,12 +57,14 @@ export const HighlightLoci = PluginBehavior.create({
let matched = false;
if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
this.ctx.managers.interactivity.lociHighlights.highlightOnly(current);
// remove repr to highlight loci everywhere on hover
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: current.loci });
matched = true;
}
if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend(current);
// remove repr to highlight loci everywhere on hover
this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci: current.loci });
matched = true;
}

View File

@@ -10,7 +10,7 @@ import { PluginStateObject } from '../../../../mol-plugin-state/objects';
import { Volume, Grid } from '../../../../mol-model/volume';
import { VolumeServerHeader, VolumeServerInfo } from './model';
import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
import { Color } from '../../../../mol-util/color';
import { PluginBehavior } from '../../behavior';
import { LRUCache } from '../../../../mol-util/lru-cache';
@@ -23,6 +23,7 @@ import { StructureElement, Structure } from '../../../../mol-model/structure';
import { PluginContext } from '../../../context';
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
import { Asset } from '../../../../mol-util/assets';
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
@@ -302,6 +303,7 @@ export namespace VolumeStreaming {
}
}
private _invTransform: Mat4 = Mat4();
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
if (Loci.isEmpty(loci)) {
return Box3D();
@@ -312,11 +314,16 @@ export namespace VolumeStreaming {
const root = this.getStructureRoot();
if (!root || root.obj?.data !== parent.obj?.data) return Box3D();
const transform = GlobalModelTransformInfo.get(root.obj?.data.models[0]!);
if (transform) Mat4.invert(this._invTransform, transform);
const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci);
const box = StructureElement.Loci.getBoundary(extendedLoci).box;
const box = StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box;
if (StructureElement.Loci.size(extendedLoci) === 1) {
Box3D.expand(box, box, Vec3.create(1, 1, 1));
}
return box;
}
@@ -360,7 +367,6 @@ export namespace VolumeStreaming {
const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box';
this.params = params;
let box: Box3D | undefined = void 0, emptyData = false;
switch (params.entry.params.view.name) {

View File

@@ -22,6 +22,7 @@ import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { PluginConfig } from '../../../config';
import { Model } from '../../../../mol-model/structure';
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
entries.push({
@@ -253,20 +254,22 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true })
}
})({
apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
apply: ({ a, params: srcParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
const channel = a.data.channels[srcParams.channel];
if (!channel) return StateObject.Null;
const params = createVolumeProps(a.data, srcParams.channel);
const provider = VolumeRepresentationRegistry.BuiltIn.isosurface;
const props = params.type.params || {};
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
await repr.createOrUpdate(props, channel.data).runInContext(ctx);
if (transform) repr.setState({ transform });
return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
}),
update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
// TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
const channel = a.data.channels[newParams.channel];
@@ -277,6 +280,13 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...params.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
// TODO: set the transform here as well in case the structure moves?
// doing this here now breaks the code for some reason...
// const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
// const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
// if (transform) b.data.repr.setState({ transform });
return StateTransformer.UpdateResult.Updated;
})
});

View File

@@ -23,6 +23,7 @@ import { PickingId } from '../../mol-geo/geometry/picking';
import { Visual } from '../visual';
import { RuntimeContext, Task } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { isDebugMode } from '../../mol-util/debug';
export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
@@ -216,7 +217,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
Representation.updateState(_state, state);
},
setTheme(theme: Theme) {
console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
if(isDebugMode) {
console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`');
}
},
destroy() {
// TODO

View File

@@ -298,6 +298,12 @@ export function ComplexTextVisual<P extends ComplexTextParams>(builder: ComplexT
if (newProps.tetherLength !== currentProps.tetherLength) state.createGeometry = true;
if (newProps.tetherBaseWidth !== currentProps.tetherBaseWidth) state.createGeometry = true;
if (newProps.attachment !== currentProps.attachment) state.createGeometry = true;
if (newProps.fontFamily !== currentProps.fontFamily) state.createGeometry = true;
if (newProps.fontQuality !== currentProps.fontQuality) state.createGeometry = true;
if (newProps.fontStyle !== currentProps.fontStyle) state.createGeometry = true;
if (newProps.fontVariant !== currentProps.fontVariant) state.createGeometry = true;
if (newProps.fontWeight !== currentProps.fontWeight) state.createGeometry = true;
},
geometryUtils: Text.Utils
}, materialId);

View File

@@ -382,6 +382,12 @@ export function UnitsTextVisual<P extends UnitsTextParams>(builder: UnitsTextVis
if (newProps.tetherLength !== currentProps.tetherLength) state.createGeometry = true;
if (newProps.tetherBaseWidth !== currentProps.tetherBaseWidth) state.createGeometry = true;
if (newProps.attachment !== currentProps.attachment) state.createGeometry = true;
if (newProps.fontFamily !== currentProps.fontFamily) state.createGeometry = true;
if (newProps.fontQuality !== currentProps.fontQuality) state.createGeometry = true;
if (newProps.fontStyle !== currentProps.fontStyle) state.createGeometry = true;
if (newProps.fontVariant !== currentProps.fontVariant) state.createGeometry = true;
if (newProps.fontWeight !== currentProps.fontWeight) state.createGeometry = true;
},
geometryUtils: Text.Utils
}, materialId);

View File

@@ -33,10 +33,10 @@ export type GaussianSurfaceMeshParams = typeof GaussianSurfaceMeshParams
async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
const { smoothness } = props;
const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
const params = {
isoLevel: Math.exp(-smoothness),
isoLevel: Math.exp(-smoothness) / radiusFactor,
scalarField: field,
idField
};
@@ -81,10 +81,10 @@ export type StructureGaussianSurfaceMeshParams = typeof StructureGaussianSurface
async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
const { smoothness } = props;
const { transform, field, idField } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
const params = {
isoLevel: Math.exp(-smoothness),
isoLevel: Math.exp(-smoothness) / radiusFactor,
scalarField: field,
idField
};
@@ -119,29 +119,49 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
//
const GaussianSurfaceName = 'gaussian-surface';
async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, textureMesh?: TextureMesh): Promise<TextureMesh> {
if (!ctx.webgl) throw new Error('webgl context required to create gaussian surface texture-mesh');
const isoLevel = Math.exp(-props.smoothness);
const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
const { namedTextures, resources, extensions: { colorBufferFloat, textureFloat } } = ctx.webgl;
if (!namedTextures[GaussianSurfaceName]) {
namedTextures[GaussianSurfaceName] = colorBufferFloat && textureFloat
? resources.texture('image-float32', 'rgba', 'float', 'linear')
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
}
// console.time('computeUnitGaussianDensityTexture2d');
const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
// console.log(densityTextureData)
// console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture))
// ctx.webgl.waitForGpuCommandsCompleteSync()
// ctx.webgl.waitForGpuCommandsCompleteSync();
// console.timeEnd('computeUnitGaussianDensityTexture2d');
const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
// console.time('calcActiveVoxels');
const activeVoxelsTex = calcActiveVoxels(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, isoLevel, densityTextureData.gridTexScale);
// ctx.webgl.waitForGpuCommandsCompleteSync()
// ctx.webgl.waitForGpuCommandsCompleteSync();
// console.timeEnd('calcActiveVoxels');
const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, densityTextureData.gridTexScale);
// ctx.webgl.waitForGpuCommandsCompleteSync()
// console.time('createHistogramPyramid');
const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, densityTextureData.gridTexScale, densityTextureData.gridTexDim);
// ctx.webgl.waitForGpuCommandsCompleteSync();
// console.timeEnd('createHistogramPyramid');
// console.time('createIsosurfaceBuffers');
const gv = createIsosurfaceBuffers(ctx.webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoLevel, textureMesh ? textureMesh.vertexGroupTexture.ref.value : undefined, textureMesh ? textureMesh.normalTexture.ref.value : undefined);
// ctx.webgl.waitForGpuCommandsCompleteSync()
// ctx.webgl.waitForGpuCommandsCompleteSync();
// console.timeEnd('createIsosurfaceBuffers');
// const boundingSphere = Sphere3D.zero()
// Sphere3D.addVec3(boundingSphere, boundingSphere, densityTextureData.gridDimension)
const boundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox);
const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, boundingSphere, textureMesh);
// console.log({
// renderables: ctx.webgl.namedComputeRenderables,
// framebuffers: ctx.webgl.namedFramebuffers,
// textures: ctx.webgl.namedTextures,
// });
// ctx.webgl.waitForGpuCommandsCompleteSync()
return surface;
}

View File

@@ -54,12 +54,12 @@ export function computeUnitGaussianDensityTexture(structure: Structure, unit: Un
});
}
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, powerOfTwo: boolean, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
const { box } = unit.lookup3d.boundary;
const p = ensureReasonableResolution(box, props);
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
return Task.create('Gaussian Density', async ctx => {
return GaussianDensityTexture2d(webgl, position, box, radius, p, texture);
return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
});
}

View File

@@ -113,6 +113,7 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
const transform = Grid.getGridToCartesianTransform(volume.grid);
const bbox = getBoundingBox(gridDimension, transform);
// TODO: handle disposal
const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
texture.load(textureImage);
@@ -195,6 +196,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
const transform = Grid.getGridToCartesianTransform(volume.grid);
const bbox = getBoundingBox(gridDimension, transform);
// TODO: handle disposal
const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
texture.load(textureVolume);

View File

@@ -328,6 +328,8 @@ class State {
const oldTree = this._tree;
this._tree = _tree;
const cells = this.cells;
const ctx: UpdateContext = {
parent: this,
editInfo: StateBuilder.is(tree) ? tree.editInfo : void 0,
@@ -346,7 +348,9 @@ class State {
changed: false,
hadError: false,
wasAborted: false,
newCurrent: void 0
newCurrent: void 0,
getCellData: ref => cells.get(ref)!.obj?.data
};
this.errorFree = true;
@@ -454,7 +458,9 @@ interface UpdateContext {
changed: boolean,
hadError: boolean,
wasAborted: boolean,
newCurrent?: Ref
newCurrent?: Ref,
getCellData: (ref: string) => any
}
async function update(ctx: UpdateContext) {
@@ -841,7 +847,7 @@ function resolveParams(ctx: UpdateContext, transform: StateTransform, src: State
(transform.params as any) = transform.params
? assignIfUndefined(transform.params, defaultValues)
: defaultValues;
ParamDefinition.resolveValueRefs(definition, transform.params);
ParamDefinition.resolveRefs(definition, transform.params, ctx.getCellData);
return { definition, values: transform.params };
}

View File

@@ -296,6 +296,13 @@ export namespace ParamDefinition {
return setInfo<ValueRef<T>>({ type: 'value-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any }, getOptions, resolveRef }, info);
}
export interface DataRef<T = any> extends Base<{ ref: string, getValue: () => T }> {
type: 'data-ref'
}
export function DataRef<T>(info?: Info & { defaultRef?: string }) {
return setInfo<DataRef<T>>({ type: 'data-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any } }, info);
}
export interface Converted<T, C> extends Base<T> {
type: 'converted',
converted: Any,
@@ -329,7 +336,7 @@ export namespace ParamDefinition {
export type Any =
| Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Mat4 | Numeric | FileParam | UrlParam | FileListParam | Interval | LineGraph
| ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef
| ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef | DataRef
export type Params = { [k: string]: Any }
export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }
@@ -360,29 +367,33 @@ export namespace ParamDefinition {
return () => resolve(ref);
}
function resolveRefValue(p: Any, value: any) {
function resolveRefValue(p: Any, value: any, getData: (ref: string) => any) {
if (!value) return;
if (p.type === 'value-ref') {
const v = value as ValueRef['defaultValue'];
if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); };
else v.getValue = _resolveRef(p.resolveRef, v.ref);
} else if (p.type === 'data-ref') {
const v = value as ValueRef['defaultValue'];
if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); };
else v.getValue = _resolveRef(getData, v.ref);
} else if (p.type === 'group') {
resolveValueRefs(p.params, value);
resolveRefs(p.params, value, getData);
} else if (p.type === 'mapped') {
const v = value as NamedParams;
const param = p.map(v.name);
resolveRefValue(param, v.params);
resolveRefValue(param, v.params, getData);
} else if (p.type === 'object-list') {
if (!hasValueRef(p.element)) return;
for (const e of value) {
resolveValueRefs(p.element, e);
resolveRefs(p.element, e, getData);
}
}
}
function hasParamValueRef(p: Any) {
if (p.type === 'value-ref') {
if (p.type === 'value-ref' || p.type === 'data-ref') {
return true;
} else if (p.type === 'group') {
if (hasValueRef(p.params)) return true;
@@ -403,9 +414,9 @@ export namespace ParamDefinition {
return false;
}
export function resolveValueRefs(params: Params, values: any) {
export function resolveRefs(params: Params, values: any, getData: (ref: string) => any) {
for (const n of Object.keys(params)) {
resolveRefValue(params[n], values?.[n]);
resolveRefValue(params[n], values?.[n], getData);
}
}

View File

@@ -1,3 +1,9 @@
# 0.9.6
* optional download parameter
# 0.9.5
* Support molstar_global_model_transform_info category.
# 0.9.4
* bug fix for /ligand queries on metal ions

View File

@@ -18,7 +18,8 @@ export interface MultipleQueryEntry<Name extends QueryName = QueryName> {
export interface MultipleQuerySpec {
queries: MultipleQueryEntry[],
encoding?: Encoding,
asTarGz?: boolean
asTarGz?: boolean,
download?: boolean
}
export function getMultiQuerySpecFilename() {

View File

@@ -11,7 +11,7 @@ import * as bodyParser from 'body-parser';
import { ModelServerConfig as Config, ModelServerConfig, mapSourceAndIdToFilename } from '../config';
import { ConsoleLogger } from '../../../mol-util/console-logger';
import { resolveJob } from './query';
import { JobManager, JobEntry } from './jobs';
import { JobManager, JobEntry, ResultWriterParams } from './jobs';
import { UUID } from '../../../mol-util';
import { QueryDefinition, normalizeRestQueryParams, normalizeRestCommonParams, QueryList } from './api';
import { getApiSchema, shortcutIconLink } from './api-schema';
@@ -45,17 +45,18 @@ async function processNextJob() {
}
}
export function createResultWriter(response: express.Response, encoding: string, entryId?: string, queryName?: string) {
const filenameBase = entryId && queryName
? `${entryId}_${splitCamelCase(queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
export function createResultWriter(response: express.Response, params: ResultWriterParams) {
const filenameBase = params.entryId && params.queryName
? `${params.entryId}_${splitCamelCase(params.queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
: `result`;
return new SimpleResponseResultWriter(`${filenameBase}.${encoding}`, response, encoding === 'bcif');
return new SimpleResponseResultWriter(`${filenameBase}.${params.encoding}`, response, params.encoding === 'bcif', params.download);
}
function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
function createJob(queryParams: any, req: express.Request, res: express.Response) {
const entryId = req.params.id;
const commonParams = normalizeRestCommonParams(req.query);
const resultWriterParams = { encoding: commonParams.encoding!, download: !!commonParams.download, entryId, queryName };
const jobId = JobManager.add({
entries: [JobEntry({
sourceId: commonParams.data_source || ModelServerConfig.defaultSource,
@@ -66,7 +67,7 @@ function mapQuery(app: express.Express, queryName: string, queryDefinition: Quer
copyAllCategories: !!commonParams.copy_all_categories,
transform: commonParams.transform
})],
writer: createResultWriter(res, commonParams.encoding!, entryId, queryName),
writer: createResultWriter(res, resultWriterParams),
options: { binary: commonParams.encoding === 'bcif', encoding: commonParams.encoding }
});
responseMap.set(jobId, res);
@@ -122,7 +123,7 @@ function serveStatic(req: express.Request, res: express.Response) {
function createMultiJob(spec: MultipleQuerySpec, res: express.Response) {
const writer = spec.asTarGz
? new TarballResponseResultWriter(getMultiQuerySpecFilename(), res)
: createResultWriter(res, spec.encoding!);
: createResultWriter(res, { encoding: spec.encoding!, download: !!spec.download });
if (spec.queries.length > ModelServerConfig.maxQueryManyQueries) {
writer.doError(400, `query-many queries limit (${ModelServerConfig.maxQueryManyQueries}) exceeded.`);

View File

@@ -48,7 +48,8 @@ export const CommonQueryParamsInfo: QueryParamInfo[] = [
{ name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF', 'MOL', or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'sdf', 'mol', 'mol2'] },
{ name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
{ name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' },
{ name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` }
{ name: 'transform', type: QueryParamType.String, description: `Transformation to apply to coordinates in '_atom_site'. Accepts a 4x4 transformation matrix, provided as array of 16 float values.` },
{ name: 'download', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, browser will download text files.' }
];
export type Encoding = 'cif' | 'bcif' | 'sdf' | 'mol' | 'mol2';
@@ -57,7 +58,8 @@ export interface CommonQueryParamsInfo {
encoding?: Encoding,
copy_all_categories?: boolean
data_source?: string,
transform?: Mat4
transform?: Mat4,
download?: boolean
}
export const AtomSiteSchemaElement = {
@@ -290,12 +292,20 @@ export function normalizeRestCommonParams(params: any): CommonQueryParamsInfo {
return {
model_nums: params.model_nums ? ('' + params.model_nums).split(',').map(n => n.trim()).filter(n => !!n).map(n => +n) : void 0,
data_source: params.data_source,
copy_all_categories: Boolean(params.copy_all_categories),
copy_all_categories: isTrue(params.copy_all_categories),
encoding: mapEncoding(('' + params.encoding).toLocaleLowerCase()),
transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity()
transform: params.transform ? ('' + params.transform).split(',').map(n => n.trim()).map(n => +n) as Mat4 : Mat4.identity(),
download: isTrue(params.download)
};
}
function isTrue(val: any): boolean {
const b = Boolean(val);
if (!b) return false;
if (typeof val === 'string') return val !== '0' && val.toLowerCase() !== 'false';
return b;
}
function mapEncoding(value: string) {
switch (value) {
case 'bcif':

View File

@@ -56,6 +56,13 @@ interface JobEntryDefinition<Name extends QueryName> {
transform?: Mat4
}
export interface ResultWriterParams {
encoding: Encoding,
download: boolean,
entryId?: string,
queryName?: string
}
export function JobEntry<Name extends QueryName>(definition: JobEntryDefinition<Name>): JobEntry {
const queryDefinition = getQueryByName(definition.queryName);
if (!queryDefinition) throw new Error(`Query '${definition.queryName}' is not supported.`);

View File

@@ -30,6 +30,7 @@ import { MolEncoder } from '../../../mol-io/writer/mol/encoder';
import { Mol2Encoder } from '../../../mol-io/writer/mol2/encoder';
import { ComponentAtom } from '../../../mol-model-formats/structure/property/atoms/chem_comp';
import { Mat4 } from '../../../mol-math/linear-algebra';
import { GlobalModelTransformInfo } from '../../../mol-model/structure/model/properties/global-transform';
export interface Stats {
structure: StructureWrapper,
@@ -247,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform);
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
perf.end('encode');

View File

@@ -48,10 +48,11 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
this.headerWritten = true;
this.res.writeHead(200, {
// TODO there seems to be a bug in swagger-ui - front-end will freeze for cif delivered as text/plain (forcing binary is a hack to circumvent this)
'Content-Type': this.isBinary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Requested-With',
'Content-Disposition': `inline; filename="${this.fn}"`
'Content-Disposition': `${this.isDownload ? 'attachment' : 'inline'}; filename="${this.fn}"`
});
}
@@ -71,7 +72,7 @@ export class SimpleResponseResultWriter implements WebResutlWriter {
this.ended = true;
}
constructor(private fn: string, private res: express.Response, private isBinary: boolean) {
constructor(private fn: string, private res: express.Response, private isBinary: boolean, private isDownload: boolean) {
}
}

View File

@@ -4,4 +4,4 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export default '0.9.4';
export default '0.9.6';

View File

@@ -68,7 +68,7 @@ async function init() {
console.timeEnd('gpu mc active2');
console.time('gpu mc pyramid2');
const compacted2 = createHistogramPyramid(webgl, activeVoxelsTex2, densityTextureData2.gridTexScale);
const compacted2 = createHistogramPyramid(webgl, activeVoxelsTex2, densityTextureData2.gridTexScale, densityTextureData2.gridTexDim);
webgl.waitForGpuCommandsCompleteSync();
console.timeEnd('gpu mc pyramid2');
@@ -91,7 +91,7 @@ async function init() {
console.timeEnd('gpu mc active');
console.time('gpu mc pyramid');
const compacted = createHistogramPyramid(webgl, activeVoxelsTex, densityTextureData.gridTexScale);
const compacted = createHistogramPyramid(webgl, activeVoxelsTex, densityTextureData.gridTexScale, densityTextureData.gridTexDim);
webgl.waitForGpuCommandsCompleteSync();
console.timeEnd('gpu mc pyramid');