Browse Source

Merge pull request #645 from molstar/structure-mol-surf

add structure molecular surface visual
Alexander Rose 2 years ago
parent
commit
509fb14473

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@ Note that since we don't clearly distinguish between a public and private interf
 
 - Excluded common protein caps `NME` and `ACE` from the ligand selection query
 - Add screen-space shadow post-processing effect
+- Add "Structure Molecular Surface" visual
 - Add `external-volume` theme (coloring of arbitrary geometries by user-selected volume)
 
 ## [v3.25.1] - 2022-11-20

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

@@ -1,13 +1,13 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { MolecularSurfaceMeshVisual, MolecularSurfaceMeshParams } from '../visual/molecular-surface-mesh';
+import { MolecularSurfaceMeshVisual, MolecularSurfaceMeshParams, StructureMolecularSurfaceMeshVisual } from '../visual/molecular-surface-mesh';
 import { UnitsRepresentation } from '../units-representation';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation';
+import { ComplexRepresentation, StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation';
 import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
@@ -16,6 +16,7 @@ import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const MolecularSurfaceVisuals = {
     'molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => UnitsRepresentation('Molecular surface mesh', ctx, getParams, MolecularSurfaceMeshVisual),
+    'structure-molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => ComplexRepresentation('Structure Molecular surface mesh', ctx, getParams, StructureMolecularSurfaceMeshVisual),
     'molecular-surface-wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceWireframeParams>) => UnitsRepresentation('Molecular surface wireframe', ctx, getParams, MolecularSurfaceWireframeVisual),
 };
 

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

@@ -11,9 +11,9 @@ import { VisualContext } from '../../visual';
 import { Unit, Structure } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
-import { computeUnitMolecularSurface } from './util/molecular-surface';
+import { computeStructureMolecularSurface, computeUnitMolecularSurface } from './util/molecular-surface';
 import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/algorithm';
-import { ElementIterator, getElementLoci, eachElement } from './util/element';
+import { ElementIterator, getElementLoci, eachElement, getSerialElementLoci, eachSerialElement } from './util/element';
 import { VisualUpdateState } from '../../util';
 import { CommonSurfaceParams } from './util/common';
 import { Sphere3D } from '../../../mol-math/geometry';
@@ -23,6 +23,7 @@ import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
 import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 import { ValueCell } from '../../../mol-util';
+import { ComplexMeshVisual, ComplexVisual } from '../complex-visual';
 
 export const MolecularSurfaceMeshParams = {
     ...UnitsMeshParams,
@@ -104,4 +105,72 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
             (geometry.meta as MolecularSurfaceMeta).colorTexture?.destroy();
         }
     }, materialId);
+}
+
+//
+
+async function createStructureMolecularSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: MolecularSurfaceMeshProps, mesh?: Mesh): Promise<Mesh> {
+    const { transform, field, idField, resolution, maxRadius } = await computeStructureMolecularSurface(structure, theme.size, props).runInContext(ctx.runtime);
+
+    const params = {
+        isoLevel: props.probeRadius,
+        scalarField: field,
+        idField
+    };
+    const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
+
+    if (props.includeParent) {
+        const iterations = Math.ceil(2 / props.resolution);
+        Mesh.smoothEdges(surface, { iterations, maxNewEdgeLength: Math.sqrt(2) });
+    }
+
+    Mesh.transform(surface, transform);
+    if (ctx.webgl && !ctx.webgl.isWebGL2) {
+        Mesh.uniformTriangleGroup(surface);
+        ValueCell.updateIfChanged(surface.varyingGroup, false);
+    } else {
+        ValueCell.updateIfChanged(surface.varyingGroup, true);
+    }
+
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, maxRadius);
+    surface.setBoundingSphere(sphere);
+    (surface.meta as MolecularSurfaceMeta).resolution = resolution;
+
+    return surface;
+}
+
+export function StructureMolecularSurfaceMeshVisual(materialId: number): ComplexVisual<MolecularSurfaceMeshParams> {
+    return ComplexMeshVisual<MolecularSurfaceMeshParams>({
+        defaultProps: PD.getDefaultValues(MolecularSurfaceMeshParams),
+        createGeometry: createStructureMolecularSurfaceMesh,
+        createLocationIterator: ElementIterator.fromStructure,
+        getLoci: getSerialElementLoci,
+        eachLocation: eachSerialElement,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<MolecularSurfaceMeshParams>, currentProps: PD.Values<MolecularSurfaceMeshParams>) => {
+            if (newProps.resolution !== currentProps.resolution) state.createGeometry = true;
+            if (newProps.probeRadius !== currentProps.probeRadius) state.createGeometry = true;
+            if (newProps.probePositions !== currentProps.probePositions) 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;
+
+            if (newProps.smoothColors.name !== currentProps.smoothColors.name) {
+                state.updateColor = true;
+            } else if (newProps.smoothColors.name === 'on' && currentProps.smoothColors.name === 'on') {
+                if (newProps.smoothColors.params.resolutionFactor !== currentProps.smoothColors.params.resolutionFactor) state.updateColor = true;
+                if (newProps.smoothColors.params.sampleStride !== currentProps.smoothColors.params.sampleStride) state.updateColor = true;
+            }
+        },
+        processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
+            const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta;
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
+            if (csp) {
+                applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
+                (geometry.meta as MolecularSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
+            }
+        },
+        dispose: (geometry: Mesh) => {
+            (geometry.meta as MolecularSurfaceMeta).colorTexture?.destroy();
+        }
+    }, materialId);
 }

+ 48 - 21
src/mol-repr/structure/visual/util/common.ts

@@ -151,7 +151,7 @@ function squaredDistance(x: number, y: number, z: number, center: Vec3) {
 }
 
 /** marks `indices` for filtering/ignoring in `id` when not in `elements` */
-function filterId(id: AssignableArrayLike<number>, elements: SortedArray, indices: SortedArray) {
+function filterUnitId(id: AssignableArrayLike<number>, elements: SortedArray, indices: SortedArray) {
     let start = 0;
     const end = elements.length;
     for (let i = 0, il = indices.length; i < il; ++i) {
@@ -168,24 +168,25 @@ function filterId(id: AssignableArrayLike<number>, elements: SortedArray, indice
 export function getUnitConformationAndRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: CommonSurfaceProps) {
     const { ignoreHydrogens, traceOnly, includeParent } = props;
     const rootUnit = includeParent ? structure.root.unitMap.get(unit.id) : unit;
+    const differentRoot = includeParent && rootUnit !== unit;
 
     const { x, y, z } = getConformation(rootUnit);
     const { elements } = rootUnit;
     const { center, radius: sphereRadius } = unit.boundary.sphere;
-    const extraRadius = (2 + 1.5) * 2; // TODO should be twice (the max vdW/sphere radius plus the probe radius)
+    const extraRadius = (4 + 1.5) * 2; // TODO should be twice (the max vdW/sphere radius plus the probe radius)
     const radiusSq = (sphereRadius + extraRadius) * (sphereRadius + extraRadius);
 
     let indices: SortedArray<ElementIndex>;
     let id: AssignableArrayLike<number>;
 
-    if (ignoreHydrogens || traceOnly || (includeParent && rootUnit !== unit)) {
-        const _indices = [];
-        const _id = [];
+    if (ignoreHydrogens || traceOnly || differentRoot) {
+        const _indices: number[] = [];
+        const _id: number[] = [];
         for (let i = 0, il = elements.length; i < il; ++i) {
             const eI = elements[i];
             if (ignoreHydrogens && isHydrogen(rootUnit, eI)) continue;
             if (traceOnly && !isTrace(rootUnit, eI)) continue;
-            if (includeParent && squaredDistance(x[eI], y[eI], z[eI], center) > radiusSq) continue;
+            if (differentRoot && squaredDistance(x[eI], y[eI], z[eI], center) > radiusSq) continue;
 
             _indices.push(eI);
             _id.push(i);
@@ -198,11 +199,11 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, s
     }
 
     if (includeParent && rootUnit !== unit) {
-        filterId(id, unit.elements, indices);
+        filterUnitId(id, unit.elements, indices);
     }
 
     const position = { indices, x, y, z, id };
-    const boundary = unit === rootUnit ? unit.boundary : getBoundary(position);
+    const boundary = differentRoot ? getBoundary(position) : unit.boundary;
 
     const l = StructureElement.Location.create(structure, rootUnit);
     const radius = (index: number) => {
@@ -213,25 +214,36 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, s
     return { position, boundary, radius };
 }
 
-export function getStructureConformationAndRadius(structure: Structure, sizeTheme: SizeTheme<any>, ignoreHydrogens: boolean, traceOnly: boolean) {
-    const l = StructureElement.Location.create(structure);
+export function getStructureConformationAndRadius(structure: Structure, sizeTheme: SizeTheme<any>, props: CommonSurfaceProps) {
+    const { ignoreHydrogens, traceOnly, includeParent } = props;
+    const differentRoot = includeParent && !!structure.parent;
+    const l = StructureElement.Location.create(structure.root);
+
+    const { center, radius: sphereRadius } = structure.boundary.sphere;
+    const extraRadius = (4 + 1.5) * 2; // TODO should be twice (the max vdW/sphere radius plus the probe radius)
+    const radiusSq = (sphereRadius + extraRadius) * (sphereRadius + extraRadius);
 
     let xs: ArrayLike<number>;
     let ys: ArrayLike<number>;
     let zs: ArrayLike<number>;
     let rs: ArrayLike<number>;
-    let id: ArrayLike<number>;
+    let id: AssignableArrayLike<number>;
+    let indices: OrderedSet<number>;
+
+    if (ignoreHydrogens || traceOnly || differentRoot) {
+        const { getSerialIndex } = structure.serialMapping;
+        const units = differentRoot ? structure.root.units : structure.units;
 
-    if (ignoreHydrogens || traceOnly) {
         const _xs: number[] = [];
         const _ys: number[] = [];
         const _zs: number[] = [];
         const _rs: number[] = [];
         const _id: number[] = [];
-        for (let i = 0, m = 0, il = structure.units.length; i < il; ++i) {
-            const unit = structure.units[i];
+        for (let i = 0, il = units.length; i < il; ++i) {
+            const unit = units[i];
             const { elements } = unit;
             const { x, y, z } = unit.conformation;
+            const childUnit = structure.unitMap.get(unit.id);
 
             l.unit = unit;
             for (let j = 0, jl = elements.length; j < jl; ++j) {
@@ -239,17 +251,30 @@ export function getStructureConformationAndRadius(structure: Structure, sizeThem
                 if (ignoreHydrogens && isHydrogen(unit, eI)) continue;
                 if (traceOnly && !isTrace(unit, eI)) continue;
 
-                _xs.push(x(eI));
-                _ys.push(y(eI));
-                _zs.push(z(eI));
+                const _x = x(eI), _y = y(eI), _z = z(eI);
+                if (differentRoot && squaredDistance(_x, _y, _z, center) > radiusSq) continue;
+
+                _xs.push(_x);
+                _ys.push(_y);
+                _zs.push(_z);
                 l.element = eI;
                 _rs.push(sizeTheme.size(l));
-                _id.push(m + j);
+
+                if (differentRoot) {
+                    const idx = childUnit ? SortedArray.indexOf(childUnit.elements, eI) : -1;
+                    if (idx === -1) {
+                        _id.push(-2); // mark for filtering/ignoring when not in `elements`
+                    } else {
+                        _id.push(getSerialIndex(childUnit, eI));
+                    }
+                } else {
+                    _id.push(getSerialIndex(unit, eI));
+                }
             }
-            m += elements.length;
         }
         xs = _xs, ys = _ys, zs = _zs, rs = _rs;
         id = _id;
+        indices = OrderedSet.ofRange(0, id.length);
     } else {
         const { elementCount } = structure;
         const _xs = new Float32Array(elementCount);
@@ -275,12 +300,14 @@ export function getStructureConformationAndRadius(structure: Structure, sizeThem
         }
         xs = _xs, ys = _ys, zs = _zs, rs = _rs;
         id = fillSerial(new Uint32Array(elementCount));
+        indices = OrderedSet.ofRange(0, id.length);
     }
 
-    const position = { indices: OrderedSet.ofRange(0, id.length), x: xs, y: ys, z: zs, id };
+    const position = { indices, x: xs, y: ys, z: zs, id };
+    const boundary = differentRoot ? getBoundary(position) : structure.boundary;
     const radius = (index: number) => rs[index];
 
-    return { position, radius };
+    return { position, boundary, radius };
 }
 
 const _H = AtomicNumbers['H'];

+ 18 - 23
src/mol-repr/structure/visual/util/gaussian.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -34,56 +34,51 @@ export function getTextureMaxCells(webgl: WebGLContext, structure?: Structure) {
 //
 
 export function computeUnitGaussianDensity(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: GaussianDensityProps) {
-    const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props);
-    const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
+    const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props);
     return Task.create('Gaussian Density', async ctx => {
-        return await GaussianDensityCPU(ctx, position, box, radius, p);
+        return await GaussianDensityCPU(ctx, position, boundary.box, radius, p);
     });
 }
 
 export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
-    const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
-    const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
+    const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props, getTextureMaxCells(webgl, structure));
     return Task.create('Gaussian Density', async ctx => {
-        return GaussianDensityTexture(webgl, position, box, radius, p, texture);
+        return GaussianDensityTexture(webgl, position, boundary.box, radius, p, texture);
     });
 }
 
 export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
-    const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
-    const { position, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, p);
+    const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props, getTextureMaxCells(webgl, structure));
     return Task.create('Gaussian Density', async ctx => {
-        return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
+        return GaussianDensityTexture2d(webgl, position, boundary.box, radius, powerOfTwo, p, texture);
     });
 }
 
 //
 
 export function computeStructureGaussianDensity(structure: Structure, sizeTheme: SizeTheme<any>, props: GaussianDensityProps) {
-    const { box } = structure.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props);
-    const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
+    const { position, boundary, radius } = getStructureConformationAndRadius(structure, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props);
     return Task.create('Gaussian Density', async ctx => {
-        return await GaussianDensityCPU(ctx, position, box, radius, p);
+        return await GaussianDensityCPU(ctx, position, boundary.box, radius, p);
     });
 }
 
 export function computeStructureGaussianDensityTexture(structure: Structure, sizeTheme: SizeTheme<any>, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
-    const { box } = structure.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
-    const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
+    const { position, boundary, radius } = getStructureConformationAndRadius(structure, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props);
     return Task.create('Gaussian Density', async ctx => {
-        return GaussianDensityTexture(webgl, position, box, radius, p, texture);
+        return GaussianDensityTexture(webgl, position, boundary.box, radius, p, texture);
     });
 }
 
 export function computeStructureGaussianDensityTexture2d(structure: Structure, sizeTheme: SizeTheme<any>, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = structure.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl));
-    const { position, radius } = getStructureConformationAndRadius(structure, sizeTheme, props.ignoreHydrogens, props.traceOnly);
+    const { position, boundary, radius } = getStructureConformationAndRadius(structure, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
     });

+ 34 - 7
src/mol-repr/structure/visual/util/molecular-surface.ts

@@ -1,12 +1,12 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { Unit, Structure } from '../../../../mol-model/structure';
 import { Task, RuntimeContext } from '../../../../mol-task';
-import { getUnitConformationAndRadius, CommonSurfaceProps, ensureReasonableResolution } from './common';
+import { getUnitConformationAndRadius, CommonSurfaceProps, ensureReasonableResolution, getStructureConformationAndRadius } from './common';
 import { PositionData, DensityData, Box3D } from '../../../../mol-math/geometry';
 import { MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
 import { OrderedSet } from '../../../../mol-data/int';
@@ -15,7 +15,7 @@ import { SizeTheme } from '../../../../mol-theme/size';
 
 export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & CommonSurfaceProps
 
-function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
+function getUnitPositionDataAndMaxRadius(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
     const { probeRadius } = props;
     const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, sizeTheme, props);
     const { indices } = position;
@@ -34,11 +34,38 @@ function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, sizeTheme
 }
 
 export function computeUnitMolecularSurface(structure: Structure, unit: Unit, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
-    const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(box, props);
-    const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, sizeTheme, p);
+    const { position, boundary, maxRadius } = getUnitPositionDataAndMaxRadius(structure, unit, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props);
     return Task.create('Molecular Surface', async ctx => {
-        return await MolecularSurface(ctx, position, boundary, maxRadius, box, p);
+        return await MolecularSurface(ctx, position, boundary, maxRadius, boundary.box, p);
+    });
+}
+
+//
+
+function getStructurePositionDataAndMaxRadius(structure: Structure, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
+    const { probeRadius } = props;
+    const { position, boundary, radius } = getStructureConformationAndRadius(structure, sizeTheme, props);
+    const { indices } = position;
+    const n = OrderedSet.size(indices);
+    const radii = new Float32Array(OrderedSet.end(indices));
+
+    let maxRadius = 0;
+    for (let i = 0; i < n; ++i) {
+        const j = OrderedSet.getAt(indices, i);
+        const r = radius(j);
+        if (maxRadius < r) maxRadius = r;
+        radii[j] = r + probeRadius;
+    }
+
+    return { position: { ...position, radius: radii }, boundary, maxRadius };
+}
+
+export function computeStructureMolecularSurface(structure: Structure, sizeTheme: SizeTheme<any>, props: MolecularSurfaceProps) {
+    const { position, boundary, maxRadius } = getStructurePositionDataAndMaxRadius(structure, sizeTheme, props);
+    const p = ensureReasonableResolution(boundary.box, props);
+    return Task.create('Molecular Surface', async ctx => {
+        return await MolecularSurface(ctx, position, boundary, maxRadius, boundary.box, p);
     });
 }