Browse Source

unit boundary handling improvements

- tryAdjustBoundary from previous boundary
- box3d & boundary tweaks
Alexander Rose 4 years ago
parent
commit
dcfe2e3072

+ 1 - 6
src/mol-math/geometry/boundary-helper.ts

@@ -97,12 +97,7 @@ export class BoundaryHelper {
     }
 
     getBox(box?: Box3D) {
-        if (!box) box = Box3D();
-        Box3D.setEmpty(box);
-        for (let i = 0; i < this.extrema.length; i++) {
-            Box3D.add(box, this.extrema[i]);
-        }
-        return box;
+        return Box3D.fromVec3Array(box || Box3D(), this.extrema);
     }
 
     reset() {

+ 34 - 4
src/mol-math/geometry/boundary.ts

@@ -11,30 +11,35 @@ import { OrderedSet } from '../../mol-data/int';
 import { BoundaryHelper } from './boundary-helper';
 import { Box3D, Sphere3D } from '../geometry';
 
+export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
+
+// avoiding namespace lookup improved performance in Chrome (Aug 2020)
+const v3set = Vec3.set;
+const v3squaredDistance = Vec3.squaredDistance;
+
 const boundaryHelperCoarse = new BoundaryHelper('14');
 const boundaryHelperFine = new BoundaryHelper('98');
 function getBoundaryHelper(count: number) {
     return count > 10_000 ? boundaryHelperCoarse : boundaryHelperFine;
 }
 
-export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
+const p = Vec3();
 
 export function getBoundary(data: PositionData): Boundary {
     const { x, y, z, radius, indices } = data;
-    const p = Vec3();
     const n = OrderedSet.size(indices);
 
     const boundaryHelper = getBoundaryHelper(n);
     boundaryHelper.reset();
     for (let t = 0; t < n; t++) {
         const i = OrderedSet.getAt(indices, t);
-        Vec3.set(p, x[i], y[i], z[i]);
+        v3set(p, x[i], y[i], z[i]);
         boundaryHelper.includePositionRadius(p, (radius && radius[i]) || 0);
     }
     boundaryHelper.finishedIncludeStep();
     for (let t = 0; t < n; t++) {
         const i = OrderedSet.getAt(indices, t);
-        Vec3.set(p, x[i], y[i], z[i]);
+        v3set(p, x[i], y[i], z[i]);
         boundaryHelper.radiusPositionRadius(p, (radius && radius[i]) || 0);
     }
 
@@ -50,4 +55,29 @@ export function getBoundary(data: PositionData): Boundary {
     }
 
     return { box: boundaryHelper.getBox(), sphere };
+}
+
+export function tryAdjustBoundary(data: PositionData, boundary: Boundary): Boundary | undefined {
+    const { x, y, z, indices } = data;
+    const n = OrderedSet.size(indices);
+    const { center, radius } = boundary.sphere;
+
+    let maxDistSq = 0;
+    for (let t = 0; t < n; t++) {
+        const i = OrderedSet.getAt(indices, t);
+        v3set(p, x[i], y[i], z[i]);
+        const distSq = v3squaredDistance(p, center);
+        if (distSq > maxDistSq) maxDistSq = distSq;
+    }
+
+    const adjustedRadius = Math.sqrt(maxDistSq);
+    const deltaRadius = adjustedRadius - radius;
+    if (Math.abs(deltaRadius) < radius / 100) {
+        // TODO: The expanded sphere extrema are not correct if the principal axes differ
+        const sphere = Sphere3D.expand(Sphere3D(), boundary.sphere, deltaRadius);
+        const box = Box3D.fromSphere3D(Box3D(), sphere);
+        return { box, sphere };
+    } else {
+        return undefined;
+    }
 }

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

@@ -30,13 +30,24 @@ namespace Box3D {
         return copy(empty(), a);
     }
 
+    /** Get box from sphere, uses extrema if available */
     export function fromSphere3D(out: Box3D, sphere: Sphere3D): Box3D {
+        if (Sphere3D.hasExtrema(sphere)) return fromVec3Array(out, sphere.extrema);
         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;
     }
 
+    /** Get box from sphere, uses extrema if available */
+    export function fromVec3Array(out: Box3D, array: Vec3[]): Box3D {
+        Box3D.setEmpty(out);
+        for (let i = 0, il = array.length; i < il; i++) {
+            Box3D.add(out, array[i]);
+        }
+        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);

+ 18 - 7
src/mol-model/structure/structure/unit.ts

@@ -20,7 +20,7 @@ import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElement
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
 import { getPrincipalAxes } from './util/principal-axes';
-import { Boundary, getBoundary } from '../../../mol-math/geometry/boundary';
+import { Boundary, getBoundary, tryAdjustBoundary } from '../../../mol-math/geometry/boundary';
 import { Mat4 } from '../../../mol-math/linear-algebra';
 
 /**
@@ -204,7 +204,12 @@ namespace Unit {
         }
 
         remapModel(model: Model) {
-            const props = { ...this.props };
+            let boundary = this.props.boundary;
+            if (boundary) {
+                const { x, y, z } = this.model.atomicConformation;
+                boundary = tryAdjustBoundary({ x, y, z, indices: this.elements }, boundary);
+            }
+            const props = { ...this.props, boundary, lookup3d: undefined, principalAxes: undefined };
             const conformation = this.model.atomicConformation !== model.atomicConformation
                 ? SymmetryOperator.createMapping(this.conformation.operator, model.atomicConformation)
                 : this.conformation;
@@ -342,15 +347,21 @@ namespace Unit {
         }
 
         remapModel(model: Model): Unit.Spheres | Unit.Gaussians {
-            const props = { ...this.props };
+            const coarseConformation = this.getCoarseConformation();
+            let boundary = this.props.boundary;
+            if (boundary) {
+                const { x, y, z } = coarseConformation;
+                boundary = tryAdjustBoundary({ x, y, z, indices: this.elements }, boundary);
+            }
+            const props = { ...this.props, boundary, lookup3d: undefined, principalAxes: undefined };
             let conformation: SymmetryOperator.ArrayMapping<ElementIndex>;
             if (this.kind === Kind.Spheres) {
-                conformation = this.model.coarseConformation.spheres !== model.coarseConformation.spheres
-                    ? SymmetryOperator.createMapping(this.conformation.operator, model.atomicConformation)
+                conformation = coarseConformation !== model.coarseConformation.spheres
+                    ? SymmetryOperator.createMapping(this.conformation.operator, coarseConformation)
                     : this.conformation;
             } else if (this.kind === Kind.Gaussians) {
-                conformation = this.model.coarseConformation.gaussians !== model.coarseConformation.gaussians
-                    ? SymmetryOperator.createMapping(this.conformation.operator, model.atomicConformation)
+                conformation = coarseConformation !== model.coarseConformation.gaussians
+                    ? SymmetryOperator.createMapping(this.conformation.operator, coarseConformation)
                     : this.conformation;
             } else {
                 throw new Error('unexpected unit kind');