diff --git a/CHANGELOG.md b/CHANGELOG.md index e78ef620c90532c1dccf2edc8f20f1b7e55af795..2665b92e56b59372f6f0da319264f1b76e373e62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that since we don't clearly distinguish between a public and private interf - StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability - Re-enable VAO with better workaround (bind null elements buffer before deleting) - Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes) +- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces ## [v2.3.9] - 2021-11-20 diff --git a/src/extensions/geo-export/glb-exporter.ts b/src/extensions/geo-export/glb-exporter.ts index 74bca43b05d1919b9fda7b014f5182dabe0152ba..592b96df38a2c4cc75df7844466f0c9535f7bf55 100644 --- a/src/extensions/geo-export/glb-exporter.ts +++ b/src/extensions/geo-export/glb-exporter.ts @@ -2,9 +2,9 @@ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sukolsak Sakshuwong <sukolsak@stanford.edu> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { BaseValues } from '../../mol-gl/renderable/schema'; import { Style } from '../../mol-gl/renderer'; import { asciiWrite } from '../../mol-io/common/ascii'; import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary'; @@ -15,7 +15,7 @@ import { RuntimeContext } from '../../mol-task'; import { Color } from '../../mol-util/color/color'; import { fillSerial } from '../../mol-util/array'; import { NumberArray } from '../../mol-util/type-helpers'; -import { MeshExporter, AddMeshInput } from './mesh-exporter'; +import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter'; // avoiding namespace lookup improved performance in Chrome (Aug 2020) const v3fromArray = Vec3.fromArray; @@ -30,6 +30,12 @@ const FLOAT = 5126; const ARRAY_BUFFER = 34962; const ELEMENT_ARRAY_BUFFER = 34963; +const GLTF_MAGIC_BYTE = 0x46546C67; +const JSON_CHUNK_TYPE = 0x4E4F534A; +const BIN_CHUNK_TYPE = 0x004E4942; +const JSON_PAD_CHAR = 0x20; +const BIN_PAD_CHAR = 0x00; + export type GlbData = { glb: Uint8Array } @@ -126,23 +132,17 @@ export class GlbExporter extends MeshExporter<GlbData> { }; } - private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined) { - const groupCount = values.uGroupCount.ref.value; + private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) { + const { values, vertexCount } = geoData; const uAlpha = values.uAlpha.ref.value; - const dTransparency = values.dTransparency.ref.value; - const tTransparency = values.tTransparency.ref.value; const colorArray = new Uint8Array(vertexCount * 4); for (let i = 0; i < vertexCount; ++i) { - let color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i); + let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint); - let alpha = uAlpha; - if (dTransparency) { - const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i]; - const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255; - alpha *= 1 - transparency; - } + const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency); + const alpha = uAlpha * (1 - transparency); color = Color.sRGBToLinear(color); Color.toArray(color, colorArray, i * 4); @@ -163,6 +163,8 @@ export class GlbExporter extends MeshExporter<GlbData> { const t = Mat4(); const colorType = values.dColorType.ref.value; + const overpaintType = values.dOverpaintType.ref.value; + const transparencyType = values.dTransparencyType.ref.value; const dTransparency = values.dTransparency.ref.value; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; @@ -170,7 +172,19 @@ export class GlbExporter extends MeshExporter<GlbData> { let interpolatedColors: Uint8Array | undefined; if (colorType === 'volume' || colorType === 'volumeInstance') { const stride = isGeoTexture ? 4 : 3; - interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!); + interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType }); + } + + let interpolatedOverpaint: Uint8Array | undefined; + if (overpaintType === 'volumeInstance') { + const stride = isGeoTexture ? 4 : 3; + interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType }); + } + + let interpolatedTransparency: Uint8Array | undefined; + if (transparencyType === 'volumeInstance') { + const stride = isGeoTexture ? 4 : 3; + interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType }); } // instancing @@ -201,7 +215,7 @@ export class GlbExporter extends MeshExporter<GlbData> { // create a color buffer if needed if (instanceIndex === 0 || !sameColorBuffer) { - colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors); + colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency); } // glTF mesh @@ -279,13 +293,13 @@ export class GlbExporter extends MeshExporter<GlbData> { const jsonBuffer = new Uint8Array(jsonString.length); asciiWrite(jsonBuffer, jsonString); - const [jsonChunk, jsonChunkLength] = createChunk(0x4E4F534A, [jsonBuffer.buffer], jsonBuffer.length, 0x20); - const [binaryChunk, binaryChunkLength] = createChunk(0x004E4942, this.binaryBuffer, binaryBufferLength, 0x00); + const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR); + const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR); const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength; const header = new ArrayBuffer(12); const headerDataView = new DataView(header); - headerDataView.setUint32(0, 0x46546C67, true); // magic number "glTF" + headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF" headerDataView.setUint32(4, 2, true); // version headerDataView.setUint32(8, glbBufferLength, true); // length const glbBuffer = [header, ...jsonChunk, ...binaryChunk]; diff --git a/src/extensions/geo-export/mesh-exporter.ts b/src/extensions/geo-export/mesh-exporter.ts index 6a96e7127e42ff5101d4b058339a8ff273a45c3a..c2af2e6bcf73fd3d8ab9ce8e3a4791e475276ef0 100644 --- a/src/extensions/geo-export/mesh-exporter.ts +++ b/src/extensions/geo-export/mesh-exporter.ts @@ -2,6 +2,7 @@ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sukolsak Sakshuwong <sukolsak@stanford.edu> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { sort, arraySwap } from '../../mol-data/util'; @@ -26,7 +27,7 @@ import { RuntimeContext } from '../../mol-task'; import { Color } from '../../mol-util/color/color'; import { decodeFloatRGB } from '../../mol-util/float-packing'; import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter'; -import { readTexture } from '../../mol-gl/compute/util'; +import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util'; const GeoExportName = 'geo-export'; @@ -49,6 +50,14 @@ export interface AddMeshInput { ctx: RuntimeContext } +export type MeshGeoData = { + values: BaseValues, + groups: Float32Array | Uint8Array, + vertexCount: number, + instanceIndex: number, + isGeoTexture: boolean +} + export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> { abstract readonly fileExtension: string; @@ -91,7 +100,8 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements return decodeFloatRGB(r, g, b); } - protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) { + protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) { + const { values, vertexCount, vertices, colorType, stride } = input; const colorGridTransform = values.uColorGridTransform.ref.value; const colorGridDim = values.uColorGridDim.ref.value; const colorTexDim = values.uColorTexDim.ref.value; @@ -99,7 +109,34 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements const instanceCount = values.uInstanceCount.ref.value; const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array; - const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 }); + const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 }); + return interpolated.array; + } + + protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) { + const { values, vertexCount, vertices, colorType, stride } = input; + const overpaintGridTransform = values.uOverpaintGridTransform.ref.value; + const overpaintGridDim = values.uOverpaintGridDim.ref.value; + const overpaintTexDim = values.uOverpaintTexDim.ref.value; + const aTransform = values.aTransform.ref.value; + const instanceCount = values.uInstanceCount.ref.value; + + const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array; + const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 }); + return interpolated.array; + } + + protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) { + const { values, vertexCount, vertices, colorType, stride } = input; + const transparencyGridTransform = values.uTransparencyGridTransform.ref.value; + const transparencyGridDim = values.uTransparencyGridDim.ref.value; + const transparencyTexDim = values.uTransparencyTexDim.ref.value; + const aTransform = values.aTransform.ref.value; + const instanceCount = values.uInstanceCount.ref.value; + + const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array; + const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 }); + return interpolated.array; } @@ -184,11 +221,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements } } - protected static getColor(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined, vertexIndex: number): Color { + protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color { + const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData; const groupCount = values.uGroupCount.ref.value; const colorType = values.dColorType.ref.value; const uColor = values.uColor.ref.value; const tColor = values.tColor.ref.value.array; + const overpaintType = values.dOverpaintType.ref.value; const dOverpaint = values.dOverpaint.ref.value; const tOverpaint = values.tOverpaint.ref.value.array; @@ -226,15 +265,70 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements } if (dOverpaint) { - const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex]; - const overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4); - const overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255; + let overpaintColor: Color; + let overpaintAlpha: number; + switch (overpaintType) { + case 'groupInstance': { + const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex]; + const idx = (instanceIndex * groupCount + group) * 4; + overpaintColor = Color.fromArray(tOverpaint, idx); + overpaintAlpha = tOverpaint[idx + 3] / 255; + break; + } + case 'vertexInstance': { + const idx = (instanceIndex * vertexCount + vertexIndex) * 4; + overpaintColor = Color.fromArray(tOverpaint, idx); + overpaintAlpha = tOverpaint[idx + 3] / 255; + break; + } + case 'volumeInstance': { + const idx = (instanceIndex * vertexCount + vertexIndex) * 4; + overpaintColor = Color.fromArray(interpolatedOverpaint!, idx); + overpaintAlpha = interpolatedOverpaint![idx + 3] / 255; + break; + } + default: throw new Error('Unsupported overpaint type.'); + } + // interpolate twice to avoid darkening due to empty overpaint + overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha); color = Color.interpolate(color, overpaintColor, overpaintAlpha); } return color; } + protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number { + const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData; + const groupCount = values.uGroupCount.ref.value; + const dTransparency = values.dTransparency.ref.value; + const tTransparency = values.tTransparency.ref.value.array; + const transparencyType = values.dTransparencyType.ref.value; + + let transparency: number = 0; + if (dTransparency) { + switch (transparencyType) { + case 'groupInstance': { + const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex]; + const idx = (instanceIndex * groupCount + group); + transparency = tTransparency[idx] / 255; + break; + } + case 'vertexInstance': { + const idx = (instanceIndex * vertexCount + vertexIndex); + transparency = tTransparency[idx] / 255; + break; + } + case 'volumeInstance': { + const idx = (instanceIndex * vertexCount + vertexIndex); + transparency = interpolatedTransparency![idx] / 255; + break; + } + default: throw new Error('Unsupported transparency type.'); + } + } + return transparency; + } + protected abstract addMeshWithColors(input: AddMeshInput): void; private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) { diff --git a/src/extensions/geo-export/obj-exporter.ts b/src/extensions/geo-export/obj-exporter.ts index 3a57dde660f818dfb1696c6e3b547d7a360b64ad..0a527417808f2657f2e171c41dc1b676f1ed2f64 100644 --- a/src/extensions/geo-export/obj-exporter.ts +++ b/src/extensions/geo-export/obj-exporter.ts @@ -2,6 +2,7 @@ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sukolsak Sakshuwong <sukolsak@stanford.edu> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { asciiWrite } from '../../mol-io/common/ascii'; @@ -47,7 +48,7 @@ export class ObjExporter extends MeshExporter<ObjData> { StringBuilder.newline(this.obj); if (!this.materialSet.has(material)) { this.materialSet.add(material); - const [r, g, b] = Color.toRgbNormalized(color); + const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000); const mtl = this.mtl; StringBuilder.writeSafe(mtl, `newmtl ${material}\n`); StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model @@ -77,18 +78,27 @@ export class ObjExporter extends MeshExporter<ObjData> { const tmpV = Vec3(); const stride = isGeoTexture ? 4 : 3; - const groupCount = values.uGroupCount.ref.value; const colorType = values.dColorType.ref.value; + const overpaintType = values.dOverpaintType.ref.value; + const transparencyType = values.dTransparencyType.ref.value; const uAlpha = values.uAlpha.ref.value; - const dTransparency = values.dTransparency.ref.value; - const tTransparency = values.tTransparency.ref.value; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; let interpolatedColors: Uint8Array | undefined; if (colorType === 'volume' || colorType === 'volumeInstance') { - interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!); - ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount); + interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType }); + } + + let interpolatedOverpaint: Uint8Array | undefined; + if (overpaintType === 'volumeInstance') { + interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType }); + } + + let interpolatedTransparency: Uint8Array | undefined; + if (transparencyType === 'volumeInstance') { + const stride = isGeoTexture ? 4 : 3; + interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType }); } await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount }); @@ -126,17 +136,23 @@ export class ObjExporter extends MeshExporter<ObjData> { StringBuilder.newline(obj); } - // face + const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture }; + + // color + const quantizedColors = new Uint8Array(drawCount * 3); for (let i = 0; i < drawCount; i += 3) { const v = isGeoTexture ? i : indices![i]; - const color = ObjExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v); - - let alpha = uAlpha; - if (dTransparency) { - const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]]; - const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255; - alpha *= 1 - transparency; - } + const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint); + Color.toArray(color, quantizedColors, i); + } + ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount); + + // face + for (let i = 0; i < drawCount; i += 3) { + const color = Color.fromArray(quantizedColors, i); + + const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency); + const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized this.updateMaterial(color, alpha); diff --git a/src/extensions/geo-export/usdz-exporter.ts b/src/extensions/geo-export/usdz-exporter.ts index 46a638e2075c553e69e1fa21b14857bc1fd89535..eb578dae37231ebfd25bb96161aa3c7e1c0330ed 100644 --- a/src/extensions/geo-export/usdz-exporter.ts +++ b/src/extensions/geo-export/usdz-exporter.ts @@ -2,6 +2,7 @@ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sukolsak Sakshuwong <sukolsak@stanford.edu> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Style } from '../../mol-gl/renderer'; @@ -42,7 +43,7 @@ export class UsdzExporter extends MeshExporter<UsdzData> { const materialKey = UsdzExporter.getMaterialKey(color, alpha); if (this.materialSet.has(materialKey)) return; this.materialSet.add(materialKey); - const [r, g, b] = Color.toRgbNormalized(color); + const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000); this.materials.push(` def Material "material${materialKey}" { @@ -68,18 +69,28 @@ def Material "material${materialKey}" const tmpV = Vec3(); const stride = isGeoTexture ? 4 : 3; - const groupCount = values.uGroupCount.ref.value; const colorType = values.dColorType.ref.value; + const overpaintType = values.dOverpaintType.ref.value; + const transparencyType = values.dTransparencyType.ref.value; const uAlpha = values.uAlpha.ref.value; - const dTransparency = values.dTransparency.ref.value; - const tTransparency = values.tTransparency.ref.value; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; let interpolatedColors: Uint8Array | undefined; if (colorType === 'volume' || colorType === 'volumeInstance') { - interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!); - UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount); + interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType }); + } + + let interpolatedOverpaint: Uint8Array | undefined; + if (overpaintType === 'volumeInstance') { + const stride = isGeoTexture ? 4 : 3; + interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType }); + } + + let interpolatedTransparency: Uint8Array | undefined; + if (transparencyType === 'volumeInstance') { + const stride = isGeoTexture ? 4 : 3; + interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType }); } await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount }); @@ -121,6 +132,8 @@ def Material "material${materialKey}" StringBuilder.writeSafe(normalBuilder, ')'); } + const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture }; + // face for (let i = 0; i < drawCount; ++i) { const v = isGeoTexture ? i : indices![i]; @@ -129,17 +142,21 @@ def Material "material${materialKey}" } // color - const faceIndicesByMaterial = new Map<number, number[]>(); + const quantizedColors = new Uint8Array(drawCount * 3); for (let i = 0; i < drawCount; i += 3) { const v = isGeoTexture ? i : indices![i]; - const color = UsdzExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v); + const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint); + Color.toArray(color, quantizedColors, i); + } + UsdzExporter.quantizeColors(quantizedColors, mesh!.vertexCount); - let alpha = uAlpha; - if (dTransparency) { - const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]]; - const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255; - alpha *= 1 - transparency; - } + // material + const faceIndicesByMaterial = new Map<number, number[]>(); + for (let i = 0; i < drawCount; i += 3) { + const color = Color.fromArray(quantizedColors, i); + + const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency); + const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized this.addMaterial(color, alpha); diff --git a/src/mol-geo/geometry/base.ts b/src/mol-geo/geometry/base.ts index c9364acccf82581c0f92f3317bb47ea63466becf..84f99104565f6a6d7c02a5a791ccd77a20d68fa8 100644 --- a/src/mol-geo/geometry/base.ts +++ b/src/mol-geo/geometry/base.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -15,6 +15,7 @@ import { ColorNames } from '../../mol-util/color/names'; import { NullLocation } from '../../mol-model/location'; import { UniformColorTheme } from '../../mol-theme/color/uniform'; import { UniformSizeTheme } from '../../mol-theme/size/uniform'; +import { smoothstep } from '../../mol-math/interpolate'; export const VisualQualityInfo = { 'custom': {}, @@ -33,6 +34,40 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames); // +export const ColorSmoothingParams = { + smoothColors: PD.MappedStatic('auto', { + auto: PD.Group({}), + on: PD.Group({ + resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }), + sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }), + }), + off: PD.Group({}) + }), +}; +export type ColorSmoothingParams = typeof ColorSmoothingParams + +export function hasColorSmoothingProp(props: PD.Values<any>): props is PD.Values<ColorSmoothingParams> { + return !!props.smoothColors; +} + +export function getColorSmoothingProps(smoothColors: PD.Values<ColorSmoothingParams>['smoothColors'], preferSmoothing?: boolean, resolution?: number) { + if ((smoothColors.name === 'on' || (smoothColors.name === 'auto' && preferSmoothing)) && resolution && resolution < 3) { + let stride = 3; + if (smoothColors.name === 'on') { + resolution *= smoothColors.params.resolutionFactor; + stride = smoothColors.params.sampleStride; + } else { + // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8 + resolution *= 2 - smoothstep(0, 1.1, resolution); + resolution = Math.max(0.5, resolution); + if (resolution > 1.2) stride = 2; + } + return { resolution, stride }; + }; +} + +// + export namespace BaseGeometry { export const Params = { alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }), diff --git a/src/mol-geo/geometry/mesh/color-smoothing.ts b/src/mol-geo/geometry/mesh/color-smoothing.ts index f14e0d4d4f9f5f6eedb3288f51e8bedd93b7ec73..e9ebf0e645439f8a49e78f59bbfebb67625f88ca 100644 --- a/src/mol-geo/geometry/mesh/color-smoothing.ts +++ b/src/mol-geo/geometry/mesh/color-smoothing.ts @@ -4,13 +4,15 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { MeshValues } from '../../../mol-gl/renderable/mesh'; import { createTextureImage, TextureImage } from '../../../mol-gl/renderable/util'; import { WebGLContext } from '../../../mol-gl/webgl/context'; import { Texture } from '../../../mol-gl/webgl/texture'; import { Box3D, Sphere3D } from '../../../mol-math/geometry'; +import { lerp } from '../../../mol-math/interpolate'; import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra'; import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util'; -import { Color } from '../../../mol-util/color'; +import { ValueCell } from '../../../mol-util'; interface ColorSmoothingInput { vertexCount: number @@ -24,10 +26,11 @@ interface ColorSmoothingInput { colorType: 'group' | 'groupInstance' boundingSphere: Sphere3D invariantBoundingSphere: Sphere3D + itemSize: 4 | 3 | 1 } export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl?: WebGLContext, texture?: Texture) { - const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer } = input; + const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer, itemSize } = input; const isInstanceType = colorType.endsWith('Instance'); const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere); @@ -43,7 +46,6 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n const { width, height } = getVolumeTexture2dLayout(gridDim); // console.log({ width, height, dim }); - const itemSize = 3; const data = new Float32Array(width * height * itemSize); const count = new Float32Array(width * height); @@ -78,10 +80,7 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n const z = Math.floor(vz); // group colors - const ci = i * groupCount + groupBuffer[j]; - const r = colors[ci * 3]; - const g = colors[ci * 3 + 1]; - const b = colors[ci * 3 + 2]; + const ci = (i * groupCount + groupBuffer[j]) * itemSize; // Extents of grid to consider for this atom const begX = Math.max(0, x - p); @@ -106,10 +105,10 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n const s = p - d; 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 k = 0; k < itemSize; ++k) { + data[index + k] += colors[ci + k] * s; + } + count[index / itemSize] += s; } } } @@ -117,11 +116,11 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n } for (let i = 0, il = count.length; i < il; ++i) { - const i3 = i * 3; + const is = i * itemSize; const c = count[i]; - grid[i3] = Math.round(data[i3] / c); - grid[i3 + 1] = Math.round(data[i3 + 1] / c); - grid[i3 + 2] = Math.round(data[i3 + 2] / c); + for (let k = 0; k < itemSize; ++k) { + grid[is + k] = Math.round(data[is + k] / c); + } } const gridTexDim = Vec2.create(width, height); @@ -129,12 +128,16 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n const type = isInstanceType ? 'volumeInstance' as const : 'volume' as const; if (webgl) { - if (!texture) texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear'); + if (!texture) { + const format = itemSize === 4 ? 'rgba' : + itemSize === 3 ? 'rgb' : 'alpha'; + texture = webgl.resources.texture('image-uint8', format, 'ubyte', 'linear'); + } texture.load(textureImage); return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type }; } else { - const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 }); + const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: itemSize, outputStride: itemSize }); return { kind: 'vertex' as const, @@ -157,16 +160,24 @@ interface ColorInterpolationInput { gridTexDim: Vec2 gridDim: Vec3 gridTransform: Vec4 - vertexStride: number - colorStride: number + vertexStride: 3 | 4 + colorStride: 1 | 3 | 4 + outputStride: 1 | 3 | 4 + itemOffset?: 0 | 1 | 2 | 3 } export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> { const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input; + const itemOffset = input.itemOffset || 0; + const outputStride = input.outputStride; + if (outputStride + itemOffset > colorStride) { + throw new Error('outputStride + itemOffset must NOT be larger than colorStride'); + } + const isInstanceType = input.colorType.endsWith('Instance'); const instanceCount = isInstanceType ? input.instanceCount : 1; - const image = createTextureImage(Math.max(1, instanceCount * vertexCount), 3, Uint8Array); + const image = createTextureImage(Math.max(1, instanceCount * vertexCount), outputStride, Uint8Array); const { array } = image; const [xn, yn] = gridDim; @@ -204,26 +215,144 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text const [x1, y1, z1] = v1; const [xd, yd, zd] = vd; - const s000 = Color.fromArray(grid, getIndex(x0, y0, z0)); - const s100 = Color.fromArray(grid, getIndex(x1, y0, z0)); - const s001 = Color.fromArray(grid, getIndex(x0, y0, z1)); - const s101 = Color.fromArray(grid, getIndex(x1, y0, z1)); - const s010 = Color.fromArray(grid, getIndex(x0, y1, z0)); - const s110 = Color.fromArray(grid, getIndex(x1, y1, z0)); - const s011 = Color.fromArray(grid, getIndex(x0, y1, z1)); - const s111 = Color.fromArray(grid, getIndex(x1, y1, z1)); + const i000 = getIndex(x0, y0, z0) + itemOffset; + const i100 = getIndex(x1, y0, z0) + itemOffset; + const i001 = getIndex(x0, y0, z1) + itemOffset; + const i101 = getIndex(x1, y0, z1) + itemOffset; + const i010 = getIndex(x0, y1, z0) + itemOffset; + const i110 = getIndex(x1, y1, z0) + itemOffset; + const i011 = getIndex(x0, y1, z1) + itemOffset; + const i111 = getIndex(x1, y1, z1) + itemOffset; + + const o = (i * vertexCount + j) * outputStride; + + for (let k = 0; k < outputStride; ++k) { + const s000 = grid[i000 + k]; + const s100 = grid[i100 + k]; + const s001 = grid[i001 + k]; + const s101 = grid[i101 + k]; + const s010 = grid[i010 + k]; + const s110 = grid[i110 + k]; + const s011 = grid[i011 + k]; + const s111 = grid[i111 + k]; + + const s00 = lerp(s000, s100, xd); + const s01 = lerp(s001, s101, xd); + const s10 = lerp(s010, s110, xd); + const s11 = lerp(s011, s111, xd); + + const s0 = lerp(s00, s10, yd); + const s1 = lerp(s01, s11, yd); + + array[o + k] = lerp(s0, s1, zd); + } + } + } - const s00 = Color.interpolate(s000, s100, xd); - const s01 = Color.interpolate(s001, s101, xd); - const s10 = Color.interpolate(s010, s110, xd); - const s11 = Color.interpolate(s011, s111, xd); + return image; +} - const s0 = Color.interpolate(s00, s10, yd); - const s1 = Color.interpolate(s01, s11, yd); +// - Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3); - } +function isSupportedColorType(x: string): x is 'group' | 'groupInstance' { + return x === 'group' || x === 'groupInstance'; +} + +export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) { + if (!isSupportedColorType(values.dColorType.ref.value)) return; + + const smoothingData = calcMeshColorSmoothing({ + 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, + itemSize: 3 + }, resolution, stride, webgl, colorTexture); + + if (smoothingData.kind === 'volume') { + 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.gridTransform); + } else if (smoothingData.kind === 'vertex') { + ValueCell.updateIfChanged(values.dColorType, smoothingData.type); + ValueCell.update(values.tColor, smoothingData.texture); + ValueCell.update(values.uColorTexDim, smoothingData.texDim); } +} - return image; +function isSupportedOverpaintType(x: string): x is 'groupInstance' { + return x === 'groupInstance'; +} + +export function applyMeshOverpaintSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) { + if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return; + + const smoothingData = calcMeshColorSmoothing({ + 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.tOverpaint.ref.value, + colorType: values.dOverpaintType.ref.value, + boundingSphere: values.boundingSphere.ref.value, + invariantBoundingSphere: values.invariantBoundingSphere.ref.value, + itemSize: 4 + }, resolution, stride, webgl, colorTexture); + if (smoothingData.kind === 'volume') { + ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type); + ValueCell.update(values.tOverpaintGrid, smoothingData.texture); + ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim); + ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim); + ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform); + } else if (smoothingData.kind === 'vertex') { + ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type); + ValueCell.update(values.tOverpaint, smoothingData.texture); + ValueCell.update(values.uOverpaintTexDim, smoothingData.texDim); + } +} + +function isSupportedTransparencyType(x: string): x is 'groupInstance' { + return x === 'groupInstance'; +} + +export function applyMeshTransparencySmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) { + if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return; + + const smoothingData = calcMeshColorSmoothing({ + 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.tTransparency.ref.value, + colorType: values.dTransparencyType.ref.value, + boundingSphere: values.boundingSphere.ref.value, + invariantBoundingSphere: values.invariantBoundingSphere.ref.value, + itemSize: 1 + }, resolution, stride, webgl, colorTexture); + if (smoothingData.kind === 'volume') { + ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type); + ValueCell.update(values.tTransparencyGrid, smoothingData.texture); + ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim); + ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim); + ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform); + } else if (smoothingData.kind === 'vertex') { + ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type); + ValueCell.update(values.tTransparency, smoothingData.texture); + ValueCell.update(values.uTransparencyTexDim, smoothingData.texDim); + } } diff --git a/src/mol-geo/geometry/overpaint-data.ts b/src/mol-geo/geometry/overpaint-data.ts index 7df9bf453b6a4abe3e9ba46a293de27dc6b3ab37..70ab4e67a41adcaad028f264b8ff98ee1ce52d9f 100644 --- a/src/mol-geo/geometry/overpaint-data.ts +++ b/src/mol-geo/geometry/overpaint-data.ts @@ -1,18 +1,24 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { ValueCell } from '../../mol-util/value-cell'; -import { Vec2 } from '../../mol-math/linear-algebra'; +import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra'; import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; import { Color } from '../../mol-util/color'; +import { createNullTexture, Texture } from '../../mol-gl/webgl/texture'; export type OverpaintData = { tOverpaint: ValueCell<TextureImage<Uint8Array>> uOverpaintTexDim: ValueCell<Vec2> dOverpaint: ValueCell<boolean>, + + tOverpaintGrid: ValueCell<Texture>, + uOverpaintGridDim: ValueCell<Vec3>, + uOverpaintGridTransform: ValueCell<Vec4>, + dOverpaintType: ValueCell<string>, } export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) { @@ -40,6 +46,11 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O tOverpaint: ValueCell.create(overpaint), uOverpaintTexDim: ValueCell.create(Vec2.create(overpaint.width, overpaint.height)), dOverpaint: ValueCell.create(count > 0), + + tOverpaintGrid: ValueCell.create(createNullTexture()), + uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)), + uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), + dOverpaintType: ValueCell.create('groupInstance'), }; } } @@ -55,6 +66,11 @@ export function createEmptyOverpaint(overpaintData?: OverpaintData): OverpaintDa tOverpaint: ValueCell.create(emptyOverpaintTexture), uOverpaintTexDim: ValueCell.create(Vec2.create(1, 1)), dOverpaint: ValueCell.create(false), + + tOverpaintGrid: ValueCell.create(createNullTexture()), + uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)), + uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), + dOverpaintType: ValueCell.create('groupInstance'), }; } } \ No newline at end of file diff --git a/src/mol-geo/geometry/texture-mesh/color-smoothing.ts b/src/mol-geo/geometry/texture-mesh/color-smoothing.ts index 6cbc7e51442b5fc5c693ceccc5c359de9b5fb51e..3e68cfd6ebd5719dbf44585845e7480c2e03292f 100644 --- a/src/mol-geo/geometry/texture-mesh/color-smoothing.ts +++ b/src/mol-geo/geometry/texture-mesh/color-smoothing.ts @@ -18,7 +18,8 @@ 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'; +import { isWebGL2 } from '../../../mol-gl/webgl/compat'; +import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh'; export const ColorAccumulateSchema = { drawCount: ValueSpec('number'), @@ -38,7 +39,7 @@ export const ColorAccumulateSchema = { tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'), uColorTexDim: UniformSpec('v2'), - tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'), + tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']), uCurrentSlice: UniformSpec('f'), @@ -50,6 +51,7 @@ export const ColorAccumulateSchema = { }; type ColorAccumulateValues = Values<typeof ColorAccumulateSchema> const ColorAccumulateName = 'color-accumulate'; +const ColorCountName = 'color-count'; interface AccumulateInput { vertexCount: number @@ -59,7 +61,7 @@ interface AccumulateInput { instanceBuffer: Float32Array positionTexture: Texture groupTexture: Texture - colorData: TextureImage<Uint8Array> + colorData: Texture colorType: 'group' | 'groupInstance' } @@ -96,7 +98,7 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box: 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.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.getWidth(), input.colorData.getHeight())); ValueCell.update(v.tColor, input.colorData); ValueCell.updateIfChanged(v.dColorType, input.colorType); @@ -135,7 +137,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b tPosition: ValueCell.create(input.positionTexture), tGroup: ValueCell.create(input.groupTexture), - uColorTexDim: ValueCell.create(Vec2.create(input.colorData.width, input.colorData.height)), + uColorTexDim: ValueCell.create(Vec2.create(input.colorData.getWidth(), input.colorData.getHeight())), tColor: ValueCell.create(input.colorData), dColorType: ValueCell.create(input.colorType), @@ -148,7 +150,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b }; const schema = { ...ColorAccumulateSchema }; - const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag); + const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag, { drawBuffers: 'required' }); const renderItem = createComputeRenderItem(ctx, 'points', shaderCode, schema, values); return createComputeRenderable(renderItem, values); @@ -172,30 +174,33 @@ export const ColorNormalizeSchema = { ...QuadSchema, tColor: TextureSpec('texture', 'rgba', 'float', 'nearest'), + tCount: TextureSpec('texture', 'alpha', 'float', 'nearest'), uTexSize: UniformSpec('v2'), }; type ColorNormalizeValues = Values<typeof ColorNormalizeSchema> const ColorNormalizeName = 'color-normalize'; -function getNormalizeRenderable(ctx: WebGLContext, color: Texture): ComputeRenderable<ColorNormalizeValues> { +function getNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture): ComputeRenderable<ColorNormalizeValues> { if (ctx.namedComputeRenderables[ColorNormalizeName]) { const v = ctx.namedComputeRenderables[ColorNormalizeName].values as ColorNormalizeValues; ValueCell.update(v.tColor, color); + ValueCell.update(v.tCount, count); 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); + ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color, count); } return ctx.namedComputeRenderables[ColorNormalizeName]; } -function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture) { +function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture) { const values: ColorNormalizeValues = { ...QuadValues, tColor: ValueCell.create(color), + tCount: ValueCell.create(count), uTexSize: ValueCell.create(Vec2.create(color.getWidth(), color.getHeight())), }; @@ -247,6 +252,9 @@ interface ColorSmoothingInput extends AccumulateInput { } export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl: WebGLContext, texture?: Texture) { + const { drawBuffers } = webgl.extensions; + if (!drawBuffers) throw new Error('need WebGL draw buffers'); + const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl; const isInstanceType = input.colorType.endsWith('Instance'); @@ -263,29 +271,55 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim); // 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'); + if (!webgl.namedFramebuffers[ColorAccumulateName]) { + webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer(); + } + const framebuffer = webgl.namedFramebuffers[ColorAccumulateName]; + + if (isWebGL2(gl)) { + if (!webgl.namedTextures[ColorAccumulateName]) { + webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat + ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest') + : resources.texture('image-float32', 'rgba', 'float', 'nearest'); + } + + if (!webgl.namedTextures[ColorCountName]) { + webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'alpha', 'float', 'nearest'); + } + } else { + // in webgl1 drawbuffers must be in the same format for some reason + // this is quite wasteful but good enough for medium size meshes + + if (!webgl.namedTextures[ColorAccumulateName]) { + webgl.namedTextures[ColorAccumulateName] = resources.texture('image-float32', 'rgba', 'float', 'nearest'); + } + + if (!webgl.namedTextures[ColorCountName]) { + webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'rgba', 'float', 'nearest'); + } } + const accumulateTexture = webgl.namedTextures[ColorAccumulateName]; + const countTexture = webgl.namedTextures[ColorCountName]; + accumulateTexture.define(width, height); + countTexture.define(width, height); + + accumulateTexture.attachFramebuffer(framebuffer, 0); + countTexture.attachFramebuffer(framebuffer, 1); const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride); + state.currentRenderItemId = -1; - // + framebuffer.bind(); + drawBuffers.drawBuffers([ + drawBuffers.COLOR_ATTACHMENT0, + drawBuffers.COLOR_ATTACHMENT1, + ]); 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); @@ -310,21 +344,31 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu currX += dx; } + accumulateTexture.detachFramebuffer(framebuffer, 0); + countTexture.detachFramebuffer(framebuffer, 1); + drawBuffers.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE]); + // 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); + // printTextureImage({ array: accImage, width, height }, { scale: 1 }); + + // const cntImage = new Float32Array(width * height * 4); + // countTexture.attachFramebuffer(framebuffer, 0); + // webgl.readPixels(0, 0, width, height, cntImage); + // console.log(cntImage); + // printTextureImage({ array: cntImage, width, height }, { scale: 1 }); // normalize - if (!texture) texture = resources.texture('image-uint8', 'rgb', 'ubyte', 'linear'); + if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); texture.define(width, height); - const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture); + const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture, countTexture); + state.currentRenderItemId = -1; setNormalizeDefaults(webgl); - state.currentRenderItemId = -1; texture.attachFramebuffer(framebuffer, 0); gl.viewport(0, 0, width, height); gl.scissor(0, 0, width, height); @@ -335,10 +379,124 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu // texture.attachFramebuffer(framebuffer, 0); // webgl.readPixels(0, 0, width, height, normImage); // console.log(normImage); - // printTextureImage({ array: normImage, width, height }, 1 / 4); + // printTextureImage({ array: normImage, width, height }, { scale: 1 }); const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor); const type = isInstanceType ? 'volumeInstance' : 'volume'; return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type }; } + +// + +const ColorSmoothingRgbName = 'color-smoothing-rgb'; +const ColorSmoothingRgbaName = 'color-smoothing-rgba'; +const ColorSmoothingAlphaName = 'color-smoothing-alpha'; + +function isSupportedColorType(x: string): x is 'group' | 'groupInstance' { + return x === 'group' || x === 'groupInstance'; +} + +export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) { + if (!isSupportedColorType(values.dColorType.ref.value)) return; + + stride *= 3; // triple because TextureMesh is never indexed (no elements buffer) + + if (!webgl.namedTextures[ColorSmoothingRgbName]) { + webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest'); + } + const colorData = webgl.namedTextures[ColorSmoothingRgbName]; + colorData.load(values.tColor.ref.value); + + const smoothingData = calcTextureMeshColorSmoothing({ + 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, + colorType: values.dColorType.ref.value, + boundingSphere: values.boundingSphere.ref.value, + invariantBoundingSphere: values.invariantBoundingSphere.ref.value, + }, resolution, stride, webgl, 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.gridTransform); +} + +function isSupportedOverpaintType(x: string): x is 'groupInstance' { + return x === 'groupInstance'; +} + +export function applyTextureMeshOverpaintSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) { + if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return; + + stride *= 3; // triple because TextureMesh is never indexed (no elements buffer) + + if (!webgl.namedTextures[ColorSmoothingRgbaName]) { + webgl.namedTextures[ColorSmoothingRgbaName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); + } + const colorData = webgl.namedTextures[ColorSmoothingRgbaName]; + colorData.load(values.tOverpaint.ref.value); + + const smoothingData = calcTextureMeshColorSmoothing({ + 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, + colorType: values.dOverpaintType.ref.value, + boundingSphere: values.boundingSphere.ref.value, + invariantBoundingSphere: values.invariantBoundingSphere.ref.value, + }, resolution, stride, webgl, colorTexture); + + ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type); + ValueCell.update(values.tOverpaintGrid, smoothingData.texture); + ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim); + ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim); + ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform); +} + +function isSupportedTransparencyType(x: string): x is 'groupInstance' { + return x === 'groupInstance'; +} + +export function applyTextureMeshTransparencySmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) { + if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return; + + stride *= 3; // triple because TextureMesh is never indexed (no elements buffer) + + if (!webgl.namedTextures[ColorSmoothingAlphaName]) { + webgl.namedTextures[ColorSmoothingAlphaName] = webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'nearest'); + } + const colorData = webgl.namedTextures[ColorSmoothingAlphaName]; + colorData.load(values.tTransparency.ref.value); + + const smoothingData = calcTextureMeshColorSmoothing({ + 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, + colorType: values.dTransparencyType.ref.value, + boundingSphere: values.boundingSphere.ref.value, + invariantBoundingSphere: values.invariantBoundingSphere.ref.value, + }, resolution, stride, webgl, colorTexture); + + ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type); + ValueCell.update(values.tTransparencyGrid, smoothingData.texture); + ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim); + ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim); + ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform); +} diff --git a/src/mol-geo/geometry/transparency-data.ts b/src/mol-geo/geometry/transparency-data.ts index ff08ccb4cca547811461659949cd1a9247ed3228..4c4f94f568b688cc141d04513400eda71dad68ee 100644 --- a/src/mol-geo/geometry/transparency-data.ts +++ b/src/mol-geo/geometry/transparency-data.ts @@ -1,18 +1,24 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { ValueCell } from '../../mol-util/value-cell'; -import { Vec2 } from '../../mol-math/linear-algebra'; +import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra'; import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; +import { createNullTexture, Texture } from '../../mol-gl/webgl/texture'; export type TransparencyData = { tTransparency: ValueCell<TextureImage<Uint8Array>> uTransparencyTexDim: ValueCell<Vec2> dTransparency: ValueCell<boolean>, transparencyAverage: ValueCell<number>, + + tTransparencyGrid: ValueCell<Texture>, + uTransparencyGridDim: ValueCell<Vec3>, + uTransparencyGridTransform: ValueCell<Vec4>, + dTransparencyType: ValueCell<string>, } export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) { @@ -48,6 +54,11 @@ export function createTransparency(count: number, transparencyData?: Transparenc uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)), dTransparency: ValueCell.create(count > 0), transparencyAverage: ValueCell.create(0), + + tTransparencyGrid: ValueCell.create(createNullTexture()), + uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)), + uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), + dTransparencyType: ValueCell.create('groupInstance'), }; } } @@ -64,6 +75,11 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)), dTransparency: ValueCell.create(false), transparencyAverage: ValueCell.create(0), + + tTransparencyGrid: ValueCell.create(createNullTexture()), + uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)), + uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)), + dTransparencyType: ValueCell.create('groupInstance'), }; } } \ No newline at end of file diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 2b2bf6a5e851d851af275a76bf60f3aceaba76b1..24eb04ac455357003c43003f0a0e267e7c3b7389 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -224,6 +224,11 @@ export const OverpaintSchema = { uOverpaintTexDim: UniformSpec('v2'), tOverpaint: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'), dOverpaint: DefineSpec('boolean'), + + uOverpaintGridDim: UniformSpec('v3'), + uOverpaintGridTransform: UniformSpec('v4'), + tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']), } as const; export type OverpaintSchema = typeof OverpaintSchema export type OverpaintValues = Values<OverpaintSchema> @@ -233,6 +238,11 @@ export const TransparencySchema = { tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), transparencyAverage: ValueSpec('number'), + + uTransparencyGridDim: UniformSpec('v3'), + uTransparencyGridTransform: UniformSpec('v4'), + tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'), + dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']), } as const; export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts index 4ecf5965ee9b0a1c0b2951bdbc127e36e4075e82..197d86b53342a3dd6fd2de899676705a95df897c 100644 --- a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts @@ -13,11 +13,11 @@ export const assign_color_varying = ` #elif defined(dColorType_vertexInstance) vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb; #elif defined(dColorType_volume) - vec3 gridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim; - vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb; + vec3 cgridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim; + vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb; #elif defined(dColorType_volumeInstance) - vec3 gridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim; - vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb; + vec3 cgridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim; + vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb; #endif #ifdef dUsePalette @@ -25,7 +25,21 @@ export const assign_color_varying = ` #endif #ifdef dOverpaint - vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim); + #if defined(dOverpaintType_groupInstance) + vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim); + #elif defined(dOverpaintType_vertexInstance) + vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim); + #elif defined(dOverpaintType_volumeInstance) + vec3 ogridPos = (uOverpaintGridTransform.w * (vModelPosition - uOverpaintGridTransform.xyz)) / uOverpaintGridDim; + vOverpaint = texture3dFrom2dLinear(tOverpaintGrid, ogridPos, uOverpaintGridDim, uOverpaintTexDim); + #endif + + // pre-mix to avoid darkening due to empty overpaint + #ifdef dColorType_uniform + vOverpaint.rgb = mix(uColor.rgb, vOverpaint.rgb, vOverpaint.a); + #else + vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a); + #endif #endif #elif defined(dRenderVariant_pick) #if defined(dRenderVariant_pickObject) @@ -39,6 +53,14 @@ export const assign_color_varying = ` #ifdef dTransparency vGroup = group; - vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a; + + #if defined(dTransparencyType_groupInstance) + vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a; + #elif defined(dTransparencyType_vertexInstance) + vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a; + #elif defined(dTransparencyType_volumeInstance) + vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim; + vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a; + #endif #endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts index 9d550b8f14277d2fd128601971edcc78cf9d9136..8215ea05732e87cd5fc0d38656e34541eb34a0d0 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts @@ -54,6 +54,9 @@ export const assign_material_color = ` // apply screendoor transparency #if defined(dTransparency) float ta = 1.0 - vTransparency; + #if defined(dRenderVariant_colorWboit) + if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better with wboit + #endif #if defined(dRenderVariant_pick) if (ta < uPickingAlphaThreshold) diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts index 83a3b98bd3f38c4d0e404664838ea4dbbb67e843..ee9f46a5f8849651f8bbae0b9c8c79ead82a33df 100644 --- a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts @@ -18,9 +18,17 @@ export const color_vert_params = ` #endif #ifdef dOverpaint - varying vec4 vOverpaint; - uniform vec2 uOverpaintTexDim; - uniform sampler2D tOverpaint; + #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance) + varying vec4 vOverpaint; + uniform vec2 uOverpaintTexDim; + uniform sampler2D tOverpaint; + #elif defined(dOverpaintType_volumeInstance) + varying vec4 vOverpaint; + uniform vec2 uOverpaintTexDim; + uniform vec3 uOverpaintGridDim; + uniform vec4 uOverpaintGridTransform; + uniform sampler2D tOverpaintGrid; + #endif #endif #elif defined(dRenderVariant_pick) #if __VERSION__ == 100 @@ -32,9 +40,17 @@ export const color_vert_params = ` #ifdef dTransparency varying float vGroup; - varying float vTransparency; - uniform vec2 uTransparencyTexDim; - uniform sampler2D tTransparency; + #if defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance) + varying float vTransparency; + uniform vec2 uTransparencyTexDim; + uniform sampler2D tTransparency; + #elif defined(dTransparencyType_volumeInstance) + varying float vTransparency; + uniform vec2 uTransparencyTexDim; + uniform vec3 uTransparencyGridDim; + uniform vec4 uTransparencyGridTransform; + uniform sampler2D tTransparencyGrid; + #endif #endif #ifdef dUsePalette diff --git a/src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts b/src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts index ff2f100422a91f91689b4a6e5064b60b7fab67a8..46944e0b8b7e7c4983a0ae62d5e61ceb8fb54b2f 100644 --- a/src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts +++ b/src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts @@ -8,7 +8,7 @@ export const accumulate_frag = ` precision highp float; varying vec3 vPosition; -varying vec3 vColor; +varying vec4 vColor; uniform float uCurrentSlice; uniform float uCurrentX; @@ -23,6 +23,8 @@ void main() { float dist = distance(fragPos, vPosition); if (dist > p) discard; - gl_FragColor = vec4(vColor, 1.0) * (p - dist); + float f = p - dist; + gl_FragColor = vColor * f; + gl_FragData[1] = vec4(f); } `; \ No newline at end of file diff --git a/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts b/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts index 466c497cccf4c0aa5ae1f13357b0473337e72e0e..85eacd029a297cc607ab71f5a3f38a1f8e504d9a 100644 --- a/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts +++ b/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts @@ -27,7 +27,7 @@ uniform vec2 uColorTexDim; uniform sampler2D tColor; varying vec3 vPosition; -varying vec3 vColor; +varying vec4 vColor; uniform vec3 uBboxSize; uniform vec3 uBboxMin; @@ -43,9 +43,9 @@ void main() { gl_Position = vec4(((position - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0); #if defined(dColorType_group) - vColor = readFromTexture(tColor, group, uColorTexDim).rgb; + vColor = readFromTexture(tColor, group, uColorTexDim); #elif defined(dColorType_groupInstance) - vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb; + vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim); #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts b/src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts index 6769c414fe91678b6e0c175bcb09898ec69c95df..2b10895aac2a0ba8a7b751aa1bbf07c0c1057860 100644 --- a/src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts +++ b/src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts @@ -9,12 +9,14 @@ precision highp float; precision highp sampler2D; uniform sampler2D tColor; +uniform sampler2D tCount; uniform vec2 uTexSize; void main(void) { vec2 coords = gl_FragCoord.xy / uTexSize; vec4 color = texture2D(tColor, coords); + float count = texture2D(tCount, coords).r; - gl_FragColor.rgb = color.rgb / color.a; + gl_FragColor = color / count; } `; \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index e6fc9d32471a93a0a8dcc115f17dd373edf4093d..de151acad04286bb4132984f7393f28c274753e2 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -110,9 +110,10 @@ uniform mat4 uCartnToUnit; #endif #ifdef dOverpaint - varying vec4 vOverpaint; - uniform vec2 uOverpaintTexDim; - uniform sampler2D tOverpaint; + #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance) + uniform vec2 uOverpaintTexDim; + uniform sampler2D tOverpaint; + #endif #endif #endif @@ -192,6 +193,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { float nextValue; vec3 color = vec3(0.45, 0.55, 0.8); + vec4 overpaint = vec4(0.0); + vec3 gradient = vec3(1.0); vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0); vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0); @@ -297,6 +300,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb; #endif + #ifdef dOverpaint + #if defined(dOverpaintType_groupInstance) + overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim); + #elif defined(dOverpaintType_vertexInstance) + overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount)).rgb; + #endif + + color = mix(color, overpaint.rgb, overpaint.a); + #endif + // handle flipping and negative isosurfaces #ifdef dFlipSided bool flipped = value < uIsoValue.y; // flipped diff --git a/src/mol-gl/shader/mesh.vert.ts b/src/mol-gl/shader/mesh.vert.ts index 15cae0285ffa0ed2fb7d343e97481b48152b6044..37b4610912318218924953c90210f2b3a3c05858 100644 --- a/src/mol-gl/shader/mesh.vert.ts +++ b/src/mol-gl/shader/mesh.vert.ts @@ -14,10 +14,7 @@ precision highp sampler2D; #include common_vert_params #include color_vert_params #include common_clip - -#if defined(dColorType_grid) - #include texture3d_from_2d_linear -#endif +#include texture3d_from_2d_linear #ifdef dGeoTexture uniform vec2 uGeoTexDim; diff --git a/src/mol-plugin-state/transforms/representation.ts b/src/mol-plugin-state/transforms/representation.ts index 1b257422f4ad930792ac663b4daf07dd5b4d7197..a831fd4632e876e06e591394c942911964f2ad16 100644 --- a/src/mol-plugin-state/transforms/representation.ts +++ b/src/mol-plugin-state/transforms/representation.ts @@ -24,7 +24,7 @@ import { unwindStructureAssembly, explodeStructure, spinStructure, SpinStructure import { Color } from '../../mol-util/color'; import { Overpaint } from '../../mol-theme/overpaint'; import { Transparency } from '../../mol-theme/transparency'; -import { BaseGeometry } from '../../mol-geo/geometry/base'; +import { BaseGeometry, hasColorSmoothingProp } from '../../mol-geo/geometry/base'; import { Script } from '../../mol-script/script'; import { UnitcellParams, UnitcellRepresentation, getUnitcellData } from '../../mol-repr/shape/model/unitcell'; import { DistanceParams, DistanceRepresentation } from '../../mol-repr/shape/loci/distance'; @@ -328,25 +328,31 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI }, apply({ a, params }) { const structure = a.data.sourceData; + const geometryVersion = a.data.repr.geometryVersion; const overpaint = Overpaint.ofScript(params.layers, structure); return new SO.Molecule.Structure.Representation3DState({ state: { overpaint }, initialState: { overpaint: Overpaint.Empty }, - info: structure, + info: { structure, geometryVersion }, repr: a.data.repr }, { label: `Overpaint (${overpaint.layers.length} Layers)` }); }, update({ a, b, newParams, oldParams }) { - const oldStructure = b.data.info as Structure; + const info = b.data.info as { structure: Structure, geometryVersion: number }; const newStructure = a.data.sourceData; - if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate; + if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate; if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate; + const newGeometryVersion = a.data.repr.geometryVersion; + // smoothing needs to be re-calculated when geometry changes + if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged; + const oldOverpaint = b.data.state.overpaint!; const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure); if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged; + info.geometryVersion = newGeometryVersion; b.data.state.overpaint = newOverpaint; b.data.repr = a.data.repr; b.label = `Overpaint (${newOverpaint.layers.length} Layers)`; @@ -380,25 +386,31 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI }, apply({ a, params }) { const structure = a.data.sourceData; + const geometryVersion = a.data.repr.geometryVersion; const overpaint = Overpaint.ofBundle(params.layers, structure); return new SO.Molecule.Structure.Representation3DState({ state: { overpaint }, initialState: { overpaint: Overpaint.Empty }, - info: structure, + info: { structure, geometryVersion }, repr: a.data.repr }, { label: `Overpaint (${overpaint.layers.length} Layers)` }); }, update({ a, b, newParams, oldParams }) { - const oldStructure = b.data.info as Structure; + const info = b.data.info as { structure: Structure, geometryVersion: number }; const newStructure = a.data.sourceData; - if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate; + if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate; if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate; + const newGeometryVersion = a.data.repr.geometryVersion; + // smoothing needs to be re-calculated when geometry changes + if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged; + const oldOverpaint = b.data.state.overpaint!; const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure); if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged; + info.geometryVersion = newGeometryVersion; b.data.state.overpaint = newOverpaint; b.data.repr = a.data.repr; b.label = `Overpaint (${newOverpaint.layers.length} Layers)`; @@ -429,24 +441,31 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui }, apply({ a, params }) { const structure = a.data.sourceData; + const geometryVersion = a.data.repr.geometryVersion; const transparency = Transparency.ofScript(params.layers, structure); return new SO.Molecule.Structure.Representation3DState({ state: { transparency }, initialState: { transparency: Transparency.Empty }, - info: structure, + info: { structure, geometryVersion }, repr: a.data.repr }, { label: `Transparency (${transparency.layers.length} Layers)` }); }, update({ a, b, newParams, oldParams }) { - const structure = b.data.info as Structure; - if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate; + const info = b.data.info as { structure: Structure, geometryVersion: number }; + const newStructure = a.data.sourceData; + if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate; if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate; + const newGeometryVersion = a.data.repr.geometryVersion; + // smoothing needs to be re-calculated when geometry changes + if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged; + const oldTransparency = b.data.state.transparency!; - const newTransparency = Transparency.ofScript(newParams.layers, structure); + const newTransparency = Transparency.ofScript(newParams.layers, newStructure); if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged; + info.geometryVersion = newGeometryVersion; b.data.state.transparency = newTransparency; b.data.repr = a.data.repr; b.label = `Transparency (${newTransparency.layers.length} Layers)`; @@ -478,24 +497,31 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui }, apply({ a, params }) { const structure = a.data.sourceData; + const geometryVersion = a.data.repr.geometryVersion; const transparency = Transparency.ofBundle(params.layers, structure); return new SO.Molecule.Structure.Representation3DState({ state: { transparency }, initialState: { transparency: Transparency.Empty }, - info: structure, + info: { structure, geometryVersion }, repr: a.data.repr }, { label: `Transparency (${transparency.layers.length} Layers)` }); }, update({ a, b, newParams, oldParams }) { - const structure = b.data.info as Structure; - if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate; + const info = b.data.info as { structure: Structure, geometryVersion: number }; + const newStructure = a.data.sourceData; + if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate; if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate; + const newGeometryVersion = a.data.repr.geometryVersion; + // smoothing needs to be re-calculated when geometry changes + if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged; + const oldTransparency = b.data.state.transparency!; - const newTransparency = Transparency.ofBundle(newParams.layers, structure); + const newTransparency = Transparency.ofBundle(newParams.layers, newStructure); if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged; + info.geometryVersion = newGeometryVersion; b.data.state.transparency = newTransparency; b.data.repr = a.data.repr; b.label = `Transparency (${newTransparency.layers.length} Layers)`; diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts index 235c80df755c19fd5771ac586e849ad22371a148..4dea5f1486570999512cdd6e5d0433f49f3126c3 100644 --- a/src/mol-repr/structure/complex-representation.ts +++ b/src/mol-repr/structure/complex-representation.ts @@ -106,12 +106,12 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, if (state.overpaint !== undefined && visual) { // Remap loci from equivalent structure to the current structure const remappedOverpaint = Overpaint.remap(state.overpaint, _structure); - visual.setOverpaint(remappedOverpaint); + visual.setOverpaint(remappedOverpaint, webgl); } if (state.transparency !== undefined && visual) { // Remap loci from equivalent structure to the current structure const remappedTransparency = Transparency.remap(state.transparency, _structure); - visual.setTransparency(remappedTransparency); + visual.setTransparency(remappedTransparency, webgl); } if (state.clipping !== undefined && visual) { // Remap loci from equivalent structure to the current structure diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 7722d33ecedb98643d53c4ca0b72b3c240568455..cda357e6b7d52fdc0533f32517368effa973bf98 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -258,11 +258,13 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) { Visual.setTransform(renderObject, matrix, instanceMatrices); }, - setOverpaint(overpaint: Overpaint) { - Visual.setOverpaint(renderObject, overpaint, lociApply, true); + setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) { + const smoothing = { geometry, props: currentProps, webgl }; + Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing); }, - setTransparency(transparency: Transparency) { - Visual.setTransparency(renderObject, transparency, lociApply, true); + setTransparency(transparency: Transparency, webgl?: WebGLContext) { + const smoothing = { geometry, props: currentProps, webgl }; + Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing); }, setClipping(clipping: Clipping) { Visual.setClipping(renderObject, clipping, lociApply, true); diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index ceccd54b7a3baa84ff9dbded3bca6bd69d36662e..226dbdaee7980a6cb94427e20b71e1db0d8a79f5 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -223,8 +223,8 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct if (visible !== undefined) visual.setVisibility(visible); if (alphaFactor !== undefined) visual.setAlphaFactor(alphaFactor); if (pickable !== undefined) visual.setPickable(pickable); - if (overpaint !== undefined) visual.setOverpaint(overpaint); - if (transparency !== undefined) visual.setTransparency(transparency); + if (overpaint !== undefined) visual.setOverpaint(overpaint, webgl); + if (transparency !== undefined) visual.setTransparency(transparency, webgl); if (clipping !== undefined) visual.setClipping(clipping); if (transform !== undefined) visual.setTransform(transform); if (unitTransforms !== undefined) { diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index 38eb5edd0447182eafa0b24bdf3823cb2c26847d..773c892895b6da8f410093680e95894ea37b816c 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -323,11 +323,13 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) { Visual.setTransform(renderObject, matrix, instanceMatrices); }, - setOverpaint(overpaint: Overpaint) { - Visual.setOverpaint(renderObject, overpaint, lociApply, true); + setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) { + const smoothing = { geometry, props: currentProps, webgl }; + Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing); }, - setTransparency(transparency: Transparency) { - Visual.setTransparency(renderObject, transparency, lociApply, true); + setTransparency(transparency: Transparency, webgl?: WebGLContext) { + const smoothing = { geometry, props: currentProps, webgl }; + Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing); }, setClipping(clipping: Clipping) { Visual.setClipping(renderObject, clipping, lociApply, true); diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts index aa7e22d4c466d3b309b84c53096c0d19084f18ef..b1a5e967365e08cab106a9ced25bac164156d55e 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts +++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts @@ -23,7 +23,9 @@ 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, ColorSmoothingParams, getColorSmoothingProps } from './util/color'; +import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing'; +import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/texture-mesh/color-smoothing'; +import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base'; const SharedParams = { ...GaussianDensityParams, @@ -131,7 +133,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss }, processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => { const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta; - const csp = getColorSmoothingProps(props, theme, resolution); + const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution); if (csp) { applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture); (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value; @@ -191,7 +193,7 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV }, processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => { const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta; - const csp = getColorSmoothingProps(props, theme, resolution); + const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution); if (csp) { applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture); (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value; @@ -264,7 +266,7 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua }, processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => { const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta; - const csp = getColorSmoothingProps(props, theme, resolution); + const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution); if (csp && webgl) { applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture); (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value; @@ -340,7 +342,7 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C }, processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => { const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta; - const csp = getColorSmoothingProps(props, theme, resolution); + const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution); if (csp && webgl) { applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture); (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value; diff --git a/src/mol-repr/structure/visual/molecular-surface-mesh.ts b/src/mol-repr/structure/visual/molecular-surface-mesh.ts index 9b4c374764261288ba03f28498e574dd541ea326..981a6c6cea122409081b46a2ce52ad0ccdc105dd 100644 --- a/src/mol-repr/structure/visual/molecular-surface-mesh.ts +++ b/src/mol-repr/structure/visual/molecular-surface-mesh.ts @@ -20,7 +20,8 @@ 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, ColorSmoothingParams, getColorSmoothingProps } from './util/color'; +import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing'; +import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base'; export const MolecularSurfaceMeshParams = { ...UnitsMeshParams, @@ -87,7 +88,7 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole }, processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => { const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta; - const csp = getColorSmoothingProps(props, theme, resolution); + const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution); if (csp) { applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture); (geometry.meta as MolecularSurfaceMeta).colorTexture = values.tColorGrid.ref.value; diff --git a/src/mol-repr/structure/visual/util/color.ts b/src/mol-repr/structure/visual/util/color.ts deleted file mode 100644 index 64e5e22d4c37f0eb911a9cc705ce3c531a7ab3a5..0000000000000000000000000000000000000000 --- a/src/mol-repr/structure/visual/util/color.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 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 { smoothstep } from '../../../../mol-math/interpolate'; -import { Theme } from '../../../../mol-theme/theme'; -import { ValueCell } from '../../../../mol-util'; -import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; - -export const ColorSmoothingParams = { - smoothColors: PD.MappedStatic('auto', { - auto: PD.Group({}), - on: PD.Group({ - resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }), - sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }), - }), - off: PD.Group({}) - }), -}; -export type ColorSmoothingParams = typeof ColorSmoothingParams - -export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number) { - if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3) { - let stride = 3; - if (props.smoothColors.name === 'on') { - resolution *= props.smoothColors.params.resolutionFactor; - stride = props.smoothColors.params.sampleStride; - } else { - // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8 - resolution *= 2 - smoothstep(0, 1.1, resolution); - resolution = Math.max(0.5, resolution); - if (resolution > 1.2) stride = 2; - } - return { resolution, stride }; - }; -} - -function isSupportedColorType(x: string): x is 'group' | 'groupInstance' { - return x === 'group' || x === 'groupInstance'; -} - -export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) { - if (!isSupportedColorType(values.dColorType.ref.value)) return; - - const smoothingData = calcMeshColorSmoothing({ - 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, stride, webgl, colorTexture); - - if (smoothingData.kind === 'volume') { - 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.gridTransform); - } else if (smoothingData.kind === 'vertex') { - ValueCell.updateIfChanged(values.dColorType, smoothingData.type); - ValueCell.update(values.tColor, smoothingData.texture); - ValueCell.update(values.uColorTexDim, smoothingData.texDim); - } -} - -export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) { - if (!isSupportedColorType(values.dColorType.ref.value)) return; - - stride *= 3; // triple because TextureMesh is never indexed (no elements buffer) - - const smoothingData = calcTextureMeshColorSmoothing({ - 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, stride, webgl, 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.gridTransform); -} \ No newline at end of file diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index 856902b21afd26cfec10bfed8e2c50ab21f004ba..24871466ee0fc9362242f0f7e60a12ec897b0807 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -24,6 +24,11 @@ import { createTransparency, clearTransparency, applyTransparencyValue, getTrans import { Clipping } from '../mol-theme/clipping'; import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data'; import { getMarkersAverage } from '../mol-geo/geometry/marker-data'; +import { Texture } from '../mol-gl/webgl/texture'; +import { Geometry } from '../mol-geo/geometry/geometry'; +import { getColorSmoothingProps, hasColorSmoothingProp } from '../mol-geo/geometry/base'; +import { applyMeshOverpaintSmoothing, applyMeshTransparencySmoothing } from '../mol-geo/geometry/mesh/color-smoothing'; +import { applyTextureMeshOverpaintSmoothing, applyTextureMeshTransparencySmoothing } from '../mol-geo/geometry/texture-mesh/color-smoothing'; export interface VisualContext { readonly runtime: RuntimeContext @@ -44,8 +49,8 @@ interface Visual<D, P extends PD.Params> { setPickable: (pickable: boolean) => void setColorOnly: (colorOnly: boolean) => void setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array | null) => void - setOverpaint: (overpaint: Overpaint) => void - setTransparency: (transparency: Transparency) => void + setOverpaint: (overpaint: Overpaint, webgl?: WebGLContext) => void + setTransparency: (transparency: Transparency, webgl?: WebGLContext) => void setClipping: (clipping: Clipping) => void destroy: () => void mustRecreate?: (data: D, props: PD.Values<P>, webgl?: WebGLContext) => boolean @@ -134,10 +139,22 @@ namespace Visual { return changed; } - export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean) { + type SurfaceMeta = { + resolution?: number + overpaintTexture?: Texture + transparencyTexture?: Texture + } + + type SmoothingContext = { + geometry: Geometry, + props: PD.Values<any>, + webgl?: WebGLContext + } + + export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) { if (!renderObject) return; - const { tOverpaint, uGroupCount, instanceCount } = renderObject.values; + const { tOverpaint, dOverpaintType, uGroupCount, instanceCount } = renderObject.values; const count = uGroupCount.ref.value * instanceCount.ref.value; // ensure texture has right size @@ -159,12 +176,34 @@ namespace Visual { lociApply(loci, apply, false); } ValueCell.update(tOverpaint, tOverpaint.ref.value); + ValueCell.updateIfChanged(dOverpaintType, 'groupInstance'); + + if (overpaint.layers.length === 0) return; + + if (smoothing && hasColorSmoothingProp(smoothing.props)) { + const { geometry, props, webgl } = smoothing; + if (geometry.kind === 'mesh') { + const { resolution, overpaintTexture } = geometry.meta as SurfaceMeta; + const csp = getColorSmoothingProps(props.smoothColors, true, resolution); + if (csp) { + applyMeshOverpaintSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, overpaintTexture); + (geometry.meta as SurfaceMeta).overpaintTexture = renderObject.values.tOverpaintGrid.ref.value; + } + } else if (webgl && geometry.kind === 'texture-mesh') { + const { resolution, overpaintTexture } = geometry.meta as SurfaceMeta; + const csp = getColorSmoothingProps(props.smoothColors, true, resolution); + if (csp) { + applyTextureMeshOverpaintSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, overpaintTexture); + (geometry.meta as SurfaceMeta).overpaintTexture = renderObject.values.tOverpaintGrid.ref.value; + } + } + } } - export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean) { + export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) { if (!renderObject) return; - const { tTransparency, transparencyAverage, uGroupCount, instanceCount } = renderObject.values; + const { tTransparency, dTransparencyType, transparencyAverage, uGroupCount, instanceCount } = renderObject.values; const count = uGroupCount.ref.value * instanceCount.ref.value; // ensure texture has right size and variant @@ -185,6 +224,28 @@ namespace Visual { } ValueCell.update(tTransparency, tTransparency.ref.value); ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count)); + ValueCell.updateIfChanged(dTransparencyType, 'groupInstance'); + + if (transparency.layers.length === 0) return; + + if (smoothing && hasColorSmoothingProp(smoothing.props)) { + const { geometry, props, webgl } = smoothing; + if (geometry.kind === 'mesh') { + const { resolution, transparencyTexture } = geometry.meta as SurfaceMeta; + const csp = getColorSmoothingProps(props.smoothColors, true, resolution); + if (csp) { + applyMeshTransparencySmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, transparencyTexture); + (geometry.meta as SurfaceMeta).transparencyTexture = renderObject.values.tTransparencyGrid.ref.value; + } + } else if (webgl && geometry.kind === 'texture-mesh') { + const { resolution, transparencyTexture } = geometry.meta as SurfaceMeta; + const csp = getColorSmoothingProps(props.smoothColors, true, resolution); + if (csp) { + applyTextureMeshTransparencySmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, transparencyTexture); + (geometry.meta as SurfaceMeta).transparencyTexture = renderObject.values.tTransparencyGrid.ref.value; + } + } + } } export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) {