浏览代码

canvas3d settings improvements

- added clipping params
- show clipping radius
- ranmed render-style to lighting
Alexander Rose 5 年之前
父节点
当前提交
22936ff6c9

+ 1 - 1
src/examples/proteopedia-wrapper/index.ts

@@ -399,7 +399,7 @@ class MolStarProteopediaWrapper {
             // Vec3.normalize(position, position);
             // Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
             const radius = Math.max(sphere.radius, 5)
-            const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius, radius);
+            const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius);
             PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
         }
     }

+ 19 - 17
src/mol-canvas3d/camera.ts

@@ -8,6 +8,7 @@
 import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
 import { Viewport, cameraProject, cameraUnproject } from './camera/util';
 import { CameraTransitionManager } from './camera/transition';
+import { BehaviorSubject } from 'rxjs';
 
 export { Camera }
 
@@ -33,6 +34,7 @@ class Camera {
     zoom = 1
 
     readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
+    readonly stateChanged = new BehaviorSubject<Partial<Camera.Snapshot>>(this.state);
 
     get position() { return this.state.position; }
     set position(v: Vec3) { Vec3.copy(this.state.position, v); }
@@ -76,18 +78,19 @@ class Camera {
 
     setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
         this.transition.apply(snapshot, durationMs);
+        this.stateChanged.next(snapshot);
     }
 
     getSnapshot() {
         return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
     }
 
-    getFocus(target: Vec3, radiusNear: number, radiusFar: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
-        const fov = this.state.fov
+    getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
+        const { fov } = this.state
         const { width, height } = this.viewport
         const aspect = width / height
         const aspectFactor = (height < width ? 1 : aspect)
-        const targetDistance = Math.abs((radiusNear / aspectFactor) / Math.sin(fov / 2))
+        const targetDistance = Math.abs((radius / aspectFactor) / Math.sin(fov / 2))
 
         Vec3.sub(this.deltaDirection, this.target, this.position)
         if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
@@ -96,17 +99,16 @@ class Camera {
 
         const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
         state.target = Vec3.clone(target)
-        state.radiusNear = radiusNear
-        state.radiusFar = radiusFar
+        state.radius = radius
         state.position = Vec3.clone(this.newPosition)
         if (up) Vec3.matchDirection(state.up, up, state.up)
 
         return state
     }
 
-    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);
+    focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
+        if (radius > 0) {
+            this.setState(this.getFocus(target, radius, up, dir), durationMs);
         }
     }
 
@@ -161,8 +163,8 @@ namespace Camera {
             up: Vec3.create(0, 1, 0),
             target: Vec3.create(0, 0, 0),
 
-            radiusNear: 10,
-            radiusFar: 10,
+            radius: 10,
+            radiusMax: 10,
             fog: 50,
             clipFar: true
         };
@@ -176,8 +178,8 @@ namespace Camera {
         up: Vec3
         target: Vec3
 
-        radiusNear: number
-        radiusFar: number
+        radius: number
+        radiusMax: number
         fog: number
         clipFar: boolean
     }
@@ -192,8 +194,8 @@ 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.radiusNear !== 'undefined') out.radiusNear = source.radiusNear;
-        if (typeof source.radiusFar !== 'undefined') out.radiusFar = source.radiusFar;
+        if (typeof source.radius !== 'undefined') out.radius = source.radius;
+        if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
         if (typeof source.fog !== 'undefined') out.fog = source.fog;
         if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
 
@@ -262,11 +264,11 @@ function updatePers(camera: Camera) {
 }
 
 function updateClip(camera: Camera) {
-    const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state
+    const { radius, radiusMax, mode, fog, clipFar } = camera.state
 
-    const normalizedFar = clipFar ? radiusNear : radiusFar
+    const normalizedFar = clipFar ? radius : radiusMax
     const cameraDistance = Vec3.distance(camera.position, camera.target)
-    let near = cameraDistance - radiusNear
+    let near = cameraDistance - radius
     let far = cameraDistance + normalizedFar
 
     const fogNearFactor = -(50 - fog) / 50

+ 20 - 13
src/mol-canvas3d/camera/transition.ts

@@ -17,20 +17,27 @@ class CameraTransitionManager {
     private start = 0;
     inTransition = false;
     private durationMs = 0;
-    private source: Camera.Snapshot = Camera.createDefaultSnapshot();
-    private target: Camera.Snapshot = Camera.createDefaultSnapshot();
-    private current = Camera.createDefaultSnapshot();
+    private _source: Camera.Snapshot = Camera.createDefaultSnapshot();
+    private _target: Camera.Snapshot = Camera.createDefaultSnapshot();
+    private _current = Camera.createDefaultSnapshot();
+
+    get source(): Readonly<Camera.Snapshot> { return this._source }
+    get target(): Readonly<Camera.Snapshot> { return this._target }
 
     apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
+        Camera.copySnapshot(this._source, this.camera.state);
+        Camera.copySnapshot(this._target, this.camera.state);
+        Camera.copySnapshot(this._target, to);
+
+        if (this._target.radius > this._target.radiusMax) {
+            this._target.radius = this._target.radiusMax
+        }
+
         if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
-            this.finish(to);
+            this.finish(this._target);
             return;
         }
 
-        Camera.copySnapshot(this.source, this.camera.state);
-        Camera.copySnapshot(this.target, this.camera.state);
-        Camera.copySnapshot(this.target, to);
-
         this.inTransition = true;
         this.func = transition || CameraTransitionManager.defaultTransition;
         this.start = this.t;
@@ -52,12 +59,12 @@ class CameraTransitionManager {
 
         const normalized = Math.min((this.t - this.start) / this.durationMs, 1);
         if (normalized === 1) {
-            this.finish(this.target!);
+            this.finish(this._target!);
             return;
         }
 
-        this.func(this.current, normalized, this.source, this.target);
-        Camera.copySnapshot(this.camera.state, this.current);
+        this.func(this._current, normalized, this._source, this._target);
+        Camera.copySnapshot(this.camera.state, this._current);
     }
 
     constructor(private camera: Camera) {
@@ -79,9 +86,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.radiusNear = lerp(source.radiusNear, target.radiusNear, t);
+        out.radius = lerp(source.radius, target.radius, t);
         // TODO take change of `clipFar` into account
-        out.radiusFar = lerp(source.radiusFar, target.radiusFar, t);
+        out.radiusMax = lerp(source.radiusMax, target.radiusMax, t);
 
         // Lerp fov & fog
         out.fov = lerp(source.fov, target.fov, t);

+ 38 - 12
src/mol-canvas3d/canvas3d.ts

@@ -38,8 +38,17 @@ import { isDebugMode } from '../mol-util/debug';
 
 export const Canvas3DParams = {
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const),
-    cameraFog: PD.Numeric(50, { min: 0, max: 100, step: 1 }),
-    cameraClipFar: PD.Boolean(true),
+    cameraFog: PD.MappedStatic('on', {
+        on: PD.Group({
+            intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
+        }),
+        off: PD.Group({})
+    }, { cycle: true, description: 'Show fog in the distance' }),
+    cameraClipping: PD.Group({
+        radius: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
+        far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
+    }, { pivot: 'radius' }),
+
     cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
     transparentBackground: PD.Boolean(false),
 
@@ -169,8 +178,8 @@ namespace Canvas3D {
         const camera = new Camera({
             position: Vec3.create(0, 0, 100),
             mode: p.cameraMode,
-            fog: p.cameraFog,
-            clipFar: p.cameraClipFar
+            fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
+            clipFar: p.cameraClipping.far
         })
 
         const controls = TrackballControls.create(input, camera, p.trackball)
@@ -293,7 +302,7 @@ namespace Canvas3D {
             const { center, radius } = scene.boundingSphere;
             if (radius > 0) {
                 const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
-                const focus = camera.getFocus(center, radius, radius);
+                const focus = camera.getFocus(center, radius);
                 const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
                 camera.setState(snapshot, duration);
             }
@@ -312,6 +321,7 @@ namespace Canvas3D {
             if (debugHelper.isEnabled) debugHelper.update();
             if (reprCount.value === 0) cameraResetRequested = true;
             reprCount.next(reprRenderObjects.size);
+            camera.setState({ radiusMax: scene.boundingSphere.radius })
             return true;
         }
 
@@ -418,15 +428,25 @@ namespace Canvas3D {
             didDraw,
             reprCount,
             setProps: (props: Partial<Canvas3DProps>) => {
+                const cameraState: Partial<Camera.Snapshot> = Object.create(null)
                 if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
-                    camera.setState({ mode: props.cameraMode })
+                    cameraState.mode = props.cameraMode
                 }
-                if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) {
-                    camera.setState({ fog: props.cameraFog })
+                if (props.cameraFog !== undefined) {
+                    const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
+                    if (newFog !== camera.state.fog) cameraState.fog = newFog
                 }
-                if (props.cameraClipFar !== undefined && props.cameraClipFar !== camera.state.clipFar) {
-                    camera.setState({ clipFar: props.cameraClipFar })
+                if (props.cameraClipping !== undefined) {
+                    if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
+                        cameraState.clipFar = props.cameraClipping.far
+                    }
+                    if (props.cameraClipping.radius !== undefined) {
+                        const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius)
+                        if (radius !== cameraState.radius) cameraState.radius = radius
+                    }
                 }
+                if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
+
                 if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
                 if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
 
@@ -442,10 +462,16 @@ namespace Canvas3D {
             },
 
             get props() {
+                const radius = scene.boundingSphere.radius > 0
+                    ? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
+                    : 0
+
                 return {
                     cameraMode: camera.state.mode,
-                    cameraFog: camera.state.fog,
-                    cameraClipFar: camera.state.clipFar,
+                    cameraFog: camera.state.fog > 0
+                        ? { name: 'on' as const, params: { intensity: camera.state.fog } }
+                        : { name: 'off' as const, params: {} },
+                    cameraClipping: { far: camera.state.clipFar, radius },
                     cameraResetDurationMs: p.cameraResetDurationMs,
                     transparentBackground: p.transparentBackground,
 

+ 3 - 3
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 radiusNear = Math.max(1, camera.state.radiusNear + 10 * factor)
-                camera.setState({ radiusNear })
+                const radius = Math.max(1, camera.state.radius + 10 * factor)
+                camera.setState({ radius })
             }
 
             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({ radiusNear: dist / 5 })
+                camera.setState({ radius: dist / 5 })
             }
             if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
         }

+ 1 - 1
src/mol-gl/renderer.ts

@@ -71,7 +71,7 @@ export const RendererParams = {
         glossy: PD.Group({}),
         metallic: PD.Group({}),
         plastic: PD.Group({}),
-    }, { label: 'Render Style', description: 'Style in which the 3D scene is rendered' }),
+    }, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
 }
 export type RendererProps = PD.Values<typeof RendererParams>
 

+ 6 - 6
src/mol-plugin-state/manager/camera.ts

@@ -22,12 +22,12 @@ export type CameraFocusOptions = typeof DefaultCameraFocusOptions
 export class CameraManager {
     focusLoci(loci: StructureElement.Loci, options?: Partial<CameraFocusOptions>) {
         // TODO: allow computation of principal axes here?
-        // perhaps have an optimized function, that does exact axes small Loci and approximate/sampled from big ones?        
+        // perhaps have an optimized function, that does exact axes small Loci and approximate/sampled from big ones?
 
         const { extraRadius, minRadius, durationMs } = { ...DefaultCameraFocusOptions, ...options };
         const { sphere } = StructureElement.Loci.getBoundary(loci);
         const radius = Math.max(sphere.radius + extraRadius, minRadius);
-        this.plugin.canvas3d?.camera.focus(sphere.center, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs);
+        this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
     }
 
     focusSphere(sphere: Sphere3D, options?: Partial<CameraFocusOptions> & { principalAxes?: PrincipalAxes }) {
@@ -36,14 +36,14 @@ export class CameraManager {
 
         if (options?.principalAxes) {
             const { origin, dirA, dirC } = options?.principalAxes.boxAxes;
-            this.plugin.canvas3d?.camera.focus(origin, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs, dirA, dirC);
+            this.plugin.canvas3d?.camera.focus(origin, radius, durationMs, dirA, dirC);
         } else {
-            this.plugin.canvas3d?.camera.focus(sphere.center, radius, this.plugin.canvas3d.boundingSphere.radius, durationMs);
+            this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
         }
     }
 
     setSnapshot(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
-        // TODO: setState and requestCameraReset are very similar now: unify them?        
+        // TODO: setState and requestCameraReset are very similar now: unify them?
         this.plugin.canvas3d?.camera.setState(snapshot, durationMs);
     }
 
@@ -51,6 +51,6 @@ export class CameraManager {
         this.plugin.canvas3d?.requestCameraReset({ snapshot, durationMs });
     }
 
-    constructor(readonly plugin: PluginContext) {     
+    constructor(readonly plugin: PluginContext) {
     }
 }

+ 20 - 14
src/mol-plugin-ui/viewport/simple-settings.tsx

@@ -21,6 +21,12 @@ import { ParameterMappingControl } from '../controls/parameters';
 export class SimpleSettingsControl extends PluginUIComponent {
     componentDidMount() {
         this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
+
+        this.plugin.canvas3d!.camera.stateChanged.subscribe(state => {
+            if (state.radiusMax !== undefined || state.radius !== undefined) {
+                this.forceUpdate()
+            }
+        })
     }
 
     render() {
@@ -35,17 +41,17 @@ const SimpleSettingsParams = {
         speed: Canvas3DParams.trackball.params.spinSpeed
     }, { pivot: 'spin' }),
     camera: Canvas3DParams.cameraMode,
-    background: PD.Group({ 
-        color: PD.Color(Color(0xFCFBF9), { label: "Background", description: 'Custom background color' }),
+    background: PD.Group({
+        color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }),
         transparent: PD.Boolean(false)
     }, { pivot: 'color' }),
-    renderer: PD.Group({
+    lighting: PD.Group({
         renderStyle: Canvas3DParams.renderer.params.style,
         occlusion: Canvas3DParams.postprocessing.params.occlusion,
         outline: Canvas3DParams.postprocessing.params.outline,
-        fog: PD.Boolean(false, { description: 'Show fog in the distance' }),
-        clipFar: PD.Boolean(true, { description: 'Clip scene in the distance' }),
+        fog: Canvas3DParams.cameraFog,
     }, { pivot: 'renderStyle' }),
+    clipping: Canvas3DParams.cameraClipping,
     layout: PD.MultiSelect<'sequence' | 'log' | 'left'>([], [['sequence', 'Sequence'], ['log', 'Log'], ['left', 'Left Panel']] as const),
 };
 
@@ -72,13 +78,13 @@ const SimpleSettingsMapping = ParamMapping({
                 color: renderer.backgroundColor,
                 transparent: canvas.transparentBackground
             },
-            renderer: {
+            lighting: {
                 renderStyle: renderer.style,
                 occlusion: canvas.postprocessing.occlusion,
                 outline: canvas.postprocessing.outline,
-                fog: ctx.canvas3d ? canvas.cameraFog > 1 : false,
-                clipFar: canvas.cameraClipFar
-            }
+                fog: canvas.cameraFog
+            },
+            clipping: canvas.cameraClipping
         };
     },
     update(s, props) {
@@ -88,11 +94,11 @@ const SimpleSettingsMapping = ParamMapping({
         canvas.cameraMode = s.camera;
         canvas.transparentBackground = s.background.transparent;
         canvas.renderer.backgroundColor = s.background.color;
-        canvas.renderer.style = s.renderer.renderStyle
-        canvas.postprocessing.occlusion = s.renderer.occlusion;
-        canvas.postprocessing.outline = s.renderer.outline;
-        canvas.cameraFog = s.renderer.fog ? 50 : 0;
-        canvas.cameraClipFar = s.renderer.clipFar;
+        canvas.renderer.style = s.lighting.renderStyle
+        canvas.postprocessing.occlusion = s.lighting.occlusion;
+        canvas.postprocessing.outline = s.lighting.outline;
+        canvas.cameraFog = s.lighting.fog
+        canvas.cameraClipping = s.clipping
 
         props.layout = s.layout;
     },

+ 1 - 1
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, this.ctx.canvas3d.boundingSphere.radius, p.durationMs);
+                            this.ctx.canvas3d.camera.focus(sphere.center, radius, p.durationMs);
                         }
                     }
                 }

+ 1 - 0
src/mol-plugin/behavior/static/camera.ts

@@ -30,6 +30,7 @@ export function SetSnapshot(ctx: PluginContext) {
 export function Focus(ctx: PluginContext) {
     PluginCommands.Camera.Focus.subscribe(ctx, ({ center, radius, durationMs }) => {
         ctx.managers.camera.focusSphere({ center, radius }, { durationMs });
+        ctx.events.canvas3d.settingsUpdated.next();
     })
 }
 

+ 1 - 1
src/mol-plugin/commands.ts

@@ -25,7 +25,7 @@ export const PluginCommands = {
 
         ToggleExpanded: PluginCommand<{ state: State, ref: StateTransform.Ref }>(),
         ToggleVisibility: PluginCommand<{ state: State, ref: StateTransform.Ref }>(),
-        
+
         Snapshots: {
             Add: PluginCommand<{ name?: string, description?: string, params?: PluginState.GetSnapshotParams }>(),
             Replace: PluginCommand<{ id: string, params?: PluginState.GetSnapshotParams }>(),

+ 1 - 1
src/mol-plugin/context.ts

@@ -77,7 +77,7 @@ export class PluginContext {
         task: this.tasks.events,
         canvas3d: {
             initialized: this.ev(),
-            settingsUpdated: this.ev()
+            settingsUpdated: this.ev(),
         }
     } as const