diff --git a/src/mol-geo/geometry/base.ts b/src/mol-geo/geometry/base.ts new file mode 100644 index 0000000000000000000000000000000000000000..2729048b8d9230efa2deb8bd9e453152132b228f --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index e0df6e5420a5e1ea0411cee2ca4a21c8ac68b258..528b9622f6a4ab585e59d23e92f5837cc8a0aa73 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/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') } diff --git a/src/mol-model/shape/shape.ts b/src/mol-model/shape/shape.ts index dc115c474ff915dce380887801a5d0aa89e6c3b6..9894542386bb4dfba4947dd40e29cc8253005aa5 100644 --- a/src/mol-model/shape/shape.ts +++ b/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), } diff --git a/src/mol-repr/representation.ts b/src/mol-repr/representation.ts index 8c620d049888c730e4bc84e964081f59980bcd12..41369fd550e27e223c926e648a694b1b0ea42a9f 100644 --- a/src/mol-repr/representation.ts +++ b/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) { diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 44e941bc649758d1940be8b7bae9e6c9b9220556..69d352f445729ab235031f3dcfed12c57df113e2 100644 --- a/src/mol-repr/shape/representation.ts +++ b/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) } } \ No newline at end of file diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 570e25e1a5bfafabc6e894110554df8b6cb798ec..d84f2c495d1908d80445ce12b0775250afb9306c 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/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) } diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index 5f20f76625fb085080ed2da6ca005c83fc75b725..d5a3d64761dc34f58045351b07a2db673ac5e7f2 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/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) }) diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index 20023f022db704196ccaa5619592f4b0b7a9af95..6c41478774ef41b361f8127e0d96880fc68ddac0 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/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) } diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index 1a2f5a369be09fa83e62e416283f25d01368b90a..d7776973cc4f1fc701cec85e3e526b88431d3b4c 100644 --- a/src/mol-repr/visual.ts +++ b/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 diff --git a/src/tests/browser/render-mesh.ts b/src/tests/browser/render-mesh.ts index 7ba18624d3bd25771e5edcf028ed6e7aaf62d571..2e50761d2c91009dee24c80adfbcf7369fc66233 100644 --- a/src/tests/browser/render-mesh.ts +++ b/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() \ No newline at end of file diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b2d03f8087888597dad119619ebf2d9a95862e0 --- /dev/null +++ b/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() \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 0599d63fdd03e070d5bfb7a0ddcee397bf5c757b..4c6910acaf927490a0a1e033888c08d8860a098b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -67,6 +67,7 @@ module.exports = [ createBrowserTest('font-atlas'), createBrowserTest('render-text'), + createBrowserTest('render-shape'), createBrowserTest('render-spheres'), createBrowserTest('render-mesh') ] \ No newline at end of file