فهرست منبع

Merge branch 'master' of https://github.com/molstar/molstar into lighting

Alexander Rose 3 سال پیش
والد
کامیت
3104ee5742
76فایلهای تغییر یافته به همراه1780 افزوده شده و 754 حذف شده
  1. 21 0
      CHANGELOG.md
  2. 255 243
      package-lock.json
  3. 29 30
      package.json
  4. 1 0
      src/cli/cifschema/util/cif-dic.ts
  5. 1 1
      src/extensions/geo-export/controls.ts
  6. 31 18
      src/extensions/geo-export/glb-exporter.ts
  7. 99 15
      src/extensions/geo-export/mesh-exporter.ts
  8. 31 15
      src/extensions/geo-export/obj-exporter.ts
  9. 6 5
      src/extensions/geo-export/ui.tsx
  10. 35 14
      src/extensions/geo-export/usdz-exporter.ts
  11. 1 1
      src/extensions/mp4-export/controls.ts
  12. 2 1
      src/extensions/mp4-export/ui.tsx
  13. 8 7
      src/extensions/rcsb/graphql/types.ts
  14. 3 24
      src/mol-canvas3d/passes/draw.ts
  15. 35 0
      src/mol-geo/geometry/base.ts
  16. 166 37
      src/mol-geo/geometry/mesh/color-smoothing.ts
  17. 18 2
      src/mol-geo/geometry/overpaint-data.ts
  18. 185 27
      src/mol-geo/geometry/texture-mesh/color-smoothing.ts
  19. 4 1
      src/mol-geo/geometry/texture-mesh/texture-mesh.ts
  20. 18 2
      src/mol-geo/geometry/transparency-data.ts
  21. 1 1
      src/mol-gl/_spec/renderer.spec.ts
  22. 3 3
      src/mol-gl/compute/marching-cubes/isosurface.ts
  23. 96 22
      src/mol-gl/compute/util.ts
  24. 10 0
      src/mol-gl/renderable/schema.ts
  25. 2 1
      src/mol-gl/renderable/texture-mesh.ts
  26. 1 1
      src/mol-gl/renderable/util.ts
  27. 4 0
      src/mol-gl/renderer.ts
  28. 28 6
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  29. 3 0
      src/mol-gl/shader/chunks/assign-material-color.glsl.ts
  30. 22 6
      src/mol-gl/shader/chunks/color-vert-params.glsl.ts
  31. 4 2
      src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts
  32. 3 3
      src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts
  33. 3 1
      src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts
  34. 16 3
      src/mol-gl/shader/direct-volume.frag.ts
  35. 1 4
      src/mol-gl/shader/mesh.vert.ts
  36. 5 4
      src/mol-gl/webgl/buffer.ts
  37. 1 1
      src/mol-gl/webgl/extensions.ts
  38. 5 3
      src/mol-gl/webgl/program.ts
  39. 5 9
      src/mol-gl/webgl/render-item.ts
  40. 2 2
      src/mol-gl/webgl/resources.ts
  41. 33 2
      src/mol-gl/webgl/state.ts
  42. 10 2
      src/mol-gl/webgl/vertex-array.ts
  43. 128 0
      src/mol-io/reader/_spec/sdf.spec.ts
  44. 2 2
      src/mol-io/reader/cif/schema/bird.ts
  45. 1 1
      src/mol-io/reader/cif/schema/ccd.ts
  46. 15 5
      src/mol-io/reader/cif/schema/cif-core.ts
  47. 1 1
      src/mol-io/reader/cif/schema/mmcif.ts
  48. 109 0
      src/mol-io/reader/sdf/parser-v3-util.ts
  49. 14 5
      src/mol-io/reader/sdf/parser.ts
  50. 3 3
      src/mol-model/structure/structure/properties.ts
  51. 4 2
      src/mol-plugin-state/actions/file.ts
  52. 3 1
      src/mol-plugin-state/formats/volume.ts
  53. 14 3
      src/mol-plugin-state/helpers/structure-selection-query.ts
  54. 2 1
      src/mol-plugin-state/manager/snapshots.ts
  55. 41 15
      src/mol-plugin-state/transforms/representation.ts
  56. 16 18
      src/mol-plugin-ui/skin/base/components/controls.scss
  57. 3 1
      src/mol-plugin-ui/skin/base/components/toast.scss
  58. 2 1
      src/mol-plugin-ui/state/common.tsx
  59. 5 2
      src/mol-plugin-ui/state/snapshots.tsx
  60. 2 1
      src/mol-plugin-ui/structure/selection.tsx
  61. 1 2
      src/mol-plugin/context.ts
  62. 35 2
      src/mol-repr/representation.ts
  63. 5 0
      src/mol-repr/shape/representation.ts
  64. 12 5
      src/mol-repr/structure/complex-representation.ts
  65. 12 5
      src/mol-repr/structure/complex-visual.ts
  66. 16 16
      src/mol-repr/structure/units-representation.ts
  67. 12 5
      src/mol-repr/structure/units-visual.ts
  68. 9 7
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  69. 5 4
      src/mol-repr/structure/visual/molecular-surface-mesh.ts
  70. 0 104
      src/mol-repr/structure/visual/util/color.ts
  71. 68 6
      src/mol-repr/visual.ts
  72. 14 3
      src/mol-repr/volume/representation.ts
  73. 7 4
      src/mol-state/state.ts
  74. 0 2
      src/mol-task/execution/observable.ts
  75. 1 1
      src/mol-util/download.ts
  76. 11 7
      webpack.config.common.js

+ 21 - 0
CHANGELOG.md

@@ -6,6 +6,27 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+## [v2.4.0] - 2021-11-25
+
+- Fix secondary-structure property handling
+    - StructureElement.Property was incorrectly resolving type & key
+    - StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
+- Re-enable VAO with better workaround (bind null elements buffer before deleting)
+- Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes)
+- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces
+
+## [v2.3.9] - 2021-11-20
+
+- Workaround: switch off VAO support for now
+
+## [v2.3.8] - 2021-11-20
+
+- Fix double canvas context creation (in plugin context)
+- Fix unused vertex attribute handling (track which are used, disable the rest)
+- Workaround for VAO issue in Chrome 96 (can cause WebGL to crash on geometry updates)
+
+## [v2.3.7] - 2021-11-15
+
 - Added ``ViewerOptions.collapseRightPanel``
 - Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
 - Fix: handle parent in Structure.remapModel

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 255 - 243
package-lock.json


+ 29 - 30
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "2.3.6",
+  "version": "2.4.0",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {
@@ -89,52 +89,51 @@
   "license": "MIT",
   "devDependencies": {
     "@graphql-codegen/add": "^3.1.0",
-    "@graphql-codegen/cli": "^2.2.0",
+    "@graphql-codegen/cli": "^2.3.0",
     "@graphql-codegen/time": "^3.1.0",
-    "@graphql-codegen/typescript": "^2.2.2",
+    "@graphql-codegen/typescript": "^2.4.1",
     "@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
-    "@graphql-codegen/typescript-graphql-request": "^4.1.4",
-    "@graphql-codegen/typescript-operations": "^2.1.6",
+    "@graphql-codegen/typescript-graphql-request": "^4.3.1",
+    "@graphql-codegen/typescript-operations": "^2.2.1",
     "@types/cors": "^2.8.12",
     "@types/gl": "^4.1.0",
-    "@types/jest": "^27.0.2",
-    "@typescript-eslint/eslint-plugin": "^4.32.0",
-    "@typescript-eslint/parser": "^4.32.0",
+    "@types/jest": "^27.0.3",
+    "@typescript-eslint/eslint-plugin": "^5.4.0",
+    "@typescript-eslint/parser": "^5.4.0",
     "benchmark": "^2.1.4",
-    "concurrently": "^6.3.0",
+    "concurrently": "^6.4.0",
     "cpx2": "^4.0.0",
     "crypto-browserify": "^3.12.0",
-    "css-loader": "^6.3.0",
-    "eslint": "^7.32.0",
+    "css-loader": "^6.5.1",
+    "eslint": "^8.3.0",
     "extra-watch-webpack-plugin": "^1.0.3",
     "file-loader": "^6.2.0",
     "fs-extra": "^10.0.0",
-    "graphql": "^15.6.0",
-    "http-server": "^13.0.2",
-    "jest": "^27.2.4",
-    "mini-css-extract-plugin": "^2.3.0",
-    "node-sass": "^6.0.1",
+    "graphql": "^15.7.2",
+    "http-server": "^14.0.0",
+    "jest": "^27.3.1",
+    "mini-css-extract-plugin": "^2.4.5",
     "path-browserify": "^1.0.1",
     "raw-loader": "^4.0.2",
-    "sass-loader": "^12.1.0",
-    "simple-git": "^2.46.0",
+    "sass": "^1.43.5",
+    "sass-loader": "^12.3.0",
+    "simple-git": "^2.47.0",
     "stream-browserify": "^3.0.0",
-    "style-loader": "^3.3.0",
-    "ts-jest": "^27.0.5",
-    "typescript": "^4.4.3",
-    "webpack": "^5.56.0",
-    "webpack-cli": "^4.8.0",
-    "webpack-version-file-plugin": "^0.4.0"
+    "style-loader": "^3.3.1",
+    "ts-jest": "^27.0.7",
+    "typescript": "^4.5.2",
+    "webpack": "^5.64.4",
+    "webpack-cli": "^4.9.1"
   },
   "dependencies": {
     "@types/argparse": "^2.0.10",
     "@types/benchmark": "^2.1.1",
     "@types/compression": "1.7.2",
     "@types/express": "^4.17.13",
-    "@types/node": "^16.10.2",
+    "@types/node": "^16.11.10",
     "@types/node-fetch": "^2.5.12",
-    "@types/react": "^17.0.27",
-    "@types/react-dom": "^17.0.9",
+    "@types/react": "^17.0.37",
+    "@types/react-dom": "^17.0.11",
     "@types/swagger-ui-dist": "3.30.1",
     "argparse": "^2.0.1",
     "body-parser": "^1.19.0",
@@ -142,13 +141,13 @@
     "cors": "^2.8.5",
     "express": "^4.17.1",
     "h264-mp4-encoder": "^1.0.12",
-    "immer": "^9.0.6",
+    "immer": "^9.0.7",
     "immutable": "^3.8.2",
     "node-fetch": "^2.6.2",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
-    "rxjs": "^7.3.1",
-    "swagger-ui-dist": "^3.52.3",
+    "rxjs": "^7.4.0",
+    "swagger-ui-dist": "^4.1.1",
     "tslib": "^2.3.1",
     "util.promisify": "^1.1.1",
     "xhr2": "^0.2.1"

+ 1 - 0
src/cli/cifschema/util/cif-dic.ts

@@ -84,6 +84,7 @@ export function getFieldType(type: string, description: string, values?: string[
         case 'DateTime':
         case 'Tag':
         case 'Implied':
+        case 'Word':
             return wrapContainer('str', ',', description, container);
         case 'Real':
             return wrapContainer('float', ',', description, container);

+ 1 - 1
src/extensions/geo-export/controls.ts

@@ -75,7 +75,7 @@ export class GeometryControls extends PluginComponent {
                     filename: filename + '.' + renderObjectExporter.fileExtension
                 };
             } catch (e) {
-                this.plugin.log.error('' + e);
+                this.plugin.log.error('Error during geometry export');
                 throw e;
             }
         });

+ 31 - 18
src/extensions/geo-export/glb-exporter.ts

@@ -5,7 +5,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { BaseValues } from '../../mol-gl/renderable/schema';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
 import { Box3D } from '../../mol-math/geometry';
@@ -15,7 +14,7 @@ import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { fillSerial } from '../../mol-util/array';
 import { NumberArray } from '../../mol-util/type-helpers';
-import { MeshExporter, AddMeshInput } from './mesh-exporter';
+import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
@@ -30,6 +29,12 @@ const FLOAT = 5126;
 const ARRAY_BUFFER = 34962;
 const ELEMENT_ARRAY_BUFFER = 34963;
 
+const GLTF_MAGIC_BYTE = 0x46546C67;
+const JSON_CHUNK_TYPE = 0x4E4F534A;
+const BIN_CHUNK_TYPE = 0x004E4942;
+const JSON_PAD_CHAR = 0x20;
+const BIN_PAD_CHAR = 0x00;
+
 export type GlbData = {
     glb: Uint8Array
 }
@@ -128,23 +133,17 @@ export class GlbExporter extends MeshExporter<GlbData> {
         };
     }
 
-    private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined) {
-        const groupCount = values.uGroupCount.ref.value;
+    private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) {
+        const { values, vertexCount } = geoData;
         const uAlpha = values.uAlpha.ref.value;
-        const dTransparency = values.dTransparency.ref.value;
-        const tTransparency = values.tTransparency.ref.value;
 
         const colorArray = new Uint8Array(vertexCount * 4);
 
         for (let i = 0; i < vertexCount; ++i) {
-            let color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i);
+            let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint);
 
-            let alpha = uAlpha;
-            if (dTransparency) {
-                const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
-                const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                alpha *= 1 - transparency;
-            }
+            const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency);
+            const alpha = uAlpha * (1 - transparency);
 
             color = Color.sRGBToLinear(color);
             Color.toArray(color, colorArray, i * 4);
@@ -180,6 +179,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const t = Mat4();
 
         const colorType = values.dColorType.ref.value;
+        const overpaintType = values.dOverpaintType.ref.value;
+        const transparencyType = values.dTransparencyType.ref.value;
         const dTransparency = values.dTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
@@ -191,7 +192,19 @@ export class GlbExporter extends MeshExporter<GlbData> {
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
+            interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        }
+
+        let interpolatedOverpaint: Uint8Array | undefined;
+        if (overpaintType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        }
+
+        let interpolatedTransparency: Uint8Array | undefined;
+        if (transparencyType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
         }
 
         // instancing
@@ -222,7 +235,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
 
                 // create a color buffer if needed
                 if (instanceIndex === 0 || !sameColorBuffer) {
-                    colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors);
+                    colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
                 }
 
                 // glTF mesh
@@ -294,13 +307,13 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const jsonBuffer = new Uint8Array(jsonString.length);
         asciiWrite(jsonBuffer, jsonString);
 
-        const [jsonChunk, jsonChunkLength] = createChunk(0x4E4F534A, [jsonBuffer.buffer], jsonBuffer.length, 0x20);
-        const [binaryChunk, binaryChunkLength] = createChunk(0x004E4942, this.binaryBuffer, binaryBufferLength, 0x00);
+        const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR);
+        const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR);
 
         const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
         const header = new ArrayBuffer(12);
         const headerDataView = new DataView(header);
-        headerDataView.setUint32(0, 0x46546C67, true); // magic number "glTF"
+        headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF"
         headerDataView.setUint32(4, 2, true); // version
         headerDataView.setUint32(8, glbBufferLength, true); // length
         const glbBuffer = [header, ...jsonChunk, ...binaryChunk];

+ 99 - 15
src/extensions/geo-export/mesh-exporter.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { sort, arraySwap } from '../../mol-data/util';
@@ -26,6 +27,7 @@ import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { decodeFloatRGB } from '../../mol-util/float-packing';
 import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
+import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
 
 const GeoExportName = 'geo-export';
 
@@ -48,6 +50,14 @@ export interface AddMeshInput {
     ctx: RuntimeContext
 }
 
+export type MeshGeoData = {
+    values: BaseValues,
+    groups: Float32Array | Uint8Array,
+    vertexCount: number,
+    instanceIndex: number,
+    isGeoTexture: boolean
+}
+
 export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
     abstract readonly fileExtension: string;
 
@@ -90,26 +100,43 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         return decodeFloatRGB(r, g, b);
     }
 
-    protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
+    protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
+        const { values, vertexCount, vertices, colorType, stride } = input;
         const colorGridTransform = values.uColorGridTransform.ref.value;
         const colorGridDim = values.uColorGridDim.ref.value;
         const colorTexDim = values.uColorTexDim.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
-        if (!webgl.namedFramebuffers[GeoExportName]) {
-            webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
-        }
-        const framebuffer = webgl.namedFramebuffers[GeoExportName];
+        const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
+        return interpolated.array;
+    }
 
-        const [width, height] = colorTexDim;
-        const colorGrid = new Uint8Array(width * height * 4);
+    protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
+        const { values, vertexCount, vertices, colorType, stride } = input;
+        const overpaintGridTransform = values.uOverpaintGridTransform.ref.value;
+        const overpaintGridDim = values.uOverpaintGridDim.ref.value;
+        const overpaintTexDim = values.uOverpaintTexDim.ref.value;
+        const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
 
-        framebuffer.bind();
-        values.tColorGrid.ref.value.attachFramebuffer(framebuffer, 0);
-        webgl.readPixels(0, 0, width, height, colorGrid);
+        const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array;
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 });
+        return interpolated.array;
+    }
+
+    protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
+        const { values, vertexCount, vertices, colorType, stride } = input;
+        const transparencyGridTransform = values.uTransparencyGridTransform.ref.value;
+        const transparencyGridDim = values.uTransparencyGridDim.ref.value;
+        const transparencyTexDim = values.uTransparencyTexDim.ref.value;
+        const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
+
+        const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array;
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 });
 
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
         return interpolated.array;
     }
 
@@ -194,11 +221,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         }
     }
 
-    protected static getColor(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined, vertexIndex: number): Color {
+    protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
+        const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
         const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
         const uColor = values.uColor.ref.value;
         const tColor = values.tColor.ref.value.array;
+        const overpaintType = values.dOverpaintType.ref.value;
         const dOverpaint = values.dOverpaint.ref.value;
         const tOverpaint = values.tOverpaint.ref.value.array;
 
@@ -236,15 +265,70 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         }
 
         if (dOverpaint) {
-            const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
-            const overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4);
-            const overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255;
+            let overpaintColor: Color;
+            let overpaintAlpha: number;
+            switch (overpaintType) {
+                case 'groupInstance': {
+                    const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
+                    const idx = (instanceIndex * groupCount + group) * 4;
+                    overpaintColor = Color.fromArray(tOverpaint, idx);
+                    overpaintAlpha = tOverpaint[idx + 3] / 255;
+                    break;
+                }
+                case 'vertexInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
+                    overpaintColor = Color.fromArray(tOverpaint, idx);
+                    overpaintAlpha = tOverpaint[idx + 3] / 255;
+                    break;
+                }
+                case 'volumeInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
+                    overpaintColor = Color.fromArray(interpolatedOverpaint!, idx);
+                    overpaintAlpha = interpolatedOverpaint![idx + 3] / 255;
+                    break;
+                }
+                default: throw new Error('Unsupported overpaint type.');
+            }
+            // interpolate twice to avoid darkening due to empty overpaint
+            overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha);
             color = Color.interpolate(color, overpaintColor, overpaintAlpha);
         }
 
         return color;
     }
 
+    protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
+        const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
+        const groupCount = values.uGroupCount.ref.value;
+        const dTransparency = values.dTransparency.ref.value;
+        const tTransparency = values.tTransparency.ref.value.array;
+        const transparencyType = values.dTransparencyType.ref.value;
+
+        let transparency: number = 0;
+        if (dTransparency) {
+            switch (transparencyType) {
+                case 'groupInstance': {
+                    const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
+                    const idx = (instanceIndex * groupCount + group);
+                    transparency = tTransparency[idx] / 255;
+                    break;
+                }
+                case 'vertexInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex);
+                    transparency = tTransparency[idx] / 255;
+                    break;
+                }
+                case 'volumeInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex);
+                    transparency = interpolatedTransparency![idx] / 255;
+                    break;
+                }
+                default: throw new Error('Unsupported transparency type.');
+            }
+        }
+        return transparency;
+    }
+
     protected abstract addMeshWithColors(input: AddMeshInput): void;
 
     private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {

+ 31 - 15
src/extensions/geo-export/obj-exporter.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { asciiWrite } from '../../mol-io/common/ascii';
@@ -47,7 +48,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
         StringBuilder.newline(this.obj);
         if (!this.materialSet.has(material)) {
             this.materialSet.add(material);
-            const [r, g, b] = Color.toRgbNormalized(color);
+            const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
             const mtl = this.mtl;
             StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
             StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
@@ -77,18 +78,27 @@ export class ObjExporter extends MeshExporter<ObjData> {
         const tmpV = Vec3();
         const stride = isGeoTexture ? 4 : 3;
 
-        const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
+        const overpaintType = values.dOverpaintType.ref.value;
+        const transparencyType = values.dTransparencyType.ref.value;
         const uAlpha = values.uAlpha.ref.value;
-        const dTransparency = values.dTransparency.ref.value;
-        const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
-            interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
-            ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
+            interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        }
+
+        let interpolatedOverpaint: Uint8Array | undefined;
+        if (overpaintType === 'volumeInstance') {
+            interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        }
+
+        let interpolatedTransparency: Uint8Array | undefined;
+        if (transparencyType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
         }
 
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -126,17 +136,23 @@ export class ObjExporter extends MeshExporter<ObjData> {
                 StringBuilder.newline(obj);
             }
 
-            // face
+            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
+
+            // color
+            const quantizedColors = new Uint8Array(drawCount * 3);
             for (let i = 0; i < drawCount; i += 3) {
                 const v = isGeoTexture ? i : indices![i];
-                const color = ObjExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
-
-                let alpha = uAlpha;
-                if (dTransparency) {
-                    const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
-                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                    alpha *= 1 - transparency;
-                }
+                const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
+                Color.toArray(color, quantizedColors, i);
+            }
+            ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
+
+            // face
+            for (let i = 0; i < drawCount; i += 3) {
+                const color = Color.fromArray(quantizedColors, i);
+
+                const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency);
+                const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
 
                 this.updateMaterial(color, alpha);
 

+ 6 - 5
src/extensions/geo-export/ui.tsx

@@ -79,10 +79,10 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
         try {
             this.setState({ busy: true });
             const data = await this.controls.exportGeometry();
-            this.setState({ busy: false });
-
             download(data.blob, data.filename);
-        } catch {
+        } catch (e) {
+            console.error(e);
+        } finally {
             this.setState({ busy: false });
         }
     }
@@ -91,7 +91,6 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
         try {
             this.setState({ busy: true });
             const data = await this.controls.exportGeometry();
-            this.setState({ busy: false });
             const a = document.createElement('a');
             a.rel = 'ar';
             a.href = URL.createObjectURL(data.blob);
@@ -100,7 +99,9 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
             a.appendChild(document.createElement('img'));
             setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
             setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
-        } catch {
+        } catch (e) {
+            console.error(e);
+        } finally {
             this.setState({ busy: false });
         }
     }

+ 35 - 14
src/extensions/geo-export/usdz-exporter.ts

@@ -41,7 +41,12 @@ export class UsdzExporter extends MeshExporter<UsdzData> {
         const materialKey = this.materialMap.size;
         this.materialMap.set(hash, materialKey);
 
-        const [r, g, b] = Color.toRgbNormalized(color);
+        // const [r, g, b] = Color.toRgbNormalized(color);
+        // private addMaterial(color: Color, alpha: number) {
+        //     const materialKey = UsdzExporter.getMaterialKey(color, alpha);
+        //     if (this.materialSet.has(materialKey)) return;
+        //     this.materialSet.add(materialKey);
+        const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
         this.materials.push(`
 def Material "material${materialKey}"
 {
@@ -68,11 +73,10 @@ def Material "material${materialKey}"
         const tmpV = Vec3();
         const stride = isGeoTexture ? 4 : 3;
 
-        const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
+        const overpaintType = values.dOverpaintType.ref.value;
+        const transparencyType = values.dTransparencyType.ref.value;
         const uAlpha = values.uAlpha.ref.value;
-        const dTransparency = values.dTransparency.ref.value;
-        const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
         const metalness = values.uMetalness.ref.value;
@@ -80,8 +84,19 @@ def Material "material${materialKey}"
 
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
-            interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
-            UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
+            interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        }
+
+        let interpolatedOverpaint: Uint8Array | undefined;
+        if (overpaintType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        }
+
+        let interpolatedTransparency: Uint8Array | undefined;
+        if (transparencyType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
         }
 
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -123,6 +138,8 @@ def Material "material${materialKey}"
                 StringBuilder.writeSafe(normalBuilder, ')');
             }
 
+            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
+
             // face
             for (let i = 0; i < drawCount; ++i) {
                 const v = isGeoTexture ? i : indices![i];
@@ -131,17 +148,21 @@ def Material "material${materialKey}"
             }
 
             // color
-            const faceIndicesByMaterial = new Map<number, number[]>();
+            const quantizedColors = new Uint8Array(drawCount * 3);
             for (let i = 0; i < drawCount; i += 3) {
                 const v = isGeoTexture ? i : indices![i];
-                const color = UsdzExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
+                const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
+                Color.toArray(color, quantizedColors, i);
+            }
+            UsdzExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
 
-                let alpha = uAlpha;
-                if (dTransparency) {
-                    const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
-                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                    alpha *= 1 - transparency;
-                }
+            // material
+            const faceIndicesByMaterial = new Map<number, number[]>();
+            for (let i = 0; i < drawCount; i += 3) {
+                const color = Color.fromArray(quantizedColors, i);
+
+                const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
+                const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
 
                 const materialKey = this.addMaterial(color, alpha, metalness, roughness);
                 let faceIndices = faceIndicesByMaterial.get(materialKey);

+ 1 - 1
src/extensions/mp4-export/controls.ts

@@ -73,7 +73,7 @@ export class Mp4Controls extends PluginComponent {
                 const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
                 return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
             } catch (e) {
-                this.plugin.log.error('' + e);
+                this.plugin.log.error('Error during animation export');
                 throw e;
             }
         });

+ 2 - 1
src/extensions/mp4-export/ui.tsx

@@ -115,7 +115,8 @@ export class Mp4EncoderUI extends CollapsableControls<{}, State> {
             this.setState({ busy: true });
             const data = await this.controls.render();
             this.setState({ busy: false, data });
-        } catch {
+        } catch (e) {
+            console.error(e);
             this.setState({ busy: false });
         }
     }

+ 8 - 7
src/extensions/rcsb/graphql/types.ts

@@ -1,9 +1,10 @@
 /* eslint-disable */
 export type Maybe<T> = T | null;
+export type InputMaybe<T> = Maybe<T>;
 export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
 export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
-// Generated on 2021-09-12T13:31:40-07:00
+// Generated on 2021-11-25T14:34:23-08:00
 
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
@@ -6769,7 +6770,7 @@ export type Query = {
 
 /** Query root */
 export type QueryAssembliesArgs = {
-  assembly_ids: ReadonlyArray<Maybe<Scalars['String']>>;
+  assembly_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
 };
 
 
@@ -6802,7 +6803,7 @@ export type QueryBranched_Entity_InstanceArgs = {
 
 /** Query root */
 export type QueryBranched_Entity_InstancesArgs = {
-  instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
+  instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
 };
 
 
@@ -6814,7 +6815,7 @@ export type QueryChem_CompArgs = {
 
 /** Query root */
 export type QueryChem_CompsArgs = {
-  comp_ids: ReadonlyArray<Maybe<Scalars['String']>>;
+  comp_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
 };
 
 
@@ -6852,7 +6853,7 @@ export type QueryNonpolymer_Entity_InstanceArgs = {
 
 /** Query root */
 export type QueryNonpolymer_Entity_InstancesArgs = {
-  instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
+  instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
 };
 
 
@@ -6878,7 +6879,7 @@ export type QueryPolymer_Entity_InstanceArgs = {
 
 /** Query root */
 export type QueryPolymer_Entity_InstancesArgs = {
-  instance_ids: ReadonlyArray<Maybe<Scalars['String']>>;
+  instance_ids: ReadonlyArray<InputMaybe<Scalars['String']>>;
 };
 
 
@@ -13144,4 +13145,4 @@ export type AssemblySymmetryQueryVariables = Exact<{
 }>;
 
 
-export type AssemblySymmetryQuery = { readonly assembly?: Maybe<{ readonly rcsb_struct_symmetry?: Maybe<ReadonlyArray<Maybe<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<Maybe<string>>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<Maybe<{ readonly avg_rmsd?: Maybe<number>, readonly members: ReadonlyArray<Maybe<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: Maybe<ReadonlyArray<Maybe<string>>> }>> }>>, readonly rotation_axes?: Maybe<ReadonlyArray<Maybe<{ readonly order?: Maybe<number>, readonly start: ReadonlyArray<Maybe<number>>, readonly end: ReadonlyArray<Maybe<number>> }>>> }>>> }> };
+export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null | undefined>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null | undefined, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null | undefined> | null | undefined } | null | undefined> } | null | undefined>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null | undefined, readonly start: ReadonlyArray<number | null | undefined>, readonly end: ReadonlyArray<number | null | undefined> } | null | undefined> | null | undefined } | null | undefined> | null | undefined } | null | undefined };

+ 3 - 24
src/mol-canvas3d/passes/draw.ts

@@ -22,11 +22,11 @@ import { Helper } from '../helper/helper';
 
 import { quad_vert } from '../../mol-gl/shader/quad.vert';
 import { depthMerge_frag } from '../../mol-gl/shader/depth-merge.frag';
-import { copy_frag } from '../../mol-gl/shader/copy.frag';
 import { StereoCamera } from '../camera/stereo';
 import { WboitPass } from './wboit';
 import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
 import { MarkingPass, MarkingProps } from './marking';
+import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
 
 const DepthMergeSchema = {
     ...QuadSchema,
@@ -53,27 +53,6 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
     return createComputeRenderable(renderItem, values);
 }
 
-const CopySchema = {
-    ...QuadSchema,
-    tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
-    uTexSize: UniformSpec('v2'),
-};
-const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
-type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
-
-function getCopyRenderable(ctx: WebGLContext, colorTexture: Texture): CopyRenderable {
-    const values: Values<typeof CopySchema> = {
-        ...QuadValues,
-        tColor: ValueCell.create(colorTexture),
-        uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
-    };
-
-    const schema = { ...CopySchema };
-    const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
-
-    return createComputeRenderable(renderItem, values);
-}
-
 export class DrawPass {
     private readonly drawTarget: RenderTarget
 
@@ -128,8 +107,8 @@ export class DrawPass {
         this.postprocessing = new PostprocessingPass(webgl, this);
         this.antialiasing = new AntialiasingPass(webgl, this);
 
-        this.copyFboTarget = getCopyRenderable(webgl, this.colorTarget.texture);
-        this.copyFboPostprocessing = getCopyRenderable(webgl, this.postprocessing.target.texture);
+        this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
+        this.copyFboPostprocessing = createCopyRenderable(webgl, this.postprocessing.target.texture);
     }
 
     reset() {

+ 35 - 0
src/mol-geo/geometry/base.ts

@@ -15,6 +15,7 @@ import { ColorNames } from '../../mol-util/color/names';
 import { NullLocation } from '../../mol-model/location';
 import { UniformColorTheme } from '../../mol-theme/color/uniform';
 import { UniformSizeTheme } from '../../mol-theme/size/uniform';
+import { smoothstep } from '../../mol-math/interpolate';
 
 export const VisualQualityInfo = {
     'custom': {},
@@ -33,6 +34,40 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames);
 
 //
 
+export const ColorSmoothingParams = {
+    smoothColors: PD.MappedStatic('auto', {
+        auto: PD.Group({}),
+        on: PD.Group({
+            resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
+            sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
+        }),
+        off: PD.Group({})
+    }),
+};
+export type ColorSmoothingParams = typeof ColorSmoothingParams
+
+export function hasColorSmoothingProp(props: PD.Values<any>): props is PD.Values<ColorSmoothingParams> {
+    return !!props.smoothColors;
+}
+
+export function getColorSmoothingProps(smoothColors: PD.Values<ColorSmoothingParams>['smoothColors'], preferSmoothing?: boolean, resolution?: number) {
+    if ((smoothColors.name === 'on' || (smoothColors.name === 'auto' && preferSmoothing)) && resolution && resolution < 3) {
+        let stride = 3;
+        if (smoothColors.name === 'on') {
+            resolution *= smoothColors.params.resolutionFactor;
+            stride = smoothColors.params.sampleStride;
+        } else {
+            // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
+            resolution *= 2 - smoothstep(0, 1.1, resolution);
+            resolution = Math.max(0.5, resolution);
+            if (resolution > 1.2) stride = 2;
+        }
+        return { resolution, stride };
+    };
+}
+
+//
+
 export namespace BaseGeometry {
     export const MaterialCategory: PD.Info = { category: 'Material' };
     export const ShadingCategory: PD.Info = { category: 'Shading' };

+ 166 - 37
src/mol-geo/geometry/mesh/color-smoothing.ts

@@ -4,13 +4,15 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { Box3D, Sphere3D } from '../../../mol-math/geometry';
+import { lerp } from '../../../mol-math/interpolate';
 import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
-import { Color } from '../../../mol-util/color';
+import { ValueCell } from '../../../mol-util';
 
 interface ColorSmoothingInput {
     vertexCount: number
@@ -24,10 +26,11 @@ interface ColorSmoothingInput {
     colorType: 'group' | 'groupInstance'
     boundingSphere: Sphere3D
     invariantBoundingSphere: Sphere3D
+    itemSize: 4 | 3 | 1
 }
 
 export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl?: WebGLContext, texture?: Texture) {
-    const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer } = input;
+    const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer, itemSize } = input;
 
     const isInstanceType = colorType.endsWith('Instance');
     const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
@@ -43,7 +46,6 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     const { width, height } = getVolumeTexture2dLayout(gridDim);
     // console.log({ width, height, dim });
 
-    const itemSize = 3;
     const data = new Float32Array(width * height * itemSize);
     const count = new Float32Array(width * height);
 
@@ -78,10 +80,7 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
             const z = Math.floor(vz);
 
             // group colors
-            const ci = i * groupCount + groupBuffer[j];
-            const r = colors[ci * 3];
-            const g = colors[ci * 3 + 1];
-            const b = colors[ci * 3 + 2];
+            const ci = (i * groupCount + groupBuffer[j]) * itemSize;
 
             // Extents of grid to consider for this atom
             const begX = Math.max(0, x - p);
@@ -106,10 +105,10 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
 
                         const s = p - d;
                         const index = getIndex(xi, yi, zi);
-                        data[index] += r * s;
-                        data[index + 1] += g * s;
-                        data[index + 2] += b * s;
-                        count[index / 3] += s;
+                        for (let k = 0; k < itemSize; ++k) {
+                            data[index + k] += colors[ci + k] * s;
+                        }
+                        count[index / itemSize] += s;
                     }
                 }
             }
@@ -117,11 +116,11 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     }
 
     for (let i = 0, il = count.length; i < il; ++i) {
-        const i3 = i * 3;
+        const is = i * itemSize;
         const c = count[i];
-        grid[i3] = Math.round(data[i3] / c);
-        grid[i3 + 1] = Math.round(data[i3 + 1] / c);
-        grid[i3 + 2] = Math.round(data[i3 + 2] / c);
+        for (let k = 0; k < itemSize; ++k) {
+            grid[is + k] = Math.round(data[is + k] / c);
+        }
     }
 
     const gridTexDim = Vec2.create(width, height);
@@ -129,12 +128,16 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     const type = isInstanceType ? 'volumeInstance' as const : 'volume' as const;
 
     if (webgl) {
-        if (!texture) texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+        if (!texture) {
+            const format = itemSize === 4 ? 'rgba' :
+                itemSize === 3 ? 'rgb' : 'alpha';
+            texture = webgl.resources.texture('image-uint8', format, 'ubyte', 'linear');
+        }
         texture.load(textureImage);
 
         return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
     } else {
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 });
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: itemSize, outputStride: itemSize });
 
         return {
             kind: 'vertex' as const,
@@ -157,16 +160,24 @@ interface ColorInterpolationInput {
     gridTexDim: Vec2
     gridDim: Vec3
     gridTransform: Vec4
-    vertexStride: number
-    colorStride: number
+    vertexStride: 3 | 4
+    colorStride: 1 | 3 | 4
+    outputStride: 1 | 3 | 4
+    itemOffset?: 0 | 1 | 2 | 3
 }
 
 export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
     const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input;
 
+    const itemOffset = input.itemOffset || 0;
+    const outputStride = input.outputStride;
+    if (outputStride + itemOffset > colorStride) {
+        throw new Error('outputStride + itemOffset must NOT be larger than colorStride');
+    }
+
     const isInstanceType = input.colorType.endsWith('Instance');
     const instanceCount = isInstanceType ? input.instanceCount : 1;
-    const image = createTextureImage(Math.max(1, instanceCount * vertexCount), 3, Uint8Array);
+    const image = createTextureImage(Math.max(1, instanceCount * vertexCount), outputStride, Uint8Array);
     const { array } = image;
 
     const [xn, yn] = gridDim;
@@ -204,26 +215,144 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text
             const [x1, y1, z1] = v1;
             const [xd, yd, zd] = vd;
 
-            const s000 = Color.fromArray(grid, getIndex(x0, y0, z0));
-            const s100 = Color.fromArray(grid, getIndex(x1, y0, z0));
-            const s001 = Color.fromArray(grid, getIndex(x0, y0, z1));
-            const s101 = Color.fromArray(grid, getIndex(x1, y0, z1));
-            const s010 = Color.fromArray(grid, getIndex(x0, y1, z0));
-            const s110 = Color.fromArray(grid, getIndex(x1, y1, z0));
-            const s011 = Color.fromArray(grid, getIndex(x0, y1, z1));
-            const s111 = Color.fromArray(grid, getIndex(x1, y1, z1));
+            const i000 = getIndex(x0, y0, z0) + itemOffset;
+            const i100 = getIndex(x1, y0, z0) + itemOffset;
+            const i001 = getIndex(x0, y0, z1) + itemOffset;
+            const i101 = getIndex(x1, y0, z1) + itemOffset;
+            const i010 = getIndex(x0, y1, z0) + itemOffset;
+            const i110 = getIndex(x1, y1, z0) + itemOffset;
+            const i011 = getIndex(x0, y1, z1) + itemOffset;
+            const i111 = getIndex(x1, y1, z1) + itemOffset;
+
+            const o = (i * vertexCount + j) * outputStride;
+
+            for (let k = 0; k < outputStride; ++k) {
+                const s000 = grid[i000 + k];
+                const s100 = grid[i100 + k];
+                const s001 = grid[i001 + k];
+                const s101 = grid[i101 + k];
+                const s010 = grid[i010 + k];
+                const s110 = grid[i110 + k];
+                const s011 = grid[i011 + k];
+                const s111 = grid[i111 + k];
+
+                const s00 = lerp(s000, s100, xd);
+                const s01 = lerp(s001, s101, xd);
+                const s10 = lerp(s010, s110, xd);
+                const s11 = lerp(s011, s111, xd);
+
+                const s0 = lerp(s00, s10, yd);
+                const s1 = lerp(s01, s11, yd);
+
+                array[o + k] = lerp(s0, s1, zd);
+            }
+        }
+    }
 
-            const s00 = Color.interpolate(s000, s100, xd);
-            const s01 = Color.interpolate(s001, s101, xd);
-            const s10 = Color.interpolate(s010, s110, xd);
-            const s11 = Color.interpolate(s011, s111, xd);
+    return image;
+}
 
-            const s0 = Color.interpolate(s00, s10, yd);
-            const s1 = Color.interpolate(s01, s11, yd);
+//
 
-            Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3);
-        }
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
+    return x === 'group' || x === 'groupInstance';
+}
+
+export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tColor.ref.value,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 3
+    }, resolution, stride, webgl, colorTexture);
+
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+        ValueCell.update(values.tColorGrid, smoothingData.texture);
+        ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+        ValueCell.update(values.tColor, smoothingData.texture);
+        ValueCell.update(values.uColorTexDim, smoothingData.texDim);
     }
+}
 
-    return image;
+function isSupportedOverpaintType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyMeshOverpaintSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tOverpaint.ref.value,
+        colorType: values.dOverpaintType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 4
+    }, resolution, stride, webgl, colorTexture);
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
+        ValueCell.update(values.tOverpaintGrid, smoothingData.texture);
+        ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
+        ValueCell.update(values.tOverpaint, smoothingData.texture);
+        ValueCell.update(values.uOverpaintTexDim, smoothingData.texDim);
+    }
+}
+
+function isSupportedTransparencyType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyMeshTransparencySmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tTransparency.ref.value,
+        colorType: values.dTransparencyType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 1
+    }, resolution, stride, webgl, colorTexture);
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
+        ValueCell.update(values.tTransparencyGrid, smoothingData.texture);
+        ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
+        ValueCell.update(values.tTransparency, smoothingData.texture);
+        ValueCell.update(values.uTransparencyTexDim, smoothingData.texDim);
+    }
 }

+ 18 - 2
src/mol-geo/geometry/overpaint-data.ts

@@ -1,18 +1,24 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ValueCell } from '../../mol-util/value-cell';
-import { Vec2 } from '../../mol-math/linear-algebra';
+import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { Color } from '../../mol-util/color';
+import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
 export type OverpaintData = {
     tOverpaint: ValueCell<TextureImage<Uint8Array>>
     uOverpaintTexDim: ValueCell<Vec2>
     dOverpaint: ValueCell<boolean>,
+
+    tOverpaintGrid: ValueCell<Texture>,
+    uOverpaintGridDim: ValueCell<Vec3>,
+    uOverpaintGridTransform: ValueCell<Vec4>,
+    dOverpaintType: ValueCell<string>,
 }
 
 export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) {
@@ -40,6 +46,11 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O
             tOverpaint: ValueCell.create(overpaint),
             uOverpaintTexDim: ValueCell.create(Vec2.create(overpaint.width, overpaint.height)),
             dOverpaint: ValueCell.create(count > 0),
+
+            tOverpaintGrid: ValueCell.create(createNullTexture()),
+            uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dOverpaintType: ValueCell.create('groupInstance'),
         };
     }
 }
@@ -55,6 +66,11 @@ export function createEmptyOverpaint(overpaintData?: OverpaintData): OverpaintDa
             tOverpaint: ValueCell.create(emptyOverpaintTexture),
             uOverpaintTexDim: ValueCell.create(Vec2.create(1, 1)),
             dOverpaint: ValueCell.create(false),
+
+            tOverpaintGrid: ValueCell.create(createNullTexture()),
+            uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dOverpaintType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 185 - 27
src/mol-geo/geometry/texture-mesh/color-smoothing.ts

@@ -18,7 +18,8 @@ import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { Box3D, Sphere3D } from '../../../mol-math/geometry';
 import { accumulate_frag } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.frag';
 import { accumulate_vert } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.vert';
-import { TextureImage } from '../../../mol-gl/renderable/util';
+import { isWebGL2 } from '../../../mol-gl/webgl/compat';
+import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 
 export const ColorAccumulateSchema = {
     drawCount: ValueSpec('number'),
@@ -38,7 +39,7 @@ export const ColorAccumulateSchema = {
     tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 
     uColorTexDim: UniformSpec('v2'),
-    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']),
 
     uCurrentSlice: UniformSpec('f'),
@@ -50,6 +51,7 @@ export const ColorAccumulateSchema = {
 };
 type ColorAccumulateValues = Values<typeof ColorAccumulateSchema>
 const ColorAccumulateName = 'color-accumulate';
+const ColorCountName = 'color-count';
 
 interface AccumulateInput {
     vertexCount: number
@@ -59,7 +61,7 @@ interface AccumulateInput {
     instanceBuffer: Float32Array
     positionTexture: Texture
     groupTexture: Texture
-    colorData: TextureImage<Uint8Array>
+    colorData: Texture
     colorType: 'group' | 'groupInstance'
 }
 
@@ -96,7 +98,7 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box:
         ValueCell.update(v.tPosition, input.positionTexture);
         ValueCell.update(v.tGroup, input.groupTexture);
 
-        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.width, input.colorData.height));
+        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.getWidth(), input.colorData.getHeight()));
         ValueCell.update(v.tColor, input.colorData);
         ValueCell.updateIfChanged(v.dColorType, input.colorType);
 
@@ -135,7 +137,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
         tPosition: ValueCell.create(input.positionTexture),
         tGroup: ValueCell.create(input.groupTexture),
 
-        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.width, input.colorData.height)),
+        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.getWidth(), input.colorData.getHeight())),
         tColor: ValueCell.create(input.colorData),
         dColorType: ValueCell.create(input.colorType),
 
@@ -148,7 +150,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
     };
 
     const schema = { ...ColorAccumulateSchema };
-    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag);
+    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag, { drawBuffers: 'required' });
     const renderItem = createComputeRenderItem(ctx, 'points', shaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
@@ -172,30 +174,33 @@ export const ColorNormalizeSchema = {
     ...QuadSchema,
 
     tColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tCount: TextureSpec('texture', 'alpha', 'float', 'nearest'),
     uTexSize: UniformSpec('v2'),
 
 };
 type ColorNormalizeValues = Values<typeof ColorNormalizeSchema>
 const ColorNormalizeName = 'color-normalize';
 
-function getNormalizeRenderable(ctx: WebGLContext, color: Texture): ComputeRenderable<ColorNormalizeValues> {
+function getNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture): ComputeRenderable<ColorNormalizeValues> {
     if (ctx.namedComputeRenderables[ColorNormalizeName]) {
         const v = ctx.namedComputeRenderables[ColorNormalizeName].values as ColorNormalizeValues;
 
         ValueCell.update(v.tColor, color);
+        ValueCell.update(v.tCount, count);
         ValueCell.update(v.uTexSize, Vec2.set(v.uTexSize.ref.value, color.getWidth(), color.getHeight()));
 
         ctx.namedComputeRenderables[ColorNormalizeName].update();
     } else {
-        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color);
+        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color, count);
     }
     return ctx.namedComputeRenderables[ColorNormalizeName];
 }
 
-function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture) {
+function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture) {
     const values: ColorNormalizeValues = {
         ...QuadValues,
         tColor: ValueCell.create(color),
+        tCount: ValueCell.create(count),
         uTexSize: ValueCell.create(Vec2.create(color.getWidth(), color.getHeight())),
     };
 
@@ -247,6 +252,9 @@ interface ColorSmoothingInput extends AccumulateInput {
 }
 
 export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl: WebGLContext, texture?: Texture) {
+    const { drawBuffers } = webgl.extensions;
+    if (!drawBuffers) throw new Error('need WebGL draw buffers');
+
     const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
 
     const isInstanceType = input.colorType.endsWith('Instance');
@@ -263,29 +271,55 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim);
     // console.log({ width, height, texCols, dim, resolution });
 
-    if (!webgl.namedTextures[ColorAccumulateName]) {
-        webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
-            ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
-            : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
+        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
+    }
+    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
+
+    if (isWebGL2(gl)) {
+        if (!webgl.namedTextures[ColorAccumulateName]) {
+            webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
+                ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
+                : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!webgl.namedTextures[ColorCountName]) {
+            webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'alpha', 'float', 'nearest');
+        }
+    } else {
+        // in webgl1 drawbuffers must be in the same format for some reason
+        // this is quite wasteful but good enough for medium size meshes
+
+        if (!webgl.namedTextures[ColorAccumulateName]) {
+            webgl.namedTextures[ColorAccumulateName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!webgl.namedTextures[ColorCountName]) {
+            webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
     }
+
     const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
+    const countTexture = webgl.namedTextures[ColorCountName];
+
     accumulateTexture.define(width, height);
+    countTexture.define(width, height);
+
+    accumulateTexture.attachFramebuffer(framebuffer, 0);
+    countTexture.attachFramebuffer(framebuffer, 1);
 
     const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
+    state.currentRenderItemId = -1;
 
-    //
+    framebuffer.bind();
+    drawBuffers.drawBuffers([
+        drawBuffers.COLOR_ATTACHMENT0,
+        drawBuffers.COLOR_ATTACHMENT1,
+    ]);
 
     const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
 
-    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
-        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
-    }
-    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
-    framebuffer.bind();
-
     setAccumulateDefaults(webgl);
-    state.currentRenderItemId = -1;
-    accumulateTexture.attachFramebuffer(framebuffer, 0);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
     gl.clear(gl.COLOR_BUFFER_BIT);
@@ -310,21 +344,31 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
         currX += dx;
     }
 
+    accumulateTexture.detachFramebuffer(framebuffer, 0);
+    countTexture.detachFramebuffer(framebuffer, 1);
+    drawBuffers.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE]);
+
     // const accImage = new Float32Array(width * height * 4);
     // accumulateTexture.attachFramebuffer(framebuffer, 0);
     // webgl.readPixels(0, 0, width, height, accImage);
     // console.log(accImage);
-    // printTextureImage({ array: accImage, width, height }, 1 / 4);
+    // printTextureImage({ array: accImage, width, height }, { scale: 1 });
+
+    // const cntImage = new Float32Array(width * height * 4);
+    // countTexture.attachFramebuffer(framebuffer, 0);
+    // webgl.readPixels(0, 0, width, height, cntImage);
+    // console.log(cntImage);
+    // printTextureImage({ array: cntImage, width, height }, { scale: 1 });
 
     // normalize
 
-    if (!texture) texture = resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+    if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.define(width, height);
 
-    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture);
+    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture, countTexture);
+    state.currentRenderItemId = -1;
 
     setNormalizeDefaults(webgl);
-    state.currentRenderItemId = -1;
     texture.attachFramebuffer(framebuffer, 0);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
@@ -335,10 +379,124 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     // texture.attachFramebuffer(framebuffer, 0);
     // webgl.readPixels(0, 0, width, height, normImage);
     // console.log(normImage);
-    // printTextureImage({ array: normImage, width, height }, 1 / 4);
+    // printTextureImage({ array: normImage, width, height }, { scale: 1 });
 
     const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor);
     const type = isInstanceType ? 'volumeInstance' : 'volume';
 
     return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
 }
+
+//
+
+const ColorSmoothingRgbName = 'color-smoothing-rgb';
+const ColorSmoothingRgbaName = 'color-smoothing-rgba';
+const ColorSmoothingAlphaName = 'color-smoothing-alpha';
+
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
+    return x === 'group' || x === 'groupInstance';
+}
+
+export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingRgbName]) {
+        webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingRgbName];
+    colorData.load(values.tColor.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+    ValueCell.update(values.tColorGrid, smoothingData.texture);
+    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
+}
+
+function isSupportedOverpaintType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyTextureMeshOverpaintSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingRgbaName]) {
+        webgl.namedTextures[ColorSmoothingRgbaName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingRgbaName];
+    colorData.load(values.tOverpaint.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dOverpaintType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
+    ValueCell.update(values.tOverpaintGrid, smoothingData.texture);
+    ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform);
+}
+
+function isSupportedTransparencyType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyTextureMeshTransparencySmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingAlphaName]) {
+        webgl.namedTextures[ColorSmoothingAlphaName] = webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingAlphaName];
+    colorData.load(values.tTransparency.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dTransparencyType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
+    ValueCell.update(values.tTransparencyGrid, smoothingData.texture);
+    ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
+}

+ 4 - 1
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -40,7 +40,7 @@ export interface TextureMesh {
 
     readonly boundingSphere: Sphere3D
 
-    meta?: unknown
+    readonly meta: { [k: string]: unknown }
 }
 
 export namespace TextureMesh {
@@ -92,6 +92,7 @@ export namespace TextureMesh {
                 normalTexture: ValueCell.create(normalTexture),
                 doubleBuffer: new DoubleBuffer(),
                 boundingSphere: Sphere3D.clone(boundingSphere),
+                meta: {}
             };
         }
     }
@@ -165,6 +166,8 @@ export namespace TextureMesh {
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
             dGeoTexture: ValueCell.create(true),
+
+            meta: ValueCell.create(textureMesh.meta),
         };
     }
 

+ 18 - 2
src/mol-geo/geometry/transparency-data.ts

@@ -1,18 +1,24 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ValueCell } from '../../mol-util/value-cell';
-import { Vec2 } from '../../mol-math/linear-algebra';
+import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
+import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
 export type TransparencyData = {
     tTransparency: ValueCell<TextureImage<Uint8Array>>
     uTransparencyTexDim: ValueCell<Vec2>
     dTransparency: ValueCell<boolean>,
     transparencyAverage: ValueCell<number>,
+
+    tTransparencyGrid: ValueCell<Texture>,
+    uTransparencyGridDim: ValueCell<Vec3>,
+    uTransparencyGridTransform: ValueCell<Vec4>,
+    dTransparencyType: ValueCell<string>,
 }
 
 export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) {
@@ -48,6 +54,11 @@ export function createTransparency(count: number, transparencyData?: Transparenc
             uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)),
             dTransparency: ValueCell.create(count > 0),
             transparencyAverage: ValueCell.create(0),
+
+            tTransparencyGrid: ValueCell.create(createNullTexture()),
+            uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dTransparencyType: ValueCell.create('groupInstance'),
         };
     }
 }
@@ -64,6 +75,11 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr
             uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)),
             dTransparency: ValueCell.create(false),
             transparencyAverage: ValueCell.create(0),
+
+            tTransparencyGrid: ValueCell.create(createNullTexture()),
+            uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dTransparencyType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 1 - 1
src/mol-gl/_spec/renderer.spec.ts

@@ -53,7 +53,7 @@ describe('renderer', () => {
         scene.commit();
         expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
         expect(ctx.stats.resourceCounts.texture).toBe(7);
-        expect(ctx.stats.resourceCounts.vertexArray).toBe(8);
+        expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 8 : 0);
         expect(ctx.stats.resourceCounts.program).toBe(8);
         expect(ctx.stats.resourceCounts.shader).toBe(16);
 

+ 3 - 3
src/mol-gl/compute/marching-cubes/isosurface.ts

@@ -116,6 +116,9 @@ function setRenderingDefaults(ctx: WebGLContext) {
 }
 
 export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
+    const { drawBuffers } = ctx.extensions;
+    if (!drawBuffers) throw new Error('need WebGL draw buffers');
+
     const { gl, resources, extensions } = ctx;
     const { pyramidTex, height, levels, scale, count } = histogramPyramid;
     const width = pyramidTex.getWidth();
@@ -173,9 +176,6 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
     ctx.state.currentRenderItemId = -1;
 
-    const { drawBuffers } = ctx.extensions;
-    if (!drawBuffers) throw new Error('need WebGL draw buffers');
-
     framebuffer.bind();
     drawBuffers.drawBuffers([
         drawBuffers.COLOR_ATTACHMENT0,

+ 96 - 22
src/mol-gl/compute/util.ts

@@ -1,17 +1,19 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { WebGLContext } from '../../mol-gl/webgl/context';
-import { Texture } from '../../mol-gl/webgl/texture';
-import { PrintImageOptions, printTextureImage } from '../../mol-gl/renderable/util';
-import { defaults, ValueCell } from '../../mol-util';
-import { ValueSpec, AttributeSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
+import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
+import { ValueCell } from '../../mol-util';
+import { ValueSpec, AttributeSpec, UniformSpec, Values, TextureSpec } from '../../mol-gl/renderable/schema';
 import { Vec2 } from '../../mol-math/linear-algebra';
-import { GLRenderingContext } from '../../mol-gl/webgl/compat';
-import { PixelData } from '../../mol-util/image';
+import { ShaderCode } from '../shader-code';
+import { copy_frag } from '../shader/copy.frag';
+import { quad_vert } from '../shader/quad.vert';
+import { createComputeRenderItem } from '../webgl/render-item';
+import { ComputeRenderable, createComputeRenderable } from '../renderable';
 
 export const QuadPositions = new Float32Array([
     1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // First triangle
@@ -34,21 +36,57 @@ export const QuadValues: Values<typeof QuadSchema> = {
 
 //
 
-function getArrayForTexture(gl: GLRenderingContext, texture: Texture, size: number) {
-    switch (texture.type) {
-        case gl.UNSIGNED_BYTE: return new Uint8Array(size);
-        case gl.FLOAT: return new Float32Array(size);
+const CopySchema = {
+    ...QuadSchema,
+    tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    uTexSize: UniformSpec('v2'),
+};
+const CopyShaderCode = ShaderCode('copy', quad_vert, copy_frag);
+export type CopyRenderable = ComputeRenderable<Values<typeof CopySchema>>
+
+export function createCopyRenderable(ctx: WebGLContext, texture: Texture): CopyRenderable {
+    const values: Values<typeof CopySchema> = {
+        ...QuadValues,
+        tColor: ValueCell.create(texture),
+        uTexSize: ValueCell.create(Vec2.create(texture.getWidth(), texture.getHeight())),
+    };
+
+    const schema = { ...CopySchema };
+    const renderItem = createComputeRenderItem(ctx, 'triangles', CopyShaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
+const SharedCopyName = 'shared-copy';
+
+export function getSharedCopyRenderable(ctx: WebGLContext, texture: Texture) {
+    if (!ctx.namedComputeRenderables[SharedCopyName]) {
+        ctx.namedComputeRenderables[SharedCopyName] = createCopyRenderable(ctx, createNullTexture());
     }
-    throw new Error('unknown/unsupported texture type');
+    const copy = ctx.namedComputeRenderables[SharedCopyName] as CopyRenderable;
+    ValueCell.update(copy.values.tColor, texture);
+    ValueCell.update(copy.values.uTexSize, Vec2.set(copy.values.uTexSize.ref.value, texture.getWidth(), texture.getHeight()));
+    copy.update();
+    return copy;
 }
 
-export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number): PixelData {
+//
+
+const ReadTextureName = 'read-texture';
+const ReadAlphaTextureName = 'read-alpha-texture';
+
+export function readTexture(ctx: WebGLContext, texture: Texture) {
     const { gl, resources } = ctx;
-    width = defaults(width, texture.getWidth());
-    height = defaults(height, texture.getHeight());
-    const size = width * height * 4;
-    const framebuffer = resources.framebuffer();
-    const array = getArrayForTexture(gl, texture, size);
+    if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
+
+    if (!ctx.namedFramebuffers[ReadTextureName]) {
+        ctx.namedFramebuffers[ReadTextureName] = resources.framebuffer();
+    }
+    const framebuffer = ctx.namedFramebuffers[ReadTextureName];
+
+    const width = texture.getWidth();
+    const height = texture.getHeight();
+    const array = new Uint8Array(width * height * 4);
     framebuffer.bind();
     texture.attachFramebuffer(framebuffer, 0);
     ctx.readPixels(0, 0, width, height, array);
@@ -56,8 +94,44 @@ export function readTexture(ctx: WebGLContext, texture: Texture, width?: number,
     return { array, width, height };
 }
 
-export function printTexture(ctx: WebGLContext, texture: Texture, options: Partial<PrintImageOptions> = {}) {
-    const pixelData = readTexture(ctx, texture);
-    PixelData.flipY(pixelData);
-    printTextureImage(pixelData, options);
+export function readAlphaTexture(ctx: WebGLContext, texture: Texture) {
+    const { gl, state, resources } = ctx;
+    if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type');
+
+    const width = texture.getWidth();
+    const height = texture.getHeight();
+
+    const copy = getSharedCopyRenderable(ctx, texture);
+    state.currentRenderItemId = -1;
+
+    if (!ctx.namedFramebuffers[ReadAlphaTextureName]) {
+        ctx.namedFramebuffers[ReadAlphaTextureName] = resources.framebuffer();
+    }
+    const framebuffer = ctx.namedFramebuffers[ReadAlphaTextureName];
+    framebuffer.bind();
+
+    if (!ctx.namedTextures[ReadAlphaTextureName]) {
+        ctx.namedTextures[ReadAlphaTextureName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
+    }
+    const copyTex = ctx.namedTextures[ReadAlphaTextureName];
+    copyTex.define(width, height);
+    copyTex.attachFramebuffer(framebuffer, 0);
+
+    state.disable(gl.CULL_FACE);
+    state.enable(gl.BLEND);
+    state.disable(gl.DEPTH_TEST);
+    state.enable(gl.SCISSOR_TEST);
+    state.depthMask(false);
+    state.clearColor(0, 0, 0, 0);
+    state.blendFunc(gl.ONE, gl.ONE);
+    state.blendEquation(gl.FUNC_ADD);
+    gl.viewport(0, 0, width, height);
+    gl.scissor(0, 0, width, height);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    copy.render();
+
+    const array = new Uint8Array(width * height * 4);
+    ctx.readPixels(0, 0, width, height, array);
+
+    return { array, width, height };
 }

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

@@ -218,6 +218,11 @@ export const OverpaintSchema = {
     uOverpaintTexDim: UniformSpec('v2'),
     tOverpaint: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
     dOverpaint: DefineSpec('boolean'),
+
+    uOverpaintGridDim: UniformSpec('v3'),
+    uOverpaintGridTransform: UniformSpec('v4'),
+    tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
+    dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
 } as const;
 export type OverpaintSchema = typeof OverpaintSchema
 export type OverpaintValues = Values<OverpaintSchema>
@@ -227,6 +232,11 @@ export const TransparencySchema = {
     tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     dTransparency: DefineSpec('boolean'),
     transparencyAverage: ValueSpec('number'),
+
+    uTransparencyGridDim: UniformSpec('v3'),
+    uTransparencyGridTransform: UniformSpec('v4'),
+    tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
+    dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
 } as const;
 export type TransparencySchema = typeof TransparencySchema
 export type TransparencyValues = Values<TransparencySchema>

+ 2 - 1
src/mol-gl/renderable/texture-mesh.ts

@@ -7,7 +7,7 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema } from './schema';
+import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema, ValueSpec } from './schema';
 import { MeshShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
@@ -24,6 +24,7 @@ export const TextureMeshSchema = {
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
     dGeoTexture: DefineSpec('boolean'),
+    meta: ValueSpec('unknown')
 };
 export type TextureMeshSchema = typeof TextureMeshSchema
 export type TextureMeshValues = Values<TextureMeshSchema>

+ 1 - 1
src/mol-gl/renderable/util.ts

@@ -91,7 +91,7 @@ export function printImageData(imageData: ImageData, options: Partial<PrintImage
     }
 
     canvas.toBlob(imgBlob => {
-        const objectURL = URL.createObjectURL(imgBlob);
+        const objectURL = URL.createObjectURL(imgBlob!);
         const existingImg = document.getElementById(o.id) as HTMLImageElement;
         const img = existingImg || document.createElement('img');
         img.id = o.id;

+ 4 - 0
src/mol-gl/renderer.ts

@@ -304,6 +304,10 @@ namespace Renderer {
             }
 
             if (r.values.dRenderMode) { // indicates direct-volume
+                if ((variant[0] === 'p' || variant === 'depth') && r.values.dRenderMode.ref.value === 'volume') {
+                    return; // no picking/depth in volume mode
+                }
+
                 // culling done in fragment shader
                 state.disable(gl.CULL_FACE);
                 state.frontFace(gl.CCW);

+ 28 - 6
src/mol-gl/shader/chunks/assign-color-varying.glsl.ts

@@ -13,11 +13,11 @@ export const assign_color_varying = `
     #elif defined(dColorType_vertexInstance)
         vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
     #elif defined(dColorType_volume)
-        vec3 gridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
-        vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb;
+        vec3 cgridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
+        vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
     #elif defined(dColorType_volumeInstance)
-        vec3 gridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim;
-        vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb;
+        vec3 cgridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim;
+        vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
     #endif
 
     #ifdef dUsePalette
@@ -25,7 +25,21 @@ export const assign_color_varying = `
     #endif
 
     #ifdef dOverpaint
-        vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
+        #if defined(dOverpaintType_groupInstance)
+            vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
+        #elif defined(dOverpaintType_vertexInstance)
+            vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
+        #elif defined(dOverpaintType_volumeInstance)
+            vec3 ogridPos = (uOverpaintGridTransform.w * (vModelPosition - uOverpaintGridTransform.xyz)) / uOverpaintGridDim;
+            vOverpaint = texture3dFrom2dLinear(tOverpaintGrid, ogridPos, uOverpaintGridDim, uOverpaintTexDim);
+        #endif
+
+        // pre-mix to avoid darkening due to empty overpaint
+        #ifdef dColorType_uniform
+            vOverpaint.rgb = mix(uColor.rgb, vOverpaint.rgb, vOverpaint.a);
+        #else
+            vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a);
+        #endif
     #endif
 #elif defined(dRenderVariant_pick)
     #if defined(dRenderVariant_pickObject)
@@ -39,6 +53,14 @@ export const assign_color_varying = `
 
 #ifdef dTransparency
     vGroup = group;
-    vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
+
+    #if defined(dTransparencyType_groupInstance)
+        vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
+    #elif defined(dTransparencyType_vertexInstance)
+        vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;
+    #elif defined(dTransparencyType_volumeInstance)
+        vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
+        vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;
+    #endif
 #endif
 `;

+ 3 - 0
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -54,6 +54,9 @@ export const assign_material_color = `
 // apply screendoor transparency
 #if defined(dTransparency)
     float ta = 1.0 - vTransparency;
+    #if defined(dRenderVariant_colorWboit)
+        if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better with wboit
+    #endif
 
     #if defined(dRenderVariant_pick)
         if (ta < uPickingAlphaThreshold)

+ 22 - 6
src/mol-gl/shader/chunks/color-vert-params.glsl.ts

@@ -18,9 +18,17 @@ export const color_vert_params = `
     #endif
 
     #ifdef dOverpaint
-        varying vec4 vOverpaint;
-        uniform vec2 uOverpaintTexDim;
-        uniform sampler2D tOverpaint;
+        #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
+            varying vec4 vOverpaint;
+            uniform vec2 uOverpaintTexDim;
+            uniform sampler2D tOverpaint;
+        #elif defined(dOverpaintType_volumeInstance)
+            varying vec4 vOverpaint;
+            uniform vec2 uOverpaintTexDim;
+            uniform vec3 uOverpaintGridDim;
+            uniform vec4 uOverpaintGridTransform;
+            uniform sampler2D tOverpaintGrid;
+        #endif
     #endif
 #elif defined(dRenderVariant_pick)
     #if __VERSION__ == 100
@@ -32,9 +40,17 @@ export const color_vert_params = `
 
 #ifdef dTransparency
     varying float vGroup;
-    varying float vTransparency;
-    uniform vec2 uTransparencyTexDim;
-    uniform sampler2D tTransparency;
+    #if defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance)
+        varying float vTransparency;
+        uniform vec2 uTransparencyTexDim;
+        uniform sampler2D tTransparency;
+    #elif defined(dTransparencyType_volumeInstance)
+        varying float vTransparency;
+        uniform vec2 uTransparencyTexDim;
+        uniform vec3 uTransparencyGridDim;
+        uniform vec4 uTransparencyGridTransform;
+        uniform sampler2D tTransparencyGrid;
+    #endif
 #endif
 
 #ifdef dUsePalette

+ 4 - 2
src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts

@@ -8,7 +8,7 @@ export const accumulate_frag = `
 precision highp float;
 
 varying vec3 vPosition;
-varying vec3 vColor;
+varying vec4 vColor;
 
 uniform float uCurrentSlice;
 uniform float uCurrentX;
@@ -23,6 +23,8 @@ void main() {
     float dist = distance(fragPos, vPosition);
     if (dist > p) discard;
 
-    gl_FragColor = vec4(vColor, 1.0) * (p - dist);
+    float f = p - dist;
+    gl_FragColor = vColor * f;
+    gl_FragData[1] = vec4(f);
 }
 `;

+ 3 - 3
src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts

@@ -27,7 +27,7 @@ uniform vec2 uColorTexDim;
 uniform sampler2D tColor;
 
 varying vec3 vPosition;
-varying vec3 vColor;
+varying vec4 vColor;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -43,9 +43,9 @@ void main() {
     gl_Position = vec4(((position - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
 
     #if defined(dColorType_group)
-        vColor = readFromTexture(tColor, group, uColorTexDim).rgb;
+        vColor = readFromTexture(tColor, group, uColorTexDim);
     #elif defined(dColorType_groupInstance)
-        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
+        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim);
     #endif
 }
 `;

+ 3 - 1
src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts

@@ -9,12 +9,14 @@ precision highp float;
 precision highp sampler2D;
 
 uniform sampler2D tColor;
+uniform sampler2D tCount;
 uniform vec2 uTexSize;
 
 void main(void) {
     vec2 coords = gl_FragCoord.xy / uTexSize;
     vec4 color = texture2D(tColor, coords);
+    float count = texture2D(tCount, coords).r;
 
-    gl_FragColor.rgb = color.rgb / color.a;
+    gl_FragColor = color / count;
 }
 `;

+ 16 - 3
src/mol-gl/shader/direct-volume.frag.ts

@@ -110,9 +110,10 @@ uniform mat4 uCartnToUnit;
     #endif
 
     #ifdef dOverpaint
-        varying vec4 vOverpaint;
-        uniform vec2 uOverpaintTexDim;
-        uniform sampler2D tOverpaint;
+        #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
+            uniform vec2 uOverpaintTexDim;
+            uniform sampler2D tOverpaint;
+        #endif
     #endif
 #endif
 
@@ -192,6 +193,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
     float nextValue;
 
     vec3 color = vec3(0.45, 0.55, 0.8);
+    vec4 overpaint = vec4(0.0);
+
     vec3 gradient = vec3(1.0);
     vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
     vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0);
@@ -297,6 +300,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
                         color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
                     #endif
 
+                    #ifdef dOverpaint
+                        #if defined(dOverpaintType_groupInstance)
+                            overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
+                        #elif defined(dOverpaintType_vertexInstance)
+                            overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount)).rgb;
+                        #endif
+
+                        color = mix(color, overpaint.rgb, overpaint.a);
+                    #endif
+
                     // handle flipping and negative isosurfaces
                     #ifdef dFlipSided
                         bool flipped = value < uIsoValue.y; // flipped

+ 1 - 4
src/mol-gl/shader/mesh.vert.ts

@@ -14,10 +14,7 @@ precision highp sampler2D;
 #include common_vert_params
 #include color_vert_params
 #include common_clip
-
-#if defined(dColorType_grid)
-    #include texture3d_from_2d_linear
-#endif
+#include texture3d_from_2d_linear
 
 #ifdef dGeoTexture
     uniform vec2 uGeoTexDim;

+ 5 - 4
src/mol-gl/webgl/buffer.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -11,6 +11,7 @@ import { idFactory } from '../../mol-util/id-factory';
 import { ValueOf } from '../../mol-util/type-helpers';
 import { GLRenderingContext } from './compat';
 import { WebGLExtensions } from './extensions';
+import { WebGLState } from './state';
 
 const getNextBufferId = idFactory();
 
@@ -192,7 +193,7 @@ export interface AttributeBuffer extends Buffer {
     bind: (location: number) => void
 }
 
-export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
+export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
     const { instancedArrays } = extensions;
 
     const buffer = createBuffer(gl, array, usageHint, 'attribute');
@@ -204,12 +205,12 @@ export function createAttributeBuffer<T extends ArrayType, S extends AttributeIt
             gl.bindBuffer(_bufferType, buffer.getBuffer());
             if (itemSize === 16) {
                 for (let i = 0; i < 4; ++i) {
-                    gl.enableVertexAttribArray(location + i);
+                    state.enableVertexAttrib(location + i);
                     gl.vertexAttribPointer(location + i, 4, _dataType, false, 4 * 4 * _bpe, i * 4 * _bpe);
                     instancedArrays.vertexAttribDivisor(location + i, divisor);
                 }
             } else {
-                gl.enableVertexAttribArray(location);
+                state.enableVertexAttrib(location);
                 gl.vertexAttribPointer(location, itemSize, _dataType, false, 0, 0);
                 instancedArrays.vertexAttribDivisor(location, divisor);
             }

+ 1 - 1
src/mol-gl/webgl/extensions.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat } from './compat';
+import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject } from './compat';
 import { isDebugMode } from '../../mol-util/debug';
 
 export type WebGLExtensions = {

+ 5 - 3
src/mol-gl/webgl/program.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -39,12 +39,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
         if (spec.type === 'attribute') {
             const loc = gl.getAttribLocation(program, k);
             // unused attributes will result in a `-1` location which is usually fine
-            // if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
+            // if (loc === -1) console.info(`Could not get attribute location for '${k}'`);
             locations[k] = loc;
         } else if (spec.type === 'uniform' || spec.type === 'texture') {
             const loc = gl.getUniformLocation(program, k);
             // unused uniforms will result in a `null` location which is usually fine
-            // if (loc === null) console.info(`Could not get uniform location for '${k}'`)
+            // if (loc === null) console.info(`Could not get uniform location for '${k}'`);
             locations[k] = loc as number;
         }
     });
@@ -192,11 +192,13 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
             }
         },
         bindAttributes: (attributeBuffers: AttributeBuffers) => {
+            state.clearVertexAttribsState();
             for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
                 const [k, buffer] = attributeBuffers[i];
                 const l = locations[k];
                 if (l !== -1) buffer.bind(l);
             }
+            state.disableUnusedVertexAttribs();
         },
         bindTextures: (textures: Textures, startingTargetUnit: number) => {
             for (let i = 0, il = textures.length; i < il; ++i) {

+ 5 - 9
src/mol-gl/webgl/render-item.ts

@@ -67,15 +67,9 @@ function createProgramVariant(ctx: WebGLContext, variant: string, defineValues:
 
 //
 
-type ProgramVariants = { [k: string]: Program }
-type VertexArrayVariants = { [k: string]: VertexArray | null }
-
-interface ValueChanges {
-    attributes: boolean
-    defines: boolean
-    elements: boolean
-    textures: boolean
-}
+type ProgramVariants = Record<string, Program>
+type VertexArrayVariants = Record<string, VertexArray | null>
+
 function createValueChanges() {
     return {
         attributes: false,
@@ -84,6 +78,8 @@ function createValueChanges() {
         textures: false,
     };
 }
+type ValueChanges = ReturnType<typeof createValueChanges>
+
 function resetValueChanges(valueChanges: ValueChanges) {
     valueChanges.attributes = false;
     valueChanges.defines = false;

+ 2 - 2
src/mol-gl/webgl/resources.ts

@@ -114,7 +114,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
 
     return {
         attribute: (array: ArrayType, itemSize: AttributeItemSize, divisor: number, usageHint?: UsageHint) => {
-            return wrap('attribute', createAttributeBuffer(gl, extensions, array, itemSize, divisor, usageHint));
+            return wrap('attribute', createAttributeBuffer(gl, state, extensions, array, itemSize, divisor, usageHint));
         },
         elements: (array: ElementsType, usageHint?: UsageHint) => {
             return wrap('elements', createElementsBuffer(gl, array, usageHint));
@@ -133,7 +133,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
             return wrap('texture', createTexture(gl, extensions, kind, format, type, filter));
         },
         vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => {
-            return wrap('vertexArray', createVertexArray(extensions, program, attributeBuffers, elementsBuffer));
+            return wrap('vertexArray', createVertexArray(gl, extensions, program, attributeBuffers, elementsBuffer));
         },
 
         getByteCounts: () => {

+ 33 - 2
src/mol-gl/webgl/state.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -59,11 +59,15 @@ export type WebGLState = {
     /** set the RGB blend equation and alpha blend equation separately, determines how a new pixel is combined with an existing */
     blendEquationSeparate: (modeRGB: number, modeAlpha: number) => void
 
+    enableVertexAttrib: (index: number) => void
+    clearVertexAttribsState: () => void
+    disableUnusedVertexAttribs: () => void
+
     reset: () => void
 }
 
 export function createState(gl: GLRenderingContext): WebGLState {
-    let enabledCapabilities: { [k: number]: boolean } = {};
+    let enabledCapabilities: Record<number, boolean> = {};
 
     let currentFrontFace = gl.getParameter(gl.FRONT_FACE);
     let currentCullFace = gl.getParameter(gl.CULL_FACE_MODE);
@@ -79,6 +83,16 @@ export function createState(gl: GLRenderingContext): WebGLState {
     let currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
     let currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
 
+    let maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+    const vertexAttribsState: number[] = [];
+
+    const clearVertexAttribsState = () => {
+        for (let i = 0; i < maxVertexAttribs; ++i) {
+            vertexAttribsState[i] = 0;
+        }
+    };
+    clearVertexAttribsState();
+
     return {
         currentProgramId: -1,
         currentMaterialId: -1,
@@ -168,6 +182,17 @@ export function createState(gl: GLRenderingContext): WebGLState {
             }
         },
 
+        enableVertexAttrib: (index: number) => {
+            gl.enableVertexAttribArray(index);
+            vertexAttribsState[index] = 1;
+        },
+        clearVertexAttribsState,
+        disableUnusedVertexAttribs: () => {
+            for (let i = 0; i < maxVertexAttribs; ++i) {
+                if (vertexAttribsState[i] === 0) gl.disableVertexAttribArray(i);
+            }
+        },
+
         reset: () => {
             enabledCapabilities = {};
 
@@ -184,6 +209,12 @@ export function createState(gl: GLRenderingContext): WebGLState {
 
             currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
             currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
+
+            maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+            vertexAttribsState.length = 0;
+            for (let i = 0; i < maxVertexAttribs; ++i) {
+                vertexAttribsState[i] = 0;
+            }
         }
     };
 }

+ 10 - 2
src/mol-gl/webgl/vertex-array.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,6 +8,7 @@ import { Program } from './program';
 import { ElementsBuffer, AttributeBuffers } from './buffer';
 import { WebGLExtensions } from './extensions';
 import { idFactory } from '../../mol-util/id-factory';
+import { GLRenderingContext } from './compat';
 
 const getNextVertexArrayId = idFactory();
 
@@ -40,7 +41,7 @@ export interface VertexArray {
     destroy: () => void
 }
 
-export function createVertexArray(extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
+export function createVertexArray(gl: GLRenderingContext, extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
     const id = getNextVertexArrayId();
     let vertexArray = getVertexArray(extensions);
     let vertexArrayObject = getVertexArrayObject(extensions);
@@ -68,6 +69,13 @@ export function createVertexArray(extensions: WebGLExtensions, program: Program,
         },
         destroy: () => {
             if (destroyed) return;
+            if (elementsBuffer) {
+                // workaround for ANGLE/Chromium bug
+                // - https://bugs.chromium.org/p/angleproject/issues/detail?id=6599
+                // - https://bugs.chromium.org/p/chromium/issues/detail?id=1272238
+                vertexArrayObject.bindVertexArray(vertexArray);
+                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+            }
             vertexArrayObject.deleteVertexArray(vertexArray);
             destroyed = true;
         }

+ 128 - 0
src/mol-io/reader/_spec/sdf.spec.ts

@@ -332,6 +332,80 @@ M  END
 $$$$
 `;
 
+const V3000SdfString = `FYI-001
+FYICenter.com
+123456789012345678901234567890123456789012345678901234567890
+ 0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 13 14 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 N 0.84 -0.16 0 0
+M  V30 2 N 1.48 0.43 0 0
+M  V30 3 N 0.09 0.27 0 0
+M  V30 4 C 1.11 1.21 0 0
+M  V30 5 C 0.27 1.12 0 0
+M  V30 6 C 0.84 -1.03 0 0
+M  V30 7 C 1.53 1.99 0 0
+M  V30 8 Cl 1.07 2.74 0.01 0
+M  V30 9 C 1.59 -1.46 0 0
+M  V30 10 C 0.08 -1.46 0 0
+M  V30 11 C 1.59 -2.33 0 0
+M  V30 12 C 0.07 -2.32 0 0
+M  V30 13 C 0.84 -2.76 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 2 1
+M  V30 2 1 3 1
+M  V30 3 1 6 1
+M  V30 4 2 4 2
+M  V30 5 2 5 3
+M  V30 6 1 7 4
+M  V30 7 1 4 5
+M  V30 8 1 9 6
+M  V30 9 2 10 6
+M  V30 10 1 8 7
+M  V30 11 2 11 9
+M  V30 12 1 12 10
+M  V30 13 1 13 11
+M  V30 14 2 13 12
+M  V30 END BOND
+M  V30 END CTAB
+M  END
+> <Comment>
+This is an SDF example.
+With a multi-line comment.
+
+> <source>
+This was retrieved from biotech.fyicenter.com
+
+$$$$
+L-Alanine
+GSMACCS-II07189510252D 1 0.00366 0.00000 0
+Figure 1, J. Chem. Inf. Comput. Sci., Vol 32, No. 3., 1992
+ 0 0 0 0 0 999 V3000
+M V30 BEGIN CTAB
+M V30 COUNTS 6 5 0 0 1
+M V30 BEGIN ATOM
+M V30 1 C -0.6622 0.5342 0 0 CFG=2
+M V30 2 C 0.6622 -0.3 0 0
+M V30 3 C -0.7207 2.0817 0 0 MASS=13
+M V30 4 N -1.8622 -0.3695 0 0 CHG=1
+M V30 5 O 0.622 -1.8037 0 0
+M V30 6 O 1.9464 0.4244 0 0 CHG=-1
+M V30 END ATOM
+M V30 BEGIN BOND
+M V30 1 1 1 2
+M V30 2 1 1 3 CFG=1
+M V30 3 1 1 4
+M V30 4 2 2 5
+M V30 5 1 2 6
+M V30 END BOND
+M V30 END CTAB
+M END
+
+$$$$
+`;
+
 describe('sdf reader', () => {
     it('basic', async () => {
         const parsed = await parseSdf(SdfString).run();
@@ -383,4 +457,58 @@ describe('sdf reader', () => {
         expect(compound3.dataItems.dataHeader.value(21)).toBe('<PUBCHEM_COORDINATE_TYPE>');
         expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
     });
+
+    it('v3000', async () => {
+        const parsed = await parseSdf(V3000SdfString).run();
+        if (parsed.isError) {
+            throw new Error(parsed.message);
+        }
+
+        expect(parsed.result.compounds.length).toBe(2);
+
+        const compound1 = parsed.result.compounds[0];
+        expect(compound1.molFile.atoms.count).toBe(13);
+        expect(compound1.molFile.atoms.x.rowCount).toBe(13);
+        expect(compound1.molFile.atoms.y.rowCount).toBe(13);
+        expect(compound1.molFile.atoms.z.rowCount).toBe(13);
+        expect(compound1.molFile.atoms.type_symbol.rowCount).toBe(13);
+        expect(compound1.molFile.bonds.count).toBe(14);
+        expect(compound1.molFile.bonds.atomIdxA.rowCount).toBe(14);
+        expect(compound1.molFile.bonds.atomIdxB.rowCount).toBe(14);
+        expect(compound1.molFile.bonds.order.rowCount).toBe(14);
+
+        expect(compound1.molFile.atoms.x.value(7)).toBe(1.07);
+        expect(compound1.molFile.atoms.y.value(7)).toBe(2.74);
+        expect(compound1.molFile.atoms.z.value(7)).toBe(0.01);
+        expect(compound1.molFile.atoms.type_symbol.value(7)).toBe('Cl');
+
+        expect(compound1.molFile.bonds.atomIdxA.value(10)).toBe(11);
+        expect(compound1.molFile.bonds.atomIdxB.value(10)).toBe(9);
+        expect(compound1.molFile.bonds.order.value(10)).toBe(2);
+
+        expect(compound1.dataItems.dataHeader.rowCount).toBe(2);
+        expect(compound1.dataItems.data.rowCount).toBe(2);
+
+        expect(compound1.dataItems.dataHeader.value(0)).toBe('<Comment>');
+        expect(compound1.dataItems.data.value(0)).toBe(`This is an SDF example.\nWith a multi-line comment.`);
+
+        expect(compound1.dataItems.dataHeader.value(1)).toBe('<source>');
+        expect(compound1.dataItems.data.value(1)).toBe('This was retrieved from biotech.fyicenter.com');
+
+        const compound2 = parsed.result.compounds[1];
+        expect(compound2.molFile.atoms.count).toBe(6);
+        expect(compound2.molFile.bonds.count).toBe(5);
+
+        expect(compound2.molFile.atoms.x.value(4)).toBe(0.622);
+        expect(compound2.molFile.atoms.y.value(4)).toBe(-1.8037);
+        expect(compound2.molFile.atoms.z.value(4)).toBe(0);
+        expect(compound2.molFile.atoms.type_symbol.value(4)).toBe('O');
+
+        expect(compound2.molFile.bonds.atomIdxA.value(1)).toBe(1);
+        expect(compound2.molFile.bonds.atomIdxB.value(1)).toBe(3);
+        expect(compound2.molFile.bonds.order.value(1)).toBe(1);
+
+        expect(compound2.dataItems.dataHeader.rowCount).toBe(0);
+        expect(compound2.dataItems.data.rowCount).toBe(0);
+    });
 });

+ 2 - 2
src/mol-io/reader/cif/schema/bird.ts

@@ -1,7 +1,7 @@
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
+ * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
  *
  * @author molstar/ciftools package
  */
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
         /**
          * An identifier for the wwPDB site creating or modifying the molecule.
          */
-        processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
+        processing_site: Aliased<'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
         /**
          * The action associated with this audit record.
          */

+ 1 - 1
src/mol-io/reader/cif/schema/ccd.ts

@@ -1,7 +1,7 @@
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
+ * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
  *
  * @author molstar/ciftools package
  */

+ 15 - 5
src/mol-io/reader/cif/schema/cif-core.ts

@@ -114,7 +114,7 @@ export const CifCore_Schema = {
      * Abstracts. This ordering is used in _chemical_formula.moiety
      * and _chemical_formula.sum.
      *
-     * _chemical_formula.iupac      '[Mo (C O)4 (C18 H33 P)2]'
+     * _chemical_formula.IUPAC      '[Mo (C O)4 (C18 H33 P)2]'
      * _chemical_formula.moiety     'C40 H66 Mo O4 P2'
      * _chemical_formula.structural '((C O)4 (P (C6 H11)3)2)Mo'
      * _chemical_formula.sum         'C40 H66 Mo O4 P2'
@@ -146,7 +146,7 @@ export const CifCore_Schema = {
         sum: str,
         /**
          * Mass corresponding to the formulae _chemical_formula.structural,
-         * *_iupac, *_moiety or *_sum and, together with the Z value and cell
+         * *_IUPAC, *_moiety or *_sum and, together with the Z value and cell
          * parameters yield the density given as _exptl_crystal.density_diffrn.
          */
         weight: float,
@@ -478,7 +478,7 @@ export const CifCore_Schema = {
          * A concatenated series of single-letter codes which indicate the
          * refinement restraints or constraints applied to this site. This
          * item should not be used. It has been replaced by
-         * _atom_site.refinement_flags_posn, _adp and _occupancy. It is
+         * _atom_site.refinement_flags_posn, _ADP and _occupancy. It is
          * retained in this dictionary only to provide compatibility with
          * legacy CIFs.
          */
@@ -505,9 +505,9 @@ export const CifCore_Schema = {
          * atomic  displacement parameter, U(equiv), in angstroms squared,
          * calculated from anisotropic atomic displacement  parameters.
          *
-         * U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~ a~j~)]
+         * U(equiv) = (1/3) sum~i~[sum~j~(U^ij^ a*~i~ a*~j~ a~i~.a~j~)]
          *
-         * a  = the real-space cell lengths
+         * a  = the real-space cell vectors
          * a* = the reciprocal-space cell lengths
          * Ref: Fischer, R. X. & Tillmanns, E. (1988). Acta Cryst. C44, 775-776.
          */
@@ -683,12 +683,21 @@ export const CifCore_Aliases = {
     'geom_bond.distance': [
         'geom_bond_dist',
     ],
+    'audit.block_doi': [
+        'audit_block_DOI',
+    ],
     'database_code.cod': [
         'database_code_COD',
     ],
     'database_code.csd': [
         'database_code_CSD',
     ],
+    'database_code.depnum_ccdc_archive': [
+        'database_code_depnum_CCDC_archive',
+    ],
+    'database_code.depnum_ccdc_fiz': [
+        'database_code_depnum_CCDC_fiz',
+    ],
     'database_code.icsd': [
         'database_code_ICSD',
     ],
@@ -699,6 +708,7 @@ export const CifCore_Aliases = {
         'database_code_NBS',
     ],
     'atom_site.adp_type': [
+        'atom_site_ADP_type',
         'atom_site_thermal_displace_type',
     ],
     'atom_site.label': [

+ 1 - 1
src/mol-io/reader/cif/schema/mmcif.ts

@@ -1,7 +1,7 @@
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.350, IHM 1.17, CARB draft.
+ * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
  *
  * @author molstar/ciftools package
  */

+ 109 - 0
src/mol-io/reader/sdf/parser-v3-util.ts

@@ -0,0 +1,109 @@
+import { Column } from '../../../mol-data/db';
+import { MolFile } from '../mol/parser';
+import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
+import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
+
+export function isV3(
+    versionLine: string
+): boolean {
+    return versionLine.trim().endsWith('V3000');
+}
+
+export function handleCountsV3(
+    tokenizer: Tokenizer
+): { atomCount: number, bondCount: number } {
+    const atomCount = TokenBuilder.create(tokenizer.data, 1);
+    const bondCount = TokenBuilder.create(tokenizer.data, 1);
+
+    Tokenizer.eatLine(tokenizer); // BEGIN CTAB
+    skipSingleValue(tokenizer); // M
+    skipSingleValue(tokenizer); // V30
+    skipSingleValue(tokenizer); // COUNTS
+
+    addSingleValue(tokenizer, atomCount);
+    addSingleValue(tokenizer, bondCount);
+    Tokenizer.eatLine(tokenizer);
+
+    return {
+        atomCount: TokenColumn(atomCount)(Column.Schema.int).value(0),
+        bondCount: TokenColumn(bondCount)(Column.Schema.int).value(0)
+    };
+}
+
+export function handleAtomsV3(
+    tokenizer: Tokenizer,
+    atomCount: number
+): MolFile['atoms'] {
+    const x = TokenBuilder.create(tokenizer.data, atomCount * 2);
+    const y = TokenBuilder.create(tokenizer.data, atomCount * 2);
+    const z = TokenBuilder.create(tokenizer.data, atomCount * 2);
+    const type_symbol = TokenBuilder.create(tokenizer.data, atomCount * 2);
+
+    for (let i = 0; i < atomCount; ++i) {
+        Tokenizer.markLine(tokenizer);
+        skipSingleValue(tokenizer); // M
+        skipSingleValue(tokenizer); // V30
+        skipSingleValue(tokenizer); // Index
+
+        const { position } = tokenizer;
+        addSingleValue(tokenizer, type_symbol);
+        addSingleValue(tokenizer, x);
+        addSingleValue(tokenizer, y);
+        addSingleValue(tokenizer, z);
+        tokenizer.position = position;
+    }
+    Tokenizer.eatLine(tokenizer); // Previous Line
+    Tokenizer.eatLine(tokenizer); // END ATOM
+
+    return {
+        count: atomCount,
+        x: TokenColumn(x)(Column.Schema.float),
+        y: TokenColumn(y)(Column.Schema.float),
+        z: TokenColumn(z)(Column.Schema.float),
+        type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
+    };
+}
+
+export function handleBondsV3(
+    tokenizer: Tokenizer,
+    bondCount: number
+): MolFile['bonds'] {
+    const atomIdxA = TokenBuilder.create(tokenizer.data, bondCount * 2);
+    const atomIdxB = TokenBuilder.create(tokenizer.data, bondCount * 2);
+    const order = TokenBuilder.create(tokenizer.data, bondCount * 2);
+
+    for (let i = 0; i < bondCount; ++i) {
+        Tokenizer.markLine(tokenizer);
+        skipSingleValue(tokenizer); // M
+        skipSingleValue(tokenizer); // V30
+        skipSingleValue(tokenizer); // Index
+
+        const { position } = tokenizer;
+        addSingleValue(tokenizer, order);
+        addSingleValue(tokenizer, atomIdxA);
+        addSingleValue(tokenizer, atomIdxB);
+        tokenizer.position = position;
+    }
+    Tokenizer.eatLine(tokenizer); // Previous Line
+    Tokenizer.eatLine(tokenizer); // END BOND
+
+    return {
+        count: bondCount,
+        atomIdxA: TokenColumn(atomIdxA)(Column.Schema.float),
+        atomIdxB: TokenColumn(atomIdxB)(Column.Schema.float),
+        order: TokenColumn(order)(Column.Schema.float),
+    };
+}
+
+function skipSingleValue(tokenizer: Tokenizer) {
+    Tokenizer.skipWhitespace(tokenizer);
+    Tokenizer.eatValue(tokenizer);
+}
+
+function addSingleValue(tokenizer: Tokenizer, tokens: Tokens) {
+    const { position: valueStart } = tokenizer;
+    Tokenizer.skipWhitespace(tokenizer);
+    Tokenizer.eatValue(tokenizer);
+    Tokenizer.trim(tokenizer, valueStart, tokenizer.position);
+    TokenBuilder.addUnchecked(tokens, tokenizer.tokenStart, tokenizer.tokenEnd);
+}

+ 14 - 5
src/mol-io/reader/sdf/parser.ts

@@ -11,8 +11,9 @@ import { Task } from '../../../mol-task';
 import { ReaderResult as Result } from '../result';
 import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
 import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
+import { handleAtomsV3, handleBondsV3, handleCountsV3, isV3 } from './parser-v3-util';
 
-/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 */
+/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 & 79 */
 
 export interface SdfFileCompound {
     readonly molFile: MolFile,
@@ -66,14 +67,22 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
     };
 }
 
+function handleCountsV2(countsAndVersion: string): { atomCount: number, bondCount: number } {
+    return {
+        atomCount: +countsAndVersion.substr(0, 3),
+        bondCount: +countsAndVersion.substr(3, 3)
+    };
+}
+
 function handleMolFile(tokenizer: Tokenizer) {
     const title = Tokenizer.readLine(tokenizer).trim();
     const program = Tokenizer.readLine(tokenizer).trim();
     const comment = Tokenizer.readLine(tokenizer).trim();
 
-    const counts = Tokenizer.readLine(tokenizer);
+    const countsAndVersion = Tokenizer.readLine(tokenizer);
+    const molIsV3 = isV3(countsAndVersion);
 
-    const atomCount = +counts.substr(0, 3), bondCount = +counts.substr(3, 3);
+    const { atomCount, bondCount } = molIsV3 ? handleCountsV3(tokenizer) : handleCountsV2(countsAndVersion);
 
     if (Number.isNaN(atomCount) || Number.isNaN(bondCount)) {
         // try to skip to next molecule
@@ -84,8 +93,8 @@ function handleMolFile(tokenizer: Tokenizer) {
         return;
     }
 
-    const atoms = handleAtoms(tokenizer, atomCount);
-    const bonds = handleBonds(tokenizer, bondCount);
+    const atoms = molIsV3 ? handleAtomsV3(tokenizer, atomCount) : handleAtoms(tokenizer, atomCount);
+    const bonds = molIsV3 ? handleBondsV3(tokenizer, bondCount) : handleBonds(tokenizer, bondCount);
     const dataItems = handleDataItems(tokenizer);
 
     return {

+ 3 - 3
src/mol-model/structure/structure/properties.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -99,12 +99,12 @@ const residue = {
     secondary_structure_type: p(l => {
         if (!Unit.isAtomic(l.unit)) notAtomic();
         const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
-        return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
+        return secStruc ? secStruc.type[secStruc.getIndex(l.unit.residueIndex[l.element])] : SecondaryStructureType.Flag.NA;
     }),
     secondary_structure_key: p(l => {
         if (!Unit.isAtomic(l.unit)) notAtomic();
         const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
-        return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
+        return secStruc ? secStruc.key[secStruc.getIndex(l.unit.residueIndex[l.element])] : -1;
     }),
     chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
 };

+ 4 - 2
src/mol-plugin-state/actions/file.ts

@@ -53,7 +53,8 @@ export const OpenFiles = StateAction.build({
                     await provider.visuals?.(plugin, parsed);
                 }
             } catch (e) {
-                plugin.log.error(e);
+                console.error(e);
+                plugin.log.error(`Error opening file '${file.name}'`);
             }
         }
     }).runInContext(taskCtx);
@@ -88,7 +89,8 @@ export const DownloadFile = StateAction.build({
                 await provider.visuals?.(plugin, parsed);
             }
         } catch (e) {
-            plugin.log.error(e);
+            console.error(e);
+            plugin.log.error(`Error downloading '${typeof params.url === 'string' ? params.url : params.url.url}'`);
         }
     }).runInContext(taskCtx);
 }));

+ 3 - 1
src/mol-plugin-state/formats/volume.ts

@@ -33,7 +33,9 @@ async function tryObtainRecommendedIsoValue(plugin: PluginContext, volume?: Volu
         try {
             const absIsoLevel = await getContourLevelEmdb(plugin, ctx, entryId);
             RecommendedIsoValue.Provider.set(volume, Volume.IsoValue.absolute(absIsoLevel));
-        } catch (e) { }
+        } catch (e) {
+            console.warn(e);
+        }
     }));
 }
 

+ 14 - 3
src/mol-plugin-state/helpers/structure-selection-query.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -18,6 +18,7 @@ import { SetUtils } from '../../mol-util/set';
 import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
 import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
+import { SecondaryStructureProvider } from '../../mol-model-props/computed/secondary-structure';
 
 export enum StructureSelectionCategory {
     Type = 'Type',
@@ -250,7 +251,12 @@ const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
             MS.core.type.bitflags([SecondaryStructureType.Flag.Helix])
         ])
     })
-]), { category: StructureSelectionCategory.Structure });
+]), {
+    category: StructureSelectionCategory.Structure,
+    ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
+        return SecondaryStructureProvider.attach(ctx, structure);
+    }
+});
 
 const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.union([
     MS.struct.generator.atomGroups({
@@ -260,7 +266,12 @@ const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.uni
             MS.core.type.bitflags([SecondaryStructureType.Flag.Beta])
         ])
     })
-]), { category: StructureSelectionCategory.Structure });
+]), {
+    category: StructureSelectionCategory.Structure,
+    ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
+        return SecondaryStructureProvider.attach(ctx, structure);
+    }
+});
 
 const water = StructureSelectionQuery('Water', MS.struct.modifier.union([
     MS.struct.generator.atomGroups({

+ 2 - 1
src/mol-plugin-state/manager/snapshots.ts

@@ -261,7 +261,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
                 return this.setStateSnapshot(snapshot);
             }
         } catch (e) {
-            this.plugin.log.error(`Reading state: ${e}`);
+            console.error(e);
+            this.plugin.log.error('Error reading state');
         }
     }
 

+ 41 - 15
src/mol-plugin-state/transforms/representation.ts

@@ -24,7 +24,7 @@ import { unwindStructureAssembly, explodeStructure, spinStructure, SpinStructure
 import { Color } from '../../mol-util/color';
 import { Overpaint } from '../../mol-theme/overpaint';
 import { Transparency } from '../../mol-theme/transparency';
-import { BaseGeometry } from '../../mol-geo/geometry/base';
+import { BaseGeometry, hasColorSmoothingProp } from '../../mol-geo/geometry/base';
 import { Script } from '../../mol-script/script';
 import { UnitcellParams, UnitcellRepresentation, getUnitcellData } from '../../mol-repr/shape/model/unitcell';
 import { DistanceParams, DistanceRepresentation } from '../../mol-repr/shape/loci/distance';
@@ -328,25 +328,31 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const overpaint = Overpaint.ofScript(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { overpaint },
             initialState: { overpaint: Overpaint.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Overpaint (${overpaint.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const oldStructure = b.data.info as Structure;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
         const newStructure = a.data.sourceData;
-        if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldOverpaint = b.data.state.overpaint!;
         const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
         if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.overpaint = newOverpaint;
         b.data.repr = a.data.repr;
         b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
@@ -380,25 +386,31 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const overpaint = Overpaint.ofBundle(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { overpaint },
             initialState: { overpaint: Overpaint.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Overpaint (${overpaint.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const oldStructure = b.data.info as Structure;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
         const newStructure = a.data.sourceData;
-        if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldOverpaint = b.data.state.overpaint!;
         const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
         if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.overpaint = newOverpaint;
         b.data.repr = a.data.repr;
         b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
@@ -429,24 +441,31 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const transparency = Transparency.ofScript(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { transparency },
             initialState: { transparency: Transparency.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Transparency (${transparency.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const structure = b.data.info as Structure;
-        if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
+        const newStructure = a.data.sourceData;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldTransparency = b.data.state.transparency!;
-        const newTransparency = Transparency.ofScript(newParams.layers, structure);
+        const newTransparency = Transparency.ofScript(newParams.layers, newStructure);
         if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.transparency = newTransparency;
         b.data.repr = a.data.repr;
         b.label = `Transparency (${newTransparency.layers.length} Layers)`;
@@ -478,24 +497,31 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const transparency = Transparency.ofBundle(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { transparency },
             initialState: { transparency: Transparency.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Transparency (${transparency.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const structure = b.data.info as Structure;
-        if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
+        const newStructure = a.data.sourceData;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldTransparency = b.data.state.transparency!;
-        const newTransparency = Transparency.ofBundle(newParams.layers, structure);
+        const newTransparency = Transparency.ofBundle(newParams.layers, newStructure);
         if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.transparency = newTransparency;
         b.data.repr = a.data.repr;
         b.label = `Transparency (${newTransparency.layers.length} Layers)`;

+ 16 - 18
src/mol-plugin-ui/skin/base/components/controls.scss

@@ -1,3 +1,5 @@
+@use "sass:math";
+
 .msp-control-row {
     position: relative;
     height: $row-height;
@@ -184,7 +186,7 @@
         z-index: 100000;
         background: $default-background;
         border-top: 1px solid $default-background;
-        padding-bottom: $control-spacing / 2;
+        padding-bottom: math.div($control-spacing, 2);
         width: 100%;
 
         // input[type=text] {
@@ -195,8 +197,8 @@
 
 .msp-toggle-color-picker-above {
     .msp-color-picker {
-        top: -2 * 32px - 16px - $control-spacing / 2;
-        height: 2 * 32px + 16px + $control-spacing / 2;
+        top: -2 * 32px - 16px - math.div($control-spacing, 2);
+        height: 2 * 32px + 16px + math.div($control-spacing, 2);
     }
 }
 
@@ -208,10 +210,6 @@
 }
 
 .msp-control-offset {
-    // border-left-width: $control-spacing / 2;
-    // border-left-style: solid;
-    // border-left-color: color-increase-contrast($default-background, 10%);
-    // padding-left: 1px;
     padding-left: $control-spacing;
 }
 
@@ -228,7 +226,7 @@
 // }
 
 .msp-control-group-wrapper {
-    //border-left-width: $control-spacing / 2;
+    //border-left-width: math.div($control-spacing, 2);
     //border-left-style: solid;
     //border-left-color: color-increase-contrast($default-background, 10%);
 
@@ -240,10 +238,10 @@
 .msp-control-group-header {
     background: $default-background;
     > button, div {
-        padding-left: 4px; // $control-spacing / 2 !important;
+        padding-left: 4px; // math.div($control-spacing, 2) !important;
         text-align: left;
-        height: 24px !important; // 2 * $row-height / 3 !important;
-        line-height: 24px !important; // 2 * $row-height / 3 !important;
+        height: 24px !important;
+        line-height: 24px !important;
         font-size: 85% !important;
         background: $default-background !important;
         color: color-lower-contrast($font-color, 15%);
@@ -253,8 +251,8 @@
         line-height: 24px !important;
     }
     > span {
-        padding-left: $control-spacing / 2;
-        line-height: 2 * $row-height / 3;
+        padding-left: math.div($control-spacing, 2);
+        line-height: math.div(2 * $row-height, 3);
         font-size: 70%;
         background: $default-background;
         color: color-lower-contrast($font-color, 15%);
@@ -267,7 +265,7 @@
 
 .msp-control-group-footer {
     background: color-increase-contrast($default-background, 5%);
-    height: $control-spacing / 2;
+    height: math.div($control-spacing, 2);
     font-size: 1px;
     margin-top: 1px;
 }
@@ -339,7 +337,7 @@
     margin-top: 1px;
 
     > div {
-        padding: ($control-spacing / 2) $control-spacing;
+        padding: (math.div($control-spacing, 2)) $control-spacing;
         text-align: left;
         color: color-lower-contrast($font-color, 15%);
     }
@@ -359,7 +357,7 @@
         height: $control-spacing * 3;
 
         > span {
-            padding: $control-spacing / 2;
+            padding: math.div($control-spacing, 2);
             color: white;
             font-weight: bold;
             background-color: rgba(0, 0, 0, 0.2);
@@ -370,7 +368,7 @@
 .msp-table-legend {
     > div {
         // min-width: 60px;
-        margin-right: $control-spacing / 2;
+        margin-right: math.div($control-spacing, 2);
         display: inline-flex;
 
         .msp-table-legend-color {
@@ -379,7 +377,7 @@
         }
 
         .msp-table-legend-text {
-            margin: 0 ($control-spacing / 2);
+            margin: 0 (math.div($control-spacing, 2));
         }
     }
 }

+ 3 - 1
src/mol-plugin-ui/skin/base/components/toast.scss

@@ -1,4 +1,6 @@
 
+@use "sass:math";
+
 .msp-toast-container {
     position: relative;
     // bottom: $control-spacing;    
@@ -75,7 +77,7 @@
                 bottom: 0;
                 width: 100%;
                 text-align: right;
-                padding-right: $control-spacing / 2;
+                padding-right: math.div($control-spacing, 2);
             }            
         }
     }

+ 2 - 1
src/mol-plugin-ui/state/common.tsx

@@ -157,7 +157,8 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
         this.setState({ busy: true });
         try {
             await this.applyAction();
-        } catch {
+        } catch (e) {
+            console.error(e);
             // eat errors because they should be handled elsewhere
         } finally {
             this.props.onApply?.();

+ 5 - 2
src/mol-plugin-ui/state/snapshots.tsx

@@ -246,7 +246,8 @@ export class RemoteStateSnapshots extends PluginUIComponent<
 
             if (this._mounted) this.setState({ entries: entries.asImmutable(), isBusy: false });
         } catch (e) {
-            this.plugin.log.error('Fetching Remote Snapshots: ' + e);
+            console.error(e);
+            this.plugin.log.error('Error fetching remote snapshots');
             if (this._mounted) this.setState({ entries: OrderedMap(), isBusy: false });
         }
     }
@@ -294,7 +295,9 @@ export class RemoteStateSnapshots extends PluginUIComponent<
 
         try {
             await fetch(entry.removeUrl);
-        } catch { }
+        } catch (e) {
+            console.error(e);
+        }
     }
 
     render() {

+ 2 - 1
src/mol-plugin-ui/structure/selection.tsx

@@ -401,7 +401,8 @@ function ResidueListSelectionHelper({ modifier, plugin, close }: { modifier: Str
             const query = compileIdListSelection(state.identifiers, state.idType);
             plugin.managers.structure.selection.fromCompiledQuery(modifier, query, false);
         } catch (e) {
-            plugin.log.error(`Failed to create selection: ${e}`);
+            console.error(e);
+            plugin.log.error('Failed to create selection');
         }
     };
 

+ 1 - 2
src/mol-plugin/context.ts

@@ -199,8 +199,7 @@ export class PluginContext {
                 const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
                 const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
                 const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
-                (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit, preferWebGl1 });
-                (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit });
+                (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
             }
             (this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
             this.canvas3dInit.next(true);

+ 35 - 2
src/mol-repr/representation.ts

@@ -23,6 +23,8 @@ import { BaseGeometry } from '../mol-geo/geometry/base';
 import { Visual } from './visual';
 import { CustomProperty } from '../mol-model-props/common/custom-property';
 import { Clipping } from '../mol-theme/clipping';
+import { SetUtils } from '../mol-util/set';
+import { cantorPairing } from '../mol-data/util';
 
 export type RepresentationProps = { [k: string]: any }
 
@@ -143,6 +145,7 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
     /** Number of addressable groups in all visuals of the representation */
     readonly groupCount: number
     readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
+    readonly geometryVersion: number
     readonly props: Readonly<PD.Values<P>>
     readonly params: Readonly<P>
     readonly state: Readonly<S>
@@ -215,7 +218,7 @@ namespace Representation {
 
     export type Any = Representation<any, any, any>
     export const Empty: Any = {
-        label: '', groupCount: 0, renderObjects: [], props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
+        label: '', groupCount: 0, renderObjects: [], geometryVersion: -1, props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(),
         createOrUpdate: () => Task.constant('', undefined),
         setState: () => {},
         setTheme: () => {},
@@ -226,9 +229,32 @@ namespace Representation {
 
     export type Def<D, P extends PD.Params = {}, S extends State = State> = { [k: string]: RepresentationFactory<D, P, S> }
 
+    export class GeometryState {
+        private curr = new Set<number>();
+        private next = new Set<number>();
+
+        private _version = -1;
+        get version() {
+            return this._version;
+        }
+
+        add(id: number, version: number) {
+            this.next.add(cantorPairing(id, version));
+        }
+
+        snapshot() {
+            if (!SetUtils.areEqual(this.curr, this.next)) {
+                this._version += 1;
+            }
+            [this.curr, this.next] = [this.next, this.curr];
+            this.next.clear();
+        }
+    }
+
     export function createMulti<D, P extends PD.Params = {}, S extends State = State>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<D, P>, stateBuilder: StateBuilder<S>, reprDefs: Def<D, P>): Representation<D, P, S> {
         let version = 0;
         const updated = new Subject<number>();
+        const geometryState = new GeometryState();
         const currentState = stateBuilder.create();
         let currentTheme = Theme.createEmpty();
 
@@ -271,6 +297,7 @@ namespace Representation {
                 }
                 return renderObjects;
             },
+            get geometryVersion() { return geometryState.version; },
             get props() { return currentProps; },
             get params() { return currentParams; },
             createOrUpdate: (props: Partial<P> = {}, data?: D) => {
@@ -288,7 +315,9 @@ namespace Representation {
                         if (!visuals || visuals.includes(reprMap[i])) {
                             await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime);
                         }
+                        geometryState.add(i, reprList[i].geometryVersion);
                     }
+                    geometryState.snapshot();
                     updated.next(version++);
                 });
             },
@@ -314,7 +343,7 @@ namespace Representation {
             setState: (state: Partial<S>) => {
                 stateBuilder.update(currentState, state);
                 for (let i = 0, il = reprList.length; i < il; ++i) {
-                    reprList[i].setState(currentState);
+                    reprList[i].setState(state); // only set the new (partial) state
                 }
             },
             setTheme: (theme: Theme) => {
@@ -334,6 +363,7 @@ namespace Representation {
     export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation<GraphicsRenderObject, BaseGeometry.Params> {
         let version = 0;
         const updated = new Subject<number>();
+        const geometryState = new GeometryState();
         const currentState = Representation.createState();
         const currentTheme = Theme.createEmpty();
 
@@ -345,6 +375,7 @@ namespace Representation {
             updated,
             get groupCount() { return renderObject.values.uGroupCount.ref.value; },
             get renderObjects() { return [renderObject]; },
+            get geometryVersion() { return geometryState.version; },
             get props() { return currentProps; },
             get params() { return currentParams; },
             createOrUpdate: (props: Partial<PD.Values<BaseGeometry.Params>> = {}) => {
@@ -353,6 +384,8 @@ namespace Representation {
 
                 return Task.create(`Updating '${label}' representation`, async runtime => {
                     // TODO
+                    geometryState.add(0, renderObject.id);
+                    geometryState.snapshot();
                     updated.next(version++);
                 });
             },

+ 5 - 0
src/mol-repr/shape/representation.ts

@@ -44,6 +44,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
     const renderObjects: GraphicsRenderObject<G['kind']>[] = [];
     let _renderObject: GraphicsRenderObject<G['kind']> | undefined;
     let _shape: Shape<G>;
+    let geometryVersion = -1;
     const _theme = Theme.createEmpty();
     let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P); // TODO avoid casting
     let currentParams: P;
@@ -157,6 +158,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
             }
 
             currentProps = newProps;
+            if (updateState.createGeometry || updateState.createNew) {
+                geometryVersion += 1;
+            }
             // increment version
             updated.next(version++);
         });
@@ -178,6 +182,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
         get state() { return _state; },
         get theme() { return _theme; },
         renderObjects,
+        get geometryVersion() { return geometryVersion; },
         updated,
         createOrUpdate,
         getLoci(pickingId?: PickingId) {

+ 12 - 5
src/mol-repr/structure/complex-representation.ts

@@ -7,7 +7,7 @@
 
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ComplexVisual, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
-import { RepresentationContext, RepresentationParamsGetter } from '../representation';
+import { Representation, RepresentationContext, RepresentationParamsGetter } from '../representation';
 import { Structure, StructureElement, Bond } from '../../mol-model/structure';
 import { Subject } from 'rxjs';
 import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
@@ -26,6 +26,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
     let version = 0;
     const { webgl } = ctx;
     const updated = new Subject<number>();
+    const geometryState = new Representation.GeometryState();
     const materialId = getNextMaterialId();
     const renderObjects: GraphicsRenderObject[] = [];
     const _state = StructureRepresentationStateBuilder.create();
@@ -59,9 +60,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
             if (newVisual) setState(_state); // current state for new visual
             // update list of renderObjects
             renderObjects.length = 0;
-            if (visual && visual.renderObject) renderObjects.push(visual.renderObject);
+            if (visual && visual.renderObject) {
+                renderObjects.push(visual.renderObject);
+                geometryState.add(visual.renderObject.id, visual.geometryVersion);
+            }
+            geometryState.snapshot();
             // increment version
-            updated.next(version++);
+            version += 1;
+            updated.next(version);
         });
     }
 
@@ -100,12 +106,12 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         if (state.overpaint !== undefined && visual) {
             // Remap loci from equivalent structure to the current structure
             const remappedOverpaint = Overpaint.remap(state.overpaint, _structure);
-            visual.setOverpaint(remappedOverpaint);
+            visual.setOverpaint(remappedOverpaint, webgl);
         }
         if (state.transparency !== undefined && visual) {
             // Remap loci from equivalent structure to the current structure
             const remappedTransparency = Transparency.remap(state.transparency, _structure);
-            visual.setTransparency(remappedTransparency);
+            visual.setTransparency(remappedTransparency, webgl);
         }
         if (state.clipping !== undefined && visual) {
             // Remap loci from equivalent structure to the current structure
@@ -138,6 +144,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         get params() { return _params; },
         get state() { return _state; },
         get theme() { return _theme; },
+        get geometryVersion() { return geometryState.version; },
         renderObjects,
         updated,
         createOrUpdate,

+ 12 - 5
src/mol-repr/structure/complex-visual.ts

@@ -80,6 +80,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
     let currentStructure: Structure;
 
     let geometry: G;
+    let geometryVersion = -1;
     let locationIt: LocationIterator;
     let positionIt: LocationIterator;
 
@@ -187,7 +188,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
         currentProps = newProps;
         currentTheme = newTheme;
         currentStructure = newStructure;
-        if (newGeometry) geometry = newGeometry;
+        if (newGeometry) {
+            geometry = newGeometry;
+            geometryVersion += 1;
+        }
     }
 
     function lociIsSuperset(loci: Loci) {
@@ -216,6 +220,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
     return {
         get groupCount() { return locationIt ? locationIt.count : 0; },
         get renderObject() { return locationIt && locationIt.count ? renderObject : undefined; },
+        get geometryVersion() { return geometryVersion; },
         createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, structure?: Structure) {
             prepareUpdate(theme, props, structure || currentStructure);
             if (updateState.createGeometry) {
@@ -253,11 +258,13 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices);
         },
-        setOverpaint(overpaint: Overpaint) {
-            Visual.setOverpaint(renderObject, overpaint, lociApply, true);
+        setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing);
         },
-        setTransparency(transparency: Transparency) {
-            Visual.setTransparency(renderObject, transparency, lociApply, true);
+        setTransparency(transparency: Transparency, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
         },
         setClipping(clipping: Clipping) {
             Visual.setClipping(renderObject, clipping, lociApply, true);

+ 16 - 16
src/mol-repr/structure/units-representation.ts

@@ -8,7 +8,7 @@
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationState } from './representation';
 import { Visual } from '../visual';
-import { RepresentationContext, RepresentationParamsGetter } from '../representation';
+import { Representation, RepresentationContext, RepresentationParamsGetter } from '../representation';
 import { Structure, Unit, StructureElement, Bond } from '../../mol-model/structure';
 import { Subject } from 'rxjs';
 import { getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
@@ -34,6 +34,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
     const updated = new Subject<number>();
     const materialId = getNextMaterialId();
     const renderObjects: GraphicsRenderObject[] = [];
+    const geometryState = new Representation.GeometryState();
     const _state = StructureRepresentationStateBuilder.create();
     let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>();
 
@@ -170,8 +171,12 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
             // update list of renderObjects
             renderObjects.length = 0;
             visuals.forEach(({ visual }) => {
-                if (visual.renderObject) renderObjects.push(visual.renderObject);
+                if (visual.renderObject) {
+                    renderObjects.push(visual.renderObject);
+                    geometryState.add(visual.renderObject.id, visual.geometryVersion);
+                }
             });
+            geometryState.snapshot();
             // set new structure
             if (structure) _structure = structure;
             // increment version
@@ -218,8 +223,8 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
         if (visible !== undefined) visual.setVisibility(visible);
         if (alphaFactor !== undefined) visual.setAlphaFactor(alphaFactor);
         if (pickable !== undefined) visual.setPickable(pickable);
-        if (overpaint !== undefined) visual.setOverpaint(overpaint);
-        if (transparency !== undefined) visual.setTransparency(transparency);
+        if (overpaint !== undefined) visual.setOverpaint(overpaint, webgl);
+        if (transparency !== undefined) visual.setTransparency(transparency, webgl);
         if (clipping !== undefined) visual.setClipping(clipping);
         if (transform !== undefined) visual.setTransform(transform);
         if (unitTransforms !== undefined) {
@@ -239,20 +244,14 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
         if (visible !== _state.visible) newState.visible = visible;
         if (alphaFactor !== _state.alphaFactor) newState.alphaFactor = alphaFactor;
         if (pickable !== _state.pickable) newState.pickable = pickable;
-        if (overpaint !== undefined && !Overpaint.areEqual(overpaint, _state.overpaint)) {
-            if (_structure) {
-                newState.overpaint = Overpaint.remap(overpaint, _structure);
-            }
+        if (overpaint !== undefined && _structure) {
+            newState.overpaint = Overpaint.remap(overpaint, _structure);
         }
-        if (transparency !== undefined && !Transparency.areEqual(transparency, _state.transparency)) {
-            if (_structure) {
-                newState.transparency = Transparency.remap(transparency, _structure);
-            }
+        if (transparency !== undefined && _structure) {
+            newState.transparency = Transparency.remap(transparency, _structure);
         }
-        if (clipping !== undefined && !Clipping.areEqual(clipping, _state.clipping)) {
-            if (_structure) {
-                newState.clipping = Clipping.remap(clipping, _structure);
-            }
+        if (clipping !== undefined && _structure) {
+            newState.clipping = Clipping.remap(clipping, _structure);
         }
         if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) {
             newState.transform = transform;
@@ -287,6 +286,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
             });
             return groupCount;
         },
+        get geometryVersion() { return geometryState.version; },
         get props() { return _props; },
         get params() { return _params; },
         get state() { return _state; },

+ 12 - 5
src/mol-repr/structure/units-visual.ts

@@ -84,6 +84,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
     let currentStructureGroup: StructureGroup;
 
     let geometry: G;
+    let geometryVersion = -1;
     let locationIt: LocationIterator;
     let positionIt: LocationIterator;
 
@@ -235,7 +236,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         currentProps = newProps;
         currentTheme = newTheme;
         currentStructureGroup = newStructureGroup;
-        if (newGeometry) geometry = newGeometry;
+        if (newGeometry) {
+            geometry = newGeometry;
+            geometryVersion += 1;
+        }
     }
 
     function _createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<P>, geometry?: G) {
@@ -270,6 +274,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
     return {
         get groupCount() { return locationIt ? locationIt.count : 0; },
         get renderObject() { return locationIt && locationIt.count ? renderObject : undefined; },
+        get geometryVersion() { return geometryVersion; },
         createOrUpdate(ctx: VisualContext, theme: Theme, props: PD.Values<P>, structureGroup?: StructureGroup) {
             prepareUpdate(theme, props, structureGroup || currentStructureGroup);
             if (updateState.createGeometry) {
@@ -318,11 +323,13 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices);
         },
-        setOverpaint(overpaint: Overpaint) {
-            Visual.setOverpaint(renderObject, overpaint, lociApply, true);
+        setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing);
         },
-        setTransparency(transparency: Transparency) {
-            Visual.setTransparency(renderObject, transparency, lociApply, true);
+        setTransparency(transparency: Transparency, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
         },
         setClipping(clipping: Clipping) {
             Visual.setClipping(renderObject, clipping, lociApply, true);

+ 9 - 7
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -23,7 +23,9 @@ import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 import { Texture } from '../../../mol-gl/webgl/texture';
-import { applyMeshColorSmoothing, applyTextureMeshColorSmoothing, ColorSmoothingParams, getColorSmoothingProps } from './util/color';
+import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
+import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/texture-mesh/color-smoothing';
+import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 
 const SharedParams = {
     ...GaussianDensityParams,
@@ -131,7 +133,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
@@ -191,7 +193,7 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
@@ -233,7 +235,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
-    (surface.meta as GaussianSurfaceMeta) = { resolution: densityTextureData.resolution };
+    (surface.meta as GaussianSurfaceMeta).resolution = densityTextureData.resolution;
 
     return surface;
 }
@@ -264,7 +266,7 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
         },
         processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp && webgl) {
                 applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
@@ -309,7 +311,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
-    (surface.meta as GaussianSurfaceMeta) = { resolution: densityTextureData.resolution };
+    (surface.meta as GaussianSurfaceMeta).resolution = densityTextureData.resolution;
 
     return surface;
 }
@@ -340,7 +342,7 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
         },
         processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp && webgl) {
                 applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;

+ 5 - 4
src/mol-repr/structure/visual/molecular-surface-mesh.ts

@@ -20,7 +20,8 @@ import { Sphere3D } from '../../../mol-math/geometry';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
-import { applyMeshColorSmoothing, ColorSmoothingParams, getColorSmoothingProps } from './util/color';
+import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
+import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 
 export const MolecularSurfaceMeshParams = {
     ...UnitsMeshParams,
@@ -58,7 +59,7 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
 
     const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius + getUnitExtraRadius(unit));
     surface.setBoundingSphere(sphere);
-    (surface.meta.resolution as MolecularSurfaceMeta['resolution']) = resolution;
+    (surface.meta as MolecularSurfaceMeta).resolution = resolution;
 
     return surface;
 }
@@ -87,10 +88,10 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
-                (geometry.meta.colorTexture as MolecularSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
+                (geometry.meta as MolecularSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
             }
         },
         dispose: (geometry: Mesh) => {

+ 0 - 104
src/mol-repr/structure/visual/util/color.ts

@@ -1,104 +0,0 @@
-/**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { calcMeshColorSmoothing } from '../../../../mol-geo/geometry/mesh/color-smoothing';
-import { calcTextureMeshColorSmoothing } from '../../../../mol-geo/geometry/texture-mesh/color-smoothing';
-import { MeshValues } from '../../../../mol-gl/renderable/mesh';
-import { TextureMeshValues } from '../../../../mol-gl/renderable/texture-mesh';
-import { WebGLContext } from '../../../../mol-gl/webgl/context';
-import { Texture } from '../../../../mol-gl/webgl/texture';
-import { smoothstep } from '../../../../mol-math/interpolate';
-import { Theme } from '../../../../mol-theme/theme';
-import { ValueCell } from '../../../../mol-util';
-import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
-
-export const ColorSmoothingParams = {
-    smoothColors: PD.MappedStatic('auto', {
-        auto: PD.Group({}),
-        on: PD.Group({
-            resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
-            sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
-        }),
-        off: PD.Group({})
-    }),
-};
-export type ColorSmoothingParams = typeof ColorSmoothingParams
-
-export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number) {
-    if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3) {
-        let stride = 3;
-        if (props.smoothColors.name === 'on') {
-            resolution *= props.smoothColors.params.resolutionFactor;
-            stride = props.smoothColors.params.sampleStride;
-        } else {
-            // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
-            resolution *= 2 - smoothstep(0, 1.1, resolution);
-            resolution = Math.max(0.5, resolution);
-            if (resolution > 1.2) stride = 2;
-        }
-        return { resolution, stride };
-    };
-}
-
-function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
-    return x === 'group' || x === 'groupInstance';
-}
-
-export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
-    if (!isSupportedColorType(values.dColorType.ref.value)) return;
-
-    const smoothingData = calcMeshColorSmoothing({
-        vertexCount: values.uVertexCount.ref.value,
-        instanceCount: values.uInstanceCount.ref.value,
-        groupCount: values.uGroupCount.ref.value,
-        transformBuffer: values.aTransform.ref.value,
-        instanceBuffer: values.aInstance.ref.value,
-        positionBuffer: values.aPosition.ref.value,
-        groupBuffer: values.aGroup.ref.value,
-        colorData: values.tColor.ref.value,
-        colorType: values.dColorType.ref.value,
-        boundingSphere: values.boundingSphere.ref.value,
-        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-    }, resolution, stride, webgl, colorTexture);
-
-    if (smoothingData.kind === 'volume') {
-        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-        ValueCell.update(values.tColorGrid, smoothingData.texture);
-        ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
-        ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
-        ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
-    } else if (smoothingData.kind === 'vertex') {
-        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-        ValueCell.update(values.tColor, smoothingData.texture);
-        ValueCell.update(values.uColorTexDim, smoothingData.texDim);
-    }
-}
-
-export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
-    if (!isSupportedColorType(values.dColorType.ref.value)) return;
-
-    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
-
-    const smoothingData = calcTextureMeshColorSmoothing({
-        vertexCount: values.uVertexCount.ref.value,
-        instanceCount: values.uInstanceCount.ref.value,
-        groupCount: values.uGroupCount.ref.value,
-        transformBuffer: values.aTransform.ref.value,
-        instanceBuffer: values.aInstance.ref.value,
-        positionTexture: values.tPosition.ref.value,
-        groupTexture: values.tGroup.ref.value,
-        colorData: values.tColor.ref.value,
-        colorType: values.dColorType.ref.value,
-        boundingSphere: values.boundingSphere.ref.value,
-        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-    }, resolution, stride, webgl, colorTexture);
-
-    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-    ValueCell.update(values.tColorGrid, smoothingData.texture);
-    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
-    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
-    ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
-}

+ 68 - 6
src/mol-repr/visual.ts

@@ -24,6 +24,11 @@ import { createTransparency, clearTransparency, applyTransparencyValue, getTrans
 import { Clipping } from '../mol-theme/clipping';
 import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data';
 import { getMarkersAverage } from '../mol-geo/geometry/marker-data';
+import { Texture } from '../mol-gl/webgl/texture';
+import { Geometry } from '../mol-geo/geometry/geometry';
+import { getColorSmoothingProps, hasColorSmoothingProp } from '../mol-geo/geometry/base';
+import { applyMeshOverpaintSmoothing, applyMeshTransparencySmoothing } from '../mol-geo/geometry/mesh/color-smoothing';
+import { applyTextureMeshOverpaintSmoothing, applyTextureMeshTransparencySmoothing } from '../mol-geo/geometry/texture-mesh/color-smoothing';
 
 export interface VisualContext {
     readonly runtime: RuntimeContext
@@ -35,6 +40,7 @@ interface Visual<D, P extends PD.Params> {
     /** Number of addressable groups in all instances of the visual */
     readonly groupCount: number
     readonly renderObject: GraphicsRenderObject | undefined
+    readonly geometryVersion: number
     createOrUpdate: (ctx: VisualContext, theme: Theme, props: PD.Values<P>, data?: D) => Promise<void> | void
     getLoci: (pickingId: PickingId) => Loci
     mark: (loci: Loci, action: MarkerAction) => boolean
@@ -43,8 +49,8 @@ interface Visual<D, P extends PD.Params> {
     setPickable: (pickable: boolean) => void
     setColorOnly: (colorOnly: boolean) => void
     setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array | null) => void
-    setOverpaint: (overpaint: Overpaint) => void
-    setTransparency: (transparency: Transparency) => void
+    setOverpaint: (overpaint: Overpaint, webgl?: WebGLContext) => void
+    setTransparency: (transparency: Transparency, webgl?: WebGLContext) => void
     setClipping: (clipping: Clipping) => void
     destroy: () => void
     mustRecreate?: (data: D, props: PD.Values<P>, webgl?: WebGLContext) => boolean
@@ -133,10 +139,22 @@ namespace Visual {
         return changed;
     }
 
-    export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean) {
+    type SurfaceMeta = {
+        resolution?: number
+        overpaintTexture?: Texture
+        transparencyTexture?: Texture
+    }
+
+    type SmoothingContext = {
+        geometry: Geometry,
+        props: PD.Values<any>,
+        webgl?: WebGLContext
+    }
+
+    export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tOverpaint, uGroupCount, instanceCount } = renderObject.values;
+        const { tOverpaint, dOverpaintType, uGroupCount, instanceCount } = renderObject.values;
         const count = uGroupCount.ref.value * instanceCount.ref.value;
 
         // ensure texture has right size
@@ -158,12 +176,34 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tOverpaint, tOverpaint.ref.value);
+        ValueCell.updateIfChanged(dOverpaintType, 'groupInstance');
+
+        if (overpaint.layers.length === 0) return;
+
+        if (smoothing && hasColorSmoothingProp(smoothing.props)) {
+            const { geometry, props, webgl } = smoothing;
+            if (geometry.kind === 'mesh') {
+                const { resolution, overpaintTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyMeshOverpaintSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, overpaintTexture);
+                    (geometry.meta as SurfaceMeta).overpaintTexture = renderObject.values.tOverpaintGrid.ref.value;
+                }
+            } else if (webgl && geometry.kind === 'texture-mesh') {
+                const { resolution, overpaintTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyTextureMeshOverpaintSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, overpaintTexture);
+                    (geometry.meta as SurfaceMeta).overpaintTexture = renderObject.values.tOverpaintGrid.ref.value;
+                }
+            }
+        }
     }
 
-    export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean) {
+    export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tTransparency, transparencyAverage, uGroupCount, instanceCount } = renderObject.values;
+        const { tTransparency, dTransparencyType, transparencyAverage, uGroupCount, instanceCount } = renderObject.values;
         const count = uGroupCount.ref.value * instanceCount.ref.value;
 
         // ensure texture has right size and variant
@@ -184,6 +224,28 @@ namespace Visual {
         }
         ValueCell.update(tTransparency, tTransparency.ref.value);
         ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count));
+        ValueCell.updateIfChanged(dTransparencyType, 'groupInstance');
+
+        if (transparency.layers.length === 0) return;
+
+        if (smoothing && hasColorSmoothingProp(smoothing.props)) {
+            const { geometry, props, webgl } = smoothing;
+            if (geometry.kind === 'mesh') {
+                const { resolution, transparencyTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyMeshTransparencySmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, transparencyTexture);
+                    (geometry.meta as SurfaceMeta).transparencyTexture = renderObject.values.tTransparencyGrid.ref.value;
+                }
+            } else if (webgl && geometry.kind === 'texture-mesh') {
+                const { resolution, transparencyTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyTextureMeshTransparencySmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, transparencyTexture);
+                    (geometry.meta as SurfaceMeta).transparencyTexture = renderObject.values.tTransparencyGrid.ref.value;
+                }
+            }
+        }
     }
 
     export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) {

+ 14 - 3
src/mol-repr/volume/representation.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -74,6 +74,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
     let currentVolume: Volume;
 
     let geometry: G;
+    let geometryVersion = -1;
     let locationIt: LocationIterator;
     let positionIt: LocationIterator;
 
@@ -156,7 +157,10 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
         currentProps = newProps;
         currentTheme = newTheme;
         currentVolume = newVolume;
-        if (newGeometry) geometry = newGeometry;
+        if (newGeometry) {
+            geometry = newGeometry;
+            geometryVersion += 1;
+        }
     }
 
     function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
@@ -170,6 +174,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
     return {
         get groupCount() { return locationIt ? locationIt.count : 0; },
         get renderObject() { return renderObject; },
+        get geometryVersion() { return geometryVersion; },
         async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, volume?: Volume) {
             prepareUpdate(theme, props, volume || currentVolume);
             if (updateState.createGeometry) {
@@ -236,6 +241,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
     let version = 0;
     const { webgl } = ctx;
     const updated = new Subject<number>();
+    const geometryState = new Representation.GeometryState();
     const materialId = getNextMaterialId();
     const renderObjects: GraphicsRenderObject[] = [];
     const _state = Representation.createState();
@@ -266,7 +272,11 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
             if (promise) await promise;
             // update list of renderObjects
             renderObjects.length = 0;
-            if (visual && visual.renderObject) renderObjects.push(visual.renderObject);
+            if (visual && visual.renderObject) {
+                renderObjects.push(visual.renderObject);
+                geometryState.add(visual.renderObject.id, visual.geometryVersion);
+            }
+            geometryState.snapshot();
             // increment version
             updated.next(version++);
         });
@@ -304,6 +314,7 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
         get params() { return _params; },
         get state() { return _state; },
         get theme() { return _theme; },
+        get geometryVersion() { return geometryState.version; },
         renderObjects,
         updated,
         createOrUpdate,

+ 7 - 4
src/mol-state/state.ts

@@ -20,7 +20,6 @@ import { now, formatTimespan } from '../mol-util/now';
 import { ParamDefinition } from '../mol-util/param-definition';
 import { StateTreeSpine } from './tree/spine';
 import { AsyncQueue } from '../mol-util/async-queue';
-import { isProductionMode } from '../mol-util/debug';
 import { arraySetAdd, arraySetRemove } from '../mol-util/array';
 import { UniqueArray } from '../mol-data/generic';
 import { assignIfUndefined } from '../mol-util/object';
@@ -207,14 +206,18 @@ class State {
                 if (!restored) {
                     restored = true;
                     await this.updateTree(snapshot).runInContext(ctx);
-                    this.events.log.next(LogEntry.error('' + e));
+                    this.events.log.next(LogEntry.error('Error during state transaction, reverting'));
                 }
                 if (isNested) {
                     this.inTransactionError = true;
                     throw e;
                 }
 
-                if (options?.rethrowErrors) throw e;
+                if (options?.rethrowErrors) {
+                    throw e;
+                } else {
+                    console.error(e);
+                }
             } finally {
                 if (!isNested) {
                     this.inTransaction = false;
@@ -829,7 +832,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) {
         ctx.changed = true;
         if (!ctx.hadError) ctx.newCurrent = root;
         doError(ctx, root, e, false);
-        if (!isProductionMode) console.error(e);
+        console.error(e);
         return;
     }
 

+ 0 - 2
src/mol-task/execution/observable.ts

@@ -10,7 +10,6 @@ import { Progress } from './progress';
 import { now } from '../../mol-util/now';
 import { Scheduler } from '../util/scheduler';
 import { UserTiming } from '../util/user-timing';
-import { isDebugMode } from '../../mol-util/debug';
 
 interface ExposedTask<T> extends Task<T> {
     f: (ctx: RuntimeContext) => Promise<T>,
@@ -116,7 +115,6 @@ async function execute<T>(task: ExposedTask<T>, ctx: ObservableRuntimeContext) {
                 task.onAbort();
             }
         }
-        if (isDebugMode) console.error(e);
         throw e;
     }
 }

+ 1 - 1
src/mol-util/download.ts

@@ -61,7 +61,7 @@ export function download(data: Blob | string, downloadName = 'download') {
                 open(data);
             }
         } else {
-            const url = URL.createObjectURL(data);
+            const url = URL.createObjectURL(typeof data === 'string' ? new Blob([data]) : data);
             location.href = url;
             setTimeout(() => URL.revokeObjectURL(url), 4E4); // 40s
         }

+ 11 - 7
webpack.config.common.js

@@ -1,8 +1,17 @@
 const path = require('path');
+const fs = require('fs');
 const webpack = require('webpack');
 const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const VersionFile = require('webpack-version-file-plugin');
+const VERSION = require('./package.json').version;
+
+class VersionFilePlugin {
+    apply() {
+        fs.writeFileSync(
+            path.resolve(__dirname, 'lib/mol-plugin/version.js'),
+            `export var PLUGIN_VERSION = '${VERSION}';\nexport var PLUGIN_VERSION_DATE = new Date(typeof __MOLSTAR_DEBUG_TIMESTAMP__ !== 'undefined' ? __MOLSTAR_DEBUG_TIMESTAMP__ : ${new Date().valueOf()});`);
+    }
+}
 
 const sharedConfig = {
     module: {
@@ -36,12 +45,7 @@ const sharedConfig = {
             '__MOLSTAR_DEBUG_TIMESTAMP__': webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true)
         }),
         new MiniCssExtractPlugin({ filename: 'molstar.css' }),
-        new VersionFile({
-            extras: { timestamp: `${new Date().valueOf()}` },
-            packageFile: path.resolve(__dirname, 'package.json'),
-            templateString: `export var PLUGIN_VERSION = '<%= package.version %>';\nexport var PLUGIN_VERSION_DATE = new Date(typeof __MOLSTAR_DEBUG_TIMESTAMP__ !== 'undefined' ? __MOLSTAR_DEBUG_TIMESTAMP__ : <%= extras.timestamp %>);`,
-            outputFile: path.resolve(__dirname, 'lib/mol-plugin/version.js')
-        })
+        new VersionFilePlugin(),
     ],
     resolve: {
         modules: [

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است