Explorar o código

Merge branch 'master' of https://github.com/corredD/molstar-proto into forkdev

# Conflicts:
#	CHANGELOG.md
ludovic autin %!s(int64=2) %!d(string=hai) anos
pai
achega
9e69f5dcfa
Modificáronse 32 ficheiros con 396 adicións e 103 borrados
  1. 27 0
      CHANGELOG.md
  2. 2 2
      package-lock.json
  3. 1 1
      package.json
  4. 31 0
      src/extensions/cellpack/model.ts
  5. 7 1
      src/extensions/cellpack/preset.ts
  6. 6 1
      src/mol-geo/geometry/base.ts
  7. 8 2
      src/mol-geo/geometry/clipping-data.ts
  8. 3 1
      src/mol-geo/geometry/cylinders/cylinders.ts
  9. 3 1
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  10. 3 1
      src/mol-geo/geometry/image/image.ts
  11. 3 1
      src/mol-geo/geometry/lines/lines.ts
  12. 9 3
      src/mol-geo/geometry/marker-data.ts
  13. 3 1
      src/mol-geo/geometry/mesh/mesh.ts
  14. 5 2
      src/mol-geo/geometry/overpaint-data.ts
  15. 3 1
      src/mol-geo/geometry/points/points.ts
  16. 3 1
      src/mol-geo/geometry/spheres/spheres.ts
  17. 5 2
      src/mol-geo/geometry/substance-data.ts
  18. 3 1
      src/mol-geo/geometry/text/text.ts
  19. 3 1
      src/mol-geo/geometry/texture-mesh/texture-mesh.ts
  20. 5 2
      src/mol-geo/geometry/transparency-data.ts
  21. 7 3
      src/mol-gl/renderable/schema.ts
  22. 5 1
      src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts
  23. 9 3
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  24. 5 1
      src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts
  25. 3 3
      src/mol-gl/shader/chunks/color-vert-params.glsl.ts
  26. 14 0
      src/mol-plugin-state/manager/structure/hierarchy.ts
  27. 42 32
      src/mol-plugin-ui/structure/superposition.tsx
  28. 30 3
      src/mol-repr/shape/representation.ts
  29. 28 4
      src/mol-repr/structure/complex-visual.ts
  30. 45 4
      src/mol-repr/structure/units-visual.ts
  31. 39 21
      src/mol-repr/visual.ts
  32. 36 3
      src/mol-repr/volume/representation.ts

+ 27 - 0
CHANGELOG.md

@@ -6,8 +6,35 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+
+## [v3.11.0] - 2022-07-04
+
+- Add ``instanceGranularity`` option for marker, transparency, clipping, overpaint, substance data to save memory
 - CellPack extension tweaks
     - Use instancing to create DNA/RNA curves to save memory
+    - Enable ``instanceGranularity`` by default
+    - Add ``adjustStyle`` option to LoadCellPackModel action (stylized, no multi-sample, no far clipping, chain picking)
+- Structure Superposition now respects pivot's coordinate system
+
+## [v3.10.2] - 2022-06-26
+
+- Fix superfluous shader varying
+- Improve use of gl_VertexID when possible
+
+## [v3.10.1] - 2022-06-26
+
+- Fix groupCount when updating TextureMesh-based visuals
+
+## [v3.10.0] - 2022-06-24
+
+- Add support for Glycam saccharide names
+- Add ``PluginConfig.Viewport.ShowTrajectoryControls`` config option
+
+## [v3.9.1] - 2022-06-19
+
+- Fix missing ``super.componentWillUnmount()`` calls (@simeonborko)
+- Fix missing ``uGroupCount`` update for visuals
+- Fix missing aromatic bond display
 
 ## [v3.9.0] - 2022-05-30
 

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "molstar",
-  "version": "3.10.2",
+  "version": "3.11.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "molstar",
-      "version": "3.10.2",
+      "version": "3.11.0",
       "license": "MIT",
       "dependencies": {
         "@types/argparse": "^2.0.10",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "3.10.2",
+  "version": "3.11.0",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {

+ 31 - 0
src/extensions/cellpack/model.ts

@@ -415,6 +415,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
             .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
             .commit({ revertOnError: true });
         const membraneParams = {
+            ignoreLight: params.preset.adjustStyle,
             representation: params.preset.representation,
         };
         await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
@@ -431,6 +432,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
             .apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
             .commit({ revertOnError: true });
         const membraneParams = {
+            ignoreLight: params.preset.adjustStyle,
             representation: params.preset.representation,
         };
         await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
@@ -514,6 +516,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
 
         const packingParams = {
             traceOnly: params.preset.traceOnly,
+            ignoreLight: params.preset.adjustStyle,
             representation: params.preset.representation,
         };
         await CellpackPackingPreset.apply(packing, packingParams, plugin);
@@ -565,6 +568,7 @@ const LoadCellPackModelParams = {
     ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
     preset: PD.Group({
         traceOnly: PD.Boolean(false),
+        adjustStyle: PD.Boolean(true),
         representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation'] as const))
     }, { isExpanded: true })
 };
@@ -575,5 +579,32 @@ export const LoadCellPackModel = StateAction.build({
     params: LoadCellPackModelParams,
     from: PSO.Root
 })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
+    if (params.preset.adjustStyle) {
+        ctx.managers.interactivity.setProps({ granularity: 'chain' });
+        ctx.canvas3d?.setProps({
+            multiSample: { mode: 'off' },
+            cameraClipping: { far: false },
+            postprocessing: {
+                occlusion: {
+                    name: 'on',
+                    params: {
+                        samples: 32,
+                        radius: 8,
+                        bias: 1,
+                        blurKernelSize: 15,
+                        resolutionScale: 1,
+                    }
+                },
+                outline: {
+                    name: 'on',
+                    params: {
+                        scale: 1,
+                        threshold: 0.33,
+                        color: ColorNames.black,
+                    }
+                }
+            }
+        });
+    }
     await loadPackings(ctx, taskCtx, state, params);
 }));

+ 7 - 1
src/extensions/cellpack/preset.ts

@@ -13,6 +13,7 @@ import { CellPackGenerateColorThemeProvider } from './color/generate';
 
 export const CellpackPackingPresetParams = {
     traceOnly: PD.Boolean(true),
+    ignoreLight: PD.Boolean(false),
     representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)),
 };
 export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
@@ -27,7 +28,9 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
 
         const reprProps = {
             ignoreHydrogens: true,
-            traceOnly: params.traceOnly
+            traceOnly: params.traceOnly,
+            instanceGranularity: true,
+            ignoreLight: params.ignoreLight,
         };
         const components = {
             polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
@@ -57,6 +60,7 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
 //
 
 export const CellpackMembranePresetParams = {
+    ignoreLight: PD.Boolean(false),
     representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)),
 };
 export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
@@ -71,6 +75,8 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
 
         const reprProps = {
             ignoreHydrogens: true,
+            instanceGranularity: true,
+            ignoreLight: params.ignoreLight,
         };
         const components = {
             membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })

+ 6 - 1
src/mol-geo/geometry/base.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -83,6 +83,7 @@ export namespace BaseGeometry {
         quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
         material: Material.getParam(),
         clip: PD.Group(Clip.Params),
+        instanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }),
     };
     export type Params = typeof Params
 
@@ -118,6 +119,8 @@ export namespace BaseGeometry {
             uClipObjectPosition: ValueCell.create(clip.objects.position),
             uClipObjectRotation: ValueCell.create(clip.objects.rotation),
             uClipObjectScale: ValueCell.create(clip.objects.scale),
+
+            instanceGranularity: ValueCell.create(props.instanceGranularity),
         };
     }
 
@@ -135,6 +138,8 @@ export namespace BaseGeometry {
         ValueCell.update(values.uClipObjectPosition, clip.objects.position);
         ValueCell.update(values.uClipObjectRotation, clip.objects.rotation);
         ValueCell.update(values.uClipObjectScale, clip.objects.scale);
+
+        ValueCell.updateIfChanged(values.instanceGranularity, props.instanceGranularity);
     }
 
     export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {

+ 8 - 2
src/mol-geo/geometry/clipping-data.ts

@@ -9,10 +9,13 @@ import { Vec2 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { Clipping } from '../../mol-theme/clipping';
 
+export type ClippingType = 'instance' | 'groupInstance';
+
 export type ClippingData = {
     tClipping: ValueCell<TextureImage<Uint8Array>>
     uClippingTexDim: ValueCell<Vec2>
-    dClipping: ValueCell<boolean>,
+    dClipping: ValueCell<boolean>
+    dClippingType: ValueCell<string>
 }
 
 export function applyClippingGroups(array: Uint8Array, start: number, end: number, groups: Clipping.Groups) {
@@ -24,18 +27,20 @@ export function clearClipping(array: Uint8Array, start: number, end: number) {
     array.fill(0, start, end);
 }
 
-export function createClipping(count: number, clippingData?: ClippingData): ClippingData {
+export function createClipping(count: number, type: ClippingType, clippingData?: ClippingData): ClippingData {
     const clipping = createTextureImage(Math.max(1, count), 1, Uint8Array, clippingData && clippingData.tClipping.ref.value.array);
     if (clippingData) {
         ValueCell.update(clippingData.tClipping, clipping);
         ValueCell.update(clippingData.uClippingTexDim, Vec2.create(clipping.width, clipping.height));
         ValueCell.updateIfChanged(clippingData.dClipping, count > 0);
+        ValueCell.updateIfChanged(clippingData.dClippingType, type);
         return clippingData;
     } else {
         return {
             tClipping: ValueCell.create(clipping),
             uClippingTexDim: ValueCell.create(Vec2.create(clipping.width, clipping.height)),
             dClipping: ValueCell.create(count > 0),
+            dClippingType: ValueCell.create(type),
         };
     }
 }
@@ -52,6 +57,7 @@ export function createEmptyClipping(clippingData?: ClippingData): ClippingData {
             tClipping: ValueCell.create(emptyClippingTexture),
             uClippingTexDim: ValueCell.create(Vec2.create(1, 1)),
             dClipping: ValueCell.create(false),
+            dClippingType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/cylinders/cylinders.ts

@@ -201,7 +201,9 @@ export namespace Cylinders {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -211,7 +211,9 @@ export namespace DirectVolume {
         const positionIt = Utils.createPositionIterator(directVolume, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/image/image.ts

@@ -143,7 +143,9 @@ namespace Image {
         const positionIt = Utils.createPositionIterator(image, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/lines/lines.ts

@@ -208,7 +208,9 @@ export namespace Lines {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 9 - 3
src/mol-geo/geometry/marker-data.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,12 +8,15 @@ import { ValueCell } from '../../mol-util/value-cell';
 import { Vec2 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 
+export type MarkerType = 'instance' | 'groupInstance';
+
 export type MarkerData = {
-    uMarker: ValueCell<number>,
+    uMarker: ValueCell<number>
     tMarker: ValueCell<TextureImage<Uint8Array>>
     uMarkerTexDim: ValueCell<Vec2>
     markerAverage: ValueCell<number>
     markerStatus: ValueCell<number>
+    dMarkerType: ValueCell<string>
 }
 
 const MarkerCountLut = new Uint8Array(0x0303 + 1);
@@ -64,7 +67,7 @@ export function getMarkersAverage(array: Uint8Array, count: number): number {
     return sum / count;
 }
 
-export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
+export function createMarkers(count: number, type: MarkerType, markerData?: MarkerData): MarkerData {
     const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array);
     const average = getMarkersAverage(markers.array, count);
     const status = average === 0 ? 0 : -1;
@@ -74,6 +77,7 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
         ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height));
         ValueCell.updateIfChanged(markerData.markerAverage, average);
         ValueCell.updateIfChanged(markerData.markerStatus, status);
+        ValueCell.updateIfChanged(markerData.dMarkerType, type);
         return markerData;
     } else {
         return {
@@ -82,6 +86,7 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
             uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)),
             markerAverage: ValueCell.create(average),
             markerStatus: ValueCell.create(status),
+            dMarkerType: ValueCell.create(type),
         };
     }
 }
@@ -102,6 +107,7 @@ export function createEmptyMarkers(markerData?: MarkerData): MarkerData {
             uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)),
             markerAverage: ValueCell.create(0),
             markerStatus: ValueCell.create(0),
+            dMarkerType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/mesh/mesh.ts

@@ -666,7 +666,9 @@ export namespace Mesh {
         const positionIt = createPositionIterator(mesh, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 5 - 2
src/mol-geo/geometry/overpaint-data.ts

@@ -10,6 +10,8 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { Color } from '../../mol-util/color';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
+export type OverpaintType = 'instance' | 'groupInstance' | 'volumeInstance';
+
 export type OverpaintData = {
     tOverpaint: ValueCell<TextureImage<Uint8Array>>
     uOverpaintTexDim: ValueCell<Vec2>
@@ -34,12 +36,13 @@ export function clearOverpaint(array: Uint8Array, start: number, end: number) {
     return true;
 }
 
-export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {
+export function createOverpaint(count: number, type: OverpaintType, overpaintData?: OverpaintData): OverpaintData {
     const overpaint = createTextureImage(Math.max(1, count), 4, Uint8Array, overpaintData && overpaintData.tOverpaint.ref.value.array);
     if (overpaintData) {
         ValueCell.update(overpaintData.tOverpaint, overpaint);
         ValueCell.update(overpaintData.uOverpaintTexDim, Vec2.create(overpaint.width, overpaint.height));
         ValueCell.updateIfChanged(overpaintData.dOverpaint, count > 0);
+        ValueCell.updateIfChanged(overpaintData.dOverpaintType, type);
         return overpaintData;
     } else {
         return {
@@ -50,7 +53,7 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O
             tOverpaintGrid: ValueCell.create(createNullTexture()),
             uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
             uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
-            dOverpaintType: ValueCell.create('groupInstance'),
+            dOverpaintType: ValueCell.create(type),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/points/points.ts

@@ -170,7 +170,9 @@ export namespace Points {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/spheres/spheres.ts

@@ -171,7 +171,9 @@ export namespace Spheres {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 5 - 2
src/mol-geo/geometry/substance-data.ts

@@ -10,6 +10,8 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 import { Material } from '../../mol-util/material';
 
+export type SubstanceType = 'instance' | 'groupInstance' | 'volumeInstance';
+
 export type SubstanceData = {
     tSubstance: ValueCell<TextureImage<Uint8Array>>
     uSubstanceTexDim: ValueCell<Vec2>
@@ -34,12 +36,13 @@ export function clearSubstance(array: Uint8Array, start: number, end: number) {
     return true;
 }
 
-export function createSubstance(count: number, substanceData?: SubstanceData): SubstanceData {
+export function createSubstance(count: number, type: SubstanceType, substanceData?: SubstanceData): SubstanceData {
     const substance = createTextureImage(Math.max(1, count), 4, Uint8Array, substanceData && substanceData.tSubstance.ref.value.array);
     if (substanceData) {
         ValueCell.update(substanceData.tSubstance, substance);
         ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(substance.width, substance.height));
         ValueCell.updateIfChanged(substanceData.dSubstance, count > 0);
+        ValueCell.updateIfChanged(substanceData.dSubstanceType, type);
         return substanceData;
     } else {
         return {
@@ -50,7 +53,7 @@ export function createSubstance(count: number, substanceData?: SubstanceData): S
             tSubstanceGrid: ValueCell.create(createNullTexture()),
             uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
             uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
-            dSubstanceType: ValueCell.create('groupInstance'),
+            dSubstanceType: ValueCell.create(type),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/text/text.ts

@@ -211,7 +211,9 @@ export namespace Text {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const substance = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -137,7 +137,9 @@ export namespace TextureMesh {
         const positionIt = Utils.createPositionIterator(textureMesh, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.instanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const substance = createEmptySubstance();

+ 5 - 2
src/mol-geo/geometry/transparency-data.ts

@@ -9,6 +9,8 @@ import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
+export type TransparencyType = 'instance' | 'groupInstance' | 'volumeInstance';
+
 export type TransparencyData = {
     tTransparency: ValueCell<TextureImage<Uint8Array>>
     uTransparencyTexDim: ValueCell<Vec2>
@@ -41,13 +43,14 @@ export function clearTransparency(array: Uint8Array, start: number, end: number)
     array.fill(0, start, end);
 }
 
-export function createTransparency(count: number, transparencyData?: TransparencyData): TransparencyData {
+export function createTransparency(count: number, type: TransparencyType, transparencyData?: TransparencyData): TransparencyData {
     const transparency = createTextureImage(Math.max(1, count), 1, Uint8Array, transparencyData && transparencyData.tTransparency.ref.value.array);
     if (transparencyData) {
         ValueCell.update(transparencyData.tTransparency, transparency);
         ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height));
         ValueCell.updateIfChanged(transparencyData.dTransparency, count > 0);
         ValueCell.updateIfChanged(transparencyData.transparencyAverage, getTransparencyAverage(transparency.array, count));
+        ValueCell.updateIfChanged(transparencyData.dTransparencyType, type);
         return transparencyData;
     } else {
         return {
@@ -59,7 +62,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc
             tTransparencyGrid: ValueCell.create(createNullTexture()),
             uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
             uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
-            dTransparencyType: ValueCell.create('groupInstance'),
+            dTransparencyType: ValueCell.create(type),
         };
     }
 }

+ 7 - 3
src/mol-gl/renderable/schema.ts

@@ -205,6 +205,7 @@ export const MarkerSchema = {
     tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     markerAverage: ValueSpec('number'),
     markerStatus: ValueSpec('number'),
+    dMarkerType: DefineSpec('string', ['instance', 'groupInstance']),
 } as const;
 export type MarkerSchema = typeof MarkerSchema
 export type MarkerValues = Values<MarkerSchema>
@@ -217,7 +218,7 @@ export const OverpaintSchema = {
     uOverpaintGridDim: UniformSpec('v3'),
     uOverpaintGridTransform: UniformSpec('v4'),
     tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
-    dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
+    dOverpaintType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
 } as const;
 export type OverpaintSchema = typeof OverpaintSchema
 export type OverpaintValues = Values<OverpaintSchema>
@@ -231,7 +232,7 @@ export const TransparencySchema = {
     uTransparencyGridDim: UniformSpec('v3'),
     uTransparencyGridTransform: UniformSpec('v4'),
     tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
-    dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
+    dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
 } as const;
 export type TransparencySchema = typeof TransparencySchema
 export type TransparencyValues = Values<TransparencySchema>
@@ -244,7 +245,7 @@ export const SubstanceSchema = {
     uSubstanceGridDim: UniformSpec('v3'),
     uSubstanceGridTransform: UniformSpec('v4'),
     tSubstanceGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
-    dSubstanceType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
+    dSubstanceType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
 } as const;
 export type SubstanceSchema = typeof SubstanceSchema
 export type SubstanceValues = Values<SubstanceSchema>
@@ -253,6 +254,7 @@ export const ClippingSchema = {
     uClippingTexDim: UniformSpec('v2'),
     tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     dClipping: DefineSpec('boolean'),
+    dClippingType: DefineSpec('string', ['instance', 'groupInstance']),
 } as const;
 export type ClippingSchema = typeof ClippingSchema
 export type ClippingValues = Values<ClippingSchema>
@@ -311,6 +313,8 @@ export const BaseSchema = {
     extraTransform: ValueSpec('float32'),
     /** denotes reflection in transform */
     hasReflection: ValueSpec('boolean'),
+    /** use instance granularity for marker, transparency, clipping, overpaint, substance */
+    instanceGranularity: ValueSpec('boolean'),
 
     /** bounding sphere taking aTransform into account and encompases all instances */
     boundingSphere: ValueSpec('sphere'),

+ 5 - 1
src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts

@@ -1,5 +1,9 @@
 export const assign_clipping_varying = `
 #if dClipObjectCount != 0 && defined(dClipping)
-    vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a;
+    #if defined(dClippingType_instance)
+        vClipping = readFromTexture(tClipping, aInstance, uClippingTexDim).a;
+    #elif defined(dMarkerType_groupInstance)
+        vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a;
+    #endif
 #endif
 `;

+ 9 - 3
src/mol-gl/shader/chunks/assign-color-varying.glsl.ts

@@ -25,7 +25,9 @@ export const assign_color_varying = `
     #endif
 
     #ifdef dOverpaint
-        #if defined(dOverpaintType_groupInstance)
+        #if defined(dOverpaintType_instance)
+            vOverpaint = readFromTexture(tOverpaint, aInstance, uOverpaintTexDim);
+        #elif defined(dOverpaintType_groupInstance)
             vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
         #elif defined(dOverpaintType_vertexInstance)
             vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
@@ -43,7 +45,9 @@ export const assign_color_varying = `
     #endif
 
     #ifdef dSubstance
-        #if defined(dSubstanceType_groupInstance)
+        #if defined(dSubstanceType_instance)
+            vSubstance = readFromTexture(tSubstance, aInstance, uSubstanceTexDim);
+        #elif defined(dSubstanceType_groupInstance)
             vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim);
         #elif defined(dSubstanceType_vertexInstance)
             vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim);
@@ -72,7 +76,9 @@ export const assign_color_varying = `
 #endif
 
 #ifdef dTransparency
-    #if defined(dTransparencyType_groupInstance)
+    #if defined(dTransparencyType_instance)
+        vTransparency = readFromTexture(tTransparency, aInstance, uTransparencyTexDim).a;
+    #elif defined(dTransparencyType_groupInstance)
         vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
     #elif defined(dTransparencyType_vertexInstance)
         vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;

+ 5 - 1
src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts

@@ -1,5 +1,9 @@
 export const assign_marker_varying = `
 #if defined(dRenderVariant_color) || defined(dRenderVariant_marking)
-    vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+    #if defined(dMarkerType_instance)
+        vMarker = readFromTexture(tMarker, aInstance, uMarkerTexDim).a;
+    #elif defined(dMarkerType_groupInstance)
+        vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+    #endif
 #endif
 `;

+ 3 - 3
src/mol-gl/shader/chunks/color-vert-params.glsl.ts

@@ -28,7 +28,7 @@ uniform float uBumpiness;
     #endif
 
     #ifdef dOverpaint
-        #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
+        #if defined(dOverpaintType_instance) || defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
             varying vec4 vOverpaint;
             uniform vec2 uOverpaintTexDim;
             uniform sampler2D tOverpaint;
@@ -42,7 +42,7 @@ uniform float uBumpiness;
     #endif
 
     #ifdef dSubstance
-        #if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
+        #if defined(dSubstanceType_instance) || defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
             varying vec4 vSubstance;
             uniform vec2 uSubstanceTexDim;
             uniform sampler2D tSubstance;
@@ -75,7 +75,7 @@ uniform float uBumpiness;
 #endif
 
 #ifdef dTransparency
-    #if defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance)
+    #if defined(dTransparencyType_instance) || defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance)
         varying float vTransparency;
         uniform vec2 uTransparencyTexDim;
         uniform sampler2D tTransparency;

+ 14 - 0
src/mol-plugin-state/manager/structure/hierarchy.ts

@@ -5,6 +5,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { Structure } from '../../../mol-model/structure';
 import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
 import { PluginCommands } from '../../../mol-plugin/commands';
 import { PluginContext } from '../../../mol-plugin/context';
@@ -12,6 +13,7 @@ import { StateTransform, StateTree } from '../../../mol-state';
 import { SetUtils } from '../../../mol-util/set';
 import { TrajectoryHierarchyPresetProvider } from '../../builder/structure/hierarchy-preset';
 import { PluginComponent } from '../../component';
+import { PluginStateObject } from '../../objects';
 import { buildStructureHierarchy, StructureHierarchyRef, ModelRef, StructureComponentRef, StructureHierarchy, StructureRef, TrajectoryRef } from './hierarchy-state';
 
 export class StructureHierarchyManager extends PluginComponent {
@@ -79,6 +81,18 @@ export class StructureHierarchyManager extends PluginComponent {
         return ret;
     }
 
+    findStructure(structure: Structure | undefined): StructureRef | undefined {
+        if (!structure) return undefined;
+
+        const parent = this.plugin.helpers.substructureParent.get(structure);
+        if (!parent) return undefined;
+
+        const root = this.plugin.state.data.selectQ(q => q.byValue(parent).rootOfType(PluginStateObject.Molecule.Structure))[0];
+        if (!root) return undefined;
+
+        return this.behaviors.selection.value.structures.find(s => s.cell === root);
+    }
+
     private syncCurrent<T extends StructureHierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] {
         const current = this.seletionSet;
         const newCurrent: T[] = [];

+ 42 - 32
src/mol-plugin-ui/structure/superposition.tsx

@@ -5,25 +5,26 @@
  * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 
-import { CollapsableControls, PurePluginUIComponent } from '../base';
-import { Icon, ArrowUpwardSvg, ArrowDownwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, TuneSvg, SuperposeAtomsSvg, SuperposeChainsSvg, SuperpositionSvg } from '../controls/icons';
-import { Button, ToggleButton, IconButton } from '../controls/common';
-import { StructureElement, StructureSelection, QueryContext, Structure, StructureProperties } from '../../mol-model/structure';
+import { SymmetryOperator } from '../../mol-math/geometry';
 import { Mat4 } from '../../mol-math/linear-algebra';
-import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { StateObjectRef, StateObjectCell, StateSelection } from '../../mol-state';
-import { StateTransforms } from '../../mol-plugin-state/transforms';
-import { PluginStateObject } from '../../mol-plugin-state/objects';
+import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping';
+import { QueryContext, Structure, StructureElement, StructureProperties, StructureSelection } from '../../mol-model/structure';
 import { alignAndSuperpose, superpose } from '../../mol-model/structure/structure/util/superposition';
+import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-mapping';
 import { StructureSelectionQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
-import { structureElementStatsLabel, elementLabel } from '../../mol-theme/label';
-import { ParameterControls } from '../controls/parameters';
-import { stripTags } from '../../mol-util/string';
 import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
-import { ToggleSelectionModeButton } from './selection';
-import { alignAndSuperposeWithSIFTSMapping } from '../../mol-model/structure/structure/util/superposition-sifts-mapping';
+import { PluginStateObject } from '../../mol-plugin-state/objects';
+import { StateTransforms } from '../../mol-plugin-state/transforms';
 import { PluginCommands } from '../../mol-plugin/commands';
-import { SIFTSMapping } from '../../mol-model-props/sequence/sifts-mapping';
+import { StateObjectCell, StateObjectRef } from '../../mol-state';
+import { elementLabel, structureElementStatsLabel } from '../../mol-theme/label';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { stripTags } from '../../mol-util/string';
+import { CollapsableControls, PurePluginUIComponent } from '../base';
+import { Button, IconButton, ToggleButton } from '../controls/common';
+import { ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, SuperposeAtomsSvg, SuperposeChainsSvg, SuperpositionSvg, TuneSvg } from '../controls/icons';
+import { ParameterControls } from '../controls/parameters';
+import { ToggleSelectionModeButton } from './selection';
 
 export class StructureSuperpositionControls extends CollapsableControls {
     defaultState() {
@@ -104,19 +105,21 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
         return this.plugin.managers.structure.selection;
     }
 
-    async transform(s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4) {
+    async transform(s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4, coordinateSystem?: SymmetryOperator) {
         const r = StateObjectRef.resolveAndCheck(this.plugin.state.data, s);
         if (!r) return;
-        // TODO should find any TransformStructureConformation decorator instance
-        const o = StateSelection.findTagInSubtree(this.plugin.state.data.tree, r.transform.ref, SuperpositionTag);
+        const o = this.plugin.state.data.selectQ(q => q.byRef(r.transform.ref).subtree().withTransformer(StateTransforms.Model.TransformStructureConformation))[0];
+
+        const transform = coordinateSystem && !Mat4.isIdentity(coordinateSystem.matrix)
+            ? Mat4.mul(Mat4(), coordinateSystem.matrix, matrix)
+            : matrix;
 
         const params = {
             transform: {
                 name: 'matrix' as const,
-                params: { data: matrix, transpose: false }
+                params: { data: transform, transpose: false }
             }
         };
-        // TODO add .insertOrUpdate to StateBuilder?
         const b = o
             ? this.plugin.state.data.build().to(o).update(params)
             : this.plugin.state.data.build().to(s)
@@ -124,19 +127,24 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
         await this.plugin.runTask(this.plugin.state.data.updateTree(b));
     }
 
+    private getRootStructure(s: Structure) {
+        const parent = this.plugin.helpers.substructureParent.get(s)!;
+        return this.plugin.state.data.selectQ(q => q.byValue(parent).rootOfType(PluginStateObject.Molecule.Structure))[0].obj?.data!;
+    }
+
     superposeChains = async () => {
         const { query } = this.state.options.traceOnly ? StructureSelectionQueries.trace : StructureSelectionQueries.polymer;
         const entries = this.chainEntries;
 
-        const locis = entries.map((e, i) => {
+        const locis = entries.map(e => {
             const s = StructureElement.Loci.toStructure(e.loci);
             const loci = StructureSelection.toLociWithSourceUnits(query(new QueryContext(s)));
-            return StructureElement.Loci.remap(loci, i === 0
-                ? this.plugin.helpers.substructureParent.get(e.loci.structure.root)!.obj!.data
-                : loci.structure.root
-            );
+            return StructureElement.Loci.remap(loci, this.getRootStructure(e.loci.structure));
         });
 
+        const pivot = this.plugin.managers.structure.hierarchy.findStructure(locis[0]?.structure);
+        const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem;
+
         const transforms = this.state.options.alignSequences
             ? alignAndSuperpose(locis)
             : superpose(locis);
@@ -145,7 +153,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
         for (let i = 1, il = locis.length; i < il; ++i) {
             const eB = entries[i];
             const { bTransform, rmsd } = transforms[i - 1];
-            await this.transform(eB.cell, bTransform);
+            await this.transform(eB.cell, bTransform, coordinateSystem);
             const labelA = stripTags(eA.label);
             const labelB = stripTags(eB.label);
             this.plugin.log.info(`Superposed [${labelA}] and [${labelB}] with RMSD ${rmsd.toFixed(2)}.`);
@@ -156,19 +164,19 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
     superposeAtoms = async () => {
         const entries = this.atomEntries;
 
-        const atomLocis = entries.map((e, i) => {
-            return StructureElement.Loci.remap(e.loci, i === 0
-                ? this.plugin.helpers.substructureParent.get(e.loci.structure.root)!.obj!.data
-                : e.loci.structure.root
-            );
+        const atomLocis = entries.map(e => {
+            return StructureElement.Loci.remap(e.loci, this.getRootStructure(e.loci.structure));
         });
         const transforms = superpose(atomLocis);
 
+        const pivot = this.plugin.managers.structure.hierarchy.findStructure(atomLocis[0]?.structure);
+        const coordinateSystem = pivot?.transform?.cell.obj?.data.coordinateSystem;
+
         const eA = entries[0];
         for (let i = 1, il = atomLocis.length; i < il; ++i) {
             const eB = entries[i];
             const { bTransform, rmsd } = transforms[i - 1];
-            await this.transform(eB.cell, bTransform);
+            await this.transform(eB.cell, bTransform, coordinateSystem);
             const labelA = stripTags(eA.label);
             const labelB = stripTags(eB.label);
             const count = entries[i].atoms.length;
@@ -184,10 +192,12 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
         const structures = input.map(s => s.cell.obj?.data!);
         const { entries, failedPairs, zeroOverlapPairs } = alignAndSuperposeWithSIFTSMapping(structures, { traceOnly });
 
+        const coordinateSystem = input[0]?.transform?.cell.obj?.data.coordinateSystem;
+
         let rmsd = 0;
 
         for (const xform of entries) {
-            await this.transform(input[xform.other].cell, xform.transform.bTransform);
+            await this.transform(input[xform.other].cell, xform.transform.bTransform, coordinateSystem);
             rmsd += xform.transform.rmsd;
         }
 

+ 30 - 3
src/mol-repr/shape/representation.ts

@@ -78,6 +78,10 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
             console.warn('unexpected state');
         }
 
+        if (props.instanceGranularity !== currentProps.instanceGranularity) {
+            updateState.updateTransform = true;
+        }
+
         if (updateState.updateTransform) {
             updateState.updateColor = true;
             updateState.updateSize = true;
@@ -125,7 +129,11 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
                     Shape.createTransform(_shape.transforms, _renderObject.values);
                     locationIt = Shape.groupIterator(_shape);
                     const { instanceCount, groupCount } = locationIt;
-                    createMarkers(instanceCount * groupCount, _renderObject.values);
+                    if (props.instanceGranularity) {
+                        createMarkers(instanceCount, 'instance', _renderObject.values);
+                    } else {
+                        createMarkers(instanceCount * groupCount, 'groupInstance', _renderObject.values);
+                    }
                 }
 
                 if (updateState.createGeometry) {
@@ -167,11 +175,30 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
         });
     }
 
+    function eachInstance(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
+        let changed = false;
+        if (!ShapeGroup.isLoci(loci)) return false;
+        if (ShapeGroup.isLociEmpty(loci)) return false;
+        if (loci.shape !== shape) return false;
+        for (const g of loci.groups) {
+            if (apply(Interval.ofSingleton(g.instance))) changed = true;
+        }
+        return changed;
+    }
+
     function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
         if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
-            return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length));
+            if (currentProps.instanceGranularity) {
+                return apply(Interval.ofBounds(0, _shape.transforms.length));
+            } else {
+                return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length));
+            }
         } else {
-            return eachShapeGroup(loci, _shape, apply);
+            if (currentProps.instanceGranularity) {
+                return eachInstance(loci, _shape, apply);
+            } else {
+                return eachShapeGroup(loci, _shape, apply);
+            }
         }
     }
 

+ 28 - 4
src/mol-repr/structure/complex-visual.ts

@@ -6,7 +6,7 @@
 
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Visual, VisualContext } from '../visual';
-import { Structure, StructureElement } from '../../mol-model/structure';
+import { Bond, Structure, StructureElement } from '../../mol-model/structure';
 import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { Theme } from '../../mol-theme/theme';
@@ -126,6 +126,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             updateState.createGeometry = true;
         }
 
+        if (newProps.instanceGranularity !== currentProps.instanceGranularity) {
+            updateState.updateTransform = true;
+        }
+
         if (updateState.updateSize && !('uSize' in renderObject.values)) {
             updateState.createGeometry = true;
         }
@@ -154,7 +158,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
                 // console.log('update transform')
                 locationIt = createLocationIterator(newStructure);
                 const { instanceCount, groupCount } = locationIt;
-                createMarkers(instanceCount * groupCount, renderObject.values);
+                if (newProps.instanceGranularity) {
+                    createMarkers(instanceCount, 'instance', renderObject.values);
+                } else {
+                    createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values);
+                }
             }
 
             if (updateState.createGeometry) {
@@ -205,11 +213,27 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
         return false;
     }
 
+    function eachInstance(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
+        let changed = false;
+        if (!StructureElement.Loci.is(loci) && !Bond.isLoci(loci)) return false;
+        if (!Structure.areEquivalent(loci.structure, structure)) return false;
+        if (apply(Interval.ofSingleton(0))) changed = true;
+        return changed;
+    }
+
     function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
         if (lociIsSuperset(loci)) {
-            return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
+            if (currentProps.instanceGranularity) {
+                return apply(Interval.ofBounds(0, locationIt.instanceCount));
+            } else {
+                return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
+            }
         } else {
-            return eachLocation(loci, currentStructure, apply, isMarking);
+            if (currentProps.instanceGranularity) {
+                return eachInstance(loci, currentStructure, apply);
+            } else {
+                return eachLocation(loci, currentStructure, apply, isMarking);
+            }
         }
     }
 

+ 45 - 4
src/mol-repr/structure/units-visual.ts

@@ -5,7 +5,7 @@
  */
 
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { Structure, Unit, StructureElement } from '../../mol-model/structure';
+import { Structure, Unit, StructureElement, Bond } from '../../mol-model/structure';
 import { RepresentationProps } from '../representation';
 import { Visual, VisualContext } from '../visual';
 import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
@@ -126,6 +126,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
             updateState.createGeometry = true;
         }
 
+        if (newProps.instanceGranularity !== currentProps.instanceGranularity) {
+            updateState.updateTransform = true;
+        }
+
         if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) {
             // console.log('new unitKinds');
             updateState.createGeometry = true;
@@ -194,7 +198,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
                 // console.log('update transform');
                 locationIt = createLocationIterator(newStructureGroup);
                 const { instanceCount, groupCount } = locationIt;
-                createMarkers(instanceCount * groupCount, renderObject.values);
+                if (newProps.instanceGranularity) {
+                    createMarkers(instanceCount, 'instance', renderObject.values);
+                } else {
+                    createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values);
+                }
             }
 
             if (updateState.updateMatrix) {
@@ -259,11 +267,44 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         return false;
     }
 
+    function eachInstance(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
+        let changed = false;
+        if (Bond.isLoci(loci)) {
+            const { structure, group } = structureGroup;
+            if (!Structure.areEquivalent(loci.structure, structure)) return false;
+            for (const b of loci.bonds) {
+                if (b.aUnit !== b.bUnit) continue;
+                const unitIdx = group.unitIndexMap.get(b.aUnit.id);
+                if (unitIdx !== undefined) {
+                    if (apply(Interval.ofSingleton(unitIdx))) changed = true;
+                }
+            }
+        } else if (StructureElement.Loci.is(loci)) {
+            const { structure, group } = structureGroup;
+            if (!Structure.areEquivalent(loci.structure, structure)) return false;
+            for (const e of loci.elements) {
+                const unitIdx = group.unitIndexMap.get(e.unit.id);
+                if (unitIdx !== undefined) {
+                    if (apply(Interval.ofSingleton(unitIdx))) changed = true;
+                }
+            }
+        }
+        return changed;
+    }
+
     function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
         if (lociIsSuperset(loci)) {
-            return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
+            if (currentProps.instanceGranularity) {
+                return apply(Interval.ofBounds(0, locationIt.instanceCount));
+            } else {
+                return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
+            }
         } else {
-            return eachLocation(loci, currentStructureGroup, apply, isMarking);
+            if (currentProps.instanceGranularity) {
+                return eachInstance(loci, currentStructureGroup, apply);
+            } else {
+                return eachLocation(loci, currentStructureGroup, apply, isMarking);
+            }
         }
     }
 

+ 39 - 21
src/mol-repr/visual.ts

@@ -82,8 +82,10 @@ namespace Visual {
     export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply, previous?: PreviousMark) {
         if (!renderObject || isEmptyLoci(loci)) return false;
 
-        const { tMarker, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tMarker, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
         const { array } = tMarker.ref.value;
         const currentStatus = markerStatus.ref.value as MarkerInfo['status'];
 
@@ -158,11 +160,14 @@ namespace Visual {
     export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tOverpaint, dOverpaintType, dOverpaint, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tOverpaint, dOverpaintType, dOverpaint, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
 
-        // ensure texture has right size
-        createOverpaint(overpaint.layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createOverpaint(overpaint.layers.length ? count : 0, type, renderObject.values);
         const { array } = tOverpaint.ref.value;
 
         // clear all if requested
@@ -180,10 +185,11 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tOverpaint, tOverpaint.ref.value);
-        ValueCell.updateIfChanged(dOverpaintType, 'groupInstance');
+        ValueCell.updateIfChanged(dOverpaintType, type);
         ValueCell.updateIfChanged(dOverpaint, overpaint.layers.length > 0);
 
         if (overpaint.layers.length === 0) return;
+        if (type === 'instance') return;
 
         if (smoothing && hasColorSmoothingProp(smoothing.props)) {
             const { geometry, props, webgl } = smoothing;
@@ -208,11 +214,14 @@ namespace Visual {
     export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tTransparency, dTransparencyType, transparencyAverage, dTransparency, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tTransparency, dTransparencyType, transparencyAverage, dTransparency, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
 
-        // ensure texture has right size and variant
-        createTransparency(transparency.layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createTransparency(transparency.layers.length ? count : 0, type, renderObject.values);
         const { array } = tTransparency.ref.value;
 
         // clear if requested
@@ -229,10 +238,11 @@ namespace Visual {
         }
         ValueCell.update(tTransparency, tTransparency.ref.value);
         ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count));
-        ValueCell.updateIfChanged(dTransparencyType, 'groupInstance');
+        ValueCell.updateIfChanged(dTransparencyType, type);
         ValueCell.updateIfChanged(dTransparency, transparency.layers.length > 0);
 
         if (transparency.layers.length === 0) return;
+        if (type === 'instance') return;
 
         if (smoothing && hasColorSmoothingProp(smoothing.props)) {
             const { geometry, props, webgl } = smoothing;
@@ -257,11 +267,14 @@ namespace Visual {
     export function setSubstance(renderObject: GraphicsRenderObject | undefined, substance: Substance, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tSubstance, dSubstanceType, dSubstance, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tSubstance, dSubstanceType, dSubstance, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
 
-        // ensure texture has right size
-        createSubstance(substance.layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createSubstance(substance.layers.length ? count : 0, type, renderObject.values);
         const { array } = tSubstance.ref.value;
 
         // clear all if requested
@@ -279,10 +292,11 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tSubstance, tSubstance.ref.value);
-        ValueCell.updateIfChanged(dSubstanceType, 'groupInstance');
+        ValueCell.updateIfChanged(dSubstanceType, type);
         ValueCell.updateIfChanged(dSubstance, substance.layers.length > 0);
 
         if (substance.layers.length === 0) return;
+        if (type === 'instance') return;
 
         if (smoothing && hasColorSmoothingProp(smoothing.props)) {
             const { geometry, props, webgl } = smoothing;
@@ -307,12 +321,15 @@ namespace Visual {
     export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) {
         if (!renderObject) return;
 
-        const { tClipping, dClipping, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tClipping, dClippingType, dClipping, uGroupCount, instanceCount, instanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
         const { layers } = clipping;
 
-        // ensure texture has right size
-        createClipping(layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createClipping(layers.length ? count : 0, type, renderObject.values);
         const { array } = tClipping.ref.value;
 
         // clear if requested
@@ -328,6 +345,7 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tClipping, tClipping.ref.value);
+        ValueCell.updateIfChanged(dClippingType, type);
         ValueCell.updateIfChanged(dClipping, clipping.layers.length > 0);
     }
 

+ 36 - 3
src/mol-repr/volume/representation.ts

@@ -33,6 +33,7 @@ import { Clipping } from '../../mol-theme/clipping';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { isPromiseLike } from '../../mol-util/type-helpers';
 import { Substance } from '../../mol-theme/substance';
+import { createMarkers } from '../../mol-geo/geometry/marker-data';
 
 export interface VolumeVisual<P extends VolumeParams> extends Visual<Volume, P> { }
 
@@ -108,6 +109,10 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
         if (updateState.createGeometry) {
             updateState.updateColor = true;
         }
+
+        if (newProps.instanceGranularity !== currentProps.instanceGranularity) {
+            updateState.updateTransform = true;
+        }
     }
 
     function update(newGeometry?: G) {
@@ -124,7 +129,18 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
                 throw new Error('expected renderObject to be available');
             }
 
-            locationIt.reset();
+            if (updateState.updateTransform) {
+                // console.log('update transform');
+                locationIt = createLocationIterator(newVolume);
+                const { instanceCount, groupCount } = locationIt;
+                if (newProps.instanceGranularity) {
+                    createMarkers(instanceCount, 'instance', renderObject.values);
+                } else {
+                    createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values);
+                }
+            } else {
+                locationIt.reset();
+            }
 
             if (updateState.createGeometry) {
                 if (newGeometry) {
@@ -165,11 +181,28 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
         }
     }
 
+    function eachInstance(loci: Loci, volume: Volume, apply: (interval: Interval) => boolean) {
+        let changed = false;
+        if (!Volume.Cell.isLoci(loci)) return false;
+        if (Volume.Cell.isLociEmpty(loci)) return false;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
+        if (apply(Interval.ofSingleton(0))) changed = true;
+        return changed;
+    }
+
     function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
         if (isEveryLoci(loci)) {
-            return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
+            if (currentProps.instanceGranularity) {
+                return apply(Interval.ofBounds(0, locationIt.instanceCount));
+            } else {
+                return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
+            }
         } else {
-            return eachLocation(loci, currentVolume, currentProps, apply);
+            if (currentProps.instanceGranularity) {
+                return eachInstance(loci, currentVolume, apply);
+            } else {
+                return eachLocation(loci, currentVolume, currentProps, apply);
+            }
         }
     }