diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 6b98c2641243757fe79146930bb31d2abd495dfa..275429978b5394174c0888b97655b3c318bee88e 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Task } from 'mol-task' +import { Task, RuntimeContext } from 'mol-task' import { createRenderObject, GraphicsRenderObject } from 'mol-gl/render-object'; import { Representation } from '../representation'; import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci'; @@ -27,7 +27,9 @@ import { Visual } from 'mol-repr/visual'; export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { } -export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>>(getShape: (data: D, props: PD.Values<P>, shape?: Shape<G>) => Shape<G>, geometryUtils: GeometryUtils<G>): ShapeRepresentation<D, G, P> { +export type ShapeGetter<D, G extends Geometry, P extends Geometry.Params<G>> = (ctx: RuntimeContext, data: D, props: PD.Values<P>, shape?: Shape<G>) => Shape<G> | Promise<Shape<G>> + +export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>>(getShape: ShapeGetter<D, G, P>, geometryUtils: GeometryUtils<G>): ShapeRepresentation<D, G, P> { let version = 0 const updated = new Subject<number>() const _state = Representation.createState() @@ -73,10 +75,10 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa } function createOrUpdate(props: Partial<PD.Values<P>> = {}, data?: D) { - 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 => { + const newProps = Object.assign(currentProps, props) + const shape = data ? await getShape(runtime, data, newProps, _shape) : undefined + prepareUpdate(shape) if (shape) { diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts index f950cd8f8ac49b1886e5855d71e319d468269da9..a688de0dc885146bd64a18503a403c73da891b5d 100644 --- a/src/tests/browser/render-shape.ts +++ b/src/tests/browser/render-shape.ts @@ -14,6 +14,7 @@ import { ShapeRepresentation } from 'mol-repr/shape/representation'; import { ColorNames } from 'mol-util/color/tables'; import { Mesh } from 'mol-geo/geometry/mesh/mesh'; import { labelFirst } from 'mol-theme/label'; +import { RuntimeContext, Progress } from 'mol-task'; const parent = document.getElementById('app')! parent.style.width = '100%' @@ -45,42 +46,67 @@ canvas3d.input.move.subscribe(async ({x, y}) => { info.innerText = label }) -const builderState = MeshBuilder.createState() -const t = Mat4.identity() -const sphere = Sphere(2) -builderState.currentGroup = 0 -MeshBuilder.addPrimitive(builderState, t, sphere) -const mesh = MeshBuilder.getMesh(builderState) +/** + * Create a mesh of spheres at given centers + * - asynchronous (using async/await) + * - progress tracking (via `ctx.update`) + * - re-use storage from an existing mesh if given + */ +async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh) { + const builderState = MeshBuilder.createState(centers.length * 128, centers.length * 128 / 2, mesh) + const t = Mat4.identity() + const v = Vec3.zero() + const sphere = Sphere(2) + builderState.currentGroup = 0 + for (let i = 0, il = centers.length / 3; i < il; ++i) { + // for production, calls to update should be guarded by `if (ctx.shouldUpdate)` + await ctx.update({ current: i, max: il, message: `adding sphere ${i}` }) + builderState.currentGroup = i + Mat4.setTranslation(t, Vec3.fromArray(v, centers, i * 3)) + MeshBuilder.addPrimitive(builderState, t, sphere) + } + return MeshBuilder.getMesh(builderState) +} const myData = { - mesh, - groupCount: 1, + centers: [0, 0, 0, 0, 3, 0], colors: [ColorNames.tomato, ColorNames.springgreen], - labels: ['FooBaz0', 'FooBaz1'], + labels: ['Sphere 0, Instance A', 'Sphere 1, Instance A', 'Sphere 0, Instance B', 'Sphere 1, Instance B'], transforms: [Mat4.identity(), Mat4.fromTranslation(Mat4.zero(), Vec3.create(3, 0, 0))] } type MyData = typeof myData -function getShape(data: MyData, props: {}, shape?: Shape<Mesh>) { - const { mesh, colors, labels, transforms, groupCount } = data + +/** + * Get shape from `MyData` object + */ +async function getShape(ctx: RuntimeContext, data: MyData, props: {}, shape?: Shape<Mesh>) { + await ctx.update('async creation of shape from myData') + const { centers, colors, labels, transforms } = data + const mesh = await getSphereMesh(ctx, centers, shape && shape.geometry) + const groupCount = centers.length / 3 return shape || Shape.create( 'test', mesh, - (groupId: number, instanceId: number) => colors[instanceId * groupCount + groupId], - (groupId: number, instanceId: number) => labels[instanceId * groupCount + groupId], + (groupId: number) => colors[groupId], // per group, same for instances + (groupId: number, instanceId: number) => labels[instanceId * groupCount + groupId], // per group and instance transforms ) } +// Init ShapeRepresentation container const repr = ShapeRepresentation(getShape, Mesh.Utils) -async function add() { - await repr.createOrUpdate({}, myData).run() +async function init() { + // Create shape from myData and add to canvas3d + await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p))) console.log(repr) canvas3d.add(repr) canvas3d.resetCamera() -} -add() -setTimeout(async () => { - myData.colors[0] = ColorNames.darkmagenta - await repr.createOrUpdate({}, myData).run() -}, 1000) \ No newline at end of file + // Change color after 1s + setTimeout(async () => { + myData.colors[0] = ColorNames.darkmagenta + // Calling `createOrUpdate` with `data` will trigger color and transform update + await repr.createOrUpdate({}, myData).run() + }, 1000) +} +init() \ No newline at end of file