소스 검색

improved bounding sphere- drop hierarchy in favor of extrema points

Alexander Rose 5 년 전
부모
커밋
0ac1cfe555

+ 15 - 11
src/mol-canvas3d/helper/bounding-sphere-helper.ts

@@ -1,5 +1,5 @@
 /**
- * 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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -45,16 +45,16 @@ export class BoundingSphereHelper {
     }
 
     update() {
-        const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey)
+        const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey, sceneMaterialId)
         if (newSceneData) this.sceneData = newSceneData
 
         this.parent.forEach((r, ro) => {
             const objectData = this.objectsData.get(ro)
-            const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato)
+            const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
             if (newObjectData) this.objectsData.set(ro, newObjectData)
 
             const instanceData = this.instancesData.get(ro)
-            const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
+            const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, instanceMaterialId, {
                 aTransform: ro.values.aTransform,
                 matrix: ro.values.matrix,
                 transform: ro.values.transform,
@@ -114,10 +114,10 @@ export class BoundingSphereHelper {
     }
 }
 
-function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, transform?: TransformData) {
+function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, materialId: number, transform?: TransformData) {
     if (!data || !Sphere3D.equals(data.boundingSphere, boundingSphere)) {
         const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh)
-        const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, transform)
+        const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform)
         if (data) {
             ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh))
         } else {
@@ -132,15 +132,19 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
     const vertexCount = sphereVertexCount(detail)
     const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
     if (boundingSphere.radius) {
-        for (const b of Sphere3D.getList(boundingSphere)) {
-            addSphere(builderState, b.center, b.radius, detail)
+        addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
+        if (Sphere3D.hasExtrema(boundingSphere)) {
+            for (const e of boundingSphere.extrema) addSphere(builderState, e, 1.0, 0)
         }
     }
     return MeshBuilder.getMesh(builderState)
 }
 
-const boundingSphereHelberMaterialId = getNextMaterialId()
-function createBoundingSphereRenderObject(mesh: Mesh, color: Color, transform?: TransformData) {
+const sceneMaterialId = getNextMaterialId()
+const objectMaterialId = getNextMaterialId()
+const instanceMaterialId = getNextMaterialId()
+
+function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
     const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform)
-    return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, boundingSphereHelberMaterialId)
+    return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, materialId)
 }

+ 30 - 45
src/mol-gl/renderable/util.ts

@@ -6,7 +6,7 @@
 
 import { Sphere3D } from '../../mol-math/geometry'
 import { Vec3 } from '../../mol-math/linear-algebra'
-import { BoundaryHelper, HierarchyHelper } from '../../mol-math/geometry/boundary-helper';
+import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
 
 export function calculateTextureInfo (n: number, itemSize: number) {
     const sqN = Math.sqrt(n)
@@ -83,22 +83,14 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
 const v = Vec3.zero()
 const boundaryHelperCoarse = new BoundaryHelper('14')
 const boundaryHelperFine = new BoundaryHelper('98')
-const hierarchyHelperCoarse = new HierarchyHelper('14')
-const hierarchyHelperFine = new HierarchyHelper('98')
 
 function getHelper(count: number) {
-    return count > 500_000 ? {
-        boundaryHelper: boundaryHelperCoarse,
-        hierarchyHelper: hierarchyHelperCoarse
-    } : {
-        boundaryHelper: boundaryHelperFine,
-        hierarchyHelper: hierarchyHelperFine
-    }
+    return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
 }
 
 export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D {
     const step = stepFactor * 3
-    const { boundaryHelper, hierarchyHelper } = getHelper(positionCount)
+    const boundaryHelper = getHelper(positionCount)
 
     boundaryHelper.reset()
     for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
@@ -111,49 +103,42 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
         boundaryHelper.radiusStep(v)
     }
 
-    const hierarchyInput = boundaryHelper.getHierarchyInput()
-    if (hierarchyInput) {
-        hierarchyHelper.reset(hierarchyInput.sphere, hierarchyInput.normal)
-        for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
-            Vec3.fromArray(v, position, i)
-            hierarchyHelper.includeStep(v)
-        }
-        hierarchyHelper.finishedIncludeStep()
-        for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
-            Vec3.fromArray(v, position, i)
-            hierarchyHelper.radiusStep(v)
-        }
-        return hierarchyHelper.getSphere()
-    } else {
-        return boundaryHelper.getSphere()
-    }
+    return boundaryHelper.getSphere()
 }
 
 export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number): Sphere3D {
-    const { boundaryHelper } = getHelper(transformCount)
+    const boundaryHelper = getHelper(transformCount)
     boundaryHelper.reset()
 
-    const transformedSpheres: Sphere3D[] = []
-    for (const b of Sphere3D.getList(invariantBoundingSphere)) {
+    const { center, radius, extrema } = invariantBoundingSphere
+
+    if (extrema) {
         for (let i = 0, _i = transformCount; i < _i; ++i) {
-            const c = Vec3.transformMat4Offset(Vec3(), b.center, transform, 0, 0, i * 16)
-            transformedSpheres.push(Sphere3D.create(c as Vec3, b.radius))
+            for (const e of extrema) {
+                Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
+                boundaryHelper.includeStep(v)
+            }
+        }
+        boundaryHelper.finishedIncludeStep()
+        for (let i = 0, _i = transformCount; i < _i; ++i) {
+            for (const e of extrema) {
+                Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
+                boundaryHelper.radiusStep(v)
+            }
+        }
+    } else {
+        for (let i = 0, _i = transformCount; i < _i; ++i) {
+            Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
+            boundaryHelper.includeSphereStep(v, radius)
+        }
+        boundaryHelper.finishedIncludeStep()
+        for (let i = 0, _i = transformCount; i < _i; ++i) {
+            Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
+            boundaryHelper.radiusSphereStep(v, radius)
         }
     }
 
-    for (const b of transformedSpheres) {
-        boundaryHelper.includeSphereStep(b.center, b.radius)
-    }
-    boundaryHelper.finishedIncludeStep()
-    for (const b of transformedSpheres) {
-        boundaryHelper.radiusSphereStep(b.center, b.radius)
-    }
-
-    const sphere = boundaryHelper.getSphere()
-    if (transformedSpheres.length > 1) {
-        (sphere as Sphere3D.Hierarchy).hierarchy = transformedSpheres
-    }
-    return sphere
+    return boundaryHelper.getSphere()
 }
 
 export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {

+ 12 - 4
src/mol-gl/scene.ts

@@ -25,8 +25,12 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base
         const boundingSphere = renderables[i].values.boundingSphere.ref.value
         if (!boundingSphere.radius) continue;
 
-        for (const b of Sphere3D.getList(boundingSphere)) {
-            boundaryHelper.includeSphereStep(b.center, b.radius);
+        if (Sphere3D.hasExtrema(boundingSphere)) {
+            for (const e of boundingSphere.extrema) {
+                boundaryHelper.includeStep(e)
+            }
+        } else {
+            boundaryHelper.includeSphereStep(boundingSphere.center, boundingSphere.radius);
         }
     }
     boundaryHelper.finishedIncludeStep();
@@ -34,8 +38,12 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base
         const boundingSphere = renderables[i].values.boundingSphere.ref.value
         if (!boundingSphere.radius) continue;
 
-        for (const b of Sphere3D.getList(boundingSphere)) {
-            boundaryHelper.radiusSphereStep(b.center, b.radius);
+        if (Sphere3D.hasExtrema(boundingSphere)) {
+            for (const e of boundingSphere.extrema) {
+                boundaryHelper.radiusStep(e)
+            }
+        } else {
+            boundaryHelper.radiusSphereStep(boundingSphere.center, boundingSphere.radius);
         }
     }
 

+ 2 - 83
src/mol-math/geometry/boundary-helper.ts

@@ -11,8 +11,6 @@ import { Box3D } from './primitives/box3d';
 
 // implementing http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
 
-const MinThresholdDist = 0.1
-
 export class BoundaryHelper {
     private dir: Vec3[]
 
@@ -74,30 +72,8 @@ export class BoundaryHelper {
         this.centroidHelper.radiusSphereStep(center, radius);
     }
 
-    getHierarchyInput() {
-        if (this.centroidHelper.getCount() < 2) return false
-
-        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 && maxDist > MinThresholdDist) ? { sphere, normal } : false
-    }
-
     getSphere(sphere?: Sphere3D) {
-        return this.centroidHelper.getSphere(sphere)
+        return Sphere3D.setExtrema(this.centroidHelper.getSphere(sphere), this.extrema)
     }
 
     getBox(box?: Box3D) {
@@ -116,69 +92,12 @@ export class BoundaryHelper {
         this.centroidHelper.reset()
     }
 
-    constructor(quality: EposQuality, private hierarchyThresholdFactor = 0.66) {
+    constructor(quality: EposQuality) {
         this.dir = getEposDir(quality)
         this.reset()
     }
 }
 
-export class HierarchyHelper {
-    private tmpV = Vec3()
-    private tmpS = Sphere3D()
-
-    private sphere = Sphere3D()
-    private normal = Vec3()
-    private helperA = new BoundaryHelper(this.quality)
-    private helperB = new BoundaryHelper(this.quality)
-
-    private checkSide(p: Vec3) {
-        return Vec3.dot(this.normal, Vec3.sub(this.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 {
-        const sphereA = this.helperA.getSphere()
-        const sphereB = this.helperB.getSphere()
-        Sphere3D.expandBySphere(this.tmpS, this.sphere, sphereA)
-        Sphere3D.expandBySphere(this.tmpS, this.tmpS, sphereB)
-        // check if the split spheres actually result in a smaller radius
-        return this.tmpS.radius < this.sphere.radius ? {
-            center: Vec3.clone(this.sphere.center),
-            radius: this.sphere.radius,
-            hierarchy: [this.helperA.getSphere(), this.helperB.getSphere()]
-        } : Sphere3D.clone(this.sphere)
-    }
-
-    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) {

+ 15 - 3
src/mol-math/geometry/boundary.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 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>
@@ -14,7 +14,7 @@ import { Box3D, Sphere3D } from '../geometry';
 const boundaryHelperCoarse = new BoundaryHelper('14');
 const boundaryHelperFine = new BoundaryHelper('98');
 function getBoundaryHelper(count: number) {
-    return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
+    return count > 100_000 ? boundaryHelperCoarse : boundaryHelperFine
 }
 
 export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
@@ -22,6 +22,7 @@ export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
 export function getBoundary(data: PositionData): Boundary {
     const { x, y, z, radius, indices } = data;
     const p = Vec3();
+
     const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
     boundaryHelper.reset();
     for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
@@ -36,5 +37,16 @@ export function getBoundary(data: PositionData): Boundary {
         boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
     }
 
-    return { box: boundaryHelper.getBox(), sphere: boundaryHelper.getSphere() };
+    const sphere = boundaryHelper.getSphere()
+
+    if (!radius && OrderedSet.size(indices) <= 98) {
+        const extrema: Vec3[] = []
+        for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
+            const i = OrderedSet.getAt(indices, t);
+            extrema.push(Vec3.create(x[i], y[i], z[i]));
+        }
+        Sphere3D.setExtrema(sphere, extrema)
+    }
+
+    return { box: boundaryHelper.getBox(), sphere };
 }

+ 29 - 26
src/mol-math/geometry/primitives/sphere3d.ts

@@ -8,24 +8,23 @@
 import { Vec3, Mat4, EPSILON } from '../../linear-algebra'
 import { PositionData } from '../common'
 import { OrderedSet } from '../../../mol-data/int';
-import { NumberArray } from '../../../mol-util/type-helpers';
+import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
 import { Box3D } from './box3d';
 import { Axes3D } from './axes3d';
 
-type Sphere3D = Sphere3D.Data | Sphere3D.Hierarchy
+interface Sphere3D {
+    center: Vec3,
+    radius: number,
+    extrema?: Vec3[]
+}
 
 function Sphere3D() {
     return Sphere3D.zero();
 }
 
 namespace Sphere3D {
-    export interface Data { center: Vec3, radius: number }
-    export interface Hierarchy extends Data { hierarchy: Sphere3D[] }
-    export function isHierarchy(x: Sphere3D | Hierarchy): x is Hierarchy {
-        return 'hierarchy' in x
-    }
-    export function getList(sphere: Sphere3D) {
-        return Sphere3D.isHierarchy(sphere) ? sphere.hierarchy : [sphere]
+    export function hasExtrema(sphere: Sphere3D): sphere is PickRequired<Sphere3D, 'extrema'> {
+        return sphere.extrema !== undefined
     }
 
     export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
@@ -33,24 +32,27 @@ namespace Sphere3D {
 
     export function clone(a: Sphere3D): Sphere3D {
         const out = create(Vec3.clone(a.center), a.radius)
-        if (isHierarchy(a)) (out as Hierarchy).hierarchy = a.hierarchy
+        if (hasExtrema(a)) out.extrema = a.extrema
         return out;
     }
 
     export function copy(out: Sphere3D, a: Sphere3D) {
         Vec3.copy(out.center, a.center)
         out.radius = a.radius
-        if (isHierarchy(a)) {
-            if (isHierarchy(out)) {
-                out.hierarchy.length = 0
-                out.hierarchy.push(...a.hierarchy)
-            } else {
-                (out as Hierarchy).hierarchy = a.hierarchy
-            }
-        }
+        if (hasExtrema(a)) setExtrema(out, a.extrema)
         return out;
     }
 
+    export function setExtrema(out: Sphere3D, extrema: Vec3[]): Sphere3D {
+        if (out.extrema !== undefined) {
+            out.extrema.length = 0
+            out.extrema.push(...extrema)
+        } else {
+            out.extrema = [...extrema]
+        }
+        return out
+    }
+
     export function computeBounding(data: PositionData): Sphere3D {
         const { x, y, z, indices } = data;
         let cx = 0, cy = 0, cz = 0;
@@ -129,18 +131,19 @@ namespace Sphere3D {
         return out
     }
 
+    const tmpDir = Vec3()
     /** Expand sphere radius by delta */
     export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
         Vec3.copy(out.center, sphere.center)
         out.radius = sphere.radius + delta
-        if (isHierarchy(sphere)) {
-            const hierarchy = sphere.hierarchy.map(s => expand(Sphere3D(), s, delta))
-            if (isHierarchy(out)) {
-                out.hierarchy.length = 0
-                out.hierarchy.push(...hierarchy)
-            } else {
-                (out as Hierarchy).hierarchy = hierarchy
-            }
+        if (hasExtrema(sphere)) {
+            setExtrema(out, sphere.extrema.map(e => {
+                Vec3.sub(tmpDir, sphere.center, e)
+                const dist = Vec3.distance(sphere.center, e)
+                Vec3.normalize(tmpDir, tmpDir)
+                return Vec3.scaleAndAdd(Vec3(), e, tmpDir, dist + delta)
+            }))
+            setExtrema(out, sphere.extrema)
         }
         return out
     }