Browse Source

Merge branch 'master' of https://github.com/molstar/molstar-proto into state_representation

David Sehnal 6 years ago
parent
commit
a7da4b020c
53 changed files with 447 additions and 225 deletions
  1. 35 14
      src/apps/canvas/component/viewport.tsx
  2. 1 1
      src/mol-app/component/color-theme.tsx
  3. 42 0
      src/mol-app/component/parameter/color.tsx
  4. 3 0
      src/mol-app/component/parameters.tsx
  5. 10 0
      src/mol-canvas3d/canvas3d.ts
  6. 1 2
      src/mol-geo/geometry/color-data.ts
  7. 23 4
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  8. 22 4
      src/mol-geo/geometry/geometry.ts
  9. 32 8
      src/mol-geo/geometry/lines/lines.ts
  10. 18 3
      src/mol-geo/geometry/mesh/mesh.ts
  11. 20 8
      src/mol-geo/geometry/points/points.ts
  12. 1 2
      src/mol-geo/geometry/size-data.ts
  13. 4 0
      src/mol-gl/_spec/renderer.spec.ts
  14. 4 13
      src/mol-gl/renderable.ts
  15. 1 0
      src/mol-gl/renderable/direct-volume.ts
  16. 7 0
      src/mol-gl/renderable/schema.ts
  17. 5 2
      src/mol-gl/scene.ts
  18. 40 1
      src/mol-math/geometry/primitives/sphere3d.ts
  19. 20 0
      src/mol-math/linear-algebra/3d/vec4.ts
  20. 5 6
      src/mol-repr/index.ts
  21. 3 2
      src/mol-repr/shape/index.ts
  22. 7 2
      src/mol-repr/structure/complex-representation.ts
  23. 15 15
      src/mol-repr/structure/complex-visual.ts
  24. 6 6
      src/mol-repr/structure/representation/cartoon.ts
  25. 9 6
      src/mol-repr/structure/units-representation.ts
  26. 14 14
      src/mol-repr/structure/units-visual.ts
  27. 4 4
      src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts
  28. 4 4
      src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
  29. 4 4
      src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts
  30. 2 1
      src/mol-repr/structure/visual/element-point.ts
  31. 2 1
      src/mol-repr/structure/visual/gaussian-density-point.ts
  32. 2 1
      src/mol-repr/structure/visual/gaussian-density-volume.ts
  33. 2 1
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  34. 2 1
      src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
  35. 3 7
      src/mol-repr/structure/visual/inter-unit-link-cylinder.ts
  36. 4 4
      src/mol-repr/structure/visual/intra-unit-link-cylinder.ts
  37. 2 1
      src/mol-repr/structure/visual/nucleotide-block-mesh.ts
  38. 4 7
      src/mol-repr/structure/visual/polymer-backbone-cylinder.ts
  39. 4 5
      src/mol-repr/structure/visual/polymer-direction-wedge.ts
  40. 5 8
      src/mol-repr/structure/visual/polymer-gap-cylinder.ts
  41. 3 7
      src/mol-repr/structure/visual/polymer-trace-mesh.ts
  42. 11 11
      src/mol-repr/structure/visual/util/common.ts
  43. 3 6
      src/mol-repr/structure/visual/util/element.ts
  44. 0 4
      src/mol-repr/structure/visual/util/link.ts
  45. 4 4
      src/mol-repr/util.ts
  46. 3 3
      src/mol-repr/volume/direct-volume.ts
  47. 16 13
      src/mol-repr/volume/index.ts
  48. 3 3
      src/mol-repr/volume/isosurface-mesh.ts
  49. 3 3
      src/mol-theme/color/chain-id.ts
  50. 1 1
      src/mol-theme/color/cross-link.ts
  51. 3 3
      src/mol-theme/color/element-index.ts
  52. 2 2
      src/mol-theme/color/polymer-index.ts
  53. 3 3
      src/mol-theme/color/unit-index.ts

+ 35 - 14
src/apps/canvas/component/viewport.tsx

@@ -12,6 +12,9 @@ import { labelFirst } from 'mol-theme/label';
 import { ButtonsType } from 'mol-util/input/input-observer';
 import { throttleTime } from 'rxjs/operators'
 import { CombinedCameraMode } from 'mol-canvas3d/camera/combined';
+import { ColorParamComponent } from 'mol-app/component/parameter/color';
+import { Color } from 'mol-util/color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
 
 interface ViewportProps {
     app: App
@@ -22,8 +25,11 @@ interface ViewportState {
     pickingInfo: string
     taskInfo: string
     cameraMode: CombinedCameraMode
+    backgroundColor: Color
 }
 
+const BackgroundColorParam = PD.Color('Background Color', '', Color(0x000000))
+
 export class Viewport extends React.Component<ViewportProps, ViewportState> {
     private container: HTMLDivElement | null = null;
     private canvas: HTMLCanvasElement | null = null;
@@ -32,7 +38,8 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
         noWebGl: false,
         pickingInfo: '',
         taskInfo: '',
-        cameraMode: 'perspective'
+        cameraMode: 'perspective',
+        backgroundColor: Color(0x000000)
     };
 
     handleResize() {
@@ -45,7 +52,10 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
         }
         this.handleResize()
 
-        this.setState({ cameraMode: this.props.app.canvas3d.camera.mode })
+        this.setState({
+            cameraMode: this.props.app.canvas3d.props.cameraMode,
+            backgroundColor: this.props.app.canvas3d.props.backgroundColor
+        })
 
         const canvas3d = this.props.app.canvas3d
 
@@ -132,19 +142,30 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
                     background: 'rgba(0, 0, 0, 0.2)'
                 }}
             >
-                <span>Camera mode </span>
-                <select
-                    value={this.state.cameraMode}
-                    style={{width: '150'}}
-                    onChange={e => {
-                        const cameraMode = e.target.value as CombinedCameraMode
-                        this.props.app.canvas3d.camera.mode = cameraMode
-                        this.setState({ cameraMode })
+                <div>
+                    <span>Camera Mode </span>
+                    <select
+                        value={this.state.cameraMode}
+                        style={{width: '150'}}
+                        onChange={e => {
+                            const p = { cameraMode: e.target.value as CombinedCameraMode }
+                            this.props.app.canvas3d.setProps(p)
+                            this.setState(p)
+                        }}
+                    >
+                        <option value='perspective'>Perspective</option>
+                        <option value='orthographic'>Orthographic</option>
+                    </select>
+                </div>
+                <ColorParamComponent
+                    param={BackgroundColorParam}
+                    value={this.state.backgroundColor}
+                    onChange={value => {
+                        const p = { backgroundColor: value }
+                        this.props.app.canvas3d.setProps(p)
+                        this.setState(p)
                     }}
-                >
-                    <option value='perspective'>Perspective</option>
-                    <option value='orthographic'>Orthographic</option>
-                </select>
+                />
             </div>
             { this.state.taskInfo ?
                 <div

+ 1 - 1
src/mol-app/component/color-theme.tsx

@@ -24,7 +24,7 @@ export class ColorThemeComponent extends React.Component<ColorThemeComponentProp
     render() {
         const ct = this.props.colorTheme
         return <div>
-            <span>Color Theme </span>
+            <span>Color Theme Info </span>
 
             {ct.description ? <div><i>{ct.description}</i></div> : ''}
             {

+ 42 - 0
src/mol-app/component/parameter/color.tsx

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { ColorNames } from 'mol-util/color/tables';
+import { Color } from 'mol-util/color';
+
+export interface ColorParamComponentProps {
+    param: PD.Color
+    value: Color
+    onChange(v: Color): void
+}
+
+export interface ColorParamComponentState {
+    value: Color
+}
+
+export class ColorParamComponent extends React.Component<ColorParamComponentProps, ColorParamComponentState> {
+    state = {
+        value: this.props.value
+    }
+
+    onChange(value: Color) {
+        this.setState({ value })
+        this.props.onChange(value)
+    }
+
+    render() {
+        return <div>
+            <span>{this.props.param.label} </span>
+            <select value={this.state.value} onChange={e => this.onChange(Color(parseInt(e.target.value))) }>
+                {Object.keys(ColorNames).map(name => {
+                    return <option key={name} value={(ColorNames as { [k: string]: Color})[name]}>{name}</option>
+                })}
+            </select>
+        </div>;
+    }
+}

+ 3 - 0
src/mol-app/component/parameters.tsx

@@ -12,6 +12,7 @@ import { RangeParamComponent } from './parameter/range';
 import { SelectParamComponent } from './parameter/select';
 import { MultiSelectParamComponent } from './parameter/multi-select';
 import { TextParamComponent } from './parameter/text';
+import { ColorParamComponent } from './parameter/color';
 
 interface ParametersProps<P extends PD.Params> {
     params: P
@@ -35,6 +36,8 @@ function getParamComponent<P extends PD.Any>(p: PD.Any, value: P['defaultValue']
             return <MultiSelectParamComponent param={p} value={value} onChange={onChange} />
         case 'text':
             return <TextParamComponent param={p} value={value} onChange={onChange} />
+        case 'color':
+            return <ColorParamComponent param={p} value={value} onChange={onChange} />
     }
     return ''
 }

+ 10 - 0
src/mol-canvas3d/canvas3d.ts

@@ -63,6 +63,7 @@ interface Canvas3D {
     readonly camera: CombinedCamera
     downloadScreenshot: () => void
     getImageData: (variant: RenderVariant) => ImageData
+    setProps: (props: Partial<Canvas3DProps>) => void
 
     /** Returns a copy of the current Canvas3D instance props */
     readonly props: Canvas3DProps
@@ -341,6 +342,15 @@ namespace Canvas3D {
             reprCount,
             identified,
             didDraw,
+            setProps: (props: Partial<Canvas3DProps>) => {
+                if (props.cameraMode !== undefined && props.cameraMode !== camera.mode) {
+                    camera.mode = props.cameraMode
+                }
+                if (props.backgroundColor !== undefined && props.backgroundColor !== renderer.props.clearColor) {
+                    renderer.setClearColor(props.backgroundColor)
+                }
+                requestDraw(true)
+            },
 
             get props() {
                 return {

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

@@ -56,8 +56,7 @@ export function getColorThemeProps(props: ColorProps): ColorThemeProps {
     return p
 }
 
-export function createColors(ctx: RuntimeContext, locationIt: LocationIterator, props: ColorProps, colorData?: ColorData): Promise<ColorData> {
-    const colorTheme = ColorTheme(getColorThemeProps(props))
+export function createColors(ctx: RuntimeContext, locationIt: LocationIterator, colorTheme: ColorTheme, colorData?: ColorData): Promise<ColorData> {
     switch (getGranularity(locationIt, colorTheme.granularity)) {
         case 'uniform': return createUniformColor(ctx, locationIt, colorTheme.color, colorData)
         case 'group': return createGroupColor(ctx, locationIt, colorTheme.color, colorData)

+ 23 - 4
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -17,7 +17,9 @@ import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { TransformData } from '../transform-data';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
-import { Geometry } from '../geometry';
+import { Geometry, Theme } from '../geometry';
+import { transformPositionArray } from 'mol-geo/util';
+import { calculateBoundingSphere } from 'mol-gl/renderable/util';
 
 const VolumeBox = Box()
 const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
@@ -75,16 +77,22 @@ export namespace DirectVolume {
     export const DefaultProps = PD.getDefaultValues(Params)
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<DirectVolumeValues> {
+    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: Props): Promise<DirectVolumeValues> {
         const { gridTexture, gridTextureDim } = directVolume
+        const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume
 
         const { instanceCount, groupCount } = locationIt
-        const color = await createColors(ctx, locationIt, props)
+        const color = await createColors(ctx, locationIt, theme.color)
         const marker = createMarkers(instanceCount * groupCount)
 
         const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount }
 
-        const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume
+        const vertices = new Float32Array(VolumeBox.vertices)
+        transformPositionArray(gridTransform.ref.value, vertices, 0, vertices.length / 3)
+        const boundingSphere = calculateBoundingSphere(
+            vertices, vertices.length / 3,
+            transform.aTransform.ref.value, transform.instanceCount.ref.value
+        )
 
         const controlPoints = getControlPointsFromString(props.controlPoints)
         const transferTex = createTransferFunctionTexture(controlPoints)
@@ -99,6 +107,7 @@ export namespace DirectVolume {
 
             aPosition: ValueCell.create(VolumeBox.vertices as Float32Array),
             elements: ValueCell.create(VolumeBox.indices as Uint32Array),
+            boundingSphere: ValueCell.create(boundingSphere),
 
             uIsoValue: ValueCell.create(props.isoValueAbsolute),
             uBboxMin: bboxMin,
@@ -117,6 +126,16 @@ export namespace DirectVolume {
     }
 
     export function updateValues(values: DirectVolumeValues, props: Props) {
+        const vertices = new Float32Array(values.aPosition.ref.value)
+        transformPositionArray(values.uTransform.ref.value, vertices, 0, vertices.length / 3)
+        const boundingSphere = calculateBoundingSphere(
+            vertices, Math.floor(vertices.length / 3),
+            values.aTransform.ref.value, values.instanceCount.ref.value
+        )
+        if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
+            ValueCell.update(values.boundingSphere, boundingSphere)
+        }
+
         ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute)
         ValueCell.updateIfChanged(values.uAlpha, props.alpha)
         ValueCell.updateIfChanged(values.dUseFog, props.useFog)

+ 22 - 4
src/mol-geo/geometry/geometry.ts

@@ -10,13 +10,14 @@ import { RenderableState } from 'mol-gl/renderable';
 import { ValueCell } from 'mol-util';
 import { BaseValues } from 'mol-gl/renderable/schema';
 import { Color } from 'mol-util/color';
-import { ColorThemeOptions, ColorThemeName, ColorScaleOptions, ColorScaleName } from 'mol-theme/color';
+import { ColorThemeOptions, ColorThemeName, ColorScaleOptions, ColorScaleName, ColorTheme } from 'mol-theme/color';
 import { LocationIterator } from '../util/location-iterator';
-import { ColorType } from './color-data';
-import { SizeType } from './size-data';
+import { ColorType, getColorThemeProps } from './color-data';
+import { SizeType, getSizeThemeProps } from './size-data';
 import { Lines } from './lines/lines';
 import { ParamDefinition as PD } from 'mol-util/param-definition'
 import { DirectVolume } from './direct-volume/direct-volume';
+import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 
 //
 
@@ -35,6 +36,18 @@ export type VisualQuality = keyof typeof VisualQualityInfo
 export const VisualQualityNames = Object.keys(VisualQualityInfo)
 export const VisualQualityOptions = VisualQualityNames.map(n => [n, n] as [VisualQuality, string])
 
+export interface Theme {
+    color: ColorTheme
+    size: SizeTheme
+}
+
+export function createTheme(props: Geometry.Props) {
+    return {
+        color: ColorTheme(getColorThemeProps(props)),
+        size: SizeTheme(getSizeThemeProps(props))
+    }
+}
+
 //
 
 export type GeometryKindType = {
@@ -64,9 +77,14 @@ export namespace Geometry {
         depthMask: PD.Boolean('Depth Mask', '', true),
         useFog: PD.Boolean('Use Fog', '', false),
         quality: PD.Select<VisualQuality>('Quality', '', 'auto', VisualQualityOptions),
-        colorTheme: PD.Select<ColorThemeName>('Color Theme', '', 'uniform', ColorThemeOptions),
+
+        colorTheme: PD.Select<ColorThemeName>('Color Name', '', 'uniform', ColorThemeOptions),
         colorList: PD.Select<ColorScaleName>('Color Scale', '', 'default', ColorScaleOptions),
         colorValue: PD.Color('Color Value', '', Color(0xCCCCCC)),
+
+        sizeTheme: PD.Select<SizeThemeName>('Size Name', '', 'uniform', SizeThemeOptions),
+        sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
+        sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
     }
     export const DefaultProps = PD.getDefaultValues(Params)
     export type Props = typeof DefaultProps

+ 32 - 8
src/mol-geo/geometry/lines/lines.ts

@@ -7,18 +7,19 @@
 import { ValueCell } from 'mol-util'
 import { Mat4 } from 'mol-math/linear-algebra'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
-import { Geometry } from '../geometry';
+import { Geometry, Theme } from '../geometry';
 import { RuntimeContext } from 'mol-task';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
 import { createSizes } from '../size-data';
 import { TransformData } from '../transform-data';
 import { LocationIterator } from '../../util/location-iterator';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { LinesValues } from 'mol-gl/renderable/lines';
 import { Mesh } from '../mesh/mesh';
 import { LinesBuilder } from './lines-builder';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { calculateBoundingSphere } from 'mol-gl/renderable/util';
+import { Sphere3D } from 'mol-math/geometry';
 
 /** Wide line */
 export interface Lines {
@@ -94,27 +95,36 @@ export namespace Lines {
     export const Params = {
         ...Geometry.Params,
         lineSizeAttenuation: PD.Boolean('Line Size Attenuation', '', false),
-        sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-        sizeValue: PD.Numeric('Size Value', '', 1, 0, 10, 0.1),
-        sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
     }
     export const DefaultProps = PD.getDefaultValues(Params)
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, lines: Lines, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<LinesValues> {
+    export async function createValues(ctx: RuntimeContext, lines: Lines, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: Props): Promise<LinesValues> {
         const { instanceCount, groupCount } = locationIt
-        const color = await createColors(ctx, locationIt, props)
-        const size = await createSizes(ctx, locationIt, props)
+        const color = await createColors(ctx, locationIt, theme.color)
+        const size = await createSizes(ctx, locationIt, theme.size)
         const marker = createMarkers(instanceCount * groupCount)
 
         const counts = { drawCount: lines.lineCount * 2 * 3, groupCount, instanceCount }
 
+        const boundingSphere = Sphere3D.addSphere(
+            calculateBoundingSphere(
+                lines.startBuffer.ref.value, lines.lineCount,
+                transform.aTransform.ref.value, transform.instanceCount.ref.value
+            ),
+            calculateBoundingSphere(
+                lines.startBuffer.ref.value, lines.lineCount,
+                transform.aTransform.ref.value, transform.instanceCount.ref.value
+            )
+        )
+
         return {
             aMapping: lines.mappingBuffer,
             aGroup: lines.groupBuffer,
             aStart: lines.startBuffer,
             aEnd: lines.endBuffer,
             elements: lines.indexBuffer,
+            boundingSphere: ValueCell.create(boundingSphere),
             ...color,
             ...size,
             ...marker,
@@ -128,6 +138,20 @@ export namespace Lines {
     }
 
     export function updateValues(values: LinesValues, props: Props) {
+        const boundingSphere = Sphere3D.addSphere(
+            calculateBoundingSphere(
+                values.aStart.ref.value, Math.floor(values.aStart.ref.value.length / 3),
+                values.aTransform.ref.value, values.instanceCount.ref.value
+            ),
+            calculateBoundingSphere(
+                values.aEnd.ref.value, Math.floor(values.aEnd.ref.value.length / 3),
+                values.aTransform.ref.value, values.instanceCount.ref.value
+            ),
+        )
+        if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
+            ValueCell.update(values.boundingSphere, boundingSphere)
+        }
+
         Geometry.updateValues(values, props)
         ValueCell.updateIfChanged(values.dLineSizeAttenuation, props.lineSizeAttenuation)
     }

+ 18 - 3
src/mol-geo/geometry/mesh/mesh.ts

@@ -10,13 +10,14 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { Sphere3D } from 'mol-math/geometry'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
 import { MeshValues } from 'mol-gl/renderable';
-import { Geometry } from '../geometry';
+import { Geometry, Theme } from '../geometry';
 import { createMarkers } from '../marker-data';
 import { TransformData } from '../transform-data';
 import { LocationIterator } from '../../util/location-iterator';
 import { createColors } from '../color-data';
 import { ChunkedArray } from 'mol-data/util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { calculateBoundingSphere } from 'mol-gl/renderable/util';
 
 export interface Mesh {
     readonly kind: 'mesh',
@@ -346,18 +347,24 @@ export namespace Mesh {
     export const DefaultProps = PD.getDefaultValues(Params)
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, mesh: Mesh, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<MeshValues> {
+    export async function createValues(ctx: RuntimeContext, mesh: Mesh, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: Props): Promise<MeshValues> {
         const { instanceCount, groupCount } = locationIt
-        const color = await createColors(ctx, locationIt, props)
+        const color = await createColors(ctx, locationIt, theme.color)
         const marker = createMarkers(instanceCount * groupCount)
 
         const counts = { drawCount: mesh.triangleCount * 3, groupCount, instanceCount }
 
+        const boundingSphere = calculateBoundingSphere(
+            mesh.vertexBuffer.ref.value, mesh.vertexCount,
+            transform.aTransform.ref.value, transform.instanceCount.ref.value
+        )
+
         return {
             aPosition: mesh.vertexBuffer,
             aNormal: mesh.normalBuffer,
             aGroup: mesh.groupBuffer,
             elements: mesh.indexBuffer,
+            boundingSphere: ValueCell.create(boundingSphere),
             ...color,
             ...marker,
             ...transform,
@@ -370,6 +377,14 @@ export namespace Mesh {
     }
 
     export function updateValues(values: MeshValues, props: Props) {
+        const boundingSphere = calculateBoundingSphere(
+            values.aPosition.ref.value, Math.floor(values.aPosition.ref.value.length / 3),
+            values.aTransform.ref.value, values.instanceCount.ref.value
+        )
+        if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
+            ValueCell.update(values.boundingSphere, boundingSphere)
+        }
+
         Geometry.updateValues(values, props)
         ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided)
         ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded)

+ 20 - 8
src/mol-geo/geometry/points/points.ts

@@ -7,7 +7,7 @@
 import { ValueCell } from 'mol-util'
 import { Mat4 } from 'mol-math/linear-algebra'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
-import { Geometry } from '../geometry';
+import { Geometry, Theme } from '../geometry';
 import { PointsValues } from 'mol-gl/renderable';
 import { RuntimeContext } from 'mol-task';
 import { createColors } from '../color-data';
@@ -15,8 +15,9 @@ import { createMarkers } from '../marker-data';
 import { createSizes } from '../size-data';
 import { TransformData } from '../transform-data';
 import { LocationIterator } from '../../util/location-iterator';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { calculateBoundingSphere } from 'mol-gl/renderable/util';
+import { Sphere3D } from 'mol-math/geometry';
 
 /** Point cloud */
 export interface Points {
@@ -58,24 +59,27 @@ export namespace Points {
         pointSizeAttenuation: PD.Boolean('Point Size Attenuation', '', false),
         pointFilledCircle: PD.Boolean('Point Filled Circle', '', false),
         pointEdgeBleach: PD.Numeric('Point Edge Bleach', '', 0.2, 0, 1, 0.05),
-        sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-        sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
-        sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
     }
     export const DefaultProps = PD.getDefaultValues(Params)
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, points: Points, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<PointsValues> {
+    export async function createValues(ctx: RuntimeContext, points: Points, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: Props): Promise<PointsValues> {
         const { instanceCount, groupCount } = locationIt
-        const color = await createColors(ctx, locationIt, props)
-        const size = await createSizes(ctx, locationIt, props)
+        const color = await createColors(ctx, locationIt, theme.color)
+        const size = await createSizes(ctx, locationIt, theme.size)
         const marker = createMarkers(instanceCount * groupCount)
 
         const counts = { drawCount: points.pointCount, groupCount, instanceCount }
 
+        const boundingSphere = calculateBoundingSphere(
+            points.centerBuffer.ref.value, points.pointCount,
+            transform.aTransform.ref.value, transform.instanceCount.ref.value
+        )
+
         return {
             aPosition: points.centerBuffer,
             aGroup: points.groupBuffer,
+            boundingSphere: ValueCell.create(boundingSphere),
             ...color,
             ...size,
             ...marker,
@@ -89,6 +93,14 @@ export namespace Points {
     }
 
     export function updateValues(values: PointsValues, props: Props) {
+        const boundingSphere = calculateBoundingSphere(
+            values.aPosition.ref.value, Math.floor(values.aPosition.ref.value.length / 3),
+            values.aTransform.ref.value, values.instanceCount.ref.value
+        )
+        if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
+            ValueCell.update(values.boundingSphere, boundingSphere)
+        }
+
         Geometry.updateValues(values, props)
         ValueCell.updateIfChanged(values.dPointSizeAttenuation, props.pointSizeAttenuation)
         ValueCell.updateIfChanged(values.dPointFilledCircle, props.pointFilledCircle)

+ 1 - 2
src/mol-geo/geometry/size-data.ts

@@ -40,8 +40,7 @@ export function getSizeThemeProps(props: SizeProps): SizeThemeProps {
     }
 }
 
-export async function createSizes(ctx: RuntimeContext, locationIt: LocationIterator, props: SizeProps, sizeData?: SizeData): Promise<SizeData> {
-    const sizeTheme = SizeTheme(getSizeThemeProps(props))
+export async function createSizes(ctx: RuntimeContext, locationIt: LocationIterator, sizeTheme: SizeTheme, sizeData?: SizeData): Promise<SizeData> {
     switch (getGranularity(locationIt, sizeTheme.granularity)) {
         case 'uniform': return createUniformSize(ctx, locationIt, sizeTheme.size, sizeData)
         case 'group': return createGroupSize(ctx, locationIt, sizeTheme.size, sizeData)

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

@@ -21,6 +21,7 @@ import Scene from '../scene';
 import { createEmptyMarkers } from 'mol-geo/geometry/marker-data';
 import { fillSerial } from 'mol-util/array';
 import { Color } from 'mol-util/color';
+import { Sphere3D } from 'mol-math/geometry';
 
 // function writeImage(gl: WebGLRenderingContext, width: number, height: number) {
 //     const pixels = new Uint8Array(width * height * 4)
@@ -56,6 +57,8 @@ function createPoints() {
     const m4 = Mat4.identity()
     Mat4.toArray(m4, aTransform.ref.value, 0)
 
+    const boundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2))
+
     const values: PointsValues = {
         aPosition,
         aGroup,
@@ -71,6 +74,7 @@ function createPoints() {
 
         drawCount: ValueCell.create(3),
         instanceCount: ValueCell.create(1),
+        boundingSphere,
 
         dPointSizeAttenuation: ValueCell.create(true),
         dPointFilledCircle: ValueCell.create(false),

+ 4 - 13
src/mol-gl/renderable.ts

@@ -8,8 +8,6 @@ import { Program } from './webgl/program';
 import { RenderableValues, Values, RenderableSchema } from './renderable/schema';
 import { RenderVariant, RenderItem } from './webgl/render-item';
 import { Sphere3D } from 'mol-math/geometry';
-// import { calculateBoundingSphereFromValues } from './renderable/util';
-// import { Sphere } from 'mol-geo/primitive/sphere';
 import { Vec3 } from 'mol-math/linear-algebra';
 
 export type RenderableState = {
@@ -30,29 +28,22 @@ export interface Renderable<T extends RenderableValues> {
 }
 
 export function createRenderable<T extends Values<RenderableSchema>>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
-    // TODO
     let boundingSphere: Sphere3D = Sphere3D.create(Vec3.zero(), 50)
 
     return {
         get values () { return values },
         get state () { return state },
         get boundingSphere () {
+            if (values.boundingSphere) {
+                Sphere3D.copy(boundingSphere, values.boundingSphere.ref.value)
+            }
             return boundingSphere
-            // TODO
-            // if (boundingSphere) return boundingSphere
-            // boundingSphere = calculateBoundingSphereFromValues(values)
-            // return boundingSphere
         },
         get opaque () { return values.uAlpha && values.uAlpha.ref.value === 1 },
 
         render: (variant: RenderVariant) => renderItem.render(variant),
         getProgram: (variant: RenderVariant) => renderItem.getProgram(variant),
-        update: () => {
-            renderItem.update()
-            // TODO
-            // const valueChanges = renderItem.update()
-            // if (valueChanges.attributes) boundingSphere = undefined
-        },
+        update: () => renderItem.update(),
         dispose: () => renderItem.destroy()
     }
 }

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

@@ -29,6 +29,7 @@ export const DirectVolumeSchema = {
 
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
+    boundingSphere: ValueSpec('sphere'),
 
     aPosition: AttributeSpec('float32', 3, 0),
     elements: ElementsSpec('uint32'),

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

@@ -11,11 +11,15 @@ import { DefineKind, DefineValues } from '../shader-code';
 import { Vec2, Vec3, Vec4, Mat3, Mat4 } from 'mol-math/linear-algebra';
 import { TextureImage, TextureVolume } from './util';
 import { TextureValues, TextureType, TextureFormat, TextureFilter, TextureKind, Texture } from '../webgl/texture';
+import { Sphere3D } from 'mol-math/geometry';
 
 export type ValueKindType = {
     'number': number
     'string': string
+    'boolean': string
     'any': any
+
+    'sphere': Sphere3D
 }
 export type ValueKind = keyof ValueKindType
 
@@ -51,6 +55,8 @@ export type KindValue = {
     'string': string
     'boolean': boolean
     'any': any
+
+    'sphere': Sphere3D
 }
 
 export type Values<S extends RenderableSchema> = { [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> }
@@ -192,6 +198,7 @@ export const BaseSchema = {
 
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
+    boundingSphere: ValueSpec('sphere'),
 
     dUseFog: DefineSpec('boolean'),
 }

+ 5 - 2
src/mol-gl/scene.ts

@@ -56,7 +56,10 @@ namespace Scene {
         const object3d = Object3D.create()
 
         return {
-            ...object3d,
+            get view () { return object3d.view },
+            get position () { return object3d.position },
+            get direction () { return object3d.direction },
+            get up () { return object3d.up },
 
             update: () => {
                 Object3D.update(object3d)
@@ -103,7 +106,7 @@ namespace Scene {
             },
             get boundingSphere() {
                 if (boundingSphere) return boundingSphere
-                // TODO avoid array creation
+                // TODO avoid object creation
                 boundingSphere = calculateBoundingSphere(renderableMap)
                 return boundingSphere
             }

+ 40 - 1
src/mol-math/geometry/primitives/sphere3d.ts

@@ -5,7 +5,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Vec3, Mat4 } from '../../linear-algebra'
+import { Vec3, Mat4, EPSILON } from '../../linear-algebra'
 import { PositionData } from '../common'
 import { OrderedSet } from 'mol-data/int';
 
@@ -15,6 +15,12 @@ namespace Sphere3D {
     export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
     export function zero(): Sphere3D { return { center: Vec3.zero(), radius: 0 }; }
 
+    export function copy(out: Sphere3D, a: Sphere3D) {
+        Vec3.copy(out.center, a.center)
+        out.radius = a.radius
+        return out;
+    }
+
     export function computeBounding(data: PositionData): Sphere3D {
         const { x, y, z, indices } = data;
         let cx = 0, cy = 0, cz = 0;
@@ -50,6 +56,39 @@ namespace Sphere3D {
         out.radius = sphere.radius * Mat4.getMaxScaleOnAxis(m)
         return out
     }
+
+    export function toArray(s: Sphere3D, out: Helpers.NumberArray, offset: number) {
+        Vec3.toArray(s.center, out, offset)
+        out[offset + 3] = s.radius
+    }
+
+    export function fromArray(out: Sphere3D, array: Helpers.NumberArray, offset: number) {
+        Vec3.fromArray(out.center, array, offset)
+        out.radius = array[offset + 3]
+        return out
+    }
+
+    export function addSphere(out: Sphere3D, sphere: Sphere3D) {
+        out.radius = Math.max(out.radius, Vec3.distance(out.center, sphere.center) + sphere.radius)
+        return out
+    }
+
+    /**
+     * Returns whether or not the spheres have exactly the same center and radius (when compared with ===)
+     */
+    export function exactEquals(a: Sphere3D, b: Sphere3D) {
+        return a.radius === b.radius && Vec3.exactEquals(a.center, b.center);
+    }
+
+    /**
+     * Returns whether or not the spheres have approximately the same center and radius.
+     */
+    export function equals(a: Sphere3D, b: Sphere3D) {
+        const ar = a.radius;
+        const br = b.radius;
+        return (Math.abs(ar - br) <= EPSILON.Value * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
+                Vec3.equals(a.center, b.center));
+    }
 }
 
 export { Sphere3D }

+ 20 - 0
src/mol-math/linear-algebra/3d/vec4.ts

@@ -18,6 +18,7 @@
  */
 
 import Mat4 from './mat4';
+import { EPSILON } from '../3d';
 
 interface Vec4 extends Array<number> { [d: number]: number, '@type': 'vec4', length: 4 }
 
@@ -196,6 +197,25 @@ namespace Vec4 {
         out[3] = 1.0 / a[3];
         return out;
     }
+
+    /**
+     * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
+     */
+    export function exactEquals(a: Vec4, b: Vec4) {
+        return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
+    }
+
+    /**
+     * Returns whether or not the vectors have approximately the same elements in the same position.
+     */
+    export function equals(a: Vec4, b: Vec4) {
+        const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
+        const b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+        return (Math.abs(a0 - b0) <= EPSILON.Value * Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
+                Math.abs(a1 - b1) <= EPSILON.Value * Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
+                Math.abs(a2 - b2) <= EPSILON.Value * Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
+                Math.abs(a3 - b3) <= EPSILON.Value * Math.max(1.0, Math.abs(a3), Math.abs(b3)));
+    }
 }
 
 export default Vec4

+ 5 - 6
src/mol-repr/index.ts

@@ -11,6 +11,8 @@ import { Loci, isEmptyLoci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../mol-geo/geometry/marker-data';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { WebGLContext } from 'mol-gl/webgl/context';
+import { getQualityProps } from './util';
+import { Theme } from 'mol-geo/geometry/geometry';
 // import { ColorTheme } from 'mol-theme/color';
 
 // export interface RepresentationProps {
@@ -68,9 +70,8 @@ export namespace Representation {
             },
             createOrUpdate: (ctx: RepresentationContext, props: Partial<P> = {}, data?: D) => {
                 if (data) currentData = data
-                // const qualityProps = getQualityProps(Object.assign({}, currentProps, props), structure)
-                // currentProps = Object.assign({}, DefaultCartoonProps, currentProps, props, qualityProps)
-                currentProps = Object.assign({}, defaultProps, currentProps, props)
+                const qualityProps = getQualityProps(Object.assign({}, currentProps, props), data)
+                currentProps = Object.assign({}, defaultProps, currentProps, props, qualityProps)
 
                 const { visuals } = currentProps
                 return Task.create(`Creating '${label}' representation`, async runtime => {
@@ -108,13 +109,11 @@ export namespace Representation {
 
 export interface VisualContext extends RepresentationContext {
     runtime: RuntimeContext,
-    // TODO
-    // colorTheme: ColorTheme,
 }
 
 export interface Visual<D, P extends RepresentationProps> {
     readonly renderObject: RenderObject | undefined
-    createOrUpdate: (ctx: VisualContext, props?: Partial<P>, data?: D) => Promise<void>
+    createOrUpdate: (ctx: VisualContext, theme: Theme, props?: Partial<P>, data?: D) => Promise<void>
     getLoci: (pickingId: PickingId) => Loci
     mark: (loci: Loci, action: MarkerAction) => boolean
     destroy: () => void

+ 3 - 2
src/mol-repr/shape/index.ts

@@ -15,7 +15,7 @@ import { OrderedSet, Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
-import { createRenderableState } from 'mol-geo/geometry/geometry';
+import { createRenderableState, createTheme } from 'mol-geo/geometry/geometry';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
@@ -49,9 +49,10 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation
 
             const mesh = _shape.mesh
             const locationIt = ShapeGroupIterator.fromShape(_shape)
+            const theme = createTheme(currentProps)
             const transform = createIdentityTransform()
 
-            const values = await Mesh.createValues(runtime, mesh, transform, locationIt, currentProps)
+            const values = await Mesh.createValues(runtime, mesh, transform, locationIt, theme, currentProps)
             const state = createRenderableState(currentProps)
 
             _renderObject = createMeshRenderObject(values, state)

+ 7 - 2
src/mol-repr/structure/complex-representation.ts

@@ -13,17 +13,22 @@ import { ComplexVisual } from './complex-visual';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { RepresentationContext } from 'mol-repr';
+import { createTheme, Theme } from 'mol-geo/geometry/geometry';
 
 export function ComplexRepresentation<P extends StructureProps>(label: string, visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> {
     let visual: ComplexVisual<P> | undefined
+    let _structure: Structure
     let _props: P
+    let _theme: Theme
 
     function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, structure?: Structure) {
-        _props = Object.assign({}, _props, props)
+        if (structure) _structure = structure
+        _props = Object.assign({}, _props, props, { structure: _structure })
+        _theme = createTheme(_props)
 
         return Task.create('Creating or updating ComplexRepresentation', async runtime => {
             if (!visual) visual = visualCtor()
-            await visual.createOrUpdate({ ...ctx, runtime }, _props, structure)
+            await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, structure)
         });
     }
 

+ 15 - 15
src/mol-repr/structure/complex-visual.ts

@@ -15,7 +15,7 @@ import { Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { RenderableValues } from 'mol-gl/renderable/schema';
 import { createSizes } from 'mol-geo/geometry/size-data';
-import { Geometry, updateRenderableState } from 'mol-geo/geometry/geometry';
+import { Geometry, updateRenderableState, Theme } from 'mol-geo/geometry/geometry';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { createColors } from 'mol-geo/geometry/color-data';
@@ -36,7 +36,7 @@ type ComplexRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderOb
 
 interface ComplexVisualBuilder<P extends ComplexProps, G extends Geometry> {
     defaultProps: P
-    createGeometry(ctx: VisualContext, structure: Structure, props: P, geometry?: G): Promise<G>
+    createGeometry(ctx: VisualContext, structure: Structure, theme: Theme, props: P, geometry?: G): Promise<G>
     createLocationIterator(structure: Structure): LocationIterator
     getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
     mark(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean): boolean,
@@ -45,7 +45,7 @@ interface ComplexVisualBuilder<P extends ComplexProps, G extends Geometry> {
 
 interface ComplexVisualGeometryBuilder<P extends ComplexProps, G extends Geometry> extends ComplexVisualBuilder<P, G> {
     createEmptyGeometry(geometry?: G): G
-    createRenderObject(ctx: VisualContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<ComplexRenderObject>
+    createRenderObject(ctx: VisualContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: P): Promise<ComplexRenderObject>
     updateValues(values: RenderableValues, newProps: P): void
 }
 
@@ -61,18 +61,18 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
     let locationIt: LocationIterator
     let conformationHash: number
 
-    async function create(ctx: VisualContext, structure: Structure, props: Partial<P> = {}) {
-        currentProps = Object.assign({}, defaultProps, props, { structure })
+    async function create(ctx: VisualContext, structure: Structure, theme: Theme, props: Partial<P> = {}) {
+        currentProps = Object.assign({}, defaultProps, props)
         currentStructure = structure
 
         conformationHash = Structure.conformationHash(currentStructure)
-        geometry = await createGeometry(ctx, currentStructure, currentProps, geometry)
+        geometry = await createGeometry(ctx, currentStructure, theme, currentProps, geometry)
 
         locationIt = createLocationIterator(structure)
-        renderObject = await createRenderObject(ctx, structure, geometry, locationIt, currentProps)
+        renderObject = await createRenderObject(ctx, structure, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, props: Partial<P>) {
+    async function update(ctx: VisualContext, theme: Theme, props: Partial<P>) {
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
 
         if (!renderObject) return false
@@ -93,7 +93,7 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
         //
 
         if (updateState.createGeometry) {
-            geometry = await createGeometry(ctx, currentStructure, newProps, geometry)
+            geometry = await createGeometry(ctx, currentStructure, theme, newProps, geometry)
             ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
             updateState.updateColor = true
         }
@@ -101,12 +101,12 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
         if (updateState.updateSize) {
             // not all geometries have size data, so check here
             if ('uSize' in renderObject.values) {
-                await createSizes(ctx.runtime, locationIt, newProps, renderObject.values)
+                await createSizes(ctx.runtime, locationIt, theme.size, renderObject.values)
             }
         }
 
         if (updateState.updateColor) {
-            await createColors(ctx.runtime, locationIt, newProps, renderObject.values)
+            await createColors(ctx.runtime, locationIt, theme.color, renderObject.values)
         }
 
         updateValues(renderObject.values, newProps)
@@ -118,18 +118,18 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
 
     return {
         get renderObject () { return renderObject },
-        async createOrUpdate(ctx: VisualContext, props: Partial<P> = {}, structure?: Structure) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<P> = {}, structure?: Structure) {
             if (!structure && !currentStructure) {
                 throw new Error('missing structure')
             } else if (structure && (!currentStructure || !renderObject)) {
-                await create(ctx, structure, props)
+                await create(ctx, structure, theme, props)
             } else if (structure && structure.hashCode !== currentStructure.hashCode) {
-                await create(ctx, structure, props)
+                await create(ctx, structure, theme, props)
             } else {
                 if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) {
                     currentStructure = structure
                 }
-                await update(ctx, props)
+                await update(ctx, theme, props)
             }
         },
         getLoci(pickingId: PickingId) {

+ 6 - 6
src/mol-repr/structure/representation/cartoon.ts

@@ -12,17 +12,17 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { UnitsRepresentation } from '../units-representation';
 import { StructureRepresentation } from '../index';
 import { Representation } from 'mol-repr';
-// import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from '../visual/polymer-direction-wedge';
+import { PolymerDirectionVisual, PolymerDirectionParams } from '../visual/polymer-direction-wedge';
 
 export const CartoonParams = {
     ...PolymerTraceParams,
     ...PolymerGapParams,
     ...NucleotideBlockParams,
-    // ...PolymerDirectionParams,
+    ...PolymerDirectionParams,
     sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.6, 0, 10, 0.1),
+    sizeValue: PD.Numeric('Size Value', '', 0.2, 0, 10, 0.1),
 }
-export const DefaultCartoonProps = PD.getDefaultValues(CartoonParams)
+export const DefaultCartoonProps = { ...PD.getDefaultValues(CartoonParams), visuals: [ '0', '1', '2' ] }
 export type CartoonProps = typeof DefaultCartoonProps
 
 export type CartoonRepresentation = StructureRepresentation<CartoonProps>
@@ -32,6 +32,6 @@ export function CartoonRepresentation(): CartoonRepresentation {
         UnitsRepresentation('Polymer trace mesh', PolymerTraceVisual),
         UnitsRepresentation('Polymer gap cylinder', PolymerGapVisual),
         UnitsRepresentation('Nucleotide block mesh', NucleotideBlockVisual),
-        // UnitsRepresentation('Polymer direction wedge', PolymerDirectionVisual)
-    ] as StructureRepresentation<CartoonProps>[])
+        UnitsRepresentation('Polymer direction wedge', PolymerDirectionVisual)
+    ] as unknown as StructureRepresentation<CartoonProps>[]) // TODO avoid cast to unknown
 }

+ 9 - 6
src/mol-repr/structure/units-representation.ts

@@ -14,6 +14,7 @@ import { StructureGroup } from './units-visual';
 import { StructureProps, StructureParams, StructureRepresentation } from './index';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
+import { Theme, createTheme } from 'mol-geo/geometry/geometry';
 
 export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
 
@@ -21,11 +22,13 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
     let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
 
     let _props: P
+    let _theme: Theme
     let _structure: Structure
     let _groups: ReadonlyArray<Unit.SymmetryGroup>
 
     function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, structure?: Structure) {
-        _props = Object.assign({}, _props, props)
+        _props = Object.assign({}, _props, props, { structure: structure || _structure })
+        _theme = createTheme(_props)
 
         return Task.create('Creating or updating UnitsRepresentation', async runtime => {
             if (!_structure && !structure) {
@@ -37,7 +40,7 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
                 for (let i = 0; i < _groups.length; i++) {
                     const group = _groups[i];
                     const visual = visualCtor()
-                    await visual.createOrUpdate({ ...ctx, runtime }, _props, { group, structure })
+                    await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                     visuals.set(group.hashCode, { visual, group })
                 }
             } else if (structure && _structure.hashCode !== structure.hashCode) {
@@ -53,13 +56,13 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
                     const visualGroup = oldVisuals.get(group.hashCode)
                     if (visualGroup) {
                         const { visual } = visualGroup
-                        await visual.createOrUpdate({ ...ctx, runtime }, _props, { group, structure })
+                        await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visuals.set(group.hashCode, { visual, group })
                         oldVisuals.delete(group.hashCode)
                     } else {
                         // newGroups.push(group)
                         const visual = visualCtor()
-                        await visual.createOrUpdate({ ...ctx, runtime }, _props, { group, structure })
+                        await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visuals.set(group.hashCode, { visual, group })
                     }
                 }
@@ -85,7 +88,7 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
                     const group = _groups[i];
                     const visualGroup = visuals.get(group.hashCode)
                     if (visualGroup) {
-                        await visualGroup.visual.createOrUpdate({ ...ctx, runtime }, _props, { group, structure })
+                        await visualGroup.visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visualGroup.group = group
                     } else {
                         throw new Error(`expected to find visual for hashCode ${group.hashCode}`)
@@ -98,7 +101,7 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
                 visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))
                 for (let i = 0, il = visualsList.length; i < il; ++i) {
                     const [ visual, group ] = visualsList[i]
-                    await visual.createOrUpdate({ ...ctx, runtime }, _props, { group, structure: _structure })
+                    await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure: _structure })
                 }
             }
             if (structure) _structure = structure

+ 14 - 14
src/mol-repr/structure/units-visual.ts

@@ -14,7 +14,7 @@ import { deepEqual, ValueCell, UUID } from 'mol-util';
 import { Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { RenderableValues } from 'mol-gl/renderable/schema';
-import { Geometry, updateRenderableState } from 'mol-geo/geometry/geometry';
+import { Geometry, updateRenderableState, Theme } from 'mol-geo/geometry/geometry';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { createMarkers, MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
@@ -48,7 +48,7 @@ type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObje
 
 interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> {
     defaultProps: P
-    createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, props: P, geometry?: G): Promise<G>
+    createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: P, geometry?: G): Promise<G>
     createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
     getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
     mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
@@ -57,7 +57,7 @@ interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> {
 
 interface UnitsVisualGeometryBuilder<P extends UnitsProps, G extends Geometry> extends UnitsVisualBuilder<P, G> {
     createEmptyGeometry(geometry?: G): G
-    createRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<UnitsRenderObject>
+    createRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: P): Promise<UnitsRenderObject>
     updateValues(values: RenderableValues, newProps: P): void
 }
 
@@ -74,22 +74,22 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
     let locationIt: LocationIterator
     let currentConformationId: UUID
 
-    async function create(ctx: VisualContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
+    async function create(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<P> = {}) {
         currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
         currentGroup = group
 
         const unit = group.units[0]
         currentConformationId = Unit.conformationId(unit)
         geometry = includesUnitKind(currentProps.unitKinds, unit)
-            ? await createGeometry(ctx, unit, currentStructure, currentProps, geometry)
+            ? await createGeometry(ctx, unit, currentStructure, theme, currentProps, geometry)
             : createEmptyGeometry(geometry)
 
         // TODO create empty location iterator when not in unitKinds
         locationIt = createLocationIterator(group)
-        renderObject = await createRenderObject(ctx, group, geometry, locationIt, currentProps)
+        renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, props: Partial<P> = {}) {
+    async function update(ctx: VisualContext, theme: Theme, props: Partial<P> = {}) {
         if (!renderObject) return
 
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
@@ -122,7 +122,7 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
 
         if (updateState.createGeometry) {
             geometry = includesUnitKind(newProps.unitKinds, unit)
-                ? await createGeometry(ctx, unit, currentStructure, newProps, geometry)
+                ? await createGeometry(ctx, unit, currentStructure, theme, newProps, geometry)
                 : createEmptyGeometry(geometry)
             ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
             updateState.updateColor = true
@@ -131,12 +131,12 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
         if (updateState.updateSize) {
             // not all geometries have size data, so check here
             if ('uSize' in renderObject.values) {
-                await createSizes(ctx.runtime, locationIt, newProps, renderObject.values)
+                await createSizes(ctx.runtime, locationIt, theme.size, renderObject.values)
             }
         }
 
         if (updateState.updateColor) {
-            await createColors(ctx.runtime, locationIt, newProps, renderObject.values)
+            await createColors(ctx.runtime, locationIt, theme.color, renderObject.values)
         }
 
         updateValues(renderObject.values, newProps)
@@ -147,24 +147,24 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
 
     return {
         get renderObject () { return renderObject },
-        async createOrUpdate(ctx: VisualContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<P> = {}, structureGroup?: StructureGroup) {
             if (structureGroup) currentStructure = structureGroup.structure
             const group = structureGroup ? structureGroup.group : undefined
             if (!group && !currentGroup) {
                 throw new Error('missing group')
             } else if (group && (!currentGroup || !renderObject)) {
                 // console.log('unit-visual first create')
-                await create(ctx, group, props)
+                await create(ctx, group, theme, props)
             } else if (group && group.hashCode !== currentGroup.hashCode) {
                 // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
-                await create(ctx, group, props)
+                await create(ctx, group, theme, props)
             } else {
                 // console.log('unit-visual update')
                 if (group && !sameGroupConformation(group, currentGroup)) {
                     // console.log('unit-visual new conformation')
                     currentGroup = group
                 }
-                await update(ctx, props)
+                await update(ctx, theme, props)
             }
         },
         getLoci(pickingId: PickingId) {

+ 4 - 4
src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts

@@ -10,7 +10,7 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { createLinkCylinderMesh, LinkCylinderProps, LinkCylinderParams } from './util/link';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { ComplexMeshVisual, ComplexVisual } from '../complex-visual';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
+import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { LinkType } from 'mol-model/structure/model/types';
 import { BitFlags } from 'mol-util';
 import { UnitsMeshParams } from '../units-visual';
@@ -20,6 +20,7 @@ import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { VisualUpdateState } from '../../util';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 // TODO create seperate visual
 // for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
@@ -35,9 +36,8 @@ import { VisualContext } from 'mol-repr';
 
 const radiusFactor = 0.3
 
-async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
     const { links, elements } = structure.carbohydrates
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue })
     const location = StructureElement.create()
 
     const builderProps = {
@@ -54,7 +54,7 @@ async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure:
             const l = links[edgeIndex]
             location.unit = elements[l.carbohydrateIndexA].unit
             location.element = elements[l.carbohydrateIndexA].anomericCarbon
-            return sizeTheme.size(location) * radiusFactor
+            return theme.size.size(location) * radiusFactor
         }
     }
 

+ 4 - 4
src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts

@@ -13,7 +13,7 @@ import { DiamondPrism, PentagonalPrism, HexagonalPrism } from 'mol-geo/primitive
 import { Structure, StructureElement } from 'mol-model/structure';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
+import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
 import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
 import { ComplexMeshParams, ComplexMeshVisual } from '../complex-visual';
@@ -25,6 +25,7 @@ import { PickingId } from 'mol-geo/geometry/picking';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { EmptyLoci, Loci } from 'mol-model/loci';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -44,10 +45,9 @@ const diamondPrism = DiamondPrism()
 const pentagonalPrism = PentagonalPrism()
 const hexagonalPrism = HexagonalPrism()
 
-async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, props: CarbohydrateSymbolProps, mesh?: Mesh) {
+async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: CarbohydrateSymbolProps, mesh?: Mesh) {
     const builder = MeshBuilder.create(256, 128, mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue })
     const { detail } = props
 
     const carbohydrates = structure.carbohydrates
@@ -60,7 +60,7 @@ async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Struc
 
         l.unit = c.unit
         l.element = c.unit.elements[c.anomericCarbon]
-        const size = sizeTheme.size(l)
+        const size = theme.size.size(l)
         const radius = size * radiusFactor
         const side = size * sideFactor
 

+ 4 - 4
src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts

@@ -12,7 +12,7 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { ComplexMeshVisual, ComplexMeshParams } from '../complex-visual';
 import { Interval } from 'mol-data/int';
-import { SizeTheme, SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
+import { SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { BitFlags } from 'mol-util';
 import { LinkType } from 'mol-model/structure/model/types';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
@@ -20,13 +20,13 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
-async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
 
     const crossLinks = structure.crossLinkRestraints
     if (!crossLinks.count) return Mesh.createEmpty(mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue })
     const location = StructureElement.create()
 
     const builderProps = {
@@ -44,7 +44,7 @@ async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structur
             const b = crossLinks.pairs[edgeIndex]
             location.unit = b.unitA
             location.element = b.unitA.elements[b.indexA]
-            return sizeTheme.size(location)
+            return theme.size.size(location)
         }
     }
 

+ 2 - 1
src/mol-repr/structure/visual/element-point.ts

@@ -15,6 +15,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Points } from 'mol-geo/geometry/points/points';
 import { PointsBuilder } from 'mol-geo/geometry/points/points-builder';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 export const ElementPointParams = {
     ...UnitsPointsParams,
@@ -27,7 +28,7 @@ export type ElementPointProps = typeof DefaultElementPointProps
 
 // TODO size
 
-export async function createElementPoint(ctx: VisualContext, unit: Unit, structure: Structure, props: ElementPointProps, points: Points) {
+export async function createElementPoint(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: ElementPointProps, points: Points) {
     const elements = unit.elements
     const n = elements.length
     const builder = PointsBuilder.create(n, n / 10, points)

+ 2 - 1
src/mol-repr/structure/visual/gaussian-density-point.ts

@@ -17,6 +17,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Points } from 'mol-geo/geometry/points/points';
 import { PointsBuilder } from 'mol-geo/geometry/points/points-builder';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 export const GaussianDensityPointParams = {
     ...UnitsPointsParams,
@@ -28,7 +29,7 @@ export const GaussianDensityPointParams = {
 export const DefaultGaussianDensityPointProps = PD.getDefaultValues(GaussianDensityPointParams)
 export type GaussianDensityPointProps = typeof DefaultGaussianDensityPointProps
 
-export async function createGaussianDensityPoint(ctx: VisualContext, unit: Unit, structure: Structure, props: GaussianDensityProps, points?: Points) {
+export async function createGaussianDensityPoint(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, points?: Points) {
     const { transform, field: { space, data } } = await unit.computeGaussianDensity(props, ctx.runtime, ctx.webgl)
 
     const { dimensions, get } = space

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

@@ -13,8 +13,9 @@ import { GaussianDensityProps, GaussianDensityParams, computeUnitGaussianDensity
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
-async function createGaussianDensityVolume(ctx: VisualContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
+async function createGaussianDensityVolume(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
     const { runtime, webgl } = ctx
     if (webgl === undefined) throw new Error('createGaussianDensityVolume requires `webgl` object in VisualContext')
 

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

@@ -14,8 +14,9 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { computeMarchingCubesMesh } from 'mol-geo/util/marching-cubes/algorithm';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
-async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
+async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props
     const { transform, field, idField } = await unit.computeGaussianDensity(props, ctx.runtime, ctx.webgl)
 

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

@@ -15,8 +15,9 @@ import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { Lines } from 'mol-geo/geometry/lines/lines';
 import { computeMarchingCubesLines } from 'mol-geo/util/marching-cubes/algorithm';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
-async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
+async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
     const { smoothness } = props
     const { transform, field, idField } = await unit.computeGaussianDensity(props, ctx.runtime)
 

+ 3 - 7
src/mol-repr/structure/visual/inter-unit-link-cylinder.ts

@@ -12,20 +12,19 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { ComplexMeshVisual, ComplexMeshParams } from '../complex-visual';
 import { Interval } from 'mol-data/int';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { BitFlags } from 'mol-util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
-async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
     const links = structure.links
     const { bondCount, bonds } = links
 
     if (!bondCount) return Mesh.createEmpty(mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue, factor: props.sizeFactor })
     const location = StructureElement.create()
 
     const builderProps = {
@@ -43,7 +42,7 @@ async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: St
             const b = bonds[edgeIndex]
             location.unit = b.unitA
             location.element = b.unitA.elements[b.indexA]
-            return sizeTheme.size(location)
+            return theme.size.size(location)
         }
     }
 
@@ -53,9 +52,6 @@ async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: St
 export const InterUnitLinkParams = {
     ...ComplexMeshParams,
     ...LinkCylinderParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.2, 0, 10, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
 }
 export const DefaultInterUnitLinkProps = PD.getDefaultValues(InterUnitLinkParams)
 export type InterUnitLinkProps = typeof DefaultInterUnitLinkProps

+ 4 - 4
src/mol-repr/structure/visual/intra-unit-link-cylinder.ts

@@ -13,17 +13,17 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
 import { Interval } from 'mol-data/int';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
+import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { BitFlags } from 'mol-util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
-async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue, factor: props.sizeFactor })
     const location = StructureElement.create(unit)
 
     const elements = unit.elements;
@@ -57,7 +57,7 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s
         flags: (edgeIndex: number) => BitFlags.create(_flags[edgeIndex]),
         radius: (edgeIndex: number) => {
             location.element = elements[a[edgeIndex]]
-            return sizeTheme.size(location)
+            return theme.size.size(location)
         }
     }
 

+ 2 - 1
src/mol-repr/structure/visual/nucleotide-block-mesh.ts

@@ -18,6 +18,7 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 const p1 = Vec3.zero()
 const p2 = Vec3.zero()
@@ -34,7 +35,7 @@ const sVec = Vec3.zero()
 const box = Box()
 
 // TODO define props, should be scalable
-async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: {}, mesh?: Mesh) {
+async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: {}, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
     // TODO better vertex count estimate

+ 4 - 7
src/mol-repr/structure/visual/polymer-backbone-cylinder.ts

@@ -11,7 +11,6 @@ import { PolymerBackboneIterator } from './util/polymer';
 import { getElementLoci, markElement, StructureElementIterator } from './util/element';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
-import { SizeTheme, SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { OrderedSet } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
@@ -19,20 +18,18 @@ import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { CylinderProps } from 'mol-geo/primitive/cylinder';
 import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 export const PolymerBackboneCylinderParams = {
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 10, 0.1),
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
 }
 export const DefaultPolymerBackboneCylinderProps = PD.getDefaultValues(PolymerBackboneCylinderParams)
 export type PolymerBackboneCylinderProps = typeof DefaultPolymerBackboneCylinderProps
 
-async function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: PolymerBackboneCylinderProps, mesh?: Mesh) {
+async function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneCylinderProps, mesh?: Mesh) {
     const polymerElementCount = unit.polymerElements.length
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue })
     const { radialSegments } = props
 
     const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2
@@ -51,11 +48,11 @@ async function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit,
         pos(centerA.element, pA)
         pos(centerB.element, pB)
 
-        cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerA)
+        cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA)
         builder.setGroup(OrderedSet.indexOf(elements, centerA.element))
         addCylinder(builder, pA, pB, 0.5, cylinderProps)
 
-        cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerB)
+        cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB)
         builder.setGroup(OrderedSet.indexOf(elements, centerB.element))
         addCylinder(builder, pB, pA, 0.5, cylinderProps)
 

+ 4 - 5
src/mol-repr/structure/visual/polymer-direction-wedge.ts

@@ -10,12 +10,13 @@ import { PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment,
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { SecondaryStructureType, isNucleic } from 'mol-model/structure/model/types';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
+import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Wedge } from 'mol-geo/primitive/wedge';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -36,12 +37,10 @@ export const PolymerDirectionWedgeParams = {
 export const DefaultPolymerDirectionWedgeProps = PD.getDefaultValues(PolymerDirectionWedgeParams)
 export type PolymerDirectionWedgeProps = typeof DefaultPolymerDirectionWedgeProps
 
-async function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: PolymerDirectionWedgeProps, mesh?: Mesh) {
+async function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerDirectionWedgeProps, mesh?: Mesh) {
     const polymerElementCount = unit.polymerElements.length
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue })
-
     const vertexCount = polymerElementCount * 24
     const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
     const linearSegments = 1
@@ -63,7 +62,7 @@ async function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, s
         interpolateCurveSegment(state, v, tension, shift)
 
         if ((isSheet && !v.secStrucChange) || !isSheet) {
-            const size = sizeTheme.size(v.center)
+            const size = theme.size.size(v.center)
             const depth = depthFactor * size
             const width = widthFactor * size
             const height = heightFactor * size

+ 5 - 8
src/mol-repr/structure/visual/polymer-gap-cylinder.ts

@@ -10,7 +10,7 @@ import { VisualUpdateState } from '../../util';
 import { PolymerGapIterator, PolymerGapLocationIterator, markPolymerGapElement, getPolymerGapElementLoci } from './util/polymer';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
-import { SizeTheme, SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
+import { SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { LinkCylinderParams } from './util/link';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
@@ -19,23 +19,20 @@ import { CylinderProps } from 'mol-geo/primitive/cylinder';
 import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
 import { addFixedCountDashedCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 const segmentCount = 10
 
 export const PolymerGapCylinderParams = {
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 10, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 0.3, 0, 10, 0.1),
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
 }
 export const DefaultPolymerGapCylinderProps = PD.getDefaultValues(PolymerGapCylinderParams)
 export type PolymerGapCylinderProps = typeof DefaultPolymerGapCylinderProps
 
-async function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: PolymerGapCylinderProps, mesh?: Mesh) {
+async function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerGapCylinderProps, mesh?: Mesh) {
     const polymerGapCount = unit.gapElements.length
     if (!polymerGapCount) return Mesh.createEmpty(mesh)
 
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue, factor: props.sizeValue })
     const { radialSegments } = props
 
     const vertexCountEstimate = segmentCount * radialSegments * 2 * polymerGapCount * 2
@@ -60,11 +57,11 @@ async function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, stru
             pos(centerA.element, pA)
             pos(centerB.element, pB)
 
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerA)
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA)
             builder.setGroup(i)
             addFixedCountDashedCylinder(builder, pA, pB, 0.5, segmentCount, cylinderProps)
 
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerB)
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB)
             builder.setGroup(i + 1)
             addFixedCountDashedCylinder(builder, pB, pA, 0.5, segmentCount, cylinderProps)
         }

+ 3 - 7
src/mol-repr/structure/visual/polymer-trace-mesh.ts

@@ -10,18 +10,15 @@ import { VisualUpdateState } from '../../util';
 import { PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator, getPolymerElementLoci, markPolymerElement } from './util/polymer';
 import { SecondaryStructureType, isNucleic } from 'mol-model/structure/model/types';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { addSheet } from 'mol-geo/geometry/mesh/builder/sheet';
 import { addTube } from 'mol-geo/geometry/mesh/builder/tube';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 export const PolymerTraceMeshParams = {
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 0.3, 0, 10, 0.1),
     linearSegments: PD.Numeric('Linear Segments', '', 8, 1, 48, 1),
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
     aspectRatio: PD.Numeric('Aspect Ratio', '', 5, 0.1, 5, 0.1),
@@ -32,11 +29,10 @@ export type PolymerTraceMeshProps = typeof DefaultPolymerTraceMeshProps
 
 // TODO handle polymer ends properly
 
-async function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: PolymerTraceMeshProps, mesh?: Mesh) {
+async function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerTraceMeshProps, mesh?: Mesh) {
     const polymerElementCount = unit.polymerElements.length
 
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue, factor: props.sizeFactor })
     const { linearSegments, radialSegments, aspectRatio, arrowFactor } = props
 
     const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2
@@ -60,7 +56,7 @@ async function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure:
 
         interpolateCurveSegment(state, v, tension, shift)
 
-        let width = sizeTheme.size(v.center)
+        let width = theme.size.size(v.center)
         if (isCoarse) width *= aspectRatio / 2
 
         if (isSheet) {

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

@@ -11,7 +11,7 @@ import { Mat4 } from 'mol-math/linear-algebra';
 import { TransformData, createTransform, createIdentityTransform } from 'mol-geo/geometry/transform-data';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
-import { createRenderableState } from 'mol-geo/geometry/geometry';
+import { createRenderableState, Theme } from 'mol-geo/geometry/geometry';
 import { Points } from 'mol-geo/geometry/points/points';
 import { Lines } from 'mol-geo/geometry/lines/lines';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
@@ -49,16 +49,16 @@ export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
 
 type StructureMeshProps = Mesh.Props & StructureProps
 
-export async function createComplexMeshRenderObject(ctx: VisualContext, structure: Structure, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps) {
+export async function createComplexMeshRenderObject(ctx: VisualContext, structure: Structure, mesh: Mesh, locationIt: LocationIterator, theme: Theme, props: StructureMeshProps) {
     const transform = createIdentityTransform()
-    const values = await Mesh.createValues(ctx.runtime, mesh, transform, locationIt, props)
+    const values = await Mesh.createValues(ctx.runtime, mesh, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createMeshRenderObject(values, state)
 }
 
-export async function createUnitsMeshRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps) {
+export async function createUnitsMeshRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, mesh: Mesh, locationIt: LocationIterator, theme: Theme, props: StructureMeshProps) {
     const transform = createUnitsTransform(group)
-    const values = await Mesh.createValues(ctx.runtime, mesh, transform, locationIt, props)
+    const values = await Mesh.createValues(ctx.runtime, mesh, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createMeshRenderObject(values, state)
 }
@@ -67,9 +67,9 @@ export async function createUnitsMeshRenderObject(ctx: VisualContext, group: Uni
 
 type StructurePointsProps = Points.Props & StructureProps
 
-export async function createUnitsPointsRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, points: Points, locationIt: LocationIterator, props: StructurePointsProps) {
+export async function createUnitsPointsRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, points: Points, locationIt: LocationIterator, theme: Theme, props: StructurePointsProps) {
     const transform = createUnitsTransform(group)
-    const values = await Points.createValues(ctx.runtime, points, transform, locationIt, props)
+    const values = await Points.createValues(ctx.runtime, points, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createPointsRenderObject(values, state)
 }
@@ -78,9 +78,9 @@ export async function createUnitsPointsRenderObject(ctx: VisualContext, group: U
 
 type StructureLinesProps = Lines.Props & StructureProps
 
-export async function createUnitsLinesRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, props: StructureLinesProps) {
+export async function createUnitsLinesRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, theme: Theme, props: StructureLinesProps) {
     const transform = createUnitsTransform(group)
-    const values = await Lines.createValues(ctx.runtime, lines, transform, locationIt, props)
+    const values = await Lines.createValues(ctx.runtime, lines, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createLinesRenderObject(values, state)
 }
@@ -89,9 +89,9 @@ export async function createUnitsLinesRenderObject(ctx: VisualContext, group: Un
 
 type StructureDirectVolumeProps = DirectVolume.Props & StructureProps
 
-export async function createUnitsDirectVolumeRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
+export async function createUnitsDirectVolumeRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, theme: Theme, props: StructureDirectVolumeProps) {
     const transform = createUnitsTransform(group)
-    const values = await DirectVolume.createValues(ctx.runtime, directVolume, transform, locationIt, props)
+    const values = await DirectVolume.createValues(ctx.runtime, directVolume, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createDirectVolumeRenderObject(values, state)
 }

+ 3 - 6
src/mol-repr/structure/visual/util/element.ts

@@ -8,7 +8,6 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { Unit, StructureElement, Structure } from 'mol-model/structure';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { Interval, OrderedSet } from 'mol-data/int';
-import { SizeTheme, SizeThemeName } from 'mol-theme/size';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { sphereVertexCount } from 'mol-geo/primitive/sphere';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
@@ -16,18 +15,16 @@ import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { VisualContext } from 'mol-repr';
+import { Theme } from 'mol-geo/geometry/geometry';
 
 export interface ElementSphereMeshProps {
-    sizeTheme: SizeThemeName,
-    sizeValue: number,
     detail: number,
 }
 
-export async function createElementSphereMesh(ctx: VisualContext, unit: Unit, structure: Structure, props: ElementSphereMeshProps, mesh?: Mesh) {
+export async function createElementSphereMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: ElementSphereMeshProps, mesh?: Mesh) {
     const { detail } = props
 
     const { elements } = unit;
-    const sizeTheme = SizeTheme({ name: props.sizeTheme, value: props.sizeValue })
     const elementCount = elements.length;
     const vertexCount = elementCount * sphereVertexCount(detail)
     const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
@@ -42,7 +39,7 @@ export async function createElementSphereMesh(ctx: VisualContext, unit: Unit, st
         pos(elements[i], v)
 
         meshBuilder.setGroup(i)
-        addSphere(meshBuilder, v, sizeTheme.size(l), detail)
+        addSphere(meshBuilder, v, theme.size.size(l), detail)
 
         if (i % 10000 === 0 && ctx.runtime.shouldUpdate) {
             await ctx.runtime.update({ message: 'Sphere mesh', current: i, max: elementCount });

+ 0 - 4
src/mol-repr/structure/visual/util/link.ts

@@ -6,7 +6,6 @@
 
 import { Vec3 } from 'mol-math/linear-algebra';
 import { LinkType } from 'mol-model/structure/model/types';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { Unit, StructureElement, Structure, Link } from 'mol-model/structure';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
@@ -17,9 +16,6 @@ import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { VisualContext } from 'mol-repr';
 
 export const LinkCylinderParams = {
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
     linkScale: PD.Range('Link Scale', '', 0.4, 0, 1, 0.1),
     linkSpacing: PD.Range('Link Spacing', '', 1, 0, 2, 0.01),
     linkRadius: PD.Range('Link Radius', '', 0.25, 0, 10, 0.05),

+ 4 - 4
src/mol-repr/util.ts

@@ -61,16 +61,16 @@ export interface QualityProps {
     resolution: number
 }
 
-export function getQualityProps(props: Partial<QualityProps>, structure?: Structure) {
+export function getQualityProps(props: Partial<QualityProps>, data?: any) {
     let quality = defaults(props.quality, 'auto' as VisualQuality)
     let detail = defaults(props.detail, 1)
     let radialSegments = defaults(props.radialSegments, 12)
     let linearSegments = defaults(props.linearSegments, 8)
     let resolution = defaults(props.resolution, 2)
 
-    if (quality === 'auto' && structure) {
-        let score = structure.elementCount
-        if (structure.isCoarse) score *= 10
+    if (quality === 'auto' && data instanceof Structure) {
+        let score = data.elementCount
+        if (data.isCoarse) score *= 10
         if (score > 500_000) {
             quality = 'lowest'
         } else if (score > 300_000) {

+ 3 - 3
src/mol-repr/volume/direct-volume.ts

@@ -17,7 +17,7 @@ import { createTexture } from 'mol-gl/webgl/texture';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
-import { Geometry, createRenderableState } from 'mol-geo/geometry/geometry';
+import { Geometry, createRenderableState, Theme } from 'mol-geo/geometry/geometry';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { VisualUpdateState } from 'mol-repr/util';
@@ -198,9 +198,9 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
         mark: () => false,
         setUpdateState: (state: VisualUpdateState, newProps: DirectVolumeProps, currentProps: DirectVolumeProps) => {
         },
-        createRenderObject: async (ctx: VisualContext, geometry: DirectVolume, locationIt: LocationIterator, props: DirectVolumeProps) => {
+        createRenderObject: async (ctx: VisualContext, geometry: DirectVolume, locationIt: LocationIterator, theme: Theme, props: DirectVolumeProps) => {
             const transform = createIdentityTransform()
-            const values = await DirectVolume.createValues(ctx.runtime, geometry, transform, locationIt, props)
+            const values = await DirectVolume.createValues(ctx.runtime, geometry, transform, locationIt, theme, props)
             const state = createRenderableState(props)
             return createDirectVolumeRenderObject(values, state)
         },

+ 16 - 13
src/mol-repr/volume/index.ts

@@ -8,8 +8,8 @@ import { Task } from 'mol-task'
 import { RepresentationProps, Representation, Visual, RepresentationContext, VisualContext } from '..';
 import { VolumeData, VolumeIsoValue } from 'mol-model/volume';
 import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
+import { Geometry, updateRenderableState, Theme, createTheme } from 'mol-geo/geometry/geometry';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { Geometry, updateRenderableState } from 'mol-geo/geometry/geometry';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
 import { DirectVolumeRenderObject, PointsRenderObject, LinesRenderObject, MeshRenderObject } from 'mol-gl/render-object';
@@ -33,7 +33,7 @@ interface VolumeVisualBuilder<P extends VolumeProps, G extends Geometry> {
 }
 
 interface VolumeVisualGeometryBuilder<P extends VolumeProps, G extends Geometry> extends VolumeVisualBuilder<P, G> {
-    createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, currentProps: P): Promise<VolumeRenderObject>
+    createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: P): Promise<VolumeRenderObject>
     updateValues(values: RenderableValues, newProps: P): void
 }
 
@@ -48,25 +48,25 @@ export function VolumeVisual<P extends VolumeProps>(builder: VolumeVisualGeometr
     let geometry: Geometry
     let locationIt: LocationIterator
 
-    async function create(ctx: VisualContext, volume: VolumeData, props: Partial<VolumeProps> = {}) {
+    async function create(ctx: VisualContext, volume: VolumeData, theme: Theme, props: Partial<VolumeProps> = {}) {
         currentProps = Object.assign({}, defaultProps, props)
         if (props.isoValueRelative) {
             currentProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative)
-            console.log('create props.isoValueRelative', props.isoValueRelative, currentProps.isoValueAbsolute, currentVolume.dataStats)
+            // console.log('create props.isoValueRelative', props.isoValueRelative, currentProps.isoValueAbsolute, currentVolume.dataStats)
         }
 
         geometry = await createGeometry(ctx, volume, currentProps, geometry)
         locationIt = LocationIterator(1, 1, () => NullLocation)
-        renderObject = await createRenderObject(ctx, geometry, locationIt, currentProps)
+        renderObject = await createRenderObject(ctx, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, props: Partial<VolumeProps> = {}) {
+    async function update(ctx: VisualContext, theme: Theme, props: Partial<VolumeProps> = {}) {
         if (!renderObject) return
         const newProps = Object.assign({}, currentProps, props)
 
         if (props.isoValueRelative) {
             newProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative)
-            console.log('update props.isoValueRelative', props.isoValueRelative, newProps.isoValueAbsolute, currentVolume.dataStats)
+            // console.log('update props.isoValueRelative', props.isoValueRelative, newProps.isoValueAbsolute, currentVolume.dataStats)
         }
 
         VisualUpdateState.reset(updateState)
@@ -85,17 +85,17 @@ export function VolumeVisual<P extends VolumeProps>(builder: VolumeVisualGeometr
 
     return {
         get renderObject () { return renderObject },
-        async createOrUpdate(ctx: VisualContext, props: Partial<VolumeProps> = {}, volume?: VolumeData) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<VolumeProps> = {}, volume?: VolumeData) {
             if (!volume && !currentVolume) {
                 throw new Error('missing volume')
             } else if (volume && (!currentVolume || !renderObject)) {
                 currentVolume = volume
-                await create(ctx, volume, props)
+                await create(ctx, volume, theme, props)
             } else if (volume && volume !== currentVolume) {
                 currentVolume = volume
-                await create(ctx, volume, props)
+                await create(ctx, volume, theme, props)
             } else {
-                await update(ctx, props)
+                await update(ctx, theme, props)
             }
 
             currentProps = Object.assign({}, defaultProps, props)
@@ -145,10 +145,13 @@ export type VolumeProps = typeof DefaultVolumeProps
 export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
     let visual: VolumeVisual<any>
     let _props: P
+    let _theme: Theme
     let busy = false
 
     function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, volumeData?: VolumeData) {
         _props = Object.assign({}, DefaultVolumeProps, _props, props)
+        _theme = createTheme(_props)
+
         return Task.create('VolumeRepresentation.create', async runtime => {
             // TODO queue it somehow
             if (busy) return
@@ -158,11 +161,11 @@ export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeD
             } else if (volumeData && !visual) {
                 busy = true
                 visual = visualCtor(volumeData)
-                await visual.createOrUpdate({ ...ctx, runtime } , props, volumeData)
+                await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volumeData)
                 busy = false
             } else {
                 busy = true
-                await visual.createOrUpdate({ ...ctx, runtime }, props, volumeData)
+                await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volumeData)
                 busy = false
             }
         });

+ 3 - 3
src/mol-repr/volume/isosurface-mesh.ts

@@ -14,7 +14,7 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { computeMarchingCubesMesh } from 'mol-geo/util/marching-cubes/algorithm';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
-import { createRenderableState } from 'mol-geo/geometry/geometry';
+import { createRenderableState, Theme } from 'mol-geo/geometry/geometry';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { VisualUpdateState } from 'mol-repr/util';
@@ -57,9 +57,9 @@ export function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> {
         setUpdateState: (state: VisualUpdateState, newProps: IsosurfaceProps, currentProps: IsosurfaceProps) => {
             if (newProps.isoValueAbsolute !== currentProps.isoValueAbsolute) state.createGeometry = true
         },
-        createRenderObject: async (ctx: VisualContext, geometry: Mesh, locationIt: LocationIterator, props: IsosurfaceProps) => {
+        createRenderObject: async (ctx: VisualContext, geometry: Mesh, locationIt: LocationIterator, theme: Theme, props: IsosurfaceProps) => {
             const transform = createIdentityTransform()
-            const values = await Mesh.createValues(ctx.runtime, geometry, transform, locationIt, props)
+            const values = await Mesh.createValues(ctx.runtime, geometry, transform, locationIt, theme, props)
             const state = createRenderableState(props)
             return createMeshRenderObject(values, state)
         },

+ 3 - 3
src/mol-theme/color/chain-id.ts

@@ -25,7 +25,7 @@ function getAsymId(unit: Unit): StructureElement.Property<string> {
 
 export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
-    let scale: ColorScale | undefined = undefined
+    const scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
     // const table: [string, Color][] = []
 
     if (props.structure) {
@@ -41,7 +41,7 @@ export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
                 }
             })
         }
-        scale = ColorScale.create({ domain: [ 0, asymIdSerialMap.size - 1 ] })
+        scale.setDomain(0, asymIdSerialMap.size - 1)
         const scaleColor = scale.color
 
         // asymIdSerialMap.forEach((v, k) => table.push([k, scaleColor(v)]))
@@ -63,7 +63,7 @@ export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: {},
+        features: { structure: true, list: true },
         granularity: 'group',
         color,
         description: Description,

+ 1 - 1
src/mol-theme/color/cross-link.ts

@@ -49,7 +49,7 @@ export function CrossLinkColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { list: true, domain: true },
+        features: { list: true, domain: true, structure: true },
         granularity: 'group',
         color,
         description: Description,

+ 3 - 3
src/mol-theme/color/element-index.ts

@@ -15,7 +15,7 @@ const Description = 'Gives every element (atom or coarse sphere/gaussian) a uniq
 
 export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
-    let scale: ColorScale | undefined = undefined
+    let scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
 
     if (props.structure) {
         const { units } = props.structure
@@ -29,7 +29,7 @@ export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
             elementCount += units[i].elements.length
             unitIdIndex.set(units[i].id, i)
         }
-        scale = ColorScale.create({ domain: [ 0, elementCount - 1 ], list: props.list })
+        scale.setDomain(0, elementCount - 1)
         const scaleColor = scale.color
 
         color = (location: Location): Color => {
@@ -48,7 +48,7 @@ export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { list: true },
+        features: { structure: true, list: true },
         granularity: 'groupInstance',
         color,
         description: Description,

+ 2 - 2
src/mol-theme/color/polymer-index.ts

@@ -14,7 +14,7 @@ const Description = 'Gives every polymer a unique color based on the position (i
 
 export function PolymerIndexColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
-    let scale: ColorScale | undefined = undefined
+    const scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
 
     if (props.structure) {
         const { units } = props.structure
@@ -22,7 +22,7 @@ export function PolymerIndexColorTheme(props: ColorThemeProps): ColorTheme {
         for (let i = 0, il = units.length; i <il; ++i) {
             if (units[i].polymerElements.length > 0) ++polymerCount
         }
-        scale = ColorScale.create({ list: props.list, domain: [ 0, polymerCount - 1 ] })
+        scale.setDomain(0, polymerCount - 1)
         const unitIdColor = new Map<number, Color>()
         for (let i = 0, j = 0, il = units.length; i <il; ++i) {
             if (units[i].polymerElements.length > 0) {

+ 3 - 3
src/mol-theme/color/unit-index.ts

@@ -14,11 +14,11 @@ const Description = 'Gives every unit (single chain or collection of single elem
 
 export function UnitIndexColorTheme(props: ColorThemeProps): ColorTheme {
     let color: LocationColor
-    let scale: ColorScale | undefined = undefined
+    const scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
 
     if (props.structure) {
         const { units } = props.structure
-        scale = ColorScale.create({ domain: [ 0, units.length - 1 ] })
+        scale.setDomain(0, units.length - 1)
         const unitIdColor = new Map<number, Color>()
         for (let i = 0, il = units.length; i <il; ++i) {
             unitIdColor.set(units[i].id, scale.color(i))
@@ -37,7 +37,7 @@ export function UnitIndexColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: {},
+        features: { structure: true, list: true },
         granularity: 'instance',
         color,
         description: Description,