Browse Source

wip, direct-volume rendering

- fix cellDim uniform
- add per unit gaussian-volume
- add option to try to jump over empty space
- fix complex structure visual not reusing renderObject
- add support for instance transforms
- only raymarch within intersection of bounding sphere a clip planes
Alexander Rose 4 years ago
parent
commit
2e3bff7d48

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

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

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