From d87387125cd0988cf1e66f2bf6c2ef4f7ce5a85e Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sat, 16 Jun 2018 15:53:31 +0200 Subject: [PATCH] wip, repr --- src/mol-geo/representation/index.ts | 14 +- src/mol-geo/representation/structure/bond.ts | 5 +- src/mol-geo/representation/structure/index.ts | 181 +++++++++++------- src/mol-geo/representation/structure/point.ts | 5 +- .../representation/structure/spacefill.ts | 7 +- src/mol-geo/representation/volume/index.ts | 43 +++-- src/mol-geo/representation/volume/surface.ts | 5 +- src/mol-model/structure/structure/symmetry.ts | 8 +- src/mol-model/structure/structure/unit.ts | 6 +- 9 files changed, 176 insertions(+), 98 deletions(-) diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index 0f0e04bf4..8318003e3 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Task } from 'mol-task' +import { Task, RuntimeContext } from 'mol-task' import { RenderObject } from 'mol-gl/render-object' import { PickingId } from '../util/picking'; import { Loci } from 'mol-model/loci'; @@ -13,9 +13,19 @@ import { MarkerAction } from '../util/marker-data'; export interface RepresentationProps {} export interface Representation<D, P extends RepresentationProps = {}> { - renderObjects: ReadonlyArray<RenderObject> + readonly renderObjects: ReadonlyArray<RenderObject> create: (data: D, props?: P) => Task<void> update: (props: P) => Task<void> getLoci: (pickingId: PickingId) => Loci mark: (loci: Loci, action: MarkerAction) => void + destroy: () => void +} + +export interface Visual<D, P extends RepresentationProps = {}> { + readonly renderObjects: ReadonlyArray<RenderObject> + create: (ctx: RuntimeContext, data: D, props: P) => Promise<void> + update: (ctx: RuntimeContext, props: P) => Promise<boolean> + getLoci: (pickingId: PickingId) => Loci + mark: (loci: Loci, action: MarkerAction) => void + destroy: () => void } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/bond.ts index 8f8cd4511..7f5841e15 100644 --- a/src/mol-geo/representation/structure/bond.ts +++ b/src/mol-geo/representation/structure/bond.ts @@ -78,7 +78,7 @@ export const DefaultBondProps = { } export type BondProps = Partial<typeof DefaultBondProps> -export default function IntraUnitBonds(): UnitsVisual<BondProps> { +export default function IntraUnitBondVisual(): UnitsVisual<BondProps> { const renderObjects: RenderObject[] = [] let cylinders: MeshRenderObject let currentProps: typeof DefaultBondProps @@ -203,6 +203,9 @@ export default function IntraUnitBonds(): UnitsVisual<BondProps> { if (changed) { ValueCell.update(tMarker, tMarker.ref.value) } + }, + destroy() { + // TODO } } } diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 91c4227b4..288135c4d 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -6,29 +6,19 @@ */ import { Structure, StructureSymmetry, Unit } from 'mol-model/structure'; -import { Task, RuntimeContext } from 'mol-task' +import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; -import { Representation, RepresentationProps } from '..'; +import { Representation, RepresentationProps, Visual } from '..'; import { ColorTheme } from '../../theme'; import { PickingId } from '../../util/picking'; import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; import { MarkerAction } from '../../util/marker-data'; -export interface UnitsVisual<P> { - readonly renderObjects: ReadonlyArray<RenderObject> - create: (ctx: RuntimeContext, group: Unit.SymmetryGroup, props: P) => Promise<void> - update: (ctx: RuntimeContext, props: P) => Promise<boolean> - getLoci: (pickingId: PickingId) => Loci - mark: (loci: Loci, action: MarkerAction) => void -} +export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { } +export interface StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { } export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { } -interface GroupVisual<T> { - readonly visual: UnitsVisual<T> - readonly group: Unit.SymmetryGroup -} - export const DefaultStructureProps = { colorTheme: { name: 'instance-index' } as ColorTheme, alpha: 1, @@ -39,68 +29,123 @@ export const DefaultStructureProps = { } export type StructureProps = Partial<typeof DefaultStructureProps> -export function StructureRepresentation<P extends StructureProps>(visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> { - const renderObjects: RenderObject[] = [] - const groupVisuals: GroupVisual<P>[] = [] - // let currentProps: typeof DefaultStructureProps +export function StructureRepresentation<P extends StructureProps>(unitsVisualCtor: () => UnitsVisual<P>, structureVisualCtor?: () => StructureVisual<P>): StructureRepresentation<P> { + let unitsVisuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>() + let structureVisual: StructureVisual<P> | undefined - return { - renderObjects, - create(structure: Structure, props: P = {} as P) { - // currentProps = Object.assign({}, DefaultStructureProps, props) - - return Task.create('StructureRepresentation.create', async ctx => { - // const { query } = currentProps - // const qs = await query(structure).runAsChild(ctx) - // const subStructure = Selection.unionStructure(qs) - - const groups = StructureSymmetry.getTransformGroups(structure); - for (let i = 0; i < groups.length; i++) { - if(ctx.shouldUpdate) await ctx.update({ - message: 'Building structure unit visuals...', - current: i, - max: groups.length - }) - const group = groups[i]; - const visual = visualCtor() - groupVisuals.push({ visual, group }) - await visual.create(ctx, group, props) - renderObjects.push(...visual.renderObjects) + let _props: Required<P> + let _structure: Structure + let _groups: ReadonlyArray<Unit.SymmetryGroup> + + function create(structure: Structure, props: P = {} as P) { + _props = Object.assign({}, DefaultStructureProps, _props, props) + + return Task.create('Creating StructureRepresentation', async ctx => { + if (!_structure) { + _groups = StructureSymmetry.getTransformGroups(structure); + for (let i = 0; i < _groups.length; i++) { + const group = _groups[i]; + const visual = unitsVisualCtor() + await visual.create(ctx, group, _props) + unitsVisuals.set(group.hashCode, { visual, group }) } - }); - }, - update(props: P) { - return Task.create('StructureRepresentation.update', async ctx => { - renderObjects.length = 0 // clear - - for (let i = 0, il = groupVisuals.length; i < il; ++i) { - if(ctx.shouldUpdate) await ctx.update({ - message: 'Updating structure unit visuals...', - current: i, - max: il + + if (structureVisualCtor) { + structureVisual = structureVisualCtor() + await structureVisual.create(ctx, structure, _props) + } + } else { + if (_structure.hashCode === structure.hashCode) { + await update(_props) + } else { + _groups = StructureSymmetry.getTransformGroups(structure); + const newGroups: Unit.SymmetryGroup[] = [] + const oldUnitsVisuals = unitsVisuals + unitsVisuals = 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 = unitsVisualCtor() + await visual.create(ctx, group, _props) + unitsVisuals.set(group.hashCode, { visual, group }) + } + } + + // for new groups, reuse leftover visuals + const unusedVisuals: UnitsVisual<P>[] = [] + oldUnitsVisuals.forEach(({ visual }) => unusedVisuals.push(visual)) + newGroups.forEach(async group => { + const visual = unusedVisuals.pop() || unitsVisualCtor() + await visual.create(ctx, group, _props) + unitsVisuals.set(group.hashCode, { visual, group }) }) - const groupVisual = groupVisuals[i] - const { visual, group } = groupVisual + unusedVisuals.forEach(visual => visual.destroy()) - if (!await visual.update(ctx, props)) { - console.log('update failed, need to rebuild') - visual.create(ctx, group, props) + if (structureVisual) { + if (!await structureVisual.update(ctx, _props)) { + await structureVisual.create(ctx, _structure, _props) + } } - renderObjects.push(...visual.renderObjects) + } + } + _structure = structure + }); + } + + function update(props: P) { + return Task.create('Updating StructureRepresentation', async ctx => { + _props = Object.assign({}, DefaultStructureProps, _props, props) + + unitsVisuals.forEach(async ({ visual, group }) => { + if (!await visual.update(ctx, _props)) { + await visual.create(ctx, group, _props) } }) - }, - getLoci(pickingId: PickingId) { - for (let i = 0, il = groupVisuals.length; i < il; ++i) { - const loc = groupVisuals[i].visual.getLoci(pickingId) - if (!isEmptyLoci(loc)) return loc + + if (structureVisual) { + if (!await structureVisual.update(ctx, _props)) { + await structureVisual.create(ctx, _structure, _props) + } } - return EmptyLoci + }) + } + + function getLoci(pickingId: PickingId) { + let loci: Loci = EmptyLoci + unitsVisuals.forEach(({ visual }) => { + const _loci = visual.getLoci(pickingId) + if (!isEmptyLoci(_loci)) loci = _loci + }) + return loci + } + + function mark(loci: Loci, action: MarkerAction) { + unitsVisuals.forEach(({ visual }) => visual.mark(loci, action)) + } + + function destroy() { + unitsVisuals.forEach(({ visual }) => visual.destroy()) + unitsVisuals.clear() + } + + return { + get renderObjects() { + const renderObjects: RenderObject[] = [] + unitsVisuals.forEach(({ visual }) => renderObjects.push(...visual.renderObjects)) + return renderObjects }, - mark(loci: Loci, action: MarkerAction) { - for (let i = 0, il = groupVisuals.length; i < il; ++i) { - groupVisuals[i].visual.mark(loci, action) - } - } + create, + update, + getLoci, + mark, + destroy } } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 88d12b60c..363cc3363 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -47,7 +47,7 @@ export function createPointVertices(unit: Unit) { return vertices } -export default function PointUnitsRepresentation(): UnitsVisual<PointProps> { +export default function PointVisual(): UnitsVisual<PointProps> { const renderObjects: RenderObject[] = [] let points: PointRenderObject let currentProps = DefaultPointProps @@ -164,6 +164,9 @@ export default function PointUnitsRepresentation(): UnitsVisual<PointProps> { }, mark(loci: Loci, action: MarkerAction) { markElement(points.values.tMarker, currentGroup, loci, action) + }, + destroy() { + // TODO } } } diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 5090efb28..d99c82521 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -9,7 +9,7 @@ import { ValueCell } from 'mol-util/value-cell' import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' import { Unit, Element, Queries } from 'mol-model/structure'; -import { UnitsVisual, DefaultStructureProps } from './index'; +import { DefaultStructureProps, UnitsVisual } from './index'; import { RuntimeContext } from 'mol-task' import { createTransforms, createColors, createSphereMesh, markElement } from './utils'; import VertexMap from '../../shape/vertex-map'; @@ -44,7 +44,7 @@ export const DefaultSpacefillProps = { } export type SpacefillProps = Partial<typeof DefaultSpacefillProps> -export default function SpacefillUnitsRepresentation(): UnitsVisual<SpacefillProps> { +export default function SpacefillVisual(): UnitsVisual<SpacefillProps> { const renderObjects: RenderObject[] = [] let spheres: MeshRenderObject let currentProps: typeof DefaultSpacefillProps @@ -152,6 +152,9 @@ export default function SpacefillUnitsRepresentation(): UnitsVisual<SpacefillPro }, mark(loci: Loci, action: MarkerAction) { markElement(spheres.values.tMarker, currentGroup, loci, action) + }, + destroy() { + // TODO } } } diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index 6a9efa84b..17da82eee 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -4,45 +4,48 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Task, RuntimeContext } from 'mol-task' +import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; -import { RepresentationProps, Representation } from '..'; +import { RepresentationProps, Representation, Visual } from '..'; import { VolumeData } from 'mol-model/volume'; import { PickingId } from '../../util/picking'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { MarkerAction } from '../../util/marker-data'; -export interface VolumeVisual<P> { - readonly renderObjects: ReadonlyArray<RenderObject> - create: (ctx: RuntimeContext, volumeData: VolumeData, props: P) => Promise<void> - update: (ctx: RuntimeContext, props: P) => Promise<boolean> - getLoci: (pickingId: PickingId) => Loci - mark: (loci: Loci, action: MarkerAction) => void -} +export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { } export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { } -export function VolumeRepresentation<P>(visualCtor: () => VolumeVisual<P>): VolumeRepresentation<P> { +export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> { const renderObjects: RenderObject[] = [] + let _volumeData: VolumeData + + function create(volumeData: VolumeData, props: P = {} as P) { + return Task.create('VolumeRepresentation.create', async ctx => { + _volumeData = volumeData + const visual = visualCtor(_volumeData) + await visual.create(ctx, _volumeData, props) + renderObjects.push(...visual.renderObjects) + }); + } + + function update(props: P) { + return Task.create('VolumeRepresentation.update', async ctx => {}) + } return { renderObjects, - create(volumeData: VolumeData, props: P = {} as P) { - return Task.create('VolumeRepresentation.create', async ctx => { - const visual = visualCtor() - await visual.create(ctx, volumeData, props) - renderObjects.push(...visual.renderObjects) - }); - }, - update(props: P) { - return Task.create('VolumeRepresentation.update', async ctx => {}) - }, + create, + update, getLoci(pickingId: PickingId) { // TODO return EmptyLoci }, mark(loci: Loci, action: MarkerAction) { // TODO + }, + destroy() { + // TODO } } } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index a5cd24830..14c40d182 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -50,7 +50,7 @@ export const DefaultSurfaceProps = { } export type SurfaceProps = Partial<typeof DefaultSurfaceProps> -export default function Surface(): VolumeVisual<SurfaceProps> { +export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { const renderObjects: RenderObject[] = [] let surface: MeshRenderObject let curProps = DefaultSurfaceProps @@ -109,6 +109,9 @@ export default function Surface(): VolumeVisual<SurfaceProps> { }, mark(loci: Loci, action: MarkerAction) { // TODO + }, + destroy() { + // TODO } } } diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 4b733f2ed..a7836b792 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -60,7 +60,7 @@ namespace StructureSymmetry { return hash2(u.invariantId, SortedArray.hashCode(u.elements)); } - function areUnitsEquivalent(a: Unit, b: Unit) { + export function areUnitsEquivalent(a: Unit, b: Unit) { return a.invariantId === b.invariantId && a.model.id === b.model.id && SortedArray.areEqual(a.elements, b.elements); } @@ -75,7 +75,11 @@ namespace StructureSymmetry { const ret: Unit.SymmetryGroup[] = []; for (const eqUnits of groups.groups) { const first = s.unitMap.get(eqUnits[0]); - ret.push({ elements: first.elements, units: eqUnits.map(id => s.unitMap.get(id)) }); + ret.push({ + elements: first.elements, + units: eqUnits.map(id => s.unitMap.get(id)), + hashCode: hashUnit(first) + }); } return ret; diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 948e429cb..92d0da497 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -35,7 +35,11 @@ namespace Unit { } /** A group of units that differ only by symmetry operators. */ - export type SymmetryGroup = { readonly elements: Element.Set, readonly units: ReadonlyArray<Unit> } + export type SymmetryGroup = { + readonly elements: Element.Set, + readonly units: ReadonlyArray<Unit> + readonly hashCode: number + } /** Find index of unit with given id, returns -1 if not found */ export function findUnitById(id: number, units: ReadonlyArray<Unit>) { -- GitLab