diff --git a/src/apps/render-test/components/viewport.tsx b/src/apps/render-test/components/viewport.tsx index 1536cd5a41f75f1efce8035cd2258296b05b34d5..27b0cd63c8cef15ffb5b1c38733cca8c3371c181 100644 --- a/src/apps/render-test/components/viewport.tsx +++ b/src/apps/render-test/components/viewport.tsx @@ -8,15 +8,21 @@ import * as React from 'react' import State from '../state' export default class Viewport extends React.Component<{ state: State }, { initialized: boolean }> { - private canvasContainer: HTMLDivElement | null = null; + private container: HTMLDivElement | null = null; + private canvas: HTMLCanvasElement | null = null; state = { initialized: false } componentDidMount() { - if (this.canvasContainer) this.props.state.initRenderer(this.canvasContainer).then(() => this.setState({ initialized: true })) + if (this.container && this.canvas) { + this.props.state.initRenderer(this.canvas, this.container).then(() => { + this.setState({ initialized: true }) + }) + } } render() { - return <div ref={elm => this.canvasContainer = elm} style={{ height: '100%' }}> + return <div ref={elm => this.container = elm} style={{ height: '100%' }}> + <canvas ref={elm => this.canvas = elm}></canvas> </div> } } \ No newline at end of file diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index e787d8c55cb84744c3eb71d83dd64b439dc5cc7e..ccf415550bdec2850ad6129e824bae02b660aaf2 100644 --- a/src/apps/render-test/state.ts +++ b/src/apps/render-test/state.ts @@ -9,7 +9,7 @@ import { BehaviorSubject } from 'rxjs'; // import { ValueCell } from 'mol-util/value-cell' // import { Vec3, Mat4 } from 'mol-math/linear-algebra' -import Renderer from 'mol-gl/renderer' +import Viewer from 'mol-view/viewer' // import { createColorTexture } from 'mol-gl/util'; // import Icosahedron from 'mol-geo/primitive/icosahedron' // import Box from 'mol-geo/primitive/box' @@ -25,21 +25,21 @@ import { StructureRepresentation } from 'mol-geo/representation/structure'; // import Cylinder from 'mol-geo/primitive/cylinder'; export default class State { - renderer: Renderer + viewer: Viewer pdbId = '1crn' initialized = new BehaviorSubject<boolean>(false) loading = new BehaviorSubject<boolean>(false) - async initRenderer (container: HTMLDivElement) { - this.renderer = Renderer.fromElement(container) + async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) { + this.viewer = Viewer.create(canvas, container) this.initialized.next(true) this.loadPdbId() - this.renderer.frame() + this.viewer.animate() } async loadPdbId () { - const { renderer, pdbId } = this - renderer.clear() + const { viewer, pdbId } = this + viewer.clear() if (pdbId.length !== 4) return this.loading.next(true) @@ -49,11 +49,11 @@ export default class State { const structPointRepr = StructureRepresentation(Point) await Run(structPointRepr.create(struct)) - structPointRepr.renderObjects.forEach(renderer.add) + structPointRepr.renderObjects.forEach(viewer.add) const structSpacefillRepr = StructureRepresentation(Spacefill) await Run(structSpacefillRepr.create(struct)) - structSpacefillRepr.renderObjects.forEach(renderer.add) + structSpacefillRepr.renderObjects.forEach(viewer.add) this.loading.next(false) } diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 76210a764f667135c06c807bfa23d7c4c9b8d647..786be32ee18ad949f915a45caf47e65314f0022c 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -8,7 +8,7 @@ import { ElementGroup, ElementSet, Structure, Unit } from 'mol-model/structure'; import { EquivalenceClasses } from 'mol-data/util'; import { OrderedSet } from 'mol-data/int' import { Task } from 'mol-task' -import { RenderObject } from 'mol-gl/renderer'; +import { RenderObject } from 'mol-gl/scene'; export interface RepresentationProps { diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 0e930adf7afc2576cb66813daa2526563b0d8fa4..7dbfb5ef282c8ec021fc550f3711b1b602a3d442 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/src/mol-geo/representation/structure/point.ts @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { createRenderObject, RenderObject } from 'mol-gl/renderer' +import { createRenderObject, RenderObject } from 'mol-gl/scene' import { createColorTexture } from 'mol-gl/util'; import { Mat4 } from 'mol-math/linear-algebra' import { OrderedSet } from 'mol-data/int' diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index f87c59cc543ff1b5230e54198f831e47dcecbb50..3d4a0c3ab05cc0c4644efaa616be9b4b41ade5ba 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { createRenderObject, RenderObject } from 'mol-gl/renderer' +import { createRenderObject, RenderObject } from 'mol-gl/scene' import { createColorTexture } from 'mol-gl/util'; import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { OrderedSet } from 'mol-data/int' diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 6ba0cfea705c5a2e01effc6368ec8857a1212e0e..bd925c6f772e47eff9083ab0b07aedd3a8a6a836 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -4,123 +4,36 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import REGL = require('regl'); -import * as glContext from './context' -import { PerspectiveCamera } from './camera/perspective' -import { PointRenderable, MeshRenderable, Renderable } from './renderable' -import Stats from './stats' - import { Vec3, Mat4 } from 'mol-math/linear-algebra' -import { ValueCell } from 'mol-util'; -import TrackballControls from './controls/trackball'; -import { Viewport } from './camera/util'; - -let _renderObjectId = 0; -function getNextId() { - return _renderObjectId++ % 0x7FFFFFFF; -} - - - -export interface RenderUpdateInfo { - -} - -export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> } - -export interface RenderObject { - id: number - type: 'mesh' | 'point' - data: PointRenderable.Data | MeshRenderable.Data - uniforms: { [k: string]: REGL.Uniform } -} +import { Viewport } from 'mol-view/camera/util'; +import { Camera } from 'mol-view/camera/base'; -export function createRenderObject(type: 'mesh' | 'point', data: PointRenderable.Data | MeshRenderable.Data, uniforms: { [k: string]: REGL.Uniform }) { - return { id: getNextId(), type, data, uniforms } -} - -export function createRenderable(regl: REGL.Regl, o: RenderObject) { - switch (o.type) { - case 'mesh': return MeshRenderable.create(regl, o.data as MeshRenderable.Data, o.uniforms || {}) - case 'point': return PointRenderable.create(regl, o.data as PointRenderable.Data) - } -} +import * as glContext from './context' +import Scene, { RenderObject } from './scene'; interface Renderer { - camera: PerspectiveCamera - controls: any // OrbitControls - add: (o: RenderObject) => void remove: (o: RenderObject) => void clear: () => void draw: () => void - frame: () => void - handleResize: () => void -} - -function resizeCanvas (canvas: HTMLCanvasElement, element: HTMLElement) { - let w = window.innerWidth - let h = window.innerHeight - if (element !== document.body) { - let bounds = element.getBoundingClientRect() - w = bounds.right - bounds.left - h = bounds.bottom - bounds.top - } - canvas.width = window.devicePixelRatio * w - canvas.height = window.devicePixelRatio * h - Object.assign(canvas.style, { width: `${w}px`, height: `${h}px` }) -} - -namespace Renderer { - export function fromElement(element: HTMLElement, contexAttributes?: WebGLContextAttributes) { - const canvas = document.createElement('canvas') - Object.assign(canvas.style, { border: 0, margin: 0, padding: 0, top: 0, left: 0 }) - element.appendChild(canvas) - - if (element === document.body) { - canvas.style.position = 'absolute' - Object.assign(element.style, { margin: 0, padding: 0 }) - } - - function resize () { - resizeCanvas(canvas, element) - } - - window.addEventListener('resize', resize, false) - // function onDestroy () { - // window.removeEventListener('resize', resize) - // element.removeChild(canvas) - // } + setViewport: (viewport: Viewport) => void - resize() - - return create(canvas) - } - - export function create(canvas: HTMLCanvasElement): Renderer { - const renderableList: Renderable[] = [] - const objectIdRenderableMap: { [k: number]: Renderable } = {} + dispose: () => void +} - const extensions = [ - 'OES_element_index_uint', - 'ANGLE_instanced_arrays' - ] - const optionalExtensions = [ - 'EXT_disjoint_timer_query' - ] +const extensions = [ + 'OES_element_index_uint', + 'ANGLE_instanced_arrays' +] +const optionalExtensions = [ + 'EXT_disjoint_timer_query' +] +namespace Renderer { + export function create(canvas: HTMLCanvasElement, camera: Camera): Renderer { const regl = glContext.create({ canvas, extensions, optionalExtensions, profile: true }) - - const camera = PerspectiveCamera.create({ - near: 0.01, - far: 10000, - position: Vec3.create(0, 0, 50) - }) - - const controls = TrackballControls.create(canvas, camera, { - - }) + const scene = Scene.create(regl) const baseContext = regl({ context: { @@ -142,68 +55,39 @@ namespace Renderer { } }) - const stats = Stats([]) - let prevTime = regl.now() - const draw = () => { - controls.update() - // controls.copyInto(camera.position, camera.direction, camera.up) + regl.poll() // updates timers and viewport camera.update() baseContext(state => { regl.clear({ color: [0, 0, 0, 1] }) // TODO painters sort, filter visible, filter picking, visibility culling? - renderableList.forEach(r => { + scene.forEach(r => { r.draw() }) - stats.update(state.time - prevTime) - prevTime = state.time }) } - window.addEventListener('resize', handleResize, false) - handleResize() - // TODO animate, draw, requestDraw return { - camera, - controls, - add: (o: RenderObject) => { - const renderable = createRenderable(regl, o) - renderableList.push(renderable) - objectIdRenderableMap[o.id] = renderable - stats.add(renderable) + scene.add(o) draw() }, remove: (o: RenderObject) => { - if (o.id in objectIdRenderableMap) { - // TODO - // objectIdRenderableMap[o.id].destroy() - delete objectIdRenderableMap[o.id] - draw() - } + scene.remove(o) + draw() }, clear: () => { - for (const id in objectIdRenderableMap) { - // TODO - // objectIdRenderableMap[id].destroy() - delete objectIdRenderableMap[id] - } - renderableList.length = 0 + scene.clear() draw() }, draw, - frame: () => { - regl.frame((ctx) => draw()) + setViewport: (viewport: Viewport) => { + regl({ viewport }) }, - handleResize - } - - function handleResize() { - const viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height } - regl({ viewport }) - Viewport.copy(camera.viewport, viewport) - Viewport.copy(controls.viewport, viewport) + dispose: () => { + regl.destroy() + } } } } diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts new file mode 100644 index 0000000000000000000000000000000000000000..c03888827e7ab1e71c814b7f9314fc5ab884e87a --- /dev/null +++ b/src/mol-gl/scene.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import { PointRenderable, MeshRenderable, Renderable } from './renderable' + +import { ValueCell } from 'mol-util'; + +let _renderObjectId = 0; +function getNextId() { + return _renderObjectId++ % 0x7FFFFFFF; +} + +export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> } + +export interface RenderObject { + id: number + type: 'mesh' | 'point' + data: PointRenderable.Data | MeshRenderable.Data + uniforms: { [k: string]: REGL.Uniform } +} + +export function createRenderObject(type: 'mesh' | 'point', data: PointRenderable.Data | MeshRenderable.Data, uniforms: { [k: string]: REGL.Uniform }) { + return { id: getNextId(), type, data, uniforms } +} + +export function createRenderable(regl: REGL.Regl, o: RenderObject) { + switch (o.type) { + case 'mesh': return MeshRenderable.create(regl, o.data as MeshRenderable.Data, o.uniforms || {}) + case 'point': return PointRenderable.create(regl, o.data as PointRenderable.Data) + } +} + +interface Scene { + add: (o: RenderObject) => void + remove: (o: RenderObject) => void + clear: () => void + forEach: (callbackFn: (value: Renderable) => void) => void +} + +namespace Scene { + export function create(regl: REGL.Regl): Scene { + const renderableList: Renderable[] = [] + const objectIdRenderableMap: { [k: number]: Renderable } = {} + + return { + add: (o: RenderObject) => { + const renderable = createRenderable(regl, o) + renderableList.push(renderable) + objectIdRenderableMap[o.id] = renderable + }, + remove: (o: RenderObject) => { + if (o.id in objectIdRenderableMap) { + // TODO + // objectIdRenderableMap[o.id].destroy() + delete objectIdRenderableMap[o.id] + } + }, + clear: () => { + for (const id in objectIdRenderableMap) { + // TODO + // objectIdRenderableMap[id].destroy() + delete objectIdRenderableMap[id] + } + renderableList.length = 0 + }, + forEach: (callbackFn: (value: Renderable) => void) => { + renderableList.forEach(callbackFn) + } + } + } +} + +export default Scene \ No newline at end of file diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index 4c070703c79742068f8a627900a0fe4075b4d216..ad197ab06f4c72f9ba06833525978862bc21b40c 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -99,6 +99,10 @@ export type PinchInput = { isStart: boolean } +export type ResizeInput = { + +} + const enum DraggingState { Stopped = 0, Started = 1, @@ -120,6 +124,7 @@ interface InputObserver { wheel: Subject<WheelInput>, pinch: Subject<PinchInput>, click: Subject<ClickInput>, + resize: Subject<ResizeInput>, dispose: () => void } @@ -150,6 +155,7 @@ namespace InputObserver { const click = new Subject<ClickInput>() const wheel = new Subject<WheelInput>() const pinch = new Subject<PinchInput>() + const resize = new Subject<ResizeInput>() attach() @@ -163,6 +169,7 @@ namespace InputObserver { wheel, pinch, click, + resize, dispose } @@ -185,6 +192,8 @@ namespace InputObserver { element.addEventListener('keyup', handleMods as EventListener) element.addEventListener('keydown', handleMods as EventListener) element.addEventListener('keypress', handleMods as EventListener) + + window.addEventListener('resize', onResize, false) } function dispose () { @@ -206,6 +215,8 @@ namespace InputObserver { element.removeEventListener('keyup', handleMods as EventListener) element.removeEventListener('keydown', handleMods as EventListener) element.removeEventListener('keypress', handleMods as EventListener) + + window.removeEventListener('resize', onResize, false) } function preventDefault (ev: Event | Touch) { @@ -368,6 +379,10 @@ namespace InputObserver { } } + function onResize (ev: Event) { + resize.next() + } + function insideBounds (pos: Vec2) { if (element instanceof Window || element instanceof Document || element === document.body) { return true diff --git a/src/mol-gl/camera/base.ts b/src/mol-view/camera/base.ts similarity index 100% rename from src/mol-gl/camera/base.ts rename to src/mol-view/camera/base.ts diff --git a/src/mol-gl/camera/orthographic.ts b/src/mol-view/camera/orthographic.ts similarity index 100% rename from src/mol-gl/camera/orthographic.ts rename to src/mol-view/camera/orthographic.ts diff --git a/src/mol-gl/camera/perspective.ts b/src/mol-view/camera/perspective.ts similarity index 100% rename from src/mol-gl/camera/perspective.ts rename to src/mol-view/camera/perspective.ts diff --git a/src/mol-gl/camera/util.ts b/src/mol-view/camera/util.ts similarity index 100% rename from src/mol-gl/camera/util.ts rename to src/mol-view/camera/util.ts diff --git a/src/mol-gl/controls/trackball.ts b/src/mol-view/controls/trackball.ts similarity index 96% rename from src/mol-gl/controls/trackball.ts rename to src/mol-view/controls/trackball.ts index 57bd3baa468bdd844fcdc9133a20c527c2d39697..3e3ee683d2aec00abd2f2b7dda7be3ae0b05262b 100644 --- a/src/mol-gl/controls/trackball.ts +++ b/src/mol-view/controls/trackball.ts @@ -48,7 +48,7 @@ interface TrackballControls { } namespace TrackballControls { - export function create (element: Element, object: Object, props: TrackballControlsProps = {}): TrackballControls { + export function create (input: InputObserver, object: Object, props: TrackballControlsProps = {}): TrackballControls { const p = { ...DefaultTrackballControlsProps, ...props } const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 } @@ -59,10 +59,9 @@ namespace TrackballControls { let disposed = false - const input = InputObserver.create(element) - input.drag.subscribe(onDrag) - input.wheel.subscribe(onWheel) - input.pinch.subscribe(onPinch) + const dragSub = input.drag.subscribe(onDrag) + const wheelSub = input.wheel.subscribe(onWheel) + const pinchSub = input.pinch.subscribe(onPinch) // internals const target = Vec3.zero() @@ -284,7 +283,10 @@ namespace TrackballControls { function dispose() { if (disposed) return disposed = true - input.dispose() + + dragSub.unsubscribe() + wheelSub.unsubscribe() + pinchSub.unsubscribe() } // force an update at start diff --git a/src/mol-view/util.ts b/src/mol-view/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..294907ad30cdd987b883f7b95848829de48a611e --- /dev/null +++ b/src/mol-view/util.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) { + let w = window.innerWidth + let h = window.innerHeight + if (container !== document.body) { + let bounds = container.getBoundingClientRect() + w = bounds.right - bounds.left + h = bounds.bottom - bounds.top + } + canvas.width = window.devicePixelRatio * w + canvas.height = window.devicePixelRatio * h + Object.assign(canvas.style, { width: `${w}px`, height: `${h}px` }) +} \ No newline at end of file diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts new file mode 100644 index 0000000000000000000000000000000000000000..14bf79505567800c730b308c55f66a5af4bafe43 --- /dev/null +++ b/src/mol-view/viewer.ts @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Vec3 } from 'mol-math/linear-algebra' +import InputObserver from 'mol-util/input/input-observer' +import Renderer from 'mol-gl/renderer' +import { RenderObject } from 'mol-gl/scene' + +import TrackballControls from './controls/trackball' +import { Viewport } from './camera/util' +import { PerspectiveCamera } from './camera/perspective' +import { resizeCanvas } from './util'; + +interface Viewer { + add: (o: RenderObject) => void + remove: (o: RenderObject) => void + clear: () => void + draw: () => void + + requestDraw: () => void + animate: () => void + + handleResize: () => void + + dispose: () => void +} + +namespace Viewer { + export function create(canvas: HTMLCanvasElement, container: Element): Viewer { + const input = InputObserver.create(canvas) + input.resize.subscribe(handleResize) + + const camera = PerspectiveCamera.create({ + near: 0.01, + far: 10000, + position: Vec3.create(0, 0, 50) + }) + + const controls = TrackballControls.create(input, camera, { + + }) + + const renderer = Renderer.create(canvas, camera) + + let drawPending = false + + function draw () { + + controls.update() + camera.update() + renderer.draw() + } + + function requestDraw () { + if (drawPending) return + drawPending = true + window.requestAnimationFrame(draw) + } + + function animate () { + draw() + window.requestAnimationFrame(animate) + } + + handleResize() + + return { + add: (o: RenderObject) => { + renderer.add(o) + }, + remove: (o: RenderObject) => { + renderer.remove(o) + }, + clear: () => { + renderer.clear() + }, + + draw, + requestDraw, + animate, + + handleResize, + + dispose: () => { + 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) + } + } +} + +export default Viewer \ No newline at end of file