From fdbb1fa42af7906dd6b5751c35dae432c9f3bfd6 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Thu, 8 Nov 2018 16:22:43 +0100 Subject: [PATCH] mol-canvas3d: refacroring, moved now() to mol-util --- src/apps/canvas/app.ts | 2 +- src/apps/canvas/component/representation.tsx | 2 +- src/apps/canvas/structure-view.ts | 4 +- src/apps/canvas/volume-view.ts | 2 +- src/examples/task.ts | 3 +- src/mol-canvas3d/camera.ts | 28 ++++++- src/mol-canvas3d/canvas3d.ts | 88 +++++++++----------- src/mol-canvas3d/controls/trackball.ts | 3 +- src/mol-model/structure/query/context.ts | 2 +- src/mol-plugin/context.ts | 5 +- src/mol-task/execution/observable.ts | 2 +- src/mol-task/index.ts | 3 +- src/mol-task/util/chunked.ts | 2 +- src/{mol-task/util => mol-util}/now.ts | 6 +- src/mol-util/performance-monitor.ts | 2 +- src/mol-util/uuid.ts | 2 +- src/perf-tests/tasks.ts | 2 +- src/servers/model/preprocess/parallel.ts | 2 +- src/servers/model/server/api-local.ts | 2 +- src/servers/model/server/query.ts | 3 +- src/servers/model/utils/fetch-props-pdbe.ts | 2 +- 21 files changed, 94 insertions(+), 73 deletions(-) rename src/{mol-task/util => mol-util}/now.ts (83%) diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts index 25b2b5aeb..35732c7e5 100644 --- a/src/apps/canvas/app.ts +++ b/src/apps/canvas/app.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import Canvas3D from 'mol-canvas3d/canvas3d'; +import { Canvas3D } from 'mol-canvas3d/canvas3d'; import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile, getVolumeFromVolcif } from './util'; import { StructureView } from './structure-view'; import { BehaviorSubject } from 'rxjs'; diff --git a/src/apps/canvas/component/representation.tsx b/src/apps/canvas/component/representation.tsx index 8bbdf8bae..0e4aedd7b 100644 --- a/src/apps/canvas/component/representation.tsx +++ b/src/apps/canvas/component/representation.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react' -import Canvas3D from 'mol-canvas3d/canvas3d'; +import { Canvas3D } from 'mol-canvas3d/canvas3d'; import { App } from '../app'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { Representation } from 'mol-repr'; diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index eeff5581a..4ba79d8bb 100644 --- a/src/apps/canvas/structure-view.ts +++ b/src/apps/canvas/structure-view.ts @@ -8,7 +8,7 @@ import { Model, Structure } from 'mol-model/structure'; import { getStructureFromModel } from './util'; import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry'; import { getAxesShape } from './assembly-symmetry'; -import Canvas3D from 'mol-canvas3d/canvas3d'; +import { Canvas3D } from 'mol-canvas3d/canvas3d'; // import { MeshBuilder } from 'mol-geo/mesh/mesh-builder'; // import { addSphere } from 'mol-geo/mesh/builder/sphere'; // import { Shape } from 'mol-model/shape'; @@ -217,7 +217,7 @@ export async function StructureView(app: App, canvas3d: Canvas3D, models: Readon } } - canvas3d.center(structure.boundary.sphere.center) + canvas3d.camera.setState({ target: structure.boundary.sphere.center }) // const mb = MeshBuilder.create() // mb.setGroup(0) diff --git a/src/apps/canvas/volume-view.ts b/src/apps/canvas/volume-view.ts index 6c05559ef..9d7b8cd1c 100644 --- a/src/apps/canvas/volume-view.ts +++ b/src/apps/canvas/volume-view.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import Canvas3D from 'mol-canvas3d/canvas3d'; +import { Canvas3D } from 'mol-canvas3d/canvas3d'; import { BehaviorSubject } from 'rxjs'; import { App } from './app'; import { VolumeData } from 'mol-model/volume'; diff --git a/src/examples/task.ts b/src/examples/task.ts index 64143cfb7..074190653 100644 --- a/src/examples/task.ts +++ b/src/examples/task.ts @@ -4,7 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Task, Progress, Scheduler, now, MultistepTask, chunkedSubtask } from 'mol-task' +import { Task, Progress, Scheduler, MultistepTask, chunkedSubtask } from 'mol-task' +import { now } from 'mol-util/now'; export async function test1() { const t = Task.create('test', async () => 1); diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index 88027f1e5..f2e482abc 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -5,13 +5,18 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Mat4, Vec3, Vec4 } from 'mol-math/linear-algebra' +import { Mat4, Vec3, Vec4, EPSILON } from 'mol-math/linear-algebra' import { Viewport, cameraLookAt, cameraProject, cameraUnproject } from './camera/util'; import { Object3D } from 'mol-gl/object3d'; +import { BehaviorSubject } from 'rxjs'; export { Camera } +// TODO: slab controls that modify near/far planes? + class Camera implements Object3D { + readonly updatedViewProjection = new BehaviorSubject<Camera>(this); + readonly view: Mat4 = Mat4.identity(); readonly projection: Mat4 = Mat4.identity(); readonly projectionView: Mat4 = Mat4.identity(); @@ -32,6 +37,9 @@ class Camera implements Object3D { get target() { return this.state.target; } set target(v: Vec3) { Vec3.copy(this.state.target, v); } + private prevProjection = Mat4.identity(); + private prevView = Mat4.identity(); + updateMatrices() { const snapshot = this.state as Camera.Snapshot; const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target); @@ -43,8 +51,22 @@ class Camera implements Object3D { default: throw new Error('unknown camera mode'); } + const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON.Value) || !Mat4.areEqual(this.view, this.prevView, EPSILON.Value); + Mat4.mul(this.projectionView, this.projection, this.view) Mat4.invert(this.inverseProjectionView, this.projectionView) + + + if (changed) { + Mat4.mul(this.projectionView, this.projection, this.view) + Mat4.invert(this.inverseProjectionView, this.projectionView) + + Mat4.copy(this.prevView, this.view); + Mat4.copy(this.prevProjection, this.projection); + this.updatedViewProjection.next(this); + } + + return changed; } setState(snapshot?: Partial<Camera.Snapshot>) { @@ -73,6 +95,10 @@ class Camera implements Object3D { return cameraUnproject(out, point, this.viewport, this.inverseProjectionView) } + dispose() { + this.updatedViewProjection.complete(); + } + constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) { this.viewport = viewport; Camera.copySnapshot(this.state, state); diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 10e2ae36f..3151bab2f 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -5,8 +5,9 @@ */ import { BehaviorSubject } from 'rxjs'; +import { now } from 'mol-util/now'; -import { Vec3, Mat4, EPSILON } from 'mol-math/linear-algebra' +import { Vec3 } 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' @@ -27,17 +28,19 @@ import { Color } from 'mol-util/color'; import { Camera } from './camera'; export const DefaultCanvas3DProps = { + // TODO: FPS cap? + // maxFps: 30, cameraPosition: Vec3.create(0, 0, 50), cameraMode: 'perspective' as Camera.Mode, backgroundColor: Color(0x000000), } export type Canvas3DProps = typeof DefaultCanvas3DProps +export { Canvas3D } + interface Canvas3D { readonly webgl: WebGLContext, - center: (p: Vec3) => void - hide: (repr: Representation<any>) => void show: (repr: Representation<any>) => void @@ -46,7 +49,7 @@ interface Canvas3D { update: () => void clear: () => void - draw: (force?: boolean) => void + // draw: (force?: boolean) => void requestDraw: (force?: boolean) => void animate: () => void pick: () => void @@ -54,9 +57,7 @@ interface Canvas3D { mark: (loci: Loci, action: MarkerAction) => void getLoci: (pickingId: PickingId) => { loci: Loci, repr?: Representation<any> } - readonly reprCount: BehaviorSubject<number> - readonly identified: BehaviorSubject<string> - readonly didDraw: BehaviorSubject<number> + readonly didDraw: BehaviorSubject<now.Timestamp> handleResize: () => void resetCamera: () => void @@ -77,11 +78,9 @@ namespace Canvas3D { const p = { ...props, ...DefaultCanvas3DProps } 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 startTime = now() + const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp) const input = InputObserver.create(canvas) const camera = new Camera({ @@ -117,8 +116,6 @@ namespace Canvas3D { let isPicking = false let drawPending = false let lastRenderTime = -1 - const prevProjectionView = Mat4.zero() - const prevSceneView = Mat4.zero() function getLoci(pickingId: PickingId) { let loci: Loci = EmptyLoci @@ -155,7 +152,7 @@ namespace Canvas3D { // return 0 // } - function render(variant: RenderVariant, force?: boolean) { + function render(variant: RenderVariant, force: boolean) { if (isPicking) return false // const p = scene.boundingSphere.center // console.log(p[0], p[1], p[2]) @@ -177,51 +174,56 @@ namespace Canvas3D { // console.log(camera.fogNear, camera.fogFar, targetDistance) - switch (variant) { - case 'pickObject': objectPickTarget.bind(); break; - case 'pickInstance': instancePickTarget.bind(); break; - case 'pickGroup': groupPickTarget.bind(); break; - case 'draw': - webgl.unbindFramebuffer(); - renderer.setViewport(0, 0, canvas.width, canvas.height); - break; - } let didRender = false controls.update() - camera.updateMatrices(); - if (force || !Mat4.areEqual(camera.projectionView, prevProjectionView, EPSILON.Value) || !Mat4.areEqual(scene.view, prevSceneView, EPSILON.Value)) { - // console.log('foo', force, prevSceneView, scene.view) - Mat4.copy(prevProjectionView, camera.projectionView) - Mat4.copy(prevSceneView, scene.view) + const cameraChanged = camera.updateMatrices(); + + if (force || cameraChanged) { + switch (variant) { + case 'pickObject': objectPickTarget.bind(); break; + case 'pickInstance': instancePickTarget.bind(); break; + case 'pickGroup': groupPickTarget.bind(); break; + case 'draw': + webgl.unbindFramebuffer(); + renderer.setViewport(0, 0, canvas.width, canvas.height); + break; + } + renderer.render(scene, variant) if (variant === 'draw') { - lastRenderTime = performance.now() + lastRenderTime = now() pickDirty = true } didRender = true } - return didRender + + return didRender && cameraChanged; } + let forceNextDraw = false; + function draw(force?: boolean) { - if (render('draw', force)) { - didDraw.next(performance.now() - startTime) + if (render('draw', !!force || forceNextDraw)) { + didDraw.next(now() - startTime as now.Timestamp) } + forceNextDraw = false; drawPending = false } function requestDraw(force?: boolean) { if (drawPending) return drawPending = true - window.requestAnimationFrame(() => draw(force)) + forceNextDraw = !!force; + // The animation frame is being requested by animate already. + // window.requestAnimationFrame(() => draw(force)) } function animate() { draw(false) - if (performance.now() - lastRenderTime > 200) { + if (now() - lastRenderTime > 200) { if (pickDirty) pick() } - window.requestAnimationFrame(() => animate()) + window.requestAnimationFrame(animate) } function pick() { @@ -273,11 +275,6 @@ namespace Canvas3D { return { webgl, - center: (target: Vec3) => { - // Vec3.set(controls.target, p[0], p[1], p[2]) - camera.setState({ target }) - }, - hide: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = false) @@ -299,7 +296,6 @@ namespace Canvas3D { repr.renderObjects.forEach(o => scene.add(o)) } reprMap.set(repr, newRO) - reprCount.next(reprMap.size) scene.update() }, remove: (repr: Representation<any>) => { @@ -307,7 +303,6 @@ namespace Canvas3D { if (renderObjectSet) { renderObjectSet.forEach(o => scene.remove(o)) reprMap.delete(repr) - reprCount.next(reprMap.size) scene.update() } }, @@ -317,7 +312,7 @@ namespace Canvas3D { scene.clear() }, - draw, + // draw, requestDraw, animate, pick, @@ -341,8 +336,6 @@ namespace Canvas3D { case 'pickGroup': return groupPickTarget.getImageData() } }, - reprCount, - identified, didDraw, setProps: (props: Partial<Canvas3DProps>) => { if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) { @@ -372,6 +365,7 @@ namespace Canvas3D { input.dispose() controls.dispose() renderer.dispose() + camera.dispose() } } @@ -388,6 +382,4 @@ namespace Canvas3D { groupPickTarget.setSize(pickWidth, pickHeight) } } -} - -export default Canvas3D \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-canvas3d/controls/trackball.ts b/src/mol-canvas3d/controls/trackball.ts index dc2f49ecb..9703eac2d 100644 --- a/src/mol-canvas3d/controls/trackball.ts +++ b/src/mol-canvas3d/controls/trackball.ts @@ -25,7 +25,7 @@ export const DefaultTrackballControlsProps = { staticMoving: true, dynamicDampingFactor: 0.2, - minDistance: 0, + minDistance: 0.01, maxDistance: Infinity } export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProps> @@ -293,7 +293,6 @@ namespace TrackballControls { return { viewport, - // target, get dynamicDampingFactor() { return dynamicDampingFactor }, set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value }, diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index 7bc0e6d68..9dd28a6b2 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -5,7 +5,7 @@ */ import { Structure, StructureElement, Unit } from '../structure'; -import { now } from 'mol-task'; +import { now } from 'mol-util/now'; import { ElementIndex } from '../model'; import { Link } from '../structure/unit/links'; diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 4aca923fe..b519cc8d7 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -5,7 +5,7 @@ */ import { StateTree, StateSelection, Transformer, Transform } from 'mol-state'; -import Canvas3D from 'mol-canvas3d/canvas3d'; +import { Canvas3D } from 'mol-canvas3d/canvas3d'; import { StateTransforms } from './state/transforms'; import { PluginStateObjects as SO } from './state/objects'; import { RxEventHelper } from 'mol-util/rx-event-helper'; @@ -141,8 +141,7 @@ export class PluginContext { if (!sel.length) return; const center = (sel[0].obj! as SO.Structure).data.boundary.sphere.center; - console.log({ sel, center, rc: this.canvas3d.reprCount }); - this.canvas3d.center(center); + this.canvas3d.camera.setState({ target: center }); this.canvas3d.requestDraw(true); } diff --git a/src/mol-task/execution/observable.ts b/src/mol-task/execution/observable.ts index 5b8e82f89..5b8d15a07 100644 --- a/src/mol-task/execution/observable.ts +++ b/src/mol-task/execution/observable.ts @@ -7,7 +7,7 @@ import { Task } from '../task' import { RuntimeContext } from './runtime-context' import { Progress } from './progress' -import { now } from '../util/now' +import { now } from 'mol-util/now'; import { Scheduler } from '../util/scheduler' import { UserTiming } from '../util/user-timing' diff --git a/src/mol-task/index.ts b/src/mol-task/index.ts index 2ef6489c9..62d09e846 100644 --- a/src/mol-task/index.ts +++ b/src/mol-task/index.ts @@ -7,9 +7,8 @@ import { Task } from './task' import { RuntimeContext } from './execution/runtime-context' import { Progress } from './execution/progress' -import { now } from './util/now' import { Scheduler } from './util/scheduler' import { MultistepTask } from './util/multistep' import { chunkedSubtask } from './util/chunked' -export { Task, RuntimeContext, Progress, now, Scheduler, MultistepTask, chunkedSubtask } \ No newline at end of file +export { Task, RuntimeContext, Progress, Scheduler, MultistepTask, chunkedSubtask } \ No newline at end of file diff --git a/src/mol-task/util/chunked.ts b/src/mol-task/util/chunked.ts index d091e5706..88233dd17 100644 --- a/src/mol-task/util/chunked.ts +++ b/src/mol-task/util/chunked.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { now } from './now' +import { now } from 'mol-util/now'; import { RuntimeContext } from '../execution/runtime-context' type UniformlyChunkedFn<S> = (chunkSize: number, state: S) => number diff --git a/src/mol-task/util/now.ts b/src/mol-util/now.ts similarity index 83% rename from src/mol-task/util/now.ts rename to src/mol-util/now.ts index f4961d217..abf35487f 100644 --- a/src/mol-task/util/now.ts +++ b/src/mol-util/now.ts @@ -7,7 +7,7 @@ declare var process: any; declare var window: any; -const now: () => number = (function () { +const now: () => now.Timestamp = (function () { if (typeof window !== 'undefined' && window.performance) { const perf = window.performance; return () => perf.now(); @@ -23,4 +23,8 @@ const now: () => number = (function () { } }()); +namespace now { + export type Timestamp = number & { '@type': 'now-timestamp' } +} + export { now } \ No newline at end of file diff --git a/src/mol-util/performance-monitor.ts b/src/mol-util/performance-monitor.ts index 614b19a29..615bf93ba 100644 --- a/src/mol-util/performance-monitor.ts +++ b/src/mol-util/performance-monitor.ts @@ -5,7 +5,7 @@ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. */ -import { now } from 'mol-task/util/now' +import { now } from 'mol-util/now'; export class PerformanceMonitor { private starts = new Map<string, number>(); diff --git a/src/mol-util/uuid.ts b/src/mol-util/uuid.ts index 4c77f7de6..7972eaf9f 100644 --- a/src/mol-util/uuid.ts +++ b/src/mol-util/uuid.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { now } from 'mol-task' +import { now } from 'mol-util/now'; type UUID = string & { '@type': 'uuid' } diff --git a/src/perf-tests/tasks.ts b/src/perf-tests/tasks.ts index f0c0eb86b..068c47b52 100644 --- a/src/perf-tests/tasks.ts +++ b/src/perf-tests/tasks.ts @@ -1,5 +1,5 @@ import * as B from 'benchmark' -import { now } from 'mol-task/util/now' +import { now } from 'mol-util/now'; import { Scheduler } from 'mol-task/util/scheduler' export namespace Tasks { diff --git a/src/servers/model/preprocess/parallel.ts b/src/servers/model/preprocess/parallel.ts index 2e24f3e0c..1b600b5b0 100644 --- a/src/servers/model/preprocess/parallel.ts +++ b/src/servers/model/preprocess/parallel.ts @@ -6,7 +6,7 @@ import * as path from 'path' import * as cluster from 'cluster' -import { now } from 'mol-task'; +import { now } from 'mol-util/now'; import { PerformanceMonitor } from 'mol-util/performance-monitor'; import { preprocessFile } from './preprocess'; import { createModelPropertiesProvider } from '../property-provider'; diff --git a/src/servers/model/server/api-local.ts b/src/servers/model/server/api-local.ts index f1d5e2f1c..83fe022eb 100644 --- a/src/servers/model/server/api-local.ts +++ b/src/servers/model/server/api-local.ts @@ -10,7 +10,7 @@ import { JobManager, Job } from './jobs'; import { ConsoleLogger } from 'mol-util/console-logger'; import { resolveJob } from './query'; import { StructureCache } from './structure-wrapper'; -import { now } from 'mol-task'; +import { now } from 'mol-util/now'; import { PerformanceMonitor } from 'mol-util/performance-monitor'; import { QueryName } from './api'; diff --git a/src/servers/model/server/query.ts b/src/servers/model/server/query.ts index 0c80ae56c..f10795964 100644 --- a/src/servers/model/server/query.ts +++ b/src/servers/model/server/query.ts @@ -8,7 +8,8 @@ import { Column } from 'mol-data/db'; import { CifWriter } from 'mol-io/writer/cif'; import { StructureQuery, StructureSelection, Structure } from 'mol-model/structure'; import { encode_mmCIF_categories } from 'mol-model/structure/export/mmcif'; -import { now, Progress } from 'mol-task'; +import { Progress } from 'mol-task'; +import { now } from 'mol-util/now'; import { ConsoleLogger } from 'mol-util/console-logger'; import { PerformanceMonitor } from 'mol-util/performance-monitor'; import Config from '../config'; diff --git a/src/servers/model/utils/fetch-props-pdbe.ts b/src/servers/model/utils/fetch-props-pdbe.ts index f28d9946b..76d77f662 100644 --- a/src/servers/model/utils/fetch-props-pdbe.ts +++ b/src/servers/model/utils/fetch-props-pdbe.ts @@ -9,7 +9,7 @@ import * as fs from 'fs' import * as path from 'path' import * as argparse from 'argparse' import { makeDir } from 'mol-util/make-dir'; -import { now } from 'mol-task'; +import { now } from 'mol-util/now'; import { PerformanceMonitor } from 'mol-util/performance-monitor'; const cmdParser = new argparse.ArgumentParser({ -- GitLab