diff --git a/src/mol-model/shape/shape.ts b/src/mol-model/shape/shape.ts index 9894542386bb4dfba4947dd40e29cc8253005aa5..075af892209e525ed7e73d75ec3eef279f9c6fac 100644 --- a/src/mol-model/shape/shape.ts +++ b/src/mol-model/shape/shape.ts @@ -5,28 +5,31 @@ */ import { Color } from 'mol-util/color'; -import { UUID, ValueCell } from 'mol-util'; +import { UUID } from 'mol-util'; import { OrderedSet } from 'mol-data/int'; import { Geometry } from 'mol-geo/geometry/geometry'; +import { Mat4 } from 'mol-math/linear-algebra'; export interface Shape<G extends Geometry = Geometry> { readonly id: UUID readonly name: string readonly geometry: G - readonly colors: ValueCell<Color[]> - readonly labels: ValueCell<string[]> + readonly transforms: Mat4[] readonly groupCount: number + getColor(groupId: number): Color + getLabel(groupId: number): string } export namespace Shape { - export function create<G extends Geometry>(name: string, geometry: G, colors: Color[], labels: string[]): Shape<G> { + export function create<G extends Geometry>(name: string, geometry: G, getColor: Shape['getColor'], getLabel: Shape['getLabel'], transforms?: Mat4[]): Shape<G> { return { id: UUID.create22(), name, geometry, + transforms: transforms || [Mat4.identity()], get groupCount() { return Geometry.getGroupCount(geometry) }, - colors: ValueCell.create(colors), - labels: ValueCell.create(labels), + getColor, + getLabel } } diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 69d352f445729ab235031f3dcfed12c57df113e2..121b53dbfea1ac70740d47c84670e26a504545e1 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -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> */ @@ -12,14 +12,18 @@ import { ValueCell } from 'mol-util'; import { Shape } from 'mol-model/shape'; import { OrderedSet, Interval } from 'mol-data/int'; import { ParamDefinition as PD } from 'mol-util/param-definition'; -import { createIdentityTransform } from 'mol-geo/geometry/transform-data'; +import { createTransform, TransformData } from 'mol-geo/geometry/transform-data'; import { PickingId } from 'mol-geo/geometry/picking'; -import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data'; +import { MarkerAction, applyMarkerAction, createMarkers } from 'mol-geo/geometry/marker-data'; import { LocationIterator } from 'mol-geo/util/location-iterator'; import { createEmptyTheme, Theme } from 'mol-theme/theme'; import { Subject } from 'rxjs'; import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry'; import { ShapeGroupColorTheme } from 'mol-theme/color/shape-group'; +import { createColors } from 'mol-geo/geometry/color-data'; +import { VisualUpdateState } from 'mol-repr/util'; +import { Mat4 } from 'mol-math/linear-algebra'; +import { Visual } from 'mol-repr/visual'; export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { } @@ -35,39 +39,83 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa let currentParams: P let locationIt: LocationIterator + const updateState = VisualUpdateState.create() + + function prepareUpdate(shape?: Shape<G>) { + VisualUpdateState.reset(updateState) + + if (!shape && !_shape) { + console.error('no shape given') + return + } else if (shape && !_shape) { + console.log('first shape') + updateState.createNew = true + } else if (shape && _shape && shape.id === _shape.id) { + console.log('same shape') + // trigger color update when shape has not changed + updateState.updateColor = true + updateState.updateTransform = true + } else if (shape && _shape && shape.id !== _shape.id) { + console.log('new shape') + // assume that ValueCells in geometry of new shape where re-used from the old one + updateState.updateColor = true + updateState.updateTransform = true + } else if (!shape) { + console.log('only props') + // nothing to set + } else { + console.warn('unexpected state') + } + + if (updateState.updateTransform) { + updateState.updateColor = true + } + } + function createOrUpdate(props: Partial<PD.Values<P>> = {}, data?: D) { - currentProps = Object.assign(currentProps, props) - const shape = data ? getShape(data, currentProps, _shape) : undefined + const newProps = Object.assign(currentProps, props) + const shape = data ? getShape(data, newProps, _shape) : undefined // TODO support async getShape return Task.create('ShapeRepresentation.create', async runtime => { - if (!shape && !_shape) { - console.error('no shape given') - return - } else if (shape && !_shape) { - console.log('first shape') + prepareUpdate(shape) - } else if (shape && _shape && shape.id === _shape.id) { - console.log('same shape') + if (shape) { + _shape = shape + _theme.color = ShapeGroupColorTheme({ shape: _shape }, {}) + } - } else if (shape && _shape && shape.id !== _shape.id) { - console.log('new shape') + if (updateState.createNew) { + renderObjects.length = 0 + locationIt = ShapeGroupIterator.fromShape(_shape) + const transform = createShapeTransform(_shape.transforms) + const values = geometryUtils.createValues(_shape.geometry, transform, locationIt, _theme, newProps) + const state = geometryUtils.createRenderableState(newProps) + _renderObject = createRenderObject(_shape.geometry.kind, values, state) + if (_renderObject) renderObjects.push(_renderObject) } else { - console.log('only props') + if (!_renderObject) { + throw new Error('expected renderObject to be available') + } - } + if (updateState.updateTransform) { + // console.log('update transform') + createShapeTransform(_shape.transforms, _renderObject.values) + locationIt = ShapeGroupIterator.fromShape(_shape) + const { instanceCount, groupCount } = locationIt + createMarkers(instanceCount * groupCount, _renderObject.values) + } - if (shape) _shape = shape - renderObjects.length = 0 - locationIt = ShapeGroupIterator.fromShape(_shape) - _theme.color = ShapeGroupColorTheme({ shape: _shape }, {}) + if (updateState.updateColor) { + // console.log('update color') + createColors(locationIt, _theme.color, _renderObject.values) + } - const transform = createIdentityTransform() - const values = geometryUtils.createValues(_shape.geometry, transform, locationIt, _theme, currentProps) - const state = geometryUtils.createRenderableState(currentProps) + geometryUtils.updateValues(_renderObject.values, newProps) + geometryUtils.updateRenderableState(_renderObject.state, newProps) + } - _renderObject = createRenderObject(_shape.geometry.kind, values, state) - if (_renderObject) renderObjects.push(_renderObject) + currentProps = newProps updated.next(version++) }); } @@ -115,14 +163,16 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa return changed }, setState(state: Partial<Representation.State>) { - if (state.visible !== undefined) renderObjects.forEach(ro => ro.state.visible = !!state.visible) - if (state.pickable !== undefined) renderObjects.forEach(ro => ro.state.pickable = !!state.pickable) - // TODO state.transform + if (_renderObject) { + if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible) + if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable) + if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform) + } Representation.updateState(_state, state) }, setTheme(theme: Theme) { - _theme = theme + console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme`. Colors are taken from `Shape.getColor`.') }, destroy() { // TODO @@ -132,9 +182,17 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa } } +function createShapeTransform(transforms: Mat4[], transformData?: TransformData) { + const transformArray = transformData && transformData.aTransform.ref.value.length >= transforms.length * 16 ? transformData.aTransform.ref.value : new Float32Array(transforms.length * 16) + for (let i = 0, il = transforms.length; i < il; ++i) { + Mat4.toArray(transforms[i], transformArray, i * 16) + } + return createTransform(transformArray, transforms.length, transformData) +} + export namespace ShapeGroupIterator { export function fromShape(shape: Shape): LocationIterator { - const instanceCount = 1 + const instanceCount = shape.transforms.length const location = Shape.Location(shape) const getLocation = (groupIndex: number) => { location.group = groupIndex diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index 6c41478774ef41b361f8127e0d96880fc68ddac0..bfb33479cfc03ff27fa79ce5a59d8199322554bd 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -146,8 +146,6 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry throw new Error('expected renderObject to be available') } - locationIt.reset() - if (updateState.updateTransform) { // console.log('update transform') locationIt = createLocationIterator(newStructureGroup.group) diff --git a/src/mol-theme/color/shape-group.ts b/src/mol-theme/color/shape-group.ts index 66c51e853aa7eb81ddece5f6fed64e74188c3bde..bec91fb435ea4e02dba9a563f7e45f8b5c986d38 100644 --- a/src/mol-theme/color/shape-group.ts +++ b/src/mol-theme/color/shape-group.ts @@ -26,7 +26,7 @@ export function ShapeGroupColorTheme(ctx: ThemeDataContext, props: PD.Values<Sha granularity: 'group', color: (location: Location): Color => { if (Shape.isLocation(location)) { - return location.shape.colors.ref.value[location.group] + return location.shape.getColor(location.group) } return DefaultColor }, diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts index befa5cea8789581c5171ce2878c13aef97cddfba..926411df1c9068fe4156c2e7da8782d6895a7bd1 100644 --- a/src/mol-theme/label.ts +++ b/src/mol-theme/label.ts @@ -36,7 +36,7 @@ export function labelFirst(loci: Loci): string { case 'group-loci': const g = loci.groups[0] if (g) { - return loci.shape.labels.ref.value[OrderedSet.getAt(g.ids, 0)] + return loci.shape.getLabel(OrderedSet.getAt(g.ids, 0)) } else { return 'Unknown' } diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts index 2b2d03f8087888597dad119619ebf2d9a95862e0..9bb7587ad7e6730019f89f67fd776ece3720301f 100644 --- a/src/tests/browser/render-shape.ts +++ b/src/tests/browser/render-shape.ts @@ -52,10 +52,14 @@ builderState.currentGroup = 0 MeshBuilder.addPrimitive(builderState, t, sphere) const mesh = MeshBuilder.getMesh(builderState) -const myData = { mesh, colors: [ColorNames.aquamarine], labels: ['FooBar'] } +const myData = { mesh, colors: [ColorNames.aquamarine], labels: ['FooBaz'] } type MyData = typeof myData function getShape(data: MyData) { - return Shape.create('test', data.mesh, data.colors, data.labels) + return Shape.create( + 'test', data.mesh, + (groupId: number) => data.colors[groupId], + (groupId: number) => data.labels[groupId] + ) } const repr = ShapeRepresentation(getShape, Mesh.Utils) @@ -66,4 +70,9 @@ async function add() { canvas3d.add(repr) canvas3d.resetCamera() } -add() \ No newline at end of file +add() + +setTimeout(async () => { + myData.colors[0] = ColorNames.darkmagenta + await repr.createOrUpdate({}, myData).run() +}, 2000) \ No newline at end of file