diff --git a/.travis.yml b/.travis.yml index 0b8ba30b1e8f78991c9103763306ae41d8a6ad89..f00ef37588aabb1b1e2e0212201f0cd4e713c613 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js node_js: - - "8" - - "6" \ No newline at end of file + - "10" + - "8" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4372edc58310e2b2e035c801969456f3831eda0b..aa8892746abe6ae4975e184b529e8f4fcf0fa10e 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index aa68e6943af64ff1812a93463f754ec1fc9c47ef..4499863a868074653689d792b668115714a3753e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ "model-server-watch": "nodemon --watch build/node_modules build/node_modules/servers/model/server.js" }, "nodemonConfig": { - "ignoreRoot": ["./node_modules", ".git"], + "ignoreRoot": [ + "./node_modules", + ".git" + ], "ignore": [], "delay": "2500" }, @@ -63,14 +66,14 @@ "author": "", "license": "MIT", "devDependencies": { - "@types/argparse": "^1.0.33", + "@types/argparse": "^1.0.34", "@types/benchmark": "^1.0.31", "@types/compression": "0.0.36", "@types/express": "^4.11.1", - "@types/jest": "^22.2.3", - "@types/node": "^10.1.2", - "@types/node-fetch": "^1.6.9", - "@types/react": "^16.3.14", + "@types/jest": "^23.0.0", + "@types/node": "~10.1.2", + "@types/node-fetch": "^2.1.1", + "@types/react": "^16.3.16", "@types/react-dom": "^16.0.5", "benchmark": "^2.1.4", "copyfiles": "^2.0.0", @@ -81,20 +84,20 @@ "file-loader": "^1.1.11", "glslify-import": "^3.1.0", "glslify-loader": "^1.0.2", - "jest": "^22.4.4", + "jest": "^23.1.0", "jest-raw-loader": "^1.0.1", "node-sass": "^4.9.0", "raw-loader": "^0.5.1", "resolve-url-loader": "^2.3.0", - "sass-loader": "^7.0.1", + "sass-loader": "^7.0.2", "style-loader": "^0.21.0", "ts-jest": "^22.4.6", "tslint": "^5.10.0", - "typescript": "^2.8.3", - "uglify-js": "^3.3.27", + "typescript": "^2.9.1", + "uglify-js": "^3.4.0", "util.promisify": "^1.0.0", - "webpack": "^4.8.3", - "webpack-cli": "^2.1.3" + "webpack": "^4.10.2", + "webpack-cli": "^3.0.1" }, "dependencies": { "argparse": "^1.0.10", @@ -102,8 +105,8 @@ "express": "^4.16.3", "immutable": "^4.0.0-rc.9", "node-fetch": "^2.1.2", - "react": "^16.3.2", - "react-dom": "^16.3.2", + "react": "^16.4.0", + "react-dom": "^16.4.0", "rxjs": "^6.2.0" } } diff --git a/src/mol-app/ui/visualization/image-canvas.tsx b/src/mol-app/ui/visualization/image-canvas.tsx index 643a087529ed8ee49c8e7340ba567d0be6415573..e6e329d0da3315cc844ae101a6ed4bc87c376042 100644 --- a/src/mol-app/ui/visualization/image-canvas.tsx +++ b/src/mol-app/ui/visualization/image-canvas.tsx @@ -22,39 +22,41 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR private canvas: HTMLCanvasElement | null = null; private ctx: CanvasRenderingContext2D | null = null; - componentWillMount() { + updateStateFromProps() { this.setState({ imageData: this.props.imageData, ...getExtend(this.props.aspectRatio, this.props.maxWidth, this.props.maxHeight) }) } - componentDidMount() { + updateImage() { if (this.canvas) { this.canvas.width = this.state.imageData.width this.canvas.height = this.state.imageData.height - this.ctx = this.canvas.getContext('2d') } if (this.ctx) { this.ctx.putImageData(this.state.imageData, 0, 0) } } + componentWillMount() { + this.updateStateFromProps() + } + + componentDidMount() { + if (this.canvas && !this.ctx) { + this.ctx = this.canvas.getContext('2d') + if (this.ctx) this.ctx.imageSmoothingEnabled = false + } + this.updateImage() + } + componentWillReceiveProps() { - this.setState({ - imageData: this.props.imageData, - ...getExtend(this.props.aspectRatio, this.props.maxWidth, this.props.maxHeight) - }) + this.updateStateFromProps() } componentDidUpdate() { - if (this.canvas) { - this.canvas.width = this.state.imageData.width - this.canvas.height = this.state.imageData.height - } - if (this.ctx) { - this.ctx.putImageData(this.state.imageData, 0, 0) - } + this.updateImage() } render() { @@ -63,10 +65,9 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR style={{ width: this.state.width + 6, height: this.state.height + 6, - position: 'absolute', + margin: 10, + display: 'inline-block', border: '3px white solid', - bottom: 10, - left: 10, }} > <canvas @@ -74,6 +75,7 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR style={{ width: this.state.width, height: this.state.height, + imageRendering: 'pixelated' }} /> </div>; diff --git a/src/mol-app/ui/visualization/viewport.tsx b/src/mol-app/ui/visualization/viewport.tsx index 707f7116e38761699a7c026e6df16e4fc62cd656..a13c12809066f2d3b9c790f5d9c082986e54e112 100644 --- a/src/mol-app/ui/visualization/viewport.tsx +++ b/src/mol-app/ui/visualization/viewport.tsx @@ -94,11 +94,39 @@ export const Logo = () => </div> -export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, showLogo?: boolean, imageData?: ImageData, aspectRatio: number }> { +type ViewportState = { + noWebGl: boolean, + showLogo: boolean, + aspectRatio: number, + width: number + height: number + images: { [k: string]: ImageData } + info: string +} + +export class Viewport extends View<ViewportController, ViewportState, { noWebGl?: boolean, showLogo?: boolean, aspectRatio: number, info: string }> { private container: HTMLDivElement | null = null; private canvas: HTMLCanvasElement | null = null; private defaultBg = { r: 1, g: 1, b: 1 } - state = { noWebGl: false, showLogo: true, imageData: undefined, aspectRatio: 1 }; + state: ViewportState = { + noWebGl: false, + showLogo: true, + images: {}, + aspectRatio: 1, + width: 0, + height: 0, + info: '' + }; + + handleResize() { + if (this.container) { + this.setState({ + aspectRatio: this.container.clientWidth / this.container.clientHeight, + width: this.container.clientWidth, + height: this.container.clientHeight + }) + } + } componentDidMount() { if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) { @@ -114,12 +142,23 @@ export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, }) }) - viewer.didDraw.subscribe(() => this.setState({ imageData: viewer.getImageData() })) - viewer.didDraw.subscribe(() => this.setState({ imageData: viewer.getImageData() })) + viewer.identified.subscribe(info => { + this.setState({ info }) + }) - if (this.container) { - this.setState({ aspectRatio: this.container.clientWidth / this.container.clientHeight }) - } + viewer.didDraw.subscribe(() => { + // this.setState({ imageData: viewer.getImageData() }) + this.setState({ + images: { + 'object': viewer.getImageData('pickObject'), + 'instance': viewer.getImageData('pickInstance'), + 'element': viewer.getImageData('pickElement') + } + }) + }) + + viewer.input.resize.subscribe(() => this.handleResize()) + this.handleResize() } componentWillUnmount() { @@ -140,14 +179,6 @@ export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, render() { if (this.state.noWebGl) return this.renderMissing(); - // const imageData = new ImageData(256, 128) - - let image: JSX.Element | undefined - const imageData = this.state.imageData - if (imageData) { - image = <ImageCanvas imageData={imageData} aspectRatio={this.state.aspectRatio} maxWidth={256} maxHeight={256} /> - } - const color = this.controller.latestState.clearColor! || this.defaultBg; return <div className='molstar-viewport' style={{ backgroundColor: `rgb(${255 * color.r}, ${255 * color.g}, ${255 * color.b})` }}> <div ref={elm => this.container = elm} className='molstar-viewport-container'> @@ -155,7 +186,36 @@ export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, </div> {this.state.showLogo ? <Logo /> : void 0} <ViewportControls controller={this.controller} /> - {image} + <div + style={{ + position: 'absolute', + top: 10, + left: 10, + padding: 10, + color: 'lightgrey', + background: 'rgba(0, 0, 0, 0.2)' + }} + > + {this.state.info} + </div> + <div + style={{ + position: 'absolute', + bottom: 10, + left: 10, + }} + > + {Object.keys(this.state.images).map(k => { + const imageData = this.state.images[k] + return <ImageCanvas + key={k} + imageData={imageData} + aspectRatio={this.state.aspectRatio} + maxWidth={this.state.width / 4} + maxHeight={this.state.height / 4} + /> + })} + </div> </div>; } } \ No newline at end of file diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index 0d16ead2e558a541588c9f86757e5886cc7f2a8d..3bb9db0d29abeaa5fca0a07c80490c0fd0da4b6f 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -6,6 +6,7 @@ import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; +import { PickingId, PickingInfo } from '../util/picking'; export interface RepresentationProps {} @@ -13,4 +14,5 @@ export interface Representation<D, P extends RepresentationProps = {}> { renderObjects: ReadonlyArray<RenderObject> create: (data: D, props?: P) => Task<void> update: (props: P) => Task<void> + getLabel: (pickingId: PickingId) => PickingInfo | null } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 11f21138d1a0604fc873c5a7cbfd6cef097d6746..b9716cef44a687d8ab542453959c3f6c2682a2e2 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -5,22 +5,26 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Structure, StructureSymmetry, Unit } from 'mol-model/structure'; +import { Structure, StructureSymmetry, Unit, Element, Queries } from 'mol-model/structure'; import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; import { Representation, RepresentationProps } from '..'; import { ColorTheme } from '../../theme'; +import { PickingId, PickingInfo } from '../../util/picking'; export interface UnitsRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> create: (group: Unit.SymmetryGroup, props: P) => Task<void> update: (props: P) => Task<boolean> + getLocation: (pickingId: PickingId) => Element.Location | null } export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { renderObjects: ReadonlyArray<RenderObject> create: (structure: Structure, props?: P) => Task<void> update: (props: P) => Task<void> + getLocation: (pickingId: PickingId) => Element.Location | null + getLabel: (pickingId: PickingId) => PickingInfo | null } interface GroupRepresentation<T> { @@ -28,12 +32,43 @@ interface GroupRepresentation<T> { group: Unit.SymmetryGroup } +function label(loc: Element.Location) { + const model = loc.unit.model.label + const instance = loc.unit.label + let element = '' + + if (Unit.isAtomic(loc.unit)) { + const asym_id = Queries.props.chain.auth_asym_id(loc) + const seq_id = Queries.props.residue.auth_seq_id(loc) + const comp_id = Queries.props.residue.auth_comp_id(loc) + const atom_id = Queries.props.atom.auth_atom_id(loc) + element = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}` + } else if (Unit.isCoarse(loc.unit)) { + const asym_id = Queries.props.coarse.asym_id(loc) + const seq_id_begin = Queries.props.coarse.seq_id_begin(loc) + const seq_id_end = Queries.props.coarse.seq_id_end(loc) + if (seq_id_begin === seq_id_end) { + const entityKey = Queries.props.coarse.entityKey(loc) + const seq = loc.unit.model.sequence.byEntityKey[entityKey] + const comp_id = seq.compId.value(seq_id_begin) + element = `[${comp_id}]${seq_id_begin}:${asym_id}` + } else { + element = `${seq_id_begin}-${seq_id_end}:${asym_id}` + } + } else { + element = 'unknown' + } + + return { label: `${model} ${instance} ${element}` } +} + export const DefaultStructureProps = { colorTheme: { name: 'instance-index' } as ColorTheme, alpha: 1, visible: true, doubleSided: false, - depthMask: true + depthMask: true, + hoverSelection: { objectId: -1, instanceId: -1, elementId: -1 } as PickingId } export type StructureProps = Partial<typeof DefaultStructureProps> @@ -42,6 +77,14 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () = const groupReprs: GroupRepresentation<P>[] = [] // let currentProps: typeof DefaultStructureProps + function getLocation(pickingId: PickingId) { + for (let i = 0, il = groupReprs.length; i < il; ++i) { + const loc = groupReprs[i].repr.getLocation(pickingId) + if (loc) return loc + } + return null + } + return { renderObjects, create(structure: Structure, props: P = {} as P) { @@ -77,6 +120,11 @@ export function StructureRepresentation<P extends StructureProps>(reprCtor: () = renderObjects.push(...repr.renderObjects) } }) + }, + getLocation, + getLabel(pickingId: PickingId) { + const loc = getLocation(pickingId) + return loc ? label(loc) : null } } } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 0984717e021af677117293dd5041214062260233..cf090acac8cafb697f1d0c96d60fe4794a843f5e 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -11,20 +11,18 @@ import { Unit, Element } from 'mol-model/structure'; import { Task } from 'mol-task' import { fillSerial } from 'mol-gl/renderable/util'; -import { UnitsRepresentation } from './index'; +import { UnitsRepresentation, DefaultStructureProps } from './index'; import VertexMap from '../../shape/vertex-map'; -import { ColorTheme, SizeTheme } from '../../theme'; -import { createTransforms, createColors, createSizes } from './utils'; +import { SizeTheme } from '../../theme'; +import { createTransforms, createColors, createSizes, createFlags } from './utils'; import { deepEqual, defaults } from 'mol-util'; import { SortedArray } from 'mol-data/int'; import { RenderableState, PointValues } from 'mol-gl/renderable'; +import { PickingId } from '../../util/picking'; export const DefaultPointProps = { - colorTheme: { name: 'instance-index' } as ColorTheme, - sizeTheme: { name: 'vdw' } as SizeTheme, - alpha: 1, - visible: true, - depthMask: true + ...DefaultStructureProps, + sizeTheme: { name: 'vdw' } as SizeTheme } export type PointProps = Partial<typeof DefaultPointProps> @@ -50,7 +48,8 @@ export function createPointVertices(unit: Unit) { export default function Point(): UnitsRepresentation<PointProps> { const renderObjects: RenderObject[] = [] let points: PointRenderObject - let curProps = DefaultPointProps + let currentProps = DefaultPointProps + let currentGroup: Unit.SymmetryGroup let _units: ReadonlyArray<Unit> let _elements: SortedArray @@ -58,14 +57,16 @@ export default function Point(): UnitsRepresentation<PointProps> { return { renderObjects, create(group: Unit.SymmetryGroup, props: PointProps = {}) { + currentProps = Object.assign({}, DefaultPointProps, props) + return Task.create('Point.create', async ctx => { renderObjects.length = 0 // clear - curProps = { ...DefaultPointProps, ...props } + currentGroup = group _units = group.units _elements = group.elements; - const { colorTheme, sizeTheme } = curProps + const { colorTheme, sizeTheme, hoverSelection } = currentProps const elementCount = _elements.length const vertexMap = VertexMap.create( @@ -87,6 +88,9 @@ export default function Point(): UnitsRepresentation<PointProps> { await ctx.update('Computing point sizes'); const size = createSizes(group, vertexMap, sizeTheme) + await ctx.update('Computing spacefill flags'); + const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId) + const instanceCount = group.units.length const values: PointValues = { @@ -95,10 +99,10 @@ export default function Point(): UnitsRepresentation<PointProps> { aTransform: transforms, aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, + ...flag, ...size, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), - uObjectId: ValueCell.create(0), uInstanceCount: ValueCell.create(instanceCount), uElementCount: ValueCell.create(group.elements.length), @@ -120,8 +124,8 @@ export default function Point(): UnitsRepresentation<PointProps> { return Task.create('Point.update', async ctx => { if (!points || !_units || !_elements) return false - const newProps = { ...curProps, ...props } - if (deepEqual(curProps, newProps)) { + const newProps = { ...currentProps, ...props } + if (deepEqual(currentProps, newProps)) { console.log('props identical, nothing to change') return true } @@ -136,20 +140,30 @@ export default function Point(): UnitsRepresentation<PointProps> { // fillSerial(new Uint32Array(elementCount + 1)) // ) - if (!deepEqual(curProps.colorTheme, newProps.colorTheme)) { - console.log('colorTheme changed', curProps.colorTheme, newProps.colorTheme) + if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) { + console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme) // await ctx.update('Computing point colors'); // const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme) // ValueCell.update(points.props.color, color) } - if (!deepEqual(curProps.sizeTheme, newProps.sizeTheme)) { - console.log('sizeTheme changed', curProps.sizeTheme, newProps.sizeTheme) + if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) { + console.log('sizeTheme changed', currentProps.sizeTheme, newProps.sizeTheme) } - curProps = newProps + currentProps = newProps return false }) + }, + getLocation(pickingId: PickingId) { + const { objectId, instanceId, elementId } = pickingId + if (points.id === objectId) { + const l = Element.Location() + l.unit = currentGroup.units[instanceId] + l.element = currentGroup.elements[elementId] + return l + } + return null } } } diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 796ab996c06064cfed35e45072db3aefdb9ab7c5..ebfbe71a1c7c2923fa8d21fd2903bce1e4ff90ff 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -8,20 +8,30 @@ import { ValueCell } from 'mol-util/value-cell' import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' -// import { createColorTexture } from 'mol-gl/util'; -import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { Unit, Element, Queries } from 'mol-model/structure'; import { UnitsRepresentation, DefaultStructureProps } from './index'; import { Task } from 'mol-task' -import { MeshBuilder } from '../../shape/mesh-builder'; -import { createTransforms, createColors } from './utils'; +import { createTransforms, createColors, createFlags, createEmptyFlags, createSphereMesh } from './utils'; import VertexMap from '../../shape/vertex-map'; -import { icosahedronVertexCount } from '../../primitive/icosahedron'; import { deepEqual, defaults } from 'mol-util'; import { fillSerial } from 'mol-gl/renderable/util'; import { RenderableState, MeshValues } from 'mol-gl/renderable'; import { getMeshData } from '../../util/mesh-data'; import { Mesh } from '../../shape/mesh'; +import { PickingId } from '../../util/picking'; + +function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) { + let radius: Element.Property<number> + if (Unit.isAtomic(unit)) { + radius = Queries.props.atom.vdw_radius + } else if (Unit.isSpheres(unit)) { + radius = Queries.props.coarse.sphere_radius + } else { + console.warn('Unsupported unit type') + return Task.constant('Empty mesh', Mesh.createEmpty(mesh)) + } + return createSphereMesh(unit, radius, detail, mesh) +} export const DefaultSpacefillProps = { ...DefaultStructureProps, @@ -31,51 +41,6 @@ export const DefaultSpacefillProps = { } export type SpacefillProps = Partial<typeof DefaultSpacefillProps> -function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) { - return Task.create('Sphere mesh', async ctx => { - const { elements } = unit; - const elementCount = elements.length; - const vertexCount = elementCount * icosahedronVertexCount(detail) - const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh) - - let radius: Element.Property<number> - if (Unit.isAtomic(unit)) { - radius = Queries.props.atom.vdw_radius - } else if (Unit.isSpheres(unit)) { - radius = Queries.props.coarse.sphere_radius - } else { - console.warn('Unsupported unit type') - return meshBuilder.getMesh() - } - - const v = Vec3.zero() - const m = Mat4.identity() - - const { x, y, z } = unit.conformation - const l = Element.Location() - l.unit = unit - - for (let i = 0; i < elementCount; i++) { - l.element = elements[i] - v[0] = x(l.element) - v[1] = y(l.element) - v[2] = z(l.element) - Mat4.setTranslation(m, v) - - meshBuilder.setId(i) - meshBuilder.addIcosahedron(m, { radius: radius(l), detail }) - - if (i % 10000 === 0 && ctx.shouldUpdate) { - await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount }); - } - } - - const _mesh = meshBuilder.getMesh() - console.log(_mesh) - return _mesh - }) -} - export default function Spacefill(): UnitsRepresentation<SpacefillProps> { const renderObjects: RenderObject[] = [] let spheres: MeshRenderObject @@ -93,7 +58,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { renderObjects.length = 0 // clear currentGroup = group - const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props } + const { detail, colorTheme, hoverSelection } = { ...DefaultSpacefillProps, ...props } mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh') // console.log(mesh) @@ -105,6 +70,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { await ctx.update('Computing spacefill colors'); const color = createColors(group, vertexMap, colorTheme) + await ctx.update('Computing spacefill flags'); + const flag = createFlags(group, hoverSelection.instanceId, hoverSelection.elementId) + const instanceCount = group.units.length const values: MeshValues = { @@ -112,9 +80,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { aTransform: transforms, aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, + ...flag, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), - uObjectId: ValueCell.create(0), uInstanceCount: ValueCell.create(instanceCount), uElementCount: ValueCell.create(group.elements.length), @@ -161,6 +129,15 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values) } + if (newProps.hoverSelection !== currentProps.hoverSelection) { + await ctx.update('Computing spacefill flags'); + if (newProps.hoverSelection.objectId === spheres.id) { + createFlags(currentGroup, newProps.hoverSelection.instanceId, newProps.hoverSelection.elementId, spheres.values) + } else { + createEmptyFlags(spheres.values) + } + } + ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha) ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided) ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided) @@ -172,6 +149,16 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { currentProps = newProps return true }) + }, + getLocation(pickingId: PickingId) { + const { objectId, instanceId, elementId } = pickingId + if (spheres.id === objectId) { + const l = Element.Location() + l.unit = currentGroup.units[instanceId] + l.element = currentGroup.elements[elementId] + return l + } + return null } } } diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts index a242839edcee47cdf8b2ec8b3eaaea48d444fd2c..5771c3f122504e4413f1423ce0e73a8d53a92f6a 100644 --- a/src/mol-geo/representation/structure/utils.ts +++ b/src/mol-geo/representation/structure/utils.ts @@ -5,8 +5,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Unit } from 'mol-model/structure'; -import { Mat4 } from 'mol-math/linear-algebra' +import { Unit, Element } from 'mol-model/structure'; +import { Mat4, Vec2, Vec3 } from 'mol-math/linear-algebra' import { createUniformColor, ColorData } from '../../util/color-data'; import { createUniformSize } from '../../util/size-data'; @@ -15,6 +15,11 @@ import VertexMap from '../../shape/vertex-map'; import { ColorTheme, SizeTheme } from '../../theme'; import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color'; import { ValueCell } from 'mol-util'; +import { TextureImage, createTextureImage } from 'mol-gl/renderable/util'; +import { Mesh } from '../../shape/mesh'; +import { Task } from 'mol-task'; +import { icosahedronVertexCount } from '../../primitive/icosahedron'; +import { MeshBuilder } from '../../shape/mesh-builder'; export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) { const unitCount = units.length @@ -48,4 +53,81 @@ export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, pro case 'vdw': return elementSizeData({ group, vertexMap }) } -} \ No newline at end of file +} + +export type FlagData = { + tFlag: ValueCell<TextureImage> + uFlagTexSize: ValueCell<Vec2> +} + +export function createFlags(group: Unit.SymmetryGroup, instanceId: number, elementId: number, flagData?: FlagData): FlagData { + const instanceCount = group.units.length + const elementCount = group.elements.length + const count = instanceCount * elementCount + const flags = flagData && flagData.tFlag.ref.value.array.length >= count ? flagData.tFlag.ref.value : createTextureImage(count, 1) + let flagOffset = 0 + for (let i = 0; i < instanceCount; i++) { + for (let j = 0, jl = elementCount; j < jl; ++j) { + flags.array[flagOffset] = (i === instanceId && j === elementId) ? 255 : 0 + flagOffset += 1 + } + } + // console.log(flags, instanceCount, elementCount) + if (flagData) { + ValueCell.update(flagData.tFlag, flags) + ValueCell.update(flagData.uFlagTexSize, Vec2.create(flags.width, flags.height)) + return flagData + } else { + return { + tFlag: ValueCell.create(flags), + uFlagTexSize: ValueCell.create(Vec2.create(flags.width, flags.height)), + } + } +} + +const emptyFlagTexture = { array: new Uint8Array(1), width: 1, height: 1 } +export function createEmptyFlags(flagData?: FlagData) { + if (flagData) { + ValueCell.update(flagData.tFlag, emptyFlagTexture) + ValueCell.update(flagData.uFlagTexSize, Vec2.create(1, 1)) + return flagData + } else { + return { + tFlag: ValueCell.create(emptyFlagTexture), + uFlagTexSize: ValueCell.create(Vec2.create(1, 1)), + } + } +} + +export function createSphereMesh(unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) { + return Task.create('Sphere mesh', async ctx => { + const { elements } = unit; + const elementCount = elements.length; + const vertexCount = elementCount * icosahedronVertexCount(detail) + const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh) + + const v = Vec3.zero() + const m = Mat4.identity() + + const { x, y, z } = unit.conformation + const l = Element.Location() + l.unit = unit + + for (let i = 0; i < elementCount; i++) { + l.element = elements[i] + v[0] = x(l.element) + v[1] = y(l.element) + v[2] = z(l.element) + Mat4.setTranslation(m, v) + + meshBuilder.setId(i) + meshBuilder.addIcosahedron(m, { radius: radius(l), detail }) + + if (i % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'Sphere mesh', current: i, max: elementCount }); + } + } + + return meshBuilder.getMesh() + }) +} diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index 5baece8ad324f559ef85c58e6acb23128e2816fd..f8edfdaad75d5dbce7d9e2c4fad7a793b9d02d56 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -8,17 +8,20 @@ import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; import { RepresentationProps, Representation } from '..'; import { VolumeData } from 'mol-model/volume'; +import { PickingId, PickingInfo } from '../../util/picking'; export interface VolumeElementRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> create: (volumeData: VolumeData, props: P) => Task<void> update: (props: P) => Task<boolean> + getLabel: (pickingId: PickingId) => PickingInfo | null } export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { renderObjects: ReadonlyArray<RenderObject> create: (volumeData: VolumeData, props?: P) => Task<void> update: (props: P) => Task<void> + getLabel: (pickingId: PickingId) => PickingInfo | null } export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentation<P>): VolumeRepresentation<P> { @@ -35,6 +38,9 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat }, update(props: P) { return Task.create('VolumeRepresentation.update', async ctx => {}) + }, + getLabel(pickingId: PickingId) { + return null } } } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index 1918626c099884b729119005e4ab50d3461d7b47..fab4c945407a158c3383537f9c1c8af0acf283d6 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -17,6 +17,8 @@ import { Mat4 } from 'mol-math/linear-algebra'; import { createUniformColor } from '../../util/color-data'; import { getMeshData } from '../../util/mesh-data'; import { RenderableState, MeshValues } from 'mol-gl/renderable'; +import { PickingId } from '../../util/picking'; +import { createEmptyFlags } from '../structure/utils'; export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) { return Task.create<Mesh>('Volume Surface', async ctx => { @@ -65,15 +67,16 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> { const instanceCount = 1 const color = createUniformColor({ value: 0x7ec0ee }) + const flag = createEmptyFlags() const values: MeshValues = { ...getMeshData(mesh), aTransform: ValueCell.create(new Float32Array(Mat4.identity())), aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))), ...color, + ...flag, uAlpha: ValueCell.create(defaults(props.alpha, 1.0)), - uObjectId: ValueCell.create(0), uInstanceCount: ValueCell.create(instanceCount), uElementCount: ValueCell.create(mesh.triangleCount), @@ -100,6 +103,9 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> { // TODO return false }) + }, + getLabel(pickingId: PickingId) { + return null } } } diff --git a/src/mol-geo/shape/mesh-builder.ts b/src/mol-geo/shape/mesh-builder.ts index b34f2b5c9fe025c52c72e1851ca2347e5da9eb1c..1236736cce4e310e301bbe6fbc0a37a074f7d054 100644 --- a/src/mol-geo/shape/mesh-builder.ts +++ b/src/mol-geo/shape/mesh-builder.ts @@ -5,13 +5,14 @@ */ import { ValueCell } from 'mol-util/value-cell' -import { Vec3, Mat4 } from 'mol-math/linear-algebra'; +import { Vec3, Mat4, Mat3 } from 'mol-math/linear-algebra'; import { ChunkedArray } from 'mol-data/util'; import Box, { BoxProps } from '../primitive/box'; import Cylinder, { CylinderProps } from '../primitive/cylinder'; import Icosahedron, { IcosahedronProps } from '../primitive/icosahedron'; import { Mesh } from './mesh'; +import { getNormalMatrix } from '../util'; type Primitive = { vertices: Float32Array @@ -29,6 +30,7 @@ export interface MeshBuilder { } const tmpV = Vec3.zero() +const tmpMat3 = Mat3.zero() // TODO cache primitives based on props @@ -47,6 +49,7 @@ export namespace MeshBuilder { const add = (t: Mat4, _vertices: Float32Array, _normals: Float32Array, _indices: Uint32Array) => { const { elementCount, elementSize } = vertices + const n = getNormalMatrix(tmpMat3, t) for (let i = 0, il = _vertices.length; i < il; i += 3) { // position Vec3.fromArray(tmpV, _vertices, i) @@ -54,7 +57,7 @@ export namespace MeshBuilder { ChunkedArray.add3(vertices, tmpV[0], tmpV[1], tmpV[2]); // normal Vec3.fromArray(tmpV, _normals, i) - // Vec3.transformDirection(tmpV, tmpV, n) // TODO + Vec3.transformMat3(tmpV, tmpV, n) ChunkedArray.add3(normals, tmpV[0], tmpV[1], tmpV[2]); ChunkedArray.add(ids, currentId); diff --git a/src/mol-geo/shape/mesh.ts b/src/mol-geo/shape/mesh.ts index 6d63fd851e2236521a0be698e734e1735a2cb5c3..9447404a353e906a8ee0d27708bd6b7743509160 100644 --- a/src/mol-geo/shape/mesh.ts +++ b/src/mol-geo/shape/mesh.ts @@ -15,6 +15,8 @@ export interface Mesh { vertexCount: number, /** Number of triangles in the mesh */ triangleCount: number, + /** Number of offsets in the mesh */ + offsetCount: number, /** Vertex buffer as array of xyz values wrapped in a value cell */ vertexBuffer: ValueCell<Float32Array>, @@ -37,6 +39,26 @@ export interface Mesh { } export namespace Mesh { + export function createEmpty(mesh?: Mesh): Mesh { + const vb = mesh ? mesh.vertexBuffer.ref.value : new Float32Array(0) + const ib = mesh ? mesh.indexBuffer.ref.value : new Uint32Array(0) + const nb = mesh ? mesh.normalBuffer.ref.value : new Float32Array(0) + const idb = mesh ? mesh.idBuffer.ref.value : new Float32Array(0) + const ob = mesh ? mesh.offsetBuffer.ref.value : new Uint32Array(0) + return { + vertexCount: 0, + triangleCount: 0, + offsetCount: 0, + vertexBuffer: mesh ? ValueCell.update(mesh.vertexBuffer, vb) : ValueCell.create(vb), + indexBuffer: mesh ? ValueCell.update(mesh.indexBuffer, ib) : ValueCell.create(ib), + normalBuffer: mesh ? ValueCell.update(mesh.normalBuffer, nb) : ValueCell.create(nb), + idBuffer: mesh ? ValueCell.update(mesh.idBuffer, idb) : ValueCell.create(idb), + offsetBuffer: mesh ? ValueCell.update(mesh.offsetBuffer, ob) : ValueCell.create(ob), + normalsComputed: true, + offsetsComputed: true, + } + } + export function computeNormalsImmediate(surface: Mesh) { if (surface.normalsComputed) return; diff --git a/src/mol-geo/util/color-data.ts b/src/mol-geo/util/color-data.ts index 09dfc525f08bc14a226198cc67f285cf7dcb01f1..aadc34027e952ff37196c00bcc1fd7dfe78279c1 100644 --- a/src/mol-geo/util/color-data.ts +++ b/src/mol-geo/util/color-data.ts @@ -5,7 +5,7 @@ */ import { ValueCell } from 'mol-util'; -import { TextureImage, createColorTexture, emptyTexture } from 'mol-gl/renderable/util'; +import { TextureImage, createTextureImage } from 'mol-gl/renderable/util'; import { Color } from 'mol-util/color'; import VertexMap from '../shape/vertex-map'; import { Vec2, Vec3 } from 'mol-math/linear-algebra'; @@ -20,6 +20,14 @@ export type ColorData = { dColorType: ValueCell<string>, } +const emptyColorTexture = { array: new Uint8Array(3), width: 1, height: 1 } +function createEmptyColorTexture() { + return { + tColor: ValueCell.create(emptyColorTexture), + uColorTexSize: ValueCell.create(Vec2.create(1, 1)) + } +} + export interface UniformColorProps { value: Color } @@ -36,8 +44,7 @@ export function createUniformColor(props: UniformColorProps, colorData?: ColorDa return { uColor: ValueCell.create(Color.toRgbNormalized(props.value) as Vec3), aColor: ValueCell.create(new Float32Array(0)), - tColor: ValueCell.create(emptyTexture), - uColorTexSize: ValueCell.create(Vec2.zero()), + ...createEmptyColorTexture(), dColorType: ValueCell.create('uniform'), } } @@ -62,7 +69,6 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col } } if (colorData) { - console.log('update colordata attribute') ValueCell.update(colorData.aColor, colors) if (colorData.dColorType.ref.value !== 'attribute') { ValueCell.update(colorData.dColorType, 'attribute') @@ -72,8 +78,7 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col return { uColor: ValueCell.create(Vec3.zero()), aColor: ValueCell.create(colors), - tColor: ValueCell.create(emptyTexture), - uColorTexSize: ValueCell.create(Vec2.zero()), + ...createEmptyColorTexture(), dColorType: ValueCell.create('attribute'), } } @@ -81,7 +86,6 @@ export function createAttributeColor(props: AttributeColorProps, colorData?: Col export function createTextureColor(colors: TextureImage, type: ColorType, colorData?: ColorData): ColorData { if (colorData) { - console.log('update colordata texture') ValueCell.update(colorData.tColor, colors) ValueCell.update(colorData.uColorTexSize, Vec2.create(colors.width, colors.height)) if (colorData.dColorType.ref.value !== type) { @@ -107,7 +111,7 @@ export interface InstanceColorProps { /** Creates color texture with color for each instance/unit */ export function createInstanceColor(props: InstanceColorProps, colorData?: ColorData): ColorData { const { colorFn, instanceCount} = props - const colors = colorData && colorData.tColor.ref.value.array.length >= instanceCount * 3 ? colorData.tColor.ref.value : createColorTexture(instanceCount) + const colors = colorData && colorData.tColor.ref.value.array.length >= instanceCount * 3 ? colorData.tColor.ref.value : createTextureImage(instanceCount, 3) for (let i = 0; i < instanceCount; i++) { Color.toArray(colorFn(i), colors.array, i * 3) } @@ -123,7 +127,7 @@ export interface ElementColorProps { export function createElementColor(props: ElementColorProps, colorData?: ColorData): ColorData { const { colorFn, vertexMap } = props const elementCount = vertexMap.offsetCount - 1 - const colors = colorData && colorData.tColor.ref.value.array.length >= elementCount * 3 ? colorData.tColor.ref.value : createColorTexture(elementCount) + const colors = colorData && colorData.tColor.ref.value.array.length >= elementCount * 3 ? colorData.tColor.ref.value : createTextureImage(elementCount, 3) for (let i = 0, il = elementCount; i < il; ++i) { Color.toArray(colorFn(i), colors.array, i * 3) } @@ -141,7 +145,7 @@ export function createElementInstanceColor(props: ElementInstanceColorProps, col const { colorFn, instanceCount, vertexMap } = props const elementCount = vertexMap.offsetCount - 1 const count = instanceCount * elementCount - const colors = colorData && colorData.tColor.ref.value.array.length >= count * 3 ? colorData.tColor.ref.value : createColorTexture(count) + const colors = colorData && colorData.tColor.ref.value.array.length >= count * 3 ? colorData.tColor.ref.value : createTextureImage(count, 3) let colorOffset = 0 for (let i = 0; i < instanceCount; i++) { for (let j = 0, jl = elementCount; j < jl; ++j) { diff --git a/src/mol-geo/util/marching-cubes/algorithm.ts b/src/mol-geo/util/marching-cubes/algorithm.ts index 248f05397889b7a43773a7b78341d1df4aec659e..4b2eaf964996164cd06ccdebd141e8a57b8a20ce 100644 --- a/src/mol-geo/util/marching-cubes/algorithm.ts +++ b/src/mol-geo/util/marching-cubes/algorithm.ts @@ -76,6 +76,7 @@ class MarchingCubesComputation { const ret: Mesh = { vertexCount: this.state.vertexCount, triangleCount: this.state.triangleCount, + offsetCount: 0, vertexBuffer: os ? ValueCell.update(os.vertexBuffer, vb) : ValueCell.create(vb), indexBuffer: os ? ValueCell.update(os.indexBuffer, ib) : ValueCell.create(ib), normalBuffer: os ? os.normalBuffer : ValueCell.create(new Float32Array(0)), diff --git a/src/mol-geo/util/picking.ts b/src/mol-geo/util/picking.ts new file mode 100644 index 0000000000000000000000000000000000000000..00d12d6fd6d297ad232fb81720eb0dc8dd63f252 --- /dev/null +++ b/src/mol-geo/util/picking.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +function decodeFloatRGBA(r: number, g: number, b: number) { + r = Math.floor(r) + g = Math.floor(g) + b = Math.floor(b) + return r * 256 * 256 + g * 256 + b +} + +export function decodeIdRGBA(r: number, g: number, b: number) { + return decodeFloatRGBA(r, g, b) - 1 +} + +export interface PickingId { + objectId: number + instanceId: number + elementId: number +} + +export interface PickingInfo { + label: string + data?: any +} \ No newline at end of file diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index ac7a872a2ead711de419d7db9c12602820c6eaf6..2b57d415255f4c8220d6520c4a4810430a89c8fb 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -18,6 +18,8 @@ import { createContext } from '../webgl/context'; import { RenderableState } from '../renderable'; import { createPointRenderObject } from '../render-object'; import { PointValues } from '../renderable/point'; +import Scene from '../scene'; +import { createEmptyFlags } from 'mol-geo/representation/structure/utils'; // function writeImage(gl: WebGLRenderingContext, width: number, height: number) { // const pixels = new Uint8Array(width * height * 4) @@ -47,6 +49,7 @@ function createPoints() { const aInstanceId = ValueCell.create(fillSerial(new Float32Array(1))) const color = createUniformColor({ value: 0xFF0000 }) const size = createUniformSize({ value: 1 }) + const flag = createEmptyFlags() const aTransform = ValueCell.create(new Float32Array(16)) const m4 = Mat4.identity() @@ -58,10 +61,10 @@ function createPoints() { aTransform, aInstanceId, ...color, + ...flag, ...size, uAlpha: ValueCell.create(1.0), - uObjectId: ValueCell.create(0), uInstanceCount: ValueCell.create(1), uElementCount: ValueCell.create(3), @@ -101,23 +104,24 @@ describe('renderer', () => { it('points', () => { const [ width, height ] = [ 32, 32 ] const gl = createGl(width, height, { preserveDrawingBuffer: true }) - const { ctx, renderer } = createRenderer(gl) + const { ctx } = createRenderer(gl) + const scene = Scene.create(ctx) const points = createPoints() - renderer.add(points) + scene.add(points) expect(ctx.bufferCount).toBe(6); - expect(ctx.textureCount).toBe(1); - expect(ctx.vaoCount).toBe(1); - expect(ctx.programCache.count).toBe(1); - expect(ctx.shaderCache.count).toBe(2); + expect(ctx.textureCount).toBe(2); + expect(ctx.vaoCount).toBe(4); + expect(ctx.programCache.count).toBe(4); + expect(ctx.shaderCache.count).toBe(8); - renderer.remove(points) + scene.remove(points) expect(ctx.bufferCount).toBe(0); expect(ctx.textureCount).toBe(0); expect(ctx.vaoCount).toBe(0); - expect(ctx.programCache.count).toBe(1); - expect(ctx.shaderCache.count).toBe(2); + expect(ctx.programCache.count).toBe(4); + expect(ctx.shaderCache.count).toBe(8); ctx.programCache.dispose() expect(ctx.programCache.count).toBe(0); diff --git a/src/mol-gl/render-object.ts b/src/mol-gl/render-object.ts index 2156c512b9c94806e5115c1808b6b95838184cda..eda1a45c123b6e8adffc4708d547cbb8fdd24ebd 100644 --- a/src/mol-gl/render-object.ts +++ b/src/mol-gl/render-object.ts @@ -27,7 +27,7 @@ export function createPointRenderObject(values: PointValues, state: RenderableSt export function createRenderable(ctx: Context, o: RenderObject) { switch (o.type) { - case 'mesh': return MeshRenderable(ctx, o.values, o.state) - case 'point': return PointRenderable(ctx, o.values, o.state) + case 'mesh': return MeshRenderable(ctx, o.id, o.values, o.state) + case 'point': return PointRenderable(ctx, o.id, o.values, o.state) } } \ No newline at end of file diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index 6251d082b8d5f1f32f38f6b2ef3c3427ab9becab..b3a72be16655e619f1c32a9dda7f9c9400fe0b8f 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -5,7 +5,8 @@ */ import { Program } from './webgl/program'; -import { RenderableValues } from './renderable/schema'; +import { RenderableValues, Values, RenderableSchema } from './renderable/schema'; +import { RenderVariant, RenderItem } from './webgl/render-item'; export type RenderableState = { visible: boolean @@ -13,14 +14,26 @@ export type RenderableState = { } export interface Renderable<T extends RenderableValues> { - draw: () => void - values: T - state: RenderableState - name: string - program: Program + readonly values: T + readonly state: RenderableState + + render: (variant: RenderVariant) => void + getProgram: (variant: RenderVariant) => Program update: () => void dispose: () => void } +export function createRenderable<T extends Values<RenderableSchema>>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> { + return { + get values () { return values }, + get state () { return state }, + + render: (variant: RenderVariant) => renderItem.render(variant), + getProgram: (variant: RenderVariant) => renderItem.getProgram(variant), + update: () => renderItem.update(), + dispose: () => renderItem.destroy() + } +} + export { PointRenderable, PointSchema, PointValues } from './renderable/point' export { MeshRenderable, MeshSchema, MeshValues } from './renderable/mesh' \ No newline at end of file diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index fbcc00804e3b0c77ccbb7eb0af9453cba8139849..562daec704a602fdbe4146b7bb4d9075f8a55a85 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -4,11 +4,12 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Renderable, RenderableState } from '../renderable' +import { Renderable, RenderableState, createRenderable } from '../renderable' import { Context } from '../webgl/context'; import { createRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values } from '../renderable/schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema } from '../renderable/schema'; import { MeshShaderCode } from '../shader-code'; +import { ValueCell } from 'mol-util'; export const MeshSchema = { ...BaseSchema, @@ -21,24 +22,13 @@ export const MeshSchema = { export type MeshSchema = typeof MeshSchema export type MeshValues = Values<MeshSchema> -export function MeshRenderable(ctx: Context, values: MeshValues, state: RenderableState): Renderable<MeshValues> { - const schema = { ...GlobalUniformSchema, ...MeshSchema } +export function MeshRenderable(ctx: Context, id: number, values: MeshValues, state: RenderableState): Renderable<MeshValues> { + const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema } + const internalValues = { + uObjectId: ValueCell.create(id) + } const schaderCode = MeshShaderCode - const renderItem = createRenderItem(ctx, 'triangles', schaderCode, schema, values) + const renderItem = createRenderItem(ctx, 'triangles', schaderCode, schema, { ...values, ...internalValues }) - return { - draw: () => { - renderItem.draw() - }, - get values () { return values }, - get state () { return state }, - name: 'mesh', - get program () { return renderItem.program }, - update: () => { - renderItem.update() - }, - dispose: () => { - renderItem.destroy() - } - } + return createRenderable(renderItem, values, state) } \ No newline at end of file diff --git a/src/mol-gl/renderable/point.ts b/src/mol-gl/renderable/point.ts index c4f51ac1843ac28afc0962f686f963b7b8887370..a200d4e40e3a1d4e6e8fc359f6276a2739b66f7a 100644 --- a/src/mol-gl/renderable/point.ts +++ b/src/mol-gl/renderable/point.ts @@ -4,11 +4,12 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Renderable, RenderableState } from '../renderable' +import { Renderable, RenderableState, createRenderable } from '../renderable' import { Context } from '../webgl/context'; import { createRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values } from '../renderable/schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema } from '../renderable/schema'; import { PointShaderCode } from '../shader-code'; +import { ValueCell } from 'mol-util'; export const PointSchema = { ...BaseSchema, @@ -20,24 +21,13 @@ export const PointSchema = { export type PointSchema = typeof PointSchema export type PointValues = Values<PointSchema> -export function PointRenderable(ctx: Context, values: PointValues, state: RenderableState): Renderable<PointValues> { - const schema = { ...GlobalUniformSchema, ...PointSchema } +export function PointRenderable(ctx: Context, id: number, values: PointValues, state: RenderableState): Renderable<PointValues> { + const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointSchema } + const internalValues = { + uObjectId: ValueCell.create(id) + } const schaderCode = PointShaderCode - const renderItem = createRenderItem(ctx, 'points', schaderCode, schema, values) + const renderItem = createRenderItem(ctx, 'points', schaderCode, schema, { ...values, ...internalValues }) - return { - draw: () => { - renderItem.draw() - }, - get values () { return values }, - get state () { return state }, - name: 'point', - get program () { return renderItem.program }, - update: () => { - renderItem.update() - }, - dispose: () => { - renderItem.destroy() - } - } + return createRenderable(renderItem, values, state) } \ No newline at end of file diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 3cc0237caff661dda8483725661a6178f98d7e46..de475c520699e703f1b8c8a729030fd7426639a5 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -130,6 +130,10 @@ export const GlobalUniformSchema = { export type GlobalUniformSchema = typeof GlobalUniformSchema export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> } +export const InternalSchema = { + uObjectId: UniformSpec('i'), +} + export const BaseSchema = { aInstanceId: AttributeSpec('float32', 1, 1), aPosition: AttributeSpec('float32', 3, 0), @@ -138,13 +142,14 @@ export const BaseSchema = { aColor: AttributeSpec('float32', 3, 0), uAlpha: UniformSpec('f'), - uObjectId: UniformSpec('i'), uInstanceCount: UniformSpec('i'), uElementCount: UniformSpec('i'), - uColorTexSize: UniformSpec('v2'), uColor: UniformSpec('v3'), + uColorTexSize: UniformSpec('v2'), + uFlagTexSize: UniformSpec('v2'), tColor: TextureSpec('rgb', 'ubyte'), + tFlag: TextureSpec('alpha', 'ubyte'), drawCount: ValueSpec('number'), instanceCount: ValueSpec('number'), diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index ea78f87d7cdbae63160291e0a093f07dce1d8b51..3c6048dd84a8a12f00fb05659bd7362dc061aa03 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -18,13 +18,11 @@ export interface TextureImage { height: number } -export function createColorTexture (n: number): TextureImage { - const { length, width, height } = calculateTextureInfo(n, 3) +export function createTextureImage (n: number, itemSize: number): TextureImage { + const { length, width, height } = calculateTextureInfo(n, itemSize) return { array: new Uint8Array(length), width, height } } -export const emptyTexture = { array: new Uint8Array(0), width: 0, height: 0 } - export function fillSerial<T extends Helpers.NumberArray> (array: T) { const n = array.length for (let i = 0; i < n; ++i) array[ i ] = i diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index be6d05324fbf4fd0f1f984cba24c05e52eb89d18..ec25ac32826ca0c2fb8623ced38b3c18f1b67a91 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -15,39 +15,30 @@ import { Renderable } from './renderable'; import { Color } from 'mol-util/color'; import { ValueCell } from 'mol-util'; import { RenderableValues, GlobalUniformValues } from './renderable/schema'; -import { RenderObject } from './render-object'; -import { BehaviorSubject } from 'rxjs'; +import { RenderVariant } from './webgl/render-item'; export interface RendererStats { - renderableCount: number programCount: number shaderCount: number + bufferCount: number + framebufferCount: number + renderbufferCount: number textureCount: number vaoCount: number } interface Renderer { - add: (o: RenderObject) => void - remove: (o: RenderObject) => void - update: () => void - clear: () => void - draw: () => void + render: (scene: Scene, variant: RenderVariant) => void setViewport: (viewport: Viewport) => void setClearColor: (color: Color) => void getImageData: () => ImageData - didDraw: BehaviorSubject<number> - stats: RendererStats dispose: () => void } -function getPixelRatio() { - return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 -} - export const DefaultRendererProps = { clearColor: 0x000000 as Color, viewport: Viewport.create(0, 0, 0, 0) @@ -58,14 +49,9 @@ namespace Renderer { export function create(ctx: Context, camera: Camera, props: RendererProps = {}): Renderer { const { gl } = ctx let { clearColor, viewport: _viewport } = { ...DefaultRendererProps, ...props } - const scene = Scene.create(ctx) - - const startTime = performance.now() - const didDraw = new BehaviorSubject(0) const model = Mat4.identity() const viewport = Viewport.clone(_viewport) - const pixelRatio = getPixelRatio() // const lightPosition = Vec3.create(0, 0, -100) const lightColor = Vec3.create(1.0, 1.0, 1.0) @@ -82,7 +68,7 @@ namespace Renderer { uView: ValueCell.create(Mat4.clone(camera.view)), uProjection: ValueCell.create(Mat4.clone(camera.projection)), - uPixelRatio: ValueCell.create(pixelRatio), + uPixelRatio: ValueCell.create(ctx.pixelRatio), uViewportHeight: ValueCell.create(viewport.height), uLightColor: ValueCell.create(Vec3.clone(lightColor)), @@ -90,12 +76,13 @@ namespace Renderer { } let currentProgramId = -1 - const drawObject = (r: Renderable<RenderableValues>) => { + const renderObject = (r: Renderable<RenderableValues>, variant: RenderVariant) => { + const program = r.getProgram(variant) if (r.state.visible) { - if (currentProgramId !== r.program.id) { - r.program.use() - r.program.setUniforms(globalUniforms) - currentProgramId = r.program.id + if (currentProgramId !== program.id) { + program.use() + program.setUniforms(globalUniforms) + currentProgramId = program.id } if (r.values.dDoubleSided.ref.value) { gl.disable(gl.CULL_FACE) @@ -113,11 +100,11 @@ namespace Renderer { gl.depthMask(r.state.depthMask) - r.draw() + r.render(variant) } } - const draw = () => { + const render = (scene: Scene, variant: RenderVariant) => { ValueCell.update(globalUniforms.uView, camera.view) ValueCell.update(globalUniforms.uProjection, camera.projection) @@ -128,29 +115,17 @@ namespace Renderer { gl.disable(gl.BLEND) gl.enable(gl.DEPTH_TEST) - scene.eachOpaque(drawObject) + scene.eachOpaque((r) => renderObject(r, variant)) gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.enable(gl.BLEND) - scene.eachTransparent(drawObject) + scene.eachTransparent((r) => renderObject(r, variant)) - didDraw.next(performance.now() - startTime) + gl.finish() } return { - add: (o: RenderObject) => { - scene.add(o) - }, - remove: (o: RenderObject) => { - scene.remove(o) - }, - update: () => { - scene.forEach((r, o) => r.update()) - }, - clear: () => { - scene.clear() - }, - draw, + render, setClearColor, setViewport: (newViewport: Viewport) => { @@ -166,20 +141,21 @@ namespace Renderer { return createImageData(buffer, width, height) }, - didDraw, - get stats(): RendererStats { + console.log(ctx) return { - renderableCount: scene.count, programCount: ctx.programCache.count, shaderCount: ctx.shaderCache.count, + bufferCount: ctx.bufferCount, + framebufferCount: ctx.framebufferCount, + renderbufferCount: ctx.renderbufferCount, textureCount: ctx.textureCount, vaoCount: ctx.vaoCount, } }, dispose: () => { - scene.clear() + // TODO } } } diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 6a31db922f0f8d59ded6e37b8c9c07813828e111..52f09894498d79d79f49858495b71b122bf8c69c 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -13,6 +13,7 @@ import { RenderObject, createRenderable } from './render-object'; interface Scene { add: (o: RenderObject) => void remove: (o: RenderObject) => void + update: () => void clear: () => void forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void eachOpaque: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void @@ -39,6 +40,9 @@ namespace Scene { renderableMap.delete(o) } }, + update: () => { + renderableMap.forEach((r, o) => r.update()) + }, clear: () => { renderableMap.forEach(renderable => renderable.dispose()) renderableMap.clear() diff --git a/src/mol-gl/shader/chunks/color-assign-material.glsl b/src/mol-gl/shader/chunks/color-assign-material.glsl index a482291f9d465cbdb25852bbea9b70b9b306d367..fa0ecfad09dbbcc4ce57d156b4e488381407b690 100644 --- a/src/mol-gl/shader/chunks/color-assign-material.glsl +++ b/src/mol-gl/shader/chunks/color-assign-material.glsl @@ -1,5 +1,5 @@ #if defined(dColorType_uniform) - vec3 material = uColor; -#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) - vec3 material = vColor; + vec4 material = vec4(uColor, 1.0); +#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) || defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking) + vec4 material = vColor; #endif \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-assign-varying.glsl b/src/mol-gl/shader/chunks/color-assign-varying.glsl index b6837d751f86afbac241643909347849ac120432..b5848197a89b908cb5664869d1206f90eb222042 100644 --- a/src/mol-gl/shader/chunks/color-assign-varying.glsl +++ b/src/mol-gl/shader/chunks/color-assign-varying.glsl @@ -1,9 +1,15 @@ #if defined(dColorType_attribute) - vColor = aColor; + vColor.rgb = aColor; #elif defined(dColorType_instance) - vColor = read_vec3(tColor, aInstanceId, uColorTexSize); + vColor.rgb = readFromTexture(tColor, aInstanceId, uColorTexSize).rgb; #elif defined(dColorType_element) - vColor = read_vec3(tColor, aElementId, uColorTexSize); + vColor.rgb = readFromTexture(tColor, aElementId, uColorTexSize).rgb; #elif defined(dColorType_elementInstance) - vColor = read_vec3(tColor, aInstanceId * float(uElementCount) + aElementId, uColorTexSize); + vColor.rgb = readFromTexture(tColor, aInstanceId * float(uElementCount) + aElementId, uColorTexSize).rgb; +#elif defined(dColorType_objectPicking) + vColor = encodeIdRGBA(float(uObjectId)); +#elif defined(dColorType_instancePicking) + vColor = encodeIdRGBA(aInstanceId); +#elif defined(dColorType_elementPicking) + vColor = encodeIdRGBA(aElementId); #endif \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-frag-params.glsl b/src/mol-gl/shader/chunks/color-frag-params.glsl index c9787920d9233cd47f9f8b900052844ca813e7af..55731de96863282a6d664f51276b3e19d335dc87 100644 --- a/src/mol-gl/shader/chunks/color-frag-params.glsl +++ b/src/mol-gl/shader/chunks/color-frag-params.glsl @@ -1,5 +1,5 @@ #if defined(dColorType_uniform) uniform vec3 uColor; -#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) - varying vec3 vColor; +#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) || defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking) + varying vec4 vColor; #endif \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl b/src/mol-gl/shader/chunks/color-vert-params.glsl index 8ffe2660087b6d39ba64cb68a58990069de9fce2..8f49becee4630b39320a3660dcae7adc8a6aafcd 100644 --- a/src/mol-gl/shader/chunks/color-vert-params.glsl +++ b/src/mol-gl/shader/chunks/color-vert-params.glsl @@ -1,12 +1,13 @@ #if defined(dColorType_uniform) uniform vec3 uColor; #elif defined(dColorType_attribute) - varying vec3 vColor; + varying vec4 vColor; attribute vec3 aColor; #elif defined(dColorType_instance) || defined(dColorType_element) || defined(dColorType_elementInstance) - varying vec3 vColor; + varying vec4 vColor; uniform vec2 uColorTexSize; uniform sampler2D tColor; -#endif - -#pragma glslify: read_vec3 = require(../utils/read-from-texture.glsl) \ No newline at end of file +#elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking) + varying vec4 vColor; + #pragma glslify: encodeIdRGBA = require(../utils/encode-id-rgba.glsl) +#endif \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl b/src/mol-gl/shader/chunks/common-frag-params.glsl new file mode 100644 index 0000000000000000000000000000000000000000..744b3a1a247019dc92cc745374f38c138ba387d2 --- /dev/null +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl @@ -0,0 +1,5 @@ +uniform int uObjectId; +uniform int uInstanceCount; +uniform int uElementCount; + +varying float vFlag; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl b/src/mol-gl/shader/chunks/common-vert-params.glsl new file mode 100644 index 0000000000000000000000000000000000000000..005d347d55fe9d79fa16f53c46681b4f44d50ef9 --- /dev/null +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl @@ -0,0 +1,10 @@ +uniform mat4 uProjection, uModel, uView; + +uniform int uObjectId; +uniform int uInstanceCount; +uniform int uElementCount; + +uniform vec2 uFlagTexSize; +uniform sampler2D tFlag; +varying float vFlag; +#pragma glslify: readFromTexture = require(../utils/read-from-texture.glsl) \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 086715e312ade63e01c4dde27d2c4691cc181334..401a0e46bc5aa9f1d6db723aeebe117e5846b38a 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -9,6 +9,10 @@ #endif precision highp float; +precision highp int; + +#pragma glslify: import('./chunks/common-frag-params.glsl') +#pragma glslify: import('./chunks/color-frag-params.glsl') // uniform vec3 uLightPosition; uniform vec3 uLightColor; @@ -21,8 +25,6 @@ uniform float uAlpha; #endif varying vec3 vViewPosition; -#pragma glslify: import('./chunks/color-frag-params.glsl') - #pragma glslify: attenuation = require(./utils/attenuation.glsl) #pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl) #pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl) @@ -36,37 +38,46 @@ void main() { // material color #pragma glslify: import('./chunks/color-assign-material.glsl') - // determine surface to light direction - // vec4 viewLightPosition = view * vec4(lightPosition, 1.0); - // vec3 lightVector = viewLightPosition.xyz - vViewPosition; - vec3 lightVector = vViewPosition; + #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_elementPicking) + // gl_FragColor = vec4(material.r, material.g, material.a, 1.0); + gl_FragColor = material; + #else + // determine surface to light direction + // vec4 viewLightPosition = view * vec4(lightPosition, 1.0); + // vec3 lightVector = viewLightPosition.xyz - vViewPosition; + vec3 lightVector = vViewPosition; - vec3 L = normalize(lightVector); // light direction - vec3 V = normalize(vViewPosition); // eye direction + vec3 L = normalize(lightVector); // light direction + vec3 V = normalize(vViewPosition); // eye direction - // surface normal - #ifdef dFlatShaded - vec3 fdx = dFdx(vViewPosition); - vec3 fdy = dFdy(vViewPosition); - vec3 N = -normalize(cross(fdx, fdy)); - #else - vec3 N = -normalize(vNormal); - #ifdef dDoubleSided - N = N * (float(gl_FrontFacing) * 2.0 - 1.0); + // surface normal + #ifdef dFlatShaded + vec3 fdx = dFdx(vViewPosition); + vec3 fdy = dFdy(vViewPosition); + vec3 N = -normalize(cross(fdx, fdy)); + #else + vec3 N = -normalize(vNormal); + #ifdef dDoubleSided + N = N * (float(gl_FrontFacing) * 2.0 - 1.0); + #endif #endif - #endif - // compute our diffuse & specular terms - float specular = calculateSpecular(L, V, N, shininess) * specularScale; - vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo); - vec3 ambient = uLightAmbient; + // compute our diffuse & specular terms + float specular = calculateSpecular(L, V, N, shininess) * specularScale; + vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo); + vec3 ambient = uLightAmbient; - // add the lighting - vec3 finalColor = material * (diffuse + ambient) + specular; + // add the lighting + vec3 finalColor = material.rgb * (diffuse + ambient) + specular; - // gl_FragColor.rgb = N; - // gl_FragColor.a = 1.0; - // gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); - gl_FragColor.rgb = finalColor; - gl_FragColor.a = uAlpha; + // gl_FragColor.rgb = N; + // gl_FragColor.a = 1.0; + // gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); + gl_FragColor.rgb = finalColor; + gl_FragColor.a = uAlpha; + + if (vFlag == 1.0) { + gl_FragColor.rgb = mix(vec3(1.0, 0.4, 0.6), gl_FragColor.rgb, 0.3); + } + #endif } \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index 405ac53a4c2de489c8f610351078584f4edce788..9b9ec9ea3ed9efe91980b50ef2fe8d3db6feb13d 100644 --- a/src/mol-gl/shader/mesh.vert +++ b/src/mol-gl/shader/mesh.vert @@ -5,13 +5,9 @@ */ precision highp float; +precision highp int; -uniform mat4 uProjection, uModel, uView; - -uniform int uObjectId; -uniform int uInstanceCount; -uniform int uElementCount; - +#pragma glslify: import('./chunks/common-vert-params.glsl') #pragma glslify: import('./chunks/color-vert-params.glsl') attribute vec3 aPosition; @@ -31,6 +27,7 @@ varying vec3 vViewPosition; void main(){ #pragma glslify: import('./chunks/color-assign-varying.glsl') + vFlag = readFromTexture(tFlag, aInstanceId * float(uElementCount) + aElementId, uFlagTexSize).a; mat4 modelView = uView * uModel * aTransform; vec4 mvPosition = modelView * vec4(aPosition, 1.0); diff --git a/src/mol-gl/shader/point.frag b/src/mol-gl/shader/point.frag index f25b04c444bab6bb16cb5d6d95b85d4270bd2367..9e7e343b1cf80d35cbbb82d69ce9d481d8985cc4 100644 --- a/src/mol-gl/shader/point.frag +++ b/src/mol-gl/shader/point.frag @@ -5,11 +5,13 @@ */ precision highp float; +precision highp int; -uniform float uAlpha; - +#pragma glslify: import('./chunks/common-frag-params.glsl') #pragma glslify: import('./chunks/color-frag-params.glsl') +uniform float uAlpha; + void main(){ #pragma glslify: import('./chunks/color-assign-material.glsl') gl_FragColor = vec4(material, uAlpha); diff --git a/src/mol-gl/shader/point.vert b/src/mol-gl/shader/point.vert index 22d07a2341fc070c6e3678edc0c3f348817cb098..44c08c8cf9bc6d8e9da55d04e573c5da52430245 100644 --- a/src/mol-gl/shader/point.vert +++ b/src/mol-gl/shader/point.vert @@ -5,18 +5,14 @@ */ precision highp float; +precision highp int; -uniform mat4 uProjection, uModel, uView; - -uniform int uObjectId; -uniform int uInstanceCount; -uniform int uElementCount; +#pragma glslify: import('./chunks/common-vert-params.glsl') +#pragma glslify: import('./chunks/color-vert-params.glsl') uniform float uPixelRatio; uniform float uViewportHeight; -#pragma glslify: import('./chunks/color-vert-params.glsl') - #if defined(dSizeType_uniform) uniform float uSize; #elif defined(dSizeType_attribute) diff --git a/src/mol-gl/shader/utils/attenuation.glsl b/src/mol-gl/shader/utils/attenuation.glsl index fdc3f4c78c8c86cbc68b8e0b2d9ef109695dd129..833423b85b9fcfc3dfd334b1714bfccfec9d78ee 100644 --- a/src/mol-gl/shader/utils/attenuation.glsl +++ b/src/mol-gl/shader/utils/attenuation.glsl @@ -4,7 +4,7 @@ // // Improved // https://imdoingitwrong.wordpress.com/2011/02/10/improved-light-attenuation/ -float attenuation(float r, float f, float d) { +float attenuation(const in float r, const in float f, const in float d) { float denom = d / r + 1.0; float attenuation = 1.0 / (denom*denom); float t = (attenuation - f) / (1.0 - f); diff --git a/src/mol-gl/shader/utils/decode-float-rgba.glsl b/src/mol-gl/shader/utils/decode-float-rgba.glsl index b55b14bf5fc1cfdaccdd23d6127443cedd981332..c4b57eb4a2ad30c02bb5d511946cb15d605c2794 100644 --- a/src/mol-gl/shader/utils/decode-float-rgba.glsl +++ b/src/mol-gl/shader/utils/decode-float-rgba.glsl @@ -1,5 +1,5 @@ -float decodeFloatRGBA(vec4 rgba) { - return dot(rgba, vec4(1.0, 1/255.0, 1/65025.0, 1/16581375.0)); +float decodeFloatRGBA(const in vec4 rgba) { + return dot(rgba, vec4(1.0, 1/255.0, 1/65025.0, 1/16581375.0)); } #pragma glslify: export(decodeFloatRGBA) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/encode-float-rgba.glsl b/src/mol-gl/shader/utils/encode-float-rgba.glsl index 6ad94bfb178d7bbd2debc3a106270d44a78366c1..fe67ca0d81e099c2bc1e6119238d364e724cc161 100644 --- a/src/mol-gl/shader/utils/encode-float-rgba.glsl +++ b/src/mol-gl/shader/utils/encode-float-rgba.glsl @@ -1,8 +1,12 @@ -vec4 encodeFloatRGBA(float v) { - vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * v; - enc = frac(enc); - enc -= enc.yzww * float4(1.0/255.0,1.0/255.0,1.0/255.0,0.0); - return enc; +vec4 encodeFloatRGBA(in float value) { + value = clamp(value, 0., 16777216.); + vec3 c = vec3(0.); + c.b = mod(value, 256.); + value = floor(value/256.); + c.g = mod(value, 256.); + value = floor(value/256.); + c.r = mod(value, 256.); + return vec4(c/255., 1.); } #pragma glslify: export(encodeFloatRGBA) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/encode-id-rgba.glsl b/src/mol-gl/shader/utils/encode-id-rgba.glsl new file mode 100644 index 0000000000000000000000000000000000000000..9a47039656c6e0e0e8e0d08b8f411d3f6f3b89eb --- /dev/null +++ b/src/mol-gl/shader/utils/encode-id-rgba.glsl @@ -0,0 +1,7 @@ +#pragma glslify: encodeFloatRGBA = require(../utils/encode-float-rgba.glsl) + +vec4 encodeIdRGBA(const in float v) { + return encodeFloatRGBA(v + 1.0); +} + +#pragma glslify: export(encodeIdRGBA) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/inverse.glsl b/src/mol-gl/shader/utils/inverse.glsl index a8bb99b4b68c4932f76dbc79f3eb31502c581f70..e2d05c08aba26f4f4728548b47552e948f428b52 100644 --- a/src/mol-gl/shader/utils/inverse.glsl +++ b/src/mol-gl/shader/utils/inverse.glsl @@ -1,16 +1,16 @@ // (c) 2014 Mikola Lysenko. MIT License // https://github.com/glslify/glsl-inverse -float inverse(float m) { +float inverse(const in float m) { return 1.0 / m; } -mat2 inverse(mat2 m) { +mat2 inverse(const in mat2 m) { return mat2(m[1][1],-m[0][1], -m[1][0], m[0][0]) / (m[0][0]*m[1][1] - m[0][1]*m[1][0]); } -mat3 inverse(mat3 m) { +mat3 inverse(const in mat3 m) { float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; @@ -26,7 +26,7 @@ mat3 inverse(mat3 m) { b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; } -mat4 inverse(mat4 m) { +mat4 inverse(const in mat4 m) { float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], diff --git a/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl b/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl index 8124048963d1fa53c7827cc780d650be3cb76d29..59a3078b831a51003f990693a0da9ff0ed06a743 100644 --- a/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl +++ b/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl @@ -3,7 +3,7 @@ #define PI 3.14159265 -float orenNayarDiffuse(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float roughness, float albedo) { +float orenNayarDiffuse(const in vec3 lightDirection, const in vec3 viewDirection, const in vec3 surfaceNormal, const in float roughness, const in float albedo) { float LdotV = dot(lightDirection, viewDirection); float NdotL = dot(lightDirection, surfaceNormal); float NdotV = dot(surfaceNormal, viewDirection); diff --git a/src/mol-gl/shader/utils/phong-specular.glsl b/src/mol-gl/shader/utils/phong-specular.glsl index a4cd935072d0ad0f1ae2fad2a6ea490e628f4e83..6d42305acf8e23d6204d7b0a13f91a69d15f9cf3 100644 --- a/src/mol-gl/shader/utils/phong-specular.glsl +++ b/src/mol-gl/shader/utils/phong-specular.glsl @@ -1,7 +1,7 @@ // (c) 2014 Mikola Lysenko. MIT License // https://github.com/glslify/glsl-specular-phong -float phongSpecular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float shininess) { +float phongSpecular(const in vec3 lightDirection, const in vec3 viewDirection, const in vec3 surfaceNormal, const in float shininess) { //Calculate Phong power vec3 R = -reflect(lightDirection, surfaceNormal); return pow(max(0.0, dot(viewDirection, R)), shininess); diff --git a/src/mol-gl/shader/utils/read-from-texture.glsl b/src/mol-gl/shader/utils/read-from-texture.glsl index 7a4b1310529b29d9ffbd6994bd403877f6728748..10b4e9d60c09519c17cd0b5a3fe60e27a8bb7fe6 100644 --- a/src/mol-gl/shader/utils/read-from-texture.glsl +++ b/src/mol-gl/shader/utils/read-from-texture.glsl @@ -4,10 +4,10 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -vec3 read_vec3 (sampler2D tex, float i, vec2 size) { +vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 size) { float x = mod(i, size.x); float y = floor(i / size.x); vec2 uv = (vec2(x, y) + 0.5) / size; - return texture2D(tex, uv).rgb; + return texture2D(tex, uv); } -#pragma glslify: export(read_vec3) \ No newline at end of file +#pragma glslify: export(readFromTexture) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/transpose.glsl b/src/mol-gl/shader/utils/transpose.glsl index e14ea7e32879b7d8ebc065c961f0f7804c5746f3..5a2a3b7147791d8752f04e92288a8047f0323898 100644 --- a/src/mol-gl/shader/utils/transpose.glsl +++ b/src/mol-gl/shader/utils/transpose.glsl @@ -1,22 +1,22 @@ // (c) 2014 Mikola Lysenko. MIT License // https://github.com/glslify/glsl-transpose -float transpose(float m) { +float transpose(const in float m) { return m; } -mat2 transpose(mat2 m) { +mat2 transpose(const in mat2 m) { return mat2(m[0][0], m[1][0], m[0][1], m[1][1]); } -mat3 transpose(mat3 m) { +mat3 transpose(const in mat3 m) { return mat3(m[0][0], m[1][0], m[2][0], m[0][1], m[1][1], m[2][1], m[0][2], m[1][2], m[2][2]); } -mat4 transpose(mat4 m) { +mat4 transpose(const in mat4 m) { return mat4(m[0][0], m[1][0], m[2][0], m[3][0], m[0][1], m[1][1], m[2][1], m[3][1], m[0][2], m[1][2], m[2][2], m[3][2], diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index f21c74c5d8bc70afb4199cd2613bf1ed1751a03f..5c5e9635ab87a99dfece1cc726e4302ba24ba7a5 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -7,6 +7,10 @@ import { createProgramCache, ProgramCache } from './program' import { createShaderCache, ShaderCache } from './shader' +function getPixelRatio() { + return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 +} + function unbindResources (gl: WebGLRenderingContext) { // bind null to all texture units const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) @@ -61,6 +65,7 @@ type Extensions = { export interface Context { gl: WebGLRenderingContext extensions: Extensions + pixelRatio: number shaderCache: ShaderCache programCache: ProgramCache @@ -71,8 +76,8 @@ export interface Context { textureCount: number vaoCount: number - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void unbindFramebuffer: () => void + readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void destroy: () => void } @@ -100,6 +105,7 @@ export function createContext(gl: WebGLRenderingContext): Context { return { gl, extensions: { angleInstancedArrays, standardDerivatives, oesElementIndexUint, oesVertexArrayObject }, + pixelRatio: getPixelRatio(), shaderCache, programCache, @@ -110,14 +116,17 @@ export function createContext(gl: WebGLRenderingContext): Context { textureCount: 0, vaoCount: 0, + unbindFramebuffer: () => unbindFramebuffer(gl), readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { - if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) { - gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) - } else { - console.error('Reading pixels failed. Framebuffer not complete.') - } + gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) + // TODO check is very expensive + // if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) { + // gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) + // } else { + // console.error('Reading pixels failed. Framebuffer not complete.') + // } }, - unbindFramebuffer: () => unbindFramebuffer(gl), + destroy: () => { unbindResources(gl) programCache.dispose() diff --git a/src/mol-gl/webgl/framebuffer.ts b/src/mol-gl/webgl/framebuffer.ts index e30d6d486c47d0df02aca4759551d964fa1e0f89..f359c1ed64d15b238e3da589ad2ca6131c8cbe0e 100644 --- a/src/mol-gl/webgl/framebuffer.ts +++ b/src/mol-gl/webgl/framebuffer.ts @@ -29,10 +29,7 @@ export function createFramebuffer (ctx: Context): Framebuffer { return { id: getNextFramebufferId(), - bind: () => { - gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer) - }, - + bind: () => gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer), destroy: () => { if (destroyed) return gl.deleteFramebuffer(_framebuffer) diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index 08cdecd3576516da8adea9c065a50e8165d2fe67..d64d1ca58868a7552bf222f8cd93e9158ff3a3c0 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -36,9 +36,9 @@ function getAttributeLocations(ctx: Context, program: WebGLProgram, schema: Rend const spec = schema[k] if (spec.type === 'attribute') { const loc = gl.getAttribLocation(program, k) - if (loc === -1) { - console.info(`Could not get attribute location for '${k}'`) - } + // if (loc === -1) { + // console.info(`Could not get attribute location for '${k}'`) + // } locations[k] = loc } }) diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index e67a29f345343f398c658d850f7b8feccc4e1b80..8202e4d33c082f97ba914364615078f04eb3d810 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -9,9 +9,11 @@ import { createTextures } from './texture'; import { Context } from './context'; import { ShaderCode, addShaderDefines } from '../shader-code'; import { Program } from './program'; -import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues } from '../renderable/schema'; +import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, Values } from '../renderable/schema'; import { idFactory } from 'mol-util/id-factory'; import { deleteVertexArray, createVertexArray } from './vertex-array'; +import { ValueCell } from 'mol-util'; +import { ReferenceItem } from 'mol-util/reference-cache'; const getNextRenderItemId = idFactory() @@ -32,14 +34,24 @@ export function getDrawMode(ctx: Context, drawMode: DrawMode) { export interface RenderItem { readonly id: number - readonly programId: number - readonly program: Program + getProgram: (variant: RenderVariant) => Program + render: (variant: RenderVariant) => void update: () => void - draw: () => void destroy: () => void } +const RenderVariantDefines = { + 'draw': {}, + 'pickObject': { dColorType: ValueCell.create('objectPicking') }, + 'pickInstance': { dColorType: ValueCell.create('instancePicking') }, + 'pickElement': { dColorType: ValueCell.create('elementPicking') } +} +export type RenderVariant = keyof typeof RenderVariantDefines + +type ProgramVariants = { [k: string]: ReferenceItem<Program> } +type VertexArrayVariants = { [k: string]: WebGLVertexArrayObjectOES | undefined } + export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues): RenderItem { const id = getNextRenderItemId() const { programCache } = ctx @@ -49,9 +61,14 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S const versions = getValueVersions(values) const glDrawMode = getDrawMode(ctx, drawMode) - let drawProgram = programCache.get(ctx, { - shaderCode: addShaderDefines(defineValues, shaderCode), - schema + + const programs: ProgramVariants = {} + Object.keys(RenderVariantDefines).forEach(k => { + const variantDefineValues: Values<RenderableSchema> = (RenderVariantDefines as any)[k] + programs[k] = programCache.get(ctx, { + shaderCode: addShaderDefines({ ...defineValues, ...variantDefineValues }, shaderCode), + schema + }) }) const textures = createTextures(ctx, schema, textureValues) @@ -63,64 +80,67 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S elementsBuffer = createElementsBuffer(ctx, elements.ref.value) } - let vertexArray: WebGLVertexArrayObjectOES | undefined = createVertexArray(ctx, drawProgram.value, attributeBuffers, elementsBuffer) + const vertexArrays: VertexArrayVariants = {} + Object.keys(RenderVariantDefines).forEach(k => { + vertexArrays[k] = createVertexArray(ctx, programs[k].value, attributeBuffers, elementsBuffer) + }) let drawCount = values.drawCount.ref.value let instanceCount = values.instanceCount.ref.value let destroyed = false - function render(program: Program) { - program.setUniforms(uniformValues) - if (oesVertexArrayObject && vertexArray) { - oesVertexArrayObject.bindVertexArrayOES(vertexArray) - } else { - program.bindAttributes(attributeBuffers) - if (elementsBuffer) elementsBuffer.bind() - } - program.bindTextures(textures) - if (elementsBuffer) { - angleInstancedArrays.drawElementsInstancedANGLE(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount); - } else { - angleInstancedArrays.drawArraysInstancedANGLE(glDrawMode, 0, drawCount, instanceCount) - } - } - return { id, - get programId () { return drawProgram.value.id }, - get program () { return drawProgram.value }, - - draw: () => { - render(drawProgram.value) + getProgram: (variant: RenderVariant) => programs[variant].value, + + render: (variant: RenderVariant) => { + const program = programs[variant].value + const vertexArray = vertexArrays[variant] + program.setUniforms(uniformValues) + if (oesVertexArrayObject && vertexArray) { + oesVertexArrayObject.bindVertexArrayOES(vertexArray) + } else { + program.bindAttributes(attributeBuffers) + if (elementsBuffer) elementsBuffer.bind() + } + program.bindTextures(textures) + if (elementsBuffer) { + angleInstancedArrays.drawElementsInstancedANGLE(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount); + } else { + angleInstancedArrays.drawArraysInstancedANGLE(glDrawMode, 0, drawCount, instanceCount) + } }, update: () => { let defineChange = false Object.keys(defineValues).forEach(k => { const value = defineValues[k] if (value.ref.version !== versions[k]) { - console.log('define version changed', k) + // console.log('define version changed', k) defineChange = true versions[k] = value.ref.version } }) if (defineChange) { - console.log('some defines changed, need to rebuild program') - drawProgram.free() - drawProgram = programCache.get(ctx, { - shaderCode: addShaderDefines(defineValues, shaderCode), - schema + // console.log('some defines changed, need to rebuild programs') + Object.keys(RenderVariantDefines).forEach(k => { + const variantDefineValues: Values<RenderableSchema> = (RenderVariantDefines as any)[k] + programs[k].free() + programs[k] = programCache.get(ctx, { + shaderCode: addShaderDefines({ ...defineValues, ...variantDefineValues }, shaderCode), + schema + }) }) } if (values.drawCount.ref.version !== versions.drawCount) { - console.log('drawCount version changed') + // console.log('drawCount version changed') drawCount = values.drawCount.ref.value versions.drawCount = values.drawCount.ref.version } if (values.instanceCount.ref.version !== versions.instanceCount) { - console.log('instanceCount version changed') + // console.log('instanceCount version changed') instanceCount = values.instanceCount.ref.value versions.instanceCount = values.instanceCount.ref.version } @@ -132,10 +152,10 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S if (value.ref.version !== versions[k]) { const buffer = attributeBuffers[k] if (buffer.length >= value.ref.value.length) { - console.log('attribute array large enough to update', k) + // console.log('attribute array large enough to update', k) attributeBuffers[k].updateData(value.ref.value) } else { - console.log('attribute array to small, need to create new attribute', k) + // console.log('attribute array to small, need to create new attribute', k) attributeBuffers[k].destroy() const spec = schema[k] as AttributeSpec<ArrayKind> attributeBuffers[k] = createAttributeBuffer(ctx, value.ref.value, spec.itemSize, spec.divisor) @@ -147,10 +167,10 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S if (elementsBuffer && values.elements.ref.version !== versions.elements) { if (elementsBuffer.length >= values.elements.ref.value.length) { - console.log('elements array large enough to update') + // console.log('elements array large enough to update') elementsBuffer.updateData(values.elements.ref.value) } else { - console.log('elements array to small, need to create new elements') + // console.log('elements array to small, need to create new elements') elementsBuffer.destroy() elementsBuffer = createElementsBuffer(ctx, values.elements.ref.value) bufferChange = true @@ -159,15 +179,17 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S } if (defineChange || bufferChange) { - console.log('program/defines or buffers changed, rebuild vao') - deleteVertexArray(ctx, vertexArray) - vertexArray = createVertexArray(ctx, drawProgram.value, attributeBuffers, elementsBuffer) + // console.log('program/defines or buffers changed, rebuild vaos') + Object.keys(RenderVariantDefines).forEach(k => { + deleteVertexArray(ctx, vertexArrays[k]) + vertexArrays[k] = createVertexArray(ctx, programs[k].value, attributeBuffers, elementsBuffer) + }) } Object.keys(textureValues).forEach(k => { const value = textureValues[k] if (value.ref.version !== versions[k]) { - console.log('texture version changed, uploading image', k) + // console.log('texture version changed, uploading image', k) textures[k].load(value.ref.value) versions[k] = value.ref.version } @@ -175,11 +197,13 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S }, destroy: () => { if (!destroyed) { - drawProgram.free() + Object.keys(RenderVariantDefines).forEach(k => { + programs[k].free() + deleteVertexArray(ctx, vertexArrays[k]) + }) Object.keys(textures).forEach(k => textures[k].destroy()) Object.keys(attributeBuffers).forEach(k => attributeBuffers[k].destroy()) if (elementsBuffer) elementsBuffer.destroy() - deleteVertexArray(ctx, vertexArray) destroyed = true } } diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts index 69afd31d7042f1ec0a47062da2af09b5e5bc8eda..e91ed62c8e0733dd24c55ba7d6834c1bfb45920b 100644 --- a/src/mol-gl/webgl/render-target.ts +++ b/src/mol-gl/webgl/render-target.ts @@ -4,11 +4,11 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Context } from './context' +import { Context, createImageData } from './context' import { idFactory } from 'mol-util/id-factory'; import { createTexture } from './texture'; import { createFramebuffer } from './framebuffer'; -// import { createRenderbuffer } from './renderbuffer'; +import { createRenderbuffer } from './renderbuffer'; const getNextRenderTargetId = idFactory() @@ -17,27 +17,29 @@ export interface RenderTarget { bind: () => void setSize: (width: number, height: number) => void - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void + getImageData: () => ImageData destroy: () => void } export function createRenderTarget (ctx: Context, _width: number, _height: number): RenderTarget { const { gl } = ctx + const image = { + array: new Uint8Array(_width * _height * 4), + width: _width, + height: _height + } + const targetTexture = createTexture(ctx, 'rgba', 'ubyte') - targetTexture.setSize(_width, _height) + targetTexture.load(image) const framebuffer = createFramebuffer(ctx) - framebuffer.bind() // attach the texture as the first color attachment - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture.texture, 0); + targetTexture.attachFramebuffer(framebuffer, 'color0') - // const depthRenderbuffer = createRenderbuffer(ctx) - // depthRenderbuffer.bind() - // // make a depth buffer and the same size as the targetTexture - // gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTexture.width, targetTexture.height); - // gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderbuffer); + // make a depth renderbuffer of the same size as the targetTexture + const depthRenderbuffer = createRenderbuffer(ctx, 'depth16', 'depth', _width, _height) let destroyed = false @@ -51,18 +53,23 @@ export function createRenderTarget (ctx: Context, _width: number, _height: numbe setSize: (width: number, height: number) => { _width = width _height = height - targetTexture.setSize(_width, _height) + image.array = new Uint8Array(_width * _height * 4) + image.width = _width + image.height = _height + targetTexture.load(image) + + depthRenderbuffer.setSize(_width, _height) }, - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { + getImageData: () => { framebuffer.bind() - ctx.readPixels(x, y, width, height, buffer) - ctx.unbindFramebuffer() + ctx.readPixels(0, 0, _width, _height, image.array) + return createImageData(image.array, _width, _height) }, destroy: () => { if (destroyed) return targetTexture.destroy() framebuffer.destroy() - // depthRenderbuffer.destroy() + depthRenderbuffer.destroy() destroyed = true } } diff --git a/src/mol-gl/webgl/renderbuffer.ts b/src/mol-gl/webgl/renderbuffer.ts index a547578bd8ea565a85ba2319845edb7859f8043e..c123a3a391d5f64569787ede78633b47197a2316 100644 --- a/src/mol-gl/webgl/renderbuffer.ts +++ b/src/mol-gl/webgl/renderbuffer.ts @@ -9,28 +9,63 @@ import { idFactory } from 'mol-util/id-factory'; const getNextRenderbufferId = idFactory() +export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil' +export type RenderbufferAttachment = 'depth' | 'stencil' | 'depth-stencil' | 'color0' + +export function getFormat(ctx: Context, format: RenderbufferFormat) { + const { gl } = ctx + switch (format) { + case 'depth16': return gl.DEPTH_COMPONENT16 + case 'stencil8': return gl.STENCIL_INDEX8 + case 'rgba4': return gl.RGBA4 + case 'depth-stencil': return gl.DEPTH_STENCIL + } +} + +export function getAttachment(ctx: Context, attachment: RenderbufferAttachment) { + const { gl } = ctx + switch (attachment) { + case 'depth': return gl.DEPTH_ATTACHMENT + case 'stencil': return gl.STENCIL_ATTACHMENT + case 'depth-stencil': return gl.DEPTH_STENCIL_ATTACHMENT + case 'color0': return gl.COLOR_ATTACHMENT0 + } +} + export interface Renderbuffer { readonly id: number bind: () => void + setSize: (width: number, height: number) => void + destroy: () => void } -export function createRenderbuffer (ctx: Context): Renderbuffer { +export function createRenderbuffer (ctx: Context, format: RenderbufferFormat, attachment: RenderbufferAttachment, _width: number, _height: number): Renderbuffer { const { gl } = ctx const _renderbuffer = gl.createRenderbuffer() if (_renderbuffer === null) { throw new Error('Could not create WebGL renderbuffer') } + const bind = () => gl.bindRenderbuffer(gl.RENDERBUFFER, _renderbuffer) + const _format = getFormat(ctx, format) + const _attachment = getAttachment(ctx, attachment) + + bind() + gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height) + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, _attachment, gl.RENDERBUFFER, _renderbuffer) + let destroyed = false ctx.renderbufferCount += 1 return { id: getNextRenderbufferId(), - bind: () => { - gl.bindRenderbuffer(gl.RENDERBUFFER, _renderbuffer) + bind, + setSize: (_width: number, _height: number) => { + bind() + gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height) }, destroy: () => { diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 80c6f67de89b70e40c586a9ee231764d2110d73e..a156f2ebd969270b170b23116423fcee39987c89 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -9,15 +9,18 @@ import { TextureImage } from '../renderable/util'; import { ValueCell } from 'mol-util'; import { RenderableSchema } from '../renderable/schema'; import { idFactory } from 'mol-util/id-factory'; +import { Framebuffer } from './framebuffer'; const getNextTextureId = idFactory() -export type TextureFormat = 'rgb' | 'rgba' +export type TextureFormat = 'alpha' | 'rgb' | 'rgba' export type TextureType = 'ubyte' | 'uint' +export type TextureAttachment = 'depth' | 'stencil' | 'color0' export function getFormat(ctx: Context, format: TextureFormat) { const { gl } = ctx switch (format) { + case 'alpha': return gl.ALPHA case 'rgb': return gl.RGB case 'rgba': return gl.RGBA } @@ -31,9 +34,17 @@ export function getType(ctx: Context, type: TextureType) { } } +export function getAttachment(ctx: Context, attachment: TextureAttachment) { + const { gl } = ctx + switch (attachment) { + case 'depth': return gl.DEPTH_ATTACHMENT + case 'stencil': return gl.STENCIL_ATTACHMENT + case 'color0': return gl.COLOR_ATTACHMENT0 + } +} + export interface Texture { readonly id: number - readonly texture: WebGLTexture readonly format: number readonly type: number @@ -43,7 +54,7 @@ export interface Texture { load: (image: TextureImage) => void bind: (id: TextureId) => void unbind: (id: TextureId) => void - setSize: (width: number, height: number) => void + attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void destroy: () => void } @@ -73,7 +84,6 @@ export function createTexture(ctx: Context, _format: TextureFormat, _type: Textu return { id, - texture, format, type, @@ -103,10 +113,9 @@ export function createTexture(ctx: Context, _format: TextureFormat, _type: Textu gl.activeTexture(gl.TEXTURE0 + id) gl.bindTexture(gl.TEXTURE_2D, null) }, - setSize: (width: number, height: number) => { - gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null) - _width = width - _height = height + attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => { + framebuffer.bind() + gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, texture, 0) }, destroy: () => { if (destroyed) return diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index 6000fe319cf9a97e7502145cc77c1b9e73d97513..b3dfeb4c90e90efe04e59ff6574c52fbc68c5a3b 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -34,7 +34,7 @@ function createUniformSetter(ctx: Context, program: WebGLProgram, name: string, const { gl } = ctx const location = gl.getUniformLocation(program, name) if (location === null) { - console.info(`Could not get WebGL uniform location for '${name}'`) + // console.info(`Could not get WebGL uniform location for '${name}'`) } switch (kind) { case 'f': return (value: number) => gl.uniform1f(location, value) diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index fe6cbc7aa04ca0deaa8869cb4afbe7998fc7669e..8f9cac24028275ce2a8882e3859e1bd8c8e63c87 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/src/mol-math/geometry/symmetry-operator.ts @@ -18,13 +18,13 @@ interface SymmetryOperator { } namespace SymmetryOperator { - export const DefaultName = '1_555' + export const DefaultName = 'identity' export const Default: SymmetryOperator = create(DefaultName, Mat4.identity()); const RotationEpsilon = 0.0001; export function create(name: string, matrix: Mat4, hkl?: Vec3): SymmetryOperator { - const _hkl = hkl ? Vec3.copy(Vec3.zero(), hkl) : Vec3.zero(); + const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero(); if (Mat4.isIdentity(matrix)) return { name, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl }; if (!Mat4.isRotationAndTranslation(matrix, RotationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`); return { name, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl }; diff --git a/src/mol-math/linear-algebra/3d/vec2.ts b/src/mol-math/linear-algebra/3d/vec2.ts index e02c793e13b4611942302dc9bbda02cdbd33cf39..a87e6fcef566a42891a9c6a3cea63644e1f50cb6 100644 --- a/src/mol-math/linear-algebra/3d/vec2.ts +++ b/src/mol-math/linear-algebra/3d/vec2.ts @@ -93,6 +93,33 @@ namespace Vec2 { return out; } + /** + * Math.round the components of a Vec2 + */ + export function round(out: Vec2, a: Vec2) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + return out; + } + + /** + * Math.ceil the components of a Vec2 + */ + export function ceil(out: Vec2, a: Vec2) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + return out; + } + + /** + * Math.floor the components of a Vec2 + */ + export function floor(out: Vec2, a: Vec2) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + return out; + } + export function distance(a: Vec2, b: Vec2) { const x = b[0] - a[0], y = b[1] - a[1]; @@ -116,6 +143,15 @@ namespace Vec2 { y = a[1]; return x * x + y * y; } + + /** + * Returns the inverse of the components of a Vec2 + */ + export function inverse(out: Vec2, a: Vec2) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + return out; + } } export default Vec2 \ No newline at end of file diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index bc44e7e6e7f9a41a855f179f6b8fd4b824c17dfc..fc7c22999b548099283ba43bc778f6a551ac8f4f 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -130,6 +130,36 @@ namespace Vec3 { return out; } + /** + * Math.round the components of a Vec3 + */ + export function round(out: Vec3, a: Vec3) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + out[2] = Math.round(a[2]); + return out; + } + + /** + * Math.ceil the components of a Vec3 + */ + export function ceil(out: Vec3, a: Vec3) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + out[2] = Math.ceil(a[2]); + return out; + } + + /** + * Math.floor the components of a Vec3 + */ + export function floor(out: Vec3, a: Vec3) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + out[2] = Math.floor(a[2]); + return out; + } + export function distance(a: Vec3, b: Vec3) { const x = b[0] - a[0], y = b[1] - a[1], @@ -162,6 +192,16 @@ namespace Vec3 { return Vec3.scale(out, Vec3.normalize(out, a), l) } + /** + * Returns the inverse of the components of a Vec3 + */ + export function inverse(out: Vec3, a: Vec3) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + out[2] = 1.0 / a[2]; + return out; + } + export function normalize(out: Vec3, a: Vec3) { const x = a[0], y = a[1], diff --git a/src/mol-math/linear-algebra/3d/vec4.ts b/src/mol-math/linear-algebra/3d/vec4.ts index 54e2cda220efba0d36d0eaed13867cd9f820c55c..00d682a1628b7f5c8a71820cb29d3e2cb045e3e9 100644 --- a/src/mol-math/linear-algebra/3d/vec4.ts +++ b/src/mol-math/linear-algebra/3d/vec4.ts @@ -108,6 +108,47 @@ namespace Vec4 { return Math.sqrt(x * x + y * y + z * z + w * w); } + export function scale(out: Vec4, a: Vec4, b: number) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[4] = a[4] * b; + return out; + } + + /** + * Math.round the components of a Vec4 + */ + export function round(out: Vec4, a: Vec4) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + out[2] = Math.round(a[2]); + out[3] = Math.round(a[3]); + return out; + } + + /** + * Math.ceil the components of a Vec4 + */ + export function ceil(out: Vec4, a: Vec4) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + out[2] = Math.ceil(a[2]); + out[3] = Math.ceil(a[3]); + return out; + } + + /** + * Math.floor the components of a Vec3 + */ + export function floor(out: Vec4, a: Vec4) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + out[2] = Math.floor(a[2]); + out[3] = Math.floor(a[3]); + return out; + } + export function squaredDistance(a: Vec4, b: Vec4) { const x = b[0] - a[0], y = b[1] - a[1], @@ -140,6 +181,21 @@ namespace Vec4 { out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; return out; } + + export function dot(a: Vec4, b: Vec4) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + + /** + * Returns the inverse of the components of a Vec4 + */ + export function inverse(out: Vec4, a: Vec4) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + out[2] = 1.0 / a[2]; + out[3] = 1.0 / a[3]; + return out; + } } export default Vec4 \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 6f135c7209df51cbb0721bc969a8510205f0711e..33c5897d3926237165403caff080e613b97daff5 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -144,6 +144,7 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): return { id: UUID.create(), + label: format.data.entry.id.value(0), sourceData: format, modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)), entities, diff --git a/src/mol-model/structure/model/formats/mmcif/sequence.ts b/src/mol-model/structure/model/formats/mmcif/sequence.ts index 3b30419696303332c6c91fec2fbc66b6de3f2451..c9f0825208a9538a3bac331162776d6d84989fbc 100644 --- a/src/mol-model/structure/model/formats/mmcif/sequence.ts +++ b/src/mol-model/structure/model/formats/mmcif/sequence.ts @@ -10,8 +10,18 @@ import { Column } from 'mol-data/db'; import { AtomicHierarchy } from '../../properties/atomic'; import { Entities } from '../../properties/common'; +// TODO how to handle microheterogeneity +// see http://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Categories/entity_poly_seq.html +// +// Data items in the ENTITY_POLY_SEQ category specify the sequence +// of monomers in a polymer. Allowance is made for the possibility +// of microheterogeneity in a sample by allowing a given sequence +// number to be correlated with more than one monomer ID. The +// corresponding ATOM_SITE entries should reflect this +// heterogeneity. + export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): Sequence { - if (!cif.entity_poly_seq._rowCount) return Sequence.fromAtomicHierarchy(hierarchy); + if (!cif.entity_poly_seq._rowCount) return Sequence.fromAtomicHierarchy(entities, hierarchy); const { entity_id, num, mon_id } = cif.entity_poly_seq; diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 1053bde91c2382c05f3a64445ae12facec884b62..214dc8d8687ce1b59cfd871513163622b2162420 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -23,6 +23,7 @@ import from_mmCIF from './formats/mmcif' */ interface Model extends Readonly<{ id: UUID, + label: string, modelNum: number, diff --git a/src/mol-model/structure/model/properties/sequence.ts b/src/mol-model/structure/model/properties/sequence.ts index b34b1ef61b624307682ef17e577b670444bbec36..229c5509b72ccff1ec691a867bdfa3f1e1f44a1b 100644 --- a/src/mol-model/structure/model/properties/sequence.ts +++ b/src/mol-model/structure/model/properties/sequence.ts @@ -6,6 +6,7 @@ import { Column } from 'mol-data/db' import { AtomicHierarchy } from './atomic/hierarchy'; +import { Entities } from './common'; interface Sequence { readonly byEntityKey: { [key: number]: Sequence.Entity } @@ -19,10 +20,36 @@ namespace Sequence { readonly compId: Column<string> } - export function fromAtomicHierarchy(hierarchy: AtomicHierarchy): Sequence { - // const { label_comp_id } = hierarchy.residues; + export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy): Sequence { + const { label_entity_id } = hierarchy.chains + const { label_comp_id, label_seq_id } = hierarchy.residues + const { chainSegments, residueSegments } = hierarchy - throw 'not implemented'; + const byEntityKey: Sequence['byEntityKey'] = {}; + + const chainCount = hierarchy.chains._rowCount + for (let i = 0; i < chainCount; ++i) { + const entityId = label_entity_id.value(i) + const entityIndex = entities.getEntityIndex(entityId) + // TODO only for polymers, mirroring _entity_poly_seq, ok??? + if (entities.data.type.value(i) !== 'polymer') continue + + const entityKey = hierarchy.entityKey[entityIndex] + if (byEntityKey[entityKey] !== undefined) continue + + const start = residueSegments.segmentMap[chainSegments.segments[i]] + let end = residueSegments.segmentMap[chainSegments.segments[i + 1]] + // TODO better way to handle end??? + if (end === undefined) end = hierarchy.residues._rowCount + + byEntityKey[entityKey] = { + entityId, + compId: Column.window(label_comp_id, start, end), + num: Column.window(label_seq_id, start, end) + } + } + + return { byEntityKey } } } diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index bbc7b301d63319d52e34c354e27ef0d816fb44bd..88abc99496ed3bc540ea936dbc6c3764e49d2447 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -92,7 +92,8 @@ namespace Structure { for (let c = 0; c < chains.count; c++) { const elements = SortedArray.ofBounds(chains.segments[c], chains.segments[c + 1]); - builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements); + const label = SymmetryOperator.Default.name + builder.addUnit(label, Unit.Kind.Atomic, model, SymmetryOperator.Default, elements); } const cs = model.coarseHierarchy; @@ -112,15 +113,16 @@ namespace Structure { const { chainSegments } = elements; for (let cI = 0; cI < chainSegments.count; cI++) { const elements = SortedArray.ofBounds(chainSegments.segments[cI], chainSegments.segments[cI + 1]); - builder.addUnit(kind, model, SymmetryOperator.Default, elements); + const label = SymmetryOperator.Default.name + builder.addUnit(label, kind, model, SymmetryOperator.Default, elements); } } export class StructureBuilder { private units: Unit[] = []; - addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit { - const unit = Unit.create(this.units.length, kind, model, operator, elements); + addUnit(label: string, kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit { + const unit = Unit.create(this.units.length, label, kind, model, operator, elements); this.units.push(unit); return unit; } diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 5c34fd555248e6b3c2cf752446930b7d47116f6e..8c6e715eacfbca24beadfa8bab3d6fa7e35e7c62 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -25,11 +25,11 @@ namespace Unit { export function isSpheres(u: Unit): u is Spheres { return u.kind === Kind.Spheres; } export function isGaussians(u: Unit): u is Gaussians { return u.kind === Kind.Gaussians; } - export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit { + export function create(id: number, label: string, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit { switch (kind) { - case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), AtomicProperties()); - case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres)); - case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians)); + case Kind.Atomic: return new Atomic(id, unitIdFactory(), label, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), AtomicProperties()); + case Kind.Spheres: return createCoarse(id, unitIdFactory(), label, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres)); + case Kind.Gaussians: return createCoarse(id, unitIdFactory(), label, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians)); } } @@ -40,6 +40,7 @@ namespace Unit { readonly id: number, // invariant ID stays the same even if the Operator/conformation changes. readonly invariantId: number, + readonly label: string, readonly elements: SortedArray, readonly model: Model, readonly conformation: SymmetryOperator.ArrayMapping, @@ -64,6 +65,7 @@ namespace Unit { readonly id: number; readonly invariantId: number; + readonly label: string; readonly elements: SortedArray; readonly model: Model; readonly conformation: SymmetryOperator.ArrayMapping; @@ -76,12 +78,13 @@ namespace Unit { getChild(elements: SortedArray): Unit { if (elements.length === this.elements.length) return this; - return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation, AtomicProperties()); + return new Atomic(this.id, this.invariantId, this.label, this.model, elements, this.conformation, AtomicProperties()); } applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit { const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator); - return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation), this.props); + const label = operator.name + return new Atomic(id, this.invariantId, label, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation), this.props); } get lookup3d() { @@ -97,9 +100,10 @@ namespace Unit { return this.props.bonds.ref; } - constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) { + constructor(id: number, invariantId: number, label: string, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) { this.id = id; this.invariantId = invariantId; + this.label = label; this.model = model; this.elements = elements; this.conformation = conformation; @@ -124,6 +128,7 @@ namespace Unit { readonly id: number; readonly invariantId: number; + readonly label: string; readonly elements: SortedArray; readonly model: Model; readonly conformation: SymmetryOperator.ArrayMapping; @@ -133,12 +138,13 @@ namespace Unit { getChild(elements: SortedArray): Unit { if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */; - return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation); + return createCoarse(this.id, this.invariantId, this.label, this.model, this.kind, elements, this.conformation); } applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit { const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator); - const ret = createCoarse(id, this.invariantId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements())); + const label = operator.name + const ret = createCoarse(id, this.invariantId, label, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements())); (ret as Coarse<K, C>)._lookup3d = this._lookup3d; return ret; } @@ -156,10 +162,11 @@ namespace Unit { return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians; } - constructor(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) { + constructor(id: number, invariantId: number, label: string, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) { this.kind = kind; this.id = id; this.invariantId = invariantId; + this.label = label; this.model = model; this.elements = elements; this.conformation = conformation; @@ -168,8 +175,8 @@ namespace Unit { } } - function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit { - return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */; + function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, label: string, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit { + return new Coarse(id, invariantId, label, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */; } export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { } diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index ad197ab06f4c72f9ba06833525978862bc21b40c..5d572d23f3a6b75b4630ed477973769b4749fcd3 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -93,6 +93,13 @@ export type ClickInput = { pageY: number, } & BaseInput +export type MoveInput = { + x: number, + y: number, + pageX: number, + pageY: number, +} & BaseInput + export type PinchInput = { delta: number, distance: number, @@ -124,6 +131,7 @@ interface InputObserver { wheel: Subject<WheelInput>, pinch: Subject<PinchInput>, click: Subject<ClickInput>, + move: Subject<MoveInput>, resize: Subject<ResizeInput>, dispose: () => void @@ -153,6 +161,7 @@ namespace InputObserver { const drag = new Subject<DragInput>() const click = new Subject<ClickInput>() + const move = new Subject<MoveInput>() const wheel = new Subject<WheelInput>() const pinch = new Subject<PinchInput>() const resize = new Subject<ResizeInput>() @@ -169,6 +178,7 @@ namespace InputObserver { wheel, pinch, click, + move, resize, dispose @@ -343,13 +353,15 @@ namespace InputObserver { function onPointerMove (ev: PointerEvent) { eventOffset(pointerEnd, ev) + const { pageX, pageY } = ev + const [ x, y ] = pointerEnd + move.next({ x, y, pageX, pageY, buttons, modifiers }) + if (dragging === DraggingState.Stopped) return Vec2.div(pointerDelta, Vec2.sub(pointerDelta, pointerEnd, pointerStart), getClientSize(rectSize)) const isStart = dragging === DraggingState.Started - const { pageX, pageY } = ev - const [ x, y ] = pointerEnd const [ dx, dy ] = pointerDelta drag.next({ x, y, dx, dy, pageX, pageY, buttons, modifiers, isStart }) diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index df5a3f8629845ce43ec9f6d8046e44dc71cce738..1992d98de3ddf92c037d8119f73eb32cf516be96 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -34,18 +34,23 @@ export class Stage { } - async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) { + initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) { this.viewer = Viewer.create(canvas, container) this.viewer.animate() this.ctx.viewer = this.viewer - this.loadPdbid('1crn') + // this.loadPdbid('1crn') + this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) } - async loadPdbid (pdbid: string) { - const urlEntity = UrlEntity.ofUrl(this.ctx, `https://files.rcsb.org/download/${pdbid}.cif`) + loadMmcifUrl (url: string) { + const urlEntity = UrlEntity.ofUrl(this.ctx, url) MmcifUrlToSpacefill.apply(this.ctx, urlEntity, spacefillProps) } + loadPdbid (pdbid: string) { + return this.loadMmcifUrl(`https://files.rcsb.org/download/${pdbid}.cif`) + } + dispose () { // TODO } diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 0074700ffb84e35bf4402d07194f4ca77d2856a1..e2a6dc57ef38e03d0279dddcebdc749afb257d0f 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -18,6 +18,10 @@ import { PerspectiveCamera } from './camera/perspective' import { resizeCanvas } from './util'; import { createContext } from 'mol-gl/webgl/context'; import { Representation } from 'mol-geo/representation'; +import { createRenderTarget } from 'mol-gl/webgl/render-target'; +import Scene from 'mol-gl/scene'; +import { RenderVariant } from 'mol-gl/webgl/render-item'; +import { PickingId, decodeIdRGBA } from 'mol-geo/util/picking'; interface Viewer { center: (p: Vec3) => void @@ -33,13 +37,17 @@ interface Viewer { draw: (force?: boolean) => void requestDraw: () => void animate: () => void + pick: () => void + identify: (x: number, y: number) => void + reprCount: BehaviorSubject<number> + identified: BehaviorSubject<string> didDraw: BehaviorSubject<number> handleResize: () => void resetCamera: () => void downloadScreenshot: () => void - getImageData: () => ImageData + getImageData: (variant: RenderVariant) => ImageData input: InputObserver stats: RendererStats @@ -61,9 +69,26 @@ namespace Viewer { export function create(canvas: HTMLCanvasElement, container: Element): Viewer { const reprMap = new Map<Representation<any>, Set<RenderObject>>() const reprCount = new BehaviorSubject(0) + const identified = new BehaviorSubject('') + + const startTime = performance.now() + const didDraw = new BehaviorSubject(0) const input = InputObserver.create(canvas) input.resize.subscribe(handleResize) + input.move.subscribe(({x, y}) => { + const p = identify(x, y) + let label = '' + reprMap.forEach((roSet, repr) => { + const info = repr.getLabel(p) + if (info) label = info.label + repr.update({ hoverSelection: p }).run().then(() => { + scene.update() + requestDraw() + }) + }) + identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`) + }) const camera = PerspectiveCamera.create({ near: 0.1, @@ -86,17 +111,42 @@ namespace Viewer { } const ctx = createContext(gl) + const scene = Scene.create(ctx) const renderer = Renderer.create(ctx, camera) + const pickScale = 1 / 4 + const pickWidth = Math.round(canvas.width * pickScale) + const pickHeight = Math.round(canvas.height * pickScale) + const objectPickTarget = createRenderTarget(ctx, pickWidth, pickHeight) + const instancePickTarget = createRenderTarget(ctx, pickWidth, pickHeight) + const elementPickTarget = createRenderTarget(ctx, pickWidth, pickHeight) + + let pickDirty = true let drawPending = false const prevProjectionView = Mat4.zero() - function draw (force?: boolean) { + function render(variant: RenderVariant, force?: boolean) { + let didRender = false controls.update() camera.update() if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value)) { Mat4.copy(prevProjectionView, camera.projectionView) - renderer.draw() + renderer.render(scene, variant) + if (variant === 'draw') { + pickDirty = true + pick() + } + didRender = true + } + return didRender + } + + function draw(force?: boolean) { + ctx.unbindFramebuffer() + const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height } + renderer.setViewport(viewport) + if (render('draw', force)) { + didDraw.next(performance.now() - startTime) } drawPending = false } @@ -108,10 +158,47 @@ namespace Viewer { } function animate () { - draw() + draw(false) window.requestAnimationFrame(() => animate()) } + function pick() { + objectPickTarget.bind() + render('pickObject', pickDirty) + + instancePickTarget.bind() + render('pickInstance', pickDirty) + + elementPickTarget.bind() + render('pickElement', pickDirty) + + pickDirty = false + } + + function identify (x: number, y: number): PickingId { + x *= ctx.pixelRatio + y *= ctx.pixelRatio + y = canvas.height - y // flip y + + const buffer = new Uint8Array(4) + const xp = Math.round(x * pickScale) + const yp = Math.round(y * pickScale) + + objectPickTarget.bind() + ctx.readPixels(xp, yp, 1, 1, buffer) + const objectId = decodeIdRGBA(buffer[0], buffer[1], buffer[2]) + + instancePickTarget.bind() + ctx.readPixels(xp, yp, 1, 1, buffer) + const instanceId = decodeIdRGBA(buffer[0], buffer[1], buffer[2]) + + elementPickTarget.bind() + ctx.readPixels(xp, yp, 1, 1, buffer) + const elementId = decodeIdRGBA(buffer[0], buffer[1], buffer[2]) + + return { objectId, instanceId, elementId } + } + handleResize() return { @@ -133,11 +220,11 @@ namespace Viewer { const newRO = new Set<RenderObject>() repr.renderObjects.forEach(o => newRO.add(o)) if (oldRO) { - SetUtils.difference(newRO, oldRO).forEach(o => renderer.add(o)) - SetUtils.difference(oldRO, newRO).forEach(o => renderer.remove(o)) - renderer.update() + SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o)) + SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o)) + scene.update() } else { - repr.renderObjects.forEach(o => renderer.add(o)) + repr.renderObjects.forEach(o => scene.add(o)) } reprMap.set(repr, newRO) reprCount.next(reprMap.size) @@ -145,20 +232,22 @@ namespace Viewer { remove: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) if (renderObjectSet) { - renderObjectSet.forEach(o => renderer.remove(o)) + renderObjectSet.forEach(o => scene.remove(o)) reprMap.delete(repr) reprCount.next(reprMap.size) } }, - update: () => renderer.update(), + update: () => scene.update(), clear: () => { reprMap.clear() - renderer.clear() + scene.clear() }, draw, requestDraw, animate, + pick, + identify, handleResize, resetCamera: () => { @@ -167,11 +256,17 @@ namespace Viewer { downloadScreenshot: () => { // TODO }, - getImageData: () => { - return renderer.getImageData() + getImageData: (variant: RenderVariant) => { + switch (variant) { + case 'draw': return renderer.getImageData() + case 'pickObject': return objectPickTarget.getImageData() + case 'pickInstance': return instancePickTarget.getImageData() + case 'pickElement': return elementPickTarget.getImageData() + } }, reprCount, - didDraw: renderer.didDraw, + identified, + didDraw, get input() { return input @@ -180,6 +275,7 @@ namespace Viewer { return renderer.stats }, dispose: () => { + scene.clear() input.dispose() controls.dispose() renderer.dispose() @@ -192,6 +288,12 @@ namespace Viewer { renderer.setViewport(viewport) Viewport.copy(camera.viewport, viewport) Viewport.copy(controls.viewport, viewport) + + const pickWidth = Math.round(canvas.width * pickScale) + const pickHeight = Math.round(canvas.height * pickScale) + objectPickTarget.setSize(pickWidth, pickHeight) + instancePickTarget.setSize(pickWidth, pickHeight) + elementPickTarget.setSize(pickWidth, pickHeight) } } } diff --git a/tsconfig.json b/tsconfig.json index eedd1592ae0767790c69a6dbeece0b25e46483fd..8da5b2ddc8d0c13c4d3d346f02177527b308f51b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "noUnusedLocals": true, "strictNullChecks": true, "strictFunctionTypes": true, + "keyofStringsOnly": true, //"downlevelIteration": true, "jsx": "react", "lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ],