Skip to content
Snippets Groups Projects
Commit 4476d914 authored by David Sehnal's avatar David Sehnal
Browse files

mol-canvas3d: refactored Camera

parent 39615dd5
No related branches found
No related tags found
No related merge requests found
......@@ -11,7 +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';
import { Camera } from 'mol-canvas3d/camera';
import { ColorParamComponent } from 'mol-app/component/parameter/color';
import { Color } from 'mol-util/color';
import { ParamDefinition as PD } from 'mol-util/param-definition'
......@@ -24,7 +24,7 @@ interface ViewportState {
noWebGl: boolean
pickingInfo: string
taskInfo: string
cameraMode: CombinedCameraMode
cameraMode: Camera.Mode
backgroundColor: Color
}
......@@ -148,7 +148,7 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
value={this.state.cameraMode}
style={{width: '150'}}
onChange={e => {
const p = { cameraMode: e.target.value as CombinedCameraMode }
const p = { cameraMode: e.target.value as Camera.Mode }
this.props.app.canvas3d.setProps(p)
this.setState(p)
}}
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3, Vec4 } from 'mol-math/linear-algebra'
import { Viewport, cameraLookAt, cameraProject, cameraUnproject } from './camera/util';
import { Object3D } from 'mol-gl/object3d';
export { Camera }
class Camera implements Object3D {
readonly view: Mat4 = Mat4.identity();
readonly projection: Mat4 = Mat4.identity();
readonly projectionView: Mat4 = Mat4.identity();
readonly inverseProjectionView: Mat4 = Mat4.identity();
readonly viewport: Viewport;
readonly state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
get position() { return this.state.position; }
set position(v: Vec3) { Vec3.copy(this.state.position, v); }
get direction() { return this.state.direction; }
set direction(v: Vec3) { Vec3.copy(this.state.direction, v); }
get up() { return this.state.up; }
set up(v: Vec3) { Vec3.copy(this.state.up, v); }
get target() { return this.state.target; }
set target(v: Vec3) { Vec3.copy(this.state.target, v); }
updateMatrices() {
const snapshot = this.state as Camera.Snapshot;
const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target);
snapshot.zoom = this.viewport.height / height;
switch (this.state.mode) {
case 'orthographic': updateOrtho(this); break;
case 'perspective': updatePers(this); break;
default: throw new Error('unknown camera mode');
}
Mat4.mul(this.projectionView, this.projection, this.view)
Mat4.invert(this.inverseProjectionView, this.projectionView)
}
setState(snapshot?: Partial<Camera.Snapshot>) {
Camera.copySnapshot(this.state, snapshot);
}
getSnapshot() {
const ret = Camera.createDefaultSnapshot();
Camera.copySnapshot(ret, this.state);
return ret;
}
lookAt(target: Vec3) {
cameraLookAt(this.direction, this.up, this.position, target);
}
translate(v: Vec3) {
Vec3.add(this.position, this.position, v)
}
project(out: Vec4, point: Vec3) {
return cameraProject(out, point, this.viewport, this.projectionView)
}
unproject(out: Vec3, point: Vec3) {
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView)
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
this.viewport = viewport;
Camera.copySnapshot(this.state, state);
}
}
namespace Camera {
export type Mode = 'perspective' | 'orthographic'
export function createDefaultSnapshot(): Snapshot {
return {
mode: 'perspective',
position: Vec3.zero(),
direction: Vec3.create(0, 0, -1),
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
near: 0.1,
far: 10000,
fogNear: 0.1,
fogFar: 10000,
fov: Math.PI / 4,
zoom: 1
};
}
export interface Snapshot {
mode: Mode,
position: Vec3,
direction: Vec3,
up: Vec3,
target: Vec3,
near: number,
far: number,
fogNear: number,
fogFar: number,
fov: number,
zoom: number
}
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
if (!source) return;
if (typeof source.mode !== 'undefined') out.mode = source.mode;
if (typeof source.position !== 'undefined') Vec3.copy(out.position, source.position);
if (typeof source.direction !== 'undefined') Vec3.copy(out.direction, source.direction);
if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up);
if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target);
if (typeof source.near !== 'undefined') out.near = source.near;
if (typeof source.far !== 'undefined') out.far = source.far;
if (typeof source.fogNear !== 'undefined') out.fogNear = source.fogNear;
if (typeof source.fogFar !== 'undefined') out.fogFar = source.fogFar;
if (typeof source.fov !== 'undefined') out.fov = source.fov;
if (typeof source.zoom !== 'undefined') out.zoom = source.zoom;
}
}
const _center = Vec3.zero();
function updateOrtho(camera: Camera) {
const { viewport, state: { zoom, near, far } } = 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(near), Math.abs(far))
// build view matrix
Vec3.add(_center, camera.position, camera.direction)
Mat4.lookAt(camera.view, camera.position, _center, camera.up)
}
function updatePers(camera: Camera) {
const aspect = camera.viewport.width / camera.viewport.height
const { fov, near, far } = camera.state;
// build projection matrix
Mat4.perspective(camera.projection, fov, aspect, Math.abs(near), Math.abs(far))
// build view matrix
Vec3.add(_center, camera.position, camera.direction)
Mat4.lookAt(camera.view, camera.position, _center, camera.up)
}
\ 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, Vec4 } from 'mol-math/linear-algebra'
import { cameraProject, cameraUnproject, cameraLookAt, Viewport } from './util';
import { Object3D } from 'mol-gl/object3d';
export interface Camera extends Object3D {
readonly projection: Mat4,
readonly projectionView: Mat4,
readonly inverseProjectionView: Mat4,
readonly viewport: Viewport,
near: number,
far: number,
fogNear: number,
fogFar: number,
}
export const DefaultCameraProps = {
position: Vec3.zero(),
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,
fogNear: 0.1,
fogFar: 10000,
}
export type CameraProps = typeof DefaultCameraProps
export namespace Camera {
export function create(props?: Partial<CameraProps>): Camera {
const p = { ...DefaultCameraProps, ...props };
const { view, position, direction, up } = Object3D.create()
Vec3.copy(position, p.position)
Vec3.copy(direction, p.direction)
Vec3.copy(up, p.up)
const projection = Mat4.identity()
const viewport = Viewport.clone(p.viewport)
const projectionView = Mat4.identity()
const inverseProjectionView = Mat4.identity()
return {
projection,
projectionView,
inverseProjectionView,
viewport,
view,
position,
direction,
up,
near: p.near,
far: p.far,
fogNear: p.fogNear,
fogFar: p.fogFar,
}
}
export function update (camera: Camera) {
Mat4.mul(camera.projectionView, camera.projection, camera.view)
Mat4.invert(camera.inverseProjectionView, camera.projectionView)
return camera
}
export function lookAt (camera: Camera, target: Vec3) {
cameraLookAt(camera.direction, camera.up, camera.position, target)
}
export function reset (camera: Camera, props: CameraProps) {
Vec3.copy(camera.position, props.position)
Vec3.copy(camera.direction, props.direction)
Vec3.copy(camera.up, props.up)
Mat4.setIdentity(camera.view)
Mat4.setIdentity(camera.projection)
Mat4.setIdentity(camera.projectionView)
Mat4.setIdentity(camera.inverseProjectionView)
}
export function translate (camera: Camera, v: Vec3) {
Vec3.add(camera.position, camera.position, v)
}
export function project (camera: Camera, out: Vec4, point: Vec3) {
return cameraProject(out, point, camera.viewport, camera.projectionView)
}
export function unproject (camera: Camera, out: Vec3, point: Vec3) {
return cameraUnproject(out, point, camera.viewport, camera.inverseProjectionView)
}
}
\ 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 { PerspectiveCamera, } from './perspective';
import { OrthographicCamera } from './orthographic';
import { Camera, DefaultCameraProps } from './base';
import { Vec3 } from 'mol-math/linear-algebra';
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
}
export function update(camera: CombinedCamera) {
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
/**
* 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
/**
* 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 PerspectiveCamera extends Camera {
fov: number
}
export const DefaultPerspectiveCameraProps = {
...DefaultCameraProps,
fov: Math.PI / 4,
}
export type PerspectiveCameraProps = Partial<typeof DefaultPerspectiveCameraProps>
export namespace PerspectiveCamera {
export function create(props: PerspectiveCameraProps = {}): PerspectiveCamera {
const { fov } = { ...DefaultPerspectiveCameraProps, ...props }
const camera = { ...Camera.create(props), fov }
update(camera)
return camera
}
const center = Vec3.zero()
export function update(camera: PerspectiveCamera) {
const aspect = camera.viewport.width / camera.viewport.height
// build projection matrix
Mat4.perspective(camera.projection, camera.fov, aspect, 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
......@@ -24,11 +24,11 @@ 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, CombinedCameraMode } from './camera/combined';
import { Camera } from './camera';
export const DefaultCanvas3DProps = {
cameraPosition: Vec3.create(0, 0, 50),
cameraMode: 'perspective' as CombinedCameraMode,
cameraMode: 'perspective' as Camera.Mode,
backgroundColor: Color(0x000000),
}
export type Canvas3DProps = typeof DefaultCanvas3DProps
......@@ -60,7 +60,7 @@ interface Canvas3D {
handleResize: () => void
resetCamera: () => void
readonly camera: CombinedCamera
readonly camera: Camera
downloadScreenshot: () => void
getImageData: (variant: RenderVariant) => ImageData
setProps: (props: Partial<Canvas3DProps>) => void
......@@ -84,7 +84,7 @@ namespace Canvas3D {
const didDraw = new BehaviorSubject(0)
const input = InputObserver.create(canvas)
const camera = CombinedCamera.create({
const camera = new Camera({
near: 0.1,
far: 10000,
position: Vec3.clone(p.cameraPosition),
......@@ -188,7 +188,7 @@ namespace Canvas3D {
}
let didRender = false
controls.update()
CombinedCamera.update(camera)
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)
......@@ -273,9 +273,9 @@ namespace Canvas3D {
return {
webgl,
center: (p: Vec3) => {
Vec3.set(controls.target, p[0], p[1], p[2])
Vec3.set(camera.target, p[0], p[1], p[2])
center: (target: Vec3) => {
// Vec3.set(controls.target, p[0], p[1], p[2])
camera.setState({ target })
},
hide: (repr: Representation<any>) => {
......@@ -345,8 +345,8 @@ namespace Canvas3D {
identified,
didDraw,
setProps: (props: Partial<Canvas3DProps>) => {
if (props.cameraMode !== undefined && props.cameraMode !== camera.mode) {
camera.mode = props.cameraMode
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
camera.setState({ mode: props.cameraMode })
}
if (props.backgroundColor !== undefined && props.backgroundColor !== renderer.props.clearColor) {
renderer.setClearColor(props.backgroundColor)
......@@ -357,7 +357,7 @@ namespace Canvas3D {
get props() {
return {
cameraPosition: Vec3.clone(camera.position),
cameraMode: camera.mode,
cameraMode: camera.state.mode,
backgroundColor: renderer.props.clearColor
}
},
......
......@@ -2,6 +2,7 @@
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
/*
......@@ -16,7 +17,6 @@ import { Object3D } from 'mol-gl/object3d';
export const DefaultTrackballControlsProps = {
noScroll: true,
target: [0, 0, 0] as Vec3,
rotateSpeed: 3.0,
zoomSpeed: 4.0,
......@@ -32,7 +32,6 @@ export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProp
interface TrackballControls {
viewport: Viewport
target: Vec3
dynamicDampingFactor: number
rotateSpeed: number
......@@ -45,11 +44,11 @@ interface TrackballControls {
}
namespace TrackballControls {
export function create (input: InputObserver, object: Object3D, props: TrackballControlsProps = {}): TrackballControls {
export function create (input: InputObserver, object: Object3D & { target: Vec3 }, props: TrackballControlsProps = {}): TrackballControls {
const p = { ...DefaultTrackballControlsProps, ...props }
const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
const target: Vec3 = Vec3.clone(p.target)
const target: Vec3 = object.target
let { rotateSpeed, zoomSpeed, panSpeed } = p
let { staticMoving, dynamicDampingFactor } = p
......@@ -294,7 +293,7 @@ namespace TrackballControls {
return {
viewport,
target,
// target,
get dynamicDampingFactor() { return dynamicDampingFactor },
set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value },
......
......@@ -6,7 +6,7 @@
import { createGl } from './gl.shim';
import { PerspectiveCamera } from 'mol-canvas3d/camera/perspective';
import { Camera } from 'mol-canvas3d/camera';
import { Vec3, Mat4 } from 'mol-math/linear-algebra';
import { ValueCell } from 'mol-util';
......@@ -36,7 +36,7 @@ import { Sphere3D } from 'mol-math/geometry';
function createRenderer(gl: WebGLRenderingContext) {
const ctx = createContext(gl)
const camera = PerspectiveCamera.create({
const camera = new Camera({
near: 0.01,
far: 10000,
position: Vec3.create(0, 0, 50)
......
......@@ -6,7 +6,7 @@
// import { Vec3, Mat4 } from 'mol-math/linear-algebra'
import { Viewport } from 'mol-canvas3d/camera/util';
import { Camera } from 'mol-canvas3d/camera/base';
import { Camera } from 'mol-canvas3d/camera';
import Scene from './scene';
import { WebGLContext, createImageData } from './webgl/context';
......@@ -100,8 +100,8 @@ namespace Renderer {
uHighlightColor: ValueCell.create(Vec3.clone(highlightColor)),
uSelectColor: ValueCell.create(Vec3.clone(selectColor)),
uFogNear: ValueCell.create(camera.near),
uFogFar: ValueCell.create(camera.far / 50),
uFogNear: ValueCell.create(camera.state.near),
uFogFar: ValueCell.create(camera.state.far / 50),
uFogColor: ValueCell.create(Vec3.clone(fogColor)),
}
......@@ -157,8 +157,8 @@ namespace Renderer {
ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection))
ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection))
ValueCell.update(globalUniforms.uFogFar, camera.fogFar)
ValueCell.update(globalUniforms.uFogNear, camera.fogNear)
ValueCell.update(globalUniforms.uFogFar, camera.state.fogFar)
ValueCell.update(globalUniforms.uFogNear, camera.state.fogNear)
currentProgramId = -1
......
......@@ -6,7 +6,7 @@
import { State, StateTree } from 'mol-state';
import { PluginStateObjects as SO } from './state/objects';
import { CombinedCamera } from 'mol-canvas3d/camera/combined';
import { Camera } from 'mol-canvas3d/camera';
export { PluginState }
......@@ -19,7 +19,7 @@ class PluginState {
data: this.data.getSnapshot(),
behaviour: this.behavior.getSnapshot(),
canvas3d: {
camera: { ...this.plugin.canvas3d.camera }
camera: this.plugin.canvas3d.camera.getSnapshot()
}
};
}
......@@ -27,6 +27,7 @@ class PluginState {
async setSnapshot(snapshot: PluginState.Snapshot) {
await this.behavior.setSnapshot(snapshot.behaviour);
await this.data.setSnapshot(snapshot.data);
this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera);
// TODO: handle camera
// console.log({ old: { ...this.plugin.canvas3d.camera }, new: snapshot.canvas3d.camera });
......@@ -61,7 +62,7 @@ namespace PluginState {
data: State.Snapshot,
behaviour: State.Snapshot,
canvas3d: {
camera: CombinedCamera
camera: Camera.Snapshot
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment