Skip to content
Snippets Groups Projects
Commit 93d4118c authored by Alexander Rose's avatar Alexander Rose
Browse files

Merge branch 'master' of https://github.com/molstar/molstar

parents 5fcdcb12 3a2a47af
No related branches found
No related tags found
No related merge requests found
......@@ -6,10 +6,12 @@
*/
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { Viewport, cameraProject, cameraUnproject, cameraSetClipping } from './camera/util';
import { Object3D } from '../mol-gl/object3d';
import { BehaviorSubject } from 'rxjs';
import { CameraTransitionManager } from './camera/transition';
import { Canvas3DProps } from './canvas3d';
import Scene from '../mol-gl/scene';
export { Camera }
......@@ -80,8 +82,8 @@ class Camera implements Object3D {
return changed;
}
setState(snapshot: Partial<Camera.Snapshot>) {
this.transition.apply(snapshot);
setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
this.transition.apply(snapshot, durationMs);
}
getSnapshot() {
......@@ -90,30 +92,27 @@ class Camera implements Object3D {
return ret;
}
getFocus(target: Vec3, radius: number, dir?: Vec3): Partial<Camera.Snapshot> {
getFocus(target: Vec3, radius: number): Partial<Camera.Snapshot> {
const fov = this.state.fov
const { width, height } = this.viewport
const aspect = width / height
const aspectFactor = (height < width ? 1 : aspect)
const currentDistance = Vec3.distance(this.state.position, target)
const targetDistance = Math.abs((radius / aspectFactor) / Math.sin(fov / 2))
const deltaDistance = Math.abs(currentDistance - targetDistance)
Vec3.setMagnitude(this.deltaDirection, this.state.direction, targetDistance)
Vec3.sub(this.newPosition, target, this.deltaDirection)
if (dir) {
Vec3.setMagnitude(this.deltaDirection, dir, targetDistance)
Vec3.add(this.newPosition, target, this.deltaDirection)
} else {
Vec3.setMagnitude(this.deltaDirection, this.state.direction, deltaDistance)
if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection)
Vec3.add(this.newPosition, this.state.position, this.deltaDirection)
}
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
state.target = Vec3.clone(target);
state.position = Vec3.clone(this.newPosition);
cameraSetClipping(state, this.scene.boundingSphere, this.canvasProps)
return { target, position: Vec3.clone(this.newPosition) };
return state;
}
focus(target: Vec3, radius: number, dir?: Vec3) {
if (radius > 0) this.setState(this.getFocus(target, radius, dir));
focus(target: Vec3, radius: number, durationMs?: number) {
if (radius > 0) this.setState(this.getFocus(target, radius), durationMs);
}
// lookAt(target: Vec3) {
......@@ -137,7 +136,7 @@ class Camera implements Object3D {
this.updatedViewProjection.complete();
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
constructor(private scene: Scene, private canvasProps: Canvas3DProps, state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
this.viewport = viewport;
Camera.copySnapshot(this.state, state);
}
......@@ -202,7 +201,7 @@ namespace Camera {
mode: Mode,
position: Vec3,
// Normalized camera direction
// Normalized camera direction, from Target to Position, for some reason?
direction: Vec3,
up: Vec3,
target: Vec3,
......@@ -217,7 +216,7 @@ namespace Camera {
}
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
if (!source) return;
if (!source) return out;
if (typeof source.mode !== 'undefined') out.mode = source.mode;
......@@ -233,6 +232,8 @@ namespace Camera {
if (typeof source.fov !== 'undefined') out.fov = source.fov;
if (typeof source.zoom !== 'undefined') out.zoom = source.zoom;
return out;
}
}
......
......@@ -22,7 +22,7 @@ class CameraTransitionManager {
private current = Camera.createDefaultSnapshot();
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
if (durationMs <= 0 || to.mode !== this.camera.state.mode) {
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(to);
return;
}
......
......@@ -5,6 +5,9 @@
*/
import { Mat4, Vec3, Vec4, EPSILON } from '../../mol-math/linear-algebra'
import { Camera } from '../camera'
import { Sphere3D } from '../../mol-math/geometry'
import { Canvas3DProps } from '../canvas3d'
export { Viewport }
......@@ -80,6 +83,43 @@ export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target:
}
}
export function cameraSetClipping(state: Camera.Snapshot, boundingSphere: Sphere3D, p: Canvas3DProps) {
const cDist = Vec3.distance(state.position, state.target)
const bRadius = Math.max(10, boundingSphere.radius)
const nearFactor = (50 - p.clip[0]) / 50
const farFactor = -(50 - p.clip[1]) / 50
let near = cDist - (bRadius * nearFactor)
let far = cDist + (bRadius * farFactor)
const fogNearFactor = (50 - p.fog[0]) / 50
const fogFarFactor = -(50 - p.fog[1]) / 50
let fogNear = cDist - (bRadius * fogNearFactor)
let fogFar = cDist + (bRadius * fogFarFactor)
if (state.mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(5, p.cameraClipDistance, near)
far = Math.max(5, far)
fogNear = Math.max(5, fogNear)
fogFar = Math.max(5, fogFar)
} else if (state.mode === 'orthographic') {
if (p.cameraClipDistance > 0) {
near = Math.max(p.cameraClipDistance, near)
}
}
state.near = near;
state.far = far;
state.fogNear = fogNear;
state.fogFar = fogFar;
// if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
// camera.setState({ near, far, fogNear, fogFar })
// currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
// }
}
const NEAR_RANGE = 0
const FAR_RANGE = 1
......
......@@ -11,7 +11,7 @@ import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/inp
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer'
import { GraphicsRenderObject } from '../mol-gl/render-object'
import { TrackballControls, TrackballControlsParams } from './controls/trackball'
import { Viewport } from './camera/util'
import { Viewport, cameraSetClipping } from './camera/util'
import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context';
import { Representation } from '../mol-repr/representation';
import Scene from '../mol-gl/scene';
......@@ -38,6 +38,7 @@ export const Canvas3DParams = {
// maxFps: PD.Numeric(30),
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
cameraClipDistance: PD.Numeric(0, { min: 0.0, max: 50.0, step: 0.1 }, { description: 'The distance between camera and scene at which to clip regardless of near clipping plane.' }),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
......@@ -116,19 +117,20 @@ namespace Canvas3D {
const startTime = now()
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
const camera = new Camera({
near: 0.1,
far: 10000,
position: Vec3.create(0, 0, 100),
mode: p.cameraMode
})
const webgl = createContext(gl)
let width = gl.drawingBufferWidth
let height = gl.drawingBufferHeight
const scene = Scene.create(webgl)
const camera = new Camera(scene, p, {
near: 0.1,
far: 10000,
position: Vec3.create(0, 0, 100),
mode: p.cameraMode
})
const controls = TrackballControls.create(input, camera, p.trackball)
const renderer = Renderer.create(webgl, camera, p.renderer)
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
......@@ -140,7 +142,7 @@ namespace Canvas3D {
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
let drawPending = false
let cameraResetRequested: boolean | Vec3 = false
let cameraResetRequested = false
function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci
......@@ -172,37 +174,8 @@ namespace Canvas3D {
}
}
let currentNear = -1, currentFar = -1, currentFogNear = -1, currentFogFar = -1
function setClipping() {
const cDist = Vec3.distance(camera.state.position, camera.state.target)
const bRadius = Math.max(10, scene.boundingSphere.radius)
const nearFactor = (50 - p.clip[0]) / 50
const farFactor = -(50 - p.clip[1]) / 50
let near = cDist - (bRadius * nearFactor)
let far = cDist + (bRadius * farFactor)
const fogNearFactor = (50 - p.fog[0]) / 50
const fogFarFactor = -(50 - p.fog[1]) / 50
let fogNear = cDist - (bRadius * fogNearFactor)
let fogFar = cDist + (bRadius * fogFarFactor)
if (camera.state.mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(5, p.cameraClipDistance, near)
far = Math.max(5, far)
fogNear = Math.max(5, fogNear)
fogFar = Math.max(5, fogFar)
} else if (camera.state.mode === 'orthographic') {
if (p.cameraClipDistance > 0) {
near = Math.max(p.cameraClipDistance, near)
}
}
if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
camera.setState({ near, far, fogNear, fogFar })
currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
}
cameraSetClipping(camera.state, scene.boundingSphere, p);
}
function render(variant: 'pick' | 'draw', force: boolean) {
......@@ -271,8 +244,7 @@ namespace Canvas3D {
runTask(scene.commit()).then(() => {
if (cameraResetRequested && !scene.isCommiting) {
const dir = typeof cameraResetRequested === 'boolean' ? undefined : cameraResetRequested
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
cameraResetRequested = false
}
if (debugHelper.isEnabled) debugHelper.update()
......@@ -345,11 +317,11 @@ namespace Canvas3D {
getLoci,
handleResize,
resetCamera: (dir?: Vec3) => {
resetCamera: (/*dir?: Vec3*/) => {
if (scene.isCommiting) {
cameraResetRequested = dir || true
cameraResetRequested = true
} else {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, p.cameraResetDurationMs)
requestDraw(true);
}
},
......@@ -373,6 +345,7 @@ namespace Canvas3D {
camera.setState({ mode: props.cameraMode })
}
if (props.cameraClipDistance !== undefined) p.cameraClipDistance = props.cameraClipDistance
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]]
if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]]
......@@ -388,6 +361,7 @@ namespace Canvas3D {
return {
cameraMode: camera.state.mode,
cameraClipDistance: p.cameraClipDistance,
cameraResetDurationMs: p.cameraResetDurationMs,
clip: p.clip,
fog: p.fog,
......
......@@ -24,10 +24,11 @@ import { Color } from '../../mol-util/color';
import { Sphere3D } from '../../mol-math/geometry';
import { createEmptyOverpaint } from '../../mol-geo/geometry/overpaint-data';
import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-data';
import { DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
function createRenderer(gl: WebGLRenderingContext) {
const ctx = createContext(gl)
const camera = new Camera({
const camera = new Camera(Scene.create(ctx), DefaultCanvas3DParams, {
near: 0.01,
far: 10000,
position: Vec3.create(0, 0, 50)
......
......@@ -9,23 +9,24 @@ import { ParamDefinition } from '../../../mol-util/param-definition';
import { PluginBehavior } from '../behavior';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number }>({
export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number, durationMs?: number }>({
name: 'focus-loci-on-select',
category: 'interaction',
ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> {
ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number, durationMs?: number }> {
register(): void {
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (!this.ctx.canvas3d || buttons !== ButtonsType.Flag.Primary || !ModifiersKeys.areEqual(modifiers, ModifiersKeys.None)) return;
const sphere = Loci.getBoundingSphere(current.loci);
if (!sphere) return;
this.ctx.canvas3d.camera.focus(sphere.center, Math.max(sphere.radius + this.params.extraRadius, this.params.minRadius));
this.ctx.canvas3d.camera.focus(sphere.center, Math.max(sphere.radius + this.params.extraRadius, this.params.minRadius), this.params.durationMs);
});
}
},
params: () => ({
minRadius: ParamDefinition.Numeric(8, { min: 1, max: 50, step: 1 }),
extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci.' })
extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci.' }),
durationMs: ParamDefinition.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'Camera transition duration.' })
}),
display: { name: 'Focus Loci on Select' }
});
\ No newline at end of file
......@@ -63,7 +63,7 @@ export const DefaultPluginSpec: PluginSpec = {
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 8, extraRadius: 4 }),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 8, extraRadius: 4, durationMs: 250 }),
// PluginSpec.Behavior(PluginBehaviors.Labels.SceneLabels),
PluginSpec.Behavior(PluginBehaviors.CustomProps.MolstarSecondaryStructure, { autoAttach: true }),
PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true, showTooltip: true }),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment