diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index 616c5bf793bb62b095e8fbfff4bcf6ab22253105..783c8883d88a2f4e4355cab4b4ffc239f86381d8 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -42,6 +42,8 @@ class Camera implements Object3D { private prevProjection = Mat4.identity(); private prevView = Mat4.identity(); + private deltaDirection = Vec3.zero(); + private newPosition = Vec3.zero(); updateMatrices() { const snapshot = this.state as Camera.Snapshot; @@ -83,10 +85,19 @@ class Camera implements Object3D { } focus(target: Vec3, radius: number) { - const position = Vec3.zero(); - Vec3.scale(position, this.state.direction, -radius); - Vec3.add(position, position, target); - this.setState({ target, position }); + 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.sub(this.deltaDirection, this.state.position, target) + 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) + this.setState({ target, position: this.newPosition }) } // lookAt(target: Vec3) { diff --git a/src/mol-canvas3d/camera/util.ts b/src/mol-canvas3d/camera/util.ts index 1fdb476a94e65293298fa5c2e7dd0545385edd07..52c431833d5e93b2633d2eb08be71f6ab19735e9 100644 --- a/src/mol-canvas3d/camera/util.ts +++ b/src/mol-canvas3d/camera/util.ts @@ -42,7 +42,7 @@ export namespace Viewport { const tmpVec3 = Vec3.zero() -/** Modifies the direction & up vectors in place */ +/** Modifies the direction & up vectors in place, both are normalized */ export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target: Vec3) { Vec3.sub(tmpVec3, target, position) Vec3.normalize(tmpVec3, tmpVec3) diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 2a9f5896f12bf3d25aa77133e24288af3718eeb0..00d773e513f8a656f99bfb15bf62f34c834a4e6c 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -62,6 +62,7 @@ interface Canvas3D { readonly didDraw: BehaviorSubject<now.Timestamp> handleResize: () => void + /** Focuses camera on scene's bounding sphere, centered and zoomed. */ resetCamera: () => void readonly camera: Camera downloadScreenshot: () => void @@ -220,8 +221,6 @@ namespace Canvas3D { if (drawPending) return drawPending = true forceNextDraw = !!force; - // The animation frame is being requested by animate already. - // window.requestAnimationFrame(() => draw(force)) } function animate() { @@ -344,7 +343,8 @@ namespace Canvas3D { handleResize, resetCamera: () => { - // TODO + camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius) + requestDraw(true); }, camera, downloadScreenshot: () => { diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 1ef3fa81c32028813bea5d3d9e2fd805d9cf2930..51815b14ead7f72119f143c0bb5bb2cf8a43f4c5 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -12,9 +12,9 @@ import { Object3D } from './object3d'; import { Sphere3D } from 'mol-math/geometry'; import { Vec3 } from 'mol-math/linear-algebra'; -function calculateBoundingSphere(renderableMap: Map<RenderObject, Renderable<RenderableValues & BaseValues>>): Sphere3D { +function calculateBoundingSphere(renderableMap: Map<RenderObject, Renderable<RenderableValues & BaseValues>>, boundingSphere: Sphere3D): Sphere3D { let count = 0 - const center = Vec3.zero() + const center = Vec3.set(boundingSphere.center, 0, 0, 0) renderableMap.forEach(r => { if (r.boundingSphere.radius) { Vec3.add(center, center, r.boundingSphere.center) @@ -31,8 +31,9 @@ function calculateBoundingSphere(renderableMap: Map<RenderObject, Renderable<Ren radius = Math.max(radius, Vec3.distance(center, r.boundingSphere.center) + r.boundingSphere.radius) } }) + boundingSphere.radius = radius - return { center, radius }; + return boundingSphere; } interface Scene extends Object3D { @@ -51,7 +52,8 @@ interface Scene extends Object3D { namespace Scene { export function create(ctx: WebGLContext): Scene { const renderableMap = new Map<RenderObject, Renderable<RenderableValues & BaseValues>>() - let boundingSphere: Sphere3D | undefined + const boundingSphere = Sphere3D.zero() + let boundingSphereDirty = true const object3d = Object3D.create() @@ -64,13 +66,13 @@ namespace Scene { update: () => { Object3D.update(object3d) renderableMap.forEach(r => r.update()) - boundingSphere = undefined + boundingSphereDirty = true }, add: (o: RenderObject) => { if (!renderableMap.has(o)) { renderableMap.set(o, createRenderable(ctx, o)) - boundingSphere = undefined + boundingSphereDirty = true } else { console.warn(`RenderObject with id '${o.id}' already present`) } @@ -80,13 +82,13 @@ namespace Scene { if (renderable) { renderable.dispose() renderableMap.delete(o) - boundingSphere = undefined + boundingSphereDirty = true } }, clear: () => { renderableMap.forEach(renderable => renderable.dispose()) renderableMap.clear() - boundingSphere = undefined + boundingSphereDirty = true }, forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => { renderableMap.forEach(callbackFn) @@ -105,9 +107,8 @@ namespace Scene { return renderableMap.size }, get boundingSphere() { - if (boundingSphere) return boundingSphere - // TODO avoid object creation - boundingSphere = calculateBoundingSphere(renderableMap) + if (boundingSphereDirty) calculateBoundingSphere(renderableMap, boundingSphere) + boundingSphereDirty = false return boundingSphere } } diff --git a/src/mol-math/geometry/primitives/box3d.ts b/src/mol-math/geometry/primitives/box3d.ts index 8b48c524a31614bd354ef8468068071240007e8f..563d10ea7cb362e67da09a624e02b5eca772b259 100644 --- a/src/mol-math/geometry/primitives/box3d.ts +++ b/src/mol-math/geometry/primitives/box3d.ts @@ -15,6 +15,13 @@ namespace Box3D { export function create(min: Vec3, max: Vec3): Box3D { return { min, max }; } export function empty(): Box3D { return { min: Vec3.zero(), max: Vec3.zero() }; } + export function clone(a: Box3D): Box3D { + const out = empty(); + Vec3.copy(out.min, a.min); + Vec3.copy(out.max, a.max); + return out; + } + export function computeBounding(data: PositionData): Box3D { const min = Vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); const max = Vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); diff --git a/src/mol-math/geometry/primitives/sphere3d.ts b/src/mol-math/geometry/primitives/sphere3d.ts index 380f3f5f2fdcc5fd7f63fdcf2e4193b820d9ca86..ef6c3c685583c12f131ca94ac1cf5419f61e57e3 100644 --- a/src/mol-math/geometry/primitives/sphere3d.ts +++ b/src/mol-math/geometry/primitives/sphere3d.ts @@ -15,6 +15,13 @@ namespace Sphere3D { export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; } export function zero(): Sphere3D { return { center: Vec3.zero(), radius: 0 }; } + export function clone(a: Sphere3D): Sphere3D { + const out = zero(); + Vec3.copy(out.center, a.center); + out.radius = a.radius + return out; + } + export function copy(out: Sphere3D, a: Sphere3D) { Vec3.copy(out.center, a.center) out.radius = a.radius diff --git a/src/mol-plugin/behavior/static/camera.ts b/src/mol-plugin/behavior/static/camera.ts index eee124a6b7ac520dbd67318e9c6fa1fca11356eb..3684aca0acc6d0e6a7ad6276090573ba84c9d2e7 100644 --- a/src/mol-plugin/behavior/static/camera.ts +++ b/src/mol-plugin/behavior/static/camera.ts @@ -6,7 +6,6 @@ import { PluginContext } from 'mol-plugin/context'; import { PluginCommands } from 'mol-plugin/command'; -import { PluginStateObject as SO } from '../../state/objects'; import { CameraSnapshotManager } from 'mol-plugin/state/camera'; export function registerDefault(ctx: PluginContext) { @@ -17,15 +16,7 @@ export function registerDefault(ctx: PluginContext) { export function Reset(ctx: PluginContext) { PluginCommands.Camera.Reset.subscribe(ctx, () => { - const sel = ctx.state.dataState.select(q => q.root.subtree().ofType(SO.Molecule.Structure)); - if (!sel.length) return; - - const center = (sel[0].obj! as SO.Molecule.Structure).data.boundary.sphere.center; - ctx.canvas3d.camera.setState({ target: center }); - ctx.canvas3d.requestDraw(true); - - // TODO - // ctx.canvas3d.resetCamera(); + ctx.canvas3d.resetCamera(); }) }