소스 검색

improved scene bounding sphere (reset) handling

Alexander Rose 5 년 전
부모
커밋
40a8cee8e5
4개의 변경된 파일89개의 추가작업 그리고 23개의 파일을 삭제
  1. 7 3
      src/mol-canvas3d/camera.ts
  2. 25 13
      src/mol-canvas3d/canvas3d.ts
  3. 20 6
      src/mol-gl/scene.ts
  4. 37 1
      src/mol-math/geometry/primitives/sphere3d.ts

+ 7 - 3
src/mol-canvas3d/camera.ts

@@ -85,13 +85,18 @@ class Camera {
         return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
     }
 
-    getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
+    getTargetDistance(radius: number) {
         const r = Math.max(radius, 0.01)
         const { fov } = this.state
         const { width, height } = this.viewport
         const aspect = width / height
         const aspectFactor = (height < width ? 1 : aspect)
-        const targetDistance = Math.abs((r / aspectFactor) / Math.sin(fov / 2))
+        return Math.abs((r / aspectFactor) / Math.sin(fov / 2))
+    }
+
+    getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
+        const r = Math.max(radius, 0.01)
+        const targetDistance = this.getTargetDistance(r)
 
         Vec3.sub(this.deltaDirection, this.target, this.position)
         if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
@@ -125,7 +130,6 @@ class Camera {
         this.viewport = viewport;
         Camera.copySnapshot(this.state, state);
     }
-
 }
 
 namespace Camera {

+ 25 - 13
src/mol-canvas3d/canvas3d.ts

@@ -300,7 +300,7 @@ namespace Canvas3D {
 
         function resolveCameraReset() {
             if (!cameraResetRequested) return;
-            const { center, radius } = scene.boundingSphere;
+            const { center, radius } = scene.boundingSphereVisible;
             if (radius > 0) {
                 const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
                 const focus = camera.getFocus(center, radius);
@@ -313,34 +313,42 @@ namespace Canvas3D {
             cameraResetRequested = false;
         }
 
-        const oldBoundary = Sphere3D.zero();
+        const oldBoundingSphereVisible = Sphere3D();
+        const cameraSphere = Sphere3D();
+
         function shouldResetCamera() {
             if (camera.state.radiusMax === 0) return true;
 
-            // check if any renderable center has moved outside of the old boundary
+            let cameraSphereOverlapsNone = true
+            Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius)
+
+            // check if any renderable has moved outside of the old bounding sphere
+            // and if no renderable is overlapping with the camera sphere
             for (const r of scene.renderables) {
                 if (!r.state.visible) continue;
-                const { center, radius } = r.values.boundingSphere.ref.value;
-                if (!radius) continue;
-                // TODO: include renderable radius into this?
-                if (Vec3.distance(oldBoundary.center, center) > 1.1 * oldBoundary.radius) {
-                    return true;
-                }
+
+                const b = r.values.boundingSphere.ref.value;
+                if (!b.radius) continue;
+
+                if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
+                if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
             }
-            return false;
+
+            return cameraSphereOverlapsNone;
         }
 
         const sceneCommitTimeoutMs = 250;
         function commitScene(isSynchronous: boolean) {
             if (!scene.needsCommit) return true;
 
-            // snapshot the current bounding sphere
-            Sphere3D.copy(oldBoundary, scene.boundingSphere);
+            // snapshot the current bounding sphere of visible objects
+            Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
 
             if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
 
             if (debugHelper.isEnabled) debugHelper.update();
             if (reprCount.value === 0 || shouldResetCamera()) cameraResetRequested = true;
+            if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
 
             camera.setState({ radiusMax: scene.boundingSphere.radius })
             reprCount.next(reprRenderObjects.size);
@@ -421,8 +429,12 @@ namespace Canvas3D {
                 reprCount.next(reprRenderObjects.size)
             },
             syncVisibility: () => {
+                if (camera.state.radiusMax === 0) {
+                    cameraResetRequested = true
+                    nextCameraResetDuration = 0
+                }
+
                 if (scene.syncVisibility()) {
-                    camera.setState({ radiusMax: scene.boundingSphere.radius })
                     if (debugHelper.isEnabled) debugHelper.update()
                 }
             },

+ 20 - 6
src/mol-gl/scene.ts

@@ -19,11 +19,11 @@ import { hash1 } from '../mol-data/util';
 
 const boundaryHelper = new BoundaryHelper('98')
 
-function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
+function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D {
     boundaryHelper.reset();
 
     for (let i = 0, il = renderables.length; i < il; ++i) {
-        if (!renderables[i].state.visible) continue;
+        if (onlyVisible && !renderables[i].state.visible) continue;
 
         const boundingSphere = renderables[i].values.boundingSphere.ref.value
         if (!boundingSphere.radius) continue;
@@ -38,7 +38,7 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base
     }
     boundaryHelper.finishedIncludeStep();
     for (let i = 0, il = renderables.length; i < il; ++i) {
-        if (!renderables[i].state.visible) continue;
+        if (onlyVisible && !renderables[i].state.visible) continue;
 
         const boundingSphere = renderables[i].values.boundingSphere.ref.value
         if (!boundingSphere.radius) continue;
@@ -74,6 +74,7 @@ interface Scene extends Object3D {
     readonly count: number
     readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
     readonly boundingSphere: Sphere3D
+    readonly boundingSphereVisible: Sphere3D
 
     /** Returns `true` if some visibility has changed, `false` otherwise. */
     syncVisibility: () => boolean
@@ -92,8 +93,10 @@ namespace Scene {
         const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
         const renderables: Renderable<RenderableValues & BaseValues>[] = []
         const boundingSphere = Sphere3D()
+        const boundingSphereVisible = Sphere3D()
 
         let boundingSphereDirty = true
+        let boundingSphereVisibleDirty = true
 
         const object3d = Object3D.create()
 
@@ -103,6 +106,7 @@ namespace Scene {
                 renderables.push(renderable)
                 renderableMap.set(o, renderable)
                 boundingSphereDirty = true
+                boundingSphereVisibleDirty = true
                 return renderable;
             } else {
                 console.warn(`RenderObject with id '${o.id}' already present`)
@@ -117,6 +121,7 @@ namespace Scene {
                 arraySetRemove(renderables, renderable);
                 renderableMap.delete(o)
                 boundingSphereDirty = true
+                boundingSphereVisibleDirty = true
             }
         }
 
@@ -161,7 +166,7 @@ namespace Scene {
         function syncVisibility() {
             const newVisibleHash = computeVisibleHash()
             if (newVisibleHash !== visibleHash) {
-                boundingSphereDirty = true
+                boundingSphereVisibleDirty = true
                 return true
             } else {
                 return false
@@ -190,6 +195,7 @@ namespace Scene {
                 }
                 if (!keepBoundingSphere) {
                     boundingSphereDirty = true
+                    boundingSphereVisibleDirty = true
                 } else {
                     syncVisibility()
                 }
@@ -208,6 +214,7 @@ namespace Scene {
                 renderables.length = 0
                 renderableMap.clear()
                 boundingSphereDirty = true
+                boundingSphereVisibleDirty = true
             },
             forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
                 renderableMap.forEach(callbackFn)
@@ -218,11 +225,18 @@ namespace Scene {
             renderables,
             get boundingSphere() {
                 if (boundingSphereDirty) {
-                    calculateBoundingSphere(renderables, boundingSphere)
+                    calculateBoundingSphere(renderables, boundingSphere, false)
                     boundingSphereDirty = false
-                    visibleHash = computeVisibleHash()
                 }
                 return boundingSphere
+            },
+            get boundingSphereVisible() {
+                if (boundingSphereVisibleDirty) {
+                    calculateBoundingSphere(renderables, boundingSphereVisible, true)
+                    boundingSphereVisibleDirty = false
+                    visibleHash = computeVisibleHash()
+                }
+                return boundingSphereVisible
             }
         }
     }

+ 37 - 1
src/mol-math/geometry/primitives/sphere3d.ts

@@ -28,7 +28,7 @@ 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 zero(): Sphere3D { return { center: Vec3(), radius: 0 }; }
 
     export function clone(a: Sphere3D): Sphere3D {
         const out = create(Vec3.clone(a.center), a.radius)
@@ -36,6 +36,12 @@ namespace Sphere3D {
         return out;
     }
 
+    export function set(out: Sphere3D, center: Vec3, radius: number) {
+        Vec3.copy(out.center, center)
+        out.radius = radius
+        return out
+    }
+
     export function copy(out: Sphere3D, a: Sphere3D) {
         Vec3.copy(out.center, a.center)
         out.radius = a.radius
@@ -128,6 +134,12 @@ namespace Sphere3D {
     export function expandBySphere(out: Sphere3D, sphere: Sphere3D, by: Sphere3D) {
         Vec3.copy(out.center, sphere.center)
         out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius)
+        if (hasExtrema(sphere) && hasExtrema(by)) {
+            setExtrema(out, [
+                ...sphere.extrema.map(e => Vec3.clone(e)),
+                ...by.extrema.map(e => Vec3.clone(e))
+            ])
+        }
         return out
     }
 
@@ -163,6 +175,30 @@ namespace Sphere3D {
         return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
                 Vec3.equals(a.center, b.center));
     }
+
+    /**
+     * Check if `a` includes `b`, use `extrema` of `b` when available
+     */
+    export function includes(a: Sphere3D, b: Sphere3D) {
+        if (hasExtrema(b)) {
+            for (const e of b.extrema) {
+                if (Vec3.distance(a.center, e) > a.radius) return false
+            }
+            return true
+        } else {
+            return Vec3.distance(a.center, b.center) + b.radius <= a.radius
+        }
+    }
+
+    /** Check if `a` and `b` are overlapping */
+    export function overlaps(a: Sphere3D, b: Sphere3D) {
+        return Vec3.distance(a.center, b.center) <= a.radius + b.radius
+    }
+
+    /** Get the signed distance of `a` and `b` */
+    export function distance(a: Sphere3D, b: Sphere3D) {
+        return Vec3.distance(a.center, b.center) - a.radius + b.radius
+    }
 }
 
 export { Sphere3D }