Bladeren bron

Merge branch 'master' of https://github.com/molstar/molstar into mp4-export

David Sehnal 4 jaren geleden
bovenliggende
commit
0d7db59c9e
28 gewijzigde bestanden met toevoegingen van 303 en 164 verwijderingen
  1. 1 1
      package.json
  2. 2 1
      src/examples/alpha-orbitals/index.ts
  3. 4 1
      src/extensions/alpha-orbitals/transforms.ts
  4. 1 1
      src/mol-canvas3d/camera.ts
  5. 4 3
      src/mol-canvas3d/canvas3d.ts
  6. 1 1
      src/mol-canvas3d/controls/trackball.ts
  7. 14 13
      src/mol-canvas3d/passes/image.ts
  8. 32 24
      src/mol-canvas3d/passes/multi-sample.ts
  9. 21 7
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  10. 3 1
      src/mol-gl/renderable/direct-volume.ts
  11. 2 2
      src/mol-gl/renderer.ts
  12. 86 58
      src/mol-gl/shader/direct-volume.frag.ts
  13. 9 5
      src/mol-gl/shader/direct-volume.vert.ts
  14. 1 1
      src/mol-gl/shader/gaussian-density.vert.ts
  15. 2 2
      src/mol-math/geometry/boundary-helper.ts
  16. 3 3
      src/mol-math/geometry/gaussian-density/gpu.ts
  17. 4 0
      src/mol-math/geometry/primitives/sphere3d.ts
  18. 7 3
      src/mol-plugin/util/viewport-screenshot.ts
  19. 5 1
      src/mol-repr/structure/complex-visual.ts
  20. 7 4
      src/mol-repr/structure/representation/gaussian-volume.ts
  21. 55 5
      src/mol-repr/structure/visual/gaussian-density-volume.ts
  22. 4 3
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  23. 3 2
      src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
  24. 2 2
      src/mol-repr/structure/visual/molecular-surface-mesh.ts
  25. 2 2
      src/mol-repr/structure/visual/molecular-surface-wireframe.ts
  26. 22 0
      src/mol-repr/structure/visual/util/common.ts
  27. 0 13
      src/mol-repr/structure/visual/util/gaussian.ts
  28. 6 5
      src/mol-repr/volume/direct-volume.ts

+ 1 - 1
package.json

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

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

@@ -102,7 +102,8 @@ export class AlphaOrbitalsExample {
             color,
             directVolume: this.state.value.gpuSurface,
             kind,
-            relativeIsovalue: this.state.value.isoValue
+            relativeIsovalue: this.state.value.isoValue,
+            pickable: false
         };
     }
 

+ 4 - 1
src/extensions/alpha-orbitals/transforms.ts

@@ -99,7 +99,8 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
         relativeIsovalue: PD.Numeric(1, { min: 0.01, max: 5, step: 0.01 }),
         kind: PD.Select<'positive' | 'negative'>('positive', [['positive', 'Positive'], ['negative', 'Negative']]),
         color: PD.Color(ColorNames.blue),
-        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 })
+        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
+        pickable: PD.Boolean(true)
     }
 })({
     canAutoUpdate() {
@@ -116,6 +117,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
             const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
             repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
             await repr.createOrUpdate(props, a.data).runInContext(ctx);
+            repr.setState({ pickable: srcParams.pickable });
             return new PluginStateObject.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
         });
     },
@@ -126,6 +128,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
             const props = { ...b.data.repr.props, ...newParams.type.params };
             b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
             await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
+            b.data.repr.setState({ pickable: srcParams.pickable });
             b.description = VolumeRepresentation3DHelpers.getDescription(props);
             return StateTransformer.UpdateResult.Updated;
         });

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

@@ -320,7 +320,7 @@ function updateClip(camera: Camera) {
 
     if (mode === 'perspective') {
         // set at least to 5 to avoid slow sphere impostor rendering
-        near = Math.max(5, near);
+        near = Math.max(Math.min(radiusMax, 5), near);
         far = Math.max(5, far);
     } else {
         near = Math.max(0, near);

+ 4 - 3
src/mol-canvas3d/canvas3d.ts

@@ -25,7 +25,7 @@ import { DebugHelperParams } from './helper/bounding-sphere-helper';
 import { SetUtils } from '../mol-util/set';
 import { Canvas3dInteractionHelper } from './helper/interaction-events';
 import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing';
-import { MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
+import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
 import { PickData } from './passes/pick';
 import { PickHelper } from './passes/pick';
 import { ImagePass, ImageProps } from './passes/image';
@@ -233,6 +233,7 @@ namespace Canvas3D {
 
         const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height });
         const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
+        const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
 
         let drawPending = false;
         let cameraResetRequested = false;
@@ -287,7 +288,7 @@ namespace Canvas3D {
             let didRender = false;
             controls.update(currentTime);
             const cameraChanged = camera.update();
-            const multiSampleChanged = passes.multiSample.update(force || cameraChanged, p.multiSample);
+            const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
 
             if (force || cameraChanged || multiSampleChanged) {
                 let cam: Camera | StereoCamera = camera;
@@ -297,7 +298,7 @@ namespace Canvas3D {
                 }
 
                 if (MultiSamplePass.isEnabled(p.multiSample)) {
-                    passes.multiSample.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
+                    multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
                 } else {
                     const toDrawingBuffer = !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0;
                     passes.draw.render(renderer, cam, scene, helper, toDrawingBuffer, p.transparentBackground);

+ 1 - 1
src/mol-canvas3d/controls/trackball.ts

@@ -69,7 +69,7 @@ namespace TrackballControls {
     export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
         const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props };
 
-        const viewport = Viewport();
+        const viewport = Viewport.clone(camera.viewport);
 
         let disposed = false;
 

+ 14 - 13
src/mol-canvas3d/passes/image.ts

@@ -11,7 +11,7 @@ import Scene from '../../mol-gl/scene';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { DrawPass } from './draw';
 import { PostprocessingPass, PostprocessingParams } from './postprocessing';
-import { MultiSamplePass, MultiSampleParams } from './multi-sample';
+import { MultiSamplePass, MultiSampleParams, MultiSampleHelper } from './multi-sample';
 import { Camera } from '../camera';
 import { Viewport } from '../camera/util';
 import { PixelData } from '../../mol-util/image';
@@ -37,10 +37,10 @@ export class ImagePass {
     private _colorTarget: RenderTarget
     get colorTarget() { return this._colorTarget; }
 
-    readonly drawPass: DrawPass
-
-    private readonly postprocessing: PostprocessingPass
-    private readonly multiSample: MultiSamplePass
+    private readonly drawPass: DrawPass
+    private readonly postprocessingPass: PostprocessingPass
+    private readonly multiSamplePass: MultiSamplePass
+    private readonly multiSampleHelper: MultiSampleHelper
     private readonly helper: Helper
 
     get width() { return this._width; }
@@ -50,8 +50,9 @@ export class ImagePass {
         this.props = { ...PD.getDefaultValues(ImageParams), ...props };
 
         this.drawPass = new DrawPass(webgl, 128, 128);
-        this.postprocessing = new PostprocessingPass(webgl, this.drawPass);
-        this.multiSample = new MultiSamplePass(webgl, this.drawPass, this.postprocessing);
+        this.postprocessingPass = new PostprocessingPass(webgl, this.drawPass);
+        this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass, this.postprocessingPass);
+        this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
 
         this.helper = {
             camera: new CameraHelper(webgl, this.props.cameraHelper),
@@ -69,8 +70,8 @@ export class ImagePass {
         this._height = height;
 
         this.drawPass.setSize(width, height);
-        this.postprocessing.syncSize();
-        this.multiSample.syncSize();
+        this.postprocessingPass.syncSize();
+        this.multiSamplePass.syncSize();
     }
 
     setProps(props: Partial<ImageProps> = {}) {
@@ -86,13 +87,13 @@ export class ImagePass {
         this.renderer.setViewport(0, 0, this._width, this._height);
 
         if (MultiSamplePass.isEnabled(this.props.multiSample)) {
-            this.multiSample.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
-            this._colorTarget = this.multiSample.colorTarget;
+            this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props);
+            this._colorTarget = this.multiSamplePass.colorTarget;
         } else {
             this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground);
             if (PostprocessingPass.isEnabled(this.props.postprocessing)) {
-                this.postprocessing.render(this._camera, false, this.props.postprocessing);
-                this._colorTarget = this.postprocessing.target;
+                this.postprocessingPass.render(this._camera, false, this.props.postprocessing);
+                this._colorTarget = this.postprocessingPass.target;
             } else {
                 this._colorTarget = this.drawPass.colorTarget;
             }

+ 32 - 24
src/mol-canvas3d/passes/multi-sample.ts

@@ -68,8 +68,6 @@ export class MultiSamplePass {
     private holdTarget: RenderTarget
     private compose: ComposeRenderable
 
-    private sampleIndex = -2
-
     constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) {
         const { extensions } = webgl;
         const width = drawPass.colorTarget.getWidth();
@@ -80,11 +78,6 @@ export class MultiSamplePass {
         this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
     }
 
-    update(changed: boolean, props: MultiSampleProps) {
-        if (changed) this.sampleIndex = -1;
-        return props.mode === 'temporal' ? this.sampleIndex !== -2 : false;
-    }
-
     syncSize() {
         const width = this.drawPass.colorTarget.getWidth();
         const height = this.drawPass.colorTarget.getHeight();
@@ -98,11 +91,12 @@ export class MultiSamplePass {
         }
     }
 
-    render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
+    render(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
         if (props.multiSample.mode === 'temporal') {
-            this.renderTemporalMultiSample(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
+            return this.renderTemporalMultiSample(sampleIndex, renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
         } else {
             this.renderMultiSample(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
+            return sampleIndex;
         }
     }
 
@@ -181,7 +175,7 @@ export class MultiSamplePass {
         camera.update();
     }
 
-    private renderTemporalMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
+    private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) {
         const { compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this;
         const { gl, state } = webgl;
 
@@ -192,17 +186,13 @@ export class MultiSamplePass {
         // each sample with camera jitter and accumulates the results.
         const offsetList = JitterVectors[ Math.max(0, Math.min(props.multiSample.sampleLevel, 5)) ];
 
-        if (this.sampleIndex === -2) return;
-        if (this.sampleIndex >= offsetList.length) {
-            this.sampleIndex = -2;
-            return;
-        }
+        if (sampleIndex === -2 || sampleIndex >= offsetList.length) return -2;
 
         const { x, y, width, height } = camera.viewport;
         const sampleWeight = 1.0 / offsetList.length;
         const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
 
-        if (this.sampleIndex === -1) {
+        if (sampleIndex === -1) {
             drawPass.render(renderer, camera, scene, helper, false, transparentBackground);
             if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing);
             ValueCell.update(compose.values.uWeight, 1.0);
@@ -216,7 +206,7 @@ export class MultiSamplePass {
             gl.viewport(x, y, width, height);
             gl.scissor(x, y, width, height);
             compose.render();
-            this.sampleIndex += 1;
+            sampleIndex += 1;
         } else {
             camera.viewOffset.enabled = true;
             ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
@@ -227,7 +217,7 @@ export class MultiSamplePass {
             // from the last and accumulate the results.
             const numSamplesPerFrame = Math.pow(2, Math.max(0, props.multiSample.sampleLevel - 2));
             for (let i = 0; i < numSamplesPerFrame; ++i) {
-                const offset = offsetList[this.sampleIndex];
+                const offset = offsetList[sampleIndex];
                 Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
                 camera.update();
 
@@ -244,14 +234,14 @@ export class MultiSamplePass {
                 state.depthMask(false);
                 gl.viewport(x, y, width, height);
                 gl.scissor(x, y, width, height);
-                if (this.sampleIndex === 0) {
+                if (sampleIndex === 0) {
                     state.clearColor(0, 0, 0, 0);
                     gl.clear(gl.COLOR_BUFFER_BIT);
                 }
                 compose.render();
 
-                this.sampleIndex += 1;
-                if (this.sampleIndex >= offsetList.length ) break;
+                sampleIndex += 1;
+                if (sampleIndex >= offsetList.length ) break;
             }
         }
 
@@ -264,7 +254,7 @@ export class MultiSamplePass {
         gl.viewport(x, y, width, height);
         gl.scissor(x, y, width, height);
 
-        const accumulationWeight = this.sampleIndex * sampleWeight;
+        const accumulationWeight = sampleIndex * sampleWeight;
         if (accumulationWeight > 0) {
             ValueCell.update(compose.values.uWeight, 1.0);
             ValueCell.update(compose.values.tColor, composeTarget.texture);
@@ -283,7 +273,8 @@ export class MultiSamplePass {
 
         camera.viewOffset.enabled = false;
         camera.update();
-        if (this.sampleIndex >= offsetList.length) this.sampleIndex = -2;
+
+        return sampleIndex >= offsetList.length ? -2 : sampleIndex;
     }
 }
 
@@ -325,4 +316,21 @@ JitterVectors.forEach(offsetList => {
         offset[0] *= 0.0625;
         offset[1] *= 0.0625;
     });
-});
+});
+
+export class MultiSampleHelper {
+    private sampleIndex = -2
+
+    update(changed: boolean, props: MultiSampleProps) {
+        if (changed) this.sampleIndex = -1;
+        return props.mode === 'temporal' ? this.sampleIndex !== -2 : false;
+    }
+
+    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);
+    }
+
+    constructor(private multiSamplePass: MultiSamplePass) {
+
+    }
+}

+ 21 - 7
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -169,6 +169,7 @@ export namespace DirectVolume {
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         renderMode: createRenderModeParam(),
         stepsPerCell: PD.Numeric(5, { min: 1, max: 20, step: 1 }),
+        jumpLength: PD.Numeric(0, { min: 0, max: 20, step: 0.1 }),
     };
     export type Params = typeof Params
 
@@ -213,6 +214,18 @@ export namespace DirectVolume {
         return out;
     }
 
+    function getMaxSteps(gridDim: Vec3, stepsPerCell: number) {
+        return Math.ceil(Vec3.magnitude(gridDim) * stepsPerCell);
+    }
+
+    function getStepScale(cellDim: Vec3, stepsPerCell: number) {
+        return Math.min(...cellDim) * (1 / stepsPerCell);
+    }
+
+    function getTransferScale(stepsPerCell: number) {
+        return (1 / stepsPerCell);
+    }
+
     function createValues(directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): DirectVolumeValues {
         const { gridTexture, gridTextureDim, gridStats } = directVolume;
         const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume;
@@ -243,8 +256,6 @@ export namespace DirectVolume {
             ? props.renderMode.params.singleLayer
             : false;
 
-        const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value) * props.stepsPerCell);
-
         return {
             ...color,
             ...marker,
@@ -264,12 +275,14 @@ export namespace DirectVolume {
             uBboxMin: bboxMin,
             uBboxMax: bboxMax,
             uBboxSize: bboxSize,
-            uMaxSteps: ValueCell.create(maxSteps),
-            uStepFactor: ValueCell.create(1 / props.stepsPerCell),
+            uMaxSteps: ValueCell.create(getMaxSteps(gridDimension.ref.value, props.stepsPerCell)),
+            uStepScale: ValueCell.create(getStepScale(directVolume.cellDim.ref.value, props.stepsPerCell)),
+            uJumpLength: ValueCell.create(props.jumpLength),
             uTransform: gridTransform,
             uGridDim: gridDimension,
             dRenderMode: ValueCell.create(props.renderMode.name),
             tTransferTex: transferTex,
+            uTransferScale: ValueCell.create(getTransferScale(props.stepsPerCell)),
 
             dGridTexType: ValueCell.create(gridTexture.ref.value.getDepth() > 0 ? '3d' : '2d'),
             uGridTexDim: gridTextureDim,
@@ -312,9 +325,10 @@ export namespace DirectVolume {
             createTransferFunctionTexture(controlPoints, props.renderMode.params.list.colors, values.tTransferTex);
         }
 
-        const maxSteps = Math.ceil(Vec3.magnitude(values.uGridDim.ref.value) * props.stepsPerCell);
-        ValueCell.updateIfChanged(values.uMaxSteps, maxSteps);
-        ValueCell.updateIfChanged(values.uStepFactor, 1 / props.stepsPerCell);
+        ValueCell.updateIfChanged(values.uMaxSteps, getMaxSteps(values.uGridDim.ref.value, props.stepsPerCell));
+        ValueCell.updateIfChanged(values.uStepScale, getStepScale(values.uCellDim.ref.value, props.stepsPerCell));
+        ValueCell.updateIfChanged(values.uTransferScale, getTransferScale(props.stepsPerCell));
+        ValueCell.updateIfChanged(values.uJumpLength, props.jumpLength);
     }
 
     function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {

+ 3 - 1
src/mol-gl/renderable/direct-volume.ts

@@ -66,12 +66,14 @@ export const DirectVolumeSchema = {
     uBboxMax: UniformSpec('v3'),
     uBboxSize: UniformSpec('v3'),
     uMaxSteps: UniformSpec('i'),
-    uStepFactor: UniformSpec('f'),
+    uStepScale: UniformSpec('f'),
+    uJumpLength: UniformSpec('f'),
     uTransform: UniformSpec('m4'),
     uGridDim: UniformSpec('v3'),
     dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
     dSingleLayer: DefineSpec('boolean'),
     tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'),
+    uTransferScale: UniformSpec('f'),
 
     dGridTexType: DefineSpec('string', ['2d', '3d']),
     uGridTexDim: UniformSpec('v3'),

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

@@ -267,7 +267,7 @@ namespace Renderer {
 
             if (depthTexture) program.bindTextures([['tDepth', depthTexture]]);
 
-            if (r.values.uStepFactor) { // indicates direct-volume
+            if (r.values.dRenderMode) { // indicates direct-volume
                 // always cull front
                 state.enable(gl.CULL_FACE);
                 state.frontFace(gl.CW);
@@ -402,7 +402,7 @@ namespace Renderer {
                 }
             }
 
-            gl.finish();
+            gl.flush();
         };
 
         return {

+ 86 - 58
src/mol-gl/shader/direct-volume.frag.ts

@@ -36,13 +36,16 @@ uniform float uFar;
 varying vec3 vOrigPos;
 varying float vInstance;
 varying vec4 vBoundingSphere;
+varying mat4 vTransform;
 
 uniform mat4 uInvView;
 uniform vec2 uIsoValue;
 uniform vec3 uGridDim;
 uniform vec3 uBboxSize;
 uniform sampler2D tTransferTex;
-uniform float uStepFactor;
+uniform float uTransferScale;
+uniform float uStepScale;
+uniform float uJumpLength;
 
 uniform int uObjectId;
 uniform int uVertexCount;
@@ -72,7 +75,6 @@ uniform float uIsOrtho;
 uniform vec3 uCellDim;
 uniform vec3 uCameraPosition;
 uniform mat4 uCartnToUnit;
-uniform mat4 uUnitToCartn;
 
 #if __VERSION__ == 300
     // for webgl1 this is given as a 'define'
@@ -139,15 +141,23 @@ float getDepth(const in vec2 coords) {
 
 const float gradOffset = 0.5;
 
-vec3 toUnit(vec3 p) {
-    return (uCartnToUnit * vec4(p, 1.0)).xyz;
+vec3 v3m4(vec3 p, mat4 m) {
+    return (m * vec4(p, 1.0)).xyz;
 }
 
-vec4 raymarch(vec3 startLoc, vec3 step) {
+vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
+    #if defined(dRenderVariant_color) && !defined(dIgnoreLight)
+        mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
+    #endif
+    mat4 cartnToUnit = uCartnToUnit * inverse4(vTransform);
+    #if defined(dClipVariant_pixel) && dClipObjectCount != 0
+        mat4 modelTransform = uModel * vTransform * uTransform;
+    #endif
+    mat4 modelViewTransform = uModelView * vTransform * uTransform;
+
     vec3 scaleVol = vec3(1.0) / uGridDim;
     vec3 pos = startLoc;
     vec4 cell;
-    vec4 prevCell = vec4(-1);
     float prevValue = -1.0;
     float value = 0.0;
     vec4 src = vec4(0.0);
@@ -160,18 +170,31 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
     vec3 unitPos;
     vec3 isoPos;
 
+    vec3 nextPos;
+    float nextValue;
+
     vec3 color = vec3(0.45, 0.55, 0.8);
     vec3 gradient = vec3(1.0);
     vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
     vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0);
     vec3 dz = vec3(0.0, 0.0, gradOffset * scaleVol.z);
 
-    for(int i = 0; i < uMaxSteps; ++i){
-        unitPos = toUnit(pos);
-        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) {
+    float maxDist = min(vBoundingSphere.w * 2.0, uFar - uNear);
+    float maxDistSq = maxDist * maxDist;
+
+    for (int i = 0; i < uMaxSteps; ++i) {
+        // break when beyond bounding-sphere or far-plane
+        vec3 distVec = startLoc - pos;
+        if (dot(distVec, distVec) > maxDistSq) break;
+
+        unitPos = v3m4(pos, cartnToUnit);
+
+        // continue when outside of grid
+        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;
-            prevCell = cell;
             pos += step;
             continue;
         }
@@ -179,20 +202,29 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
         cell = textureVal(unitPos);
         value = cell.a; // current voxel value
 
+        if (uJumpLength > 0.0 && value < 0.01) {
+            nextPos = pos + rayDir * uJumpLength;
+            nextValue = textureVal(v3m4(nextPos, cartnToUnit)).a;
+            if (nextValue < 0.01) {
+                prevValue = nextValue;
+                pos = nextPos;
+                continue;
+            }
+        }
+
         #if defined(dRenderMode_isosurface)
-            if(prevValue > 0.0 && ( // there was a prev Value
+            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 = toUnit(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)))));
+                isoPos = v3m4(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)))), cartnToUnit);
 
-                vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0);
+                vec4 mvPosition = modelViewTransform * vec4(isoPos * uGridDim, 1.0);
 
                 #if defined(dClipVariant_pixel) && dClipObjectCount != 0
-                    vec3 vModelPosition = (uModel * uTransform * vec4(isoPos * uGridDim, 1.0)).xyz;
+                    vec3 vModelPosition = v3m4(isoPos * uGridDim, modelTransform);
                     if (clipTest(vec4(vModelPosition, 0.0), 0)) {
                         prevValue = value;
-                        prevCell = cell;
                         pos += step;
                         continue;
                     }
@@ -258,7 +290,6 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                     #ifndef dDoubleSided
                         if (interior) {
                             prevValue = value;
-                            prevCell = cell;
                             pos += step;
                             continue;
                         }
@@ -281,7 +312,6 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                         #else
                             gradient = textureVal(isoPos).xyz * 2.0 - 1.0;
                         #endif
-                        mat3 normalMatrix = transpose3(inverse3(mat3(uModelView)));
                         vec3 normal = -normalize(normalMatrix * normalize(gradient));
                         normal = normal * (float(flipped) * 2.0 - 1.0);
                         normal = normal * -(float(interior) * 2.0 - 1.0);
@@ -305,57 +335,60 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
             }
             prevValue = value;
         #elif defined(dRenderMode_volume)
-            isoPos = toUnit(pos);
-            vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0);
+            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 = (uModel * uTransform * vec4(isoPos * uGridDim, 1.0)).xyz;
+                vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
                 if (clipTest(vec4(vModelPosition, 0.0), 0)) {
                     prevValue = value;
-                    prevCell = cell;
                     pos += step;
                     continue;
                 }
             #endif
 
-            vec3 vViewPosition = mvPosition.xyz;
-            vec4 material = transferFunction(value);
+            #if defined(dRenderVariant_color)
+                vec3 vViewPosition = mvPosition.xyz;
+                vec4 material = transferFunction(value);
 
-            if (material.a >= 0.01) {
-                #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;
+                #ifdef dIgnoreLight
+                    gl_FragColor.rgb = material.rgb;
                 #else
-                    gradient = cell.xyz * 2.0 - 1.0;
+                    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;
+                    }
                 #endif
-                mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * uUnitToCartn)));
-                vec3 normal = -normalize(normalMatrix * normalize(gradient));
-                #include apply_light_color
-            } else {
-                gl_FragColor.rgb = material.rgb;
-            }
 
-            gl_FragColor.a = material.a * uAlpha;
+                gl_FragColor.a = material.a * uAlpha * uTransferScale;
 
-            #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
+                #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
 
-            float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
-            #include apply_marker_color
-            #include apply_fog
+                float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+                #include apply_marker_color
+                #include apply_fog
 
-            src = gl_FragColor;
+                src = gl_FragColor;
 
-            src.rgb *= src.a;
-            dst = (1.0 - dst.a) * src + dst; // standard blending
+                src.rgb *= src.a;
+                dst = (1.0 - dst.a) * src + dst; // standard blending
+            #endif
         #endif
 
         // break if the color is opaque enough
@@ -374,10 +407,8 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
     return dst;
 }
 
-// TODO: calculate normalMatrix on CPU
 // TODO: support float texture for higher precision values???
 // TODO: support clipping exclusion texture support
-// TODO: support instance transforms
 
 void main () {
     #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
@@ -390,15 +421,12 @@ void main () {
         #endif
     #endif
 
-    vec3 rayDir = mix(normalize(vOrigPos - uCameraPosition), uCameraDir, uIsOrtho);;
-
-    // TODO: set the scale as uniform?
-    float stepScale = min(uCellDim.x, min(uCellDim.y, uCellDim.z)) * uStepFactor;
-    vec3 step = rayDir * stepScale;
+    vec3 rayDir = mix(normalize(vOrigPos - uCameraPosition), uCameraDir, uIsOrtho);
+    vec3 step = rayDir * uStepScale;
 
     float boundingSphereNear = distance(vBoundingSphere.xyz, uCameraPosition) - vBoundingSphere.w;
-    float d = max(uNear, boundingSphereNear) - distance(vOrigPos, uCameraPosition);
-    gl_FragColor = raymarch(vOrigPos + (d * rayDir), step);
+    float d = max(uNear, boundingSphereNear);
+    gl_FragColor = raymarch(uCameraPosition + (d * rayDir), step, rayDir);
 
     #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
         // discard when nothing was hit

+ 9 - 5
src/mol-gl/shader/direct-volume.vert.ts

@@ -19,6 +19,7 @@ uniform vec4 uInvariantBoundingSphere;
 varying vec3 vOrigPos;
 varying float vInstance;
 varying vec4 vBoundingSphere;
+varying mat4 vTransform;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -27,15 +28,18 @@ uniform vec3 uGridDim;
 uniform mat4 uTransform;
 
 uniform mat4 uUnitToCartn;
-uniform mat4 uCartnToUnit;
 
 void main() {
-    vec3 unitCoord = aPosition + vec3(0.5);
-    vec4 mvPosition = uModelView * uUnitToCartn * vec4(unitCoord, 1.0);
+    vec4 unitCoord = vec4(aPosition + vec3(0.5), 1.0);
+    vec4 mvPosition = uModelView * aTransform * uUnitToCartn * unitCoord;
 
-    vOrigPos = (uUnitToCartn * vec4(unitCoord, 1.0)).xyz;
+    vOrigPos = (aTransform * uUnitToCartn * unitCoord).xyz;
     vInstance = aInstance;
-    vBoundingSphere = uInvariantBoundingSphere;
+    vBoundingSphere = vec4(
+        (aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0)).xyz,
+        uInvariantBoundingSphere.w
+    );
+    vTransform = aTransform;
 
     gl_Position = uProjection * mvPosition;
 

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

@@ -29,7 +29,7 @@ void main() {
     #if defined(dCalcType_groupId)
         vGroup = aGroup;
     #endif
-    gl_PointSize = floor(((aRadius * 4.0) / uResolution) + 0.5);
+    gl_PointSize = floor(((aRadius * 6.0) / uResolution) + 0.5);
     vPosition = (aPosition - uBboxMin) / uResolution;
     gl_Position = vec4(((aPosition - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
 }

+ 2 - 2
src/mol-math/geometry/boundary-helper.ts

@@ -46,7 +46,7 @@ export class BoundaryHelper {
     }
 
     includeSphere(s: Sphere3D) {
-        if (Sphere3D.hasExtrema(s)) {
+        if (Sphere3D.hasExtrema(s) && s.extrema.length > 1) {
             for (const e of s.extrema) {
                 this.includePosition(e);
             }
@@ -75,7 +75,7 @@ export class BoundaryHelper {
     }
 
     radiusSphere(s: Sphere3D) {
-        if (Sphere3D.hasExtrema(s)) {
+        if (Sphere3D.hasExtrema(s) && s.extrema.length > 1) {
             for (const e of s.extrema) {
                 this.radiusPosition(e);
             }

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

@@ -119,6 +119,7 @@ type GaussianDensityTextureData = {
 
 function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
     // console.log('2d');
+    const { gl, resources, state, extensions } = webgl;
     const { smoothness, resolution } = props;
 
     const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
@@ -136,7 +137,6 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
 
     //
 
-    const { gl, resources, state, extensions } = webgl;
     const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values;
 
     const framebuffer = getFramebuffer(webgl);
@@ -172,7 +172,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
             ++currCol;
             currX += dx;
         }
-        gl.finish();
+        gl.flush();
     }
 
     setupDensityRendering(webgl, renderable);
@@ -227,7 +227,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
             if (clear) gl.clear(gl.COLOR_BUFFER_BIT);
             renderable.render();
         }
-        gl.finish();
+        gl.flush();
     }
 
     setupDensityRendering(webgl, renderable);

+ 4 - 0
src/mol-math/geometry/primitives/sphere3d.ts

@@ -197,6 +197,10 @@ namespace Sphere3D {
     export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
         Vec3.copy(out.center, sphere.center);
         out.radius = sphere.radius + delta;
+        if (sphere.radius < 1e-12 || (sphere.extrema?.length ?? 0) <= 1) {
+            out.extrema = void 0;
+            return out;
+        }
         if (hasExtrema(sphere)) {
             setExtrema(out, sphere.extrema.map(e => {
                 Vec3.sub(tmpDir, e, sphere.center);

+ 7 - 3
src/mol-plugin/util/viewport-screenshot.ts

@@ -90,11 +90,15 @@ class ViewportScreenshotHelper {
     get imagePass() {
         if (this._imagePass) return this._imagePass;
 
-        this._imagePass = this.plugin.canvas3d!.getImagePass({
+        const c = this.plugin.canvas3d!;
+        this._imagePass = c.getImagePass({
             transparentBackground: this.transparent,
             cameraHelper: { axes: this.axes },
-            multiSample: { mode: 'on', sampleLevel: 2 },
-            postprocessing: this.plugin.canvas3d!.props.postprocessing
+            multiSample: {
+                mode: 'on',
+                sampleLevel: c.webgl.extensions.colorBufferFloat ? 4 : 2
+            },
+            postprocessing: c.props.postprocessing
         });
         return this._imagePass;
     }

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

@@ -86,7 +86,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
 
         VisualUpdateState.reset(updateState);
 
-        if (!renderObject || !currentStructure || !Structure.areEquivalent(newStructure, currentStructure)) {
+        if (!renderObject || !currentStructure) {
             updateState.createNew = true;
             updateState.createGeometry = true;
             return;
@@ -94,6 +94,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
 
         setUpdateState(updateState, newProps, currentProps, newTheme, currentTheme, newStructure, currentStructure);
 
+        if (!Structure.areEquivalent(newStructure, currentStructure)) {
+            updateState.createGeometry = true;
+        }
+
         if (!Structure.areHierarchiesEqual(newStructure, currentStructure)) {
             updateState.updateTransform = true;
             updateState.createGeometry = true;

+ 7 - 4
src/mol-repr/structure/representation/gaussian-volume.ts

@@ -1,23 +1,25 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { GaussianDensityVolumeParams, GaussianDensityVolumeVisual } from '../visual/gaussian-density-volume';
-import { StructureRepresentation, StructureRepresentationProvider, ComplexRepresentation, StructureRepresentationStateBuilder } from '../representation';
+import { GaussianDensityVolumeParams, GaussianDensityVolumeVisual, UnitsGaussianDensityVolumeParams, UnitsGaussianDensityVolumeVisual } from '../visual/gaussian-density-volume';
+import { StructureRepresentation, StructureRepresentationProvider, ComplexRepresentation, StructureRepresentationStateBuilder, UnitsRepresentation } from '../representation';
 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)
+    'gaussian-volume': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianDensityVolumeParams>) => ComplexRepresentation('Gaussian volume', ctx, getParams, GaussianDensityVolumeVisual),
+    'units-gaussian-volume': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, UnitsGaussianDensityVolumeParams>) => UnitsRepresentation('Units-Gaussian volume', ctx, getParams, UnitsGaussianDensityVolumeVisual)
 };
 
 export const GaussianVolumeParams = {
     ...GaussianDensityVolumeParams,
+    visuals: PD.MultiSelect(['gaussian-volume'], PD.objectToOptions(GaussianVolumeVisuals)),
 };
 export type GaussianVolumeParams = typeof GaussianVolumeParams
 export function getGaussianVolumeParams(ctx: ThemeRegistryContext, structure: Structure) {
@@ -26,6 +28,7 @@ export function getGaussianVolumeParams(ctx: ThemeRegistryContext, structure: St
         // 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;
 }
 

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

@@ -6,15 +6,17 @@
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { VisualContext } from '../../visual';
-import { Structure } from '../../../mol-model/structure';
+import { Structure, Unit } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
-import { GaussianDensityTextureProps, computeStructureGaussianDensityTexture, GaussianDensityTextureParams } from './util/gaussian';
+import { GaussianDensityTextureProps, computeStructureGaussianDensityTexture, computeUnitGaussianDensityTexture, GaussianDensityTextureParams } from './util/gaussian';
 import { DirectVolume } from '../../../mol-geo/geometry/direct-volume/direct-volume';
 import { ComplexDirectVolumeParams, ComplexVisual, ComplexDirectVolumeVisual } from '../complex-visual';
 import { VisualUpdateState } from '../../util';
 import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
-import { eachSerialElement, ElementIterator, getSerialElementLoci } from './util/element';
+import { eachElement, eachSerialElement, ElementIterator, getElementLoci, getSerialElementLoci } from './util/element';
 import { Sphere3D } from '../../../mol-math/geometry';
+import { UnitsDirectVolumeParams, UnitsVisual, UnitsDirectVolumeVisual } from '../units-visual';
+import { getStructureExtraRadius, getUnitExtraRadius } from './util/common';
 
 async function createGaussianDensityVolume(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, directVolume?: DirectVolume): Promise<DirectVolume> {
     const { runtime, webgl } = ctx;
@@ -27,11 +29,11 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct
     const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 };
 
     const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
-    const cellDim = Vec3.create(1, 1, 1);
+    const cellDim = Mat4.getScaling(Vec3(), transform);
 
     const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume);
 
-    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset);
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     vol.setBoundingSphere(sphere);
 
     return vol;
@@ -60,4 +62,52 @@ export function GaussianDensityVolumeVisual(materialId: number): ComplexVisual<G
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
         }
     }, materialId);
+}
+
+//
+
+async function createUnitsGaussianDensityVolume(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, directVolume?: DirectVolume): Promise<DirectVolume> {
+    const { runtime, webgl } = ctx;
+    if (webgl === undefined) throw new Error('createUnitGaussianDensityVolume requires `webgl` object in VisualContext');
+
+    const p = { ...props, useGpu: true };
+    const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
+    const densityTextureData = await computeUnitGaussianDensityTexture(structure, unit, p, 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 sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getUnitExtraRadius(unit));
+    vol.setBoundingSphere(sphere);
+
+    return vol;
+}
+
+export const UnitsGaussianDensityVolumeParams = {
+    ...UnitsDirectVolumeParams,
+    ...GaussianDensityTextureParams,
+    ignoreHydrogens: PD.Boolean(false),
+};
+export type UnitsGaussianDensityVolumeParams = typeof UnitsGaussianDensityVolumeParams
+
+export function UnitsGaussianDensityVolumeVisual(materialId: number): UnitsVisual<UnitsGaussianDensityVolumeParams> {
+    return UnitsDirectVolumeVisual<UnitsGaussianDensityVolumeParams>({
+        defaultProps: PD.getDefaultValues(UnitsGaussianDensityVolumeParams),
+        createGeometry: createUnitsGaussianDensityVolume,
+        createLocationIterator: ElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        eachLocation: eachElement,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<GaussianDensityVolumeParams>, currentProps: PD.Values<GaussianDensityVolumeParams>) => {
+            if (newProps.resolution !== currentProps.resolution) state.createGeometry = true;
+            if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true;
+            if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true;
+            if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
+            if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
+            if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
+        }
+    }, materialId);
 }

+ 4 - 3
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -1,12 +1,12 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual } from '../units-visual';
-import { GaussianDensityParams, computeUnitGaussianDensity, GaussianDensityTextureProps, computeUnitGaussianDensityTexture2d, GaussianDensityProps, computeStructureGaussianDensity, getUnitExtraRadius } from './util/gaussian';
+import { GaussianDensityParams, computeUnitGaussianDensity, GaussianDensityTextureProps, computeUnitGaussianDensityTexture2d, GaussianDensityProps, computeStructureGaussianDensity } from './util/gaussian';
 import { VisualContext } from '../../visual';
 import { Unit, Structure } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
@@ -20,6 +20,7 @@ import { createHistogramPyramid } from '../../../mol-gl/compute/histogram-pyrami
 import { createIsosurfaceBuffers } from '../../../mol-gl/compute/marching-cubes/isosurface';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { ComplexVisual, ComplexMeshParams, ComplexMeshVisual } from '../complex-visual';
+import { getUnitExtraRadius, getStructureExtraRadius } from './util/common';
 
 export const GaussianSurfaceMeshParams = {
     ...UnitsMeshParams,
@@ -92,7 +93,7 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure:
     Mesh.transform(surface, transform);
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
 
-    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset);
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     surface.setBoundingSphere(sphere);
 
     return surface;

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,12 +9,13 @@ import { VisualContext } from '../../visual';
 import { Unit, Structure } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
 import { Lines } from '../../../mol-geo/geometry/lines/lines';
-import { computeUnitGaussianDensity, GaussianDensityParams, GaussianDensityProps, getUnitExtraRadius } from './util/gaussian';
+import { computeUnitGaussianDensity, GaussianDensityParams, GaussianDensityProps } from './util/gaussian';
 import { computeMarchingCubesLines } from '../../../mol-geo/util/marching-cubes/algorithm';
 import { UnitsLinesParams, UnitsVisual, UnitsLinesVisual } from '../units-visual';
 import { ElementIterator, getElementLoci, eachElement } from './util/element';
 import { VisualUpdateState } from '../../util';
 import { Sphere3D } from '../../../mol-math/geometry';
+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;

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

@@ -15,7 +15,7 @@ import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molec
 import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/algorithm';
 import { ElementIterator, getElementLoci, eachElement } from './util/element';
 import { VisualUpdateState } from '../../util';
-import { CommonSurfaceParams } from './util/common';
+import { CommonSurfaceParams, getUnitExtraRadius } from './util/common';
 import { Sphere3D } from '../../../mol-math/geometry';
 
 export const MolecularSurfaceMeshParams = {
@@ -39,7 +39,7 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
     Mesh.transform(surface, transform);
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
 
-    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius);
+    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius + getUnitExtraRadius(unit));
     surface.setBoundingSphere(sphere);
 
     return surface;

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

@@ -15,7 +15,7 @@ import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molec
 import { computeMarchingCubesLines } from '../../../mol-geo/util/marching-cubes/algorithm';
 import { ElementIterator, getElementLoci, eachElement } from './util/element';
 import { VisualUpdateState } from '../../util';
-import { CommonSurfaceParams } from './util/common';
+import { CommonSurfaceParams, getUnitExtraRadius } from './util/common';
 import { Sphere3D } from '../../../mol-math/geometry';
 
 export const MolecularSurfaceWireframeParams = {
@@ -39,7 +39,7 @@ async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, s
 
     Lines.transform(wireframe, transform);
 
-    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius);
+    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius + getUnitExtraRadius(unit));
     wireframe.setBoundingSphere(sphere);
 
     return wireframe;

+ 22 - 0
src/mol-repr/structure/visual/util/common.ts

@@ -280,4 +280,26 @@ export function isTrace(unit: Unit, element: ElementIndex) {
     const atomId = unit.model.atomicHierarchy.atoms.label_atom_id.value(element);
     if (atomId === 'CA' || atomId === 'P') return true;
     return false;
+}
+
+export function getUnitExtraRadius(unit: Unit) {
+    if (Unit.isAtomic(unit)) return 4;
+
+    let max = 0;
+    const { elements } = unit;
+    const { r } = unit.conformation;
+    for (let i = 0, _i = elements.length; i < _i; i++) {
+        const _r = r(elements[i]);
+        if (_r > max) max = _r;
+    }
+    return max + 1;
+}
+
+export function getStructureExtraRadius(structure: Structure) {
+    let max = 0;
+    for (const ug of structure.unitSymmetryGroups) {
+        const r = getUnitExtraRadius(ug.units[0]);
+        if (r > max) max = r;
+    }
+    return max;
 }

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

@@ -82,16 +82,3 @@ export function computeStructureGaussianDensityTexture(structure: Structure, pro
         return GaussianDensityTexture(webgl, position, box, radius, p, texture);
     });
 }
-
-export function getUnitExtraRadius(unit: Unit) {
-    if (Unit.isAtomic(unit)) return 4;
-
-    let max = 0;
-    const { elements } = unit;
-    const { r } = unit.conformation;
-    for (let i = 0, _i = elements.length; i < _i; i++) {
-        const _r = r(elements[i]);
-        if (_r > max) max = _r;
-    }
-    return max + 1;
-}

+ 6 - 5
src/mol-repr/volume/direct-volume.ts

@@ -169,12 +169,12 @@ function createVolumeTexture3d(volume: Volume) {
 
 function getUnitToCartn(grid: Grid) {
     if (grid.transform.kind === 'matrix') {
-        // TODO:
         return {
             unitToCartn: Mat4.mul(Mat4(),
-                Grid.getGridToCartesianTransform(grid),
-                Mat4.fromScaling(Mat4(), grid.cells.space.dimensions as Vec3)),
-            cellDim: Vec3.create(1, 1, 1)
+                grid.transform.matrix,
+                Mat4.fromScaling(Mat4(), grid.cells.space.dimensions as Vec3)
+            ),
+            cellDim: Mat4.getScaling(Vec3(), grid.transform.matrix)
         };
     }
     const box = grid.transform.fractionalBox;
@@ -183,7 +183,8 @@ function getUnitToCartn(grid: Grid) {
         unitToCartn: Mat4.mul3(Mat4(),
             grid.transform.cell.fromFractional,
             Mat4.fromTranslation(Mat4(), box.min),
-            Mat4.fromScaling(Mat4(), size)),
+            Mat4.fromScaling(Mat4(), size)
+        ),
         cellDim: Vec3.div(Vec3(), grid.transform.cell.size, grid.cells.space.dimensions as Vec3)
     };
 }