Bladeren bron

add support for vertex colors

use in color theme with
- PositionLocation
- 'vertex'/'vertexInstance' granularity
Alexander Rose 4 jaren geleden
bovenliggende
commit
490c6679eb
44 gewijzigde bestanden met toevoegingen van 350 en 113 verwijderingen
  1. 2 2
      src/extensions/dnatco/confal-pyramids/representation.ts
  2. 2 2
      src/extensions/rcsb/validation-report/representation.ts
  3. 3 2
      src/mol-geo/geometry/base.ts
  4. 41 7
      src/mol-geo/geometry/color-data.ts
  5. 7 3
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  6. 16 4
      src/mol-geo/geometry/geometry.ts
  7. 8 4
      src/mol-geo/geometry/image/image.ts
  8. 28 5
      src/mol-geo/geometry/lines/lines.ts
  9. 24 4
      src/mol-geo/geometry/mesh/mesh.ts
  10. 26 5
      src/mol-geo/geometry/points/points.ts
  11. 25 5
      src/mol-geo/geometry/spheres/spheres.ts
  12. 23 3
      src/mol-geo/geometry/text/text.ts
  13. 7 3
      src/mol-geo/geometry/texture-mesh/texture-mesh.ts
  14. 34 16
      src/mol-geo/util/location-iterator.ts
  15. 2 1
      src/mol-gl/_spec/renderer.spec.ts
  16. 2 1
      src/mol-gl/renderable/direct-volume.ts
  17. 3 2
      src/mol-gl/renderable/schema.ts
  18. 1 3
      src/mol-gl/scene.ts
  19. 4 0
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  20. 8 0
      src/mol-gl/shader/chunks/color-vert-params.glsl.ts
  21. 1 0
      src/mol-gl/shader/chunks/common-vert-params.glsl.ts
  22. 1 1
      src/mol-gl/shader/chunks/common.glsl.ts
  23. 2 0
      src/mol-gl/webgl/program.ts
  24. 15 0
      src/mol-gl/webgl/render-item.ts
  25. 1 1
      src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts
  26. 1 1
      src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts
  27. 1 1
      src/mol-model-props/integrative/cross-link-restraint/representation.ts
  28. 3 2
      src/mol-model/location.ts
  29. 1 1
      src/mol-model/shape/shape.ts
  30. 8 4
      src/mol-repr/shape/representation.ts
  31. 12 5
      src/mol-repr/structure/complex-visual.ts
  32. 12 6
      src/mol-repr/structure/units-visual.ts
  33. 1 1
      src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts
  34. 1 1
      src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
  35. 1 1
      src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts
  36. 1 1
      src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts
  37. 2 2
      src/mol-repr/structure/visual/util/bond.ts
  38. 2 2
      src/mol-repr/structure/visual/util/element.ts
  39. 1 1
      src/mol-repr/structure/visual/util/nucleotide.ts
  40. 2 2
      src/mol-repr/structure/visual/util/polymer.ts
  41. 1 1
      src/mol-repr/volume/direct-volume.ts
  42. 2 2
      src/mol-repr/volume/isosurface.ts
  43. 11 4
      src/mol-repr/volume/representation.ts
  44. 1 1
      src/mol-repr/volume/slice.ts

+ 2 - 2
src/extensions/dnatco/confal-pyramids/representation.ts

@@ -55,7 +55,7 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
 
     const prop = ConfalPyramidsProvider.get(structure.model).value;
     if (prop === undefined || prop.data === undefined) {
-        return LocationIterator(0, 1, () => NullLocation);
+        return LocationIterator(0, 1, 1, () => NullLocation);
     }
 
     const { locations } = prop.data;
@@ -64,7 +64,7 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
         if (locations.length <= groupIndex) return NullLocation;
         return locations[groupIndex];
     };
-    return LocationIterator(locations.length, instanceCount, getLocation);
+    return LocationIterator(locations.length, instanceCount, 1, getLocation);
 }
 
 function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) {

+ 2 - 2
src/extensions/rcsb/validation-report/representation.ts

@@ -143,7 +143,7 @@ function createIntraClashIterator(structureGroup: StructureGroup): LocationItera
         location.element = unit.elements[a[groupIndex]];
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation);
 }
 
 //
@@ -255,7 +255,7 @@ function createInterClashIterator(structure: Structure): LocationIterator {
         location.element = location.unit.elements[clash.indexA];
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation, true);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
 }
 
 //

+ 3 - 2
src/mol-geo/geometry/base.ts

@@ -46,11 +46,11 @@ export namespace BaseGeometry {
         hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
     };
 
-    export type Counts = { drawCount: number, groupCount: number, instanceCount: number }
+    export type Counts = { drawCount: number, vertexCount: number, groupCount: number, instanceCount: number }
 
     export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
         if (!transform) transform = createIdentityTransform();
-        const locationIterator = LocationIterator(1, transform.instanceCount.ref.value, () => NullLocation, false, () => false);
+        const locationIterator = LocationIterator(1, transform.instanceCount.ref.value, 1, () => NullLocation, false, () => false);
         const theme: Theme = {
             color: UniformColorTheme({}, { value: colorValue}),
             size: UniformSizeTheme({}, { value: sizeValue})
@@ -62,6 +62,7 @@ export namespace BaseGeometry {
         return {
             alpha: ValueCell.create(props.alpha),
             uAlpha: ValueCell.create(props.alpha),
+            uVertexCount: ValueCell.create(counts.vertexCount),
             uGroupCount: ValueCell.create(counts.groupCount),
             drawCount: ValueCell.create(counts.drawCount),
         };

+ 41 - 7
src/mol-geo/geometry/color-data.ts

@@ -13,7 +13,7 @@ import { NullLocation } from '../../mol-model/location';
 import { LocationColor, ColorTheme } from '../../mol-theme/color';
 import { Geometry } from './geometry';
 
-export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance'
+export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 'vertex' | 'vertexInstance'
 
 export type ColorData = {
     uColor: ValueCell<Vec3>,
@@ -22,12 +22,14 @@ export type ColorData = {
     dColorType: ValueCell<string>,
 }
 
-export function createColors(locationIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
+export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
     switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) {
         case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData);
+        case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
         case 'group': return createGroupColor(locationIt, colorTheme.color, colorData);
         case 'groupInstance': return createGroupInstanceColor(locationIt, colorTheme.color, colorData);
-        case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
+        case 'vertex': return createVertexColor(positionIt, colorTheme.color, colorData);
+        case 'vertexInstance': return createVertexInstanceColor(positionIt, colorTheme.color, colorData);
     }
 }
 
@@ -67,7 +69,7 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
     }
 }
 
-/** Creates color texture with color for each instance/unit */
+/** Creates color texture with color for each instance */
 export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
     const { instanceCount } = locationIt;
     const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
@@ -80,7 +82,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio
     return createTextureColor(colors, 'instance', colorData);
 }
 
-/** Creates color texture with color for each group (i.e. shared across instances/units) */
+/** Creates color texture with color for each group (i.e. shared across instances) */
 export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
     const { groupCount } = locationIt;
     const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
@@ -92,7 +94,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo
     return createTextureColor(colors, 'group', colorData);
 }
 
-/** Creates color texture with color for each group in each instance (i.e. for each unit) */
+/** Creates color texture with color for each group in each instance */
 export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
     const { groupCount, instanceCount } = locationIt;
     const count = instanceCount * groupCount;
@@ -103,4 +105,36 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo
         Color.toArray(color(location, isSecondary), colors.array, index * 3);
     }
     return createTextureColor(colors, 'groupInstance', colorData);
-}
+}
+
+/** Creates color texture with color for each vertex (i.e. shared across instances) */
+export function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
+    const { groupCount, stride } = locationIt;
+    const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
+    locationIt.reset();
+    locationIt.voidInstances();
+    while (locationIt.hasNext && !locationIt.isNextNewInstance) {
+        const { location, isSecondary, groupIndex } = locationIt.move();
+        const c = color(location, isSecondary);
+        for (let i = 0; i < stride; ++i) {
+            Color.toArray(c, colors.array, (groupIndex + i) * 3);
+        }
+    }
+    return createTextureColor(colors, 'vertex', colorData);
+}
+
+/** Creates color texture with color for each vertex in each instance */
+export function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
+    const { groupCount, instanceCount, stride } = locationIt;
+    const count = instanceCount * groupCount;
+    const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
+    locationIt.reset();
+    while (locationIt.hasNext) {
+        const { location, isSecondary, index } = locationIt.move();
+        const c = color(location, isSecondary);
+        for (let i = 0; i < stride; ++i) {
+            Color.toArray(c, colors.array, (index + i) * 3);
+        }
+    }
+    return createTextureColor(colors, 'vertexInstance', colorData);
+}

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

@@ -28,6 +28,7 @@ import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './
 import { createEmptyClipping } from '../clipping-data';
 import { Grid, Volume } from '../../../mol-model/volume';
 import { ColorNames } from '../../../mol-util/color/names';
+import { NullLocation } from '../../../mol-model/location';
 
 const VolumeBox = Box();
 
@@ -173,7 +174,8 @@ export namespace DirectVolume {
         updateValues,
         updateBoundingSphere,
         createRenderableState,
-        updateRenderableState
+        updateRenderableState,
+        createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
     };
 
     function getNormalizedIsoValue(out: Vec2, isoValue: Volume.IsoValue, stats: Vec4) {
@@ -188,13 +190,15 @@ export namespace DirectVolume {
         const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume;
 
         const { instanceCount, groupCount } = locationIt;
-        const color = createColors(locationIt, theme.color);
+        const positionIt = Utils.createPositionIterator(directVolume, transform);
+
+        const color = createColors(locationIt, positionIt, theme.color);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount };
+        const counts = { drawCount: VolumeBox.indices.length, vertexCount: VolumeBox.vertices.length / 3, groupCount, instanceCount };
 
         const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);

+ 16 - 4
src/mol-geo/geometry/geometry.ts

@@ -54,6 +54,7 @@ export interface GeometryUtils<G extends Geometry, P extends PD.Params = Geometr
     updateBoundingSphere(values: V, geometry: G): void
     createRenderableState(props: Partial<PD.Values<P>>): RenderableState
     updateRenderableState(state: RenderableState, props: PD.Values<P>): void
+    createPositionIterator(geometry: G, transform: TransformData): LocationIterator
 }
 
 export namespace Geometry {
@@ -72,6 +73,19 @@ export namespace Geometry {
         }
     }
 
+    export function getVertexCount(geometry: Geometry): number {
+        switch (geometry.kind) {
+            case 'mesh': return geometry.vertexCount;
+            case 'points': return geometry.pointCount;
+            case 'spheres': return geometry.sphereCount * 4;
+            case 'text': return geometry.charCount * 4;
+            case 'lines': return geometry.lineCount * 4;
+            case 'direct-volume': return 24;
+            case 'image': return 4;
+            case 'texture-mesh': return geometry.vertexCount / 3;
+        }
+    }
+
     export function getGroupCount(geometry: Geometry): number {
         switch (geometry.kind) {
             case 'mesh':
@@ -103,9 +117,7 @@ export namespace Geometry {
         }
     }
 
-    export function getGranularity(locationIt: LocationIterator, granularity: ColorType | SizeType) {
-        // Always use 'group' granularity for 'complex' location iterators,
-        // i.e. for which an instance may include multiple units
-        return granularity === 'instance' && locationIt.isComplex ? 'group' : granularity;
+    export function getGranularity<T extends ColorType | SizeType>(locationIt: LocationIterator, granularity: T) {
+        return granularity === 'instance' && locationIt.nonInstanceable ? 'group' : granularity;
     }
 }

+ 8 - 4
src/mol-geo/geometry/image/image.ts

@@ -24,6 +24,8 @@ import { createEmptyTransparency } from '../transparency-data';
 import { ImageValues } from '../../../mol-gl/renderable/image';
 import { fillSerial } from '../../../mol-util/array';
 import { createEmptyClipping } from '../clipping-data';
+import { NullLocation } from '../../../mol-model/location';
+import { QuadPositions } from '../../../mol-gl/compute/util';
 
 const QuadIndices = new Uint32Array([
     0, 1, 2,
@@ -128,19 +130,21 @@ namespace Image {
         updateValues,
         updateBoundingSphere,
         createRenderableState,
-        updateRenderableState
+        updateRenderableState,
+        createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
     };
 
     function createValues(image: Image, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): ImageValues {
-
         const { instanceCount, groupCount } = locationIt;
-        const color = createColors(locationIt, theme.color);
+        const positionIt = Utils.createPositionIterator(image, transform);
+
+        const color = createColors(locationIt, positionIt, theme.color);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: QuadIndices.length, groupCount, instanceCount };
+        const counts = { drawCount: QuadIndices.length, vertexCount: QuadPositions.length / 3, groupCount, instanceCount };
 
         const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);

+ 28 - 5
src/mol-geo/geometry/lines/lines.ts

@@ -5,14 +5,14 @@
  */
 
 import { ValueCell } from '../../../mol-util';
-import { Mat4, Vec4 } from '../../../mol-math/linear-algebra';
+import { Mat4, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
 import { GeometryUtils } from '../geometry';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
 import { createSizes } from '../size-data';
 import { TransformData } from '../transform-data';
-import { LocationIterator } from '../../util/location-iterator';
+import { LocationIterator, PositionLocation } from '../../util/location-iterator';
 import { LinesValues } from '../../../mol-gl/renderable/lines';
 import { Mesh } from '../mesh/mesh';
 import { LinesBuilder } from './lines-builder';
@@ -177,19 +177,42 @@ export namespace Lines {
         updateValues,
         updateBoundingSphere,
         createRenderableState: BaseGeometry.createRenderableState,
-        updateRenderableState: BaseGeometry.updateRenderableState
+        updateRenderableState: BaseGeometry.updateRenderableState,
+        createPositionIterator
     };
 
+    function createPositionIterator(lines: Lines, transform: TransformData): LocationIterator {
+        const groupCount = lines.lineCount * 4;
+        const instanceCount = transform.instanceCount.ref.value;
+        const location = PositionLocation();
+        const p = location.position;
+        const s = lines.startBuffer.ref.value;
+        const e = lines.endBuffer.ref.value;
+        const m = transform.aTransform.ref.value;
+        const getLocation = (groupIndex: number, instanceIndex: number) => {
+            const v = groupIndex % 4 === 0 ? s : e;
+            if (instanceIndex < 0) {
+                Vec3.fromArray(p, v, groupIndex * 3);
+            } else {
+                Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
+            }
+            return location;
+        };
+        return LocationIterator(groupCount, instanceCount, 2, getLocation);
+    }
+
     function createValues(lines: Lines, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): LinesValues {
         const { instanceCount, groupCount } = locationIt;
-        const color = createColors(locationIt, theme.color);
+        const positionIt = createPositionIterator(lines, transform);
+
+        const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: lines.lineCount * 2 * 3, groupCount, instanceCount };
+        const counts = { drawCount: lines.lineCount * 2 * 3, vertexCount: lines.lineCount * 4, groupCount, instanceCount };
 
         const invariantBoundingSphere = Sphere3D.clone(lines.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);

+ 24 - 4
src/mol-geo/geometry/mesh/mesh.ts

@@ -12,7 +12,7 @@ import { transformPositionArray, transformDirectionArray, computeIndexedVertexNo
 import { GeometryUtils } from '../geometry';
 import { createMarkers } from '../marker-data';
 import { TransformData } from '../transform-data';
-import { LocationIterator } from '../../util/location-iterator';
+import { LocationIterator, PositionLocation } from '../../util/location-iterator';
 import { createColors } from '../color-data';
 import { ChunkedArray, hashFnv32a } from '../../../mol-data/util';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -343,22 +343,42 @@ export namespace Mesh {
         updateValues,
         updateBoundingSphere,
         createRenderableState: BaseGeometry.createRenderableState,
-        updateRenderableState: BaseGeometry.updateRenderableState
+        updateRenderableState: BaseGeometry.updateRenderableState,
+        createPositionIterator
     };
 
+    function createPositionIterator(mesh: Mesh, transform: TransformData): LocationIterator {
+        const groupCount = mesh.vertexCount;
+        const instanceCount = transform.instanceCount.ref.value;
+        const location = PositionLocation();
+        const p = location.position;
+        const v = mesh.vertexBuffer.ref.value;
+        const m = transform.aTransform.ref.value;
+        const getLocation = (groupIndex: number, instanceIndex: number) => {
+            if (instanceIndex < 0) {
+                Vec3.fromArray(p, v, groupIndex * 3);
+            } else {
+                Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
+            }
+            return location;
+        };
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
+    }
+
     function createValues(mesh: Mesh, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): MeshValues {
         const { instanceCount, groupCount } = locationIt;
         if (instanceCount !== transform.instanceCount.ref.value) {
             throw new Error('instanceCount values in TransformData and LocationIterator differ');
         }
+        const positionIt = createPositionIterator(mesh, transform);
 
-        const color = createColors(locationIt, theme.color);
+        const color = createColors(locationIt, positionIt, theme.color);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: mesh.triangleCount * 3, groupCount, instanceCount };
+        const counts = { drawCount: mesh.triangleCount * 3, vertexCount: mesh.vertexCount, groupCount, instanceCount };
 
         const invariantBoundingSphere = Sphere3D.clone(mesh.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);

+ 26 - 5
src/mol-geo/geometry/points/points.ts

@@ -5,14 +5,14 @@
  */
 
 import { ValueCell } from '../../../mol-util';
-import { Mat4, Vec4 } from '../../../mol-math/linear-algebra';
+import { Mat4, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
 import { GeometryUtils } from '../geometry';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
 import { createSizes } from '../size-data';
 import { TransformData } from '../transform-data';
-import { LocationIterator } from '../../util/location-iterator';
+import { LocationIterator, PositionLocation } from '../../util/location-iterator';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Sphere3D } from '../../../mol-math/geometry';
@@ -134,19 +134,40 @@ export namespace Points {
         updateValues,
         updateBoundingSphere,
         createRenderableState,
-        updateRenderableState
+        updateRenderableState,
+        createPositionIterator
     };
 
+    function createPositionIterator(points: Points, transform: TransformData): LocationIterator {
+        const groupCount = points.pointCount;
+        const instanceCount = transform.instanceCount.ref.value;
+        const location = PositionLocation();
+        const p = location.position;
+        const v = points.centerBuffer.ref.value;
+        const m = transform.aTransform.ref.value;
+        const getLocation = (groupIndex: number, instanceIndex: number) => {
+            if (instanceIndex < 0) {
+                Vec3.fromArray(p, v, groupIndex * 3);
+            } else {
+                Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
+            }
+            return location;
+        };
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
+    }
+
     function createValues(points: Points, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): PointsValues {
         const { instanceCount, groupCount } = locationIt;
-        const color = createColors(locationIt, theme.color);
+        const positionIt = createPositionIterator(points, transform);
+
+        const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: points.pointCount, groupCount, instanceCount };
+        const counts = { drawCount: points.pointCount, vertexCount: points.pointCount, groupCount, instanceCount };
 
         const invariantBoundingSphere = Sphere3D.clone(points.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);

+ 25 - 5
src/mol-geo/geometry/spheres/spheres.ts

@@ -8,7 +8,7 @@ import { ValueCell } from '../../../mol-util';
 import { GeometryUtils } from '../geometry';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { TransformData } from '../transform-data';
-import { LocationIterator } from '../../../mol-geo/util/location-iterator';
+import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
 import { Theme } from '../../../mol-theme/theme';
 import { SpheresValues } from '../../../mol-gl/renderable/spheres';
 import { createColors } from '../color-data';
@@ -23,7 +23,7 @@ import { createEmptyTransparency } from '../transparency-data';
 import { hashFnv32a } from '../../../mol-data/util';
 import { GroupMapping, createGroupMapping } from '../../util';
 import { createEmptyClipping } from '../clipping-data';
-import { Vec4 } from '../../../mol-math/linear-algebra';
+import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 
 export interface Spheres {
     readonly kind: 'spheres',
@@ -137,23 +137,43 @@ export namespace Spheres {
         updateValues,
         updateBoundingSphere,
         createRenderableState: BaseGeometry.createRenderableState,
-        updateRenderableState: BaseGeometry.updateRenderableState
+        updateRenderableState: BaseGeometry.updateRenderableState,
+        createPositionIterator
     };
 
+    function createPositionIterator(spheres: Spheres, transform: TransformData): LocationIterator {
+        const groupCount = spheres.sphereCount * 4;
+        const instanceCount = transform.instanceCount.ref.value;
+        const location = PositionLocation();
+        const p = location.position;
+        const v = spheres.centerBuffer.ref.value;
+        const m = transform.aTransform.ref.value;
+        const getLocation = (groupIndex: number, instanceIndex: number) => {
+            if (instanceIndex < 0) {
+                Vec3.fromArray(p, v, groupIndex * 3);
+            } else {
+                Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
+            }
+            return location;
+        };
+        return LocationIterator(groupCount, instanceCount, 4, getLocation);
+    }
+
     function createValues(spheres: Spheres, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): SpheresValues {
         const { instanceCount, groupCount } = locationIt;
         if (instanceCount !== transform.instanceCount.ref.value) {
             throw new Error('instanceCount values in TransformData and LocationIterator differ');
         }
+        const positionIt = createPositionIterator(spheres, transform);
 
-        const color = createColors(locationIt, theme.color);
+        const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: spheres.sphereCount * 2 * 3, groupCount, instanceCount };
+        const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
 
         const padding = getMaxSize(size);
         const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);

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

@@ -7,7 +7,7 @@
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { ValueCell } from '../../../mol-util';
 import { GeometryUtils } from '../geometry';
-import { LocationIterator } from '../../../mol-geo/util/location-iterator';
+import { LocationIterator, PositionLocation } from '../../../mol-geo/util/location-iterator';
 import { TransformData } from '../transform-data';
 import { Theme } from '../../../mol-theme/theme';
 import { createColors } from '../color-data';
@@ -183,22 +183,42 @@ export namespace Text {
         updateBoundingSphere,
         createRenderableState,
         updateRenderableState,
+        createPositionIterator
     };
 
+    function createPositionIterator(text: Text, transform: TransformData): LocationIterator {
+        const groupCount = text.charCount * 4;
+        const instanceCount = transform.instanceCount.ref.value;
+        const location = PositionLocation();
+        const p = location.position;
+        const v = text.centerBuffer.ref.value;
+        const m = transform.aTransform.ref.value;
+        const getLocation = (groupIndex: number, instanceIndex: number) => {
+            if (instanceIndex < 0) {
+                Vec3.fromArray(p, v, groupIndex * 3);
+            } else {
+                Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16);
+            }
+            return location;
+        };
+        return LocationIterator(groupCount, instanceCount, 4, getLocation);
+    }
+
     function createValues(text: Text, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextValues {
         const { instanceCount, groupCount } = locationIt;
         if (instanceCount !== transform.instanceCount.ref.value) {
             throw new Error('instanceCount values in TransformData and LocationIterator differ');
         }
+        const positionIt = createPositionIterator(text, transform);
 
-        const color = createColors(locationIt, theme.color);
+        const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: text.charCount * 2 * 3, groupCount, instanceCount };
+        const counts = { drawCount: text.charCount * 2 * 3, vertexCount: text.charCount * 4, groupCount, instanceCount };
 
         const padding = getPadding(text.mappingBuffer.ref.value, text.depthBuffer.ref.value, text.charCount, getMaxSize(size));
         const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), text.boundingSphere, padding);

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

@@ -23,6 +23,7 @@ import { Texture } from '../../../mol-gl/webgl/texture';
 import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
 import { fillSerial } from '../../../mol-util/array';
 import { createEmptyClipping } from '../clipping-data';
+import { NullLocation } from '../../../mol-model/location';
 
 export interface TextureMesh {
     readonly kind: 'texture-mesh',
@@ -85,18 +86,21 @@ export namespace TextureMesh {
         updateValues,
         updateBoundingSphere,
         createRenderableState: BaseGeometry.createRenderableState,
-        updateRenderableState: BaseGeometry.updateRenderableState
+        updateRenderableState: BaseGeometry.updateRenderableState,
+        createPositionIterator: () => LocationIterator(1, 1, 1, () => NullLocation)
     };
 
     function createValues(textureMesh: TextureMesh, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextureMeshValues {
         const { instanceCount, groupCount } = locationIt;
-        const color = createColors(locationIt, theme.color);
+        const positionIt = Utils.createPositionIterator(textureMesh, transform);
+
+        const color = createColors(locationIt, positionIt, theme.color);
         const marker = createMarkers(instanceCount * groupCount);
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: textureMesh.vertexCount, groupCount, instanceCount };
+        const counts = { drawCount: textureMesh.vertexCount, vertexCount: textureMesh.vertexCount / 3, groupCount, instanceCount };
 
         const transformBoundingSphere = calculateTransformBoundingSphere(textureMesh.boundingSphere, transform.aTransform.ref.value, transform.instanceCount.ref.value);
 

+ 34 - 16
src/mol-geo/util/location-iterator.ts

@@ -1,10 +1,11 @@
 /**
- * Copyright (c) 2018 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>
  */
 
 import { Iterator } from '../../mol-data';
+import { Vec3 } from '../../mol-math/linear-algebra';
 import { NullLocation, Location } from '../../mol-model/location';
 
 export interface LocationValue {
@@ -15,31 +16,28 @@ export interface LocationValue {
     isSecondary: boolean
 }
 
-export const NullLocationValue: LocationValue = {
-    location: NullLocation,
-    index: 0,
-    groupIndex: 0,
-    instanceIndex: 0,
-    isSecondary: false
-};
-
 export interface LocationIterator extends Iterator<LocationValue> {
     readonly hasNext: boolean
     readonly isNextNewInstance: boolean
     readonly groupCount: number
     readonly instanceCount: number
     readonly count: number
-    /** If true, may have multiple units per instance; if false one unit per instance */
-    readonly isComplex: boolean
+    readonly stride: number
+    readonly nonInstanceable: boolean
     move(): LocationValue
     reset(): void
     skipInstance(): void
+    voidInstances(): void
 }
 
 type LocationGetter = (groupIndex: number, instanceIndex: number) => Location
 type IsSecondaryGetter = (groupIndex: number, instanceIndex: number) => boolean
 
-export function LocationIterator(groupCount: number, instanceCount: number, getLocation: LocationGetter, isComplex = false, isSecondary: IsSecondaryGetter = () => false): LocationIterator {
+export function LocationIterator(groupCount: number, instanceCount: number, stride: number, getLocation: LocationGetter, nonInstanceable = false, isSecondary: IsSecondaryGetter = () => false): LocationIterator {
+    if (groupCount % stride !== 0) {
+        throw new Error('incompatible groupCount and stride');
+    }
+
     const value: LocationValue = {
         location: NullLocation as Location,
         index: 0,
@@ -52,6 +50,7 @@ export function LocationIterator(groupCount: number, instanceCount: number, getL
     let isNextNewInstance = false;
     let groupIndex = 0;
     let instanceIndex = 0;
+    let voidInstances = false;
 
     return {
         get hasNext () { return hasNext; },
@@ -59,15 +58,16 @@ export function LocationIterator(groupCount: number, instanceCount: number, getL
         groupCount,
         instanceCount,
         count: groupCount * instanceCount,
-        isComplex,
+        stride,
+        nonInstanceable,
         move() {
             if (hasNext) {
                 value.groupIndex = groupIndex;
                 value.instanceIndex = instanceIndex;
                 value.index = instanceIndex * groupCount + groupIndex;
-                value.location = getLocation(groupIndex, instanceIndex);
-                value.isSecondary = isSecondary(groupIndex, instanceIndex);
-                ++groupIndex;
+                value.location = getLocation(groupIndex, voidInstances ? -1 : instanceIndex);
+                value.isSecondary = isSecondary(groupIndex, voidInstances ? -1 : instanceIndex);
+                groupIndex += stride;
                 if (groupIndex === groupCount) {
                     ++instanceIndex;
                     isNextNewInstance = true;
@@ -90,6 +90,7 @@ export function LocationIterator(groupCount: number, instanceCount: number, getL
             isNextNewInstance = false;
             groupIndex = 0;
             instanceIndex = 0;
+            voidInstances = false;
         },
         skipInstance() {
             if (hasNext && value.instanceIndex === instanceIndex) {
@@ -97,6 +98,23 @@ export function LocationIterator(groupCount: number, instanceCount: number, getL
                 groupIndex = 0;
                 hasNext = instanceIndex < instanceCount;
             }
+        },
+        voidInstances() {
+            voidInstances = true;
         }
     };
+}
+
+//
+
+/** A position Location */
+export interface PositionLocation {
+    readonly kind: 'position-location',
+    readonly position: Vec3
+}
+export function PositionLocation(position?: Vec3): PositionLocation {
+    return { kind: 'position-location', position: position ? Vec3.clone(position) : Vec3() };
+}
+export function isPositionLocation(x: any): x is PositionLocation {
+    return !!x && x.kind === 'position-location';
 }

+ 2 - 1
src/mol-gl/_spec/renderer.spec.ts

@@ -68,6 +68,7 @@ function createPoints() {
         ...clipping,
 
         uAlpha: ValueCell.create(1.0),
+        uVertexCount: ValueCell.create(3),
         uInstanceCount: ValueCell.create(1),
         uGroupCount: ValueCell.create(3),
         uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere.ref.value)),
@@ -128,7 +129,7 @@ describe('renderer', () => {
 
         scene.add(points);
         scene.commit();
-        expect(ctx.stats.resourceCounts.attribute).toBe(4);
+        expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
         expect(ctx.stats.resourceCounts.texture).toBe(6);
         expect(ctx.stats.resourceCounts.vertexArray).toBe(5);
         expect(ctx.stats.resourceCounts.program).toBe(5);

+ 2 - 1
src/mol-gl/renderable/direct-volume.ts

@@ -15,7 +15,7 @@ export const DirectVolumeSchema = {
     uColor: UniformSpec('v3'),
     uColorTexDim: UniformSpec('v2'),
     tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
-    dColorType: DefineSpec('string', ['uniform', 'instance', 'group', 'group_instance']),
+    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
 
     uMarkerTexDim: UniformSpec('v2'),
     tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
@@ -35,6 +35,7 @@ export const DirectVolumeSchema = {
     tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     dClipping: DefineSpec('boolean'),
 
+    uVertexCount: UniformSpec('i'),
     uInstanceCount: UniformSpec('i'),
     uGroupCount: UniformSpec('i'),
     uInvariantBoundingSphere: UniformSpec('v4'),

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

@@ -175,7 +175,7 @@ export const ColorSchema = {
     uColor: UniformSpec('v3', true),
     uColorTexDim: UniformSpec('v2'),
     tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
-    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']),
+    dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
 } as const;
 export type ColorSchema = typeof ColorSchema
 export type ColorValues = Values<ColorSchema>
@@ -185,7 +185,7 @@ export const SizeSchema = {
     uSize: UniformSpec('f', true),
     uSizeTexDim: UniformSpec('v2'),
     tSize: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
-    dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']),
+    dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance']),
     uSizeFactor: UniformSpec('f'),
 } as const;
 export type SizeSchema = typeof SizeSchema
@@ -245,6 +245,7 @@ export const BaseSchema = {
      * final alpha, calculated as `values.alpha * state.alpha`
      */
     uAlpha: UniformSpec('f', true),
+    uVertexCount: UniformSpec('i'),
     uInstanceCount: UniformSpec('i'),
     uGroupCount: UniformSpec('i'),
     uInvariantBoundingSphere: UniformSpec('v4'),

+ 1 - 3
src/mol-gl/scene.ts

@@ -193,9 +193,7 @@ namespace Scene {
                 Object3D.update(object3d);
                 if (objects) {
                     for (let i = 0, il = objects.length; i < il; ++i) {
-                        const o = renderableMap.get(objects[i]);
-                        if (!o) continue;
-                        o.update();
+                        renderableMap.get(objects[i])?.update();
                     }
                 } else {
                     for (let i = 0, il = renderables.length; i < il; ++i) {

+ 4 - 0
src/mol-gl/shader/chunks/assign-color-varying.glsl.ts

@@ -8,6 +8,10 @@ export default `
         vColor.rgb = readFromTexture(tColor, group, uColorTexDim).rgb;
     #elif defined(dColorType_groupInstance)
         vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
+    #elif defined(dColorType_vertex)
+        vColor.rgb = readFromTexture(tColor, aVertex, uColorTexDim).rgb;
+    #elif defined(dColorType_vertexInstance)
+        vColor.rgb = readFromTexture(tColor, aInstance * float(uVertexCount) + aVertex, uColorTexDim).rgb;
     #endif
 
     #ifdef dOverpaint

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

@@ -11,6 +11,14 @@ export default `
         uniform sampler2D tColor;
     #endif
 
+    #if defined(dColorType_vertex) || defined(dColorType_vertexInstance)
+        #if __VERSION__ != 300
+            attribute float aVertex;
+        #else
+            #define aVertex float(gl_VertexID)
+        #endif
+    #endif
+
     #ifdef dOverpaint
         varying vec4 vOverpaint;
         uniform vec2 uOverpaintTexDim;

+ 1 - 0
src/mol-gl/shader/chunks/common-vert-params.glsl.ts

@@ -3,6 +3,7 @@ uniform mat4 uProjection, uModel, uView;
 uniform vec3 uCameraPosition;
 
 uniform int uObjectId;
+uniform int uVertexCount;
 uniform int uInstanceCount;
 uniform int uGroupCount;
 uniform vec4 uInvariantBoundingSphere;

+ 1 - 1
src/mol-gl/shader/chunks/common.glsl.ts

@@ -5,7 +5,7 @@ export default `
     #define dRenderVariant_pick
 #endif
 
-#if defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
+#if defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance) || defined(dColorType_vertex) || defined(dColorType_vertexInstance)
     #define dColorType_texture
 #endif
 

+ 2 - 0
src/mol-gl/webgl/program.ts

@@ -61,6 +61,8 @@ function checkActiveAttributes(gl: GLRenderingContext, program: WebGLProgram, sc
                 // name assigned by `gl.shim.ts`, ignore for checks
                 continue;
             }
+            if (name === 'gl_InstanceID') continue; // WebGL2 built-in
+            if (name === 'gl_VertexID') continue; // WebGL2 built-in
             const spec = schema[name];
             if (spec === undefined) {
                 throw new Error(`missing 'uniform' or 'texture' with name '${name}' in schema`);

+ 15 - 0
src/mol-gl/webgl/render-item.ts

@@ -16,6 +16,7 @@ import { TextureImage, TextureVolume } from '../../mol-gl/renderable/util';
 import { checkFramebufferStatus } from './framebuffer';
 import { isDebugMode } from '../../mol-util/debug';
 import { VertexArray } from './vertex-array';
+import { fillSerial } from '../../mol-util/array';
 
 const getNextRenderItemId = idFactory();
 
@@ -108,6 +109,13 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
     const { stats, state, resources } = ctx;
     const { instancedArrays, vertexArrayObject } = ctx.extensions;
 
+    // emulate gl_VertexID when needed
+    if (!ctx.isWebGL2 && values.uVertexCount) {
+        const vertexCount = values.uVertexCount.ref.value;
+        (values as any).aVertex = ValueCell.create(fillSerial(new Float32Array(vertexCount)));
+        (schema as any).aVertex = AttributeSpec('float32', 1, 0);
+    }
+
     const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues } = splitValues(schema, values);
 
     const uniformValueEntries = Object.entries(uniformValues);
@@ -202,6 +210,13 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
         update: () => {
             resetValueChanges(valueChanges);
 
+            if (values.aVertex) {
+                const vertexCount = values.uVertexCount.ref.value;
+                if (values.aVertex.ref.value.length < vertexCount) {
+                    ValueCell.update(values.aVertex, fillSerial(new Float32Array(vertexCount)));
+                }
+            }
+
             for (let i = 0, il = defineValueEntries.length; i < il; ++i) {
                 const [k, value] = defineValueEntries[i];
                 if (value.ref.version !== versions[k]) {

+ 1 - 1
src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts

@@ -174,5 +174,5 @@ function createInteractionsIterator(structure: Structure): LocationIterator {
         element.indexB = c.indexB;
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation, true);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
 }

+ 1 - 1
src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts

@@ -178,5 +178,5 @@ function createInteractionsIterator(structureGroup: StructureGroup): LocationIte
         element.indexB = contacts.b[groupIndex];
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation);
 }

+ 1 - 1
src/mol-model-props/integrative/cross-link-restraint/representation.ts

@@ -84,7 +84,7 @@ function createCrossLinkRestraintIterator(structure: Structure): LocationIterato
         location.element = groupIndex;
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation, true);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
 }
 
 function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {

+ 3 - 2
src/mol-model/location.ts

@@ -7,9 +7,10 @@
 import { StructureElement } from './structure';
 import { Bond } from './structure/structure/unit/bonds';
 import { ShapeGroup } from './shape/shape';
+import { PositionLocation } from '../mol-geo/util/location-iterator';
 
 /** A null value Location */
-export const NullLocation = { kind: 'null-location' as 'null-location' };
+export const NullLocation = { kind: 'null-location' as const };
 export type NullLocation = typeof NullLocation
 export function isNullLocation(x: any): x is NullLocation {
     return !!x && x.kind === 'null-location';
@@ -29,4 +30,4 @@ export function isDataLocation(x: any): x is DataLocation {
     return !!x && x.kind === 'data-location';
 }
 
-export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | DataLocation | NullLocation
+export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | PositionLocation | DataLocation | NullLocation

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

@@ -71,7 +71,7 @@ export namespace Shape {
             location.instance = instanceIndex;
             return location;
         };
-        return LocationIterator(shape.groupCount, instanceCount, getLocation);
+        return LocationIterator(shape.groupCount, instanceCount, 1, getLocation);
     }
 
     export function createTransform(transforms: Mat4[], transformData?: TransformData) {

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

@@ -8,7 +8,7 @@ import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
 import { Representation } from '../representation';
 import { Shape, ShapeGroup } from '../../mol-model/shape';
 import { Subject } from 'rxjs';
-import { getNextMaterialId, createRenderObject, RenderObjectValues, GraphicsRenderObject } from '../../mol-gl/render-object';
+import { getNextMaterialId, createRenderObject, GraphicsRenderObject } from '../../mol-gl/render-object';
 import { Theme } from '../../mol-theme/theme';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { VisualUpdateState } from '../util';
@@ -47,6 +47,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
     let currentProps: PD.Values<P> = PD.getDefaultValues(geometryUtils.Params as P); // TODO avoid casting
     let currentParams: P;
     let locationIt: LocationIterator;
+    let positionIt: LocationIterator;
 
     if (builder.modifyState) Representation.updateState(_state, builder.modifyState(_state));
 
@@ -111,6 +112,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
 
                 _renderObject = createRenderObject(_shape.geometry.kind, values, state, materialId);
                 if (_renderObject) renderObjects.push(_renderObject); // add new renderObject to list
+                positionIt = geometryUtils.createPositionIterator(_shape.geometry, _renderObject.values);
             } else {
                 if (!_renderObject) {
                     throw new Error('expected renderObject to be available');
@@ -127,16 +129,18 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
                 if (updateState.createGeometry) {
                     // console.log('update geometry')
                     ValueCell.updateIfChanged(_renderObject.values.drawCount, Geometry.getDrawCount(_shape.geometry));
+                    ValueCell.updateIfChanged(_renderObject.values.uVertexCount, Geometry.getVertexCount(_shape.geometry));
                 }
 
                 if (updateState.updateTransform || updateState.createGeometry) {
                     // console.log('updateBoundingSphere')
-                    geometryUtils.updateBoundingSphere(_renderObject.values as RenderObjectValues<G['kind']>, _shape.geometry);
+                    geometryUtils.updateBoundingSphere(_renderObject.values, _shape.geometry);
+                    positionIt = geometryUtils.createPositionIterator(_shape.geometry, _renderObject.values);
                 }
 
                 if (updateState.updateColor) {
                     // console.log('update color')
-                    createColors(locationIt, _theme.color, _renderObject.values);
+                    createColors(locationIt, positionIt, _theme.color, _renderObject.values);
                 }
 
                 if (updateState.updateSize) {
@@ -147,7 +151,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
                     }
                 }
 
-                geometryUtils.updateValues(_renderObject.values as RenderObjectValues<G['kind']>, newProps);
+                geometryUtils.updateValues(_renderObject.values, newProps);
                 geometryUtils.updateRenderableState(_renderObject.state, newProps);
             }
 

+ 12 - 5
src/mol-repr/structure/complex-visual.ts

@@ -11,7 +11,7 @@ import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { Theme } from '../../mol-theme/theme';
 import { createIdentityTransform } from '../../mol-geo/geometry/transform-data';
-import { createRenderObject, RenderObjectValues, GraphicsRenderObject } from '../../mol-gl/render-object';
+import { createRenderObject, GraphicsRenderObject } from '../../mol-gl/render-object';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
 import { Interval } from '../../mol-data/int';
@@ -58,7 +58,7 @@ interface ComplexVisualGeometryBuilder<P extends StructureParams, G extends Geom
 
 export function ComplexVisual<G extends Geometry, P extends StructureParams & Geometry.Params<G>>(builder: ComplexVisualGeometryBuilder<P, G>, materialId: number): ComplexVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState } = builder;
-    const { updateValues, updateBoundingSphere, updateRenderableState } = builder.geometryUtils;
+    const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
     let renderObject: GraphicsRenderObject<G['kind']> | undefined;
@@ -73,6 +73,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
 
     let geometry: G;
     let locationIt: LocationIterator;
+    let positionIt: LocationIterator;
 
     function prepareUpdate(theme: Theme, props: Partial<PD.Values<P>>, structure: Structure) {
         if (!structure && !currentStructure) {
@@ -121,6 +122,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             locationIt = createLocationIterator(newStructure);
             if (newGeometry) {
                 renderObject = createComplexRenderObject(newStructure, newGeometry, locationIt, newTheme, newProps, materialId);
+                positionIt = createPositionIterator(newGeometry, renderObject.values);
             } else {
                 throw new Error('expected geometry to be given');
             }
@@ -139,12 +141,17 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             if (updateState.createGeometry) {
                 if (newGeometry) {
                     ValueCell.updateIfChanged(renderObject.values.drawCount, Geometry.getDrawCount(newGeometry));
-                    updateBoundingSphere(renderObject.values as RenderObjectValues<G['kind']>, newGeometry);
+                    ValueCell.updateIfChanged(renderObject.values.uVertexCount, Geometry.getVertexCount(newGeometry));
                 } else {
                     throw new Error('expected geometry to be given');
                 }
             }
 
+            if (updateState.updateTransform || updateState.createGeometry) {
+                updateBoundingSphere(renderObject.values, newGeometry || geometry);
+                positionIt = createPositionIterator(geometry, renderObject.values);
+            }
+
             if (updateState.updateSize) {
                 // not all geometries have size data, so check here
                 if ('uSize' in renderObject.values) {
@@ -153,10 +160,10 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             }
 
             if (updateState.updateColor) {
-                createColors(locationIt, newTheme.color, renderObject.values);
+                createColors(locationIt, positionIt, newTheme.color, renderObject.values);
             }
 
-            updateValues(renderObject.values as RenderObjectValues<G['kind']>, newProps);
+            updateValues(renderObject.values, newProps);
             updateRenderableState(renderObject.state, newProps);
         }
 

+ 12 - 6
src/mol-repr/structure/units-visual.ts

@@ -12,7 +12,7 @@ import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { Theme } from '../../mol-theme/theme';
 import { createUnitsTransform, includesUnitKind } from './visual/util/common';
-import { createRenderObject, RenderObjectValues, GraphicsRenderObject } from '../../mol-gl/render-object';
+import { createRenderObject, GraphicsRenderObject } from '../../mol-gl/render-object';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
 import { Interval } from '../../mol-data/int';
@@ -65,7 +65,7 @@ interface UnitsVisualGeometryBuilder<P extends StructureParams, G extends Geomet
 
 export function UnitsVisual<G extends Geometry, P extends StructureParams & Geometry.Params<G>>(builder: UnitsVisualGeometryBuilder<P, G>, materialId: number): UnitsVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState } = builder;
-    const { createEmpty: createEmptyGeometry, updateValues, updateBoundingSphere, updateRenderableState } = builder.geometryUtils;
+    const { createEmpty: createEmptyGeometry, updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
     let renderObject: GraphicsRenderObject<G['kind']> | undefined;
@@ -80,6 +80,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
 
     let geometry: G;
     let locationIt: LocationIterator;
+    let positionIt: LocationIterator;
 
     function prepareUpdate(theme: Theme, props: PD.Values<P>, structureGroup: StructureGroup) {
         if (!structureGroup && !currentStructureGroup) {
@@ -153,6 +154,9 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
                 updateState.updateColor = true;
                 updateState.updateSize = true;
             }
+            if (newTheme.color.granularity.startsWith('vertex')) {
+                updateState.updateColor = true;
+            }
         }
     }
 
@@ -161,6 +165,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
             locationIt = createLocationIterator(newStructureGroup);
             if (newGeometry) {
                 renderObject = createUnitsRenderObject(newStructureGroup.group, newGeometry, locationIt, newTheme, newProps, materialId);
+                positionIt = createPositionIterator(newGeometry, renderObject.values);
             } else {
                 throw new Error('expected geometry to be given');
             }
@@ -185,14 +190,15 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
                 // console.log('update geometry');
                 if (newGeometry) {
                     ValueCell.updateIfChanged(renderObject.values.drawCount, Geometry.getDrawCount(newGeometry));
+                    ValueCell.updateIfChanged(renderObject.values.uVertexCount, Geometry.getVertexCount(newGeometry));
                 } else {
                     throw new Error('expected geometry to be given');
                 }
             }
 
             if (updateState.updateTransform || updateState.createGeometry) {
-                // console.log('UnitsVisual.updateBoundingSphere');
-                updateBoundingSphere(renderObject.values as RenderObjectValues<G['kind']>, newGeometry || geometry);
+                updateBoundingSphere(renderObject.values, newGeometry || geometry);
+                positionIt = createPositionIterator(newGeometry || geometry, renderObject.values);
             }
 
             if (updateState.updateSize) {
@@ -205,10 +211,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
 
             if (updateState.updateColor) {
                 // console.log('update color');
-                createColors(locationIt, newTheme.color, renderObject.values);
+                createColors(locationIt, positionIt, newTheme.color, renderObject.values);
             }
 
-            updateValues(renderObject.values as RenderObjectValues<G['kind']>, newProps);
+            updateValues(renderObject.values, newProps);
             updateRenderableState(renderObject.state, newProps);
         }
 

+ 1 - 1
src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts

@@ -83,7 +83,7 @@ function CarbohydrateLinkIterator(structure: Structure): LocationIterator {
         location.element = carbA.unit.elements[ringA[0]];
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation, true);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
 }
 
 function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {

+ 1 - 1
src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts

@@ -199,7 +199,7 @@ function CarbohydrateElementIterator(structure: Structure): LocationIterator {
     function isSecondary (elementIndex: number, instanceIndex: number) {
         return (elementIndex % 2) === 1;
     }
-    return LocationIterator(groupCount, instanceCount, getLocation, true, isSecondary);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation, true, isSecondary);
 }
 
 /** Return a Loci for the elements of the whole residue of a carbohydrate. */

+ 1 - 1
src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts

@@ -105,7 +105,7 @@ function CarbohydrateTerminalLinkIterator(structure: Structure): LocationIterato
         }
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation, true);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
 }
 
 function getTerminalLinkLoci(pickingId: PickingId, structure: Structure, id: number) {

+ 1 - 1
src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts

@@ -104,7 +104,7 @@ function UnitIterator(structureGroup: StructureGroup): LocationIterator {
         location.element = unit.elements[groupIndex];
         return location;
     };
-    return LocationIterator(groupCount, instanceCount, getLocation);
+    return LocationIterator(groupCount, instanceCount, 1, getLocation);
 }
 
 function getUnitLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {

+ 2 - 2
src/mol-repr/structure/visual/util/bond.ts

@@ -105,7 +105,7 @@ export namespace BondIterator {
             location.element = unit.elements[(unit as Unit.Atomic).bonds.a[groupIndex]];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 
     export function fromStructure(structure: Structure): LocationIterator {
@@ -118,7 +118,7 @@ export namespace BondIterator {
             location.element = location.unit.elements[bond.indexA];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation, true);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
     }
 }
 

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

@@ -204,7 +204,7 @@ export namespace ElementIterator {
             location.element = unit.elements[groupIndex];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 
     export function fromStructure(structure: Structure): LocationIterator {
@@ -218,6 +218,6 @@ export namespace ElementIterator {
             location.element = elementIndices[groupIndex];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation, true);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
     }
 }

+ 1 - 1
src/mol-repr/structure/visual/util/nucleotide.ts

@@ -27,7 +27,7 @@ export namespace NucleotideLocationIterator {
             location.element = nucleotideElementIndices[groupIndex];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 }
 

+ 2 - 2
src/mol-repr/structure/visual/util/polymer.ts

@@ -54,7 +54,7 @@ export namespace PolymerLocationIterator {
             location.element = polymerElements[groupIndex];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 }
 
@@ -71,7 +71,7 @@ export namespace PolymerGapLocationIterator {
             location.element = gapElements[groupIndex];
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, getLocation);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 }
 

+ 1 - 1
src/mol-repr/volume/direct-volume.ts

@@ -225,7 +225,7 @@ export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolum
     return VolumeVisual<DirectVolume, DirectVolumeParams>({
         defaultProps: PD.getDefaultValues(DirectVolumeParams),
         createGeometry: createDirectVolume,
-        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
         getLoci: getDirectVolumeLoci,
         eachLocation: eachDirectVolume,
         setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {

+ 2 - 2
src/mol-repr/volume/isosurface.ts

@@ -79,7 +79,7 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
     return VolumeVisual<Mesh, IsosurfaceMeshParams>({
         defaultProps: PD.getDefaultValues(IsosurfaceMeshParams),
         createGeometry: createVolumeIsosurfaceMesh,
-        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
         getLoci: getIsosurfaceLoci,
         eachLocation: eachIsosurface,
         setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
@@ -122,7 +122,7 @@ export function IsosurfaceWireframeVisual(materialId: number): VolumeVisual<Isos
     return VolumeVisual<Lines, IsosurfaceWireframeParams>({
         defaultProps: PD.getDefaultValues(IsosurfaceWireframeParams),
         createGeometry: createVolumeIsosurfaceWireframe,
-        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
         getLoci: getIsosurfaceLoci,
         eachLocation: eachIsosurface,
         setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {

+ 11 - 4
src/mol-repr/volume/representation.ts

@@ -11,7 +11,7 @@ import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { Theme } from '../../mol-theme/theme';
 import { createIdentityTransform } from '../../mol-geo/geometry/transform-data';
-import { createRenderObject, RenderObjectValues, getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
+import { createRenderObject, getNextMaterialId, GraphicsRenderObject } from '../../mol-gl/render-object';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
 import { Interval } from '../../mol-data/int';
@@ -56,7 +56,7 @@ interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry
 
 export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geometry.Params<G>>(builder: VolumeVisualGeometryBuilder<P, G>, materialId: number): VolumeVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState } = builder;
-    const { updateValues, updateBoundingSphere, updateRenderableState } = builder.geometryUtils;
+    const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
     let renderObject: GraphicsRenderObject<G['kind']> | undefined;
@@ -71,6 +71,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
 
     let geometry: G;
     let locationIt: LocationIterator;
+    let positionIt: LocationIterator;
 
     function prepareUpdate(theme: Theme, props: Partial<PD.Values<P>>, volume: Volume) {
         if (!volume && !currentVolume) {
@@ -108,6 +109,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
             locationIt = createLocationIterator(newVolume);
             if (newGeometry) {
                 renderObject = createVolumeRenderObject(newVolume, newGeometry, locationIt, newTheme, newProps, materialId);
+                positionIt = createPositionIterator(newGeometry, renderObject.values);
             } else {
                 throw new Error('expected geometry to be given');
             }
@@ -121,12 +123,17 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
             if (updateState.createGeometry) {
                 if (newGeometry) {
                     ValueCell.updateIfChanged(renderObject.values.drawCount, Geometry.getDrawCount(newGeometry));
-                    updateBoundingSphere(renderObject.values as RenderObjectValues<G['kind']>, newGeometry);
+                    ValueCell.updateIfChanged(renderObject.values.uVertexCount, Geometry.getVertexCount(newGeometry));
                 } else {
                     throw new Error('expected geometry to be given');
                 }
             }
 
+            if (updateState.updateTransform || updateState.createGeometry) {
+                updateBoundingSphere(renderObject.values, newGeometry || geometry);
+                positionIt = createPositionIterator(newGeometry || geometry, renderObject.values);
+            }
+
             if (updateState.updateSize) {
                 // not all geometries have size data, so check here
                 if ('uSize' in renderObject.values) {
@@ -135,7 +142,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
             }
 
             if (updateState.updateColor) {
-                createColors(locationIt, newTheme.color, renderObject.values);
+                createColors(locationIt, positionIt, newTheme.color, renderObject.values);
             }
 
             updateValues(renderObject.values, newProps);

+ 1 - 1
src/mol-repr/volume/slice.ts

@@ -195,7 +195,7 @@ export function SliceVisual(materialId: number): VolumeVisual<SliceParams> {
     return VolumeVisual<Image, SliceParams>({
         defaultProps: PD.getDefaultValues(SliceParams),
         createGeometry: createImage,
-        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, 1, () => NullLocation),
         getLoci: getSliceLoci,
         eachLocation: eachSlice,
         setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>, newTheme: Theme, currentTheme: Theme) => {