Przeglądaj źródła

wip, shape repr async creation

Alexander Rose 6 lat temu
rodzic
commit
0ab1d34bc2

+ 7 - 5
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) {

+ 47 - 21
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)
+    // 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()