123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
- import { BehaviorSubject } from 'rxjs';
- import { Vec3, Mat4, EPSILON, Vec4 } from 'mol-math/linear-algebra'
- import InputObserver from 'mol-util/input/input-observer'
- import * as SetUtils from 'mol-util/set'
- import Renderer, { RendererStats } from 'mol-gl/renderer'
- import { RenderObject } from 'mol-gl/render-object'
- import TrackballControls from './controls/trackball'
- import { Viewport } from './camera/util'
- 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';
- interface Viewer {
- center: (p: Vec3) => void
- hide: (repr: Representation<any>) => void
- show: (repr: Representation<any>) => void
- add: (repr: Representation<any>) => void
- remove: (repr: Representation<any>) => void
- update: () => void
- clear: () => void
- draw: (force?: boolean) => void
- requestDraw: () => void
- animate: () => void
- pick: () => void
- identify: (x: number, y: number) => void
- reprCount: BehaviorSubject<number>
- didDraw: BehaviorSubject<number>
- handleResize: () => void
- resetCamera: () => void
- downloadScreenshot: () => void
- getImageData: (variant: RenderVariant) => ImageData
- input: InputObserver
- stats: RendererStats
- dispose: () => void
- }
- function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes) {
- function getContext(contextId: 'webgl' | 'experimental-webgl') {
- try {
- return canvas.getContext(contextId, contextAttributes)
- } catch (e) {
- return null
- }
- }
- return getContext('webgl') || getContext('experimental-webgl')
- }
- namespace Viewer {
- export function create(canvas: HTMLCanvasElement, container: Element): Viewer {
- const reprMap = new Map<Representation<any>, Set<RenderObject>>()
- const reprCount = new BehaviorSubject(0)
- const startTime = performance.now()
- const didDraw = new BehaviorSubject(0)
- const input = InputObserver.create(canvas)
- input.resize.subscribe(handleResize)
- input.move.subscribe(({x, y}) => identify(x, y))
- const camera = PerspectiveCamera.create({
- near: 0.1,
- far: 10000,
- position: Vec3.create(0, 0, 50)
- })
- const controls = TrackballControls.create(input, camera, {
- })
- const gl = getWebGLContext(canvas, {
- alpha: false,
- antialias: true,
- depth: true,
- preserveDrawingBuffer: true
- })
- if (gl === null) {
- throw new Error('Could not create a WebGL rendering context')
- }
- const ctx = createContext(gl)
- const scene = Scene.create(ctx)
- const renderer = Renderer.create(ctx, camera)
- const pickScale = 1 // 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 drawPending = false
- const prevProjectionView = Mat4.zero()
- 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.render(scene, variant)
- 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
- }
- function requestDraw () {
- if (drawPending) return
- drawPending = true
- window.requestAnimationFrame(() => draw(true))
- }
- function animate () {
- draw(false)
- window.requestAnimationFrame(() => animate())
- }
- const decodeFactors = Vec4.create(1, 1/255, 1/65025, 1/16581375)
- function decodeFloatRGBA(rgba: Vec4) {
- return Vec4.dot(rgba, decodeFactors);
- }
- function identify (x: number, y: number) {
- y = canvas.height - y // flip y
- const xp = Math.round(x * pickScale)
- const yp = Math.round(y * pickScale)
- console.log('position', x, y, xp, yp)
- const buffer = new Uint8Array(4)
- elementPickTarget.bind()
- ctx.readPixels(xp, yp, 1, 1, buffer)
- console.log('identify', buffer[0], buffer[1], buffer[2], buffer[3])
- const v = Vec4.create(buffer[0], buffer[1], buffer[2], buffer[3])
- const d = decodeFloatRGBA(v)
- console.log(d)
- console.log(d * 16777216)
- ctx.unbindFramebuffer()
- ctx.readPixels(x, y, 1, 1, buffer)
- console.log('color', buffer[0], buffer[1], buffer[2], buffer[3])
- }
- handleResize()
- return {
- center: (p: Vec3) => {
- Vec3.set(controls.target, p[0], p[1], p[2])
- },
- hide: (repr: Representation<any>) => {
- const renderObjectSet = reprMap.get(repr)
- if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = false)
- },
- show: (repr: Representation<any>) => {
- const renderObjectSet = reprMap.get(repr)
- if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = true)
- },
- add: (repr: Representation<any>) => {
- const oldRO = reprMap.get(repr)
- const newRO = new Set<RenderObject>()
- repr.renderObjects.forEach(o => newRO.add(o))
- if (oldRO) {
- 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 => scene.add(o))
- }
- reprMap.set(repr, newRO)
- reprCount.next(reprMap.size)
- },
- remove: (repr: Representation<any>) => {
- const renderObjectSet = reprMap.get(repr)
- if (renderObjectSet) {
- renderObjectSet.forEach(o => scene.remove(o))
- reprMap.delete(repr)
- reprCount.next(reprMap.size)
- }
- },
- update: () => scene.update(),
- clear: () => {
- reprMap.clear()
- scene.clear()
- },
- draw,
- requestDraw,
- animate,
- pick: () => {
- objectPickTarget.bind()
- render('pickObject', true)
- instancePickTarget.bind()
- render('pickInstance', true)
- elementPickTarget.bind()
- render('pickElement', true)
- },
- identify,
- handleResize,
- resetCamera: () => {
- // TODO
- },
- downloadScreenshot: () => {
- // TODO
- },
- 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,
- get input() {
- return input
- },
- get stats() {
- return renderer.stats
- },
- dispose: () => {
- scene.clear()
- input.dispose()
- controls.dispose()
- renderer.dispose()
- }
- }
- function handleResize() {
- resizeCanvas(canvas, container)
- const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height }
- 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)
- }
- }
- }
- export default Viewer
|