From 13021f42d9413f43e6e8930841d2dc7a39037d01 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Tue, 28 Aug 2018 19:53:03 -0700 Subject: [PATCH] wip, createOrUpdate method for representations and visuals --- src/apps/canvas/app.ts | 4 +- src/apps/canvas/component/app.tsx | 2 +- src/apps/canvas/component/structure.tsx | 27 +++- src/apps/canvas/index.ts | 4 +- .../canvas/{view.ts => structure-view.ts} | 65 +++++---- src/mol-geo/representation/index.ts | 6 +- src/mol-geo/representation/shape/index.ts | 23 ++-- .../structure/complex-representation.ts | 35 +---- .../structure/complex-visual.ts | 88 ++++++++----- src/mol-geo/representation/structure/index.ts | 3 + .../structure/representation/backbone.ts | 10 +- .../representation/ball-and-stick.ts | 16 +-- .../structure/representation/carbohydrate.ts | 13 +- .../structure/representation/cartoon.ts | 19 +-- .../representation/distance-restraint.ts | 10 +- .../structure/units-representation.ts | 113 ++++++++-------- .../representation/structure/units-visual.ts | 123 ++++++++++++------ .../structure/visual/element-point.ts | 123 +++++++++--------- .../structure/visual/util/common.ts | 17 ++- src/mol-geo/representation/util.ts | 17 ++- src/mol-geo/representation/volume/index.ts | 21 ++- src/mol-geo/representation/volume/surface.ts | 8 +- src/mol-view/stage.ts | 4 +- src/mol-view/state/transform.ts | 24 ++-- 24 files changed, 412 insertions(+), 363 deletions(-) rename src/apps/canvas/{view.ts => structure-view.ts} (82%) diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts index b6c450a26..57fce3f57 100644 --- a/src/apps/canvas/app.ts +++ b/src/apps/canvas/app.ts @@ -6,7 +6,7 @@ import Viewer from 'mol-view/viewer'; import { getCifFromUrl, getModelsFromMmcif } from './util'; -import { StructureView } from './view'; +import { StructureView } from './structure-view'; import { BehaviorSubject } from 'rxjs'; export class App { @@ -35,7 +35,7 @@ export class App { if (this.structureView) this.structureView.destroy() const cif = await getCifFromUrl(`https://files.rcsb.org/download/${id}.cif`) const models = await getModelsFromMmcif(cif) - this.structureView = await StructureView(this.viewer, models, { assembly: '1' }) + this.structureView = await StructureView(this.viewer, models) this.pdbIdLoaded.next(this.structureView) } } \ No newline at end of file diff --git a/src/apps/canvas/component/app.tsx b/src/apps/canvas/component/app.tsx index 73fcbdcec..6b0171fd0 100644 --- a/src/apps/canvas/component/app.tsx +++ b/src/apps/canvas/component/app.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react' -import { StructureView } from '../view'; +import { StructureView } from '../structure-view'; import { App } from '../app'; import { Viewport } from './viewport'; import { StructureComponent } from './structure'; diff --git a/src/apps/canvas/component/structure.tsx b/src/apps/canvas/component/structure.tsx index 124ccc684..70ddc7d7c 100644 --- a/src/apps/canvas/component/structure.tsx +++ b/src/apps/canvas/component/structure.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react' -import { StructureView } from '../view'; +import { StructureView } from '../structure-view'; // export function FileInput (props: { // accept: string @@ -24,6 +24,8 @@ export interface StructureComponentProps { export interface StructureComponentState { label: string + modelId: number + modelIds: { id: number, label: string }[] assemblyId: string assemblyIds: { id: string, label: string }[] symmetryFeatureId: number @@ -33,6 +35,8 @@ export interface StructureComponentState { export class StructureComponent extends React.Component<StructureComponentProps, StructureComponentState> { state = { label: this.props.structureView.label, + modelId: this.props.structureView.modelId, + modelIds: this.props.structureView.getModelIds(), assemblyId: this.props.structureView.assemblyId, assemblyIds: this.props.structureView.getAssemblyIds(), symmetryFeatureId: this.props.structureView.symmetryFeatureId, @@ -45,6 +49,8 @@ export class StructureComponent extends React.Component<StructureComponentProps, this.setState({ ...this.state, label: sv.label, + modelId: sv.modelId, + modelIds: sv.getModelIds(), assemblyId: sv.assemblyId, assemblyIds: sv.getAssemblyIds(), symmetryFeatureId: sv.symmetryFeatureId, @@ -55,12 +61,15 @@ export class StructureComponent extends React.Component<StructureComponentProps, async update(state: Partial<StructureComponentState>) { const sv = this.props.structureView + if (state.modelId !== undefined) await sv.setModel(state.modelId) if (state.assemblyId !== undefined) await sv.setAssembly(state.assemblyId) if (state.symmetryFeatureId !== undefined) await sv.setSymmetryFeature(state.symmetryFeatureId) const newState = { ...this.state, label: sv.label, + modelId: sv.modelId, + modelIds: sv.getModelIds(), assemblyId: sv.assemblyId, assemblyIds: sv.getAssemblyIds(), symmetryFeatureId: sv.symmetryFeatureId, @@ -70,8 +79,11 @@ export class StructureComponent extends React.Component<StructureComponentProps, } render() { - const { label, assemblyIds, symmetryFeatureIds } = this.state + const { label, modelIds, assemblyIds, symmetryFeatureIds } = this.state + const modelIdOptions = modelIds.map(m => { + return <option key={m.id} value={m.id}>{m.label}</option> + }) const assemblyIdOptions = assemblyIds.map(a => { return <option key={a.id} value={a.id}>{a.label}</option> }) @@ -84,6 +96,17 @@ export class StructureComponent extends React.Component<StructureComponentProps, <span>{label}</span> </div> <div> + <div> + <span>Model</span> + <select + value={this.state.modelId} + onChange={(e) => { + this.update({ modelId: parseInt(e.target.value) }) + }} + > + {modelIdOptions} + </select> + </div> <div> <span>Assembly</span> <select diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts index 49d4094c0..72a9077d0 100644 --- a/src/apps/canvas/index.ts +++ b/src/apps/canvas/index.ts @@ -11,6 +11,7 @@ import './index.html' import { App } from './app'; import { AppComponent } from './component/app'; +import { urlQueryParameter } from 'mol-util/url-query'; const elm = document.getElementById('app') as HTMLElement if (!elm) throw new Error('Can not find element with id "app".') @@ -18,4 +19,5 @@ if (!elm) throw new Error('Can not find element with id "app".') const app = new App() ReactDOM.render(React.createElement(AppComponent, { app }), elm); -app.loadPdbId('2ONK') \ No newline at end of file +const pdbid = urlQueryParameter('pdbid') +if (pdbid) app.loadPdbId(pdbid) \ No newline at end of file diff --git a/src/apps/canvas/view.ts b/src/apps/canvas/structure-view.ts similarity index 82% rename from src/apps/canvas/view.ts rename to src/apps/canvas/structure-view.ts index ca30b1692..13f8b3f16 100644 --- a/src/apps/canvas/view.ts +++ b/src/apps/canvas/structure-view.ts @@ -6,7 +6,7 @@ import { Model, Structure } from 'mol-model/structure'; import { CartoonRepresentation } from 'mol-geo/representation/structure/representation/cartoon'; -// import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick'; +import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick'; import { getStructureFromModel } from './util'; import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry'; import { ShapeRepresentation, ShapeProps } from 'mol-geo/representation/shape'; @@ -20,29 +20,31 @@ export interface StructureView { readonly assemblySymmetry: AssemblySymmetry | undefined readonly cartoon: CartoonRepresentation - // readonly ballAndStick: BallAndStickRepresentation + readonly ballAndStick: BallAndStickRepresentation readonly axes: ShapeRepresentation<ShapeProps> readonly modelId: number readonly assemblyId: string readonly symmetryFeatureId: number - setAssembly(assembly: string): Promise<void> + setModel(modelId: number): Promise<void> + getModelIds(): { id: number, label: string }[] + setAssembly(assemblyId: string): Promise<void> getAssemblyIds(): { id: string, label: string }[] - setSymmetryFeature(symmetryFeature: number): Promise<void> + setSymmetryFeature(symmetryFeatureId: number): Promise<void> getSymmetryFeatureIds(): { id: number, label: string }[] destroy: () => void } interface StructureViewProps { - assembly?: string - symmetryFeature?: number + assemblyId?: string + symmetryFeatureId?: number } -export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps): Promise<StructureView> { +export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> { const cartoon = CartoonRepresentation() - // const ballAndStick = BallAndStickRepresentation() + const ballAndStick = BallAndStickRepresentation() const axes = ShapeRepresentation() let label: string @@ -55,16 +57,24 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> let symmetryFeatureId: number async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) { + console.log('setModel', newModelId) modelId = newModelId - model = models[modelId] await AssemblySymmetry.attachFromCifOrAPI(model) assemblySymmetry = AssemblySymmetry.get(model) - await setAssembly(newAssemblyId, newSymmetryFeatureId) } + function getModelIds() { + const modelIds: { id: number, label: string }[] = [] + models.forEach((m, i) => { + modelIds.push({ id: i, label: `${i}: ${m.label} #${m.modelNum}` }) + }) + return modelIds + } + async function setAssembly(newAssemblyId?: string, newSymmetryFeatureId?: number) { + console.log('setAssembly', newAssemblyId) if (newAssemblyId !== undefined) { assemblyId = newAssemblyId } else if (model && model.symmetry.assemblies.length) { @@ -75,7 +85,6 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> assemblyId = '-1' } await getStructure() - await createStructureRepr() await setSymmetryFeature(newSymmetryFeatureId) } @@ -90,6 +99,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> } async function setSymmetryFeature(newSymmetryFeatureId?: number) { + console.log('setSymmetryFeature', newSymmetryFeatureId) if (newSymmetryFeatureId !== undefined) { symmetryFeatureId = newSymmetryFeatureId } else if (assemblySymmetry) { @@ -133,26 +143,27 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> async function createStructureRepr() { if (structure) { - await cartoon.create(structure, { + console.log('createStructureRepr') + await cartoon.createOrUpdate({ colorTheme: { name: 'chain-id' }, sizeTheme: { name: 'uniform', value: 0.2 }, useFog: false // TODO fog not working properly - }).run() + }, structure).run() - // await ballAndStick.create(structure, { - // colorTheme: { name: 'element-symbol' }, - // sizeTheme: { name: 'uniform', value: 0.1 }, - // useFog: false // TODO fog not working properly - // }).run() + await ballAndStick.createOrUpdate({ + colorTheme: { name: 'element-symbol' }, + sizeTheme: { name: 'uniform', value: 0.1 }, + useFog: false // TODO fog not working properly + }, structure).run() viewer.center(structure.boundary.sphere.center) } else { cartoon.destroy() - // ballAndStick.destroy() + ballAndStick.destroy() } viewer.add(cartoon) - // viewer.add(ballAndStick) + viewer.add(ballAndStick) } async function createSymmetryRepr() { @@ -162,11 +173,11 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry) if (axesShape) { // getClusterColorTheme(symmetryFeatureId, assemblySymmetry) - await axes.create(axesShape, { + await axes.createOrUpdate({ colorTheme: { name: 'shape-group' }, // colorTheme: { name: 'uniform', value: Color(0xFFCC22) }, useFog: false // TODO fog not working properly - }).run() + }, axesShape).run() } else { axes.destroy() } @@ -180,7 +191,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> viewer.requestDraw() } - await setModel(0, props.assembly, props.symmetryFeature) + await setModel(0, props.assemblyId, props.symmetryFeatureId) return { get label() { return label }, @@ -189,13 +200,15 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> get assemblySymmetry() { return assemblySymmetry }, cartoon, - // ballAndStick, + ballAndStick, axes, get modelId() { return modelId }, get assemblyId() { return assemblyId }, get symmetryFeatureId() { return symmetryFeatureId }, + setModel, + getModelIds, setAssembly, getAssemblyIds, setSymmetryFeature, @@ -203,12 +216,12 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model> destroy: () => { viewer.remove(cartoon) - // viewer.remove(ballAndStick) + viewer.remove(ballAndStick) viewer.remove(axes) viewer.requestDraw() cartoon.destroy() - // ballAndStick.destroy() + ballAndStick.destroy() axes.destroy() } } diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index d7cac572d..f1d635551 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -15,8 +15,7 @@ export interface RepresentationProps {} export interface Representation<D, P extends RepresentationProps = {}> { readonly renderObjects: ReadonlyArray<RenderObject> readonly props: Readonly<P> - create: (data: D, props?: Partial<P>) => Task<void> - update: (props: Partial<P>) => Task<void> + createOrUpdate: (props?: Partial<P>, data?: D) => Task<void> getLoci: (pickingId: PickingId) => Loci mark: (loci: Loci, action: MarkerAction) => void destroy: () => void @@ -24,8 +23,7 @@ export interface Representation<D, P extends RepresentationProps = {}> { export interface Visual<D, P extends RepresentationProps = {}> { readonly renderObject: RenderObject | undefined - create: (ctx: RuntimeContext, data: D, props?: Partial<P>) => Promise<void> - update: (ctx: RuntimeContext, props: Partial<P>) => Promise<boolean> + createOrUpdate: (ctx: RuntimeContext, props?: Partial<P>, data?: D) => Promise<void> getLoci: (pickingId: PickingId) => Loci mark: (loci: Loci, action: MarkerAction) => void destroy: () => void diff --git a/src/mol-geo/representation/shape/index.ts b/src/mol-geo/representation/shape/index.ts index dc6393dee..5e12d81c9 100644 --- a/src/mol-geo/representation/shape/index.ts +++ b/src/mol-geo/representation/shape/index.ts @@ -37,17 +37,20 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation let _shape: Shape let _props: P - function create(shape: Shape, props: Partial<P> = {}) { + function createOrUpdate(props: Partial<P> = {}, shape?: Shape) { _props = Object.assign({}, DefaultShapeProps, _props, props) - _shape = shape + if (shape) _shape = shape return Task.create('ShapeRepresentation.create', async ctx => { renderObjects.length = 0 - const mesh = shape.mesh - const locationIt = ShapeGroupIterator.fromShape(shape) + if (!_shape) return + + const mesh = _shape.mesh + const locationIt = ShapeGroupIterator.fromShape(_shape) const { groupCount, instanceCount } = locationIt + const transform = createIdentityTransform() const color = createColors(locationIt, _props.colorTheme) const marker = createMarkers(instanceCount * groupCount) const counts = { drawCount: mesh.triangleCount * 3, groupCount, instanceCount } @@ -55,7 +58,7 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation const values: MeshValues = { ...getMeshData(mesh), ...createMeshValues(_props, counts), - aTransform: createIdentityTransform(), + ...transform, ...color, ...marker, @@ -68,18 +71,10 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation }); } - function update(props: Partial<P>) { - return Task.create('ShapeRepresentation.update', async ctx => { - // TODO handle general update - // TODO check shape.colors.ref.version - }) - } - return { get renderObjects () { return renderObjects }, get props () { return _props }, - create, - update, + createOrUpdate, getLoci(pickingId: PickingId) { const { objectId, groupId } = pickingId if (_renderObject && _renderObject.id === objectId) { diff --git a/src/mol-geo/representation/structure/complex-representation.ts b/src/mol-geo/representation/structure/complex-representation.ts index f10ddbdf2..ed9a0b1f3 100644 --- a/src/mol-geo/representation/structure/complex-representation.ts +++ b/src/mol-geo/representation/structure/complex-representation.ts @@ -16,42 +16,18 @@ import { ComplexVisual } from './complex-visual'; export function ComplexRepresentation<P extends StructureProps>(visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> { let visual: ComplexVisual<P> | undefined - let _props: P - let _structure: Structure - function create(structure: Structure, props: Partial<P> = {}) { + function createOrUpdate(props: Partial<P> = {}, structure?: Structure) { _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure)) - _props.colorTheme.structure = structure + if (structure) _props.colorTheme.structure = structure return Task.create('Creating StructureRepresentation', async ctx => { - if (!_structure) { - visual = visualCtor() - await visual.create(ctx, structure, _props) - } else { - if (_structure.hashCode === structure.hashCode) { - await update(_props) - } else { - if (visual && !await visual.update(ctx, _props)) { - await visual.create(ctx, structure, _props) - } - } - } - _structure = structure + if (!visual) visual = visualCtor() + await visual.createOrUpdate(ctx, _props, structure) }); } - function update(props: Partial<P>) { - return Task.create('Updating StructureRepresentation', async ctx => { - _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure)) - _props.colorTheme.structure = _structure - - if (visual && !await visual.update(ctx, _props)) { - await visual.create(ctx, _structure, _props) - } - }) - } - function getLoci(pickingId: PickingId) { return visual ? visual.getLoci(pickingId) : EmptyLoci } @@ -69,8 +45,7 @@ export function ComplexRepresentation<P extends StructureProps>(visualCtor: () = return visual && visual.renderObject ? [ visual.renderObject ] : [] }, get props() { return _props }, - create, - update, + createOrUpdate, getLoci, mark, destroy diff --git a/src/mol-geo/representation/structure/complex-visual.ts b/src/mol-geo/representation/structure/complex-visual.ts index 514e8adae..3e405edc4 100644 --- a/src/mol-geo/representation/structure/complex-visual.ts +++ b/src/mol-geo/representation/structure/complex-visual.ts @@ -44,52 +44,72 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe let mesh: Mesh let currentStructure: Structure let locationIt: LocationIterator + let conformationHashCode: number - return { - get renderObject () { return renderObject }, - async create(ctx: RuntimeContext, structure: Structure, props: Partial<P> = {}) { - currentProps = Object.assign({}, defaultProps, props) - currentStructure = structure + async function create(ctx: RuntimeContext, structure: Structure, props: Partial<P> = {}) { + currentProps = Object.assign({}, defaultProps, props) + currentStructure = structure - mesh = await createMesh(ctx, currentStructure, currentProps, mesh) + conformationHashCode = Structure.conformationHash(currentStructure) + mesh = await createMesh(ctx, currentStructure, currentProps, mesh) - locationIt = createLocationIterator(structure) - renderObject = createComplexMeshRenderObject(structure, mesh, locationIt, currentProps) - }, - async update(ctx: RuntimeContext, props: Partial<P>) { - const newProps = Object.assign({}, currentProps, props) + locationIt = createLocationIterator(structure) + renderObject = createComplexMeshRenderObject(structure, mesh, locationIt, currentProps) + } - if (!renderObject) return false + async function update(ctx: RuntimeContext, props: Partial<P>) { + const newProps = Object.assign({}, currentProps, props) - locationIt.reset() - MeshUpdateState.reset(updateState) - setUpdateState(updateState, newProps, currentProps) + if (!renderObject) return false - if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) { - updateState.createMesh = true - } + locationIt.reset() + MeshUpdateState.reset(updateState) + setUpdateState(updateState, newProps, currentProps) - if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) { - updateState.updateColor = true - } + const newConformationHashCode = Structure.conformationHash(currentStructure) + if (newConformationHashCode !== conformationHashCode) { + conformationHashCode = newConformationHashCode + updateState.createMesh = true + } - // + if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.createMesh = true + if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true + // if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createMesh = true // TODO - if (updateState.createMesh) { - mesh = await createMesh(ctx, currentStructure, newProps, mesh) - ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) - updateState.updateColor = true - } + // - if (updateState.updateColor) { - createColors(locationIt, newProps.colorTheme, renderObject.values) - } + if (updateState.createMesh) { + mesh = await createMesh(ctx, currentStructure, newProps, mesh) + ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + updateState.updateColor = true + } + + if (updateState.updateColor) { + createColors(locationIt, newProps.colorTheme, renderObject.values) + } - updateMeshValues(renderObject.values, newProps) - updateRenderableState(renderObject.state, newProps) + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + currentProps = newProps + return true + } - currentProps = newProps - return true + return { + get renderObject () { return renderObject }, + async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structure?: Structure) { + if (!structure && !currentStructure) { + throw new Error('missing structure') + } else if (structure && (!currentStructure || !renderObject)) { + await create(ctx, structure, props) + } else if (structure && structure.hashCode !== currentStructure.hashCode) { + await create(ctx, structure, props) + } else { + if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) { + currentStructure = structure + } + await update(ctx, props) + } }, getLoci(pickingId: PickingId) { return renderObject ? getLoci(pickingId, currentStructure, renderObject.id) : EmptyLoci diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 06a4cba0e..7628d9e08 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -27,17 +27,20 @@ export const DefaultStructureMeshProps = { export type StructureMeshProps = typeof DefaultStructureMeshProps export interface MeshUpdateState { + updateTransform: boolean updateColor: boolean createMesh: boolean } export namespace MeshUpdateState { export function create(): MeshUpdateState { return { + updateTransform: false, updateColor: false, createMesh: false } } export function reset(state: MeshUpdateState) { + state.updateTransform = false state.updateColor = false state.createMesh = false } diff --git a/src/mol-geo/representation/structure/representation/backbone.ts b/src/mol-geo/representation/structure/representation/backbone.ts index 9d80d5ece..a76e239a5 100644 --- a/src/mol-geo/representation/structure/representation/backbone.ts +++ b/src/mol-geo/representation/structure/representation/backbone.ts @@ -30,16 +30,10 @@ export function BackboneRepresentation(): BackboneRepresentation { get props() { return { ...traceRepr.props } }, - create: (structure: Structure, props: Partial<BackboneProps> = {}) => { + createOrUpdate: (props: Partial<BackboneProps> = {}, structure?: Structure) => { currentProps = Object.assign({}, DefaultBackboneProps, props) return Task.create('BackboneRepresentation', async ctx => { - await traceRepr.create(structure, currentProps).runInContext(ctx) - }) - }, - update: (props: Partial<BackboneProps>) => { - currentProps = Object.assign(currentProps, props) - return Task.create('Updating BackboneRepresentation', async ctx => { - await traceRepr.update(currentProps).runInContext(ctx) + await traceRepr.createOrUpdate(currentProps, structure).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-geo/representation/structure/representation/ball-and-stick.ts b/src/mol-geo/representation/structure/representation/ball-and-stick.ts index ee5675b3c..3c236ede1 100644 --- a/src/mol-geo/representation/structure/representation/ball-and-stick.ts +++ b/src/mol-geo/representation/structure/representation/ball-and-stick.ts @@ -39,20 +39,12 @@ export function BallAndStickRepresentation(): BallAndStickRepresentation { get props() { return { ...elmementRepr.props, ...intraLinkRepr.props, ...interLinkRepr.props } }, - create: (structure: Structure, props: Partial<BallAndStickProps> = {}) => { + createOrUpdate: (props: Partial<BallAndStickProps> = {}, structure?: Structure) => { currentProps = Object.assign({}, DefaultBallAndStickProps, props) return Task.create('DistanceRestraintRepresentation', async ctx => { - await elmementRepr.create(structure, currentProps).runInContext(ctx) - await intraLinkRepr.create(structure, currentProps).runInContext(ctx) - await interLinkRepr.create(structure, currentProps).runInContext(ctx) - }) - }, - update: (props: Partial<BallAndStickProps>) => { - currentProps = Object.assign(currentProps, props) - return Task.create('Updating BallAndStickRepresentation', async ctx => { - await elmementRepr.update(currentProps).runInContext(ctx) - await intraLinkRepr.update(currentProps).runInContext(ctx) - await interLinkRepr.update(currentProps).runInContext(ctx) + await elmementRepr.createOrUpdate(currentProps, structure).runInContext(ctx) + await intraLinkRepr.createOrUpdate(currentProps, structure).runInContext(ctx) + await interLinkRepr.createOrUpdate(currentProps, structure).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-geo/representation/structure/representation/carbohydrate.ts b/src/mol-geo/representation/structure/representation/carbohydrate.ts index 9f547b1b2..6a01069b4 100644 --- a/src/mol-geo/representation/structure/representation/carbohydrate.ts +++ b/src/mol-geo/representation/structure/representation/carbohydrate.ts @@ -33,18 +33,11 @@ export function CarbohydrateRepresentation(): CarbohydrateRepresentation { get props() { return { ...carbohydrateSymbolRepr.props, ...carbohydrateLinkRepr.props } }, - create: (structure: Structure, props: Partial<CarbohydrateProps> = {} as CarbohydrateProps) => { + createOrUpdate: (props: Partial<CarbohydrateProps> = {}, structure?: Structure) => { currentProps = Object.assign({}, DefaultCartoonProps, props) return Task.create('Creating CarbohydrateRepresentation', async ctx => { - await carbohydrateSymbolRepr.create(structure, currentProps).runInContext(ctx) - await carbohydrateLinkRepr.create(structure, currentProps).runInContext(ctx) - }) - }, - update: (props: Partial<CarbohydrateProps>) => { - currentProps = Object.assign(currentProps, props) - return Task.create('Updating CarbohydrateRepresentation', async ctx => { - await carbohydrateSymbolRepr.update(currentProps).runInContext(ctx) - await carbohydrateLinkRepr.update(currentProps).runInContext(ctx) + await carbohydrateSymbolRepr.createOrUpdate(currentProps, structure).runInContext(ctx) + await carbohydrateLinkRepr.createOrUpdate(currentProps, structure).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-geo/representation/structure/representation/cartoon.ts b/src/mol-geo/representation/structure/representation/cartoon.ts index 62b1a2ef2..cd25442b0 100644 --- a/src/mol-geo/representation/structure/representation/cartoon.ts +++ b/src/mol-geo/representation/structure/representation/cartoon.ts @@ -41,22 +41,13 @@ export function CartoonRepresentation(): CartoonRepresentation { get props() { return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props } }, - create: (structure: Structure, props: Partial<CartoonProps> = {}) => { + createOrUpdate: (props: Partial<CartoonProps> = {}, structure?: Structure) => { currentProps = Object.assign({}, DefaultCartoonProps, props) return Task.create('Creating CartoonRepresentation', async ctx => { - await traceRepr.create(structure, currentProps).runInContext(ctx) - await gapRepr.create(structure, currentProps).runInContext(ctx) - await blockRepr.create(structure, currentProps).runInContext(ctx) - await directionRepr.create(structure, currentProps).runInContext(ctx) - }) - }, - update: (props: Partial<CartoonProps>) => { - currentProps = Object.assign(currentProps, props) - return Task.create('Updating CartoonRepresentation', async ctx => { - await traceRepr.update(currentProps).runInContext(ctx) - await gapRepr.update(currentProps).runInContext(ctx) - await blockRepr.update(currentProps).runInContext(ctx) - await directionRepr.update(currentProps).runInContext(ctx) + await traceRepr.createOrUpdate(currentProps, structure).runInContext(ctx) + await gapRepr.createOrUpdate(currentProps, structure).runInContext(ctx) + await blockRepr.createOrUpdate(currentProps, structure).runInContext(ctx) + await directionRepr.createOrUpdate(currentProps, structure).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-geo/representation/structure/representation/distance-restraint.ts b/src/mol-geo/representation/structure/representation/distance-restraint.ts index 83f4f23d6..47a340e9a 100644 --- a/src/mol-geo/representation/structure/representation/distance-restraint.ts +++ b/src/mol-geo/representation/structure/representation/distance-restraint.ts @@ -32,16 +32,10 @@ export function DistanceRestraintRepresentation(): DistanceRestraintRepresentati get props() { return { ...crossLinkRepr.props } }, - create: (structure: Structure, props: Partial<DistanceRestraintProps> = {}) => { + createOrUpdate: (props: Partial<DistanceRestraintProps> = {}, structure?: Structure) => { currentProps = Object.assign({}, DefaultDistanceRestraintProps, props) return Task.create('DistanceRestraintRepresentation', async ctx => { - await crossLinkRepr.create(structure, currentProps).runInContext(ctx) - }) - }, - update: (props: Partial<DistanceRestraintProps>) => { - currentProps = Object.assign(currentProps, props) - return Task.create('Updating DistanceRestraintRepresentation', async ctx => { - await crossLinkRepr.update(currentProps).runInContext(ctx) + await crossLinkRepr.createOrUpdate(currentProps, structure).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-geo/representation/structure/units-representation.ts b/src/mol-geo/representation/structure/units-representation.ts index 03f2945b1..8f9f05ff1 100644 --- a/src/mol-geo/representation/structure/units-representation.ts +++ b/src/mol-geo/representation/structure/units-representation.ts @@ -27,72 +27,80 @@ export function UnitsRepresentation<P extends StructureProps>(visualCtor: () => let _structure: Structure let _groups: ReadonlyArray<Unit.SymmetryGroup> - function create(structure: Structure, props: Partial<P> = {}) { + function createOrUpdate(props: Partial<P> = {}, structure?: Structure) { _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure)) - _props.colorTheme!.structure = structure + _props.colorTheme.structure = structure - return Task.create('Creating StructureRepresentation', async ctx => { - if (!_structure) { + return Task.create('Creating or updating StructureRepresentation', async ctx => { + if (!_structure && !structure) { + throw new Error('missing structure') + } else if (structure && !_structure) { + // First call with a structure, create visuals for each group. _groups = structure.unitSymmetryGroups; for (let i = 0; i < _groups.length; i++) { const group = _groups[i]; const visual = visualCtor() - await visual.create(ctx, group, _props) + await visual.createOrUpdate(ctx, _props, group) visuals.set(group.hashCode, { visual, group }) } - } else { - if (_structure.hashCode === structure.hashCode) { - await update(_props) - } else { - _groups = structure.unitSymmetryGroups; - const newGroups: Unit.SymmetryGroup[] = [] - const oldUnitsVisuals = visuals - visuals = new Map() - for (let i = 0; i < _groups.length; i++) { - const group = _groups[i]; - const visualGroup = oldUnitsVisuals.get(group.hashCode) - if (visualGroup) { - const { visual, group } = visualGroup - if (!await visual.update(ctx, _props)) { - await visual.create(ctx, group, _props) - } - oldUnitsVisuals.delete(group.hashCode) - } else { - newGroups.push(group) - const visual = visualCtor() - await visual.create(ctx, group, _props) - visuals.set(group.hashCode, { visual, group }) - } + } else if (structure && _structure.hashCode !== structure.hashCode) { + // Tries to re-use existing visuals for the groups of the new structure. + // Creates additional visuals if needed, destroys left-over visuals. + _groups = structure.unitSymmetryGroups; + // const newGroups: Unit.SymmetryGroup[] = [] + const oldVisuals = visuals + visuals = new Map() + for (let i = 0; i < _groups.length; i++) { + const group = _groups[i]; + const visualGroup = oldVisuals.get(group.hashCode) + if (visualGroup) { + const { visual } = visualGroup + await visual.createOrUpdate(ctx, _props, group) + visuals.set(group.hashCode, { visual, group }) + oldVisuals.delete(group.hashCode) + } else { + // newGroups.push(group) + const visual = visualCtor() + await visual.createOrUpdate(ctx, _props, group) + visuals.set(group.hashCode, { visual, group }) } + } + oldVisuals.forEach(({ visual }) => visual.destroy()) - // for new groups, re-use leftover visuals - const unusedVisuals: UnitsVisual<P>[] = [] - oldUnitsVisuals.forEach(({ visual }) => unusedVisuals.push(visual)) - newGroups.forEach(async group => { - const visual = unusedVisuals.pop() || visualCtor() - await visual.create(ctx, group, _props) - visuals.set(group.hashCode, { visual, group }) - }) - unusedVisuals.forEach(visual => visual.destroy()) + // For new groups, re-use left-over visuals + // const unusedVisuals: UnitsVisual<P>[] = [] + // oldVisuals.forEach(({ visual }) => unusedVisuals.push(visual)) + // newGroups.forEach(async group => { + // const visual = unusedVisuals.pop() || visualCtor() + // await visual.createOrUpdate(ctx, _props, group) + // visuals.set(group.hashCode, { visual, group }) + // }) + // unusedVisuals.forEach(visual => visual.destroy()) + } else if (structure && _structure.hashCode === structure.hashCode) { + // Expects that for structures with the same hashCode, + // the unitSymmetryGroups are the same as well. + // Re-uses existing visuals for the groups of the new structure. + _groups = structure.unitSymmetryGroups; + for (let i = 0; i < _groups.length; i++) { + const group = _groups[i]; + const visualGroup = visuals.get(group.hashCode) + if (visualGroup) { + await visualGroup.visual.createOrUpdate(ctx, _props, group) + visualGroup.group = group + } else { + throw new Error(`expected to find visual for hashCode ${group.hashCode}`) + } } + } else { + // No new structure given, just update all visuals with new props. + visuals.forEach(async ({ visual, group }) => { + await visual.createOrUpdate(ctx, _props, group) + }) } - _structure = structure + if (structure) _structure = structure }); } - function update(props: Partial<P>) { - return Task.create('Updating StructureRepresentation', async ctx => { - _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure)) - _props.colorTheme!.structure = _structure - - visuals.forEach(async ({ visual, group }) => { - if (!await visual.update(ctx, _props)) { - await visual.create(ctx, group, _props) - } - }) - }) - } - function getLoci(pickingId: PickingId) { let loci: Loci = EmptyLoci visuals.forEach(({ visual }) => { @@ -122,8 +130,7 @@ export function UnitsRepresentation<P extends StructureProps>(visualCtor: () => get props() { return _props }, - create, - update, + createOrUpdate, getLoci, mark, destroy diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index ff390f123..fe22e9fae 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/src/mol-geo/representation/structure/units-visual.ts @@ -11,13 +11,14 @@ import { RuntimeContext } from 'mol-task'; import { PickingId } from '../../util/picking'; import { LocationIterator } from '../../util/location-iterator'; import { Mesh } from '../../mesh/mesh'; -import { MarkerAction, applyMarkerAction } from '../../util/marker-data'; +import { MarkerAction, applyMarkerAction, createMarkers } from '../../util/marker-data'; import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci'; import { MeshRenderObject } from 'mol-gl/render-object'; -import { createUnitsMeshRenderObject, createColors } from './visual/util/common'; -import { deepEqual, ValueCell } from 'mol-util'; +import { createUnitsMeshRenderObject, createColors, createTransforms } from './visual/util/common'; +import { deepEqual, ValueCell, UUID } from 'mol-util'; import { updateMeshValues, updateRenderableState } from '../util'; import { Interval } from 'mol-data/int'; +import { fillSerial } from 'mol-util/array'; export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { } @@ -45,56 +46,89 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu let mesh: Mesh let currentGroup: Unit.SymmetryGroup let locationIt: LocationIterator + let currentConformationId: UUID - return { - get renderObject () { return renderObject }, - async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) { - currentProps = Object.assign({}, defaultProps, props) - currentGroup = group + async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) { + currentProps = Object.assign({}, defaultProps, props) + currentGroup = group - const unit = group.units[0] - mesh = currentProps.unitKinds.includes(unit.kind) - ? await createMesh(ctx, unit, currentProps, mesh) - : Mesh.createEmpty(mesh) + const unit = group.units[0] + currentConformationId = Unit.conformationId(unit) + mesh = currentProps.unitKinds.includes(unit.kind) + ? await createMesh(ctx, unit, currentProps, mesh) + : Mesh.createEmpty(mesh) - locationIt = createLocationIterator(group) - renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps) - }, - async update(ctx: RuntimeContext, props: Partial<P>) { - const newProps = Object.assign({}, currentProps, props) - const unit = currentGroup.units[0] + locationIt = createLocationIterator(group) + renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps) + } - if (!renderObject) return false + async function update(ctx: RuntimeContext, props: Partial<P> = {}) { + if (!renderObject) return - locationIt.reset() - MeshUpdateState.reset(updateState) - setUpdateState(updateState, newProps, currentProps) + const newProps = Object.assign({}, currentProps, props) + const unit = currentGroup.units[0] - if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) { - updateState.createMesh = true - } + locationIt.reset() + MeshUpdateState.reset(updateState) + setUpdateState(updateState, newProps, currentProps) - if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) { - updateState.updateColor = true - } + const newConformationId = Unit.conformationId(unit) + if (newConformationId !== currentConformationId) { + currentConformationId = newConformationId + updateState.createMesh = true + } - // + if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - if (updateState.createMesh) { - mesh = await createMesh(ctx, unit, newProps, mesh) - ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) - updateState.updateColor = true - } + if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.createMesh = true + if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createMesh = true - if (updateState.updateColor) { - createColors(locationIt, newProps.colorTheme, renderObject.values) - } + // - updateMeshValues(renderObject.values, newProps) - updateRenderableState(renderObject.state, newProps) + if (updateState.updateTransform) { + locationIt = createLocationIterator(currentGroup) + const { instanceCount, groupCount } = locationIt + createTransforms(currentGroup, renderObject.values) + createMarkers(instanceCount * groupCount, renderObject.values) + ValueCell.update(renderObject.values.instanceCount, instanceCount) + ValueCell.update(renderObject.values.aInstance, fillSerial(new Float32Array(instanceCount))) // TODO + updateState.updateColor = true + } - currentProps = newProps - return true + if (updateState.createMesh) { + mesh = newProps.unitKinds.includes(unit.kind) + ? await createMesh(ctx, unit, newProps, mesh) + : Mesh.createEmpty(mesh) + ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + updateState.updateColor = true + } + + if (updateState.updateColor) { + createColors(locationIt, newProps.colorTheme, renderObject.values) + } + + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + currentProps = newProps + } + + return { + get renderObject () { return renderObject }, + async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, group?: Unit.SymmetryGroup) { + if (!group && !currentGroup) { + throw new Error('missing group') + } else if (group && (!currentGroup || !renderObject)) { + await create(ctx, group, props) + } else if (group && group.hashCode !== currentGroup.hashCode) { + await create(ctx, group, props) + } else { + if (group && !areGroupsIdentical(group, currentGroup)) { + currentGroup = group + } + await update(ctx, props) + } }, getLoci(pickingId: PickingId) { return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci @@ -126,4 +160,11 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu renderObject = undefined } } +} + +function areGroupsIdentical(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) { + return ( + groupA.units.length === groupB.units.length && + Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0]) + ) } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/element-point.ts b/src/mol-geo/representation/structure/visual/element-point.ts index 1cfca7104..b374bf8fe 100644 --- a/src/mol-geo/representation/structure/visual/element-point.ts +++ b/src/mol-geo/representation/structure/visual/element-point.ts @@ -22,6 +22,7 @@ import { MarkerAction, createMarkers } from '../../../util/marker-data'; import { Vec3 } from 'mol-math/linear-algebra'; import { fillSerial } from 'mol-util/array'; import { SizeThemeProps } from 'mol-view/theme/size'; +import { LocationIterator } from '../../../util/location-iterator'; export const DefaultPointProps = { ...DefaultStructureProps, @@ -51,76 +52,72 @@ export default function PointVisual(): UnitsVisual<PointProps> { let renderObject: PointRenderObject | undefined let currentProps = DefaultPointProps let currentGroup: Unit.SymmetryGroup + let locationIt: LocationIterator let _units: ReadonlyArray<Unit> let _elements: SortedArray return { get renderObject () { return renderObject }, - async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PointProps = {}) { - currentProps = Object.assign({}, DefaultPointProps, props) - currentGroup = group - - _units = group.units - _elements = group.elements; - - const { colorTheme, sizeTheme } = currentProps - const elementCount = _elements.length - const instanceCount = group.units.length - - const locationIt = StructureElementIterator.fromGroup(group) - - const vertices = createPointVertices(_units[0]) - const transforms = createTransforms(group) - const color = createColors(locationIt, colorTheme) - const size = createSizes(locationIt, sizeTheme) - const marker = createMarkers(instanceCount * elementCount) - - const values: PointValues = { - aPosition: ValueCell.create(vertices), - aGroup: ValueCell.create(fillSerial(new Float32Array(elementCount))), - aTransform: transforms, - aInstance: ValueCell.create(fillSerial(new Float32Array(instanceCount))), - ...color, - ...marker, - ...size, - - uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), - uInstanceCount: ValueCell.create(instanceCount), - uGroupCount: ValueCell.create(group.elements.length), - - drawCount: ValueCell.create(vertices.length / 3), - instanceCount: ValueCell.create(instanceCount), - - dPointSizeAttenuation: ValueCell.create(true), - dUseFog: ValueCell.create(defaults(props.useFog, true)), + async createOrUpdate(ctx: RuntimeContext, props: PointProps = {}, group?: Unit.SymmetryGroup) { + if (!group && !currentGroup) { + throw new Error('missing group') + } else if (group && !currentGroup) { + currentProps = Object.assign({}, DefaultPointProps, props) + currentGroup = group + locationIt = StructureElementIterator.fromGroup(group) + + _units = group.units + _elements = group.elements; + + const { colorTheme, sizeTheme } = currentProps + const elementCount = _elements.length + const instanceCount = group.units.length + + const vertices = createPointVertices(_units[0]) + const transform = createTransforms(group) + const color = createColors(locationIt, colorTheme) + const size = createSizes(locationIt, sizeTheme) + const marker = createMarkers(instanceCount * elementCount) + + const values: PointValues = { + aPosition: ValueCell.create(vertices), + aGroup: ValueCell.create(fillSerial(new Float32Array(elementCount))), + aInstance: ValueCell.create(fillSerial(new Float32Array(instanceCount))), + ...transform, + ...color, + ...marker, + ...size, + + uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), + uInstanceCount: ValueCell.create(instanceCount), + uGroupCount: ValueCell.create(group.elements.length), + + drawCount: ValueCell.create(vertices.length / 3), + instanceCount: ValueCell.create(instanceCount), + + dPointSizeAttenuation: ValueCell.create(true), + dUseFog: ValueCell.create(defaults(props.useFog, true)), + } + const state: RenderableState = { + depthMask: defaults(props.depthMask, true), + visible: defaults(props.visible, true) + } + + renderObject = createPointRenderObject(values, state) + } else if (renderObject) { + const newProps = { ...currentProps, ...props } + + if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) { + createColors(locationIt, newProps.colorTheme, renderObject.values) + } + + if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) { + createSizes(locationIt, newProps.sizeTheme, renderObject.values) + } + + currentProps = newProps } - const state: RenderableState = { - depthMask: defaults(props.depthMask, true), - visible: defaults(props.visible, true) - } - - renderObject = createPointRenderObject(values, state) - }, - async update(ctx: RuntimeContext, props: PointProps) { - if (!renderObject || !_units || !_elements) return false - - const newProps = { ...currentProps, ...props } - if (deepEqual(currentProps, newProps)) { - console.log('props identical, nothing to change') - return true - } - - if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) { - console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme) - } - - if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) { - console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme) - } - - currentProps = newProps - return false }, getLoci(pickingId: PickingId) { return renderObject ? getElementLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci diff --git a/src/mol-geo/representation/structure/visual/util/common.ts b/src/mol-geo/representation/structure/visual/util/common.ts index e069563c8..7dd651ecf 100644 --- a/src/mol-geo/representation/structure/visual/util/common.ts +++ b/src/mol-geo/representation/structure/visual/util/common.ts @@ -15,21 +15,26 @@ import { LocationIterator } from '../../../../util/location-iterator'; import { Mesh } from '../../../../mesh/mesh'; import { MeshValues } from 'mol-gl/renderable'; import { getMeshData } from '../../../../util/mesh-data'; -import { MeshProps, createMeshValues, createRenderableState, createIdentityTransform } from '../../../util'; +import { MeshProps, createMeshValues, createRenderableState, createIdentityTransform, TransformData } from '../../../util'; import { StructureProps } from '../..'; import { createMarkers } from '../../../../util/marker-data'; import { createMeshRenderObject } from 'mol-gl/render-object'; import { ColorThemeProps, ColorTheme } from 'mol-view/theme/color'; import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; -export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) { +export function createTransforms({ units }: Unit.SymmetryGroup, transformData?: TransformData) { const unitCount = units.length const n = unitCount * 16 - const array = transforms && transforms.ref.value.length >= n ? transforms.ref.value : new Float32Array(n) + const array = transformData && transformData.aTransform && transformData.aTransform.ref.value.length >= n ? transformData.aTransform.ref.value : new Float32Array(n) for (let i = 0; i < unitCount; i++) { Mat4.toArray(units[i].conformation.operator.matrix, array, i * 16) } - return transforms ? ValueCell.update(transforms, array) : ValueCell.create(array) + if (transformData) { + ValueCell.update(transformData.aTransform, array) + return transformData + } else { + return { aTransform: ValueCell.create(array) } + } } export function createColors(locationIt: LocationIterator, props: ColorThemeProps, colorData?: ColorData) { @@ -54,7 +59,7 @@ export function createSizes(locationIt: LocationIterator, props: SizeThemeProps, type StructureMeshProps = Required<MeshProps & StructureProps> -function _createMeshValues(transforms: ValueCell<Float32Array>, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps): MeshValues { +function _createMeshValues(transforms: TransformData, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps): MeshValues { const { instanceCount, groupCount } = locationIt const color = createColors(locationIt, props.colorTheme) const marker = createMarkers(instanceCount * groupCount) @@ -65,7 +70,7 @@ function _createMeshValues(transforms: ValueCell<Float32Array>, mesh: Mesh, loca ...getMeshData(mesh), ...color, ...marker, - aTransform: transforms, + ...transforms, elements: mesh.indexBuffer, ...createMeshValues(props, counts) } diff --git a/src/mol-geo/representation/util.ts b/src/mol-geo/representation/util.ts index 4edca150e..7bcb56266 100644 --- a/src/mol-geo/representation/util.ts +++ b/src/mol-geo/representation/util.ts @@ -29,10 +29,19 @@ export const DefaultMeshProps = { } export type MeshProps = typeof DefaultMeshProps +export type TransformData = { + aTransform: ValueCell<Float32Array>, +} + const identityTransform = new Float32Array(16) Mat4.toArray(Mat4.identity(), identityTransform, 0) -export function createIdentityTransform(transforms?: ValueCell<Float32Array>) { - return transforms ? ValueCell.update(transforms, identityTransform) : ValueCell.create(identityTransform) +export function createIdentityTransform(transformData?: TransformData): TransformData { + if (transformData) { + ValueCell.update(transformData.aTransform, identityTransform) + return transformData + } else { + return { aTransform: ValueCell.create(identityTransform) } + } } type Counts = { drawCount: number, groupCount: number, instanceCount: number } @@ -90,12 +99,12 @@ interface QualityProps { radialSegments: number } -export function getQualityProps(props: Partial<QualityProps>, structure: Structure) { +export function getQualityProps(props: Partial<QualityProps>, structure?: Structure) { let quality = defaults(props.quality, 'auto' as VisualQuality) let detail = 1 let radialSegments = 12 - if (quality === 'auto') { + if (quality === 'auto' && structure) { const score = structure.elementCount if (score > 500_000) { quality = 'lowest' diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index b2a6afeda..588712d7a 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -27,25 +27,24 @@ export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeD let _volumeData: VolumeData let _props: P - function create(volumeData: VolumeData, props: Partial<P> = {}) { + function createOrUpdate(props: Partial<P> = {}, volumeData?: VolumeData) { _props = Object.assign({}, DefaultVolumeProps, _props, props) return Task.create('VolumeRepresentation.create', async ctx => { - _volumeData = volumeData - const visual = visualCtor(_volumeData) - await visual.create(ctx, _volumeData, props) - if (visual.renderObject) renderObjects.push(visual.renderObject) + if (volumeData) { + _volumeData = volumeData + const visual = visualCtor(_volumeData) + await visual.createOrUpdate(ctx, props, _volumeData) + if (visual.renderObject) renderObjects.push(visual.renderObject) + } else { + throw new Error('missing volumeData') + } }); } - function update(props: Partial<P>) { - return Task.create('VolumeRepresentation.update', async ctx => {}) - } - return { get renderObjects () { return renderObjects }, get props () { return _props }, - create, - update, + createOrUpdate, getLoci(pickingId: PickingId) { // TODO return EmptyLoci diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index 1d289273d..7fa9446da 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -57,9 +57,11 @@ export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { return { get renderObject () { return renderObject }, - async create(ctx: RuntimeContext, volume: VolumeData, props: SurfaceProps = {}) { + async createOrUpdate(ctx: RuntimeContext, props: SurfaceProps = {}, volume?: VolumeData) { props = { ...DefaultSurfaceProps, ...props } + if (!volume) return + const mesh = await computeVolumeSurface(volume, curProps.isoValue).runAsChild(ctx) if (!props.flatShaded) { Mesh.computeNormalsImmediate(mesh) @@ -97,10 +99,6 @@ export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { renderObject = createMeshRenderObject(values, state) }, - async update(ctx: RuntimeContext, props: SurfaceProps) { - // TODO - return false - }, getLoci(pickingId: PickingId) { // TODO return EmptyLoci diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 6ecac7aa5..271b2f9d7 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -122,8 +122,8 @@ export class Stage { // this.loadMmcifUrl(`../../examples/1crn.cif`) // this.loadPdbid('5u0q') // mixed dna/rna in same polymer // this.loadPdbid('1xj9') // PNA (peptide nucleic acid) - this.loadPdbid('5eme') // PNA (peptide nucleic acid) and RNA - // this.loadPdbid('5eme') // temp + // this.loadPdbid('5eme') // PNA (peptide nucleic acid) and RNA + this.loadPdbid('2X3T') // temp // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000002.cif`) // ok diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index bda97a320..bb454c3c5 100644 --- a/src/mol-view/state/transform.ts +++ b/src/mol-view/state/transform.ts @@ -99,7 +99,7 @@ export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEnti export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill', async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<SpacefillProps> = {}) { const spacefillRepr = SpacefillRepresentation() - await spacefillRepr.create(structureEntity.value, props).run(ctx.log) + await spacefillRepr.createOrUpdate(props, structureEntity.value).run(ctx.log) ctx.viewer.add(spacefillRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -110,7 +110,7 @@ export type StructureToBallAndStick = StateTransform<StructureEntity, BallAndSti export const StructureToBallAndStick: StructureToBallAndStick = StateTransform.create('structure', 'ballandstick', 'structure-to-ballandstick', async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<BallAndStickProps> = {}) { const ballAndStickRepr = BallAndStickRepresentation() - await ballAndStickRepr.create(structureEntity.value, props).run(ctx.log) + await ballAndStickRepr.createOrUpdate(props, structureEntity.value).run(ctx.log) ctx.viewer.add(ballAndStickRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -121,7 +121,7 @@ export type StructureToDistanceRestraint = StateTransform<StructureEntity, Dista export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateTransform.create('structure', 'distancerestraint', 'structure-to-distancerestraint', async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<DistanceRestraintProps> = {}) { const distanceRestraintRepr = DistanceRestraintRepresentation() - await distanceRestraintRepr.create(structureEntity.value, props).run(ctx.log) + await distanceRestraintRepr.createOrUpdate(props, structureEntity.value).run(ctx.log) ctx.viewer.add(distanceRestraintRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -132,7 +132,7 @@ export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity export const StructureToBackbone: StructureToBackbone = StateTransform.create('structure', 'backbone', 'structure-to-backbone', async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<BackboneProps> = {}) { const backboneRepr = BackboneRepresentation() - await backboneRepr.create(structureEntity.value, props).run(ctx.log) + await backboneRepr.createOrUpdate(props, structureEntity.value).run(ctx.log) ctx.viewer.add(backboneRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -143,7 +143,7 @@ export type StructureToCartoon = StateTransform<StructureEntity, CartoonEntity, export const StructureToCartoon: StructureToCartoon = StateTransform.create('structure', 'cartoon', 'structure-to-cartoon', async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<CartoonProps> = {}) { const cartoonRepr = CartoonRepresentation() - await cartoonRepr.create(structureEntity.value, props).run(ctx.log) + await cartoonRepr.createOrUpdate(props, structureEntity.value).run(ctx.log) ctx.viewer.add(cartoonRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -154,7 +154,7 @@ export type StructureToCarbohydrate = StateTransform<StructureEntity, Carbohydra export const StructureToCarbohydrate: StructureToCarbohydrate = StateTransform.create('structure', 'carbohydrate', 'structure-to-cartoon', async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<CarbohydrateProps> = {}) { const carbohydrateRepr = CarbohydrateRepresentation() - await carbohydrateRepr.create(structureEntity.value, props).run(ctx.log) + await carbohydrateRepr.createOrUpdate(props, structureEntity.value).run(ctx.log) ctx.viewer.add(carbohydrateRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -165,7 +165,7 @@ export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, Partia export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update', async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: Partial<SpacefillProps> = {}) { const spacefillRepr = spacefillEntity.value - await spacefillRepr.update(props).run(ctx.log) + await spacefillRepr.createOrUpdate(props).run(ctx.log) ctx.viewer.add(spacefillRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -176,7 +176,7 @@ export type BallAndStickUpdate = StateTransform<BallAndStickEntity, NullEntity, export const BallAndStickUpdate: BallAndStickUpdate = StateTransform.create('ballandstick', 'null', 'ballandstick-update', async function (ctx: StateContext, ballAndStickEntity: BallAndStickEntity, props: Partial<BallAndStickProps> = {}) { const ballAndStickRepr = ballAndStickEntity.value - await ballAndStickRepr.update(props).run(ctx.log) + await ballAndStickRepr.createOrUpdate(props).run(ctx.log) ctx.viewer.add(ballAndStickRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -187,7 +187,7 @@ export type DistanceRestraintUpdate = StateTransform<DistanceRestraintEntity, Nu export const DistanceRestraintUpdate: DistanceRestraintUpdate = StateTransform.create('distancerestraint', 'null', 'distancerestraint-update', async function (ctx: StateContext, distanceRestraintEntity: DistanceRestraintEntity, props: Partial<DistanceRestraintProps> = {}) { const distanceRestraintRepr = distanceRestraintEntity.value - await distanceRestraintRepr.update(props).run(ctx.log) + await distanceRestraintRepr.createOrUpdate(props).run(ctx.log) ctx.viewer.add(distanceRestraintRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -198,7 +198,7 @@ export type BackboneUpdate = StateTransform<BackboneEntity, NullEntity, Partial< export const BackboneUpdate: BackboneUpdate = StateTransform.create('backbone', 'null', 'backbone-update', async function (ctx: StateContext, backboneEntity: BackboneEntity, props: Partial<BackboneProps> = {}) { const backboneRepr = backboneEntity.value - await backboneRepr.update(props).run(ctx.log) + await backboneRepr.createOrUpdate(props).run(ctx.log) ctx.viewer.add(backboneRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -209,7 +209,7 @@ export type CartoonUpdate = StateTransform<CartoonEntity, NullEntity, Partial<Ca export const CartoonUpdate: CartoonUpdate = StateTransform.create('cartoon', 'null', 'cartoon-update', async function (ctx: StateContext, cartoonEntity: CartoonEntity, props: Partial<CartoonProps> = {}) { const cartoonRepr = cartoonEntity.value - await cartoonRepr.update(props).run(ctx.log) + await cartoonRepr.createOrUpdate(props).run(ctx.log) ctx.viewer.add(cartoonRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) @@ -220,7 +220,7 @@ export type CarbohydrateUpdate = StateTransform<CarbohydrateEntity, NullEntity, export const CarbohydrateUpdate: CarbohydrateUpdate = StateTransform.create('carbohydrate', 'null', 'carbohydrate-update', async function (ctx: StateContext, carbohydrateEntity: CarbohydrateEntity, props: Partial<CarbohydrateProps> = {}) { const carbohydrateRepr = carbohydrateEntity.value - await carbohydrateRepr.update(props).run(ctx.log) + await carbohydrateRepr.createOrUpdate(props).run(ctx.log) ctx.viewer.add(carbohydrateRepr) ctx.viewer.requestDraw() console.log('stats', ctx.viewer.stats) -- GitLab