Browse Source

smarter ply-shape repr update

Alexander Rose 6 years ago
parent
commit
a14236c483

+ 112 - 60
src/mol-model-formats/shape/ply.ts

@@ -17,10 +17,53 @@ import { arrayMax, fillSerial } from 'mol-util/array';
 import { Column } from 'mol-data/db';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { ColorNames } from 'mol-util/color/tables';
+import { deepClone } from 'mol-util/object';
 
 // TODO support 'edge' and 'material' elements, see https://www.mathworks.com/help/vision/ug/the-ply-format.html
 
-async function getPlyMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, groupIds: ArrayLike<number>, mesh?: Mesh) {
+function createPlyShapeParams(vertex?: PlyTable) {
+    const options: [string, string][] = [['', '']]
+    const defaultValues = { group: '', red: '', green: '', blue: '' }
+    if (vertex) {
+        for (let i = 0, il = vertex.propertyNames.length; i < il; ++i) {
+            const name = vertex.propertyNames[i]
+            options.push([ name, name ])
+        }
+
+        // TODO hardcoded as convenience for data provided by MegaMol
+        if (vertex.propertyNames.includes('atomid')) defaultValues.group = 'atomid'
+
+        if (vertex.propertyNames.includes('red')) defaultValues.red = 'red'
+        if (vertex.propertyNames.includes('green')) defaultValues.green = 'green'
+        if (vertex.propertyNames.includes('blue')) defaultValues.blue = 'blue'
+    }
+
+    return {
+        ...Mesh.Params,
+
+        coloring: PD.MappedStatic(defaultValues.red && defaultValues.green && defaultValues.blue ? 'vertex' : 'uniform', {
+            vertex: PD.Group({
+                red: PD.Select(defaultValues.red, options, { label: 'Red Property' }),
+                green: PD.Select(defaultValues.green, options, { label: 'Green Property' }),
+                blue: PD.Select(defaultValues.blue, options, { label: 'Blue Property' }),
+            }, { isFlat: true }),
+            uniform: PD.Group({
+                color: PD.Color(ColorNames.grey)
+            }, { isFlat: true })
+        }),
+        grouping: PD.MappedStatic(defaultValues.group ? 'vertex' : 'none', {
+            vertex: PD.Group({
+                group: PD.Select(defaultValues.group, options, { label: 'Group Property' }),
+            }, { isFlat: true }),
+            none: PD.Group({ })
+        }),
+    }
+}
+
+export const PlyShapeParams = createPlyShapeParams()
+export type PlyShapeParams = typeof PlyShapeParams
+
+async function getMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, groupIds: ArrayLike<number>, mesh?: Mesh) {
     const builderState = MeshBuilder.createState(vertex.rowCount, vertex.rowCount / 4, mesh)
     const { vertices, normals, indices, groups } = builderState
 
@@ -36,7 +79,7 @@ async function getPlyMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList,
     const hasNormals = !!nx && !!ny && !!nz
 
     for (let i = 0, il = vertex.rowCount; i < il; ++i) {
-        if (i % 10000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding vertex ${i}` })
+        if (i % 100000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding vertex ${i}` })
 
         ChunkedArray.add3(vertices, x.value(i), y.value(i), z.value(i))
         if (hasNormals) ChunkedArray.add3(normals, nx!.value(i), ny!.value(i), nz!.value(i));
@@ -44,10 +87,13 @@ async function getPlyMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList,
     }
 
     for (let i = 0, il = face.rowCount; i < il; ++i) {
-        if (i % 10000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding face ${i}` })
+        if (i % 100000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding face ${i}` })
 
-        const { entries } = face.value(i)
-        ChunkedArray.add3(indices, entries[0], entries[1], entries[2])
+        const { entries, count } = face.value(i)
+        if (count === 3) {
+            ChunkedArray.add3(indices, entries[0], entries[1], entries[2])
+        }
+        // TODO support quadriliterals
     }
 
     const m = MeshBuilder.getMesh(builderState);
@@ -57,24 +103,25 @@ async function getPlyMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList,
     return m
 }
 
-function getGrouping(count: number, column?: Column<number>) {
-    const ids = column ? column.toArray({ array: Int32Array }) : fillSerial(new Uint32Array(count))
+const int = Column.Schema.int
+
+type Grouping = { ids: ArrayLike<number>, map: ArrayLike<number> }
+function getGrouping(vertex: PlyTable, props: PD.Values<PlyShapeParams>): Grouping {
+    const { grouping } = props
+    const { rowCount } = vertex
+    const column = grouping.name === 'vertex' ? vertex.getProperty(grouping.params.group) : undefined
+
+    const ids = column ? column.toArray({ array: Uint32Array }) : fillSerial(new Uint32Array(rowCount))
     const maxId = arrayMax(ids) // assumes uint ids
     const map = new Uint32Array(maxId + 1)
     for (let i = 0, il = ids.length; i < il; ++i) map[ids[i]] = i
     return { ids, map }
 }
 
-async function getShape(ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<PlyShapeParams>, shape?: Shape<Mesh>) {
-    await ctx.update('async creation of shape from ply file')
-
-    const { coloring, grouping } = props
-
-    const vertex = plyFile.getElement('vertex') as PlyTable
-    if (!vertex) throw new Error('missing vertex element')
-
+type Coloring = { red: Column<number>, green: Column<number>, blue: Column<number> }
+function getColoring(vertex: PlyTable, props: PD.Values<PlyShapeParams>): Coloring {
+    const { coloring } = props
     const { rowCount } = vertex
-    const int = Column.Schema.int
 
     let red: Column<number>, green: Column<number>, blue: Column<number>
     if (coloring.name === 'vertex') {
@@ -88,16 +135,14 @@ async function getShape(ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<
         blue = Column.ofConst(b, rowCount, int)
     }
 
-    const face = plyFile.getElement('face') as PlyList
-    if (!face) throw new Error('missing face element')
-
-    const { ids, map } = getGrouping(vertex.rowCount, grouping.name === 'vertex' ? vertex.getProperty(grouping.params.group) : undefined)
+    return { red, green, blue }
+}
 
-    const mesh = await getPlyMesh(ctx, vertex, face, ids, shape && shape.geometry)
+function createShape(plyFile: PlyFile, mesh: Mesh, coloring: Coloring, grouping: Grouping) {
+    const { red, green, blue } = coloring
+    const { ids, map } = grouping
     return Shape.create(
-
-        'test', plyFile, mesh,
-
+        'ply-mesh', plyFile, mesh,
         (groupId: number) => {
             const idx = map[groupId]
             return Color.fromRgb(red.value(idx), green.value(idx), blue.value(idx))
@@ -109,57 +154,64 @@ async function getShape(ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<
     )
 }
 
-function createPlyShapeParams(vertex?: PlyTable) {
-    const options: [string, string][] = [['', '']]
-    const defaultValues = { group: '', red: '', green: '', blue: '' }
-    if (vertex) {
-        for (let i = 0, il = vertex.propertyNames.length; i < il; ++i) {
-            const name = vertex.propertyNames[i]
-            options.push([ name, name ])
+function makeShapeGetter() {
+    let _plyFile: PlyFile | undefined
+    let _props: PD.Values<PlyShapeParams> | undefined
+
+    let _shape: Shape<Mesh>
+    let _mesh: Mesh
+    let _coloring: Coloring
+    let _grouping: Grouping
+
+    const getShape = async (ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<PlyShapeParams>, shape?: Shape<Mesh>) => {
+
+        const vertex = plyFile.getElement('vertex') as PlyTable
+        if (!vertex) throw new Error('missing vertex element')
+
+        const face = plyFile.getElement('face') as PlyList
+        if (!face) throw new Error('missing face element')
+
+        let newMesh = false
+        let newColor = false
+
+        if (!_plyFile || _plyFile !== plyFile) {
+            newMesh = true
         }
 
-        // TODO harcoded as convenience for data provided by MegaMol
-        if (vertex.propertyNames.includes('atomid')) defaultValues.group = 'atomid'
+        if (!_props || !PD.isParamEqual(PlyShapeParams.grouping, _props.grouping, props.grouping)) {
+            newMesh = true
+        }
 
-        if (vertex.propertyNames.includes('red')) defaultValues.red = 'red'
-        if (vertex.propertyNames.includes('green')) defaultValues.green = 'green'
-        if (vertex.propertyNames.includes('blue')) defaultValues.blue = 'blue'
-    }
+        if (!_props || !PD.isParamEqual(PlyShapeParams.coloring, _props.coloring, props.coloring)) {
+            newColor = true
+        }
 
-    return {
-        ...Mesh.Params,
+        if (newMesh) {
+            _coloring = getColoring(vertex, props)
+            _grouping = getGrouping(vertex, props)
+            _mesh = await getMesh(ctx, vertex, face, _grouping.ids, shape && shape.geometry)
+            _shape = createShape(plyFile, _mesh, _coloring, _grouping)
+        } else if (newColor) {
+            _coloring = getColoring(vertex, props)
+            _shape = createShape(plyFile, _mesh, _coloring, _grouping)
+        }
 
-        coloring: PD.MappedStatic(defaultValues.red && defaultValues.green && defaultValues.blue ? 'vertex' : 'uniform', {
-            vertex: PD.Group({
-                red: PD.Select(defaultValues.red, options, { label: 'Red Property' }),
-                green: PD.Select(defaultValues.green, options, { label: 'Green Property' }),
-                blue: PD.Select(defaultValues.blue, options, { label: 'Blue Property' }),
-            }, { isFlat: true }),
-            uniform: PD.Group({
-                color: PD.Color(ColorNames.grey)
-            }, { isFlat: true })
-        }),
-        grouping: PD.MappedStatic(defaultValues.group ? 'vertex' : 'none', {
-            vertex: PD.Group({
-                group: PD.Select(defaultValues.group, options, { label: 'Group Property' }),
-            }, { isFlat: true }),
-            none: PD.Group({ })
-        }),
+        _plyFile = plyFile
+        _props = deepClone(props)
+
+        return _shape
     }
+    return getShape
 }
 
-export const PlyShapeParams = createPlyShapeParams()
-export type PlyShapeParams = typeof PlyShapeParams
-
 export function shapeFromPly(source: PlyFile, params?: {}) {
     return Task.create<ShapeProvider<PlyFile, Mesh, PlyShapeParams>>('Shape Provider', async ctx => {
         return {
             label: 'Mesh',
             data: source,
             params: createPlyShapeParams(source.getElement('vertex') as PlyTable),
-            getShape,
+            getShape: makeShapeGetter(),
             geometryUtils: Mesh.Utils
         }
     })
-
 }

+ 1 - 3
src/mol-repr/shape/representation.ts

@@ -56,9 +56,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
             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
+            // nothing to set
         } else if (shape && _shape && shape.id !== _shape.id) {
             // console.log('new shape')
             updateState.updateTransform = true

+ 1 - 1
src/mol-util/param-definition.ts

@@ -284,7 +284,7 @@ export namespace ParamDefinition {
         return true;
     }
 
-    function isParamEqual(p: Any, a: any, b: any): boolean {
+    export function isParamEqual(p: Any, a: any, b: any): boolean {
         if (a === b) return true;
         if (!a) return !b;
         if (!b) return !a;

+ 1 - 1
src/tests/browser/render-shape.ts

@@ -96,7 +96,7 @@ async function getShape(ctx: RuntimeContext, data: MyData, props: {}, shape?: Sh
     const { centers, colors, labels, transforms } = data
     const mesh = await getSphereMesh(ctx, centers, shape && shape.geometry)
     const groupCount = centers.length / 3
-    return shape || Shape.create(
+    return Shape.create(
         'test', data, mesh,
         (groupId: number) => colors[groupId], // color: per group, same for instances
         () => 1, // size: constant