Explorar el Código

wip, shape and geometry refactoring

Alexander Rose hace 6 años
padre
commit
ee3fdc5bf9

+ 94 - 0
src/mol-geo/geometry/base.ts

@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { RenderableState } from 'mol-gl/renderable';
+import { ValueCell } from 'mol-util';
+import { BaseValues } from 'mol-gl/renderable/schema';
+import { LocationIterator } from '../util/location-iterator';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { Color } from 'mol-util/color';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { TransformData, createIdentityTransform } from './transform-data';
+import { Theme } from 'mol-theme/theme';
+import { ColorNames } from 'mol-util/color/tables';
+import { NullLocation } from 'mol-model/location';
+import { UniformColorTheme } from 'mol-theme/color/uniform';
+import { UniformSizeTheme } from 'mol-theme/size/uniform';
+
+export const VisualQualityInfo = {
+    'custom': {},
+    'auto': {},
+    'highest': {},
+    'higher': {},
+    'high': {},
+    'medium': {},
+    'low': {},
+    'lower': {},
+    'lowest': {},
+}
+export type VisualQuality = keyof typeof VisualQualityInfo
+export const VisualQualityNames = Object.keys(VisualQualityInfo)
+export const VisualQualityOptions = VisualQualityNames.map(n => [n, n] as [VisualQuality, string])
+
+//
+
+export namespace BaseGeometry {
+    export const Params = {
+        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity' }),
+        useFog: PD.Boolean(true),
+        highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
+        selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
+
+        quality: PD.Select<VisualQuality>('auto', VisualQualityOptions),
+    }
+    export type Params = typeof Params
+
+    export type Counts = { drawCount: number, groupCount: number, instanceCount: number }
+
+    export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
+        if (!transform) transform = createIdentityTransform()
+        const locationIterator = LocationIterator(1, transform.instanceCount.ref.value, () => NullLocation, false, () => false)
+        const theme: Theme = {
+            color: UniformColorTheme({}, { value: colorValue}),
+            size: UniformSizeTheme({}, { value: sizeValue})
+        }
+        return { transform, locationIterator, theme }
+    }
+
+    export function createValues(props: PD.Values<Params>, counts: Counts) {
+        return {
+            uAlpha: ValueCell.create(props.alpha),
+            uHighlightColor: ValueCell.create(Color.toArrayNormalized(props.highlightColor, Vec3.zero(), 0)),
+            uSelectColor: ValueCell.create(Color.toArrayNormalized(props.selectColor, Vec3.zero(), 0)),
+            uGroupCount: ValueCell.create(counts.groupCount),
+            drawCount: ValueCell.create(counts.drawCount),
+            dUseFog: ValueCell.create(props.useFog),
+        }
+    }
+
+    export function updateValues(values: BaseValues, props: PD.Values<Params>) {
+        if (Color.fromNormalizedArray(values.uHighlightColor.ref.value, 0) !== props.highlightColor) {
+            ValueCell.update(values.uHighlightColor, Color.toArrayNormalized(props.highlightColor, values.uHighlightColor.ref.value, 0))
+        }
+        if (Color.fromNormalizedArray(values.uSelectColor.ref.value, 0) !== props.selectColor) {
+            ValueCell.update(values.uSelectColor, Color.toArrayNormalized(props.selectColor, values.uSelectColor.ref.value, 0))
+        }
+        ValueCell.updateIfChanged(values.uAlpha, props.alpha)
+        ValueCell.updateIfChanged(values.dUseFog, props.useFog)
+    }
+
+    export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {
+        return {
+            visible: true,
+            pickable: true,
+            opaque: props.alpha === undefined ? true : props.alpha === 1
+        }
+    }
+
+    export function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
+        state.opaque = props.alpha === 1
+    }
+}

+ 9 - 8
src/mol-geo/geometry/geometry.ts

@@ -78,14 +78,15 @@ export namespace Geometry {
         }
     }
 
-    export function getUtils<K extends GeometryKind>(kind: K): GeometryUtils<GeometryKindType[K]> {
-        switch (kind) {
-            case 'mesh': return Mesh.Utils
-            case 'points': return Points.Utils
-            case 'spheres': return Spheres.Utils
-            case 'text': return Text.Utils
-            case 'lines': return Lines.Utils
-            case 'direct-volume': return DirectVolume.Utils
+    export function getUtils<G extends Geometry>(geometry: G): GeometryUtils<G> {
+        // TODO avoid casting
+        switch (geometry.kind) {
+            case 'mesh': return Mesh.Utils as any
+            case 'points': return Points.Utils as any
+            case 'spheres': return Spheres.Utils as any
+            case 'text': return Text.Utils as any
+            case 'lines': return Lines.Utils as any
+            case 'direct-volume': return DirectVolume.Utils as any
         }
         throw new Error('unknown geometry kind')
     }

+ 4 - 4
src/mol-model/shape/shape.ts

@@ -9,22 +9,22 @@ import { UUID, ValueCell } from 'mol-util';
 import { OrderedSet } from 'mol-data/int';
 import { Geometry } from 'mol-geo/geometry/geometry';
 
-export interface Shape {
+export interface Shape<G extends Geometry = Geometry> {
     readonly id: UUID
     readonly name: string
-    readonly geometry: Geometry
+    readonly geometry: G
     readonly colors: ValueCell<Color[]>
     readonly labels: ValueCell<string[]>
     readonly groupCount: number
 }
 
 export namespace Shape {
-    export function create(name: string, geometry: Geometry, colors: Color[], labels: string[]): Shape {
+    export function create<G extends Geometry>(name: string, geometry: G, colors: Color[], labels: string[]): Shape<G> {
         return {
             id: UUID.create22(),
             name,
             geometry,
-            groupCount: Geometry.getGroupCount(geometry),
+            get groupCount() { return Geometry.getGroupCount(geometry) },
             colors: ValueCell.create(colors),
             labels: ValueCell.create(labels),
         }

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

@@ -5,7 +5,7 @@
  */
 
 import { Task } from 'mol-task'
-import { RenderObject, GraphicsRenderObject } from 'mol-gl/render-object'
+import { GraphicsRenderObject } from 'mol-gl/render-object'
 import { PickingId } from '../mol-geo/geometry/picking';
 import { Loci, isEmptyLoci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../mol-geo/geometry/marker-data';
@@ -93,7 +93,7 @@ interface Representation<D, P extends PD.Params = {}> {
     readonly updated: Subject<number>
     /** Number of addressable groups in all visuals of the representation */
     readonly groupCount: number
-    readonly renderObjects: ReadonlyArray<RenderObject>
+    readonly renderObjects: ReadonlyArray<GraphicsRenderObject>
     readonly props: Readonly<PD.Values<P>>
     readonly params: Readonly<P>
     readonly state: Readonly<Representation.State>
@@ -171,7 +171,7 @@ namespace Representation {
                 return groupCount
             },
             get renderObjects() {
-                const renderObjects: RenderObject[] = []
+                const renderObjects: GraphicsRenderObject[] = []
                 if (currentProps) {
                     const { visuals } = currentProps
                     for (let i = 0, il = reprList.length; i < il; ++i) {

+ 41 - 32
src/mol-repr/shape/representation.ts

@@ -5,65 +5,75 @@
  */
 
 import { Task } from 'mol-task'
-import { RenderObject, createRenderObject, MeshRenderObject } from 'mol-gl/render-object';
-import { Representation, RepresentationContext } from '../representation';
+import { createRenderObject, GraphicsRenderObject } from 'mol-gl/render-object';
+import { Representation } from '../representation';
 import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
 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 { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction, applyMarkerAction } 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';
 
-export interface ShapeRepresentation<P extends ShapeParams> extends Representation<Shape, P> { }
+export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
 
-export const ShapeParams = {
-    ...Mesh.Params,
-    // TODO
-    // colorTheme: PD.Select<ColorThemeName>('Color Theme', '', 'shape-group', ColorThemeOptions)
-}
-export type ShapeParams = typeof ShapeParams
-
-export function ShapeRepresentation<P extends ShapeParams>(ctx: RepresentationContext): ShapeRepresentation<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> {
     let version = 0
     const updated = new Subject<number>()
     const _state = Representation.createState()
-    const renderObjects: RenderObject[] = []
-    let _renderObject: MeshRenderObject | undefined
-    let _shape: Shape
+    const renderObjects: GraphicsRenderObject[] = []
+    let _renderObject: GraphicsRenderObject | undefined
+    let _shape: Shape<G>
     let _theme = createEmptyTheme()
-    let currentProps: PD.Values<P> = PD.getDefaultValues(ShapeParams) as PD.Values<P>
+    let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P) // TODO avoid casting
     let currentParams: P
     let locationIt: LocationIterator
 
-    function createOrUpdate(props: Partial<PD.Values<P>> = {}, shape?: Shape) {
-        currentProps = Object.assign({}, currentProps, props)
-        if (shape) _shape = shape
+    function createOrUpdate(props: Partial<PD.Values<P>> = {}, data?: D) {
+        currentProps = Object.assign(currentProps, props)
+        const shape = data ? getShape(data, currentProps, _shape) : undefined
 
         return Task.create('ShapeRepresentation.create', async runtime => {
-            renderObjects.length = 0
+            if (!shape && !_shape) {
+                console.error('no shape given')
+                return
+            } else if (shape && !_shape) {
+                console.log('first shape')
+
+            } else if (shape && _shape && shape.id === _shape.id) {
+                console.log('same shape')
+
+            } else if (shape && _shape && shape.id !== _shape.id) {
+                console.log('new shape')
 
-            if (!_shape) return
+            } else {
+                console.log('only props')
 
+            }
+
+            if (shape) _shape = shape
+            renderObjects.length = 0
             locationIt = ShapeGroupIterator.fromShape(_shape)
-            const transform = createIdentityTransform()
+            _theme.color = ShapeGroupColorTheme({ shape: _shape }, {})
 
-            const values = Mesh.Utils.createValues(_shape.mesh, transform, locationIt, _theme, currentProps)
-            const state = Mesh.Utils.createRenderableState(currentProps)
+            const transform = createIdentityTransform()
+            const values = geometryUtils.createValues(_shape.geometry, transform, locationIt, _theme, currentProps)
+            const state = geometryUtils.createRenderableState(currentProps)
 
-            _renderObject = createRenderObject('mesh', values, state)
-            renderObjects.push(_renderObject)
+            _renderObject = createRenderObject(_shape.geometry.kind, values, state)
+            if (_renderObject) renderObjects.push(_renderObject)
             updated.next(version++)
         });
     }
 
     return {
-        label: 'Shape mesh',
+        label: 'Shape geometry',
         get groupCount () { return locationIt ? locationIt.count : 0 },
         get renderObjects () { return renderObjects },
         get props () { return currentProps },
@@ -84,7 +94,7 @@ export function ShapeRepresentation<P extends ShapeParams>(ctx: RepresentationCo
             const { tMarker } = _renderObject.values
             let changed = false
             if (isEveryLoci(loci)) {
-                if (applyMarkerAction(tMarker.ref.value.array, 0, _shape.mesh.triangleCount, action)) changed = true
+                if (applyMarkerAction(tMarker.ref.value.array, 0, _shape.groupCount, action)) changed = true
             } else if (Shape.isLoci(loci)) {
                 for (const g of loci.groups) {
                     if (Interval.is(g.ids)) {
@@ -105,8 +115,8 @@ export function ShapeRepresentation<P extends ShapeParams>(ctx: RepresentationCo
             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!)
+            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
 
             Representation.updateState(_state, state)
@@ -124,13 +134,12 @@ export function ShapeRepresentation<P extends ShapeParams>(ctx: RepresentationCo
 
 export namespace ShapeGroupIterator {
     export function fromShape(shape: Shape): LocationIterator {
-        const { groupCount } = shape
         const instanceCount = 1
         const location = Shape.Location(shape)
         const getLocation = (groupIndex: number) => {
             location.group = groupIndex
             return location
         }
-        return LocationIterator(groupCount, instanceCount, getLocation)
+        return LocationIterator(shape.groupCount, instanceCount, getLocation)
     }
 }

+ 3 - 3
src/mol-repr/structure/complex-visual.ts

@@ -31,10 +31,10 @@ import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 
 export interface  ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
 
-function createComplexRenderObject(structure: Structure, geometry: Geometry, locationIt: LocationIterator, theme: Theme, props: PD.Values<StructureParams>) {
-    const { createValues, createRenderableState } = Geometry.getUtils(geometry.kind)
+function createComplexRenderObject<G extends Geometry>(structure: Structure, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>) {
+    const { createValues, createRenderableState } = Geometry.getUtils(geometry)
     const transform = createIdentityTransform()
-    const values = createValues(geometry, transform, locationIt, theme, props as any) // TODO
+    const values = createValues(geometry, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createRenderObject(geometry.kind, values, state)
 }

+ 2 - 2
src/mol-repr/structure/units-representation.ts

@@ -7,7 +7,7 @@
 
 import { Structure, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
-import { RenderObject } from 'mol-gl/render-object';
+import { GraphicsRenderObject } from 'mol-gl/render-object';
 import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
 import { Visual } from '../visual';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
@@ -189,7 +189,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
             return groupCount
         },
         get renderObjects() {
-            const renderObjects: RenderObject[] = []
+            const renderObjects: GraphicsRenderObject[] = []
             visuals.forEach(({ visual }) => {
                 if (visual.renderObject) renderObjects.push(visual.renderObject)
             })

+ 2 - 2
src/mol-repr/structure/units-visual.ts

@@ -37,9 +37,9 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
 
 function createUnitsRenderObject<G extends Geometry>(group: Unit.SymmetryGroup, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>) {
-    const { createValues, createRenderableState } = Geometry.getUtils(geometry.kind)
+    const { createValues, createRenderableState } = Geometry.getUtils(geometry)
     const transform = createUnitsTransform(group)
-    const values = createValues(geometry, transform, locationIt, theme, props as any) // TODO
+    const values = createValues(geometry, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createRenderObject(geometry.kind, values, state)
 }

+ 2 - 2
src/mol-repr/visual.ts

@@ -5,7 +5,7 @@
  */
 
 import { RuntimeContext } from 'mol-task'
-import { RenderObject, GraphicsRenderObject } from 'mol-gl/render-object'
+import { GraphicsRenderObject } from 'mol-gl/render-object'
 import { PickingId } from '../mol-geo/geometry/picking';
 import { Loci } from 'mol-model/loci';
 import { MarkerAction } from '../mol-geo/geometry/marker-data';
@@ -27,7 +27,7 @@ export { Visual }
 interface Visual<D, P extends PD.Params> {
     /** Number of addressable groups in all instances of the visual */
     readonly groupCount: number
-    readonly renderObject: RenderObject | undefined
+    readonly renderObject: GraphicsRenderObject | undefined
     createOrUpdate: (ctx: VisualContext, theme: Theme, props?: Partial<PD.Values<P>>, data?: D) => Promise<void> | void
     getLoci: (pickingId: PickingId) => Loci
     mark: (loci: Loci, action: MarkerAction) => boolean

+ 13 - 10
src/tests/browser/render-mesh.ts

@@ -26,16 +26,19 @@ parent.appendChild(canvas)
 const canvas3d = Canvas3D.create(canvas, parent)
 canvas3d.animate()
 
-const builderState = MeshBuilder.createState()
-const t = Mat4.identity()
-const sphere = Sphere(2)
-MeshBuilder.addPrimitive(builderState, t, sphere)
-const mesh = MeshBuilder.getMesh(builderState)
+function meshRepr() {
+    const builderState = MeshBuilder.createState()
+    const t = Mat4.identity()
+    const sphere = Sphere(2)
+    MeshBuilder.addPrimitive(builderState, t, sphere)
+    const mesh = MeshBuilder.getMesh(builderState)
 
-const values = Mesh.Utils.createValuesSimple(mesh, {}, Color(0xFF0000), 1)
-const state = Mesh.Utils.createRenderableState({})
-const renderObject = createRenderObject('mesh', values, state)
-const repr = Representation.fromRenderObject('sphere-mesh', renderObject)
+    const values = Mesh.Utils.createValuesSimple(mesh, {}, Color(0xFF0000), 1)
+    const state = Mesh.Utils.createRenderableState({})
+    const renderObject = createRenderObject('mesh', values, state)
+    const repr = Representation.fromRenderObject('sphere-mesh', renderObject)
+    return repr
+}
 
-canvas3d.add(repr)
+canvas3d.add(meshRepr())
 canvas3d.resetCamera()

+ 69 - 0
src/tests/browser/render-shape.ts

@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import './index.html'
+import { Canvas3D } from 'mol-canvas3d/canvas3d';
+import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
+import { Sphere } from 'mol-geo/primitive/sphere';
+import { Mat4 } from 'mol-math/linear-algebra';
+import { Shape } from 'mol-model/shape';
+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';
+
+const parent = document.getElementById('app')!
+parent.style.width = '100%'
+parent.style.height = '100%'
+
+const canvas = document.createElement('canvas')
+canvas.style.width = '100%'
+canvas.style.height = '100%'
+parent.appendChild(canvas)
+
+const info = document.createElement('div')
+info.style.position = 'absolute'
+info.style.fontFamily = 'sans-serif'
+info.style.fontSize = '24pt'
+info.style.bottom = '20px'
+info.style.right = '20px'
+info.style.color = 'white'
+parent.appendChild(info)
+
+const canvas3d = Canvas3D.create(canvas, parent)
+canvas3d.animate()
+canvas3d.input.move.subscribe(async ({x, y}) => {
+    const pickingId = await canvas3d.identify(x, y)
+    let label = ''
+    if (pickingId) {
+        const { loci } = canvas3d.getLoci(pickingId)
+        label = labelFirst(loci)
+    }
+    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)
+
+const myData = { mesh, colors: [ColorNames.aquamarine], labels: ['FooBar'] }
+type MyData = typeof myData
+function getShape(data: MyData) {
+    return Shape.create('test', data.mesh, data.colors, data.labels)
+}
+
+const repr = ShapeRepresentation(getShape, Mesh.Utils)
+
+async function add() {
+    await repr.createOrUpdate({}, myData).run()
+    console.log(repr)
+    canvas3d.add(repr)
+    canvas3d.resetCamera()
+}
+add()

+ 1 - 0
webpack.config.js

@@ -67,6 +67,7 @@ module.exports = [
 
     createBrowserTest('font-atlas'),
     createBrowserTest('render-text'),
+    createBrowserTest('render-shape'),
     createBrowserTest('render-spheres'),
     createBrowserTest('render-mesh')
 ]