Browse Source

add structure-element-sphere visual to spacefill repr

Alexander Rose 1 year ago
parent
commit
22a57d8f48

+ 1 - 0
CHANGELOG.md

@@ -9,6 +9,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Fix return type of `State.tryGetCellData`
 - Don't change camera.target unless flyMode or pointerLock are enabled
 - Handle empty CIF files
+- Add `structure-element-sphere` visual to `spacefill` representation
 
 ## [v3.42.0] - 2023-11-05
 

+ 20 - 1
src/mol-repr/structure/complex-visual.ts

@@ -31,12 +31,13 @@ import { Text } from '../../mol-geo/geometry/text/text';
 import { SizeTheme } from '../../mol-theme/size';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
 import { createMarkers } from '../../mol-geo/geometry/marker-data';
-import { StructureParams, StructureMeshParams, StructureTextParams, StructureDirectVolumeParams, StructureLinesParams, StructureCylindersParams, StructureTextureMeshParams } from './params';
+import { StructureParams, StructureMeshParams, StructureTextParams, StructureDirectVolumeParams, StructureLinesParams, StructureCylindersParams, StructureTextureMeshParams, StructureSpheresParams } from './params';
 import { Clipping } from '../../mol-theme/clipping';
 import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { isPromiseLike } from '../../mol-util/type-helpers';
 import { Substance } from '../../mol-theme/substance';
+import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
 
 export interface ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
 
@@ -338,6 +339,24 @@ export function ComplexMeshVisual<P extends ComplexMeshParams>(builder: ComplexM
     }, materialId);
 }
 
+// spheres
+
+export const ComplexSpheresParams = { ...StructureSpheresParams, ...StructureParams };
+export type ComplexSpheresParams = typeof ComplexSpheresParams
+
+export interface ComplexSpheresVisualBuilder<P extends ComplexSpheresParams> extends ComplexVisualBuilder<P, Spheres> { }
+
+export function ComplexSpheresVisual<P extends ComplexSpheresParams>(builder: ComplexSpheresVisualBuilder<P>, materialId: number): ComplexVisual<P> {
+    return ComplexVisual<Spheres, P>({
+        ...builder,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme, newStructure, currentStructure);
+            if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true;
+        },
+        geometryUtils: Spheres.Utils
+    }, materialId);
+}
+
 // cylinders
 
 export const ComplexCylindersParams = { ...StructureCylindersParams, ...StructureParams };

+ 6 - 4
src/mol-repr/structure/representation/spacefill.ts

@@ -1,25 +1,27 @@
 /**
- * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementSphereVisual, ElementSphereParams } from '../visual/element-sphere';
+import { ElementSphereVisual, ElementSphereParams, StructureElementSphereVisual } from '../visual/element-sphere';
 import { UnitsRepresentation } from '../units-representation';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation';
+import { ComplexRepresentation, StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation';
 import { RepresentationParamsGetter, RepresentationContext, Representation } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
 import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const SpacefillVisuals = {
-    'element-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => UnitsRepresentation('Sphere mesh', ctx, getParams, ElementSphereVisual),
+    'element-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => UnitsRepresentation('Sphere mesh/impostor', ctx, getParams, ElementSphereVisual),
+    'structure-element-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => ComplexRepresentation('Structure sphere mesh/impostor', ctx, getParams, StructureElementSphereVisual),
 };
 
 export const SpacefillParams = {
     ...ElementSphereParams,
     bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
+    visuals: PD.MultiSelect(['element-sphere'], PD.objectToOptions(SpacefillVisuals)),
 };
 export type SpacefillParams = typeof SpacefillParams
 

+ 70 - 4
src/mol-repr/structure/visual/element-sphere.ts

@@ -8,15 +8,14 @@
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual } from '../units-visual';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
-import { createElementSphereImpostor, ElementIterator, getElementLoci, eachElement, createElementSphereMesh } from './util/element';
+import { createElementSphereImpostor, ElementIterator, getElementLoci, eachElement, createElementSphereMesh, createStructureElementSphereImpostor, getSerialElementLoci, eachSerialElement, createStructureElementSphereMesh } from './util/element';
 import { VisualUpdateState } from '../../util';
 import { BaseGeometry } from '../../../mol-geo/geometry/base';
 import { Structure } from '../../../mol-model/structure';
 import { StructureGroup } from './util/common';
+import { ComplexMeshParams, ComplexMeshVisual, ComplexSpheresParams, ComplexSpheresVisual, ComplexVisual } from '../complex-visual';
 
-export const ElementSphereParams = {
-    ...UnitsMeshParams,
-    ...UnitsSpheresParams,
+export const CommonElementSphereParams = {
     sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
     detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
     ignoreHydrogens: PD.Boolean(false),
@@ -25,6 +24,14 @@ export const ElementSphereParams = {
     tryUseImpostor: PD.Boolean(true),
     stride: PD.Numeric(1, { min: 1, max: 100, step: 1 }),
 };
+
+//
+
+export const ElementSphereParams = {
+    ...UnitsMeshParams,
+    ...UnitsSpheresParams,
+    ...CommonElementSphereParams,
+};
 export type ElementSphereParams = typeof ElementSphereParams
 
 export function ElementSphereVisual(materialId: number, structure: Structure, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
@@ -75,4 +82,63 @@ export function ElementSphereMeshVisual(materialId: number): UnitsVisual<Element
             return props.tryUseImpostor && !!webgl;
         }
     }, materialId);
+}
+
+//
+
+export const StructureElementSphereParams = {
+    ...ComplexMeshParams,
+    ...ComplexSpheresParams,
+    ...CommonElementSphereParams,
+};
+export type StructureElementSphereParams = typeof ElementSphereParams
+
+export function StructureElementSphereVisual(materialId: number, structure: Structure, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
+    return props.tryUseImpostor && webgl && webgl.extensions.fragDepth && webgl.extensions.textureFloat
+        ? StructureElementSphereImpostorVisual(materialId)
+        : StructureElementSphereMeshVisual(materialId);
+}
+
+export function StructureElementSphereImpostorVisual(materialId: number): ComplexVisual<StructureElementSphereParams> {
+    return ComplexSpheresVisual<StructureElementSphereParams>({
+        defaultProps: PD.getDefaultValues(StructureElementSphereParams),
+        createGeometry: createStructureElementSphereImpostor,
+        createLocationIterator: ElementIterator.fromStructure,
+        getLoci: getSerialElementLoci,
+        eachLocation: eachSerialElement,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<StructureElementSphereParams>, currentProps: PD.Values<StructureElementSphereParams>) => {
+            state.createGeometry = (
+                newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
+                newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
+                newProps.traceOnly !== currentProps.traceOnly ||
+                newProps.stride !== currentProps.stride
+            );
+        },
+        mustRecreate: (structure: Structure, props: PD.Values<StructureElementSphereParams>, webgl?: WebGLContext) => {
+            return !props.tryUseImpostor || !webgl;
+        }
+    }, materialId);
+}
+
+export function StructureElementSphereMeshVisual(materialId: number): ComplexVisual<StructureElementSphereParams> {
+    return ComplexMeshVisual<StructureElementSphereParams>({
+        defaultProps: PD.getDefaultValues(StructureElementSphereParams),
+        createGeometry: createStructureElementSphereMesh,
+        createLocationIterator: ElementIterator.fromStructure,
+        getLoci: getSerialElementLoci,
+        eachLocation: eachSerialElement,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<StructureElementSphereParams>, currentProps: PD.Values<StructureElementSphereParams>) => {
+            state.createGeometry = (
+                newProps.sizeFactor !== currentProps.sizeFactor ||
+                newProps.detail !== currentProps.detail ||
+                newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
+                newProps.ignoreHydrogensVariant !== currentProps.ignoreHydrogensVariant ||
+                newProps.traceOnly !== currentProps.traceOnly ||
+                newProps.stride !== currentProps.stride
+            );
+        },
+        mustRecreate: (structure: Structure, props: PD.Values<StructureElementSphereParams>, webgl?: WebGLContext) => {
+            return props.tryUseImpostor && !!webgl;
+        }
+    }, materialId);
 }

+ 134 - 4
src/mol-repr/structure/visual/util/element.ts

@@ -37,6 +37,10 @@ export type ElementSphereMeshProps = {
     sizeFactor: number,
 } & ElementProps
 
+export type ElementSphereImpostorProps = {
+    sizeFactor: number,
+} & ElementProps
+
 export function makeElementIgnoreTest(structure: Structure, unit: Unit, props: ElementProps): undefined | ((i: ElementIndex) => boolean) {
     const { ignoreHydrogens, ignoreHydrogensVariant, traceOnly } = props;
 
@@ -111,10 +115,6 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
     return m;
 }
 
-export type ElementSphereImpostorProps = {
-    sizeFactor: number,
-} & ElementProps
-
 export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: ElementSphereImpostorProps, spheres?: Spheres): Spheres {
     const { child } = structure;
     const childUnit = child?.unitMap.get(unit.id);
@@ -220,6 +220,136 @@ export function getElementLoci(pickingId: PickingId, structureGroup: StructureGr
 
 //
 
+export function createStructureElementSphereMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: ElementSphereMeshProps, mesh?: Mesh): Mesh {
+    const { child } = structure;
+    const { detail, sizeFactor, stride } = props;
+
+    const { getSerialIndex } = structure.serialMapping;
+    const structureElementCount = structure.elementCount;
+    const vertexCount = structureElementCount * sphereVertexCount(detail);
+    const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh);
+
+    const themeSize = theme.size.size;
+    const center = Vec3();
+    let maxSize = 0;
+    let count = 0;
+
+    for (const unit of structure.units) {
+        const childUnit = child?.unitMap.get(unit.id);
+        if (child && !childUnit) continue;
+
+        const { elements } = unit;
+        const elementCount = elements.length;
+        const v = Vec3();
+        const pos = unit.conformation.position;
+        const ignore = makeElementIgnoreTest(structure, unit, props);
+        const l = StructureElement.Location.create(structure, unit);
+
+        for (let i = 0; i < elementCount; i++) {
+            const eI = elements[i];
+            if (stride && i % stride !== 0) continue;
+            if (ignore && ignore(eI)) continue;
+
+
+            pos(eI, v);
+            v3add(center, center, v);
+            count += 1;
+
+            l.element = eI;
+            const size = themeSize(l);
+            if (size > maxSize) maxSize = size;
+
+            builderState.currentGroup = getSerialIndex(unit, eI);
+            addSphere(builderState, v, size * sizeFactor, detail);
+        }
+    }
+
+    const oldBoundingSphere = mesh ? Sphere3D.clone(mesh.boundingSphere) : undefined;
+    const m = MeshBuilder.getMesh(builderState);
+    if (count === 0) return m;
+
+    // re-use boundingSphere if it has not changed much
+    let boundingSphere: Sphere3D;
+    Vec3.scale(center, center, 1 / count);
+    if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
+        boundingSphere = oldBoundingSphere;
+    } else {
+        boundingSphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, maxSize * sizeFactor + 0.05);
+    }
+    m.setBoundingSphere(boundingSphere);
+
+    return m;
+}
+
+export function createStructureElementSphereImpostor(ctx: VisualContext, structure: Structure, theme: Theme, props: ElementSphereImpostorProps, spheres?: Spheres): Spheres {
+    const { child } = structure;
+    const { sizeFactor, stride } = props;
+
+    const { getSerialIndex } = structure.serialMapping;
+    const structureElementCount = structure.elementCount;
+    const builder = SpheresBuilder.create(structureElementCount, structureElementCount / 2, spheres);
+
+    const themeSize = theme.size.size;
+    const center = Vec3();
+    let maxSize = 0;
+    let count = 0;
+
+    for (const unit of structure.units) {
+        const childUnit = child?.unitMap.get(unit.id);
+        if (child && !childUnit) return Spheres.createEmpty(spheres);
+
+        const { elements } = unit;
+        const elementCount = elements.length;
+
+        const v = Vec3();
+        const pos = unit.conformation.position;
+        const ignore = makeElementIgnoreTest(structure, unit, props);
+        const l = StructureElement.Location.create(structure, unit);
+
+        if ((stride && stride > 1) || ignore || theme.size.granularity !== 'uniform') {
+            for (let i = 0; i < elementCount; i++) {
+                const eI = elements[i];
+                if (stride && i % stride !== 0) continue;
+                if (ignore && ignore(eI)) continue;
+
+                pos(eI, v);
+                builder.add(v[0], v[1], v[2], getSerialIndex(unit, eI));
+                v3add(center, center, v);
+                count += 1;
+
+                l.element = eI;
+                const size = themeSize(l);
+                if (size > maxSize) maxSize = size;
+            }
+        } else {
+            for (let i = 0; i < elementCount; i++) {
+                const eI = elements[i];
+                pos(eI, v);
+                builder.add(v[0], v[1], v[2], getSerialIndex(unit, eI));
+                v3add(center, center, v);
+            }
+            count += elementCount;
+            maxSize = themeSize(l);
+        }
+    }
+
+    const oldBoundingSphere = spheres ? Sphere3D.clone(spheres.boundingSphere) : undefined;
+    const s = builder.getSpheres();
+    if (count === 0) return s;
+
+    // re-use boundingSphere if it has not changed much
+    let boundingSphere: Sphere3D;
+    Vec3.scale(center, center, 1 / count);
+    if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
+        boundingSphere = oldBoundingSphere;
+    } else {
+        boundingSphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, maxSize * sizeFactor + 0.05);
+    }
+    s.setBoundingSphere(boundingSphere);
+
+    return s;
+}
+
 export function eachSerialElement(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
     let changed = false;
     if (!StructureElement.Loci.is(loci)) return false;