Browse Source

volumetric color smoothing

- for Mesh and TextureMesh geometries
Alexander Rose 3 years ago
parent
commit
10ca32f9d7

+ 153 - 0
src/mol-geo/geometry/mesh/color-smoothing.ts

@@ -0,0 +1,153 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { TextureImage } from '../../../mol-gl/renderable/util';
+import { WebGLContext } from '../../../mol-gl/webgl/context';
+import { Box3D, Sphere3D } from '../../../mol-math/geometry';
+import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
+import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
+
+interface ColorSmoothingInput {
+    vertexCount: number
+    instanceCount: number
+    groupCount: number
+    transformBuffer: Float32Array
+    instanceBuffer: Float32Array
+    positionBuffer: Float32Array
+    groupBuffer: Float32Array
+    colorData: TextureImage<Uint8Array>
+    colorType: 'group' | 'groupInstance' | 'vertex' | 'vertexInstance'
+    boundingSphere: Sphere3D
+    invariantBoundingSphere: Sphere3D
+}
+
+export function calcMeshColorSmoothing(webgl: WebGLContext, input: ColorSmoothingInput, resolution: number, stride: number) {
+    if (!input.colorType.startsWith('group') && !input.colorType.startsWith('vertex')) {
+        throw new Error('unsupported color type');
+    };
+
+    const isInstanceType = input.colorType.endsWith('Instance');
+    const isVertexColor = input.colorType.startsWith('vertex');
+    const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
+
+    const scaleFactor = 1 / resolution;
+    const scaledBox = Box3D.scale(Box3D(), box, scaleFactor);
+    const dim = Box3D.size(Vec3(), scaledBox);
+    Vec3.ceil(dim, dim);
+    Vec3.add(dim, dim, Vec3.create(2, 2, 2));
+    const { min } = box;
+
+    const [ xn, yn ] = dim;
+    const { width, height } = getVolumeTexture2dLayout(dim);
+    // console.log({ width, height, dim });
+
+    const itemSize = 3;
+    const data = new Float32Array(width * height * itemSize);
+    const count = new Float32Array(width * height);
+
+    const array = new Uint8Array(width * height * itemSize);
+    const textureImage: TextureImage<Uint8Array> = { array, width, height, filter: 'linear' };
+
+    const vertexCount = input.vertexCount;
+    const groupCount = input.groupCount;
+    const instanceCount = isInstanceType ? input.instanceCount : 1;
+    const positions = input.positionBuffer;
+    const colors = input.colorData.array;
+    const groups = input.groupBuffer;
+    const transforms = input.transformBuffer;
+
+    function getIndex(x: number, y: number, z: number) {
+        const column = Math.floor(((z * xn) % width) / xn);
+        const row = Math.floor((z * xn) / width);
+        const px = column * xn + x;
+        return itemSize * ((row * yn * width) + (y * width) + px);
+    }
+
+    const p = 2;
+    const [dimX, dimY, dimZ] = dim;
+    const v = Vec3();
+
+    for (let i = 0; i < instanceCount; ++i) {
+        for (let j = 0; j < vertexCount; j += stride) {
+            Vec3.fromArray(v, positions, j * 3);
+            if (isInstanceType) Vec3.transformMat4Offset(v, v, transforms, 0, 0, i * 16);
+            Vec3.sub(v, v, min);
+            Vec3.scale(v, v, scaleFactor);
+            const [vx, vy, vz] = v;
+
+            // vertex mapped to grid
+            const x = Math.floor(vx);
+            const y = Math.floor(vy);
+            const z = Math.floor(vz);
+
+            // group colors
+            const ci = isVertexColor
+                ? i * vertexCount + j
+                : i * groupCount + groups[j];
+            const r = colors[ci * 3];
+            const g = colors[ci * 3 + 1];
+            const b = colors[ci * 3 + 2];
+
+            // Extents of grid to consider for this atom
+            const begX = Math.max(0, x - p);
+            const begY = Math.max(0, y - p);
+            const begZ = Math.max(0, z - p);
+
+            // Add two to these points:
+            // - x, y, z are floor'd values so this ensures coverage
+            // - these are loop limits (exclusive)
+            const endX = Math.min(dimX, x + p + 2);
+            const endY = Math.min(dimY, y + p + 2);
+            const endZ = Math.min(dimZ, z + p + 2);
+
+            for (let xi = begX; xi < endX; ++xi) {
+                const dx = xi - vx;
+                for (let yi = begY; yi < endY; ++yi) {
+                    const dy = yi - vy;
+                    for (let zi = begZ; zi < endZ; ++zi) {
+                        const dz = zi - vz;
+                        const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
+                        if (d > p) continue;
+
+                        // const s = Math.pow(p, 10) - Math.pow(d, 10);
+                        let s = p - d;
+                        // if (d < 0.5) s *= 50;
+                        const index = getIndex(xi, yi, zi);
+                        data[index] += r * s;
+                        data[index + 1] += g * s;
+                        data[index + 2] += b * s;
+                        count[index / 3] += s;
+                    }
+                }
+            }
+        }
+    }
+
+    for (let i = 0, il = count.length; i < il; ++i) {
+        const i3 = i * 3;
+        const c = count[i];
+        array[i3] = Math.round(data[i3] / c);
+        array[i3 + 1] = Math.round(data[i3 + 1] / c);
+        array[i3 + 2] = Math.round(data[i3 + 2] / c);
+    }
+
+    const texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+    texture.load(textureImage);
+
+    // ValueCell.updateIfChanged(values.dColorType, isInstanceType ? 'volumeInstance' : 'volume');
+    // ValueCell.update(values.tColorGrid, texture);
+    // ValueCell.update(values.uColorTexDim, Vec2.create(width, height));
+    // ValueCell.update(values.uColorGridDim, dim);
+    // ValueCell.update(values.uColorGridTransform, Vec4.create(min[0], min[1], min[2], scaleFactor));
+    // ValueCell.updateIfChanged(values.dColorGridType, '2d');
+
+    // return values;
+
+    const transform = Vec4.create(min[0], min[1], min[2], scaleFactor);
+    const type = isInstanceType ? 'volumeInstance' : 'volume';
+
+    return { texture, gridDim: dim, gridTexDim: Vec2.create(width, height), transform, type };
+}

+ 6 - 200
src/mol-geo/geometry/mesh/mesh.ts

@@ -6,8 +6,8 @@
  */
 
 import { ValueCell } from '../../../mol-util';
-import { Vec3, Mat4, Mat3, Vec4, Vec2 } from '../../../mol-math/linear-algebra';
-import { Box3D, Sphere3D } from '../../../mol-math/geometry';
+import { Vec3, Mat4, Mat3, Vec4 } from '../../../mol-math/linear-algebra';
+import { Sphere3D } from '../../../mol-math/geometry';
 import { transformPositionArray, transformDirectionArray, computeIndexedVertexNormals, GroupMapping, createGroupMapping} from '../../util';
 import { GeometryUtils } from '../geometry';
 import { createMarkers } from '../marker-data';
@@ -16,7 +16,7 @@ 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';
-import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere, createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
+import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Theme } from '../../../mol-theme/theme';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { Color } from '../../../mol-util/color';
@@ -25,8 +25,6 @@ import { createEmptyOverpaint } from '../overpaint-data';
 import { createEmptyTransparency } from '../transparency-data';
 import { createEmptyClipping } from '../clipping-data';
 import { RenderableState } from '../../../mol-gl/renderable';
-import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
-import { arraySetAdd } from '../../../mol-util/array';
 
 export interface Mesh {
     readonly kind: 'mesh',
@@ -51,6 +49,8 @@ export interface Mesh {
     readonly groupMapping: GroupMapping
 
     setBoundingSphere(boundingSphere: Sphere3D): void
+
+    meta?: unknown
 }
 
 export namespace Mesh {
@@ -336,7 +336,6 @@ export namespace Mesh {
         flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        smoothColors: PD.Select('off', PD.arrayToOptions(['off', 'vertex', 'volume'] as const), { isHidden: true }),
     };
     export type Params = typeof Params
 
@@ -388,7 +387,7 @@ export namespace Mesh {
         const invariantBoundingSphere = Sphere3D.clone(mesh.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
-        const values = {
+        return {
             aPosition: mesh.vertexBuffer,
             aNormal: mesh.normalBuffer,
             aGroup: mesh.groupBuffer,
@@ -410,197 +409,6 @@ export namespace Mesh {
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
         };
-
-        return processValues(values, props);
-    }
-
-    function processValues(values: MeshValues, props: PD.Values<Params>) {
-        if (props.smoothColors === 'vertex') {
-            values = vertexColors(values, props);
-        } else if (props.smoothColors === 'volume') {
-            values = gridColors(values, props);
-        }
-        return values;
-    }
-
-    function gridColors(values: MeshValues, props: PD.Values<Params>): MeshValues {
-        const type = values.dColorType.ref.value;
-        if (type === 'uniform' || type.startsWith('vertex')) return values;
-        // TODO handle instance and vertex types
-
-        const box = Box3D.fromSphere3D(Box3D(), values.invariantBoundingSphere.ref.value);
-
-        const resolution = (props as any).resolution ?? 0.5; // TODO better default
-        const scaleFactor = 1 / resolution;
-        const scaledBox = Box3D.scale(Box3D(), box, scaleFactor);
-        const dim = Box3D.size(Vec3(), scaledBox);
-        Vec3.ceil(dim, dim);
-        const { min } = box;
-
-        const [ xn, yn ] = dim;
-        const { width, height } = getVolumeTexture2dLayout(dim);
-
-        const itemSize = 3;
-        const data = new Float32Array(width * height * itemSize);
-        const count = new Float32Array(width * height);
-
-        const array = new Uint8Array(width * height * itemSize);
-        const textureImage = { array, width, height };
-
-        const vertexCount = values.uVertexCount.ref.value;
-        const positions = values.aPosition.ref.value;
-        const groupColors = values.tColor.ref.value.array;
-        const groups = values.aGroup.ref.value;
-
-        function getIndex(x: number, y: number, z: number) {
-            const column = Math.floor(((z * xn) % width) / xn);
-            const row = Math.floor((z * xn) / width);
-            const px = column * xn + x;
-            return itemSize * ((row * yn * width) + (y * width) + px);
-        }
-
-        const p = 2;
-        const f = Math.sqrt(2) * 2 * p * resolution;
-
-        for (let i = 0; i < vertexCount; ++i) {
-            // vertex mapped to grid
-            const x = Math.floor(scaleFactor * (positions[i * 3] - min[0]));
-            const y = Math.floor(scaleFactor * (positions[i * 3 + 1] - min[1]));
-            const z = Math.floor(scaleFactor * (positions[i * 3 + 2] - min[2]));
-
-            for (let xi = x - p, xl = x + p; xi < xl; ++ xi) {
-                for (let yi = y - p, yl = y + p; yi < yl; ++ yi) {
-                    for (let zi = z - p, zl = z + p; zi < zl; ++ zi) {
-                        const index = getIndex(xi, yi, zi);
-
-                        const dx = min[0] + (resolution * xi) - positions[i * 3];
-                        const dy = min[1] + (resolution * yi) - positions[i * 3 + 1];
-                        const dz = min[2] + (resolution * zi) - positions[i * 3 + 2];
-                        const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
-
-                        const s = 2 - (d / f);
-
-                        const g = groups[i];
-                        data[index] += groupColors[g * 3] * s;
-                        data[index + 1] += groupColors[g * 3 + 1] * s;
-                        data[index + 2] += groupColors[g * 3 + 2] * s;
-                        count[index / 3] += 1 * s;
-                    }
-                }
-            }
-        }
-
-        for (let i = 0, il = count.length; i < il; ++i) {
-            const i3 = i * 3;
-            const c = count[i];
-            array[i3] = Math.floor(data[i3] / c);
-            array[i3 + 1] = Math.floor(data[i3 + 1] / c);
-            array[i3 + 2] = Math.floor(data[i3 + 2] / c);
-        }
-
-        ValueCell.updateIfChanged(values.dColorType, 'volume');
-        ValueCell.update(values.tColor, textureImage);
-        ValueCell.update(values.uColorTexDim, Vec2.create(width, height));
-        ValueCell.update(values.uColorGridDim, dim);
-        ValueCell.update(values.uColorGridTransform, Vec4.create(min[0], min[1], min[2], scaleFactor));
-
-        return values;
-    }
-
-    function getNeighboursMap(values: MeshValues) {
-        const vertexCount = values.uVertexCount.ref.value;
-        const elements = values.elements.ref.value;
-
-        const neighboursMap: number[][] = [];
-        for (let i = 0; i < vertexCount; ++i) {
-            neighboursMap[i] = [];
-        }
-
-        for (let i = 0; i < values.drawCount.ref.value / 3; ++i) {
-            const v1 = elements[i * 3];
-            const v2 = elements[i * 3 + 1];
-            const v3 = elements[i * 3 + 2];
-            arraySetAdd(neighboursMap[v1], v1);
-            arraySetAdd(neighboursMap[v1], v2);
-            arraySetAdd(neighboursMap[v1], v3);
-            arraySetAdd(neighboursMap[v2], v1);
-            arraySetAdd(neighboursMap[v2], v2);
-            arraySetAdd(neighboursMap[v2], v3);
-            arraySetAdd(neighboursMap[v3], v1);
-            arraySetAdd(neighboursMap[v3], v2);
-            arraySetAdd(neighboursMap[v3], v3);
-        }
-        return neighboursMap;
-    }
-
-    function vertexColors(values: MeshValues, props: PD.Values<Params>): MeshValues {
-        const type = values.dColorType.ref.value;
-        if (type === 'uniform' || type.startsWith('volume')) return values;
-
-        const vertexCount = values.uVertexCount.ref.value;
-        const groupCount = values.uGroupCount.ref.value;
-        const instanceCount = type.endsWith('Instance') ? values.uInstanceCount.ref.value : 1;
-        const iterations = Math.round((vertexCount / groupCount) / 20); // TODO better formula?
-        const neighboursMap = getNeighboursMap(values);
-
-        let vertexColors: Uint8Array;
-        let colors: TextureImage<Uint8Array>;
-
-        if (type.startsWith('vertex')) {
-            vertexColors = values.tColor.ref.value.array;
-            colors = values.tColor.ref.value;
-        } else {
-            colors = createTextureImage(vertexCount * instanceCount, 3, Uint8Array);
-            vertexColors = colors.array;
-
-            const groupColors = values.tColor.ref.value.array;
-            const groups = values.aGroup.ref.value;
-
-            for (let i = 0; i < instanceCount; ++i) {
-                const og = i * groupCount * 3;
-                const ov = i * vertexCount * 3;
-                for (let j = 0; j < vertexCount; ++j) {
-                    const neighbours = neighboursMap[j];
-                    const nc = neighbours.length;
-                    let r = 0, g = 0, b = 0;
-                    for (let k = 0; k < nc; ++k) {
-                        const neighbourGroup = groups[neighbours[k]];
-                        r += groupColors[og + neighbourGroup * 3];
-                        g += groupColors[og + neighbourGroup * 3 + 1];
-                        b += groupColors[og + neighbourGroup * 3 + 2];
-                    }
-                    vertexColors[ov + j * 3] = Math.round(r / nc);
-                    vertexColors[ov + j * 3 + 1] = Math.round(g / nc);
-                    vertexColors[ov + j * 3 + 2] = Math.round(b / nc);
-                }
-            }
-        }
-
-        for (let x = 0; x < iterations; ++x) {
-            for (let i = 0; i < instanceCount; ++i) {
-                const ov = i * vertexCount * 3;
-                for (let j = 0; j < vertexCount; ++j) {
-                    const neighbours = neighboursMap[j];
-                    const nc = neighbours.length;
-                    let r = 0, g = 0, b = 0;
-                    for (let k = 0; k < nc; ++k) {
-                        const neighbour = neighbours[k];
-                        r += vertexColors[ov + neighbour * 3];
-                        g += vertexColors[ov + neighbour * 3 + 1];
-                        b += vertexColors[ov + neighbour * 3 + 2];
-                    }
-                    vertexColors[ov + j * 3] = Math.round(r / nc);
-                    vertexColors[ov + j * 3 + 1] = Math.round(g / nc);
-                    vertexColors[ov + j * 3 + 2] = Math.round(b / nc);
-                }
-            }
-        }
-
-        ValueCell.updateIfChanged(values.dColorType, type.endsWith('Instance') ? 'vertexInstance' : 'vertex');
-        ValueCell.update(values.tColor, colors);
-        ValueCell.update(values.uColorTexDim, Vec2.create(colors.width, colors.height));
-
-        return values;
     }
 
     function createValuesSimple(mesh: Mesh, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
@@ -616,8 +424,6 @@ export namespace Mesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-
-        processValues(values, props);
     }
 
     function updateBoundingSphere(values: MeshValues, mesh: Mesh) {

+ 345 - 0
src/mol-geo/geometry/texture-mesh/color-smoothing.ts

@@ -0,0 +1,345 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from '../../../mol-util';
+import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
+import { WebGLContext } from '../../../mol-gl/webgl/context';
+import { Texture } from '../../../mol-gl/webgl/texture';
+import { ShaderCode } from '../../../mol-gl/shader-code';
+import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
+import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, Values, DefineSpec } from '../../../mol-gl/renderable/schema';
+import { quad_vert } from '../../../mol-gl/shader/quad.vert';
+import { normalize_frag } from '../../../mol-gl/shader/compute/color-smoothing/normalize.frag';
+import { QuadSchema, QuadValues } from '../../../mol-gl/compute/util';
+import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
+import { Box3D, Sphere3D } from '../../../mol-math/geometry';
+import { accumulate_frag } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.frag';
+import { accumulate_vert } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.vert';
+import { TextureImage } from '../../../mol-gl/renderable/util';
+
+export const ColorAccumulateSchema = {
+    drawCount: ValueSpec('number'),
+    instanceCount: ValueSpec('number'),
+
+    uTotalCount: UniformSpec('i'),
+    uInstanceCount: UniformSpec('i'),
+    uGroupCount: UniformSpec('i'),
+
+    aTransform: AttributeSpec('float32', 16, 1),
+    aInstance: AttributeSpec('float32', 1, 1),
+    aSample: AttributeSpec('float32', 1, 0),
+
+    uGeoTexDim: UniformSpec('v2', 'buffered'),
+    tPosition: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+
+    uColorTexDim: UniformSpec('v2'),
+    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']),
+
+    uCurrentSlice: UniformSpec('f'),
+    uCurrentX: UniformSpec('f'),
+    uCurrentY: UniformSpec('f'),
+    uBboxMin: UniformSpec('v3', 'material'),
+    uBboxSize: UniformSpec('v3', 'material'),
+    uResolution: UniformSpec('f', 'material'),
+};
+type ColorAccumulateValues = Values<typeof ColorAccumulateSchema>
+const ColorAccumulateName = 'color-accumulate';
+
+interface AccumulateInput {
+    vertexCount: number
+    instanceCount: number
+    groupCount: number
+    transformBuffer: Float32Array
+    instanceBuffer: Float32Array
+    positionTexture: Texture
+    groupTexture: Texture
+    colorData: TextureImage<Uint8Array>
+    colorType: 'group' | 'groupInstance' | 'vertex' | 'vertexInstance'
+}
+
+function getSampleBuffer(sampleCount: number, stride: number) {
+    const sampleBuffer = new Float32Array(sampleCount);
+    for (let i = 0; i < sampleCount; ++i) {
+        sampleBuffer[i] = i * stride;
+    }
+    return sampleBuffer;
+}
+
+function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box: Box3D, resolution: number, stride: number): ComputeRenderable<ColorAccumulateValues> {
+    if (ctx.namedComputeRenderables[ColorAccumulateName]) {
+        const extent = Vec3.sub(Vec3(), box.max, box.min);
+        const v = ctx.namedComputeRenderables[ColorAccumulateName].values as ColorAccumulateValues;
+
+        const sampleCount = input.vertexCount / stride;
+        if (sampleCount > v.drawCount.ref.value) {
+            ValueCell.update(v.aSample, getSampleBuffer(sampleCount, stride));
+        }
+
+        ValueCell.updateIfChanged(v.drawCount, sampleCount);
+        ValueCell.updateIfChanged(v.instanceCount, input.instanceCount);
+
+        ValueCell.updateIfChanged(v.uTotalCount, input.vertexCount);
+        ValueCell.updateIfChanged(v.uInstanceCount, input.instanceCount);
+        ValueCell.updateIfChanged(v.uGroupCount, input.groupCount);
+
+        ValueCell.update(v.aTransform, input.transformBuffer);
+        ValueCell.update(v.aInstance, input.instanceBuffer);
+
+        ValueCell.update(v.uGeoTexDim, Vec2.set(v.uGeoTexDim.ref.value, input.positionTexture.getWidth(), input.positionTexture.getHeight()));
+        ValueCell.update(v.tPosition, input.positionTexture);
+        ValueCell.update(v.tGroup, input.groupTexture);
+
+        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.width, input.colorData.height));
+        ValueCell.update(v.tColor, input.colorData);
+        ValueCell.updateIfChanged(v.dColorType, input.colorType);
+
+        ValueCell.updateIfChanged(v.uCurrentSlice, 0);
+        ValueCell.updateIfChanged(v.uCurrentX, 0);
+        ValueCell.updateIfChanged(v.uCurrentY, 0);
+        ValueCell.update(v.uBboxMin, box.min);
+        ValueCell.update(v.uBboxSize, extent);
+        ValueCell.updateIfChanged(v.uResolution, resolution);
+
+        ctx.namedComputeRenderables[ColorAccumulateName].update();
+    } else {
+        ctx.namedComputeRenderables[ColorAccumulateName] = createAccumulateRenderable(ctx, input, box, resolution, stride);
+    }
+    return ctx.namedComputeRenderables[ColorAccumulateName];
+}
+
+function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box: Box3D, resolution: number, stride: number) {
+    const extent = Vec3.sub(Vec3(), box.max, box.min);
+    const sampleCount = input.vertexCount / stride;
+
+    const values: ColorAccumulateValues = {
+        drawCount: ValueCell.create(sampleCount),
+        instanceCount: ValueCell.create(input.instanceCount),
+
+        uTotalCount: ValueCell.create(input.vertexCount),
+        uInstanceCount: ValueCell.create(input.instanceCount),
+        uGroupCount: ValueCell.create(input.groupCount),
+
+        aTransform: ValueCell.create(input.transformBuffer),
+        aInstance: ValueCell.create(input.instanceBuffer),
+        aSample: ValueCell.create(getSampleBuffer(sampleCount, stride)),
+
+        uGeoTexDim: ValueCell.create(Vec2.create(input.positionTexture.getWidth(), input.positionTexture.getHeight())),
+        tPosition: ValueCell.create(input.positionTexture),
+        tGroup: ValueCell.create(input.groupTexture),
+
+        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.width, input.colorData.height)),
+        tColor: ValueCell.create(input.colorData),
+        dColorType: ValueCell.create(input.colorType),
+
+        uCurrentSlice: ValueCell.create(0),
+        uCurrentX: ValueCell.create(0),
+        uCurrentY: ValueCell.create(0),
+        uBboxMin: ValueCell.create(box.min),
+        uBboxSize: ValueCell.create(extent),
+        uResolution: ValueCell.create(resolution),
+    };
+
+    const schema = { ...ColorAccumulateSchema };
+    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag);
+    const renderItem = createComputeRenderItem(ctx, 'points', shaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
+function setAccumulateDefaults(ctx: WebGLContext) {
+    const { gl, state } = ctx;
+    state.disable(gl.CULL_FACE);
+    state.enable(gl.BLEND);
+    state.disable(gl.DEPTH_TEST);
+    state.enable(gl.SCISSOR_TEST);
+    state.depthMask(false);
+    state.clearColor(0, 0, 0, 0);
+    state.blendFunc(gl.ONE, gl.ONE);
+    state.blendEquation(gl.FUNC_ADD);
+}
+
+//
+
+export const ColorNormalizeSchema = {
+    ...QuadSchema,
+
+    tColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    uTexSize: UniformSpec('v2'),
+
+};
+type ColorNormalizeValues = Values<typeof ColorNormalizeSchema>
+const ColorNormalizeName = 'color-normalize';
+
+function getNormalizeRenderable(ctx: WebGLContext, color: Texture): ComputeRenderable<ColorNormalizeValues> {
+    if (ctx.namedComputeRenderables[ColorNormalizeName]) {
+        const v = ctx.namedComputeRenderables[ColorNormalizeName].values as ColorNormalizeValues;
+
+        ValueCell.update(v.tColor, color);
+        ValueCell.update(v.uTexSize, Vec2.set(v.uTexSize.ref.value, color.getWidth(), color.getHeight()));
+
+        ctx.namedComputeRenderables[ColorNormalizeName].update();
+    } else {
+        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color);
+    }
+    return ctx.namedComputeRenderables[ColorNormalizeName];
+}
+
+function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture) {
+    const values: ColorNormalizeValues = {
+        ...QuadValues,
+        tColor: ValueCell.create(color),
+        uTexSize: ValueCell.create(Vec2.create(color.getWidth(), color.getHeight())),
+    };
+
+    const schema = { ...ColorNormalizeSchema };
+    const shaderCode = ShaderCode('normalize', quad_vert, normalize_frag);
+    const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
+function setNormalizeDefaults(ctx: WebGLContext) {
+    const { gl, state } = ctx;
+    state.disable(gl.CULL_FACE);
+    state.enable(gl.BLEND);
+    state.disable(gl.DEPTH_TEST);
+    state.enable(gl.SCISSOR_TEST);
+    state.depthMask(false);
+    state.clearColor(0, 0, 0, 0);
+    state.blendFunc(gl.ONE, gl.ONE);
+    state.blendEquation(gl.FUNC_ADD);
+}
+
+//
+
+function getTexture2dSize(gridDim: Vec3) {
+    const area = gridDim[0] * gridDim[1] * gridDim[2];
+    const squareDim = Math.sqrt(area);
+    const powerOfTwoSize = Math.pow(2, Math.ceil(Math.log(squareDim) / Math.log(2)));
+
+    let texDimX = 0;
+    let texDimY = gridDim[1];
+    let texRows = 1;
+    let texCols = gridDim[2];
+    if (powerOfTwoSize < gridDim[0] * gridDim[2]) {
+        texCols = Math.floor(powerOfTwoSize / gridDim[0]);
+        texRows = Math.ceil(gridDim[2] / texCols);
+        texDimX = texCols * gridDim[0];
+        texDimY *= texRows;
+    } else {
+        texDimX = gridDim[0] * gridDim[2];
+    }
+    // console.log(texDimX, texDimY, texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2);
+    return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
+}
+
+interface ColorSmoothingInput extends AccumulateInput {
+    boundingSphere: Sphere3D
+    invariantBoundingSphere: Sphere3D
+}
+
+export function calcTextureMeshColorSmoothing(webgl: WebGLContext, input: ColorSmoothingInput, resolution: number, stride: number, texture?: Texture) {
+    if (!input.colorType.startsWith('group') && !input.colorType.startsWith('vertex')) {
+        throw new Error('unsupported color type');
+    };
+
+    const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
+
+    const isInstanceType = input.colorType.endsWith('Instance');
+    const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
+
+    const scaleFactor = 1 / resolution;
+    const scaledBox = Box3D.scale(Box3D(), box, scaleFactor);
+    const dim = Box3D.size(Vec3(), scaledBox);
+    Vec3.ceil(dim, dim);
+    Vec3.add(dim, dim, Vec3.create(2, 2, 2));
+    const { min } = box;
+
+    const [ dx, dy, dz ] = dim;
+    const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(dim);
+    // console.log({ width, height, texCols, dim, resolution });
+
+    if (!webgl.namedTextures[ColorAccumulateName]) {
+        webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
+            ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
+            : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    }
+    const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
+    accumulateTexture.define(width, height);
+
+    const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
+
+    //
+
+    const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
+
+    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
+        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
+    }
+    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
+    framebuffer.bind();
+
+    setAccumulateDefaults(webgl);
+    state.currentRenderItemId = -1;
+    accumulateTexture.attachFramebuffer(framebuffer, 0);
+    gl.viewport(0, 0, width, height);
+    gl.scissor(0, 0, width, height);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    ValueCell.update(uCurrentY, 0);
+    let currCol = 0;
+    let currY = 0;
+    let currX = 0;
+    for (let i = 0; i < dz; ++i) {
+        if (currCol >= texCols) {
+            currCol -= texCols;
+            currY += dy;
+            currX = 0;
+            ValueCell.update(uCurrentY, currY);
+        }
+        // console.log({ i, currX, currY });
+        ValueCell.update(uCurrentX, currX);
+        ValueCell.update(uCurrentSlice, i);
+        gl.viewport(currX, currY, dx, dy);
+        gl.scissor(currX, currY, dx, dy);
+        accumulateRenderable.render();
+        ++currCol;
+        currX += dx;
+    }
+
+    // const accImage = new Float32Array(width * height * 4);
+    // accumulateTexture.attachFramebuffer(framebuffer, 0);
+    // webgl.readPixels(0, 0, width, height, accImage);
+    // console.log(accImage);
+    // printTextureImage({ array: accImage, width, height }, 1 / 4);
+
+    // normalize
+
+    if (!texture) texture = resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+    texture.define(width, height);
+
+    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture);
+
+    setNormalizeDefaults(webgl);
+    state.currentRenderItemId = -1;
+    texture.attachFramebuffer(framebuffer, 0);
+    gl.viewport(0, 0, width, height);
+    gl.scissor(0, 0, width, height);
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    normalizeRenderable.render();
+
+    // const normImage = new Uint8Array(width * height * 4);
+    // texture.attachFramebuffer(framebuffer, 0);
+    // webgl.readPixels(0, 0, width, height, normImage);
+    // console.log(normImage);
+    // printTextureImage({ array: normImage, width, height }, 1 / 4);
+
+    const transform = Vec4.create(min[0], min[1], min[2], scaleFactor);
+    const type = isInstanceType ? 'volumeInstance' : 'volume';
+
+    return { texture, gridDim: dim, gridTexDim: Vec2.create(width, height), transform, type };
+}

+ 2 - 0
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -39,6 +39,8 @@ export interface TextureMesh {
     readonly doubleBuffer: TextureMesh.DoubleBuffer
 
     readonly boundingSphere: Sphere3D
+
+    meta?: unknown
 }
 
 export namespace TextureMesh {

+ 28 - 0
src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export const accumulate_frag = `
+precision highp float;
+
+varying vec3 vPosition;
+varying vec3 vColor;
+
+uniform float uCurrentSlice;
+uniform float uCurrentX;
+uniform float uCurrentY;
+uniform float uResolution;
+
+const float p = 2.0;
+
+void main() {
+    vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
+    vec3 fragPos = vec3(v.x, v.y, uCurrentSlice);
+    float dist = distance(fragPos, vPosition);
+    if (dist > p) discard;
+
+    gl_FragColor = vec4(vColor, 1.0) * (p - dist);
+}
+`;

+ 55 - 0
src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export const accumulate_vert = `
+precision highp float;
+
+#include common
+#include read_from_texture
+
+uniform int uTotalCount;
+uniform int uGroupCount;
+
+attribute float aSample;
+#define SampleID int(aSample)
+
+attribute mat4 aTransform;
+attribute float aInstance;
+
+uniform vec2 uGeoTexDim;
+uniform sampler2D tPosition;
+uniform sampler2D tGroup;
+
+uniform vec2 uColorTexDim;
+uniform sampler2D tColor;
+
+varying vec3 vPosition;
+varying vec3 vColor;
+
+uniform vec3 uBboxSize;
+uniform vec3 uBboxMin;
+uniform float uResolution;
+
+void main() {
+    vec3 position = readFromTexture(tPosition, SampleID, uGeoTexDim).xyz;
+    float group = decodeFloatRGB(readFromTexture(tGroup, SampleID, uGeoTexDim).rgb);
+
+    position = (aTransform * vec4(position, 1.0)).xyz;
+    gl_PointSize = 7.0;
+    vPosition = (position - uBboxMin) / uResolution;
+    gl_Position = vec4(((position - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
+
+    #if defined(dColorType_group)
+        vColor = readFromTexture(tColor, group, uColorTexDim).rgb;
+    #elif defined(dColorType_groupInstance)
+        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
+    #elif defined(dColorType_vertex)
+        vColor = readFromTexture(tColor, SampleID, uColorTexDim).rgb;
+    #elif defined(dColorType_vertexInstance)
+        vColor = readFromTexture(tColor, int(aInstance) * uTotalCount + SampleID, uColorTexDim).rgb;
+    #endif
+}
+`;

+ 20 - 0
src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts

@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export const normalize_frag = `
+precision highp float;
+precision highp sampler2D;
+
+uniform sampler2D tColor;
+uniform vec2 uTexSize;
+
+void main(void) {
+    vec2 coords = gl_FragCoord.xy / uTexSize;
+    vec4 color = texture2D(tColor, coords);
+
+    gl_FragColor.rgb = color.rgb / color.a;
+}
+`;

+ 1 - 0
src/mol-math/geometry/common.ts

@@ -26,6 +26,7 @@ export type DensityData = {
     transform: Mat4,
     field: Tensor,
     idField: Tensor,
+    resolution: number
 }
 
 export type DensityTextureData = {

+ 2 - 0
src/mol-math/geometry/gaussian-density.ts

@@ -21,10 +21,12 @@ export type GaussianDensityProps = typeof DefaultGaussianDensityProps
 
 export type GaussianDensityData = {
     radiusFactor: number
+    resolution: number
 } & DensityData
 
 export type GaussianDensityTextureData = {
     radiusFactor: number
+    resolution: number
 } & DensityTextureData
 
 export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps) {

+ 1 - 1
src/mol-math/geometry/gaussian-density/cpu.ts

@@ -129,5 +129,5 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
     Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
     Mat4.setTranslation(transform, expandedBox.min);
 
-    return { field, idField, transform, radiusFactor: 1 };
+    return { field, idField, transform, radiusFactor: 1, resolution };
 }

+ 11 - 12
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -22,7 +22,7 @@ import { gaussianDensity_vert } from '../../../mol-gl/shader/gaussian-density.ve
 import { gaussianDensity_frag } from '../../../mol-gl/shader/gaussian-density.frag';
 import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
 
-export const GaussianDensitySchema = {
+const GaussianDensitySchema = {
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
 
@@ -49,9 +49,6 @@ export const GaussianDensitySchema = {
 type GaussianDensityValues = Values<typeof GaussianDensitySchema>
 type GaussianDensityRenderable = ComputeRenderable<GaussianDensityValues>
 const GaussianDensityName = 'gaussian-density';
-const GaussianDensityShaderCode = ShaderCode(
-    GaussianDensityName, gaussianDensity_vert, gaussianDensity_frag
-);
 
 function getFramebuffer(webgl: WebGLContext): Framebuffer {
     if (!webgl.namedFramebuffers[GaussianDensityName]) {
@@ -73,12 +70,12 @@ export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (
     // it's faster than texture3d
     // console.time('GaussianDensityTexture2d')
     const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
-    const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
+    const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor, resolution } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
     // webgl.waitForGpuCommandsCompleteSync()
     // console.timeEnd('GaussianDensityTexture2d')
     const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
 
-    return { field, idField, transform: getTransform(scale, bbox), radiusFactor };
+    return { field, idField, transform: getTransform(scale, bbox), radiusFactor, resolution };
 }
 
 export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
@@ -95,8 +92,8 @@ export function GaussianDensityTexture3d(webgl: WebGLContext, position: Position
     return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
 }
 
-function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor }: _GaussianDensityTextureData): GaussianDensityTextureData {
-    return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor };
+function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution }: _GaussianDensityTextureData): GaussianDensityTextureData {
+    return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution };
 }
 
 function getTransform(scale: Vec3, bbox: Box3D) {
@@ -116,6 +113,7 @@ type _GaussianDensityTextureData = {
     gridTexDim: Vec3
     gridTexScale: Vec2
     radiusFactor: number
+    resolution: number
 }
 
 function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
@@ -200,7 +198,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
 
     // printTexture(webgl, minDistTex, 0.75);
 
-    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor };
+    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution };
 }
 
 function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
@@ -256,7 +254,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
     setupGroupIdRendering(webgl, renderable);
     render(texture, false);
 
-    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor };
+    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor, resolution };
 }
 
 //
@@ -362,7 +360,8 @@ function createGaussianDensityRenderable(webgl: WebGLContext, drawCount: number,
     };
 
     const schema = { ...GaussianDensitySchema };
-    const renderItem =  createComputeRenderItem(webgl, 'points', GaussianDensityShaderCode, schema, values);
+    const shaderCode = ShaderCode(GaussianDensityName, gaussianDensity_vert, gaussianDensity_frag);
+    const renderItem =  createComputeRenderItem(webgl, 'points', shaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
 }
@@ -430,7 +429,7 @@ function getTexture2dSize(gridDim: Vec3) {
     return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
 }
 
-export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
+function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
     // console.time('fieldFromTexture2d')
     const [ dx, dy, dz ] = dim;
     const [ width, height ] = texDim;

+ 1 - 1
src/mol-math/geometry/molecular-surface.ts

@@ -370,5 +370,5 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
     Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
     Mat4.setTranslation(transform, expandedBox.min);
     // console.log({ field, idField, transform, updateChunk })
-    return { field, idField, transform };
+    return { field, idField, transform, resolution };
 }

+ 18 - 3
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, GraphicsRenderObject } from '../../mol-gl/render-object';
+import { createRenderObject, GraphicsRenderObject, RenderObjectValues } 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';
@@ -34,6 +34,7 @@ import { createMarkers } from '../../mol-geo/geometry/marker-data';
 import { StructureParams, StructureMeshParams, StructureTextParams, StructureDirectVolumeParams, StructureLinesParams, StructureCylindersParams, StructureTextureMeshParams } from './params';
 import { Clipping } from '../../mol-theme/clipping';
 import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
+import { WebGLContext } from '../../mol-gl/webgl/context';
 
 export interface  ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
 
@@ -53,6 +54,7 @@ interface ComplexVisualBuilder<P extends StructureParams, G extends Geometry> {
     eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean): boolean,
     setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure): void
     mustRecreate?: (structure: Structure, props: PD.Values<P>) => boolean
+    processValues?: (values: RenderObjectValues<G['kind']>, geometry: G, props: PD.Values<P>, webgl?: WebGLContext) => void
     dispose?: (geometry: G) => void
 }
 
@@ -61,7 +63,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, mustRecreate, dispose } = builder;
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, processValues, dispose } = builder;
     const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
@@ -198,6 +200,12 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
         }
     }
 
+    function finalize(ctx: VisualContext) {
+        if (renderObject) {
+            processValues?.(renderObject.values, geometry, currentProps, ctx.webgl);
+        }
+    }
+
     return {
         get groupCount() { return locationIt ? locationIt.count : 0; },
         get renderObject () { return locationIt && locationIt.count ? renderObject : undefined; },
@@ -205,10 +213,17 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             prepareUpdate(theme, props, structure || currentStructure);
             if (updateState.createGeometry) {
                 const newGeometry = createGeometry(ctx, newStructure, newTheme, newProps, geometry);
-                return newGeometry instanceof Promise ? newGeometry.then(update) : update(newGeometry);
+                if (newGeometry instanceof Promise) {
+                    return newGeometry.then(g => {
+                        update(g);
+                        finalize(ctx);
+                    });
+                }
+                update(newGeometry);
             } else {
                 update();
             }
+            finalize(ctx);
         },
         getLoci(pickingId: PickingId) {
             return renderObject ? getLoci(pickingId, currentStructure, renderObject.id) : EmptyLoci;

+ 0 - 1
src/mol-repr/structure/representation/gaussian-surface.ts

@@ -22,7 +22,6 @@ const GaussianSurfaceVisuals = {
 export const GaussianSurfaceParams = {
     ...GaussianSurfaceMeshParams,
     ...GaussianWireframeParams,
-    smoothColors: PD.Select('off', PD.arrayToOptions(['off', 'vertex', 'volume'] as const)),
     visuals: PD.MultiSelect(['gaussian-surface-mesh'], PD.objectToOptions(GaussianSurfaceVisuals)),
 };
 export type GaussianSurfaceParams = typeof GaussianSurfaceParams

+ 0 - 1
src/mol-repr/structure/representation/molecular-surface.ts

@@ -21,7 +21,6 @@ const MolecularSurfaceVisuals = {
 export const MolecularSurfaceParams = {
     ...MolecularSurfaceMeshParams,
     ...MolecularSurfaceWireframeParams,
-    smoothColors: PD.Select('off', PD.arrayToOptions(['off', 'vertex', 'volume'] as const)),
     visuals: PD.MultiSelect(['molecular-surface-mesh'], PD.objectToOptions(MolecularSurfaceVisuals)),
 };
 export type MolecularSurfaceParams = typeof MolecularSurfaceParams

+ 18 - 4
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, GraphicsRenderObject } from '../../mol-gl/render-object';
+import { createRenderObject, GraphicsRenderObject, RenderObjectValues } 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';
@@ -38,6 +38,7 @@ import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
 import { SizeValues } from '../../mol-gl/renderable/schema';
 import { StructureParams, StructureMeshParams, StructureSpheresParams, StructurePointsParams, StructureLinesParams, StructureTextParams, StructureDirectVolumeParams, StructureTextureMeshParams, StructureCylindersParams } from './params';
 import { Clipping } from '../../mol-theme/clipping';
+import { WebGLContext } from '../../mol-gl/webgl/context';
 
 export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
@@ -59,6 +60,7 @@ interface UnitsVisualBuilder<P extends StructureParams, G extends Geometry> {
     eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean): boolean
     setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup): void
     mustRecreate?: (structureGroup: StructureGroup, props: PD.Values<P>) => boolean
+    processValues?: (values: RenderObjectValues<G['kind']>, geometry: G, props: PD.Values<P>, webgl?: WebGLContext) => void
     dispose?: (geometry: G) => void
 }
 
@@ -67,7 +69,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, mustRecreate, dispose } = builder;
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, processValues, dispose } = builder;
     const { createEmpty: createEmptyGeometry, updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
@@ -254,6 +256,12 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         }
     }
 
+    function finalize(ctx: VisualContext) {
+        if (renderObject) {
+            processValues?.(renderObject.values, geometry, currentProps, ctx.webgl);
+        }
+    }
+
     return {
         get groupCount() { return locationIt ? locationIt.count : 0; },
         get renderObject () { return locationIt && locationIt.count ? renderObject : undefined; },
@@ -261,10 +269,17 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
             prepareUpdate(theme, props, structureGroup || currentStructureGroup);
             if (updateState.createGeometry) {
                 const newGeometry = _createGeometry(ctx, newStructureGroup.group.units[0], newStructureGroup.structure, newTheme, newProps, geometry);
-                return newGeometry instanceof Promise ? newGeometry.then(update) : update(newGeometry as G);
+                if (newGeometry instanceof Promise) {
+                    return newGeometry.then(g => {
+                        update(g);
+                        finalize(ctx);
+                    });
+                }
+                update(newGeometry);
             } else {
                 update();
             }
+            finalize(ctx);
         },
         getLoci(pickingId: PickingId) {
             return renderObject ? getLoci(pickingId, currentStructureGroup, renderObject.id) : EmptyLoci;
@@ -319,7 +334,6 @@ export function UnitsMeshVisual<P extends UnitsMeshParams>(builder: UnitsMeshVis
         setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
             builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme, newStructureGroup, currentStructureGroup);
             if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true;
-            if (newProps.smoothColors !== currentProps.smoothColors) state.updateColor = true;
         },
         geometryUtils: Mesh.Utils
     }, materialId);

+ 49 - 2
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -20,11 +20,16 @@ import { Sphere3D } from '../../../mol-math/geometry';
 import { ComplexVisual, ComplexMeshParams, ComplexMeshVisual, ComplexTextureMeshVisual, ComplexTextureMeshParams } from '../complex-visual';
 import { getUnitExtraRadius, getStructureExtraRadius, getVolumeSliceInfo } from './util/common';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
+import { MeshValues } from '../../../mol-gl/renderable/mesh';
+import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
+import { Texture } from '../../../mol-gl/webgl/texture';
+import { applyMeshColorSmoothing, applyTextureMeshColorSmoothing } from './util/color';
 
 const SharedParams = {
     ...GaussianDensityParams,
     ignoreHydrogens: PD.Boolean(false),
     tryUseGpu: PD.Boolean(true),
+    smoothColors: PD.Select('off', PD.arrayToOptions(['on', 'off'] as const)),
 };
 type SharedParams = typeof SharedParams
 
@@ -71,11 +76,16 @@ export function StructureGaussianSurfaceVisual(materialId: number, structure: St
     return StructureGaussianSurfaceMeshVisual(materialId);
 }
 
+type GaussianSurfaceMeta = {
+    resolution?: number
+    colorTexture?: Texture
+}
+
 //
 
 async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor, resolution } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -83,6 +93,7 @@ async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structu
         idField
     };
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
+    (surface.meta as GaussianSurfaceMeta) = { resolution };
 
     Mesh.transform(surface, transform);
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
@@ -107,9 +118,16 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
+            if (newProps.smoothColors !== currentProps.smoothColors) state.updateColor = true;
         },
         mustRecreate: (structureGroup: StructureGroup, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
             return props.tryUseGpu && !!webgl && suitableForGpu(structureGroup.structure, props, webgl);
+        },
+        processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            const { resolution } = geometry.meta as GaussianSurfaceMeta;
+            if (props.smoothColors !== 'off' && resolution && webgl) {
+                applyMeshColorSmoothing(webgl, values, resolution);
+            }
         }
     }, materialId);
 }
@@ -118,7 +136,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
 
 async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor, resolution } = await computeStructureGaussianDensity(structure, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -126,6 +144,7 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure:
         idField
     };
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx.runtime);
+    (surface.meta as GaussianSurfaceMeta) = { resolution };
 
     Mesh.transform(surface, transform);
     if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
@@ -149,9 +168,16 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true;
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
+            if (newProps.smoothColors !== currentProps.smoothColors) state.updateColor = true;
         },
         mustRecreate: (structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
             return props.tryUseGpu && !!webgl && suitableForGpu(structure, props, webgl);
+        },
+        processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            const { resolution } = geometry.meta as GaussianSurfaceMeta;
+            if (props.smoothColors !== 'off' && resolution && webgl) {
+                applyMeshColorSmoothing(webgl, values, resolution);
+            }
         }
     }, materialId);
 }
@@ -186,6 +212,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
+    (surface.meta as GaussianSurfaceMeta) = { resolution: densityTextureData.resolution };
 
     return surface;
 }
@@ -204,15 +231,25 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
+            if (newProps.smoothColors !== currentProps.smoothColors) state.updateColor = true;
         },
         mustRecreate: (structureGroup: StructureGroup, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
             return !props.tryUseGpu || !webgl || !suitableForGpu(structureGroup.structure, props, webgl);
         },
+        processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
+            if (props.smoothColors !== 'off' && resolution && webgl) {
+                applyTextureMeshColorSmoothing(webgl, values, resolution, colorTexture);
+                (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
+            }
+        },
         dispose: (geometry: TextureMesh) => {
             geometry.vertexTexture.ref.value.destroy();
             geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
             geometry.doubleBuffer.destroy();
+
+            (geometry.meta as GaussianSurfaceMeta).colorTexture?.destroy();
         }
     }, materialId);
 }
@@ -245,6 +282,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
+    (surface.meta as GaussianSurfaceMeta) = { resolution: densityTextureData.resolution };
 
     return surface;
 }
@@ -266,11 +304,20 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
         mustRecreate: (structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
             return !props.tryUseGpu || !webgl || !suitableForGpu(structure, props, webgl);
         },
+        processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
+            if (props.smoothColors !== 'off' && resolution && webgl) {
+                applyTextureMeshColorSmoothing(webgl, values, resolution, colorTexture);
+                (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
+            }
+        },
         dispose: (geometry: TextureMesh) => {
             geometry.vertexTexture.ref.value.destroy();
             geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
             geometry.doubleBuffer.destroy();
+
+            (geometry.meta as GaussianSurfaceMeta).colorTexture?.destroy();
         }
     }, materialId);
 }

+ 25 - 2
src/mol-repr/structure/visual/molecular-surface-mesh.ts

@@ -17,18 +17,29 @@ import { ElementIterator, getElementLoci, eachElement } from './util/element';
 import { VisualUpdateState } from '../../util';
 import { CommonSurfaceParams, getUnitExtraRadius } from './util/common';
 import { Sphere3D } from '../../../mol-math/geometry';
+import { MeshValues } from '../../../mol-gl/renderable/mesh';
+import { Texture } from '../../../mol-gl/webgl/texture';
+import { WebGLContext } from '../../../mol-gl/webgl/context';
+import { applyMeshColorSmoothing } from './util/color';
 
 export const MolecularSurfaceMeshParams = {
     ...UnitsMeshParams,
     ...MolecularSurfaceCalculationParams,
-    ...CommonSurfaceParams
+    ...CommonSurfaceParams,
+    smoothColors: PD.Select('off', PD.arrayToOptions(['on', 'off'] as const)),
 };
 export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams
 
+type MolecularSurfaceMeta = {
+    resolution?: number
+    colorTexture?: Texture
+}
+
 //
 
 async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, mesh?: Mesh): Promise<Mesh> {
-    const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
+    const { transform, field, idField, resolution } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime);
+
     const params = {
         isoLevel: props.probeRadius,
         scalarField: field,
@@ -41,6 +52,7 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
 
     const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius + getUnitExtraRadius(unit));
     surface.setBoundingSphere(sphere);
+    (surface.meta as MolecularSurfaceMeta) = { resolution };
 
     return surface;
 }
@@ -57,7 +69,18 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
             if (newProps.probeRadius !== currentProps.probeRadius) state.createGeometry = true;
             if (newProps.probePositions !== currentProps.probePositions) state.createGeometry = true;
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
+            if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
+            if (newProps.smoothColors !== currentProps.smoothColors) state.updateColor = true;
+        },
+        processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, webgl?: WebGLContext) => {
+            const { resolution } = geometry.meta as MolecularSurfaceMeta;
+            if (props.smoothColors !== 'off' && resolution && webgl) {
+                applyMeshColorSmoothing(webgl, values, resolution);
+            }
+        },
+        dispose: (geometry: Mesh) => {
+            (geometry.meta as MolecularSurfaceMeta).colorTexture?.destroy();
         }
     }, materialId);
 }

+ 67 - 0
src/mol-repr/structure/visual/util/color.ts

@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { calcMeshColorSmoothing } from '../../../../mol-geo/geometry/mesh/color-smoothing';
+import { calcTextureMeshColorSmoothing } from '../../../../mol-geo/geometry/texture-mesh/color-smoothing';
+import { MeshValues } from '../../../../mol-gl/renderable/mesh';
+import { TextureMeshValues } from '../../../../mol-gl/renderable/texture-mesh';
+import { WebGLContext } from '../../../../mol-gl/webgl/context';
+import { Texture } from '../../../../mol-gl/webgl/texture';
+import { ValueCell } from '../../../../mol-util';
+
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' | 'vertex' | 'vertexInstance' {
+    return x === 'group' || x === 'groupInstance' || x === 'vertex' || x === 'vertexInstance';
+}
+
+export function applyMeshColorSmoothing(webgl: WebGLContext, values: MeshValues, resolution: number) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing(webgl, {
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tColor.ref.value,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution * 2, 6);
+
+    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+    ValueCell.update(values.tColorGrid, smoothingData.texture);
+    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uColorGridTransform, smoothingData.transform);
+    ValueCell.updateIfChanged(values.dColorGridType, '2d');
+}
+
+export function applyTextureMeshColorSmoothing(webgl: WebGLContext, values: TextureMeshValues, resolution: number, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    const smoothingData = calcTextureMeshColorSmoothing(webgl, {
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData: values.tColor.ref.value,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution * 2, 12, colorTexture);
+
+    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+    ValueCell.update(values.tColorGrid, smoothingData.texture);
+    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uColorGridTransform, smoothingData.transform);
+    ValueCell.updateIfChanged(values.dColorGridType, '2d');
+}

+ 0 - 1
src/mol-repr/visual.ts

@@ -28,7 +28,6 @@ export interface VisualContext {
     readonly runtime: RuntimeContext
     readonly webgl?: WebGLContext
 }
-// export type VisualFactory<D, P extends PD.Params> = (ctx: VisualContext) => Visual<D, P>
 
 export { Visual };
 interface Visual<D, P extends PD.Params> {