Browse Source

Shape.Loci improvements including bounding-sphere calculation

Alexander Rose 5 years ago
parent
commit
31850b3a71

+ 1 - 1
src/mol-model-formats/shape/ply.ts

@@ -182,7 +182,7 @@ function getGrouping(vertex: PlyTable, props: PD.Values<PlyShapeParams>): Groupi
     const label = grouping.name === 'vertex' ? stringToWords(grouping.params.group) : 'Vertex'
 
     const ids = column ? column.toArray({ array: Uint32Array }) : fillSerial(new Uint32Array(rowCount))
-    const maxId = arrayMax(ids) // assumes uint ids
+    const maxId = column ? arrayMax(ids) : rowCount - 1 // assumes uint ids
     const map = new Uint32Array(maxId + 1)
     for (let i = 0, il = ids.length; i < il; ++i) map[ids[i]] = i
     return { ids, map, label  }

+ 7 - 3
src/mol-model/loci.ts

@@ -163,8 +163,7 @@ namespace Loci {
         } else if (loci.kind === 'shape-loci') {
             return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
         } else if (loci.kind === 'group-loci') {
-            // TODO
-            return Sphere3D.copy(boundingSphere, loci.shape.geometry.boundingSphere)
+            return ShapeGroup.getBoundingSphere(loci, boundingSphere)
         } else if (loci.kind === 'data-loci') {
             // TODO maybe add loci.getBoundingSphere()???
             return void 0;
@@ -249,7 +248,12 @@ namespace Loci {
             return StructureElement.Loci.is(loci)
                 ? Structure.toStructureElementLoci(loci.structure)
                 : loci
-        }
+        },
+        'shape': (loci: Loci) => {
+            return ShapeGroup.isLoci(loci)
+                ? Shape.Loci(loci.shape)
+                : loci
+        },
     }
     export type Granularity = keyof typeof Granularity
     export const GranularityOptions = ParamDefinition.objectToOptions(Granularity);

+ 84 - 10
src/mol-model/shape/shape.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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,7 +8,10 @@ import { Color } from '../../mol-util/color';
 import { UUID } from '../../mol-util';
 import { OrderedSet } from '../../mol-data/int';
 import { Geometry } from '../../mol-geo/geometry/geometry';
-import { Mat4 } from '../../mol-math/linear-algebra';
+import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
+import { Sphere3D } from '../../mol-math/geometry';
+import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
+import { GroupMapping } from '../../mol-geo/util';
 
 export interface Shape<G extends Geometry = Geometry> {
     /** A uuid to identify a shape object */
@@ -73,13 +76,13 @@ export namespace ShapeGroup {
         readonly kind: 'group-loci',
         readonly shape: Shape,
         readonly groups: ReadonlyArray<{
-            ids: OrderedSet<number>
+            readonly ids: OrderedSet<number>
+            readonly instance: number
         }>
-        readonly instance: number
     }
 
-    export function Loci(shape: Shape, groups: ArrayLike<{ ids: OrderedSet<number> }>, instance: number): Loci {
-        return { kind: 'group-loci', shape, groups: groups as Loci['groups'], instance };
+    export function Loci(shape: Shape, groups: Loci['groups']): Loci {
+        return { kind: 'group-loci', shape, groups: groups as Loci['groups'] };
     }
 
     export function isLoci(x: any): x is Loci {
@@ -89,11 +92,11 @@ export namespace ShapeGroup {
     export function areLociEqual(a: Loci, b: Loci) {
         if (a.shape !== b.shape) return false
         if (a.groups.length !== b.groups.length) return false
-        if (a.instance !== b.instance) return false
         for (let i = 0, il = a.groups.length; i < il; ++i) {
-            const groupA = a.groups[i]
-            const groupB = b.groups[i]
-            if (!OrderedSet.areEqual(groupA.ids, groupB.ids)) return false
+            const { ids: idsA, instance: instanceA } = a.groups[i]
+            const { ids: idsB, instance: instanceB } = b.groups[i]
+            if (instanceA !== instanceB) return false
+            if (!OrderedSet.areEqual(idsA, idsB)) return false
         }
         return true
     }
@@ -109,4 +112,75 @@ export namespace ShapeGroup {
         }
         return size
     }
+
+    const sphereHelper = new CentroidHelper(), tmpPos = Vec3.zero();
+
+    function sphereHelperInclude(groups: Loci['groups'], mapping: GroupMapping, positions: Float32Array, transforms: Mat4[]) {
+        const { indices, offsets } = mapping
+        for (const { ids, instance } of groups) {
+            OrderedSet.forEach(ids, v => {
+                for (let i = offsets[v], il = offsets[v + 1]; i < il; ++i) {
+                    Vec3.fromArray(tmpPos, positions, indices[i] * 3)
+                    Vec3.transformMat4(tmpPos, tmpPos, transforms[instance])
+                    sphereHelper.includeStep(tmpPos)
+                }
+            })
+        }
+    }
+
+    function sphereHelperRadius(groups: Loci['groups'], mapping: GroupMapping, positions: Float32Array, transforms: Mat4[]) {
+        const { indices, offsets } = mapping
+        for (const { ids, instance } of groups) {
+            OrderedSet.forEach(ids, v => {
+                for (let i = offsets[v], il = offsets[v + 1]; i < il; ++i) {
+                    Vec3.fromArray(tmpPos, positions, indices[i] * 3)
+                    Vec3.transformMat4(tmpPos, tmpPos, transforms[instance])
+                    sphereHelper.radiusStep(tmpPos)
+                }
+            })
+        }
+    }
+
+    export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D) {
+        if (!boundingSphere) boundingSphere = Sphere3D()
+
+        sphereHelper.reset();
+        let padding = 0
+
+        const { geometry, transforms } = loci.shape
+
+        if (geometry.kind === 'mesh') {
+            const positions = geometry.vertexBuffer.ref.value
+            sphereHelperInclude(loci.groups, geometry.groupMapping, positions, transforms)
+            sphereHelper.finishedIncludeStep()
+            sphereHelperRadius(loci.groups, geometry.groupMapping, positions, transforms)
+        } else if (geometry.kind === 'lines') {
+            const start = geometry.startBuffer.ref.value
+            const end = geometry.endBuffer.ref.value
+            sphereHelperInclude(loci.groups, geometry.groupMapping, start, transforms)
+            sphereHelperInclude(loci.groups, geometry.groupMapping, end, transforms)
+            sphereHelper.finishedIncludeStep()
+            sphereHelperRadius(loci.groups, geometry.groupMapping, start, transforms)
+            sphereHelperRadius(loci.groups, geometry.groupMapping, end, transforms)
+        } else if (geometry.kind === 'spheres') {
+            const positions = geometry.centerBuffer.ref.value
+            sphereHelperInclude(loci.groups, geometry.groupMapping, positions, transforms)
+            sphereHelper.finishedIncludeStep()
+            sphereHelperRadius(loci.groups, geometry.groupMapping, positions, transforms)
+            for (const { ids, instance } of loci.groups) {
+                OrderedSet.forEach(ids, v => {
+                    const value = loci.shape.getSize(v, instance)
+                    if (padding < value) padding = value
+                })
+            }
+        } else {
+            // TODO implement for other geometry kinds
+            return Sphere3D.copy(boundingSphere, geometry.boundingSphere)
+        }
+
+        Vec3.copy(boundingSphere.center, sphereHelper.center)
+        boundingSphere.radius = Math.sqrt(sphereHelper.radiusSq)
+        Sphere3D.expand(boundingSphere, boundingSphere, padding)
+        return boundingSphere
+    }
 }

+ 8 - 8
src/mol-repr/shape/representation.ts

@@ -184,7 +184,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
             if (pickingId === undefined) return Shape.Loci(_shape)
             const { objectId, groupId, instanceId } = pickingId
             if (_renderObject && _renderObject.id === objectId) {
-                return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId) }], instanceId)
+                return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }])
             }
             return EmptyLoci
         },
@@ -239,15 +239,15 @@ function eachShapeGroup(loci: Loci, shape: Shape, apply: (interval: Interval) =>
     if (loci.shape !== shape) return false
     let changed = false
     const { groupCount } = shape
-    const { instance, groups } = loci
-    for (const g of groups) {
-        if (Interval.is(g.ids)) {
-            const start = instance * groupCount + Interval.start(g.ids)
-            const end = instance * groupCount + Interval.end(g.ids)
+    const { groups } = loci
+    for (const { ids, instance } of groups) {
+        if (Interval.is(ids)) {
+            const start = instance * groupCount + Interval.start(ids)
+            const end = instance * groupCount + Interval.end(ids)
             if (apply(Interval.ofBounds(start, end))) changed = true
         } else {
-            for (let i = 0, _i = g.ids.length; i < _i; i++) {
-                const idx = instance * groupCount + g.ids[i];
+            for (let i = 0, _i = ids.length; i < _i; i++) {
+                const idx = instance * groupCount + ids[i];
                 if (apply(Interval.ofSingleton(idx))) changed = true
             }
         }

+ 1 - 1
src/mol-theme/label.ts

@@ -39,7 +39,7 @@ export function lociLabel(loci: Loci, options: Partial<LabelOptions> = {}): stri
             return loci.shape.name
         case 'group-loci':
             const g = loci.groups[0]
-            return g ? loci.shape.getLabel(OrderedSet.start(g.ids), loci.instance) : 'Unknown'
+            return g ? loci.shape.getLabel(OrderedSet.start(g.ids), g.instance) : 'Unknown'
         case 'every-loci':
             return 'Everything'
         case 'empty-loci':