|
@@ -1,5 +1,5 @@
|
|
|
/**
|
|
|
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
|
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
|
*
|
|
|
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
|
|
*/
|
|
@@ -9,94 +9,152 @@ import { Representation, RepresentationContext, RepresentationProvider, Represen
|
|
|
import { Visual, VisualContext } from '../visual';
|
|
|
import { VolumeData } from 'mol-model/volume';
|
|
|
import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
|
|
|
-import { Geometry } from 'mol-geo/geometry/geometry';
|
|
|
+import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
|
|
|
import { ParamDefinition as PD } from 'mol-util/param-definition';
|
|
|
import { PickingId } from 'mol-geo/geometry/picking';
|
|
|
import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
|
|
|
-import { GraphicsRenderObject } from 'mol-gl/render-object';
|
|
|
+import { GraphicsRenderObject, createRenderObject } from 'mol-gl/render-object';
|
|
|
import { Interval } from 'mol-data/int';
|
|
|
-import { RenderableValues } from 'mol-gl/renderable/schema';
|
|
|
import { LocationIterator } from 'mol-geo/util/location-iterator';
|
|
|
-import { NullLocation } from 'mol-model/location';
|
|
|
import { VisualUpdateState } from 'mol-repr/util';
|
|
|
import { ValueCell } from 'mol-util';
|
|
|
import { Theme, createEmptyTheme } from 'mol-theme/theme';
|
|
|
import { Subject } from 'rxjs';
|
|
|
-import { RenderableState } from 'mol-gl/renderable';
|
|
|
import { Mat4 } from 'mol-math/linear-algebra';
|
|
|
import { BaseGeometry } from 'mol-geo/geometry/base';
|
|
|
+import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
|
|
|
+import { ColorTheme } from 'mol-theme/color';
|
|
|
+import { createColors } from 'mol-geo/geometry/color-data';
|
|
|
+import { createSizes } from 'mol-geo/geometry/size-data';
|
|
|
|
|
|
export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeData, P> { }
|
|
|
|
|
|
+function createVolumeRenderObject<G extends Geometry>(volume: VolumeData, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>) {
|
|
|
+ const { createValues, createRenderableState } = Geometry.getUtils(geometry)
|
|
|
+ const transform = createIdentityTransform()
|
|
|
+ const values = createValues(geometry, transform, locationIt, theme, props)
|
|
|
+ const state = createRenderableState(props)
|
|
|
+ return createRenderObject(geometry.kind, values, state)
|
|
|
+}
|
|
|
+
|
|
|
interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
|
|
|
defaultProps: PD.Values<P>
|
|
|
- createGeometry(ctx: VisualContext, volumeData: VolumeData, props: PD.Values<P>, geometry?: G): Promise<G>
|
|
|
+ createGeometry(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
|
|
|
+ createLocationIterator(volume: VolumeData): LocationIterator
|
|
|
getLoci(pickingId: PickingId, id: number): Loci
|
|
|
mark(loci: Loci, apply: (interval: Interval) => boolean): boolean
|
|
|
- setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>): void
|
|
|
+ setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
|
|
|
}
|
|
|
|
|
|
interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> {
|
|
|
- createRenderObject(geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): GraphicsRenderObject
|
|
|
- updateValues(values: RenderableValues, newProps: PD.Values<P>): void,
|
|
|
- updateBoundingSphere(values: RenderableValues, geometry: G): void
|
|
|
- updateRenderableState(state: RenderableState, props: PD.Values<P>): void
|
|
|
+ geometryUtils: GeometryUtils<G>
|
|
|
}
|
|
|
|
|
|
-export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>): VolumeVisual<P> {
|
|
|
- const { defaultProps, createGeometry, getLoci, mark, setUpdateState } = builder
|
|
|
- const { createRenderObject, updateValues, updateBoundingSphere, updateRenderableState } = builder
|
|
|
+export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geometry.Params<G>>(builder: VolumeVisualGeometryBuilder<P, G>): VolumeVisual<P> {
|
|
|
+ const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
|
|
|
+ const { updateValues, updateBoundingSphere, updateRenderableState } = builder.geometryUtils
|
|
|
const updateState = VisualUpdateState.create()
|
|
|
|
|
|
- let currentProps: PD.Values<P>
|
|
|
let renderObject: GraphicsRenderObject | undefined
|
|
|
+
|
|
|
+ let newProps: PD.Values<P>
|
|
|
+ let newTheme: Theme
|
|
|
+ let newVolume: VolumeData
|
|
|
+
|
|
|
+ let currentProps: PD.Values<P> = Object.assign({}, defaultProps)
|
|
|
+ let currentTheme: Theme = createEmptyTheme()
|
|
|
let currentVolume: VolumeData
|
|
|
- let geometry: Geometry
|
|
|
+
|
|
|
+ let geometry: G
|
|
|
let locationIt: LocationIterator
|
|
|
|
|
|
- async function create(ctx: VisualContext, volume: VolumeData, theme: Theme, props: Partial<PD.Values<P>> = {}) {
|
|
|
- currentProps = Object.assign({}, defaultProps, props)
|
|
|
- geometry = await createGeometry(ctx, volume, currentProps, geometry)
|
|
|
- locationIt = LocationIterator(1, 1, () => NullLocation)
|
|
|
- renderObject = createRenderObject(geometry, locationIt, theme, currentProps)
|
|
|
- }
|
|
|
+ function prepareUpdate(theme: Theme, props: Partial<PD.Values<P>>, volume: VolumeData) {
|
|
|
+ if (!volume && !currentVolume) {
|
|
|
+ throw new Error('missing volume')
|
|
|
+ }
|
|
|
|
|
|
- async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}) {
|
|
|
- if (!renderObject) return
|
|
|
- const newProps = Object.assign({}, currentProps, props)
|
|
|
+ newProps = Object.assign({}, currentProps, props)
|
|
|
+ newTheme = theme
|
|
|
+ newVolume = volume
|
|
|
|
|
|
VisualUpdateState.reset(updateState)
|
|
|
- setUpdateState(updateState, newProps, currentProps)
|
|
|
+
|
|
|
+ if (!renderObject) {
|
|
|
+ updateState.createNew = true
|
|
|
+ } else if (!currentVolume || !VolumeData.areEquivalent(newVolume, currentVolume)) {
|
|
|
+ updateState.createNew = true
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updateState.createNew) {
|
|
|
+ updateState.createGeometry = true
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ setUpdateState(updateState, newProps, currentProps, newTheme, currentTheme)
|
|
|
+
|
|
|
+ if (!ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
|
|
|
|
|
|
if (updateState.createGeometry) {
|
|
|
- geometry = await createGeometry(ctx, currentVolume, currentProps, geometry)
|
|
|
- ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
|
|
|
- updateBoundingSphere(renderObject.values, geometry)
|
|
|
+ updateState.updateColor = true
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- updateValues(renderObject.values, newProps)
|
|
|
- updateRenderableState(renderObject.state, newProps)
|
|
|
+ function update(newGeometry?: G) {
|
|
|
+ if (updateState.createNew) {
|
|
|
+ locationIt = createLocationIterator(newVolume)
|
|
|
+ if (newGeometry) {
|
|
|
+ renderObject = createVolumeRenderObject(newVolume, newGeometry, locationIt, newTheme, newProps)
|
|
|
+ } else {
|
|
|
+ throw new Error('expected geometry to be given')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!renderObject) {
|
|
|
+ throw new Error('expected renderObject to be available')
|
|
|
+ }
|
|
|
+
|
|
|
+ locationIt.reset()
|
|
|
+
|
|
|
+ if (updateState.createGeometry) {
|
|
|
+ if (newGeometry) {
|
|
|
+ ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(newGeometry))
|
|
|
+ updateBoundingSphere(renderObject.values, newGeometry)
|
|
|
+ } else {
|
|
|
+ throw new Error('expected geometry to be given')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updateState.updateSize) {
|
|
|
+ // not all geometries have size data, so check here
|
|
|
+ if ('uSize' in renderObject.values) {
|
|
|
+ createSizes(locationIt, newTheme.size, renderObject.values)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updateState.updateColor) {
|
|
|
+ createColors(locationIt, newTheme.color, renderObject.values)
|
|
|
+ }
|
|
|
+
|
|
|
+ updateValues(renderObject.values, newProps)
|
|
|
+ updateRenderableState(renderObject.state, newProps)
|
|
|
+ }
|
|
|
|
|
|
currentProps = newProps
|
|
|
+ currentTheme = newTheme
|
|
|
+ currentVolume = newVolume
|
|
|
+ if (newGeometry) geometry = newGeometry
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
get groupCount() { return locationIt ? locationIt.count : 0 },
|
|
|
get renderObject () { return renderObject },
|
|
|
async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, volume?: VolumeData) {
|
|
|
- if (!volume && !currentVolume) {
|
|
|
- throw new Error('missing volume')
|
|
|
- } else if (volume && (!currentVolume || !renderObject)) {
|
|
|
- currentVolume = volume
|
|
|
- await create(ctx, volume, theme, props)
|
|
|
- } else if (volume && volume !== currentVolume) {
|
|
|
- currentVolume = volume
|
|
|
- await create(ctx, volume, theme, props)
|
|
|
+ prepareUpdate(theme, props, volume || currentVolume)
|
|
|
+ if (updateState.createGeometry) {
|
|
|
+ const newGeometry = createGeometry(ctx, newVolume, newTheme, newProps, geometry)
|
|
|
+ return newGeometry instanceof Promise ? newGeometry.then(update) : update(newGeometry)
|
|
|
} else {
|
|
|
- await update(ctx, theme, props)
|
|
|
+ update()
|
|
|
}
|
|
|
-
|
|
|
- currentProps = Object.assign({}, defaultProps, props)
|
|
|
},
|
|
|
getLoci(pickingId: PickingId) {
|
|
|
return renderObject ? getLoci(pickingId, renderObject.id) : EmptyLoci
|
|
@@ -151,18 +209,17 @@ export const VolumeParams = {
|
|
|
}
|
|
|
export type VolumeParams = typeof VolumeParams
|
|
|
|
|
|
-export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: (volume: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
|
|
|
+export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: () => VolumeVisual<P>): VolumeRepresentation<P> {
|
|
|
let version = 0
|
|
|
const updated = new Subject<number>()
|
|
|
const renderObjects: GraphicsRenderObject[] = []
|
|
|
const _state = Representation.createState()
|
|
|
- let visual: VolumeVisual<P>
|
|
|
+ let visual: VolumeVisual<P> | undefined
|
|
|
|
|
|
let _volume: VolumeData
|
|
|
- let _props: PD.Values<P>
|
|
|
let _params: P
|
|
|
+ let _props: PD.Values<P>
|
|
|
let _theme = createEmptyTheme()
|
|
|
- let busy = false
|
|
|
|
|
|
function createOrUpdate(props: Partial<PD.Values<P>> = {}, volume?: VolumeData) {
|
|
|
if (volume && volume !== _volume) {
|
|
@@ -172,22 +229,10 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
|
|
|
}
|
|
|
_props = Object.assign({}, _props, props)
|
|
|
|
|
|
- return Task.create('VolumeRepresentation.create', async runtime => {
|
|
|
- // TODO queue it somehow
|
|
|
- if (busy) return
|
|
|
-
|
|
|
- if (!visual && !volume) {
|
|
|
- throw new Error('volume data missing')
|
|
|
- } else if (volume && !visual) {
|
|
|
- busy = true
|
|
|
- visual = visualCtor(volume)
|
|
|
- await visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, volume)
|
|
|
- busy = false
|
|
|
- } else {
|
|
|
- busy = true
|
|
|
- await visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, volume)
|
|
|
- busy = false
|
|
|
- }
|
|
|
+ return Task.create('Creating or updating VolumeRepresentation', async runtime => {
|
|
|
+ if (!visual) visual = visualCtor()
|
|
|
+ const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, volume)
|
|
|
+ if (promise) await promise
|
|
|
// update list of renderObjects
|
|
|
renderObjects.length = 0
|
|
|
if (visual && visual.renderObject) renderObjects.push(visual.renderObject)
|