diff --git a/src/apps/canvas/component/structure-view.tsx b/src/apps/canvas/component/structure-view.tsx index bbf3263c86bce921fe8b4a49a2bf87e67f86d483..d987ec1fe9e5a0839d243c4855bc03253b06ef9b 100644 --- a/src/apps/canvas/component/structure-view.tsx +++ b/src/apps/canvas/component/structure-view.tsx @@ -174,7 +174,7 @@ export class StructureViewComponent extends React.Component<StructureViewCompone return <div key={i}> <RepresentationComponent repr={structureRepresentations[k] as Representation<any>} - canvas3d={structureView.viewer} + canvas3d={structureView.canvas3d} app={structureView.app} /> </div> diff --git a/src/apps/canvas/component/viewport.tsx b/src/apps/canvas/component/viewport.tsx index 68a243fa3b21f7f8cfa5b1646254d639a7d1dae8..5a1f97077cd0a81e81dc957bd053df70b32ed1ee 100644 --- a/src/apps/canvas/component/viewport.tsx +++ b/src/apps/canvas/component/viewport.tsx @@ -11,6 +11,7 @@ import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci'; import { labelFirst } from 'mol-theme/label'; import { ButtonsType } from 'mol-util/input/input-observer'; import { throttleTime } from 'rxjs/operators' +import { CombinedCameraMode } from 'mol-canvas3d/camera/combined'; interface ViewportProps { app: App @@ -20,6 +21,7 @@ interface ViewportState { noWebGl: boolean pickingInfo: string taskInfo: string + cameraMode: CombinedCameraMode } export class Viewport extends React.Component<ViewportProps, ViewportState> { @@ -29,7 +31,8 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { state: ViewportState = { noWebGl: false, pickingInfo: '', - taskInfo: '' + taskInfo: '', + cameraMode: 'perspective' }; handleResize() { @@ -42,6 +45,8 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { } this.handleResize() + this.setState({ cameraMode: this.props.app.canvas3d.camera.mode }) + const canvas3d = this.props.app.canvas3d canvas3d.input.resize.subscribe(() => this.handleResize()) @@ -117,6 +122,30 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { > {this.state.pickingInfo} </div> + <div + style={{ + position: 'absolute', + bottom: 10, + right: 10, + padding: 10, + color: 'lightgrey', + background: 'rgba(0, 0, 0, 0.2)' + }} + > + <span>Camera mode </span> + <select + value={this.state.cameraMode} + style={{width: '150'}} + onChange={e => { + const cameraMode = e.target.value as CombinedCameraMode + this.props.app.canvas3d.camera.mode = cameraMode + this.setState({ cameraMode }) + }} + > + <option value='perspective'>Perspective</option> + <option value='orthographic'>Orthographic</option> + </select> + </div> { this.state.taskInfo ? <div style={{ diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts index dd72f8aaa98406272ed4a2d583781a38dc97743e..cbc1a3a568a9c81c31a702134dce10a4c80471d7 100644 --- a/src/apps/canvas/index.ts +++ b/src/apps/canvas/index.ts @@ -25,8 +25,8 @@ if (pdbId) app.loadPdbIdOrMmcifUrl(pdbId, { assemblyId }) // app.loadPdbIdOrMmcifUrl('http://localhost:8091/ngl/data/1crn.cif') -// app.loadPdbIdOrMmcifUrl('3pqr') -// app.loadCcp4Url('http://localhost:8091/ngl/data/3pqr-mode0.ccp4') +app.loadPdbIdOrMmcifUrl('3pqr') +app.loadCcp4Url('http://localhost:8091/ngl/data/3pqr-mode0.ccp4') // app.loadPdbIdOrMmcifUrl('1lee') // app.loadCcp4Url('http://localhost:8091/ngl/data/1lee.ccp4') diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index 8be00f5eda814f0e67bb04b4bffac7a38fe025c1..6d2313897c0aa3e7dbc00f2c6257848083295541 100644 --- a/src/apps/canvas/structure-view.ts +++ b/src/apps/canvas/structure-view.ts @@ -29,7 +29,7 @@ import { App } from './app'; export interface StructureView { readonly app: App - readonly viewer: Canvas3D + readonly canvas3d: Canvas3D readonly label: string readonly models: ReadonlyArray<Model> @@ -63,7 +63,7 @@ interface StructureViewProps { symmetryFeatureId?: number } -export async function StructureView(app: App, viewer: Canvas3D, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> { +export async function StructureView(app: App, canvas3d: Canvas3D, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> { const active: { [k: string]: boolean } = { cartoon: true, point: false, @@ -208,17 +208,17 @@ export async function StructureView(app: App, viewer: Canvas3D, models: Readonly console.log('createStructureRepr') for (const k in structureRepresentations) { if (active[k]) { - const p = { webgl: viewer.webgl } + const p = { webgl: canvas3d.webgl } await app.runTask(structureRepresentations[k].createOrUpdate(p, structure).run( progress => app.log(progress) ), 'Create/update representation') - viewer.add(structureRepresentations[k]) + canvas3d.add(structureRepresentations[k]) } else { - viewer.remove(structureRepresentations[k]) + canvas3d.remove(structureRepresentations[k]) } } - viewer.center(structure.boundary.sphere.center) + canvas3d.center(structure.boundary.sphere.center) // const mb = MeshBuilder.create() // mb.setGroup(0) @@ -246,11 +246,11 @@ export async function StructureView(app: App, viewer: Canvas3D, models: Readonly polymerSphere.destroy() } - viewer.add(polymerSphere) + canvas3d.add(polymerSphere) updated.next(null) - viewer.requestDraw(true) - console.log('stats', viewer.stats) + canvas3d.requestDraw(true) + console.log('stats', canvas3d.stats) } async function createSymmetryRepr() { @@ -266,25 +266,25 @@ export async function StructureView(app: App, viewer: Canvas3D, models: Readonly // colorGranularity: colorTheme.granularity, // }).run() await symmetryAxes.createOrUpdate({}, axesShape).run() - viewer.add(symmetryAxes) + canvas3d.add(symmetryAxes) } else { - viewer.remove(symmetryAxes) + canvas3d.remove(symmetryAxes) } } else { - viewer.remove(symmetryAxes) + canvas3d.remove(symmetryAxes) } } else { - viewer.remove(symmetryAxes) + canvas3d.remove(symmetryAxes) } updated.next(null) - viewer.requestDraw(true) + canvas3d.requestDraw(true) } await setModel(0, props.assemblyId, props.symmetryFeatureId) return { app, - viewer, + canvas3d, get label() { return label }, models, @@ -312,12 +312,12 @@ export async function StructureView(app: App, viewer: Canvas3D, models: Readonly destroy: () => { for (const k in structureRepresentations) { - viewer.remove(structureRepresentations[k]) + canvas3d.remove(structureRepresentations[k]) structureRepresentations[k].destroy() } - viewer.remove(polymerSphere) - viewer.remove(symmetryAxes) - viewer.requestDraw(true) + canvas3d.remove(polymerSphere) + canvas3d.remove(symmetryAxes) + canvas3d.requestDraw(true) polymerSphere.destroy() symmetryAxes.destroy() diff --git a/src/mol-canvas3d/camera/base.ts b/src/mol-canvas3d/camera/base.ts index 27c26287bd9386fa6bd16c5338ce7d81c4aebad1..3350d411fc58364279b9f644ae5a4cdf1be60092 100644 --- a/src/mol-canvas3d/camera/base.ts +++ b/src/mol-canvas3d/camera/base.ts @@ -25,6 +25,7 @@ export const DefaultCameraProps = { direction: Vec3.create(0, 0, -1), up: Vec3.create(0, 1, 0), viewport: Viewport.create(-1, -1, 1, 1), + target: Vec3.create(0, 0, 0), near: 0.1, far: 10000, diff --git a/src/mol-canvas3d/camera/combined.ts b/src/mol-canvas3d/camera/combined.ts new file mode 100644 index 0000000000000000000000000000000000000000..5432afa9b7b614eb49c0b061947a02657f5a1057 --- /dev/null +++ b/src/mol-canvas3d/camera/combined.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PerspectiveCamera, } from './perspective'; +import { OrthographicCamera } from './orthographic'; +import { Camera, DefaultCameraProps } from './base'; +import { Vec3 } from 'mol-math/linear-algebra'; + +// TODO + +// updateZoom () { +// const fov = degToRad(this.perspectiveCamera.fov) +// const height = 2 * Math.tan(fov / 2) * -this.camera.position.z +// this.orthographicCamera.zoom = this.height / height +// } + +export type CombinedCameraMode = 'perspective' | 'orthographic' + +export interface CombinedCamera extends Camera { + target: Vec3 + fov: number + zoom: number + mode: CombinedCameraMode +} + +export const DefaultCombinedCameraProps = { + ...DefaultCameraProps, + target: Vec3.zero(), + fov: Math.PI / 4, + zoom: 1, + mode: 'perspective' as CombinedCameraMode +} +export type CombinedCameraProps = Partial<typeof DefaultCombinedCameraProps> + +export namespace CombinedCamera { + export function create(props: CombinedCameraProps = {}): CombinedCamera { + const { zoom, fov, mode, target: t } = { ...DefaultCombinedCameraProps, ...props }; + const target = Vec3.create(t[0], t[1], t[2]) + const camera = { ...Camera.create(props), zoom, fov, mode, target } + update(camera) + + return camera + } + + // const center = Vec3.zero() + export function update(camera: CombinedCamera) { + // console.log(camera.position, camera.direction) + // Vec3.add(center, camera.position, camera.direction) + + const height = 2 * Math.tan(camera.fov / 2) * Vec3.distance(camera.position, camera.target) + camera.zoom = camera.viewport.height / height + + switch (camera.mode) { + case 'orthographic': OrthographicCamera.update(camera); break + case 'perspective': PerspectiveCamera.update(camera); break + } + } +} \ No newline at end of file diff --git a/src/mol-canvas3d/camera/orthographic.ts b/src/mol-canvas3d/camera/orthographic.ts index 0ffdd02fcbce683e436c0030ffe0517135c6ceda..26321535ff6021972f2cfda45c4004039aa3698b 100644 --- a/src/mol-canvas3d/camera/orthographic.ts +++ b/src/mol-canvas3d/camera/orthographic.ts @@ -1 +1,58 @@ -// TODO \ No newline at end of file +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Mat4, Vec3 } from 'mol-math/linear-algebra' +import { DefaultCameraProps, Camera } from './base' + +export interface OrthographicCamera extends Camera { + zoom: number +} + +export const DefaultOrthographicCameraProps = { + ...DefaultCameraProps, + zoom: 1 +} +export type OrthographicCameraProps = Partial<typeof DefaultOrthographicCameraProps> + +export namespace OrthographicCamera { + export function create(props: OrthographicCameraProps = {}): OrthographicCamera { + const { zoom } = { ...DefaultOrthographicCameraProps, ...props }; + const camera = { ...Camera.create(props), zoom } + update(camera) + + return camera + } + + const center = Vec3.zero() + export function update(camera: OrthographicCamera) { + const { viewport, zoom } = camera + + const fullLeft = (viewport.width - viewport.x) / -2 + const fullRight = (viewport.width - viewport.x) / 2 + const fullTop = (viewport.height - viewport.y) / 2 + const fullBottom = (viewport.height - viewport.y) / -2 + + const dx = (fullRight - fullLeft) / (2 * zoom) + const dy = (fullTop - fullBottom) / (2 * zoom) + const cx = (fullRight + fullLeft) / 2 + const cy = (fullTop + fullBottom) / 2 + + const left = cx - dx + const right = cx + dx + const top = cy + dy + const bottom = cy - dy + + // build projection matrix + Mat4.ortho(camera.projection, left, right, bottom, top, Math.abs(camera.near), Math.abs(camera.far)) + + // build view matrix + Vec3.add(center, camera.position, camera.direction) + Mat4.lookAt(camera.view, camera.position, center, camera.up) + + // update projection * view and invert + Camera.update(camera) + } +} \ No newline at end of file diff --git a/src/mol-canvas3d/camera/perspective.ts b/src/mol-canvas3d/camera/perspective.ts index 9fb89fe070af6e0c5d4410dc1f9b91adecef98a6..b00f427aa776900974d2aec42ce4902a3dca7911 100644 --- a/src/mol-canvas3d/camera/perspective.ts +++ b/src/mol-canvas3d/camera/perspective.ts @@ -8,24 +8,19 @@ import { Mat4, Vec3 } from 'mol-math/linear-algebra' import { DefaultCameraProps, Camera } from './base' export interface PerspectiveCamera extends Camera { - fov: number, + fov: number } export const DefaultPerspectiveCameraProps = { + ...DefaultCameraProps, fov: Math.PI / 4, - ...DefaultCameraProps } export type PerspectiveCameraProps = Partial<typeof DefaultPerspectiveCameraProps> export namespace PerspectiveCamera { export function create(props: PerspectiveCameraProps = {}): PerspectiveCamera { - let { fov } = { ...DefaultPerspectiveCameraProps, ...props }; - - const camera = { - ...Camera.create(props), - fov - } - + const { fov } = { ...DefaultPerspectiveCameraProps, ...props } + const camera = { ...Camera.create(props), fov } update(camera) return camera diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index f4f40be83da76dce59f98a8e8a572747dcad4076..78befa72d895ba5a282d520e458bba73d803863c 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -14,7 +14,6 @@ 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, getGLContext, WebGLContext } from 'mol-gl/webgl/context'; import { Representation } from 'mol-geo/representation'; @@ -25,6 +24,7 @@ import { PickingId, decodeIdRGB } from 'mol-geo/geometry/picking'; import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; import { Color } from 'mol-util/color'; +import { CombinedCamera } from './camera/combined'; interface Canvas3D { webgl: WebGLContext, @@ -53,6 +53,7 @@ interface Canvas3D { handleResize: () => void resetCamera: () => void + camera: CombinedCamera downloadScreenshot: () => void getImageData: (variant: RenderVariant) => ImageData @@ -71,11 +72,16 @@ namespace Canvas3D { const didDraw = new BehaviorSubject(0) const input = InputObserver.create(canvas) - const camera = PerspectiveCamera.create({ + const camera = CombinedCamera.create({ near: 0.1, far: 10000, - position: Vec3.create(0, 0, 50) + position: Vec3.create(0, 0, 50), + mode: 'orthographic' }) + // const camera = OrthographicCamera.create({ + // zoom: 8, + // position: Vec3.create(0, 0, 50) + // }) // camera.lookAt(Vec3.create(0, 0, 0)) const gl = getGLContext(canvas, { @@ -173,7 +179,7 @@ namespace Canvas3D { } let didRender = false controls.update() - PerspectiveCamera.update(camera) + CombinedCamera.update(camera) 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) @@ -260,6 +266,7 @@ namespace Canvas3D { center: (p: Vec3) => { Vec3.set(controls.target, p[0], p[1], p[2]) + Vec3.set(camera.target, p[0], p[1], p[2]) }, hide: (repr: Representation<any>) => { @@ -313,6 +320,7 @@ namespace Canvas3D { resetCamera: () => { // TODO }, + camera, downloadScreenshot: () => { // TODO }, diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index c74acbe2a8f7fdb681880bfa0afe0ac6b63cc3b4..24cee288c53f8661251f77b424229dc7627ae589 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/src/mol-math/linear-algebra/3d/mat4.ts @@ -718,9 +718,9 @@ namespace Mat4 { * Generates a frustum matrix with the given bounds */ export function frustum(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number) { - let rl = 1 / (right - left); - let tb = 1 / (top - bottom); - let nf = 1 / (near - far); + const rl = 1 / (right - left); + const tb = 1 / (top - bottom); + const nf = 1 / (near - far); out[0] = (near * 2) * rl; out[1] = 0; out[2] = 0; @@ -744,8 +744,8 @@ namespace Mat4 { * Generates a perspective projection matrix with the given bounds */ export function perspective(out: Mat4, fovy: number, aspect: number, near: number, far: number) { - let f = 1.0 / Math.tan(fovy / 2); - let nf = 1 / (near - far); + const f = 1.0 / Math.tan(fovy / 2); + const nf = 1 / (near - far); out[0] = f / aspect; out[1] = 0; out[2] = 0; @@ -769,9 +769,9 @@ namespace Mat4 { * Generates a orthogonal projection matrix with the given bounds */ export function ortho(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number) { - let lr = 1 / (left - right); - let bt = 1 / (bottom - top); - let nf = 1 / (near - far); + const lr = 1 / (left - right); + const bt = 1 / (bottom - top); + const nf = 1 / (near - far); out[0] = -2 * lr; out[1] = 0; out[2] = 0; @@ -796,15 +796,15 @@ namespace Mat4 { */ export function lookAt(out: Mat4, eye: Vec3, center: Vec3, up: Vec3) { let x0, x1, x2, y0, y1, y2, z0, z1, z2, len; - let eyex = eye[0]; - let eyey = eye[1]; - let eyez = eye[2]; - let upx = up[0]; - let upy = up[1]; - let upz = up[2]; - let centerx = center[0]; - let centery = center[1]; - let centerz = center[2]; + const eyex = eye[0]; + const eyey = eye[1]; + const eyez = eye[2]; + const upx = up[0]; + const upy = up[1]; + const upz = up[2]; + const centerx = center[0]; + const centery = center[1]; + const centerz = center[2]; if (Math.abs(eyex - centerx) < EPSILON.Value && Math.abs(eyey - centery) < EPSILON.Value &&