From e7a0ba8db09187703507bfc69b10d34011e501fb Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Wed, 29 Aug 2018 18:10:36 -0700
Subject: [PATCH] wip, better boundary calculation, making use of
 transformations

---
 src/apps/canvas/structure-view.ts             |  64 ++++++++--
 .../structure/structure/util/boundary.ts      | 110 +++++++++++++++++-
 2 files changed, 159 insertions(+), 15 deletions(-)

diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts
index 13f8b3f16..912f0090b 100644
--- a/src/apps/canvas/structure-view.ts
+++ b/src/apps/canvas/structure-view.ts
@@ -12,6 +12,13 @@ import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
 import { ShapeRepresentation, ShapeProps } from 'mol-geo/representation/shape';
 import { getAxesShape } from './assembly-symmetry';
 import Viewer from 'mol-view/viewer';
+import { CarbohydrateRepresentation } from 'mol-geo/representation/structure/representation/carbohydrate';
+import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
+import { addSphere } from 'mol-geo/mesh/builder/sphere';
+import { Shape } from 'mol-model/shape';
+import { Color } from 'mol-util/color';
+import { computeUnitBoundary } from 'mol-model/structure/structure/util/boundary';
+import { addBoundingBox } from 'mol-geo/mesh/builder/bounding-box';
 
 export interface StructureView {
     readonly label: string
@@ -21,7 +28,8 @@ export interface StructureView {
 
     readonly cartoon: CartoonRepresentation
     readonly ballAndStick: BallAndStickRepresentation
-    readonly axes: ShapeRepresentation<ShapeProps>
+    readonly carbohydrate: CarbohydrateRepresentation
+    readonly symmetryAxes: ShapeRepresentation<ShapeProps>
 
     readonly modelId: number
     readonly assemblyId: string
@@ -45,7 +53,9 @@ interface StructureViewProps {
 export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
     const cartoon = CartoonRepresentation()
     const ballAndStick = BallAndStickRepresentation()
-    const axes = ShapeRepresentation()
+    const carbohydrate = CarbohydrateRepresentation()
+    const symmetryAxes = ShapeRepresentation()
+    const polymerSphere = ShapeRepresentation()
 
     let label: string
     let model: Model | undefined
@@ -156,14 +166,46 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
                 useFog: false // TODO fog not working properly
             }, structure).run()
 
+            await carbohydrate.createOrUpdate({
+                colorTheme: { name: 'carbohydrate-symbol' },
+                sizeTheme: { name: 'uniform', value: 1, factor: 1 },
+                useFog: false // TODO fog not working properly
+            }, structure).run()
+
             viewer.center(structure.boundary.sphere.center)
+
+            const mb = MeshBuilder.create()
+            mb.setGroup(0)
+            addSphere(mb, structure.boundary.sphere.center, structure.boundary.sphere.radius, 3)
+            addBoundingBox(mb, structure.boundary.box, 1, 2, 8)
+            for (let i = 0, il = structure.units.length; i < il; ++i) {
+                mb.setGroup(1)
+                const u = structure.units[i]
+                const ci = u.model.atomicHierarchy.chainAtomSegments.index[u.elements[0]]
+                const ek = u.model.atomicHierarchy.getEntityKey(ci)
+                if (u.model.entities.data.type.value(ek) === 'water') continue
+                const boundary = computeUnitBoundary(u)
+                addSphere(mb, boundary.sphere.center, boundary.sphere.radius, 3)
+                addBoundingBox(mb, boundary.box, 0.5, 2, 8)
+            }
+            const shape = Shape.create('boundary', mb.getMesh(), [Color(0xCC6633), Color(0x3366CC)], ['sphere boundary'])
+            await polymerSphere.createOrUpdate({
+                alpha: 0.5,
+                doubleSided: false,
+                depthMask: false,
+                useFog: false // TODO fog not working properly
+            }, shape).run()
         } else {
             cartoon.destroy()
             ballAndStick.destroy()
+            carbohydrate.destroy()
+            polymerSphere.destroy()
         }
 
         viewer.add(cartoon)
         viewer.add(ballAndStick)
+        viewer.add(carbohydrate)
+        viewer.add(polymerSphere)
     }
 
     async function createSymmetryRepr() {
@@ -173,21 +215,21 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
                 const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry)
                 if (axesShape) {
                     // getClusterColorTheme(symmetryFeatureId, assemblySymmetry)
-                    await axes.createOrUpdate({
+                    await symmetryAxes.createOrUpdate({
                         colorTheme: { name: 'shape-group' },
                         // colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
                         useFog: false // TODO fog not working properly
                     }, axesShape).run()
                 } else {
-                    axes.destroy()
+                    symmetryAxes.destroy()
                 }
             } else {
-                axes.destroy()
+                symmetryAxes.destroy()
             }
         } else {
-            axes.destroy()
+            symmetryAxes.destroy()
         }
-        viewer.add(axes)
+        viewer.add(symmetryAxes)
         viewer.requestDraw()
     }
 
@@ -201,7 +243,8 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
 
         cartoon,
         ballAndStick,
-        axes,
+        carbohydrate,
+        symmetryAxes,
 
         get modelId() { return modelId },
         get assemblyId() { return assemblyId },
@@ -217,12 +260,13 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
         destroy: () => {
             viewer.remove(cartoon)
             viewer.remove(ballAndStick)
-            viewer.remove(axes)
+            viewer.remove(carbohydrate)
+            viewer.remove(symmetryAxes)
             viewer.requestDraw()
 
             cartoon.destroy()
             ballAndStick.destroy()
-            axes.destroy()
+            symmetryAxes.destroy()
         }
     }
 }
diff --git a/src/mol-model/structure/structure/util/boundary.ts b/src/mol-model/structure/structure/util/boundary.ts
index 2a595f8cd..ec3821263 100644
--- a/src/mol-model/structure/structure/util/boundary.ts
+++ b/src/mol-model/structure/structure/util/boundary.ts
@@ -2,13 +2,115 @@
  * Copyright (c) 2018 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 Structure from '../structure'
-import { Box3D, Sphere3D } from 'mol-math/geometry';
+import Unit from '../unit';
+import { Box3D, Sphere3D, SymmetryOperator } from 'mol-math/geometry';
 import { Vec3 } from 'mol-math/linear-algebra';
+import { SortedArray } from 'mol-data/int';
+import { ElementIndex } from '../../model/indexing';
 
-function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D } {
+export type Boundary = { box: Box3D, sphere: Sphere3D }
+
+function computeElementsPositionBoundary(elements: SortedArray<ElementIndex>, position: SymmetryOperator.CoordinateMapper): 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 center = Vec3.zero()
+
+    let radiusSq = 0
+    let size = 0
+
+    const p = Vec3.zero()
+
+    size += elements.length
+    for (let j = 0, _j = elements.length; j < _j; j++) {
+        position(elements[j], p)
+        Vec3.min(min, min, p)
+        Vec3.max(max, max, p)
+        Vec3.add(center, center, p)
+    }
+
+    if (size > 0) Vec3.scale(center, center, 1/size)
+
+    for (let j = 0, _j = elements.length; j < _j; j++) {
+        position(elements[j], p)
+        const d = Vec3.squaredDistance(p, center)
+        if (d > radiusSq) radiusSq = d;
+    }
+
+    return {
+        box: { min, max },
+        sphere: { center, radius: Math.sqrt(radiusSq) }
+    };
+}
+
+function computeInvariantUnitBoundary(u: Unit): Boundary {
+    return computeElementsPositionBoundary(u.elements, u.conformation.invariantPosition)
+}
+
+export function computeUnitBoundary(u: Unit): Boundary {
+    return computeElementsPositionBoundary(u.elements, u.conformation.position)
+}
+
+const tmpBox = Box3D.empty()
+const tmpSphere = Sphere3D.zero()
+
+export function computeStructureBoundary(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 center = Vec3.zero()
+
+    const { units } = s;
+
+    const boundaryMap: Map<number, Boundary> = new Map()
+    function getInvariantBoundary(u: Unit) {
+        let boundary: Boundary
+        if (boundaryMap.has(u.invariantId)) {
+            boundary = boundaryMap.get(u.invariantId)!
+        } else {
+            boundary = computeInvariantUnitBoundary(u)
+            boundaryMap.set(u.invariantId, boundary)
+        }
+        return boundary
+    }
+
+    let radiusSq = 0;
+    let size = 0;
+
+    for (let i = 0, _i = units.length; i < _i; i++) {
+        size += 1
+        const u = units[i]
+        const invariantBoundary = getInvariantBoundary(u)
+        const m = u.conformation.operator.matrix
+        Box3D.transform(tmpBox, invariantBoundary.box, m)
+        Vec3.min(min, min, tmpBox.min)
+        Vec3.max(max, max, tmpBox.max)
+        Sphere3D.transform(tmpSphere, invariantBoundary.sphere, m)
+        Vec3.add(center, center, tmpSphere.center)
+    }
+
+    if (size > 0) Vec3.scale(center, center, 1/size)
+
+    for (let i = 0, _i = units.length; i < _i; i++) {
+        const u = units[i]
+        const invariantBoundary = getInvariantBoundary(u)
+        const m = u.conformation.operator.matrix
+        Sphere3D.transform(tmpSphere, invariantBoundary.sphere, m)
+        const d = Vec3.squaredDistance(tmpSphere.center, center) + (tmpSphere.radius * tmpSphere.radius) * 4
+        if (d > radiusSq) radiusSq = d
+    }
+
+    const b = {
+        box: { min, max },
+        sphere: { center, radius: Math.sqrt(radiusSq) }
+    };
+    console.log(b, computeStructureBoundary2(s))
+    return b
+}
+
+export function computeStructureBoundary2(s: Structure): Boundary {
     const min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
     const max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
 
@@ -62,6 +164,4 @@ function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D
         box: { min: Vec3.ofArray(min), max: Vec3.ofArray(max) },
         sphere: { center: Vec3.create(cx, cy, cz), radius: Math.sqrt(radiusSq) }
     };
-}
-
-export { computeStructureBoundary }
\ No newline at end of file
+}
\ No newline at end of file
-- 
GitLab