diff --git a/src/examples/proteopedia-wrapper/index.ts b/src/examples/proteopedia-wrapper/index.ts index 70808ed6121642c0351f84f123c072f6c6729405..6ff75c437a1e695d450528cd989490c60ddf9e69 100644 --- a/src/examples/proteopedia-wrapper/index.ts +++ b/src/examples/proteopedia-wrapper/index.ts @@ -385,7 +385,8 @@ class MolStarProteopediaWrapper { // const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter); // Vec3.normalize(position, position); // Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius); - const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, Math.max(sphere.radius, 5)); + const radius = Math.max(sphere.radius, 5) + const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius, radius); PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 }); } } diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index 899279b5becc66f2d3a6e173803e4c9da25fa328..dc64ccf73d9b178d0cca0bd5a9af3fdcc042a537 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -82,12 +82,12 @@ class Camera { return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state); } - getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> { + getFocus(target: Vec3, radiusNear: number, radiusFar: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> { const fov = this.state.fov const { width, height } = this.viewport const aspect = width / height const aspectFactor = (height < width ? 1 : aspect) - const targetDistance = Math.abs((radius / aspectFactor) / Math.sin(fov / 2)) + const targetDistance = Math.abs((radiusNear / aspectFactor) / Math.sin(fov / 2)) Vec3.sub(this.deltaDirection, this.target, this.position) if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection) @@ -96,15 +96,18 @@ class Camera { const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state) state.target = Vec3.clone(target) - state.radius = radius + state.radiusNear = radiusNear + state.radiusFar = radiusFar state.position = Vec3.clone(this.newPosition) if (up) Vec3.matchDirection(state.up, up, state.up) return state } - focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) { - if (radius > 0) this.setState(this.getFocus(target, radius, up, dir), durationMs); + focus(target: Vec3, radiusNear: number, radiusFar: number, durationMs?: number, up?: Vec3, dir?: Vec3) { + if (radiusNear > 0 && radiusFar > 0) { + this.setState(this.getFocus(target, radiusNear, radiusFar, up, dir), durationMs); + } } project(out: Vec4, point: Vec3) { @@ -158,8 +161,10 @@ namespace Camera { up: Vec3.create(0, 1, 0), target: Vec3.create(0, 0, 0), - radius: 10, + radiusNear: 10, + radiusFar: 10, fog: 50, + clipFar: true }; } @@ -171,8 +176,10 @@ namespace Camera { up: Vec3 target: Vec3 - radius: number + radiusNear: number + radiusFar: number fog: number + clipFar: boolean } export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) { @@ -185,8 +192,10 @@ namespace Camera { 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.radius !== 'undefined') out.radius = source.radius; + if (typeof source.radiusNear !== 'undefined') out.radiusNear = source.radiusNear; + if (typeof source.radiusFar !== 'undefined') out.radiusFar = source.radiusFar; if (typeof source.fog !== 'undefined') out.fog = source.fog; + if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar; return out; } @@ -253,17 +262,15 @@ function updatePers(camera: Camera) { } function updateClip(camera: Camera) { - const { radius, mode, fog } = camera.state + const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state const cDist = Vec3.distance(camera.position, camera.target) - const bRadius = Math.max(1, radius) - - let near = cDist - bRadius - let far = cDist + bRadius + let near = cDist - radiusNear + let far = cDist + (clipFar ? radiusNear : radiusFar) const fogNearFactor = -(50 - fog) / 50 - let fogNear = cDist - (bRadius * fogNearFactor) - let fogFar = cDist + bRadius + let fogNear = cDist - (radiusNear * fogNearFactor) + let fogFar = far if (mode === 'perspective') { // set at least to 5 to avoid slow sphere impostor rendering diff --git a/src/mol-canvas3d/camera/transition.ts b/src/mol-canvas3d/camera/transition.ts index 67ee4e890a8805bcc35479ca5f9feec849608266..8b3ad8a9d98d153a4a0292b57d74dcb2eaf25db9 100644 --- a/src/mol-canvas3d/camera/transition.ts +++ b/src/mol-canvas3d/camera/transition.ts @@ -79,7 +79,9 @@ namespace CameraTransitionManager { // Lerp target, position & radius Vec3.lerp(out.target, source.target, target.target, t); Vec3.lerp(out.position, source.position, target.position, t); - out.radius = lerp(source.radius, target.radius, t); + out.radiusNear = lerp(source.radiusNear, target.radiusNear, t); + // TODO take change of `clipFar` into account + out.radiusFar = lerp(source.radiusFar, target.radiusFar, t); // Lerp fov & fog out.fov = lerp(source.fov, target.fov, t); diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 95bd539a88148221f0a2d63320845fda68febe01..44e3ca0e63fb28239dce764d67538879d633180d 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -33,10 +33,12 @@ import { DrawPass } from './passes/draw'; import { PickPass } from './passes/pick'; import { Task } from '../mol-task'; import { ImagePass, ImageProps } from './passes/image'; +import { Sphere3D } from '../mol-math/geometry'; export const Canvas3DParams = { cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]), cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }), + cameraClipFar: PD.Boolean(true), cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }), transparentBackground: PD.Boolean(false), @@ -73,6 +75,7 @@ interface Canvas3D { /** Focuses camera on scene's bounding sphere, centered and zoomed. */ resetCamera: () => void readonly camera: Camera + readonly boundingSphere: Readonly<Sphere3D> downloadScreenshot: () => void getPixelData: (variant: GraphicsRenderVariant) => PixelData setProps: (props: Partial<Canvas3DProps>) => void @@ -127,7 +130,8 @@ namespace Canvas3D { const camera = new Camera({ position: Vec3.create(0, 0, 100), mode: p.cameraMode, - fog: p.cameraFog + fog: p.cameraFog, + clipFar: p.cameraClipFar }) const controls = TrackballControls.create(input, camera, p.trackball) @@ -240,7 +244,8 @@ namespace Canvas3D { return runTask(scene.commit()).then(() => { if (cameraResetRequested && !scene.isCommiting) { - camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius) + const { center, radius } = scene.boundingSphere + camera.focus(center, radius, radius) cameraResetRequested = false } if (debugHelper.isEnabled) debugHelper.update() @@ -320,11 +325,13 @@ namespace Canvas3D { if (scene.isCommiting) { cameraResetRequested = true } else { - camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, p.cameraResetDurationMs) + const { center, radius } = scene.boundingSphere + camera.focus(center, radius, radius, p.cameraResetDurationMs) requestDraw(true); } }, camera, + boundingSphere: scene.boundingSphere, downloadScreenshot: () => { // TODO }, @@ -346,6 +353,9 @@ namespace Canvas3D { if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) { camera.setState({ fog: props.cameraFog }) } + if (props.cameraClipFar !== undefined && props.cameraClipFar !== camera.state.clipFar) { + camera.setState({ clipFar: props.cameraClipFar }) + } if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground @@ -364,6 +374,7 @@ namespace Canvas3D { return { cameraMode: camera.state.mode, cameraFog: camera.state.fog, + cameraClipFar: camera.state.clipFar, cameraResetDurationMs: p.cameraResetDurationMs, transparentBackground: p.transparentBackground, diff --git a/src/mol-canvas3d/controls/trackball.ts b/src/mol-canvas3d/controls/trackball.ts index 08e5e43b60afe002863b354deaa764b99f901bd1..639b6c8b6ca7f4231d18a5b48f120298078491a7 100644 --- a/src/mol-canvas3d/controls/trackball.ts +++ b/src/mol-canvas3d/controls/trackball.ts @@ -208,8 +208,8 @@ namespace TrackballControls { function focusCamera() { const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed if (factor !== 0.0) { - const radius = Math.max(1, camera.state.radius + 10 * factor) - camera.setState({ radius }) + const radiusNear = Math.max(1, camera.state.radiusNear + 10 * factor) + camera.setState({ radiusNear }) } if (p.staticMoving) { @@ -343,7 +343,7 @@ namespace TrackballControls { if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2) if (dragFocusZoom) { const dist = Vec3.distance(camera.state.position, camera.state.target); - camera.setState({ radius: dist / 5 }) + camera.setState({ radiusNear: dist / 5 }) } if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2) } diff --git a/src/mol-plugin-ui/structure/selection.tsx b/src/mol-plugin-ui/structure/selection.tsx index aa1000edb666e1b9e40b8b8ed664a53b02c5df78..8847ccc0b9e8154c62ce29936bed0e2e6c4bcae3 100644 --- a/src/mol-plugin-ui/structure/selection.tsx +++ b/src/mol-plugin-ui/structure/selection.tsx @@ -65,7 +65,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS const { origin, dirA, dirC } = principalAxes.boxAxes const { sphere } = this.plugin.helpers.structureSelectionManager.getBoundary() const radius = Math.max(sphere.radius + extraRadius, minRadius); - this.plugin.canvas3d?.camera.focus(origin, radius, durationMs, dirA, dirC); + this.plugin.canvas3d?.camera.focus(origin, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs, dirA, dirC); } focusLoci(loci: StructureElement.Loci) { @@ -74,7 +74,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return const { sphere } = StructureElement.Loci.getBoundary(loci) const radius = Math.max(sphere.radius + extraRadius, minRadius); - this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs); + this.plugin.canvas3d?.camera.focus(sphere.center, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs); } } diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx index 8d55f0768f2a83d8e9025f2edbd525954c6bf432..699c7c484d49c87f962ce8c3c57540b2c920ef65 100644 --- a/src/mol-plugin-ui/viewport/simple-settings.tsx +++ b/src/mol-plugin-ui/viewport/simple-settings.tsx @@ -25,6 +25,7 @@ const SimpleSettingsParams = { occlusion: PD.Boolean(false, { description: 'Darken occluded crevices with the ambient occlusion effect' }), outline: PD.Boolean(false, { description: 'Draw outline around 3D objects' }), fog: PD.Boolean(false, { description: 'Show fog in the distance' }), + clipFar: PD.Boolean(true, { description: 'Clip scene in the distance' }), }; export class SimpleSettingsControl extends PluginUIComponent { @@ -81,6 +82,10 @@ export class SimpleSettingsControl extends PluginUIComponent { PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { cameraFog: p.value ? 50 : 1, } }); + } else if (p.name === 'clipFar') {; + PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { + cameraClipFar: p.value, + } }); } } @@ -117,7 +122,8 @@ export class SimpleSettingsControl extends PluginUIComponent { renderStyle, occlusion: this.plugin.canvas3d?.props.postprocessing.occlusionEnable, outline: this.plugin.canvas3d?.props.postprocessing.outlineEnable, - fog: this.plugin.canvas3d ? this.plugin.canvas3d.props.cameraFog > 1 : false + fog: this.plugin.canvas3d ? this.plugin.canvas3d.props.cameraFog > 1 : false, + clipFar: this.plugin.canvas3d?.props.cameraClipFar } } diff --git a/src/mol-plugin/behavior/dynamic/camera.ts b/src/mol-plugin/behavior/dynamic/camera.ts index a5bd15ca34bd373deb981505bc237611ac297a57..ecf921484653e5601fc667a2a1c5d8d5e2d4fe8b 100644 --- a/src/mol-plugin/behavior/dynamic/camera.ts +++ b/src/mol-plugin/behavior/dynamic/camera.ts @@ -47,7 +47,7 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({ const sphere = Loci.getBoundingSphere(loci); if (sphere) { const radius = Math.max(sphere.radius + p.extraRadius, p.minRadius); - this.ctx.canvas3d.camera.focus(sphere.center, radius, p.durationMs); + this.ctx.canvas3d.camera.focus(sphere.center, radius, this.ctx.canvas3d.boundingSphere.radius, p.durationMs); } } }