Browse Source

Merge branch 'master' into feature/formal-charge-labels

ptourlas 3 years ago
parent
commit
239fef281e
77 changed files with 643 additions and 658 deletions
  1. 17 0
      CHANGELOG.md
  2. 2 2
      package-lock.json
  3. 1 1
      package.json
  4. 4 0
      src/apps/viewer/index.html
  5. 2 0
      src/apps/viewer/index.ts
  6. 1 1
      src/examples/alpha-orbitals/index.ts
  7. 4 4
      src/extensions/geo-export/mesh-exporter.ts
  8. 33 28
      src/mol-canvas3d/canvas3d.ts
  9. 29 15
      src/mol-canvas3d/passes/draw.ts
  10. 3 2
      src/mol-canvas3d/passes/image.ts
  11. 1 1
      src/mol-canvas3d/passes/marking.ts
  12. 22 12
      src/mol-canvas3d/passes/multi-sample.ts
  13. 2 2
      src/mol-canvas3d/passes/pick.ts
  14. 16 0
      src/mol-geo/geometry/_spec/marker.spec.ts
  15. 24 1
      src/mol-geo/geometry/color-data.ts
  16. 2 0
      src/mol-geo/geometry/cylinders/cylinders.ts
  17. 27 80
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  18. 4 9
      src/mol-geo/geometry/direct-volume/transfer-function.ts
  19. 2 0
      src/mol-geo/geometry/image/image.ts
  20. 2 0
      src/mol-geo/geometry/lines/lines.ts
  21. 13 6
      src/mol-geo/geometry/marker-data.ts
  22. 2 0
      src/mol-geo/geometry/mesh/mesh.ts
  23. 2 0
      src/mol-geo/geometry/points/points.ts
  24. 5 5
      src/mol-geo/geometry/size-data.ts
  25. 2 0
      src/mol-geo/geometry/spheres/spheres.ts
  26. 2 0
      src/mol-geo/geometry/text/text.ts
  27. 2 1
      src/mol-geo/geometry/texture-mesh/texture-mesh.ts
  28. 2 2
      src/mol-gl/compute/histogram-pyramid/sum.ts
  29. 12 9
      src/mol-gl/compute/marching-cubes/isosurface.ts
  30. 2 12
      src/mol-gl/renderable/direct-volume.ts
  31. 3 1
      src/mol-gl/renderable/schema.ts
  32. 0 1
      src/mol-gl/renderable/texture-mesh.ts
  33. 10 16
      src/mol-gl/renderer.ts
  34. 1 1
      src/mol-gl/shader-code.ts
  35. 3 3
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  36. 2 2
      src/mol-gl/shader/chunks/assign-group.glsl.ts
  37. 1 1
      src/mol-gl/shader/chunks/assign-position.glsl.ts
  38. 3 3
      src/mol-gl/shader/chunks/assign-size.glsl.ts
  39. 8 9
      src/mol-gl/shader/chunks/common.glsl.ts
  40. 1 1
      src/mol-gl/shader/chunks/wboit-write.glsl.ts
  41. 1 1
      src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts
  42. 112 275
      src/mol-gl/shader/direct-volume.frag.ts
  43. 1 1
      src/mol-gl/shader/gaussian-density.frag.ts
  44. 5 5
      src/mol-gl/shader/histogram-pyramid/reduction.frag.ts
  45. 4 4
      src/mol-gl/shader/image.frag.ts
  46. 22 8
      src/mol-gl/shader/marching-cubes/isosurface.frag.ts
  47. 2 2
      src/mol-gl/shader/mesh.vert.ts
  48. 2 2
      src/mol-math/geometry/gaussian-density/gpu.ts
  49. 31 9
      src/mol-plugin-state/actions/structure.ts
  50. 3 2
      src/mol-plugin-state/manager/structure/selection.ts
  51. 2 2
      src/mol-plugin-state/transforms/representation.ts
  52. 3 3
      src/mol-plugin/behavior/dynamic/representation.ts
  53. 3 27
      src/mol-plugin/config.ts
  54. 37 0
      src/mol-plugin/features.ts
  55. 1 1
      src/mol-repr/structure/complex-representation.ts
  56. 1 1
      src/mol-repr/structure/representation/gaussian-surface.ts
  57. 2 7
      src/mol-repr/structure/representation/gaussian-volume.ts
  58. 1 1
      src/mol-repr/structure/representation/label.ts
  59. 1 1
      src/mol-repr/structure/representation/molecular-surface.ts
  60. 1 1
      src/mol-repr/structure/units-representation.ts
  61. 6 6
      src/mol-repr/structure/visual/gaussian-density-volume.ts
  62. 9 6
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  63. 1 1
      src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
  64. 1 2
      src/mol-repr/structure/visual/label-text.ts
  65. 1 1
      src/mol-repr/structure/visual/molecular-surface-mesh.ts
  66. 1 1
      src/mol-repr/structure/visual/molecular-surface-wireframe.ts
  67. 3 5
      src/mol-repr/structure/visual/util/common.ts
  68. 13 12
      src/mol-repr/structure/visual/util/gaussian.ts
  69. 5 4
      src/mol-repr/structure/visual/util/molecular-surface.ts
  70. 8 12
      src/mol-repr/volume/direct-volume.ts
  71. 10 2
      src/mol-repr/volume/isosurface.ts
  72. 2 2
      src/mol-repr/volume/slice.ts
  73. 2 2
      src/mol-repr/volume/util.ts
  74. 2 0
      src/mol-theme/color.ts
  75. 62 0
      src/mol-theme/color/volume-value.ts
  76. 6 16
      src/mol-util/number-packing.ts
  77. 2 2
      src/tests/browser/marching-cubes.ts

+ 17 - 0
CHANGELOG.md

@@ -6,6 +6,23 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Add PDBj as a pdb-provider option
+
+## [v3.0.0-dev.8] - 2021-12-31
+
+- Add ``PluginFeatureDetection`` and disable WBOIT in Safari 15.
+- Add ``disable-wboit`` Viewer GET param
+- Add ``prefer-webgl1`` Viewer GET param
+- [Breaking] Refactor direct-volume rendering
+    - Remove isosurface render-mode (use GPU MC instead)
+    - Move coloring into theme (like for other geometries/renderables)
+        - Add ``direct`` color type
+        - Remove color from transfer-function (now only alpha)
+        - Add direct-volume color theme support
+        - Add volume-value color theme
+- [Breaking] Use size theme in molecular/gaussian surface & label representations
+    - This is breaking because it was hardcoded to ``physical`` internally but the repr size theme default was ``uniform`` (now ``physical``)
+
 ## [v3.0.0-dev.7] - 2021-12-20
 
 - Reduce number of created programs/shaders

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "molstar",
-  "version": "3.0.0-dev.7",
+  "version": "3.0.0-dev.8",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "molstar",
-      "version": "3.0.0-dev.7",
+      "version": "3.0.0-dev.8",
       "license": "MIT",
       "dependencies": {
         "@types/argparse": "^2.0.10",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "3.0.0-dev.7",
+  "version": "3.0.0-dev.8",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {

+ 4 - 0
src/apps/viewer/index.html

@@ -56,6 +56,8 @@
             var pixelScale = getParam('pixel-scale', '[^&]+').trim();
             var pickScale = getParam('pick-scale', '[^&]+').trim();
             var pickPadding = getParam('pick-padding', '[^&]+').trim();
+            var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
+            var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1';
 
             molstar.Viewer.create('app', {
                 layoutShowControls: !hideControls,
@@ -69,6 +71,8 @@
                 pixelScale: parseFloat(pixelScale) || 1,
                 pickScale: parseFloat(pickScale) || 0.25,
                 pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
+                enableWboit: !disableWboit,
+                preferWebgl1: preferWebgl1,
             }).then(viewer => {
                 var snapshotId = getParam('snapshot-id', '[^&]+').trim();
                 if (snapshotId) viewer.setRemoteSnapshot(snapshotId);

+ 2 - 0
src/apps/viewer/index.ts

@@ -84,6 +84,7 @@ const DefaultViewerOptions = {
     pickScale: PluginConfig.General.PickScale.defaultValue,
     pickPadding: PluginConfig.General.PickPadding.defaultValue,
     enableWboit: PluginConfig.General.EnableWboit.defaultValue,
+    preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
 
     viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
     viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -144,6 +145,7 @@ export class Viewer {
                 [PluginConfig.General.PickScale, o.pickScale],
                 [PluginConfig.General.PickPadding, o.pickPadding],
                 [PluginConfig.General.EnableWboit, o.enableWboit],
+                [PluginConfig.General.PreferWebGl1, o.preferWebgl1],
                 [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
                 [PluginConfig.Viewport.ShowControls, o.viewportShowControls],
                 [PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],

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

@@ -97,7 +97,7 @@ export class AlphaOrbitalsExample {
     }
 
     readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
-    readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
+    readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: true });
 
     private selectors?: Selectors = void 0;
     private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;

+ 4 - 4
src/extensions/geo-export/mesh-exporter.ts

@@ -25,7 +25,7 @@ import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
-import { decodeFloatRGB } from '../../mol-util/float-packing';
+import { unpackRGBToInt } from '../../mol-util/number-packing';
 import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
 import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
 
@@ -65,7 +65,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const r = tSize.array[i * 3];
         const g = tSize.array[i * 3 + 1];
         const b = tSize.array[i * 3 + 2];
-        return decodeFloatRGB(r, g, b) / sizeDataFactor;
+        return unpackRGBToInt(r, g, b) / sizeDataFactor;
     }
 
     private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
@@ -95,9 +95,9 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const g = groups[i4 + 1];
         const b = groups[i4 + 2];
         if (groups instanceof Float32Array) {
-            return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
+            return unpackRGBToInt(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
         }
-        return decodeFloatRGB(r, g, b);
+        return unpackRGBToInt(r, g, b);
     }
 
     protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {

+ 33 - 28
src/mol-canvas3d/canvas3d.ts

@@ -223,7 +223,7 @@ interface Canvas3D {
     clear(): void
     syncVisibility(): void
 
-    requestDraw(force?: boolean): void
+    requestDraw(): void
 
     /** Reset the timers, used by "animate" */
     resetTime(t: number): void
@@ -355,17 +355,24 @@ namespace Canvas3D {
                 changed = helper.camera.mark(loci, action) || changed;
                 reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
             }
-            if (changed && !noDraw) {
-                scene.update(void 0, true);
-                helper.handle.scene.update(void 0, true);
-                helper.camera.scene.update(void 0, true);
-                const prevPickDirty = pickHelper.dirty;
-                draw(true);
-                pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
+            if (changed) {
+                if (noDraw) {
+                    // Even with `noDraw` make sure changes will be rendered.
+                    // Note that with this calling mark (with or without `noDraw`) multiple times
+                    // during a JS event loop iteration will only result in a single render call.
+                    forceNextRender = true;
+                } else {
+                    scene.update(void 0, true);
+                    helper.handle.scene.update(void 0, true);
+                    helper.camera.scene.update(void 0, true);
+                    const prevPickDirty = pickHelper.dirty;
+                    draw({ force: true, allowMulti: true });
+                    pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
+                }
             }
         }
 
-        function render(force: boolean) {
+        function render(force: boolean, allowMulti: boolean) {
             if (webgl.isContextLost) return false;
 
             let resized = false;
@@ -395,14 +402,12 @@ namespace Canvas3D {
                     cam = stereoCamera;
                 }
 
+                const ctx = { renderer, camera: cam, scene, helper };
                 if (MultiSamplePass.isEnabled(p.multiSample)) {
-                    if (!cameraChanged) {
-                        while (!multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p));
-                    } else {
-                        multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
-                    }
+                    const forceOn = !cameraChanged && allowMulti && !controls.props.spin;
+                    multiSampleHelper.render(ctx, p, true, forceOn);
                 } else {
-                    passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking);
+                    passes.draw.render(ctx, p, true);
                 }
                 pickHelper.dirty = true;
                 didRender = true;
@@ -416,15 +421,15 @@ namespace Canvas3D {
         let currentTime = 0;
         let drawPaused = false;
 
-        function draw(force?: boolean) {
+        function draw(options?: { force?: boolean, allowMulti?: boolean }) {
             if (drawPaused) return;
-            if (render(!!force) && notifyDidDraw) {
+            if (render(!!options?.force, !!options?.allowMulti) && notifyDidDraw) {
                 didDraw.next(now() - startTime as now.Timestamp);
             }
         }
 
-        function requestDraw(force?: boolean) {
-            forceNextRender = forceNextRender || !!force;
+        function requestDraw() {
+            forceNextRender = true;
         }
 
         let animationFrameHandle = 0;
@@ -438,8 +443,8 @@ namespace Canvas3D {
                 return;
             }
 
-            draw(false);
-            if (!camera.transition.inTransition && !webgl.isContextLost) {
+            draw();
+            if (!camera.transition.inTransition && !controls.props.spin && !webgl.isContextLost) {
                 interactionHelper.tick(currentTime);
             }
         }
@@ -478,7 +483,7 @@ namespace Canvas3D {
                 resolveCameraReset();
                 if (forceDrawAfterAllCommited) {
                     if (helper.debug.isEnabled) helper.debug.update();
-                    draw(true);
+                    draw({ force: true });
                     forceDrawAfterAllCommited = false;
                 }
                 commited.next(now());
@@ -661,11 +666,11 @@ namespace Canvas3D {
 
         const contextRestoredSub = contextRestored.subscribe(() => {
             pickHelper.dirty = true;
-            draw(true);
+            draw({ force: true });
             // Unclear why, but in Chrome with wboit enabled the first `draw` only clears
             // the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
             // context loss so it is unclear if it behaves the same.
-            draw(true);
+            draw({ force: true });
         });
 
         const resized = new BehaviorSubject<any>(0);
@@ -674,7 +679,7 @@ namespace Canvas3D {
             passes.updateSize();
             updateViewport();
             syncViewport();
-            if (draw) requestDraw(true);
+            if (draw) requestDraw();
             resized.next(+new Date());
         }
 
@@ -699,7 +704,7 @@ namespace Canvas3D {
                 reprRenderObjects.clear();
                 scene.clear();
                 helper.debug.clear();
-                requestDraw(true);
+                requestDraw();
                 reprCount.next(reprRenderObjects.size);
             },
             syncVisibility: () => {
@@ -711,7 +716,7 @@ namespace Canvas3D {
                 if (scene.syncVisibility()) {
                     if (helper.debug.isEnabled) helper.debug.update();
                 }
-                requestDraw(true);
+                requestDraw();
             },
 
             requestDraw,
@@ -799,7 +804,7 @@ namespace Canvas3D {
                 }
 
                 if (!doNotRequestDraw) {
-                    requestDraw(true);
+                    requestDraw();
                 }
             },
             getImagePass: (props: Partial<ImageProps> = {}) => {

+ 29 - 15
src/mol-canvas3d/passes/draw.ts

@@ -53,6 +53,19 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text
     return createComputeRenderable(renderItem, values);
 }
 
+type Props = {
+    postprocessing: PostprocessingProps
+    marking: MarkingProps
+    transparentBackground: boolean;
+}
+
+type RenderContext = {
+    renderer: Renderer;
+    camera: Camera | StereoCamera;
+    scene: Scene;
+    helper: Helper;
+}
+
 export class DrawPass {
     private readonly drawTarget: RenderTarget;
 
@@ -264,25 +277,25 @@ export class DrawPass {
         renderer.renderBlendedTransparent(scene.primitives, camera, null);
     }
 
-    private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
+    private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, props: Props) {
         const volumeRendering = scene.volumes.renderables.length > 0;
-        const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
-        const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps);
-        const markingEnabled = MarkingPass.isEnabled(markingProps);
+        const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
+        const antialiasingEnabled = AntialiasingPass.isEnabled(props.postprocessing);
+        const markingEnabled = MarkingPass.isEnabled(props.marking);
 
         const { x, y, width, height } = camera.viewport;
         renderer.setViewport(x, y, width, height);
         renderer.update(camera);
 
-        if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
+        if (props.transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
             this.drawTarget.bind();
             renderer.clear(false);
         }
 
         if (this.wboitEnabled) {
-            this._renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps);
+            this._renderWboit(renderer, camera, scene, props.transparentBackground, props.postprocessing);
         } else {
-            this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, postprocessingProps);
+            this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, props.transparentBackground, props.postprocessing);
         }
 
         if (postprocessingEnabled) {
@@ -294,7 +307,7 @@ export class DrawPass {
         }
 
         if (markingEnabled) {
-            const markingDepthTest = markingProps.ghostEdgeStrength < 1;
+            const markingDepthTest = props.marking.ghostEdgeStrength < 1;
             if (markingDepthTest) {
                 this.marking.depthTarget.bind();
                 renderer.clear(false);
@@ -305,7 +318,7 @@ export class DrawPass {
             renderer.clear(false);
             renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
 
-            this.marking.update(markingProps);
+            this.marking.update(props.marking);
             this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
         }
 
@@ -323,7 +336,7 @@ export class DrawPass {
         }
 
         if (antialiasingEnabled) {
-            this.antialiasing.render(camera, toDrawingBuffer, postprocessingProps);
+            this.antialiasing.render(camera, toDrawingBuffer, props.postprocessing);
         } else if (toDrawingBuffer) {
             this.drawTarget.bind();
 
@@ -338,16 +351,17 @@ export class DrawPass {
         this.webgl.gl.flush();
     }
 
-    render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) {
-        renderer.setTransparentBackground(transparentBackground);
+    render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
+        const { renderer, camera, scene, helper } = ctx;
+        renderer.setTransparentBackground(props.transparentBackground);
         renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
         renderer.setPixelRatio(this.webgl.pixelRatio);
 
         if (StereoCamera.is(camera)) {
-            this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
-            this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
+            this._render(renderer, camera.left, scene, helper, toDrawingBuffer, props);
+            this._render(renderer, camera.right, scene, helper, toDrawingBuffer, props);
         } else {
-            this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps);
+            this._render(renderer, camera, scene, helper, toDrawingBuffer, props);
         }
     }
 

+ 3 - 2
src/mol-canvas3d/passes/image.ts

@@ -83,11 +83,12 @@ export class ImagePass {
         Viewport.set(this._camera.viewport, 0, 0, this._width, this._height);
         this._camera.update();
 
+        const ctx = { renderer: this.renderer, camera: this._camera, scene: this.scene, helper: this.helper };
         if (MultiSamplePass.isEnabled(this.props.multiSample)) {
-            this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
+            this.multiSampleHelper.render(ctx, this.props, false);
             this._colorTarget = this.multiSamplePass.colorTarget;
         } else {
-            this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking);
+            this.drawPass.render(ctx, this.props, false);
             this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing);
         }
     }

+ 1 - 1
src/mol-canvas3d/passes/marking.ts

@@ -22,7 +22,7 @@ import { Color } from '../../mol-util/color';
 import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
 
 export const MarkingParams = {
-    enabled: PD.Boolean(false),
+    enabled: PD.Boolean(true),
     highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)),
     selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)),
     edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }),

+ 22 - 12
src/mol-canvas3d/passes/multi-sample.ts

@@ -59,6 +59,14 @@ type Props = {
     multiSample: MultiSampleProps
     postprocessing: PostprocessingProps
     marking: MarkingProps
+    transparentBackground: boolean;
+}
+
+type RenderContext = {
+    renderer: Renderer;
+    camera: Camera | StereoCamera;
+    scene: Scene;
+    helper: Helper;
 }
 
 export class MultiSamplePass {
@@ -97,12 +105,12 @@ export class MultiSamplePass {
         }
     }
 
-    render(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
-        if (props.multiSample.mode === 'temporal') {
-            return this.renderTemporalMultiSample(sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
+    render(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean, forceOn: boolean) {
+        if (props.multiSample.mode === 'temporal' && !forceOn) {
+            return this.renderTemporalMultiSample(sampleIndex, ctx, props, toDrawingBuffer);
         } else {
-            this.renderMultiSample(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
-            return sampleIndex;
+            this.renderMultiSample(ctx, toDrawingBuffer, props);
+            return -2;
         }
     }
 
@@ -114,7 +122,8 @@ export class MultiSamplePass {
         }
     }
 
-    private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
+    private renderMultiSample(ctx: RenderContext, toDrawingBuffer: boolean, props: Props) {
+        const { camera } = ctx;
         const { compose, composeTarget, drawPass, webgl } = this;
         const { gl, state } = webgl;
 
@@ -148,7 +157,7 @@ export class MultiSamplePass {
             ValueCell.update(compose.values.uWeight, sampleWeight);
 
             // render scene
-            drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
+            drawPass.render(ctx, props, false);
 
             // compose rendered scene with compose target
             composeTarget.bind();
@@ -181,7 +190,8 @@ export class MultiSamplePass {
         camera.update();
     }
 
-    private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
+    private renderTemporalMultiSample(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
+        const { camera } = ctx;
         const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
         const { gl, state } = webgl;
 
@@ -198,7 +208,7 @@ export class MultiSamplePass {
         const sampleWeight = 1.0 / offsetList.length;
 
         if (sampleIndex === -1) {
-            drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
+            drawPass.render(ctx, props, false);
             ValueCell.update(compose.values.uWeight, 1.0);
             ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture);
             compose.update();
@@ -226,7 +236,7 @@ export class MultiSamplePass {
                 camera.update();
 
                 // render scene
-                drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking);
+                drawPass.render(ctx, props, false);
 
                 // compose rendered scene with compose target
                 composeTarget.bind();
@@ -325,8 +335,8 @@ export class MultiSampleHelper {
     }
 
     /** Return `true` while more samples are needed */
-    render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
-        this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
+    render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean, forceOn?: boolean) {
+        this.sampleIndex = this.multiSamplePass.render(this.sampleIndex, ctx, props, toDrawingBuffer, !!forceOn);
         return this.sampleIndex < 0;
     }
 

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

@@ -12,7 +12,7 @@ import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
 import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { spiral2d } from '../../mol-math/misc';
-import { decodeFloatRGB, unpackRGBAToDepth } from '../../mol-util/float-packing';
+import { unpackRGBToInt, unpackRGBAToDepth } from '../../mol-util/number-packing';
 import { Camera, ICamera } from '../camera';
 import { StereoCamera } from '../camera/stereo';
 import { cameraUnproject } from '../camera/util';
@@ -174,7 +174,7 @@ export class PickHelper {
 
     private getId(x: number, y: number, buffer: Uint8Array) {
         const idx = this.getBufferIdx(x, y);
-        return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
+        return unpackRGBToInt(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
     }
 
     private render(camera: Camera | StereoCamera) {

+ 16 - 0
src/mol-geo/geometry/_spec/marker.spec.ts

@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { getMarkersAverage } from '../marker-data';
+
+describe('marker-data', () => {
+    it('getMarkersAverage', () => {
+        expect(getMarkersAverage(new Uint8Array([0, 0, 0, 0]), 3)).toBe(0);
+        expect(getMarkersAverage(new Uint8Array([0, 0, 1, 0]), 3)).toBe(1 / 3);
+        expect(getMarkersAverage(new Uint8Array([0, 0, 0, 0]), 4)).toBe(0);
+        expect(getMarkersAverage(new Uint8Array([0, 0, 1, 0]), 4)).toBe(1 / 4);
+    });
+});

+ 24 - 1
src/mol-geo/geometry/color-data.ts

@@ -15,7 +15,7 @@ import { LocationColor, ColorTheme } from '../../mol-theme/color';
 import { Geometry } from './geometry';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
-export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance'
+export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' | 'volume' | 'volumeInstance' | 'direct'
 
 export type ColorData = {
     uColor: ValueCell<Vec3>,
@@ -50,6 +50,7 @@ function _createColors(locationIt: LocationIterator, positionIt: LocationIterato
         case 'vertexInstance': return createVertexInstanceColor(positionIt, colorTheme.color, colorData);
         case 'volume': return createGridColor((colorTheme as any).grid, 'volume', colorData);
         case 'volumeInstance': return createGridColor((colorTheme as any).grid, 'volumeInstance', colorData);
+        case 'direct': return createDirectColor(colorData);
     }
 }
 
@@ -237,3 +238,25 @@ export function createGridColor(grid: ColorVolume, type: ColorType, colorData?:
         };
     }
 }
+
+//
+
+/** Creates direct color */
+function createDirectColor(colorData?: ColorData): ColorData {
+    if (colorData) {
+        ValueCell.updateIfChanged(colorData.dColorType, 'direct');
+        return colorData;
+    } else {
+        return {
+            uColor: ValueCell.create(Vec3()),
+            tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
+            tColorGrid: ValueCell.create(createNullTexture()),
+            tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
+            uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
+            uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uColorGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dColorType: ValueCell.create('direct'),
+            dUsePalette: ValueCell.create(false),
+        };
+    }
+}

+ 2 - 0
src/mol-geo/geometry/cylinders/cylinders.ts

@@ -213,6 +213,8 @@ export namespace Cylinders {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('cylinders'),
+
             aMapping: cylinders.mappingBuffer,
             aGroup: cylinders.groupBuffer,
             aStart: cylinders.startBuffer,

+ 27 - 80
src/mol-geo/geometry/direct-volume/direct-volume.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>
  */
@@ -26,8 +26,7 @@ import { TransformData } from '../transform-data';
 import { createEmptyTransparency } from '../transparency-data';
 import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
 import { createEmptyClipping } from '../clipping-data';
-import { Grid, Volume } from '../../../mol-model/volume';
-import { ColorNames } from '../../../mol-util/color/names';
+import { Grid } from '../../../mol-model/volume';
 import { createEmptySubstance } from '../substance-data';
 
 const VolumeBox = Box();
@@ -48,6 +47,7 @@ export interface DirectVolume {
     readonly unitToCartn: ValueCell<Mat4>
     readonly cartnToUnit: ValueCell<Mat4>
     readonly packedGroup: ValueCell<boolean>
+    readonly axisOrder: ValueCell<Vec3>
 
     /** Bounding sphere of the volume */
     readonly boundingSphere: Sphere3D
@@ -56,10 +56,10 @@ export interface DirectVolume {
 }
 
 export namespace DirectVolume {
-    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume?: DirectVolume): DirectVolume {
+    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume?: DirectVolume): DirectVolume {
         return directVolume ?
-            update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume) :
-            fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup);
+            update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume) :
+            fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder);
     }
 
     function hashCode(directVolume: DirectVolume) {
@@ -70,7 +70,7 @@ export namespace DirectVolume {
         ]);
     }
 
-    function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean): DirectVolume {
+    function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3): DirectVolume {
         const boundingSphere = Sphere3D();
         let currentHash = -1;
 
@@ -101,6 +101,7 @@ export namespace DirectVolume {
                 return boundingSphere;
             },
             packedGroup: ValueCell.create(packedGroup),
+            axisOrder: ValueCell.create(axisOrder),
             setBoundingSphere(sphere: Sphere3D) {
                 Sphere3D.copy(boundingSphere, sphere);
                 currentHash = hashCode(directVolume);
@@ -109,7 +110,7 @@ export namespace DirectVolume {
         return directVolume;
     }
 
-    function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, directVolume: DirectVolume): DirectVolume {
+    function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume: DirectVolume): DirectVolume {
         const width = texture.getWidth();
         const height = texture.getHeight();
         const depth = texture.getDepth();
@@ -126,6 +127,7 @@ export namespace DirectVolume {
         ValueCell.update(directVolume.unitToCartn, unitToCartn);
         ValueCell.update(directVolume.cartnToUnit, Mat4.invert(Mat4(), unitToCartn));
         ValueCell.updateIfChanged(directVolume.packedGroup, packedGroup);
+        ValueCell.updateIfChanged(directVolume.axisOrder, Vec3.fromArray(directVolume.axisOrder.ref.value, axisOrder, 0));
         return directVolume;
     }
 
@@ -138,47 +140,19 @@ export namespace DirectVolume {
         const texture = createNullTexture();
         const stats = Grid.One.stats;
         const packedGroup = false;
-        return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, directVolume);
-    }
-
-    export function createRenderModeParam(stats?: Grid['stats']) {
-        const isoValueParam = stats
-            ? Volume.createIsoValueParam(Volume.IsoValue.relative(2), stats)
-            : Volume.IsoValueParam;
-
-        return PD.MappedStatic('volume', {
-            isosurface: PD.Group({
-                isoValue: isoValueParam,
-                singleLayer: PD.Boolean(false, { isEssential: true }),
-            }, { isFlat: true }),
-            volume: PD.Group({
-                controlPoints: PD.LineGraph([
-                    Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
-                    Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
-                ]),
-                list: PD.ColorList({
-                    kind: 'interpolate',
-                    colors: [
-                        [ColorNames.white, 0],
-                        [ColorNames.red, 0.25],
-                        [ColorNames.white, 0.5],
-                        [ColorNames.blue, 0.75],
-                        [ColorNames.white, 1]
-                    ]
-                }, { offsets: true }),
-            }, { isFlat: true })
-        }, { isEssential: true });
+        const axisOrder = Vec3.create(0, 1, 2);
+        return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume);
     }
 
     export const Params = {
         ...BaseGeometry.Params,
-        doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
-        flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        renderMode: createRenderModeParam(),
-        stepsPerCell: PD.Numeric(5, { min: 1, max: 20, step: 1 }),
+        controlPoints: PD.LineGraph([
+            Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
+            Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
+        ], { isEssential: true }),
+        stepsPerCell: PD.Numeric(3, { min: 1, max: 10, step: 1 }),
         jumpLength: PD.Numeric(0, { min: 0, max: 20, step: 0.1 }),
     };
     export type Params = typeof Params
@@ -217,13 +191,6 @@ export namespace DirectVolume {
         return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 
-    function getNormalizedIsoValue(out: Vec2, isoValue: Volume.IsoValue, stats: Vec4) {
-        const [min, max, mean, sigma] = stats;
-        const value = Volume.IsoValue.toAbsolute(isoValue, { min, max, mean, sigma }).absoluteValue;
-        Vec2.set(out, (value - min) / (max - min), (0 - min) / (max - min));
-        return out;
-    }
-
     function getMaxSteps(gridDim: Vec3, stepsPerCell: number) {
         return Math.ceil(Vec3.magnitude(gridDim) * stepsPerCell);
     }
@@ -256,18 +223,12 @@ export namespace DirectVolume {
         const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
-        const controlPoints = props.renderMode.name === 'volume' ? getControlPointsFromVec2Array(props.renderMode.params.controlPoints) : [];
-        const transferTex = createTransferFunctionTexture(controlPoints, props.renderMode.name === 'volume' ? props.renderMode.params.list.colors : []);
-
-        const isoValue = props.renderMode.name === 'isosurface'
-            ? props.renderMode.params.isoValue
-            : Volume.IsoValue.relative(2);
-
-        const singleLayer = props.renderMode.name === 'isosurface'
-            ? props.renderMode.params.singleLayer
-            : false;
+        const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
+        const transferTex = createTransferFunctionTexture(controlPoints);
 
         return {
+            dGeometryType: ValueCell.create('directVolume'),
+
             ...color,
             ...marker,
             ...overpaint,
@@ -283,7 +244,6 @@ export namespace DirectVolume {
             invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
             uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
 
-            uIsoValue: ValueCell.create(getNormalizedIsoValue(Vec2(), isoValue, directVolume.gridStats.ref.value)),
             uBboxMin: bboxMin,
             uBboxMax: bboxMax,
             uBboxSize: bboxSize,
@@ -292,7 +252,6 @@ export namespace DirectVolume {
             uJumpLength: ValueCell.create(props.jumpLength),
             uTransform: gridTransform,
             uGridDim: gridDimension,
-            dRenderMode: ValueCell.create(props.renderMode.name),
             tTransferTex: transferTex,
             uTransferScale: ValueCell.create(getTransferScale(props.stepsPerCell)),
 
@@ -305,11 +264,8 @@ export namespace DirectVolume {
             uCartnToUnit: directVolume.cartnToUnit,
             uUnitToCartn: directVolume.unitToCartn,
             dPackedGroup: directVolume.packedGroup,
-            dSingleLayer: ValueCell.create(singleLayer),
+            dAxisOrder: ValueCell.create(directVolume.axisOrder.ref.value.join('')),
 
-            uDoubleSided: ValueCell.create(props.doubleSided),
-            dFlatShaded: ValueCell.create(props.flatShaded),
-            dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
         };
@@ -323,20 +279,11 @@ export namespace DirectVolume {
 
     function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
         BaseGeometry.updateValues(values, props);
-        ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
-        ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
-        ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-        ValueCell.updateIfChanged(values.dRenderMode, props.renderMode.name);
-
-        if (props.renderMode.name === 'isosurface') {
-            ValueCell.updateIfChanged(values.uIsoValue, getNormalizedIsoValue(values.uIsoValue.ref.value, props.renderMode.params.isoValue, values.uGridStats.ref.value));
-            ValueCell.updateIfChanged(values.dSingleLayer, props.renderMode.params.singleLayer);
-        } else if (props.renderMode.name === 'volume') {
-            const controlPoints = getControlPointsFromVec2Array(props.renderMode.params.controlPoints);
-            createTransferFunctionTexture(controlPoints, props.renderMode.params.list.colors, values.tTransferTex);
-        }
+
+        const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
+        createTransferFunctionTexture(controlPoints, values.tTransferTex);
 
         ValueCell.updateIfChanged(values.uMaxSteps, getMaxSteps(values.uGridDim.ref.value, props.stepsPerCell));
         ValueCell.updateIfChanged(values.uStepScale, getStepScale(values.uCellDim.ref.value, props.stepsPerCell));
@@ -360,14 +307,14 @@ export namespace DirectVolume {
     function createRenderableState(props: PD.Values<Params>): RenderableState {
         const state = BaseGeometry.createRenderableState(props);
         state.opaque = false;
-        state.writeDepth = props.renderMode.name === 'isosurface';
+        state.writeDepth = false;
         return state;
     }
 
     function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
         BaseGeometry.updateRenderableState(state, props);
         state.opaque = false;
-        state.writeDepth = props.renderMode.name === 'isosurface';
+        state.writeDepth = false;
     }
 }
 

+ 4 - 9
src/mol-geo/geometry/direct-volume/transfer-function.ts

@@ -1,16 +1,13 @@
 /**
- * Copyright (c) 2018 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>
  */
 
 import { TextureImage } from '../../../mol-gl/renderable/util';
 import { spline } from '../../../mol-math/interpolate';
-import { ColorScale } from '../../../mol-util/color';
 import { ValueCell } from '../../../mol-util';
 import { Vec2 } from '../../../mol-math/linear-algebra';
-import { ColorListName } from '../../../mol-util/color/lists';
-import { ColorListEntry } from '../../../mol-util/color/color';
 
 export interface ControlPoint { x: number, alpha: number }
 
@@ -25,7 +22,7 @@ export function getControlPointsFromVec2Array(array: Vec2[]): ControlPoint[] {
     return array.map(v => ({ x: v[0], alpha: v[1] }));
 }
 
-export function createTransferFunctionTexture(controlPoints: ControlPoint[], listOrName: ColorListEntry[] | ColorListName, texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> {
+export function createTransferFunctionTexture(controlPoints: ControlPoint[], texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> {
     const cp = [
         { x: 0, alpha: 0 },
         { x: 0, alpha: 0 },
@@ -33,10 +30,9 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], lis
         { x: 1, alpha: 0 },
         { x: 1, alpha: 0 },
     ];
-    const scale = ColorScale.create({ domain: [0, 1], listOrName });
 
     const n = 256;
-    const array = texture ? texture.ref.value.array : new Uint8Array(n * 4);
+    const array = texture ? texture.ref.value.array : new Uint8Array(n);
 
     let k = 0;
     let x1: number, x2: number;
@@ -55,8 +51,7 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], lis
         const jl = Math.round((x2 - x1) * n);
         for (let j = 0; j < jl; ++j) {
             const t = j / jl;
-            array[k * 4 + 3] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255);
-            scale.colorToArray(k / 255, array, k * 4);
+            array[k] = Math.max(0, spline(a0, a1, a2, a3, t, 0.5) * 255);
             ++k;
         }
     }

+ 2 - 0
src/mol-geo/geometry/image/image.ts

@@ -155,6 +155,8 @@ namespace Image {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('image'),
+
             ...color,
             ...marker,
             ...overpaint,

+ 2 - 0
src/mol-geo/geometry/lines/lines.ts

@@ -220,6 +220,8 @@ export namespace Lines {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('lines'),
+
             aMapping: lines.mappingBuffer,
             aGroup: lines.groupBuffer,
             aStart: lines.startBuffer,

+ 13 - 6
src/mol-geo/geometry/marker-data.ts

@@ -47,12 +47,19 @@ export function getMarkersAverage(array: Uint8Array, count: number): number {
     const backStart = 4 * viewEnd;
 
     let sum = 0;
-    for (let i = 0; i < viewEnd; ++i) {
-        const v = view[i];
-        sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
-    }
-    for (let i = backStart; i < count; ++i) {
-        sum += array[i] && 1;
+    if (viewEnd < 0) {
+        // avoid edge cases with small arrays
+        for (let i = 0; i < count; ++i) {
+            sum += array[i] && 1;
+        }
+    } else {
+        for (let i = 0; i < viewEnd; ++i) {
+            const v = view[i];
+            sum += MarkerCountLut[v & 0xFFFF] + MarkerCountLut[v >> 16];
+        }
+        for (let i = backStart; i < count; ++i) {
+            sum += array[i] && 1;
+        }
     }
     return sum / count;
 }

+ 2 - 0
src/mol-geo/geometry/mesh/mesh.ts

@@ -677,6 +677,8 @@ export namespace Mesh {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('mesh'),
+
             aPosition: mesh.vertexBuffer,
             aNormal: mesh.normalBuffer,
             aGroup: mesh.groupBuffer,

+ 2 - 0
src/mol-geo/geometry/points/points.ts

@@ -182,6 +182,8 @@ export namespace Points {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('points'),
+
             aPosition: points.centerBuffer,
             aGroup: points.groupBuffer,
             boundingSphere: ValueCell.create(boundingSphere),

+ 5 - 5
src/mol-geo/geometry/size-data.ts

@@ -11,7 +11,7 @@ import { LocationIterator } from '../util/location-iterator';
 import { Location, NullLocation } from '../../mol-model/location';
 import { SizeTheme } from '../../mol-theme/size';
 import { Geometry } from './geometry';
-import { decodeFloatRGB, encodeFloatRGBtoArray } from '../../mol-util/float-packing';
+import { unpackRGBToInt, packIntToRGBArray } from '../../mol-util/number-packing';
 
 export type SizeType = 'uniform' | 'instance' | 'group' | 'groupInstance'
 
@@ -44,7 +44,7 @@ export function getMaxSize(sizeData: SizeData): number {
             let maxSize = 0;
             const array = sizeData.tSize.ref.value.array;
             for (let i = 0, il = array.length; i < il; i += 3) {
-                const value = decodeFloatRGB(array[i], array[i + 1], array[i + 2]);
+                const value = unpackRGBToInt(array[i], array[i + 1], array[i + 2]);
                 if (maxSize < value) maxSize = value;
             }
             return maxSize / sizeDataFactor;
@@ -103,7 +103,7 @@ export function createInstanceSize(locationIt: LocationIterator, sizeFn: Locatio
     locationIt.reset();
     while (locationIt.hasNext && !locationIt.isNextNewInstance) {
         const v = locationIt.move();
-        encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.instanceIndex * 3);
+        packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.instanceIndex * 3);
         locationIt.skipInstance();
     }
     return createTextureSize(sizes, 'instance', sizeData);
@@ -116,7 +116,7 @@ export function createGroupSize(locationIt: LocationIterator, sizeFn: LocationSi
     locationIt.reset();
     while (locationIt.hasNext && !locationIt.isNextNewInstance) {
         const v = locationIt.move();
-        encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.groupIndex * 3);
+        packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.groupIndex * 3);
     }
     return createTextureSize(sizes, 'group', sizeData);
 }
@@ -129,7 +129,7 @@ export function createGroupInstanceSize(locationIt: LocationIterator, sizeFn: Lo
     locationIt.reset();
     while (locationIt.hasNext) {
         const v = locationIt.move();
-        encodeFloatRGBtoArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.index * 3);
+        packIntToRGBArray(sizeFn(v.location) * sizeDataFactor, sizes.array, v.index * 3);
     }
     return createTextureSize(sizes, 'groupInstance', sizeData);
 }

+ 2 - 0
src/mol-geo/geometry/spheres/spheres.ts

@@ -183,6 +183,8 @@ export namespace Spheres {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('spheres'),
+
             aPosition: spheres.centerBuffer,
             aMapping: spheres.mappingBuffer,
             aGroup: spheres.groupBuffer,

+ 2 - 0
src/mol-geo/geometry/text/text.ts

@@ -224,6 +224,8 @@ export namespace Text {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('text'),
+
             aPosition: text.centerBuffer,
             aMapping: text.mappingBuffer,
             aDepth: text.depthBuffer,

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

@@ -147,6 +147,8 @@ export namespace TextureMesh {
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
         return {
+            dGeometryType: ValueCell.create('textureMesh'),
+
             uGeoTexDim: textureMesh.geoTextureDim,
             tPosition: textureMesh.vertexTexture,
             tGroup: textureMesh.groupTexture,
@@ -172,7 +174,6 @@ export namespace TextureMesh {
             dXrayShaded: ValueCell.create(props.xrayShaded),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
-            dGeoTexture: ValueCell.create(true),
 
             meta: ValueCell.create(textureMesh.meta),
         };

+ 2 - 2
src/mol-gl/compute/histogram-pyramid/sum.ts

@@ -11,7 +11,7 @@ import { Values, TextureSpec } from '../../renderable/schema';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
-import { decodeFloatRGB } from '../../../mol-util/float-packing';
+import { unpackRGBToInt } from '../../../mol-util/number-packing';
 import { QuadSchema, QuadValues } from '../util';
 import { quad_vert } from '../../../mol-gl/shader/quad.vert';
 import { sum_frag } from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
@@ -96,5 +96,5 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
 
     return isWebGL2(gl)
         ? sumInts[0]
-        : decodeFloatRGB(sumBytes[0], sumBytes[1], sumBytes[2]);
+        : unpackRGBToInt(sumBytes[0], sumBytes[1], sumBytes[2]);
 }

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

@@ -39,13 +39,14 @@ const IsosurfaceSchema = {
     uGridTransform: UniformSpec('m4'),
     uScale: UniformSpec('v2'),
 
-    dPackedGroup: DefineSpec('boolean')
+    dPackedGroup: DefineSpec('boolean'),
+    dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
 };
 type IsosurfaceValues = Values<typeof IsosurfaceSchema>
 
 const IsosurfaceName = 'isosurface';
 
-function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
+function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3): ComputeRenderable<IsosurfaceValues> {
     if (ctx.namedComputeRenderables[IsosurfaceName]) {
         const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
 
@@ -65,15 +66,16 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
         ValueCell.update(v.uScale, scale);
 
         ValueCell.update(v.dPackedGroup, packedGroup);
+        ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join(''));
 
         ctx.namedComputeRenderables[IsosurfaceName].update();
     } else {
-        ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
+        ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
     }
     return ctx.namedComputeRenderables[IsosurfaceName];
 }
 
-function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean) {
+function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3) {
     // console.log('uSize', Math.pow(2, levels))
     const values: IsosurfaceValues = {
         ...QuadValues,
@@ -94,7 +96,8 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
         uGridTransform: ValueCell.create(transform),
         uScale: ValueCell.create(scale),
 
-        dPackedGroup: ValueCell.create(packedGroup)
+        dPackedGroup: ValueCell.create(packedGroup),
+        dAxisOrder: ValueCell.create(axisOrder.join('')),
     };
 
     const schema = { ...IsosurfaceSchema };
@@ -115,7 +118,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
     state.clearColor(0, 0, 0, 0);
 }
 
-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) {
+export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
     const { drawBuffers } = ctx.extensions;
     if (!drawBuffers) throw new Error('need WebGL draw buffers');
 
@@ -173,7 +176,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     groupTexture.attachFramebuffer(framebuffer, 1);
     normalTexture.attachFramebuffer(framebuffer, 2);
 
-    const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
+    const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder);
     ctx.state.currentRenderItemId = -1;
 
     framebuffer.bind();
@@ -204,7 +207,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
  *
  * Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
  */
-export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
+export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
     // console.time('calcActiveVoxels');
     const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
     // ctx.waitForGpuCommandsCompleteSync();
@@ -216,7 +219,7 @@ export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDi
     // console.timeEnd('createHistogramPyramid');
 
     // console.time('createIsosurfaceBuffers');
-    const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, vertexTexture, groupTexture, normalTexture);
+    const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, vertexTexture, groupTexture, normalTexture);
     // ctx.waitForGpuCommandsCompleteSync();
     // console.timeEnd('createIsosurfaceBuffers');
 

+ 2 - 12
src/mol-gl/renderable/direct-volume.ts

@@ -17,12 +17,6 @@ export const DirectVolumeSchema = {
     aPosition: AttributeSpec('float32', 3, 0),
     elements: ElementsSpec('uint32'),
 
-    uColor: UniformSpec('v3'),
-    uColorTexDim: UniformSpec('v2'),
-    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
-    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
-
-    uIsoValue: UniformSpec('v2'),
     uBboxMin: UniformSpec('v3'),
     uBboxMax: UniformSpec('v3'),
     uBboxSize: UniformSpec('v3'),
@@ -31,9 +25,7 @@ export const DirectVolumeSchema = {
     uJumpLength: UniformSpec('f'),
     uTransform: UniformSpec('m4'),
     uGridDim: UniformSpec('v3'),
-    dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
-    dSingleLayer: DefineSpec('boolean'),
-    tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'),
+    tTransferTex: TextureSpec('image-uint8', 'alpha', 'ubyte', 'linear'),
     uTransferScale: UniformSpec('f'),
 
     dGridTexType: DefineSpec('string', ['2d', '3d']),
@@ -45,10 +37,8 @@ export const DirectVolumeSchema = {
     uCartnToUnit: UniformSpec('m4'),
     uUnitToCartn: UniformSpec('m4'),
     dPackedGroup: DefineSpec('boolean'),
+    dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
 
-    uDoubleSided: UniformSpec('b'),
-    dFlipSided: DefineSpec('boolean'),
-    dFlatShaded: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
 };

+ 3 - 1
src/mol-gl/renderable/schema.ts

@@ -182,7 +182,7 @@ export const ColorSchema = {
     tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
     tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
     tColorGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
-    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance', 'volume', 'volumeInstance']),
+    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance', 'volume', 'volumeInstance', 'direct']),
     dUsePalette: DefineSpec('boolean'),
 } as const;
 export type ColorSchema = typeof ColorSchema
@@ -258,6 +258,8 @@ export type ClippingSchema = typeof ClippingSchema
 export type ClippingValues = Values<ClippingSchema>
 
 export const BaseSchema = {
+    dGeometryType: DefineSpec('string', ['cylinders', 'directVolume', 'image', 'lines', 'mesh', 'points', 'spheres', 'text', 'textureMesh']),
+
     ...ColorSchema,
     ...MarkerSchema,
     ...OverpaintSchema,

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

@@ -23,7 +23,6 @@ export const TextureMeshSchema = {
     dFlipSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
-    dGeoTexture: DefineSpec('boolean'),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
     meta: ValueSpec('unknown')

+ 10 - 16
src/mol-gl/renderer.ts

@@ -89,8 +89,8 @@ export const RendererParams = {
 
     highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
     selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
-    highlightStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
-    selectStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }),
+    highlightStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
+    selectStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }),
     markerPriority: PD.Select(1, [[1, 'Highlight'], [2, 'Select']]),
 
     xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
@@ -136,7 +136,7 @@ function getLight(props: RendererProps['light'], light?: Light): Light {
 
 namespace Renderer {
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
-        const { gl, state, stats, extensions: { fragDepth } } = ctx;
+        const { gl, state, stats } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
         const light = getLight(p.light);
 
@@ -245,9 +245,9 @@ namespace Renderer {
                 globalUniformsNeedUpdate = false;
             }
 
-            if (r.values.dRenderMode) { // indicates direct-volume
-                if ((variant === 'pick' || variant === 'depth') && r.values.dRenderMode.ref.value === 'volume') {
-                    return; // no picking/depth in volume mode
+            if (r.values.dGeometryType.ref.value === 'directVolume') {
+                if (variant !== 'colorWboit' && variant !== 'colorBlended') {
+                    return; // only color supported
                 }
 
                 // culling done in fragment shader
@@ -256,14 +256,8 @@ namespace Renderer {
 
                 if (variant === 'colorBlended') {
                     // depth test done manually in shader against `depthTexture`
-                    // still need to enable when fragDepth can be used to write depth
-                    if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) {
-                        state.disable(gl.DEPTH_TEST);
-                        state.depthMask(false);
-                    } else {
-                        state.enable(gl.DEPTH_TEST);
-                        state.depthMask(r.values.uAlpha.ref.value === 1.0);
-                    }
+                    state.disable(gl.DEPTH_TEST);
+                    state.depthMask(false);
                 }
             } else {
                 if (r.values.uDoubleSided) {
@@ -506,7 +500,7 @@ namespace Renderer {
                 // TODO: simplify, handle in renderable.state???
                 // uAlpha is updated in "render" so we need to recompute it here
                 const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
-                if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
+                if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
                     renderObject(r, 'colorWboit');
                 }
             }
@@ -522,7 +516,7 @@ namespace Renderer {
                 // TODO: simplify, handle in renderable.state???
                 // uAlpha is updated in "render" so we need to recompute it here
                 const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
-                if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
+                if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
                     renderObject(r, 'colorWboit');
                 }
             }

+ 1 - 1
src/mol-gl/shader-code.ts

@@ -198,7 +198,7 @@ export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { drawBuf
 
 import { directVolume_vert } from './shader/direct-volume.vert';
 import { directVolume_frag } from './shader/direct-volume.frag';
-export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_vert, directVolume_frag, { fragDepth: 'optional', drawBuffers: 'optional' });
+export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_vert, directVolume_frag, { fragDepth: 'optional', drawBuffers: 'optional' }, {}, ignoreDefine);
 
 import { image_vert } from './shader/image.vert';
 import { image_frag } from './shader/image.frag';

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

@@ -57,11 +57,11 @@ export const assign_color_varying = `
     #endif
 #elif defined(dRenderVariant_pick)
     if (uPickType == 1) {
-        vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
+        vColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
     } else if (uPickType == 2) {
-        vColor = vec4(encodeFloatRGB(aInstance), 1.0);
+        vColor = vec4(packIntToRGB(aInstance), 1.0);
     } else {
-        vColor = vec4(encodeFloatRGB(group), 1.0);
+        vColor = vec4(packIntToRGB(group), 1.0);
     }
 #endif
 

+ 2 - 2
src/mol-gl/shader/chunks/assign-group.glsl.ts

@@ -1,6 +1,6 @@
 export const assign_group = `
-#ifdef dGeoTexture
-    float group = decodeFloatRGB(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
+#ifdef dGeometryType_textureMesh
+    float group = unpackRGBToInt(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
 #else
     float group = aGroup;
 #endif

+ 1 - 1
src/mol-gl/shader/chunks/assign-position.glsl.ts

@@ -1,7 +1,7 @@
 export const assign_position = `
 mat4 model = uModel * aTransform;
 mat4 modelView = uView * model;
-#ifdef dGeoTexture
+#ifdef dGeometryType_textureMesh
     vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
 #else
     vec3 position = aPosition;

+ 3 - 3
src/mol-gl/shader/chunks/assign-size.glsl.ts

@@ -4,11 +4,11 @@ export const assign_size = `
 #elif defined(dSizeType_attribute)
     float size = aSize;
 #elif defined(dSizeType_instance)
-    float size = decodeFloatRGB(readFromTexture(tSize, aInstance, uSizeTexDim).rgb);
+    float size = unpackRGBToInt(readFromTexture(tSize, aInstance, uSizeTexDim).rgb);
 #elif defined(dSizeType_group)
-    float size = decodeFloatRGB(readFromTexture(tSize, group, uSizeTexDim).rgb);
+    float size = unpackRGBToInt(readFromTexture(tSize, group, uSizeTexDim).rgb);
 #elif defined(dSizeType_groupInstance)
-    float size = decodeFloatRGB(readFromTexture(tSize, aInstance * float(uGroupCount) + group, uSizeTexDim).rgb);
+    float size = unpackRGBToInt(readFromTexture(tSize, aInstance * float(uGroupCount) + group, uSizeTexDim).rgb);
 #endif
 
 #if defined(dSizeType_instance) || defined(dSizeType_group) || defined(dSizeType_groupInstance)

+ 8 - 9
src/mol-gl/shader/chunks/common.glsl.ts

@@ -25,6 +25,10 @@ export const common = `
 
 #define saturate(a) clamp(a, 0.0, 1.0)
 
+#if __VERSION__ == 100
+    #define round(x) floor((x) + 0.5)
+#endif
+
 float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
 vec2 ivec2Div(const in vec2 a, const in vec2 b) { return vec2(ivec2(a) / ivec2(b)); }
 float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
@@ -32,13 +36,8 @@ int imod(const in int a, const in int b) { return a - b * (a / b); }
 
 float pow2(const in float x) { return x * x; }
 
-const float maxFloat = 10000.0; // NOTE constant also set in TypeScript
-const float floatLogFactor = 9.210440366976517; // log(maxFloat + 1.0);
-float encodeFloatLog(const in float value) { return log(value + 1.0) / floatLogFactor; }
-float decodeFloatLog(const in float value) { return exp(value * floatLogFactor) - 1.0; }
-
-vec3 encodeFloatRGB(in float value) {
-    value = clamp(value, 0.0, 16777216.0 - 1.0) + 1.0;
+vec3 packIntToRGB(in float value) {
+    value = clamp(round(value), 0.0, 16777216.0 - 1.0) + 1.0;
     vec3 c = vec3(0.0);
     c.b = mod(value, 256.0);
     value = floor(value / 256.0);
@@ -47,8 +46,8 @@ vec3 encodeFloatRGB(in float value) {
     c.r = mod(value, 256.0);
     return c / 255.0;
 }
-float decodeFloatRGB(const in vec3 rgb) {
-    return (rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0) - 1.0;
+float unpackRGBToInt(const in vec3 rgb) {
+    return (floor(rgb.r * 255.0 + 0.5) * 256.0 * 256.0 + floor(rgb.g * 255.0 + 0.5) * 256.0 + floor(rgb.b * 255.0 + 0.5)) - 1.0;
 }
 
 vec2 packUnitIntervalToRG(const in float v) {

+ 1 - 1
src/mol-gl/shader/chunks/wboit-write.glsl.ts

@@ -18,7 +18,7 @@ export const wboit_write = `
             float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0);
             gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);
             // extra alpha is to handle pre-multiplied alpha
-            #if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface)
+            #ifndef dGeometryType_directVolume
                 gl_FragData[1] = vec4((uTransparentBackground ? alpha : 1.0) * alpha * wboitWeight);
             #else
                 gl_FragData[1] = vec4(alpha * alpha * wboitWeight);

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

@@ -35,7 +35,7 @@ uniform float uResolution;
 
 void main() {
     vec3 position = readFromTexture(tPosition, SampleID, uGeoTexDim).xyz;
-    float group = decodeFloatRGB(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
+    float group = unpackRGBToInt(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
 
     position = (aTransform * vec4(position, 1.0)).xyz;
     gl_PointSize = 7.0;

+ 112 - 275
src/mol-gl/shader/direct-volume.frag.ts

@@ -38,7 +38,6 @@ varying vec4 vBoundingSphere;
 varying mat4 vTransform;
 
 uniform mat4 uInvView;
-uniform vec2 uIsoValue;
 uniform vec3 uGridDim;
 uniform vec3 uBboxSize;
 uniform sampler2D tTransferTex;
@@ -76,11 +75,9 @@ uniform float uXrayEdgeFalloff;
 uniform float uInteriorDarkening;
 uniform bool uInteriorColorFlag;
 uniform vec3 uInteriorColor;
-bool interior;
 
 uniform bool uRenderWboit;
 uniform bool uDoubleSided;
-uniform int uPickType;
 
 uniform float uNear;
 uniform float uFar;
@@ -104,27 +101,22 @@ uniform mat4 uCartnToUnit;
     uniform sampler3D tGridTex;
 #endif
 
-#if defined(dRenderVariant_color)
-    #if defined(dColorType_uniform)
-        uniform vec3 uColor;
-    #elif defined(dColorType_texture)
-        uniform vec2 uColorTexDim;
-        uniform sampler2D tColor;
-    #endif
+#if defined(dColorType_uniform)
+    uniform vec3 uColor;
+#elif defined(dColorType_texture)
+    uniform vec2 uColorTexDim;
+    uniform sampler2D tColor;
+#endif
 
-    #ifdef dOverpaint
-        #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
-            uniform vec2 uOverpaintTexDim;
-            uniform sampler2D tOverpaint;
-        #endif
+#ifdef dOverpaint
+    #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
+        uniform vec2 uOverpaintTexDim;
+        uniform sampler2D tOverpaint;
     #endif
+#endif
 
-    #ifdef dSubstance
-        #if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
-            uniform vec2 uSubstanceTexDim;
-            uniform sampler2D tSubstance;
-        #endif
-    #endif
+#ifdef dUsePalette
+    uniform sampler2D tPalette;
 #endif
 
 #if defined(dGridTexType_2d)
@@ -148,8 +140,8 @@ float calcDepth(const in vec3 pos) {
     return 0.5 + 0.5 * clipZW.x / clipZW.y;
 }
 
-vec4 transferFunction(float value) {
-    return texture2D(tTransferTex, vec2(value, 0.0));
+float transferFunction(float value) {
+    return texture2D(tTransferTex, vec2(value, 0.0)).a;
 }
 
 float getDepth(const in vec2 coords) {
@@ -174,7 +166,7 @@ vec3 v3m4(vec3 p, mat4 m) {
 float preFogAlphaBlended = 0.0;
 
 vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
-    #if defined(dRenderVariant_color) && !defined(dIgnoreLight)
+    #if !defined(dIgnoreLight)
         mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
     #endif
     mat4 cartnToUnit = uCartnToUnit * inverse4(vTransform);
@@ -190,21 +182,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
     float value = 0.0;
     vec4 src = vec4(0.0);
     vec4 dst = vec4(0.0);
-    bool hit = false;
     float fragmentDepth;
 
     vec3 posMin = vec3(0.0);
     vec3 posMax = vec3(1.0) - vec3(1.0) / uGridDim;
 
     vec3 unitPos;
-    vec3 isoPos;
 
     vec3 nextPos;
     float nextValue;
 
-    vec3 color = vec3(0.45, 0.55, 0.8);
-    vec4 overpaint = vec4(0.0);
-    vec3 substance = vec3(0.0);
+    vec4 material;
+    vec4 overpaint;
     float metalness = uMetalness;
     float roughness = uRoughness;
 
@@ -227,7 +216,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
         if (unitPos.x > posMax.x || unitPos.y > posMax.y || unitPos.z > posMax.z ||
             unitPos.x < posMin.x || unitPos.y < posMin.y || unitPos.z < posMin.z
         ) {
-            if (hit) break;
             prevValue = value;
             pos += step;
             continue;
@@ -246,226 +234,108 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
             }
         }
 
-        #if defined(dRenderMode_isosurface)
-            if (prevValue > 0.0 && ( // there was a prev Value
-                (prevValue < uIsoValue.x && value > uIsoValue.x) || // entering isosurface
-                (prevValue > uIsoValue.x && value < uIsoValue.x) // leaving isosurface
-            )) {
-                isoPos = v3m4(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)))), cartnToUnit);
-
-                vec4 mvPosition = modelViewTransform * vec4(isoPos * uGridDim, 1.0);
-
-                #if defined(dClipVariant_pixel) && dClipObjectCount != 0
-                    vec3 vModelPosition = v3m4(isoPos * uGridDim, modelTransform);
-                    if (clipTest(vec4(vModelPosition, 0.0), 0)) {
-                        prevValue = value;
-                        pos += step;
-                        continue;
-                    }
-                #endif
+        vec4 mvPosition = modelViewTransform * vec4(unitPos * uGridDim, 1.0);
+        if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
+            break;
 
-                float depth = calcDepth(mvPosition.xyz);
-                if (depth > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
-                    break;
+        #if defined(dClipVariant_pixel) && dClipObjectCount != 0
+            vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
+            if (clipTest(vec4(vModelPosition, 0.0), 0)) {
+                prevValue = value;
+                pos += step;
+                continue;
+            }
+        #endif
 
-                #ifdef enabledFragDepth
-                    if (!hit) {
-                        gl_FragDepthEXT = depth;
-                    }
-                #endif
+        vec3 vViewPosition = mvPosition.xyz;
+        material.a = transferFunction(value);
 
-                #if defined(dRenderVariant_pick)
-                    if (uPickType == 1) {
-                        return vec4(encodeFloatRGB(float(uObjectId)), 1.0);
-                    } else if (uPickType == 2) {
-                        return vec4(encodeFloatRGB(vInstance), 1.0);
-                    } else {
-                        #ifdef dPackedGroup
-                            return vec4(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb, 1.0);
-                        #else
-                            vec3 g = floor(isoPos * uGridDim + 0.5);
-                            return vec4(encodeFloatRGB(g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y), 1.0);
-                        #endif
-                    }
-                #elif defined(dRenderVariant_depth)
-                    #ifdef enabledFragDepth
-                        return packDepthToRGBA(gl_FragDepthEXT);
-                    #else
-                        return packDepthToRGBA(depth);
-                    #endif
-                #elif defined(dRenderVariant_color)
-                    #ifdef dPackedGroup
-                        float group = decodeFloatRGB(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb);
-                    #else
-                        vec3 g = floor(isoPos * uGridDim + 0.5);
-                        float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
-                    #endif
-
-                    #if defined(dColorType_uniform)
-                        color = uColor;
-                    #elif defined(dColorType_instance)
-                        color = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
-                    #elif defined(dColorType_group)
-                        color = readFromTexture(tColor, group, uColorTexDim).rgb;
-                    #elif defined(dColorType_groupInstance)
-                        color = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
-                    #elif defined(dColorType_vertex)
-                        color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
-                    #elif defined(dColorType_vertexInstance)
-                        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));
-                        #endif
-
-                        color = mix(color, overpaint.rgb, overpaint.a);
-                    #endif
-
-                    // handle flipping and negative isosurfaces
-                    #ifdef dFlipSided
-                        bool flipped = value < uIsoValue.y; // flipped
-                    #else
-                        bool flipped = value > uIsoValue.y;
-                    #endif
-                    interior = value < uIsoValue.x && flipped;
-                    if (uDoubleSided) {
-                        if (interior) {
-                            prevValue = value;
-                            pos += step;
-                            continue;
-                        }
-                    }
-                    vec3 vViewPosition = mvPosition.xyz;
-                    vec4 material = vec4(color, uAlpha);
-
-                    #ifdef dIgnoreLight
-                        gl_FragColor = material;
-                    #else
-                        #if defined(dFlatShaded)
-                            // nearest grid point
-                            isoPos = floor(isoPos * uGridDim + 0.5) / uGridDim;
-                        #endif
-                        #ifdef dPackedGroup
-                            // compute gradient by central differences
-                            gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
-                            gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
-                            gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a;
-                        #else
-                            gradient = textureVal(isoPos).xyz * 2.0 - 1.0;
-                        #endif
-                        vec3 normal = -normalize(normalMatrix * normalize(gradient));
-                        normal = normal * (float(flipped) * 2.0 - 1.0);
-                        normal = normal * -(float(interior) * 2.0 - 1.0);
-                        #ifdef dSubstance
-                            #if defined(dSubstanceType_groupInstance)
-                                substance = readFromTexture(tSubstance, vInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
-                            #elif defined(dSubstanceType_vertexInstance)
-                                substance = texture3dFrom1dTrilinear(tSubstance, isoPos, uGridDim, uSubstanceTexDim, vInstance * float(uVertexCount)).rgb;
-                            #endif
-                            metalness = mix(metalness, substance.r, substance.b);
-                            roughness = mix(roughness, substance.g, substance.b);
-                        #endif
-                        #include apply_light_color
-                    #endif
-
-                    float marker = uMarker;
-                    if (uMarker == -1.0) {
-                        marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
-                        marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
-                    }
-                    #include apply_interior_color
-                    #include apply_marker_color
-
-                    preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
-                    fragmentDepth = depth;
-                    #include apply_fog
-
-                    src = gl_FragColor;
-
-                    if (!uTransparentBackground) {
-                        // done in 'apply_fog' otherwise
-                        src.rgb *= src.a;
-                    }
-                    dst = (1.0 - dst.a) * src + dst; // standard blending
-                #endif
+        #ifdef dPackedGroup
+            float group = unpackRGBToInt(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
+        #else
+            vec3 g = floor(unitPos * uGridDim + 0.5);
+            // note that we swap x and z because the texture is flipped around y
+            #if defined(dAxisOrder_012)
+                float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y; // 210
+            #elif defined(dAxisOrder_021)
+                float group = g.y + g.z * uGridDim.y + g.x * uGridDim.y * uGridDim.z; // 120
+            #elif defined(dAxisOrder_102)
+                float group = g.z + g.x * uGridDim.z + g.y * uGridDim.z * uGridDim.x; // 201
+            #elif defined(dAxisOrder_120)
+                float group = g.x + g.z * uGridDim.x + g.y * uGridDim.x * uGridDim.z; // 021
+            #elif defined(dAxisOrder_201)
+                float group = g.y + g.x * uGridDim.y + g.z * uGridDim.y * uGridDim.x; // 102
+            #elif defined(dAxisOrder_210)
+                float group = g.x + g.y * uGridDim.x + g.z * uGridDim.x * uGridDim.y; // 012
+            #endif
+        #endif
 
-                #ifdef dSingleLayer
-                    break;
-                #endif
+        #if defined(dColorType_direct) && defined(dUsePalette)
+            material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
+        #elif defined(dColorType_uniform)
+            material.rgb = uColor;
+        #elif defined(dColorType_instance)
+            material.rgb = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
+        #elif defined(dColorType_group)
+            material.rgb = readFromTexture(tColor, group, uColorTexDim).rgb;
+        #elif defined(dColorType_groupInstance)
+            material.rgb = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
+        #elif defined(dColorType_vertex)
+            material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
+        #elif defined(dColorType_vertexInstance)
+            material.rgb = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
+        #endif
 
-                hit = true;
-            }
-            prevValue = value;
-        #elif defined(dRenderMode_volume)
-            vec4 mvPosition = modelViewTransform * vec4(unitPos * uGridDim, 1.0);
-            if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
-                break;
-
-            #if defined(dClipVariant_pixel) && dClipObjectCount != 0
-                vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
-                if (clipTest(vec4(vModelPosition, 0.0), 0)) {
-                    prevValue = value;
-                    pos += step;
-                    continue;
-                }
+        #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));
             #endif
 
-            #if defined(dRenderVariant_color)
-                vec3 vViewPosition = mvPosition.xyz;
-                vec4 material = transferFunction(value);
+            material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
+        #endif
 
-                #ifdef dIgnoreLight
-                    gl_FragColor.rgb = material.rgb;
+        #ifdef dIgnoreLight
+            gl_FragColor.rgb = material.rgb;
+        #else
+            if (material.a >= 0.01) {
+                #ifdef dPackedGroup
+                    // compute gradient by central differences
+                    gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
+                    gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
+                    gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
                 #else
-                    if (material.a >= 0.01) {
-                        #ifdef dPackedGroup
-                            // compute gradient by central differences
-                            gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
-                            gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
-                            gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
-                        #else
-                            gradient = cell.xyz * 2.0 - 1.0;
-                        #endif
-                        vec3 normal = -normalize(normalMatrix * normalize(gradient));
-                        #include apply_light_color
-                    } else {
-                        gl_FragColor.rgb = material.rgb;
-                    }
+                    gradient = cell.xyz * 2.0 - 1.0;
                 #endif
-
-                gl_FragColor.a = material.a * uAlpha * uTransferScale;
-
-                float marker = uMarker;
-                if (uMarker == -1.0) {
-                    #ifdef dPackedGroup
-                        float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
-                    #else
-                        vec3 g = floor(unitPos * uGridDim + 0.5);
-                        float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
-                    #endif
-                    marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
-                    marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
-                }
-                #include apply_marker_color
-
-                preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
-                fragmentDepth = calcDepth(mvPosition.xyz);
-                #include apply_fog
-
-                src = gl_FragColor;
-
-                if (!uTransparentBackground) {
-                    // done in 'apply_fog' otherwise
-                    src.rgb *= src.a;
-                }
-                dst = (1.0 - dst.a) * src + dst; // standard blending
-            #endif
+                vec3 normal = -normalize(normalMatrix * normalize(gradient));
+                #include apply_light_color
+            } else {
+                gl_FragColor.rgb = material.rgb;
+            }
         #endif
 
+        gl_FragColor.a = material.a * uAlpha * uTransferScale;
+
+        float marker = uMarker;
+        if (uMarker == -1.0) {
+            marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+            marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
+        }
+        #include apply_marker_color
+
+        preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;
+        fragmentDepth = calcDepth(mvPosition.xyz);
+        #include apply_fog
+
+        src = gl_FragColor;
+
+        if (!uTransparentBackground) {
+            // done in 'apply_fog' otherwise
+            src.rgb *= src.a;
+        }
+        dst = (1.0 - dst.a) * src + dst; // standard blending
+
         // break if the color is opaque enough
         if (dst.a > 0.95)
             break;
@@ -473,12 +343,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
         pos += step;
     }
 
-    #if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
-        // ensure depth is written everywhere
-        if (!hit)
-            gl_FragDepthEXT = 1.0;
-    #endif
-
     return dst;
 }
 
@@ -489,21 +353,6 @@ void main() {
     if (gl_FrontFacing)
         discard;
 
-    #ifdef dRenderVariant_marking
-        // not supported
-        discard;
-    #endif
-
-    #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
-        #if defined(dRenderMode_volume)
-            // always ignore pick & depth for volume
-            discard;
-        #elif defined(dRenderMode_isosurface)
-            if (uAlpha < uPickingAlphaThreshold)
-                discard; // ignore so the element below can be picked
-        #endif
-    #endif
-
     vec3 rayDir = mix(normalize(vOrigPos - uCameraPosition), uCameraDir, uIsOrtho);
     vec3 step = rayDir * uStepScale;
 
@@ -512,21 +361,9 @@ void main() {
     vec3 start = mix(uCameraPosition, vOrigPos, uIsOrtho) + (d * rayDir);
     gl_FragColor = raymarch(start, step, rayDir);
 
-    #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
-        // discard when nothing was hit
-        if (gl_FragColor == vec4(0.0))
-            discard;
-    #endif
-
-    #if defined(dRenderVariant_color)
-        #if defined(dRenderMode_isosurface) && defined(enabledFragDepth)
-            float fragmentDepth = gl_FragDepthEXT;
-        #else
-            float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
-        #endif
-        float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
-        interior = false;
-        #include wboit_write
-    #endif
+    float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
+    float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
+    bool interior = false;
+    #include wboit_write
 }
 `;

+ 1 - 1
src/mol-gl/shader/gaussian-density.frag.ts

@@ -51,7 +51,7 @@ void main() {
         #endif
         if (dist * uRadiusFactorInv > minDistance + uResolution * 0.05)
             discard;
-        gl_FragColor.rgb = encodeFloatRGB(vGroup);
+        gl_FragColor.rgb = packIntToRGB(vGroup);
     #endif
 }
 `;

+ 5 - 5
src/mol-gl/shader/histogram-pyramid/reduction.frag.ts

@@ -33,12 +33,12 @@ void main(void) {
             c = texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0;
             d = texture2D(tInputLevel, position + vec2(k, k)).r * 255.0;
         } else {
-            a = decodeFloatRGB(texture2D(tPreviousLevel, position).rgb);
-            b = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
-            c = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
-            d = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
+            a = unpackRGBToInt(texture2D(tPreviousLevel, position).rgb);
+            b = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
+            c = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
+            d = unpackRGBToInt(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
         }
-        gl_FragColor = vec4(encodeFloatRGB(a + b + c + d), 1.0);
+        gl_FragColor = vec4(packIntToRGB(a + b + c + d), 1.0);
     #else
         int a, b, c, d;
 

+ 4 - 4
src/mol-gl/shader/image.frag.ts

@@ -105,9 +105,9 @@ void main() {
         if (imageData.a < 0.3)
             discard;
         if (uPickType == 1) {
-            gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
+            gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
         } else if (uPickType == 2) {
-            gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0);
+            gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
         } else {
             gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
         }
@@ -118,7 +118,7 @@ void main() {
     #elif defined(dRenderVariant_marking)
         float marker = uMarker;
         if (uMarker == -1.0) {
-            float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
+            float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
             marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
             marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
         }
@@ -144,7 +144,7 @@ void main() {
 
         float marker = uMarker;
         if (uMarker == -1.0) {
-            float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
+            float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
             marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
             marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
         }

+ 22 - 8
src/mol-gl/shader/marching-cubes/isosurface.frag.ts

@@ -74,7 +74,7 @@ int idot4(const in ivec4 a, const in ivec4 b) {
 
 #if __VERSION__ == 100
     int pyramidVoxel(vec2 pos) {
-        return int(decodeFloatRGB(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
+        return int(unpackRGBToInt(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
     }
 #else
     int pyramidVoxel(vec2 pos) {
@@ -86,6 +86,25 @@ vec4 baseVoxel(vec2 pos) {
     return texture2D(tActiveVoxelsBase, pos / uSize);
 }
 
+vec4 getGroup(const in vec3 p) {
+    vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
+    // note that we swap x and z because the texture is flipped around y
+    #if defined(dAxisOrder_012)
+        float group = p.z + p.y * gridDim.z + p.x * gridDim.z * gridDim.y; // 210
+    #elif defined(dAxisOrder_021)
+        float group = p.y + p.z * gridDim.y + p.x * gridDim.y * gridDim.z; // 120
+    #elif defined(dAxisOrder_102)
+        float group = p.z + p.x * gridDim.z + p.y * gridDim.z * gridDim.x; // 201
+    #elif defined(dAxisOrder_120)
+        float group = p.x + p.z * gridDim.x + p.y * gridDim.x * gridDim.z; // 021
+    #elif defined(dAxisOrder_201)
+        float group = p.y + p.x * gridDim.y + p.z * gridDim.y * gridDim.x; // 102
+    #elif defined(dAxisOrder_210)
+        float group = p.x + p.y * gridDim.x + p.z * gridDim.x * gridDim.y; // 012
+    #endif
+    return vec4(group > 16777215.5 ? vec3(1.0) : packIntToRGB(group), 1.0);
+}
+
 void main(void) {
     // get 1D index
     int vI = int(gl_FragCoord.x) + int(gl_FragCoord.y) * int(uSize);
@@ -255,18 +274,13 @@ void main(void) {
         #ifdef dPackedGroup
             gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0);
         #else
-            vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
-            float group = coord3d.z + coord3d.y * gridDim.z + coord3d.x * gridDim.z * gridDim.y;
-            gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
+            gl_FragData[1] = getGroup(coord3d);
         #endif
     #else
         #ifdef dPackedGroup
             gl_FragData[1] = vec4(t < 0.5 ? d0.rgb : d1.rgb, 1.0);
         #else
-            vec3 b = t < 0.5 ? b0 : b1;
-            vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
-            float group = b.z + b.y * gridDim.z + b.x * gridDim.z * gridDim.y;
-            gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
+            gl_FragData[1] = getGroup(t < 0.5 ? b0 : b1);
         #endif
     #endif
 

+ 2 - 2
src/mol-gl/shader/mesh.vert.ts

@@ -16,7 +16,7 @@ precision highp sampler2D;
 #include common_clip
 #include texture3d_from_2d_linear
 
-#ifdef dGeoTexture
+#ifdef dGeometryType_textureMesh
     uniform vec2 uGeoTexDim;
     uniform sampler2D tPosition;
     uniform sampler2D tGroup;
@@ -39,7 +39,7 @@ void main(){
     #include assign_color_varying
     #include clip_instance
 
-    #ifdef dGeoTexture
+    #ifdef dGeometryType_textureMesh
         vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
     #else
         vec3 normal = aNormal;

+ 2 - 2
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -14,7 +14,7 @@ import { ValueCell } from '../../../mol-util';
 import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
-import { decodeFloatRGB } from '../../../mol-util/float-packing';
+import { unpackRGBToInt } from '../../../mol-util/number-packing';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
 import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values } from '../../../mol-gl/renderable/schema';
@@ -462,7 +462,7 @@ function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texD
             for (let ix = 0; ix < dx; ++ix) {
                 const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix);
                 data[j] = image[idx + 3] / 255;
-                idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2]);
+                idData[j] = unpackRGBToInt(image[idx], image[idx + 1], image[idx + 2]);
                 j++;
             }
         }

+ 31 - 9
src/mol-plugin-state/actions/structure.ts

@@ -19,6 +19,7 @@ import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAn
 import { Asset } from '../../mol-util/assets';
 import { PluginConfig } from '../../mol-plugin/config';
 import { getFileInfo } from '../../mol-util/file-info';
+import { assertUnreachable } from '../../mol-util/type-helpers';
 
 const DownloadModelRepresentationOptions = (plugin: PluginContext) => {
     const representationDefault = plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
@@ -39,6 +40,7 @@ export const PdbDownloadProvider = {
     'pdbe': PD.Group({
         variant: PD.Select('updated-bcif', [['updated-bcif', 'Updated (bcif)'], ['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'updtaed-bcif' | 'archival', string][]),
     }, { label: 'PDBe', isFlat: true }),
+    'pdbj': PD.EmptyGroup({ label: 'PDBj' }),
 };
 export type PdbDownloadProvider = keyof typeof PdbDownloadProvider;
 
@@ -104,15 +106,14 @@ const DownloadStructure = StateAction.build({
             format = src.params.format;
             break;
         case 'pdb':
-            downloadParams = await (src.params.provider.server.name === 'pdbe'
-                ? src.params.provider.server.params.variant === 'updated'
-                    ? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
-                    : src.params.provider.server.params.variant === 'updated-bcif'
-                        ? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
-                        : getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false)
-                : src.params.provider.server.params.encoding === 'cif'
-                    ? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id} (cif)`, false)
-                    : getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB: ${id} (bcif)`, true)
+            downloadParams = await (
+                src.params.provider.server.name === 'pdbe'
+                    ? getPdbeDownloadParams(src)
+                    : src.params.provider.server.name === 'pdbj'
+                        ? getPdbjDownloadParams(src)
+                        : src.params.provider.server.name === 'rcsb'
+                            ? getRcsbDownloadParams(src)
+                            : assertUnreachable(src as never)
             );
             asTrajectory = !!src.params.options.asTrajectory;
             break;
@@ -205,6 +206,27 @@ async function getDownloadParams(src: string, url: (id: string) => string | Prom
     return ret;
 }
 
+async function getPdbeDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
+    if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbe') throw new Error('expected pdbe');
+    return src.params.provider.server.params.variant === 'updated'
+        ? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
+        : src.params.provider.server.params.variant === 'updated-bcif'
+            ? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
+            : getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false);
+}
+
+async function getPdbjDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
+    if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbj') throw new Error('expected pdbj');
+    return getDownloadParams(src.params.provider.id, id => `https://data.pdbjbk1.pdbj.org/pub/pdb/data/structures/divided/mmCIF/${id.toLowerCase().substring(1, 3)}/${id.toLowerCase()}.cif`, id => `PDBj: ${id} (cif)`, false);
+}
+
+async function getRcsbDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
+    if (src.name !== 'pdb' || src.params.provider.server.name !== 'rcsb') throw new Error('expected rcsb');
+    return src.params.provider.server.params.encoding === 'cif'
+        ? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB PDB: ${id} (cif)`, false)
+        : getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB PDB: ${id} (bcif)`, true);
+}
+
 export const UpdateTrajectory = StateAction.build({
     display: { name: 'Update Trajectory' },
     params: {

+ 3 - 2
src/mol-plugin-state/manager/structure/selection.ts

@@ -245,7 +245,6 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
     }
 
     private onUpdate(ref: string, oldObj: PSO.Molecule.Structure | undefined, obj: PSO.Molecule.Structure) {
-
         // no change to structure
         if (oldObj === obj || oldObj?.data === obj.data) return;
 
@@ -253,7 +252,9 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
         const cell = this.plugin.helpers.substructureParent.get(obj.data, true);
         if (!cell) return;
 
-        ref = cell.transform.ref;
+        // only need to update the root
+        if (ref !== cell.transform.ref) return;
+
         if (!this.entries.has(ref)) return;
 
         // use structure from last decorator as reference

+ 2 - 2
src/mol-plugin-state/transforms/representation.ts

@@ -120,7 +120,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
             ),
             sizeTheme: PD.Mapped<any>(
                 type.defaultSizeTheme.name,
-                themeCtx.sizeThemeRegistry.types,
+                themeCtx.sizeThemeRegistry.getApplicableTypes(dataCtx),
                 name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
             )
         });
@@ -823,7 +823,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
             ),
             sizeTheme: PD.Mapped<any>(
                 type.defaultSizeTheme.name,
-                themeCtx.sizeThemeRegistry.types,
+                themeCtx.sizeThemeRegistry.getApplicableTypes(dataCtx),
                 name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
             )
         });

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

@@ -57,7 +57,7 @@ export const HighlightLoci = PluginBehavior.create({
                 if (!this.ctx.canvas3d || this.ctx.isBusy) return;
 
                 const loci = this.getLoci(current.loci);
-                if (this.params.ignore?.indexOf(loci.kind) >= 0) {
+                if (this.params.ignore.includes(loci.kind)) {
                     this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci });
                     return;
                 }
@@ -161,7 +161,7 @@ export const SelectLoci = PluginBehavior.create({
                 if (!this.ctx.canvas3d || this.ctx.isBusy || !this.ctx.selectionMode) return;
 
                 const loci = this.getLoci(current.loci);
-                if (this.params.ignore?.indexOf(loci.kind) >= 0) return;
+                if (this.params.ignore.includes(loci.kind)) return;
 
                 // only trigger the 1st action that matches
                 for (const [binding, action, condition] of actions) {
@@ -186,7 +186,7 @@ export const SelectLoci = PluginBehavior.create({
                         Structure.areEquivalent(structure, oldStructure) &&
                         Structure.areHierarchiesEqual(structure, oldStructure)) return;
 
-                    const reprs = this.ctx.state.data.select(StateSelection.Generators.ofType(SO.Molecule.Structure.Representation3D, ref));
+                    const reprs = this.ctx.state.data.select(StateSelection.children(ref).ofType(SO.Molecule.Structure.Representation3D));
                     for (const repr of reprs) this.applySelectMark(repr.transform.ref, true);
                 }
             });

+ 3 - 27
src/mol-plugin/config.ts

@@ -10,6 +10,7 @@ import { PluginContext } from './context';
 import { PdbDownloadProvider } from '../mol-plugin-state/actions/structure';
 import { EmdbDownloadProvider } from '../mol-plugin-state/actions/volume';
 import { StructureRepresentationPresetProvider } from '../mol-plugin-state/builder/structure/representation-preset';
+import { PluginFeatureDetection } from './features';
 
 export class PluginConfigItem<T = any> {
     toString() { return this.key; }
@@ -19,31 +20,6 @@ export class PluginConfigItem<T = any> {
 
 function item<T>(key: string, defaultValue?: T) { return new PluginConfigItem(key, defaultValue); }
 
-
-export function preferWebGl1() {
-    if (typeof navigator === 'undefined' || typeof window === 'undefined') return false;
-
-    // WebGL2 isn't working in MacOS 12.0.1 Safari 15.1, 15.2. It is working in Safari 15.4 tech preview, so disabling all versions before that.
-    // prefer webgl 1 based on the userAgent substring
-    const unpportedSafariVersions = [
-        'Version/15.1 Safari',
-        'Version/15.2 Safari',
-        'Version/15.3 Safari'
-    ];
-    if (unpportedSafariVersions.some(v => navigator.userAgent.indexOf(v) > 0)) {
-        return true;
-    }
-
-    // Check for iOS device which enabled WebGL2 recently but it doesn't seem
-    // to be full up to speed yet.
-
-    // adapted from https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
-    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
-    const isAppleDevice = navigator.userAgent.includes('Macintosh');
-    const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
-    return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
-}
-
 export const PluginConfig = {
     item,
     General: {
@@ -53,10 +29,10 @@ export const PluginConfig = {
         PixelScale: item('plugin-config.pixel-scale', 1),
         PickScale: item('plugin-config.pick-scale', 0.25),
         PickPadding: item('plugin-config.pick-padding', 3),
-        EnableWboit: item('plugin-config.enable-wboit', true),
+        EnableWboit: item('plugin-config.enable-wboit', PluginFeatureDetection.wboit),
         // as of Oct 1 2021, WebGL 2 doesn't work on iOS 15.
         // TODO: check back in a few weeks to see if it was fixed
-        PreferWebGl1: item('plugin-config.prefer-webgl1', preferWebGl1()),
+        PreferWebGl1: item('plugin-config.prefer-webgl1', PluginFeatureDetection.preferWebGl1),
     },
     State: {
         DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),

+ 37 - 0
src/mol-plugin/features.ts

@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export const PluginFeatureDetection = {
+    get preferWebGl1() {
+        if (typeof navigator === 'undefined' || typeof window === 'undefined') return false;
+
+        // WebGL2 isn't working in MacOS 12.0.1 Safari 15.1, 15.2. It is working in Safari 15.4 tech preview, so disabling all versions before that.
+        // prefer webgl 1 based on the userAgent substring
+        const unpportedSafariVersions = [
+            'Version/15.1 Safari',
+            'Version/15.2 Safari',
+            'Version/15.3 Safari'
+        ];
+        if (unpportedSafariVersions.some(v => navigator.userAgent.indexOf(v) > 0)) {
+            return true;
+        }
+
+        // Check for iOS device which enabled WebGL2 recently but it doesn't seem
+        // to be full up to speed yet.
+
+        // adapted from https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
+        const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
+        const isAppleDevice = navigator.userAgent.includes('Macintosh');
+        const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
+        return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
+    },
+    get wboit() {
+        if (typeof navigator === 'undefined' || typeof window === 'undefined') return true;
+
+        // disable Wboit in Safari 15
+        return !/Version\/15.\d Safari/.test(navigator.userAgent);
+    }
+};

+ 1 - 1
src/mol-repr/structure/complex-representation.ts

@@ -84,7 +84,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
             if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
             // Remap `loci` from equivalent structure to the current `_structure`
             loci = Loci.remap(loci, _structure);
-            if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
+            if (Structure.isLoci(loci) || (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci))) {
                 // Change to `EveryLoci` to allow for downstream optimizations
                 loci = EveryLoci;
             }

+ 1 - 1
src/mol-repr/structure/representation/gaussian-surface.ts

@@ -44,6 +44,6 @@ export const GaussianSurfaceRepresentationProvider = StructureRepresentationProv
     getParams: getGaussianSurfaceParams,
     defaultValues: PD.getDefaultValues(GaussianSurfaceParams),
     defaultColorTheme: { name: 'chain-id' },
-    defaultSizeTheme: { name: 'uniform' },
+    defaultSizeTheme: { name: 'physical' },
     isApplicable: (structure: Structure) => structure.elementCount > 0
 });

+ 2 - 7
src/mol-repr/structure/representation/gaussian-volume.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>
  */
@@ -10,7 +10,6 @@ import { StructureRepresentation, StructureRepresentationProvider, ComplexRepres
 import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
-import { DirectVolume } from '../../../mol-geo/geometry/direct-volume/direct-volume';
 
 const GaussianVolumeVisuals = {
     'gaussian-volume': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianDensityVolumeParams>) => ComplexRepresentation('Gaussian volume', ctx, getParams, GaussianDensityVolumeVisual),
@@ -24,10 +23,6 @@ export const GaussianVolumeParams = {
 export type GaussianVolumeParams = typeof GaussianVolumeParams
 export function getGaussianVolumeParams(ctx: ThemeRegistryContext, structure: Structure) {
     const p = PD.clone(GaussianVolumeParams);
-    p.renderMode = DirectVolume.createRenderModeParam({
-        // TODO find a better way to set
-        min: 0, max: 1, mean: 0.04, sigma: 0.01
-    });
     p.jumpLength = PD.Numeric(4, { min: 0, max: 20, step: 0.1 });
     return p;
 }
@@ -45,6 +40,6 @@ export const GaussianVolumeRepresentationProvider = StructureRepresentationProvi
     getParams: getGaussianVolumeParams,
     defaultValues: PD.getDefaultValues(GaussianVolumeParams),
     defaultColorTheme: { name: 'chain-id' },
-    defaultSizeTheme: { name: 'uniform' },
+    defaultSizeTheme: { name: 'physical' },
     isApplicable: (structure: Structure) => structure.elementCount > 0
 });

+ 1 - 1
src/mol-repr/structure/representation/label.ts

@@ -40,6 +40,6 @@ export const LabelRepresentationProvider = StructureRepresentationProvider({
     getParams: getLabelParams,
     defaultValues: PD.getDefaultValues(LabelParams),
     defaultColorTheme: { name: 'uniform' },
-    defaultSizeTheme: { name: 'uniform' },
+    defaultSizeTheme: { name: 'physical' },
     isApplicable: (structure: Structure) => structure.elementCount > 0
 });

+ 1 - 1
src/mol-repr/structure/representation/molecular-surface.ts

@@ -43,6 +43,6 @@ export const MolecularSurfaceRepresentationProvider = StructureRepresentationPro
     getParams: getMolecularSurfaceParams,
     defaultValues: PD.getDefaultValues(MolecularSurfaceParams),
     defaultColorTheme: { name: 'chain-id' },
-    defaultSizeTheme: { name: 'uniform' },
+    defaultSizeTheme: { name: 'physical' },
     isApplicable: (structure: Structure) => structure.elementCount > 0
 });

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

@@ -202,7 +202,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
             if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false;
             // Remap `loci` from equivalent structure to the current `_structure`
             loci = Loci.remap(loci, _structure);
-            if (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci)) {
+            if (Structure.isLoci(loci) || (StructureElement.Loci.is(loci) && StructureElement.Loci.isWholeStructure(loci))) {
                 // Change to `EveryLoci` to allow for downstream optimizations
                 loci = EveryLoci;
             }

+ 6 - 6
src/mol-repr/structure/visual/gaussian-density-volume.ts

@@ -25,14 +25,14 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct
     }
 
     const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
-    const densityTextureData = await computeStructureGaussianDensityTexture(structure, props, webgl, oldTexture).runInContext(runtime);
+    const densityTextureData = await computeStructureGaussianDensityTexture(structure, theme.size, props, webgl, oldTexture).runInContext(runtime);
     const { transform, texture, bbox, gridDim } = densityTextureData;
     const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
 
     const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
     const cellDim = Mat4.getScaling(Vec3(), transform);
-
-    const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume);
+    const axisOrder = Vec3.create(0, 1, 2);
+    const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, directVolume);
 
     const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     vol.setBoundingSphere(sphere);
@@ -81,14 +81,14 @@ async function createUnitsGaussianDensityVolume(ctx: VisualContext, unit: Unit,
     }
 
     const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
-    const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, props, webgl, oldTexture).runInContext(runtime);
+    const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, theme.size, props, webgl, oldTexture).runInContext(runtime);
     const { transform, texture, bbox, gridDim } = densityTextureData;
     const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
 
     const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
     const cellDim = Mat4.getScaling(Vec3(), transform);
-
-    const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume);
+    const axisOrder = Vec3.create(0, 1, 2);
+    const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, directVolume);
 
     const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getUnitExtraRadius(unit));
     vol.setBoundingSphere(sphere);

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

@@ -26,6 +26,7 @@ import { Texture } from '../../../mol-gl/webgl/texture';
 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';
+import { Vec3 } from '../../../mol-math/linear-algebra';
 
 const SharedParams = {
     ...GaussianDensityParams,
@@ -88,7 +89,7 @@ type GaussianSurfaceMeta = {
 
 async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField, radiusFactor, resolution } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor, resolution } = await computeUnitGaussianDensity(structure, unit, theme.size, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -149,7 +150,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
 
 async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField, radiusFactor, resolution } = await computeStructureGaussianDensity(structure, props).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor, resolution } = await computeStructureGaussianDensity(structure, theme.size, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -222,7 +223,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
     }
 
     // console.time('computeUnitGaussianDensityTexture2d');
-    const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
+    const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, theme.size, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
     // console.log(densityTextureData);
     // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture));
     // ctx.webgl.waitForGpuCommandsCompleteSync();
@@ -230,8 +231,9 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
 
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
+    const axisOrder = Vec3.create(0, 1, 2);
     const buffer = textureMesh?.doubleBuffer.get();
-    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, buffer?.vertex, buffer?.group, buffer?.normal);
+    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
 
     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);
@@ -298,7 +300,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
     }
 
     // console.time('computeUnitGaussianDensityTexture2d');
-    const densityTextureData = await computeStructureGaussianDensityTexture2d(structure, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
+    const densityTextureData = await computeStructureGaussianDensityTexture2d(structure, theme.size, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
     // console.log(densityTextureData);
     // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture));
     // ctx.webgl.waitForGpuCommandsCompleteSync();
@@ -306,8 +308,9 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
 
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
+    const axisOrder = Vec3.create(0, 1, 2);
     const buffer = textureMesh?.doubleBuffer.get();
-    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, buffer?.vertex, buffer?.group, buffer?.normal);
+    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
 
     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);

+ 1 - 1
src/mol-repr/structure/visual/gaussian-surface-wireframe.ts

@@ -19,7 +19,7 @@ import { getUnitExtraRadius } from './util/common';
 
 async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
     const { smoothness } = props;
-    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
+    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, theme.size, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness),

+ 1 - 2
src/mol-repr/structure/visual/label-text.ts

@@ -16,7 +16,6 @@ import { ComplexTextVisual, ComplexTextParams, ComplexVisual } from '../complex-
 import { ElementIterator, getSerialElementLoci, eachSerialElement } from './util/element';
 import { ColorNames } from '../../../mol-util/color/names';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { PhysicalSizeTheme } from '../../../mol-theme/size/physical';
 import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
 
 export const LabelTextParams = {
@@ -151,7 +150,7 @@ function createElementText(ctx: VisualContext, structure: Structure, theme: Them
     const { label_atom_id, label_alt_id } = StructureProperties.atom;
     const { cumulativeUnitElementCount } = serialMapping;
 
-    const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
+    const sizeTheme = theme.size;
 
     const count = structure.elementCount;
     const { elementScale } = props;

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

@@ -40,7 +40,7 @@ type MolecularSurfaceMeta = {
 //
 
 async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> {
-    const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
+    const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, theme.size, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: props.probeRadius,

+ 1 - 1
src/mol-repr/structure/visual/molecular-surface-wireframe.ts

@@ -29,7 +29,7 @@ export type MolecularSurfaceWireframeParams = typeof MolecularSurfaceWireframePa
 //
 
 async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, lines?: Lines): Promise<Lines> {
-    const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
+    const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, theme.size, props).runInContext(ctx.runtime);
     const params = {
         isoLevel: props.probeRadius,
         scalarField: field,

+ 3 - 5
src/mol-repr/structure/visual/util/common.ts

@@ -9,13 +9,13 @@ import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
 import { TransformData, createTransform } from '../../../../mol-geo/geometry/transform-data';
 import { OrderedSet, SortedArray } from '../../../../mol-data/int';
 import { EmptyLoci, Loci } from '../../../../mol-model/loci';
-import { PhysicalSizeTheme } from '../../../../mol-theme/size/physical';
 import { AtomicNumbers } from '../../../../mol-model/structure/model/properties/atomic';
 import { fillSerial } from '../../../../mol-util/array';
 import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
 import { AssignableArrayLike } from '../../../../mol-util/type-helpers';
 import { getBoundary } from '../../../../mol-math/geometry/boundary';
 import { Box3D } from '../../../../mol-math/geometry';
+import { SizeTheme } from '../../../../mol-theme/size';
 
 /** Return a Loci for the elements of a whole residue the elementIndex belongs to. */
 export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci {
@@ -165,7 +165,7 @@ function filterId(id: AssignableArrayLike<number>, elements: SortedArray, indice
     }
 }
 
-export function getUnitConformationAndRadius(structure: Structure, unit: Unit, props: CommonSurfaceProps) {
+export function getUnitConformationAndRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: CommonSurfaceProps) {
     const { ignoreHydrogens, traceOnly, includeParent } = props;
     const rootUnit = includeParent ? structure.root.unitMap.get(unit.id) : unit;
 
@@ -205,7 +205,6 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
     const boundary = unit === rootUnit ? unit.boundary : getBoundary(position);
 
     const l = StructureElement.Location.create(structure, rootUnit);
-    const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
     const radius = (index: number) => {
         l.element = index as ElementIndex;
         return sizeTheme.size(l);
@@ -214,9 +213,8 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
     return { position, boundary, radius };
 }
 
-export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens: boolean, traceOnly: boolean) {
+export function getStructureConformationAndRadius(structure: Structure, sizeTheme: SizeTheme<any>, ignoreHydrogens: boolean, traceOnly: boolean) {
     const l = StructureElement.Location.create(structure);
-    const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
 
     let xs: ArrayLike<number>;
     let ys: ArrayLike<number>;

+ 13 - 12
src/mol-repr/structure/visual/util/gaussian.ts

@@ -13,6 +13,7 @@ import { WebGLContext } from '../../../../mol-gl/webgl/context';
 import { getUnitConformationAndRadius, getStructureConformationAndRadius, CommonSurfaceParams, ensureReasonableResolution } from './common';
 import { BaseGeometry } from '../../../../mol-geo/geometry/base';
 import { GaussianDensityCPU } from '../../../../mol-math/geometry/gaussian-density/cpu';
+import { SizeTheme } from '../../../../mol-theme/size';
 
 export const GaussianDensityParams = {
     resolution: PD.Numeric(1, { min: 0.1, max: 20, step: 0.1 }, { description: 'Grid resolution/cell spacing.', ...BaseGeometry.CustomQualityParamInfo }),
@@ -32,28 +33,28 @@ export function getTextureMaxCells(webgl: WebGLContext, structure?: Structure) {
 
 //
 
-export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps) {
+export function computeUnitGaussianDensity(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: GaussianDensityProps) {
     const { box } = unit.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props);
-    const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
     return Task.create('Gaussian Density', async ctx => {
         return await GaussianDensityCPU(ctx, position, box, radius, p);
     });
 }
 
-export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
+export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = unit.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
-    const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture(webgl, position, box, radius, p, texture);
     });
 }
 
-export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
+export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = unit.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
-    const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
+    const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
     });
@@ -61,28 +62,28 @@ export function computeUnitGaussianDensityTexture2d(structure: Structure, unit:
 
 //
 
-export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps) {
+export function computeStructureGaussianDensity(structure: Structure, sizeTheme: SizeTheme<any>, props: GaussianDensityProps) {
     const { box } = structure.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props);
-    const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
+    const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
     return Task.create('Gaussian Density', async ctx => {
         return await GaussianDensityCPU(ctx, position, box, radius, p);
     });
 }
 
-export function computeStructureGaussianDensityTexture(structure: Structure, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
+export function computeStructureGaussianDensityTexture(structure: Structure, sizeTheme: SizeTheme<any>, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = structure.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
-    const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
+    const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture(webgl, position, box, radius, p, texture);
     });
 }
 
-export function computeStructureGaussianDensityTexture2d(structure: Structure, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
+export function computeStructureGaussianDensityTexture2d(structure: Structure, sizeTheme: SizeTheme<any>, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = structure.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
-    const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
+    const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
     });

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

@@ -11,12 +11,13 @@ import { PositionData, DensityData, Box3D } from '../../../../mol-math/geometry'
 import { MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
 import { OrderedSet } from '../../../../mol-data/int';
 import { Boundary } from '../../../../mol-math/geometry/boundary';
+import { SizeTheme } from '../../../../mol-theme/size';
 
 export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & CommonSurfaceProps
 
-function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
+function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
     const { probeRadius } = props;
-    const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, props);
+    const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, props);
     const { indices } = position;
     const n = OrderedSet.size(indices);
     const radii = new Float32Array(OrderedSet.end(indices));
@@ -32,10 +33,10 @@ function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: Mo
     return { position: { ...position, radius: radii }, boundary, maxRadius };
 }
 
-export function computeUnitMolecularSurface(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
+export function computeUnitMolecularSurface(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
     const { box } = unit.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props);
-    const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, p);
+    const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, sizeTheme, p);
     return Task.create('Molecular Surface', async ctx => {
         return await MolecularSurface(ctx, position, boundary, maxRadius, box, p);
     });

+ 8 - 12
src/mol-repr/volume/direct-volume.ts

@@ -48,7 +48,8 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
     texture.load(textureImage);
 
     const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
-    return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume);
+    const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
+    return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, directVolume);
 }
 
 // 3d volume texture
@@ -89,7 +90,8 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
     texture.load(textureVolume);
 
     const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
-    return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, directVolume);
+    const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
+    return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, directVolume);
 }
 
 //
@@ -104,9 +106,7 @@ export async function createDirectVolume(ctx: VisualContext, volume: Volume, the
 }
 
 function getLoci(volume: Volume, props: PD.Values<DirectVolumeParams>) {
-    return props.renderMode.name === 'isosurface'
-        ? Volume.Isosurface.Loci(volume, props.renderMode.params.isoValue)
-        : Volume.Loci(volume);
+    return Volume.Loci(volume);
 }
 
 export function getDirectVolumeLoci(pickingId: PickingId, volume: Volume, props: DirectVolumeProps, id: number) {
@@ -118,9 +118,7 @@ export function getDirectVolumeLoci(pickingId: PickingId, volume: Volume, props:
 }
 
 export function eachDirectVolume(loci: Loci, volume: Volume, props: DirectVolumeProps, apply: (interval: Interval) => boolean) {
-    const isoValue = props.renderMode.name === 'isosurface'
-        ? props.renderMode.params.isoValue : undefined;
-    return eachVolumeLoci(loci, volume, isoValue, apply);
+    return eachVolumeLoci(loci, volume, undefined, apply);
 }
 
 //
@@ -131,9 +129,7 @@ export const DirectVolumeParams = {
 };
 export type DirectVolumeParams = typeof DirectVolumeParams
 export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
-    const p = PD.clone(DirectVolumeParams);
-    p.renderMode = DirectVolume.createRenderModeParam(volume.grid.stats);
-    return p;
+    return PD.clone(DirectVolumeParams);
 }
 export type DirectVolumeProps = PD.Values<DirectVolumeParams>
 
@@ -164,7 +160,7 @@ export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
     factory: DirectVolumeRepresentation,
     getParams: getDirectVolumeParams,
     defaultValues: PD.getDefaultValues(DirectVolumeParams),
-    defaultColorTheme: { name: 'uniform' },
+    defaultColorTheme: { name: 'volume-value' },
     defaultSizeTheme: { name: 'uniform' },
     isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
 });

+ 10 - 2
src/mol-repr/volume/isosurface.ts

@@ -43,6 +43,10 @@ function gpuSupport(webgl: WebGLContext) {
 const Padding = 1;
 
 function suitableForGpu(volume: Volume, webgl: WebGLContext) {
+    // small volumes are about as fast or faster on CPU vs integrated GPU
+    if (volume.grid.cells.data.length < Math.pow(10, 3)) return false;
+    // the GPU is much more memory contraint, especially true for integrated GPUs,
+    // fallback to CPU for large volumes
     const gridDim = volume.grid.cells.space.dimensions as Vec3;
     const { powerOfTwoSize } = getVolumeTexture2dLayout(gridDim, Padding);
     return powerOfTwoSize <= webgl.maxTextureSize / 2;
@@ -131,7 +135,6 @@ namespace VolumeIsosurfaceTexture {
     export function get(volume: Volume, webgl: WebGLContext) {
         const { resources } = webgl;
 
-
         const transform = Grid.getGridToCartesianTransform(volume.grid);
         const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
         const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
@@ -169,6 +172,10 @@ namespace VolumeIsosurfaceTexture {
 async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, theme: Theme, props: VolumeIsosurfaceProps, textureMesh?: TextureMesh) {
     if (!ctx.webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
 
+    if (volume.grid.cells.data.length <= 1) {
+        return TextureMesh.createEmpty(textureMesh);
+    }
+
     const { max, min } = volume.grid.stats;
     const diff = max - min;
     const value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
@@ -176,8 +183,9 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
 
     const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
 
+    const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
     const buffer = textureMesh?.doubleBuffer.get();
-    const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, buffer?.vertex, buffer?.group, buffer?.normal);
+    const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
 
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
 

+ 2 - 2
src/mol-repr/volume/slice.ts

@@ -21,7 +21,7 @@ import { transformPositionArray } from '../../mol-geo/util';
 import { RenderableState } from '../../mol-gl/renderable';
 import { Color } from '../../mol-util/color';
 import { ColorTheme } from '../../mol-theme/color';
-import { encodeFloatRGBtoArray } from '../../mol-util/float-packing';
+import { packIntToRGBArray } from '../../mol-util/number-packing';
 import { eachVolumeLoci } from './util';
 
 export async function createImage(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
@@ -116,7 +116,7 @@ function getPackedGroupArray(grid: Grid, props: PD.Values<SliceParams>) {
     for (let iy = y0; iy < ny; ++iy) {
         for (let ix = x0; ix < nx; ++ix) {
             for (let iz = z0; iz < nz; ++iz) {
-                encodeFloatRGBtoArray(space.dataOffset(ix, iy, iz), groupArray, j);
+                packIntToRGBArray(space.dataOffset(ix, iy, iz), groupArray, j);
                 j += 4;
             }
         }

+ 2 - 2
src/mol-repr/volume/util.ts

@@ -9,7 +9,7 @@ import { Loci } from '../../mol-model/loci';
 import { Interval, OrderedSet } from '../../mol-data/int';
 import { equalEps } from '../../mol-math/linear-algebra/3d/common';
 import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
-import { encodeFloatRGBtoArray } from '../../mol-util/float-packing';
+import { packIntToRGBArray } from '../../mol-util/number-packing';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3set = Vec3.set;
@@ -110,7 +110,7 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
                     array[index] = Math.round(((data[offset] - min) / diff) * 255);
                 } else {
                     if (variant === 'groups') {
-                        encodeFloatRGBtoArray(offset, array, index);
+                        packIntToRGBArray(offset, array, index);
                     } else {
                         v3set(n0,
                             data[o(Math.max(0, x - 1), y, z)],

+ 2 - 0
src/mol-theme/color.ts

@@ -36,6 +36,7 @@ import { PartialChargeColorThemeProvider } from './color/partial-charge';
 import { AtomIdColorThemeProvider } from './color/atom-id';
 import { EntityIdColorThemeProvider } from './color/entity-id';
 import { TextureFilter } from '../mol-gl/webgl/texture';
+import { VolumeValueColorThemeProvider } from './color/volume-value';
 
 export type LocationColor = (location: Location, isSecondary: boolean) => Color
 
@@ -120,6 +121,7 @@ namespace ColorTheme {
         'uncertainty': UncertaintyColorThemeProvider,
         'unit-index': UnitIndexColorThemeProvider,
         'uniform': UniformColorThemeProvider,
+        'volume-value': VolumeValueColorThemeProvider,
     };
     type _BuiltIn = typeof BuiltIn
     export type BuiltIn = keyof _BuiltIn

+ 62 - 0
src/mol-theme/color/volume-value.ts

@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ColorTheme } from '../color';
+import { Color, ColorScale } from '../../mol-util/color';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { ThemeDataContext } from '../theme';
+import { ColorNames } from '../../mol-util/color/names';
+
+const DefaultColor = Color(0xCCCCCC);
+const Description = 'Assign color based on the given value of a volume cell.';
+
+export const VolumeValueColorThemeParams = {
+    colorList: PD.ColorList({
+        kind: 'interpolate',
+        colors: [
+            [ColorNames.white, 0],
+            [ColorNames.red, 0.25],
+            [ColorNames.white, 0.5],
+            [ColorNames.blue, 0.75],
+            [ColorNames.white, 1]
+        ]
+    }, { offsets: true, isEssential: true }),
+};
+export type VolumeValueColorThemeParams = typeof VolumeValueColorThemeParams
+export function getVolumeValueColorThemeParams(ctx: ThemeDataContext) {
+    return VolumeValueColorThemeParams; // TODO return copy
+}
+
+export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueColorThemeParams>): ColorTheme<VolumeValueColorThemeParams> {
+    const scale = ColorScale.create({ domain: [0, 1], listOrName: props.colorList.colors });
+
+    const colors: Color[] = [];
+    for (let i = 0; i < 256; ++i) {
+        colors[i] = scale.color(i / 255);
+    }
+
+    const palette: ColorTheme.Palette = { colors, filter: 'linear' };
+
+    return {
+        factory: VolumeValueColorTheme,
+        granularity: 'direct',
+        color: () => DefaultColor,
+        props: props,
+        description: Description,
+        legend: scale.legend,
+        palette,
+    };
+}
+
+export const VolumeValueColorThemeProvider: ColorTheme.Provider<VolumeValueColorThemeParams, 'volume-value'> = {
+    name: 'volume-value',
+    label: 'Volume Value',
+    category: ColorTheme.Category.Misc,
+    factory: VolumeValueColorTheme,
+    getParams: getVolumeValueColorThemeParams,
+    defaultValues: PD.getDefaultValues(VolumeValueColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.volume,
+};

+ 6 - 16
src/mol-util/float-packing.ts → src/mol-util/number-packing.ts

@@ -1,26 +1,16 @@
 /**
- * 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 { clamp } from '../mol-math/interpolate';
-import { fasterExp, fasterLog } from '../mol-math/approx';
 import { Vec3, Vec4 } from '../mol-math/linear-algebra';
 import { NumberArray } from './type-helpers';
 
-const maxFloat = 10000.0; // NOTE same constant is set in shaders
-const floatLogFactor = fasterLog(maxFloat + 1);
-
-/** encode float logarithmically */
-export function encodeFloatLog(value: number) { return fasterLog(value + 1) / floatLogFactor; }
-
-/** decode logarithmically encoded float */
-export function decodeFloatLog(value: number) { return fasterExp(value * floatLogFactor) - 1; }
-
-/** encode float as rgb triplet into array at offset */
-export function encodeFloatRGBtoArray(value: number, array: NumberArray, offset: number) {
-    value = clamp(value, 0, 16777216 - 1) + 1;
+/** encode positive integer as rgb byte triplet into array at offset */
+export function packIntToRGBArray(value: number, array: NumberArray, offset: number) {
+    value = clamp(Math.round(value), 0, 16777216 - 1) + 1;
     array[offset + 2] = value % 256;
     value = Math.floor(value / 256);
     array[offset + 1] = value % 256;
@@ -29,8 +19,8 @@ export function encodeFloatRGBtoArray(value: number, array: NumberArray, offset:
     return array;
 }
 
-/** decode float encoded as rgb triplet */
-export function decodeFloatRGB(r: number, g: number, b: number) {
+/** decode positive integer encoded as rgb byte triplet */
+export function unpackRGBToInt(r: number, g: number, b: number) {
     return (Math.floor(r) * 256 * 256 + Math.floor(g) * 256 + Math.floor(b)) - 1;
 }
 

+ 2 - 2
src/tests/browser/marching-cubes.ts

@@ -73,7 +73,7 @@ async function init() {
         console.timeEnd('gpu mc pyramid2');
 
         console.time('gpu mc vert2');
-        createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true);
+        createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true, Vec3.create(0, 1, 2));
         webgl.waitForGpuCommandsCompleteSync();
         console.timeEnd('gpu mc vert2');
         console.timeEnd('gpu mc2');
@@ -96,7 +96,7 @@ async function init() {
     console.timeEnd('gpu mc pyramid');
 
     console.time('gpu mc vert');
-    const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true);
+    const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2));
     webgl.waitForGpuCommandsCompleteSync();
     console.timeEnd('gpu mc vert');
     console.timeEnd('gpu mc');