Browse Source

wip, shape

Alexander Rose 6 years ago
parent
commit
179461ed4d

+ 9 - 6
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
         }
     }
 

+ 88 - 30
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

+ 0 - 2
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)

+ 1 - 1
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
         },

+ 1 - 1
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'
             }

+ 12 - 3
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()
+add()
+
+setTimeout(async () => {
+    myData.colors[0] = ColorNames.darkmagenta
+    await repr.createOrUpdate({}, myData).run()
+}, 2000)