ソースを参照

Merge remote-tracking branch 'upstream/master' into postprocessing

AronKovacs 4 年 前
コミット
6561732f57

ファイルの差分が大きいため隠しています
+ 18132 - 412
package-lock.json


+ 38 - 38
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "1.2.3",
+  "version": "1.2.6",
   "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"
   }

+ 2 - 1
src/cli/state-docs/pd-to-md.ts

@@ -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)}`;

+ 2 - 2
src/examples/alpha-orbitals/index.ts

@@ -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).`

+ 4 - 4
src/extensions/alpha-orbitals/data-model.ts

@@ -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),
         },
     };

+ 4 - 3
src/extensions/alpha-orbitals/density.ts

@@ -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.');

+ 60 - 199
src/extensions/alpha-orbitals/gpu/compute.ts

@@ -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);
 }

+ 7 - 193
src/extensions/alpha-orbitals/gpu/shader.frag.ts

@@ -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);
-    }
-}
 `;

+ 6 - 3
src/extensions/alpha-orbitals/orbitals.ts

@@ -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');

+ 8 - 30
src/extensions/anvil/representation.ts

@@ -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);
-            }
-        }
-    }
-}

+ 10 - 1
src/mol-canvas3d/canvas3d.ts

@@ -150,9 +150,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 +198,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 });
     }
 

+ 2 - 2
src/mol-canvas3d/passes/postprocessing.ts

@@ -283,7 +283,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
@@ -452,7 +452,7 @@ export class PostprocessingPass {
         /*
         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);

+ 223 - 0
src/mol-gl/compute/grid3d.ts

@@ -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);
+}

+ 2 - 0
src/mol-gl/renderable/schema.ts

@@ -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 = {

+ 9 - 2
src/mol-gl/renderer.ts

@@ -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
@@ -474,8 +475,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 +491,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');
                 }
             }

+ 181 - 0
src/mol-gl/shader/util/grid3d-template.frag.ts

@@ -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});
+}
+`;

+ 26 - 0
src/mol-gl/webgl/uniform.ts

@@ -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;
+    }
 }

+ 0 - 57
src/mol-io/reader/3dg/parser.ts

@@ -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 });
-    });
-}

+ 0 - 33
src/mol-io/reader/_spec/3dg.spec.ts

@@ -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);
-    });
-});

+ 9 - 0
src/mol-math/geometry/common.ts

@@ -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++) {

+ 0 - 83
src/mol-model-formats/structure/3dg.ts

@@ -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);
-    });
-}

+ 33 - 31
src/mol-model/structure/export/categories/atom_site.ts

@@ -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();

+ 0 - 10
src/mol-plugin-state/formats/trajectory.ts

@@ -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;

+ 0 - 2
src/mol-plugin-state/objects.ts

@@ -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 } |

+ 0 - 21
src/mol-plugin-state/transforms/model.ts

@@ -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({

+ 18 - 5
src/mol-plugin-state/transforms/representation.ts

@@ -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 });
         });

+ 1 - 0
src/mol-plugin-ui/controls/parameters.tsx

@@ -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;

+ 5 - 3
src/mol-plugin/behavior/dynamic/representation.ts

@@ -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;
                 }
 

+ 4 - 1
src/mol-repr/shape/representation.ts

@@ -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

+ 9 - 3
src/mol-state/state.ts

@@ -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 };
 }
 

+ 19 - 8
src/mol-util/param-definition.ts

@@ -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);
         }
     }
 

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません