/** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose */ import { Task } from 'mol-task' import { Representation, Visual, RepresentationContext, VisualContext, RepresentationProvider, RepresentationParamsGetter } from '../representation'; import { VolumeData } from 'mol-model/volume'; import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci'; import { Geometry, updateRenderableState } 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 { DirectVolumeRenderObject, PointsRenderObject, LinesRenderObject, MeshRenderObject } 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, createTheme } from 'mol-theme/theme'; import { Subject } from 'rxjs'; export interface VolumeVisual

extends Visual { } type VolumeRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject interface VolumeVisualBuilder

{ defaultProps: PD.Values

createGeometry(ctx: VisualContext, volumeData: VolumeData, props: PD.Values

, geometry?: G): Promise getLoci(pickingId: PickingId, id: number): Loci mark(loci: Loci, apply: (interval: Interval) => boolean): boolean setUpdateState(state: VisualUpdateState, newProps: PD.Values

, currentProps: PD.Values

): void } interface VolumeVisualGeometryBuilder

extends VolumeVisualBuilder { createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values

): Promise updateValues(values: RenderableValues, geometry: G, newProps: PD.Values

): void } export function VolumeVisual

(builder: VolumeVisualGeometryBuilder): VolumeVisual

{ const { defaultProps, createGeometry, getLoci, mark, setUpdateState } = builder const { createRenderObject, updateValues } = builder const updateState = VisualUpdateState.create() let currentProps: PD.Values

let renderObject: VolumeRenderObject | undefined let currentVolume: VolumeData let geometry: Geometry let locationIt: LocationIterator async function create(ctx: VisualContext, volume: VolumeData, theme: Theme, props: Partial> = {}) { currentProps = Object.assign({}, defaultProps, props) geometry = await createGeometry(ctx, volume, currentProps, geometry) locationIt = LocationIterator(1, 1, () => NullLocation) renderObject = await createRenderObject(ctx, geometry, locationIt, theme, currentProps) } async function update(ctx: VisualContext, theme: Theme, props: Partial> = {}) { if (!renderObject) return const newProps = Object.assign({}, currentProps, props) VisualUpdateState.reset(updateState) setUpdateState(updateState, newProps, currentProps) if (updateState.createGeometry) { geometry = await createGeometry(ctx, currentVolume, currentProps, geometry) ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry)) } updateValues(renderObject.values, geometry, newProps) updateRenderableState(renderObject.state, newProps) currentProps = newProps } return { get groupCount() { return locationIt ? locationIt.count : 0 }, get renderObject () { return renderObject }, async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial> = {}, 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) } else { await update(ctx, theme, props) } currentProps = Object.assign({}, defaultProps, props) }, getLoci(pickingId: PickingId) { return renderObject ? getLoci(pickingId, renderObject.id) : EmptyLoci }, mark(loci: Loci, action: MarkerAction) { if (!renderObject) return false const { tMarker } = renderObject.values const { groupCount, instanceCount } = locationIt function apply(interval: Interval) { const start = Interval.start(interval) const end = Interval.end(interval) return applyMarkerAction(tMarker.ref.value.array, start, end, action) } let changed = false if (isEveryLoci(loci)) { changed = apply(Interval.ofBounds(0, groupCount * instanceCount)) } else { changed = mark(loci, apply) } if (changed) { ValueCell.update(tMarker, tMarker.ref.value) } return changed }, setVisibility(value: boolean) { if (renderObject) renderObject.state.visible = value }, setPickable(value: boolean) { if (renderObject) renderObject.state.pickable = value }, destroy() { // TODO renderObject = undefined } } } export interface VolumeRepresentation

extends Representation { } export type VolumeRepresentationProvider

= RepresentationProvider // export const VolumeParams = { ...Geometry.Params, isoValue: PD.Numeric(0.22, { min: -1, max: 1, step: 0.01 }), } export type VolumeParams = typeof VolumeParams export function VolumeRepresentation

(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter, visualCtor: (volume: VolumeData) => VolumeVisual

): VolumeRepresentation

{ let version = 0 const updated = new Subject() const _state = Representation.createState() let visual: VolumeVisual

let _volume: VolumeData let _props: PD.Values

let _params: P let _theme: Theme let busy = false function createOrUpdate(props: Partial> = {}, volume?: VolumeData) { if (volume && volume !== _volume) { _params = getParams(ctx, volume) _volume = volume if (!_props) _props = PD.getDefaultValues(_params) } _props = Object.assign({}, _props, props) _theme = createTheme(ctx, _props, {}, _theme) 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 } updated.next(version++) }); } function getLoci(pickingId: PickingId) { return visual ? visual.getLoci(pickingId) : EmptyLoci } function mark(loci: Loci, action: MarkerAction) { return visual ? visual.mark(loci, action) : false } function setState(state: Partial) { if (state.visible !== undefined && visual) visual.setVisibility(state.visible) if (state.pickable !== undefined && visual) visual.setPickable(state.pickable) Representation.updateState(_state, state) } function destroy() { if (visual) visual.destroy() } return { label, get groupCount() { return visual ? visual.groupCount : 0 }, get renderObjects() { return visual && visual.renderObject ? [ visual.renderObject ] : [] }, get props () { return _props }, get params() { return _params }, get state() { return _state }, updated, createOrUpdate, setState, getLoci, mark, destroy } }