Parcourir la source

use EPOS-based boundary-helper throughout

Alexander Rose il y a 5 ans
Parent
commit
03668216fa

+ 3 - 3
src/mol-gl/renderable/util.ts

@@ -6,7 +6,7 @@
 
 import { Sphere3D } from '../../mol-math/geometry'
 import { Vec3 } from '../../mol-math/linear-algebra'
-import { EposHelper, HierarchyHelper } from '../../mol-math/geometry/epos-helper';
+import { BoundaryHelper, HierarchyHelper } from '../../mol-math/geometry/boundary-helper';
 
 export function calculateTextureInfo (n: number, itemSize: number) {
     const sqN = Math.sqrt(n)
@@ -78,8 +78,8 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
 //
 
 const v = Vec3.zero()
-const eposHelperCoarse = new EposHelper('14')
-const eposHelperFine = new EposHelper('98')
+const eposHelperCoarse = new BoundaryHelper('14')
+const eposHelperFine = new BoundaryHelper('98')
 const hierarchyHelperCoarse = new HierarchyHelper('14')
 const hierarchyHelperFine = new HierarchyHelper('98')
 

+ 2 - 2
src/mol-gl/scene.ts

@@ -14,9 +14,9 @@ import { Sphere3D } from '../mol-math/geometry';
 import { CommitQueue } from './commit-queue';
 import { now } from '../mol-util/now';
 import { arraySetRemove } from '../mol-util/array';
-import { EposHelper } from '../mol-math/geometry/epos-helper';
+import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
 
-const eposHelper = new EposHelper('98')
+const eposHelper = new BoundaryHelper('98')
 
 function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
     eposHelper.reset();

+ 174 - 85
src/mol-math/geometry/boundary-helper.ts

@@ -1,124 +1,213 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Vec3 } from '../../mol-math/linear-algebra/3d';
+import { Vec3 } from '../linear-algebra/3d';
+import { CentroidHelper } from './centroid-helper';
+import { Sphere3D } from '../geometry';
 import { Box3D } from './primitives/box3d';
-import { Sphere3D } from './primitives/sphere3d';
 
-/**
- * Usage:
- *
- * 1. .reset(tolerance); tolerance plays part in the "extend" step
- * 2. for each point/sphere call boundaryStep()
- * 3. .finishBoundaryStep
- * 4. for each point/sphere call extendStep
- * 5. use .center/.radius or call getSphere/getBox
- */
+// implementing http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
+
 export class BoundaryHelper {
-    private count = 0;
-    private extremes = [Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero()];
-    private u = Vec3.zero();
-    private v = Vec3.zero();
-
-    tolerance = 0;
-    center: Vec3 = Vec3.zero();
-    radius = 0;
-
-    reset(tolerance: number) {
-        Vec3.set(this.center, 0, 0, 0);
-        for (let i = 0; i < 6; i++) {
-            const e = i % 2 === 0 ? Number.MAX_VALUE : -Number.MAX_VALUE;
-            Vec3.set(this.extremes[i], e, e, e);
+    private dir: Vec3[]
+
+    private minDist: number[] = []
+    private maxDist: number[] = []
+    private extrema: Vec3[] = []
+    centroidHelper = new CentroidHelper()
+
+    private computeExtrema(i: number, p: Vec3) {
+        const d = Vec3.dot(this.dir[i], p)
+
+        if (d < this.minDist[i]) {
+            this.minDist[i] = d
+            Vec3.copy(this.extrema[i * 2], p)
+        }
+        if (d > this.maxDist[i]) {
+            this.maxDist[i] = d
+            Vec3.copy(this.extrema[i * 2 + 1], p)
+        }
+    }
+
+    private computeSphereExtrema(i: number, center: Vec3, radius: number) {
+        const d = Vec3.dot(this.dir[i], center)
+
+        if (d - radius < this.minDist[i]) {
+            this.minDist[i] = d - radius
+            Vec3.scaleAndSub(this.extrema[i * 2], center, this.dir[i], radius)
+        }
+        if (d + radius > this.maxDist[i]) {
+            this.maxDist[i] = d + radius
+            Vec3.scaleAndAdd(this.extrema[i * 2 + 1], center, this.dir[i], radius)
         }
-        this.radius = 0;
-        this.count = 0;
-        this.tolerance = tolerance;
     }
 
-    boundaryStep(p: Vec3, r: number) {
-        updateExtremeMin(0, this.extremes[0], p, r);
-        updateExtremeMax(0, this.extremes[1], p, r);
+    includeStep(p: Vec3) {
+        for (let i = 0, il = this.dir.length; i < il; ++i) {
+            this.computeExtrema(i, p)
+        }
+    }
 
-        updateExtremeMin(1, this.extremes[2], p, r);
-        updateExtremeMax(1, this.extremes[3], p, r);
+    includeSphereStep(center: Vec3, radius: number) {
+        for (let i = 0, il = this.dir.length; i < il; ++i) {
+            this.computeSphereExtrema(i, center, radius)
+        }
+    }
+
+    finishedIncludeStep() {
+        for (let i = 0; i < this.extrema.length; i++) {
+            this.centroidHelper.includeStep(this.extrema[i]);
+        }
+        this.centroidHelper.finishedIncludeStep();
+    }
+
+    radiusStep(p: Vec3) {
+        this.centroidHelper.radiusStep(p);
+    }
 
-        updateExtremeMin(2, this.extremes[4], p, r);
-        updateExtremeMax(2, this.extremes[5], p, r);
-        this.count++;
+    radiusSphereStep(center: Vec3, radius: number) {
+        this.centroidHelper.radiusSphereStep(center, radius);
     }
 
-    finishBoundaryStep() {
-        if (this.count === 0) return;
+    getHierarchyInput() {
+        const sphere = this.centroidHelper.getSphere();
+        const normal = Vec3()
+        const t = sphere.radius * this.hierarchyThresholdFactor
 
-        let maxSpan = 0, mI = 0, mJ = 0;
+        let maxDist = -Infinity
+        let belowThreshold = false
 
-        for (let i = 0; i < 5; i++) {
-            for (let j = i + 1; j < 6; j++) {
-                const d = Vec3.squaredDistance(this.extremes[i], this.extremes[j]);
-                if (d > maxSpan) {
-                    maxSpan = d;
-                    mI = i;
-                    mJ = j;
-                }
+        for (let i = 0; i < this.extrema.length; i += 2) {
+            const halfDist = Vec3.distance(this.extrema[i], this.extrema[i + 1]) / 2
+            if (halfDist > maxDist) {
+                maxDist = halfDist
+                Vec3.normalize(normal, Vec3.sub(normal, this.extrema[i], this.extrema[i + 1]))
             }
+            if (halfDist < t) belowThreshold = true
         }
 
-        Vec3.add(this.center, this.extremes[mI], this.extremes[mJ]);
-        Vec3.scale(this.center, this.center, 0.5);
-        this.radius = Vec3.distance(this.center, this.extremes[mI]);
+        return belowThreshold ? { sphere, normal } : false
     }
 
-    extendStep(p: Vec3, r: number) {
-        const d = Vec3.distance(p, this.center);
-        if ((1 + this.tolerance) * this.radius >= r + d) return;
+    getSphere(sphere?: Sphere3D) {
+        return this.centroidHelper.getSphere(sphere)
+    }
 
-        Vec3.sub(this.u, p, this.center);
-        Vec3.normalize(this.u, this.u);
+    getBox(box?: Box3D) {
+        // TODO can we get a tighter box from the extrema???
+        if (!box) box = Box3D()
+        return Box3D.fromSphere3D(box, this.centroidHelper.getSphere())
+    }
 
-        Vec3.scale(this.v, this.u, -this.radius);
-        Vec3.add(this.v, this.v, this.center);
-        Vec3.scale(this.u, this.u, r + d);
-        Vec3.add(this.u, this.u, this.center);
+    reset() {
+        for (let i = 0, il = this.dir.length; i < il; ++i) {
+            this.minDist[i] = Infinity
+            this.maxDist[i] = -Infinity
+            this.extrema[i * 2] = Vec3()
+            this.extrema[i * 2 + 1] = Vec3()
+        }
+        this.centroidHelper.reset()
+    }
 
-        Vec3.add(this.center, this.u, this.v);
-        Vec3.scale(this.center, this.center, 0.5);
-        this.radius = 0.5 * (r + d + this.radius);
+    constructor(quality: EposQuality, private hierarchyThresholdFactor = 0.66) {
+        this.dir = getEposDir(quality)
+        this.reset()
     }
+}
+
+const tmpV = Vec3()
 
-    getBox(): Box3D {
-        Vec3.copy(this.u, this.extremes[0]);
-        Vec3.copy(this.v, this.extremes[0]);
+export class HierarchyHelper {
+    private sphere = Sphere3D()
+    private normal = Vec3()
+    private helperA = new BoundaryHelper(this.quality)
+    private helperB = new BoundaryHelper(this.quality)
 
-        for (let i = 1; i < 6; i++) {
-            Vec3.min(this.u, this.u, this.extremes[i]);
-            Vec3.max(this.v, this.v, this.extremes[i]);
+    private checkSide(p: Vec3) {
+        return Vec3.dot(this.normal, Vec3.sub(tmpV, this.sphere.center, p)) > 0
+    }
+
+    includeStep(p: Vec3) {
+        if (this.checkSide(p)) {
+            this.helperA.includeStep(p)
+        } else {
+            this.helperB.includeStep(p)
         }
+    }
 
-        return { min: Vec3.clone(this.u), max: Vec3.clone(this.v) };
+    finishedIncludeStep() {
+        this.helperA.finishedIncludeStep();
+        this.helperB.finishedIncludeStep();
     }
 
-    getSphere(): Sphere3D {
-        return { center: Vec3.clone(this.center), radius: this.radius };
+    radiusStep(p: Vec3) {
+        if (this.checkSide(p)) {
+            this.helperA.radiusStep(p)
+        } else {
+            this.helperB.radiusStep(p)
+        }
     }
 
-    constructor() {
-        this.reset(0);
+    getSphere(): Sphere3D.Hierarchy {
+        return {
+            center: this.sphere.center,
+            radius: this.sphere.radius,
+            hierarchy: [this.helperA.getSphere(), this.helperB.getSphere()]
+        }
     }
-}
 
-function updateExtremeMin(d: number, e: Vec3, center: Vec3, r: number) {
-    if (center[d] - r < e[d]) {
-        Vec3.copy(e, center);
-        e[d] -= r;
+    reset(sphere: Sphere3D, normal: Vec3) {
+        Sphere3D.copy(this.sphere, sphere)
+        Vec3.copy(this.normal, normal)
+        this.helperA.reset()
+        this.helperB.reset()
     }
+
+    constructor(private quality: EposQuality) { }
 }
 
-function updateExtremeMax(d: number, e: Vec3, center: Vec3, r: number) {
-    if (center[d] + r > e[d]) {
-        Vec3.copy(e, center);
-        e[d] += r;
+type EposQuality = '6' | '14' | '26' | '98'
+
+function getEposDir(quality: EposQuality) {
+    let dir: number[][]
+    switch (quality) {
+        case '6': dir = [ ...Type001 ]; break
+        case '14': dir = [ ...Type001, ...Type111 ]; break
+        case '26': dir = [ ...Type001, ...Type111, ...Type011 ]; break
+        case '98': dir = [ ...Type001, ...Type111, ...Type011, ...Type012, ...Type112, ...Type122 ]; break
     }
-}
+    return dir.map(a => {
+        const v = Vec3.create(a[0], a[1], a[2])
+        return Vec3.normalize(v, v)
+    })
+}
+
+const Type001 = [
+    [1, 0, 0], [0, 1, 0], [0, 0, 1]
+]
+
+const Type111 = [
+    [1, 1, 1], [-1, 1, 1], [-1, -1, 1], [1, -1, 1]
+]
+
+const Type011 = [
+    [1, 1, 0], [1, -1, 0], [1, 0, 1], [1, 0, -1], [0, 1, 1], [0, 1, -1]
+]
+
+const Type012 = [
+    [0, 1, 2], [0, 2, 1], [1, 0, 2], [2, 0, 1], [1, 2, 0], [2, 1, 0],
+    [0, 1, -2], [0, 2, -1], [1, 0, -2], [2, 0, -1], [1, -2, 0], [2, -1, 0]
+]
+
+const Type112 = [
+    [1, 1, 2], [2, 1, 1], [1, 2, 1], [1, -1, 2], [1, 1, -2], [1, -1, -2],
+    [2, -1, 1], [2, 1, -1], [2, -1, -1], [1, -2, 1], [1, 2, -1], [1, -2, -1]
+]
+
+const Type122 = [
+    [2, 2, 1], [1, 2, 2], [2, 1, 2], [2, -2, 1], [2, 2, -1], [2, -2, -1],
+    [1, -2, 2], [1, 2, -2], [1, -2, -2], [2, -1, 2], [2, 1, -2], [2, -1, -2]
+]

+ 0 - 206
src/mol-math/geometry/epos-helper.ts

@@ -1,206 +0,0 @@
-/**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Vec3 } from '../linear-algebra/3d';
-import { CentroidHelper } from './centroid-helper';
-import { Sphere3D } from '../geometry';
-
-// implementing http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
-
-export class EposHelper {
-    private dir: Vec3[]
-
-    private minDist: number[] = []
-    private maxDist: number[] = []
-    private extrema: Vec3[] = []
-    centroidHelper = new CentroidHelper()
-
-    private computeExtrema(i: number, p: Vec3) {
-        const d = Vec3.dot(this.dir[i], p)
-
-        if (d < this.minDist[i]) {
-            this.minDist[i] = d
-            Vec3.copy(this.extrema[i * 2], p)
-        }
-        if (d > this.maxDist[i]) {
-            this.maxDist[i] = d
-            Vec3.copy(this.extrema[i * 2 + 1], p)
-        }
-    }
-
-    private computeSphereExtrema(i: number, center: Vec3, radius: number) {
-        const d = Vec3.dot(this.dir[i], center)
-
-        if (d - radius < this.minDist[i]) {
-            this.minDist[i] = d - radius
-            Vec3.scaleAndSub(this.extrema[i * 2], center, this.dir[i], radius)
-        }
-        if (d + radius > this.maxDist[i]) {
-            this.maxDist[i] = d + radius
-            Vec3.scaleAndAdd(this.extrema[i * 2 + 1], center, this.dir[i], radius)
-        }
-    }
-
-    includeStep(p: Vec3) {
-        for (let i = 0, il = this.dir.length; i < il; ++i) {
-            this.computeExtrema(i, p)
-        }
-    }
-
-    includeSphereStep(center: Vec3, radius: number) {
-        for (let i = 0, il = this.dir.length; i < il; ++i) {
-            this.computeSphereExtrema(i, center, radius)
-        }
-    }
-
-    finishedIncludeStep() {
-        for (let i = 0; i < this.extrema.length; i++) {
-            this.centroidHelper.includeStep(this.extrema[i]);
-        }
-        this.centroidHelper.finishedIncludeStep();
-    }
-
-    radiusStep(p: Vec3) {
-        this.centroidHelper.radiusStep(p);
-    }
-
-    radiusSphereStep(center: Vec3, radius: number) {
-        this.centroidHelper.radiusSphereStep(center, radius);
-    }
-
-    getHierarchyInput() {
-        const sphere = this.centroidHelper.getSphere();
-        const normal = Vec3()
-        const t = sphere.radius * this.hierarchyThresholdFactor
-
-        let maxDist = -Infinity
-        let belowThreshold = false
-
-        for (let i = 0; i < this.extrema.length; i += 2) {
-            const halfDist = Vec3.distance(this.extrema[i], this.extrema[i + 1]) / 2
-            if (halfDist > maxDist) {
-                maxDist = halfDist
-                Vec3.normalize(normal, Vec3.sub(normal, this.extrema[i], this.extrema[i + 1]))
-            }
-            if (halfDist < t) belowThreshold = true
-        }
-
-        return belowThreshold ? { sphere, normal } : false
-    }
-
-    getSphere(sphere?: Sphere3D) {
-        return this.centroidHelper.getSphere(sphere)
-    }
-
-    reset() {
-        for (let i = 0, il = this.dir.length; i < il; ++i) {
-            this.minDist[i] = Infinity
-            this.maxDist[i] = -Infinity
-            this.extrema[i * 2] = Vec3()
-            this.extrema[i * 2 + 1] = Vec3()
-        }
-        this.centroidHelper.reset()
-    }
-
-    constructor(quality: EposQuality, private hierarchyThresholdFactor = 0.66) {
-        this.dir = getEposDir(quality)
-        this.reset()
-    }
-}
-
-const tmpV = Vec3()
-
-export class HierarchyHelper {
-    private sphere = Sphere3D()
-    private normal = Vec3()
-    private helperA = new EposHelper(this.quality)
-    private helperB = new EposHelper(this.quality)
-
-    private checkSide(p: Vec3) {
-        return Vec3.dot(this.normal, Vec3.sub(tmpV, this.sphere.center, p)) > 0
-    }
-
-    includeStep(p: Vec3) {
-        if (this.checkSide(p)) {
-            this.helperA.includeStep(p)
-        } else {
-            this.helperB.includeStep(p)
-        }
-    }
-
-    finishedIncludeStep() {
-        this.helperA.finishedIncludeStep();
-        this.helperB.finishedIncludeStep();
-    }
-
-    radiusStep(p: Vec3) {
-        if (this.checkSide(p)) {
-            this.helperA.radiusStep(p)
-        } else {
-            this.helperB.radiusStep(p)
-        }
-    }
-
-    getSphere(): Sphere3D.Hierarchy {
-        return {
-            center: this.sphere.center,
-            radius: this.sphere.radius,
-            hierarchy: [this.helperA.getSphere(), this.helperB.getSphere()]
-        }
-    }
-
-    reset(sphere: Sphere3D, normal: Vec3) {
-        Sphere3D.copy(this.sphere, sphere)
-        Vec3.copy(this.normal, normal)
-        this.helperA.reset()
-        this.helperB.reset()
-    }
-
-    constructor(private quality: EposQuality) { }
-}
-
-type EposQuality = '6' | '14' | '26' | '98'
-
-function getEposDir(quality: EposQuality) {
-    let dir: number[][]
-    switch (quality) {
-        case '6': dir = [ ...Type001 ]; break
-        case '14': dir = [ ...Type001, ...Type111 ]; break
-        case '26': dir = [ ...Type001, ...Type111, ...Type011 ]; break
-        case '98': dir = [ ...Type001, ...Type111, ...Type011, ...Type012, ...Type112, ...Type122 ]; break
-    }
-    return dir.map(a => {
-        const v = Vec3.create(a[0], a[1], a[2])
-        return Vec3.normalize(v, v)
-    })
-}
-
-const Type001 = [
-    [1, 0, 0], [0, 1, 0], [0, 0, 1]
-]
-
-const Type111 = [
-    [1, 1, 1], [-1, 1, 1], [-1, -1, 1], [1, -1, 1]
-]
-
-const Type011 = [
-    [1, 1, 0], [1, -1, 0], [1, 0, 1], [1, 0, -1], [0, 1, 1], [0, 1, -1]
-]
-
-const Type012 = [
-    [0, 1, 2], [0, 2, 1], [1, 0, 2], [2, 0, 1], [1, 2, 0], [2, 1, 0],
-    [0, 1, -2], [0, 2, -1], [1, 0, -2], [2, 0, -1], [1, -2, 0], [2, -1, 0]
-]
-
-const Type112 = [
-    [1, 1, 2], [2, 1, 1], [1, 2, 1], [1, -1, 2], [1, 1, -2], [1, -1, -2],
-    [2, -1, 1], [2, 1, -1], [2, -1, -1], [1, -2, 1], [1, 2, -1], [1, -2, -1]
-]
-
-const Type122 = [
-    [2, 2, 1], [1, 2, 2], [2, 1, 2], [2, -2, 1], [2, 2, -1], [2, -2, -1],
-    [1, -2, 2], [1, 2, -2], [1, -2, -2], [2, -1, 2], [2, 1, -2], [2, -1, -2]
-]

+ 11 - 5
src/mol-math/geometry/lookup3d/grid.ts

@@ -166,21 +166,27 @@ function _build(state: BuildState): Grid3D {
     }
 }
 
-const boundaryHelper = new BoundaryHelper();
+const boundaryHelperCoarse = new BoundaryHelper('14');
+const boundaryHelperFine = new BoundaryHelper('98');
+function getBoundaryHelper(count: number) {
+    return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
+}
+
 function getBoundary(data: PositionData) {
     const { x, y, z, radius, indices } = data;
     const p = Vec3();
-    boundaryHelper.reset(0);
+    const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
+    boundaryHelper.reset();
     for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
         const i = OrderedSet.getAt(indices, t);
         Vec3.set(p, x[i], y[i], z[i]);
-        boundaryHelper.boundaryStep(p, (radius && radius[i]) || 0);
+        boundaryHelper.includeSphereStep(p, (radius && radius[i]) || 0);
     }
-    boundaryHelper.finishBoundaryStep();
+    boundaryHelper.finishedIncludeStep();
     for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
         const i = OrderedSet.getAt(indices, t);
         Vec3.set(p, x[i], y[i], z[i]);
-        boundaryHelper.extendStep(p, (radius && radius[i]) || 0);
+        boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
     }
 
     return { boundingBox: boundaryHelper.getBox(), boundingSphere: boundaryHelper.getSphere() };

+ 8 - 0
src/mol-math/geometry/primitives/box3d.ts

@@ -8,6 +8,7 @@
 import { Vec3, Mat4 } from '../../linear-algebra'
 import { PositionData } from '../common'
 import { OrderedSet } from '../../../mol-data/int';
+import { Sphere3D } from './sphere3d';
 
 interface Box3D { min: Vec3, max: Vec3 }
 
@@ -29,6 +30,13 @@ namespace Box3D {
         return copy(empty(), a);
     }
 
+    export function fromSphere3D(out: Box3D, sphere: Sphere3D): Box3D {
+        const r = Vec3.create(sphere.radius, sphere.radius, sphere.radius)
+        Vec3.sub(out.min, sphere.center, r)
+        Vec3.add(out.max, sphere.center, r)
+        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);

+ 0 - 10
src/mol-math/geometry/primitives/sphere3d.ts

@@ -11,7 +11,6 @@ import { OrderedSet } from '../../../mol-data/int';
 import { NumberArray } from '../../../mol-util/type-helpers';
 import { Box3D } from './box3d';
 import { Axes3D } from './axes3d';
-import { BoundaryHelper } from '../boundary-helper';
 
 type Sphere3D = Sphere3D.Data | Sphere3D.Hierarchy
 
@@ -111,15 +110,6 @@ namespace Sphere3D {
         return out
     }
 
-    const boundaryHelper = new BoundaryHelper();
-    export function fromSphere3Ds(spheres: Sphere3D[]): Sphere3D {
-        boundaryHelper.reset(0);
-        for (const s of spheres) boundaryHelper.boundaryStep(s.center, s.radius);
-        boundaryHelper.finishBoundaryStep();
-        for (const s of spheres) boundaryHelper.extendStep(s.center, s.radius);
-        return boundaryHelper.getSphere();
-    }
-
     const tmpAddVec3 = Vec3()
     export function addVec3(out: Sphere3D, s: Sphere3D, v: Vec3) {
         const d = Vec3.distance(s.center, v)

+ 8 - 2
src/mol-model/loci.ts

@@ -14,6 +14,7 @@ import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes'
 import { ParamDefinition } from '../mol-util/param-definition';
 import { shallowEqual } from '../mol-util';
 import { FiniteArray } from '../mol-util/type-helpers';
+import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
 
 /** A Loci that includes every loci */
 export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
@@ -65,9 +66,14 @@ type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | Emp
 namespace Loci {
     export interface Bundle<L extends number> { loci: FiniteArray<Loci, L> }
 
+    const boundaryHelper = new BoundaryHelper('98');
     export function getBundleBoundingSphere(bundle: Bundle<any>): Sphere3D {
-        const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s)
-        return Sphere3D.fromSphere3Ds(spheres as Sphere3D[])
+        const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s) as Sphere3D[]
+        boundaryHelper.reset();
+        for (const s of spheres) boundaryHelper.includeSphereStep(s.center, s.radius);
+        boundaryHelper.finishedIncludeStep();
+        for (const s of spheres) boundaryHelper.radiusSphereStep(s.center, s.radius);
+        return boundaryHelper.getSphere();
     }
 
     export function areEqual(lociA: Loci, lociB: Loci) {

+ 7 - 7
src/mol-model/structure/structure/element/loci.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,7 +7,6 @@
 
 import { UniqueArray } from '../../../../mol-data/generic';
 import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int';
-import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
 import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
 import Structure from '../structure';
@@ -22,6 +21,7 @@ import { ChainIndex } from '../../model/indexing';
 import { PrincipalAxes } from '../../../../mol-math/linear-algebra/matrix/principal-axes';
 import { NumberArray } from '../../../../mol-util/type-helpers';
 import StructureProperties from '../properties';
+import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
 
 /** Represents multiple structure element index locations */
 export interface Loci {
@@ -456,10 +456,10 @@ export namespace Loci {
 
     //
 
-    const boundaryHelper = new BoundaryHelper();
+    const boundaryHelper = new BoundaryHelper('98');
     const tempPosBoundary = Vec3.zero();
     export function getBoundary(loci: Loci): Boundary {
-        boundaryHelper.reset(0);
+        boundaryHelper.reset();
 
         for (const e of loci.elements) {
             const { indices } = e;
@@ -468,10 +468,10 @@ export namespace Loci {
             for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
                 const eI = elements[OrderedSet.getAt(indices, i)];
                 pos(eI, tempPosBoundary);
-                boundaryHelper.boundaryStep(tempPosBoundary, r(eI));
+                boundaryHelper.includeSphereStep(tempPosBoundary, r(eI));
             }
         }
-        boundaryHelper.finishBoundaryStep();
+        boundaryHelper.finishedIncludeStep();
         for (const e of loci.elements) {
             const { indices } = e;
             const pos = e.unit.conformation.position, r = e.unit.conformation.r;
@@ -479,7 +479,7 @@ export namespace Loci {
             for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
                 const eI = elements[OrderedSet.getAt(indices, i)];
                 pos(eI, tempPosBoundary);
-                boundaryHelper.extendStep(tempPosBoundary, r(eI));
+                boundaryHelper.radiusSphereStep(tempPosBoundary, r(eI));
             }
         }
 

+ 11 - 56
src/mol-model/structure/structure/util/boundary.ts

@@ -1,74 +1,29 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { Box3D, Sphere3D } from '../../../../mol-math/geometry';
-import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
 import Structure from '../structure';
-import { CentroidHelper } from '../../../../mol-math/geometry/centroid-helper';
+import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
 
 export type Boundary = { box: Box3D, sphere: Sphere3D }
 
-const tmpBox = Box3D.empty();
-const tmpSphere = Sphere3D.zero();
-const tmpVec = Vec3.zero();
+const tmpBox = Box3D();
+const tmpSphere = Sphere3D();
 
-const boundaryHelper = new BoundaryHelper();
-const centroidHelper = new CentroidHelper();
-
-// TODO: show this be enabled? does not solve problem with "renderable bounding spheres"
-const CENTROID_COMPUTATION_THRESHOLD = -1;
+const boundaryHelper = new BoundaryHelper('98');
 
 export function computeStructureBoundary(s: Structure): Boundary {
-    if (s.elementCount <= CENTROID_COMPUTATION_THRESHOLD && s.isAtomic) return computeStructureBoundaryFromElements(s);
-    return computeStructureBoundaryFromUnits(s);
-}
-
-export function computeStructureBoundaryFromElements(s: Structure): Boundary {
-    centroidHelper.reset();
-
-    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)
-
-    const v = tmpVec;
-    for (const unit of s.units) {
-        const { x, y, z } = unit.conformation;
-        const elements = unit.elements;
-        for (let j = 0, _j = elements.length; j < _j; j++) {
-            const e = elements[j];
-            Vec3.set(v, x(e), y(e), z(e));
-            centroidHelper.includeStep(v);
-            Vec3.min(min, min, v);
-            Vec3.max(max, max, v);
-        };
-    }
-
-    centroidHelper.finishedIncludeStep();
-
-    for (const unit of s.units) {
-        const { x, y, z } = unit.conformation;
-        const elements = unit.elements;
-        for (let j = 0, _j = elements.length; j < _j; j++) {
-            const e = elements[j];
-            Vec3.set(v, x(e), y(e), z(e));
-            centroidHelper.radiusStep(v);
-        };
-    }
-
-    return { box: { min, max }, sphere: centroidHelper.getSphere() };
-}
-
-export function computeStructureBoundaryFromUnits(s: Structure): Boundary {
     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)
 
     const { units } = s;
 
-    boundaryHelper.reset(0);
+    boundaryHelper.reset();
 
     for (let i = 0, _i = units.length; i < _i; i++) {
         const u = units[i];
@@ -79,18 +34,18 @@ export function computeStructureBoundaryFromUnits(s: Structure): Boundary {
             Vec3.min(min, min, invariantBoundary.box.min);
             Vec3.max(max, max, invariantBoundary.box.max);
 
-            boundaryHelper.boundaryStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
+            boundaryHelper.includeSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
         } else {
             Box3D.transform(tmpBox, invariantBoundary.box, o.matrix);
             Vec3.min(min, min, tmpBox.min);
             Vec3.max(max, max, tmpBox.max);
 
             Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
-            boundaryHelper.boundaryStep(tmpSphere.center, tmpSphere.radius);
+            boundaryHelper.includeSphereStep(tmpSphere.center, tmpSphere.radius);
         }
     }
 
-    boundaryHelper.finishBoundaryStep();
+    boundaryHelper.finishedIncludeStep();
 
     for (let i = 0, _i = units.length; i < _i; i++) {
         const u = units[i];
@@ -98,10 +53,10 @@ export function computeStructureBoundaryFromUnits(s: Structure): Boundary {
         const o = u.conformation.operator;
 
         if (o.isIdentity) {
-            boundaryHelper.extendStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
+            boundaryHelper.radiusSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
         } else {
             Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
-            boundaryHelper.extendStep(tmpSphere.center, tmpSphere.radius);
+            boundaryHelper.radiusSphereStep(tmpSphere.center, tmpSphere.radius);
         }
     }
 

+ 8 - 10
src/mol-plugin-state/manager/structure/selection.ts

@@ -13,13 +13,13 @@ import { Boundary } from '../../../mol-model/structure/structure/util/boundary';
 import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
 import { structureElementStatsLabel } from '../../../mol-theme/label';
 import { OrderedSet } from '../../../mol-data/int';
-import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
 import { arrayRemoveAtInPlace } from '../../../mol-util/array';
 import { EmptyLoci, Loci } from '../../../mol-model/loci';
 import { StateObject, StateSelection } from '../../../mol-state';
 import { PluginStateObject } from '../../objects';
 import { StructureSelectionQuery } from '../../helpers/structure-selection-query';
 import { Task } from '../../../mol-task';
+import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
 
 interface StructureSelectionManagerState {
     entries: Map<string, SelectionEntry>,
@@ -27,12 +27,12 @@ interface StructureSelectionManagerState {
     stats?: SelectionStats
 }
 
-const boundaryHelper = new BoundaryHelper();
+const boundaryHelper = new BoundaryHelper('98');
 const HISTORY_CAPACITY = 8;
 
 export type StructureSelectionModifier = 'add' | 'remove' | 'set'
 
-export class StructureSelectionManager extends PluginComponent<StructureSelectionManagerState> {    
+export class StructureSelectionManager extends PluginComponent<StructureSelectionManagerState> {
     readonly events = {
         changed: this.ev<undefined>()
     }
@@ -83,7 +83,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
 
     private add(loci: Loci): boolean {
         if (!StructureElement.Loci.is(loci)) return false;
-        
+
         const entry = this.getEntry(loci.structure);
         if (!entry) return false;
 
@@ -288,7 +288,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
         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)
 
-        boundaryHelper.reset(0);
+        boundaryHelper.reset();
 
         const boundaries: Boundary[] = []
         this.entries.forEach(v => {
@@ -302,14 +302,12 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
             const { box, sphere } = boundaries[i];
             Vec3.min(min, min, box.min);
             Vec3.max(max, max, box.max);
-            boundaryHelper.boundaryStep(sphere.center, sphere.radius)
+            boundaryHelper.includeSphereStep(sphere.center, sphere.radius)
         }
-
-        boundaryHelper.finishBoundaryStep();
-
+        boundaryHelper.finishedIncludeStep();
         for (let i = 0, il = boundaries.length; i < il; ++i) {
             const { sphere } = boundaries[i];
-            boundaryHelper.extendStep(sphere.center, sphere.radius);
+            boundaryHelper.radiusSphereStep(sphere.center, sphere.radius);
         }
 
         return { box: { min, max }, sphere: boundaryHelper.getSphere() };

+ 6 - 6
src/mol-repr/structure/visual/label-text.ts

@@ -64,7 +64,7 @@ function createLabelText(ctx: VisualContext, structure: Structure, theme: Theme,
 //
 
 const tmpVec = Vec3();
-const boundaryHelper = new BoundaryHelper();
+const boundaryHelper = new BoundaryHelper('98');
 
 function createChainText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text {
     const l = StructureElement.Location.create(structure);
@@ -117,20 +117,20 @@ function createResidueText(ctx: VisualContext, structure: Structure, theme: Them
             j++;
             while (j < jl && residueIndex[elements[j]] === rI) j++;
 
-            boundaryHelper.reset(0);
+            boundaryHelper.reset();
             for (let eI = start; eI < j; eI++) {
                 pos(elements[eI], tmpVec);
-                boundaryHelper.boundaryStep(tmpVec, 0);
+                boundaryHelper.includeStep(tmpVec);
             }
-            boundaryHelper.finishBoundaryStep();
+            boundaryHelper.finishedIncludeStep();
             for (let eI = start; eI < j; eI++) {
                 pos(elements[eI], tmpVec);
-                boundaryHelper.extendStep(tmpVec, 0);
+                boundaryHelper.radiusStep(tmpVec);
             }
 
             l.element = elements[start];
 
-            const { center, radius } = boundaryHelper
+            const { center, radius } = boundaryHelper.getSphere()
             const authSeqId = auth_seq_id(l)
             const compId = label_comp_id(l)